深度学习学习笔记(4)——多层感知机

 本文记录了在学习pytorch版《动手学深度学习》时所涉及到的知识和所做的练习。

 原书地址:https://tangshusen.me/Dive-into-DL-PyTorch/#/

1 隐藏层

 在前面学习的线性回归和softmax回归中,都是只用到了单层的网络,从输入层直接就到了输出层,比如softmax中,可以画出网络的结构图

在这里插入图片描述

 从这个结构图中,可以发现,每一个输出层的输出,都是由所有的输入共同来决定的,这种输出层被称为全连接层。

多层感知机就是含有隐藏层的由全连接层组成的神经网络,而隐藏层是在输入层和输出层之间,并且可以有多个隐藏层,比如下面的网络结构图,就含有一个隐藏层,这个隐藏层中含有5个隐藏单元

在这里插入图片描述

 并且隐藏层的所有单元,都是由全部的输入共同决定的,而输出层的所有单元,也都是由隐藏层所共同决定的,所以这个神经网络的隐藏层和输出层都是全连接层。因为输入层不涉及计算,所以这是一个两层的神经网络。

 对于这种单隐藏层的神经网络,设输入批量大小为n,输入的个数为d,那么令输入为$X \in R^{n \times d}$,而假设隐藏层有h个隐藏单元,那么令隐藏层的输出为$H \in R^{n \times h}$,输出层有q个隐藏单元,那么令隐藏层的输出为$O \in R^{n \times q}$。其中,隐藏层和输出层都是全连接层,所以可以设隐藏层的权重参数何偏差参数分别为$W_h \in R^{d \times h}$和$b_h \in R^{1 \times h}$,输出层的权重参数何偏差参数分别为$W_o \in R^{h \times q}$和$b_o \in R^{1 \times q}$,那么这个模型可以表示为

 将两个式子联立起来,可以得到

 可以看出来,其实加了隐藏层以后,虽然是两层的网络模型,但是其实依然可以等价于权重参数为$W_hW_o$,偏差参数为$b_hW_o+b_o$的单层的网络。造成这样的原因是因为全连接层只是做的一个仿射变换,不管多少个全连接层叠加在一起也都只能做仿射变换。仿射变换的意思是把一个东西映射到另一个空间中,在几何上就类似于把一个图形翻转,拉伸这样的操作,矩阵的乘法加法就是在做仿射变换。

2 激活函数

 要想解决上面的问题,就要引入一个激活函数。激活函数是一个非线性的函数,一般在隐藏层输出以后,再对每个单元都做这样一个非线性变换,再作为下一个全连接层的输入。之前实现的softmax回归其实也用到了softmax这个激活函数,它常常用来作为输出层的激活函数。其他常用的激活函数还有以下几种。

2.1 ReLU函数

 ReLU函数的定义式为

 定义式非常简单,而且可以发现,ReLU函把小于0的部分都去掉了,只保留了正数部分。可以通过matplotlib画图来看看。

import matplotlib.pyplot as plt
import torch

# 定义一个ReLU函数
def ReLU(x):
    #torch.max可以让input的元素和other的元素逐个对比,取两者的最大值,最终返回的也是一个tensor
    return torch.max(input=x, other=torch.tensor(0.))

# 从-5到5,步长为1初始化出一个tensor
x = torch.arange(-4.0,4.0,0.1, requires_grad=True)
y = ReLU(x)
print('x=',x)
print('ReLU(x)=',y)

# 定义绘图函数
def plot(x, y, name):
    plt.figure(figsize=(9,6))
    # 画图传进去的参数是不需要梯度numpy,而x和y都是有梯度的tensor,所以使用detach来截断梯度,再转成numpy进行绘图
    plt.plot(x.detach().numpy(), y.detach().numpy())
    plt.title(name)
    plt.show()
    
plot(x,y, 'ReLU')

