Yanwei Liu

本文把文章標題取名成PyTorch深度學習工具箱的用意是在於:

日常在進行PyTorch模型訓練的時候,如果想知道模型的好壞,我們會用不少方式去評估。例如:train/val loss、train/val accuracy、Confusion Matrix、per class accuracy、F1 Score、t-SNE視覺化、GradCAM、觀看模型分類時,判斷錯誤的圖片,及其原始圖片路徑。

然而,目前大多數research公開的程式碼當中,幾乎找不太到有一個將前述評估方法整合在一起的程式。也因此,如果我們想要得到這些資訊,就必須到Google搜尋,找別人是怎麼完成這些功能的,往往花上不少時間。

在本文當中,我將彙整過去寫過關於這些主題的文章及程式碼,製作成一個目錄,供讀者及自己查詢使用:

1.使用TensorBoard視覺化train/val loss、train/val accuracy
https://pytorch.org/docs/stable/tensorboard.html

2. Confusion Matrix和per class accuracy
如何根據PyTorch的Model預測的output繪製出混淆矩陣(Confusion Matrix)並取得每個class的accuracy?

3. F1 Score(imblanced dataset可用macro和weighted)
sklearn官方F1 Score文檔

4. t-SNE視覺化
如何使用PyTorch的Feature Extractor輸出進行t-SNE視覺化?

5. GradCAM
如何在PyTorch上使用GradCAM進行神經網路分類依據視覺化?

6. 觀看模型預測時,判斷錯誤的圖片
這個功能將預測錯誤的圖片保存到TensorBoard的log當中,再從中使用Tensorflow==1.13.1版本還原出來到資料夾中,有兩篇文章需要參考。

如何顯示PyTorch中預測錯誤的圖片?
如何將保存到TensorBoard的圖片擷取出來?

7.檢查模型的參數量及模型檔案大小
PyTorch如何檢查模型的參數量及模型檔案大小?

8.列出分類錯誤之原始圖片路徑?
PyTorch如何列出分類錯誤之原始圖片路徑?

9.讓訓練結果具有再現性(Reproducibility)
如何確保PyTorch訓練的結果具有再現性?

10.將模型的多個Label輸出改成只有2種輸出
如何修改PyTorch的Prediction結果?

11.快速使用一些基於PyTorch所開發的套件
基於PyTorch的實用Library

12.降低訓練模型所需的時間
如何縮減PyTorch訓練時所需要的時間?

13.計算資料集的mean和std,用來作為Normalize的參數
PyTorch計算dataset的mean和std

14.計算Overkill和Leakage
PyTorch如何計算Overkill和Leakage並輸出成Excel檔案?

15.在訓練過程中,使用自定義的DataAugmentation方法
如何在PyTorch的transforms.Compose中使用自定義的Data Augmentation?

16.訓練過程中,讓各個類別的樣本數量平均被抽取
如何在訓練PyTorch時的每個Batch中使各個類別擁有相同數量的樣本?

17.搭配TensorBoard進行2D或3D的T-SNE視覺化
如何使用TensorBoard針對PyTorch的embedding輸出進行2D或3D的T-SNE視覺化?

18.進行Transfer Learning
PyTorch如何進行Transfer Learning

19.提取神經網路指定層輸出之特徵
使用PyTorch實作ResNet並提取指定層輸出之特徵

20.提取圖片資料集特徵並存成單獨的Pickle檔案
如何將PyTorch圖像資料集提取出Tensor特徵並保存成Pickle檔案?

假設模型在訓練過程中,需要同時使用不同的dataset進行訓練;則可以透過以下方式進行:

