有沒有什么可以節(jié)省大量時(shí)間的 Deep Learning 效率神器?
點(diǎn)擊左上方藍(lán)字關(guān)注我們

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

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

在張量代碼中定位問題令人抓狂!
即使是專家,執(zhí)行張量操作的 Python 代碼行中發(fā)生異常,也很難快速定位原因。調(diào)試過程通常是在有問題的行前面添加一個(gè) print 語(yǔ)句,以打出每個(gè)張量的形狀。這需要編輯代碼添加調(diào)試語(yǔ)句并重新運(yùn)行訓(xùn)練過程?;蛘?,我們可以使用交互式調(diào)試器手動(dòng)單擊或鍵入命令來(lái)請(qǐng)求所有張量形狀。(這在像 PyCharm 這樣的 IDE 中不太實(shí)用,因?yàn)樵谡{(diào)試模式很慢。)下面將詳細(xì)對(duì)比展示看了讓人貧血的缺省異常消息和 TensorSensor 提出的方法,而不用調(diào)試器或 print 大法。
調(diào)試一個(gè)簡(jiǎn)單的線性層
讓我們來(lái)看一個(gè)簡(jiǎn)單的張量計(jì)算,來(lái)說(shuō)明缺省異常消息提供的信息不太理想。下面是一個(gè)包含張量維度錯(cuò)誤的硬編碼單(線性)網(wǎng)絡(luò)層的簡(jiǎn)單 NumPy 實(shí)現(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í)行該代碼會(huì)觸發(fā)一個(gè)異常,其重要元素如下:
...---> 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)
異常顯示了出錯(cuò)的行以及是哪個(gè)操作(matmul: 矩陣乘法),但是如果給出完整的張量維數(shù)會(huì)更有用。此外,這個(gè)異常也無(wú)法區(qū)分在 Python 的一行中的多個(gè)矩陣乘法。
接下來(lái),讓我們看看 TensorSensor 如何使調(diào)試語(yǔ)句更加容易的。如果我們使用 Python with 和tsensor 的 clarify()包裝語(yǔ)句,我們將得到一個(gè)可視化和增強(qiáng)的錯(cuò)誤消息。
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 的行匹配。您還可以檢查一個(gè)完整的帶有和不帶闡明()的并排圖像,以查看它在筆記本中的樣子。下面是帶有和沒有 clarify() 的例子在notebook 中的比較。

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

PyTorch 消息沒有標(biāo)識(shí)是哪個(gè)操作觸發(fā)了異常,但 TensorFlow 的消息指出了是矩陣乘法。兩者都顯示操作對(duì)象維度。
調(diào)試復(fù)雜的張量表達(dá)式
缺省消息缺乏具體細(xì)節(jié),在包含大量操作符的更復(fù)雜的語(yǔ)句中,識(shí)別出有問題的子表達(dá)式很難。例如,下面是從一個(gè)門控循環(huán)單元(GRU)實(shí)現(xiàn)的內(nèi)部提取的一個(gè)語(yǔ)句:
h_ = torch.tanh(Whh_ @ (r*h) + Uxh_ @ X.T + bh_)這是什么計(jì)算或者變量代表什么不重要,它們只是張量變量。有兩個(gè)矩陣乘法,兩個(gè)向量加法,還有一個(gè)向量逐元素修改(r*h)。如果沒有增強(qiáng)的錯(cuò)誤消息或可視化,我們就無(wú)法知道是哪個(gè)操作符或操作對(duì)象導(dǎo)致了異常。為了演示 TensorSensor 在這種情況下是如何分清異常的,我們需要給語(yǔ)句中使用的變量(為 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í)際計(jì)算,將重點(diǎn)放在張量變量的形狀上。
對(duì)于我們大多數(shù)人來(lái)說(shuō),僅僅通過張量維數(shù)和張量代碼是不可能識(shí)別問題的。當(dāng)然,默認(rèn)的異常消息是有幫助的,但是我們中的大多數(shù)人仍然難以定位問題。以下是默認(rèn)異常消息的關(guān)鍵部分(注意對(duì) 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
我們需要知道的是哪個(gè)操作符和操作對(duì)象出錯(cuò)了,然后我們可以通過維數(shù)來(lái)確定問題。以下是 TensorSensor 的可視化和增強(qiáng)的異常消息:

---> 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 代碼塊中使用我們自己直接指定的張量計(jì)算。那么在張量庫(kù)的內(nèi)置預(yù)建網(wǎng)絡(luò)層中觸發(fā)的異常又會(huì)如何呢?
理清預(yù)建層中觸發(fā)的異常
TensorSensor 可視化進(jìn)入你選擇的張量庫(kù)前的最后一段代碼。例如,讓我們使用標(biāo)準(zhǔn)的 PyTorch nn.Linear 線性層,但輸入一個(gè) 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)

