Skip to content

[Python] 使用 tqdm 套件展示進度條、並嘗試自己實作進度條

Last Updated on 2024-07-27 by Clay

在我們撰寫 Python 程式碼的時候,有時候,我們會希望我們正在做的工作能夠視覺化顯示『進度條』,好方便我們掌握我們的程式執行到哪裡了。如果是這種需求的話,除了自己寫進度條外,也可以考慮使用 Python 當中相當知名的進度條模組——tqdm

你也可以去到 tqdm 的官方網站:https://tqdm.github.io/ 閱讀更多詳細的說明。

實際上,tqdm 也是阿拉伯語當中的『進度條』的意思,只需要安裝了這個 Python 的套件,我們就可以在任何需要迭代的地方使用 tqdm 來顯示進度條。

那麼,以下開始介紹怎麼使用 tqdm 吧!


tqdm 使用方法

由於這個模組並不是 Python 內建的,所以如果我們第一次使用,我們可以使用 pip 指令進行安裝:

pip3 install tqdm

安裝好後我們來看段 Sample Code:

import time
from tqdm import tqdm

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

Output:

100%|██████████| 1000/1000 [00:10<00:00, 95.76it/s]

我們會看到進度條被逐漸新增的模樣。最開頭自然是我們心心念念的進度條,後面的 1000/1000 其實是『當前迭代次數 / 總共迭代次數』,而 00:10<00:00 則是『當前執行時間 / 預估剩餘時間』,最後的 95.76it/s 則是一秒執行了幾次迭代,有時也會看到反過來的 s/it 則是代表一次迭代時間執行了幾秒。

另外解釋一下,time.sleep() 函式的功能是讓程式每次執行到這裡時停止 0.01 秒,總共 1000 次。如果不設個暫停的話,恐怕會看到 tqdm 所印出來的進度條一下子就被執行完了、這樣就無法模擬真正的執行狀況了,大家可以實際測試看看。

tqdm() 對於所有可迭代物件都是有效的,比方說對單純的列表List)也可以進行遞迴。

import time
from tqdm import tqdm

arr = [1, 2, 3, 4, 5]

for i in tqdm(arr):
    time.sleep(1)


Output:

100%|█████████████████████████████████████████████| 5/5 [00:05<00:00,  1.00s/it]


另外,tqdm 也提供了一個 trange() 的功能來直接實現對於指定長度迭代的顯示,可以想像成 trange(x) 等價於 tqdm(range(x))

import time
from tqdm import trange

for i in trange(1000):
    time.sleep(0.01)


Output:

100%|██████████| 1000/1000 [00:10<00:00, 95.72it/s]


以上就是 tqdm 的基本使用方式,非常淺顯易懂。


tqdm 進階技巧

以下我會提及一些方便的小技巧:

  • 更改進度條長度
  • 更改單位
  • 更改進度條格式
  • 更改進度條說明
  • 更改進度條的顏色
  • 使用 .update() 自訂要何時更新、更新多少


更改進度條長度

tqdm 本來會自動偵測螢幕寬度,自適應成剛剛好的進度條寬度;但是我們也可以手動設定:

for i in tqdm(1000, ncols=100):
    time.sleep(0.01)


Output:

上方 ncols=None 為沒有設定的狀況、下面的 ncols=100 則是規定了寬度的情況。


更改單位

這個功能我不是很常用,但是在做深度學習訓練時,總難免會想要將進度的單位從 iter 改成比較直覺的 epoch 或 step。這時候我們就可以設定 unit 參數。

for i in tqdm(range(1000), unit="epoch"):
    time.sleep(0.01)


Output:

100%|██████████| 1000/1000 [00:10<00:00, 98.27epoch/s]


更改進度條格式

tqdm 中的 bar_format 允許我們自定義進度條的顯示格式。以下是我們可以使用的格式標記:

  • {l_bar}:左側進度條部分,包括描述和進度條
  • {bar}:進度條本身
  • {r_bar}:右側進度條部分,包括計數器、時間估算等
  • {n}:當前進度數量
  • {n_fmt}:格式化後的當前進度數量
  • {total}:進度條總長度
  • {total_fmt}:格式化後的進度條總長度
  • {elapsed}:已用時間
  • {elapsed_s}:已用時間(秒)
  • {elapsed_td}:已用時間(timedelta 格式)
  • {remaining}:剩餘時間
  • {remaining_s}:剩餘時間(秒)
  • {remaining_td}:剩餘時間(timedelta 格式)
  • {percentage}:完成百分比
  • {rate}:每秒處理數
  • {rate_fmt}:格式化後的每秒處理數
  • {rate_inv}:每項所需時間
  • {rate_inv_fmt}:格式化後的每項所需時間
  • {postfix}:進度條後綴字串

比方說,我喜歡格式乾淨一點的進度條,我可以這樣寫:

for i in tqdm(range(100), bar_format="{l_bar}{bar}| {n_fmt}/{total_fmt}"):
     time.sleep(0.01)


Output:

100%|███████████████| 100/100


更改進度條說明

