Make Your First GAN With PyTorch 之 第一個 PyTorch 神經網路

soih0718發表於2020-10-09

我們繼續通過構建一個用於簡單但是常見的實際神經網路,來繼續學習 PyTorch。

MNIST 影像資料集

Make Your Own Neural Network 中,我們開發了一個學習對手寫數字進行分類的神經網路。

Fig_1

MNIST 資料集是著名的影像集合,常用來測量和比較機器學習演算法的效能。該資料集包括了用於訓練機器學習模型的 60,000 個影像和用於測試效能的 10,000 個影像。

這些影像尺寸是 28*28 畫素,是黑白而不是彩色的。根據你如何獲得資料的情況,每個畫素點可能對淺色或暗色給出數值,範圍在 0255 之間。

獲取 MNIST 資料

進入你在 Drive 中儲存 Python notebook 檔案的 Colab Notebooks 資料夾。使用 New 按鈕來建立新的資料夾,命名為 mnist_data

mnist_data 資料夾應該與我們的 notebook 檔案在相同的資料夾中,如圖所示:

Fig_2

使用下面的連結下載 MNIST 資料到你的電腦上:

下載完成這兩個檔案後,將其上傳到 mnist_data 資料夾,你可以通過導航到 mnist_data 資料夾並使用 New 按鈕來選擇 Upload Files

Fig_3

稍後,你可以看到這兩個檔案列在了 mnist_data 資料夾中。

觀察資料

在使用工具和演算法處理資料前,先通過探索新資料產生一些直觀感受是個好習慣。

我們需要使得我們上傳的資料能被我們 Python 程式碼獲取,我們通過掛載我們的 Drive 使其顯示為一個資料夾。

新建一個 notebook,執行如下程式碼:

# mount Drive to access data files
from google.colab import drive
drive.mount('./mount')

你將被提示點選一個連結,帶你開啟一個新的標籤頁。該標籤頁詢問你確認賬號並獲取掛載 Google Drive 的許可權,你將獲取一個程式碼並拷貝到 notebook 的 cell 中。

Fig_4
完成後,我們將得到 Drive 已經掛載的資訊,並可以使用資料夾 ./mount 被我們的 Python 程式碼所訪問。

我們的 MNIST 資料檔案是 CSV 格式的,也就是多行的 逗號分隔值檔案(comma separated values)。有許多方法來載入並顯示資料,我們將使用 pandas 庫來完成,因為這將使得任務非常簡單。

在一個新的 cell 中, 匯入 pandas 庫:

# import pandas to read csv files
import pandas

在下一個 cell 裡,我們使用 pandas 將訓練資料載入到 dataframe 中。下面是單獨的一行程式碼,長度很長是因為 mnist_train.csv 檔案的路徑很長

df = pandas.read_csv('mount/My Drive/Colab  Notebooks/myo_gan/mnist_data/mnist_train.csv', header=None) 

檢查你使用的檔案路徑是否與你放置檔案的位置一直。我自己的程式碼和資料都放在 Colab Notebooks 內的 myo_gan 中。

pandas 的 dataframenumpy 的陣列類似,但是有行列命名等附加特性,而且有求和、過濾資料等許多方便的函式。

可以通過使用 head() 函式來看一下大的 dataframe 的頂部內容:

df.head() 

這顯示了資料集的前五行:

Fig_5

MNIST 資料的每行包括了 785 個值,第一個值是影像的實際“數字”,其他的 784 個值是 28*28 影像的畫素值。

我們可以使用 info() 函式獲得 dataframe 的概要:
Fig_6
下面的總結告訴我們這個 dataframe 有 60,000 行,也就是 60,000 個訓練影像,同樣確認了我們每行有 785 個值。

下面我們通過將一行畫素值轉變為實際影像,進行資料視覺化。

我們使用多功能的 matplotlib 庫。從 matplotlib 中匯入 pyplot 包,更新notebook 檔案中匯入庫的 cell。

# import pandas to read csv files
import pandas

