从吴恩达课程到实战:手把手教你用Python实现一个5层深度神经网络(附完整代码)

深度学习的魅力在于它能够通过层层抽象自动提取特征,而不仅仅是依赖人工设计的特征工程。吴恩达教授的课程为我们奠定了坚实的理论基础,但真正掌握深度神经网络的精髓,还需要将数学公式转化为可运行的代码。本文将带你从零开始构建一个5层深度神经网络,重点关注维度匹配、梯度流动和参数优化等实战关键点。

1. 环境准备与数据加载

在开始构建神经网络之前,我们需要确保开发环境配置正确。推荐使用Python 3.8+版本,并安装以下核心库:

import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_moons
from sklearn.model_selection import train_test_split

我们使用 make_moons 生成非线性可分数据集,这种数据非常适合验证深度神经网络的表达能力:

X, y = make_moons(n_samples=1000, noise=0.2, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)

数据预处理是模型成功的关键第一步:

  1. 标准化 :将特征值缩放到相似范围
  2. 维度调整 :确保输入数据形状符合网络要求
  3. 可视化 :直观理解数据分布特征
# 数据标准化
mean = X_train.mean(axis=0)
std = X_train.std(axis=0)
X_train = (X_train - mean) / std
X_test = (X_test - mean) / std

# 调整维度
X_train = X_train.T
y_train = y_train.reshape(1, -1)
X_test = X_test.T
y_test = y_test.reshape(1, -1)

2. 网络架构设计与初始化

我们构建的5层神经网络结构如下:

层类型 单元数量 激活函数
输入层 2 -
隐藏层1 64 ReLU
隐藏层2 32 ReLU
隐藏层3 16 ReLU
输出层 1 Sigmoid

参数初始化对深度神经网络的训练至关重要。不恰当的初始化可能导致梯度消失或爆炸:

def initialize_parameters(layer_dims):
    np.random.seed(42)
    parameters = {}
    L = len(layer_dims)
    
    for l in range(1, L):
        parameters['W' + str(l)] = np.random.randn(layer_dims[l], layer_dims[l-1]) * 0.01
        parameters['b' + str(l)] = np.zeros((layer_dims[l], 1))
        
    return parameters

layer_dims = [2, 64, 32, 16, 1]
parameters = initialize_parameters(layer_dims)

注意 :权重初始化使用较小的随机值可以避免初始阶段神经元输出饱和,而偏置初始化为零是常见做法。

3. 前向传播实现与维度核对

前向传播需要为每一层实现两个关键操作:

  1. 线性变换:Z = W·A_prev + b
  2. 非线性激活:A = g(Z)
def relu(Z):
    return np.maximum(0, Z)

def sigmoid(Z):
    return 1 / (1 + np.exp(-Z))

def forward_propagation(X, parameters):
    caches = []
    A = X
    L = len(parameters) // 2
    
    for l in range(1, L):
        A_prev = A
        W = parameters['W' + str(l)]
        b = parameters['b' + str(l)]
        Z = np.dot(W, A_prev) + b
        A = relu(Z)
        caches.append((A_prev, W, b, Z))
    
    # 输出层使用sigmoid激活
    W = parameters['W' + str(L)]
    b = parameters['b' + str(L)]
    Z = np.dot(W, A) + b
    AL = sigmoid(Z)
    caches.append((A, W, b, Z))
    
    return AL, caches

维度核对是调试神经网络的关键技能。以下表格展示了各层矩阵的正确维度:

变量 维度公式 示例维度
W^[l] (n^[l], n^[l-1]) W1: (64, 2)
b^[l] (n^[l], 1) b1: (64, 1)
Z^[l], A^[l] (n^[l], m) A1: (64, 700)

4. 损失函数计算与反向传播

二元分类问题使用交叉熵损失函数:

def compute_cost(AL, Y):
    m = Y.shape[1]
    cost = -np.mean(Y * np.log(AL) + (1-Y) * np.log(1-AL))
    return np.squeeze(cost)

反向传播计算梯度是深度神经网络训练的核心。我们需要为每一层计算三个关键梯度:

  1. dW:权重梯度
  2. db:偏置梯度
  3. dA_prev:前一层激活梯度
def relu_backward(dA, Z):
    dZ = np.array(dA, copy=True)
    dZ[Z <= 0] = 0
    return dZ

