使用教程,參考:
https://github.com/facebookresearch/visdom
https://www.pytorchtutorial.com/using-visdom-for-visualization-in-pytorch/
https://www.pytorchtutorial.com/pytorch-visdom/
⚠️中間發現visdom安裝的版本過低,導致發生了一些問題,后面更改了版本為最新版本0.1.8.8,所以可能會發現截圖有些不同,但是功能不會有太多影響
Visdom是Facebook專門為PyTorch開發的一款可視化工具,其開源於2017年3月。Visdom十分輕量級,但卻支持非常豐富的功能,能勝任大多數的科學運算可視化任務。
Visdom可以創造、組織和共享多種數據的可視化,包括數值、圖像、文本,甚至是視頻,其支持PyTorch、Torch及Numpy。用戶可通過編程組織可視化空間,或通過用戶接口為生動數據打造儀表板,檢查實驗結果或調試代碼。
Visdom中有兩個重要概念:
- envs:環境。不同環境的可視化結果相互隔離,互不影響,在使用時如果不指定env,默認使用
main
。不同用戶、不同程序一般使用不同的env。 - Panes:窗格。窗格可用於可視化圖像、數值或打印文本等,其可以拖動、縮放、保存和關閉。一個程序中可使用同一個env中的不同pane,每個pane可視化或記錄某一信息。Panes是保存在 envs 中的, envs的狀態 存儲在會話之間
使用Visdom就是在env中的pane上畫圖。
1.envs
您可以使用envs對可視化空間進行分區。默認地,每個用戶都會有一個叫做main的envs。
1)創建新環境
可以通過編程或UI創建新的envs。envs的狀態是長期保存的。
修改env的名字后點擊fork,保存當前env的狀態至更名后的env
然后就會生成該新命名的環境:
然后可見在$HOME/.visdom/文件中也生成了相應的json文件:
您可以通過http://localhost:8097/env/test訪問特定的env。如果您的服務器是被托管的,那么您可以將此url分享給其他人,那么其他人也會看到您的可視化結果。
在初始化服務器的時候,您的 envs 默認通過$HOME/.visdom/ 加載。您也可以將自定義的路徑當作命令行參數傳入。如果您移除了$HOME/.visdom/文件夾下的.json文件,那么相應的環境也會被刪除。
使用命令python -m visdom.server開啟visdom時可以使用的命令行參數有:
-port
: 指定運行服務的端口.-hostname
: 指定運行服務的主機名.-base_url
: 指定初始網址(default = /).-env_path
: 指定序列會話重新下載的路徑T-logging_level
: 日志級別(default = INFO).同時接受標准文本和數字日志值-readonly
: 標記再只讀模式下開啟服務-enable_login
: 標記為服務設置權限,需要用戶名和密碼來登錄服務-force_new_cookie
: 標記重置服務使用的安全cookie,禁用當前的登錄cookie。需要和-enable_login
一起使用
2)清除和保存
點擊clear按鈕可以清空當前env的所有pane,點擊save按鈕可將當前env保存成json文件,保存路徑位於~/.visdom/
目錄下。
新版本為:
新版本的clear為:
2.Panes
UI剛開始是個白板–您可以用圖像,圖片,文本填充它。這些填充的數據出現在 Panes 中,您可以這些Panes進行 拖放,刪除,調整大小和銷毀操作。Panes是保存在 envs 中的, envs的狀態存儲在會話之間。您可以下載Panes中的內容–包括您在svg中的繪圖。
舉例:
import visdom
import numpy as np vis = visdom.Visdom() #默認使用的env是main vis.text('Hello, world') #打印些字符 vis.image(np.ones((3,10,10))) #復制上面的圖
返回:
'pane_3738d21f24c328'
圖示:
然后點擊save,可見終端命令為:
from web client: {"cmd":"save","data":{"pane_3738d212484eec":null,"pane_3738d21f1f0050":null,"pane_3738d21f24c328":null},"prev_eid":"main","eid":"main"}
然后查看json文件:
存儲了如下的信息:

