3.6 使用tf.keras从零开始实现Softmax回归
这一节我们来动手实现softmax回归。首先导入本节实现所需的包或者模块
%matplotlib inline
import tensorflow as tf
import matplotlib.pyplot as plt
from IPython import display
from tensorflow import keras
import tensorflow.data as tfdata
tf.enable_eager_execution()
3.6.1. 获取和读取数据
我们将使用Fashion-MNIST数据集,并设置批量大小为256。
batch_size=256
buffer_size=10000
def load_data_fashion_mnist(batch_size,buffer_size):
(x_train,y_train),(x_test,y_test)=keras.datasets.fashion_mnist.load_data()
train_iter=tfdata.Dataset.from_tensor_slices((x_train,y_train)).map(lambda x,y:(x/255,y)).shuffle(buffer_size).batch(batch_size)
test_iter=tfdata.Dataset.from_tensor_slices((x_test,y_test)).map(lambda x,y:(x/255,y)).batch(batch_size)
return train_iter,test_iter
train_iter,test_iter=load_data_fashion_mnist(batch_size=batch_size,buffer_size=buffer_size)
3.6.2. 初始化模型参数
跟线性回归中的例子一样,我们将使用向量表示每个样本。已知每个样本输入是高和宽均为28像素的图像。模型的输入向量的长度是 28×28=784 :该向量的每个元素对应图像中每个像素。由于图像有10个类别,单层神经网络输出层的输出个数为10,因此softmax回归的权重和偏差参数分别为 784×10 和 1×10 的矩阵。
num_inputs=784
num_outputs=10
W=tf.Variable(tf.random.normal(shape=(num_inputs,num_outputs)),trainable=True)
b=tf.Variable(tf.zeros(num_outputs),trainable=True)
3.6.3. 实现softmax运算
在介绍如何定义softmax回归之前,我们先描述一下对如何对多维张量按维度操作。在下面的例子中,给定一个矩阵X。我们可以只对其中同一列(axis=0)或同一行(axis=1)的元素求和,并在结果中保留行和列这两个维度(keepdims=True)。
X=tf.constant([[1,2,3],[4,5,6]])
tf.reduce_sum(input_tensor=X,axis=0,keep_dims=True),tf.reduce_sum(input_tensor=X,axis=1,keep_dims=True)
下面我们就可以定义前面小节里介绍的softmax运算了。在下面的函数中,矩阵X的行数是样本数,列数是输出个数。为了表达样本预测各个输出的概率,softmax运算会先通过exp函数对每个元素做指数运算,再对exp矩阵同行元素求和,最后令矩阵每行各元素与该行元素之和相除。这样一来,最终得到的矩阵每行元素和为1且非负。因此,该矩阵每行都是合法的概率分布。softmax运算的输出矩阵中的任意一行元素代表了一个样本在各个输出类别上的预测概率。
def softmax(X):
X_exp=tf.exp(X)
partition=tf.reduce_sum(X_exp,axis=1,keep_dims=True)
return X_exp/partition
可以看到,对于随机输入,我们将每个元素变成了非负数,且每一行和为1。
X=tf.random.normal(shape=(2,5))
X_prob=softmax(X)
X_prob,tf.reduce_sum(X_prob,axis=1)
3.6.4. 定义模型
有了softmax运算,我们可以定义上节描述的softmax回归模型了。这里通过reshape函数将每张原始图像改成长度为num_inputs的向量。
def net(X):
return softmax(tf.matmul(tf.reshape(X,(-1,num_inputs)),W)+b)
3.6.5. 定义损失函数
上一节中,我们介绍了softmax回归使用的交叉熵损失函数。在下面的例子中,变量y_hat是2个样本在3个类别的预测概率,变量y是这2个样本的标签类别,通过计算得到了2个样本的标签的预测概率。与“softmax回归”一节数学表述中标签类别离散值从1开始逐一递增不同,在代码中,标签类别的离散值是从0开始逐一递增的。
y_hat=tf.constant([[0.1,0.3,0.6],[0.3,0.2,0.5]])
y=tf.constant([0,2])
tf.reduce_max(tf.one_hot(y,depth=tf.shape(y_hat)[-1])*y_hat,axis=1)
下面实现了交叉熵损失函数。
def cross_entropy(y_hat,y):
return -tf.log(tf.reduce_max(tf.one_hot(y,depth=tf.shape(y_hat)[-1])*y_hat,axis=1))
3.6.6. 计算分类准确率
给定一个类别的预测概率分布y_hat,我们把预测概率最大的类别作为输出类别。如果它与真实类别y一致,说明这次预测是正确的。分类准确率即正确预测数量与总预测数量之比。
为了演示准确率的计算,下面定义准确率accuracy函数。其中tf.argmax(axis=1)返回矩阵y_hat每行中最大元素的索引,且返回结果与变量y形状相同。由于标签类型为整数,我们先将变量y变换为浮点数再进行相等条件判断。
def accuracy(y_hat,y):
return tf.equal(tf.cast(tf.argmax(y_hat,axis=1),tf.float32),tf.cast(y,tf.float32)).numpy().mean()
tf.equal(tf.cast(tf.argmax(y_hat,axis=1),tf.float32),tf.cast(y,tf.float32)).numpy().mean()
accuracy(y_hat,y)
类似地,我们可以评价模型net在数据集data_iter上的准确率。
def evaluate_accuracy(data_iter,net):
acc_sum,n=0.0,0
for X,y in data_iter:
# y=tf.cast(y,tf.float32)
acc_sum+=tf.equal(tf.cast(tf.argmax(net(X),axis=1),tf.float32),tf.cast(y,tf.float32)).numpy().sum()
n+=tf.shape(y)[0].numpy()
return acc_sum/n
evaluate_accuracy(test_iter,net)
3.6.7. 训练模型
训练softmax回归的实现跟“线性回归的从零开始实现”一节介绍的线性回归中的实现非常相似。我们同样使用小批量随机梯度下降来优化模型的损失函数。在训练模型时,迭代周期数num_epochs和学习率lr都是可以调的超参数。改变它们的值可能会得到分类更准确的模型。
num_epochs,lr=5,0.1
def sgd(params,loss,t,lr,batch_size):
for param in params:
dl_dp=t.gradient(loss,param) #求梯度
param.assign_sub(lr*dl_dp/batch_size) #更新梯度
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:
if trainer is None:
with tf.GradientTape(persistent=True) as t:
y_hat=net(X)
l=tf.reduce_sum(loss(y_hat,y))
sgd(params,l,t,lr,batch_size)
else:
# print('更新梯度')
y_hat=net(X)
l=tf.reduce_sum(loss(y_hat,y))
trainer.minimize(lambda:loss(net(X),y),global_step=tf.train.get_or_create_global_step())
# print('梯度更新完毕')
train_l_sum+=l.numpy()
train_acc_sum+=tf.equal(tf.cast(tf.argmax(y_hat,axis=1),tf.float32),tf.cast(y,tf.float32)).numpy().sum()
n+=tf.shape(y)[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))
train_ch3(net,train_iter,test_iter,cross_entropy,num_epochs,batch_size,[W,b],lr)
optimizer=tf.train.GradientDescentOptimizer(learning_rate=0.001)
train_ch3(net,train_iter,test_iter,cross_entropy,num_epochs=10,batch_size=batch_size,params=[W,b],lr=None,trainer=optimizer)
for X,y in train_iter:
out=net(X)
print(out.shape)
break
3.6.8. 小结
可以使用softmax回归做多类别分类。与训练线性回归相比,你会发现训练softmax回归的步骤和它非常相似:获取并读取数据、定义模型和损失函数并使用优化算法训练模型。事实上,绝大多数深度学习模型的训练都有着类似的步骤。
代码:https://github.com/chongzicbo/Dive-into-Deep-Learning-tf.keras/blob/master/3.6.%20softmax%E5%9B%9E%E5%BD%92%E5%9C%B0%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E5%AE%9E%E7%8E%B0.ipynb
参考:《动手学深度学习》