from itertools import cyclefor i, data in enumerate(zip(cycle(dataloaders1), dataloaders2)):
img_0, label_0 = data[0]
img_1, label_1 = data[1]
.
.
.
.
.
.
# 假設dataloaders1只有100張圖片;而dataloaders2可能有1000張圖片。
# 透過cycle(dataloaders1)的方式可以讓dataloaders1中的這100張圖片不斷地被循環抽出,直到dataloaders2的圖片被完整使用過一次,才會完成一整個epoch的訓練。
# 特別注意的是,因為batch size設定的原因,在訓練的最後一個mini-batch中,有可能會出現dataloaders1的數量(100張)比dataloaders2的圖片數量多的情形(假設dataloaders2只剩下90張)。在計算loss的時候,可能會出現shape不同的錯誤,因此要給dataloaders2設定drop_last=True的參數,才會避免這種情況發生。

若是用以下的方式訓練:

for i, data in enumerate(zip(dataloaders1, dataloaders2)):
img_0, label_0 = data[0]
img_1, label_1 = data[1]
.
.
.
.
.
.
# 訓練過程中,將會受限於dataloaders1的100張圖片,dataloaders2的1000張圖片將不會被完整使用,每次利用完dataloaders1的圖片後,就會切換到下一個epoch

參考資料

假設output為模型的輸出;target為ground truth label;而criterion_a、criterion_b、criterion_c分別為三種不同的損失函數,其形式可能是其他寫法,但本文為求簡潔,故簡略寫成以下之形式:

loss_a = criterion_a(output, target)
loss_b = criterion_b(output, target)
loss_c = criterion_c(output, target)

此時可有兩種作法:

1. 相加後直接進行反向傳播

通常這一種作法不太會有錯誤產生,接下來介紹的第二種可能就會有錯誤出現。

loss = loss_a + loss_b + loss_c
loss.backward()

2.各別進行反向傳播

若按照下面的寫法可能會出現RuntimeError: Trying to backward through the graph a second time, but the buffers have already been freed.的錯誤

loss_a.backward()
loss_b.backward()
loss_c.backward()

可以將第一個進行backward的loss添加retain_graph=True,如下所示:

loss_a.backward(retain_graph=True)
loss_b.backward()
loss_c.backward()

參考資料

RuntimeError: Trying to backward through the graph a second time, but the buffers have already been freed. Specify retain_graph=True when calling backward the first time — PyTorch Forums

pytorch程序中错误的集锦 — 知乎 (zhihu.com)

RuntimeError: Trying to backward through the graph a second time…_Huiyu Blog-CSDN博客

在採用對抗學習方法的Domain Adaptation程式碼當中,大多數都會使用Gradient Reversal的方式來進行反向傳播。

只不過,舊版PyTorch(如:0.3或0.4)寫法與現在新版(1.3之後)無法相容,會出現RuntimeError: Legacy autograd function with non-static forward method is deprecated. Please use new-style autograd function with static forward method.的錯誤,因此需要一些調整。詳細可參考下方的程式碼。

舊版PyTorch實作方式

class GradReverse(torch.autograd.Function):
def __init__(self, lambd):
self.lambd = lambd
def forward(self, x):
return x.view_as(x)
def backward(self, grad_output):
return (grad_output * -self.lambd)
def grad_reverse(x, lambd=1.0):
return GradReverse(lambd)(x)

新版本PyTorch實作方式

class GradReverse(torch.autograd.Function):
def __init__(self):
super(GradReverse, self).__init__()
@ staticmethod
def forward(ctx, x, lambda_):
ctx.save_for_backward(lambda_)
return x.view_as(x)
@ staticmethod
def backward(ctx, grad_output):
lambda_, = ctx.saved_variables
grad_input = grad_output.clone()
return - lambda_ * grad_input, None
def grad_reverse(x, lambd=1.0):
lam = torch.tensor(lambd)
return GradReverse.apply(x,lam)

參考資料

Gradient Reversal Layer指什么? — 知乎 (zhihu.com)

Open Set Domain Adaptation by Backpropagation(OSBP)论文数字数据集复现_且慢-CSDN博客

pytorch 实现Gradient Flipping 各种坑 — 知乎 (zhihu.com)