用PyTorch構建卷積神經網路

在pytorch中神經網路的構建是通過torch.nn工具

上一章介紹了autograd,而nn正是基於autograd來定義模型並求取其中的各種梯度。

  • nn.Module定義了網路的每一層
  • forward(input) 則用來計算網路的輸出

舉一個例子,來看這樣一個用來識別手寫體數字的網路(圖片來自官方文檔):

一個典型的訓練神經網路的步驟是:

  • 定義一個包含一組待學習的參數的神經網路
  • 將數據輸入到神經網路中並進行前向傳播
  • 根據損失函數計算輸出結果與目標值之間的差距
  • 進行梯度反向傳播到各個參數
  • 更新網路參數,典型的更新方式是:weight=weight-learning_rate*gradinet

Define the network

import torchfrom torch.autograd import Variableimport torch.nn as nnimport torch.nn.functional as Fclass Net(nn.Module): def __init__(self): #使用super()方法調用基類的構造器,即nn.Module.__init__(self) super(Net,self).__init__() # 1 input image channel ,6 output channels,5x5 square convolution kernel self.conv1=nn.Conv2d(1,6,5) # 6 input channl,16 output channels,5x5 square convolution kernel self.conv2=nn.Conv2d(6,16,5) # an affine operation:y=Wx+b self.fc1=nn.Linear(16*5*5,120) self.fc2=nn.Linear(120,84) self.fc3=nn.Linear(84,10) def forward(self,x): # x是網路的輸入,然後將x前向傳播,最後得到輸出 # 下面兩句定義了兩個2x2的池化層 x=F.max_pool2d(F.relu(self.conv1(x)),(2,2)) # if the size is square you can only specify a single number x=F.max_pool2d(F.relu(self.conv2(x)),2) x=x.view(-1,self.num_flat_features(x)) x=F.relu(self.fc1(x)) x=F.relu(self.fc2(x)) x=self.fc3(x) return x def num_flat_features(self,x): size=x.size()[1:] # all dimensions except the batch dimension num_features=1 for s in size: num_features*=s return num_featuresnet=Net()print(net)

以上是定義了一個網路,並且定義了值的前向傳播過程。在前向傳播的過程中,可以進行各種Tensor可以進行的運算。而在梯度反向傳播的時候,要用到上章介紹的autograd

卷積運算

這是卷積核矩陣和輸入矩陣中的感受野矩陣之間做內積,也就是兩個向量之間的內積,如兩個n維向量a和b的內積為:

acdot b=sum^n_{i=1}a_ib_i=a_1b_1+a_2b_2+cdots+a_nb_n

那麼torch.nn.Conv2d()是怎麼運算的呢,先來看它有哪些參數(把這些參數的作用都搞清楚,就基本弄清了卷積層的具體工作方式):

  • in_channels (int) – Number of channels in the input image
  • out_channels (int) – Number of channels produced by the convolution
  • kernel_size (int or tuple) – Size of the convolving kernel
  • stride (int or tuple, optional) – Stride of the convolution. Default: 1
  • padding (int or tuple, optional) – Zero-padding added to both sides of the input. Default: 0
  • dilation (int or tuple, optional) – Spacing between kernel elements. Default: 1
  • groups (int, optional) – Number of blocked connections from input channels to output channels. Default: 1
  • bias (bool, optional) – If True, adds a learnable bias to the output. Default: True

有關torch.nn的詳細文檔請看這裡

網路的輸入用四元組 (N,C_{in},H,W) 來表示,輸出表示為 (N,C_{out},H_{out},W_{out}) 。其中:

  • HW 分別表示矩陣的高和寬
  • N 表示數據的batch dimension,就是批處理數據一批有多少個
  • C_{in} 表示輸入數據的通道數in_channels,按上圖中可以理解為輸入該層的有幾個矩陣
  • C_{out} 表示輸出數據的通道數out_channels,可理解為該層輸出的有多少個矩陣

卷積層的輸出滿足這樣的表達式:

out(N_i,C_{out_j})=bias(C_{out_j})+sum_{k=0}^{C_{in}-1}weight(C_{out_j},k)star input(N_i,k)

現在只看其中一個數據,即 N_i 為一個常數,代表當前數據是batch中的第幾個。以上面定義的網路的第一個卷積層為例:

藍色方框中的紅點稱為A元素。對這一層上面表達式的各個數值為:

  • N_i ——假設為一批數據中的第一個,即值為1
  • C_{out_j} ——輸出有6個通道, C_0,ldots,C_5 ,對A點 out_j=0
  • C_{in} ——輸入通道,只有一個
  • k ——是計數器,對應著輸入數據的每一個通道,這裡只有一個
  • bias ——是偏置參數,維度和 C_{out} 一致

