pytorch搭建CNN,手寫數字識別,初識神經網絡

大概內容:

  • torchvision.datasets加載MNIST數據集,及顯示裏面的內容
  • 搭建3層神經網絡,介紹相關參數
  • 訓練網絡和測試準確率,圖像顯示loss的變化
  • 使用GPU,即cuda()
  • 神經網絡模型參數的保存和提取

1.搭建神經網絡

一些概念:

  • 優化器:常見SDG,RMSporp,Adam等(Adam需要大顯存)
  • 卷積神經網絡:常用在圖片識別,視頻分析、自然語言處理
  • 結構:神經元構成神經層,神經層構成神經網絡
  • 每個神經層都有其輸入和輸出,當輸入是圖片時(以彩色圖片爲例),輸入的是一個三維向量:對應長寬高(像素點和通道數(RGB3通道))
  • 卷積:不在對每個單獨特徵點(像素)輸入信息進行處理,而是對一小塊的像素區域進行處理,加強了信息的連續性
  • 卷積神經網絡:有一個批量過濾器,不斷在圖片上移動,收集信息,不斷重複這個過程。一次收集之後,長和寬更小、高更大的圖片
  • 爲什麼高變高了呢?這是卷積覈定義的,可以定義高度,這個定義值表示這個卷積核在這個區域提取了幾次信息
  • 這個批量過濾器,也叫濾波器,也叫過濾器,也叫卷積核
  • 池化:處理卷積的信息,改變圖片的大小
class CNN(nn.Module):
    def __init__(self):
        super().__init__()
        # 第一層:輸入層
        self.conv1 = nn.Sequential(  # conv1是一個卷積層(一般包括:卷積層,激勵函數,池化層),Sequential是其內部的層包裹起來(是一個序貫模型)
            nn.Conv2d(  # 卷積層(2d:二維卷積):是一個3維過濾器filter,有長寬高,高度指filter的個數(用於提取特徵屬性)
                in_channels=1, # 輸入數據的高度(是上一層的高度,也稱卷積核的通道數),圖片RGB爲3層
                out_channels=16, # 輸出的個數(也是filter的個數,是卷積核的個數),是自定義的,表示同時有16個filter在收集信息,這裏是使圖片變厚的原因
                kernel_size=5, # 表示卷積核的長和寬都是5個像素點
                stride=1,  # 步長:每次卷積核移動的像素點個數(表示每隔多少步跳一下,掃描下一塊區域)
                padding=2,  # padding=(kernel_size-stride)/2.表示在圖像的邊緣填充2個像素點(補全邊緣像素點,爲了提取邊緣像素點的特徵)
            ), 
            nn.ReLU(), # 激活函數
            nn.MaxPool2d(kernel_size=2), # 池化:往下一層篩選你重要的部分,比如篩選這個區域的最大值,作爲這個區域的特徵,這裏是導致圖片變小的原因
        )
        ################################################
        # 第一層,輸入圖片的變化:
        # (1, 28, 28) -> nn.Conv2d() -> (16, 28, 28)
        # -> nn.ReLU() -> (16, 28, 28)
        # -> nn.MaxPool2d -> (16, 14, 14)
        #################################################
        # 第二層神經網絡
        self.conv2 = nn.Sequential(  # -> (16, 14, 14)
            nn.Conv2d(16, 32, 5, 1, 2),  # -> (32, 14, 14)
            nn.ReLU(),  # -> (32, 14, 14)
            nn.MaxPool2d(2)  # -> (32, 7, 7)
        )
        # 輸出層:全連接層
        self.output = nn.Linear(32 * 7 * 7, 10)
    
    # 前向計算
    def forward(self, x):
        x = self.conv1(x)
        x = self.conv2(x) # (batch, 32, 7, 7)
        x = x.view(x.size(0), -1)  # (batch, 32 * 7 * 7) 這裏是把圖像展平,全連接,相當於reshape
        x = self.output(x)
        return x

源碼如下:

# coding=utf-8
####################################################
# 卷積神經網絡:
#     手寫數字識別
#     搭建卷積神經網絡:CNN

# 查看顯卡情況:(打開CMD)
#     cd C:\Program Files\NVIDIA Corporation\NVSMI
#     nvidia-smi.exe
####################################################

import os
import sys

import torch
import torch.nn as nn
import torch.utils.data as Data
import torchvision
import matplotlib.pyplot as plt

