PyTorch(二)——搭建和自定義網絡


PyTorch(二)——搭建和自定義網絡

 

目錄連接 
(1) 數據處理 
(2) 搭建和自定義網絡 
(3) 使用訓練好的模型測試自己圖片 
(4) 視頻數據的處理 
(5) PyTorch源碼修改之增加ConvLSTM層 
(6) 梯度反向傳遞(BackPropogate)的理解 
(總) PyTorch遇到令人迷人的BUG

PyTorch的學習和使用(二)

最近剛好在看一篇與Siamese network有關的論文,在PyTorch中沒有example,caffe中有,剛好使用PyTorch實現。(PS:圖片單獨打開更清晰) 
主要步驟為:

  • 數據預處理
  • 模型搭建
  • 模型訓練

數據預處理

Siamese的網絡結構如下:

siamese

通過輸入兩張圖片X1和X2,經過權重共享的CNN,各自得到一個輸出特征向量Gw(X1)Gw(X2),然后使用特征向量的距離度量兩張圖片的相似度。

我們還是使用手寫數據集MINIST作為測試(主要有mnist的列子可以參考),網絡輸入的圖片為成對的,因此在構造訓練的batch時需要定義2張圖片,其標簽為兩張圖片是否為同一個數字。根據上篇文章講的,重新定義transforms.Compose()和torchvision.datasets.SIAMESE()。

由於對數據的讀取和處理都沒有改變,因此不用修改Compose()。torchvision.datasets.SIAMESE()則把相鄰的圖片捆綁在一起,作為一對輸入圖片,如果相同的數字,則標簽為1,不同則為0。 
如下所示: 
這里寫圖片描述

如圖所示每個batch中有2個圖片,但是在訓練中我們希望每個相鄰的batch為一對圖片,因此做如下處理:images.view(batch*2, 1, 28, 28)得到如下結果: 
這里寫圖片描述

網絡的搭建

Siamese的網絡定義與LeNet模型基本一致,唯一不同的是把生成10個數字類別概率的頂層替換為生成二維向量的特征層,最后損失函數換為對比損失函數(Contrastive_loss)(很憂傷,該損失函數在PyTorch中暫時沒有實現)。因此需要自己實現Contrastive_loss層。

Contrastive_loss函數定義

首先,理解caffe中Siamese網絡如和實現的。contrastive_loss_layer定義如下:

forward()實現如下:

Siamese

上述代碼中,bottom[0],bottom[1]存的2張圖片的特征,bottom[2],bottom[3]存的2張圖片的標簽,然后根據其是否相同進行如下計算: 

loss=⎧⎩⎨||aibi||22,max{0,m||aibi||22}2,if a == bif a != b

 

 

Loss=12Nloss


在論文Learning a Similarity Metric Discriminatively, with Application to Face Verification中,其定義的損失函數為:
Siamese1 
要求滿足: 
Siamese2

 

則當EW(X1,X2)>0時, EW(X1,X2)>m

backward()如下: 
Siamese3

12loss的導數為 

lossa=⎧⎩⎨(ab),(m||aibi||22)(ab),if a == bif a != b


根據導數計算,實現bankward()。當前導數乘后一層梯度為當前梯度。

 

CosineEmbeddingLoss實現

然后,在PyTorch中實現。在PyTorch中有實現CosineEmbeddingLoss損失函數,其定義為: 

loss(x,y)=⎧⎩⎨1cos(x1,x2),max{0,cos(x1,x2)},if y == 1if y == -1


該函數和我們需要實現的Contrastive_loss損失函數類似,我們先分析CosineEmbeddingLoss函數的實現,任何構造自己的Contrastive_loss損失函數。

 

就如同PyTorch文檔中所講的,如果實現擴展 torch.autograd,需要實現3個方法: 
init (optional), 用於傳遞一些參數,比如margin, 和size_average.。 
- forward(), 前向傳播,就是進行計算。 
- backward(), 反向傳播,就是求導計算梯度。

forward()實現如下:

a'

實現CosineEmbeddingLoss函數主要就是完成cos(a, b)的計算。cos(a,b)=ab|a||b|
代碼主要可以分為4部分,如下圖所示: 
- 第一部分計算a向量和b向量的乘積ab 
- 第二部分計算a向量和b向量模平方分之1,1|a|21|b|2 
- 第三部分計算a向量乘b向量模分之1,1|a||b| 
- 第四部分計算cos(a,b)=ab|a||b|

backward()如下: 
b'

backward()就是實現CosineEmbeddingLoss的導數,主要計算cos(a,b)=ab|a||b|的導數。根據(uv)=uvuvv2得: 

cos(a,b)=(ab|a||b|)=1|b|(ab)|a|(ab)|a||a|2


由於: 

(ab)=[(a1,a2,...,an)b]=b


|a|=[(a21,a22,...,a2n)1/2]=122(a1,a2,...,an)(a21,a22,...,a2n)1/2=a|a|

 

因此,可得: 

1|b|(ab)|a|(ab)|a||a|2=1|b|b|a|(ab)a|a||a|2=ab|a|2ab|a||b|

 

在上圖代碼的說明中: 
- 1表示:ab|a|2 
- 2表示:ab|a|2ab 
- 3表示:ab|a|2ab|a||b| 
最后,當前導數乘后一層梯度為當前梯度。

Contrastive_loss損失函數實現

