浅层神经网络的反向传播

1. 反向传播的基本概念

1.1 什么是反向传播

反向传播(Backpropagation)是一种用于计算神经网络中参数梯度的算法。它通过从输出层开始,反向计算损失函数对各层参数的梯度,然后使用梯度下降法更新参数,从而最小化损失函数。

1.2 反向传播的重要性

反向传播是神经网络训练的核心算法,它使得训练深层神经网络成为可能。通过高效计算梯度,反向传播算法大大加速了神经网络的训练过程,为深度学习的发展奠定了基础。

1.3 反向传播与前向传播的关系

  • 前向传播:从输入层到输出层,计算网络的输出和各层的激活值
  • 反向传播:从输出层到输入层,计算损失函数对各层参数的梯度

两者相辅相成,共同构成了神经网络的训练过程。

2. 反向传播的数学原理

2.1 损失函数

在反向传播之前,我们需要定义一个损失函数来衡量网络输出与真实值之间的差距。常见的损失函数包括:

  • 均方误差(MSE):适用于回归问题

    L(y, \hat{y}) = \frac{1}{n} \sum_{i=1}^{n} (y_i - \hat{y}_i)^2
  • 交叉熵损失:适用于分类问题

    L(y, \hat{y}) = -\sum_{i=1}^{n} y_i \log(\hat{y}_i)

2.2 链式法则

反向传播算法的核心是利用链式法则计算梯度。链式法则是微积分中的一个基本法则,用于计算复合函数的导数。

链式法则:如果 z = f(y) 且 y = g(x) ,则 \frac{dz}{dx} = \frac{dz}{dy} \cdot \frac{dy}{dx} 。

2.3 梯度计算

在浅层神经网络中,我们需要计算损失函数对以下参数的梯度:

  • 输入层到隐藏层的权重( W^{(1)} )和偏置( b^{(1)} )
  • 隐藏层到输出层的权重( W^{(2)} )和偏置( b^{(2)} )

2.4 数学推导

2.4.1 符号定义

为了便于推导,我们定义以下符号:

  • L :损失函数
  • W^{(1)} :输入层到隐藏层的权重矩阵
  • b^{(1)} :隐藏层的偏置向量
  • W^{(2)} :隐藏层到输出层的权重矩阵
  • b^{(2)} :输出层的偏置向量
  • Z^{(1)} :隐藏层的加权和向量
  • A^{(1)} :隐藏层的激活值向量
  • Z^{(2)} :输出层的加权和向量
  • A^{(2)} :输出层的激活值向量(网络的输出)
  • Y :真实标签向量
  • f :隐藏层的激活函数
  • g :输出层的激活函数
  • f' :隐藏层激活函数的导数
  • g' :输出层激活函数的导数

2.4.2 输出层梯度计算

首先,计算损失函数对输出层加权和的梯度:

\delta^{(2)} = \nabla_{Z^{(2)}} L = \frac{\partial L}{\partial Z^{(2)}}

对于交叉熵损失和Softmax激活函数,这个梯度可以简化为:

\delta^{(2)} = A^{(2)} - Y

然后,计算损失函数对输出层权重和偏置的梯度:

math\frac{\partial L}{\partial W^{(2)}} = \delta^{(2)} (A^{(1)})^T

math\frac{\partial L}{\partial b^{(2)}} = \delta^{(2)}

2.4.3 隐藏层梯度计算

对于隐藏层,计算损失函数对隐藏层加权和的梯度:

\delta^{(1)} = ((W^{(2)})^T \delta^{(2)}) \odot f'(Z^{(1)})

其中 \odot 表示元素级乘法(哈达玛积)。

然后,计算损失函数对隐藏层权重和偏置的梯度:

math\frac{\partial L}{\partial W^{(1)}} = \delta^{(1)} X^T

math\frac{\partial L}{\partial b^{(1)}} = \delta^{(1)}

3. 反向传播的实现方法

3.1 使用NumPy实现反向传播

3.1.1 基本实现

import numpy as np

# 定义激活函数及其导数
def sigmoid(x):
    return 1 / (1 + np.exp(-x))

def sigmoid_derivative(x):
    return sigmoid(x) * (1 - sigmoid(x))