## import matplotlib to show images
import matplotlib.pyplot as plt

執行這個更新後的 cell,使得 pyplot 可用。

我們來觀察下面的程式碼:

# get data from dataframe
row = 0
data = df.iloc[row]

#label is the first value
label = data[0]

#image data is the remaining 784 values
img = data[1: ].values.reshape(28, 28)
plt.title("label = " + str(label))
plt.imshow(img, interpolation='none', cmap='Blues')
plt.show() 

第一段選擇了我們關心的 MNIST 資料中的影像。我們通過設定 row = 0 來選擇第一行,也就是第一個影像。 df.iloc[row] 選擇了資料集的第一行並將其指定為 data

下一段我們選擇了該行的第一個數字,稱之為 label,資料中實際也是標籤。

第三段獲取該行剩下的 784 個值並將其形狀改變為 28*28 的方形陣列,並將其指定給名為 img 的變數,因為這就是影像。然後將該陣列畫為點陣圖(bitmap),同時有一個標題顯示我們從資料中提取的 label。使用 imshow() 函式畫出點陣圖可以有很多選項,我們使用了其中兩個選項來設定調色盤(colour palette)並告訴 pyplot 不要對畫素點進行平滑。
Fig_7

我們可以看到 MNIST 訓練資料集中的第一個影像,該影像看起來像 5,而且標籤也確認了它應該是 5。

通過改變 row 的值並重新執行該 cell,可以對其他的影像進行試驗。舉例而言,如果你嘗試 row = 13,你應該可以得到像一個 6 的影像。

我們目前開發的程式碼線上地址為:

簡單的神經網路

在我們陷入到為神經網路編寫程式碼之前,我們返回一些,畫出我們將實現的流程圖。下面這張圖給出了起始和我們想結束的流程:
Fig_8

起始點是一個 MNIST 影像,尺寸為 28*28,或 784 個畫素值。這意味著我們神經網路的第一層必須有 784 個節點,我們對輸入層尺寸並沒有更多的選擇。

相反,我們對最後的輸出層有一些選項。我們工作的目標是為了能回答 “這是哪個數字?” 的問題。這個問題的答案可以是從 09 的任意一個數字,也就是有 10 種可能。最簡單的方法是對這 10 個可能的類別分別設定節點。

我們對隱藏的中間層具有更多的選項。因為我們這裡專注於學習 PyTorch 而不是試圖找到最佳尺寸,我們將使用在 Make Your Own Neural Network 中的知識,選擇尺寸為 200,使得我們繼續進行。

每層所有的節點都連線到下一層的每個節點上,這也被稱之為 全連線層(fully connected layers)

上圖缺少了一個關鍵的東西,我們需要對選擇 啟用函式(activation function),應用於隱藏和輸出層的輸出。在 Make Your Own Neural Network 中,我們使用了 s-型 logistic 函式。為了簡化,我們在這裡直接使用這個函式。
Fig_9

我們已經做好了將神經網路 架構(architecture) 遷移到 PyTorch 程式碼的準備。 PyTorch 做了很多幕後工作,對構建和執行神經網路進行了簡化。為了實現這一點,我們需要遵循PyTorch的程式設計模式。

當我們建立了一個神經網路類(class),我們需要繼承自從 PyTorch 自身的 torch.nn。這將帶來很多 Pytorch 的機制(machinery),如自動構建計算圖、打理權重,並在訓練時更新權重。

在一個新的 notebook 檔案中,先匯入 torchtorch.nn

# import libraries

import torch
import torch.nn as nn

其中, torch.nn 模組匯入為 nn,這將作為命名規範(naming convention)。

下面來構建我們的神經網路類,下面的程式碼給出了稱之為 Classifier 的類,其繼承自 nn.Module

class Classifier(nn.Module):

    def __init__(self):
        # Initialise parent PyTorch class
        super().__init__()

