1. 程式人生 > 其它 >新手也能看懂的LeNet網路分類MINST資料集解析,附詳細程式碼及註解

新手也能看懂的LeNet網路分類MINST資料集解析,附詳細程式碼及註解

技術標籤:基礎python神經網路機器學習卷積

目錄

MINST資料集的介紹

MINST資料集是手寫0到9組成的資料集,來自 250 個不同人手寫的數字構成,分為六萬張訓練集和1萬張測試集,包含以下四個檔案:
Training set images: train-images-idx3-ubyte.gz (9.9 MB, 解壓後 47 MB, 包含 60,000 個樣本)
Training set labels: train-labels-idx1-ubyte.gz (29 KB, 解壓後 60 KB, 包含 60,000 個標籤)

Test set images: t10k-images-idx3-ubyte.gz (1.6 MB, 解壓後 7.8 MB, 包含 10,000 個樣本)
Test set labels: t10k-labels-idx1-ubyte.gz (5KB, 解壓後 10 KB, 包含 10,000 個標籤)
也就是一個標籤對應一張圖,這樣方便計算機確認自己識別的是否是對的

LeNet網路結構

LeNet-5是一個較簡單的卷積神經網路包含了深度學習的基本模組:卷積層,池化層,全連線層下圖就是網路的基本流程圖,接下來我們將逐層分析
LeNet網路結構

輸入層

首先是輸入層INPUT,輸入32*32大小的原始影象

C1層

接下來是C1層,第一個卷積層。卷積層的目的就是為了獲取影象特徵,獲取 的方式就是通過卷積核實現。比如說,曲線卷積核就可以提取曲線這一影象特徵。C1使用的卷積核大小是55,一共有六個卷積核,步長(卷積核掃過圖片前一次與後一次相隔的距離)為1。

下圖就是卷積的工作原理原圖66,卷積核3*3,圖中就是正在進行卷積,Kernel就是卷積核,紫色是圖片該部分與卷積核進行卷積運算,結果就是第三張圖藍色的部分。第三張圖就是我們獲得的特徵圖。
卷積影象

使用六種卷積核分別與原圖進行卷積後就會獲得六個大小為28*28的特徵圖,也就是我們C1顯示的影象

S2層

下一個是S2,池化層。池化大小是22,步長為2。池化層的目的是減少計算引數,因為我們目前獲得的特徵圖是六層的2828的,所包含的引數數量是十分龐大的,這就需要我們減少引數,池化層的作用就是這個。如何壓縮呢,目前池化有兩種,一種是通過選擇框的資料求和再取平均值然後在乘上一個權值和加上一個偏置值,組成一個新的圖片,另一種是取四個數中的最大值。。如下圖4x4的圖片經過取樣後還剩2x2,直接壓縮了4倍。本層具有啟用函式,為sigmod函式,而卷積層沒有啟用函式。同時池化層也不含卷積核

在這裡插入圖片描述
池化完成後就會獲得如S2所示的14*14的六層特徵圖

C3層

C3層同樣是卷積層,卷積核大小為5*5,個數為16但是這一步的卷積方式跟前面有所不同,這一層的卷積核為16個且並不是全連線而是部分連線,有些是C3連線到S2三層、有些四層、甚至達到6層,通過這種方式提取更多特徵,下表就是這16個卷積核與S2層的六個特徵圖結合的方式
在這裡插入圖片描述
比如表格第一列代表的就是16個卷積核中的第一個卷積核,它是與S2六層特徵圖中的1,2,3三層進行卷積,然後將卷積的結果相加求和,再加上一個偏置,再取sigmoid得出卷積後對應的特徵圖,也就是C3的第一個特徵圖。其它列也是類似(有些是3個卷積模板,有些是4個,有些是6個)總共16個特徵圖。

S4層

S4與S2在結構核作用上一致,同樣是池化作用,池化大小為22,個數為16。不再贅述,生成16個55的特徵圖

C5層