def relu(x):
    return np.maximum(0, x)

def relu_derivative(x):
    return np.where(x > 0, 1, 0)

def softmax(x):
    exp_x = np.exp(x - np.max(x, axis=0, keepdims=True))
    return exp_x / np.sum(exp_x, axis=0, keepdims=True)

# 前向传播
def forward_propagation(X, parameters):
    W1 = parameters['W1']
    b1 = parameters['b1']
    W2 = parameters['W2']
    b2 = parameters['b2']
    
    Z1 = np.dot(W1, X) + b1
    A1 = relu(Z1)
    Z2 = np.dot(W2, A1) + b2
    A2 = sigmoid(Z2)
    
    cache = {
        'Z1': Z1,
        'A1': A1,
        'Z2': Z2,
        'A2': A2
    }
    
    return A2, cache

# 计算损失
def compute_loss(A2, Y):
    m = Y.shape[1]
    loss = -np.sum(Y * np.log(A2) + (1 - Y) * np.log(1 - A2)) / m
    return loss

# 反向传播
def backward_propagation(parameters, cache, X, Y):
    m = X.shape[1]
    W1 = parameters['W1']
    W2 = parameters['W2']
    A1 = cache['A1']
    A2 = cache['A2']
    Z1 = cache['Z1']
    
    # 计算输出层的梯度
    delta2 = A2 - Y
    dW2 = np.dot(delta2, A1.T) / m
    db2 = np.sum(delta2, axis=1, keepdims=True) / m
    
    # 计算隐藏层的梯度
    delta1 = np.dot(W2.T, delta2) * relu_derivative(Z1)
    dW1 = np.dot(delta1, X.T) / m
    db1 = np.sum(delta1, axis=1, keepdims=True) / m
    
    grads = {
        'dW1': dW1,
        'db1': db1,
        'dW2': dW2,
        'db2': db2
    }
    
    return grads

# 更新参数
def update_parameters(parameters, grads, learning_rate):
    W1 = parameters['W1']
    b1 = parameters['b1']
    W2 = parameters['W2']
    b2 = parameters['b2']
    
    dW1 = grads['dW1']
    db1 = grads['db1']
    dW2 = grads['dW2']
    db2 = grads['db2']
    
    W1 -= learning_rate * dW1
    b1 -= learning_rate * db1
    W2 -= learning_rate * dW2
    b2 -= learning_rate * db2
    
    parameters = {
        'W1': W1,
        'b1': b1,
        'W2': W2,
        'b2': b2
    }
    
    return parameters

3.1.2 初始化参数

def initialize_parameters(n_x, n_h, n_y):
    """
    初始化参数
    n_x: 输入层大小
    n_h: 隐藏层大小
    n_y: 输出层大小
    """
    W1 = np.random.randn(n_h, n_x) * 0.01
    b1 = np.zeros((n_h, 1))
    W2 = np.random.randn(n_y, n_h) * 0.01
    b2 = np.zeros((n_y, 1))
    
    parameters = {
        'W1': W1,
        'b1': b1,
        'W2': W2,
        'b2': b2
    }
    
    return parameters

3.2 使用TensorFlow实现反向传播

import tensorflow as tf

# 创建模型
def create_model(n_x, n_h, n_y):
    """
    创建浅层神经网络模型
    n_x: 输入层大小
    n_h: 隐藏层大小
    n_y: 输出层大小
    """
    model = tf.keras.Sequential([
        tf.keras.layers.Dense(n_h, activation='relu', input_shape=(n_x,)),
        tf.keras.layers.Dense(n_y, activation='sigmoid')
    ])
    
    return model

# 编译模型
model = create_model(n_x=2, n_h=4, n_y=1)
model.compile(optimizer='adam',
              loss='binary_crossentropy',
              metrics=['accuracy'])

# 训练模型
# model.fit(X_train, Y_train, epochs=100, batch_size=32)

3.3 使用PyTorch实现反向传播

import torch
import torch.nn as nn
import torch.optim as optim

