MNIST雖然很簡單,但是值得我們學習的東西還是有很多的。
項目雖然簡單,但是個人建議還是將各個模塊分開創建,特別是對於新人而言,模塊化的創建更加清晰、易懂,我們看下項目都包含哪些部分:
- CNN模塊:卷積神經網絡的組成;
- train模塊:利用CNN模型 對 MNIST數據集 進行訓練並保存模型
- test模塊:加載訓練好的模型對測試集數據進行測試
- cnn.pt : train 的CNN模型
注意!
有GPU的小夥伴儘量使用GPU訓練,GPU的訓練速度比CPU的訓練速度高許多倍,可以節約大量訓練時間
CNN 模塊
MNIST的識別算法有很多,在此提供的是 卷積神經網絡CNN ,其他算法也同樣可以取得很好的識別效果,有興趣的小夥伴可以自己嘗試下。
在此就不得不提 Pytorch的優勢了,都知道 Pytorch 是動態計算模型。但是何爲動態計算模型呢?
- 在此對比 Tensorflow。在流行的神經網絡架構中, Tensorflow 就是最典型的靜態計算架構。使用 Tensorflow 就必須先搭建好這樣一個計算系統, 一旦搭建好了, 就不能改動了 (也有例外), 所有的計算都會在這種圖中流動, 當然很多情況下這樣就夠了, 我們不需要改動什麼結構。
- 不動結構當然可以提高效率. 但是一旦計算流程不是靜態的, 計算圖要變動. 最典型的例子就是 RNN, 有時候 RNN 的 time step 不會一樣, 或者在 training 和 testing 的時候, batch_size 和 time_step 也不一樣, 這時, Tensorflow 就頭疼了。
- 如果用一個動態計算圖的 Pytorch, 我們就好理解多了, 寫起來也簡單多了. PyTorch 支持在運行過程中根據運行參數動態改變應用模型。可以簡單理解爲:一種是先定義後使用,另一種是邊使用邊定義。動態計算圖模式是 PyTorch 的天然優勢之一,Google 2019年 3 月份發佈的 TensorFlow 2.0 Alpha 版本中的 Eager Execution,被認爲是在動態計算圖模式上追趕 PyTorch 的舉措。
如果暫時看不懂的小夥伴,可以先不管,先往後學習,等將來需要的時候再回頭思考這段話。
CNN 模塊分析
CNN 模塊主要分爲兩個部分,一個是定義CNN模塊,另一個是將各個模塊組成前向傳播通道
-
super() 函數 是用於調用父類(超類)的一個方法。
用來解決多重繼承問題的,直接用類名調用父類方法在使用單繼承的時候沒問題,但是如果使用多繼承,會涉及到查找順序(MRO)、重複調用(鑽石繼承)等種種問題。
super(SimpleCNN, self) 首先找到 SimpleCNN 的父類(就是類 nn.Module ),然後把類 SimpleCNN 的對象轉換爲類 nn.Module 的對象 -
nn.Sequential() 是一個有順序的容器,將 神經網絡模塊 按照傳入構造器的順序依次被添加到計算圖中執行。由於每一個神經網絡模塊都繼承於nn.Module,通過索引的方式利用add_module函數將 nn.Sequential()模塊 添加到現有模塊中。
-
forward() 是前向傳播函數,將之前定義好的每層神經網絡模塊串聯起來,同時也定義了模型的輸入參數
# !/usr/bin/env python
# -*- coding:utf-8 -*-
# @Time : 2020.
# @Author : 綠色羽毛
# @Email : [email protected]
# @Blog : https://blog.csdn.net/ViatorSun
# @Note :
from torch import nn
class SimpleCNN(nn.Module):
def __init__(self):
super(SimpleCNN, self).__init__()
self.layer1 = nn.Sequential(
nn.Conv2d(1,16,kernel_size=3) ,
nn.BatchNorm2d(16) ,
nn.ReLU(inplace=True))
self.layer2 = nn.Sequential(
nn.Conv2d(16,32,kernel_size=3) ,
nn.BatchNorm2d(32) ,
nn.ReLU(inplace=True) ,
nn.MaxPool2d(kernel_size=2 , stride=2))
self.layer3 = nn.Sequential(
nn.Conv2d(32,64,kernel_size=3) ,
nn.BatchNorm2d(64) ,
nn.ReLU(inplace=True))
self.layer4 = nn.Sequential(
nn.Conv2d(64,128,kernel_size=3) ,
nn.BatchNorm2d(128) ,
nn.ReLU(inplace=True) ,
nn.MaxPool2d(kernel_size=2 , stride=2))
self.fc = nn.Sequential(nn.Linear(128*4*4,1024) ,
nn.ReLU(inplace=True) ,
nn.Linear(1024,128) ,
nn.ReLU(inplace=True) ,
nn.Linear(128,10) )
def forward( self , x):
x = self.layer1(x)
x = self.layer2(x)
x = self.layer3(x)
x = self.layer4(x)
x = x.view(x.size(0) , -1)
fc_out = self.fc(x)
return fc_out
train 模塊
CNN 模塊分析
# !/usr/bin/env python
# -*- coding:utf-8 -*-
# @Time : 2020.
# @Author : 綠色羽毛
# @Email : [email protected]
# @Blog : https://blog.csdn.net/ViatorSun
# @Note :
import torch
import CNN
from torch import nn , optim
from torchvision import datasets
from torchvision import transforms
from torch.autograd import Variable
from torch.utils.data import DataLoader
# 定義超參數
learning_rate = 1e-2 # 學習率
batch_size = 128 # 批的大小
epoches_num = 20 # 遍歷訓練集的次數
# 下載訓練集 MNIST 手寫數字訓練集
train_dataset = datasets.MNIST( root='./data', train=True, transform=transforms.ToTensor(), download=True )
train_loader = DataLoader( train_dataset, batch_size=batch_size, shuffle=True )
# 定義model 、loss 、optimizer
model = CNN.SimpleCNN()
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD( model.parameters(), lr=learning_rate )
if torch.cuda.is_available():
print("CUDA is enable!")
model = model.cuda()
# 開始訓練
for epoch in range(epoches_num):
print('*' * 40)
running_loss = 0.0
running_acc = 0.0
# 訓練
for i, data in enumerate(train_loader, 1 ):
img, label = data
if torch.cuda.is_available():
img = Variable(img).cuda()
label = Variable(label).cuda()
else:
img = Variable(img)
label = Variable(label)
# 前向傳播
out = model(img)
loss = criterion(out, label)
running_loss += loss.item() * label.size(0)
_ , pred = torch.max(out, 1)
num_correct = (pred == label).sum()
accuracy = (pred == label).float().mean()
running_acc += num_correct.item()
# 反向傳播
optimizer.zero_grad()
loss.backward()
optimizer.step()
print('Finish {} Loss: {:.6f}, Acc: {:.6f}'.format( epoch+1 , running_loss / (len(train_dataset)), running_acc / (len(train_dataset))))
# 保存模型
torch.save(model, 'cnn.pt')
test 模塊
# !/usr/bin/env python
# -*- coding:utf-8 -*-
# @Time : 2020.
# @Author : 綠色羽毛
# @Email : [email protected]
# @Blog : https://blog.csdn.net/ViatorSun
# @Note :
import torch
from torch import nn
from torchvision import datasets
from torchvision import transforms
from torch.autograd import Variable
from torch.utils.data import DataLoader
# 定義超參數
batch_size = 128 # 批的大小
# 下載訓練集 MNIST 手寫數字測試集
test_dataset = datasets.MNIST( root='./data', train=False, transform=transforms.ToTensor())
test_loader = DataLoader(test_dataset , batch_size=batch_size, shuffle=False)
# 加載 Train 模型
model = torch.load('cnn.pt')
criterion = nn.CrossEntropyLoss()
model.eval()
eval_acc = 0
eval_loss = 0
# 測試
for data in test_loader:
img, label = data
if torch.cuda.is_available():
img = Variable(img ).cuda()
label = Variable(label).cuda()
else:
img = Variable(img )
label = Variable(label)
out = model(img)
loss = criterion(out, label)
eval_loss += loss.item() * label.size(0)
_ , pred = torch.max(out,1)
num_correct = (pred==label).sum()
eval_acc += num_correct.item()
print('Test Loss: {:.6f} , Acc: {:.6f}'.format( eval_loss/(len(test_dataset)), eval_acc/(len(test_dataset)) ))