ps:雖然看了CosineEmbeddingLoss的實現,但是對PyTorch的矩陣計算函數還是不太熟悉,前前后后花了不少時間。

根據上面的公式,Contrastive_loss的代碼實現如下:(輸入為一對圖片input1, input2和標簽y,y==1表示同一物體,y==0表示不同物體)

class ContrastiveLoss(Function):
    def __init__(self, margin=1, size_average=True): super(ContrastiveLoss, self).__init__() self.margin = margin self.size_average = size_average def forward(self, input1, input2, y): assert input1.size() == input2.size(), "Input sizes must be equal." self.l22 = input1.new() #l22 = ||a - b||^2 self._outputs = input1.new() _idx = input1.new().byte() epsilon = 1e-12 #avoid div 0 #l22 = ||a - b||^2 _diff = torch.abs(input1 - input2) self.l22 = torch.pow(_diff + epsilon, 2).sum(dim=1) #_output = l22 self._outputs.resize_as_(self.l22).copy_(self.l22) self._outputs = self._outputs.select(1, 0) #_output = first column and it one column vector torch.eq(y, 0, out=_idx) #y==0 self._outputs[_idx] = self._outputs[_idx].mul_(-1).add_(self.margin).clamp_(min=0) #max{0, m-||a-b||}^2 self._outputs[_idx] = self._outputs[_idx].pow_(2) #_output = 1/2 * _output self._outputs.mul_(1.0 / 2.0) output = self._outputs.sum() #sum if self.size_average: output = output / y.size(0) #mean self.save_for_backward(input1, input2, y) return input1.new((output,)) def backward(self, grad_output): v1, v2, y = self.saved_tensors buffer = v1.new() _idx = v1.new().byte() gw1 = grad_output.new() gw2 = grad_output.new() gw1.resize_as_(v1).copy_(v2) #gw1 = b gw2.resize_as_(v2).copy_(v1) #gw2 = a gw1.mul_(-1).add_(v1) #a' = sum(a - b) gw2.mul_(-1).add_(v2) #b' = sum(b- a) torch.le(self._outputs, 0, out=_idx) #find _output < 0 because loss>0 _idx = _idx.view(-1, 1).expand(gw1.size()) gw1[_idx] = 0 gw2[_idx] = 0 #y==0 torch.eq(y, 0, out=_idx) _idx = _idx.view(-1, 1).expand(gw2.size()) torch.add(self.l22, -self.margin, out=buffer) buffer = buffer.expand(gw1.size()) gw1[_idx] = gw1[_idx].mul(buffer[_idx]) gw2[_idx] = gw2[_idx].mul(buffer[_idx]).mul_(-1) if self.size_average: gw1.div_(y.size(0)) gw2.div_(y.size(0)) grad_output_val = grad_output[0] #current = lastgrad*dervative if grad_output_val != 1: gw1.mul_(grad_output_val) gw2.mul_(grad_output_val) return gw1, gw2, None
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75

使用梯度檢驗:

input = (Variable(torch.randn(20, 2).double(), requires_grad = True), Variable(torch.randn(20, 2).double(), requires_grad = True),) test = gradcheck(ContrastiveLoss(), input, eps=1e-6, atol=1e-4) print test
  • 1
  • 2
  • 3
  • 4

返回的值為True,說明求導的backward沒有問題。(需要注意的是:我們的loss函數需要3個輸入,input1, input2, target. 但是在增加target時會報Kernel died, restarting錯誤,以為是loss代碼寫錯了,使用自帶的CosineEmbeddingLoss進行測試也是同樣的結果,最后去除target得以解決。)(PS:2017.2.15, 官方以修復此bug 
通過以上代碼,可以發現PyTorch的實現要比caffe使用C++實現要方便的多(至少不用指針指來指去),值得注意的有以下幾點:

  1. in-palce 和out-place. 比如有Tensor變量a和b,torch.add() 和torch.add_(), 分別為in-palce 和out-place版本。a.add(b) 輸出 a+b,a和b的值不改變,a.add_(b) 輸出a+b 並且把結果給b。這樣做在進行連續計算時有好處,比如計算a*b+c, 只需要a.mul_(b).add_(c)即可。 
    in

  2. 使用索引時不能進行in-place。 比如有個索引idx,Tensor變量a,當使用a[idx].add_()時不會改變a的值。 
    idx

  3. 少使用=進行賦值,多使用in-place和out=。 因為=進行賦值為淺拷貝,賦值的是地址指針,當其中一個改變時,會影響另一個值。如下所示: 
    out

Contrastive_loss損失層的增加

Contrastive_loss的算法已經實現,需要增加到PyTorch中。PyTorch為動態的實現,因此在改變源碼后不需要重新編譯。

增加一個自定義的層需要完成以下幾步: 
1. 找到PyTorch的包路徑。一般在自己python環境路徑下的torch下。 
2. 在nn._functions.loss.py中增加上面的Contrastive_loss實現代碼。 
1 
3. 在nn.functional.py 中增加Contrastive_loss的包裝。(可選) 
2 
4. 在nn.modules.loss.py中增加Contrastive_loss擴展。 
3

然后在nn.modules.init.py中進行定義,以便可以進行調用。

4

最后在nn.backends.thnn.py中進行backend訪問定義。

5

Siamese網絡訓練結果

至此,數據的處理和網絡的搭建都已近完成了。其訓練結果如下:

train


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM