標籤:

一文搞定Pytorch+CNN講解

在折騰過各種神經網路框架之後,我決定入Pytorch坑。

如果你是科研或者學習之用,強烈推薦Pytorch,如果是工業使用,需要大規模部署,請轉Tensorflow。

Pytorch簡單入門

  • Pytorch中最重要的就是Variable模塊,該模塊集成了圍繞一個張量所有的操作,包括前向傳播、反向傳播的各種求偏導數的數值。
  • Pytorch所有的網路在nn包里,我們待會會實現經典的Lenet5模型。
  • Pytorch計算GPU和CPU切換很快,直接使用x.cuda()即可

Lenet5模型的實現:

網上的Lenet5已經爛大街了,為什麼還要講一下呢?原因在於今天我在學習經典的神經網路的時候,發現Lenet5論文中在卷積層之後直接得到120個全連接層,我就一直在考慮120是哪來的?問了很多人,都沒有回答我,問了師兄,師兄直接說看ufldl去。於是自己做實驗,一步一步研究,終於得出了結果120是你隨便設的!如果你和我一樣,開始不知道為啥,看了我這篇文章就懂了,涉及概念比較多,我們先剖析代碼,用到什麼,就解決什麼。


導入各種庫

import torchnfrom torch.autograd import Variablenimport numpy as npnimport torch.nn as nnnfrom torchvision import datasets,transformsn

  • Variable是Pytorch數據格式模塊
  • nn是神經網路模塊
  • torchvision是Pytorch的外圍庫,該庫包含了各種關於圖像的各種功能函數

讀取模型

train_dataset = datasets.MNIST(data/,download=False,train=True,n transform=transforms.Compose([n transforms.ToTensor(),n transforms.Normalize((0.1307,), (0.3081,)),n ]))ntest_dataset = datasets.MNIST(data/,download=False,n transform=transforms.Compose([n transforms.ToTensor(),n transforms.Normalize((0.1307,), (0.3081,)),n ]))n

  • 我們使用MNIST數據集,一定要記得,該數據集的大小為28*28,通道為1(黑白)。
  • transform表示了對數據集進行的操作,包括了轉成張量,以及規則話,說道理,如果不進行的話,也沒有關係,至於(0.1307,), (0.3081,)怎麼來的,我也不知道,抄的官網的。

建立數據集迭代器:

train_loader = torch.utils.data.DataLoader(train_dataset,batch_size=64,shuffle=True)ntest_loader = torch.utils.data.DataLoader(test_dataset,batch_size=64,shuffle=True)n

  • Pytorch中,訓練最好使用迭代器來進行,不然數據集大,內存吃不消,如果你不知道迭代器是什麼(手動再見)
  • 我們使用的batch_size為64,有的人好奇為什麼使用64,或者32,我的理解是這樣的,當我們的數據大小為2的冪次數,計算機會計算的特別快,因為計算機是二進位嘛,如果是2的冪次數的話,計算機很容易通過移位來進行計算。
  • shuffle(打亂)將數據集打亂。

建立神經網路

首先我們要實現Lenet5模型,請看(yann.lecun.com/exdb/pub)

有沒有發現問題?

  • 最大的問題就是,我們的數據集明明是28*28的,怎麼在論文中變成32*32了?
  • 按照論文的標記,C表示卷積,S表示池化,但是在S4到C5應該是卷積,怎麼變成了全連接層了?

是不是同學們都有這個疑問呢?