输出结果:
x= tensor([-4.0000, -3.9000, -3.8000, -3.7000, -3.6000, -3.5000, -3.4000, -3.3000,
        -3.2000, -3.1000, -3.0000, -2.9000, -2.8000, -2.7000, -2.6000, -2.5000,
        -2.4000, -2.3000, -2.2000, -2.1000, -2.0000, -1.9000, -1.8000, -1.7000,
        -1.6000, -1.5000, -1.4000, -1.3000, -1.2000, -1.1000, -1.0000, -0.9000,
        -0.8000, -0.7000, -0.6000, -0.5000, -0.4000, -0.3000, -0.2000, -0.1000,
         0.0000,  0.1000,  0.2000,  0.3000,  0.4000,  0.5000,  0.6000,  0.7000,
         0.8000,  0.9000,  1.0000,  1.1000,  1.2000,  1.3000,  1.4000,  1.5000,
         1.6000,  1.7000,  1.8000,  1.9000,  2.0000,  2.1000,  2.2000,  2.3000,
         2.4000,  2.5000,  2.6000,  2.7000,  2.8000,  2.9000,  3.0000,  3.1000,
         3.2000,  3.3000,  3.4000,  3.5000,  3.6000,  3.7000,  3.8000,  3.9000],
       requires_grad=True)
ReLU(x)= tensor([0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
        0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
        0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
        0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
        0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.1000, 0.2000, 0.3000, 0.4000,
        0.5000, 0.6000, 0.7000, 0.8000, 0.9000, 1.0000, 1.1000, 1.2000, 1.3000,
        1.4000, 1.5000, 1.6000, 1.7000, 1.8000, 1.9000, 2.0000, 2.1000, 2.2000,
        2.3000, 2.4000, 2.5000, 2.6000, 2.7000, 2.8000, 2.9000, 3.0000, 3.1000,
        3.2000, 3.3000, 3.4000, 3.5000, 3.6000, 3.7000, 3.8000, 3.9000],
       grad_fn=<MaxBackward2>)

在这里插入图片描述
 我们还可以绘制出这个ReLU函数的导数的图像,求导的方法还是直接使用pytorch提供的自动求导的方法。
# y是一个tensor,要先转换成标量
y.sum().backward()
plot(x, x.grad, 'ReLU_grad')

输出结果:
在这里插入图片描述

2.2 sigmoid函数

 sigmoid函数也是一个比较常用的函数,和之前的softmax函数一样,也如果直接作为输出层的激活函数,则把这个模型称为逻辑回归。逻辑回归一般用来解决二分类的问题,而这个sigmoid函数可以把输出结果转换到0到1的区间上。它的表达式为

 和上面一样实现一个sigmoid激活函数,并画出sigmoid的图像。

def sigmoid(x):
    return 1.0 / (1.0+(-1.0*x).exp())
x = torch.arange(-4.0,4.0,0.1, requires_grad=True)
y = sigmoid(x)
print('x=',x)
print('sigmoid(x)=',y)
plot(x, y, 'sigmoid')

输出结果:

x= tensor([-4.0000, -3.9000, -3.8000, -3.7000, -3.6000, -3.5000, -3.4000, -3.3000,
        -3.2000, -3.1000, -3.0000, -2.9000, -2.8000, -2.7000, -2.6000, -2.5000,
        -2.4000, -2.3000, -2.2000, -2.1000, -2.0000, -1.9000, -1.8000, -1.7000,
        -1.6000, -1.5000, -1.4000, -1.3000, -1.2000, -1.1000, -1.0000, -0.9000,
        -0.8000, -0.7000, -0.6000, -0.5000, -0.4000, -0.3000, -0.2000, -0.1000,
         0.0000,  0.1000,  0.2000,  0.3000,  0.4000,  0.5000,  0.6000,  0.7000,
         0.8000,  0.9000,  1.0000,  1.1000,  1.2000,  1.3000,  1.4000,  1.5000,
         1.6000,  1.7000,  1.8000,  1.9000,  2.0000,  2.1000,  2.2000,  2.3000,
         2.4000,  2.5000,  2.6000,  2.7000,  2.8000,  2.9000,  3.0000,  3.1000,
         3.2000,  3.3000,  3.4000,  3.5000,  3.6000,  3.7000,  3.8000,  3.9000],
       requires_grad=True)
