有沒有什么可以節(jié)省大量時間的 Deep Learning 效率神器?
點擊上方“程序員大白”,選擇“星標(biāo)”公眾號
重磅干貨,第一時間送達(dá)

1
作者:Fing
https://www.zhihu.com/question/384519338/answer/1160886439

wandb,weights&bias,最近發(fā)現(xiàn)的一個神庫。
深度學(xué)習(xí)實驗結(jié)果保存與分析是最讓我頭疼的一件事情,每個實驗要保存對應(yīng)的log,training curve還有生成圖片等等,光這些visualization就需要寫很多重復(fù)的代碼??缭O(shè)備的話還得把之前實驗的記錄都給拷到新設(shè)備去。
wandb這個庫真是深得我心,只要幾行代碼就可以把每一次實驗打包保存在云端,而且提供了自家的可視化接口,不用每次都自己寫一個logger,也省掉了import matplotlib, tensorboard等一大堆重復(fù)堆積的代碼塊。
最關(guān)鍵的是,它是免費的:)
https://github.com/wandb/client
2
作者:jpzLTIBaseline
https://www.zhihu.com/question/384519338/answer/1196326124
關(guān)于實驗管理,其他人的回答已經(jīng)寫得十分詳細(xì)了。雖然我自己還是習(xí)慣直接Google Sheet然后在表格里的每一行記錄【git commit hashcode】、【server name】、【pid】、【bash script to run exp】、【實驗具體結(jié)果】、【notes】、【log position】、【ckpt position】,而且Google Sheet增加column以及合并格子用起來還是很flexible的。
這里我提一下其他方面的一些有助于提高效率的工具:
給自己的model起一個酷炫的縮寫:http://acronymify.com/
現(xiàn)在越來越多的論文標(biāo)題(尤其是Deep Learning方向)都是 [model縮寫]: [正經(jīng)論文題目] 的格式,而且一個朗朗上口的名字確實有助于記憶與傳播。
寫paper時候的用詞搭配:https://linggle.com/
作為一個non-native speaker,寫paper的時候詞語搭配真是讓人頭禿。這個網(wǎng)站可以比較方便地找一些詞語搭配。
手寫/截圖 轉(zhuǎn) LaTex公式:https://mathpix.com/
LaTex如果所有公式都要自己手打還是很痛苦的。(雖然很多時候一篇Deep Learning方向的paper公式數(shù)量只有十個左右(這還是在強行加上LSTM等被翻來覆去寫爛的公式的情況下))
顏色搭配(色盲友好型):http://colorbrewer2.org/
這個網(wǎng)站不僅能很方便找到各種常用的 color schemes,而且都是 grayscale friendly and colorblind-friendly,對于paper里畫圖幫助比較大。
找前人paper的code:https://paperswithcode.com/
有的時候自己復(fù)現(xiàn)真是玄學(xué),這個網(wǎng)站和搜索引擎 "[論文題目] site:http://github.com"配合使用即可。
暫時想到這么多,有空再更。
Update:
文字轉(zhuǎn)語音:https://cloud.google.com/text-to-speech
有的paper需要做一個video來介紹,對自己口語不是很有信心的話可以用G家的text2speech(這個領(lǐng)域Google應(yīng)該是當(dāng)之無愧的霸主),還能調(diào)節(jié)語速,非常貼心。
3
作者:McGL
https://www.zhihu.com/question/384519338/answer/1534457655
寫深度學(xué)習(xí)網(wǎng)絡(luò)代碼,最大的挑戰(zhàn)之一,尤其對新手來說,就是把所有的張量維度正確對齊。如果以前就有TensorSensor這個工具,相信我的頭發(fā)一定比現(xiàn)在更濃密茂盛!
【此處防脫發(fā)洗發(fā)水廣告位火熱招租......】
TensorSensor,碼癡教授 Terence Parr 出品,他也是著名 parser 工具 ANTLR 的作者。
在包含多個張量和張量運算的復(fù)雜表達(dá)式中,張量的維數(shù)很容易忘了。即使只是將數(shù)據(jù)輸入到預(yù)定義的 TensorFlow 網(wǎng)絡(luò)層,維度也要弄對。當(dāng)你要求進行錯誤的計算時,通常會得到一些沒啥用的異常消息。為了幫助自己和其他程序員調(diào)試張量代碼,Terence Parr 寫了一個名叫 TensorSensor 的庫(pip install tensor-sensor 直接安裝) 。TensorSensor 通過增加消息和可視化 Python 代碼來展示張量變量的形狀,讓異常更清晰(見下圖)。它可以兼容 TensorFlow、PyTorch 和 Numpy以及 Keras 和 fastai 等高級庫。

