本文基于 Neural Networks: Zero to Hero 系列教程整理,系统介绍神经网络从基础到实践的知识点。
神经网络基础
什么是神经网络?
神经网络(Neural Network)是一种受生物神经元启发的计算模型,由大量相互连接的节点(神经元)组成,能够通过学习和训练来识别模式、进行分类和预测。
神经网络的核心思想:
- 模拟人脑神经元的工作方式
- 通过大量简单的计算单元组合实现复杂功能
- 能够从数据中自动学习特征和模式
神经网络的应用:
- 图像识别和分类
- 自然语言处理
- 语音识别
- 推荐系统
- 游戏 AI
- 自动驾驶
神经元模型
生物神经元 vs 人工神经元:
生物神经元:
- 树突接收信号
- 细胞体处理信号
- 轴突传递信号
- 突触连接其他神经元
人工神经元(感知机):
- 输入:x₁, x₂, …, xₙ
- 权重:w₁, w₂, …, wₙ
- 偏置:b
- 激活函数:f
- 输出:y = f(Σwᵢxᵢ + b)
数学表示:
y = f(∑(wᵢ × xᵢ) + b)
其中:
- xᵢ:输入特征
- wᵢ:权重
- b:偏置
- f:激活函数
感知机(Perceptron)
单层感知机
单层感知机是最简单的神经网络,只能解决线性可分问题。
结构:
import numpy as np
class Perceptron:
def __init__(self, learning_rate=0.01, n_iterations=1000):
self.learning_rate = learning_rate
self.n_iterations = n_iterations
self.weights = None
self.bias = None
def fit(self, X, y):
"""训练感知机"""
n_samples, n_features = X.shape
# 初始化权重和偏置
self.weights = np.zeros(n_features)
self.bias = 0
# 训练
for _ in range(self.n_iterations):
for idx, x_i in enumerate(X):
# 计算输出
linear_output = np.dot(x_i, self.weights) + self.bias
y_predicted = self.activation(linear_output)
# 更新权重和偏置
update = self.learning_rate * (y[idx] - y_predicted)
self.weights += update * x_i
self.bias += update
def activation(self, x):
"""阶跃激活函数"""
return 1 if x >= 0 else 0
def predict(self, X):
"""预测"""
linear_output = np.dot(X, self.weights) + self.bias
y_predicted = self.activation(linear_output)
return y_predicted
局限性:
- 只能解决线性可分问题
- 无法解决 XOR 问题
- 需要多层网络才能解决非线性问题
多层感知机(MLP)
多层感知机通过添加隐藏层来解决非线性问题。
结构:
输入层 → 隐藏层1 → 隐藏层2 → ... → 输出层
前向传播:
import numpy as np
class MLP:
def __init__(self, layers, activation='sigmoid'):
self.layers = layers # [输入维度, 隐藏层1, 隐藏层2, ..., 输出维度]
self.activation = activation
self.weights = []
self.biases = []
# 初始化权重和偏置
for i in range(len(layers) - 1):
w = np.random.randn(layers[i], layers[i+1]) * 0.1
b = np.zeros((1, layers[i+1]))
self.weights.append(w)
self.biases.append(b)
def sigmoid(self, x):
"""Sigmoid 激活函数"""
return 1 / (1 + np.exp(-np.clip(x, -250, 250)))
def sigmoid_derivative(self, x):
"""Sigmoid 导数"""
s = self.sigmoid(x)
return s * (1 - s)
def forward(self, X):
"""前向传播"""
self.activations = [X]
self.z_values = []
for i in range(len(self.weights)):
z = np.dot(self.activations[-1], self.weights[i]) + self.biases[i]
self.z_values.append(z)
a = self.sigmoid(z)
self.activations.append(a)
return self.activations[-1]
反向传播算法(Backpropagation)
反向传播是训练神经网络的核心算法,通过计算梯度来更新权重。
算法原理
步骤:
- 前向传播:计算网络输出
- 计算损失:比较输出和真实值
- 反向传播:计算梯度
- 更新权重:使用梯度下降更新参数
数学推导:
对于输出层:
δᴸ = (aᴸ - y) ⊙ f'(zᴸ)
对于隐藏层:
δˡ = ((Wˡ⁺¹)ᵀ δˡ⁺¹) ⊙ f'(zˡ)
权重梯度:
∂C/∂Wˡ = aˡ⁻¹ (δˡ)ᵀ
偏置梯度:
∂C/∂bˡ = δˡ
实现:
class MLP:
def backward(self, X, y, output):
"""反向传播"""
m = X.shape[0]
# 输出层误差
self.deltas = []
delta = (output - y) * self.sigmoid_derivative(self.z_values[-1])
self.deltas.insert(0, delta)
# 隐藏层误差
for i in range(len(self.weights) - 2, -1, -1):
delta = np.dot(self.deltas[0], self.weights[i+1].T) * \
self.sigmoid_derivative(self.z_values[i])
self.deltas.insert(0, delta)
# 计算梯度
self.dW = []
self.db = []
for i in range(len(self.weights)):
dW = np.dot(self.activations[i].T, self.deltas[i]) / m
db = np.sum(self.deltas[i], axis=0, keepdims=True) / m
self.dW.append(dW)
self.db.append(db)
def update_weights(self, learning_rate):
"""更新权重"""
for i in range(len(self.weights)):
self.weights[i] -= learning_rate * self.dW[i]
self.biases[i] -= learning_rate * self.db[i]
def train(self, X, y, learning_rate=0.01, epochs=1000):
"""训练网络"""
for epoch in range(epochs):
# 前向传播
output = self.forward(X)
# 计算损失
loss = np.mean((output - y) ** 2)
# 反向传播
self.backward(X, y, output)
# 更新权重
self.update_weights(learning_rate)
if epoch % 100 == 0:
print(f"Epoch {epoch}, Loss: {loss:.4f}")
激活函数
激活函数引入非线性,使神经网络能够学习复杂模式。
常用激活函数
1. Sigmoid
def sigmoid(x):
return 1 / (1 + np.exp(-np.clip(x, -250, 250)))
def sigmoid_derivative(x):
s = sigmoid(x)
return s * (1 - s)
特点:
- 输出范围:(0, 1)
- 适合二分类输出层
- 缺点:梯度消失问题
2. Tanh
def tanh(x):
return np.tanh(x)
def tanh_derivative(x):
return 1 - np.tanh(x) ** 2
特点:
- 输出范围:(-1, 1)
- 零中心化
- 比 Sigmoid 梯度更大
3. ReLU(Rectified Linear Unit)
def relu(x):
return np.maximum(0, x)
def relu_derivative(x):
return (x > 0).astype(float)
特点:
- 计算简单,梯度大
- 解决梯度消失问题
- 缺点:死亡 ReLU 问题(负值输出为 0)
4. Leaky ReLU
def leaky_relu(x, alpha=0.01):
return np.where(x > 0, x, alpha * x)
def leaky_relu_derivative(x, alpha=0.01):
return np.where(x > 0, 1, alpha)
特点:
- 解决死亡 ReLU 问题
- 负值有小的梯度
5. Softmax
def softmax(x):
exp_x = np.exp(x - np.max(x, axis=1, keepdims=True))
return exp_x / np.sum(exp_x, axis=1, keepdims=True)
特点:
- 输出概率分布
- 适合多分类输出层
- 所有输出和为 1
激活函数对比:
| 激活函数 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Sigmoid | 输出范围固定 | 梯度消失 | 输出层(二分类) |
| Tanh | 零中心化 | 梯度消失 | 隐藏层 |
| ReLU | 计算快,梯度大 | 死亡 ReLU | 隐藏层(最常用) |
| Leaky ReLU | 解决死亡 ReLU | - | 隐藏层 |
| Softmax | 概率分布 | - | 输出层(多分类) |
损失函数
损失函数衡量模型预测与真实值的差距。
常用损失函数
1. 均方误差(MSE)
def mse_loss(y_pred, y_true):
return np.mean((y_pred - y_true) ** 2)
def mse_derivative(y_pred, y_true):
return 2 * (y_pred - y_true) / len(y_true)
适用场景: 回归问题
2. 交叉熵损失(Cross-Entropy)
def cross_entropy_loss(y_pred, y_true):
# y_pred 是 softmax 输出
epsilon = 1e-15
y_pred = np.clip(y_pred, epsilon, 1 - epsilon)
return -np.mean(np.sum(y_true * np.log(y_pred), axis=1))
def cross_entropy_derivative(y_pred, y_true):
return y_pred - y_true
适用场景: 分类问题(配合 Softmax)
3. 二元交叉熵(Binary Cross-Entropy)
def binary_cross_entropy_loss(y_pred, y_true):
epsilon = 1e-15
y_pred = np.clip(y_pred, epsilon, 1 - epsilon)
return -np.mean(y_true * np.log(y_pred) + (1 - y_true) * np.log(1 - y_pred))
适用场景: 二分类问题(配合 Sigmoid)
优化器
优化器决定如何更新权重来最小化损失函数。
梯度下降
1. 批量梯度下降(BGD)
def gradient_descent(X, y, learning_rate=0.01, epochs=1000):
weights = np.random.randn(X.shape[1], 1)
bias = 0
for epoch in range(epochs):
# 使用全部数据
predictions = X @ weights + bias
error = predictions - y
# 计算梯度
dw = (1 / len(X)) * X.T @ error
db = (1 / len(X)) * np.sum(error)
# 更新参数
weights -= learning_rate * dw
bias -= learning_rate * db
return weights, bias
特点:
- 使用全部数据
- 稳定但慢
2. 随机梯度下降(SGD)
def stochastic_gradient_descent(X, y, learning_rate=0.01, epochs=1000):
weights = np.random.randn(X.shape[1], 1)
bias = 0
for epoch in range(epochs):
for i in range(len(X)):
# 使用单个样本
x_i = X[i:i+1]
y_i = y[i:i+1]
prediction = x_i @ weights + bias
error = prediction - y_i
dw = x_i.T @ error
db = error
weights -= learning_rate * dw
bias -= learning_rate * db
return weights, bias
特点:
- 使用单个样本
- 快但不稳定
3. 小批量梯度下降(Mini-batch GD)
def mini_batch_gradient_descent(X, y, batch_size=32, learning_rate=0.01, epochs=1000):
weights = np.random.randn(X.shape[1], 1)
bias = 0
for epoch in range(epochs):
# 打乱数据
indices = np.random.permutation(len(X))
X_shuffled = X[indices]
y_shuffled = y[indices]
# 分批处理
for i in range(0, len(X), batch_size):
X_batch = X_shuffled[i:i+batch_size]
y_batch = y_shuffled[i:i+batch_size]
predictions = X_batch @ weights + bias
error = predictions - y_batch
dw = (1 / len(X_batch)) * X_batch.T @ error
db = (1 / len(X_batch)) * np.sum(error)
weights -= learning_rate * dw
bias -= learning_rate * db
return weights, bias
特点:
- 使用小批量数据
- 平衡速度和稳定性(最常用)
高级优化器
1. 动量(Momentum)
class MomentumOptimizer:
def __init__(self, learning_rate=0.01, momentum=0.9):
self.learning_rate = learning_rate
self.momentum = momentum
self.v_w = None
self.v_b = None
def update(self, weights, biases, dw, db):
if self.v_w is None:
self.v_w = np.zeros_like(weights)
self.v_b = np.zeros_like(biases)
# 更新速度
self.v_w = self.momentum * self.v_w + self.learning_rate * dw
self.v_b = self.momentum * self.v_b + self.learning_rate * db
# 更新参数
weights -= self.v_w
biases -= self.v_b
return weights, biases
2. Adam(Adaptive Moment Estimation)
class AdamOptimizer:
def __init__(self, learning_rate=0.001, beta1=0.9, beta2=0.999, epsilon=1e-8):
self.learning_rate = learning_rate
self.beta1 = beta1
self.beta2 = beta2
self.epsilon = epsilon
self.m_w = None
self.v_w = None
self.m_b = None
self.v_b = None
self.t = 0
def update(self, weights, biases, dw, db):
if self.m_w is None:
self.m_w = np.zeros_like(weights)
self.v_w = np.zeros_like(weights)
self.m_b = np.zeros_like(biases)
self.v_b = np.zeros_like(biases)
self.t += 1
# 更新一阶矩估计
self.m_w = self.beta1 * self.m_w + (1 - self.beta1) * dw
self.m_b = self.beta1 * self.m_b + (1 - self.beta1) * db
# 更新二阶矩估计
self.v_w = self.beta2 * self.v_w + (1 - self.beta2) * (dw ** 2)
self.v_b = self.beta2 * self.v_b + (1 - self.beta2) * (db ** 2)
# 偏差修正
m_w_corrected = self.m_w / (1 - self.beta1 ** self.t)
m_b_corrected = self.m_b / (1 - self.beta1 ** self.t)
v_w_corrected = self.v_w / (1 - self.beta2 ** self.t)
v_b_corrected = self.v_b / (1 - self.beta2 ** self.t)
# 更新参数
weights -= self.learning_rate * m_w_corrected / (np.sqrt(v_w_corrected) + self.epsilon)
biases -= self.learning_rate * m_b_corrected / (np.sqrt(v_b_corrected) + self.epsilon)
return weights, biases
优化器对比:
| 优化器 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| SGD | 简单 | 收敛慢,震荡 | 小数据集 |
| Momentum | 加速收敛 | 需要调参 | 一般场景 |
| Adam | 自适应学习率 | 内存占用大 | 深度学习(最常用) |
完整的神经网络实现
完整的 MLP 类
import numpy as np
class NeuralNetwork:
def __init__(self, layers, activation='sigmoid', learning_rate=0.01):
self.layers = layers
self.activation = activation
self.learning_rate = learning_rate
self.weights = []
self.biases = []
# 初始化权重和偏置
for i in range(len(layers) - 1):
w = np.random.randn(layers[i], layers[i+1]) * np.sqrt(2.0 / layers[i])
b = np.zeros((1, layers[i+1]))
self.weights.append(w)
self.biases.append(b)
def sigmoid(self, x):
return 1 / (1 + np.exp(-np.clip(x, -250, 250)))
def relu(self, x):
return np.maximum(0, x)
def sigmoid_derivative(self, x):
s = self.sigmoid(x)
return s * (1 - s)
def relu_derivative(self, x):
return (x > 0).astype(float)
def activate(self, x):
if self.activation == 'sigmoid':
return self.sigmoid(x)
elif self.activation == 'relu':
return self.relu(x)
def activate_derivative(self, x):
if self.activation == 'sigmoid':
return self.sigmoid_derivative(x)
elif self.activation == 'relu':
return self.relu_derivative(x)
def forward(self, X):
self.activations = [X]
self.z_values = []
for i in range(len(self.weights)):
z = np.dot(self.activations[-1], self.weights[i]) + self.biases[i]
self.z_values.append(z)
a = self.activate(z)
self.activations.append(a)
return self.activations[-1]
def backward(self, X, y, output):
m = X.shape[0]
# 输出层误差
self.deltas = []
delta = (output - y) * self.activate_derivative(self.z_values[-1])
self.deltas.insert(0, delta)
# 隐藏层误差
for i in range(len(self.weights) - 2, -1, -1):
delta = np.dot(self.deltas[0], self.weights[i+1].T) * \
self.activate_derivative(self.z_values[i])
self.deltas.insert(0, delta)
# 计算梯度
self.dW = []
self.db = []
for i in range(len(self.weights)):
dW = np.dot(self.activations[i].T, self.deltas[i]) / m
db = np.sum(self.deltas[i], axis=0, keepdims=True) / m
self.dW.append(dW)
self.db.append(db)
def update_weights(self):
for i in range(len(self.weights)):
self.weights[i] -= self.learning_rate * self.dW[i]
self.biases[i] -= self.learning_rate * self.db[i]
def train(self, X, y, epochs=1000, batch_size=32, verbose=True):
for epoch in range(epochs):
# 打乱数据
indices = np.random.permutation(len(X))
X_shuffled = X[indices]
y_shuffled = y[indices]
# 分批训练
total_loss = 0
for i in range(0, len(X), batch_size):
X_batch = X_shuffled[i:i+batch_size]
y_batch = y_shuffled[i:i+batch_size]
# 前向传播
output = self.forward(X_batch)
# 计算损失
loss = np.mean((output - y_batch) ** 2)
total_loss += loss
# 反向传播
self.backward(X_batch, y_batch, output)
# 更新权重
self.update_weights()
if verbose and epoch % 100 == 0:
avg_loss = total_loss / (len(X) // batch_size)
print(f"Epoch {epoch}, Loss: {avg_loss:.4f}")
def predict(self, X):
return self.forward(X)
使用示例
# 生成示例数据
X = np.random.randn(1000, 10)
y = np.random.randn(1000, 1)
# 创建网络
nn = NeuralNetwork(layers=[10, 64, 32, 1], activation='relu', learning_rate=0.001)
# 训练
nn.train(X, y, epochs=1000, batch_size=32)
# 预测
predictions = nn.predict(X)
正则化技术
正则化防止过拟合,提高模型泛化能力。
1. L1 和 L2 正则化
def l2_regularization(weights, lambda_reg):
"""L2 正则化(权重衰减)"""
return lambda_reg * np.sum([np.sum(w ** 2) for w in weights])
def l1_regularization(weights, lambda_reg):
"""L1 正则化(Lasso)"""
return lambda_reg * np.sum([np.sum(np.abs(w)) for w in weights])
在损失函数中添加:
def compute_loss_with_regularization(y_pred, y_true, weights, lambda_reg, reg_type='l2'):
mse = np.mean((y_pred - y_true) ** 2)
if reg_type == 'l2':
reg = l2_regularization(weights, lambda_reg)
else:
reg = l1_regularization(weights, lambda_reg)
return mse + reg
2. Dropout
class Dropout:
def __init__(self, dropout_rate=0.5):
self.dropout_rate = dropout_rate
self.mask = None
def forward(self, x, training=True):
if training:
self.mask = np.random.binomial(1, 1 - self.dropout_rate, size=x.shape) / (1 - self.dropout_rate)
return x * self.mask
else:
return x
def backward(self, dout):
return dout * self.mask
3. 批量归一化(Batch Normalization)
class BatchNormalization:
def __init__(self, epsilon=1e-8):
self.epsilon = epsilon
self.gamma = None
self.beta = None
self.running_mean = None
self.running_var = None
def forward(self, x, training=True):
if training:
mean = np.mean(x, axis=0, keepdims=True)
var = np.var(x, axis=0, keepdims=True)
# 更新运行统计
if self.running_mean is None:
self.running_mean = mean
self.running_var = var
else:
self.running_mean = 0.9 * self.running_mean + 0.1 * mean
self.running_var = 0.9 * self.running_var + 0.1 * var
# 归一化
x_norm = (x - mean) / np.sqrt(var + self.epsilon)
else:
x_norm = (x - self.running_mean) / np.sqrt(self.running_var + self.epsilon)
# 缩放和偏移
if self.gamma is None:
self.gamma = np.ones((1, x.shape[1]))
self.beta = np.zeros((1, x.shape[1]))
return self.gamma * x_norm + self.beta
实践案例
案例1:手写数字识别(MNIST)
# 加载数据(需要先下载 MNIST 数据集)
from sklearn.datasets import fetch_openml
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
# 加载数据
mnist = fetch_openml('mnist_784', version=1)
X, y = mnist.data, mnist.target.astype(int)
# 二值化(简化问题)
X = (X > 127).astype(float)
y = (y == 0).astype(int).reshape(-1, 1) # 识别是否为 0
# 划分数据集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 创建网络
nn = NeuralNetwork(layers=[784, 128, 64, 1], activation='relu', learning_rate=0.001)
# 训练
nn.train(X_train, y_train, epochs=100, batch_size=32)
# 评估
predictions = nn.predict(X_test)
accuracy = np.mean((predictions > 0.5).astype(int) == y_test)
print(f"Accuracy: {accuracy:.4f}")
案例2:XOR 问题
# XOR 数据集
X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
y = np.array([[0], [1], [1], [0]])
# 创建网络(需要隐藏层才能解决 XOR)
nn = NeuralNetwork(layers=[2, 4, 1], activation='sigmoid', learning_rate=0.5)
# 训练
nn.train(X, y, epochs=10000, batch_size=4, verbose=False)
# 预测
predictions = nn.predict(X)
print("Predictions:")
print(predictions)
print("Expected:")
print(y)
总结
核心要点
-
神经网络基础
- 神经元模型
- 感知机和多层感知机
- 前向传播
-
反向传播算法
- 梯度计算
- 链式法则
- 权重更新
-
激活函数
- Sigmoid、Tanh、ReLU
- 选择合适的激活函数
-
损失函数和优化器
- MSE、交叉熵
- SGD、Adam
-
正则化
- L1/L2 正则化
- Dropout
- 批量归一化
学习路径建议
-
基础阶段
- 理解神经元和感知机
- 掌握前向传播
- 理解激活函数
-
进阶阶段
- 深入理解反向传播
- 实现完整的 MLP
- 学习优化器
-
实践阶段
- 解决实际问题
- 调参和优化
- 学习正则化技术
下一步学习
- 卷积神经网络(CNN):图像处理
- 循环神经网络(RNN):序列数据
- 注意力机制:Transformer
- 生成对抗网络(GAN):生成模型
- 强化学习:决策问题
常见问题与解决方案
梯度消失和梯度爆炸
问题描述:
在深层网络中,梯度在反向传播过程中可能变得非常小(梯度消失)或非常大(梯度爆炸),导致训练困难。
梯度消失的原因:
- 使用 Sigmoid 或 Tanh 激活函数时,导数最大值小于 1
- 深层网络中,多个小于 1 的导数相乘,梯度指数级衰减
梯度爆炸的原因:
- 权重初始化过大
- 深层网络中,梯度指数级增长
解决方案:
-
使用 ReLU 激活函数
# ReLU 的导数在正区间为 1,不会导致梯度消失 def relu(x): return np.maximum(0, x) -
梯度裁剪(Gradient Clipping)
def clip_gradients(gradients, max_norm=1.0): """裁剪梯度,防止梯度爆炸""" total_norm = np.sqrt(sum(np.sum(g**2) for g in gradients)) if total_norm > max_norm: scale = max_norm / total_norm gradients = [g * scale for g in gradients] return gradients -
残差连接(Residual Connections)
class ResidualBlock: def forward(self, x): # 残差连接:y = f(x) + x out = self.layer(x) return out + x # 跳跃连接 -
批量归一化
# Batch Normalization 可以稳定梯度 # 见前面的 BatchNormalization 类
权重初始化
好的初始化方法:
-
Xavier 初始化(Glorot)
def xavier_init(fan_in, fan_out): """Xavier 初始化,适合 Sigmoid 和 Tanh""" limit = np.sqrt(6.0 / (fan_in + fan_out)) return np.random.uniform(-limit, limit, (fan_in, fan_out)) -
He 初始化
def he_init(fan_in, fan_out): """He 初始化,适合 ReLU""" std = np.sqrt(2.0 / fan_in) return np.random.randn(fan_in, fan_out) * std -
改进的初始化
def initialize_weights(layers, init_type='he'): weights = [] for i in range(len(layers) - 1): fan_in = layers[i] fan_out = layers[i+1] if init_type == 'xavier': limit = np.sqrt(6.0 / (fan_in + fan_out)) w = np.random.uniform(-limit, limit, (fan_in, fan_out)) elif init_type == 'he': std = np.sqrt(2.0 / fan_in) w = np.random.randn(fan_in, fan_out) * std else: w = np.random.randn(fan_in, fan_out) * 0.1 weights.append(w) return weights
初始化方法对比:
| 方法 | 适用激活函数 | 公式 |
|---|---|---|
| 随机初始化 | 通用 | w ~ N(0, 0.01) |
| Xavier | Sigmoid, Tanh | w ~ U(-√6/(n_in+n_out), √6/(n_in+n_out)) |
| He | ReLU, Leaky ReLU | w ~ N(0, √2/n_in) |
超参数调优
重要超参数:
-
学习率(Learning Rate)
# 学习率调度 class LearningRateScheduler: def __init__(self, initial_lr=0.01, decay_rate=0.95, decay_step=100): self.initial_lr = initial_lr self.decay_rate = decay_rate self.decay_step = decay_step self.step = 0 def get_lr(self): """指数衰减学习率""" lr = self.initial_lr * (self.decay_rate ** (self.step // self.decay_step)) self.step += 1 return lr def step_decay(self, epoch): """阶梯衰减""" if epoch < 30: return 0.01 elif epoch < 60: return 0.001 else: return 0.0001 -
批量大小(Batch Size)
- 小批量(32-128):更频繁的更新,可能更稳定
- 大批量(256+):更快的训练,但可能陷入局部最优
- 网络深度和宽度
- 深度:更多层,学习更复杂的特征
- 宽度:每层更多神经元,增加容量
调优策略:
def hyperparameter_search():
"""网格搜索超参数"""
learning_rates = [0.001, 0.01, 0.1]
batch_sizes = [32, 64, 128]
hidden_sizes = [64, 128, 256]
best_score = -np.inf
best_params = None
for lr in learning_rates:
for bs in batch_sizes:
for hs in hidden_sizes:
# 训练模型
nn = NeuralNetwork(layers=[10, hs, 1], learning_rate=lr)
nn.train(X_train, y_train, batch_size=bs, epochs=100)
# 评估
score = evaluate(nn, X_val, y_val)
if score > best_score:
best_score = score
best_params = {'lr': lr, 'batch_size': bs, 'hidden_size': hs}
return best_params
调试技巧
1. 检查梯度
def check_gradients(network, X, y):
"""检查梯度是否正确"""
# 数值梯度
def numerical_gradient(f, x, h=1e-5):
grad = np.zeros_like(x)
it = np.nditer(x, flags=['multi_index'], op_flags=['readwrite'])
while not it.finished:
idx = it.multi_index
old_value = x[idx]
x[idx] = old_value + h
fxh1 = f(x)
x[idx] = old_value - h
fxh2 = f(x)
grad[idx] = (fxh1 - fxh2) / (2 * h)
x[idx] = old_value
it.iternext()
return grad
# 比较数值梯度和解析梯度
output = network.forward(X)
network.backward(X, y, output)
# 检查第一个权重矩阵的梯度
analytical_grad = network.dW[0]
# 计算数值梯度(简化版)
# ...
2. 监控训练过程
def train_with_monitoring(network, X_train, y_train, X_val, y_val, epochs=1000):
"""训练并监控过拟合"""
train_losses = []
val_losses = []
for epoch in range(epochs):
# 训练
output_train = network.forward(X_train)
train_loss = np.mean((output_train - y_train) ** 2)
network.backward(X_train, y_train, output_train)
network.update_weights()
# 验证
output_val = network.forward(X_val)
val_loss = np.mean((output_val - y_val) ** 2)
train_losses.append(train_loss)
val_losses.append(val_loss)
# 早停(Early Stopping)
if epoch > 100 and val_loss > min(val_losses[-100:]):
print(f"Early stopping at epoch {epoch}")
break
return train_losses, val_losses
3. 可视化
import matplotlib.pyplot as plt
def plot_training_curves(train_losses, val_losses):
"""绘制训练曲线"""
plt.figure(figsize=(10, 6))
plt.plot(train_losses, label='Train Loss')
plt.plot(val_losses, label='Validation Loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.legend()
plt.title('Training and Validation Loss')
plt.show()
高级主题
自编码器(Autoencoder)
自编码器是一种无监督学习模型,用于学习数据的压缩表示。
class Autoencoder:
def __init__(self, input_dim, encoding_dim):
# 编码器
self.encoder = NeuralNetwork(
layers=[input_dim, 128, 64, encoding_dim],
activation='relu'
)
# 解码器
self.decoder = NeuralNetwork(
layers=[encoding_dim, 64, 128, input_dim],
activation='sigmoid'
)
def encode(self, X):
"""编码:输入 -> 潜在表示"""
return self.encoder.forward(X)
def decode(self, encoded):
"""解码:潜在表示 -> 重构"""
return self.decoder.forward(encoded)
def forward(self, X):
"""前向传播:编码 -> 解码"""
encoded = self.encode(X)
decoded = self.decode(encoded)
return decoded
def train(self, X, epochs=1000):
"""训练自编码器"""
for epoch in range(epochs):
# 重构
reconstructed = self.forward(X)
# 重构误差
loss = np.mean((X - reconstructed) ** 2)
# 反向传播(需要实现)
# ...
if epoch % 100 == 0:
print(f"Epoch {epoch}, Loss: {loss:.4f}")
变分自编码器(VAE)
VAE 在自编码器基础上,学习数据的概率分布。
class VAE:
def __init__(self, input_dim, latent_dim):
self.latent_dim = latent_dim
# 编码器:输出均值和方差
self.encoder_mean = NeuralNetwork([input_dim, 128, latent_dim])
self.encoder_logvar = NeuralNetwork([input_dim, 128, latent_dim])
# 解码器
self.decoder = NeuralNetwork([latent_dim, 128, input_dim])
def encode(self, X):
"""编码:输出潜在空间的均值和方差"""
mean = self.encoder_mean.forward(X)
logvar = self.encoder_logvar.forward(X)
return mean, logvar
def reparameterize(self, mean, logvar):
"""重参数化技巧"""
std = np.exp(0.5 * logvar)
epsilon = np.random.randn(*std.shape)
return mean + std * epsilon
def decode(self, z):
"""解码"""
return self.decoder.forward(z)
def forward(self, X):
"""前向传播"""
mean, logvar = self.encode(X)
z = self.reparameterize(mean, logvar)
reconstructed = self.decode(z)
return reconstructed, mean, logvar
def loss(self, X, reconstructed, mean, logvar):
"""VAE 损失:重构损失 + KL 散度"""
# 重构损失
recon_loss = np.mean((X - reconstructed) ** 2)
# KL 散度(正则化项)
kl_loss = -0.5 * np.sum(1 + logvar - mean**2 - np.exp(logvar))
return recon_loss + kl_loss
注意力机制基础
注意力机制允许模型关注输入的不同部分。
class Attention:
def __init__(self, hidden_dim):
self.hidden_dim = hidden_dim
self.W_q = np.random.randn(hidden_dim, hidden_dim) * 0.1
self.W_k = np.random.randn(hidden_dim, hidden_dim) * 0.1
self.W_v = np.random.randn(hidden_dim, hidden_dim) * 0.1
def forward(self, query, key, value):
"""计算注意力"""
# 计算 Q, K, V
Q = query @ self.W_q
K = key @ self.W_k
V = value @ self.W_v
# 计算注意力分数
scores = Q @ K.T / np.sqrt(self.hidden_dim)
# Softmax
attention_weights = softmax(scores, axis=1)
# 加权求和
output = attention_weights @ V
return output, attention_weights
性能优化
计算优化
1. 向量化操作
# 慢:循环
def slow_forward(X, weights, bias):
output = np.zeros((X.shape[0], weights.shape[1]))
for i in range(X.shape[0]):
for j in range(weights.shape[1]):
output[i, j] = np.sum(X[i] * weights[:, j]) + bias[j]
return output
# 快:向量化
def fast_forward(X, weights, bias):
return X @ weights + bias
2. 使用 NumPy 优化
# 使用 einsum 进行复杂计算
def einsum_example(A, B, C):
# 等价于 np.sum(A[:, :, None] * B[None, :, :] * C[:, None, :], axis=1)
return np.einsum('ij,jk,ik->i', A, B, C)
内存优化
1. 梯度检查点
# 不存储所有中间值,需要时重新计算
class CheckpointedNetwork:
def forward(self, X, checkpoint=False):
if checkpoint:
# 只存储关键节点
# 需要时重新计算
pass
else:
# 正常前向传播
pass
2. 混合精度训练
# 使用 float16 减少内存占用
X_float16 = X.astype(np.float16)
# 注意:需要处理精度问题
实践建议
开发流程
-
从简单开始
- 先实现单层网络
- 逐步增加复杂度
- 验证每一步
-
使用小数据集测试
- 先用小数据集验证代码正确性
- 再扩展到完整数据集
-
逐步调试
- 检查前向传播
- 检查反向传播
- 检查梯度更新
-
监控训练
- 绘制损失曲线
- 监控验证集性能
- 使用早停防止过拟合
常见错误
-
维度不匹配
# 错误 output = X @ weights # 如果维度不匹配会报错 # 正确:检查维度 assert X.shape[1] == weights.shape[0], "维度不匹配" output = X @ weights -
忘记转置
# 错误 gradient = X @ delta # 维度可能不对 # 正确 gradient = X.T @ delta -
激活函数应用错误
# 错误:在输出层使用错误的激活函数 output = sigmoid(linear_output) # 对于回归问题可能不合适 # 正确:根据任务选择 if task == 'classification': output = sigmoid(linear_output) elif task == 'regression': output = linear_output
总结
核心要点回顾
-
神经网络基础
- 神经元模型和感知机
- 多层感知机(MLP)
- 前向传播
-
反向传播算法
- 链式法则
- 梯度计算
- 权重更新
-
激活函数
- Sigmoid、Tanh、ReLU
- 选择合适的激活函数
-
损失函数和优化器
- MSE、交叉熵
- SGD、Adam
-
正则化技术
- L1/L2 正则化
- Dropout
- 批量归一化
-
常见问题
- 梯度消失/爆炸
- 权重初始化
- 超参数调优
学习路径
- 基础阶段:理解神经元、感知机、前向传播
- 进阶阶段:掌握反向传播、实现完整 MLP
- 高级阶段:学习正则化、优化技巧、解决实际问题
下一步学习方向
- 卷积神经网络(CNN):图像识别和处理
- 循环神经网络(RNN/LSTM):序列数据和自然语言处理
- Transformer:注意力机制和现代 NLP
- 生成模型:GAN、VAE、扩散模型
- 强化学习:决策和游戏 AI
参考资料:
- Neural Networks: Zero to Hero - Andrej Karpathy 的神经网络教程系列
- 《深度学习》(Deep Learning)- Ian Goodfellow, Yoshua Bengio, Aaron Courville
- 《神经网络与深度学习》- 邱锡鹏
- Neural Networks and Deep Learning - Michael Nielsen
- CS231n: Convolutional Neural Networks for Visual Recognition - Stanford University
- Deep Learning Specialization - Andrew Ng
Originally published on mlangTse's Blog. View source