大概內容:
- 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