目录
一:回顾
在上一节,介绍了softmax回归,主要思想是利用将输出转换为有效的概率分布,它的实现思想很简单,步骤简单分为:1:获取数据,2:定义模型,3:定义损失函数,4:定义优化算法,5:训练,6:预测。如果你了解了优化算法的核心内容的话,你就明白整个训练和预测的处理过程了。接下来介绍 MLP感知机,
二:线性模型可能会出错
回顾softmax回归的模型架构中, 该模型通过单个仿射变换将我们的输入直接映射到输出,然后进行softmax操作,如果我们的标签通过仿射变换后确实与我们的输入数据相关,那么这种方法确实足够了。 但是,仿射变换中的线性是一个很强的假设。为什么怎么说呢?因为它假设了输入特征和输出类别之间的关系是线性的,即输入特征和输出类别之间的关系可以用一个线性变换来表示。这个假设在某些情况下是成立的,比如当输入特征和输出类别之间的关系本来就是线性的时候,但是在很多情况下,这个假设是不成立的,因为输入特征和输出类别之间的关系往往是更加复杂的非线性关系。这时候,我们需要使用更加复杂的模型来进行建模。
举个例子:如何对猫和狗的图像进行分类呢? 增加位置(13,17)处像素的强度是否总是增加(或降低)图像描绘狗的似然? 对线性模型的依赖对应于一个隐含的假设, 即区分猫和狗的唯一要求是评估单个像素的强度。 在一个倒置图像后依然保留类别的世界里,这种方法注定会失败。
三:在网络中加入隐藏层
我们可以通过在网络中加入一个或多个隐藏层来克服线性模型的限制, 使其能处理更普遍的函数关系类型。 要做到这一点,最简单的方法是将许多全连接层堆叠在一起。 每一层都输出到上面的层,直到生成最后的输出。 我们可以把前L−1层看作表示,把最后一层看作线性预测器。 这种架构通常称为多层感知机(multilayer perceptron),通常缩写为MLP。 下面,我们以图的方式描述了多层感知机。
这个多层感知机有4个输入,3个输出,其隐藏层包含5个隐藏单元。 输入层不涉及任何计算,因此使用此网络产生输出只需要实现隐藏层和输出层的计算。 因此,这个多层感知机中的层数为2。 注意,这两个层都是全连接的。 每个输入都会影响隐藏层中的每个神经元, 而隐藏层中的每个神经元又会影响输出层中的每个神经元。
四:从线性到非线性
同之前的章节一样, 我们通过矩阵X∈R(n*m) 来表示n个样本的小批量, 其中每个样本具有m个输入特征。 对于具有ℎ个隐藏单元的单隐藏层多层感知机, 用H∈R(N×ℎ)表示隐藏层的输出, 称为隐藏表示(hidden representations)。 在数学或代码中,H也被称为隐藏层变量(hidden-layer variable) 或隐藏变量(hidden variable)。 因为隐藏层和输出层都是全连接的, 所以我们有隐藏层权重W(1)∈R(m×ℎ) 和隐藏层偏置b(1)∈R(1×ℎ)以及输出层权重W(2)∈R(m×ℎ) 和输出层偏置b(2)∈R(1×ℎ)。 形式上,我们按如下方式计算单隐藏层多层感知机的输出 O∈R(n×ℎ)。
注意在添加隐藏层之后,模型现在需要跟踪和更新额外的参数。 可我们能从中得到什么好处呢?在上面定义的模型里,我们没有好处! 原因很简单:上面的隐藏单元由输入的仿射函数给出, 而输出(softmax操作前)只是隐藏单元的仿射函数。 仿射函数的仿射函数本身就是仿射函数, 但是我们之前的线性模型已经能够表示任何仿射函数。也就是结果仍然是线性模型!
为了发挥多层架构的潜力, 我们还需要一个额外的关键要素: 在仿射变换之后对每个隐藏单元应用非线性的激活函数(activation function)δ。 激活函数的输出(例如,δ(⋅))被称为活性值(activations)。 一般来说,有了激活函数,就不可能再将我们的多层感知机退化成线性模型:
由于X中的每一行对应于小批量中的一个样本, 出于记号习惯的考量, 我们定义非线性函数δ也以按行的方式作用于其输入, 即一次计算一个样本。 但是本节应用于隐藏层的激活函数通常不仅按行操作,也按元素操作。 这意味着在计算每一层的线性部分之后,我们可以计算每个活性值, 而不需要查看其他隐藏单元所取的值。对于大多数激活函数都是这样。一层叠一层,从而产生更有表达能力的模型。
五:激活函数
激活函数(activation function)通过计算加权和并加上偏置来确定神经元是否应该被激活, 它们将输入信号转换为输出的可微运算。 大多数激活函数都是非线性的。 由于激活函数是深度学习的基础,下面简要介绍一些常见的激活函数。
5.1:ReLU函数
最受欢迎的激活函数是修正线性单元(Rectified linear unit,ReLU), 因为它实现简单,同时在各种预测任务中表现良好。 ReLU提供了一种非常简单的非线性变换。 给定元素x,ReLU函数被定义为该元素与0的最大值:
通俗地说,ReLU函数通过将相应的活性值设为0,仅保留正元素并丢弃所有负元素。 为了直观感受一下,我们可以画出函数的曲线图。 正如从图中所看到,激活函数是分段线性的。
当输入为负时,ReLU函数的导数为0,而当输入为正时,ReLU函数的导数为1。 注意,当输入值精确等于0时,ReLU函数不可导。 在此时,我们默认使用左侧的导数,即当输入为0时导数为0。 我们可以忽略这种情况,因为输入可能永远都不会是0。
关于y.backward(torch.ones_like(x), retain_graph=True)的参数的含义:
torch.ones_like(x):
表示对于 x
张量中的每个元素,都赋值为 1,这就是指定了反向传播中每个元素的权重都是 1。
retain_graph=True
:作用是保留计算图,因为在计算完梯度之后,如果还需要进行更多的计算(但是部分情况下都不需要在backward函数中使用retain_graph=True
参数,因为PyTorch会在每次反向传播完成后自动释放计算图,从而释放内存。),保留计算图可以避免重新构建计算图带来的额外开销。如果在上个例子中 把retain_graph=True去掉,运行两次以上就会报错,所以如果您需要多次反向传播或在反向传播后访问中间结果的时候,你再在第一次反向传播时将 retain_graph
设置为True即可,以保留计算图。
使用ReLU的原因是,它求导表现得特别好:要么让参数消失,要么让参数通过。 这使得优化表现得更好,并且ReLU减轻了困扰以往神经网络的梯度消失问题
5.2:Sigmoid函数
sigmoid的梯度计算:
sigmoid函数的导数图像如下所示。 注意,当输入为0时,sigmoid函数的导数达到最大值0.25; 而输入在任一方向上越远离0点时,导数越接近0。
5.3:tanh函数
与sigmoid函数类似, tanh(双曲正切)函数也能将其输入压缩转换到区间(-1, 1)上。 tanh函数的公式如下
下面我们绘制tanh函数。 注意,当输入在0附近时,tanh函数接近线性变换。 函数的形状类似于sigmoid函数, 不同的是tanh函数关于坐标系原点中心对称。
sigmoid函数的导数是在其自变量(输入)为0时达到最大值0.25,而当自变量越接近正负无穷大时,其导数越趋近于0。也就是说,当自变量在任意方向上远离0时,导数都趋近于0
tanh的导数:
tanh函数的导数图像如下所示。 当输入接近0时,tanh函数的导数接近最大值1。 与我们在sigmoid函数图像中看到的类似, 输入在任一方向上越远离0点,导数越接近0。
这些知识已经是20世纪90年代的了,相比于之前,现在我们已经可以利用强大的开源深度学习框架快速构建模型。而以前训练这些网络需要研究人员编写数千行的C或Fortran代码。
六:多层感知机的从零开始实现
我们用几个张量来表示我们的参数。 注意,对于每一层我们都要记录一个权重矩阵和一个偏置向量。 跟以前一样,我们要为损失关于这些参数的梯度分配内存。
6.1:生成数据
# 通常,我们选择2的若干次幂作为层的宽度。 因为内存在硬件中的分配和寻址方式,这么做往往可以在计算上更高效。
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
num_inputs, num_outputs, num_hiddens = 784, 10, 256
# 乘0.01我的理解:randn是正态(0,1)分布。乘0.01使得分布为正态(0,0.1)分布,数据方差更小
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]
6.2:激活函数
def relu(x):
a = torch.zeros_like(x)
return torch.max(x,a)
#relu(torch.Tensor([1,3,0,0,-1]))
#tensor([1., 3., 0., 0., 0.])
6.3:模型
def net(X):
X = X.reshape((-1, num_inputs))
# 这里“@”代表矩阵乘法,把带有激活函数的输入层赋值隐藏层,也可以使用torch.matmul()
H = relu(X@W1 + b1)
return (H@W2 + b2)#返回隐藏层与输出的计算
loss:由于我们已经从零实现过softmax函数,因此在这里我们直接使用高级API中的内置函数来计算softmax和交叉熵损失。
注意:reduction要不要设置为‘none’具体是看你pytorch版本号的,如果你的这句报错了,就不要设置参数,我的pytorch是2.0版本的,不设置为'none'的话,plt画图不会输出trian_loss那条线
loss = nn.CrossEntropyLoss(reduction='none')
6.4:训练
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)
6.5:预测
七:总结
比较了softmax和MLP的性能,以及介绍了线性非线性,激活函数。手动实现一个简单的多层感知机是很容易的。然而如果有大量的层,从零开始实现多层感知机会变得很麻烦(例如,要命名和记录模型的参数)。
所有项目代码+UI界面
视频,笔记和代码,以及注释都已经上传网盘,放在主页置顶文章