sigmoid(x)= tensor([0.0180, 0.0198, 0.0219, 0.0241, 0.0266, 0.0293, 0.0323, 0.0356, 0.0392,
        0.0431, 0.0474, 0.0522, 0.0573, 0.0630, 0.0691, 0.0759, 0.0832, 0.0911,
        0.0998, 0.1091, 0.1192, 0.1301, 0.1419, 0.1545, 0.1680, 0.1824, 0.1978,
        0.2142, 0.2315, 0.2497, 0.2689, 0.2891, 0.3100, 0.3318, 0.3543, 0.3775,
        0.4013, 0.4256, 0.4502, 0.4750, 0.5000, 0.5250, 0.5498, 0.5744, 0.5987,
        0.6225, 0.6457, 0.6682, 0.6900, 0.7109, 0.7311, 0.7503, 0.7685, 0.7858,
        0.8022, 0.8176, 0.8320, 0.8455, 0.8581, 0.8699, 0.8808, 0.8909, 0.9002,
        0.9089, 0.9168, 0.9241, 0.9309, 0.9370, 0.9427, 0.9478, 0.9526, 0.9569,
        0.9608, 0.9644, 0.9677, 0.9707, 0.9734, 0.9759, 0.9781, 0.9802],
       grad_fn=<MulBackward0>)

在这里插入图片描述
 从图中可以发现,越靠近0的部分,越接近于线性变换。在早期的神经网络中常常使用sigmoid作为激活函数,但是现在ReLu函数使用更多一些。

 然后再画出sigmoid的导数图像。sigmoid的导数在0的地方处于最大值0.25,而离0越远的地方导数值也就越接近于0。

# y是一个tensor,要先转换成标量
y.sum().backward()
plot(x, x.grad, 'sigmoid_grad')

输出结果:
在这里插入图片描述

2.3 tanh函数

 tanh函数也被称为双曲正切函数,可以把输出值变到-1到1的区间上。它的表达式为

 然后画出tanh函数的图像。

def tanh(x):
    return (1.0-(-2.0*x).exp()) / (1.0+(-2.0*x).exp())

x = torch.arange(-4.0,4.0,0.1, requires_grad=True)
y = tanh(x)
print('x=',x)
print('tanh(x)=',y)
plot(x, y, 'tanh')

输出结果:
x= tensor([-4.0000, -3.9000, -3.8000, -3.7000, -3.6000, -3.5000, -3.4000, -3.3000,
        -3.2000, -3.1000, -3.0000, -2.9000, -2.8000, -2.7000, -2.6000, -2.5000,
        -2.4000, -2.3000, -2.2000, -2.1000, -2.0000, -1.9000, -1.8000, -1.7000,
        -1.6000, -1.5000, -1.4000, -1.3000, -1.2000, -1.1000, -1.0000, -0.9000,
        -0.8000, -0.7000, -0.6000, -0.5000, -0.4000, -0.3000, -0.2000, -0.1000,
         0.0000,  0.1000,  0.2000,  0.3000,  0.4000,  0.5000,  0.6000,  0.7000,
         0.8000,  0.9000,  1.0000,  1.1000,  1.2000,  1.3000,  1.4000,  1.5000,
         1.6000,  1.7000,  1.8000,  1.9000,  2.0000,  2.1000,  2.2000,  2.3000,
         2.4000,  2.5000,  2.6000,  2.7000,  2.8000,  2.9000,  3.0000,  3.1000,
         3.2000,  3.3000,  3.4000,  3.5000,  3.6000,  3.7000,  3.8000,  3.9000],
       requires_grad=True)
tanh(x)= tensor([-0.9993, -0.9992, -0.9990, -0.9988, -0.9985, -0.9982, -0.9978, -0.9973,
        -0.9967, -0.9959, -0.9951, -0.9940, -0.9926, -0.9910, -0.9890, -0.9866,
        -0.9837, -0.9801, -0.9757, -0.9705, -0.9640, -0.9562, -0.9468, -0.9354,
        -0.9217, -0.9051, -0.8854, -0.8617, -0.8337, -0.8005, -0.7616, -0.7163,
        -0.6640, -0.6044, -0.5370, -0.4621, -0.3799, -0.2913, -0.1974, -0.0997,
         0.0000,  0.0997,  0.1974,  0.2913,  0.3799,  0.4621,  0.5370,  0.6044,
         0.6640,  0.7163,  0.7616,  0.8005,  0.8337,  0.8617,  0.8854,  0.9051,
         0.9217,  0.9354,  0.9468,  0.9562,  0.9640,  0.9705,  0.9757,  0.9801,
         0.9837,  0.9866,  0.9890,  0.9910,  0.9926,  0.9940,  0.9951,  0.9959,
         0.9967,  0.9973,  0.9978,  0.9982,  0.9985,  0.9988,  0.9990,  0.9992],
       grad_fn=<DivBackward0>)

