tensorflow2.1從零開始實現softmax迴歸

softmax迴歸的從零開始實現

xiaoyao 動手學深度學習 tensorflow2.1.0

這一節我們來動手實現softmax迴歸。首先導入本節實現所需的包或模塊。

import tensorflow as tf
import numpy as np
import sys
print(tf.__version__)
2.1.0

獲取和讀取數據

使用Fashion-MNIST數據集,並設置批量大小爲256。

from tensorflow.keras.datasets import fashion_mnist

batch_size=256
(x_train, y_train), (x_test, y_test) = fashion_mnist.load_data()

x_train = tf.cast(x_train, tf.float32) / 255  # 在進行矩陣相乘時需要float型,故強制類型轉換爲float型
x_test = tf.cast(x_test,tf.float32) / 255  # 在進行矩陣相乘時需要float型,故強制類型轉換爲float型
train_iter = tf.data.Dataset.from_tensor_slices((x_train, y_train)).batch(batch_size)
test_iter = tf.data.Dataset.from_tensor_slices((x_test, y_test)).batch(batch_size)

初始化模型參數

跟線性迴歸中的例子一樣,我們將使用向量表示每個樣本。已知每個樣本輸入是高和寬均爲28像素的圖像。模型的輸入向量的長度是 28×28=784:該向量的每個元素對應圖像中每個像素。由於圖像有10個類別,單層神經網絡輸出層的輸出個數爲10,因此softmax迴歸的權重和偏差參數分別爲784×10和1×10的矩陣。

# Variable來標註需要記錄梯度的向量
num_inputs = 784
num_outputs = 10
W = tf.Variable(tf.random.normal(shape=(num_inputs, num_outputs), mean=0, stddev=0.01, dtype=tf.float32))
b = tf.Variable(tf.zeros(num_outputs, dtype=tf.float32))

實現softmax運算

在介紹如何定義softmax迴歸之前,我們先描述一下對如何對多維Tensor按維度操作。在下面的例子中,給定一個Tensor矩陣X。我們可以只對其中同一列(axis=0)或同一行(axis=1)的元素求和,並在結果中保留行和列這兩個維度(keepdims=True)。

X = tf.constant([[1, 2, 3], [4, 5, 6]])
tf.reduce_sum(X, axis=0, keepdims=True), tf.reduce_sum(X, axis=1, keepdims=True)
(<tf.Tensor: shape=(1, 3), dtype=int32, numpy=array([[5, 7, 9]])>,
 <tf.Tensor: shape=(2, 1), dtype=int32, numpy=
 array([[ 6],
        [15]])>)

下面我們就可以定義前面小節裏介紹的softmax運算了。在下面的函數中,矩陣logits的行數是樣本數,列數是輸出個數。爲了表達樣本預測各個輸出的概率,softmax運算會先通過exp函數對每個元素做指數運算,再對exp矩陣同行元素求和,最後令矩陣每行各元素與該行元素之和相除。這樣一來,最終得到的矩陣每行元素和爲1且非負。因此,該矩陣每行都是合法的概率分佈。softmax運算的輸出矩陣中的任意一行元素代表了一個樣本在各個輸出類別上的預測概率.

def softmax(logits, axis=-1):
    return tf.exp(logits)/tf.reduce_sum(tf.exp(logits), axis, keepdims=True)

可以看到,對於隨機輸入,我們將每個元素變成了非負數,且每一行和爲1。

X = tf.random.normal(shape=(2, 5))
X_prob = softmax(X)
X_prob, tf.reduce_sum(X_prob, axis=1)
(<tf.Tensor: shape=(2, 5), dtype=float32, numpy=
 array([[0.10089684, 0.50853884, 0.07086046, 0.15535463, 0.16434924],
        [0.12961236, 0.16926959, 0.14917475, 0.37836784, 0.17357543]],
       dtype=float32)>,
 <tf.Tensor: shape=(2,), dtype=float32, numpy=array([1., 1.], dtype=float32)>)

