目录

一、先搞懂:神经网络的 “四大核心组件”

二、别踩坑:PyTorch 的 “两大核心工具”

1. nn.Module:带 “参数自动管理” 的 “智能工具”

2. nn.functional:轻量的 “纯函数工具”

三、实操来啦:三种搭模型的方法(附 MNIST 代码)

方法 1:继承nn.Module—— 灵活度拉满,复杂模型首选

方法 2:用nn.Sequential—— 线性模型首选,快到飞起

① 可变参数写法(最快,但层没名字)

② add_module写法(能自定义层名,清晰)

方法 3:结合模型容器 —— 灵活与整洁兼顾

四、新手小结:怎么选、怎么查错

五、结尾:接下来要试试搭 ResNet


      之前跟着教程搭神经网络时,总在各种细节上卡壳:要么忘了写forward函数导致数据传不动,要么手动定义权重时维度对不上,甚至用 dropout 时测试阶段忘了关,结果准确率忽高忽低。今天系统学了 PyTorch 的神经网络工具箱,才算摸清了门道 —— 原来核心就是 “认全组件、选对工具、按需求拼模型”。整理了我觉得最实用的知识点,用自己踩过的坑当例子,帮和我一样刚入门的小伙伴少走弯路。

一、先搞懂:神经网络的 “四大核心组件”

      其实搭神经网络就像搭流水线,这四个组件缺一不可。老师用 “工厂生产零件” 打了个比方,我一下子就理解了它们的分工:

组件 作用(工厂类比) 新手常接触的例子
层(Layer) 数据的 “加工厂”,把输入张量转成更有用的输出 Flatten(展平 28×28 的手写数字)、Linear(全连接层)、Conv2d(卷积层)
模型(Model) 多个 “加工厂” 串成的流水线,完成端到端计算 手写数字识别模型(Flatten+2 个 Linear+Softmax)
损失函数 质检标准,衡量 “预测零件” 和 “合格零件” 的差距 分类用交叉熵损失(比如判断是数字 0 还是 9)、回归用 MSE(比如预测房价)
优化器 调整流水线的 “工程师”,通过调权重让损失变小 SGD(随机梯度下降)、Adam(更灵活的优化器,新手常用)

      举个具体例子:用模型识别手写数字时,输入是 28×28 的灰度图(像待加工的原材料),先经 Flatten 层展成 784 维向量(把 “块状原材料” 拆成 “细条”),再用 Linear 层做特征变换(加工成 “半成品”),最后用 Softmax 输出 10 类概率(判断是 0-9 中的哪类 “成品”);损失函数算预测概率和真实标签的差距(比如预测是 “3” 的概率 0.2,真实是 “3”,损失就大),优化器再调整 Linear 层的权重和偏置(优化加工流程),直到损失变小。

二、别踩坑:PyTorch 的 “两大核心工具”

      PyTorch 里搭模型主要靠nn.Modulenn.functional,之前我总混用它们,今天才分清 —— 这俩的区别直接关系到代码会不会出 bug。

1. nn.Module:带 “参数自动管理” 的 “智能工具”

      nn.Module是新手的 “定心丸”,最大的好处是不用自己管权重和偏置。比如定义全连接层时,不用手动初始化weightbias,它会自动帮你处理,还能一键打印模型结构,方便查错。

      就像搭积木时,nn.Module给你的是 “带卡扣的标准块”,直接拼就行。比如定义一个全连接层:

import torch.nn as nn
# 输入784维(28×28),输出300维,不用管weight怎么初始化
linear = nn.Linear(in_features=784, out_features=300)
# 随便喂个数据试试,直接调用就行
x = torch.randn(1, 784)  # 1个样本,784维
output = linear(x)  # 自动用内部的weight计算,不用手动传

它还有两个超实用的特性,帮我避过坑:

  • 能和nn.Sequential(模型容器)配合,把层按顺序串起来,不用写复杂的传播逻辑;
  • 处理 dropout 时,调用model.eval()会自动切换到测试模式(比如关掉 dropout),之前我用别的工具忘关,测试准确率比训练时低了 15%,踩过的坑提醒大家别犯。