卷積核或者說過濾器 weight 是一個 1	imes6	imes5	imes5 的多維向量, k 確定了其第一維, C_{out_j} 確定了其第二維。對於輸入向量 inputk 確定了其第一維。於是求和符號里就是兩個向量的內積。當 C_{in} 大於1時,來看第二個卷積層:

這時 C_{in} 有6個取值,求和符號中, k=0,...,5 ,以右側紅點元素為例:當 k=0 時是綠色5x5的向量和左邊第一個紅色5x5的向量做內積,然後 k=1 是綠色5x5的向量和第二個紅色5x5的向量做內積,做6次之後,把這六個內積的結果加和,再加上和第一個輸出通道對應的 bias ,就得到了右側紅點元素的值。

然後左側紅色方框即感受野再按步長移動。輸出向量的尺寸是這樣的(floor代表向下取整):

H_{out}=floor((H_{in}+2*padding[0]-dilation[0]*(kernel\_size[0]-1)-1)/stride[0]+1)

W_{out}=floor((W_{in}+2*padding[1]-dilation[1]*(kernel\_size[1]-1)-1)/stride[1]+1)

可以看到上面兩個式子涉及到了torch.nn.Conv2d()中的四個參數,kernel_size和stride都好理解,分別是卷積核尺寸和移動步長。

padding(零填充):

在網路的早期層中,我們想要儘可能多地保留原始輸入內容的信息,這樣我們就能提取出那些低層的特徵。比如說我們想要應用同樣的卷積層,但又想讓輸出量維持為 32 x 32 x 3 。為做到這點,我們可以對這個層應用大小為 2 的零填充(zero padding)。零填充在輸入內容的邊界周圍補充零。

關於padding圖片來自這裡,這是一個很好的講解卷積神經網路的文章,還有文字來自對這篇文章翻譯的中文版。

dilation:對於這個參數,鏈接中的最後一個圖是關於dilation的。

來自這個問題下賈揚清的回答,有幾個說明卷積運算過程的圖(其中KxK代表卷積核的size,其他符號和上文所用的一致):

對於卷積神經網路每一層究竟做了什麼,提取出什麼特徵,這裡有個將神經網路可視化的視頻

參數個數

對於卷積層來說,參數個數就是 weight 向量和 bias 向量的元素個數和,比如對第二個卷積層來說,就是 6 	imes 16 	imes 5	imes 5+16

可以通過:

net.parameters()

來獲得網路的參數。

網路的輸入與輸出

下面來給網路一個輸入:

input = Variable(torch.randn(1, 1, 32, 32))out = net(input)print(out)

在前面寫卷積運算的時候也提到過,實際上網路的輸入向量,第一個維度 N 是批處理數據一批的個數。在torch.nn方法中必須要有這一維的數值,即輸入的向量必須是 (nSamples	imes nChannels 	imes Height 	imes Width) 這樣的四維向量。而如果數據沒這第一個維度,也就是並不是一批數據,那麼可以通過:

torch.Tensor.unsqueeze(0)

在不改變數據所含數值的情況,增加一個維度。

損失函數

如何定義損失函數也很簡單的:

output = net(input)target = Variable(torch.arange(1, 11)) # a dummy target, for examplecriterion = nn.MSELoss() # mean-squared error between the input and the targetloss = criterion(output, target)print(loss)

torch.nn中還有很多種損失函數可以使用,見這裡

梯度的反向傳播

這就要用到上一章autograd.Variable()的.backward()功能。在學習autograd.Variable時也發現,如果Variable.grad已經儲存有梯度值,那麼當再次寫出.backward()命令進行梯度回傳時,計算出的值將與各個創建變數(graph leaves)的.grad中已有的值累加。所以對於神經網路,要使用net.zero_grad()清除掉原有的梯度值。

net.zero_grad() # zeroes the gradient buffers of all parametersprint(conv1.bias.grad before backward)print(net.conv1.bias.grad)loss.backward()print(conv1.bias.grad after backward)print(net.conv1.bias.grad)

如果原來已有梯度值,然後執行net.zero_grad(),梯度值就變成0,如果原來就沒有梯度值,那麼變數x處的梯度x.grad=None

參數更新

使用torch.optim可以方便的定義參數更新演算法:

import torch.optim as optim# create your optimizer,such as SGDoptimizer=optim.SGD(net.parameters(),lr=0.01) # lr is learning rate# in your training loop:optimizer.zero_grad() # zero the gradient buffersoutput=net(input)loss=criterion(output,target)loss.backward()optimizer.step() # Does the update

推薦閱讀:

DenseNet論文翻譯及pytorch實現解析(下)
pytorch例子-強化學習(DQN)
pytorch學習體會(三)NLP詞袋
2017 年 8 月 6 日發布的 pytorch 0.2.0 哪個特性最吸引你?
PyTorch源碼淺析(目錄)

TAG:PyTorch | 卷積神經網路CNN |