天天看點

pytorch實戰1:手把手教你基于pytorch實作LeNet5

pytorch實戰1:手把手教你基于pytorch實作LeNet5

前言

​ 最近在看經典的卷積網絡架構,打算自己嘗試複現一下,在此系列文章中,會參考很多文章,有些已經忘記了出處,是以就不貼連結了,希望大家了解。

​ 完整的代碼在最後。

本系列必須的基礎

​ python基礎知識、CNN原理知識、pytorch基礎知識

本系列的目的

​ 一是幫助自己鞏固知識點;

​ 二是自己實作一次,可以發現很多之前的不足;

​ 三是希望可以給大家一個參考。

目錄結構

文章目錄

    • pytorch實戰1:手把手教你基于pytorch實作LeNet5
      • 1. 資料集介紹與下載下傳:
        • 1.1 介紹:
        • 1.2 下載下傳:
      • 2. LeNet5模型建立:
        • 2.1 架構介紹:
        • 2.2 模型類建立:
      • 3. 模型訓練和評估:
        • 3.1 資料加載:
        • 3.2 模型執行個體化和放入GPU中:
        • 3.3 定義損失函數、優化器:
        • 3.4 訓練:
        • 3.5 評估:
        • 3.6 探究:
      • 4. 總結:

1. 資料集介紹與下載下傳:

1.1 介紹:

​ LeNet5是1998年提出的,主要用來當時的手寫數字識别,是以使用的資料集是MNIST資料集。

​ MNIST是一個經典的手寫數字資料,也是一個公開的小型資料。 MNIST中的圖像每個都是28*28=784的大小,并且為灰階圖,值為0-255。

1.2 下載下傳:

​ 資料集可以通過官網進行下載下傳

http://yann.lecun.com/exdb/mnist/

,(建議)也可以通過

pytorch代碼擷取

# 導包
from torchvision.datasets import MNIST
import torchvision.transforms as transforms
# 下載下傳資料集或者加載資料集
train_dataset = MNIST(root='../data/',train=True,transform=transforms.ToTensor(),download=True)
test_dataset = MNIST(root='../data/',train=False,transform=transforms.ToTensor())
           

​ 注意,上面下載下傳代碼中root參數指明了下載下傳後儲存的位址,需要根據自己的檔案夾路徑進行修改。

2. LeNet5模型建立:

2.1 架構介紹:

​ LeNet5模型架構如下圖所示:

pytorch實戰1:手把手教你基于pytorch實作LeNet5

​ 其中,值得注意的地方是:

  • 輸入大小為32*32,而資料集的圖像大小為28*28,是以需要做出處理,方法是為原圖像進行填充,即padding=2(這樣大小為:28+2+2=32)。
  • 上圖中全連接配接層寫的不是特别清晰,比如FC1層120個神經元是指的是輸出的神經元個數,輸入神經元個數應該是5*5*16。

2.2 模型類建立:

​ 下面我們來建立這個模型類,首先根據pytorch建立模型的基本結構,寫出:

# 建立模型
class LeNet(nn.Module):
    def __init__(self):
        super(LeNet,self).__init__()
        # 定義模型
        pass

    def forward(self,x):
		pass
           

​ 接着,我們來定義我們的模型,這裡我們采取的思路是:先定義前面的卷積、池化層,再定義全連接配接層。

​ 好的,先定義卷積層(看注釋):

self.features = nn.Sequential(
    # C1層,輸入通道數為1是因為為灰階圖,不是彩色圖。其餘的就是根據架構填寫的參數,除去padding=2,
    # padding=2是為了讓28*28的圖檔變為32*32
    nn.Conv2d(in_channels=1,out_channels=6,kernel_size=(5,5),stride=1,padding=2),
    nn.ReLU(),
    # Pool 1 層
    nn.MaxPool2d(kernel_size=2,stride=2),
    # C2層,輸入通道數是上一層的輸出通道數,其餘同上
    nn.Conv2d(in_channels=6,out_channels=16,kernel_size=(5,5),stride=1),
    nn.ReLU(),
    # Pool 2 層
    nn.MaxPool2d(kernel_size=2,stride=2),
)
           

