參考深度學習框架pytorch:入門和實踐一書第六章
以深度學習框架PyTorch一書的學習-第六章-實戰指南為前提
在pytorch中Debug
pytorch作為一個動態圖框架,與ipdb結合能為調試過程帶來便捷
對tensorflow等靜態圖來說,使用python接口定義計算圖,然后使用c++代碼執行底層運算,在定義圖的時候不進行任何計算,而在計算的時候又無法使用pdb進行調試,因為pdb調試只能挑事python代碼,故調試一直是此類靜態圖框架的一個痛點
與tensorflow不同,pytorch可以在執行計算的同時定義計算圖,這些計算定義過程是使用python完成的。雖然底層的計算也是用C/C++完成的但是我們能夠查看python定義部分都變量值,這就足夠了
下面我們將舉例說明:
- 如何在PyTorch中查看神經網絡各個層的輸出
- 如何在PyTorch中分析各個參數的梯度
- 如何動態修改PyTorch的訓練流程
以第六章的貓狗分類為例,里面的train()函數中有一個設置:
# 進入debug模式 if os.path.exists(opt.debug_file): import ipdb; ipdb.set_trace()
即如果設置了這個文件,那么就進入了調試模式
即每次迭代訓練到這里的時候就會進入debug模式
首先先將訓練運行起來:
user@home:/opt/user/dogcat/chapter6$ python main.py train --env=main --train-data-root=./data/train/ --lr=0.005 --batch-size=32 --model='ResNet34' --max-epoch=100 --load-model-path=None --debug-file=./tmp/debug user config: env main vis_port 8097 model ResNet34 train_data_root ./data/train/ test_data_root ./data/test1 load_model_path None batch_size 32 use_gpu True num_workers 4 print_freq 20 debug_file ./tmp/debug result_file result.csv max_epoch 100 lr 0.005 lr_decay 0.5 weight_decay 0.0 WARNING:root:Setting up a new session... WARNING:visdom:Without the incoming socket you cannot receive events from the server or register event handlers to your Visdom client. /home/home/anaconda3/lib/python3.6/site-packages/torchvision/transforms/transforms.py:619: UserWarning: The use of the transforms.RandomSizedCrop transform is deprecated, please use transforms.RandomResizedCrop instead. "please use transforms.RandomResizedCrop instead.")
然后這個時候在本地路徑下創建tmp/debug文件夾:
user@home:/opt/user/dogcat/chapter6/tmp$ mkdir debug
那么就會因為檢測到這個文件夾而進入到調試模式:
39it [00:15, 2.58it/s]> /opt/user/dogcat/chapter6/main.py(81)train() 80 ---> 81 for ii,(data,label) in tqdm(enumerate(train_dataloader)): 82 ipdb>
首先就可以使用l 90命令去查看第90行附近的代碼,即上下五行的代碼:
19it [00:05, 3.92it/s]> /opt/user/dogcat/chapter6/main.py(81)train() 80 ---> 81 for ii,(data,label) in tqdm(enumerate(train_dataloader)): 82 ipdb> l 90 85 target = label.to(opt.device) 86 87 88 optimizer.zero_grad() 89 score = model(input) 90 loss = criterion(score,target) 91 loss.backward() 92 optimizer.step() 93 94 95 # meters update and visualize
然后使用break 89在89行處設置斷點:
ipdb> break 89 Breakpoint 1 at /opt/user/dogcat/chapter6/main.py:89
這個時候打印一下所有參數及其梯度的標准差:
ipdb> model.named_parameters() <generator object Module.named_parameters at 0x7f18b61431a8> ipdb> for (name, p) in model.named_parameters(): print(name, p.data.std(), p.grad.data.std()) pre.0.weight tensor(0.0541, device='cuda:0') tensor(0.0073, device='cuda:0') pre.1.weight tensor(0.2988, device='cuda:0') tensor(0.0037, device='cuda:0') pre.1.bias tensor(0.0238, device='cuda:0') tensor(0.0042, device='cuda:0') layer1.0.left.0.weight tensor(0.0358, device='cuda:0') tensor(0.0003, device='cuda:0') layer1.0.left.1.weight tensor(0.2853, device='cuda:0') tensor(0.0008, device='cuda:0') layer1.0.left.1.bias tensor(0.0272, device='cuda:0') tensor(0.0005, device='cuda:0') layer1.0.left.3.weight tensor(0.0313, device='cuda:0') tensor(9.1792e-05, device='cuda:0') layer1.0.left.4.weight tensor(0.2931, device='cuda:0') tensor(0.0010, device='cuda:0') layer1.0.left.4.bias tensor(0.0233, device='cuda:0') tensor(0.0005, device='cuda:0') layer1.0.right.0.weight tensor(0.0771, device='cuda:0') tensor(0.0011, device='cuda:0') layer1.0.right.1.weight tensor(0.2923, device='cuda:0') tensor(0.0012, device='cuda:0') layer1.0.right.1.bias tensor(0.0233, device='cuda:0') tensor(0.0005, device='cuda:0') layer1.1.left.0.weight tensor(0.0313, device='cuda:0') tensor(0.0002, device='cuda:0') layer1.1.left.1.weight tensor(0.2865, device='cuda:0') tensor(0.0005, device='cuda:0') layer1.1.left.1.bias tensor(0.0267, device='cuda:0') tensor(0.0003, device='cuda:0') layer1.1.left.3.weight tensor(0.0311, device='cuda:0') tensor(7.7873e-05, device='cuda:0') layer1.1.left.4.weight tensor(0.2890, device='cuda:0') tensor(0.0008, device='cuda:0') layer1.1.left.4.bias tensor(0.0260, device='cuda:0') tensor(0.0002, device='cuda:0') layer1.2.left.0.weight tensor(0.0313, device='cuda:0') tensor(0.0001, device='cuda:0') layer1.2.left.1.weight tensor(0.3063, device='cuda:0') tensor(0.0005, device='cuda:0') layer1.2.left.1.bias tensor(0.0272, device='cuda:0') tensor(0.0002, device='cuda:0') layer1.2.left.3.weight tensor(0.0312, device='cuda:0') tensor(6.5418e-05, device='cuda:0') layer1.2.left.4.weight tensor(0.2905, device='cuda:0') tensor(0.0006, device='cuda:0') layer1.2.left.4.bias tensor(0.0249, device='cuda:0') tensor(0.0001, device='cuda:0') layer2.0.left.0.weight tensor(0.0313, device='cuda:0') tensor(0.0001, device='cuda:0') layer2.0.left.1.weight tensor(0.2959, device='cuda:0') tensor(0.0001, device='cuda:0') layer2.0.left.1.bias tensor(0.0254, device='cuda:0') tensor(0.0001, device='cuda:0') layer2.0.left.3.weight tensor(0.0289, device='cuda:0') tensor(1.8391e-05, device='cuda:0') layer2.0.left.4.weight tensor(0.2847, device='cuda:0') tensor(0.0002, device='cuda:0') layer2.0.left.4.bias tensor(0.0261, device='cuda:0') tensor(7.7771e-05, device='cuda:0') layer2.0.right.0.weight tensor(0.0574, device='cuda:0') tensor(0.0002, device='cuda:0') layer2.0.right.1.weight tensor(0.2861, device='cuda:0') tensor(0.0002, device='cuda:0') layer2.0.right.1.bias tensor(0.0261, device='cuda:0') tensor(7.7771e-05, device='cuda:0') ... layer4.2.left.4.weight tensor(0.2819, device='cuda:0') tensor(0.0020, device='cuda:0') layer4.2.left.4.bias tensor(0.0155, device='cuda:0') tensor(0.0041, device='cuda:0') fc.weight tensor(0.0233, device='cuda:0') tensor(0.0978, device='cuda:0') fc.bias tensor(0.0338, device='cuda:0') tensor(0.3471, device='cuda:0')
查看變量——如學習率:
ipdb> opt.lr 0.005
然后修改學習率,同時更改優化器中的學習率,並將參數保存
ipdb> opt.lr = 0.001 ipdb> opt.lr 0.001 ipdb> for p in optimizer.param_groups: p['lr']=opt.lr ipdb> model.save() 'checkpoints/resnet34_0417_14:55:38.pth'
然后c繼續運行,會運行到之前89行的斷點處
ipdb> c 20it [48:31, 872.13s/it]> /opt/user/dogcat/chapter6/main.py(89)train() 88 optimizer.zero_grad() 1--> 89 score = model(input) 90 loss = criterion(score,target)
然后調用s進入model(input)內部,即model.__call__(input)
然后一直向下運行,找到調用內部forward函數的地方:
ipdb> s --Call-- > /home/home/anaconda3/lib/python3.6/site-packages/torch/nn/modules/module.py(483)__call__() 482 --> 483 def __call__(self, *input, **kwargs): 484 for hook in self._forward_pre_hooks.values(): ipdb> n > /home/home/anaconda3/lib/python3.6/site-packages/torch/nn/modules/module.py(484)__call__() 483 def __call__(self, *input, **kwargs): --> 484 for hook in self._forward_pre_hooks.values(): 485 hook(self, input) ipdb> > /home/home/anaconda3/lib/python3.6/site-packages/torch/nn/modules/module.py(486)__call__() 485 hook(self, input) --> 486 if torch._C._get_tracing_state(): 487 result = self._slow_forward(*input, **kwargs) ipdb> > /home/home/anaconda3/lib/python3.6/site-packages/torch/nn/modules/module.py(489)__call__() 488 else: --> 489 result = self.forward(*input, **kwargs) 490 for hook in self._forward_hooks.values():
然后再調用s進入forward函數內部
然后向下運行,運行過第一層pre層后查看它們的輸出的平均值和標准差
ipdb> s --Call-- > /opt/user/dogcat/chapter6/models/resnet34.py(71)forward() 70 ---> 71 def forward(self, x): 72 x = self.pre(x) ipdb> n > /opt/user/dogcat/chapter6/models/resnet34.py(72)forward() 71 def forward(self, x): ---> 72 x = self.pre(x) 73 ipdb> > /opt/user/dogcat/chapter6/models/resnet34.py(74)forward() 73 ---> 74 x = self.layer1(x) 75 x = self.layer2(x) ipdb> x.data.mean(), x.data.std() (tensor(0.2565, device='cuda:0'), tensor(0.3837, device='cuda:0'))
然后使用u跳回上一層,直到model(input)處:
ipdb> u > /home/home/anaconda3/lib/python3.6/site-packages/torch/nn/modules/module.py(489)__call__() 488 else: --> 489 result = self.forward(*input, **kwargs) 490 for hook in self._forward_hooks.values(): ipdb> u > /opt/user/dogcat/chapter6/main.py(89)train() 88 optimizer.zero_grad() 1--> 89 score = model(input) 90 loss = criterion(score,target)
然后清除所有斷點:
ipdb> clear Clear all breaks? y Deleted breakpoint 1 at /opt/user/dogcat/chapter6/main.py:89
然后這就實現了在訓練中間對參數進行更改然后再繼續進行訓練的操作
然后就能夠使用命令c再繼續運行
⚠️當然,這個時候要記得將之前創建的./tmp/debug文件夾刪除,否則下次迭代又會進入debug模式
ipdb> c 30it [1:16:42, 45.41s/it]
總結
1)調試
所以當我們想要進入debug模式,修改程序中的某些參數值或者想要分析程序時,就可以在運行是添加參數:--debug-file=./tmp/debug
然后就能夠在訓練過程中通過創建./tmp/debug文件夾,這樣程序就會進入調試模式
調試完成后就能夠刪除./tmp/debug文件夾並在ipdb調試接口輸入c繼續運行訓練程序
2)退出程序
如果想要退出程序,也可以使用這種方法,先創建./tmp/debug文件夾進入調試模式,然后輸入quit在退出debug的同時退出程序
這種退出程序的方法,與ctrl+c的方法相比更加安全,因為這能夠保證數據加載的多進程程序也能正確地退出,並釋放內存、顯存等資源
pytorch和ipdb結合能夠完成很多其他框架不能完成或很難實現的功能,主要有下面的幾部分:
1)通過debug暫停程序:當程序進入debug模式之后,將不再執行GPU和CPU運算,但是內存和顯存集相應的堆棧空間不會釋放
2)通過debug分析程序,查看每個層的輸出,查看網絡的參數情況:通過u\d\s等命令,能夠進入指定的代碼,通過n可以進行單步執行,從而可以看見每一層的運算結果,便於分析網絡的數值分布等信息
3)作為動態圖框架,pytorch擁有python動態語言解釋執行的優點,我們能夠在運行程序時,通過ipdb修改某些變量的值或屬性,這些修改能夠立即生效。例如可以在訓練開始不久后根據損失函數調整學習率,不必重啟程序
4)如果在IPython中通過%run魔法方法運行程序,那么在程序異常退出時,可以使用%debug命令,直接進入debug模式,通過u和d調到報錯的地方,查看對應的變量。然后找出原因后修改相應的代碼即可。
因為有時模型訓練好幾個小時后,卻在要保存模型之前,因為一個小小的拼寫錯誤異常退出。這時候最好的辦法就是利用%debug進入調試模式,在調試模式中直接運行model.save()保存模型
在ipython中,%pdb魔術方法能夠使得程序出現問題后,不用手動輸入%debug而自動進入調試模式,建議使用
pytorch調用cuDNN報錯時,報錯信息諸如CUDNN_STATUS_BAD_PARAM,從這些報錯信息內容很難得到有用的幫助信息,最好先利用CPU運行代碼,此時一般會得到相對友好的報錯信息。
常見的錯誤有如下幾種:
1)類型不匹配問題:如CrossEntropyLoss的輸入target應該是一個LongTensor,而很多人輸入FloatTensor
2)部分數據忘記從CPU轉到GPU:例如當model存放與GPU時,輸入input耶需要轉移到GPU才能輸入到model中
還有可能是把多個model存放在一個list對象,而在執行model.cuda()時,這個list中的對象是不會被轉移到CUDA上的,正確的用法是使用ModuleList替代
3)Tensor形狀不匹配:此類問題一般是輸入數據形狀不對,或是網絡結構設計有問題,一般通過u命令跳到指定代碼,查看輸入和模型參數的形狀即可得知
4)程序正常運行、沒有報錯,但是模型無法收斂的問題:例如二分類問題,交叉熵損失一直徘徊在0.69附近(ln2),或是數值出現溢出等問題
此時可以進入debug模式,用單步執行看看每一層輸出的均值或方差,觀察從哪一層開始出現數值異常。還要查看每個參數梯度的均值和方差,看看是否出現梯度消失或梯度爆炸的問題。
但是一般在激活函數前增加BatchNorm層、合理的參數初始化、使用Adam優化器,學習率設為0.001,基本上就能確保模型在一定程度上收斂