神经网络训练过程详解

神经网络训练过程详解

神经网络训练过程是一个动态的、迭代的学习过程,接下来基于一段代码展示模型是如何逐步学习数据规律的。

神经网络拟合二次函数:代码详解

下面将详细解释这段代码,它使用神经网络拟合一个带有噪声的二次函数 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}')

训练过程说明

  1. 前向传播:输入数据通过网络得到预测值
  2. 损失计算:比较预测值与真实值的差异(均方误差)
  3. 反向传播
    • zero_grad(): 清空之前的梯度
    • backward(): 计算新的梯度
  4. 参数更新step()使用梯度更新权重
  5. 损失记录:跟踪训练过程中的损失变化

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()

可视化解读

  1. 损失曲线:展示训练过程中损失值如何下降
  2. 预测对比
    • 散点:带噪声的原始数据
    • 绿色虚线:真实的二次函数 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}")

模型保存与加载

  1. torch.save(model, 'full_model.pth'):保存整个模型结构+参数
  2. torch.load('full_model.pth'):加载完整模型
  3. model.eval():将模型设置为评估模式(影响dropout、batchnorm等层)

预测示例

  • 输入x=3.0:真实值=3² + 2×3 + 1 = 16.00
  • 输入x=-2.5:真实值=(-2.5)² + 2×(-2.5) + 1 = 2.25

代码关键点总结

  1. 数据生成

    • 创建二次函数 y = x² + 2x + 1
    • 添加均匀分布噪声模拟真实数据
  2. 网络结构

    • 深度网络(1-64-128-1)适合拟合非线性关系
    • 使用ReLU激活函数引入非线性能力
  3. 训练配置

    • 均方误差损失(MSE)适合回归问题
    • Adam优化器自动调整学习率
    • 200个训练轮次足够收敛
  4. 可视化

    • 损失曲线监控训练过程
    • 预测对比评估模型性能
  5. 模型部署

    • 保存和加载完整模型
    • 对新数据进行预测

为什么这个网络能拟合二次函数?

  1. 非线性激活函数:ReLU使网络能学习非线性关系
  2. 足够容量:两个隐藏层提供足够的表达能力
  3. 优化能力:Adam优化器有效调整参数
  4. 迭代训练: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:
  预测线: 几乎与绿色虚线重合

为什么训练有效?

  1. 梯度下降原理:每次更新都向减少损失的方向移动
  2. 链式法则:高效计算所有参数的梯度
  3. 自适应优化器:Adam自动调整学习率
  4. 非线性能力:ReLU激活函数使网络能拟合复杂模式
  5. 迭代优化:多次重复使模型逐步接近最优解

训练结束后的模型状态

  • 权重和偏置已优化到最佳值
  • 网络学习到了潜在规律 y = 2x + 1
  • 能够准确预测新数据点
  • 损失值稳定在最低点(约1.97)

这个训练过程展示了神经网络如何从随机初始状态开始,通过反复的预测、评估和调整,最终学习到数据背后的规律。即使数据中存在噪声,神经网络也能识别出真实的线性关系。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值