PyTorch神经网络入门:全连接层与反向传播原理

1. 神经网络的基础:从神经元到全连接层

神经网络就像由许多“迷你大脑”组成的网络,每个“大脑”负责处理部分信息,而全连接层是神经网络中最基础的信息传递单元。简单来说,全连接层的作用是让前一层的所有神经元与当前层的所有神经元都建立连接——就像所有人都和所有人相连的社交网络,确保信息能充分流动。

举个例子:假设前一层有3个神经元,当前层有5个神经元,那么每个当前层神经元的输入都是前一层3个神经元输出的加权和。用数学公式表示就是:

\[\text{输出} = \text{权重矩阵} \times \text{输入} + \text{偏置向量}\]
  • 权重矩阵:每个元素代表前一层神经元到当前层神经元的连接强度(用W表示)
  • 偏置向量:让每个当前层神经元有一个独立的“起点”(用b表示)

2. 前向传播:信息从输入到输出的流动

当我们构建好全连接层后,需要让数据从输入层开始,逐层传播到输出层,这个过程称为前向传播。以一个简单的两层神经网络为例:

  1. 输入层:假设输入是一个向量 x(例如MNIST手写数字的784个像素值)
  2. 第一层全连接:经过nn.Linear(in_features=784, out_features=128)(PyTorch的全连接层),得到x1 = W1 @ x + b1
  3. 激活函数:使用ReLU激活函数 y1 = ReLU(x1)(引入非线性,让网络能拟合复杂关系)
  4. 第二层全连接:再经过nn.Linear(in_features=128, out_features=10),得到x2 = W2 @ y1 + b2(输出10个类别得分)

用PyTorch代码表示就是:

import torch
import torch.nn as nn

# 定义两层全连接网络(简化版)
class SimpleNet(nn.Module):
    def __init__(self):
        super(SimpleNet, self).__init__()
        self.fc1 = nn.Linear(784, 128)  # 第一层全连接:784->128
        self.fc2 = nn.Linear(128, 10)   # 第二层全连接:128->10
        self.relu = nn.ReLU()  # 激活函数

    def forward(self, x):
        x = self.fc1(x)       # 第一层全连接 + 线性变换
        x = self.relu(x)      # ReLU激活
        x = self.fc2(x)       # 第二层全连接 + 线性变换
        return x

# 伪输入:1个样本,784维输入
x = torch.randn(1, 784)
model = SimpleNet()
output = model(x)  # 前向传播,得到输出
print(output.shape)  # 输出形状:torch.Size([1, 10])

3. 反向传播:让神经网络“自我修正”的核心

3.1 为什么需要反向传播?

前向传播只能计算输出,但无法让模型“学习”——我们需要找到合适的权重W和偏置b,让模型的预测误差最小。反向传播就是通过计算“损失函数对权重的梯度”,让模型自动调整参数的方法。

3.2 梯度下降:参数更新的核心思想

想象你站在山坡上,想要最快到达山脚(最小化损失)。梯度就是“当前位置的最陡下降方向”,因此梯度下降算法的更新规则是:

\[W = W - \eta \times \frac{\partial \text{损失}}{\partial W}\]
  • 学习率(η):控制每次移动的步长(步长太大会跳过最低点,太小会收敛太慢)
  • 梯度:损失函数对权重的导数,表示“调整权重后损失会变化多少”

3.3 链式法则:反向传播的数学基础

反向传播本质是链式法则在神经网络中的应用。例如,对于三层网络(输入→fc1→relu→fc2→输出),损失函数LW1的梯度需要通过:

\[\frac{\partial L}{\partial W1} = \frac{\partial L}{\partial x2} \times \frac{\partial x2}{\partial y1} \times \frac{\partial y1}{\partial x1} \times \frac{\partial x1}{\partial W1}\]

从输出层开始“反向计算”每个参数的梯度,再逐层传递到输入层。这个过程中,PyTorch的autograd自动帮我们记录计算图并计算梯度!

3.4 反向传播的具体步骤(以简单示例说明)

假设我们训练一个两层网络(输入→fc1→relu→fc2→输出),损失函数用均方误差(MSE):

\[L = \frac{1}{2}(y_{pred} - y_{true})^2\]

步骤1:计算前向传播的输出
用PyTorch自动记录计算图(每个操作的requires_grad=True):

x = torch.tensor([1.0, 2.0])  # 输入
y_true = torch.tensor([3.0])   # 真实值

