天天看點

PyTorch resnet18實作MNIST手寫數字識别

PyTorch resnet18實作MNIST手寫數字識别

Warning: 這是一個學習筆記及分享向的文章, 對于初學者可能不太友好

最近突然出現了一個疑問, 用了ResNet等主流主幹網絡這麼久, 也知道每個網絡最基本的原理, 比如ResNet是利用H(x)=F(x)+x恒等變換讓網絡易于訓練, 在有downsample的層将x進行變換比對F(x)之後的size, 但是具體是怎麼實作的呢?抱着這個疑問, 在重新閱讀了ResNet這篇論文之後, 把官方源碼研究了一遍。花了大概一個星期的時間, 終于把源碼中和ResNet這篇論文相關的部分都了解透徹了。

不得不說源碼的作者是真的牛逼, 看完感覺自己寫代碼的能力又增加了(增加了, 但沒有完全增加, 隻增加了一點點)

最後寫完了resnet18等幾個函數後, 想找點資料來練練手。第一時間我想到了MNIST, 在印象中MNIST的圖檔是 28* 28* 1 的, 但是resnet18接收的是3通道的RGB圖檔。于是乎, 我寫了以下這段代碼:

model = resnet18(num_classes=10)

img = torch.Tensor(1, 3, 28, 28)
output = model(img)
           

然後就得到了以下的輸出:

(輸出部分是我在自己重寫的代碼裡面加入了print(), 源碼中沒有輸出每層的output.size())

>>> inputs: torch.Size([1, 3, 28, 28])
>>> conv1 -> maxpool: torch.Size([1, 64, 7, 7])
>>> layer1: torch.Size([1, 64, 7, 7])
>>> layer2: torch.Size([1, 128, 4, 4])
>>> layer3: torch.Size([1, 256, 2, 2])

>>> ValueError                                Traceback (most recent call last)
......
>>> ValueError: Expected more than 1 value per channel when training, got input size torch.Size([1, 512, 1, 1])
           

顯然 layer3 輸出的[1, 256, 2, 2]的feature map太小導緻後續的操作出錯, 原因則是ResNet類中處理輸入圖檔的部分

# 部分源碼
self.conv1 = nn.Conv2d(3, self.inplanes, kernel_size=7, stride=2, padding=3, bias=False)
self.bn1   = norm_layer(self.inplanes)
self.relu  = nn.ReLU(inplace=True)
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
           

從輸出也可以看到, (1, 3, 28, 28)的inputs經過了 conv1 -> bn1 -> relu -> maxpool 之後輸出為(1, 64, 7, 7)

那麼就要修改處理輸入圖檔的部分, 于是我就又去百度了, 然而看到的大部分文章都是重新寫了一個網絡。

能不能不重新寫呢?

思考了一會兒, 我把網絡進行如下修改:

model = resnet18(num_classes=10)
model.conv1 = nn.Conv2d(1, model.inplanes, kernel_size=3, stride=1, padding=1, bias=False)
img = torch.Tensor(1, 3, 28, 28)
output = model(img)
           

OK, 再次報錯

inputs: torch.Size([1, 1, 28, 28])

RuntimeError Traceback (most recent call last)

RuntimeError: running_mean should contain 512 elements not 64

哦, model.inplanes最後變成了512, 但是最開始應該是64, 再次修改

model = resnet18(num_classes=10)
model.conv1 = nn.Conv2d(1, 64, kernel_size=3, stride=1, padding=1, bias=False)
img = torch.Tensor(1, 3, 28, 28)
output = model(img)
           
>>> inputs: torch.Size([1, 1, 28, 28])
>>> conv1 -> maxpool: torch.Size([1, 64, 14, 14])
>>> layer1: torch.Size([1, 64, 14, 14])
>>> layer2: torch.Size([1, 128, 7, 7])
>>> layer3: torch.Size([1, 256, 4, 4])
>>> layer4: torch.Size([1, 512, 2, 2])
>>> avgpool: torch.Size([1, 512, 1, 1])
>>> flatten: torch.Size([1, 512])
>>> fc: torch.Size([1, 10])
>>> torch.Size([1, 10])
           

後面的資料準備和訓練部分都大同小異, 就不再贅述了。

batch_size為64, 附上90輪次的loss

>>> [1, 10] loss: 0.01002
>>> [1, 20] loss: 0.00601
>>> [1, 30] loss: 0.00381
>>> [1, 40] loss: 0.00274
>>> [1, 50] loss: 0.00216
>>> [1, 60] loss: 0.00206
>>> [1, 70] loss: 0.00161
>>> [1, 80] loss: 0.00136
>>> [1, 90] loss: 0.00144
           

繼續閱讀