2. nn.functional:轻量的 “纯函数工具”

   nn.functional更像 “没卡扣的积木片”,比如激活函数 ReLU、池化层 max_pool2d,它们没有可学习的参数,用的时候要自己传输入数据。

      但如果用它定义带参数的层(比如全连接层),就很麻烦 —— 得手动初始化权重,还得每次调用时传进去:

import torch.nn.functional as F
# 手动初始化weight和bias,维度还得自己算对(输出维度×输入维度)
weight = torch.randn(300, 784)
bias = torch.randn(300)
x = torch.randn(1, 784)
# 每次调用都要手动传weight和bias,万一漏了就报错
output = F.linear(x, weight, bias)

      老师说,新手尽量用nn.Module处理带参数的层(比如 Linear、Conv2d),nn.functional只用来做激活、池化这种 “无参数操作”,这样代码又简洁又不容易错。

三、实操来啦:三种搭模型的方法(附 MNIST 代码)

      老师用 “手写数字识别(MNIST)” 当例子,讲了三种最常用的模型构建方法,每种都有适用场景。我都亲手跑了一遍,整理出了 “新手友好版” 代码和注意点。

方法 1:继承nn.Module—— 灵活度拉满,复杂模型首选

      这种方法是 “万能模板”,不管模型有没有分支,都能用。核心就两步:在__init__里定义所有层,在forward里写数据怎么从输入传到输出。

      比如搭一个带 BatchNorm(防止过拟合)的全连接模型:

import torch
from torch import nn
import torch.nn.functional as F

# 继承nn.Module基类
class MNIST_Model(nn.Module):
    def __init__(self, in_dim=784, n_hidden1=300, n_hidden2=100, out_dim=10):
        super().__init__()  # 必须调用父类的初始化
        # 1. 展平层:把28×28的图像转成784维向量
        self.flatten = nn.Flatten()
        # 2. 全连接层1:784→300,加BatchNorm
        self.linear1 = nn.Linear(in_dim, n_hidden1)
        self.bn1 = nn.BatchNorm1d(n_hidden1)
        # 3. 全连接层2:300→100,加BatchNorm
        self.linear2 = nn.Linear(n_hidden1, n_hidden2)
        self.bn2 = nn.BatchNorm1d(n_hidden2)
        # 4. 输出层:100→10(对应0-9)
        self.output = nn.Linear(n_hidden2, out_dim)

    # 前向传播:定义数据的流动路径
    def forward(self, x):
        x = self.flatten(x)  # 展平:[batch, 1, 28, 28]→[batch, 784]
        x = self.linear1(x)  # 第一层全连接
        x = self.bn1(x)      # BatchNorm正则化
        x = F.relu(x)        # ReLU激活(用nn.functional,无参数)
        x = self.linear2(x)  # 第二层全连接
        x = self.bn2(x)      # BatchNorm正则化
        x = F.relu(x)        # ReLU激活
        x = self.output(x)   # 输出层
        x = F.softmax(x, dim=1)  # 转成概率(dim=1表示按类别维度)
        return x

# 实例化模型,打印结构看看对不对
model = MNIST_Model()
print(model)

      运行后会看到清晰的层结构,比如(flatten): Flatten(start_dim=1, end_dim=-1),能直观检查有没有漏层。这种方法适合搭复杂模型(比如带分支的 ResNet),但一定要写forward函数 —— 我之前忘写,报错 “Model has no forward method”,卡了半小时才发现。

方法 2:用nn.Sequential—— 线性模型首选,快到飞起

      如果模型是 “一层接一层” 的线性结构(没有分支),用nn.Sequential最省事,不用写forward函数,直接按顺序把层塞进去就行。老师讲了三种写法,各有优缺点:

① 可变参数写法(最快,但层没名字)

      直接把层作为参数传进去,缺点是层会被自动命名为 0、1、2,打印时有点乱:

# 超参数:28×28=784输入,300/100隐藏层,10输出
in_dim, n_hidden1, n_hidden2, out_dim = 784, 300, 100, 10

