透過RNN手寫辨識練習明白batch_size的使用規範(附code)

王柏鈞
12 min readFeb 20, 2020

--

RNN學習歷程part3,TF2.0-RNN(遞迴神經網路)實作練習

#機器學習百日馬拉松12

文章類型:技術深化

完整程式碼連結(在此),歡迎下載。

目錄:

  1. 了解資料-MNIST與文字數位化
  2. 模型架構(with Keras Functional API)
  3. 實作(4個Steps完成訓練)
  4. 了解為什麼不要在Input Layer設置batch_size

第一節:了解資料-MNIST與文字數位化

我們要使用的資料集是在機器學習領域的入門經典-MNIST手寫數字資料集,他是由CNN的創建者 Yann Lecun與其團隊收集的資料集。在裡面包含了60000張的手寫辨識影像,每一張的解析度都是28x28。雖然畫質很低,但他可以用來解決我們很常見的問題:

如何讓電腦看懂紙本文件。

在過去,我們想要把紙本文件轉換成數位資產,會需要大量的人力進行登打,對於郵務或銀行產業,這種惱人的登打更是每日固定的庶務。
為了解決這個問題,我們可以使用MNIST來訓練一個能夠理解紙本手寫數字的人工智慧模型,讓電腦直接看懂不同使用者所寫的數字,自動化生成表單或文件並進行存檔。這可以讓作業效率提升,也能夠把陳年的舊文件轉換成數位資產。

第二節:模型架構(with Keras Functional API)

如上圖所示就是我們的模型架構,模型的參數說明可以參考上一篇(RNN(遞迴神經網路)技術說明)。首先要介紹的是SimpleRNN這個Layer,Input Tensor(輸入層)所承接的資料會連接到Simple RNN的輸入。

SimpleRNN就是最單純的RNN,與Dense的單純神經元相比,他多了一個隱藏層用以儲存歷史資訊,這讓他在進行輸出時,不僅是考慮當前資訊、也會參考歷史資訊,進而取得更好的輸出。

Note: RNN就只是在 NN(神經網路 )中為了使用歷史資訊而設計的一個特別神經層。

當你以後熟悉運用深度學習的各種技巧你就可以把RNN和CNN隨意的混合使用了。RNN和CNN都只是功能取向的選擇而已。

第三節:簡單的實作(4個Steps完成訓練)

接下來要介紹如何使用Tensorflow來完成這個模型的建立與訓練。

Step1: import Library以及取得資料

import tensorflow as tf
from tensorflow.python import keras
from keras.layers import Input, Dense, SimpleRNN, RNN, LSTM
from keras.models import Model
import numpy as np
import matplotlib.pyplot as plt

import的部分不做太多解釋,接下來需要取得資料,在Keras裡面已經收錄了MNIST這個資料集,我們不需要自行下載。

mnist = tf.keras.datasets.mnist
(X_train, y_train), (X_test, y_test) = mnist.load_data()
# 標準化數據
X_train = X_train/255.0
X_test = X_test/255.0
print(X_train.shape)
print(X_train[0].shape)

值得注意的是在上面的程式碼第4行有一個 # 標準化數據的註解,在其下的兩行程序X_train = X_train/255.0 X_test = X_test/255.0 則是用來標準化MNIST資料集影像。

那為什麼要標準化呢?

因為對神經網路這種深度學習的技術來說,數值的大小會影響到梯度下降時的權重調整,所以所有資料的數值需要被限制在0~1之間。否則會有很差的訓練效果。

如下所示,在沒有標準化的情況下訓練了3個epochs,最後的accuracy(正確率)僅有0.5208。

訓練了1個 epoch表示把所有資料都放進神經網路進行計算1次,以此類推。

但如果進行標準化,結果則如下圖所示。(accuracy(正確率)可達0.9509)

如果要對影像標準化,因為其中的每一個像素其值都是在0~255之間的一個數字,所以我們只要把每一個像素的值都除以255就可以讓所有數字被收斂到0~1之間,完成標準化。

Step2: 設定batch_size以及建構RNN模型

首先要設定batch_size,batch_size表示單次訓練中,要投入多少資料給神經網路。

sizeOfBatch = 100

那為什麼不要全部資料都進去就好?

這是為了讓梯度下降法有更好的效果。一個神經網路就像一條曲折的山稜線,梯度下降法就像是你在山稜線上的某一點,然後四下觀望後開始往下走一樣。

這個移動的過程分成兩個步驟:

  1. 觀察四周狀況,決定移動方向。
  2. 往前走一小步。

所以如果你計算完所有的資料,才開始往下走,那會有兩個問題:

  1. 因為你要觀察很久才走一步,儘管這一步是最有效率的一步,但你的一小步還是一小步,所以你還是會走得很慢。
  2. 你會很容易走進離你最近的局域最低點(Local Minimun)。然後就卡在那裏了。

那為什麼設定 batch_size就可以避免呢?

使用batch_size的時候,我們只會觀察相當於batch_size數量的樣本並求其梯度的平均,也就是只看某些方向然後就開始移動,儘管還是會下降,但它移動的方向並不一定是最佳解,甚至可能會走錯。

你可以想像設定batch_size後,你的移動就變成不由自主地橫衝直撞,雖然還是可以稍微控制要往哪裡移動,但你大部分都會被逼著隨便亂跑,但看似沒效率的情況,反而避免了掉入局域最低點(Local Minimun)的情況。

接下來要建構模型。

