09-使用 paddle.nn 构建第一个多层感知机

概述

前面一篇文章已经讲过 paddle.nn.Linear、激活函数和 paddle.nn.Sequential。这一篇继续向前走一步:用 paddle.nn 构建并训练第一个多层感知机,也就是常说的 MLP。

很多初学者第一次写 MLP 时,容易只记住几行代码:

python 复制代码
model = nn.Sequential(
    nn.Linear(2, 16),
    nn.ReLU(),
    nn.Linear(16, 2),
)

但只会写这几行还不够。一个真正可训练的 MLP 至少要串起这些环节:

text 复制代码
准备数据
    |
定义模型
    |
前向计算 logits
    |
计算损失 loss
    |
反向传播 backward
    |
优化器更新参数
    |
清理梯度
    |
评估准确率

本篇文章会围绕一个可运行的二分类任务展开,用随机生成的二维数据训练一个 MLP,让你看清楚模型从输入到输出、从 loss 到参数更新的完整流程。

读完这篇文章,你应该能独立写出一个基于 paddle.nn 的 MLP,并能解释每一层的 shape、每一步训练代码的作用和常见错误的排查方式。

背景:什么是多层感知机

多层感知机(Multilayer Perceptron,MLP)是一类最基础的前馈神经网络。它通常由多层全连接层和非线性激活函数组成。

一个简单 MLP 可以写成:

text 复制代码
输入特征 x
    |
Linear
    |
ReLU
    |
Linear
    |
ReLU
    |
Linear
    |
输出 logits

如果用公式表示两层隐藏层结构,大致是:

h 1 = R e L U ( x W 1 + b 1 ) h_1 = ReLU(xW_1 + b_1) h1=ReLU(xW1+b1)

h 2 = R e L U ( h 1 W 2 + b 2 ) h_2 = ReLU(h_1W_2 + b_2) h2=ReLU(h1W2+b2)

l o g i t s = h 2 W 3 + b 3 logits = h_2W_3 + b_3 logits=h2W3+b3

这里的 logits 是分类分数,还不是概率。训练分类模型时,通常直接把 logits 交给交叉熵损失函数。

MLP 的适用场景包括:

  • 表格特征分类。
  • 表格特征回归。
  • 简单向量特征建模。
  • 作为更大模型中的分类头或回归头。
  • 学习神经网络训练流程的入门模型。

MLP 是由 Linear + 激活函数 堆叠出的前馈网络,是理解深度学习训练流程的最好起点之一。

任务设计:用二维点做一个二分类问题

为了专注于 paddle.nn 和 MLP 本身,本文不引入外部数据集,而是构造一个可控的二分类任务。

输入是二维点:

text 复制代码
x = [x1, x2]

标签规则:

text 复制代码
如果 x1^2 + x2^2 > 1,则类别为 1
否则类别为 0

这个任务的分类边界是一个圆:

text 复制代码
类别 0:圆内点
类别 1:圆外点

为什么不用简单的 x1 + x2 > 0?因为那是线性可分问题,一个线性层就可能拟合得很好。圆形边界更适合展示 MLP 的非线性表达能力。

数据流如下:

text 复制代码
随机生成二维点 [N, 2]
    |
根据半径生成标签 [N]
    |
MLP 输出 logits [N, 2]
    |
CrossEntropyLoss 计算分类损失

这个例子小、直观、可复制,适合用来理解第一个 MLP。

准备代码:导入依赖和设置随机种子

新建文件:

text 复制代码
first_mlp_with_paddle_nn.py

写入基础依赖:

python 复制代码
import paddle
import paddle.nn as nn
import paddle.nn.functional as F

paddle.seed(2026)

再准备两个调试函数:

python 复制代码
def show_tensor(name, tensor):
    print(f"\n{name}")
    print("-" * 60)
    print(tensor)
    print("shape:", tensor.shape)
    print("dtype:", tensor.dtype)
    print("stop_gradient:", tensor.stop_gradient)