C5全連線層,大小為120。我們都知道圖片是一個二維的陣列,而全連線層就是要將他們降成一維的陣列。方法同樣是使用卷積。比如我們全連線前一層的特徵圖大小是55的,那麼我們同樣用55大小的卷積核掃過影象就可以只得到一個數,那麼使用120個大小為55的卷積核就可以得到我們的C5層,120的一維陣列。

F6和輸出層

F6和輸出層:全連線層中陣列的每個數都代表著用於分類的最基本特徵,比如有耳朵,有尾巴,有眼睛…(只是比如)最後再經過一個分類器(也是一個全連線層),假設要分貓,狗,兔子,那麼對這些特徵再進行一次計算,將其中的某幾個特徵求和,輸出最後的10的矩陣(輸出層的10),每個數代表了各個類別的概率(或得分),本次專案代表的就是數字0到9的概率,採用的是徑向基函式RBF來計算概率,也就是下圖一。F6層中之所以大小是84的原因就是出於輸出層的設計。計算機ASC碼值的字元圖,也就是下圖二每一個都對應一個712的位元圖,也就是84大小。而我們的數字0到9也在內這樣方便我們對每一個畫素點進行估計,確定輸出的是哪一個。
在這裡插入圖片描述
在這裡插入圖片描述

結果輸出及損失函式的解釋

損失函式

損失函式,就是用來估量你模型的預測值f(x)與真實值Y的不一致程度,它是一個非負實值函式,通常使用L(Y, f(x))來表示,損失函式越小,模型的魯棒性(內部穩定性)就越好。
在這裡插入圖片描述
比如上面這張圖,直線就是我們的預測值,而叉叉就是真實值,他們兩的差值就是誤差,我們自然是希望誤差變小的,所以定義損失函式(用來表現預測與實際資料的差距程度)
在這裡插入圖片描述
總差距程度,用公式Y-實際Y的絕對值:2+1+1+2+0+0=6為後續數學計算方便,我們通常使用平方損失函式代替絕對損失函式,將絕對值改為平方。那麼可以看到,當總差距程度越小時我們的結果越準確,也就是為什麼希望損失函式小。

結果展示

所以我們在訓練的時候可以通過損失函式來判斷我們模型的精確程度,同時來調節我們的內部引數,最終在訓練完成後達到高識別度。
我們在使用MINST訓練集訓練完成模型後,就可以使用測試集進行測試了,將測試集圖片輸入模型,模型自己判斷輸入的圖片對應哪一個數字,然後跟圖片的標籤進行對比來判斷是不是正確的。準確率就是這麼來的

LeNet程式碼附詳細註解

這裡會有一個錯誤,只是儲存路徑的錯誤,無上大雅

import torch
import torchvision as tv
import torchvision.transforms as transforms
import torch.nn as nn
import torch.optim as optim
import argparse
# 定義是否使用GPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# 定義網路結構,只是定義,沒有執行順序
class LeNet(nn.Module):
    def __init__(self):
        super(LeNet, self).__init__()
        #構造網路有兩種方式一個是seqential還有一個是module,前者在後者中也可以使用,這裡使用的是sequential方式,將網路結構按順序新增即可
        self.conv1 = nn.Sequential(     #input_size=(1*28*28)
            #第一個卷積層,輸入通道為1,輸出通道為6,卷積核大小為5,步長為1,填充為2保證輸入輸出尺寸相同
            nn.Conv2d(1, 6, 5, 1, 2), #padding=2保證輸入輸出尺寸相同
            #啟用函式,兩個網路層之間加入,引入非線性

            nn.ReLU(),      #input_size=(6*28*28)
            #池化層,大小為2步長為2
            nn.MaxPool2d(kernel_size=2, stride=2),#output_size=(6*14*14)
        )
        self.conv2 = nn.Sequential(
            nn.Conv2d(6, 16, 5),
            nn.ReLU(),      #input_size=(16*10*10)
            nn.MaxPool2d(2, 2)  #output_size=(16*5*5)
        )
        #全連線層,輸入是16*5*5特徵圖,神經元數目120
        self.fc1 = nn.Sequential(
            nn.Linear(16 * 5 * 5, 120),
            nn.ReLU()
        )
        #全連線層神經元數目輸入為上一層的120,輸出為84
        self.fc2 = nn.Sequential(
            nn.Linear(120, 84),
            nn.ReLU()
        )
        #最後一層全連線層神經元數目10,與上一個全連線層同理
        self.fc3 = nn.Linear(84, 10)

    # 定義前向傳播過程,輸入為x,也就是把前面定義的網路結構賦予了一個執行順序
    def forward(self, x):
        x = self.conv1(x)
        x = self.conv2(x)
        # nn.Linear()的輸入輸出都是維度為一的值,所以要把多維度的tensor展平成一維
        x = x.view(x.size()[0], -1)
        x = self.fc1(x)
        x = self.fc2(x)
        x = self.fc3(x)
        return x