# 定义模型类
class ShallowNN(nn.Module):
    def __init__(self, n_x, n_h, n_y):
        """
        初始化模型
        n_x: 输入层大小
        n_h: 隐藏层大小
        n_y: 输出层大小
        """
        super(ShallowNN, self).__init__()
        self.fc1 = nn.Linear(n_x, n_h)
        self.fc2 = nn.Linear(n_h, n_y)
        self.relu = nn.ReLU()
        self.sigmoid = nn.Sigmoid()
    
    def forward(self, x):
        """
        前向传播
        x: 输入数据
        """
        x = self.fc1(x)
        x = self.relu(x)
        x = self.fc2(x)
        x = self.sigmoid(x)
        return x

# 创建模型
model = ShallowNN(n_x=2, n_h=4, n_y=1)

# 定义损失函数和优化器
criterion = nn.BCELoss()
optimizer = optim.Adam(model.parameters(), lr=0.01)

# 训练模型
# for epoch in range(100):
#     optimizer.zero_grad()
#     outputs = model(inputs)
#     loss = criterion(outputs, targets)
#     loss.backward()
#     optimizer.step()

4. 反向传播的数学推导详解

4.1 损失函数对输出层加权和的梯度

对于二分类问题,使用交叉熵损失函数:

L = -\frac{1}{m} \sum_{i=1}^{m} [y^{(i)} \log(a^{(2)(i)}) + (1 - y^{(i)}) \log(1 - a^{(2)(i)})]

其中, m 是样本数量, y^{(i)} 是第i个样本的真实标签, a^{(2)(i)} 是网络对第i个样本的预测输出。

输出层使用Sigmoid激活函数:

a^{(2)(i)} = g(z^{(2)(i)}) = \frac{1}{1 + e^{-z^{(2)(i)}}}

计算损失函数对输出层加权和的梯度:

\frac{\partial L}{\partial z^{(2)(i)}} = a^{(2)(i)} - y^{(i)}

4.2 损失函数对输出层参数的梯度

计算损失函数对输出层权重的梯度:

\frac{\partial L}{\partial w_{jk}^{(2)}} = \frac{\partial L}{\partial z^{(2)(j)}} \cdot \frac{\partial z^{(2)(j)}}{\partial w_{jk}^{(2)}} = \delta^{(2)(j)} \cdot a^{(1)(k)}

计算损失函数对输出层偏置的梯度:

\frac{\partial L}{\partial b_{j}^{(2)}} = \frac{\partial L}{\partial z^{(2)(j)}} \cdot \frac{\partial z^{(2)(j)}}{\partial b_{j}^{(2)}} = \delta^{(2)(j)}

4.3 损失函数对隐藏层加权和的梯度

计算损失函数对隐藏层加权和的梯度:

\frac{\partial L}{\partial z^{(1)(k)}} = \sum_{j=1}^{n_y} \frac{\partial L}{\partial z^{(2)(j)}} \cdot \frac{\partial z^{(2)(j)}}{\partial a^{(1)(k)}} \cdot \frac{\partial a^{(1)(k)}}{\partial z^{(1)(k)}} = \sum_{j=1}^{n_y} \delta^{(2)(j)} \cdot w_{jk}^{(2)} \cdot f'(z^{(1)(k)})

4.4 损失函数对隐藏层参数的梯度

计算损失函数对隐藏层权重的梯度:

\frac{\partial L}{\partial w_{ki}^{(1)}} = \frac{\partial L}{\partial z^{(1)(k)}} \cdot \frac{\partial z^{(1)(k)}}{\partial w_{ki}^{(1)}} = \delta^{(1)(k)} \cdot x^{(i)}

计算损失函数对隐藏层偏置的梯度:

\frac{\partial L}{\partial b_{k}^{(1)}} = \frac{\partial L}{\partial z^{(1)(k)}} \cdot \frac{\partial z^{(1)(k)}}{\partial b_{k}^{(1)}} = \delta^{(1)(k)}

5. 反向传播的应用示例

5.1 解决XOR问题

XOR问题是一个经典的线性不可分问题,单层感知器无法解决,但浅层神经网络可以轻松解决。

5.1.1 数据准备

import numpy as np

# 准备XOR数据
X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]]).T
Y = np.array([[0], [1], [1], [0]]).T

print("输入数据:")
print(X)
print("\n标签数据:")
print(Y)

5.1.2 模型训练

import numpy as np