定義模型

有了softmax運算,我們可以定義上節描述的softmax迴歸模型了。這裏通過reshape函數將每張原始圖像改成長度爲num_inputs的向量。

def net(X):
    logits = tf.matmul(tf.reshape(X, shape=(-1, W.shape[0])), W) + b
    return softmax(logits)

定義損失函數

上一節中,我們介紹了softmax迴歸使用的交叉熵損失函數。爲了得到標籤的預測概率,使用booleam_maek函數。在下面的例子中,變量y_hat是2個樣本在3個類別的預測概率,變量y是這2個樣本的標籤類別。通過使用gather函數,我們得到了2個樣本的標籤的預測概率。與“softmax迴歸”一節數學表述中標籤類別離散值從1開始逐一遞增不同,在代碼中,標籤類別的離散值是從0開始逐一遞增的。

下面實現了“softmax迴歸”一節中介紹的交叉熵損失函數。

y_hat = np.array([[0.1, 0.3, 0.6], [0.3, 0.2, 0.5]])
y = np.array([0, 2], dtype='int32')
tf.boolean_mask(y_hat, tf.one_hot(y, depth=3))
<tf.Tensor: shape=(2,), dtype=float64, numpy=array([0.1, 0.5])>
# 定義實現交叉熵損失函數
def cross_entropy(y_hat, y):
        y = tf.cast(tf.reshape(y, shape=[-1, 1]),dtype=tf.int32)
        y = tf.one_hot(y, depth=y_hat.shape[-1])
        y = tf.cast(tf.reshape(y, shape=[-1, y_hat.shape[-1]]),dtype=tf.int32)
        return -tf.math.log(tf.boolean_mask(y_hat, y)+1e-8)

計算分類準確率

給定一個類別的預測概率分佈y_hat,我們把預測概率最大的類別作爲輸出類別。如果它與真實類別y一致,說明這次預測是正確的。分類準確率即正確預測數量與總預測數量之比。

爲了演示準確率的計算,下面定義準確率accuracy函數。其中tf.argmax(y_hat, axis=1)返回矩陣y_hat每行中最大元素的索引,且返回結果與變量y形狀相同。相等條件判斷式(tf.argmax(y_hat, axis=1) == y)是一個類型爲bool的Tensor,實際取值爲:0或1

def accuracy(y_hat, y):
    return np.mean((tf.argmax(y_hat, axis=1) == y))

讓我們繼續使用在演示boolean_mask函數時定義的變量y_hat和y,並將它們分別作爲預測概率分佈和標籤。可以看到,第一個樣本預測類別爲2(該行最大元素0.6在本行的索引爲2),與真實標籤0不一致;第二個樣本預測類別爲2(該行最大元素0.5在本行的索引爲2),與真實標籤2一致。因此,這兩個樣本上的分類準確率爲0.5。

accuracy(y_hat, y)
0.5

類似地,我們可以評價模型net在數據集data_iter上的準確率

# 描述,對於tensorflow2中,比較的雙方必須類型都是int型,所以要將輸出和標籤都轉爲int型
def evaluate_accuracy(data_iter, net):
    acc_sum, n = 0.0, 0
    for _, (X, y) in enumerate(data_iter):
        y = tf.cast(y,dtype=tf.int64)
        acc_sum += np.sum(tf.cast(tf.argmax(net(X), axis=1), dtype=tf.int64) == y)
        n += y.shape[0]
    return acc_sum / n
print(evaluate_accuracy(test_iter, net))
0.0989

訓練模型

訓練softmax迴歸的實現跟“線性迴歸的從零開始實現”一節介紹的線性迴歸中的實現非常相似。我們同樣使用小批量隨機梯度下降來優化模型的損失函數。在訓練模型時,迭代週期數num_epochs和學習率lr都是可以調的超參數。改變它們的值可能會得到分類更準確的模型。