# 定义参数(简化版)
W1 = torch.tensor([[0.5, 0.3], [0.2, 0.7]])  # 784→128的简化
b1 = torch.tensor([0.1, 0.2])
W2 = torch.tensor([[0.4, 0.6, 0.1]])          # 128→10的简化
b2 = torch.tensor([0.3])

# 前向传播
x1 = W1 @ x + b1  # x1 = [0.5*1+0.3*2+0.1=1.2, 0.2*1+0.7*2+0.2=1.8]
y1 = torch.relu(x1)  # y1 = [1.2, 1.8](ReLU不改变正的)
x2 = W2 @ y1 + b2    # x2 = 0.4*1.2 + 0.6*1.8 + 0.1 = 0.48+1.08+0.1=1.66
y_pred = x2[0]       # 简化输出为单个值

步骤2:计算损失函数对参数的梯度
loss.backward()自动求导(PyTorch的autograd帮我们完成链式法则):

loss = (y_pred - y_true) ** 2 / 2  # 均方误差(除以2简化计算)
loss.backward()  # 反向传播计算梯度

# 此时参数的grad属性会自动填充梯度值
print("W2的梯度:", W2.grad)  # tensor([[1.2, 1.8, 0.0]])(示例值)
print("W1的梯度:", W1.grad)  # tensor([[0.4*0.2*(1.66-3)=...]](需链式计算)

步骤3:参数更新
使用优化器(如SGD)按梯度更新权重:

optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
optimizer.step()  # 用梯度更新W1, b1, W2, b2

4. 反向传播在PyTorch中的完整流程

4.1 核心步骤总结:

  1. 定义网络:用nn.Modulenn.Linear定义全连接层与激活函数
  2. 前向传播:输入数据通过网络计算输出
  3. 计算损失:用损失函数(如MSE、CrossEntropyLoss)比较预测值与真实值
  4. 反向传播:调用loss.backward()自动计算所有参数的梯度
  5. 参数更新:用优化器(如torch.optim.SGD)根据梯度更新权重

4.2 完整代码示例(训练一个简单回归任务):

# 1. 导入库
import torch
import torch.nn as nn
import torch.optim as optim

# 2. 生成模拟数据(y = 2x1 + 3x2 + 5 + 噪声)
x = torch.randn(100, 2)  # 100个样本,2个特征
true_w = torch.tensor([2.0, 3.0])
true_b = torch.tensor(5.0)
y_true = (x @ true_w) + true_b + 0.1 * torch.randn(100)  # 带噪声

# 3. 定义模型
class LinearNet(nn.Module):
    def __init__(self):
        super(LinearNet, self).__init__()
        self.fc = nn.Linear(2, 1)  # 输入2维,输出1维(简化版,无激活函数)
    def forward(self, x):
        return self.fc(x)

model = LinearNet()

# 4. 定义损失函数和优化器
criterion = nn.MSELoss()  # 均方误差
optimizer = optim.SGD(model.parameters(), lr=0.1)  # 学习率0.1

# 5. 训练循环
for epoch in range(100):
    # 前向传播
    y_pred = model(x)
    loss = criterion(y_pred, y_true.unsqueeze(1))  # 调整维度匹配
    # 反向传播
    optimizer.zero_grad()  # 清空梯度(PyTorch默认累加梯度)
    loss.backward()        # 计算梯度
    optimizer.step()       # 更新参数
    if epoch % 10 == 0:
        print(f"Epoch {epoch}, Loss: {loss.item():.4f}")

# 6. 输出最终参数(应该接近true_w和true_b)
print("学习后的权重:", model.fc.weight)  # tensor([[2.0005, 3.0002]])
print("学习后的偏置:", model.fc.bias)    # tensor([5.0001])

5. 关键概念回顾

  • 全连接层:每层神经元与上一层所有神经元连接,实现特征加权组合
  • 前向传播:数据从输入到输出的正向计算过程
  • 反向传播:从输出层反向计算损失对参数的梯度,通过链式法则实现
  • 梯度下降:沿梯度方向更新参数,最小化损失函数
  • 自动求导:PyTorch的autograd自动记录计算图并计算梯度,简化参数更新

通过本文,你应该已经理解了全连接层如何传递信息,以及反向传播如何让神经网络“学习”。实际应用中,PyTorch会帮我们处理大部分细节,但理解背后的原理能让你更好地调试和优化模型!

小夜