__init__(self) 函式是一個特殊函式,當從類建立物件時,需要呼叫該函式。該函式常用來建立物件並使之做好準備。你可能聽說過這叫 建構函式(constructor),這是一個相當具有描述性的名字。這裡設定的是 super().__init__(),看起來很神祕,但實際上可以簡單稱之為父類(parent class)的建構函式。所以 PyTorch 的 nn.Module 將為我們建立我們的 Classifier。不錯!

下面來定義神經網路的架構。有幾種不同的方法來完成該項工作,對於簡單網路而言,我們可以使用 nn.Sequential() 來提供網路組成部分的清單。該清單必須按照我們想要資訊通過的順序提供:

class Classifier(nn.Module):

    def __init__(self):
        # Initialise parent PyTorch class
        super().__init__()
        
        # define neural network layers
        self.model = nn.Sequential(
            nn.Linear(784, 200),
            nn.Sigmoid(),
            nn.Linear(200, 10),
            nn.Sigmoid()
        )

可以看到在 nn.Sequential() 中定義的不同元素:

  • nn.Linear(784, 200) 是從 784 個節點到 200 個節點的全連線對映。這個元素包括了不同節點連線的權重,將在訓練中被更新;
  • nn.Sigmoid() 在前一個元素的輸出端應用 s-型 logistic 啟用函式,該案例中為 200 個節點。
  • nn.Linear(200,10)200 個節點對映為 10 個節點。這包括了在中間隱藏層和 10 個輸出節點的最後一層的連線的權重。
  • nn.Sigmoid() 對 10 個節點的輸出應用 s-型 logistic 啟用函式。該結果是網路的最終輸出。

你可能好奇為何 nn.Linear 如此稱呼。原因是它對輸入到輸出的值應用了一個形式為 Ax+B 的線性函式。其中 A 為連線權重,B 可以認為是偏置(bias)。所有的這些引數都在訓練時被更新。有些人稱之為 可學習引數(learnable parameters)

這就完成了資訊正向流動的神經網路的元素定義,但是我們還沒有定義它如何計算誤差,來更新網路的可學習引數。

有很多的方法來定義網路的誤差,而且 PyTorch 為流行的選項提供了方便的函式。其中最簡單的一個是 均方誤差(mean squared error),也就是在計算平均值前,計算每個輸出節點實際和預期輸出差別的平方。 PyTorch 提供了這個為 torch.nn.MSELoss()

我們可以選擇這個誤差函式(error function),並在建構函式(constructor)中給出名稱。

# create loss function
self.loss_function = nn.MSELoss()

你可以無差別的使用 error 函式或 loss 函式,這通常沒有差別。如果想要更加精確一些, error 是在預期輸出和實際輸出的簡單差別,而 loss 是對 error 進行計算,用來解決我們實際問題的。

我們需要使用 error 函式,或者更正確的說法叫做 loss 函式來更新網路的連線權重。同時,有多重方法完成這個, PyTorch 提供了一些流行選項的函式。我們使用在 Make Your Own Neural Network 中開發的一個簡單的函式,稱之為 隨機梯度下降(stochastic gradient descent),或 SGD,學習率為 0.01

# create optimiser, using simple stochastic gradient descent
self.optimiser = torch.optim.SGD(self.parameters(), lr=0.01) 

比較有趣的是我們傳遞所有的可學習引數到 SGD 優化器的方法,也就是通過 PyTorch 為我們建立的機制 self.parameters() 使得所有的引數都可獲得。

為了通過網路傳遞資訊,PyTorch 假設了一個 forward() 方法。我們可以建立一個,但是可以非常小:

def forward(self, inputs):
    # simply run model
    return self.model(inputs)

這裡我們簡單獲得輸入並將其傳遞給之前使用 nn.Sequential() 定義的 self.model()中。模型的輸出簡單的返回給任何呼叫 forward() 的地方。