​ 這裡,我把本來使用的sigmoid函數改為了relu函數,大家可以寫的時候可以改回來。

​ 然後,定義全連接配接層(這裡注意,我自己寫的時候開始還有點懵,後來才了解):

self.classifier = nn.Sequential(
    # FC1層,輸入為5*5*16=400,輸出數為指定的參數值
    nn.Linear(in_features=400, out_features=120),
    nn.ReLU(),
    # FC2 層
    nn.Linear(in_features=120, out_features=84),
    nn.ReLU(),
    # FC3 層
    nn.Linear(in_features=84, out_features=10)
)
           

​ 最開始寫的時候,沒有想到還有400這個值,是以很糾結,後來才想起來:卷積層到全連接配接層的時候,需要把多元的資料拉平,是以顯然此時的輸入為卷積層輸出的各個次元相乘,即6*6*25。

​ 最後,來定義前向算法,這個非常簡單,隻是注意要多一步把資料拉平(變為1維)的操作:

def forward(self,x):
    # 定義前向算法
    x = self.features(x)
    x = torch.flatten(x,1)
    result = self.classifier(x)
    return result
           

​ 這裡,也許你會問:**為什麼flatten參數要寫一個1?這是因為,我們這裡會采取批量訓練,是以傳入的資料是一個類似于[batch,32,32,5]的思維資料,其中batch指的是每批的個數,後面分别書圖像大小(32-32)和卷積核個數。是以,我們拉平的時候,需要從第二位開始拉平,使之變為[batch,32*32*5]**的形式,是以需要指定為1。

3. 模型訓練和評估:

3.1 資料加載:

​ 使用pytorch加載資料,很簡單:

# 下載下傳資料集或者加載資料集
train_dataset = MNIST(root='../data/',train=True,transform=transforms.ToTensor(),download=True)
test_dataset = MNIST(root='../data/',train=False,transform=transforms.ToTensor())
# 加載資料: 分批次,每批32個資料
batch_size = 32
train_loader = DataLoader(dataset=train_dataset,batch_size=batch_size,shuffle=True)
test_loader = DataLoader(dataset=test_dataset,batch_size=batch_size,shuffle=False)
           

​ **基本上(也有需要自己定義Dataset的情況)**加載已有的資料都是這樣的格式。

3.2 模型執行個體化和放入GPU中:

​ 這段很簡單,另外調用GPU的方法也是固定的,就不多說:

# 建立模型
model = LeNet()
# 模型放入GPU中
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
model = model.to(device)
           

3.3 定義損失函數、優化器:

​ 損失函數我們使用分類任務中常用的交叉熵損失函數,而優化器采用SGD優化器,學習率設定維常用的0.001,Momentum設定為0.9。

# 定義損失函數
loss_func = nn.CrossEntropyLoss()
loss_list = [] # 用來存儲損失值
# 定義優化器:第一個參數是模型的參數
SGD = optim.SGD(params=model.parameters(),lr=0.001,momentum=0.9)
           

3.4 訓練:

​ 首先,指定訓練次數:

# 訓練指定次數,這裡寫為了3
for i in range(3):
	xx
           

​ 然後,疊代讀取資料:

for i in range(3):
    loss_temp = 0 # 定義一個損失值,用來列印檢視
    # 其中j是疊代次數,data和label都是批量的,每批32個
    for j,(batch_data,batch_label) in enumerate(train_loader):
    	xxx
           

​ 接着将資料放入GPU中:

for i in range(3):
    loss_temp = 0
    for j,(batch_data,batch_label) in enumerate(train_loader):
        # 啟用GPU
        batch_data,batch_label = batch_data.cuda(),batch_label.cuda()
           

​ 然後,就是常見的操作:清空梯度、計算模型、計算損失、反向傳播、更新梯度

for i in range(3):
    loss_temp = 0 # 定義一個損失值,用來列印檢視
    # 其中j是疊代次數,data和label都是批量的,每批32個
    for j,(batch_data,batch_label) in enumerate(train_loader):
        # 啟用GPU
        batch_data,batch_label = batch_data.cuda(),batch_label.cuda()
        # 清空梯度
        SGD.zero_grad()
        # 模型訓練
        prediction = model(batch_data)
        # 計算損失
        loss = loss_func(prediction,batch_label)
        loss_temp += loss
        # BP算法
        loss.backward()
        # 更新梯度
        SGD.step()
           