Callbacks回調
python Visdom實現支持窗口上的回調。演示以可編輯文本pad的形式展示了一個示例。這些回調的功能允許Visdom對象接收並響應前端發生的事件。
您可以通過使用您的handler處理程序和窗口id調用viz.register_event_handler(handler, win_id)來為你想要訂閱的窗口id向事件handler處理程序字典添加一個函數來訂閱窗口到事件中。多個handler處理程序可以注冊到同一個窗口。您可以使用viz.clear_event_handlers(win_id)從窗口中刪除所有事件handler處理程序。當事件發生在該窗口時,您的callback回調將調用一個包含以下內容的dict:
event_type
: 某個下面事件類型pane_data
: 該窗口的所有存儲內容,包括布局和內容。eid
: 當前環境idtarget
: 該事件調用的環境id
額外的參數定義在下方
現在支持如下的三個回調事件:
Close
- 當窗口關閉時觸發,返回只有上述字段的dict字典。KeyPress
- 按鍵時觸發。包含額外的參數:
key
- 按鍵的字符串表示(應用狀態修飾符,如SHIFT)key_code
- 按鍵的javascript事件鍵碼(沒有修飾符)
PropertyUpdate
- 在屬性窗格中更新屬性時觸發
propertyId
- 更改的屬性在屬性列表中的位置value
- 新屬性的值
Tip: 您可以使用瀏覽器的放大縮小功能來調整UI的大小。
3.State
一旦您創建了一些可視化,狀態是被保存的。服務器自動緩存您的可視化–如果您重新加載網頁,您的可視化會重新出現。
- Save: 你可以手動的保存env通過點擊save按鈕。它會首先序列化env的狀態,然后以json文件的形式保存到硬盤上,包括窗口的位置。 同樣,您也可以通過編程來實現env的保存。當面對一些十分復雜的可視化,例如參數設置非常重要,這中保存env狀態的方法是十分有用的。例:數據豐富的演示,模型的訓練dashboard, 或者系統實驗。這種設計依舊可以使這些可視化十分容易分享和復用。
- Fork: 有過您輸入了一個新的env 名字,saving會建立一個新的env – 有效的forking(復制)之前的狀態。
4.Filter
您可以使用filter動態篩選env中出現的窗口——只需提供一個正則表達式來匹配要顯示的窗口標題。這在涉及具有多個窗口的env的用例中很有用,例如在系統地檢查實驗結果時。
如:
5.Views
可以簡單地通過拖拽窗口頂部來管理視圖,但是還存在其他功能來保持視圖的組織和保存公共視圖。視圖管理對於在windows的多個公共組織之間保存和切換非常有用。
Saving/Deleting Views
使用文件夾圖標,將打開一個對話框窗口,其中視圖可以以與env相同的方式分叉。保存視圖將保留給定環境中所有窗口的位置和大小。視圖保存在$HOME/.visdom/view/layout中。visdom文件路徑中的json。
比如:
import numpy as np from visdom import Visdom viz = Visdom() viz.image( np.random.rand(3, 512, 256), #隨機生成一張圖 opts = dict(title = 'Random!', caption = 'how random'), )
圖示:
然后將該views保存為views1,然后點擊fork:
然后到相應的文件夾下面就能夠看見生成了layouts.json文件
注意:保存的視圖是靜態的,編輯保存的視圖會將視圖復制到當前視圖,在當前視圖中可以進行編輯。
Re-Packing
使用repack圖標(9個框),visdom將嘗試以最適合的方式打包窗口,同時保留行/列的順序。
注意:由於依賴行/列排序和ReactGridLayout,最終的布局可能與預期略有不同。我們正在努力改善這種體驗,或提供更多的替代方案,以實現更精確的控制。
Reloading Views重載視圖
使用視圖下拉框可以選擇以前保存的視圖,將當前環境中所有窗口的位置和大小恢復到上次保存視圖時的位置。
6.API
下面測試需要導入的包:
from __future__ import absolute_import from __future__ import division from __future__ import print_function from __future__ import unicode_literals from visdom import Visdom import argparse import numpy as np import math import os.path import time import tempfile from six.moves import urllib DEFAULT_PORT = 8097 DEFAULT_HOSTNAME = "http://localhost"
然后設置:
viz = Visdom(port=DEFAULT_PORT, server=DEFAULT_HOSTNAME)
1)visdom參數(python only)
當客戶端使用命令調用visdom時,寫法類似:
viz = visdom.Visdom() #使用參數進行設置
可以使用的參數有:
- server: 指定要調用的visdom服務端的主機名(default: 'http://localhost')
- port: 指定調用的visdom服務端的端口 (default: 8097)
- base_url: 指定初始調用的服務端url (default: /)
- env: 當沒有提供環境時,要plot到的默認環境 (default: main)
- raise_exceptions: 失敗時拋出異常,而不是打印異常 (default: True (soon))
- log_to_filename: 如果為None,則將所有繪圖和更新事件記錄到給定的文件中(append模式),以便稍后可以使用replay_log重播這些事件 (default: None)
- use_incoming_socket: 啟用套接字來接收來自web客戶機的事件,允許用戶注冊回調 (default: True)
- http_proxy_host: 棄用。使用proxy參數獲得完整的代理支持.
- http_proxy_port: 棄用。使用proxy參數獲得完整的代理支持.
- username: 如果服務器以-enable_login啟動,則使用username進行身份驗證 (default: None)
- password: 如果服務器以-enable_login啟動,則使用password進行身份驗證 (default: None)
- proxies: 字典映射協議到每個請求上使用的代理的URL(例如{http: foo.bar:3128}). (default: None)
- offline: 標記為在脫機模式下運行visdom,其中所有請求都記錄到文件中,而不是記錄到服務器。需要設置log_to_filename。在脫機模式下,所有不創建或更新圖的visdom命令將返回True. (default: False)
2)基本可視化函數
vis.image
: 圖片vis.images
: 圖片列表vis.text
: 抽象HTMLvis.properties
: 屬性網格vis.audio
: 音頻vis.video
: 視頻vis.svg
: SVG對象vis.matplot
: matplotlib圖vis.save
: 序列化狀態服務端
以下opts選項是通用的,因為它們對於所有可視化都是相同的(除了plot.image, plot.text, plot.video, and plot.audio):
opts.title
: 圖標題opts.width
: 圖寬opts.height
: 圖高opts.showlegend
: 顯示圖例 (true
orfalse
)opts.xtype
: x軸的類型 ('linear'
or'log'
)opts.xlabel
: x軸的標簽opts.xtick
: 顯示x軸上的刻度 (boolean
)opts.xtickmin
: 指定x軸上的第一個刻度 (number
)opts.xtickmax
: 指定x軸上的最后一個刻度 (number
)opts.xtickvals
: x軸上刻度的位置(table
ofnumber
s)opts.xticklabels
: 在x軸上標記標簽 (table
ofstring
s)opts.xtickstep
: x軸上刻度之間的距離 (number
)opts.xtickfont
:x軸標簽的字體 (dict of font information)opts.ytype
: type of y-axis ('linear'
or'log'
)opts.ylabel
: label of y-axisopts.ytick
: show ticks on y-axis (boolean
)opts.ytickmin
: first tick on y-axis (number
)opts.ytickmax
: last tick on y-axis (number
)opts.ytickvals
: locations of ticks on y-axis (table
ofnumber
s)opts.yticklabels
: ticks labels on y-axis (table
ofstring
s)opts.ytickstep
: distances between ticks on y-axis (number
)opts.ytickfont
: font for y-axis labels (dict of font information)opts.marginleft
: 左邊框 (in pixels)opts.marginright
:右邊框 (in pixels)opts.margintop
: 上邊框 (in pixels)opts.marginbottom
: 下邊框 (in pixels)
其他選項是具體於某些可視化的,並在下面的函數文檔中進行了描述。
1》vis.image
該函數繪制一張img圖。它將輸入設置為一個包含圖像的大小為CxHxW的tensor img
支持的opts有:
opts.jpgquality
: JPG 質量 (number
0-100; default = 100)opts.caption
: 圖片標題
⚠️可以在圖像窗格上使用alt查看光標的x/y坐標。您還可以ctrl-scroll來縮放,alt- scroll來垂直平移,alt-shift來水平平移。雙擊窗格內,將圖像恢復為默認值。
舉例:
# image demo
viz.image(
np.random.rand(3, 512, 256), opts=dict(title='Random!', caption='How random.'), )
圖示:
回調函數:
底圖為:
viz = Visdom()
assert viz.check_connection(timeout_seconds=3), \ 'No connection could be formed quickly' # image callback demo def show_color_image_window(color, win=None): image = np.full([3, 256, 256], color, dtype=float) return viz.image( image, opts=dict(title='Colors', caption='Press arrows to alter color.'), win=win ) image_color = 0 callback_image_window = show_color_image_window(image_color)
設置回調:
def image_callback(event): global image_color if event['event_type'] == 'KeyPress': if event['key'] == 'ArrowRight': image_color = min(image_color + 0.2, 1) if event['key'] == 'ArrowLeft': image_color = max(image_color - 0.2, 0) show_color_image_window(image_color, callback_image_window) viz.register_event_handler(image_callback, callback_image_window)
圖示:
然后可以通過左右鍵來調節該圖的顏色,直至白色,調到中間可見變為:
2》vis.images
該函數繪制一列圖。它取一個輸入為B x C x H x W大小的張量或一組大小相同的圖像。它生成一個大小為(B / nrow, nrow)的圖像網格。
支持的opts有:
nrow
: 一行圖像的數量padding
: 圖像四周的邊距,等於4條邊的邊距opts.jpgquality
: JPG質量 (number
0-100; default = 100)opts.caption
: 圖片標題
舉例:
# grid of images
viz.images(
np.random.randn(20, 3, 64, 64), opts=dict(title='Random images', caption='How random.') )
圖示:
設置行數nrow:
# grid of images
viz.images(
np.random.randn(20, 3, 64, 64), opts=dict(title='Random images', caption='How random.'), nrow=5 )
圖示:
3》vis.text
該函數在box中打印文本。您可以使用它來嵌入任意的HTML。輸入是一個文本string。目前沒有具體支持的opts
舉例:
viz = Visdom(port=DEFAULT_PORT, server=DEFAULT_HOSTNAME)
assert viz.check_connection(timeout_seconds=3), \ 'No connection could be formed quickly' textwindow = viz.text('Hello World!') #生成一個窗口,里面帶文本Hello World! #生成另一個窗口,里面帶文本Hello World! More text should be here updatetextwindow = viz.text('Hello World! More text should be here') #斷言查看updatetextwindow窗口對象是否存在 assert updatetextwindow is not None, 'Window was none' #窗口存在的話,就在該窗口中添加下面的文本,win指定添加到的窗口對象,append指定進行的操作是在元原有的基礎上添加 viz.text('And here it is', win=updatetextwindow, append=True)
返回生成的窗口編號:
'window_373974331dca16'
圖示:
帶有回調的操作:
#帶Callbacks回調的文本窗口
txt = 'This is a write demo notepad. Type below. Delete clears text:<br>' callback_text_window = viz.text(txt) #聲明回調時調用的函數,這個函數使得窗口能夠被編輯 def type_callback(event): #首先判斷是不是在窗口處進行了按鍵操作 if event['event_type'] == 'KeyPress': #如果是,那就將現在的窗口的數據內容作為curr_txt變量的值 curr_txt = event['pane_data']['content'] #如果輸入的是回車,就會在變量curr_txt中添加一個換行符,這樣在窗口中的文本就會換行 if event['key'] == 'Enter': curr_txt += '<br>' #如果輸入的是刪除鍵,就使用索引[:-1]刪除最后一個值 elif event['key'] == 'Backspace': curr_txt = curr_txt[:-1] #如果輸入的是刪除鍵,mac中是fn+Backspace,那么就返回原始狀態 elif event['key'] == 'Delete': curr_txt = txt #如果只是添加一些內容:字符數字等的操作,就直接添加在curr_txt后面即可 elif len(event['key']) == 1: curr_txt += event['key'] #然后根據上面對curr_txt的操作,再在callback_text_window窗口對象中覆蓋內容curr_txt實現編輯 viz.text(curr_txt, win=callback_text_window) #然后將該處理程序和窗口連接起來 viz.register_event_handler(type_callback, callback_text_window)
圖示:
4》vis.properties
該函數在窗口中顯示可編輯的屬性。屬性是一個字典的列表,如下所示:
properties = [
{'type': 'text', 'name': 'Text input', 'value': 'initial'}, {'type': 'number', 'name': 'Number input', 'value': '12'}, {'type': 'button', 'name': 'Button', 'value': 'Start'}, {'type': 'checkbox', 'name': 'Checkbox', 'value': True}, {'type': 'select', 'name': 'Select', 'value': 1, 'values': ['Red', 'Green', 'Blue']}, ]
支持的type屬性有:
- text: 字符串
- number: 十進制數
- button: 帶有 "value"標簽的按鈕
- checkbox: 將布爾值呈現為復選框
- select: 多值選擇框
value
:可選值的id (從0開始)values
: 可能值的列表
當屬性值更新時callback被調用:
event_type
: 這個命令的類型為"PropertyUpdate"
propertyId
: 更新的值在屬性列表中的位置value
: 更新的新值
目前沒有具體支持的opts
舉例:
首先要實現text的回調,否則在下面更改number input的內容時會報錯:
ERROR:websocket:error from callback <function Visdom.setup_socket.<locals>.on_message at 0x1039c2488>: name 'callback_text_window' is not defined
text回調為:
# text window with Callbacks
txt = 'This is a write demo notepad. Type below. Delete clears text:<br>' callback_text_window = viz.text(txt) def type_callback(event): if event['event_type'] == 'KeyPress': curr_txt = event['pane_data']['content'] if event['key'] == 'Enter': curr_txt += '<br>' elif event['key'] == 'Backspace': curr_txt = curr_txt[:-1] elif event['key'] == 'Delete': curr_txt = txt elif len(event['key']) == 1: curr_txt += event['key'] viz.text(curr_txt, win=callback_text_window) viz.register_event_handler(type_callback, callback_text_window)
實現:
# Properties window
properties = [ {'type': 'text', 'name': 'Text input', 'value': 'initial'}, {'type': 'number', 'name': 'Number input', 'value': '12'}, {'type': 'button', 'name': 'Button', 'value': 'Start'}, {'type': 'checkbox', 'name': 'Checkbox', 'value': True}, {'type': 'select', 'name': 'Select', 'value': 1, 'values': ['Red', 'Green', 'Blue']}, ] properties_window = viz.properties(properties)
返回:
實現回調:
#這樣就能夠對表格中的屬性進行更改
def properties_callback(event): if event['event_type'] == 'PropertyUpdate': prop_id = event['propertyId'] value = event['value'] #更改Text input的內容,並在后面添加進_updated if prop_id == 0: new_value = value + '_updated' #更改Number input,改成更改的值並在后面添加0 elif prop_id == 1: new_value = value + '0' #更改Button,點擊使其在start和stop中更改 elif prop_id == 2: new_value = 'Stop' if properties[prop_id]['value'] == 'Start' else 'Start' #當更改的是checkbox和Select時 else: new_value = value properties[prop_id]['value'] = new_value viz.properties(properties, win=properties_window) viz.text("Updated: {} => {}".format(properties[event['propertyId']]['name'], str(event['value'])), win=callback_text_window, append=True) viz.register_event_handler(properties_callback, properties_window)
返回:
可見當我將Text input的內容改成change once后回車,改處的值就變成了:
當我更改Number input處為13,就可見其后面添加了一個0:
當然我還進行了一些其他的操作,一些相應的更改信息會寫在text窗口中:
5》vis.audio
該函數播放音頻。它將音頻文件的文件名或包含波形的N張量作為輸入(立體聲音頻使用Nx2矩陣)。該函數不支持任何特定plot的opts選項。
支持的opts有:
opts.sample_frequency
: 采樣頻率 (integer
> 0; default = 44100)
已知問題:Visdom使用scipy將張量輸入轉換為wave文件。Chrome的一些版本不播放這些wave文件(Firefox和Safari運行良好)。
運行:
# audio demo:
tensor = np.random.uniform(-1, 1, 441000) viz.audio(tensor=tensor, opts={'sample_frequency': 441000})
返回:
但好像並不能真正播放
使用真正的音頻文件 :
# audio demo:
# download from http://www.externalharddrive.com/waves/animal/dolphin.wav try: audio_url = 'http://www.externalharddrive.com/waves/animal/dolphin.wav' audiofile = os.path.join(tempfile.gettempdir(), 'dolphin.wav') urllib.request.urlretrieve(audio_url, audiofile) if os.path.isfile(audiofile): viz.audio(audiofile=audiofile) except BaseException: print('Skipped audio example')
圖示:
並且真正能播放聲音
6》vis.video
該函數播放視頻。它以視頻文件的文件名或包含視頻所有幀的LxHxWxC大小的張量作為輸入。該函數不支持任何特定plot的選項。
支持的opts有:
opts.fps
: 視頻的FPS (integer
> 0; default = 25)
注意:使用張量輸入需要安裝ffmpeg並使其工作。您播放視頻的能力可能取決於您使用的瀏覽器:您的瀏覽器必須在OGG容器中支持Theano編解碼器(Chrome支持這一點)。
舉例:
使用tensor
video = np.empty([256, 250, 250, 3], dtype=np.uint8) for n in range(256): video[n, :, :, :].fill(n) viz.video(tensor=video)
運行時會出錯:
ModuleNotFoundError: No module named 'cv2'
解決辦法:
(deeplearning) userdeMBP:~ user$ pip install opencv-python
...
Successfully installed opencv-python-4.0.0.21
但是還是會出現問題:
local variable 'fourcc' referenced before assignment
這是因為安裝的opencv版本在4及以上的原因,因為源碼 /anaconda3/envs/deeplearning/lib/python3.6/site-packages/visdom/__init__.py 中:
elif cv2.__version__.startswith('3'): # OpenCV 3 ,指定使用版本3 fourcc = cv2.VideoWriter_fourcc( chr(ord('T')), chr(ord('H')), chr(ord('E')), chr(ord('O')) ) writer = cv2.VideoWriter( videofile, fourcc, opts.get('fps'), (tensor.shape[2], tensor.shape[1]) )
解決辦法是將上面的命令改成:
elif cv2.__version__.startswith(('3', '4')):
但是我好像沒有起效果,所以后面我把opencv-python改成3版本:
pip install opencv-python==3.4.5.20
記住,一定要重啟visdom ,最后返回,該視頻可播放:
使用已有視頻文件
try: # video demo: # download video from http://media.w3.org/2010/05/sintel/trailer.ogv video_url = 'http://media.w3.org/2010/05/sintel/trailer.ogv' videofile = os.path.join(tempfile.gettempdir(), 'trailer.ogv') urllib.request.urlretrieve(video_url, videofile) if os.path.isfile(videofile): #使用opts設定窗口的大小 viz.video(videofile=videofile, opts={'width': 864, 'height': 480}) except BaseException: print('Skipped video file example')
需要等待一段時間,然后就會返回一個窗格,點擊該窗格就會開始播放視頻:
7》vis.svg
該函數繪制一個SVG對象。它接受SVG字符串svgstr或SVG文件svgfile的名稱作為輸入。該函數不支持任何特定的選項。
舉例:
# SVG plotting
svgstr = """ <svg height="300" width="300"> <ellipse cx="80" cy="80" rx="50" ry="30" style="fill:red;stroke:purple;stroke-width:2" /> Sorry, your browser does not support inline SVG. </svg> """ viz.svg( svgstr=svgstr, opts=dict(title='Example of SVG Rendering') )
圖示:
8》vis.matplot
該函數繪制Matplotlib圖。該函數支持一個特定於場景的選項:resizable
⚠️當resizable設置為True時,將使用窗格調整繪圖的大小。您需要安裝beautifulsoup4和lxml包來使用此選項。
⚠️matplot不是使用與plotly圖相同的后端呈現的,而且效率略低。使用太多matplot窗口可能會降低visdom性能。
舉例:
# matplotlib demo:
try: import matplotlib.pyplot as plt plt.plot([1, 23, 2, 4]) plt.ylabel('some numbers') viz.matplot(plt) except BaseException as err: print('Skipped matplotlib example') print('Error message: ', err)
圖示:
9》vis.plotlyplot
這個函數繪制一個圖形對象。它並不像它假設您已經顯式配置了圖形的布局那樣顯式地接受選項。
⚠️必須安裝了plotly Python包才能使用此函數。它通常可以通過運行pip install來安裝。
10》vis.save
這個函數保存了在visdom服務器上活動的env。它接受要保存的env id的輸入列表(在python中)或表(在lua中)作為輸入。
比如你想要保存現在在環境'main'上的數據,不然它是不會記錄到其相應的main.json文件中的:
vis.save(['main'])
⚠️環境名參數envs是list