在張量代碼中定位問題令人抓狂!
即使是專家,執(zhí)行張量操作的 Python 代碼行中發(fā)生異常,也很難快速定位原因。調(diào)試過程通常是在有問題的行前面添加一個 print 語句,以打出每個張量的形狀。這需要編輯代碼添加調(diào)試語句并重新運行訓(xùn)練過程?;蛘?,我們可以使用交互式調(diào)試器手動單擊或鍵入命令來請求所有張量形狀。(這在像 PyCharm 這樣的 IDE 中不太實用,因為在調(diào)試模式很慢。)下面將詳細(xì)對比展示看了讓人貧血的缺省異常消息和 TensorSensor 提出的方法,而不用調(diào)試器或 print 大法。
調(diào)試一個簡單的線性層
讓我們來看一個簡單的張量計算,來說明缺省異常消息提供的信息不太理想。下面是一個包含張量維度錯誤的硬編碼單(線性)網(wǎng)絡(luò)層的簡單 NumPy 實現(xiàn)。
import numpy as npn = 200 # number of instancesd = 764 # number of instance featuresn_neurons = 100 # how many neurons in this layer?W = np.random.rand(d,n_neurons) # Ooops! Should be (n_neurons,d) <=======b = np.random.rand(n_neurons,1)X = np.random.rand(n,d) # fake input matrix with n rows of d-dimensionsY = W @ X.T + b # pass all X instances through layer
執(zhí)行該代碼會觸發(fā)一個異常,其重要元素如下:
...---> 10 Y = W @ X.T + bValueError: matmul: Input operand 1 has a mismatch in its core dimension 0, with gufunc signature (n?,k),(k,m?)->(n?,m?) (size 764 is different from 100)
異常顯示了出錯的行以及是哪個操作(matmul: 矩陣乘法),但是如果給出完整的張量維數(shù)會更有用。此外,這個異常也無法區(qū)分在 Python 的一行中的多個矩陣乘法。
接下來,讓我們看看 TensorSensor 如何使調(diào)試語句更加容易的。如果我們使用 Python with 和tsensor 的 clarify()包裝語句,我們將得到一個可視化和增強的錯誤消息。
import tsensorwith tsensor.clarify():Y = W @ X.T + b

...ValueError: matmul: Input operand ...Cause: @ on tensor operand W w/shape (764, 100) and operand X.T w/shape (764, 200)
從可視化中可以清楚地看到,W 的維度應(yīng)該翻轉(zhuǎn)為 n _ neurons x d; W 的列必須與 X.T 的行匹配。您還可以檢查一個完整的帶有和不帶闡明()的并排圖像,以查看它在筆記本中的樣子。下面是帶有和沒有 clarify() 的例子在notebook 中的比較。

clarify() 功能在沒有異常時不會增加正在執(zhí)行的程序任何開銷。有異常時, clarify():
增加由底層張量庫創(chuàng)建的異常對象消息。
給出出錯操作所涉及的張量大小的可視化表示; 只突出顯示異常涉及的操作對象和運算符,而其他 Python 元素則不突出顯示。
TensorSensor 還區(qū)分了 PyTorch 和 TensorFlow 引發(fā)的與張量相關(guān)的異常。下面是等效的代碼片段和增強的異常錯誤消息(Cause: @ on tensor ...)以及 TensorSensor 的可視化:

