天天看點

Pytorch實戰:手寫數字識别Pytorch實戰:手寫數字識别

Pytorch實戰:手寫數字識别

1、資料導入

#實戰手寫數字識别
#************************************************************導入包*********************************************************************

import  torch , torchvision           #該句中torchvision可省略,torchvision可實作資料的處理、導入和預覽等
import  matplotlib.pyplot as plt      #該句可略

from torch.autograd import Variable
from torchvision import datasets, transforms       #torchvision.datasets可實作對自帶訓練集和測試集的下載下傳
                                                   #torchvision.transforms中提供了豐富的類對載入的資料進行變換
from torch.utils.data import DataLoader            #dataLoader實作對資料的裝載      

#**************************************************擷取手寫數字的訓練集和測試集**********************************************************

data_train = torchvision.datasets.MNIST(root = "./data/",     #MNIST是系統自帶的手寫數字資料集,
                                                              #root指定下載下傳位址,這裡是放在根目錄下的data檔案夾中
                    transform= transforms.ToTensor(),       #對資料進行變換操作
                    train = True ,                #将下載下傳好的資料進行指定資料載入,True--載入的是訓練集,False--載入的是測試集
                    download = True)              #确認下載下傳


data_test = datasets.MNIST(root = "./data/",
                      transform= transforms.ToTensor() ,      #對資料進行變換操作
                      train = False)      #無需進行 download = True 的下載下傳操作,反而會報錯
 
#pytorch.torch.transforms
transform = transforms.Compose(                            #Compose可以當作一種容器,它能夠同時對多種資料變換進行組合
                                [transforms.ToTensor() ,   #類型的轉換變換
                                transforms.Normalize(mean = [0.5, 0.5, 0.5],  #(圖像去均值)資料标準化變換,這裡是将資料變換為标準正太分布                                                  
                                                     std= [0.5, 0.5, 0.5])]   # 歸一化可以移除圖像的平均亮度值,也有利于學習率的自動選擇
                               )                                              #normalize函數:X(normal)=(X-mean(均值))/std(标準差)
                                                                              #這裡取巧(偷懶),并沒有使用原始的資料集,而是自定義了
                                                                              #一個normalize函數

data_loader_train =DataLoader(dataset = data_train,            #用于指定載入的資料集名稱
                                        batch_size = 64,       #每個包中的圖像資料個數
                                        shuffle =True)         #true表明裝載的過程中将資料随機打亂順序,并進行打包(寫False會報錯)

data_loader_test = DataLoader(dataset = data_test,
                              batch_size = 64,
                              shuffle = True)   
           

2、資料預覽

#**********************************************************資料預覽******************************************************************

images, labels = next(iter(data_loader_train))  #使用iter和next來擷取一個批次的圖檔資料和其對應的圖檔标簽
                                               #iter()函數擷取這些可疊代對象的疊代器
                                               #next()不斷的擷取下一條資料
                                               #疊代器可以看作是一個特殊的對象,每次調用該對象時會傳回自身的下一個元素
            
img = torchvision.utils.make_grid(images)  #使用torchvision.utils中的make_grid類方法将一個批次的圖檔構造成網絡模式
                                           #傳遞給grid的參數就是一個批次的裝載資料,每個批次的裝載資料都是4維的,次元
                                           #從前往後分别是:batch_size(一個批次中的資料個數),channel(每張圖檔的色彩通道數),
                                           #height(每張圖檔的高度),weight(每張圖檔的寬度)
                                           #經過grid整合後,這個批次的圖檔全部被整合到一起,圖檔的次元變成(channel,height,weight)
                                           #channel的值和之前一樣,其餘值發生了變化
                    
img = img.numpy().transpose(1,2,0)  #transpose實作坐标軸的旋轉,原始為(0,1,2)表示(x,y,z)軸,在這裡變成(1,2,0)表示(y,z,x)軸,實作了90度翻轉
std = [0.5,0.5,0.5]    #标準差
mean = [0.5,0.5,0.5]   #均值
img = img*std + mean   #一種歸一化處理
print([labels[i] for i in range(64)])
plt.imshow(img)

           
Pytorch實戰:手寫數字識别Pytorch實戰:手寫數字識别

3、模型搭建

#********************************************************* CNN訓練模型搭建 ******************************************************************

