目录
前言
一、神经网络结构
二、手写自动微分模块回顾
三、实现前向传播
四、实现 ReLU 激活
五、组合网络
六、训练网络
七、验证梯度正确性
八、总结
在前几篇文章中,我们讲解了:
自动微分原理
Softmax + 交叉熵的前向与反向传播
计算图的概念与梯度计算方法
有了这些基础知识,我们已经能够手写一个自动微分模块。
接下来,我们要做的就是:
将自动微分模块应用到神经网络,实现从前向传播到反向传播的完整训练流程
本文将带你实现一个简单的全连接神经网络,利用手写的自动微分训练模型。
一、神经网络结构
为了演示,我们使用一个两层全连接神经网络:
输入层 (D) -> 隐藏层 (H, ReLU) -> 输出层 (C, Softmax)输入维度 D = 4
隐藏层 H = 5
输出类别 C = 3
网络可用公式描述:
[
\begin{aligned}
h &= \text{ReLU}(X W_1 + b_1) \
\hat{y} &= \text{Softmax}(h W_2 + b_2) \
L &= \text{CrossEntropy}(\hat{y}, y)
\end{aligned}
]
二、手写自动微分模块回顾
我们之前实现了Softmax + CrossEntropy的自动微分:
import numpy as np class SoftmaxCrossEntropy: def __init__(self): self.y_pred = None self.y_true = None def forward(self, logits, y_true): self.y_true = y_true logits = logits - np.max(logits, axis=1, keepdims=True) exp_logits = np.exp(logits) self.y_pred = exp_logits / np.sum(exp_logits, axis=1, keepdims=True) epsilon = 1e-12 loss = -np.sum(y_true * np.log(self.y_pred + epsilon), axis=1, keepdims=True) return loss def backward(self): batch_size = self.y_true.shape[0] grad = (self.y_pred - self.y_true) / batch_size return grad核心思想:
前向传播:计算 Softmax 输出和交叉熵损失
反向传播:梯度 = Softmax输出 - one-hot标签
三、实现前向传播
我们需要一个简单的全连接层(Linear Layer),支持前向传播与反向传播。
class Linear: def __init__(self, input_dim, output_dim): self.W = np.random.randn(input_dim, output_dim) * 0.01 self.b = np.zeros((1, output_dim)) # 前向缓存 self.x = None def forward(self, x): self.x = x return x @ self.W + self.b def backward(self, grad_output): # grad_output: dL/dy self.dW = self.x.T @ grad_output self.db = np.sum(grad_output, axis=0, keepdims=True) grad_input = grad_output @ self.W.T return grad_input四、实现 ReLU 激活
class ReLU: def __init__(self): self.mask = None def forward(self, x): self.mask = (x > 0).astype(float) return x * self.mask def backward(self, grad_output): return grad_output * self.mask五、组合网络
定义两层网络:
class SimpleNN: def __init__(self, input_dim, hidden_dim, output_dim): self.fc1 = Linear(input_dim, hidden_dim) self.relu = ReLU() self.fc2 = Linear(hidden_dim, output_dim) self.loss_fn = SoftmaxCrossEntropy() def forward(self, x, y_true): out = self.fc1.forward(x) out = self.relu.forward(out) logits = self.fc2.forward(out) loss = self.loss_fn.forward(logits, y_true) return loss, logits def backward(self): grad_logits = self.loss_fn.backward() grad_hidden = self.fc2.backward(grad_logits) grad_hidden = self.relu.backward(grad_hidden) _ = self.fc1.backward(grad_hidden)六、训练网络
定义小批量数据:
# 输入: 4维特征, 输出: 3类 X = np.array([[0.2, 0.5, 0.1, 0.4], [0.9, 0.1, 0.7, 0.3], [0.3, 0.8, 0.5, 0.2]]) y_true = np.array([[1,0,0], [0,1,0], [0,0,1]])训练步骤:
nn = SimpleNN(input_dim=4, hidden_dim=5, output_dim=3) lr = 0.1 epochs = 100 for epoch in range(epochs): # 前向传播 loss, logits = nn.forward(X, y_true) loss_value = np.mean(loss) # 反向传播 nn.backward() # 更新参数 for layer in [nn.fc1, nn.fc2]: layer.W -= lr * layer.dW layer.b -= lr * layer.db if (epoch + 1) % 10 == 0: print(f"Epoch {epoch+1}, Loss: {loss_value:.4f}")输出示例:
Epoch 10, Loss: 1.0598 Epoch 20, Loss: 0.9253 Epoch 30, Loss: 0.8032 ... Epoch 100, Loss: 0.1235七、验证梯度正确性
我们可以使用数值梯度检查来验证手写反向传播是否正确。
def numerical_grad(layer, x, y_true, epsilon=1e-5): grad_W = np.zeros_like(layer.W) grad_b = np.zeros_like(layer.b) # 对每个权重做微小扰动 for i in range(layer.W.shape[0]): for j in range(layer.W.shape[1]): original = layer.W[i,j] layer.W[i,j] = original + epsilon loss_plus, _ = nn.forward(X, y_true) loss_plus = np.mean(loss_plus) layer.W[i,j] = original - epsilon loss_minus, _ = nn.forward(X, y_true) loss_minus = np.mean(loss_minus) grad_W[i,j] = (loss_plus - loss_minus) / (2 * epsilon) layer.W[i,j] = original # 偏置同理 for j in range(layer.b.shape[1]): original = layer.b[0,j] layer.b[0,j] = original + epsilon loss_plus, _ = nn.forward(X, y_true) loss_plus = np.mean(loss_plus) layer.b[0,j] = original - epsilon loss_minus, _ = nn.forward(X, y_true) loss_minus = np.mean(loss_minus) grad_b[0,j] = (loss_plus - loss_minus) / (2 * epsilon) layer.b[0,j] = original return grad_W, grad_b # 检查 fc1 梯度 grad_W_num, grad_b_num = numerical_grad(nn.fc1, X, y_true) print("Numerical grad W1:", grad_W_num) print("Backprop grad W1:", nn.fc1.dW)你会发现,手写反向传播与数值梯度高度一致。
八、总结
通过本篇文章,我们完成了以下任务:
将手写的Softmax + CrossEntropy模块应用到神经网络训练
实现前向传播 + 反向传播完整流程
使用全连接层 + ReLU构建简单神经网络
实现参数更新,并进行训练
验证手写梯度的正确性
核心思路:
自动微分模块只需要实现
forward()和backward()每层记录中间结果用于链式法则计算梯度
训练循环:前向 → 损失 → 反向 → 参数更新
通过这种方式,你可以理解 PyTorch / TensorFlow 内部梯度计算的原理,并在没有框架的情况下训练一个神经网络,为深入学习Autograd 源码打下坚实基础。