# 定义激活函数及其导数
def sigmoid(x):
    return 1 / (1 + np.exp(-x))

def relu(x):
    return np.maximum(0, x)

def relu_derivative(x):
    return np.where(x > 0, 1, 0)

# 初始化参数
def initialize_parameters(n_x, n_h, n_y):
    W1 = np.random.randn(n_h, n_x) * 0.01
    b1 = np.zeros((n_h, 1))
    W2 = np.random.randn(n_y, n_h) * 0.01
    b2 = np.zeros((n_y, 1))
    
    parameters = {
        'W1': W1,
        'b1': b1,
        'W2': W2,
        'b2': b2
    }
    
    return parameters

# 前向传播
def forward_propagation(X, parameters):
    W1 = parameters['W1']
    b1 = parameters['b1']
    W2 = parameters['W2']
    b2 = parameters['b2']
    
    Z1 = np.dot(W1, X) + b1
    A1 = relu(Z1)
    Z2 = np.dot(W2, A1) + b2
    A2 = sigmoid(Z2)
    
    cache = {
        'Z1': Z1,
        'A1': A1,
        'Z2': Z2,
        'A2': A2
    }
    
    return A2, cache

# 计算损失
def compute_loss(A2, Y):
    m = Y.shape[1]
    loss = -np.sum(Y * np.log(A2) + (1 - Y) * np.log(1 - A2)) / m
    return loss

# 反向传播
def backward_propagation(parameters, cache, X, Y):
    m = X.shape[1]
    W1 = parameters['W1']
    W2 = parameters['W2']
    A1 = cache['A1']
    A2 = cache['A2']
    Z1 = cache['Z1']
    
    # 计算输出层的梯度
    delta2 = A2 - Y
    dW2 = np.dot(delta2, A1.T) / m
    db2 = np.sum(delta2, axis=1, keepdims=True) / m
    
    # 计算隐藏层的梯度
    delta1 = np.dot(W2.T, delta2) * relu_derivative(Z1)
    dW1 = np.dot(delta1, X.T) / m
    db1 = np.sum(delta1, axis=1, keepdims=True) / m
    
    grads = {
        'dW1': dW1,
        'db1': db1,
        'dW2': dW2,
        'db2': db2
    }
    
    return grads

# 更新参数
def update_parameters(parameters, grads, learning_rate):
    W1 = parameters['W1']
    b1 = parameters['b1']
    W2 = parameters['W2']
    b2 = parameters['b2']
    
    dW1 = grads['dW1']
    db1 = grads['db1']
    dW2 = grads['dW2']
    db2 = grads['db2']
    
    W1 -= learning_rate * dW1
    b1 -= learning_rate * db1
    W2 -= learning_rate * dW2
    b2 -= learning_rate * db2
    
    parameters = {
        'W1': W1,
        'b1': b1,
        'W2': W2,
        'b2': b2
    }
    
    return parameters

# 训练模型
def train_model(X, Y, n_h, num_iterations, learning_rate):
    n_x = X.shape[0]
    n_y = Y.shape[0]
    
    # 初始化参数
    parameters = initialize_parameters(n_x, n_h, n_y)
    
    # 训练模型
    for i in range(num_iterations):
        # 前向传播
        A2, cache = forward_propagation(X, parameters)
        
        # 计算损失
        loss = compute_loss(A2, Y)
        
        # 反向传播
        grads = backward_propagation(parameters, cache, X, Y)
        
        # 更新参数
        parameters = update_parameters(parameters, grads, learning_rate)
        
        # 打印损失
        if (i + 1) % 1000 == 0:
            print(f"迭代 {i+1}, 损失: {loss:.4f}")
    
    return parameters

# 预测
def predict(parameters, X):
    A2, _ = forward_propagation(X, parameters)
    predictions = np.where(A2 > 0.5, 1, 0)
    return predictions

# 训练模型
print("训练模型...")
parameters = train_model(X, Y, n_h=4, num_iterations=10000, learning_rate=0.1)

# 预测
print("\n预测结果:")
predictions = predict(parameters, X)
print(predictions)

# 计算准确率
accuracy = np.mean(predictions == Y)
print(f"\n准确率: {accuracy:.4f}")

5.2 可视化训练过程