在这里插入图片描述
 这个图像和sigmoid非常相似,不过它的值域是-1到1之间,而sigmoid是0到1之间。同样它的导数图像和sigmoid也非常类似,在0处导数的最大值为1。
# y是一个tensor,要先转换成标量
y.sum().backward()
plot(x, x.grad, 'tanh_grad')

输出结果:
在这里插入图片描述

3 多层感知机的简单实现

 这里依然使用前面的FashionMINST数据集,不过这次是使用多层感知机来对这个图片进行分类。

3.1 数据读入

 代码和之前一样,只是模型参数上面会和之前有一些变化,因为之前只有一层网络,只需要设定一组参数。而这次使用的模型是带有ReLU激活函数的两层神经网络,所以还要设置隐藏层的单元个数。

import torchvision
import torchvision.transforms as transforms
import torch.utils.data as data
import numpy as np
import torch

BATCH_SIZE=256
lr = 0.05
NUM_EPOCH = 10
NUM_INPUT = 784
# 设置隐藏层为256个隐藏单元
NUM_HIDDEN = 256
NUM_OUTPUT = 10

mnist_train = torchvision.datasets.FashionMNIST(root='~/Datasets/FashionMNIST', train=True, download=True, transform=transforms.ToTensor())
mnist_test = torchvision.datasets.FashionMNIST(root='~/Datasets/FashionMNIST', train=True, download=True, transform=transforms.ToTensor())

train_iter = data.DataLoader(mnist_train, batch_size=BATCH_SIZE, shuffle=True)
test_iter = data.DataLoader(mnist_test, batch_size=BATCH_SIZE, shuffle=True)

# 初始化模型参数,w1和b1是隐藏层的模型参数,w2和b2是输出层的模型参数,
w1 = torch.tensor(np.random.normal(0, 0.01, (NUM_INPUT, NUM_HIDDEN)), requires_grad=True, dtype=torch.float)
w2 = torch.tensor(np.random.normal(0, 0.01, (NUM_HIDDEN, NUM_OUTPUT)), requires_grad=True, dtype=torch.float)
b1 = torch.zeros(NUM_HIDDEN, requires_grad=True, dtype=torch.float)
b2 = torch.zeros(NUM_OUTPUT, requires_grad=True, dtype=torch.float)

3.2 模型定义和训练

 这里的模型还是使用的之前的模型,唯一需要改变的就是net这个函数,要把它改为使用了ReLU激活函数的两层神经网络结构。代码中没有注释的地方都是前面写过的代码。对于这部分代码,为了提高效率,其实可以写成一个函数,封装在一个文件中,这样就可以直接调用了,而且训练模型的代码,可以发现每次都几乎一样,也完全可以封装成一个函数,加快效率。

def softmax(y):
    y_exp = y.exp()
    y_exp_sum = y_exp.sum(dim=1, keepdim=True)
    return y_exp / y_exp_sum

def ReLU(x):
    return torch.max(input=x, other=torch.tensor(0.))

def sgd(params, learning_rate, batch_size):
    for param in params:
        param.data = param.data - learning_rate * param.grad / batch_size
        
def crossEntropy(y_pred, y):
    return -1 * torch.log(y_pred.gather(1,y.view(-1,1)))

# 需要改动的地方:定义两层的神经网络
def net(X):
    # 把X变成1*784的矩阵,然后和权重参数做矩阵乘法,再加上偏差
    x = X.view(-1, NUM_INPUT) @ w1 + b1
    # 再对隐藏层输出做ReLU操作
    h = ReLU(x)
    # 再由输出层做softmax操作
    return softmax(h @ w2 + b2)

def accuracy(data_iter, net):
    acc_num = 0
    total_num = 0
    for X, y in data_iter:
        y_pred = net(X)
        acc_num += (y_pred.argmax(dim=1) == y).float().sum().item()
        total_num += y.shape[0]
    return acc_num / total_num

loss_fn = crossEntropy

