神经网络训练过程详解
神经网络训练过程是一个动态的、迭代的学习过程,接下来基于一段代码展示模型是如何逐步学习数据规律的。
神经网络拟合二次函数:代码详解
下面将详细解释这段代码,它使用神经网络拟合一个带有噪声的二次函数 y = x² + 2x + 1
。
import torch
import numpy as np
import matplotlib.pyplot as plt
# 1. 生成模拟数据
np.random.seed(22) # 设置随机种子确保结果可重现
X = np.linspace(-5, 5, 100).reshape(-1,1) # 创建100个点,范围-5到5
y_ = X**2 + 2* X + 1 # 二次函数:y = x² + 2x + 1
y = y_ + np.random.rand(100, 1) * 1.5 # 添加均匀分布噪声
# 转换为PyTorch张量
X_tensor = torch.tensor(X, dtype=torch.float32)
y_tensor = torch.tensor(y, dtype=torch.float32)
# 打印数据信息
print("X 原类型:", type(X), " 形状:", X.shape)
print("X_tensor 类型:", type(X_tensor), " 形状:", X_tensor.shape)
print("\ny 原类型:", type(y), " 形状:", y.shape)
print("y_tensor 类型:", type(y_tensor), " 形状:", y_tensor.shape)
print("\nX_tensor 前 2 个元素:\n", X_tensor[:2])
print("y_tensor 前 2 个元素:\n", y_tensor[:2])
代码解析
1. 神经网络定义
import torch.nn as nn
class SimpleNN(nn.Module):
def __init__(self):
super(SimpleNN, self).__init__()
# 定义三个全连接层
self.layer1 = nn.Linear(1, 64) # 输入层到隐藏层1 (1个特征->64个神经元)
self.layer2 = nn.Linear(64, 128) # 隐藏层1到隐藏层2 (64个神经元->128个神经元)
self.layer3 = nn.Linear(128, 1) # 隐藏层2到输出层 (128个神经元->1个输出)
self.relu = nn.ReLU() # ReLU激活函数
def forward(self, x):
# 前向传播过程(带激活函数)
x = self.relu(self.layer1(x)) # 第一层后接ReLU激活
x = self.relu(self.layer2(x)) # 第二层后接ReLU激活
x = self.layer3(x) # 输出层(无激活函数)
return x
def forward_line(self, x):
# 前向传播过程(不带激活函数)
x = self.layer1(x) # 无激活
x = self.layer2(x) # 无激活
x = self.layer3(x) # 输出层
return x
网络结构详解:
输入层(1个神经元) → [ReLU激活] → 隐藏层1(64个神经元) → [ReLU激活] → 隐藏层2(128个神经元) → 输出层(1个神经元)
- 输入层:1个神经元,对应单个特征x
- 隐藏层1:64个神经元,使用ReLU激活函数
- 隐藏层2:128个神经元,使用ReLU激活函数
- 输出层:1个神经元,无激活函数(回归问题)
- 为什么需要多层和ReLU?:为了拟合非线性关系(二次函数)
2. 模型训练
import torch.optim as optim
# 初始化模型、损失函数和优化器
model = SimpleNN()
criterion = nn.MSELoss() # 均方误差损失(回归问题常用)
optimizer = optim.Adam(model.parameters(), lr=0.01) # Adam优化器
epochs = 200 # 训练轮数
# 训练循环
loss_history = [] # 记录损失变化
for epoch in range(epochs):
# 前向传播
outputs = model(X_tensor)
loss = criterion(outputs, y_tensor)
# 反向传播和优化
optimizer.zero_grad() # 清空梯度
loss.backward() # 反向传播计算梯度
optimizer.step() # 更新参数
# 记录损失
loss_history.append(loss.item())
# 每50轮打印一次损失
if(epoch + 1)%50 == 0:
print(f'Epoch [{epoch + 1}/{epochs}], Loss: {loss.item():.4f}')
训练过程说明:
- 前向传播:输入数据通过网络得到预测值
- 损失计算:比较预测值与真实值的差异(均方误差)
- 反向传播:
zero_grad()
: 清空之前的梯度backward()
: 计算新的梯度
- 参数更新:
step()
使用梯度更新权重 - 损失记录:跟踪训练过程中的损失变化
3. 结果可视化
# 创建图表
plt.figure(figsize=(12,4))
# 左图:训练损失曲线
plt.subplot(1,2,1)
plt.plot(loss_history)
plt.title('Training Loss')
plt.xlabel('Epochs')
plt.ylabel('MSE Loss')
# 右图:预测结果对比
plt.subplot(1, 2, 2)
with torch.no_grad(): # 禁用梯度计算(预测时不需要)
predictions = model(X_tensor).numpy() # 获取预测值
# 绘制三种数据:
plt.scatter(X, y, label='original data') # 散点:带噪声的原始数据
plt.plot(X, y_, 'g--', label='True Relation', linewidth=4) # 绿色虚线:真实二次函数
plt.plot(X, predictions, 'r--', label='prediction', linewidth=4) # 红色虚线:神经网络预测
plt.title('pre vs true')
plt.legend() # 显示图例
plt.tight_layout()
plt.show()
可视化解读:
- 损失曲线:展示训练过程中损失值如何下降
- 预测对比:
- 散点:带噪声的原始数据
- 绿色虚线:真实的二次函数
y = x² + 2x + 1
- 红色虚线:神经网络预测的曲线
4. 模型保存与预测
# 1. 保存完整模型
torch.save(model, 'full_model.pth')
# 2. 加载完整模型(无需提前定义模型结构)
loaded_model = torch.load('full_model.pth')
loaded_model.eval() # 设置为评估模式(关闭dropout等)
# 使用模型进行预测
new_data = torch.tensor([[3.0], [-2.5]], dtype=torch.float32)
predictions = loaded_model(new_data).detach().numpy()
print("\nPrediction Examples:")
for x, pred in zip(new_data, predictions):
# 计算真实值(注意:这里是二次函数,不是线性)
true_value = x.item()**2 + 2 * x.item() + 1
print(f"Input {x.item():.1f} -> Predicted: {pred[0]:.2f} | True: {true_value:.2f}")
模型保存与加载:
torch.save(model, 'full_model.pth')
:保存整个模型结构+参数torch.load('full_model.pth')
:加载完整模型model.eval()
:将模型设置为评估模式(影响dropout、batchnorm等层)
预测示例:
- 输入x=3.0:真实值=3² + 2×3 + 1 = 16.00
- 输入x=-2.5:真实值=(-2.5)² + 2×(-2.5) + 1 = 2.25
代码关键点总结
-
数据生成:
- 创建二次函数
y = x² + 2x + 1
- 添加均匀分布噪声模拟真实数据
- 创建二次函数
-
网络结构:
- 深度网络(1-64-128-1)适合拟合非线性关系
- 使用ReLU激活函数引入非线性能力
-
训练配置:
- 均方误差损失(MSE)适合回归问题
- Adam优化器自动调整学习率
- 200个训练轮次足够收敛
-
可视化:
- 损失曲线监控训练过程
- 预测对比评估模型性能
-
模型部署:
- 保存和加载完整模型
- 对新数据进行预测
为什么这个网络能拟合二次函数?
- 非线性激活函数:ReLU使网络能学习非线性关系
- 足够容量:两个隐藏层提供足够的表达能力
- 优化能力:Adam优化器有效调整参数
- 迭代训练:200轮训练使网络逐步逼近目标函数
这个示例展示了神经网络如何学习复杂的非线性关系,即使数据中存在噪声,网络也能捕捉到潜在的函数规律。
训练过程可视化
让我们通过一个动画来理解训练过程(想象以下动态变化):
Epoch 0: 损失: 35.42 | 预测线: 随机波动
Epoch 50: 损失: 2.65 | 预测线: 开始呈现线性趋势
Epoch 100:损失: 2.29 | 预测线: 接近真实关系但仍有偏差
Epoch 200:损失: 1.97 | 预测线: 几乎与真实关系重合
Epoch 500:损失: 1.97 | 预测线: 稳定在最优解附近
训练过程分步解析
1. 初始化阶段 (Epoch 0)
- 权重和偏置随机初始化(通常使用正态分布或均匀分布)
- 神经网络对数据一无所知
- 预测结果完全随机
- 损失值非常高(约35.42)
2. 早期训练阶段 (Epoch 1-50)
# 第一次迭代
outputs = model(X_tensor) # 随机预测
loss = criterion(outputs, y_tensor) # 计算损失(很大)
loss.backward() # 计算梯度
optimizer.step() # 首次更新参数
- 网络开始识别数据的基本模式
- 预测线开始呈现大致正确的斜率
- 损失值快速下降(从35.42到约2.65)
- 模型学习速度最快(梯度最大)
3. 中期训练阶段 (Epoch 50-200)
# 典型迭代
outputs = model(X_tensor) # 预测接近真实值
loss = criterion(outputs, y_tensor) # 中等损失
loss.backward() # 计算较小梯度
optimizer.step() # 微调参数
- 网络捕捉到线性关系的基本特征
- 预测线越来越接近绿色真实关系线
- 损失值缓慢下降(从2.65到1.97)
- 学习速度变慢(梯度变小)
4. 后期训练阶段 (Epoch 200-500)
# 后期迭代
outputs = model(X_tensor) # 预测非常接近真实值
loss = criterion(outputs, y_tensor) # 小损失
loss.backward() # 计算微小梯度
optimizer.step() # 微小调整参数
- 网络优化细节
- 预测线与真实关系线几乎重合
- 损失值稳定在约1.97
- 模型收敛(参数变化很小)
训练过程关键元素详解
1. 前向传播 (Forward Pass)
outputs = model(X_tensor)
- 输入数据通过神经网络各层
- 计算过程:
输入x → 线性变换: z1 = w1*x + b1 → ReLU激活: a1 = max(0, z1) → 线性变换: output = w2*a1 + b2
2. 损失计算 (Loss Calculation)
loss = criterion(outputs, y_tensor)
- 计算预测值与真实值的差异
- 使用均方误差公式:
MSE = 1/N * Σ(预测值 - 真实值)²
3. 反向传播 (Backward Pass)
loss.backward()
- 计算损失函数对每个参数的梯度
- 使用链式法则从输出层向输入层反向传播
- 梯度表示"参数应该如何调整以减少损失"
4. 参数更新 (Parameter Update)
optimizer.step()
- Adam优化器根据梯度更新参数
- 更新公式简化表示:
新参数 = 旧参数 - 学习率 * 梯度
- 学习率(0.01)控制更新步长
训练过程可视化分析
损失曲线图
损失值
35 |*················
30 | *···············
25 | *··············
20 | *·············
15 | *············
10 | *···········
5 | **·········
2 | ****····
1 | ****
0 +-----------------→ Epoch
0 50 100 200 500
- 曲线特点:开始陡峭下降,后期平缓
- 表明:初期学习快,后期优化细调
预测结果演变
真实关系: y = 2x + 1 (绿色虚线)
Epoch 0:
预测线: 随机波动 (红色线)
Epoch 50:
预测线: 大致正确斜率但截距偏差
Epoch 100:
预测线: 接近真实线,部分区域过拟合噪声
Epoch 500:
预测线: 几乎与绿色虚线重合
为什么训练有效?
- 梯度下降原理:每次更新都向减少损失的方向移动
- 链式法则:高效计算所有参数的梯度
- 自适应优化器:Adam自动调整学习率
- 非线性能力:ReLU激活函数使网络能拟合复杂模式
- 迭代优化:多次重复使模型逐步接近最优解
训练结束后的模型状态
- 权重和偏置已优化到最佳值
- 网络学习到了潜在规律
y = 2x + 1
- 能够准确预测新数据点
- 损失值稳定在最低点(约1.97)
这个训练过程展示了神经网络如何从随机初始状态开始,通过反复的预测、评估和调整,最终学习到数据背后的规律。即使数据中存在噪声,神经网络也能识别出真实的线性关系。