理解"神经网络"到底是个什么东西(大白话版)
搞清楚层(Layer)、激活函数(Activation)、前向传播(Forward) 三个核心概念
用 PyTorch 搭一个最简单的单层网络,看看它怎么 "算" 出一个结果
⚠️ 这章会比较 "干"
因为要一次性讲好几个基础概念。但相信我,理解了这章之后,后面所有代码你都能看懂了。如果一遍没完全懂,跑完代码再回头读一遍就好。
先忘掉那些复杂的数学公式。我们用一个生活例子理解:
你到一家陌生的餐厅点菜,想知道某道菜好不好吃。你会怎么判断?
你可能会看:颜色(输入 1)、香味(输入 2)、摆盘(输入 3)
大脑里有个模糊的判断规则:颜色好看 +0.5 分,香味浓郁 +0.8 分,摆盘精致 +0.3 分
最终得出一个 "好吃指数":0.5×颜色 + 0.8×香味 + 0.3×摆盘
神经网络本质上就是做了这件事:接收输入 → 加权求和 → 输出结果。
只不过:
"输入"是图片的像素值(28×28 = 784 个数字)
"权重"是网络要学的东西(一开始瞎猜,通过训练慢慢调准)
"输出"是 0~9 每个数字的"得分"
想象一个场景:识别手写数字图片(0‑9)。
一张 MNIST 图片是 28×28 像素,一共 28×28 = 784 个小光点。每个光点有明暗数值。
全连接层指前一层的每一个点,全部都和后一层的每一个点相连。如下图:

举例子:输入 784 个光点,输出 10 个数字分数(代表是 0、1、2…9 的可能性)。
输入:784 个小光点(784 个小圆点)
输出:10 个结果点(分别代表数字 0 到 9 的打分)
“全连接” 含义:
784 个输入点,每一个,都连到后面 10 个输出点上面。
总共:784 × 10 = 7840 根连接线。
每一根连线上面,都有一个权重(weight),可以理解成这根线的“重要程度”。
线的数字大 = 这个输入对结果影响大;
数字小 = 影响小;
负数 = 起反作用;
每个输出节点,还要再加一个小偏移数字,叫偏置 bias。
计算过程(大白话):
拿输出“数字 0 的分数” 举例:
784 个像素,分别乘上各自连线的权重
全部加在一起
再加一个偏移 bias
最后得到一个分数。
分数越高,模型认为这张图片越有可能是数字 0。
数字 1~9 的分数,也是用一模一样的方式算出来。
全连接层是神经网络最基本的组件。它做的事就是上面说的 "加权求和":

用人话翻译:
x(输入):比如一张图展开成 784 个像素值
W(权重/Weight):每个输入有多重要,是网络要学习的核心参数
b(偏置/Bias):一个"基础分",比如某道菜天生就难吃,基础分扣 5 分
y(输出):加权后的结果
📌 术语:参数(Parameter)
模型里需要通过训练来调整的变量,就是参数。Weight 和 Bias 是最主要的参数。
训练的本质就是不断调整 W 和 b,让模型的输出越来越接近正确答案。
用代码演示一下:
import torch
# 导入神经网络模块,里面包含各种网络层、激活函数
# nn = neural network,PyTorch 的神经网络模块
import torch.nn as nn
# 定义一个最简单的全连接层:784个输入 → 10个输出(数字0~9各一个得分)
layer = nn.Linear(in_features=784, out_features=10)
# 随便造一个"假图片"输入(形状:1张图 × 784像素)
dummy_input = torch.randn(1, 784)
# 前向传播:输入流过这一层,得到输出
output = layer(dummy_input)
print(f"输入形状: {dummy_input.shape}") # → (1, 784)
print(f"输出形状: {output.shape}") # → (1, 10)
print(f"10个输出值: ")
# range(10) 等价 range(0,10),遍历 0,1,2...9 全部 10 个类别
for i in range(10):
# output[0,i]:第0张样本,第i类的输出分数
print(f"{i} = {output[0, i].item():.4f}")运行代码,输出如下:
输入形状: torch.Size([1, 784])
输出形状: torch.Size([1, 10])
10个输出值:
0 = -0.3233
1 = -0.5019
2 = -0.1596
3 = -1.1589
4 = 0.7955
5 = 0.4612
6 = 0.4138
7 = -0.5528
8 = -0.0840
9 = 0.0490上面的 10 个输出值就是模型对 "这张图是数字 0~9 各几分" 的初步判断。值越大,模型越倾向于是那个数字。
光有全连接层不够。因为全连接层只做线性运算(乘法和加法),无论堆多少层,最终还是线性的,表达能力有限。
激活函数的作用就是引入"非线性",让网络能学习更复杂的模式。
📌 术语:线性 vs 非线性
线性:y = 2x + 1,画出来是一条直线
非线性:y = x²,画出来是条曲线;或者 y = max(0, x),折线
现实世界的问题几乎没有纯线性的,所以必须引入非线性。
如下图:

ReLU 全称 Rectified Linear Unit(修正线性单元),听起来吓人,函数超简单:

翻译成人话:负数变成 0,正数保持不变。就这。
import torch
# 导入神经网络模块,里面包含各种网络层、激活函数
import torch.nn as nn
# 实例化ReLU激活函数对象
# ReLU作用:负数全部变成0,正数保持不变;给神经网络引入非线性能力
relu = nn.ReLU()
# 创建一维张量,包含正负、零,用来测试 ReLU 效果
x = torch.tensor([-2.0, -1.0, 0.0, 1.0, 2.0])
# 将张量 x 送入 ReLU 做计算,得到输出y
y = relu(x)
print(x) # → tensor([-2., -1., 0., 1., 2.])
print(y) # → tensor([0., 0., 0., 1., 2.])为什么这么简单的东西能有用?因为它给了网络 "选择忽略" 某些信息的能力。输出是 0,意味着后面的层就收不到这个信号了。
其他常见激活函数:
| 名称 | 特点 | 什么时候用 |
| ReLU | 负数清零,正数不变 | 隐藏层默认首选 |
| Sigmoid | 输出压缩到 (0, 1) | 二分类的最后一层 |
| Softmax | 输出变成概率分布(所有值加起来=1) | 多分类的最后一层 |
对于 MNIST 的 10 分类问题,最后一层输出后跟一个 Softmax,把 10 个 "得分" 转成 10 个 "概率"。
前向传播 = 数据从输入层开始,一层一层往前算,直到输出层得到结果。
这名字听起来很深奥,其实就是 "数据从左到右流一遍" 而已。如下图:

用代码模拟一个简单的 3 层网络的前向传播:
import torch
import torch.nn as nn
# 定义网络结构,继承 nn.Module,所有神经网络都要继承这个基类
class SimpleNet(nn.Module):
def __init__(self):
super().__init__() # 必须调用父类构造函数,初始化网络基础功能
self.fc1 = nn.Linear(784, 128) # 第1层全连接:784输入 → 128输出神经元
self.fc2 = nn.Linear(128, 64) # 第2层全连接:128输入 → 64输出神经元
self.fc3 = nn.Linear(64, 10) # 第3层全连接:64输入 → 10输出(0‑9类别得分)
def forward(self, x):
# 这就是"前向传播":一层接一层往下计算数据
x = self.fc1(x) # 数据流过第1层全连接
x = torch.relu(x) # ReLU激活函数,引入非线性,网络才能学习复杂特征
x = self.fc2(x) # 数据流过第2层全连接
x = torch.relu(x) # ReLU激活函数
x = self.fc3(x) # 数据流过第3层全连接,得到10个类别的原始得分
return x # 返回网络输出
# SimpleNet 使用示例
# 1. 创建网络实例,实例化神经网络对象
model = SimpleNet()
# 2. 构造模拟输入数据:batch_size=2,每张图片摊平为784维向量(28x28)
# 模拟2张 MNIST 图片,形状 [批次大小,784]
dummy_input = torch.randn(2, 784)
print(f"模拟输入张量形状:{dummy_input.shape}")
# 3. 执行前向传播,把数据喂给模型;自动调用 forward 函数
output = model(dummy_input)
# 4. 打印输出信息
print(f"模型输出张量形状:{output.shape}") # [2,10],2个样本,每个样本10个类别得分
print("\n第0号样本10个类别得分:")
for cls_idx in range(10):
print(f"类别 {cls_idx} 得分: {output[0, cls_idx].item():.4f}")
print("\n第1号样本10个类别得分:")
for cls_idx in range(10):
print(f"类别 {cls_idx} 得分: {output[1, cls_idx].item():.4f}")
# 5. 获取预测类别:得分最大的下标就是模型预测的数字
# 张量 output 的形状 [2,10]
pred = torch.argmax(output, dim=1)
print(f"\n模型预测类别:{pred.tolist()}")代码中,torch.argmax(output, dim=1) 用于在维度为 1上,找出最大值对应的下标索引,不是返回最大值本身,返回最大值的位置编号。
运行代码,输出如下:
模拟输入张量形状:torch.Size([2, 784])
模型输出张量形状:torch.Size([2, 10])
第0号样本10个类别得分:
类别 0 得分: 0.2391
类别 1 得分: 0.1663
类别 2 得分: 0.0741
类别 3 得分: 0.0821
类别 4 得分: -0.0421
类别 5 得分: -0.1602
类别 6 得分: 0.0773
类别 7 得分: -0.2579
类别 8 得分: -0.0625
类别 9 得分: 0.0067
第1号样本10个类别得分:
类别 0 得分: 0.1215
类别 1 得分: 0.1625
类别 2 得分: 0.0418
类别 3 得分: 0.0115
类别 4 得分: -0.0278
类别 5 得分: -0.1786
类别 6 得分: 0.2596
类别 7 得分: -0.2208
类别 8 得分: -0.0045
类别 9 得分: 0.0191
模型预测类别:[0, 6]记住一个规律:每个 fc 层之后必须跟一个激活函数(除了最后一层可以不加,等后面用 Softmax 处理)。否则堆多少层都没意义(线性堆叠还是线性)。
你注意到上面的代码有个 class SimpleNet(nn.Module)。nn.Module 是 PyTorch 里所有神经网络的父类。继承它之后,你的网络就自动获得了:
管理参数的能力(model.parameters() 能列出所有要训练的参数)
保存/加载模型的能力(torch.save / torch.load)
搬到 GPU 的能力(model.to('cuda'))
所以你只需要在 __init__ 里定义有哪些层,在 forward 里写怎么计算,剩下的 PyTorch 自动帮你搞定。
全连接层(nn.Linear):做 y = x·W + b 的加权求和
激活函数:引入非线性,让网络能学复杂模式。隐藏层用 ReLU,分类输出层用 Softmax
前向传播:数据从输入层流到输出层的过程,就是 forward() 方法里写的
nn.Module:PyTorch 网络的基类,继承它能省很多事
点击查看完整的 搭一个简单网络试试前向传播 示例代码。