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 激活函数,所以表达能力更强。

对比一下:

模型结构是否有非线性能力
MyModel10 → 1没有,本质是线性模型
MLPinput_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

它就能适配不同的数据和任务。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