建構模型的方法其實很簡單,就是用看似相乘的方式來把資料傳遞就好。如下所示:

inputs = Input(batch_shape=(batch_size, 28, 28))
RNN1 = SimpleRNN(units=128, activation='tanh', return_sequences=False, return_state=False)
RNN1_output= RNN1(inputs)

我們先看到第一行inputs = Input(batch_shape=(sizeOfBatch, 28, 28))

他是把Input Layer的輸出指定給inputs這個變數。然後第二行RNN1 = SimpleRNN(units=128, activation=’tanh’, return_sequences=False, return_state=False)

則是宣告RNN1這個變數相當於SimpleRNN Layer。

所以創建神經層其實很簡單,就是要把你想使用的Layer指定到變數上。

Note:Input Layer永遠是模型的第一層。

我們接下來看到第三行RNN1_output = RNN1(inputs)

這邊相當於在RNN1這個SimpleRNN投入inputs這個Input Layer的輸出,而RNN1的輸出則被指定給RNN1_output這個變數。那麼我們依樣畫葫蘆地把其它神經層連接起來,如下所示:

Dense1_output = Dense(128, activation='relu')(RNN1_output)
Dense2_output = Dense(64, activation='relu')(Dense1_output)
output = Dense(10, activation='softmax')(Dense2_output)

而最後還有一個步驟,就是宣告一個模型。宣告模型的敘述如下所示:

rnn = Model(inputs=inputs, outputs=output)

這個敘述會把模型儲在rnn這個變數裡,以後我們只要呼叫rnn這個變數就可以取得整個模型了。

而Model這個函式裡面需要定義inputs和outputs,也就是模型的輸入和輸出。

  • inputs永遠是Input Layer的輸出(ex: inputs)
  • outputs則是最後一個神經層的輸出

如此一來就完成了模型的建構。

Step3: 設定最佳化器與編譯模型

opt = tf.keras.optimizers.Adam(lr=0.001, decay=1e-6)
rnn.compile(optimizer=opt,
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
rnn.summary()

首先來看最佳化器,在前面解釋為什麼要有batch_size的時候有提到過,他其實就是用來修正當我們使用batch_size時那橫衝直撞的行為,而我們使用的是最經典的adam最佳化器。

學習率暫且不談,我們先看損失函數的定義:

loss=’sparse_categorical_crossentropy’

這個損失函數有孿生兄弟categorical_crossentropy

他們之間的差異就在於如果使用sparse_categorical_crossentropy就不把y值做one-hot encoding。

Note: lr建議在0.001~0.000001之間調整。

而最後的rnn.summary()則會輸出我們整個模型的架構,如下所示:

Step4: 訓練模型

rnn.fit(X_train, y_train, epochs=3, validation_data=(X_test, y_test), batch_size=sizeOfBatch)

我們使用fit()函數來進行訓練,這邊我們必須設定batch_size=sizeOfBatch。因為我們一開始有把Input Layer設batch_size,當你沒有為fit()函數指定batch_size的時候,它就會使用預設的batch_size=32來進行訓練,但是這會跟前面衝突以至於發生下面的錯誤。

要注意的有兩個地方,一個是[100] vs. [32],另一個是dense_18_loss。我把他們用螢光筆塗起來,如下所示:

dense_18_loss表示是在dense_18出現錯誤,那你可以去翻summary找到底dense18是哪一層:

從上圖可以得知,dense18是最後一層輸出層,那我們就會想,ㄟ我輸出層不是只有10個units,跟100有什麼關係。但是你仔細往summary一看,會發現它是維度(100, 10)的張量(tensor)。前面的100就是batch_size。

其實你仔細一想就會發現這道理很簡單,因為不管你怎麼用 flatten或是pooling來處理 tensor,batch_size是不會被更動的。

總之,如果你沒有把batch_size統一,Input Layer的batch_size就只能設32或是None(預設)(建議是 None,請看第四節)

訓練結果

完成訓練後的準確率如上所示,到這裡我們的MNIST就訓練完了。

第四節:了解為什麼不要在Input Layer設置batch_size

我們接下來進行預測,我們想要預測Test data的第一筆資料,所以指定

ans = rnn.predict(X_test[0])

但發現出現Error,如下所示

這是因為predict()函數有指定輸入資料必須是3維,而這裏我們只有指定一筆資料,所以會出現Error。

這邊看起來最直覺的解決方法就是

使用np.expand_dims(X_test[0], axis=0)增加維度

但會出現新的錯誤,如下所示:

這是因為沒有考慮到Input Layer的tensor不對稱,所以你還必須改成:

ans = rnn.predict(X_test[0:100], batch_size=sizeOfBatch)

同時設置predict()函數的batch_size為sizeOfBatch與Input Layer同步,並且輸入的資料必須是sizeOfBatch的整數倍。

或者更直接一點,不要在Input Layer設置batch_size。那麼只要是3維的資料,任意batch_size都是可以被接受的。

所以不要在 Input Layer設置 batch_size。

完整程式碼連結(在此),歡迎下載。

結論

偶爾就會遇到有學員跑來問各種變數的向量問題,希望這一篇文章可以讓你知道batch_size的使用規範。

今天的課程評鑑問題是:

  1. 一個batch裡面有多少筆資料?
  2. 幾個batch是一個epoch。
  3. 如果你的測試資料總數小於batch_size,那你在predict的時候會出現error嗎?

歡迎自行思考或在下面一起討論喔。

--

--

No responses yet