PyTorch 消息沒有標(biāo)識是哪個操作觸發(fā)了異常,但 TensorFlow 的消息指出了是矩陣乘法。兩者都顯示操作對象維度。
調(diào)試復(fù)雜的張量表達(dá)式
缺省消息缺乏具體細(xì)節(jié),在包含大量操作符的更復(fù)雜的語句中,識別出有問題的子表達(dá)式很難。例如,下面是從一個門控循環(huán)單元(GRU)實現(xiàn)的內(nèi)部提取的一個語句:
h_ = torch.tanh(Whh_ @ (r*h) + Uxh_ @ X.T + bh_)這是什么計算或者變量代表什么不重要,它們只是張量變量。有兩個矩陣乘法,兩個向量加法,還有一個向量逐元素修改(r*h)。如果沒有增強的錯誤消息或可視化,我們就無法知道是哪個操作符或操作對象導(dǎo)致了異常。為了演示 TensorSensor 在這種情況下是如何分清異常的,我們需要給語句中使用的變量(為 h _ 賦值)一些偽定義,以得到可執(zhí)行代碼:
nhidden = 256Whh_ = torch.eye(nhidden, nhidden) # Identity matrixUxh_ = torch.randn(d, nhidden)bh_ = torch.zeros(nhidden, 1)h = torch.randn(nhidden, 1) # fake previous hidden state hr = torch.randn(nhidden, 1) # fake this computationX = torch.rand(n,d) # fake inputwith tsensor.clarify():h_ = torch.tanh(Whh_ @ (r*h) + Uxh_ @ X.T + bh_)
同樣,你可以忽略代碼執(zhí)行的實際計算,將重點放在張量變量的形狀上。
對于我們大多數(shù)人來說,僅僅通過張量維數(shù)和張量代碼是不可能識別問題的。當(dāng)然,默認(rèn)的異常消息是有幫助的,但是我們中的大多數(shù)人仍然難以定位問題。以下是默認(rèn)異常消息的關(guān)鍵部分(注意對 C++ 代碼的不太有用的引用) :
---> 10 h_ = torch.tanh(Whh_ @ (r*h) + Uxh_ @ X.T + bh_)RuntimeError: size mismatch, m1: [764 x 256], m2: [764 x 200] at /tmp/pip-req-build-as628lz5/aten/src/TH/generic/THTensorMath.cpp:41
我們需要知道的是哪個操作符和操作對象出錯了,然后我們可以通過維數(shù)來確定問題。以下是 TensorSensor 的可視化和增強的異常消息:

---> 10 h_ = torch.tanh(Whh_ @ (r*h) + Uxh_ @ X.T + bh_)RuntimeError: size mismatch, m1: [764 x 256], m2: [764 x 200] at /tmp/pip-req-build-as628lz5/aten/src/TH/generic/THTensorMath.cpp:41Cause: @ on tensor operand Uxh_ w/shape [764, 256] and operand X.T w/shape [764, 200]
人眼可以迅速鎖定在指示的算子和矩陣相乘的維度上。哎呀, Uxh 的列必須與 X.T的行匹配,Uxh_的維度翻轉(zhuǎn)了,應(yīng)該為:
Uxh_ = torch.randn(nhidden, d)現(xiàn)在,我們只在 with 代碼塊中使用我們自己直接指定的張量計算。那么在張量庫的內(nèi)置預(yù)建網(wǎng)絡(luò)層中觸發(fā)的異常又會如何呢?
理清預(yù)建層中觸發(fā)的異常
TensorSensor 可視化進入你選擇的張量庫前的最后一段代碼。例如,讓我們使用標(biāo)準(zhǔn)的 PyTorch nn.Linear 線性層,但輸入一個 X 矩陣維度是 n x n,而不是正確的 n x d:
L = torch.nn.Linear(d, n_neurons)X = torch.rand(n,n) # oops! Should be n x dwith tsensor.clarify():Y = L(X)