# 全局變量 ###################################################################
use_cuda = torch.cuda.is_available() # GPU
if use_cuda: # 打印GPU情況
    print(torch.cuda.get_device_name(0), use_cuda)

# 隨機種子
manualSeed = 2020
torch.manual_seed(manualSeed)
if use_cuda:
    torch.cuda.manual_seed_all(manualSeed)

# 超參數
EPOCH = 2
BATCH_SIZE = 100
LR = 0.001

save_path = os.path.join(sys.path[0], "cnn.pth") # 保存模型參數的文件

# 顯示圖片
def display_image(image, label):
    plt.imshow(image, cmap="gray") # 輸入一個矩陣
    plt.title('%i' % label.item())
    plt.show()

# 數據分佈曲線,用來查看loss的變化
def polt_curve(data): 
    # 數據分佈曲線
    fig = plt.figure()
    plt.plot(range(len(data)), data, color = 'blue')
    plt.legend(['value'], loc = 'upper right')
    plt.xlabel('step')
    plt.ylabel('value')
    plt.show()

# 搭建神經網絡 ###############################################################
class CNN(nn.Module):
    def __init__(self):
        super().__init__()
        # 第一層:輸入層
        self.conv1 = nn.Sequential(  # conv1是一個卷積層(一般包括:卷積層,激勵函數,池化層),Sequential是其內部的層包裹起來(是一個序貫模型)
            nn.Conv2d(  # 卷積層(2d:二維卷積):是一個3維過濾器filter,有長寬高,高度指filter的個數(用於提取特徵屬性)
                in_channels=1, # 輸入數據的高度(是上一層的高度,也稱卷積核的通道數),圖片RGB爲3層
                out_channels=16, # 輸出的個數(也是filter的個數,是卷積核的個數),是自定義的,表示同時有16個filter在收集信息,這裏是使圖片變厚的原因
                kernel_size=5, # 表示卷積核的長和寬都是5個像素點
                stride=1,  # 步長:每次卷積核移動的像素點個數(表示每隔多少步跳一下,掃描下一塊區域)
                padding=2,  # padding=(kernel_size-stride)/2.表示在圖像的邊緣填充2個像素點(補全邊緣像素點,爲了提取邊緣像素點的特徵)
            ), 
            nn.ReLU(), # 激活函數
            nn.MaxPool2d(kernel_size=2), # 池化:往下一層篩選你重要的部分,比如篩選這個區域的最大值,作爲這個區域的特徵,這裏是導致圖片變小的原因
        )
        ################################################
        # 第一層,輸入圖片的變化:
        # (1, 28, 28) -> nn.Conv2d() -> (16, 28, 28)
        # -> nn.ReLU() -> (16, 28, 28)
        # -> nn.MaxPool2d -> (16, 14, 14)
        #################################################
        # 第二層神經網絡
        self.conv2 = nn.Sequential(  # -> (16, 14, 14)
            nn.Conv2d(16, 32, 5, 1, 2),  # -> (32, 14, 14)
            nn.ReLU(),  # -> (32, 14, 14)
            nn.MaxPool2d(2)  # -> (32, 7, 7)
        )
        # 輸出層:全連接層
        self.output = nn.Linear(32 * 7 * 7, 10)
    
    # 前向計算
    def forward(self, x):
        x = self.conv1(x)
        x = self.conv2(x) # (batch, 32, 7, 7)
        x = x.view(x.size(0), -1)  # (batch, 32 * 7 * 7) 這裏是把圖像展平,全連接,相當於reshape
        x = self.output(x)
        return x