import numpy as np
import matplotlib.pyplot as plt

# 训练模型并记录损失
def train_model_with_history(X, Y, n_h, num_iterations, learning_rate):
    n_x = X.shape[0]
    n_y = Y.shape[0]
    
    # 初始化参数
    parameters = initialize_parameters(n_x, n_h, n_y)
    
    # 记录损失
    loss_history = []
    
    # 训练模型
    for i in range(num_iterations):
        # 前向传播
        A2, cache = forward_propagation(X, parameters)
        
        # 计算损失
        loss = compute_loss(A2, Y)
        loss_history.append(loss)
        
        # 反向传播
        grads = backward_propagation(parameters, cache, X, Y)
        
        # 更新参数
        parameters = update_parameters(parameters, grads, learning_rate)
        
        # 打印损失
        if (i + 1) % 1000 == 0:
            print(f"迭代 {i+1}, 损失: {loss:.4f}")
    
    return parameters, loss_history

# 训练模型
print("训练模型...")
parameters, loss_history = train_model_with_history(X, Y, n_h=4, num_iterations=10000, learning_rate=0.1)

# 绘制损失曲线
plt.figure(figsize=(10, 6))
plt.plot(loss_history)
plt.title('训练过程中的损失变化')
plt.xlabel('迭代次数')
plt.ylabel('损失')
plt.grid(True)
plt.show()

# 可视化决策边界
def plot_decision_boundary(parameters, X, Y):
    """
    可视化决策边界
    parameters: 模型参数
    X: 输入数据
    Y: 标签数据
    """
    # 创建网格
    x_min, x_max = X[0, :].min() - 0.5, X[0, :].max() + 0.5
    y_min, y_max = X[1, :].min() - 0.5, X[1, :].max() + 0.5
    h = 0.01
    xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
    
    # 预测网格点
    grid = np.c_[xx.ravel(), yy.ravel()].T
    A2, _ = forward_propagation(grid, parameters)
    Z = np.where(A2 > 0.5, 1, 0)
    Z = Z.reshape(xx.shape)
    
    # 绘制决策边界
    plt.figure(figsize=(8, 6))
    plt.contourf(xx, yy, Z, alpha=0.8)
    
    # 绘制训练数据点
    plt.scatter(X[0, :], X[1, :], c=Y.ravel(), edgecolors='k', cmap=plt.cm.Spectral)
    plt.title('浅层神经网络的决策边界')
    plt.xlabel('输入1')
    plt.ylabel('输入2')
    plt.show()

# 绘制决策边界
plot_decision_boundary(parameters, X, Y)

6. 反向传播的优化技巧

6.1 批量梯度下降

批量梯度下降(Batch Gradient Descent)使用整个训练集计算梯度,每次迭代更新一次参数。

优点

  • 梯度估计准确
  • 收敛稳定

缺点

  • 计算量大,训练速度慢
  • 内存消耗大

6.2 随机梯度下降

随机梯度下降(Stochastic Gradient Descent)使用单个样本计算梯度,每次迭代更新一次参数。

优点

  • 计算量小,训练速度快
  • 可以在线学习

缺点

  • 梯度估计不准确
  • 收敛不稳定

6.3 小批量梯度下降

小批量梯度下降(Mini-batch Gradient Descent)使用一小批样本计算梯度,每次迭代更新一次参数。

优点

  • 平衡了计算效率和梯度估计准确性
  • 可以利用向量化计算
  • 收敛相对稳定

缺点

  • 需要选择合适的批量大小

6.4 学习率调度

学习率调度(Learning Rate Scheduling)是一种在训练过程中调整学习率的技术。

常见的学习率调度方法

  • 恒定学习率:整个训练过程使用相同的学习率
  • 时间衰减:学习率随时间线性或指数衰减
  • 步骤衰减:在特定的迭代次数降低学习率
  • 自适应学习率:根据梯度动态调整学习率(如Adagrad、RMSprop、Adam等)

7. 反向传播的常见问题与解决方案

7.1 梯度消失问题

问题:当使用Sigmoid或Tanh等激活函数时,梯度可能会随着网络深度的增加而逐渐减小,导致深层网络的参数难以更新。

