分析模型的不確定性(Uncertainty),是機器學習過程中重要的工作。當我們訓練一個準確率90%的模型,我們還要進一步問「有多少信心說此模型的準確率是90%」。為什麼要如此麻煩?因為有些應用會擔心「是否下一次訓練模型的時候,準確率剩下50%」,在這種情況下,有辦法衡量、描述模型的不確定性,就很重要。
今天,我們會用標準差來衡量模型的不確定性,並且使用2種折線圖來呈現模型的準確率以及不確定性。讀者學會這些方法後,就可以清晰地展示模型的訓練成果。
本文的程式是參考旗標出版的「自學機器學習 — 上Kaggle接軌世界,成為資料科學家」第6章程式,做部分修改並且加上不確定性的衡量方式。關於程式的細節描述,請參考本書的內容。
一、準備函式庫以及資料集
以下2段程式是先準備函式庫以及資料集。
import numpy as np
import matplotlib.pyplot as plt
from tensorflow.keras.datasets import cifar10
from tensorflow.keras.utils import to_categorical
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Dropout, Flatten
from tensorflow.keras.layers import Conv2D, MaxPooling2D
from tensorflow.keras import optimizers
from sklearn.model_selection import KFold
此範例使用Keras內建的CIFAR-10的資料,這是10個類別的多元圖像分類問題。
def prepare_data():
(x, y), (x_test, y_test) = cifar10.load_data()
x, x_test = x.astype('float32'), x_test.astype('float32')
x, x_test = x/255.0, x_test/255.0
y, y_test = to_categorical(y), to_categorical(y_test)
return x, y, x_test, y_test
二、建立模型
由於CIFAR-10是圖像分類問題,因此我們使用卷積神經網路(Convolutional Neural Network):3個卷積層、1個展平層、1個密集層、1個輸出層。優化器使用Adam,學習率為0.001。
def build_model(x_train,
y_train,
x_valid,
y_valid,
batch_size,
epochs): model = Sequential()
model.add(Conv2D(filters = 64,
kernel_size = 3,
padding = 'same',
activation = 'relu',
input_shape = (32,32,3)))
model.add(MaxPooling2D(pool_size = 2))
model.add(Dropout(0.25))
model.add(Conv2D(filters = 128,
kernel_size = 3,
padding = 'same',
activation = 'relu'))
model.add(MaxPooling2D(pool_size = 2))
model.add(Dropout(0.25))
model.add(Conv2D(filters = 256,
kernel_size = 3,
padding = 'same',
activation = 'relu'))
model.add(MaxPooling2D(pool_size = 2))
model.add(Dropout(0.25))
model.add(Flatten())
model.add(Dense(512, activation = 'relu'))
model.add(Dropout(0.4))
model.add(Dense(10, activation = 'softmax'))model.compile(loss = "categorical_crossentropy",
optimizer = optimizers.Adam(lr = 0.001),
metrics = ["accuracy"])history = model.fit(x_train,
y_train,
batch_size = batch_size,
epochs = epochs,
verbose = 1,
validation_data = (x_valid, y_valid))return history
三、訓練模型
由於我們需要評估模型的不確定性,因此只訓練一次模型是不夠。本範例使用10 fold交叉驗證,來獲得10次模型的訓練成果。此外,每一次訓練模型20個週期(epoch),批次大小為128。
每一個fold訓練結束後,我們便將訓練資料、驗證資料的準確率存下來。
x, y, x_test, y_test = prepare_data()
batch_size = 128
epochs = 20train_result = []
valid_result = []
kf = KFold(n_splits = 10)
for train_index, valid_index in kf.split(x):
x_train, x_valid = x[train_index], x[valid_index]
y_train, y_valid = y[train_index], y[valid_index]
history = build_model(x_train,
y_train,
x_valid,
y_valid,
batch_size,
epochs)
train_result.append(history.history['accuracy'])
valid_result.append(history.history['val_accuracy'])
四、初步檢查訓練結果
我們先來看看第1個fold的訓練成果,做法是將訓練資料跟驗證資料的準確率, 隨著訓練週期的變化,用折線圖呈現出來。
plt.figure(figsize = (15, 10))
plt.plot(list(range(epochs)), train_result[0], label = 'Train', linestyle = '--')
plt.plot(list(range(epochs)), valid_result[0], label = 'Validation')
plt.legend()plt.grid()
plt.xticks(ticks = list(range(1, epochs + 1)))
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.show()
訓練結束後,驗證資料的準確率可以達到80%,我們就以此為基礎,繼續說明衡量、呈現模型不確定性的方法吧。
五、使用標準差來衡量模型的不確定性
現在我們有了10次的模型訓練結果,我們想知道會不會某一次訓練的結果很好(比如準確率90%),又有某一次訓練成果很爛(比如準確率50%)。該怎麼衡量?想要衡量某個事情的分散程度,其實可以使用標準差。作法如下:
1、取10次模型訓練結果中,第1個epoch的準確率(總共10個數字),計算標準差。這樣就得到第1個epoch的模型準確率分散程度。
2、換計算第2個epoch的模型準確率標準差,直到第20個epoch。
train_std = []
valid_std = []
for i in range(epochs):
train_std.append(np.std([row[i] for row in train_result]))
valid_std.append(np.std([row[i] for row in valid_result]))
現在,我們得到「不同epoch,模型準確率的標準差」。不過呢,目前我們得到的模型準確率,都是只使用9/10的資料(為了要做交叉驗證)。因此我們可以再用所有資料,重新訓練模型,預期有了更多資料,模型的準確率可以達到最高。
history = build_model(x, y, x_test, y_test, batch_size, epochs)
六、視覺化模型不確定性
Python的Matplotlib提供了errorbar的函式,可以讓我們將不確定性呈現在折線圖上。使用方法很簡單,只要將剛剛計算出來的模型準確率標準差,餵給errorbar函式的yerr引數就可以了。
plt.figure(figsize = (15, 10))
plt.errorbar(list(range(epochs)),
history.history['accuracy'],
yerr = train_std,
label = 'Train',
linestyle = '--')
plt.errorbar(list(range(epochs)),
history.history['val_accuracy'],
yerr = valid_std,
label = 'Validation')
plt.legend()plt.grid()
plt.xticks(ticks = list(range(1, epochs + 1)))
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
我們可以看到圖上的每一個資料點,都有一個短短的垂直線,這代表是模型的準確率,有可能在這個垂直線上的某一個點。
另外,我們也可以使用Matplotlib提供的fill_between來視覺化模型的不確定性。這個作法是將模型準確率可能的數值,用一個網底框出來。使用這個方法之前,我們要先定義網底的上界跟下界,作法很簡單:
網底上界 = 模型準確率 + 模型準確率的標準差
網底下界 = 模型準確率 - 模型準確率的標準差
最後,將網底上界跟下界餵給fill_between函式,圖形就畫好囉。
train_lower = np.array(history.history['accuracy']) -
np.array(train_std)
train_upper = np.array(history.history['accuracy']) +
np.array(train_std)
valid_lower = np.array(history.history['val_accuracy']) -
np.array(valid_std)
valid_upper = np.array(history.history['val_accuracy']) +
np.array(valid_std)plt.figure(figsize = (15, 10))
plt.plot(list(range(epochs)),
history.history['accuracy'],
label = 'Train',
linestyle = '--')
plt.fill_between(list(range(epochs)),
train_lower,
train_upper,
alpha = 0.3)
plt.plot(list(range(epochs)),
history.history['val_accuracy'],
label = 'Validation')
plt.fill_between(list(range(epochs)),
valid_lower,
valid_upper,
alpha = 0.3)
plt.legend()plt.grid()
plt.xticks(ticks = list(range(1, epochs + 1)))
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
可以發現,以這個範例來說,使用fill_between,可以清晰地看出模型的準確率,可能的變化範圍。透過視覺化的方法,讓我們比較能夠呈現模型的不確定性,並作為模型改善的方向。
關於作者
Chia-Hao Li received the M.S. degree in computer science from Durham University, United Kingdom. He engages in computer algorithm, machine learning, and hardware/software codesign. He was former senior engineer in Mediatek, Taiwan. His currently research topic is the application of machine learning techniques for fault detection in the high-performance computing systems.