def show_model_parameters(model):
    print("\nmodel parameters")
    print("-" * 60)
    total = 0
    for name, param in model.named_parameters():
        num = param.numel()
        total += num
        print(name, param.shape, "numel=", num)
    print("total parameters:", total)

学习模型时建议养成一个习惯:每写一个新模型,先打印输入 shape、输出 shape 和参数量。

数据构造:生成训练集和验证集

先写一个数据生成函数:

python 复制代码
def make_circle_data(num_samples):
    x = paddle.rand([num_samples, 2], dtype="float32") * 4.0 - 2.0
    radius_square = paddle.sum(x ** 2, axis=1)
    labels = (radius_square > 1.0).astype("int64")
    return x, labels

这段代码做了几件事:

  • paddle.rand([num_samples, 2]) 生成 [0, 1) 的随机数。
  • * 4.0 - 2.0 把范围变成 [-2, 2)
  • paddle.sum(x ** 2, axis=1) 计算每个点的 x 1 2 + x 2 2 x_1^2 + x_2^2 x12+x22。
  • radius_square > 1.0 得到布尔标签。
  • .astype("int64") 转成分类标签需要的整数类型。

创建训练集和验证集:

python 复制代码
train_x, train_y = make_circle_data(1024)
val_x, val_y = make_circle_data(256)

show_tensor("train_x", train_x)
show_tensor("train_y", train_y)

你应该重点观察:

text 复制代码
train_x shape: [1024, 2]
train_y shape: [1024]
train_x dtype: float32
train_y dtype: int64

这正是分类任务常见输入结构:

text 复制代码
features: [batch_size, feature_dim]
labels: [batch_size]

模型定义:用 nn.Layer 构建 MLP

先用自定义 nn.Layer 写法构建模型:

python 复制代码
class MLP(nn.Layer):
    def __init__(self, input_dim, hidden_dim, num_classes):
        super().__init__()
        self.fc1 = nn.Linear(input_dim, hidden_dim)
        self.fc2 = nn.Linear(hidden_dim, hidden_dim)
        self.fc3 = nn.Linear(hidden_dim, num_classes)

    def forward(self, x):
        x = self.fc1(x)
        x = F.relu(x)
        x = self.fc2(x)
        x = F.relu(x)
        logits = self.fc3(x)
        return logits

创建模型:

python 复制代码
model = MLP(input_dim=2, hidden_dim=16, num_classes=2)
show_model_parameters(model)

模型结构是:

text 复制代码
输入 [N, 2]
    |
fc1: Linear(2, 16)
    |
ReLU
    |
fc2: Linear(16, 16)
    |
ReLU
    |
fc3: Linear(16, 2)
    |
logits [N, 2]

这里的 num_classes=2 表示二分类输出两个类别分数。

为什么最后一层不加 ReLU?

因为最后一层输出的是分类 logits。logits 可以是任意实数,后续交叉熵损失会处理这些分数。如果最后强行加 ReLU,会限制输出范围,通常不是分类模型的常规做法。

前向验证:先确认模型输出 shape

训练前不要急着写优化器,先跑一次前向:

python 复制代码
sample_x = train_x[:4]
logits = model(sample_x)

show_tensor("sample_x", sample_x)
show_tensor("logits", logits)

形状应该是:

text 复制代码
sample_x: [4, 2]
logits: [4, 2]

这表示:

  • 输入 4 个样本。
  • 每个样本 2 个特征。
  • 模型为每个样本输出 2 个类别分数。

如果这里 shape 不对,先不要进入训练循环。优先检查:

python 复制代码
print(sample_x.shape)
print(model)

以及每个 Linear 的输入输出维度。

损失函数:为什么分类任务使用交叉熵

二分类任务可以有不同写法。本文使用两个类别 logits,因此损失函数使用:

python 复制代码
loss = F.cross_entropy(logits, labels)

