神经网络这门课,很多人是“理论会背,代码不会写”。看了几天反向传播公式,觉得自己懂了,打开 PyTorch 准备跑个模型,结果连DataLoader都报错。这次我们直接换个思路:不按教材目录走,而是挑几个最常见、最容易上手的神经网络,配可运行的 Python 代码,一行行解释输入输出和训练流程。看完这篇文章,你应该能自己跑通 BP 神经网络、卷积神经网络、循环神经网络,并且能判断不同任务应该优先选哪种网络结构。
本文覆盖的内容不是某个开源项目的一键部署,而是“神经网络代码实操”。我会用 PyTorch 写 4 组完整示例:第一个是 BP 前馈网络解决异或问题,用来理解反向传播到底在做什么;第二个是 CNN 手写数字识别,对应网上常见的“基于卷积神经网络的手写数字识别”教程,但用 PyTorch 重新实现;第三个是循环神经网络,直接从核心公式开始手写一个 vanilla RNN,用来理解时间步和隐藏状态;最后补充图神经网络、物理信息神经网络、强化学习 TD3 等进阶方向的代码入口。环境上只需要 Python 加 PyTorch,CPU 也能跑通,有 NVIDIA 显卡就顺手开 CUDA 加速,门槛不高,适合作为神经网络入门和代码巩固的第一篇实操笔记。
1. 核心能力速览
| 能力项 | 说明 |
|---|---|
| 覆盖网络类型 | BP/前馈神经网络、CNN 卷积神经网络、RNN 循环神经网络、图神经网络、物理信息神经网络、TD3 强化学习入门代码 |
| 开发语言 | Python 3.8+ |
| 深度学习框架 | PyTorch 1.10 及以上,CPU 版本即可运行 |
| 硬件需求 | CPU 可跑通全部示例;CNN 的 MNIST 训练集建议使用 GPU 加速,显存 4G 以上即可,实际占用随 batch size 和模型结构调整 |
| 主要代码库 | torch、torchvision、matplotlib、numpy |
| 学习收益 | 理解反向传播过程、卷积特征提取流程、RNN 隐藏状态更新机制,以及进阶网络的设计思想 |
| 适合人群 | 学过神经网络理论、但缺少代码经验的学生;准备做论文实验的入门研究者;想从分类任务转向序列建模的开发者 |
从材料中的热词可以看到,神经网络相关的高频搜索包括“卷积神经网络”“循环神经网络”“BP神经网络结构图”“物理信息神经网络”“hopfield神经网络”“td3代码pytorch”等。下面我会尽量覆盖这些方向,并把重点放在能直接运行的代码上。
2. 神经网络分类与适用场景
很多初学者会混淆“神经网络”这个大概念下的不同分支。实际工程中,选择哪种网络结构,取决于输入数据的形态和任务目标。
从结构上划分,神经网络大致可以分为 5 类:
- 前馈神经网络 / BP 神经网络:数据从输入层向输出层单向流动,常见结构为多层感知机。适用于结构化数据分类、回归任务,是理解反向传播算法的基础。
- 卷积神经网络 CNN:通过卷积核提取局部特征,适合图像、视频帧、语音频谱等多维数据。典型任务包括手写数字识别、图像分类、目标检测。
- 循环神经网络 RNN:在时间步之间传递隐藏状态,适合文本、语音、股票序列等时间序列数据。vanilla RNN 是理解 LSTM、GRU 的起点。
- 图神经网络 GNN:处理非欧几里得数据,例如社交网络、分子结构、知识图谱,核心操作是节点特征聚合。
- 物理信息神经网络 PINN:把物理方程作为损失约束,嵌入神经网络训练过程中,用于偏微分方程求解、流体模拟等领域。
此外还有 Hopfield 神经网络这类早期的联想记忆模型、贝叶斯神经网络这类不确定性建模模型,以及 TD3、PPO 等强化学习中使用的 Actor-Critic 网络。
选择建议可以简化为三句话:图像任务优先 CNN;序列任务优先 RNN/LSTM/Transformer;表格数据任务优先 XGBoost 或带正则化的 MLP。神经网络不是越复杂越好,而是越匹配数据形态越好。
3. 环境准备与前置条件
3.1 安装 Python 与 PyTorch
示例代码全部基于 Python 3.8 以上版本,推荐使用 3.9 或 3.10,兼容性更稳。PyTorch 安装建议直接走官方网站的镜像命令,CPU 用户可以这样做:
pip install torch torchvision matplotlib numpy如果你有 NVIDIA 显卡,并且已经安装好对应版本的 CUDA 驱动,可以先到 PyTorch 官网复制适合本机 CUDA 版本的安装命令,例如:
# 示例:CUDA 12.1 版本,实际命令以官网为准 pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu1213.2 验证 PyTorch 是否可用
安装完成后先跑一段验证代码,确保torch能正常导入,同时确认 CUDA 是否可用:
import torch print("PyTorch 版本:", torch.__version__) print("CUDA 是否可用:", torch.cuda.is_available()) print("CUDA 设备名称:", torch.cuda.get_device_name(0) if torch.cuda.is_available() else "CPU")如果你用的是 CPU 环境,torch.cuda.is_available()会返回False,这是正常现象,不影响本文代码运行。
3.3 检查磁盘与内存
MNIST 数据集会自动下载,占用约 60MB 磁盘空间。模型文件都很小,整个项目在磁盘上占用不到 500MB。内存方面,4G 以上内存可以流畅运行所有示例。
4. BP 神经网络代码实操:从异或问题理解反向传播
4.1 为什么选异或问题
异或(XOR)问题是神经网络入门最经典的实验。单层感知机无法解决异或问题,因为这个数据集不是线性可分的;而带一个隐藏层的 BP 神经网络可以轻松拟合。通过观察 loss 从大到小下降的过程,你能直观理解反向传播在做什么。
4.2 完整训练代码
import torch import torch.nn as nn import torch.optim as optim # 1. 构造异或数据集 X = torch.tensor([[0.0, 0.0], [0.0, 1.0], [1.0, 0.0], [1.0, 1.0]]) y = torch.tensor([[0.0], [1.0], [1.0], [0.0]]) # 2. 定义两层 BP 神经网络 class BPNet(nn.Module): def __init__(self): super(BPNet, self).__init__() self.fc1 = nn.Linear(2, 4) # 输入层到隐藏层,隐藏层 4 个神经元 self.fc2 = nn.Linear(4, 1) # 隐藏层到输出层,输出 1 个值 self.sigmoid = nn.Sigmoid() def forward(self, x): x = self.sigmoid(self.fc1(x)) x = self.sigmoid(self.fc2(x)) return x model = BPNet() criterion = nn.MSELoss() # 回归任务用均方误差 optimizer = optim.SGD(model.parameters(), lr=0.5) # 3. 训练 5000 轮 for epoch in range(5000): optimizer.zero_grad() outputs = model(X) loss = criterion(outputs, y) loss.backward() optimizer.step() if epoch % 500 == 0: print(f"Epoch [{epoch}/5000], Loss: {loss.item():.6f}") # 4. 测试推理结果 print("\n训练后的预测结果:") with torch.no_grad(): for i in range(4): pred = model(X[i]).item() print(f"输入 {X[i].tolist()} -> 预测 {pred:.4f} -> 四舍五入 {round(pred)}")4.3 代码逐段解释
第一段构造异或数据,四条数据分别对应 0^0=0、0^1=1、1^0=1、1^1=0。第二段定义网络结构,nn.Linear(2, 4)表示输入维度是 2,输出维度是 4,也就是隐藏层有 4 个神经元;nn.Linear(4, 1)输出单个值。激活函数使用 Sigmoid,把输出压缩到 0 到 1 之间。
训练循环的核心是loss.backward()。PyTorch 会自动计算每个参数对 loss 的梯度,然后optimizer.step()用梯度更新权重。这个过程就是反向传播加梯度下降。训练结束后,用model(X[i])验证预测结果。
预期输出是一个接近 0 和 1 的浮动值,例如0.021和0.978,说明模型已经学到了异或规则。如果 loss 一直不下降,检查学习率是否过大或过小,通常0.1到1.0之间的学习率对这个任务都有效。
5. 卷积神经网络代码实操:基于 PyTorch 的手写数字识别
5.1 网络设计思路
这一节对应很多同学在搜索的“基于卷积神经网络的手写数字识别”。MNIST 数据集包含 0 到 9 的灰度手写数字图片,每张图片尺寸是28x28。CNN 的处理流程是:卷积层提取边缘和纹理特征,池化层压缩特征尺寸,展平后接全连接层完成分类。
5.2 完整代码
import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import DataLoader from torchvision import datasets, transforms # 1. 数据预处理 transform = transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,)) ]) train_dataset = datasets.MNIST( root='./data', train=True, download=True, transform=transform ) test_dataset = datasets.MNIST( root='./data', train=False, download=True, transform=transform ) train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True) test_loader = DataLoader(test_dataset, batch_size=256, shuffle=False) # 2. 定义 CNN 模型 class CNN(nn.Module): def __init__(self): super(CNN, self).__init__() self.conv1 = nn.Conv2d(1, 32, kernel_size=3, padding=1) # 输入通道1,输出32 self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1) # 输入32,输出64 self.pool = nn.MaxPool2d(2, 2) self.fc1 = nn.Linear(64 * 7 * 7, 128) self.fc2 = nn.Linear(128, 10) self.relu = nn.ReLU() self.dropout = nn.Dropout(0.25) def forward(self, x): x = self.pool(self.relu(self.conv1(x))) # 28x28 -> 14x14 x = self.pool(self.relu(self.conv2(x))) # 14x14 -> 7x7 x = x.view(-1, 64 * 7 * 7) # 展平 x = self.relu(self.fc1(x)) x = self.dropout(x) x = self.fc2(x) return x model = CNN() criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(model.parameters(), lr=0.001) # 3. 训练函数 def train_one_epoch(epoch): model.train() total_loss = 0 for batch_idx, (images, labels) in enumerate(train_loader): optimizer.zero_grad() outputs = model(images) loss = criterion(outputs, labels) loss.backward() optimizer.step() total_loss += loss.item() print(f"Epoch {epoch}, 平均 Loss: {total_loss / len(train_loader):.6f}") # 4. 测试函数 def evaluate(): model.eval() correct = 0 total = 0 with torch.no_grad(): for images, labels in test_loader: outputs = model(images) _, predicted = torch.max(outputs, 1) total += labels.size(0) correct += (predicted == labels).sum().item() print(f"测试集准确率: {100.0 * correct / total:.2f}%") # 5. 训练 5 个 epoch 并评估 for epoch in range(1, 6): train_one_epoch(epoch) evaluate()5.3 结果判断
在 CPU 环境下,一个 epoch 大约需要几十秒,5 个 epoch 能跑到 98% 以上的测试准确率。如果使用 GPU,比如 4G 显存的入门显卡,训练时间能明显缩短。
这组代码有几个值得关注的点:第一,Conv2d(1, 32, kernel_size=3, padding=1)把单通道灰度图转成 32 个特征图;第二,MaxPool2d(2, 2)把特征图尺寸减半;第三,x.view(-1, 64*7*7)是把多维特征图展平成一维向量,方便送入全连接层。如果报维度错误,问题通常出在这一步,需要根据输入尺寸重新计算。
6. 循环神经网络代码实操:从核心公式理解时间步
6.1 vanilla RNN 的核心公式
标准循环神经网络在每个时间步t的隐藏状态更新公式如下:
h_t = tanh(W_hh * h_{t-1} + W_xh * x_t + b_h) y_t = W_hy * h_t + b_y其中h_{t-1}是上一个时间步的隐藏状态,x_t是当前时间步的输入,W_hh是隐藏状态到隐藏状态的权重,W_xh是输入到隐藏状态的权重。循环神经网络的工作方式类似于一个人逐字阅读文本,每读一个字,都会带着上一个字的记忆去理解当前字。
下面我用 NumPy 手写这个公式,方便你直观理解循环网络内部发生了什么。
6.2 手写 vanilla RNN 前向传播
import numpy as np def vanilla_rnn_forward(input_seq, hidden_state, W_xh, W_hh, W_hy, b_h, b_y): """ input_seq: shape (seq_len, input_size) hidden_state: shape (hidden_size,) W_xh: shape (input_size, hidden_size) W_hh: shape (hidden_size, hidden_size) W_hy: shape (hidden_size, output_size) """ seq_len = input_seq.shape[0] hidden_states = [] outputs = [] for t in range(seq_len): x_t = input_seq[t] # 当前时间步输入 hidden_state = np.tanh( np.dot(hidden_state, W_hh) + np.dot(x_t, W_xh) + b_h ) y_t = np.dot(hidden_state, W_hy) + b_y hidden_states.append(hidden_state.copy()) outputs.append(y_t) return np.array(hidden_states), np.array(outputs) input_size = 1 hidden_size = 8 output_size = 1 np.random.seed(0) W_xh = np.random.randn(input_size, hidden_size) * 0.1 W_hh = np.random.randn(hidden_size, hidden_size) * 0.1 W_hy = np.random.randn(hidden_size, output_size) * 0.1 b_h = np.zeros(hidden_size) b_y = np.zeros(output_size) # 模拟 10 个时间步的正弦序列输入 t = np.linspace(0, 1, 10) input_seq = np.sin(2 * np.pi * t).reshape(-1, 1) hidden_state = np.zeros(hidden_size) hidden_states, outputs = vanilla_rnn_forward( input_seq, hidden_state, W_xh, W_hh, W_hy, b_h, b_y ) print("输入序列长度:", input_seq.shape) print("隐藏状态序列形状:", hidden_states.shape) print("输出序列形状:", outputs.shape)6.3 用 PyTorch 调用内置 RNN
实际开发中不需要手写公式,直接使用nn.RNN即可。下面是一个用 PyTorch 内置 RNN 层拟合正弦序列下一个值的示例:
import torch import torch.nn as nn import numpy as np # 生成正弦序列数据 X = [] y = [] sequence = np.sin(np.linspace(0, 20 * np.pi, 500)) seq_len = 10 for i in range(len(sequence) - seq_len - 1): X.append(sequence[i:i + seq_len]) y.append(sequence[i + seq_len]) X = torch.tensor(X, dtype=torch.float32).unsqueeze(-1) # shape: (样本数, seq_len, 1) y = torch.tensor(y, dtype=torch.float32).unsqueeze(-1) class SimpleRNN(nn.Module): def __init__(self): super(SimpleRNN, self).__init__() self.rnn = nn.RNN(input_size=1, hidden_size=16, batch_first=True) self.fc = nn.Linear(16, 1) def forward(self, x): out, _ = self.rnn(x) # out shape: (batch, seq_len, hidden_size) out = self.fc(out[:, -1, :]) # 取最后一个时间步的输出 return out model = SimpleRNN() criterion = nn.MSELoss() optimizer = torch.optim.Adam(model.parameters(), lr=0.01) for epoch in range(100): seq_len = min(64, X.shape[0]) idx = np.random.choice(X.shape[0], seq_len, replace=False) inputs = X[idx] targets = y[idx] optimizer.zero_grad() outputs = model(inputs) loss = criterion(outputs, targets) loss.backward() optimizer.step() if epoch % 20 == 0: print(f"Epoch {epoch}, Loss: {loss.item():.6f}")batch_first=True的含义是输入张量的维度顺序为(batch, seq_len, input_size)。最后取out[:, -1, :]是因为我们只关心最后一个时间步的预测,如果做序列到序列的翻译任务,通常需要返回完整输出序列。
7. 进阶方向代码入口:图神经网络、PINN 与 TD3
7.1 图神经网络 GNN
图神经网络处理的是节点和边的数据,最常用的库是 PyTorch Geometric(PyG)。安装命令如下:
pip install torch_geometric一个简单的图分类或节点分类模型,核心步骤包括定义GCNConv卷积层、传递节点特征和边索引。入门时建议先跑官方提供的 Cora 数据集节点分类示例,它展示了图数据的基本格式:节点特征矩阵x、邻接边索引edge_index、节点标签y。GNN 与 CNN 的最大区别在于,CNN 卷积核作用在固定网格区域,GNN 的卷积作用在任意拓扑结构的邻居节点上。
7.2 物理信息神经网络 PINN
物理信息神经网络把科学原理嵌入 AI 模型的设计与实践中,核心思想是在损失函数里加入物理方程的残差项。以最简单的一维热传导方程为例,网络不仅要拟合初始条件和边界条件,还要让模型输出满足方程本身。损失函数通常写成:
L = L_data + λ * L_pde这里L_data是数据拟合误差,L_pde是控制方程残差,λ 是权重系数。实现时使用自动微分计算输出对输入空间坐标的导数,把导数代入 PDE 计算残差。PINN 的代码难点不在网络结构,而在于损失函数设计和自动微分的正确使用。建议新手先用常微分方程练手,再切换到偏微分方程。
7.3 TD3 强化学习网络
TD3 是强化学习中面向连续动作控制的经典算法,它的网络中包含了 Actor 网络和两个 Critic 网络。如果你在搜索“td3代码pytorch”,可以先不急着看完整训练代码,先理解三个网络各自的作用:
- Actor:输入状态,输出动作。
- Critic 1 和 Critic 2:输入状态加动作,输出 Q 值,取两者较小值来避免过度估计。
- 目标网络:延迟更新,提升训练稳定性。
在 PyTorch 中定义 TD3 的 Actor 网络通常就是一个多层感知机,输入维度是观测空间维度,隐藏层用 ReLU,输出层用 Tanh 把动作限制在 [-1, 1]。建议把 TD3 放在 BP 网络代码跑通之后再学习,因为它的核心仍然是最简单的全连接网络,只是多了目标网络和延迟更新机制。
7.4 其他值得一提的网络
Hopfield 神经网络是早期用于联想记忆的模型,核心公式是状态更新,适合理解能量函数概念。贝叶斯神经网络通过给权重加先验分布来估计不确定性,在 PyTorch 中可以用torch.distributions实现变分推断的简化版。小波 Elman 神经网络则是把 Elman 循环网络和小波变换结合,常用于时间序列预测和故障诊断。这些方向都有各自的代码库和论文复现仓库,初学者不建议一开始就扎进太偏门的模型。
8. 神经网络代码常见问题与排查方法
写神经网络代码时遇到的问题,90% 集中在依赖、维度、数据、训练效果四个方面。下面按优先级整理一份排查清单。
8.1 依赖与运行环境问题
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
ModuleNotFoundError: No module named 'torch' | PyTorch 未安装 | 执行python -c "import torch" | 按 3.1 节命令安装 |
| CUDA 不可用 | 显卡驱动较旧或安装的是 CPU 版 PyTorch | 打印torch.cuda.is_available() | 更新驱动或重新安装对应 CUDA 版本 PyTorch |
| 下载 MNIST 数据集失败 | 网络受限 | 检查网络,看./data目录是否生成缓存 | 手动下载数据集并放入指定目录 |
8.2 维度不匹配
这是最常见的问题。PyTorch 报错信息中会明确写出 Upsert 维度,例如:
RuntimeError: mat1 and mat2 shapes cannot be multiplied排查顺序是:先看输入张量的 shape,再看nn.Linear的第一个参数是否等于输入维度。CNN 模型展平之前的维度计算尤其容易出错。一个通用办法是在forward里临时加一行print(x.shape),确认维度后再删掉。
8.3 Loss 不下降
Loss 不下降的原因通常有几个:
- 学习率过大或过小。SGD 在 XOR 问题上建议用 0.1 到 1.0,Adam 在分类任务上建议 0.0001 到 0.001。
- 数据没有归一化。MNIST 使用
transforms.Normalize后收敛速度明显提升。 - 激活函数选错。分类任务输出层不要用 Sigmoid,应该用 Softmax 加 CrossEntropyLoss。
- 标签和数据不匹配。检查
dataloader中images和labels是否一一对应,shuffle=True时尤其注意。
8.4 过拟合
训练集准确率高、测试集准确率低,说明模型过拟合。常用解法有三个:增加Dropout层、减小模型容量、加入数据增强。对 MNIST 这类简单数据集,两层卷积加一个 Dropout 通常不会出现过拟合。
8.5 训练速度慢
CPU 上训练 MNIST 可能需要几十秒一个 epoch,这很正常。如果速度慢到无法接受,优先检查 batch size 是否太小;batch size 从 32 调到 128,训练时间能下降不少。如果安装了 PyTorch GPU 版,运行前用torch.cuda.is_available()确认已经在使用 GPU。
8.6 显存不足
如果你在训练更复杂的模型时遇到CUDA out of memory,处理方法按优先级排列:减小 batch size;降低输入图像分辨率;使用torch.no_grad()包裹推理流程;在with torch.no_grad()中不要调用loss.backward()。本文的 MNIST 示例显存占用很低,4G 显存完全够用,实际占用会随 batch size 变化而变化。
9. 神经网络代码训练最佳实践
9.1 固定随机种子
神经网络训练涉及随机初始化,同一个代码跑两次结果可能不同。做实验对比时,在代码开头固定随机种子:
import random import numpy as np import torch def set_seed(seed=42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) if torch.cuda.is_available(): torch.cuda.manual_seed_all(seed) set_seed(42)9.2 数据、模型、输出分目录管理
写项目时不要把所有文件堆在同一个目录。建议按下面的结构组织:
project/ ├── data/ # 数据集存放位置 ├── models/ # 模型结构定义 ├── checkpoints/ # 训练得到的模型权重 ├── logs/ # 训练日志和 loss 曲线 └── outputs/ # 预测结果和可视化图片这样做的好处是,模型文件可以独立保存,后续做批量推理时只需要加载 checkpoint,不需要重新训练。
9.3 保存与加载模型
训练完成后,用torch.save保存参数,推理时用torch.load加载:
torch.save(model.state_dict(), "./checkpoints/cnn_mnist.pth") # 推理时 model = CNN() model.load_state_dict(torch.load("./checkpoints/cnn_mnist.pth")) model.eval()注意model.eval()不能省略,它会关闭 Dropout 层和 BatchNorm 层的训练行为,保证推理结果稳定。
9.4 第一次训练用最小配置
任何新数据集,第一次跑通代码比跑出好效果更重要。先把 batch size 调小、epoch 调小、网络层数调浅,确认代码没有报错、loss 能下降,再逐步增加参数。这个习惯能帮你节省大量排错时间。
9.5 数据版权与隐私合规提醒
使用公开数据集训练模型时,注意数据集本身的授权协议。MNIST、CIFAR 这类公开数据集可以用于学习和研究。如果自己采集图像、声音、人脸数据训练神经网络,必须确保数据来源合法,获得相关人员的授权,并遵守所在地区对个人信息保护的法律要求。涉及他人肖像、声音、隐私数据时,不能随意公开或商用训练出来的模型。工程应用前,建议对训练数据做匿名化处理。
10. 从代码理解神经网络的关键:先跑通,再深挖
神经网络代码实操的最终目的,不是把所有模型背下来,而是建立“结构 -> 输入 -> 输出 -> 损失函数”的直觉。先用 BP 网络跑通异或问题,你就能理解反向传播和梯度下降的关系;再用 CNN 跑通 MNIST,你就能理解卷积层为什么适合图像;接着手写一遍 RNN 核心公式,你就能明白时间步和隐藏状态的工作机制。
这篇代码教程覆盖了入门阶段最值得花时间的几个方向:BP 前馈网络、CNN 卷积网络、RNN 循环网络,以及 GNN、PINN、TD3 的进阶入口。建议你把代码复制到本地,按照 3.1 节安装环境后逐段运行。遇到维度报错就打印 shape,遇到 loss 不下降就调学习率,固定随机种子后再看效果。先跑通全部示例,再考虑把网络结构加深、换数据集、接入自己的项目。后面如果继续深入学习,可以沿着“CNN 变体(ResNet、EfficientNet)”“RNN 变体(LSTM、GRU、Transformer)”“物理信息神经网络论文复现”这三条主线走,每一步都配代码实验。这套代码跑完,你不仅会说神经网络,还能真正写出来。