用pytorch踩過的坑

2021-02-21 人工智慧前沿講習

作者:知乎號—土豆

地址:https://www.zhihu.com/people/FesianXu

pytorch的交叉熵nn.CrossEntropyLoss在訓練階段,裡面是內置了softmax操作的,因此只需要餵入原始的數據結果即可,不需要在之前再添加softmax層。這個和tensorflow的tf.softmax_cross_entropy_with_logits如出一轍.[1][2]pytorch的交叉熵nn.CrossEntropyLoss在訓練階段,裡面是內置了softmax操作的,因此只需要餵入原始的數據結果即可,不需要在之前再添加softmax層。這個和tensorflow的tf.softmax_cross_entropy_with_logits如出一轍.[1][2]

pytorch中的MSELoss和KLDivLoss

在深度學習中,MSELoss均方差損失和KLDivLossKL散度是經常使用的兩種損失,在pytorch中,也有這兩個函數,如:

loss = nn.MSELoss()input = torch.randn(3, 5, requires_grad=True)target = torch.randn(3, 5)output = loss(input, target)output.backward()

這個時候我們要注意到,我們的標籤target是需要一個不能被訓練的,也就是requires_grad=False的值,否則將會報錯,出現如:

AssertionError: nn criterions don’t compute the gradient w.r.t. targets - please mark these variables as volatile or not requiring gradients

我們注意到,其實不只是MSELoss,其他很多loss,比如交叉熵,KL散度等,其target都需要是一個不能被訓練的值的,這個和TensorFlow中的tf.nn.softmax_cross_entropy_with_logits_v2不太一樣,後者可以使用可訓練的target,具體見[3]
一般來說,我們在進行模型訓練的過程中,因為要監控模型的性能,在跑完若干個epoch訓練之後,需要進行一次在驗證集[4]上的性能驗證。一般來說,在驗證或者是測試階段,因為只是需要跑個前向傳播(forward)就足夠了,因此不需要保存變量的梯度。保存梯度是需要額外顯存或者內存進行保存的,佔用了空間,有時候還會在驗證階段導致OOM(Out Of Memory)錯誤,因此我們在驗證和測試階段,最好顯式地取消掉模型變量的梯度。 在pytroch 0.4及其以後的版本中,用torch.no_grad()這個上下文管理器就可以了,例子如下:

model.train()# here train the model, just skip the codesmodel.eval() # here we start to evaluate the modelwith torch.no_grad(): for each in eval_data: data, label = each logit = model(data) ... # here we just skip the codes

如上,我們只需要在加上上下文管理器就可以很方便的取消掉梯度。這個功能在pytorch以前的版本中,通過設置volatile=True生效,不過現在這個用法已經被拋棄了。

顯式指定model.train()和model.eval()

我們的模型中經常會有一些子模型,其在訓練時候和測試時候的參數是不同的,比如dropout[6]中的丟棄率和Batch Normalization[5]中的和等,這個時候我們就需要顯式地指定不同的階段(訓練或者測試),在pytorch中我們通過model.train()和model.eval()進行顯式指定,具體如:
model = CNNNet(params)# here we start the trainingmodel.train()for each in train_data: data, label = each logit = model(data) loss = criterion(logit, label) ... # just skip# here we start the evaluation
model.eval() with torch.no_grad(): # we dont need grad in eval phase for each in eval_data: data, label = each logit = model(data) loss = criterion(logit, label) ... # just skip

注意,在模型中有BN層或者dropout層時,在訓練階段和測試階段必須顯式指定train()和eval()。
在對一個損失進行反向傳播時,在pytorch中調用out.backward()即可實現,給個小例子如:
import torchimport torch.nn as nnimport numpy as npclass net(nn.Module):    def __init__(self):        super().__init__()        self.fc1 = nn.Linear(10,2)        self.act = nn.ReLU()    def forward(self,inputv):        return self.act(self.fc1(inputv))n = net()opt = torch.optim.Adam(n.parameters(),lr=3e-4)inputv = torch.tensor(np.random.normal(size=(4,10))).float()output = n(inputv)target = torch.tensor(np.ones((4,2))).float()loss = nn.functional.mse_loss(output, target)loss.backward() # here we calculate the gradient w.r.t the leaf

對loss進行反向傳播就可以求得,即是損失對於每個葉子節點的梯度。我們注意到,在.backward()這個API的文檔中,有幾個參數,如:
backward(gradient=None, retain_graph=None, create_graph=False)

