矩阵求导基础
手动计算
问题的背景:预测一套房产的价格(一个连续数值)
输入:面积 (0.5), 房龄 (0.8) 输出:现在房子的价格
为了把问题简单化,这里的话我们使用了2×3×1 网络结构(2 输入 → 3 隐藏 → 1 输出),保持
无偏置项的简化原则。
网络的结构如下:
前向传播
在这个网络结构中,根据给定的房产面积和房龄,我们预测的房价是 0.260(这个值是归一化后
的结果)。
计算损失函数
现在我们有了前向传播的结果,我们可以用它来演示反向传播了。
我们假设这套房产的真实价格 (Y) 是 0.4。
我们将使用均方误差(MSE)损失函数,这在回归问题中是标准做法:
反向传播和更新参数
代码实现
一个样本的神经网络的实现
在 PyTorch 和大多数现代深度学习框架中,惯例是使用 行向量(Row Vector)作为单个样本
的输入:手动计算时习惯用 X 形式 (列向量),但 PyTorch 采用 X 形式 (行向量) 来处理输
入。
import torch import torch.nn as nn import torch.optim as optim import numpy as np # 1. 定义网络结构 (2 -> 3 -> 1) class SimpleNet(nn.Module): def __init__(self, W1_data, W2_data): super(SimpleNet, self).__init__() # 隐藏层 (Input=2, Output=3) self.fc1 = nn.Linear(2, 3, bias=False) # 设置 bias=False 对应“无偏置项” # 输出层 (Input=3, Output=1) self.fc2 = nn.Linear(3, 1, bias=False) # 设置 bias=False # 将我们手算的初始权重加载到网络中 # 注意 PyTorch 的 nn.Linear 默认使用 W^T 形式,即 W 是 (out_features, in_features) with torch.no_grad(): self.fc1.weight.data = W1_data # W1 是 3x2 self.fc2.weight.data = W2_data # W2 是 1x3 def forward(self, x): # 隐藏层:Z1 = W1^T * X -> A1 = ReLU(Z1) # 注意:PyTorch 内部处理了 W^T * X,我们只需传入 W 即可 Z1 = self.fc1(x) A1 = nn.functional.relu(Z1) # 输出层:Z2 = W2^T * A1 -> A2 = Z2 (恒等激活) Z2 = self.fc2(A1) A2 = Z2 # A2 = Z2 for identity activation (regression) return Z2, A1, Z2 # 返回 Z2, A1, A2 方便检查中间值 # 2. 定义输入和参数 # 注意 PyTorch 默认是 FloatTensor,且输入需要是 [Batch_Size, Features] # X (1x2 矩阵): [面积, 房龄] X_tensor = torch.tensor([[0.5, 0.8]], dtype=torch.float32) # W1 (3x2 矩阵): 对应 PyTorch 的 (out_features, in_features) W1_init = torch.tensor([ [0.1, 0.2], # W1^T 的第一行 (Z1,1) [0.3, 0.4], # W1^T 的第二行 (Z1,2) [0.5, 0.6] # W1^T 的第三行 (Z1,3) ], dtype=torch.float32) # W2 (1x3 矩阵): 对应 PyTorch 的 (out_features, in_features) W2_init = torch.tensor([ [-0.9, 0.8, 0.1] ], dtype=torch.float32) # 真实标签 Y Y_tensor = torch.tensor([[0.4]], dtype=torch.float32) # 学习率 learning_rate = 0.1 # 3. 初始化模型、损失函数和优化器 model = SimpleNet(W1_init, W2_init) # MSE Loss (PyTorch 默认是 0.5 * (A2 - Y)^2) criterion = nn.MSELoss() optimizer = optim.SGD(model.parameters(), lr=learning_rate) print("--- 开始前向传播验证 ---") # 清空之前的梯度 optimizer.zero_grad() # 4. 前向传播 (Forward Propagation) Z2_out, A1_out, A2_pred = model(X_tensor) # 验证中间结果 print(f"X:\n{X_tensor.numpy()}") print(f"\nW1 (3x2):\n{model.fc1.weight.data.numpy()}") print(f"\n隐藏层激活输出 A1 (手算: 0.21, 0.47, 0.73):\n{A1_out.detach().numpy()}") print(f"\n最终预测 A2 (手算: 0.260):\n{A2_pred.detach().numpy()}") # 5. 计算损失 (Loss) loss = criterion(A2_pred, Y_tensor) print(f"\n真实标签 Y: {Y_tensor.numpy()[0, 0]}") print(f"损失 L (0.5 * (0.26 - 0.4)^2): {loss.item():.5f}") # 6. 反向传播 (Backward Propagation) loss.backward() # 7. 检查梯度 (Verification of Gradients) print("\n--- 反向传播梯度验证 (手动计算的梯度值) ---") # 检查 dL/dW2 (W2 梯度) # 手算: dL/dW2 = [-0.0294, -0.0658, -0.1022] (转置后) dL_dW2 = model.fc2.weight.grad print(f"dL/dW2 (W2 梯度):\n{dL_dW2.numpy()}") # 检查 dL/dW1 (W1 梯度) # 手算: dL/dW1 = [[0.0630, -0.0560, -0.0070], [0.1008, -0.0896, -0.0112]] dL_dW1 = model.fc1.weight.grad print(f"dL/dW1 (W1 梯度):\n{dL_dW1.numpy()}") # 8. 权重更新 (Gradient Descent) optimizer.step() # 9. 检查更新后的权重 (Verification of New Weights) print("\n--- 权重更新验证 ---") # W2_new = W2 - alpha * dL/dW2 W2_new = model.fc2.weight.data # 手算 W2_new: [-0.8971, 0.8066, 0.1102] print(f"W2 新权重:\n{W2_new.numpy()}") # W1_new = W1 - alpha * dL/dW1 W1_new = model.fc1.weight.data # 手算 W1_new: [[0.0937, 0.3056, 0.5007], [0.1899, 0.4090, 0.6011]] print(f"W1 新权重:\n{W1_new.numpy()}")
逐行解析
初始化
计算第一层
计算第二层和损失函数
多个样本的神经网络的实现
现在我们将会使用的 2×3×1 回归网络来构造一个小型数据集,并定义一个简单的训练循环,展
示网络如何通过多轮迭代(Epochs)来不断学习和减少误差。
们构造四个数据点,代表四种不同房产的面积、房龄和实际价格(均已归一化到 0 到 1 之间)。
| 编号 | 面积 (X1) | 房龄 (X2) | 实际价格 (Y) | 描述 |
| D1 | 0.5 | 0.8 | 0.40 | (我们刚才手算的数据) |
| D2 | 0.9 | 0.2 | 0.85 | 新房,面积大,价格高 |
| D3 | 0.2 | 0.9 | 0.15 | 旧房,面积小,价格低 |
| D4 | 0.6 | 0.4 | 0.50 | 中等房,价格中等 |
import torch import torch.nn as nn import torch.optim as optim import numpy as np # --- 1. 定义网络模型 --- class SimpleNet(nn.Module): def __init__(self, W1_data, W2_data): super(SimpleNet, self).__init__() # 隐藏层 (Input=2, Output=3), 无偏置项 self.fc1 = nn.Linear(2, 3, bias=False) # 输出层 (Input=3, Output=1), 无偏置项 self.fc2 = nn.Linear(3, 1, bias=False) # 加载手动计算的初始权重 with torch.no_grad(): # fc1.weight 维度是 (out_features, in_features) 即 (3, 2) self.fc1.weight.data = W1_data # fc2.weight 维度是 (out_features, in_features) 即 (1, 3) self.fc2.weight.data = W2_data def forward(self, x): # 隐藏层:ReLU 激活 Z1 = self.fc1(x) A1 = nn.functional.relu(Z1) # 输出层:恒等激活 (回归) A2 = self.fc2(A1) return A2 # --- 2. 数据集与初始参数 --- # 输入特征 X (4x2 矩阵: 4个样本, 2个特征 [面积, 房龄]) X_data = torch.tensor([ [0.5, 0.8], # D1 (手算起始点) [0.9, 0.2], # D2 [0.2, 0.9], # D3 [0.6, 0.4] # D4 ], dtype=torch.float32) # 真实标签 Y (4x1 矩阵: 实际价格) Y_data = torch.tensor([ [0.40], [0.85], [0.15], [0.50] ], dtype=torch.float32) # 初始权重 (与手算时使用的 W1^T 和 W2^T 维度保持一致) W1_init = torch.tensor([[0.1, 0.2], [0.3, 0.4], [0.5, 0.6]], dtype=torch.float32) W2_init = torch.tensor([[-0.9, 0.8, 0.1]], dtype=torch.float32) learning_rate = 0.1 EPOCHS = 50 # --- 3. 初始化模型、损失函数和优化器 --- model = SimpleNet(W1_init, W2_init) criterion = nn.MSELoss() optimizer = optim.SGD(model.parameters(), lr=learning_rate) # --- 4. 训练循环 --- print("--- 神经网络开始学习 (50 轮训练) ---") print(f"初始损失: {criterion(model(X_data), Y_data).item():.6f}") for epoch in range(EPOCHS): # 零化梯度 optimizer.zero_grad() # 前向传播 predictions = model(X_data) # 计算损失 loss = criterion(predictions, Y_data) # 反向传播 loss.backward() # 权重更新 (梯度下降) optimizer.step() if (epoch + 1) % 10 == 0: print(f"Epoch {epoch + 1:2d}/{EPOCHS} | Loss: {loss.item():.6f}") print("--- 训练结束,网络已学习 ---") # --- 5. 最终结果展示 --- final_predictions = model(X_data) print("\n--- 最终预测与权重对比 ---") print("样本数据 (面积/房龄):", X_data.numpy().round(2).T) print("实际价格 Y: ", Y_data.squeeze().numpy().round(4)) print("最终预测 A2: ", final_predictions.squeeze().detach().numpy().round(4)) print("\n最终 W1 (2x3) 权重:\n", model.fc1.weight.data.numpy().round(4)) print("\n最终 W2 (1x3) 权重:\n", model.fc2.weight.data.numpy().round(4))
初始化
第一层的计算
第二层的计算
import torch import torch.nn as nn import torch.optim as optim import matplotlib.pyplot as plt # 导入绘图库 import os os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE" # --- 1. 定义网络模型 --- class SimpleNet(nn.Module): def __init__(self, W1_data, W2_data): super(SimpleNet, self).__init__() self.fc1 = nn.Linear(2, 3, bias=False) self.fc2 = nn.Linear(3, 1, bias=False) with torch.no_grad(): self.fc1.weight.data = W1_data self.fc2.weight.data = W2_data def forward(self, x): Z1 = self.fc1(x) A1 = nn.functional.relu(Z1) A2 = self.fc2(A1) return A2 # --- 2. 数据集与初始参数 --- X_data = torch.tensor([ [0.5, 0.8], [0.9, 0.2], [0.2, 0.9], [0.6, 0.4] ], dtype=torch.float32) Y_data = torch.tensor([ [0.40], [0.85], [0.15], [0.50] ], dtype=torch.float32) W1_init = torch.tensor([[0.1, 0.2], [0.3, 0.4], [0.5, 0.6]], dtype=torch.float32) W2_init = torch.tensor([[-0.9, 0.8, 0.1]], dtype=torch.float32) learning_rate = 0.1 EPOCHS = 50 # --- 3. 初始化模型、损失函数和优化器 --- model = SimpleNet(W1_init, W2_init) criterion = nn.MSELoss() optimizer = optim.SGD(model.parameters(), lr=learning_rate) # 新增:用于存储每一步的损失值 loss_history = [] # --- 4. 训练循环 --- print("--- 神经网络开始学习 ---") initial_loss = criterion(model(X_data), Y_data).item() print(f"初始损失: {initial_loss:.6f}") loss_history.append(initial_loss) for epoch in range(EPOCHS): optimizer.zero_grad() predictions = model(X_data) loss = criterion(predictions, Y_data) # 记录当前损失 loss_history.append(loss.item()) loss.backward() optimizer.step() if (epoch + 1) % 10 == 0: print(f"Epoch {epoch + 1:2d}/{EPOCHS} | Loss: {loss.item():.6f}") print("--- 训练结束 ---") # --- 5. 损失曲线可视化 --- plt.figure(figsize=(10, 6)) # 注意:我们记录了 1个初始损失 + 50个训练损失,共 51 个点 plt.plot(range(len(loss_history)), loss_history, label='Training Loss', marker='o', markersize=4, linestyle='-') plt.title('Loss Curve during Training (MSE)') plt.xlabel('Epoch (0 is Initial Loss)') plt.ylabel('Mean Squared Error (MSE)') plt.grid(True) plt.legend() plt.show() # --- 6. 最终结果展示 (略) --- final_predictions = model(X_data) print("\n--- 最终预测与权重对比 ---") print("实际价格 Y: ", Y_data.squeeze().numpy().round(4)) print("最终预测 A2: ", final_predictions.squeeze().detach().numpy().round(4))
挖空版本
由于AI的出现,详细很多的小伙伴和我一样已经很久没有自己写过代码,或者是项目了,但是我
个人还是觉得,写代码是计算机专业的基本的内容,下面的内容的目的是为了辅助你自己写一个完
整的多个项目神经网络的实现。我会把上面的项目分解成的四个模块的挖空版本,重点突出需要你
自己动手完成的关键步骤和数值。
一般简单任务的框架:
编辑
第一个任务:导入我们所需要的包
import (基础张量运算) import (神经网络模块) import (优化器) imoirt (可视化模块)
第二个任务:定义网络框架
# --- 1. 定义网络模型 (无需修改) --- class SimpleNet(nn.Module): def __init__(self, W1_data, W2_data): super(SimpleNet, self).__init__() # 隐藏层:2输入, 3输出, 无偏置 self.fc1 = # 输出层:3输入, 1输出, 无偏置 self.fc2 = with torch.no_grad(): self.fc1.weight.data = W1_data self.fc2.weight.data = W2_data def forward(self, x): Z1 = A1 = # ReLU 激活 A2 = # 恒等激活 (Z2) return A2
第3个任务:数据准备与初始化 (Data & Initialization)
这部分定义了训练数据、初始权重和超参数。
# 真实标签 Y (4x1 矩阵) Y_data = torch.tensor([ [0.40], [0.85], [0.15], [0.50] ], dtype=torch.float32) # W1 (3x2): 初始化权重矩阵 W1 W1_init = torch.tensor([ [0.1, 0.2], [0.3, 0.4], [____, ____] # 请补齐 (0.5, 0.6) ], dtype=torch.float32) # W2 (1x3): 初始化权重矩阵 W2 W2_init = torch.tensor([[-0.9, 0.8, 0.1]], dtype=torch.float32) # 超参数 learning_rate = #把学习率设置成0.1 EPOCHS = #把迭代次数设置成50次 # --- 3. 初始化模型、损失函数和优化器 (请填充) --- model = #初始化模型 criterion = nn.____() # 请选择损失函数 (MSE) optimizer = optim.____(model.parameters(), lr=learning_rate) # 请选择优化器 (SGD) # 用于存储每一步的损失值 loss_history = []
第4个任务:核心训练循环 (Training Loop)
这部分是反向传播和梯度下降逻辑实现的地方。
# --- 4. 训练循环 (请填充反向传播和更新的核心步骤) --- print("--- 神经网络开始学习 ---") initial_loss = criterion(model(X_data), Y_data).item() print(f"初始损失: {initial_loss:.6f}") loss_history.append(initial_loss) for epoch in range(EPOCHS): # 步骤 1: 零化梯度 optimizer.____() # 步骤 2: 前向传播 predictions = model(X_data) # 步骤 3: 计算损失 loss = criterion(predictions, Y_data) # 步骤 4: 反向传播 loss.____() # 步骤 5: 权重更新 (梯度下降) optimizer.____() # 记录损失值 loss_history.append(loss.item()) if (epoch + 1) % 10 == 0: print(f"Epoch {epoch + 1:2d}/{EPOCHS} | Loss: {loss.item():.6f}") print("--- 训练结束 ---")
第5个任务:可视化与结果展示 (Visualization & Results)
这部分用于验证训练效果。
# --- 5. 损失曲线可视化 (请补齐绘图指令) --- plt.figure(figsize=(10, 6)) plt.plot(range(len(loss_history)), loss_history, label='Training Loss', marker='o', markersize=4, linestyle='-') plt.title('Loss Curve during Training (MSE)') plt.xlabel('Epoch (0 is Initial Loss)') plt.ylabel('____') # 请补齐 Y 轴标签 plt.grid(True) plt.legend() plt.____() # 请补齐显示图表的指令 # --- 6. 最终结果展示 (请补齐用于提取数值的关键方法) --- final_predictions = model(X_data) print("\n--- 最终预测与权重对比 ---") # 提取最终预测的数值并格式化 print("最终预测 A2: ", final_predictions.squeeze().____().numpy().round(4)) print("\n最终 W1 (3x2) 权重:\n", model.fc1.weight.data.numpy().round(4)) print("\n最终 W2 (1x3) 权重:\n", model.fc2.weight.data.numpy().round(4))