官方 cross_entropy 文档说明,该函数会把 softmax 操作和交叉熵计算合并,以提供更稳定的数值计算。它的输入 input 应该是未缩放的 logits,不应该先对 softmax 输出再计算。

示例:

python 复制代码
sample_y = train_y[:4]
loss = F.cross_entropy(logits, sample_y)

show_tensor("loss", loss)

这里:

text 复制代码
logits shape: [4, 2]
sample_y shape: [4]
loss shape: []

loss 是一个 0 维 Tensor,也就是标量 Tensor,可以直接调用:

python 复制代码
loss.backward()

重要提醒:训练时通常不要写成:

python 复制代码
prob = F.softmax(logits, axis=1)
loss = F.cross_entropy(prob, labels)

更推荐:

python 复制代码
loss = F.cross_entropy(logits, labels)

推理展示概率时再使用 softmax

优化器:用 Adam 更新模型参数

优化器负责根据梯度更新参数。本文使用 paddle.optimizer.Adam

python 复制代码
optimizer = paddle.optimizer.Adam(
    learning_rate=0.01,
    parameters=model.parameters(),
)

这里最关键的是:

python 复制代码
parameters=model.parameters()

它告诉优化器:更新这个模型中所有可训练参数。

动态图训练中,官方优化器文档也强调需要提供 parameters。如果没有传入模型参数,优化器不知道应该更新谁。

训练中的固定步骤是:

text 复制代码
logits = model(x)
loss = F.cross_entropy(logits, y)
loss.backward()
optimizer.step()
optimizer.clear_grad()

含义:

  • model(x):前向计算。
  • loss:得到训练目标。
  • loss.backward():计算梯度。
  • optimizer.step():根据梯度更新参数。
  • optimizer.clear_grad():清理梯度,避免下一轮累积。

指标函数:手写 accuracy

分类任务常用准确率:

python 复制代码
def accuracy(logits, labels):
    pred = paddle.argmax(logits, axis=1)
    correct = pred == labels
    acc = paddle.mean(correct.astype("float32"))
    return acc

这段代码的 shape 关系:

text 复制代码
logits: [N, 2]
labels: [N]
pred: [N]
correct: [N]
acc: []

paddle.argmax(logits, axis=1) 表示在类别维上取分数最高的类别。

如果 logits 的 shape 是 [batch_size, num_classes],分类预测通常就是:

python 复制代码
pred = paddle.argmax(logits, axis=1)

不要写成 axis=0,那会跨 batch 聚合,含义通常不对。

训练循环:把 MLP 真正训练起来

完整训练代码如下:

python 复制代码
import paddle
import paddle.nn as nn
import paddle.nn.functional as F


class MLP(nn.Layer):
    def __init__(self, input_dim, hidden_dim, num_classes):
        super().__init__()
        self.fc1 = nn.Linear(input_dim, hidden_dim)
        self.fc2 = nn.Linear(hidden_dim, hidden_dim)
        self.fc3 = nn.Linear(hidden_dim, num_classes)

    def forward(self, x):
        x = self.fc1(x)
        x = F.relu(x)
        x = self.fc2(x)
        x = F.relu(x)
        logits = self.fc3(x)
        return logits


def make_circle_data(num_samples):
    x = paddle.rand([num_samples, 2], dtype="float32") * 4.0 - 2.0
    radius_square = paddle.sum(x ** 2, axis=1)
    labels = (radius_square > 1.0).astype("int64")
    return x, labels


def accuracy(logits, labels):
    pred = paddle.argmax(logits, axis=1)
    correct = pred == labels
    acc = paddle.mean(correct.astype("float32"))
    return acc


paddle.seed(2026)

train_x, train_y = make_circle_data(1024)
val_x, val_y = make_circle_data(256)

model = MLP(input_dim=2, hidden_dim=16, num_classes=2)
optimizer = paddle.optimizer.Adam(
    learning_rate=0.01,
    parameters=model.parameters(),
)