​ 最後,我們可以列印一下損失值來檢視模型訓練的怎麼樣了,這裡我們每隔兩百小批次就列印一次損失值:

# 訓練指定次數
for i in range(3):
    loss_temp = 0 # 定義一個損失值,用來列印檢視
    # 其中j是疊代次數,data和label都是批量的,每批32個
    for j,(batch_data,batch_label) in enumerate(train_loader):
        # 啟用GPU
        batch_data,batch_label = batch_data.cuda(),batch_label.cuda()
        # 清空梯度
        SGD.zero_grad()
        # 模型訓練
        prediction = model(batch_data)
        # 計算損失
        loss = loss_func(prediction,batch_label)
        loss_temp += loss
        # BP算法
        loss.backward()
        # 更新梯度
        SGD.step()
        if (j + 1) % 200 == 0:
            print('第%d次訓練,第%d批次,損失值: %.3f' % (i + 1, j + 1, loss_temp / 200))
            loss_temp = 0
           

​ 運作上面的代碼,顯示的結果為:

第1次訓練,第200批次,損失值: 2.298
第1次訓練,第400批次,損失值: 2.285
第1次訓練,第600批次,損失值: 2.241
第1次訓練,第800批次,損失值: 1.812
第1次訓練,第1000批次,損失值: 0.754
第1次訓練,第1200批次,損失值: 0.521
第1次訓練,第1400批次,損失值: 0.423
第1次訓練,第1600批次,損失值: 0.365
第1次訓練,第1800批次,損失值: 0.334
第2次訓練,第200批次,損失值: 0.284
第2次訓練,第400批次,損失值: 0.245
第2次訓練,第600批次,損失值: 0.235
第2次訓練,第800批次,損失值: 0.211
第2次訓練,第1000批次,損失值: 0.202
第2次訓練,第1200批次,損失值: 0.179
第2次訓練,第1400批次,損失值: 0.180
第2次訓練,第1600批次,損失值: 0.165
第2次訓練,第1800批次,損失值: 0.148
第3次訓練,第200批次,損失值: 0.147
第3次訓練,第400批次,損失值: 0.143
第3次訓練,第600批次,損失值: 0.136
第3次訓練,第800批次,損失值: 0.130
第3次訓練,第1000批次,損失值: 0.115
第3次訓練,第1200批次,損失值: 0.114
第3次訓練,第1400批次,損失值: 0.114
第3次訓練,第1600批次,損失值: 0.093
第3次訓練,第1800批次,損失值: 0.122
           

3.5 評估:

​ 我們按照上面訓練的思路,可以輕松寫出測試的代碼:

correct = 0
for batch_data,batch_label in test_loader:
    batch_data, batch_label = batch_data.cuda(), batch_label.cuda()
    prediction = model(batch_data)
    predicted = torch.max(prediction.data, 1)[1]
    correct += (predicted == batch_label).sum()
print('準确率: %.2f %%' % (100 * correct / 10000)) # 因為總共10000個測試資料
           

​ 當然上面的代碼第一次接觸還是有點疑問,主要的一點是

predicted = torch.max(prediction.data, 1)[1]

這段代碼在幹什麼。

​ 首先,我們一個圖像傳入模型,輸出的是一個向量,這個向量10個值,代表數字0-9的概念值。而torch.max(x,1)表示按行(1:行,0:列)取出最大值,它傳回的是一個特殊格式的資料,第一個元素是各個最大值,第二個元素是其索引(在這裡就等價于0-9數字),是以使用[1]取出。

​ 運作結果:

準确率: 91.25 %
# 這個是隻訓練一次的結果
           

3.6 探究:

​ 下面将模型訓練1、2、3、4次的準确率結果:

# 1次 : 準确率: 91.25 %
# 2次 : 準确率: 93.44 %
# 3次 : 準确率: 97.19 %
# 4次 : 準确率: 97.58 %
           

