【深度學習與TensorFlow 2.0】圖片分類——多層感知機


:在很長一段時間,MNIST數據集都是機器學習界很多分類算法的benchmark,這個數據集被Hinton稱為機器學習界的果蠅(學生物的同學應該都知道果蠅這種模式生物對生物學研究的重要性)。初學深度學習,在這個數據集上訓練一個有效的卷積神經網絡就相當於學習編程的時候打印出一行“Hello World!”。下面基於與MNIST數據集非常類似的另一個數據集Fashion-MNIST數據集來構建一個多層感知機,並介紹一些TF構建神經網絡時的基本概念。

 

0. Fashion-MNIST數據集


MNIST數據集在機器學習算法中被廣泛使用,下面這句話能概況其重要性和地位:

In fact, MNIST is often the first dataset researchers try. "If it doesn't work on MNIST, it won't work at all", they said. "Well, if it does work on MNIST, it may still fail on others."

Fashion-MNIST數據集是由ZALANDO實驗室制作,發表於2017年。在該數據集的介紹中,列出了MNIST數據集的不足之處:

  1. MNIST太容易了,卷積神經網絡可以達到99.7%的正確率,傳統的分類算法也能很輕易的達到97%的正確率;
  2. 被過度使用了;
  3. 不能很好的代表現代計算機視覺任務.

Fashion-MNIST數據集的規格(28×28像素的灰度圖片,10個不同類型),數據量(訓練集包括60000張圖片,測試集包括10000張圖片)都與MNIST保持一致。差別是,MNIST的數據是手寫數字0-9,Fashion-MNIST的數據是不同類型的衣服和鞋的圖片。

下面是該數據集中的標簽:

Label Description
0 T-shirt/top
1 Trouser
2 Pullover
3 Dress
4 Coat
5 Sandal
6 Shirt
7 Sneaker
8 Bag
9 Ankle boot

下面是一些例子:

圖0-1:Fashion-MNIST 中的圖片示例

 

1. 多層感知機


線性回歸相當於是單層網絡,深度學習主要關注多層網絡。多層感知機在單層網絡的基礎上引入了一到多個隱藏層,隱藏層位於輸入層和輸出層之間。

1.1 導入依賴的包

下面導入了一些必要的 package(包括前面安裝的 tensorflow-datasets),並且輸出了當前使用的 TensorFlow(TF) 的版本號。如果不是最新的 TF,可以使用下面的命令安裝最新的TF。

pip install tensorflow==2.1.0  # 安裝最新版的TF

 

Tips: 如果國外的源安裝比較慢,可以使用下面的命令來指定國內的源安裝:

# 利用清華大學 pypi 鏡像更新pip
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pip -U
# 將清華大學 pypi 鏡像設為默認源 pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple

 

 1 from __future__ import absolute_import, division, print_function, unicode_literals
 2 
 3 # TensorFlow and tf.keras
 4 import tensorflow as tf
 5 from tensorflow import keras
 6 
 7 # Helper libraries
 8 import numpy as np
 9 import matplotlib.pyplot as plt
10 
11 print(tf.__version__)  # 2.1.0

 

1.2 導入數據集

TF中包含了調用一些常用數據集的API:boston_housing, cifar10, cifar100, fashion_mnist, imdb, mnist, reuters

Fashion-MNIST數據集與MNIST數據集相同,train_dataset 中包含60000張圖片用來做訓練集,test_dataset 中包含10000張圖片用來做測試集.

fashion_mnist = keras.datasets.fashion_mnist

(train_images, train_labels), (test_images, test_labels) = fashion_mnist.load_data()  # 得到4個Numpy Array

下面是所有衣服或鞋的名稱,其順序與其前面列出的該數據集的標簽順序相同:

class_names = ['T-shirt/top', 'Trouser', 'Pullover', 'Dress', 'Coat', 
               'Sandal',      'Shirt',   'Sneaker',  'Bag',   'Ankle boot']

 

可以利用一些常用的函數來查看數據集的信息:

print(type(train_images))  # <class 'numpy.ndarray'>