for epoch in range(NUM_EPOCH):
    train_loss, train_acc, total_num = 0, 0, 0
    
    for X, y in train_iter:
        y_pred = net(X)
        loss = loss_fn(y_pred, y).sum()
        loss.backward()
        # sgd优化算法,这里的参数需要改成四个参数
        sgd([w1,b1, w2, b2], lr, BATCH_SIZE)
        # 四个参数都要梯度清零
        w1.grad.data.zero_()
        b1.grad.data.zero_()
        w2.grad.data.zero_()
        b2.grad.data.zero_()
        
        train_loss += loss.item()
        train_acc += (y_pred.argmax(dim=1) == y).float().sum().item()
        total_num += y.shape[0]
    test_acc = accuracy(test_iter, net)
    print('当前第{}轮 训练集:loss = {:.4f}, acc = {:.4f}  测试集:acc = {:.4f}'.format(epoch+1, train_loss/total_num, train_acc/total_num, test_acc))

输出结果:
当前第1轮 训练集:loss = 0.7208, acc = 0.7402  测试集:acc = 0.7758
当前第2轮 训练集:loss = 0.6048, acc = 0.7921  测试集:acc = 0.8123
当前第3轮 训练集:loss = 0.5472, acc = 0.8124  测试集:acc = 0.8208
当前第4轮 训练集:loss = 0.5117, acc = 0.8232  测试集:acc = 0.8317
当前第5轮 训练集:loss = 0.4874, acc = 0.8322  测试集:acc = 0.8381
当前第6轮 训练集:loss = 0.4737, acc = 0.8354  测试集:acc = 0.8280
当前第7轮 训练集:loss = 0.4588, acc = 0.8391  测试集:acc = 0.8462
当前第8轮 训练集:loss = 0.4473, acc = 0.8442  测试集:acc = 0.8495
当前第9轮 训练集:loss = 0.4372, acc = 0.8475  测试集:acc = 0.8502
当前第10轮 训练集:loss = 0.4304, acc = 0.8495  测试集:acc = 0.8524

 可以发现,测试集的准确率相比之前的单层网络更高,接下来还是对测试集的几张图片来进行分类,其中的代码都是之前使用过的代码。
def get_fashionMNIST_label(label):
    text_labels = ['t-shirt', 'trouser', 'pullover', 'dress', 'coat',
                   'sandal', 'shirt', 'sneaker', 'bag', 'ankle boot']
    return [text_labels[i] for i in label]

def show_fashion_mnist(images, labels):
    fig, ax = plt.subplots(1, len(images), figsize=(12, 12))
    for a, img, lbl in zip(ax, images, labels):
        a.imshow(img.view((28, 28)).cpu().numpy())
        a.set_title(lbl)
        a.axes.get_xaxis().set_visible(False)
        a.axes.get_yaxis().set_visible(False)
    plt.show()

X, y = iter(test_iter).next()

true_labels = get_fashionMNIST_label(y.cpu().numpy())
pred_labels = get_fashionMNIST_label(net(X).argmax(dim=1).cpu().numpy())
titles = [true + '\n' + pred for true, pred in zip(true_labels, pred_labels)]
show_fashion_mnist(X[0:8], titles[0:8])

输出结果:
在这里插入图片描述

4 pytorch版多层感知机

 还是和之前一样,在自己实现了网络以后再用pytorch提供的工具来简化实现的代码,首先还是读取数据,这一部分还是和上面一样的。

import torchvision
import torch
import torch.nn as nn
import torch.utils.data as data
import torchvision.transforms as transforms
import torch.nn.init as init
import torch.nn.functional as F
# 设定超参数
lr = 0.05
BATCH_SIZE = 256
NUM_EPOCH = 10

NUM_INPUT=784
NUM_HIDDEN = 256
NUM_OUTPUT = 10

mnist_train = torchvision.datasets.FashionMNIST(root='~/Datasets/FashionMNIST', train=True, download=True, transform=transforms.ToTensor())
mnist_test = torchvision.datasets.FashionMNIST(root='~/Datasets/FashionMNIST', train=False, download=True, transform=transforms.ToTensor())