增強的異常信息
RuntimeError: size mismatch, m1: [200 x 200], m2: [764 x 100] at /tmp/pip-req-build-as628lz5/aten/src/TH/generic/THTensorMath.cpp:41Cause: L(X) tensor arg X w/shape [200, 200]
TensorSensor 將張量庫的調(diào)用視為操作符,無論是對網(wǎng)絡(luò)層還是對 torch.dot(a,b) 之類的簡單操作的調(diào)用。在庫函數(shù)中觸發(fā)的異常會產(chǎn)生消息,消息標(biāo)示了函數(shù)和任何張量參數(shù)的維數(shù)。
更多的功能比如不拋異常的情況下解釋張量代碼,可視化3D及更高維度張量,以及可視化子表達(dá)式張量形狀等請瀏覽官方Blog。
參考:
Clarifying exceptions and visualizing tensor operations in deep learning code
4
作者:崔權(quán)
https://www.zhihu.com/question/384519338/answer/1206812752
如何在睡覺的時候也調(diào)參???
最近試了一下PFN (Preferred Networks)出品的自動調(diào)參工具optuna,覺得使用起來非常方便,在自己的code上也很好集成,寫個東西分享一下用法,扒一個PyTorch的example code。
Quick Start Example
optuna的官網(wǎng)還是很有意思的,直接上來就用code說話,貼了一個quick start example code:
import optunadef objective(trial):x = trial.suggest_uniform('x', -10, 10)return (x - 2) ** 2study = optuna.create_study()study.optimize(objective, n_trials=100)study.best_params # E.g. {'x': 2.002108042}
要使用optuna,只需要用戶自己定義一個objective函數(shù),剩下的就交給optuna自己完成就行。所以對這個例子的解讀我們也分兩個部分,第一個是定義objective函數(shù),第二個是optuna對超參的自動優(yōu)化;
1)定義的objective函數(shù)只有一個參數(shù)trial,在這個例子里,trial調(diào)用了一個函數(shù)「suggest_uniform()」,這個函數(shù)用來將變量'x'標(biāo)記為需要optimize的超參,且是從-10到10的uniform分布里采的值。objective函數(shù)返回的是需要maximize or minimize的數(shù)值 (例如Acc, mAP, 等),這個例子里需要優(yōu)化的就是(x-2)^2;
2)要使用optuna自動優(yōu)化超參'x',需要創(chuàng)建一個study對象 (optuna.create_study),然后將objective當(dāng)作參數(shù)傳入study的optimize函數(shù)就可以實現(xiàn)對超參的自動搜索了。另一個需要給optimize的參數(shù)是n_trials,這個參數(shù)可以理解為要搜多少次;
等搜完以后就可以用best_params查看最合適的參數(shù)啦。
Optuna簡介
簡單介紹一下optuna里最重要的幾個term,想直接看在PyTorch里咋用可以直接跳過。
1)在optuna里最重要的三個term:
(1)Trial:對objective函數(shù)的一次調(diào)用;
(2)Study:一個優(yōu)化超參的session,由一系列的trials組成;
(3)Parameter:需要優(yōu)化的超參;
在optuna里,study對象用來管理對超參的優(yōu)化,optuna.create_study()返回一個study對象。
2)study又有很多有用的property:
(1)study.best_params:搜出來的最優(yōu)超參;
(2)study.best_value:最優(yōu)超參下,objective函數(shù)返回的值 (如最高的Acc,最低的Error rate等);
(3)study.best_trial:最優(yōu)超參對應(yīng)的trial,有一些時間、超參、trial編號等信息;
(4)study.optimize(objective, n_trials):對objective函數(shù)里定義的超參進行搜索;
3)optuna支持很多種搜索方式:
(1)trial.suggest_categorical('optimizer', ['MomentumSGD', 'Adam']):表示從SGD和adam里選一個使用;
(2)trial.suggest_int('num_layers', 1, 3):從1~3范圍內(nèi)的int里選;
(3)trial.suggest_uniform('dropout_rate', 0.0, 1.0):從0~1內(nèi)的uniform分布里選;
(4)trial.suggest_loguniform('learning_rate', 1e-5, 1e-2):從1e-5~1e-2的log uniform分布里選;
(5)trial.suggest_discrete_uniform('drop_path_rate', 0.0, 1.0, 0.1):從0~1且step為0.1的離散uniform分布里選;
PyTorch Example Code
扒一個官方給的PyTorch例子,鏈接在這:
https://github.com/optuna/optuna/blob/master/examples/pytorch_simple.py
import osimport torchimport torch.nn as nnimport torch.nn.functional as Fimport torch.optim as optimimport torch.utils.datafrom torchvision import datasetsfrom torchvision import transformsimport optunaDEVICE = torch.device("cpu")BATCHSIZE = 128CLASSES = 10DIR = os.getcwd()EPOCHS = 10LOG_INTERVAL = 10N_TRAIN_EXAMPLES = BATCHSIZE * 30N_VALID_EXAMPLES = BATCHSIZE * 10
到目前為止是一些package的導(dǎo)入和參數(shù)的定義;
def define_model(trial):# We optimize the number of layers, hidden untis and dropout ratio in each layer.n_layers = trial.suggest_int("n_layers", 1, 3)layers = []in_features = 28 * 28for i in range(n_layers):out_features = trial.suggest_int("n_units_l{}".format(i), 4, 128)layers.append(nn.Linear(in_features, out_features))layers.append(nn.ReLU())p = trial.suggest_uniform("dropout_l{}".format(i), 0.2, 0.5)layers.append(nn.Dropout(p))in_features = out_featureslayers.append(nn.Linear(in_features, CLASSES))layers.append(nn.LogSoftmax(dim=1))return nn.Sequential(*layers)def get_mnist():# Load MNIST dataset.train_loader = torch.utils.data.DataLoader(datasets.MNIST(DIR, train=True, download=True, transform=transforms.ToTensor()),batch_size=BATCHSIZE,shuffle=True,)valid_loader = torch.utils.data.DataLoader(datasets.MNIST(DIR, train=False, transform=transforms.ToTensor()),batch_size=BATCHSIZE,shuffle=True,)return train_loader, valid_loader
這一段代碼里有個很有趣的用法,定義了一個函數(shù)define_model,里面用定義了一個需要優(yōu)化的變量n_layers,用來搜索網(wǎng)絡(luò)的層數(shù),所以其實optuna還可以用來做NAS之類的工作,return成nn.Module就可以搜起來了;
def objective(trial):# Generate the model.model = define_model(trial).to(DEVICE)# Generate the optimizers.optimizer_name = trial.suggest_categorical("optimizer", ["Adam", "RMSprop", "SGD"])lr = trial.suggest_loguniform("lr", 1e-5, 1e-1)optimizer = getattr(optim, optimizer_name)(model.parameters(), lr=lr)# Get the MNIST dataset.train_loader, valid_loader = get_mnist()# Training of the model.model.train()for epoch in range(EPOCHS):for batch_idx, (data, target) in enumerate(train_loader):# Limiting training data for faster epochs.if batch_idx * BATCHSIZE >= N_TRAIN_EXAMPLES:breakdata, target = data.view(-1, 28 * 28).to(DEVICE), target.to(DEVICE)# Zeroing out gradient buffers.optimizer.zero_grad()# Performing a forward pass.output = model(data)# Computing negative Log Likelihood loss.loss = F.nll_loss(output, target)# Performing a backward pass.loss.backward()# Updating the weights.optimizer.step()# Validation of the model.model.eval()correct = 0with torch.no_grad():for batch_idx, (data, target) in enumerate(valid_loader):# Limiting validation data.if batch_idx * BATCHSIZE >= N_VALID_EXAMPLES:breakdata, target = data.view(-1, 28 * 28).to(DEVICE), target.to(DEVICE)output = model(data)pred = output.argmax(dim=1, keepdim=True) # Get the index of the max log-probability.correct += pred.eq(target.view_as(pred)).sum().item()accuracy = correct / N_VALID_EXAMPLESreturn accuracy
這里是最重要的objective函數(shù),首先定義了幾個需要優(yōu)化的parameter,「optimizer_name, lr和model里的n_layers和p」。剩下的就是一些常規(guī)的訓(xùn)練和測試代碼,其中N_TRAIN_EXAMPLES和N_VAL_EXAMPLES是為了篩選出一小部分?jǐn)?shù)據(jù)集用來搜索,畢竟用整個數(shù)據(jù)集來搜還是挺費勁的。
值得注意的是,objective函數(shù)返回的是accuracy,講道理,搜索參數(shù)的目標(biāo)是為了最大化該分類任務(wù)的accuracy,所以在創(chuàng)建study object的時候指定了direction為"maximize"。如果定義objective函數(shù)時返回的是類似error rate的值,則應(yīng)該將direction指定為"minimize"。
if __name__ == "__main__":study = optuna.create_study(direction="maximize")study.optimize(objective, n_trials=100)print("Number of finished trials: ", len(study.trials))print("Best trial:")trial = study.best_trialprint(" Value: ", trial.value)print(" Params: ")for key, value in trial.params.items():print(" {}: {}".format(key, value))
這里就是一些optuna的調(diào)用代碼,很容易理解就不多說了。
自己使用optuna的思路
1)sample個靠譜的子數(shù)據(jù)集;
2)大概寫個objective函數(shù)的訓(xùn)練和測試代碼,objective函數(shù)返回一個需要優(yōu)化的metric;
3)把要優(yōu)化的變量定義成optuna的parameter(通過trial.suggest_xxx);
4)copy個main部分代碼,開始搜超參;
5)睡覺;
6)醒來用最優(yōu)的參數(shù)在整個數(shù)據(jù)集上跑跑效果;
在自己的code上試了一下optuna,搜出來的參數(shù)和我手調(diào)的最佳參數(shù)差不太多,感覺還是靠譜的。祝大家使用愉快!
推薦閱讀
關(guān)于程序員大白
程序員大白是一群哈工大,東北大學(xué),西湖大學(xué)和上海交通大學(xué)的碩士博士運營維護的號,大家樂于分享高質(zhì)量文章,喜歡總結(jié)知識,歡迎關(guān)注[程序員大白],大家一起學(xué)習(xí)進步!
