深度學習模型開發過程中,除錯與效能最佳化至關重要。本文介紹如何使用 TensorBoard 和火焰圖等工具,有效地進行 PyTorch 模型的除錯和效能分析。首先,瞭解資料特性是模型開發的基礎,需檢查資料標籤正確性、類別平衡以及異常值。TensorBoard 提供視覺化介面,可監控模型訓練指標、視覺化模型結構和層啟用值,藉此觀察模型學習過程並及早發現潛在問題。此外,類別啟用對映能幫助理解模型決策依據,透過熱力圖顯示模型關注的影像區域。效能方面,火焰圖能有效識別 CPU 上的效能瓶頸,例如找出緩慢的資料轉換操作,並提供最佳化方向。
除錯 PyTorch 模型
在前面的章節中,我們已經建立了許多模型。在本章中,我們將簡要探討如何解讀這些模型,並瞭解其內部運作機制。我們將介紹如何使用類別啟用對映(Class Activation Mapping)與 PyTorch 鉤子(Hooks)來判斷模型的決策焦點,以及如何將 PyTorch 連線到 Google 的 TensorBoard 以進行除錯。此外,我們還將展示如何使用火焰圖(Flame Graphs)來識別轉換和訓練流程中的瓶頸,並提供一個加速緩慢轉換的例項。最後,我們將討論如何在處理大型模型時透過檢查點(Checkpointing)來權衡計算和記憶體的使用。
瞭解你的資料
在探討 TensorBoard 或梯度檢查點等技術之前,首先要問自己:你是否瞭解你的資料?如果正在進行輸入分類別,是否在所有可用的標籤中擁有平衡的樣本?在訓練集、驗證集和測試集中是否也是如此?
此外,你是否確定你的標籤是正確的?一些重要的影像資料集,如 MNIST 和 CIFAR-10,已知包含一些錯誤的標籤。特別是在類別相似(如狗的品種或植物種類別)的情況下,你應該檢查你的資料。簡單地對資料進行完整性檢查可能會節省大量時間,如果你發現,例如,某一類別的標籤只有很小的影像,而其他類別則具有高解析度的範例。
資料檢查清單
- 檢查資料標籤的正確性
- 驗證不同類別之間的平衡
- 檢查資料集中的異常值或錯誤標記的樣本
TensorBoard 簡介
TensorBoard 是一個網頁應用程式,旨在視覺化神經網路的各種導向。它允許輕鬆、即時地檢視諸如準確率、損失、啟用值等統計資料。雖然它最初是為 TensorFlow 設計的,但其 API 相當通用且直接,因此在 PyTorch 中使用它與在 TensorFlow 中使用並無太大差異。
安裝 TensorBoard
可以透過 pip 或 conda 安裝 TensorBoard:
pip install tensorboard
conda install tensorboard
PyTorch 需要 TensorBoard v1.14 或更高版本。
啟動 TensorBoard:
tensorboard --logdir=runs
然後,可以存取 http://[your-machine]:6006 檢視歡迎畫面。
將資料傳送到 TensorBoard
PyTorch 中使用 TensorBoard 的模組位於 torch.utils.tensorboard:
from torch.utils.tensorboard import SummaryWriter
writer = SummaryWriter()
writer.add_scalar('example', 3)
使用 SummaryWriter 類別與 TensorBoard 進行通訊,預設的日誌輸出位置是 ./runs。可以透過 add_scalar 方法傳送標量資料,並附上標籤。
由於 SummaryWriter 是非同步工作的,可能需要片刻時間,但應該會看到 TensorBoard 更新顯示資料點。
使用 TensorBoard 的好處
- 即時視覺化模型效能
- 可以用於比較不同的模型或超引陣列態
- 有助於除錯和最佳化模型訓練過程
使用TensorBoard進行模型除錯與視覺化
TensorBoard是一個強大的工具,能夠幫助開發者視覺化和除錯PyTorch模型。在本章中,我們將介紹如何使用TensorBoard來追蹤模型的訓練過程、視覺化模型結構以及監控層的啟用值。
設定TensorBoard
首先,我們需要設定TensorBoard並將資料寫入其中。以下是一個簡單的例子,展示如何使用TensorBoard來記錄一個隨機漫步的過程:
import random
from torch.utils.tensorboard import SummaryWriter
writer = SummaryWriter()
value = 10
writer.add_scalar('test_loop', value, 0)
for i in range(1, 10000):
value += random.random() - 0.5
writer.add_scalar('test_loop', value, i)
內容解密:
SummaryWriter():建立一個TensorBoard的寫入器,用於將資料寫入TensorBoard。add_scalar():將一個標量值寫入TensorBoard,第一個引數是標籤名稱,第二個引數是值,第三個引數是全域性步數。
視覺化模型結構
我們可以使用add_graph()函式將模型結構視覺化:
import torch
import torchvision
from torchvision import models
model = models.resnet18(False)
writer.add_graph(model, torch.rand([1, 3, 224, 224]))
內容解密:
models.resnet18(False):載入一個ResNet-18模型,不使用預訓練權重。add_graph():將模型結構寫入TensorBoard,第一個引數是模型例項,第二個引數是一個虛擬輸入,用於追蹤模型的計算圖。
在訓練迴圈中使用TensorBoard
我們可以在訓練迴圈中使用TensorBoard來記錄損失和準確率:
def train(model, optimizer, loss_fn, train_data_loader, test_data_loader, epochs=20):
for epoch in range(epochs):
# ...
loss = loss_fn(output, target)
writer.add_scalar('loss', loss, epoch)
# ...
accuracy = num_correct / num_examples
writer.add_scalar('accuracy', accuracy, epoch)
內容解密:
writer.add_scalar('loss', loss, epoch):將損失值寫入TensorBoard。writer.add_scalar('accuracy', accuracy, epoch):將準確率寫入TensorBoard。
使用PyTorch Hooks監控層的啟用值
PyTorch提供了hooks機制,允許我們在模型的forward或backward過程中插入自定義函式。以下是一個例子,展示如何使用hook來監控層的啟用值:
def send_stats(i, module, input, output):
writer.add_scalar(f"{i}-mean", output.data.mean())
writer.add_scalar(f"{i}-stddev", output.data.std())
for i, m in enumerate(model.children()):
m.register_forward_hook(partial(send_stats, i))
內容解密:
send_stats():一個hook函式,用於計算層的啟用值的平均值和標準差,並將其寫入TensorBoard。register_forward_hook():註冊一個forward hook,第一個引數是hook函式。partial(send_stats, i):使用functools.partial建立一個新的函式,將i固定為當前層的索引。
TensorBoard 的應用與模型視覺化
在深度學習模型訓練過程中,TensorBoard 是一個非常有用的工具,可以幫助我們視覺化模型的表現、損失函式的變化以及網路層的啟用值等重要資訊。理想情況下,神經網路中的各層應該具有接近 0 的平均值和接近 1 的標準差,以避免計算結果趨於無窮大或零。
觀察 TensorBoard 中的層啟用值
在 TensorBoard 中,我們可以觀察各層的平均值和標準差。如果這些值出現劇烈波動或趨近於零,可能意味著網路在訓練過程中遇到了困難。這種情況下,檢查啟用函式(如 ReLU)是否適合所處理的問題是非常重要的。PyTorch 中的 LeakyReLU 是一種不錯的替代方案,它允許更多的資訊透過,從而可能改善模型的訓練效果。
類別啟用對映(Class Activation Mapping, CAM)
類別啟用對映是一種用於視覺化網路在分類別輸入張量時的啟用情況的技術。在影像分類別任務中,CAM 通常以熱力圖的形式疊加在原始影像上,顯示網路對不同區域的關注程度。
如何實作類別啟用對映
要生成熱力圖,我們需要捕捉網路最後一個卷積層的啟用值。這可以透過 PyTorch 的 hook 功能實作。我們定義了一個名為 SaveActivations 的類別來儲存啟用值:
class SaveActivations():
activations = None
def __init__(self, m):
self.hook = m.register_forward_hook(self.hook_fn)
def hook_fn(self, module, input, output):
self.features = output.data
def remove(self):
self.hook.remove()
然後,我們將影像輸入網路,並使用 softmax 函式將輸出轉換為機率。接著,使用 torch.topk() 取得最大機率及其索引:
casper_tensor = preprocess(casper)
model = models.resnet18(pretrained=True)
model.eval()
casper_activations = SaveActivations(model.layer4)
prediction = model(casper_tensor.unsqueeze(0))
pred_probabilities = F.softmax(prediction).data.squeeze()
casper_activations.remove()
torch.topk(pred_probabilities, 1)
內容解密:
SaveActivations類別:用於註冊 hook 以捕捉特定層的輸出資料。hook_fn方法:當指定的層被執行時,該方法會被呼叫,並將輸出的資料儲存到self.features。preprocess函式:對輸入影像進行預處理,包括縮放、轉換為張量以及正規化。model.layer4:指定我們要捕捉其啟用值的層,在本例中是 ResNet18 的最後一個卷積層。F.softmax(prediction):將模型的輸出轉換為機率分佈。torch.topk(pred_probabilities, 1):取得機率最高的類別及其對應的機率值。
透過計算啟用值與機率的點積,並進行適當的縮放,我們可以生成熱力圖,顯示網路對影像中不同區域的關注程度。
fts = casper_activations.features[0]
prob = np.exp(to_np(log_prob))
preds = np.argmax(prob[0])
fts_np = to_np(fts)
f2 = np.dot(np.rollaxis(fts_np, 0, 3), prob[0])
f2 -= f2.min()
f2 /= f2.max()
plt.imshow(dx)
plt.imshow(scipy.misc.imresize(f2, dx.shape), alpha=0.5, cmap='jet')
內容解密:
np.dot(np.rollaxis(fts_np, 0, 3), prob[0]):計算啟用值與機率的點積,以獲得最終的熱力圖資料。f2 -= f2.min()和f2 /= f2.max():對熱力圖資料進行歸一化處理,以突出顯示最重要的區域。plt.imshow:用於顯示原始影像和疊加的熱力圖。
火焰圖(Flame Graphs)
火焰圖是一種用於視覺化程式執行時間分佈的工具,最初由 Brendan Gregg 在 2011 年提出,用於除錯 MySQL 的效能問題。火焰圖透過對程式的堆積疊追蹤進行取樣,並將結果以圖形化的方式呈現,從而幫助開發者快速識別程式中的效能瓶頸。
火焰圖的工作原理
火焰圖透過頻繁取樣程式的堆積疊追蹤來收集函式呼叫的資訊。每次取樣都會記錄當前執行的函式及其呼叫者,形成一條堆積疊追蹤路徑。這些路徑被收集起來並轉換成火焰圖,每個矩形代表一個函式,其寬度對應於該函式在取樣中出現的頻率。
65.00% 0.00% mysqld [kernel.kallsyms] [k] entry_SYSCALL_64_fastpath
|
---
entry_SYSCALL_64_fastpath
|
|--18.75%-- sys_io_getevents
| read_events
| schedule
| __schedule
| finish_task_switch
|
|--10.00%-- sys_fsync
| do_fsync
內容解密:
- 火焰圖的基本結構:顯示了函式呼叫堆積疊及其佔用的 CPU 時間百分比。
entry_SYSCALL_64_fastpath:表示系統呼叫的入口函式,其下的子函式佔用了不同的 CPU 時間比例。sys_io_getevents和sys_fsync:是兩個不同的系統呼叫路徑,分別對應不同的操作和 CPU 時間佔用。
火焰圖為我們提供了一種直觀的方式來理解程式的執行情況和效能瓶頸,從而有助於最佳化程式的效能。
深度學習中的火焰圖:效能瓶頸分析利器
在深度學習領域,訓練模型的速度往往是研究人員和工程師關注的重點。當訓練過程變慢時,通常的解決方案是增加GPU資源或使用更強大的硬體。然而,有些效能瓶頸可能並不在GPU上,而是在CPU上。火焰圖(Flame Graph)是一種視覺化工具,可以幫助我們快速識別CPU上的效能瓶頸。
火焰圖簡介
火焰圖是一種根據堆積疊追蹤(Stack Trace)的視覺化表示。它將程式的執行時間以圖形的方式呈現,幫助開發者快速找到效能瓶頸。在火焰圖中,y軸代表堆積疊高度,x軸代表函式被取樣的頻率。
使用 py-spy 生成火焰圖
py-spy 是一個根據Rust的堆積疊分析工具,可以直接生成火焰圖。首先,我們需要安裝 py-spy:
pip install py-spy
然後,我們可以使用以下命令生成火焰圖:
py-spy --flame profile.svg --pid 12345
或者,我們可以傳遞一個Python指令碼:
py-spy -r 99 -d 30 --flame profile.svg -- python flametest.py
閱讀火焰圖
生成的火焰圖是一個SVG檔案,可以在瀏覽器中開啟。圖中顯示了程式的執行時間分佈。在我們的例子中,大部分執行時間都花在 forward() 函式上,這是因為我們正在進行大量的模型預測。
內容解密:
py-spy使用 icicle 格式生成火焰圖,因此堆積疊看起來像冰柱而不是火焰。- 圖中的每個區塊代表一個函式,區塊的寬度代表函式被取樣的頻率。
- 可以點選區塊來縮放檢視詳細資訊。
修復緩慢的轉換
在真實世界的應用中,資料管道中的某些部分可能會導致效能瓶頸。例如,某些轉換操作可能會非常緩慢。以下是使用火焰圖來識別和修復緩慢轉換的例子:
import torch
import torchvision
from torch import optim
import torch.nn as nn
from torchvision import datasets, transforms, models
import torch.utils.data
from PIL import Image
import numpy as np
device = "cuda:0"
model = models.resnet18(pretrained=True)
model.to(device)
class BadRandom(object):
def __call__(self, img):
img_np = np.array(img)
random = np.random.random_sample(img_np.shape)
out_np = img_np + random
out = Image.fromarray(out_np.astype('uint8'), 'RGB')
return out
def __repr__(self):
str = f"{self.__class__.__name__ }"
return str
train_data_path = "catfish/train"
image_transforms = torchvision.transforms.Compose(
[transforms.Resize((224,224)), BadRandom(), transforms.ToTensor()])
內容解密:
BadRandom類別是一個自定義的轉換操作,它對影像進行隨機擾動。image_transforms是一個轉換管道,包含Resize、BadRandom和ToTensor三個操作。- 使用火焰圖可以幫助我們識別
BadRandom操作是否是效能瓶頸。