多元分類神經網路(multiple classification neural network)可以分類多於一種類別。相較於二元分類(binary classification),在實務上是比較常被使用的。本文章將詳細介紹 multiple classification neural network 的理論。
Table of Contents
神經網路(Neural Network)
在本文章開始之前,讀者必須要了解 neural network 和 binary classification。本文章中許多的觀念都與 binary classification 雷同。我們不會在本文章中重複這些雷同的部分。因此,不論讀者是否了解 binary classification,建立先閱讀以下文章。
Softmax 函數
Softmax 函數
相對於 binary classification,multiple classification 使用 softmax 函數作為 output layer 的 activation function。
Softmax 函數定義如下。每一個 都大於零,並且都除以總和。因此
會是一個總和為 1 的機率分佈。
以下程式碼實作了 softmax 函數。
def softmax(Z):
"""
Implements the softmax activation.
Parameters
----------
Z: (ndarray of any shape) - input to the activation function
Returns
-------
A: (ndarray of same shape as Z) - output of the activation function
"""
# Subtracting the maximum value in each column for numerical stability to avoid overflow
Z_stable = Z - np.max(Z, axis=0, keepdims=True)
exp_Z = np.exp(Z_stable)
A = exp_Z / np.sum(exp_Z, axis=0, keepdims=True)
return ASoftmax 函數的導數
Softmax 函數的導數求解有點複雜。首先,softmax 函數的輸出的第 k 項如下。
對
做微分,如下。
首先,我們先考慮 。此時,有兩種情況。
- 當
,則
。
- 當
,則
。
我們可以用 kronecker delta 將這兩種情況合併。
現在來處理 。
將以上的兩個部分代入回去,則我們可以得出以下。
化簡之後,最後 對
做微分如下。
有
K 項。每一項都要分別對 做微分。因此,softmax 的導數是一個
的 jacobian matrix,如下。
而當 或
時,
會有不同的值。因為
J 是一個 的矩陣,因此
會是對角線上的項。
以下程式碼中,輸入值 z 是 softmax 函數的輸出值。softmax_jacobian() 實作了 softmax 函數的導數,而其導數會是一個 jacobian matrix。
def softmax_jacobian(z):
"""
Computes the Jacobian matrix for the softmax function.
Parameters
----------
Z: (ndarray (K,1)) - the input to the softmax function
Returns
-------
dZ: (ndarray (K,K)) - the Jacobian matrix
"""
z_stable = z - np.max(z, axis=0, keepdims=True)
exp_Z = np.exp(z_stable)
g = exp_z / np.sum(exp_z, axis=0, keepdims=True)
return np.diag(g) - np.outer(g, g)多元神經網路(Multiple Classification Neural Network)
下圖是一個 multiple classification 的 neural network。與 binary classification 相比,它的 output layer 裡的 activation function 是 softmax 。