解决方案

  • 使用ReLU及其变体作为激活函数
  • 使用残差连接(Residual Connection)
  • 使用批量归一化(Batch Normalization)
  • 合理初始化参数

7.2 梯度爆炸问题

问题:当网络参数初始化过大时,梯度可能会变得非常大,导致训练不稳定。

解决方案

  • 合理的参数初始化
  • 使用梯度裁剪(Gradient Clipping)
  • 使用批量归一化
  • 减小学习率

7.3 过拟合问题

问题:模型在训练数据上表现良好,但在测试数据上表现较差。

解决方案

  • 正则化(L1、L2正则化)
  • Dropout
  • 早停法(Early Stopping)
  • 数据增强

7.4 局部最小值问题

问题:梯度下降法可能会陷入局部最小值,无法找到全局最小值。

解决方案

  • 使用动量优化器
  • 使用随机梯度下降
  • 尝试不同的初始化参数
  • 使用自适应优化算法

8. 反向传播在深度学习框架中的应用

8.1 TensorFlow中的反向传播

TensorFlow使用自动微分(Automatic Differentiation)来计算梯度,无需手动实现反向传播算法。

import tensorflow as tf

# 创建模型
model = tf.keras.Sequential([
    tf.keras.layers.Dense(4, activation='relu', input_shape=(2,)),
    tf.keras.layers.Dense(1, activation='sigmoid')
])

# 编译模型
model.compile(optimizer='adam',
              loss='binary_crossentropy',
              metrics=['accuracy'])

# 准备数据
X = tf.constant([[0, 0], [0, 1], [1, 0], [1, 1]], dtype=tf.float32)
Y = tf.constant([[0], [1], [1], [0]], dtype=tf.float32)

# 训练模型
history = model.fit(X, Y, epochs=1000, batch_size=4, verbose=0)

# 评估模型
loss, accuracy = model.evaluate(X, Y, verbose=0)
print(f"准确率: {accuracy:.4f}")

# 预测
predictions = model.predict(X)
print("\n预测结果:")
print(predictions)

8.2 PyTorch中的反向传播

PyTorch同样使用自动微分来计算梯度,通过反向传播算法更新参数。

import torch
import torch.nn as nn
import torch.optim as optim

# 准备数据
X = torch.tensor([[0, 0], [0, 1], [1, 0], [1, 1]], dtype=torch.float32)
Y = torch.tensor([[0], [1], [1], [0]], dtype=torch.float32)

# 定义模型
class ShallowNN(nn.Module):
    def __init__(self):
        super(ShallowNN, self).__init__()
        self.fc1 = nn.Linear(2, 4)
        self.fc2 = nn.Linear(4, 1)
        self.relu = nn.ReLU()
        self.sigmoid = nn.Sigmoid()
    
    def forward(self, x):
        x = self.fc1(x)
        x = self.relu(x)
        x = self.fc2(x)
        x = self.sigmoid(x)
        return x

# 创建模型
model = ShallowNN()

# 定义损失函数和优化器
criterion = nn.BCELoss()
optimizer = optim.Adam(model.parameters(), lr=0.01)

# 训练模型
for epoch in range(1000):
    # 前向传播
    outputs = model(X)
    
    # 计算损失
    loss = criterion(outputs, Y)
    
    # 反向传播
    optimizer.zero_grad()
    loss.backward()
    
    # 更新参数
    optimizer.step()

# 评估模型
with torch.no_grad():
    outputs = model(X)
    predictions = (outputs > 0.5).float()
    accuracy = (predictions == Y).float().mean()
    print(f"准确率: {accuracy:.4f}")

# 预测
print("\n预测结果:")
print(outputs)

9. 实战:使用反向传播训练图像分类模型

9.1 数据集介绍

我们将使用MNIST数据集进行图像分类。MNIST数据集包含60,000张训练图像和10,000张测试图像,每张图像是28x28像素的手写数字。

9.2 代码实现

import numpy as np
import matplotlib.pyplot as plt
from tensorflow.keras.datasets import mnist

# 加载MNIST数据集
(x_train, y_train), (x_test, y_test) = mnist.load_data()

# 数据预处理
x_train = x_train.reshape(-1, 28*28) / 255.0
x_test = x_test.reshape(-1, 28*28) / 255.0

