訓練模型過程中,經常需要追蹤一些效能指標的變化情況,以便了解模型的實時動態,例如:迴歸任務中的MSE、分類任務中的Accuracy、生成對抗網路中的圖片、網路模型結構視覺化…… 除了追蹤外,我們還希望能夠將這些指標以動態圖表的形式視覺化顯示出來。
TensorFlow的附加工具Tensorboard就完美的提供了這些功能。不過現在經過Pytorch團隊的努力,TensorBoard已經整合到了Pytorch中,只要安裝有pytorch也可以直接使用TensorBoard。
Tensorboard同時提供了後端資料記錄功能和前端資料視覺化功能。透過後端資料記錄功能,我們可以將需要追蹤的效能指標寫入到指定檔案;透過前端資料視覺化功能,我們可是實時檢視當前訓練情況。
在接下來的文章中,將對TensorBoard的使用方法進行介紹,如果你還沒有安裝,可以透過一下命令進行安裝。注意,雖然torch整合有TensorBoard,但是並不完整,需要使用下面命令完整安裝後,才能開啟TensorBoard的WEB應用。
pip install tensorboard
1 開啟TensorBoard的WEB應用¶
在透過上述命令完成tensorboard的安裝後,即可在命令列呼叫tensorboard進行啟動。如下所示:
tensorboard --logdir=./run
執行後輸出如下:
logdir引數的作用是指定讀取記錄資料的目錄,如果該目錄內又多個記錄檔案,也會在頁面中列表顯示。另外從輸出結果中,tensorboard預設從6006埠啟動,當然也可以透過port引數指定埠,如下所示,我們指定從8088埠啟動:
tensorboard --logdir=./run --port 8088
在瀏覽器位址列,我們輸入對應地址,開啟頁面如下:
現在之所以提示這些資訊,是因為我們還沒有記錄過任何資料。
2 SummaryWriter類¶
SummaryWriter是tensorboard中專門用來記錄資料的類,只有透過SummaryWriter記錄好的資料,才能在前端頁面中展示。SummaryWriter類例項化時,主要引數如下:
-
log_dir (str):指定了資料儲存的資料夾的位置,如果該資料夾不存在則會建立一個出來。如果沒有指定的話,預設的儲存的資料夾是./runs/現在的時間_主機名,例如:Dec16_21-13-54_DESKTOP-E782FS1,因此每次執行之後都會建立一個新的資料夾。
-
comment (string):給預設的log_dir新增的字尾,如果我們已經指定了log_dir具體的值,那麼這個引數就不會有任何的效果
-
purge_step (int):TensorBoard在記錄資料的時候有可能會崩潰,例如在某一個epoch中,進行到第$T + X$個step的時候由於各種原因(記憶體溢位)導致崩潰,那麼當服務重啟之後,就會從$T$個step重新開始將資料寫入檔案,而中間的$X$,即purge_step指定的step內的資料都被被丟棄。
- max_queue (int):在記錄資料的時候,在記憶體中開的佇列的長度,當佇列慢了之後就會把資料寫入磁碟(檔案)中。
- flush_secs (int):以秒為單位的寫入磁碟的間隔,預設是120秒,即兩分鐘。
- filename_suffix (string):新增到log_dir中每個檔案的字尾.
import torch
from torch.utils.tensorboard import SummaryWriter
# 使用預設引數建立summary writer,程式將會自動生成檔名
writer = SummaryWriter()
# 生成的檔案路徑為: runs/Dec16_21-13-54_DESKTOP-E782FS1/
# 建立summary writer時,指定檔案路徑
writer = SummaryWriter("my_experiment")
# 生成的檔案路徑為: my_experiment
# 建立summary writer時,使用comment作為字尾
writer = SummaryWriter(comment="LR_0.1_BATCH_16")
# folder location: runs/Dec16_21-19-59_DESKTOP-E782FS1LR_0.1_BATCH_16/
3 寫入資料¶
SummaryWriter類中定義了各式各樣的方法,用於記錄不同的資料,這些方法都已“add_”開頭,我們先羅列一下這些方法:
writer = SummaryWriter('runs')
for i in SummaryWriter.__dict__.keys():
if i.startswith("add_"):
print(i)
add_hparams add_scalar add_scalars add_histogram add_histogram_raw add_image add_images add_image_with_boxes add_figure add_video add_audio add_text add_onnx_graph add_graph add_embedding add_pr_curve add_pr_curve_raw add_custom_scalars_multilinechart add_custom_scalars_marginchart add_custom_scalars add_mesh
大體來說,所能記錄的資料型別包括標量(scalar)、影像(image)、統計圖(diagram)、影片(video)、音訊(audio)、文字(text)、Embedding等等。下面我們一次來說說怎麼記錄這些不同型別的資料。
3.1 標量資料¶
注意:訓練過程中,新增loss等資料是,一定要透過item()方法,轉換為標量之後才能新增到tensorboard中。
(1)add_scalar:一圖一曲線
-
tag (str):用於給資料進行分類的標籤,標籤中可以包含父級和子級標籤。例如給訓練的loss以loss/train的tag,而給驗證以loss/val的tag,這樣的話,最終的效果就是訓練的loss和驗證的loss都被分到了loss這個父級標籤下。而train和val則是具體用於區分兩個引數的識別符號(identifier)。此外,只支援二級標籤。
-
global_step (int):首先,每個epoch中我們都會更新固定的step。因此,在一個資料被加入的時候,有兩種step,第一種step是資料被加入時當前epoch已經進行了多少個step,第二種step是資料被加入時候,累計(包括之前的epoch)已經進行了多少個step。而考慮到我們在繪圖的時候往往是需要觀察所有的step下的資料的變化,因此global_step指的就是當前資料被加入的時候已經計算了多少個step。計算global_step的步驟很簡單,就是$global\_step=epoch∗len(dataloader)+current\_step$
-
wlltime (int):從SummaryWriter例項化開始到當前資料被加入時候所經歷時間(以秒計算),預設是使用time.time()來自動計算的,當然我們也可以指定這個引數來進行修改。這個引數一般不改
writer = SummaryWriter('runs/add_scalar')
for n_iter in range(100):
writer.add_scalar('Loss/train', np.random.random(), n_iter)
writer.add_scalar('Loss/test', np.random.random(), n_iter)
writer.add_scalar('Accuracy/train', np.random.random(), n_iter)
writer.add_scalar('Accuracy/test', np.random.random(), n_iter)
writer.close()
使用不同的SummaryWriter例項,相同的tag進行記錄資料時,將可以實現在同一張圖表中顯示多條曲線。注意,例項化SummaryWriter時,必須指定不同的資料夾,否則多個記錄資料雖然也是在同一圖表中,但是圖是混亂的。
# 兩個Accuracy寫入不同的資料夾中
writer1 = SummaryWriter('runs/add_scalar-1')
writer2 = SummaryWriter('runs/add_scalar-2')
for n_iter in range(100):
# 使用兩個SummaryWriter分別記錄LOSS和Accuracy,注意,tag必須一樣
writer1.add_scalar('Loss', np.random.random(), n_iter)
writer2.add_scalar('Loss', np.random.random(), n_iter)
writer1.add_scalar('Accuracy', np.random.random(), n_iter)
writer2.add_scalar('Accuracy', np.random.random(), n_iter)
writer1.close()
writer2.close()
(2)add_scalars:一圖多曲線
一張圖顯示多條曲線可以更加方便得對比資料,SummaryWriter中提供add_scalars方法實現在一張圖中繪製多條曲線。
-
main_tag (str):多條曲線共用的標籤
-
tag_scalar_dict (dict):多個需要記錄的資料組成的鍵值對
-
global_step (int):訓練的 step
-
walltime (float):從SummaryWriter例項化開始到當前資料被加入時候所經歷時間(以秒計算)
writer = SummaryWriter('runs/add_scalar')
for n_iter in range(100):
writer.add_scalars('Loss', {'test':np.random.random(),'train':np.random.random()}, n_iter)
writer.close()
3.2 影像資料¶
(1)add_image
-
tag (str):資料標籤
-
img_tensor:影像資料,資料型別可以使torch.Tensor, numpy.ndarray, string/blobname
-
global_step (int):訓練的step
-
walltime (float):從SummaryWriter例項化開始到當前資料被加入時候所經歷時間(以秒計算)
-
dataformats (str):影像資料的格式,可以是CHW, HWC, HW, WH等,預設為CHW,即Channel x Height x Width。通常來說,預設即可,但如果影像tensor不是CHW,就要透過這個引數指定了。
在本地資料夾有images下有多張圖片,我們選擇一張將其記錄到tensorboard中:
from torchvision.io import read_image
# 將圖片開啟為torch.Tensor型別
img = read_image('images/0975.jpg')
img.shape
torch.Size([3, 224, 224])
可見,圖片為CHW型別。我們將所有圖片上傳記錄:
writer = SummaryWriter('runs/add_image')
path_lst = [os.path.join('images', i) for i in os.listdir('images')]
for i, img in enumerate(path_lst):
img = read_image(img)
writer.add_image('img', img, i)
writer.close()
add_image方法一般情況下只能一次插入一張圖片。如果要一次性插入多張圖片,可以使用 torchvision 中的 make_grid 方法,將多張圖片拼合成一張圖片後,再呼叫 add_image 方法。
from torchvision.utils import make_grid
path_lst = [os.path.join('images', i) for i in os.listdir('images')]
img_lst = []
for i, img in enumerate(path_lst):
img = read_image(img)
img_lst.append(img)
writer = SummaryWriter('runs/add_image')
img_grid = make_grid(img_lst, nrow=5)
writer.add_image('img_grid', img_grid)
writer.close()
(2) add_images
add_images是tensorboard中提供直接一次性記錄多張圖片的方法,此方法引數與add_image基本一致,區別就在於記錄的資料是多張圖片組成的torch.Tensor或numpy.array, 資料的shape為(N,3,H,W),其中N為圖片數量。
path_lst = [os.path.join('images', i) for i in os.listdir('images')]
img_lst = []
for i, img in enumerate(path_lst):
img = read_image(img)
img_lst.append(img)
imgs_tensor = torch.stack(img_lst,0)
writer = SummaryWriter('runs/add_image')
writer.add_images('add_images', imgs_tensor)
writer.close()
3.3 模型結構¶
使用add_graph方法,可以繪製模型結構:
-
model (torch.nn.Module) :需要繪製的模型
-
input_to_model (torch.Tensor or list of torch.Tensor):傳遞給模型的一個資料
-
verbose (bool) :是否同時在命令列中繪製
import torch.nn as nn
import torch.nn.functional as F
from torch.autograd import Variable
先定義一個模型:
class Net1(nn.Module):
def __init__(self):
super(Net1, self).__init__()
self.conv1 = nn.Conv2d(1, 10, kernel_size=5)
self.conv2 = nn.Conv2d(10, 20, kernel_size=5)
self.conv2_drop = nn.Dropout2d()
self.fc1 = nn.Linear(320, 50)
self.fc2 = nn.Linear(50, 10)
self.bn = nn.BatchNorm2d(20)
def forward(self, x):
x = F.max_pool2d(self.conv1(x), 2)
x = F.relu(x) + F.relu(-x)
x = F.relu(F.max_pool2d(self.conv2_drop(self.conv2(x)), 2))
x = self.bn(x)
x = x.view(-1, 320)
x = F.relu(self.fc1(x))
x = F.dropout(x, training=self.training)
x = self.fc2(x)
x = F.softmax(x, dim=1)
return x
dummy_input = Variable(torch.rand(13, 1, 28, 28))
model = Net1()
with SummaryWriter('runs/Net1') as w:
w.add_graph(model, (dummy_input, ))
關於add_graph的例子,可以參考這裡。