解答: 原因在於使用了padding技術(具體請參考cs231ncs231n.stanford.edu/sli

這樣的話,如果使用了28*28,我填充2個0,左邊2個,右邊2個,不就是成了32了嗎?

論文使用的卷積核都是為5*5的,那麼根據上圖的邏輯,下面的層數應該是這樣的:

  1. 28*28*1輸入,首先padding=2,變成32*32*1
  2. 6個卷積核 輸出 6@28*28,(28=32-5+1)步長為1
  3. 池化 輸出 6@14*14,池化步長為2
  4. 16卷積核 輸出16@10*10
  5. 池化 16@5*5

關鍵點來了:根據論文的實現,下一層應該是卷積,使用了120個卷積核,也就是120@1*1(1=5-5+1),可以發現,進行卷積以後,變成了全連接層,(非常重要,如果不能理解,卷積神經網路展開成全連接就不懂。)同時,我查看了網友的解答,很多朋友在這裡說可以將其看成全鏈接,也就是在S4的時候,下一步直接展開,為(16*5*5=400個神經元,然後在全連接到120個神經元),經過試驗,這是正確的,我們待會來看如何試驗。

接下來的全連接就簡單了,先是120到84的全連接(這裡是84的解釋):

輸出層由歐式徑向基函數(Euclidean Radial Basis Function)單元組成,每類一個單元,每個有84個輸入。換句話說,每個輸出RBF單元計算輸入向量和參數向量之間的歐式距離。輸入離參數向量越遠,RBF輸出的越大。一個RBF輸出可以被理解為衡量輸入模式和與RBF相關聯類的一個模型的匹配程度的懲罰項。用概率術語來說,RBF輸出可以被理解為F6層配置空間的高斯分布的負log-likelihood。給定一個輸入模式,損失函數應能使得F6的配置與RBF參數向量(即模式的期望分類)足夠接近。這些單元的參數是人工選取並保持固定的(至少初始時候如此)。這些參數向量的成分被設為-1或1。雖然這些參數可以以-1和1等概率的方式任選,或者構成一個糾錯碼,但是被設計成一個相應字元類的7*12大小(即84)的格式化圖片。這種表示對識別單獨的數字不是很有用,但是對識別可列印ASCII集中的字元串很有用。n

然後就是輸出類別84到10的輸出。到這裡,每一層都講解完了。

首先我們來實現論文中的網路結構(S4到C5採用卷積神經網路)

class Net(nn.Module):n def __init__(self):n super(Net,self).__init__()n self.conv1 = nn.Conv2d(1, 6, kernel_size=5,padding=2)n self.conv2 = nn.Conv2d(6, 16, kernel_size=5)n self.conv3 = nn.Conv2d(16,120,kernel_size=5)n self.mp = nn.MaxPool2d(2)n self.relu = nn.ReLU()n self.fc1 = nn.Linear(120,84)n self.fc2 = nn.Linear(84,10)n self.logsoftmax = nn.LogSoftmax()n n def forward(self,x):n in_size = x.size(0)n out = self.relu(self.mp(self.conv1(x)))n out = self.relu(self.mp(self.conv2(out)))n out = self.relu(self.conv3(out))n out = out.view(in_size, -1)n out = self.relu(self.fc1(out))n out = self.fc2(out)n return self.logsoftmax(out)n

  • 首先定義網路結構,如果對pytorch不熟悉的話,請參考Learning PyTorch with Examples
  • 首先定義6個卷積核,padding=2,卷積核大小為5
  • 再次定義16個,大小一樣
  • 再次定義120個,大小一樣(這裡的120可以隨便設,當時卡了好長時間
  • 定義max_pooling,大小為2
  • 定義Relu函數
  • 定義全連接120-84
  • 定義全連接84-10

這樣,跑的效果大概在99.645%

實現另一種網路結構(lenet5另一種解釋S4到C5採用全連接)

  • 在S4層,輸出為16@5*5,那麼全連接輸出的話就是16*5*5=400個神經元,那麼神經網路如下:

class Net(nn.Module):n def __init__(self):n super(Net,self).__init__()n self.conv1 = nn.Conv2d(1, 6, kernel_size=5,padding=2)n self.conv2 = nn.Conv2d(6, 16, kernel_size=5)n self.mp = nn.MaxPool2d(2)n self.relu = nn.ReLU()n self.fc1 = nn.Linear(16*5*5,120) # 必須為16*5*5n self.fc2 = nn.Linear(120,84)n self.fc3 = nn.Linear(84,10)n self.logsoftmax = nn.LogSoftmax()n n def forward(self,x):n in_size = x.size(0)n out = self.relu(self.mp(self.conv1(x)))n out = self.relu(self.mp(self.conv2(out)))n out = out.view(in_size, -1)n out = self.relu(self.fc1(out))n out = self.relu(self.fc2(out))n out = self.fc3(out)n return self.logsoftmax(out)n

這樣跑下來的結果是99.567%

兩者效果是差不多的,也就證明的我前面的觀點:

卷積以後如果是1*1的結果,直接拿來作為全連接網路即可。整個代碼在我的github中,請多多star(HadXu/machine-learning)


推薦閱讀:

pytorch中nn和nn.functional有什麼區別?
關於PyTorch在Windows下的CI和whl包
PyTorch發布一年團隊總結:運行資源降低至十分之一,單機王者
總結近期CNN模型的發展(一)
知乎「看山杯」 奪冠記

TAG:PyTorch |