這篇文章中,我們將使用CNN構建一個Tensorflow.js模型來分辨手寫的數字。首先,我們通過使之“查看”數以千計的數字圖片以及他們對應的標識來訓練分辨器。然后我們再通過此模型從未“見到”過的測試數據評估這個分辨器的精確度。
一、運行代碼
這篇文章的全部代碼可以在倉庫TensorFlow.js examples 中的tfjs-examples/mnist 下找到,你可以通過下面的方式clone下來然后運行這個demo:
$ git clone https://github.com/tensorflow/tfjs-examples $ cd tfjs-examples/mnist $ yarn $ yarn watch
上面的這個目錄完全是獨立的,所以完全可以copy下來然后創建你個人的項目。
二、數據相關
這篇文章中,我們將會使用 MNIST 的手寫數據,這些我們將要去分辨的手寫數據如下所示:
為了預處理這些數據,我們已經寫了 data.js, 這個文件包含了Minsdata類,而這個類可以幫助我們從MNIST的數據集中獲取到任意的一些列的MNIST。
而MnistData這個類將全部的數據分割成了訓練數據和測試數據。我們訓練模型的時候,分辨器就會只觀察訓練數據。而當我們評價模型時,我們就僅僅使用測試數據,而這些測試數據是模型還沒有看見到的,這樣就可以來觀察模型預測全新的數據了。
這個MnistData有兩個共有方法:
- nextTrainBatch(batchSize): 從訓練數據中返回一批任意的圖片以及他們的標識。
- nextTestBatch(batchSize): 從測試數據中返回一批圖片以及他們的標識。
注意:當我們訓練MNIST分辨器時,應當注意數據獲取的任意性是非常重要的,這樣模型預測才不會受到我們提供圖片順序的干擾。例如,如果我們每次給這個模型第一次都提供的是數字1,那么在訓練期間,這個模型就會簡單的預測第一個就是1(因為這樣可以減小損失函數)。 而如果我們每次訓練時都提供的是2,那么它也會簡單切換為預測2並且永遠不會預測1(同樣的,也是因為這樣可以減少損失函數)。如果每次都提供這樣典型的、有代表性的數字,那么這個模型將永遠也學不會做出一個精確的預測。
三、創建模型
在這一部分,我們將會創建一個卷積圖片識別模型。為了這樣做,我們使用了Sequential模型(模型中最為簡單的一個類型),在這個模型中,張量(tensors)可以連續的從一層傳遞到下一層中。
首先,我們需要使用tf.sequential先初始化一個sequential模型:
const model = tf.sequential();
既然我們已經創建了一個模型,那么我們就可以添加層了。
四、添加第一層
我們要添加的第一層是一個2維的卷積層。卷積將過濾窗口掠過圖片來學習空間上來說不會轉變的變量(即圖片中不同位置的模式或者物體將會被平等對待)。
我們可以通過tf.layers.conv2d來創建一個2維的卷積層,這個卷積層可以接受一個配置對象來定義層的結構,如下所示:
model.add(tf.layers.conv2d({ inputShape: [28, 28, 1], kernelSize: 5, filters: 8, strides: 1, activation: 'relu', kernelInitializer: 'VarianceScaling' }));
讓我們拆分對象中的每個參數吧:
- inputShape。這個數據的形狀將回流入模型的第一層。在這個示例中,我們的MNIST例子是28 x 28像素的黑白圖片,這個關於圖片的特定的格式即[row, column, depth],所以我們想要配置一個[28, 28, 1]的形狀,其中28行和28列是這個數字在每個維度上的像素數,且其深度為1,這是因為我們的圖片只有1個顏色:
- kernelSize。划過卷積層過濾窗口的數量將會被應用到輸入數據中去。這里,我們設置了kernalSize的值為5,也就是指定了一個5 x 5的卷積窗口。
- filters。這個kernelSize的過濾窗口的數量將會被應用到輸入數據中,我們這里將8個過濾器應用到數據中。
- strides。 即滑動窗口每一步的步長。比如每當過濾器移動過圖片時將會由多少像素的變化。這里,我們指定其步長為1,這意味着每一步都是1像素的移動。
- activation。這個activation函數將會在卷積完成之后被應用到數據上。在這個例子中,我們應用了relu函數,這個函數在機器學習中是一個非常常見的激活函數。
- kernelInitializer。這個方法對於訓練動態的模型是非常重要的,他被用於任意地初始化模型的weights。我們這里將不會深入細節來講,但是 VarianceScaling (即這里用的)真的是一個初始化非常好的選擇。
五、添加第二層
讓我們為這個模型添加第二層:一個最大的池化層(pooling layer),這個層中我們將通過 tf.layers.maxPooling2d 來創建。這一層將會通過在每個滑動窗口中計算最大值來降頻取樣得到結果。
model.add(tf.layers.maxPooling2d({ poolSize: [2, 2], strides: [2, 2] }));
- poolSize。這個滑動池窗口的數量將會被應用到輸入的數據中。這里我們設置poolSize為[2, 2],所以這就意味着池化層將會對輸入數據應用2x2的窗口。
- strides。 這個池化層的步長大小。比如,當每次挪開輸入數據時窗口需要移動多少像素。這里我們指定strides為[2, 2],這就意味着過濾器將會以在水平方向和豎直方向上同時移動2個像素的方式來划過圖片。
注意:因為poolSize和strides都是2x2,所以池化層空口將會完全不會重疊。這也就意味着池化層將會把激活的大小從上一層減少一半。
六、添加剩下的層