文章目錄
- 前置環境
- Visdom
- 安裝并啟動Visdom
- Visdom圖形API
- Visdom靜態更新API詳解
- 通用參數說明
- 使用示例
- Visdom動態更新API詳解
- 1. 使用`update='append'`參數
- 2. ~~使用vis.updateTrace方法~~
- 3. 完整訓練監控示例
- Visdom可視化操作
- 散點圖plot.scatter()
- 散點圖案例
- 線性圖vis.line()
- `vis.line()` 參數說明
- `opts` 選項明細表
- 關鍵特性說明
- 線性圖案例
- 莖葉圖vis.stem
- 🔄 Visdom莖葉圖參數設計
- ? 為什么Visdom這樣設計
- 💡 記憶技巧
- 📊 修正后的代碼解釋
- 🖼? **可視化效果說明**
- 深度學習訓練案例
前置環境
- 深度學習開發環境 Anaconda PyTorch
- Jupyter Notebook
Visdom
- Visdom是Facebook Research開發的一款開源可視化工具,專門為PyTorch等深度學習框架設計,但也可以用于其他機器學習任務的可視化。它提供了一個輕量級的Web服務,允許用戶在瀏覽器中實時查看訓練過程中的各種指標、圖像和其他數據。
- Visdom的主要特點包括:實時可視化訓練過程、支持多種數據類型(標量、圖像、文本、直方圖等)、簡單的API接口、可遠程訪問的Web界面、支持多環境組織實驗。
- Visdom可以直接接受來自PyTorch的張量,而不用轉換成NumPy中的數組,運行效率很高。此外,Visdom可以直接在內存中獲取數據,具有毫秒級刷新,速度很快。
安裝并啟動Visdom
- Visdom本質上時一個類似于Jupyter Notebook的web服務器,在使用前需要在終端打開服務。
- 進入anaconda虛擬環境:
conda activate env_name
- 安裝Visdom非常簡單,只需使用pip:
pip install visdom
- 安裝完成后,啟動Visdom服務器:
python -m visdom.server
- 默認情況下,Visdom會在
http://localhost:8097
啟動服務。
Visdom圖形API
Visdom靜態更新API詳解
API名稱 | 圖形類型 | 描述 | 主要參數 |
---|---|---|---|
vis.scatter | 2D/3D散點圖 | 繪制二維或三維散點圖 | X : 數據點坐標Y : 數據點標簽(可選)opts : 標題/顏色/大小等選項 |
vis.line | 線圖 | 繪制單條或多條線圖,常用于展示訓練曲線 | X : x軸數據Y : y軸數據opts : 標題/圖例/坐標軸標簽等選項 |
vis.updateTrace | 更新線圖 | 更新現有的線圖或散點圖數據 | X : 新x數據Y : 新y數據win : 要更新的窗口名name : 線名稱(可選) |
vis.stem | 莖葉圖 | 繪制離散數據的莖葉圖 | X : x軸數據Y : y軸數據opts : 標題/線條樣式等選項 |
vis.heatmap | 熱力圖 | 用顏色矩陣表示數據值大小 | X : 矩陣數據opts : 標題/顏色映射/x,y軸標簽等選項 |
vis.bar | 條形圖 | 繪制垂直或水平條形圖 | X : 條形數據opts : 標題/堆疊/方向(水平或垂直)等選項 |
vis.histogram | 直方圖 | 展示數據分布情況 | X : 輸入數據opts : 標題/箱數/顏色等選項 |
vis.boxplot | 箱線圖 | 展示數據分布的五數概括(最小值、Q1、中位數、Q3、最大值) | X : 輸入數據opts : 標題/異常值顯示等選項 |
vis.surf | 表面圖 | 繪制三維表面圖 | X : 矩陣數據opts : 標題/顏色映射/光照等選項 |
vis.contour | 等高線圖 | 繪制二維等高線圖 | X : 矩陣數據opts : 標題/線數/顏色映射等選項 |
vis.quiver | 矢量場圖 | 繪制二維矢量場(箭頭圖) | X : 起點坐標Y : 矢量分量opts : 標題/箭頭大小/顏色等選項 |
vis.mesh | 網格圖 | 繪制三維網格圖 | X : 頂點坐標Y : 面索引opts : 標題/顏色/光照等選項 |
通用參數說明
win
: 可選參數,指定要繪制或更新的窗口名稱。如果不指定,Visdom會自動分配一個新的pane。如果兩次操作指定的win名字一樣,新的操作將覆蓋當前的pane內容,因此建議每次操作都重新指定win。opts
: 字典形式的選項參數,可設置標題(title)、圖例(legend)、坐標軸標簽(xlabel/ylabel)、寬度(width)等,主要用于設置pane的顯示格式。- 大多數API接受PyTorch Tensor或Numpy數組作為輸入數據。但不支持Python的int、float等類型,因此每次傳入時都需先將數據轉化成ndarray或tensor。
env
: 指定可視化環境(默認為’main’),用于組織不同實驗的可視化結果。Visdom允許創建不同的"環境"來組織實驗:
# 創建一個新環境
vis = visdom.Visdom(env='my_experiment')# 保存當前環境
vis.save(['my_experiment'])
使用示例
import visdom
import numpy as npvis = visdom.Visdom()# 線圖示例
vis.line(X=np.arange(10),Y=np.random.rand(10),opts=dict(title='Random Line', showlegend=True)
)# 散點圖示例
vis.scatter(X=np.random.rand(100, 2),Y=(np.random.rand(100) > 0.5).astype(int)+1,opts=dict(title='2D Scatter', markersize=10)
)
Visdom動態更新API詳解
- 在深度學習訓練過程中,我們經常需要實時更新可視化圖表來監控訓練進度。Visdom提供了兩種主要方式來實現數據的動態更新:
1. 使用update='append'
參數
- 最基礎的動態更新方式,適用于大多數繪圖API。
參數 | 取值 | 效果 |
---|---|---|
update | None (默認) | 覆蓋窗口中的現有內容 |
'append' | 在現有圖形上追加數據點 | |
'replace' | 替換整個圖形(與None不同,會保留窗口設置) |
使用示例:
import visdom
import numpy as npvis = visdom.Visdom()# 初始化線圖
vis.line(X=[0],Y=[0.5],win='loss',opts=dict(title='Training Loss')
)# 模擬訓練過程
for epoch in range(1, 10):loss = np.random.rand() * 0.1 + 1.0/(epoch+1)vis.line(X=[epoch],Y=[loss],win='loss',update='append' # 關鍵參數,避免覆蓋)
2. 使用vis.updateTrace方法
- 0.1.8版本之后已廢棄
方法 | 功能 |
---|---|
vis.updateTrace() | 1. 在現有圖形上追加數據點(類似update='append' )2. 添加新的獨立軌跡 |
參數說明:
參數 | 類型 | 說明 |
---|---|---|
X | array/tensor | 新x坐標 |
Y | array/tensor | 新y坐標 |
win | str | 目標窗口名稱 |
name | str | 軌跡名稱(可選,用于區分多條軌跡) |
append | bool | True=追加到現有軌跡,False=創建新軌跡(默認True) |
使用示例:
# 方法一:在現有軌跡上追加數據(等效于update='append')
vis.updateTrace(X=[epoch],Y=[train_loss],win='loss_win',name='train' # 必須指定要更新的軌跡名稱
)# 方法二:添加全新獨立軌跡
vis.updateTrace(X=[epoch],Y=[val_loss],win='loss_win',name='validation', # 新軌跡名稱append=False # 創建新軌跡
)
3. 完整訓練監控示例
import visdom
import numpy as np
import timevis = visdom.Visdom(env='training_monitor')# 初始化所有窗口
vis.line(X=[0], Y=[0], win='loss', opts=dict(title='Loss', legend=['Train', 'Val']))
vis.line(X=[0], Y=[0], win='acc', opts=dict(title='Accuracy', legend=['Train', 'Val']))for epoch in range(1, 11):# 模擬訓練數據train_loss = np.random.rand()*0.1 + 1.0/epochval_loss = np.random.rand()*0.1 + 1.2/epochtrain_acc = 1 - train_loss + np.random.rand()*0.1val_acc = 1 - val_loss + np.random.rand()*0.1# 更新損失曲線(兩種方式等價)vis.line(X=[epoch], Y=[train_loss],win='loss', name='Train',update='append')vis.line(X=[epoch], Y=[val_loss],win='loss', name='Val',update='append')# 更新準確率曲線vis.line(X=[epoch], Y=[train_acc],win='acc', name='Train',update='append')vis.line(X=[epoch], Y=[val_acc],win='acc', name='Val',update='append')# 每5個epoch可視化一批樣本if epoch % 5 == 0:samples = np.random.rand(16, 3, 64, 64) # 模擬圖像數據vis.images(samples,win='samples',opts=dict(title=f'Epoch {epoch} Samples'))time.sleep(0.5) # 模擬訓練時間
Visdom可視化操作
散點圖plot.scatter()
- scatter函數用來畫2D或3D數據的散點圖。需要輸入 N × 2 N\times 2 N×2或 N × 3 N\times 3 N×3的張量來指定N個點的位置。一個可供選擇的長度為N的向量用來保存X中的點對應的標簽。標簽可以通過點的顏色反應出來。
- Visdom
vis.scatter
參數與選項說明
參數/選項 | 類型 | 描述 | 默認值 | 注意事項 |
---|---|---|---|---|
基本參數 | ||||
X | Tensor/ndarray | N×2(2D)或N×3(3D)數據點坐標 | 必填 | 不支持Python列表 |
Y | Tensor/ndarray | 長度為N的標簽向量(可選) | None | 用于分類著色 |
win | str | 目標窗口名稱 | 自動生成 | 留空則創建新窗口 |
標記樣式選項 | ||||
opts.markersymbol | str | 標記形狀 | 'dot' | 可選:‘circle’, ‘cross’, 'diamond’等 |
opts.markersize | int | 標記大小(像素) | 10 | 非字符串類型 |
opts.markercolor | Tensor/ndarray | 顏色設置 | 自動分配 | 見下方顏色規則 |
布局選項 | ||||
opts.legend | list | 圖例名稱列表 | None | 需與Y的類別數匹配 |
opts.textlabels | list | 每個點的文本標簽 | None | 長度需等于N |
opts.webgl | bool | 啟用WebGL加速 | False | 大數據量時建議開啟 |
高級選項 | ||||
opts.layoutopts | dict | Plotly布局參數 | None | 如{'plotly': {'legend': {'x':0}}} |
opts.traceopts | dict | Plotly軌跡參數 | None | 如{'plotly': {'mode':'markers'}} |
markercolor
顏色編碼規則
輸入形狀 | 顏色模式 | 示例值 | 效果 |
---|---|---|---|
N | 單通道灰度 | [0,127,255] | 0(黑)→255(紅) |
N×3 | RGB三通道 | [[0,0,255], [255,0,0]] | 藍→紅 |
K | 類別單通道 | [255, 0] | 類別1紅/類別2黑 |
K×3 | 類別RGB | [[255,0,0], [0,255,0]] | 類別1紅/類別2綠 |
散點圖案例
- 簡單散點圖
import visdom
import numpy as npvis=visdom.Visdom(env='training_monitor')Y=np.random.rand(100)old_scatter=vis.scatter(X=np.random.rand(100,2),Y=(Y[Y>0]+1.5).astype(int),opts=dict(legend=['Didnt', 'Update'], # 圖例標簽xtickmin=-50, # x軸刻度最小值xtickmax=50, # x軸刻度最大值xtickstep=0.5, # x軸刻度間隔ytickmin=-50, # y軸刻度最小值ytickmax=50, # y軸刻度最大值ytickstep=0.5, # y軸刻度間隔markersymbol='cross-thin-open', # 標記符號)
)
# 使用update_window_opts函數更新之前繪制的散點圖的配置選項
vis.update_window_opts(win=old_scatter,opts=dict(legend=['2019年', '2020年'],xtickmin=0,xtickmax=1,xtickstep=0.5,ytickmin=0,ytickmax=1,ytickstep=0.5,markersymbol='cross-thin-open',),
)
- 帶文本標簽的散點圖
# 帶文本標簽的散點圖
import visdom
import numpy as np
vis=visdom.Visdom(env='training_monitor')
vis.scatter(X=np.random.rand(6, 2),opts=dict(textlabels=['Label %d' % (i + 1) for i in range(6)])
)
- 3D散點圖
#三維散點圖
import visdom
import numpy as np
# 設置環境
vis=visdom.Visdom(env='training_monitor')
# 繪制3D散點圖
vis.scatter(# X軸數據 隨機生成100行3列數據X=np.random.rand(100, 3),# Y軸數據 隨機生成100行1列數據Y=(Y + 1.5).astype(int),opts=dict(legend=['男性', '女性'], # 圖例標簽markersize=5, # 標記大小xtickmin=0, # x軸刻度最小值xtickmax=2, # x軸刻度最大值xlabel='數量', # x軸標簽xtickvals=[0, 0.75, 1.6, 2], # x軸刻度值ytickmin=0, # y軸刻度最小值ytickmax=2, # y軸刻度最大值ytickstep=0.5, # y軸刻度間隔ztickmin=0, # z軸刻度最小值ztickmax=1, # z軸刻度最大值ztickstep=0.5, # z軸刻度間隔)
)
線性圖vis.line()
vis.line()
參數說明
參數 | 類型 | 描述 | 默認值 | 注意事項 |
---|---|---|---|---|
X | Tensor/ndarray | X軸坐標值,N或N×M維 | None | 可省略(自動生成0-N) |
Y | Tensor/ndarray | Y軸坐標值,N或N×M維 | 必填 | M表示線條數量 |
win | str | 目標窗口名稱 | 自動生成 | 留空則創建新窗口 |
opts | dict | 繪圖選項字典 | None | 見下方選項表格 |
opts
選項明細表
選項 | 類型 | 描述 | 默認值 | 有效值/示例 |
---|---|---|---|---|
fillarea | bool | 是否填充線下區域 | False | True /False |
markers | bool | 是否顯示數據點標記 | False | True /False |
markersymbol | str | 標記形狀 | 'dot' | 'circle' , 'cross' , 'diamond' 等 |
markersize | int | 標記大小(像素) | 10 | 正整數(如 15 ) |
linecolor | np.array | 線條顏色數組 | None | RGB數組,如 np.array([255,0,0]) |
dash | np.array | 線條類型數組 | 'solid' | 'dash' , 'dot' , 'dashdot' |
legend | list | 圖例名稱列表 | None | ['Train', 'Val'] |
layoutopts | dict | Plotly布局擴展選項 | None | {'plotly': {'legend': {'x':0, 'y':1}}} |
traceopts | dict | Plotly軌跡擴展選項 | None | {'plotly': {'mode':'lines+markers'}} |
webgl | bool | 是否啟用WebGL加速 | False | 大數據量時建議 True |
關鍵特性說明
-
X/Y維度規則:
- 單線模式:
Y
為N×1,X
為N×1(或省略) - 多線模式:
Y
為N×M,X
為N×M或N×1(共享X軸)
- 單線模式:
-
顏色與線條控制:
opts = {'linecolor': np.array([[255,0,0], [0,0,255]]), # 第一條紅,第二條藍'dash': np.array(['solid', 'dash']) # 第一條實線,第二條虛線 }
線性圖案例
- 簡單線性圖
import numpy as np
import visdomvis = visdom.Visdom()# 基本線圖
vis.line(Y=np.random.rand(10),opts=dict(title='Basic Line', markers=True)
)# 多線帶圖例
vis.line(X=np.arange(10),Y=np.column_stack([np.sin(np.arange(10)), np.cos(np.arange(10))]),opts=dict(title='Trig Functions',legend=['Sin', 'Cos'],linecolor=np.array([[255,0,0], [0,0,255]]), # 紅藍雙線dash=np.array(['solid', 'dash']) )
)
- 實線、虛線的線條圖
#實線、虛線等不同線
import visdom
import numpy as npvis=visdom.Visdom(env='training_monitor')
# 繪制三種線
win = vis.line(# X軸數據 將三個在0和1之間的等差數列組成一個3列的矩陣X=np.column_stack((np.arange(0, 10),np.arange(0, 10),np.arange(0, 10),)),# Y軸數據 將三個在5和10之間的線性插值分別加上5、10后組成一個3列的矩陣Y=np.column_stack((np.linspace(5, 10, 10),np.linspace(5, 10, 10) + 5,np.linspace(5, 10, 10) + 10,)),opts={'dash': np.array(['solid', 'dash', 'dashdot']),'linecolor': np.array([[0, 191, 255],[0, 191, 255],[255, 0, 0],]),'title': '不同類型的線'}
)
# 在之前創建的窗口win上繼續繪制線條
vis.line(X=np.arange(0, 10), # X軸數據Y=np.linspace(5, 10, 10) + 15, # Y軸數據win=win, # 使用之前創建的窗口name='4', # 線條名稱update='insert', # 更新方式為插入opts={ # 繪制選項'linecolor': np.array([ # 線條顏色[255, 0, 0], # 紅色]),'dash': np.array(['dot']), # 線條樣式 只包含點}
)
- 堆疊區域線性圖
#堆疊區域
import visdom
import numpy as npvis=visdom.Visdom(env='training_monitor')Y = np.linspace(0, 4, 200)
win = vis.line(Y=np.column_stack((np.sqrt(Y), np.sqrt(Y) + 2)),X=np.column_stack((Y, Y)),opts=dict(fillarea=True, # 填充區域showlegend=False, # 不顯示圖例width=380, # 寬度height=330, # 高度ytype='log', # y軸類型title='堆積面積圖', # 標題marginleft=30, # 左邊距marginright=30, # 右邊距marginbottom=80, # 底邊距margintop=30, # 上邊距),
)
莖葉圖vis.stem
- 函數可繪制一個莖葉圖。它接受一個N或N×M張量X作為輸入,它指定M時間序列中N個點的值。還可以指定一個包含時間戳的可選N或NXM張量Y,如果Y是一個N張量,那么所有M個時間序列都假設有相同的時間戳。
opts.colormap
: 色圖(string; default = 'Viridis')
。opts.legend
:包含圖例名稱的表。opts.layoutopts
:圖形后端為布局接受的任何附加選項的字典,比如layoutopts={plotly:{legend': {x':0, 'y':0}}}
您完全正確!在Visdom的莖葉圖(vis.stem
)中,X和Y軸的設定確實容易讓人混淆,因為它的參數命名與常規的數學繪圖習慣相反。讓我們重新梳理清楚:
🔄 Visdom莖葉圖參數設計
在vis.stem(X, Y)
中:
Y
參數:實際對應的是X軸數據(自變量)X
參數:實際對應的是Y軸數據(因變量)
? 為什么Visdom這樣設計
Visdom的API設計可能源于:
- 數據優先原則:
X
參數接受主要可視化數據(函數值更關鍵) - 與線圖一致:
vis.line(Y=values)
的延續性 - 工程習慣:某些庫將輸入數據稱為
X
(如機器學習中的特征矩陣)
💡 記憶技巧
想象莖葉圖的物理形態:
- 莖(Stem)的根部固定在
Y
值(X軸) - 莖的頂端達到
X
值(Y軸高度)
📊 修正后的代碼解釋
import math
import numpy as np
import visdomvis = visdom.Visdom(env='training_monitor')# 生成X軸數據(自變量:角度)
angles = np.linspace(0, 2 * math.pi, 70) # 0到2π的70個點# 生成Y軸數據(因變量:函數值)
function_values = np.column_stack((np.sin(angles), np.cos(angles))) # 兩列:sin和cosvis.stem(X=function_values, # 莖頂的位置(Y軸值)Y=angles, # 莖的位置(X軸值)opts=dict(legend=['sin(θ)', 'cos(θ)'],title='莖葉圖:sin和cos函數對比',xtickvals=np.arange(0, 7, 1).tolist(), # 使用 .tolist() 轉換為 Python 列表xticklabels=['0', '1', '2', '3', '4', '5', '6'],ytickvals=np.arange(-1, 1.5, 0.5).tolist() # 同樣使用 .tolist())
)
🖼? 可視化效果說明
元素 | 對應數據 | 示例值 |
---|---|---|
莖的位置 | Y=angles | 0, 0.1π, 0.2π,… |
莖頂高度 | X=function_values | sin(0)=0, cos(0)=1 |
X軸 | 角度(θ) | 0到2π |
Y軸 | 函數值 | -1到1 |
深度學習訓練案例
- 完整的PyTorch訓練過程,使用Visdom進行可視化:
import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
import visdom# 初始化Visdom
vis = visdom.Visdom(env='MNIST_Experiment')# 定義簡單CNN模型
class SimpleCNN(nn.Module):def __init__(self):super(SimpleCNN, self).__init__()self.conv1 = nn.Conv2d(1, 10, kernel_size=5)self.conv2 = nn.Conv2d(10, 20, kernel_size=5)self.fc1 = nn.Linear(320, 50)self.fc2 = nn.Linear(50, 10)def forward(self, x):x = torch.relu(torch.max_pool2d(self.conv1(x), 2))x = torch.relu(torch.max_pool2d(self.conv2(x), 2))x = x.view(-1, 320)x = torch.relu(self.fc1(x))x = self.fc2(x)return torch.log_softmax(x, dim=1)# 準備數據
transform = transforms.Compose([transforms.ToTensor()])
train_dataset = datasets.MNIST('./data', train=True, download=True, transform=transform)
test_dataset = datasets.MNIST('./data', train=False, transform=transform)train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=1000, shuffle=False)# 初始化模型和優化器
model = SimpleCNN()
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.5)
criterion = nn.NLLLoss()# 訓練函數
def train(epoch):model.train()for batch_idx, (data, target) in enumerate(train_loader):optimizer.zero_grad()output = model(data)loss = criterion(output, target)loss.backward()optimizer.step()if batch_idx % 100 == 0:vis.line(X=[epoch * len(train_loader) + batch_idx],Y=[loss.item()],win='training_loss',update='append' if epoch + batch_idx > 0 else None,opts=dict(title='Training Loss', xlabel='Iterations', ylabel='Loss'))# 測試函數
def test(epoch):model.eval()test_loss = 0correct = 0with torch.no_grad():for data, target in test_loader:output = model(data)test_loss += criterion(output, target).item()pred = output.argmax(dim=1, keepdim=True)correct += pred.eq(target.view_as(pred)).sum().item()test_loss /= len(test_loader.dataset)accuracy = 100. * correct / len(test_loader.dataset)vis.line(X=[epoch],Y=[test_loss],win='test_loss',update='append' if epoch > 0 else None,opts=dict(title='Test Loss', xlabel='Epoch', ylabel='Loss'))vis.line(X=[epoch],Y=[accuracy],win='test_accuracy',update='append' if epoch > 0 else None,opts=dict(title='Test Accuracy', xlabel='Epoch', ylabel='Accuracy (%)'))# 可視化一些測試樣本和預測結果if epoch % 5 == 0:sample_data = next(iter(test_loader))[0][:10]outputs = model(sample_data)preds = outputs.argmax(dim=1)vis.images(sample_data,opts=dict(title=f'Predictions at Epoch {epoch}', caption=' '.join(str(p.item()) for p in preds)))# 運行訓練和測試
for epoch in range(1, 11):train(epoch)test(epoch)