num_epochs, lr = 8, 0.1
def train_ch3(net, train_iter, test_iter, loss, num_epochs, batch_size, params=None, lr=None, trainer=None):
    for epoch in range(num_epochs):
        train_l_sum, train_acc_sum, n = 0.0, 0.0, 0
        for X, y in train_iter:
            with tf.GradientTape() as tape:
                y_hat = net(X)
                l = tf.reduce_sum(loss(y_hat, y))
            grads = tape.gradient(l, params)
            if trainer is None:
                # 如果沒有傳入優化器,則使用原先編寫的小批量隨機梯度下降
                for i, param in enumerate(params):
                    param.assign_sub(lr * grads[i] / batch_size)
            else:
                # tf.keras.optimizers.SGD 直接使用是隨機梯度下降 theta(t+1) = theta(t) - learning_rate * gradient
                # 這裏使用批量梯度下降,需要對梯度除以 batch_size, 對應原書代碼的 trainer.step(batch_size)
                trainer.apply_gradients(zip([grad / batch_size for grad in grads], params))  # “softmax迴歸的簡潔實現”一節將用到
                
            y = tf.cast(y, dtype=tf.float32)
            train_l_sum += l.numpy()
            train_acc_sum += tf.reduce_sum(tf.cast(tf.argmax(y_hat, axis=1) == tf.cast(y, dtype=tf.int64), dtype=tf.int64)).numpy()
            n += y.shape[0]
        test_acc = evaluate_accuracy(test_iter, net)
        print('epoch %d, loss %.4f, train acc %.3f, test acc %.3f'% (epoch + 1, train_l_sum / n, train_acc_sum / n, test_acc))

trainer = tf.keras.optimizers.SGD(lr)
train_ch3(net, train_iter, test_iter, cross_entropy, num_epochs, batch_size, [W, b], lr)
epoch 1, loss 0.4067, train acc 0.862, test acc 0.842
epoch 2, loss 0.4033, train acc 0.862, test acc 0.842
epoch 3, loss 0.4013, train acc 0.862, test acc 0.842
epoch 4, loss 0.3999, train acc 0.863, test acc 0.842
epoch 5, loss 0.3988, train acc 0.863, test acc 0.842
epoch 6, loss 0.3979, train acc 0.863, test acc 0.843
epoch 7, loss 0.3970, train acc 0.863, test acc 0.842
epoch 8, loss 0.3963, train acc 0.864, test acc 0.843

預測

訓練完成之後,下面就可以演示對圖像的分類。給定一系列圖像(第三行),比較他們的真實標籤(第一行文本輸出)和模型預測結果(第二行)

import matplotlib.pyplot as plt
X, y = iter(test_iter).next()

def get_fashion_mnist_labels(labels):
    text_labels = ['t-shirt', 'trouser', 'pullover', 'dress', 'coat', 'sandal', 'shirt', 'sneaker', 'bag', 'ankle boot']
    return [text_labels[int(i)] for i in labels] # 將數值標籤轉爲文本標籤

def show_fashion_mnist(images, labels):
    # 這⾥的_表示我們忽略(不使⽤)的變量
    _, figs = plt.subplots(1, len(images), figsize=(12, 12)) # 這裏注意subplot 和subplots 的區別
    for f, img, lbl in zip(figs, images, labels):
        f.imshow(tf.reshape(img, shape=(28, 28)).numpy())
        f.set_title(lbl)
        f.axes.get_xaxis().set_visible(False)
        f.axes.get_yaxis().set_visible(False)
    plt.show()

true_labels = get_fashion_mnist_labels(y.numpy())
pred_labels = get_fashion_mnist_labels(tf.argmax(net(X), axis=1).numpy())
titles = [true + '\n' + pred for true, pred in zip(true_labels, pred_labels)]

show_fashion_mnist(X[0:9], titles[0:9])

在這裏插入圖片描述

可以使用softmax迴歸做多類別分類。此過程與線性迴歸類似


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章