# 整個訓練集的維度信息
print(train_images.shape)  # (60000, 28, 28)
# 總樣本數 print(len(train_labels)) # 6000
# 單個樣本的維度信息 print(train_images[0].shape) # (28, 28)

 

1.3 數據的預處理

原始數據中圖片的每個像素由[0, 255]區間上的整數表示。為了更好的訓練模型,需要將所有的值都標准化到區間[0, 1]

- 經過測試,如果不做這一步,最終在測試集的准確率會下降大概8% - 5%。

train_images = train_images / 255.0

test_images = test_images / 255.0

預處理后的數據同樣可以表示一張圖片,下面取出測試集中的一張圖片並顯示:

1 plt.figure(figsize=(5, 5))
2 plt.imshow(test_images[0], cmap=plt.cm.binary)
3 plt.colorbar()
4 plt.grid(False)
5 # plt.show()
6 plt.tight_layout()
7 plt.savefig('demo_single_img.png', dpi=100)

圖1-1:標准化后的圖片

 

取出訓練集中前25張圖片:

 1 plt.figure(figsize=(10,10))
 2 for i in range(25):
 3     plt.subplot(5,5,i+1)
 4     plt.xticks([])
 5     plt.yticks([])
 6     plt.grid(False)
 7     plt.imshow(train_images[i], cmap=plt.cm.binary)
 8     plt.xlabel(class_names[train_labels[i]])
 9 # plt.show()
10 plt.tight_layout()
11 plt.savefig('demo_25_img.png', dpi=100)

圖1-2:訓練集中前25張圖片

 

1.4 建立模型

 准備好數據之后,就可以構建神經網絡模型了。主要包括構建網絡編譯兩部分。

1.4.1 構建網絡

在構建網絡時需要明確以下參數:

  • 網絡中包含的總層數;
  • 每一層的類型:例如Flattten,Dense等;
  • 每一層中包含的神經單元的個數;
  • 每一層使用的激活函數:例如Relu,Softmax等,不設置該參數表示不對該層進行任何非線性變換.

下面時構建網絡的代碼:

1 model = keras.Sequential([
2     keras.layers.Flatten(input_shape=(28, 28)),
3     keras.layers.Dense(128, activation='relu'),
4     keras.layers.Dense(10)
5 ])

該網絡一共有3層(下面假設僅輸入單個樣本,即一張圖片):

  • 第一層是Flatten層(下圖中的L0,輸入層):輸入的單個樣本是一個28*28的矩陣(矩陣每一個元素的值表示圖片中對應的一個像素點的值),輸出一個長度為784的向量;
  • 第二層是Dense層(下圖中的L1,隱藏層):輸入是上一層的輸出,即長度為784的向量;該層具有128個神經單元,激活函數為Relu,輸出為一個長度為128的向量;
  • 第三層是Dense層(下圖中的L2,輸出層):輸入是上一層的輸出;該層具有10個神經單元,未設置激活函數,輸出為一個長度為10的向量,也是該網絡的輸出層.

:在第一層網絡中需要指定input_shape參數,該參數不包含batch_size的信息,與單個樣本的維度信息相同;最后一層未設置激活函數得到的向量被稱為logits,官方解釋如下

對數 (logits)
分類模型生成的原始(非標准化)預測向量,通常會傳遞給標准化函數。如果模型要解決多類別分類問題,則對數通常變成 softmax 函數的輸入。之后,softmax 函數會生成一個(標准化)概率向量,對應於每個可能的類別。https://developers.google.cn/machine-learning/glossary

 圖1-3:網絡的結構

上圖中上角標表示層的編號,$\theta$表示各層的參數,$b$表示各層的偏執單元。

1.4.2 編譯

網絡構建好之后,需要編譯。在編譯過程中需要確定以下幾個參數:

  • 損失函數(Loss function):用來優化參數,評價模型預測值與樣本標簽之間的差別;
  • 優化器(Optimizer):根據誤差和梯度更新參數,從而最小化誤差;
  • 評估標准(Metrics):同樣用於評價模型的好壞.

