#P4486. 反向传播(两层全连接网络)
-
1000ms
Tried: 6
Accepted: 3
Difficulty: 5
-
算法标签>机器学习算法
反向传播(两层全连接网络)
这题要你手写一个 2-3-1 的前馈神经网络,并对单个样本做一次完整的:
- 前向传播
- 计算二分类交叉熵损失(BCE)
- 反向传播求梯度
- 用梯度下降更新参数
网络结构:
-
输入层:2 维向量 x=[x1,x2]
-
隐藏层:3 个神经元,激活为 ReLU
$$z_1 = W_1 x + b_1 \in \mathbb{R}^3,\quad h = \text{ReLU}(z_1)$$ -
输出层:1 个神经元,激活为 Sigmoid
$$z_2 = W_2^\top h + b_2,\quad \hat{y} = \sigma(z_2)$$
损失函数(二分类交叉熵):
$$\text{loss} = -\left[y \log \hat{y} + (1 - y)\log(1 - \hat{y})\right]$$注意:只有一个样本,所以梯度不需要再除以 batch 大小。
反向传播推导
我们从输出往前推:
-
BCE + Sigmoid 的链式法则
对于单样本,有一个经典结论:
$$\frac{\partial \text{loss}}{\partial z_2} = \hat{y} - y$$代码里记为:
dL_dz2 = y_hat - y -
输出层参数的梯度
z2=j=1∑3W2jhj+b2因此:
$$\frac{\partial \text{loss}}{\partial W_{2j}} = \frac{\partial \text{loss}}{\partial z_2}\cdot \frac{\partial z_2}{\partial W_{2j}} = dL\_dz2 \cdot h_j$$$$\frac{\partial \text{loss}}{\partial b_2} = dL\_dz2$$ -
传回到隐藏层输出 h
$$\frac{\partial \text{loss}}{\partial h_j} = \frac{\partial \text{loss}}{\partial z_2}\cdot \frac{\partial z_2}{\partial h_j} = dL\_dz2 \cdot W_{2j}$$ -
ReLU 的反向传播
hj=max(0,z1j)导数为:
$$\frac{\partial h_j}{\partial z_{1j}} = \begin{cases} 1, & z_{1j} > 0 \\ 0, & z_{1j} \le 0 \end{cases}$$所以:
$$\frac{\partial \text{loss}}{\partial z_{1j}} = \begin{cases} dL\_dh_j, & z_{1j} > 0 \\ 0, & z_{1j} \le 0 \end{cases}$$ -
隐藏层参数 W1,b1 的梯度
z1j=W1,0jx1+W1,1jx2+b1j因此:
$$\frac{\partial \text{loss}}{\partial W_{1,ij}} = x_i \cdot \frac{\partial \text{loss}}{\partial z_{1j}}$$$$\frac{\partial \text{loss}}{\partial b_{1j}} = \frac{\partial \text{loss}}{\partial z_{1j}}$$ -
梯度下降更新
按题意,使用学习率
lr,一次更新:W1_new = W1 - lr * dW1 b1_new = b1 - lr * db1 W2_new = W2 - lr * dW2 b2_new = b2 - lr * db2
Python 实现
import math
from typing import List, Tuple
def sigmoid(z: float) -> float:
"""数值稳定版 sigmoid,避免 exp 溢出。"""
if z >= 0:
ez = math.exp(-z)
return 1.0 / (1.0 + ez)
else:
ez = math.exp(z)
return ez / (1.0 + ez)
class Solution:
def twoLayerNN(
self,
lr: float,
W1: List[List[float]], # 2x3
b1: List[float], # 长度 3
W2: List[float], # 长度 3
b2: float, # 标量
x: List[float], # 长度 2
y: int # 0 或 1
) -> Tuple[
float, # loss
List[List[float]], # 更新后的 W1(2x3)
List[float], # 更新后的 b1(3)
List[float], # 更新后的 W2(3)
float # 更新后的 b2
]:
# ---------- 1. Forward 前向传播 ----------
# 1) 隐藏层线性变换 z1 = W1 x + b1
z1 = []
for j in range(3):
z = x[0] * W1[0][j] + x[1] * W1[1][j] + b1[j]
z1.append(z)
# 2) ReLU 激活 h = ReLU(z1)
h = [max(0.0, z) for z in z1]
# 3) 输出层线性 z2 = W2^T h + b2
z2 = 0.0
for j in range(3):
z2 += W2[j] * h[j]
z2 += b2
# 4) Sigmoid 输出 y_hat
y_hat = sigmoid(z2)
# 5) BCE 损失(clip 避免 log(0))
eps = 1e-12
y_hat_clipped = min(max(y_hat, eps), 1.0 - eps)
loss = -(y * math.log(y_hat_clipped) + (1 - y) * math.log(1 - y_hat_clipped))
# ---------- 2. Backward 反向传播 ----------
# 输出层:dL/dz2 = y_hat - y(BCE + Sigmoid 合并后的结果)
dL_dz2 = y_hat - y
# 输出层参数梯度
# dL/dW2_j = dL/dz2 * h_j
dW2 = [dL_dz2 * h_j for h_j in h]
# dL/db2 = dL/dz2
db2 = dL_dz2
# 传回隐藏层:dL/dh_j = dL/dz2 * W2_j
dL_dh = [dL_dz2 * W2_j for W2_j in W2]
# ReLU 反向:z1_j <= 0 时梯度为 0
dL_dz1 = []
for j in range(3):
if z1[j] > 0:
dL_dz1.append(dL_dh[j])
else:
dL_dz1.append(0.0)
# 隐藏层参数梯度
# dL/dW1[i][j] = x_i * dL/dz1_j
dW1 = [[0.0] * 3 for _ in range(2)]
for i in range(2):
for j in range(3):
dW1[i][j] = x[i] * dL_dz1[j]
# dL/db1_j = dL/dz1_j
db1 = dL_dz1[:] # 复制一份
# ---------- 3. 参数更新(Gradient Descent) ----------
# 更新 W1
W1_new = [[0.0] * 3 for _ in range(2)]
for i in range(2):
for j in range(3):
W1_new[i][j] = W1[i][j] - lr * dW1[i][j]
# 更新 b1
b1_new = [0.0] * 3
for j in range(3):
b1_new[j] = b1[j] - lr * db1[j]
# 更新 W2
W2_new = [0.0] * 3
for j in range(3):
W2_new[j] = W2[j] - lr * dW2[j]
# 更新 b2
b2_new = b2 - lr * db2
# ---------- 4. 返回 loss 和更新后的参数 ----------
return loss, W1_new, b1_new, W2_new, b2_new
题目描述
实现一个两层全连接神经网络,网络结构为:
- 输入层:2 维
- 隐藏层:3 个神经元,激活函数为 ReLU
- 输出层:1 个神经元,激活函数为 Sigmoid
给定网络参数 W1, b1, W2, b2,单个输入样本 x = [x1, x2] 和标签 y(0 或 1),以及学习率 lr,对这个样本执行:
- 一次前向传播(Forward)
- 计算二分类交叉熵损失(Binary Cross Entropy)
- 一次反向传播(Backward)
- 使用梯度下降更新参数
需要返回 loss 和更新后的参数。
前向传播公式
设输入为 x∈R2:
-
隐藏层线性变换: z1=W1x+b1(维度 3)
-
ReLU 激活: h=ReLU(z1)=max(0,z1)
-
输出层线性变换: z2=W2⊤h+b2(标量)
-
Sigmoid 输出: y^=σ(z2)=1+e−z21
损失函数(BCE)
$\text{loss} = -\big[y \log \hat{y} + (1 - y)\log(1 - \hat{y})\big]$
反向传播(单样本)
你需要对以上计算过程进行反向传播,求出对所有参数的梯度,并使用学习率 lr 做一次梯度下降更新:
- $W_1 \leftarrow W_1 - lr \cdot \frac{\partial \text{loss}}{\partial W_1}$
- $b_1 \leftarrow b_1 - lr \cdot \frac{\partial \text{loss}}{\partial b_1}$
- $W_2 \leftarrow W_2 - lr \cdot \frac{\partial \text{loss}}{\partial W_2}$
- $b_2 \leftarrow b_2 - lr \cdot \frac{\partial \text{loss}}{\partial b_2}$
输入参数
lr:学习率W1:形状为 2×3 的隐藏层权重矩阵b1:长度为 3 的隐藏层偏置向量W2:长度为 3 的输出层权重向量b2:输出层偏置标量x:长度为 2 的输入向量y:标签,y∈0,1
返回值
loss:当前样本的二分类交叉熵损失W1'、b1'、W2'、b2':一次梯度更新后的参数
示例 1
输入:
lr = 0.1
W1 = [[0.1, -0.5, 0.3],[0.4, 0.5, -0.6]]
b1 = [0.0, 0.1, 0.0]
W2 = [0.2, -0.1, 0.4]
b2 = 0.0
x = [1.0, 2.0]
y = 1
输出:
loss = 0.63
W1' = [[0.11, -0.5, 0.3], [0.42, 0.49, -0.6]]
b1' = [0.01, 0.1, 0.0]
W2' = [0.24, -0.07, 0.4]
b2' = 0.05
提示
-
输入和参数均为浮点数,使用双精度计算
-
维度约束:
W1为 2×3b1长度为 3W2长度为 3b2为标量x为长度 2
-
学习率范围: 0<lr≤1
-
输入向量范围: −1000≤x[i]≤1000
-
权重与偏置范围: −10≤W1[i][j]≤10
−10≤b1[j]≤10
−10≤W2[j]≤10
−10≤b2≤10