1、tqdm 进度条显示

📘 tqdm 详细使用笔记(Python 进度条神器)


一、什么是 tqdm

  • 全称taqadum(阿拉伯语“进展”)

  • 作用:为 Python 中的可迭代对象(如列表、range、生成器等)自动添加动态进度条

  • 核心特点

    • 轻量无侵入:不改变原有循环逻辑
    • 自动计算:进度百分比、迭代速度、剩余时间
    • 多环境支持:终端、Jupyter Notebook、多线程/进程
  • 安装命令

    pip install tqdm
    

二、核心用途

在长时间运行的循环或迭代任务中,实时可视化执行进度,提升开发体验与用户感知。

典型应用场景:

  • 批量文件处理(图像、日志、CSV)
  • 数据清洗与转换(Pandas、NumPy)
  • 网络请求(爬虫、API 调用)
  • 机器学习训练/验证循环
  • 大规模数据遍历(数据库、HDF5)

三、基本用法(最常用)

✅ 包装可迭代对象(推荐方式)

from tqdm import tqdm

for item in tqdm(iterable, desc="描述"):
    # 你的处理逻辑
    pass

💡 只需将原循环中的 iterable 传给 tqdm() 即可!

✅ 手动控制进度(适用于 while 或非标准循环)

with tqdm(total=100) as pbar:
    while condition:
        do_work()
        pbar.update(1)  # 每完成一步 +1

四、函数签名(tqdm 类初始化参数)

tqdm(
    iterable=None,        # 要包装的可迭代对象(可选;若不传,需手动 update)
    desc=None,            # 进度条前缀描述文字(如 "下载中")
    total=None,           # 总迭代次数(若 iterable 无 __len__,必须手动指定)
    leave=True,           # 循环结束后是否保留进度条
    file=None,            # 输出流(默认 sys.stderr,即标准错误输出)
    ncols=None,           # 进度条总宽度(字符数),None 表示自动适配
    mininterval=0.1,      # 最小刷新间隔(秒),防止刷屏
    maxinterval=10.0,     # 最大刷新间隔(用于极慢任务)
    miniters=None,        # 最小更新步数(与 mininterval 互斥)
    ascii=False,          # 使用 ASCII 字符(True 时兼容老旧终端)
    disable=False,        # 是否完全禁用进度条(True 时透明通过)
    unit="it",            # 迭代单位(如 "file", "sample")
    unit_scale=False,     # 自动缩放单位(如 1k, 1M, 1G)
    dynamic_ncols=False,  # 动态调整宽度(适应终端窗口大小)
    smoothing=0.3,        # 速度平滑因子(0=瞬时速度,1=完全平均)
    bar_format=None,      # 自定义格式(见下方示例)
    initial=0,            # 初始计数值(用于恢复中断任务)
    position=None,        # 多进度条位置索引(嵌套或多线程时使用)
    postfix=None,         # 末尾附加信息(支持 dict 或 str)
    unit_divisor=1000,    # 单位缩放除数(1000 → KB/MB;1024 → KiB/MiB)
    colour=None,          # 进度条颜色(如 'green', '#ff0000')
    delay=0,              # 延迟显示(秒),避免短任务闪现
    gui=False,            # GUI 模式(一般不用)
    **kwargs
)

⚠️ 日常只需关注iterable, desc, total, unit, disable


五、常用参数详解