這裡我們關注的是retain_graph這個參數,這個參數如果為False或者None則在反向傳播完後,就釋放掉構建出來的graph,如果為True則不對graph進行釋放[7][8]。我們這裡就有個問題,我們既然已經計算忘了梯度了,為什麼還要保存graph呢?直接釋放掉等待下一個迭代不就好了嗎,不釋放掉不會白白浪費內存嗎?我們這裡根據[7]中的討論,簡要介紹下為什麼在某些情況下需要保留graph。如下圖所示,我們用代碼構造出此graph:
import torchfrom torch.autograd import Variablea = Variable(torch.rand(1, 4), requires_grad=True)b = a**2c = b*2d = c.mean()e = c.sum()

問題是在執行完反向傳播之後,因為沒有顯式地要求它保留graph,系統對graph內存進行釋放,如果下一步需要對節點e進行求梯度,那麼將會因為沒有這個graph而報錯。因此有例子:
d.backward(retain_graph=True) # finee.backward(retain_graph=True) # fined.backward() # also finee.backward() # error will occur!

利用這個性質在某些場景是有作用的,比如在對抗生成網絡GAN中需要先對某個模塊比如生成器進行訓練,後對判別器進行訓練,這個時候整個網絡就會存在兩個以上的loss,例子如:
G_loss = ...D_loss = ...
opt.zero_grad() # 對所有梯度清0D_loss.backward(retain_graph=True) # 保存graph結構,後續還要用opt.step() # 更新梯度,只更新D的,因為只有D的不為0
opt.zero_grad() # 對所有梯度清0G_loss.backward(retain_graph=False) # 不保存graph結構了,可以釋放graph,# 下一個迭代中通過forward還可以build出來的opt.step() # 更新梯度,只更新G的,因為只有G的不為0

這個時候就可以對網絡中多個loss進行分步的訓練了。

進行梯度累積,實現內存緊張情況下的大batch_size訓練

在上面討論的retain_graph參數中,還可以用於累積梯度,在GPU顯存緊張的情況下使用可以等價於用更大的batch_size進行訓練。首先我們要明白,當調用.backward()時,其實是對損失到各個節點的梯度進行計算,計算結果將會保存在各個節點上,如果不用opt.zero_grad()對其進行清0,那麼只要你一直調用.backward()梯度就會一直累積,相當於是在大的batch_size下進行的訓練。我們給出幾個例子闡述我們的觀點。
import torchimport torch.nn as nnimport numpy as npclass net(nn.Module):    def __init__(self):        super().__init__()        self.fc1 = nn.Linear(10,2)        self.act = nn.ReLU()    def forward(self,inputv):        return self.act(self.fc1(inputv))n = net()inputv = torch.tensor(np.random.normal(size=(4,10))).float()output = n(inputv)target = torch.tensor(np.ones((4,2))).float()loss = nn.functional.mse_loss(output, target)loss.backward(retain_graph=True)opt = torch.optim.Adam(n.parameters(),lr=0.01)for each in n.parameters():    print(each.grad)

tensor([[ 0.0493, -0.0581, -0.0451,  0.0485,  0.1147,  0.1413, -0.0712, -0.1459,          0.1090, -0.0896],        [ 0.0000,  0.0000,  0.0000,  0.0000,  0.0000,  0.0000,  0.0000,  0.0000,          0.0000,  0.0000]])tensor([-0.1192,  0.0000])

在運行一次loss.backward(retain_graph=True),輸出為:
tensor([[ 0.0987, -0.1163, -0.0902,  0.0969,  0.2295,  0.2825, -0.1424, -0.2917,          0.2180, -0.1792],        [ 0.0000,  0.0000,  0.0000,  0.0000,  0.0000,  0.0000,  0.0000,  0.0000,          0.0000,  0.0000]])tensor([-0.2383,  0.0000])

tensor([[ 0.1480, -0.1744, -0.1353,  0.1454,  0.3442,  0.4238, -0.2136, -0.4376,          0.3271, -0.2688],        [ 0.0000,  0.0000,  0.0000,  0.0000,  0.0000,  0.0000,  0.0000,  0.0000,          0.0000,  0.0000]])tensor([-0.3575,  0.0000])

tensor([[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],        [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]])tensor([0., 0.])