增強(qiáng)的異常信息
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 將張量庫(kù)的調(diào)用視為操作符,無(wú)論是對(duì)網(wǎng)絡(luò)層還是對(duì) torch.dot(a,b) 之類的簡(jiǎn)單操作的調(diào)用。在庫(kù)函數(shù)中觸發(fā)的異常會(huì)產(chǎn)生消息,消息標(biāo)示了函數(shù)和任何張量參數(shù)的維數(shù)。
更多的功能比如不拋異常的情況下解釋張量代碼,可視化3D及更高維度張量,以及可視化子表達(dá)式張量形狀等請(qǐng)瀏覽官方Blog。
參考:
Clarifying exceptions and visualizing tensor operations in deep learning code
4
作者:崔權(quán)
https://www.zhihu.com/question/384519338/answer/1206812752
如何在睡覺的時(shí)候也調(diào)參???
最近試了一下PFN (Preferred Networks)出品的自動(dòng)調(diào)參工具optuna,覺得使用起來(lái)非常方便,在自己的code上也很好集成,寫個(gè)東西分享一下用法,扒一個(gè)PyTorch的example code。
Quick Start Example
optuna的官網(wǎng)還是很有意思的,直接上來(lái)就用code說(shuō)話,貼了一個(gè)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,只需要用戶自己定義一個(gè)objective函數(shù),剩下的就交給optuna自己完成就行。所以對(duì)這個(gè)例子的解讀我們也分兩個(gè)部分,第一個(gè)是定義objective函數(shù),第二個(gè)是optuna對(duì)超參的自動(dòng)優(yōu)化;
1)定義的objective函數(shù)只有一個(gè)參數(shù)trial,在這個(gè)例子里,trial調(diào)用了一個(gè)函數(shù)「suggest_uniform()」,這個(gè)函數(shù)用來(lái)將變量'x'標(biāo)記為需要optimize的超參,且是從-10到10的uniform分布里采的值。objective函數(shù)返回的是需要maximize or minimize的數(shù)值 (例如Acc, mAP, 等),這個(gè)例子里需要優(yōu)化的就是(x-2)^2;
2)要使用optuna自動(dòng)優(yōu)化超參'x',需要?jiǎng)?chuàng)建一個(gè)study對(duì)象 (optuna.create_study),然后將objective當(dāng)作參數(shù)傳入study的optimize函數(shù)就可以實(shí)現(xiàn)對(duì)超參的自動(dòng)搜索了。另一個(gè)需要給optimize的參數(shù)是n_trials,這個(gè)參數(shù)可以理解為要搜多少次;
等搜完以后就可以用best_params查看最合適的參數(shù)啦。
Optuna簡(jiǎn)介
簡(jiǎn)單介紹一下optuna里最重要的幾個(gè)term,想直接看在PyTorch里咋用可以直接跳過。
1)在optuna里最重要的三個(gè)term:
(1)Trial:對(duì)objective函數(shù)的一次調(diào)用;
(2)Study:一個(gè)優(yōu)化超參的session,由一系列的trials組成;
(3)Parameter:需要優(yōu)化的超參;
在optuna里,study對(duì)象用來(lái)管理對(duì)超參的優(yōu)化,optuna.create_study()返回一個(gè)study對(duì)象。
2)study又有很多有用的property:
(1)study.best_params:搜出來(lái)的最優(yōu)超參;
(2)study.best_value:最優(yōu)超參下,objective函數(shù)返回的值 (如最高的Acc,最低的Error rate等);
(3)study.best_trial:最優(yōu)超參對(duì)應(yīng)的trial,有一些時(shí)間、超參、trial編號(hào)等信息;
(4)study.optimize(objective, n_trials):對(duì)objective函數(shù)里定義的超參進(jìn)行搜索;
3)optuna支持很多種搜索方式:
(1)trial.suggest_categorical('optimizer', ['MomentumSGD', 'Adam']):表示從SGD和adam里選一個(gè)使用;
(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
扒一個(gè)官方給的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
這一段代碼里有個(gè)很有趣的用法,定義了一個(gè)函數(shù)define_model,里面用定義了一個(gè)需要優(yōu)化的變量n_layers,用來(lái)搜索網(wǎng)絡(luò)的層數(shù),所以其實(shí)optuna還可以用來(lái)做NAS之類的工作,return成nn.Module就可以搜起來(lái)了;
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ù),首先定義了幾個(gè)需要優(yōu)化的parameter,「optimizer_name, lr和model里的n_layers和p」。剩下的就是一些常規(guī)的訓(xùn)練和測(cè)試代碼,其中N_TRAIN_EXAMPLES和N_VAL_EXAMPLES是為了篩選出一小部分?jǐn)?shù)據(jù)集用來(lái)搜索,畢竟用整個(gè)數(shù)據(jù)集來(lái)搜還是挺費(fèi)勁的。
值得注意的是,objective函數(shù)返回的是accuracy,講道理,搜索參數(shù)的目標(biāo)是為了最大化該分類任務(wù)的accuracy,所以在創(chuàng)建study object的時(shí)候指定了direction為"maximize"。如果定義objective函數(shù)時(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)用代碼,很容易理解就不多說(shuō)了。
自己使用optuna的思路
1)sample個(gè)靠譜的子數(shù)據(jù)集;
2)大概寫個(gè)objective函數(shù)的訓(xùn)練和測(cè)試代碼,objective函數(shù)返回一個(gè)需要優(yōu)化的metric;
3)把要優(yōu)化的變量定義成optuna的parameter(通過trial.suggest_xxx);
4)copy個(gè)main部分代碼,開始搜超參;
5)睡覺;
6)醒來(lái)用最優(yōu)的參數(shù)在整個(gè)數(shù)據(jù)集上跑跑效果;
在自己的code上試了一下optuna,搜出來(lái)的參數(shù)和我手調(diào)的最佳參數(shù)差不太多,感覺還是靠譜的。祝大家使用愉快!
END
點(diǎn)贊三連,支持一下吧↓