for epoch in range(101):
    model.train()

    logits = model(train_x)
    loss = F.cross_entropy(logits, train_y)
    train_acc = accuracy(logits, train_y)

    loss.backward()
    optimizer.step()
    optimizer.clear_grad()

    if epoch % 20 == 0:
        model.eval()
        with paddle.no_grad():
            val_logits = model(val_x)
            val_loss = F.cross_entropy(val_logits, val_y)
            val_acc = accuracy(val_logits, val_y)

        print(
            "epoch:", epoch,
            "train_loss:", float(loss.numpy()),
            "train_acc:", float(train_acc.numpy()),
            "val_loss:", float(val_loss.numpy()),
            "val_acc:", float(val_acc.numpy()),
        )

这段代码就是一个最小但完整的 MLP 训练程序。

训练时你应该能看到:

  • train_loss 整体下降。
  • train_acc 整体上升。
  • val_acc 逐步提高。

因为数据是随机生成的,小规模实验结果可能有波动,但整体趋势应该是可学习的。

训练过程拆解:每一行代码在做什么

训练循环中最核心的是这几行:

python 复制代码
logits = model(train_x)
loss = F.cross_entropy(logits, train_y)
train_acc = accuracy(logits, train_y)

loss.backward()
optimizer.step()
optimizer.clear_grad()

逐行解释:

代码 作用
model(train_x) 调用 forward,得到分类 logits
F.cross_entropy(logits, train_y) 计算分类损失
accuracy(logits, train_y) 计算训练准确率
loss.backward() 根据 loss 反向计算梯度
optimizer.step() 根据梯度更新模型参数
optimizer.clear_grad() 清理梯度,避免累积

这里特别要注意两点:

  • train_acc 是指标,不参与参数更新。
  • 真正驱动参数更新的是 loss.backward() 计算出的梯度。

可以把训练循环理解成:

text 复制代码
先问模型现在错多少
    |
再根据错误反推参数该怎么改
    |
最后让优化器完成实际修改

评估逻辑:model.eval 与 no_grad

验证阶段代码:

python 复制代码
model.eval()
with paddle.no_grad():
    val_logits = model(val_x)
    val_loss = F.cross_entropy(val_logits, val_y)
    val_acc = accuracy(val_logits, val_y)

这里有两个关键点:

  • model.eval():切换到评估模式。
  • with paddle.no_grad():不记录梯度,减少不必要的计算和内存占用。

当前 MLP 只有 LinearReLU,训练模式和评估模式差异不大。但后续模型如果包含 DropoutBatchNormtrain()eval() 就非常重要。

建议养成习惯:

text 复制代码
训练阶段:
model.train()

验证/推理阶段:
model.eval()
with paddle.no_grad():
    ...

这样后续模型变复杂时,不容易犯模式切换错误。

推理预测:从 logits 得到类别和概率

训练完成后,可以拿几个样本做预测:

python 复制代码
model.eval()

with paddle.no_grad():
    test_x = paddle.to_tensor(
        [[0.0, 0.0], [1.5, 1.5], [0.5, 0.5], [-1.2, 0.0]],
        dtype="float32",
    )
    logits = model(test_x)
    prob = F.softmax(logits, axis=1)
    pred = paddle.argmax(prob, axis=1)

print("test_x:")
print(test_x)
print("prob:")
print(prob)
print("pred:")
print(pred)

解释:

  • [0.0, 0.0] 在圆内,应该更倾向类别 0。
  • [1.5, 1.5] 在圆外,应该更倾向类别 1。
  • [0.5, 0.5] 在圆内,应该更倾向类别 0。
  • [-1.2, 0.0] 在圆外,应该更倾向类别 1。

推理阶段可以使用 softmax 把 logits 转成概率:

python 复制代码
prob = F.softmax(logits, axis=1)

但训练阶段仍然建议把原始 logits 交给 F.cross_entropy

用 Sequential 重写同一个 MLP

