我们已经从上一节里了解了多层感知机的原理。下面,我们一起来动手实现一个多层感知机。首先导入实现所需的包或模块。
import tensorflow as tf
import numpy as np
import sys
print(tf.__version__)
这里继续使用Fashion-MNIST数据集。我们将使用多层感知机对图像进行分类。
from tensorflow.keras.datasets import fashion_mnist
(x_train, y_train), (x_test, y_test) = fashion_mnist.load_data()
batch_size = 256
x_train = tf.cast(x_train, tf.float32)
x_test = tf.cast(x_test, tf.float32)
x_train = x_train/255.0
x_test = x_test/255.0
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)
我们在3.6节(softmax回归的从零开始实现)里已经介绍了,Fashion-MNIST数据集中图像形状为
num_inputs, num_outputs, num_hiddens = 784, 10, 256
w1 = tf.Variable(tf.random.truncated_normal([num_inputs, num_hiddens], stddev=0.1))
b1 = tf.Variable(tf.random.truncated_normal([num_hiddens], stddev=0.1))
w2 = tf.Variable(tf.random.truncated_normal([num_hiddens, num_outputs], stddev=0.1))
b2=tf.Variable(tf.random.truncated_normal([num_outputs], stddev=0.1))
这里我们使用基础的max
函数来实现ReLU,而非直接调用relu
函数。
def relu(x):
return tf.math.maximum(x,0)
同softmax回归一样,我们通过view
函数将每张原始图像改成长度为num_inputs
的向量。然后我们实现上一节中多层感知机的计算表达式。
def net(X):
def net(x,w1,b1,w2,b2):
x = tf.reshape(x,shape=[-1,num_inputs])
h = relu(tf.matmul(x,w1) + b1 )
y = tf.math.softmax( tf.matmul(h,w2) + b2 )
return y
为了得到更好的数值稳定性,我们直接使用Tensorflow提供的包括softmax运算和交叉熵损失计算的函数。
def loss(y_hat,y_true):
return tf.losses.sparse_categorical_crossentropy(y_true,y_hat)
训练多层感知机的步骤和3.6节中训练softmax回归的步骤没什么区别。我们直接调用d2l
包中的train_ch3
函数,它的实现已经在3.6节里介绍过。我们在这里设超参数迭代周期数为5,学习率为0.5。
注:由于原书的mxnet中的
SoftmaxCrossEntropyLoss
在反向传播的时候相对于沿batch维求和了,而PyTorch默认的是求平均,所以用PyTorch计算得到的loss比mxnet小很多(大概是maxnet计算得到的1/batch_size这个量级),所以反向传播得到的梯度也小很多,所以为了得到差不多的学习效果,我们把学习率调得成原书的约batch_size倍,原书的学习率为0.5,这里设置成100.0。(之所以这么大,应该是因为d2lzh_pytorch里面的sgd函数在更新的时候除以了batch_size,其实PyTorch在计算loss的时候已经除过一次了,sgd这里应该不用除了)
def acc(y_hat,y):
return np.mean((tf.argmax(y_hat,axis=1) == y))
num_epochs, lr = 5, 0.5
for epoch in range(num_epochs):
loss_all = 0
for x,y in train_iter:
with tf.GradientTape() as tape:
y_hat = net(x,w1,b1,w2,b2)
l = tf.reduce_mean(loss(y_hat,y))
loss_all += l.numpy()
grads = tape.gradient(l, [w1, b1, w2, b2])
w1.assign_sub(grads[0])
b1.assign_sub(grads[1])
w2.assign_sub(grads[2])
b2.assign_sub(grads[3])
print(epoch, 'loss:', l.numpy())
total_correct, total_number = 0, 0
for x,y in test_iter:
with tf.GradientTape() as tape:
y_hat = net(x,w1,b1,w2,b2)
y=tf.cast(y,'int64')
correct=acc(y_hat,y)
print(epoch,"test_acc:", correct)
输出:
0 loss: 1.0416569
0 test_acc: 0.75
1 loss: 1.0674641
1 test_acc: 0.75
2 loss: 0.90997523
2 test_acc: 0.875
3 loss: 0.8479213
3 test_acc: 0.9375
4 loss: 0.84292793
4 test_acc: 0.9375
- 可以通过手动定义模型及其参数来实现简单的多层感知机。
- 当多层感知机的层数较多时,本节的实现方法会显得较烦琐,例如在定义模型参数的时候。
注:本节除了代码之外与原书基本相同,原书传送门