這一節(jié)將使用 TensorFlow 官方教程 的一個例子——Basic classification: Classify images of clothing——基礎(chǔ)分類:衣物圖片分類,訓(xùn)練一個神經(jīng)網(wǎng)絡(luò)模型來分類像運動鞋和襯衫這樣的衣服的圖像。
有了《TensorFlow 2.x - MNIST 圖像分類(1)》
和《TensorFlow 2.x - MNIST 圖像分類(2)》這兩節(jié)的鋪墊,本節(jié)將顯得比較簡單。
一、加載數(shù)據(jù)
在之前的文章中,我們都是使用 MNIST,但是 MNIST 太簡單了,只需要幾分鐘的訓(xùn)練,準(zhǔn)確率就可以達(dá)到 99% 以上,并且大部分圖片可能只需要幾個像素點就可以區(qū)分開來。真實世界千變?nèi)f化,我們需要更真實的圖片。
本次使用的數(shù)據(jù)集是 Fashion MNIST。該數(shù)據(jù)集由 70,000 張黑白圖片構(gòu)成,每張圖片大小為 28x28,圖片數(shù)量和大小與經(jīng)典 MNIST 完全相同,但 Fashion MNIST 是由十類服飾圖片構(gòu)成,MNIST 是手寫數(shù)字。Fashion MNIST 更有挑戰(zhàn)性,更適合用來驗證算法。Fashion-MNIST 的目的是要代替 MNIST。

廢話不多說,先下載四個數(shù)據(jù)集:
- train-images-idx3-ubyte.gz,訓(xùn)練集圖片,60,000張,26 MBytes;
- train-labels-idx1-ubyte.gz,訓(xùn)練集標(biāo)簽,60,000張,29 KBytes;
- t10k-images-idx3-ubyte.gz,測試集圖片,10,000張,4.3 MBytes;
- t10k-labels-idx1-ubyte.gz,測試集標(biāo)簽,10,000張,5.1 KBytes。
下載后,加載數(shù)據(jù):
import tensorflow as tf
from tensorflow import keras
import matplotlib.pyplot as plt
import os
import gzip
import numpy as np
def load_data(data_folder):
files = ['train-labels-idx1-ubyte.gz', 'train-images-idx3-ubyte.gz',
't10k-labels-idx1-ubyte.gz', 't10k-images-idx3-ubyte.gz']
paths = []
for f_name in files:
paths.append(os.path.join(data_folder, f_name))
with gzip.open(paths[0], 'rb') as lb_path:
y_train = np.frombuffer(lb_path.read(), np.uint8, offset=8)
with gzip.open(paths[1], 'rb') as img_path:
x_train = np.frombuffer(img_path.read(), np.uint8, offset=16).reshape(len(y_train), 28, 28)
with gzip.open(paths[2], 'rb') as lb_path:
y_test = np.frombuffer(lb_path.read(), np.uint8, offset=8)
with gzip.open(paths[3], 'rb') as img_path:
x_test = np.frombuffer(img_path.read(), np.uint8, offset=16).reshape(len(y_test), 28, 28)
return (x_train, y_train), (x_test, y_test)
(train_images, train_labels), (test_images, test_labels) = load_data(os.path.dirname(__file__) + '/data/fashion')
這里的數(shù)據(jù)加載方法與 TensorFlow 官方教程上的不同,因為官方教程加載的數(shù)據(jù)需要科學(xué)上網(wǎng),如果你能科學(xué)上網(wǎng),可以使用如下官方教程,替代上述方法:
(train_images, train_labels), (test_images, test_labels) = keras.datasets.fashion_mnist.load_data()
二、數(shù)據(jù)預(yù)處理
數(shù)據(jù)格式如下:
train_images.shape # (60000, 28, 28)
len(train_labels) # 60000
train_labels # ([9, 0, 0, ..., 3, 0, 5], dtype=uint8)
test_images.shape # (10000, 28, 28)
len(test_labels) # 10000
然后我們查看第一張圖片:
plt.figure()
plt.imshow(train_images[0])
plt.show()
print(train_labels[0]) # 9

圖片大小 28x28,每個像素值取值范圍 0-255。標(biāo)簽是整數(shù),取值范圍 0-9,與實際的服飾類別對應(yīng)關(guān)系如下。
| Label | Class |
|---|---|
| 0 | T-shirt/top (T恤) |
| 1 | Trouser(褲子) |
| 2 | Pullover(套衫) |
| 3 | Dress (裙子) |
| 4 | Coat(外套) |
| 5 | Sandal(涼鞋) |
| 6 | Shirt(汗衫) |
| 7 | Sneaker(運動鞋) |
| 8 | Bag(包) |
| 9 | Ankle boot(短靴) |
每個圖像都映射到一個標(biāo)簽。上圖的標(biāo)簽是 9,所以圖片中應(yīng)該是 Ankle boot (短靴),雖然沒看出哪里短。
class_names = ['T-shirt/top', 'Trouser', 'Pullover', 'Dress', 'Coat', 'Sandal', 'Shirt', 'Sneaker', 'Bag', 'Ankle boot']
圖片的每個像素值在 0-255 之間,需要轉(zhuǎn)為 0-1。訓(xùn)練集和測試集都需要經(jīng)過相同的處理。
train_images = train_images / 255.0
test_images = test_images / 255.0
顯示前 25 張圖片與標(biāo)簽的對應(yīng)關(guān)系:
# show first 25 images and labels
plt.figure(figsize=(10, 10))
for i in range(25):
plt.subplot(5, 5, i + 1)
plt.xticks([])
plt.yticks([])
plt.imshow(train_images[i], cmap=plt.cm.binary)
plt.xlabel(class_names[train_labels[i]])
plt.show()

