
數據集及預處理
從這個例子開始,相當比例的代碼都來自於官方新版文檔的示例。開始的幾個還好,但隨后的程序都將需要大量的算力支持。Google Colab是一個非常棒的雲端實驗室,提供含有TPU/GPU支持的Python執行環境(需要在Edit→Notebook Settings設置中打開)。速度比不上配置優良的本地電腦,但至少超過平均的開發環境。
所以如果你的電腦運行速度不理想,建議你嘗試去官方文檔中,使用相應代碼的對應鏈接進入Colab執行試一試。
Colab還允許新建Python筆記,來嘗試自己的實驗代碼。當然這一切的前提,是需要你科學上網。
上一個例子已經完全使用了TensorFlow 2.0的庫來實現。但數據集仍然沿用了TensorFlow 1.x講解時所使用的樣本。
TensorFlow 2.0默認使用Keras的datasets類來管理數據集,包括Keras內置模型已經訓練好的生產數據集,和類似MNIST這種學習項目所用到的練習數據集。
使用Keras載入數據集同樣只是一行代碼:
(train_images, train_labels), (test_images, test_labels) = keras.datasets.mnist.load_data()
Keras.datasets默認是從谷歌網站下載數據集,以MNIST為例,數據下載地址是:https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz。文件下載之后,放置到~/.keras/datasets文件夾,以后執行程序的時候,會自動從本地讀取數據。數據保存路徑Linux/Mac都是如此,Windows同樣是在用戶主目錄,比如:c:\Users\Administrator.keras\datasets。
接着是數據預處理的問題,主要是從原始的圖片、標注,轉換為機器學習所需要的規范化之后的數據。我們在TensorFlow 1.x中所使用的數據實際是已經規范化之后的。我們可以使用Python3的交互模式,載入數據之后,查看一下數據:
$ python3
Python 3.7.3 (default, Mar 27 2019, 09:23:39)
[Clang 10.0.0 (clang-1000.11.45.5)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import input_data
>>> mnist = input_data.read_data_sets("data/", one_hot=True)
Extracting data/train-images-idx3-ubyte.gz
Extracting data/train-labels-idx1-ubyte.gz
Extracting data/t10k-images-idx3-ubyte.gz
Extracting data/t10k-labels-idx1-ubyte.gz
>>> mnist.train.images[0]
array([0. , 0. , 0. , 0. , 0. ,
...省略部分...
0. , 0. , 0. , 0. , 0. ,
0.9215687 , 0.9215687 , 0.9215687 , 0.9215687 , 0.9215687 ,
0.9843138 , 0.9843138 , 0.9725491 , 0.9960785 , 0.9607844 ,
0.9215687 , 0.74509805, 0.08235294, 0. , 0. ,
...省略部分...
0. , 0. , 0. , 0. , 0. ,
0. , 0. , 0. , 0. ], dtype=float32)
>>> mnist.train.labels[0]
array([0., 0., 0., 0., 0., 0., 0., 1., 0., 0.])
對於圖,就是28x28的二維數組,其中每一個數據,代表一個點,數據的值越接近1,代表這個點的顏色越接近白色;反之,則顏色越接近黑色。借用原文第四篇中的一幅圖來幫你回憶一下這個關系(上一篇中,圖片顯示部分的代碼,功能也是還原這組數據):

對於標簽,因為我們是識別為0-9共10個數字,是10個輸出的分類器。所以標簽組某一個值為1,表示圖像代表的手寫數字屬於該分類。同樣借用一下原圖:

如果想將這樣的分類數據轉成我們習慣的0-9數字,可以使用TensorFlow中內置的函數argmax:
...接着上面的交互模式繼續執行...
>>> import tensorflow as tf
>>> train_labels = tf.argmax(mnist.train.labels, 1)
>>> train_labels
<tf.Tensor: id=2, shape=(55000,), dtype=int64, numpy=array([7, 3, 4, ..., 5, 6, 8])>
現在的數據看起來很習慣了吧?更幸福的是,使用Keras的的分類器模型訓練,已經可以直接使用這樣的標簽數據了。
keras.datasets.mnist.load_data()所載入的樣本數據,跟TensorFlow 1.0所使用的數據有一些區別。其中的圖像數據並未做規范化,仍然是通常BMP圖像中的0-255的字節數據方式。標簽數據,也直接是我們更熟悉的0-9數字標簽。這兩個微小的變化提現了TensorFlow理念的轉變,TensorFlow更貼近真實的工作環境了。
我們同樣在交互模式來看一下這兩組數據:
$ python3
Python 3.7.3 (default, Mar 27 2019, 09:23:39)
[Clang 10.0.0 (clang-1000.11.45.5)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from tensorflow import keras
>>> (train_images, train_labels), (test_images, test_labels) = keras.datasets.mnist.load_data()
>>> train_images[0]
array([[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0],
...省略部分...
[ 0, 0, 0, 0, 0, 0, 0, 0, 30, 36, 94, 154, 170,
253, 253, 253, 253, 253, 225, 172, 253, 242, 195, 64, 0, 0,
0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 49, 238, 253, 253, 253, 253,
253, 253, 253, 253, 251, 93, 82, 82, 56, 39, 0, 0, 0,
0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 18, 219, 253, 253, 253, 253,
253, 198, 182, 247, 241, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 0, 80, 156, 107, 253, 253,
205, 11, 0, 43, 154, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 1, 154, 253,
90, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0],
...省略部分...
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0]], dtype=uint8)
>>> train_labels
array([5, 0, 4, ..., 5, 6, 8], dtype=uint8)
就像從上一個系列中我就一直強調的,TensorFlow的使用越來越容易,成熟的模型越來越多。難度更多的會集中在樣本的選取和預處理,所以一定要多關注對原始數據的理解。
TensorFlow 2.0可以直接處理如上所示的標簽數據。圖像的數據則仍然需要規范化,圖像數據的取值范圍我們很清楚是0-255,規范化也很簡單:
# 數據規范化為0-1范圍的浮點數
train_images = train_images / 255.0
test_images1 = test_images / 255.0
下面看看完整的代碼:
#!/usr/bin/env python3
# tensorflow庫
import tensorflow as tf
# tensorflow 已經內置了keras
from tensorflow import keras
# 引入繪圖庫
import matplotlib.pyplot as plt
# 第一次使用會自動從網上下載mnist的訓練樣本
(train_images, train_labels), (test_images, test_labels) = keras.datasets.mnist.load_data()
def plot_image(i, imgs, labels, predictions):
# TensorFlow 2.x的數據已經是0-255范圍,無需再次還原
image = imgs[i]
label = labels[i]
prediction = tf.argmax(predictions[i])
plt.grid(False)
plt.xticks([])
plt.yticks([])
# 繪制樣本圖
plt.imshow(image)
# 顯示標簽值,對比顯示預測值和實際標簽值
plt.xlabel("predict:{} label:{}".format(prediction, label))
def show_samples(num_rows, num_cols, images, labels, predictions):
num_images = num_rows*num_cols
plt.figure('Predict Samples', figsize=(2*num_cols, 2*num_rows))
# 循環顯示前num_rows*num_cols副樣本圖片
for i in range(num_images):
plt.subplot(num_rows, num_cols, i+1)
plot_image(i, images, labels, predictions)
plt.show()
# 數據規范化為0-1范圍的浮點數
train_images = train_images / 255.0
test_images1 = test_images / 255.0
# 定義神經網絡模型
model = keras.Sequential([
# 輸入層把28x28的2維矩陣轉換成1維
keras.layers.Flatten(input_shape=(28, 28)),
keras.layers.Dense(128, activation='relu'),
keras.layers.Dense(10, activation='softmax')
])
# 編譯模型
model.compile(optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
# 使用訓練集數據訓練模型
model.fit(train_images, train_labels, epochs=5)
# 使用測試集樣本驗證識別准確率
test_loss, test_acc = model.evaluate(test_images1, test_labels)
print('\nTest accuracy:', test_acc)
# 完整預測測試集樣本
predictions = model.predict(test_images1)
# 顯示測試樣本預測結果
show_samples(4, 6, test_images, test_labels, predictions)
代碼中,圖片顯示的部分也對應取消了把規范化的數據還原為0-255原始圖像數據的過程。其它部分則並沒有什么變化。
現在,MNIST已經是完整的TensorFlow 2.0的原生代碼了。繞了這么遠,希望能幫你更深刻理解這些代碼背后的工作。
卷積和池化
在前一個系列中,卷積和池化部分據很多反饋說是一個很嚴重的門檻。有讀者說完全算不清每一層和相連接的層之間的數據關系。
在TensorFlow 2.0中,就像前面說過的,這種層與層之間的數據維度模型完全是無需自己計算的,Keras會自動匹配這種數據關系。因此單純從這一點上說,在TensorFlow 2.0中,無論多復雜的模型構建都不會再成為問題。只是會多一點其它的擔心,那就是這樣隱藏起來機器學習本質上的數學模型,究竟對程序員來說是好事還是壞事?
TensorFlow 1.x中使用卷積神經網絡解決MNIST問題的講解在前系列第六篇。篇幅很長,這里就不重貼了。在TensorFlow 2.0中,則只是一個函數幾行代碼(請盡量跟TensorFlow 1.x版本的代碼對應着看。對卷積、池化的概念已經忘記的也強烈建議去前系列復習一下):
# 定義卷積池化神經網絡模型
model = keras.Sequential([
keras.layers.Conv2D(32, (5, 5), strides=(1, 1),
padding='same', activation='relu'),
keras.layers.MaxPooling2D(pool_size=(2, 2),
strides=(2, 2), padding='same'),
keras.layers.Conv2D(64, (5, 5), strides=(1, 1),
padding='same', activation='relu'),
keras.layers.MaxPooling2D(pool_size=(2, 2),
strides=(2, 2), padding='same'),
keras.layers.Flatten(), # 下面的神經網絡需要1維的數據
keras.layers.Dense(1024, activation='relu'),
keras.layers.Dropout(0.5),
keras.layers.Dense(10, activation='softmax')
])
Keras讓模型構建的過程變得極其容易。
從原理上說,卷積是對圖像的二維數據做掃描,還需要指定圖像的色深。所以在樣本預處理的階段,我們還要對其做一個變形:
# 卷積需要2維數據,還需要指定色深,因此是(樣本數,長,寬,色深)
train_images = train_images.reshape(train_labels.shape[0], 28, 28, 1)
test_images1 = test_images1.reshape(test_labels.shape[0], 28, 28, 1)
訓練集的樣本我們直接用變形后的數據替代了原始樣本。測試集則另外使用了一個變量保留了原始的測試集,這是因為我們顯示測試集圖片的時候,使用原始數據集顯然更方便。
實際上整個代碼只有這么兩點區別,不過為了你練習的時候方便,還是把完整代碼貼一遍:
#!/usr/bin/env python3
# tensorflow庫
import tensorflow as tf
# tensorflow 已經內置了keras
from tensorflow import keras
# 引入繪圖庫
import matplotlib.pyplot as plt
# 第一次使用會自動從網上下載mnist的訓練樣本
(train_images, train_labels), (test_images, test_labels) = keras.datasets.mnist.load_data()
# 數據路徑:~/.keras/datasets/mnist.npz
def plot_image(i, imgs, labels, predictions):
image = imgs[i]
label = labels[i]
prediction = tf.argmax(predictions[i])
plt.grid(False)
plt.xticks([])
plt.yticks([])
# 繪制樣本圖
plt.imshow(image)
# 顯示標簽值
plt.xlabel("predict:{} label:{}".format(prediction, label))
def show_samples(num_rows, num_cols, images, labels, predictions):
num_images = num_rows*num_cols
plt.figure('Predict Samples', figsize=(2*num_cols, 2*num_rows))
# 循環顯示前4*6副訓練集樣本圖片
for i in range(num_images):
plt.subplot(num_rows, num_cols, i+1)
plot_image(i, images, labels, predictions)
plt.show()
# 數據規范化為0-1范圍的浮點數
train_images = train_images / 255.0
test_images1 = test_images / 255.0
# 卷積需要2維數據,還需要指定色深,因此是(樣本數,長,寬,色深)
train_images = train_images.reshape(train_labels.shape[0], 28, 28, 1)
test_images1 = test_images1.reshape(test_labels.shape[0], 28, 28, 1)
# 定義卷積池化神經網絡模型
model = keras.Sequential([
keras.layers.Conv2D(32, (5, 5), strides=(1, 1),
padding='same', activation='relu'),
keras.layers.MaxPooling2D(pool_size=(2, 2),
strides=(2, 2), padding='same'),
keras.layers.Conv2D(64, (5, 5), strides=(1, 1),
padding='same', activation='relu'),
keras.layers.MaxPooling2D(pool_size=(2, 2),
strides=(2, 2), padding='same'),
keras.layers.Flatten(), # 下面的神經網絡需要1維的數據
keras.layers.Dense(1024, activation='relu'),
keras.layers.Dropout(0.5),
keras.layers.Dense(10, activation='softmax')
])
# 編譯模型
model.compile(optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
# 使用訓練集數據訓練模型
model.fit(train_images, train_labels, epochs=3)
# 使用測試集樣本驗證識別准確率
test_loss, test_acc = model.evaluate(test_images1, test_labels)
print('\nTest accuracy:', test_acc)
# 完整預測測試集樣本
predictions = model.predict(test_images1)
# 顯示測試樣本預測結果
show_samples(4, 6, test_images, test_labels, predictions)
程序執行的時候,在控制台的輸出信息類似下面:
$ chmod +x mnist-conv-maxpool-v2.py
$ ./mnist-conv-maxpool-v2.py
Epoch 1/3
60000/60000 [==============================] - 141s 2ms/sample - loss: 0.1139 - accuracy: 0.9643
Epoch 2/3
60000/60000 [==============================] - 143s 2ms/sample - loss: 0.0417 - accuracy: 0.9869
Epoch 3/3
60000/60000 [==============================] - 138s 2ms/sample - loss: 0.0312 - accuracy: 0.9904
10000/10000 [==============================] - 7s 659us/sample - loss: 0.0289 - accuracy: 0.9903
Test accuracy: 0.9903
在樣本集的測試上,卷積神經網絡的版本可以達到超過99%的正確率。
這個正確率,只進行了3次的訓練迭代,當然因為卷積神經網絡模型的復雜,這3次的訓練就遠遠比上一例中的5次訓練速度更慢。
(待續...)
