PyTorch多层感知机(MLP)模型详解
import torch from torch import nn class MyModel(nn.Module):
def __init__(self):
super().__init__()
self.linear = nn.Linear(10, 1)
def forward(self, x):
return self.linear(x)
这段代码定义的是一个多层感知机模型,也就是常说的 MLP:Multi-Layer Perceptron。
它比你上一段 MyModel 更灵活,因为上一段模型的输入维度和输出维度写死了:
nn.Linear(10, 1)
而这段代码把输入维度、隐藏层维度、输出维度都做成了参数:
input_dim
hidden_dim
output_dim
所以它可以适配不同任务。
1. 完整代码
class MLP(nn.Module):
def __init__(self, input_dim, hidden_dim, output_dim):
super().__init__()
self.net = nn.Sequential(
nn.Linear(input_dim, hidden_dim),
nn.ReLU(),
nn.Linear(hidden_dim, output_dim)
)
def forward(self, x):
return self.net(x)
它的整体结构是:
输入 x
↓
线性层:input_dim → hidden_dim
↓
ReLU 激活函数
↓
线性层:hidden_dim → output_dim
↓
输出
可以画成:
x: [batch_size, input_dim]
↓
nn.Linear(input_dim, hidden_dim)
↓
nn.ReLU()
↓
nn.Linear(hidden_dim, output_dim)
↓
output: [batch_size, output_dim]
2. class MLP(nn.Module):
class MLP(nn.Module):
这里定义了一个模型类,名字叫 MLP。
MLP 是英文 Multi-Layer Perceptron 的缩写,中文一般叫:
多层感知机
它继承自:
nn.Module
也就是说,MLP 是一个标准的 PyTorch 神经网络模型。
只要继承了 nn.Module,PyTorch 就可以帮你管理:
模型参数
自动求导
训练模式 / 推理模式
GPU 转移
参数保存和加载
3. __init__:定义模型结构
def __init__(self, input_dim, hidden_dim, output_dim):
__init__ 是模型初始化函数。
当你创建模型时:
model = MLP(input_dim=10, hidden_dim=32, output_dim=1)
Python 会自动调用这个函数。
这三个参数分别表示:
input_dim 输入维度
hidden_dim 隐藏层维度
output_dim 输出维度
例如:
model = MLP(10, 32, 1)
意思是:
输入是 10 维
中间隐藏层是 32 维
输出是 1 维
结构就是:
10 → 32 → 1
4. super().__init__()
super().__init__()
这行是在初始化父类 nn.Module。
可以理解为:
先让 PyTorch 把“一个模型该有的基础功能”准备好。
比如后面这段:
self.net = nn.Sequential(...)
之所以能被 PyTorch 识别为模型的一部分,就是因为前面调用了:
super().__init__()
如果不写它,模型参数可能无法被正确注册,训练时就会出问题。
5. nn.Sequential:按顺序搭模型
self.net = nn.Sequential(
nn.Linear(input_dim, hidden_dim),
nn.ReLU(),
nn.Linear(hidden_dim, output_dim)
)
nn.Sequential 的作用是:把多个网络层按顺序串起来。
它就像一个流水线。
输入数据进来之后,会依次经过:
第 1 层:nn.Linear(input_dim, hidden_dim)
第 2 层:nn.ReLU()
第 3 层:nn.Linear(hidden_dim, output_dim)
等价于你手动写:
x = self.linear1(x)
x = self.relu(x)
x = self.linear2(x)
return x
只不过 nn.Sequential 更简洁。
6. 第一层:nn.Linear(input_dim, hidden_dim)
nn.Linear(input_dim, hidden_dim)
这是第一个线性层。
它的作用是把输入从 input_dim 维变成 hidden_dim 维。
比如:
input_dim = 10
hidden_dim = 32
那么这一层就是:
nn.Linear(10, 32)
意思是:
每个样本原来有 10 个特征
经过这一层后变成 32 个特征
输入形状:
[batch_size, 10]
输出形状:
[batch_size, 32]
7. 中间层为什么叫 hidden layer?
这一层输出的 32 个值,一般叫做:
隐藏层特征
hidden features
因为它不是原始输入,也不是最终输出,而是模型自己学习出来的中间表示。
比如输入是一个人的 10 个特征:
年龄、收入、消费次数、浏览时长……
经过第一层后,模型可能学出一些更抽象的特征:
购买倾向
活跃程度
价格敏感度
风险水平
这些特征不是人工直接给的,而是模型在训练中自己学出来的。
8. nn.ReLU():激活函数
nn.ReLU()
ReLU 是一个激活函数。
它的公式很简单:
ReLU(x) = max(0, x)
意思是:
如果 x > 0,保留 x
如果 x <= 0,变成 0
例如:
输入:[-2, -0.5, 0, 3, 7]
输出:[ 0, 0, 0, 3, 7]
9. 为什么要加 ReLU?
这是非常关键的一点。
如果没有 ReLU,模型变成这样:
nn.Linear(input_dim, hidden_dim)
nn.Linear(hidden_dim, output_dim)
虽然看起来有两层,但本质上还是一个线性变换。
因为:
线性层 + 线性层 = 仍然是线性层
也就是说,如果没有激活函数,中间加再多层,模型表达能力也不会真正变强。
加上 ReLU 后,模型就有了非线性表达能力,可以学习更复杂的关系。
比如:
房价不是简单地随着面积线性增长
用户是否点击广告也不是由某一个特征线性决定
图像、文本、声音里的规律更不可能只是线性的
所以神经网络通常需要:
线性层 + 非线性激活函数 + 线性层
这也是 MLP 的核心。
10. 第二层:nn.Linear(hidden_dim, output_dim)
nn.Linear(hidden_dim, output_dim)
这是输出层。
它把隐藏层特征转换成最终结果。
如果:
hidden_dim = 32
output_dim = 1
那么这一层就是:
nn.Linear(32, 1)
意思是:
把 32 个中间特征压缩成 1 个输出值
输入形状:
[batch_size, 32]
输出形状:
[batch_size, 1]
11. forward:定义数据怎么流动
def forward(self, x):
return self.net(x)
forward 是前向传播函数。
它定义的是:
输入数据
x进入模型后,应该怎么计算输出。
因为前面已经用 nn.Sequential 把所有层封装进了 self.net,所以这里只需要写:
return self.net(x)
也就是说:
把 x 送入 self.net
让它依次经过 Linear → ReLU → Linear
最后返回结果
12. 一个具体例子
假设你创建模型:
model = MLP(input_dim=10, hidden_dim=32, output_dim=1)
然后准备输入:
x = torch.randn(4, 10)
这里的 x 形状是:
[4, 10]
意思是:
一共有 4 条样本
每条样本有 10 个特征
送入模型:
output = model(x)
数据流动过程是:
x: [4, 10]
↓
Linear(10, 32)
↓
[4, 32]
↓
ReLU
↓
[4, 32]
↓
Linear(32, 1)
↓
[4, 1]
所以最终:
print(output.shape)
会得到:
torch.Size([4, 1])
13. 为什么写 model(x),不是写 model.forward(x)?
一般使用模型时写:
output = model(x)
而不是:
output = model.forward(x)
因为 model(x) 会自动调用 forward(x),同时还会触发 PyTorch 内部的一些机制,比如:
hook 机制
自动求导相关处理
训练 / 推理模式下的一些行为
所以标准写法是:
output = model(x)
14. 查看模型结构
你可以这样打印模型:
model = MLP(10, 32, 1)
print(model)
输出类似:
MLP(
(net): Sequential(
(0): Linear(in_features=10, out_features=32, bias=True)
(1): ReLU()
(2): Linear(in_features=32, out_features=1, bias=True)
)
)
解释一下:
0 号层:Linear(10 → 32)
1 号层:ReLU
2 号层:Linear(32 → 1)
15. 查看模型参数
for name, param in model.named_parameters():
print(name, param.shape)
输出类似:
net.0.weight torch.Size([32, 10])
net.0.bias torch.Size([32])
net.2.weight torch.Size([1, 32])
net.2.bias torch.Size([1])
逐个解释:
第一层权重
net.0.weight torch.Size([32, 10])
第一层是:
nn.Linear(10, 32)
它有一个权重矩阵,形状是:
[32, 10]
表示:
输入 10 维
输出 32 维
第一层偏置
net.0.bias torch.Size([32])
因为第一层输出 32 个值,所以有 32 个偏置。
第二层权重
net.2.weight torch.Size([1, 32])
第二层是:
nn.Linear(32, 1)
权重形状是:
[1, 32]
第二层偏置
net.2.bias torch.Size([1])
因为最后输出 1 个值,所以有 1 个偏置。
16. 这个模型一共有多少参数?
如果:
model = MLP(10, 32, 1)
第一层:
权重:32 × 10 = 320
偏置:32
合计:352
第二层:
权重:1 × 32 = 32
偏置:1
合计:33
总参数量:
352 + 33 = 385
也就是这个模型一共有:
385 个可训练参数
你也可以用代码算:
sum(p.numel() for p in model.parameters())
17. 它和上一段 MyModel 的区别
上一段模型是:
self.linear = nn.Linear(10, 1)
结构是:
10 → 1
它只有一层线性层,本质上是线性模型。
现在这个 MLP 是:
nn.Linear(input_dim, hidden_dim)
nn.ReLU()
nn.Linear(hidden_dim, output_dim)
结构是:
input_dim → hidden_dim → output_dim
中间多了一个隐藏层和一个 ReLU 激活函数,所以表达能力更强。
对比一下:
| 模型 | 结构 | 是否有非线性能力 |
|---|---|---|
MyModel | 10 → 1 | 没有,本质是线性模型 |
MLP | input_dim → hidden_dim → output_dim | 有,因为中间有 ReLU |
18. 这个模型能做什么?
取决于 output_dim 和损失函数。
回归任务
比如预测房价、销量、温度:
model = MLP(input_dim=10, hidden_dim=32, output_dim=1)
loss_fn = nn.MSELoss()
输出是一个连续值。
二分类任务
比如判断是否点击、是否患病、是否垃圾邮件:
model = MLP(input_dim=10, hidden_dim=32, output_dim=1)
loss_fn = nn.BCEWithLogitsLoss()
注意:如果使用 BCEWithLogitsLoss,模型最后一层不需要手动加 Sigmoid。
因为 BCEWithLogitsLoss 内部已经把 sigmoid 和 binary cross entropy 结合起来了。
多分类任务
比如识别 3 个类别:
model = MLP(input_dim=10, hidden_dim=32, output_dim=3)
loss_fn = nn.CrossEntropyLoss()
输出形状是:
[batch_size, 3]
每一列对应一个类别的分数。
注意:如果使用 CrossEntropyLoss,模型最后不需要手动加 Softmax。
因为 CrossEntropyLoss 内部会处理。
19. 一个完整训练例子
下面是一个简单的回归任务示例:
import torch
from torch import nn
class MLP(nn.Module):
def __init__(self, input_dim, hidden_dim, output_dim):
super().__init__()
self.net = nn.Sequential(
nn.Linear(input_dim, hidden_dim),
nn.ReLU(),
nn.Linear(hidden_dim, output_dim)
)
def forward(self, x):
return self.net(x)
model = MLP(input_dim=10, hidden_dim=32, output_dim=1)
x = torch.randn(64, 10)
y = torch.randn(64, 1)
loss_fn = nn.MSELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
for epoch in range(100):
pred = model(x)
loss = loss_fn(pred, y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
if epoch % 10 == 0:
print(epoch, loss.item())
训练流程是:
1. 输入 x
2. 模型输出 pred
3. pred 和真实值 y 计算 loss
4. loss.backward() 反向传播
5. optimizer.step() 更新参数
20. 这段代码的核心思想
这段 MLP 可以用一句话理解:
它先把输入特征映射到一个隐藏空间,再通过 ReLU 增加非线性表达能力,最后映射到目标输出维度。
更短地说:
输入特征 → 隐藏表示 → 输出结果
对应代码就是:
self.net = nn.Sequential(
nn.Linear(input_dim, hidden_dim),
nn.ReLU(),
nn.Linear(hidden_dim, output_dim)
)
你可以把它理解成一个“小型神经网络模板”。
只要改这三个参数:
input_dim
hidden_dim
output_dim
它就能适配不同的数据和任务。
更多推荐



所有评论(0)