損失函數與評估標准的異同

  • 都是評價模型好壞的方式,且具有高度的相關性;
  • 損失函數必須可導,是待訓練參數的函數,模型的訓練過程就是基於損失函數的優化過程;
  • 評估標准不一定可導,具有更好的可解釋性,例如分類問題中分類的准確率.

下面是編譯的代碼:

model.compile(optimizer='adam',
              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
              metrics=['accuracy'])

 

1.5 訓練模型 

建立好模型之后,就可以訓練模型了。因為是使用梯度下降來訓練模型,因此除了訓練集,還需要指定兩個參數:

  • 批次大小(batch size):單次訓練模型使用的樣本數(下面設置該值為32,也就是每次訓練只使用全部訓練集中的32個樣本,使用完所有訓練集樣本需要訓練60000/32=1875次);
  • 訓練迭代次數(epochs):在整個訓練集上訓練的次數,如果該值為10且批次大小為32,那么參數總共會更新10*1875次,訓練集中的每張圖片會被用到10次,;

下面是訓練模型的代碼:

model.fit(train_images, train_labels, batch_size=32, epochs=10)

下面是訓練過程中的輸出:

可以看到隨着迭代次數的增加,損失函數的值在下降,分類的准確率在上升。最后該模型在訓練集上的分類准確率為91%.

 

1.6 模型的最終評價

前面是在訓練集中訓練模型,訓練的終止條件是人為設定的訓練次數。訓練停止后,模型在訓練集上的分類准確率為91%。如果我們認為現在模型訓練已經完成,最后一步就是在測試集上評價模型。測試集中包含的數據是模型之前從未見過新樣本,如果在測試集上表現好,說明該模型有很好的泛化能力,學習到了這類數據的本質特征。

test_loss, test_acc = model.evaluate(test_images,  test_labels, verbose=2)

print('\nTest accuracy:', test_acc)

下面是輸出:

10000/10000 - 0s - loss: 0.3512 - accuracy: 0.8805

Test accuracy: 0.8805

最終在測試集中分類准確率為88%.

 

1.7 使用模型進行預測以及結果的可視化

下面從測試集取一個 batch 的樣本(32個樣本)進行預測,並將真實的label保存在test_labels中,最終得到第一個樣本的預測分類與真實分類都是6.

為了做預測,在模型的后面添加一個softmax層,將logits轉換成概率,這樣可解釋性更好:

probability_model = tf.keras.Sequential([model, tf.keras.layers.Softmax()])
# 預測測試集中圖片的類型
predictions = probability_model.predict(test_images)

# 取出第一個預測值,並查看對應的label
print(predictions[0])
print(np.argmax(predictions[0]))
print(test_labels[0])

下面是輸出結果:

[1.5879639e-08 1.1686364e-10 2.4405375e-08 2.9096781e-10 4.6984933e-08
 5.7084635e-03 3.3691771e-07 1.2553993e-02 4.3468383e-08 9.8173714e-01]
9
9

下面對部分結果進行可視化:

 1 def plot_image(i, predictions_array, true_label, img):
 2   predictions_array, true_label, img = predictions_array, true_label[i], img[i]
 3   plt.grid(False)
 4   plt.xticks([])
 5   plt.yticks([])
 6 
 7   plt.imshow(img, cmap=plt.cm.binary)
 8 
 9   predicted_label = np.argmax(predictions_array)
10   if predicted_label == true_label:
11     color = 'blue'
12   else:
13     color = 'red'
14 
15   plt.xlabel("{} {:2.0f}% ({})".format(class_names[predicted_label],
16                                 100*np.max(predictions_array),
17                                 class_names[true_label]),
18                                 color=color)
19 
20 def plot_value_array(i, predictions_array, true_label):
21   predictions_array, true_label = predictions_array, true_label[i]
22   plt.grid(False)
23   plt.xticks(range(10))
24   plt.yticks([])
25   thisplot = plt.bar(range(10), predictions_array, color="#777777")
26   plt.ylim([0, 1])
27   predicted_label = np.argmax(predictions_array)
28 
29   thisplot[predicted_label].set_color('red')
30   thisplot[true_label].set_color('blue')