# 加載數據集 #################################################################
def data_set(data_path): # 傳入數據保存的路徑
    # 下載數據集
    train_set = torchvision.datasets.MNIST( #訓練集
        root=data_path, # 保存的目錄
        train=True, # 是否用於訓練
        transform=torchvision.transforms.ToTensor(), # 數據增強、轉化,將原始數據改變成什麼樣的形式,這裏主要是將圖片的數據點轉化成tensor, 同時將像素點的值標準化,壓縮到0到1之間
        download=True # 如果當前目錄下有數據集,就不會下載
    )

    train_loader = Data.DataLoader(
        dataset=train_set, # 加載數據
        batch_size=BATCH_SIZE, # 每次加載數據的數量
        shuffle=True, # 是否打亂
        num_workers=0 # 工作線程
    )

    test_set = torchvision.datasets.MNIST( # 測試集
        root=data_path,
        train=False,
        transform=torchvision.transforms.ToTensor(),
        download=True
    )

    test_loader = Data.DataLoader(
        dataset=test_set,
        batch_size=BATCH_SIZE,
        shuffle=True,
        num_workers=0
    )

    print(f'訓練數據集:{train_set.train_data.size()}') # 打印訓練數據量
    # 訓練數據集:torch.Size([60000, 28, 28])
    print(f'測試數據集:{test_set.test_data.size()}')
    # 測試數據集:torch.Size([10000, 28, 28])

    # # 顯示一張train_set的一個數據
    # display_image(train_set.train_data[0].numpy(), train_set.train_labels[0])

    # # 顯示一張test_loader的一個數據
    # x, y = next(iter(test_loader)) # 返回的是一個批次的數據
    # ##
    # # x 是一批圖片 shape is [batch, 1, 28, 28]
    # # x[0] 是這批圖片的第一張圖片 shape is [1, 28, 28] 只有一個通道的灰色照片
    # # x[0][0] shape is [28, 28]
    # # y[0]是一個tensor
    # display_image(x[0][0], y[0])

    return train_loader, test_loader

# 訓練網絡模型 ###############################################################
def train_CNN(train_loader): # 傳入訓練集
    # 訓練網絡
    cnn = CNN()
    if use_cuda:
        cnn = cnn.cuda()
    # print(cnn)
    ''' 打印結果
    CNN(
    (conv1): Sequential(
        (0): Conv2d(1, 16, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))
        (1): ReLU()
        (2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    )
    (conv2): Sequential(
        (0): Conv2d(16, 32, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))
        (1): ReLU()
        (2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    )
    (output): Linear(in_features=1568, out_features=10, bias=True)
    )
    '''
    # 分割線 ######################################################################
    # 定義優化器和損失函數
    optimization = torch.optim.Adam(cnn.parameters(), LR)
    loss_func = nn.CrossEntropyLoss() # 交叉熵

    loss_data = []
    # 訓練網絡
    for epoch in range(EPOCH):
        for step, (x, y) in enumerate(train_loader):
            if use_cuda:
                x, y = x.cuda(), y.cuda()

            out = cnn(x)
            loss = loss_func(out, y)
            # loss_data.append(loss.item())

            optimization.zero_grad()
            loss.backward()
            optimization.step()

            # 每50步打印一下結果
            step += 1
            if step % 50 == 0:
                loss_data.append(loss.item())
                print(f'Epoch:{epoch + 1} Step:{step} Train loss:{loss.item()}')
                # Epoch:10 Step:600 Train loss:0.015473434701561928

    # 打印誤差曲線
    polt_curve(loss_data)

    # 保存模型參數
    torch.save(cnn.state_dict(), save_path)
    print(f'網絡參數保存成功:{save_path}')
    # 網絡參數保存成功:d:\YDDUONG\Ydduong\VSPython\A-GCN-N\test-dir\cnn.pth

# 提取和測試網絡 ##############################################################
def test_CNN(test_loader): # 傳入測試集
    # 模型提取和測試
    pre_cnn = CNN()
    pre_cnn.load_state_dict(torch.load(save_path))
    if use_cuda:
        pre_cnn = pre_cnn.cuda()

    # 測試通過率
    acc_num = 0 # 預測正確的數目
    for step, (x, y) in enumerate(test_loader):
        if use_cuda:
            x, y = x.cuda(), y.cuda()
        out = pre_cnn(x)
        pred = out.argmax(dim=1) # 預測值
        acc_num += pred.eq(y).sum().item() # 比較、得到正確的

        step += 1
        if step % 10 == 0:
            print(f'Step:{step} Pred miss:{step * 100 - acc_num}')
            # Step:100 Pred miss:88

    print(f'準確率爲:{acc_num / len(test_loader.dataset)}')
    # 準確率爲:0.9912

# 主流程 #####################################################################
def main():
    # 1. 加載數據
    data_path = sys.path[0] # 數據保存的目錄
    train_loader, test_loader = data_set(data_path)

    # 2.訓練網絡
    train_CNN(train_loader)

    # 3.測試網絡
    test_CNN(test_loader)

if __name__ == "__main__":
    main()

參考莫煩pytorch教程:https://space.bilibili.com

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章