三、搭建神經(jīng)網(wǎng)絡(luò)
搭建神經(jīng)網(wǎng)絡(luò)分為兩步,首先配置模型的每一層,然后編譯模型。
配置模型層
神經(jīng)網(wǎng)絡(luò)的基本構(gòu)成是網(wǎng)絡(luò)層 (layer),大部分深度學(xué)習(xí)網(wǎng)絡(luò)都由多個簡單的 layers 構(gòu)成。
model = keras.Sequential([
keras.layers.Flatten(input_shape=(28, 28)),
keras.layers.Dense(128, activation='relu'),
keras.layers.Dense(10, activation='softmax')
])
網(wǎng)絡(luò)的輸入層 tf.keras.layers.Flatten 將輸入的圖片從 28 × 28 的二維數(shù)組(28 × 28 像素)轉(zhuǎn)為 784 的一維數(shù)組,這一層的作用僅僅是將每一行值平鋪在一行,沒有參數(shù)需要學(xué)習(xí)。
接下來是 2 層 tf.keras.layers.Dense,即全連接層 (Fully Connected, FC),第一層 Dense 有 128 個神經(jīng)元。第二層有 10 個神經(jīng)元,經(jīng)過 softmax 后,返回了和為 1 長度為 10 的概率數(shù)組,每一個數(shù)分別代表當(dāng)前圖片屬于分類 0-9 的概率。
編譯模型
在開始訓(xùn)練前,模型需要編譯 (Compile) 并設(shè)置一些參數(shù):
- Loss function - 損失函數(shù),訓(xùn)練時評估模型的正確率,希望最小化這個函數(shù),往正確的方向訓(xùn)練模型。
- Optimizer - 優(yōu)化器算法,更新模型參數(shù)的算法。
- Metrics - 指標(biāo),用來監(jiān)視訓(xùn)練和測試步數(shù),下面的例子中使用 accuracy,即圖片被正確分類的比例。
model.compile(optimizer='adam',
loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
metrics=['accuracy'])
四、訓(xùn)練模型
訓(xùn)練神經(jīng)網(wǎng)絡(luò),通常有以下幾個步驟。
- 傳入訓(xùn)練集數(shù)據(jù),
train_images和train_labels。 - 訓(xùn)練模型,關(guān)聯(lián)圖片和標(biāo)簽。
- 使用測試集
test_images預(yù)測,并用標(biāo)簽test_labels進行驗證。
Feed the model
調(diào)用 model.fit 方法開始訓(xùn)練。
model.fit(train_images, train_labels, epochs=10)
Epoch 1/10
1875/1875 [==============================] - 5s 3ms/step - loss: 0.5025 - accuracy: 0.8243
..............................
Epoch 7/10
1875/1875 [==============================] - 5s 3ms/step - loss: 0.2696 - accuracy: 0.9001
Epoch 8/10
1875/1875 [==============================] - 5s 3ms/step - loss: 0.2576 - accuracy: 0.9035
Epoch 9/10
1875/1875 [==============================] - 6s 3ms/step - loss: 0.2491 - accuracy: 0.9075
Epoch 10/10
1875/1875 [==============================] - 6s 3ms/step - loss: 0.2396 - accuracy: 0.9112
經(jīng)過 10 輪訓(xùn)練后,準(zhǔn)確率達(dá)到了 91.12%。
評估準(zhǔn)確率
使用測試集數(shù)據(jù)看看模型訓(xùn)練的結(jié)果:
test_loss, test_acc = model.evaluate(test_images, test_labels, verbose=2)
print('\nTest accuracy:', test_acc) # Test accuracy: 0.8865000009536743
測試集的準(zhǔn)確率低于訓(xùn)練集,訓(xùn)練集和測試集準(zhǔn)確率之間的差距代表模型過擬合 (overfitting)。即對于訓(xùn)練中沒有見過的新數(shù)據(jù),模型表現(xiàn)差。如果你對過擬合不熟悉,可以看看《過擬合(Overfitting) 與 Dropout》,沒錯,也是我的文章。
預(yù)測
使用 predict 函數(shù)進行預(yù)測,并顯示圖片,打印預(yù)測結(jié)果及標(biāo)簽。
# 預(yù)測
predictions = model.predict(test_images)
idx = 20
plt.figure()
plt.imshow(train_images[idx]) # 顯示圖片
plt.show()
print(predictions[idx])
predict = int(np.argmax(predictions[idx]))
print('predict: ' + class_names[predict]) # 預(yù)測結(jié)果
print('label: ' + class_names[test_labels[idx]]) # 標(biāo)簽
看一下第一個預(yù)測結(jié)果。
[3.1960328e-09 1.2830121e-11 1.0000000e+00 1.5766790e-18 1.2614347e-16
7.3473108e-24 1.2678178e-11 2.2807704e-21 2.7815026e-11 3.0496564e-14]
predict: Pullover
label: Pullover
結(jié)果是一個長度為 10 的數(shù)組,數(shù)組的每個值代表模型認(rèn)為是對應(yīng)標(biāo)簽的概率。上述例子中 9 號標(biāo)簽的概率最大 1.0000000e+00,即模型認(rèn)為該圖片是 9 號標(biāo)簽的概率是 100%。

預(yù)測結(jié)果和標(biāo)簽結(jié)果相同,都是 Pullover。