下面我們暫停並回想我們已經建立的內容:

  • 我們建立了一個繼承自 nn.Module 的神經網路類。繼承自 nn.Module 提供了訓練神經網路的更多機制。
  • 我們通過資訊流定義了神經網路的元素。由於對簡單網路很簡潔,我們使用了 nn.Sequential 方法。
  • 我們定義了 損失(loss) 函式和 優化器(optimiser) 方法來更新網路的 可學習引數(learnable parameters)
  • 最後,我們增加了一個 forward() 函式,PyTorch用於通過網路傳遞資訊。

我們神經網路類應看起來如此:

class Classifier(nn.Module):

    def __init__(self):
        # Initialise parent PyTorch class
        super().__init__()
        
        # define neural network layers
        self.model = nn.Sequential(
            nn.Linear(784, 200),
            nn.Sigmoid(),
            nn.Linear(200, 10),
            nn.Sigmoid()
        )
        
        # create loss function
        self.loss_function = nn.MSELoss()
        
        # create optimiser, using simple stochastic gradient descent
        self.optimiser = torch.optim.SGD(self.parameters(), lr=0.01) 
        
        pass
        
    def forward(self, inputs):
        # simply run model
        return self.model(inputs)

那麼如何來訓練網路呢?我們是否像 forward() 函式一樣需要一個 train() 函式呢?實際上,PyTorch 並不需要一個 train() 方法。由我們來構建我們的程式碼來完成訓練。
(Actually, PyTorch doesn’t require a train() method. It is left up to us to structure our code to do the training.)
我們通過在 forward() 方法旁邊建立 train() 方法來保證我們程式碼的簡潔性和一致性。

train() 方法需要網路的 輸入,也需要預期的 目標(target) 輸出來與實際輸出比較來計算損失(loss)

def train(self, inputs, targets):
    #calculate the output of the network
    outputs = self.forward(inputs)
    
    # calculate loss
    loss = self.loss_function(outputs, targets) 

train() 函式做的第一件是使用 forward() 方法來通過網路傳遞 輸入(inputs) 來獲得 輸出(outputs)

我們之前的損失函式是用來計算 損失(loss)。我們可以看到 PyTorch 使得這很簡單,我們做的只是為該函式提供網路的輸出和預期的輸出。

下一個步驟是用 損失(loss) 來更新網路的連線權重。你可能記得在 Make Your Own Neural Network 中我們需要對每個節點計算誤差梯度,並使用他們來更新連線到這些節點的連線權重(link weights)。

PyTorch 使得這很簡單:

# zero gradients, perform a backward pass, and update the weights  self.optimiser.zero_grad()
loss.backward()
self.optimiser.step() 

這三個步驟是幾乎所有由 PyTorch 構建的神經網路的關鍵模式。下面分步驟進行說明:

  • 首先使用 optimiser.zero_grad() 將計算圖中的所有梯度均設為 0 。
  • 使用 loss.backward() 計算損失函式,獲得網路內部的梯度。
  • 使用 optimiser.step() 來使用這些梯度更新網路的可學習引數。

每次訓練網路時,我們都需要將梯度設定為 0。如果沒有設定的話,使用 loss.backward() 計算梯度時均會將值累加。

我們在對很簡單的網路計算梯度前,已經使用了 backward() 函式。這裡的使用並沒有區別,我們可以考慮計算圖最後的節點為損失函式。

訓練視覺化

當我們在 Make Your Own Neural Network 訓練網路是,我們並沒有辦法對訓練的程式進行觀察。我們在訓練之後測量了網路的表現,但我們並沒有辦法得到過去訓練如何順利或未來的訓練是否還有用處的資訊。

保持追蹤訓練的一個方法是監測損失(loss)。我們在 train() 函式每次計算獲得 損失(loss) 值後,複製並儲存該值到一個列表中。但是由於神經網路訓練可能成千上萬次甚至更多,所以這個列表將會非常巨大。MNIST 資料集由 60,000 個訓練例項,而且我們需要執行這些例項的若干次 epoch。一個更好的方法是每 10 次訓練例項儲存一個 loss 值的副本,這意味著我們需要對 train() 執行次數進行計數。

下面的程式碼建立了一個 counter,初始值為 0,並在神經網路類的建構函式中建立一個空的列表 progress