#使得我們能夠手動輸入命令列引數,就是讓風格變得和Linux命令列差不多
parser = argparse.ArgumentParser()
parser.add_argument('--outf', default='./model/', help='folder to output images and model checkpoints') #模型儲存路徑
parser.add_argument('--net', default='./model/net.pth', help="path to netG (to continue training)")  #模型載入路徑
opt = parser.parse_args()

# 超引數設定
EPOCH = 8   #遍歷資料集次數
BATCH_SIZE = 64      #批處理尺寸(batch_size)一次訓練的樣本數,相當於一次將64張圖送入
LR = 0.001        #學習率

# 定義資料預處理方式,將圖片轉換成張量的形式,因為後續的操作都是以張量形式進行的
transform = transforms.ToTensor()

#下載四個資料集
# 定義訓練資料集
trainset = tv.datasets.MNIST(
    root='./data/',
    train=True,
    download=True,
    transform=transform)

# 定義訓練批處理資料
trainloader = torch.utils.data.DataLoader(
    trainset,
    batch_size=BATCH_SIZE,
    shuffle=True,
    )

# 定義測試資料集
testset = tv.datasets.MNIST(
    root='./data/',
    train=False,
    download=True,
    transform=transform)

# 定義測試批處理資料
testloader = torch.utils.data.DataLoader(
    testset,
    batch_size=BATCH_SIZE,
    shuffle=False,
    )

# 定義損失函式loss function 和優化方式(採用SGD)
net = LeNet().to(device)
criterion = nn.CrossEntropyLoss()  # 交叉熵損失函式,通常用於多分類問題上
optimizer = optim.SGD(net.parameters(), lr=LR, momentum=0.9) #梯度下降法求損失函式最小值

# 訓練
if __name__ == "__main__":
      #八次遍歷訓練
    for epoch in range(EPOCH):
        sum_loss = 0.0
        # 讀取下載的資料集
        for i, data in enumerate(trainloader):
            inputs, labels = data
            inputs, labels = inputs.to(device), labels.to(device)

            # 梯度清零
            optimizer.zero_grad()

            # forward + backward正向傳播以及反向傳播更新網路引數
            outputs = net(inputs)
            loss = criterion(outputs, labels)
            loss.backward()
            optimizer.step()

            # 每訓練100個batch列印一次平均loss,基本上是一直減小的,一個epoch有9個因為是6w張,一次batch64個
            sum_loss += loss.item()
            if i % 100 == 99:
                print('[%d, %d] loss: %.03f'
                      % (epoch + 1, i + 1, sum_loss / 100))
                sum_loss = 0.0
        # 每跑完一次epoch測試一下準確率
        with torch.no_grad():
            correct = 0
            total = 0
            for data in testloader:
                images, labels = data
                images, labels = images.to(device), labels.to(device)
                outputs = net(images)
                # 取得分最高的那個類
                _, predicted = torch.max(outputs.data, 1)
                total += labels.size(0)
                correct += (predicted == labels).sum()
            print('第%d個epoch的識別準確率為:%d%%' % (epoch + 1, (100 * correct / total)))
    torch.save(net.state_dict(), '%s/net_%03d.pth' % (opt.outf, epoch + 1))