全连接神经网络(MLP)
本教程共 60 篇 · 第 19 篇 · 更新于 2026-08-17 · 约 3 分钟阅读
本节目标:理解为什么单层线性模型不够用,学会用「线性层 + 激活函数」搭出多层网络。
一条直线搞不定的事
上一章的线性回归,本质是在画一条直线。直线能拟合的数据,形状都很简单。
现实问题没这么好说话。垃圾邮件不是一刀切能分的,图片分类的边界弯弯曲曲。单层线性模型遇到这种问题,只能干瞪眼。
怎么办?把多条直线拼起来,就能逼近任意曲线。这就是多层网络的想法。
隐藏层:中间加一层
全连接神经网络,也叫多层感知机(MLP,Multi-Layer Perceptron)。结构就三层:
- 输入层:数据进来的地方
- 隐藏层(hidden layer):中间做加工的一层或多层
- 输出层:给出最终结果
「全连接」的意思是:相邻两层之间,每个神经元都和另一层的所有神经元相连。
每个神经元干的事,可以拆成两步:先把所有输入加权求和,再过一道激活函数。一整层的神经元合起来,就是一次矩阵乘法加一次变换。所以 nn.Linear 一个层,就代表一层神经元。
在 PyTorch 里,一个隐藏层就是一个 nn.Linear。上一层的输出,喂给下一层当输入:
import torch.nn as nn
model = nn.Sequential(
nn.Linear(4, 8), # 隐藏层:4 个输入 -> 8 个神经元
nn.ReLU(), # 激活函数
nn.Linear(8, 1), # 输出层:8 -> 1
)
数据流:4 个特征进来,隐藏层放大到 8 维,再压缩成 1 个输出。
顺手算算参数量:第一层 4×8 个权重加 8 个偏置,共 40 个;第二层 8×1 加 1,共 9 个。整个模型 49 个参数,一目了然。以后看网络大小,就按这个思路手算。
如果只有输入和输出两层、中间不加隐藏层,那就是上一章的逻辑回归。加不加隐藏层,是 MLP 和它的本质区别。
激活函数:非线性的开关
重点来了:隐藏层之间必须夹激活函数(activation function)。少了它,叠多少层都白搭。
道理很简单。线性层算的是 wx + b,线性函数套线性函数,结果还是线性函数。三层线性网络,数学上等价于一层。模型一点没变强,参数倒多了一堆。
激活函数引入非线性,网络才真正「深」起来。最常用的是 ReLU:
import torch.nn.functional as F
x = torch.tensor([-2.0, -0.5, 0.0, 3.0])
print(F.relu(x)) # 负数归零,正数原样
ReLU 就是把负数砍成 0。实现简单、计算快,是深度学习默认首选。用的时候注意:输入为负的神经元会彻底失活,梯度传不过去,这也是 ReLU 唯一的小脾气。
除了 ReLU,还有两个老牌激活函数:sigmoid 把值压到 0 到 1 之间,适合当概率输出;tanh 压到 -1 到 1,早期网络常用。它们梯度容易饱和,现在隐藏层基本被 ReLU 家族取代。知道名字、知道 ReLU 是主角,就够了。
Note输出层一般不再加 ReLU。回归任务直接输出数值,分类任务交给损失函数内部处理(交叉熵自带 softmax)。
用 MLP 学一条曲线
上一章线性回归拟合直线,这次让 MLP 拟合抛物线,验证「多层真的更强」:
import torch
import torch.nn as nn
torch.manual_seed(0)
# 1. 造数据:y = x^2 的曲线
x = torch.linspace(-2, 2, 200).unsqueeze(1)
y = x ** 2
# 2. 定义 MLP:两个隐藏层
model = nn.Sequential(
nn.Linear(1, 16),
nn.ReLU(),
nn.Linear(16, 16),
nn.ReLU(),
nn.Linear(16, 1),
)
# 3. 训练
criterion = nn.MSELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.02)
for epoch in range(2000):
pred = model(x)
loss = criterion(pred, y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
if (epoch + 1) % 500 == 0:
print(f"epoch {epoch + 1}, loss = {loss.item():.4f}")
2000 轮后 loss 会降到很小。直线永远拟合不了抛物线,MLP 可以。
这个例子也顺便演示了怎么描述网络:两个隐藏层各 16 个神经元,就叫 16-16 网络。以后看论文、看模型名,都是这个口径。
Tip记住这个规律:隐藏层神经元越多,模型越「能装」,但也越容易过拟合。从小的开始,够用就好。
形状怎么流动
MLP 最常翻车的点,是层与层之间的形状对不上。规则很简单:上一层的输出维度,必须等于下一层的输入维度。
x = torch.randn(32, 4) # 一个 batch 32 个样本,每个 4 个特征
out = model(x)
print(out.shape) # torch.Size([32, 1])
batch 维度(32)从头到尾不动,变的只是特征维度。把每层输入输出写成一条链:4 → 8 → 1,一目了然。
想核对网络结构,直接 print(model)。每一层的输入输出维度都列得清清楚楚,报错时先看它,比瞎猜快得多。
要是哪层对不上,报错会提示类似 “mat1 and mat2 shapes cannot be multiplied” 的信息。翻译过来就是:上一层的输出维度和这一层的输入维度不匹配。顺着链往回查,多半是某个 nn.Linear 的第一个参数写错了。
为什么激活函数能「万能」
理论上有句话:带一个隐藏层的网络,只要神经元够多,就能逼近任意连续函数。这叫万能逼近定理(universal approximation theorem),是 MLP 的理论底气。
实操中别真的只堆一层。深层网络更容易学到层次化特征,训练也更高效。这也是后面 CNN、Transformer 的思路起点。
MLP 的主场和局限
MLP 最擅长的是表格型数据:每一行一个样本,每一列一个特征。房价预测、信用评分、点击率预估,都是它的主场。
到了图像和序列,MLP 就力不从心了。它不知道像素之间的邻居关系,也记不住时间顺序。于是有了为图像设计的 CNN(第 20 章),为序列设计的 RNN 和 Transformer(第 22、23 章)。模型结构跟着数据形态走,这是选型的第一原则。
最后提醒一个隐蔽的坑:忘了加激活函数,网络不会报错,只是悄悄退化成线性模型。怀疑自己踩了,就把 nn.ReLU() 的行数数一遍,隐藏层之间必须一一对应。