# counter and accumulator for progress
self.counter = 0
self.progress = []

train() 函式內部,我們可以每 10 次訓練增加一次 counter,並將損失值增加到 progress 列表的末尾。

# increase counter and accumulate error every 10
self.counter += 1
if (self.counter % 10 == 0):
    self.progress.append(loss.item())
    pass

其中 % 10 表示被 10 除的餘數,僅當 counter10,20,30 等數時,該餘數為 0。這裡使用的 item() 函式是對單一值的張量展開獲得內部數字的便捷函式(convenience function)。

我們可以每 10000 次列印輸出 counter,來顯示整個資料集過程中訓練程式的快慢:

if (self.counter % 10000 == 0):
    print("counter = ", self.counter)
    pass

為了用圖表顯示損失值,我們可以在神經網路類中增加一個新函式 plot_progress()

def plot_progress(self):
    df = pandas.DataFrame(self.progress, columns=['loss'])
    df.plot(ylim=(0, 1.0), figsize=(16,8), alpha=0.1, marker='.',  grid=True, yticks=(0, 0.25, 0.5)) 
    pass

這些程式碼看起來複雜,但實際上僅有 2 行。第一行將損失值的列表 progress 轉化為一個 pandas dataframe,便於我們方便地繪製這些值的影像。剩下的選項使得 plot() 函式設定影像的設計和風格。

我們馬上就完成訓練網路的準備了!

MNIST 資料集類 (MNIST Dataset Class)

之前,我們將 MNIST 資料從一個 CSV 檔案載入到 pandas dataframe 中。我們可以直接從 dataframe 中直接使用這些資料。但是,既然我們在學習 PyTorch,那我們應該用 PyTorch 的方法載入並使用這些資料。

PyTorch 可以做很多有用的工作,如自動打亂資料(automatically shuffle data)、並行多程式載入資料、分批提供資料(provide it in batches)等。PyTorch 使用 torch.utils.data.DataLoader 來將資料實現 torch.utils.data.Dataset 物件。

為了簡化,我們不使用打亂(shuffling)或分批(batching)操作,但是使用 torch.utils.data.Dataset 類來獲得 PyTorch 機制的一些經驗。

使用下面的程式碼匯入 torch.utils.data.Dataset 類:

from torch.utils.data import Dataset

就像我們從 nn.Module 中繼承一個神經網路類,並提供了一個 forward() 函式,對於繼承自 Dataset 的資料集,我們提供了下面兩個特殊的函式:

  • __len__() 返回資料集(dataset)的專案數。
  • __getitem__() 返回資料集的第 n 個專案。

建立一個 MnistDataset 類並提供 __len__() 方法使得 PyTorch 能夠使用 len(mnist_dataset) 來獲得資料集的尺寸。而 __getitem__() 允許我們按照索引獲得專案,如可以使用 mnist_dataset[3]。

我們對下面的 MnistDataset 類進行觀察:

class MnistDataset(Dataset):

    def __init__(self, csv_file):
        self.data_df = pandas.read_csv(csv_file, header=None)
        pass
        
    def __len__(self):
        return len(self.data_df)
        
    def __getitem__(self, index):
        # image target (label)
        label = self.data_df.iloc[index,0]
        target = torch.zeros((10))
        target[label] = 1.0 
        
        # image data, normalised from 0-255 to 0-1
        image_values = torch.FloatTensor(self.data_df.iloc[index,1:].values)/ 255.0
        
        # return label, image data tensor and target tensor
        return label, image_values, target
        
    pass

當使用該類建立一個物件時, csv_file 讀入到一個稱之為 data_df 的 pandas dataframe 中。

__len__() 函式簡單的返回 dataframe 的長度。Easy!

__getitem__() 函式更有趣一些。就像之前使用 MNIST 資料的實驗一樣,我們可以獲取資料集中第 index 個專案的 標籤(label)

