参考文献:
[1]4.1. 多层感知机 — 动手学深度学习 2.0.0 documentation (d2l.ai)
[2]李沐Softmax模型的构建以及代码实现方法(未完工)-CSDN博客
一、线性模型的问题以及改进方式,多层感知机的理论基础:
1.线性模型存在的问题:
对于在之前文章中所提到的仿射变化[1],对于输入的标签和输出的数据呈正相关型数据,线性回归模型足够使用。但是仿射变换中的线性是一个很强的假设,在银行贷款偿还的问题中,年收入在0~5万美元的人相较于年收入在100~120万美元的人,还款可能性更大,不符合特征和还款数据的正相关性。
对于非线性的模型,我们难以通过softmax这样的线性模型去预测[2]。我们可以通过在网络中加入一个或者多个隐藏层来克服线性模型的局限,使其能处理更加普遍的函数关系类型。最简单的方法是使用多个隐藏层,彼此之间使用全连接层堆叠在一起。假设存在L层,前L-1层位表示层,最后一层位线性预测层,这种数据架构被称为多层感知机(MLP)。
2.多层感知机的定义以及各个层的作用:
多层感知机是一种前向传播的人工神经网络,包含输入层、输出层以及多个隐藏层。上述的神经网络中输入层的四个输入特征不涉及计算,只有隐藏层和输出层涉及计算,故这个多层感知机神经网络的层数为2。
二、为什么多层感知机是非线性的:
首先让我们回顾一下Softmax神经网络, ①输入的特征X的batch_size为d(上一章batch_size为10),向量长度为n(对于MNIST向量长度为28^2,784个元素的张量),X∈。②存在q个输出类别(上一章中输出类别数为4),故权重为W∈。③存在q个输出,输出的偏置为b∈
我们再看一下这个多层感知机模型,输入的矩阵特征为X∈。假设存在h个隐藏单元的单隐藏层多层感知机,用H∈表示隐藏层的输出(注意这里是输出不是偏置!!),输出的这些向量被称为隐藏表示(hidden representations)。由于隐藏层和输出层全连接涉及计算,我们可以分别假设隐藏层权重和输出层权重,隐藏层偏置和输出层偏置。隐藏层权重∈,隐藏层偏置∈。输出层权重∈,我们按照以下方式计算单隐藏层多层感知机的输出:
添加隐藏层之后,上面的隐藏单元()由输入的特征不断更新,方程式本身就是仿射函数,而使用隐藏层输入的特征得到的仿射函数依然是仿射函数。但是之前的线性模型能偶表示仍和仿射函数,仿射函数的仿射函数本身就是仿射函数。
为了证明“仿射函数的仿射函数=仿射函数”这一过程,将隐藏层的输出数值代入Softmax层的公式中,合并隐藏层可以得到参数和。得到如下公式证明包含隐藏层仍然是仿射函数。
三、激活函数的作用:
激活函数可以在隐藏层仿射变化之后对每个隐藏单元应用非线性的激活函数(例如RelU激活函数,在0附近变化巨大)。激活函数在公式中通常使用σ来表示,存在激活函数,多层感知机不会再退化成简单的线性模型:
在之前Softmax模型中我们讲到将图像转变成一个28*28的矩阵输入,使用激活函数后σ按照行的形式作用于其输入,一次计算一个样本。在本教程中,激活函数不仅仅按行计算还可以按矩阵中的元素进行计算,我们可以设置函数计算每个矩阵内元素的激活函数活性值σ。对于含有多个隐藏层的多层感知机,可以通过隐藏层之间输入和输出,对激活函数的活性值进行叠加,从而产生更有表达能力的模型。
多层感知机可以通过隐藏神经元,捕捉到隐藏层输入之间复杂的相互作用。在一对输入上进行基本逻辑操作,多层感知机是通用近似器。即便网络只有一个隐藏层,给定足够的神经元和正确的权重。
激活函数通过计算加权和并上偏置来确定神经元是否应该被激活,他们将输入信号转化为输出的可微计算。
四、多层感知机的实现以及Softmax模型的比较:
1.程序包的调用以及初始化模型参数:
在之前的Softmax模型课程中,Fashion-MNIST图像由28*28个灰度图像组成,一共被分为十个类别,我们可以将输入的图像看作是具有784个输入特征和10个类的简单分类数据集。在本次练习中我们将使用具有256个隐藏单元的单隐藏层的多层感知机。我们可以将上述的784个输入特征和256个隐藏单元视作超函数。通常,我们选择2的若干次幂作为层的宽度(256个隐藏单元)。
首先我们设置单个图像的输入特征数,输出的类别数,隐藏层的层数。对输入层和隐藏层与隐藏层和输出层的权值和偏重设置初始参数,将这些参数保存为一个名为params的数列。
import torch
from torch import nn
from d2l import torch as d2l
batzh_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
num_inputs, num_outputs, num_hiddens = 784, 10, 256
W1 = nn.Parameter(torch.randn(
num_inputs, num_hiddens, requires_grad=True) * 0.01)
b1 = nn.Parameter(torch.zeros(num_hiddens, requires_grad=True))
W2 = nn.Parameter(torch.randn(
num_hiddens, num_outputs, requires_grad=True) * 0.01)
b2 = nn.Parameter(torch.zeros(num_outputs, requires_grad=True))
params = [W1, b1, W2, b2]
2.定义relu函数并且调用:
注意此处调用的是自身定义的relu函数而不是pytorch自带的激活函数ReLU ,在定义激活函数的使用使用了torch.zeros_like()函数,这个函数的参数是torch.zeros_like(input, *, dtype=None, layout=None, device=None, requires_grad=False, memory_format=torch.preserve_format) ,可以将输入的特定维度的张量数值变为0(在*中设置特定维度),在下面relu()函数定义中,未设置特定的维度,故将张两种的数值全部变成零。
我们再看一下函数返回值torch.max()的函数,这个函数常用于函数定义的返回函数。该函数的参数是torch.max(input, dim, keepdim=False, *, out=None),输入的矩阵是X输出的矩阵是a(数值全为0的矩阵)。
def relu(X):
a = torch.zeros_like(X)
return torch.max(X, a)
3.模型&损失函数的定义:
参考文献:[3]NumPy: reshape() 更改数组的形状 |note.nkmk.me
对于二维图片不存在空间结构,可以将训练集图片的每个特征输入为一个长度为num_inputs的向量(即28*28=784,长度为784的向量)。我们可以看到这个定义的函数中包含了numpy.reshape的模块,通过该模块可以使数列按照从小到大的顺序进行排列。[3],转换好X的格式之后进行隐藏层的计算,返回的是隐藏层输入输出层输出的数值。
注意,这里@起到的是一个乘法的功能。
def net(X):
X = X.reshape((-1, num_inputs))
H = relu(X@W1 + b1)
return (H@W2 + b2)
损失函数这里直接调用的是API(pytorch自带的)中的交叉熵损失函数计算。
loss = nn.CrossEntropyLoss(reduction='none')
4.训练函数的定义:
此处我们选择调用本次课程中使用的工具包d2l,调用d2l包中的train_ch3函数,将迭代周期设置为10,学习率设置为0.1。
我们再利用之前Softmax中定义的预测函数d2l.predict_ch3()对数据集进行训练。
num_epochs, lr = 10, 0.1
updater = torch.optim.SGD(params, lr=lr)
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, updater)
d2l.predict_ch3(net, test_iter)