参数 默认值 说明 示例
iterable None 要包装的可迭代对象(如 range(100) tqdm(range(100))
desc None 描述前缀 desc="处理文件"处理文件: 50%...
total None 总步数(当 iterable 无长度时必填) tqdm(gen(), total=1000)
unit "it" 单位名称 unit="张图片" → 显示 50 张图片
leave True 循环结束是否保留进度条 leave=False → 执行完自动清除
disable False 临时关闭进度条(调试/生产开关) tqdm(..., disable=not DEBUG)
colour None 进度条颜色 colour='green''#00ff00'
postfix None 动态附加信息(支持 dict) pbar.set_postfix(loss=0.123)

🔔 注意:如果 total 与实际迭代次数不一致,进度条会出错!


六、常用操作示例

  1. 基础循环 + 描述
from tqdm import tqdm
import time

for i in tqdm(range(50), desc="处理数据"):
    time.sleep(0.05)
  1. 生成器 + 手动指定 total
def data_generator():
    for i in range(200):
        yield i ** 2

for x in tqdm(data_generator(), total=200, desc="生成数据"):
    pass
  1. 手动控制进度(推荐用上下文管理器)
from tqdm import tqdm

with tqdm(total=100, desc="安全模式") as pbar:
    for _ in range(10):
        work()
        pbar.update(10)  # 每次 +10
# 自动 close(),避免资源泄漏
  1. 动态更新附加信息(如 loss、acc)
pbar = tqdm(range(100))
for i in pbar:
    loss = 1.0 / (i + 1)
    pbar.set_postfix(loss=f"{loss:.4f}", stage="train")

输出效果:

 50%|█████     | 50/100 [00:00<00:00, 98.76it/s, loss=0.0200, stage=train]
  1. 禁用进度条(调试/生产环境开关)
DEBUG = False
for i in tqdm(range(1000), disable=not DEBUG):
    process(i)

→ 当 DEBUG=False 时,tqdm 完全透明,等价于原生循环。

  1. 嵌套进度条(多层循环)
for i in tqdm(range(3), desc="外层"):
    for j in tqdm(range(100), desc="内层", leave=False):
        time.sleep(0.001)

✅ 内层建议 leave=False,避免终端刷屏。

  1. 自定义格式(bar_format
bar_format = "{l_bar}{bar}| {n_fmt}/{total_fmt} [{elapsed}<{remaining}]"
for i in tqdm(range(100), bar_format=bar_format):
    time.sleep(0.01)

七、注意事项 & 技巧

问题 解决方案
进度条闪烁/错乱 增大 mininterval=0.5 减少刷新频率
Jupyter 中显示异常 改用 from tqdm.notebook import tqdm
想隐藏进度条 disable=True(比注释掉更灵活)
进度条太长/太短 ncols=80 固定宽度,或 dynamic_ncols=True 自适应
处理字节数据? unit='B', unit_scale=True → 自动转为 KB/MB/GB
恢复中断任务 initial=N 设置起始点(需配合 total
多线程冲突 使用 position 参数分配独立行

八、Jupyter Notebook 专用

在 Jupyter 中,使用更美观的交互式 HTML 进度条

from tqdm.notebook import tqdm

for i in tqdm(range(100)):
    time.sleep(0.01)

→ 显示为彩色、可折叠的进度条,体验更佳。

💡 也可以自动检测环境:
from tqdm.auto import tqdm → 自动选择终端或 notebook 后端


九、一句话总结

tqdm 是一个“可迭代对象的进度显示器”,你只需把要遍历的对象传给它,它就自动告诉你:“干了多少、还剩多少、还要多久”。


最佳实践口诀

“有循环,就包 tqdm;没长度,就加 total;要描述,就写 desc;不想看,就设 disable=True。”


2、tqdm 打印日志解读

  2%|| 9/455 [00:00<00:39, 11.32it/s]当前 epoch = 1, 已训练样本个数 = 320, 当前样本平均损失 = 0.16439218819141388, 本轮已耗时 = 2.3281254%|| 19/455 [00:01<00:33, 13.15it/s]当前 epoch = 1, 已训练样本个数 = 640, 当前样本平均损失 = 0.1520037204027176, 本轮已耗时 = 3.0468756%|| 29/455 [00:02<00:31, 13.46it/s]当前 epoch = 1, 已训练样本个数 = 960, 当前样本平均损失 = 0.14173820614814758, 本轮已耗时 = 3.734375

通过对比你提供的三行日志数据,我们可以更精准地解读 it/s 的具体含义以及它与“已训练样本个数”之间的关系。

核心结论

在这里,it (iteration) 代表的是“批次 (Batch)”,而不是单个“样本 (Sample)”

进度条中的 9/45519/455 等数字表示已经处理了多少个批次,而 11.32it/s 表示每秒处理了多少个批次


详细推导过程

我们可以通过计算来验证这个结论:

1. 计算每个批次的大小 (Batch Size)
观察“已训练样本个数”的变化与进度条数字(迭代次数)的变化关系:

  • 第一行到第二行:

    • 进度条数字变化: 19 − 9 = 10 19 - 9 = 10 199=10 (增加了 10 次迭代)
    • 样本个数变化: 640 − 320 = 320 640 - 320 = 320 640320=320 (增加了 320 个样本)
    • 单批次样本数: 320 ÷ 10 = 32 320 \div 10 = 32 320÷10=32
  • 第二行到第三行:

    • 进度条数字变化: 29 − 19 = 10 29 - 19 = 10 2919=10 (增加了 10 次迭代)
    • 样本个数变化: 960 − 640 = 320 960 - 640 = 320 960640=320 (增加了 320 个样本)
    • 单批次样本数: 320 ÷ 10 = 32 320 \div 10 = 32 320÷10=32
  • 验证第一行:

    • 进度条显示 9,样本个数 320
    • 这里有一个小细节: 9 × 32 = 288 9 \times 32 = 288 9×32=288,但日志显示 320
    • 可能性分析
      1. 代码逻辑可能是先打印日志再更新进度条,或者反之,导致微小的计数不同步。
      2. 或者第一个批次的大小略有不同(例如包含了预热或剩余样本)。
      3. 但最明显的规律是:每增加 1 个进度单位(it),样本数大约增加 32 个。因此,你的 Batch Size (批次大小) 设置为 32

2. 重新解读 11.32it/s
既然 it = 1 个 Batch (包含 32 个样本):

  • 含义:模型每秒能完成 11.32 个批次 的前向传播和反向传播。
  • 换算成样本速度
    11.32  (batches/s) × 32  (samples/batch) ≈ 362.24  samples/s 11.32 \text{ (batches/s)} \times 32 \text{ (samples/batch)} \approx 362.24 \text{ samples/s} 11.32 (batches/s)×32 (samples/batch)362.24 samples/s
    这意味着你的模型实际上每秒正在处理约 362 个样本,而不是 11 个。

3. 验证耗时 (Time Check)
让我们用速度验证一下时间是否吻合:

  • 从第 9 次迭代到第 29 次迭代(共 20 个 batches):
    • 平均速度约为 13  it/s 13 \text{ it/s} 13 it/s (取中间值)。
    • 理论耗时: 20 ÷ 13 ≈ 1.54 20 \div 13 \approx 1.54 20÷131.54 秒。
    • 实际日志时间差: 3.734375 − 2.328125 = 1.40625 3.734375 - 2.328125 = 1.40625 3.7343752.328125=1.40625 秒。
    • 两者非常接近(考虑到速度是动态变化的,从 11.32 升到了 13.46),逻辑完全自洽。

总结对照表

日志项 含义 你的数据对应解读
9/455 当前批次 / 总批次 总共要跑 455 个批次,当前刚跑完第 9 个左右。
it/s Batches per Second 每秒处理约 11~13 个批次
已训练样本个数 Total Samples Processed 当前已处理的样本总数 = 当前批次号 × \times × 32 (大致)。
隐含参数 Batch Size 你的代码中 batch_size 设为 32

一句话总结:
11.32it/s 表示你的显卡/处理器每秒能跑完 11.32 个批次(每个批次含 32 条数据),相当于每秒处理约 362 条数据。随着训练进行(GPU 缓存预热等),速度已从 11.32 提升到了 13.46 it/s。


3、tqdm 进度条的单位

it/s :强调的是 it,每秒可以处理多少 it,看到 it/s 说明你的显卡正在“起飞”

s/it:强调的是 s,一个 it 需要多少秒,看到 s/it 说明任务比较“沉重”。

虽然代码看起来一样,但 tqdm 会根据循环执行的快慢总任务量自动调整显示策略,以便展示最有意义的信息。

简单来说:

  • it/s:侧重于展示**“处理速度”**(吞吐量)。
  • s/it:侧重于展示**“单次耗时”**(延迟)。

以下是导致这两种显示模式切换的具体原因分析:

  1. 速度阈值:快则用 it/s,慢则用 s/it

tqdm 内部有一个判断逻辑(通常以 1 秒 为界限):

  • 当处理速度很快时(< 1秒/次) -> 显示 it/s
    • 场景:你的 AMP+GA 训练模式(约 0.9s/it)或者处理小数据时。
    • 原因:如果显示 s/it,数值会是 0.90s/it0.05s/it,这种小数在视觉上不如整数直观。显示 1.11 it/s 能让人一眼看出“一秒钟能跑多少个”,更有成就感。
  • 当处理速度很慢时(> 1秒/次) -> 显示 s/it
    • 场景:你的纯 FP32 训练模式(约 2.54s/it)或者处理超大图片时。
    • 原因:如果显示 it/s,数值会是 0.39 it/s。这种小数(每秒不到1个)很难让人直观感受到“还要等多久”。显示 2.54s/it 能直接告诉你“再等2秒多就好”,更符合人类对长时间等待的心理预期。
  1. 总任务量未知:默认显示 s/it

如果你没有告诉 tqdm 总共有多少个任务(即没有传 total 参数),或者 my_dataloader 的长度无法被获取:

  • 现象:进度条左边不显示百分比,右边通常默认显示 s/it
  • 原因:因为不知道总数,无法计算剩余时间,所以 tqdm 只能告诉你“当前处理这一个花了多久”,让你心里有个底。
  1. 结合你的训练场景分析

根据你之前的测试数据,我们可以完美对应上:

你的训练场景 实测速度 tqdm 显示模式 原因分析
AMP + GA (x2) 0.90s/it 1.11 it/s 速度快(<1s),显示吞吐量更直观。
纯 FP32 2.54s/it 0.39 it/s (或自动切为 2.54s/it) 速度慢(>1s),显示单次耗时更直观。
  1. 如何强制固定显示格式?

如果你希望无论快慢都统一显示某一种格式,可以手动指定 unit 参数:

  • 强制显示 it/s(强调吞吐量):

    # unit='it' 告诉 tqdm 单位是“个”,它会自动计算每秒多少个
    for i, batch in enumerate(tqdm(my_dataloader, unit='it')):
        ...
    
  • 强制显示 s/it(强调耗时):

    # unit='s' 告诉 tqdm 单位是“秒”,它会自动计算每个要多少秒
    for i, batch in enumerate(tqdm(my_dataloader, unit='s')):
        ...
    

总结: 这不是代码出错了,而是 tqdm 足够智能,它在**“让你看速度”“让你看等待时间”**之间自动做了切换。在你的训练日志里,看到 it/s 说明你的显卡正在“起飞”,看到 s/it 说明任务比较“沉重”。



更多推荐