class Model (torch.nn.Module): #繼承Module
    def __init__(self):
        super(Model , self).__init__( )
        self.conv1 = torch.nn.Sequential(  #Sequential是一個容器,将一些子產品裝入其中
                     torch.nn.Conv2d(1,64,kernel_size = 3 , stride = 1 , padding = 1),#(1是輸入圖像的深度,64是輸出圖像的深度)
                     torch.nn.ReLU(),
                     torch.nn.Conv2d(64, 128, kernel_size = 3 ,stride = 1 , padding = 1),
                     torch.nn.ReLU(),
                     torch.nn.MaxPool2d(stride = 2 , kernel_size = 2))
        
        self.dence = torch.nn.Sequential(
                     torch.nn.Linear(14*14*128 , 1024), #(14*14*128是輸入層,1024是隐藏層)
                     torch.nn.ReLU(),
                     torch.nn.Dropout(p=0.5), #将所有元素按機率P=0.5進行置0,以防止過拟合
                     torch.nn.Linear(1024,10)) 
     
    def forward(self , x):          #前向傳播forward函數中的内容,首先經過self.conv1進行卷積處理,然後進行x.view(-1,14*14*128),對參數實作扁平化,
        x = self.conv1(x)           #/因為之後緊接着的就是全連接配接層,是以如果不進行扁平化,則全連接配接層的實際輸出的參數次元和其定義輸入的次元将不比對,
        x = x.view(-1 , 14*14*128)  #/程式将會報錯,最後通過self.dense定義的全連接配接層進行最後的分類。   view中的-1表示一個不确定的數
        x = self.dence(x)           #/輸入-1會使計算機進行自我計算,view傳回的資料和傳入的tensor一樣,隻是形狀不同
        return x   
        
           

4、定義在訓練之前使用哪種損失函數和優化函數

model = Model()
cost = torch.nn.CrossEntropyLoss()  #損失函數使用的是交叉熵
optimizer = torch.optim.Adam(model.parameters())  #優化函數使用的是Adam自适應算法,優化參數範圍是所有參數
print(model)
           

5、導入CNN模型與優化函數,進行後向傳播

n_epochs = 5
 
for epoch in range (n_epochs):
    running_loss = 0.0   #損失個數初始化為0
    running_correct = 0  #命中個數初始化為0
    print("Epoch {}/{}".format(epoch , n_epochs))
    print("-"*20)
    for data in data_loader_train :   #逐一讀取訓練集中的資料
        X_train , y_train = data   
        X_train , y_train = Variable(X_train),Variable(y_train)  #使用Variable将Tensor資料變量進行封裝,以便自動梯度的實作
        outputs = model(X_train)      #輸出卷積後的訓練集
        _,pred = torch.max(outputs.data , 1) 
        optimizer.zero_grad()         #自适應下的梯度清零
        loss = cost(outputs , y_train)  #損失函數的計算:将訓練後的參數與目标數進行 交叉熵 的計算        
        loss.backward()    #後向傳播
        optimizer.step()   #自适應算法下的梯度更新
        running_loss += loss.data.item()    #識别失敗資料統計,.item()等同于data[0]
        running_correct += torch.sum(pred == y_train.data)   #識别正确資料統計

    testing_correct = 0
    
    for data in data_loader_test:  #逐一讀取測試集中的資料
        X_test , y_test = data
        X_test , y_test = Variable(X_test),Variable(y_test) 
        outputs = model(X_test)    #輸出卷積後的測試集
        _ , pred = torch.max(outputs.data ,1)
        testing_correct += torch.sum(pred == y_test.data)
     
    print("Loss is : {:.4f} , Train Accuracy is : {:.4f}% , Test Accuracy is :{:.4f}".format(running_loss/len(data_train),#len求字元串長度
                                                                                      100*running_correct/len(data_train), #百分比樣式
                                                                                      100*testing_correct/len(data_test))) #百分比樣式
           

6、最終結果

Epoch 0/5
--------------------
Loss is : 0.0021 , Train Accuracy is : 95.0000% , Test Accuracy is :98.0000
Epoch 1/5
--------------------
Loss is : 0.0007 , Train Accuracy is : 98.0000% , Test Accuracy is :99.0000
Epoch 2/5
--------------------
Loss is : 0.0004 , Train Accuracy is : 99.0000% , Test Accuracy is :99.0000
Epoch 3/5
--------------------
Loss is : 0.0003 , Train Accuracy is : 99.0000% , Test Accuracy is :99.0000
Epoch 4/5
--------------------
Loss is : 0.0002 , Train Accuracy is : 99.0000% , Test Accuracy is :99.0000

           

繼續閱讀