这个 MLP 结构是纯顺序结构,因此也可以用 nn.Sequential 写:

python 复制代码
model = nn.Sequential(
    nn.Linear(2, 16),
    nn.ReLU(),
    nn.Linear(16, 16),
    nn.ReLU(),
    nn.Linear(16, 2),
)

这和自定义 MLP 类的结构等价:

text 复制代码
Linear(2, 16)
    |
ReLU
    |
Linear(16, 16)
    |
ReLU
    |
Linear(16, 2)

什么时候用 Sequential

  • 模型是简单单路顺序结构。
  • 中间没有复杂分支。
  • 不需要复用多个中间结果。
  • 不需要在 forward 中写额外逻辑。

什么时候用自定义 nn.Layer

  • 有多个输入或多个输出。
  • 有条件分支。
  • 有残差连接。
  • 需要复用中间结果。
  • 需要在 forward 里写更复杂的控制流。

本文后续仍然推荐用自定义 MLP 类,因为它更接近真实项目结构,也方便后面继续扩展。

模型参数量:确认 MLP 规模

当前模型:

python 复制代码
model = MLP(input_dim=2, hidden_dim=16, num_classes=2)

参数量计算:

text 复制代码
fc1 weight: 2 * 16 = 32
fc1 bias: 16

fc2 weight: 16 * 16 = 256
fc2 bias: 16

fc3 weight: 16 * 2 = 32
fc3 bias: 2

total = 32 + 16 + 256 + 16 + 32 + 2 = 354

用代码验证:

python 复制代码
total = 0
for name, param in model.named_parameters():
    num = param.numel()
    total += num
    print(name, param.shape, num)

print("total:", total)

参数量不是越大越好。对于这个小任务,hidden_dim=16 已经足够。如果隐藏层太宽、训练数据太少,模型可能更容易过拟合。

学习率影响:太大和太小分别会怎样

优化器中的学习率:

python 复制代码
learning_rate=0.01

它控制每次参数更新的步幅。

可以简单理解:

text 复制代码
学习率太小:训练很慢,loss 下降不明显
学习率太大:训练不稳定,loss 震荡甚至变成 NaN

建议初学时尝试三组值:

python 复制代码
0.001
0.01
0.1

观察训练日志:

  • loss 是否下降。
  • accuracy 是否上升。
  • 是否出现大幅震荡。
  • 是否出现 NaN

不要只看最后一次结果,应该观察整个训练曲线趋势。

隐藏层宽度影响:hidden_dim 是什么

模型中的:

python 复制代码
hidden_dim=16

表示隐藏层特征维度。它决定中间表示的容量。

例如:

python 复制代码
MLP(input_dim=2, hidden_dim=8, num_classes=2)
MLP(input_dim=2, hidden_dim=32, num_classes=2)
MLP(input_dim=2, hidden_dim=128, num_classes=2)

影响包括:

  • hidden_dim 越大,参数越多。
  • 模型表达能力可能更强。
  • 计算开销和内存占用增加。
  • 小数据集上过拟合风险增加。

对初学者建议:

text 复制代码
先用小模型跑通流程
再逐步增大 hidden_dim
观察训练集和验证集指标变化

这比一开始堆很大的网络更稳。

保存模型参数:训练结果不要只停留在内存里

训练完成后,可以保存模型参数:

python 复制代码
paddle.save(model.state_dict(), "mlp_circle.pdparams")

加载参数:

python 复制代码
new_model = MLP(input_dim=2, hidden_dim=16, num_classes=2)
state_dict = paddle.load("mlp_circle.pdparams")
new_model.set_state_dict(state_dict)
new_model.eval()

验证加载后的模型:

python 复制代码
with paddle.no_grad():
    logits = new_model(val_x)
    acc = accuracy(logits, val_y)

print("loaded model val_acc:", float(acc.numpy()))

这部分会在后续"模型保存与加载"中详细展开。这里先建立基本认知:

text 复制代码
state_dict 保存的是模型参数
模型结构仍然需要用代码重新创建

如果模型结构和保存时不一致,加载参数时就可能出现 shape 不匹配。

完整脚本:可直接复制运行

下面给出完整脚本,方便一次性运行。

python 复制代码
import paddle
import paddle.nn as nn
import paddle.nn.functional as F


class MLP(nn.Layer):
    def __init__(self, input_dim, hidden_dim, num_classes):
        super().__init__()
        self.fc1 = nn.Linear(input_dim, hidden_dim)
        self.fc2 = nn.Linear(hidden_dim, hidden_dim)
        self.fc3 = nn.Linear(hidden_dim, num_classes)

    def forward(self, x):
        x = self.fc1(x)
        x = F.relu(x)
        x = self.fc2(x)
        x = F.relu(x)
        logits = self.fc3(x)
        return logits


def make_circle_data(num_samples):
    x = paddle.rand([num_samples, 2], dtype="float32") * 4.0 - 2.0
    radius_square = paddle.sum(x ** 2, axis=1)
    labels = (radius_square > 1.0).astype("int64")
    return x, labels


def accuracy(logits, labels):
    pred = paddle.argmax(logits, axis=1)
    correct = pred == labels
    acc = paddle.mean(correct.astype("float32"))
    return acc


def print_parameters(model):
    total = 0
    for name, param in model.named_parameters():
        num = param.numel()
        total += num
        print(name, param.shape, num)
    print("total parameters:", total)


def main():
    paddle.seed(2026)

    train_x, train_y = make_circle_data(1024)
    val_x, val_y = make_circle_data(256)

    model = MLP(input_dim=2, hidden_dim=16, num_classes=2)
    print_parameters(model)

    optimizer = paddle.optimizer.Adam(
        learning_rate=0.01,
        parameters=model.parameters(),
    )

    for epoch in range(101):
        model.train()

        logits = model(train_x)
        loss = F.cross_entropy(logits, train_y)
        train_acc = accuracy(logits, train_y)

        loss.backward()
        optimizer.step()
        optimizer.clear_grad()

        if epoch % 20 == 0:
            model.eval()
            with paddle.no_grad():
                val_logits = model(val_x)
                val_loss = F.cross_entropy(val_logits, val_y)
                val_acc = accuracy(val_logits, val_y)

            print(
                "epoch:", epoch,
                "train_loss:", float(loss.numpy()),
                "train_acc:", float(train_acc.numpy()),
                "val_loss:", float(val_loss.numpy()),
                "val_acc:", float(val_acc.numpy()),
            )

    model.eval()
    with paddle.no_grad():
        test_x = paddle.to_tensor(
            [[0.0, 0.0], [1.5, 1.5], [0.5, 0.5], [-1.2, 0.0]],
            dtype="float32",
        )
        logits = model(test_x)
        prob = F.softmax(logits, axis=1)
        pred = paddle.argmax(prob, axis=1)

    print("test_x:")
    print(test_x)
    print("prob:")
    print(prob)
    print("pred:")
    print(pred)

    paddle.save(model.state_dict(), "mlp_circle.pdparams")


if __name__ == "__main__":
    main()

运行:

bash 复制代码
python first_mlp_with_paddle_nn.py

如果你使用 CPU 环境,这个小例子也能很快运行完。

常见错误

错误一:输入最后一维和 Linear 不匹配

错误示例:

python 复制代码
model = MLP(input_dim=2, hidden_dim=16, num_classes=2)
x = paddle.randn([32, 3])
logits = model(x)

模型第一层是:

python 复制代码
nn.Linear(2, 16)

它要求输入最后一维是 2,但 x[32, 3]

排查方式:

python 复制代码
print(x.shape)
print(model.fc1.weight.shape)

错误二:分类标签 dtype 不正确

分类标签应是类别编号,通常使用 int64