train_iter = data.DataLoader(mnist_train, batch_size=BATCH_SIZE, shuffle=True)
test_iter = data.DataLoader(mnist_test, batch_size=BATCH_SIZE, shuffle=True)

 pytorch版相比之前要改动的地方就是定义模型,还是使用torch.nn来构建网络。pytorch提供了一个torch.nn.functional这个包,里面有很多常用的函数,所以像ReLU函数就不在需要自己定义了,直接调用就可以了。其他部分的代码也都是前面写过很多遍的代码了。

 为了在提高效率,这里把之前写的训练的代码定义为一个函数,并且放到自己定义的一个工具包pytorch_tools.py,地址:https://github.com/nsytsqdtn/tools

class MLPNet(nn.Module):
    def __init__(self, NUM_INPUT, NUM_HIDDEN, NUM_OUTPUT):
        super(MLPNet, self).__init__()
        # 隐藏层
        self.hidden = nn.Linear(NUM_INPUT, NUM_HIDDEN)
        # 输出层
        self.output = nn.Linear(NUM_HIDDEN, NUM_OUTPUT)
        
    def forward(self, x):
        # 隐藏层的输出做一个relu操作
        hidden = F.relu(self.hidden(x.view(x.shape[0], -1)))
        # 输出层结果
        output = self.output(hidden)
        return output

net = MLPNet(NUM_INPUT, NUM_HIDDEN, NUM_OUTPUT)
net.cuda()
# 要把每层网络的参数都初始化
init.normal_(net.hidden.weight, mean=0, std=0.01)
init.constant_(net.hidden.bias, val=0)
init.normal_(net.output.weight, mean=0, std=0.01)
init.constant_(net.output.bias, val=0)


optimizer = torch.optim.SGD(net.parameters(), lr=lr)

loss_fn = nn.CrossEntropyLoss()

import pytorch_tools
pytorch_tools.train(net, train_iter, NUM_EPOCH, loss_fn, optimizer, test_iter=test_iter)

输出结果:
当前第1轮 训练集:loss = 0.0052, acc = 0.5896  测试集:acc = 0.6955
当前第2轮 训练集:loss = 0.0028, acc = 0.7413  测试集:acc = 0.7724
当前第3轮 训练集:loss = 0.0024, acc = 0.7932  测试集:acc = 0.7935
当前第4轮 训练集:loss = 0.0021, acc = 0.8137  测试集:acc = 0.8003
当前第5轮 训练集:loss = 0.0020, acc = 0.8235  测试集:acc = 0.8116
当前第6轮 训练集:loss = 0.0019, acc = 0.8300  测试集:acc = 0.8078
当前第7轮 训练集:loss = 0.0018, acc = 0.8361  测试集:acc = 0.8225
当前第8轮 训练集:loss = 0.0018, acc = 0.8406  测试集:acc = 0.8201
当前第9轮 训练集:loss = 0.0018, acc = 0.8435  测试集:acc = 0.8257
当前第10轮 训练集:loss = 0.0017, acc = 0.8468  测试集:acc = 0.8295

 最后再来测试一下模型的效果。
import matplotlib.pyplot as plt
def get_fashionMNIST_label(label):
    text_labels = ['t-shirt', 'trouser', 'pullover', 'dress', 'coat',
                   'sandal', 'shirt', 'sneaker', 'bag', 'ankle boot']
    return [text_labels[i] for i in label]

def show_fashion_mnist(images, labels):
    fig, ax = plt.subplots(1, len(images), figsize=(12, 12))
    for a, img, lbl in zip(ax, images, labels):
        a.imshow(img.view((28, 28)).cpu().numpy())
        a.set_title(lbl)
        a.axes.get_xaxis().set_visible(False)
        a.axes.get_yaxis().set_visible(False)
    plt.show()

X, y = iter(test_iter).next()
X = X.cuda()
y = y.cuda()

true_labels = get_fashionMNIST_label(y.cpu().numpy())
pred_labels = get_fashionMNIST_label(net(X).argmax(dim=1).cpu().numpy())
titles = [true + '\n' + pred for true, pred in zip(true_labels, pred_labels)]
show_fashion_mnist(X[0:8], titles[0:8])

输出结果:
在这里插入图片描述

5 总结

  • 多层感知机在输出层与输入层之间加入了一个或多个全连接隐藏层,并通过激活函数对隐藏层输出进行非线性变换。
  • 常用的激活函数包括ReLU函数、sigmoid函数和tanh函数。