作為圖像識别與機器視覺界的 "hello world!" ,
MNIST ("Modified National Institute of Standards and Technology")
資料集有着舉足輕重的地位。基本上每本人工智能、機器學習相關的書上都以它作為開始。
下面我們會用 TensorFlow 搭建一個淺層的神經網絡來運作 "hello world!" 模型。 以下内容和子產品的運算,均在矩池雲平台進行。
本次教程分五步:
第一步:資料預處理,包括提取資料标簽、檢視圖檔資料、資料可視化、檢視資料是否平衡等
第二步:資料加載,打亂資料集
第三步:構模組化型,簡單介紹網絡卷積模型和激活函數,定義訓練函數和學習率
第四步:模型訓練,檢視訓練過程和結果,使用圖表檢視模型精确度和學習率變化
第五步:嘗試提升精準度,不斷探索和優化
在搭建開始前,我們需要加載以下對應的子產品:
第一步:資料預處理
1.1檢視資料标簽
在任何模型建立之前,應當優先檢視資料的情況。例如資料集的大小、訓練集和測試集的資料數量、标簽的資料數量分布等。
下方為訓練集和測試集的資料檢視代碼:
train = pd.read_csv('mnist/mnist_train.csv') # read train
test = pd.read_csv('mnist/mnist_test.csv') # read train
下方為訓練集和測試集的數量結果:
train.shape (6000,785)
test.shape (10000,785)
我們可以看到 train 訓練集裡面有6000條資料,test 測試集裡面有10000條資料,兩個測試集每行都有785個資料。
接下來,我們來看下資料集的預覽:其中第一列是标簽列,剩餘784列則為像素點資料,由該784列資料組成一張28*28的像素圖檔。
1.2 提取資料标簽
接下來,我們進行資料标簽的提取和處理。先來看下标簽資料的提取代碼:
train_labels = np.array(train.pop('label'))
test_labels = np.array(test.pop('label'))
檢視标簽種類,我們可以看出标簽表示了從0~9的數字,沒有其他的錯誤資料。
由于運算需要,我們需要将一維的圖檔資料轉換成二維圖檔資料。将圖檔資料轉換成長28,寬28,通道為1的格式,友善卷積計算。
第二步:資料可視化
2.1 随機生成資料比對
現在随機選取一些我們已經轉換好的圖檔資料,用 matplot 來檢視下标簽和圖檔是否能夠對上。
方框内是随機生成的一些非規則寫法,圖檔上方正中間則為對應的數字。
2.2 檢視資料是否平衡
分類器的設計都是基于類分布大緻平衡這一假設,通常假定用于訓練的資料是平衡的,即各類所含樣本數大緻相當。
下面我們來看下标簽的分布情況,檢視每個标簽種類的資料量是否分布均勻。
在 MINST 資料集中,我們的資料是處于一個均勻分布的狀态。
sns.distplot(train_labels, kde=False, bins=10)
2.3 資料加載
在建立模型之前,我們需要先定義一些常量:
# 圖像寬度
width = 28
# 圖像高度
height = 28
# batch size
batch_size = 100
# 訓練圖檔數量
train_images_num = train.shape[0]
下一步,我們為模型建立資料集。TensorFlow 提供了 Dataset 類可以友善加載訓練的資料,使用方式為 tf.data.Dataset。
其中,訓練集的資料,我們進行了随機打亂。
train = tf.cast(train, tf.float32)
test = tf.cast(test, tf.float32)
train_ds = tf.data.Dataset.from_tensor_slices((train, train_labels)).shuffle(train_images_num).batch(batch_size)
test_ds = tf.data.Dataset.from_tensor_slices((test, test_labels)).batch(batch_size)
第三步:模型建構
3.1 構模組化型的網絡層次結構
數字識别作為入門工程,我們的模型也會相對的簡單。目前構模組化型,采用了以下幾層網絡層次結構:
- 第一層二維卷積層
- Flatten 層:這層的作用是将第一層的卷積曾平坦壓縮成一維,常用在從卷積層到全連接配接曾的過度,當然 Flatten 不影響 batch 的大小
- Dense 層:全連接配接神經網絡層
每一層對應的激活函數如下:
- 第一層使用 ReLU 函數
- Flatten 層( 無 )
- Dense 層 ReLU 函數
- Dense 層使用 softmax 損失函數進行輸出
3.2 關于激活函數的解釋說明
ReLU函數
ReLU 函數全名為線性整流函數(Rectified Linear Unit, ReLU),又稱修正線性單元,是一種人工神經網絡中常用的激活函數(activation function),通常指代以斜坡函數及其變種為代表的非線性函數。
ReLU的函數表達式為:
用向量形式表達為:
函數曲線形态為:
從函數的表達可以看出,函數抑制了比 0 小的輸入,這個激活函數有以下特點:
- 收斂快
- 在[ 0, x ]區間内不會飽和,即它可以對抗梯度消失問題
- 求導簡單,也就是它的計算效率很高
softmax 函數
softmax 用于多分類過程中,它将多個神經元的輸出映射到(0,1)區間内,可以看成機率來了解,進而來進行多分類。
我們來看下它的數學表達式,假設我們有一個數組,𝑉V,𝑉𝑖Vi 表示 𝑉V 中的第 𝑖i 個元素,那麼這個元素的 softmax 值就是:
在我們的數字識别的模型中,我們将最後的輸出成一個10個元素的數組,數組從0下标開始到9,分别表示對應的标簽。
然後對這個輸出進行 softmax 計算,取出 softmax 值最大的那個元素對應的标簽作為我們的分類結果。
class MNIST(Model):
def __init__(self):
super(MNIST, self).__init__()
self.conv1 = Conv2D(width, 3, activation='relu')
self.flatten = Flatten()
self.d1 = Dense(128, activation='relu')
self.d2 = Dense(10, activation='softmax')
def call(self, x):
x = self.conv1(x)
x = self.flatten(x)
x = self.d1(x)
return self.d2(x)
model = MNIST()
model.build(input_shape=train.shape
3.3 檢視模型的建構情況
本文利用 summary 接口來檢視模型的情況,可以看到我們的每層網絡的類型、輸出、參數的個數,最下面還是統計了可訓練參數,全部參數的情況。
我們選用交叉熵函數作為我們的損失函數,基本公式如下:
batch 公式:
用随機梯度下降算法作為我們的優化器:
loss_object = tf.keras.losses.SparseCategoricalCrossentropy()
optimizer = tf.keras.optimizers.SGD()
train_loss = tf.keras.metrics.Mean(name='train_loss')
train_accuracy = tf.keras.metrics.SparseCategoricalAccuracy(name='train_accuracy')
test_loss = tf.keras.metrics.Mean(name='test_loss')
test_accuracy = tf.keras.metrics.SparseCategoricalAccuracy(name='test_accuracy')
定義 train_step 函數:
@tf.function
def train_step(images, labels):
with tf.GradientTape() as tape:
predictions = model(images)
loss = loss_object(labels, predictions)
gradients = tape.gradient(loss, model.trainable_variables)
optimizer.apply_gradients(zip(gradients, model.trainable_variables))
train_loss(loss)
train_accuracy(labels, predictions)
定義 test_step 函數:
@tf.function
def test_step(images, labels):
predictions = model(images)
t_loss = loss_object(labels, predictions)
test_loss(t_loss)
test_accuracy(labels, predictions)
一般情況下,學習率 ( learning rate ) 不适合設定為常數。在訓練不斷疊代的情況下,常量的學習率會導緻模型收斂性變差。
在不斷的疊代過程中,損失函數 ( loss ) 越來越小,是以我們希望學習率也越來越小,進而能夠讓模型收斂到一個更好的局部最優點。
這裡我們簡單的讓學習率在每 epoch 中都以一定大小遞減。
def lr_fn(epoch, lr):
if epoch == 0:
return 0.001
return lr * 0.9
設定一個較大的 epoch,我們在模型訓練的時候做了 early stop 政策。當訓練精度小于上一次 epoch 的精度,我們認為模型進入了過拟合了。
我們會停止訓練這個也是一種防止過拟合的政策。
第四步:模型訓練
我們在訓練中記錄下了每一次 epoch 的訓練集和測試集精度的統計以及學習率,為了訓練完成後檢視訓練過程的效果。
我們可以看到的訓練結果:
然後我們把訓練中的記錄下來的訓練集和測試集的精确度結果放到圖表中,用以檢視我們的訓練情況:綠色為測試集曲線,藍色為訓練集曲線。
plt.plot(epoch_range, train_accuracy_total, '-b', label= "training")
plt.plot(epoch_range, test_accuracy_total, '-g', label= "test")
plt.legend()
plt.xlabel('epoch')
plt.ylabel('accuracy')
從圖表中可以看出,在經過不斷的 epoch 疊代以後,模型的精度在開始的幾個 epoch 後迅速提升(這表示收斂速度很快)。後面的幾個 epoch 模型的精度曲線趨向于平穩,收斂速度放緩。
檢視學習率的遞減情況:
plt.plot(epoch_range, lr_total, '-b', label= "training")
plt.legend()
plt.xlabel('epoch')
plt.ylabel('learning rate')
第五步:探索和優化
後續讀者可以從以下這幾個方面來進行嘗試,嘗試提高模型的準确率。
- 更深的網絡層次,可以更換模型,比如使用 VGG16,ResNet 等更深的網絡,或者在現有的網絡中添加更多的卷積層進行嘗試
- 更多的訓練資料,資料量的增長能極大的提高模型的精度跟泛化能力
- 使用别的優化器,比如:adam
- 調整學習率
現在已經全線上架 “機器圖像識别” 鏡像;
選擇 “ 圖像識别demo ” 鏡像,機器啟動後,在 JupyterLab 目錄中選擇
digit-recognizer 檔案夾,矩池雲已經将資料集和腳本都內建在其中,執行其中的 ipynb 檔案,即可運作上述識别腳本。