# MNIST手寫辨識(Pytorch version) 此文章是介紹MNIST手寫辨識的作法及知識,分為兩個版本,一個為Pytorch版本,另一個為Tensorflow版本,觀念的部分大致相同,比較不一樣的地會是在實作的部分,那廢話不多說,開始吧! ## Contents 我會一步一步的講解每一個步驟,有些步驟是幫助我們做理解的,但對最後實作結果沒有明顯幫助,在文章最後面會有完整程式碼,而某些步驟只是讓我們更了解實作過程的那些程式碼不會被包含在內。 ## 1.查看是否有cuda 如果要使用cuda,需先有GPUs的電腦,如果有,會大大提升運算速度,檢查方式如以下: ```python= if torch.cuda.is_available(): print(True) else: print(False) ``` 輸出如果是True則代表可以使用cuda,反之如果輸出是False,代表只能使用cpu計算。 ## 2. 引用第三方套件 ```python= import torch from torch.utils import data as data_ import torch.nn as nn from torch.autograd import Variable import matplotlib.pyplot as plt import torchvision ``` 首先引入pytorch,接著引入torch.utils.data,由於我們在做影像辨識大多都會用到CNN(Cnvolutional Neural Network),而此時數據量非常大,如果要做fully convolutional networks會花很多時間,然而在近代比較沒有這個問題,因為硬體設備的進步,進行運算的速度以及運算量的大小皆大大提升了,但礙於我的Mac和ASUS沒有cuda,也就是gpu計算,所以只能用cpu計算,這樣運算速度還是稍慢了一點,所以這邊會使用batch training。然後引用torch.nn,裡面有許多已經寫好的neural network和activation function。由於我們會使用到變數,這邊引用pytorch的Variable。為了能達成數據可視化,這邊會引用matplotlib。最後引用torchvision,裡面有很多常用的dataset、image transform、 Model Architecture等資料。 ## 3. hyperparameter 所謂的hyperparameter超參數就是給設計者自由設定的參數,而變數名稱通常使用大寫,然而資料和變數名稱皆沒有硬性規定。 ```python= EPOCH = 1 BATCH_SIZE = 50 LR = 0.001 DOWNLOAD_MNIST = False ``` 這裡只特別講DOWNLOAD_MNIST,其他超參數的介紹請看前幾篇pytorch與機器學習。我們定義DOWNLOAD_MNIST是一個bool,如果我們的MNIST已經被下載過了就設為False,如果沒有下載過就設為True。 ## 4. 查看MNIST照片 ```python= train_data = torchvision.datasets.MNIST(root = './mnist',train = True,transform = torchvision.transforms.ToTensor(),download = DOWNLOAD_MNIST) print(train_data.train_data.size()) print(train_data.train_labels.size()) plt.ion() for i in range(11): plt.imshow(train_data.train_data[i].numpy(), cmap = 'gray') plt.title('%i' % train_data.train_labels[i]) plt.pause(0.5) plt.show() ``` 首先我們設一個變數叫train_data,裡面存放MNIST的資料,首先第一個參數是我們MNIST資料存放的位置,第二個參數代表我們要存放哪種資料,在MNIST裡有兩種,一種為training data,另一種為testing data,然而training data的數量比較多,有60000張,而testing data只有10000張,我們當然要選擇資料量大的數據,幫助我們做訓練,第三個參數是將我們的資料轉成Tensor(張量),第四個參數代表我們是否要下載MNIST,這邊就會用到之前DOWNLOAD_MNIST。 接著將我們的MNIST的大小輸出,可看到我們的MNIST的training data有60000張,而每張大小是28乘28。 接著利用matplotlib將MNIST的圖片輸出,由於我想一次看到10張圖片,所以這邊使用plt.pause(0.5),這樣它會每0.5秒顯示一張圖片。 ### 照片樣子 ![](https://i.imgur.com/Roe99YB.png) ![](https://i.imgur.com/0opvw6H.png) ## 5. 數據處理 ```python= train_loader = data_.DataLoader(dataset = train_data, batch_size = BATCH_SIZE, shuffle = True,num_workers = 2) test_data = torchvision.datasets.MNIST(root = './mnist/', train = False) test_x = torch.unsqueeze(test_data.test_data, dim = 1).type(torch.FloatTensor)[:2000]/255. test_y = test_data.test_labels[:2000] ``` 我們設一個變數叫train_loader,代表稍後要做batch training,那參數的部分可以去看之前的batch training,這邊就不多做贅述,接著是我們的testing data,我們從剛才下載好的root取資料,接著我社一個變數叫test_x,然後使用unsqueeze來增加一個維度,並將資料轉成TensorFloat的形式,這邊要除以255,代表我要將資料縮小到0和1之間,因為只做黑白判斷,而一個彩色的圖片的最大值是255,這才是我們要除以255的原因。然後建立一個test_y,當作testing的結果。 ## 6. 建立卷積神經網路(CNN) ```python= class CNN(nn.Module): def __init__(self): super(CNN, self).__init__() self.conv1 = nn.Sequential( nn.Conv2d(in_channels = 1, out_channels = 16, kernel_size = 5, stride = 1, padding = 2,),# stride = 1, padding = (kernel_size-1)/2 = (5-1)/2 nn.ReLU(),# (16, 28, 28) nn.MaxPool2d(kernel_size = 2),# (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.out = nn.Linear(32*7*7, 10) def forward(self, x): x = self.conv1(x) x = self.conv2(x) x = x.view(x.size(0), -1) output = self.out(x) return output, x cnn = CNN() print(cnn) ``` 來看這部分前請先觀看李宏毅教授介紹關於CNN的教學,教的非常好,清晰易懂。 https://www.youtube.com/watch?v=OP5HcXJg2Aw&list=PLJV_el3uVTsMhtt7_Y6sgTHGHp1Vb2P2J&index=9 首先繼承pytorch的nn.Module這個class,接著我們使用Sequential()這個容器搭建我們第一次CNN,而我們在做CNN時有三個主要步驟,第一個是我們的卷積層,而第一個參數代表我們輸入照片的channel有幾個,由於我們在做MNIST時是利用黑白相片在做的,所以這邊的channel數是1,第二個參數代表我們輸出的時候是幾層channel,其實輸入的image會經過filter的判斷,如果我們的filter的數量越多,我們的out_channels會越大。接著是kernel_size,kernel_size是指我們在做pattern的時候的範圍大小,而stride代表我們每次做pattern是要往旁邊幾格,最後是padding,padding是說當我們在做pattern的時候,當kernel超出image的範圍的時候我們要補的數值。 第二步是進入激勵函數,做一些非線性的調整,第三步也是最後一步,我們要進行pooling的動作,pooling有好幾種,我們這邊使用max pooling,所謂的max pooling就是找目前數據中的數值最大的為代表,然後取出,其他較小的值就拿掉(不做考慮),此時我們圖像大小可以減小許多。 我們做兩次上面的動作,只是在conv2的時候有些參數需要微調,由於上一層的out_channels是16,這一層的in_channels就要變成16,那此時out_channels會變成32,關原因和conv1一樣,剩下的參數接不用變,只是要注意,pooling的時候會使圖像大小改變,我們的image也因此從原本的28\*28變成7\*7,所以在傳資料的時候要注意。 再來我們將照片的維度打成二維,方便後面做計算,最後在做正向傳遞,我們利用view將原本的batch和tensor做reshape,-1這個參數代表自動判斷,然後傳入out(),最後再output,就完成我們的神經網路了。 ### 輸出CNN的流程 ![](https://i.imgur.com/np0pmAr.png) 這邊只是幫助理解而已,對於整個實作沒有影響。 ## 7. optimization & loss function ```python= optimization = torch.optim.Adam(cnn.parameters(), lr = LR) loss_func = nn.CrossEntropyLoss() ``` 這裡使用的optimizer是Adam,它的效率比SGD好太多了,接著loss function一樣是使用跟classification一樣的cross entropy。 ## 8. 開始訓練(training) ```python= for epoch in range(EPOCH): for step, (batch_x, batch_y) in enumerate(train_loader): bx = Variable(batch_x) by = Variable(batch_y) output = cnn(bx)[0] loss = loss_func(output, by) optimization.zero_grad() loss.backward() optimization.step() if step % 50 == 0: test_output, last_layer = cnn(test_x) pred_y = torch.max(test_output, 1)[1].data.numpy() accuracy = float((pred_y == test_y.data.numpy()).astype(int).sum()) / float(test_y.size(0)) print('Epoch: ', epoch, '| train loss: %.4f' % loss.data.numpy(), '| test accuracy: %.2f' % accuracy) ``` 這邊的訓練和之前的訓練作法一樣,這邊就不多贅述了,如果有需要,可以回去看之前的文章。 ## 9. testing ```python= test_output, _ = cnn(test_x[:10]) pred_y = torch.max(test_output, 1)[1].data.numpy() print(pred_y, 'prediction number') print(test_y[:10].numpy(), 'real number') ``` 這邊我將前面已經存好的testing data拿10份出來做testing,在predict的時候是使用機率的方式,所以用torch.max()找機率比較大的當答案,最後將predict的結果和答案輸出,就完成這次的實作。 ## 總程式碼(第一次實做版本) ```python= import torch from torch.utils import data as data_ import torch.nn as nn from torch.autograd import Variable import matplotlib.pyplot as plt import torchvision import os EPOCH = 1 BATCH_SIZE = 50 LR = 0.001 DOWNLOAD_MNIST = False train_data = torchvision.datasets.MNIST(root = './mnist',train = True,transform = torchvision.transforms.ToTensor(),download = DOWNLOAD_MNIST) print(train_data.train_data.size()) print(train_data.train_labels.size()) plt.ion() for i in range(11): plt.imshow(train_data.train_data[i].numpy(), cmap = 'gray') plt.title('%i' % train_data.train_labels[i]) plt.pause(0.5) plt.show() train_loader = data_.DataLoader(dataset = train_data, batch_size = BATCH_SIZE, shuffle = True,num_workers = 2) test_data = torchvision.datasets.MNIST(root = './mnist/', train = False) test_x = torch.unsqueeze(test_data.test_data, dim = 1).type(torch.FloatTensor)[:2000]/255. test_y = test_data.test_labels[:2000] class CNN(nn.Module): def __init__(self): super(CNN, self).__init__() self.conv1 = nn.Sequential( nn.Conv2d(in_channels = 1, out_channels = 16, kernel_size = 5, stride = 1, padding = 2,),# stride = 1, padding = (kernel_size-1)/2 = (5-1)/2 nn.ReLU(), nn.MaxPool2d(kernel_size = 2), ) self.conv2 = nn.Sequential( nn.Conv2d(16, 32, 5, 1, 2), nn.ReLU(), nn.MaxPool2d(2) ) self.out = nn.Linear(32*7*7, 10) def forward(self, x): x = self.conv1(x) x = self.conv2(x) x = x.view(x.size(0), -1) output = self.out(x) return output, x cnn = CNN() print(cnn) optimization = torch.optim.Adam(cnn.parameters(), lr = LR) loss_func = nn.CrossEntropyLoss() for epoch in range(EPOCH): for step, (batch_x, batch_y) in enumerate(train_loader): bx = Variable(batch_x) by = Variable(batch_y) output = cnn(bx)[0] loss = loss_func(output, by) optimization.zero_grad() loss.backward() optimization.step() if step % 50 == 0: test_output, last_layer = cnn(test_x) pred_y = torch.max(test_output, 1)[1].data.numpy() accuracy = float((pred_y == test_y.data.numpy()).astype(int).sum()) / float(test_y.size(0)) print('Epoch: ', epoch, '| train loss: %.4f' % loss.data.numpy(), '| test accuracy: %.2f' % accuracy) test_output, _ = cnn(test_x[:10]) pred_y = torch.max(test_output, 1)[1].data.numpy() print(pred_y, 'prediction number') print(test_y[:10].numpy(), 'real number') ```