# 转换为one-hot编码
y_train_one_hot = np.zeros((10, x_train.shape[0]))
for i, label in enumerate(y_train):
    y_train_one_hot[label, i] = 1

y_test_one_hot = np.zeros((10, x_test.shape[0]))
for i, label in enumerate(y_test):
    y_test_one_hot[label, i] = 1

# 转换为正确的形状
X_train = x_train.T
Y_train = y_train_one_hot
X_test = x_test.T
Y_test = y_test_one_hot

# 定义激活函数及其导数
def relu(x):
    return np.maximum(0, x)

def relu_derivative(x):
    return np.where(x > 0, 1, 0)

def softmax(x):
    exp_x = np.exp(x - np.max(x, axis=0, keepdims=True))
    return exp_x / np.sum(exp_x, axis=0, keepdims=True)

# 初始化参数
def initialize_parameters(n_x, n_h, n_y):
    W1 = np.random.randn(n_h, n_x) * 0.01
    b1 = np.zeros((n_h, 1))
    W2 = np.random.randn(n_y, n_h) * 0.01
    b2 = np.zeros((n_y, 1))
    
    parameters = {
        'W1': W1,
        'b1': b1,
        'W2': W2,
        'b2': b2
    }
    
    return parameters

# 前向传播
def forward_propagation(X, parameters):
    W1 = parameters['W1']
    b1 = parameters['b1']
    W2 = parameters['W2']
    b2 = parameters['b2']
    
    Z1 = np.dot(W1, X) + b1
    A1 = relu(Z1)
    Z2 = np.dot(W2, A1) + b2
    A2 = softmax(Z2)
    
    cache = {
        'Z1': Z1,
        'A1': A1,
        'Z2': Z2,
        'A2': A2
    }
    
    return A2, cache

# 计算损失
def compute_loss(A2, Y):
    m = Y.shape[1]
    loss = -np.sum(Y * np.log(A2 + 1e-15)) / m
    return loss

# 反向传播
def backward_propagation(parameters, cache, X, Y):
    m = X.shape[1]
    W1 = parameters['W1']
    W2 = parameters['W2']
    A1 = cache['A1']
    A2 = cache['A2']
    Z1 = cache['Z1']
    
    # 计算输出层的梯度
    delta2 = A2 - Y
    dW2 = np.dot(delta2, A1.T) / m
    db2 = np.sum(delta2, axis=1, keepdims=True) / m
    
    # 计算隐藏层的梯度
    delta1 = np.dot(W2.T, delta2) * relu_derivative(Z1)
    dW1 = np.dot(delta1, X.T) / m
    db1 = np.sum(delta1, axis=1, keepdims=True) / m
    
    grads = {
        'dW1': dW1,
        'db1': db1,
        'dW2': dW2,
        'db2': db2
    }
    
    return grads

# 更新参数
def update_parameters(parameters, grads, learning_rate):
    W1 = parameters['W1']
    b1 = parameters['b1']
    W2 = parameters['W2']
    b2 = parameters['b2']
    
    dW1 = grads['dW1']
    db1 = grads['db1']
    dW2 = grads['dW2']
    db2 = grads['db2']
    
    W1 -= learning_rate * dW1
    b1 -= learning_rate * db1
    W2 -= learning_rate * dW2
    b2 -= learning_rate * db2
    
    parameters = {
        'W1': W1,
        'b1': b1,
        'W2': W2,
        'b2': b2
    }
    
    return parameters

# 训练模型
def train_model(X, Y, n_h, num_iterations, learning_rate, batch_size=64):
    n_x = X.shape[0]
    n_y = Y.shape[0]
    m = X.shape[1]
    
    # 初始化参数
    parameters = initialize_parameters(n_x, n_h, n_y)
    
    # 记录损失
    loss_history = []
    
    # 训练模型
    for i in range(num_iterations):
        # 随机选择批次
        batch_indices = np.random.choice(m, batch_size, replace=False)
        X_batch = X[:, batch_indices]
        Y_batch = Y[:, batch_indices]
        
        # 前向传播
        A2, cache = forward_propagation(X_batch, parameters)
        
        # 计算损失
        loss = compute_loss(A2, Y_batch)
        loss_history.append(loss)
        
        # 反向传播
        grads = backward_propagation(parameters, cache, X_batch, Y_batch)
        
        # 更新参数
        parameters = update_parameters(parameters, grads, learning_rate)
        
        # 打印损失
        if (i + 1) % 1000 == 0:
            print(f"迭代 {i+1}, 损失: {loss:.4f}")
    
    return parameters, loss_history