​ 另外,測試一下使用GPU與不使用GPU的時間差:

# 為了測試,我訓練10次,并且僅僅記錄訓練花費時間
# 使用GPU: 訓練花了: 124 s
# 不适用GPU:訓練花了: 160 s
           

​ 上面GPU測試不嚴謹,因為首次調用GPU是需要花費時間的,但是從兩者差别看出,調用GPU确實效率很好。

4. 總結:

​ 這次算是把整個流程從頭到尾跑了一遍,并簡單探究了訓練次數和調用GPU對訓練的影響。會了本篇文章的代碼,至少後面再實作CNN的圖像分類架構,應該還是比較簡單了。

完整代碼
# author: baiCai
# 導包
import time
import torch
from torch import nn
from torch import optim
from torch.utils.data import DataLoader
from torchvision.datasets import MNIST
import torchvision.transforms as transforms
from torchvision.models import AlexNet

# 建立模型
class LeNet(nn.Module):
    def __init__(self):
        super(LeNet,self).__init__()
        # 定義模型
        self.features = nn.Sequential(
            nn.Conv2d(in_channels=1,out_channels=6,kernel_size=(5,5),stride=1,padding=2),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2,stride=2),
            nn.Conv2d(in_channels=6,out_channels=16,kernel_size=(5,5),stride=1),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2,stride=2),
        )
        self.classifier = nn.Sequential(
            nn.Linear(in_features=400, out_features=120),
            nn.ReLU(),
            nn.Linear(in_features=120, out_features=84),
            nn.ReLU(),
            nn.Linear(in_features=84, out_features=10)
        )

    def forward(self,x):
        # 定義前向算法
        x = self.features(x)
        # print(x.shape)
        x = torch.flatten(x,1)
        # print(x.shape)
        result = self.classifier(x)
        return result

# 下載下傳資料集或者加載資料集
train_dataset = MNIST(root='../data/',train=True,transform=transforms.ToTensor(),download=True)
test_dataset = MNIST(root='../data/',train=False,transform=transforms.ToTensor())
# 加載資料: 分批次,每批256個資料
batch_size = 32
train_loader = DataLoader(dataset=train_dataset,batch_size=batch_size,shuffle=True)
test_loader = DataLoader(dataset=test_dataset,batch_size=batch_size,shuffle=False)
# start time
start_time = time.time()
# 建立模型
model = LeNet()
# 模型放入GPU中
# device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
# model = model.to(device)
# 定義損失函數
loss_func = nn.CrossEntropyLoss()
loss_list = [] # 用來存儲損失值
# 定義優化器
SGD = optim.SGD(params=model.parameters(),lr=0.001,momentum=0.9)
# 訓練指定次數
for i in range(10):
    loss_temp = 0 # 定義一個損失值,用來列印檢視
    # 其中j是疊代次數,data和label都是批量的,每批32個
    for j,(batch_data,batch_label) in enumerate(train_loader):
        # 啟用GPU
        # batch_data,batch_label = batch_data.cuda(),batch_label.cuda()
        # 清空梯度
        SGD.zero_grad()
        # 模型訓練
        prediction = model(batch_data)
        # 計算損失
        loss = loss_func(prediction,batch_label)
        loss_temp += loss
        # BP算法
        loss.backward()
        # 更新梯度
        SGD.step()
        if (j + 1) % 200 == 0:
            print('第%d次訓練,第%d批次,損失值: %.3f' % (i + 1, j + 1, loss_temp / 200))
            loss_temp = 0
# end_time
end_time = time.time()
print('訓練花了: %d s' % int((end_time-start_time)))
# 使用GPU: 訓練花了: 124 s
# 不适用GPU:訓練花了: 160 s
# 測試
correct = 0
for batch_data,batch_label in test_loader:
    batch_data, batch_label = batch_data.cuda(), batch_label.cuda()
    prediction = model(batch_data)
    predicted = torch.max(prediction.data, 1)[1]
    correct += (predicted == batch_label).sum()
print('準确率: %.2f %%' % (100 * correct / 10000)) # 因為總共10000個測試資料
           

繼續閱讀