PyTorch修煉手冊
其實PyTorch這個框架非常簡單,沒有對比就沒有傷害,大多數的語法也非常python。如果你已瞭解numpy的一些語法,那麼上手會比較快;如果你是和我曾經一樣的小白也不要緊,跟住文章來理解他是如何處理數據的,就明白背後創作者的思路了。在下面的文章裏面,我們將從下面的幾點去了解,學習pytorch這個深度學習框架:
- 我們應該怎麼從頭開始搭建一個網絡,我們會用到什麼pytorch的哪些工具,最後我們要怎麼用這個網絡,他的輸入輸出是什麼,我們的數據要怎麼處理;
- 在知道怎麼使用pytorch去搭建一個神經網絡之後,我們還需要注意什麼,有什麼使用的小技巧;
- 最後我們將學習一些高級一點的方法去優化我們網絡;
幾個小問題?
問題一:遇到不會的函數怎麼辦?
英文手冊 傳送門;中文手冊推薦使用 傳送門
問題二:有推薦的書嗎,我想看書?
最近官方出了教程書籍,不看他看誰的? 有下載地址嗎?想白嫖不點贊?行:非洲傳送門(提取碼:zk94 )
問題三:pytorch版本?
不同版本的pytorch裏面的有些功能可能會不太一樣,文章中的代碼適用於pytorch0.4.1以上的版本。
PyTorch常見小問題
- 通道問題:不同的視覺庫對於圖像讀取的方式不一樣,圖像的通道也不一樣,這是一個很基礎視覺問題。像是opencv的默認imread就是
H x W x C
,Pytorch的Tensor爲C X H X W
,TensorFlow的兩種都支持的。 - pytorch的數據形式:我們都知道高維的數據我們就把它稱之爲張量,在TensorFlow和pytorch裏面都是稱爲tensor;pytorch0.4.1版本的tensor有三個屬性:
tensor
數據本身,device
數據所在的設備(GPU或者是CPU),grad_fn
指向Function對象,用於反向傳播的梯度計算之用,下面取出一個在訓練中的張量tensor([-0.1621, 0.2753, -0.8891, ..., 0.8509, 0.3875, 0.0798],device='cuda:0', grad_fn=<GatherBackward>)
,device='cuda:0'
這裏表示的是數據是在第一張顯卡當中的,如果數據被存放在內存中的話則是cpu。 - tensor的轉換:pytorch中的數據形式是tensor,如果像是numpy,pandas,python的list中的數據要放到神經網絡中使用的話,需要將其先做轉換,比如numpy數據轉tensor:
b = torch.from_numpy(a)
,這在tensorFlow中也是一樣的,具體的指令就得再去百度下了。
理解PyTorch的框架(基於Train的分析)
對於當時懵懂的我來說,圖像的輸入在哪?該如何將數據和網絡結合,來訓練一個自己的網絡?
在pytorch中就只需要分三步,1.寫好網絡,2.編寫數據的標籤和路徑索引,3.把數據送到網絡。
1.1 PyTorch模型—網絡架構
- 神經元零件進口: 通過繼承
class Net_name(nn.Module):
這個類,就能獲取pytorch庫中的零件啦;(點我看更多:使用Module類來自定義模型 , 使用Module類來自定義模型) - 神經元零件預處理: 像普通地寫一個類一樣,先要在
__init__(self)
中先初始化需要的“零件"(如 conv、pooling、Linear、BatchNorm等層)並讓他們繼承Net_name
這個父類 ;其中可以通過torch.nn.Sequetial
就是一個可以按照順序一層層地封裝初始化層的容器(使用Sequential類來自定義順序連接模型);下一步就可以在forward(self, x):
中用定義好的“組件”進行組裝; - 神經元零件組裝: 通過編寫
def forward(self, x):
這個函數,x 爲模型的輸入(就是你處理好的圖像的入口啦),選取上一步中你處理好的神經元零件,在函數中按照你想構建的模型來拼湊,最終return出結果,而輸入也會按照forward函數中的順序通過神經網絡,實現正向傳播,最後輸出結果。那麼到此爲止,你的模型就搭建完成啦!(PyTorch之前向傳播函數forward)
下面用AlexNet來感受一下:
import torch
import torch.nn as nn
class AlexNet(nn.Module): #定義網絡,推薦使用Sequential,結構清晰
def __init__(self):
super(AlexNet,self).__init__()
self.conv1 = torch.nn.Sequential( #input_size = 227*227*3
torch.nn.Conv2d(in_channels=3,out_channels=96,kernel_size=11,stride=4,padding=0),
torch.nn.ReLU(),
torch.nn.MaxPool2d(kernel_size=3, stride=2) #output_size = 27*27*96
)
self.conv2 = torch.nn.Sequential( #input_size = 27*27*96
torch.nn.Conv2d(96, 256, 5, 1, 2),
torch.nn.ReLU(),
torch.nn.MaxPool2d(3, 2) #output_size = 13*13*256
)
self.conv3 = torch.nn.Sequential( #input_size = 13*13*256
torch.nn.Conv2d(256, 384, 3, 1, 1),
torch.nn.ReLU(), #output_size = 13*13*384
)
self.conv4 = torch.nn.Sequential( #input_size = 13*13*384
torch.nn.Conv2d(384, 384, 3, 1, 1),
torch.nn.ReLU(), #output_size = 13*13*384
)
self.conv5 = torch.nn.Sequential( #input_size = 13*13*384
torch.nn.Conv2d(384, 256, 3, 1, 1),
torch.nn.ReLU(),
torch.nn.MaxPool2d(3, 2) #output_size = 6*6*256
)
#網絡前向傳播過程
self.dense = torch.nn.Sequential(
torch.nn.Linear(9216, 4096),
torch.nn.ReLU(),
torch.nn.Dropout(0.5),
torch.nn.Linear(4096, 4096),
torch.nn.ReLU(),
torch.nn.Dropout(0.5),
torch.nn.Linear(4096, 50)
)
def forward(self, x): #正向傳播過程
conv1_out = self.conv1(x)
conv2_out = self.conv2(conv1_out)
conv3_out = self.conv3(conv2_out)
conv4_out = self.conv4(conv3_out)
conv5_out = self.conv5(conv4_out)
res = conv5_out.view(conv5_out.size(0), -1)
out = self.dense(res)
return out
1.2 PyTorch模型—網絡權值初始化
每個神經元零件一開始一般都會有初始化的參數的,當你的網絡很深的時候,這些看似無關痛癢的初始參數就會對你的迭代過程以及最終結果有很大的影響。那麼,這時候你就需要重新對這些網絡層進行統一的規劃安排,權值初始化方法就出來啦。pytorch中也提供了像是Xavier和MSRA方法來供你初始化,這裏說下一般的初始化方法。
- 基礎步驟:先設定什麼層用什麼初始化方法,實例化一個模型之後,執行該函數,即可完成初始化;
- 按需定義初始化方法,例如:
if isinstance(m, nn.Conv2d):
n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
m.weight.data.normal_(0, math.sqrt(2. / n))
- 初始化之後,在forward中就可以像積木一樣,搭建自己整個神經網絡。
2.1 PyTorch數據索引處理—Dataset 類: (主要包括數據的標籤和路徑索引)
pytorch數據的讀取等預處理,我們都會寫在class dataset_name(dataset.Dataset):
這個類裏面來構造自定義數據流容器。其中我們一樣可以使用python的魔法方法來實現構造、初始化以及屬性訪問等等功能。常見的:
__init__
我們很熟悉了,它在對象初始化的時候調用,我們一般將它理解爲"構造函數";_getitem_
魔法方法,接收一個 index,然後返回圖片數據和標籤,這個index 通常指的是一個 list 的 index,這個 list 的每個元素就包含了圖片數據的路徑和標籤信息;- 還有
__len__:
等等的一些構造自定義容器的方法都能使用; - dataset模版框架:
class RAMDataset(Dataset):
def __init__(image_fnames, targets):
self.targets = targets
self.images = []
for fname in tqdm(image_fnames, desc="Loading files in RAM"):
with open(fname, "rb") as f:
self.images.append(f.read())
def __len__(self):
return len(self.targets)
def __getitem__(self, index):
target = self.targets[index]
image, retval = cv2.imdecode(self.images[index], cv2.IMREAD_COLOR)
return image, target
3.1 PyTorch數據迭代處理—DataLoder:
接收來自Dataset 類的數據的標籤和路徑索引後,並不是直接把數據扔進去我們的神經網絡中的,需要我們把數據和索引先送進一個訓練時迭代用的容器:
- 將 train_data 傳入,從而使 DataLoder 擁有圖片的路徑,繼而初始化DataLoder。
- 在一個 iteration 進行時,對DataLoder進行迭代、採樣,此時DataLoder就會返回來自dataset 中
def __getitem__(self, index):
的值,像是上面模版中的就是返回一組image, target ,當然你自己來選擇返回對象。常見的模版:for batch, (inputs, labels) in enumerate(self.train_loader):
- class DataLoader()中再調用 class _DataLoderIter() ,獲取一個 batch 的索引(來自Dataset 類 “getitem”)送到網絡中開始處理,向前傳播,向後傳播。
# Demo of dataloader
train_loader = dataloader.DataLoader(RAMDataset, \
sampler=RandomSampler(self.trainset,args.batchid,batch_image=args.batchimage),\
batch_size=batchsize,\
num_workers=nThread)
# Demo of sampler
from torch.utils.data import sampler
class RandomSampler(sampler.Sampler):
def __init__(self, data_source, batchsize):
super(RandomSampler, self).__init__(data_source)
self.data_source = data_source
self.batchsize = batchsize
def __iter__(self):
imgs = []
for img in self.data_source:
imgs.extend(img)
return iter(imgs)
def __len__(self):
return self.batchsize
4. PyTorch訓練的時候迭代一次,需要怎麼做:
optimizer = optim.SGD(model.parameters(), lr = 0.01, momentum = 0.9)
scheduler = lr_scheduler.StepLR(optimizer, step_size = 100, gamma = 0.1)
model.train() # 訓練的時候需要設置.train(),推理的時候需要設置.eval()
for i in range(epoch):
for batch, (inputs, labels) in enumerate(train_loader):
inputs = inputs.to('cuda')
labels = labels.to('cuda')
optimizer.zero_grad() # 清理優化器中的梯度
outputs = model(inputs) # 輸入並向前傳播
loss = loss(outputs, labels)
loss.backward() # 向後傳播
optimizer.step() # 更新模型的梯度
scheduler.step() # 更新模型的學習率
PyTorch代碼小手冊
torch
torch的寫法和numpy是相近的,但是要注意的是部分寫法在反向傳遞的時候會有問題,自己寫的時候就會發現啦,多寫點~
torch.sum(input, dim, out=None)
→ Tensor #與python中的sum一樣
input (Tensor) – 輸入張量
dim (int) – 縮減的維度,開始的時候不理解爲什麼英文文檔裏面會說這是縮減的維度,對於高維度的數組如(QxWxExR),對dim=1進行sum,那麼其得到的維度就是QxExR,保留最高維度Q的形狀,對W維度對應最小的元素進行求和,所以W維度就會消失,其他的函數的維度處理也是這樣理解。
out (Tensor, optional) – 結果張量
print(x.sum(0))
#對一維的數求和,按列求和
print(x.sum(1))
#對二維求和按行求和
print(x.sum(2))
#將最小單位的數組元素相加即可new_features = super(_DenseLayer, self).forward(x)
最後在官方論壇上得到結果,含義是將調用所有add_module方法添加到sequence的模塊的forward函數。torch.where(condition, x, y) → Tensor
對於x而言,如果其中的每個元素都滿足condition,就返回x的值;如果不滿足condition,就將y對應位置的元素或者y的值torch.narrow(input, dimension, start, length)
張量剪裁permute
把張量變換成不同維度,view
相當於reshape
,將元素按照行的順序放置在新的不同大小的張量當中torch.cat(tensors, dim=0, out=None) → Tensor
將張量按照維度進行銜接torch.gesv(B, A, out=None) -> (Tensor, Tensor)
是解線性方程AX=B後得到的解- (1)
torch.unsqueeze(input, dim, out=None) → Tensor
在指定位置增加一個一維的維度
dim (int) – the index at which to insert the singleton dimension
(2)torch.squeeze(input, dim, out=None) → Tensor
在指定位置減去一個一維的維度,默認()就是把所有shape中爲1的維度去掉 detach()
就是取出一個該個tensor,並且它不會再參與梯度下降
torch.nn
-
nn.Sequential
一個有序的容器,神經網絡模塊將按照在傳入構造器的順序依次被添加到計算圖中執行,同時以神經網絡模塊爲元素的有序字典也可以作爲傳入參數。 -
torch.nn.functional.grid_sample(input, grid, mode='bilinear', padding_mode='zeros')
將其歸一化到一個(-1,1)的二維平面上,outpuy_{x,y} 的像素值與input_{x0,y0} 的像素值一致, 矩陣索引通過grid矩陣保存。 grid_{x,y}=(x0,y0)
參考網站 https://blog.csdn.net/houdong1992/article/details/88122682
英文手冊原文 https://pytorch.org/docs/stable/nn.html?highlight=grid_sample#torch.nn.functional.grid_sample
PyTorch_Tricks
在這裏將介紹一些在實際編程中比較常用的小技巧。
- 指定GPU:
os.environ["CUDA_VISIBLE_DEVICES"] = "0,1"
這個命令是GPU使用的命令,一般放在程序的開頭,根據順序表示優先使用0號設備,然後使用1號設備; - 想看網絡的具體輸出:這裏會用到pytorch中的一個庫
from torchsummary import summary
,然後寫好輸入(input_size 是根據你自己的網絡模型的輸入尺寸進行設置)summary(your_model, input_size=(channels, H, W))
; - 在test的時候需要關閉求導功能:我沒在推理網絡的時候,我們不需要計算梯度,這時候就需要使用
with torch.no_grad():
,後續就是寫你用model測試的圖片; - 當你的forward函數輸出多個結果 :假如你forward函數最終return出來的結果是兩個張量,那麼他們會被放到一個tuple裏面,
(tensor1, tensor2)
,並且每一個張量仍舊是存在顯存當中的;若你想對結果進行單獨的結果測試,需要進行如下的從顯存中取出的操作tensor1.detch().cup()
,這時候會把你的結果放到內存當中,不影響後續張量的求導;
PyTorch_Tools
- 使用torchsummary實現網絡的可視化
import torch, torchvision
from torchsummary import summary
model = torchvision.models.vgg16()
summary(model, (3, 224, 224))
Pytorch_advanced—Adjustment
終究逃不過調參,畢竟是進階學習嘛。其實瞭解了pytorch的整個框架的思路之後,看代碼基本上是沒有問題的了,但是說到調參的話,自己又是一臉懵逼了,邊學邊做記錄就完事了,莽鴨!
辣麼,調參是怎麼開始,要從框架的那一部分的代碼開始呢?(pytorch version is 0.4.1)
1.1 權重參數的調整
- 加載模型
微調微調,不先加載模型怎麼微調呢,第一步就是先學下如何花式加載模型:
model_dict = model.state_dict();
pre_model_dict_feat = {k:v for k,v in pre_model_dict.items() if k in model_dict};
# update the entries #
model_dict.update( pre_model_dict_feat)
# load the new state dict #
model.load_state_dict( pre_model_dict_feat )
- 不同層設置不同學習率的方法
和參考的帖子的有所不同,我的版本是0.4.1,這個版本和以往最大的不同就是變量和張量合併在一起了,可以對tensor直接操作 ,這裏直接將每個參數的計算梯度的開關關上就可以了(官網說明鏈接)
for param in model.parameters():
param.requires_grad = False
- 加載權重參數和查看神經網絡層
pytorch編寫網絡的方法是很直觀很舒服的,每一層的網絡都是class torch.nn.Module的一個子類,利用Module.children()方法返回所有直接子模塊的一個iterator,官網原文Returns an iterator over immediate children modules。(children()與modules()都是返回網絡模型裏的組成元素,但是children()返回的是最外層的元素,modules()返回的是所有的元素,包括不同級別的子元素,children()不能返回Sequential中的Sequential。)
#這裏在小小說明一下,這一步是建立在上面你一步步加載了模型之後的操作哦
#在單步調試的時候不能這麼做的哦,別問我怎麼知道的
#*** AttributeError: 'Tensor' object has no attribute 'modules'
#ipdb> self.lastconv.modules() ---> <generator object Module.modules at 0x7f46a04b1390>
list(nn.Sequential(nn.Linear(10, 20), nn.ReLU()).modules())
list(nn.Sequential(nn.Linear(10, 20), nn.ReLU()).children())
- 對特定層進行finetune
有兩種不同的方法來得到想處理的層,第一種是全部打印出來,在選好自己想凍結的層,第二種就是先使用Module.children()方法查看網絡的直接子模塊;之後就是將不需要調整的模塊中的參數設置爲param.requires_grad = False,同時用一個list收集需要調整的模塊中的參數。具體代碼爲:
Plan_A
1.先獲取所處理的層
net = Network() # 獲取自定義網絡結構
for name, value in net.named_parameters():
print('name: {0},\t grad: {1}'.format(name, value.requires_grad))
2.寫一個凍結層的列表
no_grad = [
'cnn.VGG_16.convolution1_1.weight',
'cnn.VGG_16.convolution1_1.bias',
'cnn.VGG_16.convolution1_2.weight',
'cnn.VGG_16.convolution1_2.bias'
]
3.凍他
net = Net.CTPN() # 獲取網絡結構
for name, value in net.named_parameters():
if name in no_grad:
value.requires_grad = False
else:
value.requires_grad = True
4.優化器的調整
optimizer = optim.Adam(filter(lambda p: p.requires_grad, net.parameters()), lr=0.01)
Plan_B
count = 0
para_optim = []#裝參數的盒子
for k in model.children():
count += 1
if count > 6:
for param in k.parameters():
para_optim.append(param)#需要調整的參數
else:
for param in k.parameters():
param.requires_grad = False
optimizer = optim.RMSprop(para_optim, lr)
參考帖子:傳送門1
1.2 網絡調整
網絡權重的參數調整訓練之後,那我想在這個基礎上只是利用部分網絡的參數,後面接個新東西,我要怎麼辦呢,劍來!
- 簡單的利用pytorch中的pre-train模型,比如vgg,resnet啊就不多bb了
- 部分層的參數調整
這個可以認真學一手,這裏用了分類來舉例,resnet網絡最後一層分類層fc是對1000種類型進行劃分,對於自己的數據集,如果只有9類,修改的代碼如下:
import torchvision.models as models
#調用模型
model = models.resnet50(pretrained=True)
#提取fc層中固定的參數
fc_features = model.fc.in_features
#修改類別爲9
model.fc = nn.Linear(fc_features, 9)
- 增減卷積層
基本的思路和第一點中利用pytorch中的pre-train模型是一樣的,先擬一個和原本的網絡一樣的新網絡,再在這個網絡的基礎上增加新的網絡,具體還是上代碼:
import torchvision.models as models
import torch
import torch.nn as nn
import math
import torch.utils.model_zoo as model_zoo
class CNN(nn.Module):
def __init__(self, block, layers, num_classes=9):
#網絡初始化
def _make_layer(self, block, planes, blocks, stride=1):
#自定義層
def forward(self, x):
#再新加層的forward
return x
#加載model
resnet50 = models.resnet50(pretrained=True)
cnn = CNN(Bottleneck, [3, 4, 6, 3])#輸入__init__的參數
#讀取參數
pretrained_dict = resnet50.state_dict()
model_dict = cnn.state_dict()
# 將pretrained_dict裏不屬於model_dict的鍵剔除掉
pretrained_dict = {k: v for k, v in pretrained_dict.items() if k in model_dict}
# 更新現有的model_dict
model_dict.update(pretrained_dict)
# 加載我們真正需要的state_dict
cnn.load_state_dict(model_dict)
參考帖子:傳送門2
2.1梯度剪裁
梯度裁剪一般用於解決 梯度爆炸(gradient explosion) 問題,而梯度爆炸問題在訓練 RNN 過程中出現得尤爲頻繁,所以訓練 RNN 基本都需要帶上這個參數(在RNN中,相同的權重參數 W 會在各個時間步複用,最終 W 的梯度 g = g1 + g2 + … + gT,即各個時間步的梯度之和,即梯度往往不會消失,而在不加入梯度剪裁的情況下往往會存在梯度爆炸的;而反過來說消失問題可以通過 gradient scaling 來解決,沒人這麼做的原因是這麼做太麻煩了,而且我們現在已經有了更好的方案(自適應學習率、LSTM/GRU)。)
import torch.nn as nn
outputs = model(data)
loss= loss_fn(outputs, target)
optimizer.zero_grad()
loss.backward()
nn.utils.clip_grad_norm_(model.parameters(), max_norm=20, norm_type=2)
optimizer.step()
2.2 when you write a test
- 防止驗證模型時爆顯存
with torch.no_grad():
# 使用model進行預測的代碼
pass
PyTorch_thinking
記錄一下在使用時候的一些反思
- model.train()和model.eval()的思考:
model.eval()作用是爲了固定BN和dropout層(兩者都是防止過擬合的手段),使得偏置參數不隨着發生變化。因爲當batchsize小時,如果沒有固定,會對圖像的失真有很大的影響,因爲在一定範圍內,一般來說 Batch_Size 越大,其確定的下降方向越準,引起訓練震盪越小,就越不容易過擬合,反之BS越小就月難擬合(dropout是拋棄一部分節點的操作,bn是白話,增加偏置的)。
(知乎中的一個回答:會不會可能是BN在模型中位置的問題?我用 Dropout 的時候也出現過這個問題,就是 train mode 下得到的結果要比 eval mode 下要好很多。後來發現是因爲我的 Dropout 放在了卷積層,將 Dropout 放到 FC 層後就正常了。作爲常用的防止過擬合的手段,Dropout 和 BN 都能起到比較好的結果,但不是在任意位置都奏效。我瞭解到的是,Dropout 通常在 FC 層有效,而在卷積層就不推薦用了。至於 BN,至少我用在卷積層是 ok 的。或者你可以在 BN 操作那行設置一個斷點,debug 到那行的時候分別測試在同樣輸入下 train mode 和 eval mode 的輸出,然後分析比較二者的差別,或許能給解決你的問題帶來靈感。)
model.train() :啓用 BatchNormalization 和 Dropout(需要對backbone的網絡,比如ResNet50之類的batchnorm層設置爲eval mode)
PyTorch_train_log
2019.7.15(pytorch版本0.4.1)
cuda is out of memory
在訓練一段時間後,再次運行程序,發生‘cuda is out of memory’,我的電腦12g的顯存,不可能不夠吧,我在終端輸入nvidia-smi打開顯卡管理界面,果然python3的進程佔用了6g左右的顯存,我搜索瞭如何手動釋放顯存的指令:
sudo kill -9 PID
PID爲對應的進程ID,在終端輸入後文件解決。
回想爲什麼會發生這個錯誤,大概是因爲我在測試的時候記錄了比較大的數組,建議後續代碼在最後加入torch.cuda.empty_cache()
清除沒有用的顯存佔用。
—— 時間分割線 2019.8.13(pytorch版本0.4.1)——
RuntimeError: Subtraction, the -
operator, with a bool tensor is not support
關於這個error,其實是在重新配置環境後,使用比較高版本的pytorch所導致的,原本自己的代碼使用的是pytorch=0.4.1,cuda=9.0,結果現在使用pytorch=1.0.0、1.1.0、 1.2.0以及cuda=10後就出現了這個錯誤,網上的解釋也比較少,唯一的答案就是pytorch版本不對,但是事情沒有這麼簡單。
由於管理員安裝的是cuda=10,pytorch=0.4.1不支持這個版本的cuda,我這樣需要換可以嗎,答案是可以的。
解答: 使用conda的安裝命令會自動匹配你需要的包,使用在使用conda install pytorch=0.4.1
cuda100,cudatoolkit=9.0,cudnn=7.6.0 會自動安裝上,不需要大環境的cuda支持。
—— 時間分割線 2.19.8.27(pytorch版本0.4.1)——
RuntimeError: Expected object of type torch.cuda.FloatTensor but found type torch.FloatTensor for ar
解答:很明顯嘛,就是要用cuda的數據,在相應的變量後面加上 .cuda()就好了
但是要是你想print出來的話也可以print(var.device)
就知道你的變量是cpu還是gpu了
—— 時間分割線 2.19.9.16(pytorch版本0.4.1)——
RuntimeError: one of the variables needed for gradient computation has been modified by an inplace
解決我遇到的原因是第二種,將out+=residual這樣所有的+=操作,改成out=out+residual,這種操作其實是張量自己實現累加的時候,在反向傳遞時loss.backward()
會不知道算哪個。
#第一種錯誤寫法:正如我上面講的一樣,這是一種變相的顯存修改方式,這樣在反向傳播中會出問題
for i in range(ndepth):
BV_predict[0, i, ...] = BV_predict[0, i, ...] * self.d_candi[i]
第二種錯誤寫法:新建的torch list 本質上並沒有新建一個新的內存分配,是在原來的基礎上進行的淺拷貝
BV_predict_ = []
for i in range(ndepth):
BV_predict_[0, i, ...] = BV_predict[0, i, ...] * self.d_candi[i]
正確的寫法:新建一種新的張量,在顯存中你的目標張量就不會被覆蓋,在求梯度的時候就可以反向傳播了
BV_predict_ = torch.zeros_like(BV_predict)
for i in range(ndepth):
BV_predict_[0, i, ...] = BV_predict[0, i, ...] * self.d_candi[i]