Part 5 多层感知机
约 1358 字大约 5 分钟
2025-05-30
神经网络之所以得名,是因为其中的参数相互连接,形成类似于神经系统一样的网状结构。在之前我们实现的线性回归和 softmax 回归并非严格意义上的神经网络,因为其只有一层。
softmax 回归在结构上是对输入做线性变换后再通过 softmax 函数得到概率分布,但其本质仍然是线性模型,无法处理复杂的非线性关系。
而在这里我们将会学习真正的深度神经网络。最简单的深度神经网络称为多层感知机。它由多层神经元组成,每一层从它的上一层接收输入,又向下一层输出。
这一节我们会学习更多的基础概念,如隐藏层和激活函数。
1 多层感知机的数学基础
线性回归和 softmax 回归都是线性模型。但是这个世界并不总是线性的。我们需要寻找一些变换来突破线性模型的限制。例如,我们可以在输入和输出之间添加一个或者多个函数,使其能处理更普遍的函数关系类型,也就是隐藏层。
我们以图的方式描述多层感知机。
图示的多层感知机共有两层(输入层不计入,因为输入层不参与计算)。假设两层均为线性模型,那么:
对于从输入层到隐藏层,有
H=XW1+b1
对于从隐藏层到输出层,有
O=HW2+b2
不难发现最终输出O仍然是输入X的线性函数。那么这使得隐藏层失去了原本的意义——我们想引入隐藏层以突破线性模型的限制。
这启发我们,在隐藏层处理结束后,应该对结果应用非线性函数,以保证多层感知机不会退化为线性模型。这个非线性函数称为激活函数。
为了构建更通用的多层感知机,我们可以继续堆叠隐藏层。通过简单隐藏层的堆叠,实现对复杂函数的精确模拟。理论上,具有足够隐藏单元的一层感知机就能逼近任意连续函数;而多层结构则可以用更少的神经元,更高效地表示复杂函数。
1.1 ReLu 函数及其导数
修正线性单元(ReLU)提供了一种非常简单的非线性变换。
ReLU(x)=max{x,0}
它的导数为
dxdReLU(x)={0,x≤01,x>0
提示
事实上,根据严格的数学定义,ReLU 函数在 0 处的导数不存在。但是我们可以忽略这种情况,因为输入可能永远都不为 0。在深度学习领域内,“如果微妙的边界条件很重要,那么我们很可能是在研究数学而非工程”。
1.2 sigmoid 函数及其导数
对于一个定义域为全体实数的函数,挤压函数(sigmoid)可以将输入变换为(0,1)上的输出。
sigmoid(x)=1+exp(−x)1
它的导数为
dxdsigmoid(x)=(1+exp(−x))2exp(−x)=sigmoid(x)(1−sigmoid(x))
1.3 tanh 函数及其导数
与 sigmoid 函数类似,双曲正切函数(tanh)可以将全体实数上的输入变换为(−1,1)上的输出。
tanh(x)=1+exp(−2x)1−exp(−2x)
它的导数为
dxdtanh(x)=1−tanh2(x)
2 从框架实现多层感知机
我们还是以 softmax 回归时用的 Fashion-MINST 数据集训练。
与 softmax 的框架实现相比,唯一的区别就是在定义网络时添加了一层 ReLU 函数和一层线性层。
net = nn.Sequential(
nn.Flatten(),
nn.Linear(784, 256),
nn.ReLU(),
nn.Linear(256, 10),
)
其他部分完全一致。
可以看到,在经过 10 轮训练后,损失函数和准确率都比 softmax 回归好很多。
epoch 1, train loss 1.046, train acc 0.634, test acc 0.717
epoch 2, train loss 0.603, train acc 0.789, test acc 0.777
epoch 3, train loss 0.522, train acc 0.818, test acc 0.812
epoch 4, train loss 0.481, train acc 0.833, test acc 0.830
epoch 5, train loss 0.452, train acc 0.841, test acc 0.834
epoch 6, train loss 0.434, train acc 0.847, test acc 0.832
epoch 7, train loss 0.416, train acc 0.853, test acc 0.838
epoch 8, train loss 0.405, train acc 0.857, test acc 0.808
epoch 9, train loss 0.391, train acc 0.862, test acc 0.826
epoch 10, train loss 0.382, train acc 0.864, test acc 0.855
那我们继续叠加激活函数,训练表现会不会更好呢?很遗憾,并不会。简单堆叠层数并不总是带来性能提升。
net = nn.Sequential(
nn.Flatten(),
nn.Linear(784, 256),
nn.ReLU(),
nn.Linear(256, 128),
nn.ReLU(),
nn.Linear(128, 10)
)
epoch 1, train loss 1.860, train acc 0.299, test acc 0.526
epoch 2, train loss 0.914, train acc 0.652, test acc 0.613
epoch 3, train loss 0.691, train acc 0.750, test acc 0.750
epoch 4, train loss 0.590, train acc 0.788, test acc 0.799
epoch 5, train loss 0.527, train acc 0.811, test acc 0.810
epoch 6, train loss 0.489, train acc 0.824, test acc 0.802
epoch 7, train loss 0.458, train acc 0.835, test acc 0.834
epoch 8, train loss 0.433, train acc 0.843, test acc 0.838
epoch 9, train loss 0.417, train acc 0.849, test acc 0.821
epoch 10, train loss 0.403, train acc 0.855, test acc 0.827