現在明白為什麼我們一般在求梯度時要用opt.zero_grad()了吧,那是為什麼不要這次的梯度結果被上一次給影響,但是在某些情況下這個『影響』是可以利用的。
這個在利用torch.nn.functional.dropout的時候,其參數為:
torch.nn.functional.dropout(input, p=0.5, training=True, inplace=False)

注意這裡有個training指明了是否是在訓練階段,是否需要對神經元輸出進行隨機丟棄,這個是需要自行指定的,即便是用了model.train()或者model.eval()都是如此,這個和torch.nn.dropout不同,因為後者是一個層(Layer),而前者只是一個函數,不能紀錄狀態[9]。
torch.index_select()是一個用於索引給定張量中某一個維度中元素的方法,其API手冊如:
torch.index_select(input, dim, index, out=None) → TensorParameters:  input (Tensor) – 輸入張量,需要被索引的張量 dim (int) – 在某個維度被索引 index (LongTensor) – 一維張量,用於提供索引信息 out (Tensor, optional) – 輸出張量,可以不填

其作用很簡單,比如我現在的輸入張量為1000 * 10的尺寸大小,其中1000為樣本數量,10為特徵數目,如果我現在需要指定的某些樣本,比如第1-100,300-400等等樣本,我可以用一個index進行索引,然後應用torch.index_select()就可以索引了,例子如:
>>> x = torch.randn(3, 4)>>> xtensor([[ 0.1427,  0.0231, -0.5414, -1.0009],        [-0.4664,  0.2647, -0.1228, -1.1068],        [-1.1734, -0.6571,  0.7230, -0.6004]])>>> indices = torch.tensor([0, 2])>>> torch.index_select(x, 0, indices) # 按行索引tensor([[ 0.1427,  0.0231, -0.5414, -1.0009],        [-1.1734, -0.6571,  0.7230, -0.6004]])>>> torch.index_select(x, 1, indices) # 按列索引tensor([[ 0.1427, -0.5414],        [-0.4664, -0.1228],        [-1.1734,  0.7230]])

然而有一個問題是,pytorch似乎在使用GPU的情況下,不檢查index是否會越界,因此如果你的index越界了,但是報錯的地方可能不在使用index_select()的地方,而是在後續的代碼中,這個似乎就需要留意下你的index了。同時,index是一個LongTensor,這個也是要留意的。
在trainning狀態下,BN層的統計參數running_mean和running_var是在調用forward()後就更新的,這個和一般的參數不同,容易造成疑惑,考慮到篇幅較長,請移步到[11]。
我們經常需要對圖像進行插值,而pytorch的確也是提供對以tensor形式表示的圖像進行插值的功能,那就是函數torch.nn.functional.interpolate[12],但是我們注意到這個插值函數有點特別,它是對以batch為單位的圖像進行插值的,如果你想要用以下的代碼去插值:
image = torch.rand(3,112,112) # H = 112, W = 112, C = 3的圖像image = torch.nn.functional.interpolate(image, size=(224,224))

那麼這樣就會報錯,因為此處的size只接受一個整數,其對W這個維度進行縮放,這裡,interpolate會認為3是batch_size,因此如果需要對圖像的H和W進行插值,那麼我們應該如下操作:
image = torch.rand(3,112,112) # H = 112, W = 112, C = 3的圖像image = image.unsqueeze(0) # shape become (1,3,112,112)image = torch.nn.functional.interpolate(image, size=(224,224))


[1]. Why does CrossEntropyLoss include the softmax function?https://discuss.pytorch.org/t/why-does-crossentropyloss-include-the-softmax-function/4420

[2]. Do I need to use softmax before nn.CrossEntropyLoss()?https://discuss.pytorch.org/t/do-i-need-to-use-softmax-before-nn-crossentropyloss/16739/2

[3]. tf.nn.softmax_cross_entropy_with_logits 將在未來棄用,https://blog.csdn.net/LoseInVain/article/details/80932605

[4]. 訓練集,測試集,檢驗集的區別與交叉檢驗,https://blog.csdn.net/LoseInVain/article/details/78108955

[5]. Ioffe S, Szegedy C. Batch normalization: Accelerating deep network training by reducing internal covariate shift[J]. arXiv preprint arXiv:1502.03167, 2015.

[6]. Hinton G E, Srivastava N, Krizhevsky A, et al. Improving neural networks by preventing co-adaptation of feature detectors[J]. arXiv preprint arXiv:1207.0580, 2012. 

[7]. What does the parameter retain_graph mean in the Variable's backward() method?https://stackoverflow.com/questions/46774641/what-does-the-parameter-retain-graph-mean-in-the-variables-backward-method

[8]. https://pytorch.org/docs/stable/autograd.html?highlight=backward#torch.Tensor.backward

[9] https://pytorch.org/docs/stable/nn.html?highlight=dropout#torch.nn.functional.dropout

[10]. https://github.com/pytorch/pytorch/issues/571

[11]. Pytorch的BatchNorm層使用中容易出現的問題,https://blog.csdn.net/LoseInVain/article/details/86476010

[12]. https://pytorch.org/docs/stable/nn.functional.html#torch.nn.functional.interpolate

中國人工智慧大會

相關焦點

  • 那些用pytorch踩過的坑
    pytorch的交叉熵nn.CrossEntropyLoss在訓練階段,裡面是內置了softmax操作的,因此只需要餵入原始的數據結果即可,不需要在之前再添加softmax層。這個功能在pytorch以前的版本中,通過設置volatile=True生效,不過現在這個用法已經被拋棄了。
  • 模型部署翻車記:PyTorch轉onnx踩坑實錄
    在深度學習模型部署時,從pytorch轉換onnx的過程中,踩了一些坑。本文總結了這些踩坑記錄,希望可以幫助其他人。首先,簡單說明一下pytorch轉onnx的意義。在pytorch訓練出一個深度學習模型後,需要在TensorRT或者openvino部署,這時需要先把Pytorch模型轉換到onnx模型之後再做其它轉換。因此,在使用pytorch訓練深度學習模型完成後,在TensorRT或者openvino或者opencv和onnxruntime部署時,pytorch模型轉onnx這一步是必不可少的。
  • 經驗 | 在C++平臺上部署PyTorch模型流程+踩坑實錄
    的模型部署到c++平臺上,基本過程主要參照官網的教學示例,期間發現了不少坑,特此記錄。).to(embeds.device)解決:需要轉gpu的地方顯示調用.cuda()總之一句話:除了原生python和pytorch以外的庫,比如numpy什麼的能不用就不用,儘量用pytorch的各種API。
  • 踩坑|NVIDIA驅動安裝
    昨天重裝完環境,又發現了一個新的問題:GPU用不了了,只有CPU在苦逼地跑。
  • Prometheus使用總結:我踩過的那些坑
    我在工作中也比較深入地使用過 Prometheus,最大的感受就是它非常容易維護,突出一個簡單省心成本低。當然,這當中也免不了踩過一些坑,下面就總結一下。假如你沒有用過 Prometheus,建議先看一遍官方文檔:https://prometheus.io/docs/introduction/overview/。
  • 數據挖掘:聊聊那些年你我踩過的「坑」
    今天就來談一談數據挖掘中常常被我們忽略的小問題(踩過的坑)。讓我們從下面這張圖開始吧! 圖一 從現實世界到模型世界(圖片來自網絡) 咳咳注意,本篇不是八卦文,在這裡我們要正經地討論一些小case。如圖所示,我們以左圖代表現實世界,右圖代表模型世界——對,數據挖掘的世界。
  • 一文詳解pytorch的「動態圖」與「自動微分」技術
    用過caffe或者tensorflow的同學應該知道,在訓練之前需要構建一個神經網絡,caffe裡面使用配置文件prototxt來進行描述,tensorflow中使用python代碼來描述。訓練之前,框架都會有一個解析和構建神經網絡的過程。構建完了之後再進行數據讀取和訓練。在訓練過程中網絡一般是不會變的,所以叫做「靜態圖」。
  • 用PyTorch部署模型
    使用Docker安裝官方文檔:https://github.com/pytorch/serve/blob/master/README.md##install-torchserve安裝torchserve最好的方法是使用docker。你只需要把鏡像拉下來。
  • 電腦風扇的誤區,這些坑你都踩過嗎?
    很多小夥伴在裝機的時候都盯著CPU、顯卡、內存等與「性能」直接相關的硬體,卻很容易忽略了電腦主機是一個系統工程這個事實,其實裝一個沒有短板的「水桶機」,性能是一方面,能否讓主機發揮全部或者大部分性能的周邊配置是另一方面,比方說機箱的散熱風扇吧,這個常被玩家忽略的硬體其實「坑」
  • 踩坑日誌 | kingfisher 公共測序數據 SRA/Fastq 下載神器!
    幫大夥踩坑測試,與大夥分享。- CJ - 陳程傑寫在前面    一般在進行公共測序數據挖掘的時候,需要從公共資料庫中(SRA、ENA、DDBJ等)下載自己所需的測序數據。ena-ftp,調用curl從ENA中下載.fastq.gz數據prefetch,調用prefetch從NCBI SRA資料庫中下載SRA數據,然後默認使用fasterq-dump對其進行拆分轉換aws-http,調用aria2c從AWS Open Data Program中下載SRA數據,然後默認使用fasterq-dump對其進行拆分轉換也就是說,如果是用的
  • virtualbox centos7 nat+host-only方式聯網踩坑總結
    設置前先把虛擬機關機,筆者在這裡踩坑了很久。3.2 設置Host-only網絡設置前先把虛擬機關機,筆者在這裡踩坑了很久。在【管理】--【全局設定】--【網絡】中,應該能看到【僅主機(Host-Only)網絡】的選項卡。
  • pytorch如何將訓練提速?
    向AI轉型的程式設計師都關注了這個號👇👇👇機器學習AI算法工程   公眾號:datayx入門pytorch,如learning rate,不過它對性能的影響似乎更重要【佔坑】少用循環,多用向量化操作經典操作儘量用別人優化好的庫,別自己寫(想自己實現鍛鍊能力除外)數據很多時少用append,雖然使用很方便,不過它每次都會重新分配空間?
  • PyG的使用及踩坑
    (可以看我的https://zhuanlan.zhihu.com/p/430446184先有個大致的認識)1.1 圖數據的處理PyG用torch_geometric.data.Data保存圖結構的數據,導入的data(這個data指的是你導入的具體數據,不是前面那個torch_geometric.data)在PyG中會包含以下屬性data.x:圖節點的屬性信息
  • PyTorch使用總覽
    PyTorch是Facebook的官方深度學習框架之一,開源以來,勢頭非常猛,相信使用過的人都會被其輕便和快速等特點深深吸引,因此本文從整體上介紹如何使用PyTorch。 PyTorch的官方github地址:https://github.com/pytorch/pytorch PyTorch官方文檔:http://pytorch.org/docs/0.3.0/接下來就按照上述的3個方面來介紹如何使用PyTorch。
  • 【學術福利】Pytorch的tensorboard食譜幫你可視化誤差結果
    t-SNE全部可以用tensorboad實現。開源庫tensorboardX地址如下:https://github.com/lanpa/tensorboardX可以把大部分的功能接入pytorch,不過有些例子不詳細,我自己記錄一下。
  • 輕鬆學pytorch之使用onnx runtime實現pytorch模型部署
    ONNX(Open Neural Network Exchange)是一種標準與開放的網絡模型交換格式,直白點說就是tensorflow/pytorch
  • 聊聊Pytorch中的dataloader
    今天為啥突然要寫一下pytorch的dataloader呢,首先來說說事情的來龍去脈。還有一個問題是關於num_workers的設置,因為我有對比過,在我的單機RTX 2080Ti上和八卡伺服器TITAN RTX上(僅使用單卡,其它卡有在跑其它任務),使用相同的num_workers,在單機上的訓練速度反而更快,於是猜想可能和CPU或者內存有關係,下面會具體分析。
  • PyTorch與深度學習 | PyTorch的安裝與環境配置(Anaconda)
    在正式學習pytorch之前我們先來學習安裝pytorch以及其環境配置,pytorch是一個python優先的深度學習框架,我們也可以把它看作是python
  • 講述|踩到地雷,你經歷過嗎?
    挖汙水處理池時,我們曾掘地兩米深,發現依舊是沙子,而且是乾燥的,一點水分都沒有,「一腳踩、半腳沙」是生活的真實寫照。這麼厚的沙子,對官兵日常活動和部隊執行任務非常不利。每次車輛和隊伍走過,都是沙土飛揚。載重物時,卡車還會陷在沙窩裡。如果處置突發事件時發生陷車事故,情況就會很尷尬。
  • PyTorch 0.4.0 安裝命令大全
    小編今天已經在 Windows 10 x64 上的 Anaconda3 5.1.0 上裝了 PyTorch 0.4.0 CPU 版,感覺很好用!