檢查測試集中的第一個樣本

1 i = 0
2 plt.figure(figsize=(6,3))
3 plt.subplot(1,2,1)
4 plot_image(i, predictions[i], test_labels, test_images)
5 plt.subplot(1,2,2)
6 plot_value_array(i, predictions[i],  test_labels)
7 plt.tight_layout()
8 plt.savefig('demo_single_img2.png', dpi=100)
9 plt.show()

結果顯示該樣本有98%的概率為最后一類:

圖1-4:測試集中單個樣本預測結果的可視化

 

畫出前15個樣本:

 1 num_rows = 5
 2 num_cols = 3
 3 num_images = num_rows*num_cols
 4 plt.figure(figsize=(2*2*num_cols, 2*num_rows))
 5 for i in range(num_images):
 6   plt.subplot(num_rows, 2*num_cols, 2*i+1)
 7   plot_image(i, predictions[i], test_labels, test_images)
 8   plt.subplot(num_rows, 2*num_cols, 2*i+2)
 9   plot_value_array(i, predictions[i], test_labels)
10 plt.tight_layout()
11 plt.savefig('demo_15_img2.png', dpi=100)
12 plt.show()

結果如下:

圖1-5:測試集中部分預測結果的可視化

上圖中,藍色字體表示預測正確,藍色柱狀圖表示正確的類;紅色表示預測錯誤。第13個樣本本來屬於Sneaker,但是以60%的概率被預測為Sandal.

 

2. 小結


構建深度學習模型的一般流程

  1. 准備數據集:明確數據的特征、標簽和樣本總數,將數據集拆分成訓練集和測試集(有時候還會包括驗證集),數據的預處理(例如標准化等操作);
  2. 定義網絡結構:在 Keras 和 TF 中,層(layer)是網絡的基本結構,所有的網絡類型都可以使用基本類型的層搭建起來。這里需要確定網絡的層數,每一層的類型、激活函數、神經單元的個數等超參數;
  3. 編譯模型:編譯構建好的網絡,需要明確三個參數,損失函數(loss function)、優化器(optimizer)和評估標准(metrics);
  4. 訓練模型:需要指定批次大小(batch size)和迭代次數(epochs);
  5. 評價模型:在測試集上評價模型的效果.

更多與層有關的操作

參考:https://keras.io/layers/about-keras-layers/

             https://tensorflow.google.cn/api_docs/python/tf/keras/layers

損失函數的選擇

參考:https://keras.io/losses/

             https://tensorflow.google.cn/api_docs/python/tf/keras/losses

  • 兩分類:binary crossentropy
  • 對分類問題:categorical crossentropy
  • 回歸問題:mean-squared error

優化器的選擇

參考:https://keras.io/optimizers/

             https://tensorflow.google.cn/api_docs/python/tf/keras/optimizers

現在用的比較多的是RMSprop和Adam

度量

參考:https://keras.io/metrics/

             https://tensorflow.google.cn/api_docs/python/tf/keras/metrics

 

Reference


https://github.com/zalandoresearch/fashion-mnist#why-we-made-fashion-mnist

https://arxiv.org/abs/1708.07747

https://datascience.stackexchange.com/questions/13663/neural-networks-loss-and-accuracy-correlation

http://cs231n.stanford.edu/slides/2019/cs231n_2019_lecture05.pdf

https://blogs.nvidia.com/blog/2018/09/05/whats-the-difference-between-a-cnn-and-an-rnn/

https://github.com/OnlyBelter/examples/blob/master/courses/udacity_intro_to_tensorflow_for_deep_learning/l03c01_classifying_images_of_clothing.ipynb,代碼

https://github.com/keras-team/keras-docs-zh,一些名詞的翻譯參考了該文檔

Deep Learning with Python, by François Chollet, 2017.11

https://tensorflow.google.cn/tutorials/keras/classification

 《動手學深度學習》,阿斯頓·張、李沐等,人民郵電出版社,2019.6

 

修改記錄
2020.3.17 更新代碼,修正typo,分離CNN部分另成一篇

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM