一文搞定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模型,請看(http://yann.lecun.com/exdb/publis/pdf/lecun-01a.pdf)
有沒有發現問題?
- 最大的問題就是,我們的數據集明明是28*28的,怎麼在論文中變成32*32了?
- 按照論文的標記,C表示卷積,S表示池化,但是在S4到C5應該是卷積,怎麼變成了全連接層了?
是不是同學們都有這個疑問呢?
解答: 原因在於使用了padding技術(具體請參考cs231nhttp://cs231n.stanford.edu/slides/2017/cs231n_2017_lecture5.pdf)
這樣的話,如果使用了28*28,我填充2個0,左邊2個,右邊2個,不就是成了32了嗎?
論文使用的卷積核都是為5*5的,那麼根據上圖的邏輯,下面的層數應該是這樣的:
- 28*28*1輸入,首先padding=2,變成32*32*1
- 6個卷積核 輸出 6@28*28,(28=32-5+1)步長為1
- 池化 輸出 6@14*14,池化步長為2
- 16卷積核 輸出16@10*10
- 池化 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 |