python 复制代码
labels = labels.astype("int64")

不要写成浮点标签:

python 复制代码
labels = labels.astype("float32")

排查方式:

python 复制代码
print(labels.shape)
print(labels.dtype)

错误三:手动 softmax 后再 cross_entropy

错误倾向:

python 复制代码
prob = F.softmax(logits, axis=1)
loss = F.cross_entropy(prob, labels)

更推荐:

python 复制代码
loss = F.cross_entropy(logits, labels)

因为 cross_entropy 已经合并了 softmax 和交叉熵计算,数值上更稳定。

错误四:忘记 clear_grad

错误写法:

python 复制代码
loss.backward()
optimizer.step()

正确写法:

python 复制代码
loss.backward()
optimizer.step()
optimizer.clear_grad()

梯度默认会累积,不清理会影响下一轮训练。

错误五:评估时没有使用 no_grad

评估阶段不需要梯度:

python 复制代码
model.eval()
with paddle.no_grad():
    val_logits = model(val_x)

这能减少不必要的计算图记录。

错误六:loss 下降但 accuracy 不变

先检查:

  • argmaxaxis 是否写对。
  • 标签 shape 是否是 [N]
  • 标签取值是否在 [0, num_classes)
  • num_classes 是否和模型最后一层输出一致。

对于本文:

text 复制代码
logits: [N, 2]
labels: [N]
argmax axis=1

错误七:训练集很好,验证集很差

这通常是过拟合或训练验证分布不一致。

可尝试:

  • 减小 hidden_dim
  • 减少训练轮数。
  • 增加训练数据。
  • 加入正则化。
  • 检查训练集和验证集生成规则是否一致。

本文数据是同一规则随机生成,一般不会出现严重分布不一致。

总结

这一篇用 paddle.nn 构建并训练了第一个多层感知机,核心知识点包括:

  • MLP 通常由 Linear + 激活函数 堆叠组成。
  • 输入 shape 通常是 [batch_size, feature_dim]
  • 分类输出 logits shape 通常是 [batch_size, num_classes]
  • 分类标签 shape 通常是 [batch_size],dtype 通常是 int64
  • F.cross_entropy 应直接接收 logits,不要先手动 softmax。
  • loss.backward() 负责计算梯度。
  • optimizer.step() 负责更新参数。
  • optimizer.clear_grad() 负责清理梯度。
  • model.train() 用于训练模式,model.eval() 用于评估模式。
  • paddle.no_grad() 用于验证和推理,避免无意义的梯度记录。
  • model.state_dict() 可以保存模型参数。

如果只能记住一句话,那就是:

一个可训练的 MLP 不只是几层 Linear,而是数据、模型、损失、梯度、优化器和评估指标共同组成的闭环。

相关推荐
小大宇1 小时前
python sqlalchemy 案例
开发语言·python
handsomestWei2 小时前
RNN和QKV区别
人工智能·rnn·深度学习
Ulyanov2 小时前
雷达导引头仿真中的坐标系——从惯性系到量测角
开发语言·python·算法·系统仿真·雷达信号处理·雷达导引头
codeboss2 小时前
做网页变更监控,我在“降噪”上踩过的 7 个坑
爬虫·python
果汁华2 小时前
CLI 命令行与 Python 框架实战
git·python·github
Maiko Star2 小时前
FastAPI 进阶三部曲:中间件、依赖注入与 ORM 实战
python·中间件·fastapi
MrDJun2 小时前
长期稳定跑网页监控:TLS 指纹、代理选路与请求节流的工程实践
运维·爬虫·python·网络协议·网站监控
m沐沐2 小时前
【计算机视觉】OpenCV 物体跟踪——原理、算法与CSRT跟踪器实战
人工智能·python·深度学习·opencv·算法·计算机视觉·人脸识别
LaughingZhu3 小时前
Product Hunt 每日热榜 | 2026-07-26
人工智能·深度学习·神经网络·搜索引擎·百度