本教程旨在帮助初学者理解神经网络的基本原理和实现,特别针对二分类任务,深入解析其正向传播和反向传播的数学推导,并逐步用 numpy
实现完整的神经网络模型,最终使用 PyTorch
简化实现。
神经网络基本概念
神经网络是一种**基于神经元(节点)构建的机器学习模型。每个节点接收输入、执行计算并输出结果。通过多个层(layer)**的堆叠和复杂的计算结构,神经网络可以逼近任意的非线性关系。
数据准备
我们首先生成一个简单的二分类数据集。每个数据点有两个特征,我们希望训练一个神经网络模型预测其类别(0 或 1)。
import numpy as np
import matplotlib.pyplot as plt# 生成数据
np.random.seed(0)
num_points = 200
X = np.random.randn(num_points, 2)
y = (X[:, 0] * X[:, 1] > 0).astype(int) # 创建一个简单的二分类数据集# 数据可视化
plt.scatter(X[y == 0, 0], X[y == 0, 1], color='red', label='Class 0')
plt.scatter(X[y == 1, 0], X[y == 1, 1], color='blue', label='Class 1')
plt.xlabel("Feature 1")
plt.ylabel("Feature 2")
plt.title("Binary Classification Data")
plt.legend()
plt.show()
构建神经网络的基本结构
我们构建一个简单的神经网络结构,包含:
- 输入层:接受数据输入。
- 隐藏层:使用ReLU激活函数进行非线性变换。
- 输出层:通过sigmoid激活函数输出0到1之间的概率,用于二分类。
正向传播(Forward Propagation)
正向传播是指数据从输入层流向输出层的计算过程。
- 输入层到隐藏层:计算
Z1 = X * W1 + b1
。 - 激活函数:在隐藏层使用 ReLU 函数。
- 隐藏层到输出层:计算
Z2 = A1 * W2 + b2
,然后使用 sigmoid 激活函数获得输出概率。
# 初始化参数函数
def initialize_parameters(input_dim, hidden_dim, output_dim):"""初始化神经网络的权重和偏置参数。参数:- input_dim: 输入层神经元数量(即特征数量)- hidden_dim: 隐藏层神经元数量- output_dim: 输出层神经元数量(对于二分类输出为1)返回:- W1: 输入层到隐藏层的权重矩阵,形状为 (input_dim, hidden_dim)- b1: 隐藏层的偏置项,形状为 (1, hidden_dim)- W2: 隐藏层到输出层的权重矩阵,形状为 (hidden_dim, output_dim)- b2: 输出层的偏置项,形状为 (1, output_dim)"""np.random.seed(1) # 固定随机种子,保证每次初始化相同# 初始化 W1,权重较小以防止初始值过大影响训练W1 = np.random.randn(input_dim, hidden_dim) * 0.01b1 = np.zeros((1, hidden_dim)) # 初始化 b1 为零,避免对初始输出的影响W2 = np.random.randn(hidden_dim, output_dim) * 0.01b2 = np.zeros((1, output_dim))return W1, b1, W2, b2# ReLU 激活函数
def relu(Z):"""ReLU(线性整流)激活函数,返回输入中每个值的最大值和 0 的较大值。参数:- Z: 输入数组,可以是任意形状返回:- A: ReLU 激活后的输出,与 Z 形状相同"""return np.maximum(0, Z)# Sigmoid 激活函数
def sigmoid(Z):"""Sigmoid 激活函数,将输入值映射到 0 到 1 之间的范围,用于二分类问题。参数:- Z: 输入数组,可以是任意形状返回:- A: Sigmoid 激活后的输出,与 Z 形状相同"""return 1 / (1 + np.exp(-Z))# 正向传播函数
def forward_propagation(X, W1, b1, W2, b2):"""执行神经网络的正向传播过程。参数:- X: 输入数据矩阵,形状为 (样本数, 输入层神经元数)- W1, b1: 输入层到隐藏层的权重和偏置- W2, b2: 隐藏层到输出层的权重和偏置返回:- A2: 输出层激活值,形状为 (样本数, 输出层神经元数)- cache: 包含正向传播中间计算结果的字典(供反向传播使用)"""# 计算隐藏层的线性组合 Z1 = X * W1 + b1Z1 = np.dot(X, W1) + b1# 通过 ReLU 激活函数得到 A1A1 = relu(Z1)# 计算输出层的线性组合 Z2 = A1 * W2 + b2Z2 = np.dot(A1, W2) + b2# 通过 Sigmoid 激活函数得到最终输出 A2A2 = sigmoid(Z2)# 将所有计算结果缓存,以便反向传播时使用cache = (Z1, A1, W1, b1, Z2, A2, W2, b2)return A2, cache
损失函数(Loss Function)
对于二分类任务,常用的损失函数是二元交叉熵损失(binary cross-entropy loss),其定义为:
Loss = − 1 m ∑ i = 1 m ( y ( i ) log ( y ^ ( i ) ) + ( 1 − y ( i ) ) log ( 1 − y ^ ( i ) ) ) \text{Loss} = -\frac{1}{m} \sum_{i=1}^m (y^{(i)} \log(\hat{y}^{(i)}) + (1 - y^{(i)}) \log(1 - \hat{y}^{(i)})) Loss=−m1i=1∑m(y(i)log(y^(i))+(1−y(i))log(1−y^(i)))
# 定义损失函数
def compute_loss(A2, Y):"""计算二分类问题的交叉熵损失函数。参数:- A2: 模型的预测输出,形状为 (样本数, 1),值在 0 到 1 之间- Y: 实际标签,形状为 (样本数, 1),值为 0 或 1返回:- loss: 交叉熵损失的平均值,标量"""m = Y.shape[0] # 获取样本数# 计算交叉熵损失,每个样本的损失为 -[y*log(a) + (1-y)*log(1-a)]loss = -np.mean(Y * np.log(A2) + (1 - Y) * np.log(1 - A2))return loss
反向传播(Backward Propagation)
反向传播通过链式法则计算损失函数对每个参数的梯度,从而更新参数以最小化损失。具体步骤如下:
- 计算输出层的梯度。
- 通过输出层的梯度,进一步计算隐藏层的梯度。
- 使用梯度下降算法更新参数。
# 反向传播函数
def backward_propagation(X, Y, cache):"""执行神经网络的反向传播计算梯度,以用于更新参数。参数:- X: 输入数据矩阵,形状为 (样本数, 输入层神经元数)- Y: 实际标签,形状为 (样本数, 1)- cache: 包含正向传播中间结果的字典,供反向传播计算使用返回:- gradients: 包含各层参数梯度的字典,供梯度下降法更新参数"""# 解包缓存的中间结果Z1, A1, W1, b1, Z2, A2, W2, b2 = cachem = X.shape[0] # 获取样本数量# 计算输出层的梯度dZ2 = A2 - Y # A2 是模型预测值,Y 是实际值,计算损失对 Z2 的梯度dW2 = (1 / m) * np.dot(A1.T, dZ2) # 损失对 W2 的梯度db2 = (1 / m) * np.sum(dZ2, axis=0, keepdims=True) # 损失对 b2 的梯度# 计算隐藏层的梯度dA1 = np.dot(dZ2, W2.T) # 反向传播 dZ2,通过 W2 获得 dA1dZ1 = dA1 * (Z1 > 0) # ReLU 导数,当 Z1 > 0 时导数为 1,否则为 0dW1 = (1 / m) * np.dot(X.T, dZ1) # 损失对 W1 的梯度db1 = (1 / m) * np.sum(dZ1, axis=0, keepdims=True) # 损失对 b1 的梯度# 将各梯度存入字典,便于更新gradients = {"dW1": dW1, "db1": db1, "dW2": dW2, "db2": db2}return gradients
参数更新
通过反向传播获得梯度后,我们使用梯度下降算法更新参数。更新公式如下:
W = W − α ⋅ d W b = b − α ⋅ d b W = W - \alpha \cdot dW\\ b = b - \alpha \cdot db W=W−α⋅dWb=b−α⋅db
# 参数更新函数
def update_parameters(W1, b1, W2, b2, gradients, learning_rate=0.01):"""使用梯度下降法更新模型参数。参数:- W1, b1, W2, b2: 当前神经网络的权重和偏置参数- gradients: 包含各参数梯度的字典(从反向传播计算得出)- learning_rate: 学习率,控制更新步长,默认为 0.01返回:- W1, b1, W2, b2: 更新后的参数"""# 使用学习率调整各参数的梯度,更新 W1W1 -= learning_rate * gradients["dW1"]# 更新 b1b1 -= learning_rate * gradients["db1"]# 更新 W2W2 -= learning_rate * gradients["dW2"]# 更新 b2b2 -= learning_rate * gradients["db2"]return W1, b1, W2, b2
训练神经网络
我们将上述步骤整合到一个完整的训练过程中,循环执行正向传播、损失计算、反向传播和参数更新。
# 定义训练神经网络的函数
def train_neural_network(X, Y, input_dim, hidden_dim, output_dim, epochs=1000, learning_rate=0.01):"""训练神经网络模型,使用梯度下降法优化参数。参数:- X: 输入数据矩阵,形状为 (样本数, 输入层神经元数)- Y: 实际标签矩阵,形状为 (样本数, 1)- input_dim: 输入层的神经元数量- hidden_dim: 隐藏层的神经元数量- output_dim: 输出层的神经元数量- epochs: 训练迭代次数,默认为 1000- learning_rate: 学习率,控制每次参数更新的步长,默认为 0.01返回:- W1, b1, W2, b2: 训练好的参数- losses: 每个 epoch 的损失值列表,用于可视化"""# 初始化参数W1, b1, W2, b2 = initialize_parameters(input_dim, hidden_dim, output_dim)losses = [] # 存储每个 epoch 的损失值,用于后续绘图# 迭代训练for epoch in range(epochs):# 正向传播A2, cache = forward_propagation(X, W1, b1, W2, b2)# 计算损失loss = compute_loss(A2, Y)losses.append(loss) # 保存当前损失值# 反向传播gradients = backward_propagation(X, Y, cache)# 更新参数W1, b1, W2, b2 = update_parameters(W1, b1, W2, b2, gradients, learning_rate)# 每 100 个 epoch 打印一次损失if epoch % 100 == 0:print(f"Epoch {epoch}, Loss: {loss}")return W1, b1, W2, b2, losses# 使用数据训练神经网络
Y = y.reshape(-1, 1) # 确保标签是列向量,符合网络输入要求
W1, b1, W2, b2, losses = train_neural_network(X, Y, input_dim=2, hidden_dim=4, output_dim=1, epochs=1000, learning_rate=0.1)# 可视化训练过程中的损失变化
import matplotlib.pyplot as pltplt.plot(losses)
plt.xlabel("Epoch")
plt.ylabel("Loss")
plt.title("Training Loss")
plt.show()
模型预测
模型训练后,我们可以使用训练得到的参数进行预测。
# 定义预测函数
def predict(X, W1, b1, W2, b2):A2, _ = forward_propagation(X, W1, b1, W2, b2)predictions = (A2 > 0.5).astype(int)return predictions# 预测并可视化分类结果
predictions = predict(X, W1, b1, W2, b2)# 绘制预测结果
plt.scatter(X[predictions.flatten() == 0, 0], X[predictions.flatten() == 0, 1], color='red', label='Class 0')
plt.scatter(X[predictions.flatten() == 1, 0], X[predictions.flatten() == 1, 1], color='blue', label='Class 1')
plt.xlabel("Feature 1")
plt.ylabel("Feature 2")
plt.title("Prediction Results")
plt.legend()
plt.show()
使用PyTorch简化实现
在实际应用中,可以使用深度学习库如 PyTorch
快速实现同样的网络结构。
# 导入必要库
import torch
import torch.nn as nn
import torch.optim as optim
import numpy as np
import matplotlib.pyplot as plt# 将数据转换为 PyTorch tensor
X_tensor = torch.FloatTensor(X) # 将数据 X 转换为浮点数 tensor
y_tensor = torch.FloatTensor(Y) # 将标签 y 转换为浮点数 tensor# 定义简单的二分类神经网络
class SimpleNN(nn.Module):def __init__(self):super(SimpleNN, self).__init__()self.hidden = nn.Linear(2, 4) # 隐藏层self.output = nn.Linear(4, 1) # 输出层def forward(self, x):x = torch.relu(self.hidden(x)) # 使用 ReLU 激活函数x = torch.sigmoid(self.output(x)) # 使用 Sigmoid 激活函数return x# 创建模型实例、定义损失函数和优化器
model = SimpleNN()
criterion = nn.BCELoss()
optimizer = optim.Adam(model.parameters(), lr=0.01)# 训练模型并记录损失
epochs = 1000
losses = []
for epoch in range(epochs):optimizer.zero_grad()output = model(X_tensor)loss = criterion(output, y_tensor)loss.backward()optimizer.step()losses.append(loss.item())if epoch % 100 == 0:print(f"Epoch {epoch}, Loss: {loss.item()}")# 绘制训练过程中损失值变化
plt.plot(losses)
plt.xlabel("Epoch")
plt.ylabel("Loss")
plt.title("Training Loss over Epochs")
plt.show()# 使用训练好的模型进行预测
with torch.no_grad(): # 禁用梯度计算,提高效率predictions = model(X_tensor) # 获取预测值predicted_labels = (predictions > 0.5).float() # 0.5 阈值将预测值转为二分类# 绘制预测结果
plt.figure(figsize=(8, 6))
# 绘制预测为类别 1 的点
plt.scatter(X[predicted_labels.squeeze() == 1, 0], X[predicted_labels.squeeze() == 1, 1], color="blue", label="Predicted Class 1")
# 绘制预测为类别 0 的点
plt.scatter(X[predicted_labels.squeeze() == 0, 0], X[predicted_labels.squeeze() == 0, 1], color="red", label="Predicted Class 0")
# 绘制原始数据的分布
plt.scatter(X[Y.squeeze() == 1, 0], X[Y.squeeze() == 1, 1], color="cyan", marker="x", label="Actual Class 1")
plt.scatter(X[Y.squeeze() == 0, 0], X[Y.squeeze() == 0, 1], color="orange", marker="x", label="Actual Class 0")# 添加图例和标签
plt.xlabel("Feature 1")
plt.ylabel("Feature 2")
plt.title("Prediction Results vs Actual Data")
plt.legend()
plt.show()
总结
通过本教程,我们从原理到实现全程了解了一个简单二分类神经网络的核心构造,并掌握了正向传播和反向传播的推导和实现。希望这为你深入学习神经网络和深度学习打下了坚实的基础。