def backward_propagation(AL, Y, caches):
    grads = {}
    L = len(caches)
    m = AL.shape[1]
    Y = Y.reshape(AL.shape)
    
    # 初始化反向传播
    dAL = - (np.divide(Y, AL) - np.divide(1 - Y, 1 - AL))
    
    # 输出层梯度
    current_cache = caches[L-1]
    A_prev, W, b, Z = current_cache
    dZ = AL - Y
    grads['dW' + str(L)] = np.dot(dZ, A_prev.T) / m
    grads['db' + str(L)] = np.sum(dZ, axis=1, keepdims=True) / m
    grads['dA' + str(L-1)] = np.dot(W.T, dZ)
    
    # 隐藏层梯度
    for l in reversed(range(L-1)):
        current_cache = caches[l]
        A_prev, W, b, Z = current_cache
        dA = grads['dA' + str(l+1)]
        dZ = relu_backward(dA, Z)
        grads['dW' + str(l+1)] = np.dot(dZ, A_prev.T) / m
        grads['db' + str(l+1)] = np.sum(dZ, axis=1, keepdims=True) / m
        grads['dA' + str(l)] = np.dot(W.T, dZ)
    
    return grads

5. 参数更新与训练循环

使用梯度下降算法更新参数:

def update_parameters(parameters, grads, learning_rate):
    L = len(parameters) // 2
    
    for l in range(1, L+1):
        parameters['W' + str(l)] -= learning_rate * grads['dW' + str(l)]
        parameters['b' + str(l)] -= learning_rate * grads['db' + str(l)]
    
    return parameters

完整的训练循环整合了前向传播、损失计算、反向传播和参数更新:

def model(X, Y, layer_dims, learning_rate=0.01, num_iterations=3000, print_cost=False):
    costs = []
    parameters = initialize_parameters(layer_dims)
    
    for i in range(num_iterations):
        # 前向传播
        AL, caches = forward_propagation(X, parameters)
        
        # 计算损失
        cost = compute_cost(AL, Y)
        
        # 反向传播
        grads = backward_propagation(AL, Y, caches)
        
        # 参数更新
        parameters = update_parameters(parameters, grads, learning_rate)
        
        # 记录损失
        if i % 100 == 0:
            costs.append(cost)
            if print_cost:
                print(f"迭代次数 {i}: 损失 {cost}")
    
    # 绘制学习曲线
    plt.plot(costs)
    plt.xlabel('迭代次数(每100次)')
    plt.ylabel('损失')
    plt.title('学习率 =' + str(learning_rate))
    plt.show()
    
    return parameters

6. 模型评估与可视化

训练完成后,我们需要评估模型在测试集上的表现:

def predict(X, parameters):
    AL, _ = forward_propagation(X, parameters)
    predictions = (AL > 0.5).astype(int)
    return predictions

def accuracy(y_true, y_pred):
    return np.mean(y_true == y_pred) * 100

# 训练模型
parameters = model(X_train, y_train, layer_dims, learning_rate=0.03, num_iterations=3000, print_cost=True)

# 测试集评估
predictions = predict(X_test, parameters)
print(f"测试集准确率: {accuracy(y_test, predictions):.2f}%")

可视化决策边界可以直观理解模型的分类能力:

def plot_decision_boundary(X, y, parameters):
    x_min, x_max = X[0, :].min() - 1, X[0, :].max() + 1
    y_min, y_max = X[1, :].min() - 1, X[1, :].max() + 1
    h = 0.01
    xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
    
    Z = predict(np.c_[xx.ravel(), yy.ravel()].T, parameters)
    Z = Z.reshape(xx.shape)
    
    plt.contourf(xx, yy, Z, cmap=plt.cm.Spectral, alpha=0.8)
    plt.scatter(X[0, :], X[1, :], c=y, cmap=plt.cm.Spectral)
    plt.xlabel('特征1')
    plt.ylabel('特征2')
    plt.title('决策边界')
    plt.show()

plot_decision_boundary(X_test, y_test[0], parameters)

7. 超参数调优与实战技巧

深度神经网络的性能很大程度上取决于超参数的选择。以下是几个关键超参数的经验范围:

超参数 推荐范围 调整策略
学习率 0.0001-0.1 指数衰减
批量大小 32-256 2的幂次
隐藏层数 2-5 逐步增加
单元数量 16-512 网格搜索

调试深度神经网络时常见的几个问题及解决方案:

  1. 梯度消失 :使用ReLU激活函数、批归一化、残差连接
  2. 过拟合 :添加L2正则化、Dropout层、数据增强
  3. 训练不稳定 :梯度裁剪、学习率预热、自适应优化器
# 添加L2正则化的损失计算
def compute_cost_with_regularization(AL, Y, parameters, lambd):
    m = Y.shape[1]
    cross_entropy_cost = compute_cost(AL, Y)
    
    L = len(parameters) // 2
    L2_cost = 0
    for l in range(1, L+1):
        L2_cost += np.sum(np.square(parameters['W' + str(l)]))
    
    L2_cost = (lambd / (2 * m)) * L2_cost
    cost = cross_entropy_cost + L2_cost
    return np.squeeze(cost)

在实际项目中,我发现使用学习率衰减策略可以显著提升模型性能:

def learning_rate_decay(initial_lr, epoch, decay_rate=0.1, decay_step=100):
    return initial_lr * (decay_rate ** (epoch // decay_step))

更多推荐