梯度下降(Gradient Descent)
Multiple classification neural network 的 gradient descent 如下。由於每一層都有對應的參數 W 和 b,因此我們必須要計算 J 對每一層 W 和 b 的偏導數。
損失函式(Loss Function)
在 multiple classification 的 neural network 裡,output layer 的 activation function 是 softmax function。因此,我們使用 cross-entropy loss 作為它的 loss function。
以下程式碼實作了 loss function。
def compute_cost(AL, Y):
"""
Computes the cross-entropy cost.
Parameters
----------
AL: (ndarray (output size, number of examples)) - probability vector corresponding to the label predictions
Y: (ndarray (output size, number of examples)) - true label vector
Returns
-------
cost: (float) - the cross-entropy cost
"""
m = Y.shape[1]
cost = -(1 / m) * np.sum(Y * np.log(AL))
return cost前向傳播(Forward Propagation)
在前向傳播(forward propagation)中,activation functions 要將 Z 回傳給呼叫者,而呼叫者會將它存入 caches 中。這些 caches 會在 backpropagation 中被使用。
以下的程式碼時實作了 softmax activation function。
def softmax(Z):
"""
Implements the softmax activation.
Parameters
----------
Z: (ndarray of any shape) - input to the activation function
Returns
-------
A: (ndarray of same shape as Z) - output of the activation function
cache: (ndarray) - returning Z for backpropagation
"""
# Subtracting the maximum value in each column for numerical stability to avoid overflow
Z_stable = Z - np.max(Z, axis=0, keepdims=True)
exp_Z = np.exp(Z_stable)
A = exp_Z / np.sum(exp_Z, axis=0, keepdims=True)
cache = Z
return A, cache反向傳播(Backpropagation or Backward Propagation)
反向傳播(backpropagation)其實就是微分的連鎖率(chain rule)。
在 binary classification neural network 中,我們提及到如何求取各參數的導數。在 multiple classification neural network 中,我們也是要求取各參數的導數。不同的是,在這邊我們使用的 loss function 和 output layer 裡的 activation function 是不相同的。
首先,我們先計算 。
對每一個 example 計算 。這邊要對每一個 example 分開計算是因為它會是一個 jacobian matrix。
對每一個 example 計算 後,再將它們合併起來。
其他參數的偏導數計算,請參考 binary classification neural network。
以下程式碼實作 softmax activation function 的偏導數。
def softmax_backward(dA, cache):
"""
Implements the backward propagation for a single softmax unit.
Parameters
----------
dA: (ndarray of any shape) - post-activation gradient
cache: (ndarray) - Z from the forward propagation
Returns
-------
dZ: (ndarray of the same shape as A) - gradient of the cost with respect to Z
"""
def softmax_jacobian(Z):
Z_stable = Z - np.max(Z, axis=0, keepdims=True)
exp_Z = np.exp(Z_stable)
g = exp_Z / np.sum(exp_Z, axis=0, keepdims=True)
return np.diag(g) - np.outer(g, g)
Z = cache
m = Z.shape[1]
dZ = np.zeros_like(Z)
for k in range(m):
dZ[:, k] = softmax_jacobian(Z[:, k]) @ dA[:, k]
return dZ最後,以下程式碼中的 model_backward() 實作了整個 backpropagation。
def model_backward(AL, Y, caches, activation_functions):
"""
Implements the backward propagation for the entire network.
Parameters
----------
AL: (ndarray (output size, number of examples)) - the output of the last layer
Y: (ndarray (output size, number of examples)) - true labels
caches: (list of tuples) - containing linear_cache (A_prev, W, b) and activation_cache (Z) for each layer
activation_functions: (list) - the activation function for each layer. The first element is unused.
Returns
-------
gradients: (dict) with keys where 0 <= l <= len(activation_functions) - 1:
dA{l-1}: (ndarray (size of previous layer, number of examples)) - gradient of the cost with respect to the activation for previous layer l - 1
dWl: (ndarray (size of current layer, size of previous layer)) - gradient of the cost with respect to W for layer l
dbl: (ndarray (size of current layer, 1)) - gradient of the cost with respect to b for layer l
"""
gradients = {}
L = len(activation_functions)
m = AL.shape[1]
dAL = -(1 / m) * (Y / AL)
dA_prev = dAL
for l in reversed(range(1, L)):
current_cache = caches[l - 1]
dA_prev, dW, db = linear_activation_backward(dA_prev, current_cache, activation_functions[l])
gradients[f'dA{l - 1}'] = dA_prev
gradients[f'dW{l}'] = dW
gradients[f'db{l}'] = db
return gradients整合全部
以下程式碼中的 nn_model() 實作了整個模型。它先執行 forward propagation,然後執行 backpropagation,最後更新參數。
def nn_model(X, Y, init_parameters, layer_activation_functions, learning_rate, num_iterations):
"""
Implements a neural network.
Parameters
----------
X: (ndarray (input size, number of examples)) - input data
Y: (ndarray (output size, number of examples)) - true labels
init_parameters: (dict) - the initial parameters for the network
layer_activation_functions: (list) - the activation function for each layer. The first element is unused.
learning_rate: (float) - the learning rate
num_iterations: (int) - the number of iterations
Returns
-------
parameters: (dict) - the learned parameters
costs: (list) - the costs at every 100th iteration
"""
costs = []
parameters = init_parameters.copy()
for i in range(num_iterations):
AL, caches = model_forward(X, parameters, layer_activation_functions)
cost = compute_cost(AL, Y)
gradients = model_backward(AL, Y, caches, layer_activation_functions)
parameters = update_parameters(parameters, gradients, learning_rate)
if i % 100 == 0 or i == num_iterations:
costs.append(cost)
return parameters, costs當訓練好參數後,我們可以用以下的 nn_model_predict() 來做預測。
def nn_model_predict(X, parameters, activation_functions):
"""
Predicts the output of the neural network.
Parameters
----------
X: (ndarray (input size, number of examples)) - input data
parameters: (dict) - the learned parameters
activation_functions: (list) - the activation function for each layer. The first element is unused.
Returns
-------
predictions: (ndarray (number of classes, number of examples)) - the predicted labels
"""
probabilities, _ = model_forward(X, parameters, activation_functions)
pred = np.argmax(probabilities, axis=0)
predictions = np.zeros_like(probabilities)
for i in range(predictions.shape[1]):
predictions[pred[i], i] = 1
return predictions範例
我們將藉由一個範例來展示如何使用我們的模型。首先,我們先將訓練資料 x_orig 和 y 載入。x_orig 是一個包含 100 張圖片的陣列。每一張圖片的大小是 64 x 64,而且有三個 channels。y 是一個包含 0 或 1 的陣列,1 表示圖片裡是貓,0 表示不是貓。
x_orig, y_orig = load_data()
print(f'x_orig shape: {x_orig.shape}')
print(f'y_orig shape: {y_orig.shape}')
# Output
x_orig shape: ndarray(100, 64, 64, 3)
y_orig shape: ndarray(1, 100)之前我們有列出 X 的維度是 (nh, m),所以每一張圖片是一個行向量。以下我們將 x_orig 的維度,並將數值 0 至 255 轉換成 0 至 1 的值。將 y_orig 轉化成 one hot encoding。
x_flatten = x_orig.reshape(x_orig.shape[0], -1).T
x = train_x_flatten / 255.
y = np.zeros((2, y_orig.shape[1]))
y[0, y_orig[0, :] == 0] = 1
y[1, y_orig[0, :] == 1] = 1
print("x shape: " + str(x.shape))
print("y shape: " + str(y.shape))
# Output
x shape: ndarray(1228, 100)
y shape: ndarray(2, 200)首先,我們要先決定模型的層數,以及每一層 neurons 個數。以下我們設定模型有一個 input layer、hidden layer 裡有三層、以及一個 output layer。我們還要決定每一層的 activation function,其中 layer_activation_functions[0] 對應 input layer,所以不會被使用到。
這些決定好後,我們就可以初始化所有的參數 W 和 b,然後呼叫 nn_model() 來訓練模型。最後,取得訓練好的參數。
layer_dims = [12288, 20, 7, 10, 1] init_parameters = initialize_parameters(layer_dims) layer_activation_functions = ['none', 'relu', 'relu', 'relu', 'softmax'] learning_rate = 0.0075 parameters, costs = nn_model(x, y, init_parameters, layer_activation_functions, learning_rate, 3000)
有了訓練好的參數後,我們就可以用來預測其他的圖片。
x_new_orig = load_new_data() x_new_flatten = x_new_orig.reshape(x_new_orig.shape[0], -1).T x_new = x_new_flatten / 255. y_new = nn_model_predict(x_new, parameters, layer_activation_functions)
結語
求取 softmax 函數的偏導數是蠻複雜的,所幸現在我們不再需要自己實作這部分,而是使用像 PyTorch 和 TensorFlow 這類的函式庫來實作 neural network。不過了解它內部的細節,讓我們可以更加地了解它。
參考
- Andrew Ng, Deep Learning Specialization, Coursera.
- 西内啓,機器學習的數學基礎 : AI、深度學習打底必讀,旗標。