我們也建立一個張量來代表對神經網路預期的輸出。一個長度為 10 的張量,除了有一位根據標籤設定為 1.0,其他位都是 0。比如 0 的標籤對應一個類似 [1,0,0,0,0,0,0,0,0,0] 的張量,而 4 的標籤對應一個類似 [0,0,0,0,1,0,0,0,0,0] 的張量,這稱之為 獨熱編碼(one-hot encoding)

之後,我們根據影像的畫素值建立一個張量 image_values,該數字除以 255,使得範圍為 01

最後, __getitem__() 返回所有三個變數:labelimage_valuestarget

雖然不是 PyTorch 必須的,我們對 MnistDataset 類增加一個方法,來顯示資料集選定的影像。實踐證明,有一個方法來檢視我們使用的資料很有用。正如之前一樣,我們需要匯入 matplotlib.pyplot 庫。

def plot_image(self, index):
    arr = self.data_df.iloc[index,1:].values.reshape(28,28)
    plt.title("label = " + str(self.data_df.iloc[index,0]))
    plt.imshow(arr, interpolation='none', cmap='Blues')
    pass 

下面我們來檢查目前工作的內容。首先,我們通過傳遞 CSV 檔案位置的方式,使用這個類來建立一個資料集物件:

mnist_dataset = MnistDataset('mount/My Drive/Colab Notebooks/myo_gan/mnist_data/mnist_train.csv') 

我們知道 class 建構函式將 CSV 檔案的資料載入到一個 pandas dataframe。我們使用 plot_image() 函式來畫出資料集的第 10 個影像。第 10 個影像的索引為 9,因為第一個影像的索引為 0

mnist_dataset.plot_image(9)

輸出為一個手寫體的 4 的影像,標籤也告訴我們該影像為 4
Fig_10

這確認了我們的 dataset 類正確的載入了資料。使用類似 mnist_dataset[100] 的例子,確定我們的 mnist_dataset 類允許通過索引獲取。你應該看到它返回 label,畫素值和 target 張量。

訓練我們的分類器

由於我們已經完成了設定 dataset class 和 神經網路 class 的艱苦工作,現在訓練一個分類器神經網路非常簡單。

首先使用我們的 Classifier 類建立一個神經網路:

# create neural network
C = Classifier()

為了訓練該網路,程式碼也非常簡單:

# train network on MNIST data set
for label, image_data_tensor, target_tensor in mnist_dataset:
    C.train(image_data_tensor, target_tensor)
    pass

由於 mnist_dataset 是繼承自 PyTorch 的 Dataset,允許我們使用簡潔的 for 迴圈來對整個訓練資料進行操作。對每個訓練例子,我們只是簡單的講影像資料和目標張量傳遞到分類器的 train() 方法中。

Make Your Own Neural Network 中,我們知道對整個資料操作不知一次來訓練我們的網路可以很有幫助。我們可以在整個訓練迴圈中簡單地增加外部的 epoch 迴圈。

Python notebook 檔案每個 cell 的執行計時很簡單。我們可以在需要計時的 cell 頂端簡單增加一個 %%time 即可。這在神經網路實驗是作用很大,可以用來估計神經網路訓練的時間。

我們的 cell 應該看起來像這樣:

# create neural network
C = Classifier()

# train network on MNIST data set
epochs = 3

for i in range(epochs):
    print('training epoch', i + 1, "of", epochs)
    for label, image_data_tensor, target_tensor in mnist_dataset:
        C.train(image_data_tensor, target_tensor)
        pass
    psss

執行這個 cell 需要些時間。在每完成 10000 次呼叫後, train() 方法將列印已經完成的例子數量。
Fig_11

可以看到 3 個訓練 epochs 使用了大約 4 分鐘。由於每個 epoch 意味著 60,000 個訓練案例,這個時間並不差。

下面來對獲得的損失值畫圖,獲得訓練程式的全貌。

# plot classifier error
C.plot_progress()

你應該可以看到類似下面的圖表。由於神經網路訓練是一個隨機的過程,所以這個圖不會完全相同。
Fig_12