我們也可以替 tqdm 進度條加上一些說明,首先來看看最單純的說明設定方式:

for i in tqdm(range(1000), desc="Processing..."):
     time.sleep(0.01)


Output:

Processing...: 100%|███████████| 1000/1000 [00:10<00:00, 98.77it/s]

這在有雙重迴圈甚至深層結構的迴圈中非常好用,至少我們可印出這是第幾個進度條,比方說 epoch-1, epoch-2,... , epoch-n。

另一個好用的技巧在於,我們也可以在迭代過程中改變進度條的說明!這樣可以更即時地掌握處理資料的狀態。

import time
import random
from tqdm import tqdm

# Initialize counters
good = 0
bad = 0

# Create progress bar
pbar = tqdm(range(1000), desc="Starting")

# Simulate a loop with dynamic desc update
for _ in pbar:
    # Simulate work
    time.sleep(0.01)
    
    # Randomly choose True or False
    if random.choice([True, True, False]):
        good += 1
    else:
        bad += 1
    
    # Update progress bar description
    pbar.set_description(f"Good: {good} Bad: {bad}")

# Close progress bar
pbar.close()


Output:

Good: 650 Bad: 350: 100%|████████████████| 1000/1000 [00:10<00:00, 98.17it/s]

過程中我會隨機地選取 True 或 False,並且把 tqdm 進度條賦值成的 pbar 使用 .set_description() 來更新當前處理狀況。

這樣如果在跑迭代的過程中,有什麼需要即時看到的資訊也可以直接看到。


更改進度條的顏色

在跑進度條的時候,若是單純的預設顏色實在不起眼,我們也可以設定顏色給 colour 參數;不過,在早期的 tqdm 並沒有這個功能,需要到一定的版本(例如 4.38.0)以上才支援這個參數。

for i in tqdm(range(1000), colour="green"):
     time.sleep(0.01)


Output:


使用 .update() 自訂要何時更新、更新多少

另外,tqdm 不僅僅是可以使用在 for 迴圈裡頭而已,我們甚至可以自己決定在什麼時候更新進度條、推進迭代進度。以下是一段簡單的範例:

times = 0
progress = tqdm(total=1000)

while times < 1000:
    progress.update(1)
    times += 1
    sleep(0.01)



Output:

100%|██████████| 1000/1000 [00:10<00:00, 95.68it/s]

看起來是一樣的功能,但我們這次可以自己選擇在什麼時候將 progress.update() ,也可以自己選擇 update() 的數量。

最後,再順帶一提:如果是在 Google Colab 或是 Kaggle Notebook、Jupyter Notebook 等平台上,tqdm 的寫法是得用他們提供的 notebook 包

from tqdm.notebook import tqdm

實作自己的進度條

當然,除了使用 tqdm 模組來實現自己的進度條外,我們當然可以自己來手動寫屬於自己的進度條!

以下我來拋磚引玉一下,讓各位見笑了:

from time import sleep

curr = 0
total = 1000
total_block_num = 50

for n in range(total):
    curr += 1
    block_num = int(curr * total_block_num / total)
    space_num = total_block_num - block_num

    print(f"\r[{curr / total * 100:.2f}%]: [{'█' * block_num}{' ' * space_num}]", end="")
    sleep(0.01)


Output:

[100.00%]:[██████████████████████████████████████████████████];


進階挑戰

接下來我也嘗試把上述的方法寫成一個函式,可以輸入可迭代物件產生進度條,並且也學了 tqdm 一樣設定了顏色參數。

from time import sleep
import sys
import itertools

def progress_bar(iterable, total=None, total_block_num=50, color="default"):
    if total is None:
        total = len(iterable)
        
    color_codes = {
        "default": "\033[0m",    # Default
        "red": "\033[91m",       # Red
        "green": "\033[92m",     # Green
        "yellow": "\033[93m",    # Yellow
        "blue": "\033[94m",      # Blue
        "magenta": "\033[95m",   # Magenta
        "cyan": "\033[96m",      # Cyan
        "white": "\033[97m",     # White
    }
    
    if color not in color_codes:
        color = "default"
    
    color_code = color_codes[color]
    
    for curr, item in enumerate(iterable, 1):
        block_num = int(curr * total_block_num / total)
        space_num = total_block_num - block_num
        
        progress_str = f"\r{color_code}[{float(curr / total * 100):.2f}%]: [{'█' * block_num}{' ' * space_num}]\033[0m"
        print(progress_str, end="")
        
        yield item
        
    print()  # Move to the next line after completion


if __name__ == "__main__":
    for _ in progress_bar(range(1000), color="cyan"):
        sleep(0.01)


Output:

大家若有興趣,也不妨也寫寫看自己的進度條吧!我目前的進度條寫法還比較死,沒辦法如同 tqdm 一般任意選擇更新的時間點與更新量 —— 但這也是我們可以後續加強的地方。

挑戰看看寫成 class 類別,或是可以讓使用者使用 update() 函式來更新進度吧?

我有空的時候,也想進行這樣的挑戰呢。


References


Read More

Leave a Reply