# 预测
def predict(parameters, X):
    A2, _ = forward_propagation(X, parameters)
    predictions = np.argmax(A2, axis=0)
    return predictions

# 训练模型
print("训练模型...")
parameters, loss_history = train_model(X_train, Y_train, n_h=128, num_iterations=10000, learning_rate=0.01, batch_size=64)

# 绘制损失曲线
plt.figure(figsize=(10, 6))
plt.plot(loss_history)
plt.title('训练过程中的损失变化')
plt.xlabel('迭代次数')
plt.ylabel('损失')
plt.grid(True)
plt.show()

# 评估模型
train_predictions = predict(parameters, X_train)
train_accuracy = np.mean(train_predictions == y_train)
print(f"训练准确率: {train_accuracy:.4f}")

test_predictions = predict(parameters, X_test)
test_accuracy = np.mean(test_predictions == y_test)
print(f"测试准确率: {test_accuracy:.4f}")

# 可视化一些预测结果
plt.figure(figsize=(12, 8))
for i in range(10):
    plt.subplot(2, 5, i+1)
    plt.imshow(X_test[:, i].reshape(28, 28), cmap='gray')
    plt.title(f"预测: {test_predictions[i]}, 真实: {y_test[i]}")
    plt.axis('off')
plt.tight_layout()
plt.show()

10. 总结与展望

10.1 反向传播的重要性

反向传播是神经网络训练的核心算法,它使得训练深层神经网络成为可能。通过高效计算梯度,反向传播算法大大加速了神经网络的训练过程,为深度学习的发展奠定了基础。

10.2 反向传播的发展历程

  1. 1960s:反向传播的基本思想首次提出
  2. 1970s:反向传播算法在控制理论中得到应用
  3. 1980s:反向传播算法被应用于神经网络训练,推动了神经网络的复兴
  4. 1990s:反向传播算法的局限性(如梯度消失问题)被认识到
  5. 2000s:各种优化技术(如ReLU、批量归一化等)被提出,缓解了反向传播的局限性
  6. 2010s:深度学习兴起,反向传播算法得到广泛应用和进一步优化

10.3 未来发展趋势

  1. 更高效的优化算法:如AdamW、RAdam等
  2. 自动化机器学习:自动设计网络结构和超参数
  3. 神经架构搜索:通过搜索找到最优的网络架构
  4. 联邦学习:在保护隐私的前提下进行分布式训练
  5. 硬件优化:专门为深度学习设计的硬件(如TPU)不断发展

10.4 学习建议

  1. 理解基本原理:掌握反向传播的数学原理和实现方法
  2. 动手实践:实现反向传播算法,解决实际问题
  3. 实验不同超参数:探索不同学习率、批量大小和优化器对模型性能的影响
  4. 学习深度学习框架:熟悉TensorFlow、PyTorch等深度学习框架的使用
  5. 循序渐进:从浅层神经网络开始,逐步学习深层神经网络

11. 思考与练习

11.1 思考问题

  1. 反向传播的基本原理是什么?
  2. 反向传播与梯度下降的关系是什么?
  3. 如何计算损失函数对神经网络参数的梯度?
  4. 梯度消失和梯度爆炸问题是如何产生的?如何解决?
  5. 不同的优化算法(如SGD、Momentum、Adam)有什么区别?

11.2 编程练习

  1. 实现一个浅层神经网络,使用反向传播算法训练,解决二分类问题。
  2. 实现不同的优化算法(如SGD、Momentum、Adam),比较它们的性能。
  3. 实现批量归一化,观察其对模型训练的影响。
  4. 实现残差连接,观察其对深层神经网络训练的影响。
  5. 使用反向传播算法训练一个深层神经网络,解决图像分类问题。

12. 扩展阅读

« 上一篇 浅层神经网络的前向传播 下一篇 » 深层神经网络的表示与特性