可以看到 損失(loss) 值快速的下降到大約 0.1,然後由於訓練的原因,下降的更慢了且噪聲更大了,逐漸趨近於 0

損失值的下降表明網路分類影像正確性變得越來越好。

這些損失值的圖表用處很大,這允許我們觀察網路訓練是否真正工作,同樣給我們訓練平滑穩定或不穩定混沌的直觀感覺。

使用我們的神經網路(Querying Our Neural Network)

由於我們有了一個訓練相當好的網路,下面使用這個網路來對影像進行分類。我們轉向 MNIST 的 10,000 個測試資料集。我們的神經網路還沒有見過這些影像。

使用一個新的 Dataset 物件載入該資料集:

# load MNIST test data
mnist_test_dataset = MnistDataset('mount/My Drive/Colab Notebooks/gan/mnist_data/mnist_test.csv') 

我們選擇測試資料集中一個資料來觀察影像的樣式。下面的程式碼選擇了第 20 個資料,索引數為 19

# pick a record
record = 19

# plot image and correct label
mnist_test_datset.plot_image(record)

可以看到影像看起來像 4,資料的標籤也確認確實是 4
Fig_13

下面來看一下訓練好的網路如何識別這個影像。下面的程式碼使用了 record19 的設定,來提取影像畫素值為 image_data。使用 forward() 函式將影像通過神經網路。

image_data = mnist_test_dataset[record][1]

# query from trained network
output = C.forward(image_data)

# plot output tensor
pandas.DataFrame(output.detach().numpy()).plot(kind='bar',legend=False, ylim=(0.1))

為了便於使用柱狀圖顯示,output 在包括到一個 DataFrame 之前被轉變為一個更簡單的 numpy 陣列。
Fig_14

10 個柱子是 10 個神經網路輸出節點的值。最大的值對應了節點 4,告訴我們網路認為這個影像是一個 4

不錯,神經網路正確的對測試影像進行了分類!

如果觀察更認真的話,可以看到其他的節點的輸出並不是 0。我們並不期望分類神經網路能產生一個明確的答案。實際上這次還認為這個影像也可能是一個 9,雖然可能性不如 4。返回看實際的影像,我們可以看到 94 的區別並不是很明顯。

你可以選擇其他的資料來進行嘗試。第 42 個資料是一個很好的多義影像(ambiguous image)。同樣的,看看你是否能發現網路識別錯誤的影像。我自己的訓練網路識別第 33 個影像是錯誤的,檢視原始影像,可以看到確實是一個書寫糟糕的數字。

簡單分類器的效能(Simple Classifier Performance)

一個檢視我們神經網路正確分類影像的能力的簡單方法,是對 MNIST 測試資料集所有的 10,000 個影像進行操作,計算多少個識別正確。我們對網路的輸出和影像本身的標籤進行比較來完成此項工作。

下面的程式碼設定一個變數 score 計數為 0,然後對整個資料進行處理,如果每次網路輸出與影像標籤相同,則增加 score 的值。

# test trained neural network on training data

score = 0
items = 0

for label, image_data_tensor, target_tensor in mnist_test_dataset:
    answer = C.forward(image_data_tensor).detach().numpy()
    if (answer.argmax() == label):
       score += 1
       pass
    item += 1
    
    pass
    
print(scores, items, score/items)

其中, answer.argmax() 程式碼是找到張量 answer 最大值的索引值。如果第一個值最大,則 argmax() 的輸出為 0。這是詢問 “哪個節點的值最大?”的一種簡潔方法。

我們最後輸出 score 分數,使用分數來確定神經網路多少個答案是正確的。
Fig_15

我獲得了 87% 的分數,由於網路很簡單,所以分數還不錯。

你可以嘗試是否可以通過超過 3 次 epochs 的訓練來改進這個分數;同時,如果你訓練不夠 3 次 epochs 將會發生什麼呢?

你可以線上探索我們開發的這個簡單的 MNIST 分類器程式碼:

相關文章