# 按顺序塞层
model_seq = nn.Sequential(
    nn.Flatten(),
    nn.Linear(in_dim, n_hidden1),
    nn.BatchNorm1d(n_hidden1),
    nn.ReLU(),
    nn.Linear(n_hidden1, n_hidden2),
    nn.BatchNorm1d(n_hidden2),
    nn.ReLU(),
    nn.Linear(n_hidden2, out_dim),
    nn.Softmax(dim=1)
)
print(model_seq)  # 层名会是(0):Flatten, (1):Linear...
② add_module写法(能自定义层名,清晰)

      如果想给层起个好记的名字(比如 “linear1”“bn1”),用add_module逐个添加,查错时更方便:

model_add = nn.Sequential()
# 第一个参数是层名,第二个是层实例
model_add.add_module("flatten", nn.Flatten())
model_add.add_module("linear1", nn.Linear(in_dim, n_hidden1))
model_add.add_module("bn1", nn.BatchNorm1d(n_hidden1))
model_add.add_module("relu1", nn.ReLU())
model_add.add_module("linear2", nn.Linear(n_hidden1, n_hidden2))
model_add.add_module("bn2", nn.BatchNorm1d(n_hidden2))
model_add.add_module("relu2", nn.ReLU())
model_add.add_module("output", nn.Linear(n_hidden2, out_dim))
model_add.add_module("softmax", nn.Softmax(dim=1))
print(model_add)  # 层名清晰,比如(linear1): Linear(...)

      这种方法我现在搭简单模型时必用,比如做回归预测、简单分类,比写forward函数快一倍。

方法 3:结合模型容器 —— 灵活与整洁兼顾

      如果模型有点复杂,但又不想写太多forward逻辑,可以用nn.Sequential“打包” 一部分层,再继承nn.Module。比如把 “Linear+BatchNorm” 打包成一个块,代码更整洁:

class Model_With_Container(nn.Module):
    def __init__(self, in_dim=784, n_hidden1=300, n_hidden2=100, out_dim=10):
        super().__init__()
        self.flatten = nn.Flatten()
        # 用nn.Sequential打包第一层(Linear+BatchNorm)
        self.layer1 = nn.Sequential(
            nn.Linear(in_dim, n_hidden1),
            nn.BatchNorm1d(n_hidden1)
        )
        # 打包第二层(Linear+BatchNorm)
        self.layer2 = nn.Sequential(
            nn.Linear(n_hidden1, n_hidden2),
            nn.BatchNorm1d(n_hidden2)
        )
        # 输出层
        self.output = nn.Linear(n_hidden2, out_dim)

    def forward(self, x):
        x = self.flatten(x)
        x = F.relu(self.layer1(x))  # 直接调用打包好的层
        x = F.relu(self.layer2(x))
        x = F.softmax(self.output(x), dim=1)
        return x

model_container = Model_With_Container()
print(model_container)

      这种方法既能灵活调整层的顺序,又不用写太多重复代码,比如想加一层卷积,直接在layer1里塞个nn.Conv2d就行。

四、新手小结:怎么选、怎么查错

学完这三种方法,我总结了个 “新手选择指南”:

  • 简单线性模型(比如 MNIST 分类、房价回归)→ 用nn.Sequentialadd_module写法);
  • 复杂模型(带分支、多输入)→ 继承nn.Module
  • 中等复杂度(想打包部分层)→ 继承nn.Module+ 用nn.Sequential容器。

      另外,查错时一定要打印模型结构(print(model)),看看层的输入输出维度对不对,比如 Linear 层的in_features是不是前一层的out_features—— 我之前把 784 写成 748,报错 “size mismatch”,看结构才发现。

五、结尾:接下来要试试搭 ResNet

      今天学完这些,终于能独立搭出能跑的模型了!接下来打算用这些方法搭个简单的 ResNet 残差块,比如老师讲的RestNetBasicBlock,把今天的工具用熟。

      如果和我一样刚入门,建议先从 MNIST 入手,用nn.Sequential搭个简单模型跑通流程,再慢慢尝试复杂结构 ——PyTorch 的工具箱其实很友好,只要认全组件、选对工具,搭模型真的没那么难~

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