All articles

神经网络原理与实践

从神经元、反向传播到训练实践,系统整理神经网络的基础原理与实现思路。

2025-10-26 · Updated 2025-11-24 · 22 分钟阅读

本文基于 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)

反向传播是训练神经网络的核心算法,通过计算梯度来更新权重。

算法原理

步骤:

  1. 前向传播:计算网络输出
  2. 计算损失:比较输出和真实值
  3. 反向传播:计算梯度
  4. 更新权重:使用梯度下降更新参数

数学推导:

对于输出层:

δᴸ = (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)

总结

核心要点

  1. 神经网络基础

    • 神经元模型
    • 感知机和多层感知机
    • 前向传播
  2. 反向传播算法

    • 梯度计算
    • 链式法则
    • 权重更新
  3. 激活函数

    • Sigmoid、Tanh、ReLU
    • 选择合适的激活函数
  4. 损失函数和优化器

    • MSE、交叉熵
    • SGD、Adam
  5. 正则化

    • L1/L2 正则化
    • Dropout
    • 批量归一化

学习路径建议

  1. 基础阶段

    • 理解神经元和感知机
    • 掌握前向传播
    • 理解激活函数
  2. 进阶阶段

    • 深入理解反向传播
    • 实现完整的 MLP
    • 学习优化器
  3. 实践阶段

    • 解决实际问题
    • 调参和优化
    • 学习正则化技术

下一步学习

  • 卷积神经网络(CNN):图像处理
  • 循环神经网络(RNN):序列数据
  • 注意力机制:Transformer
  • 生成对抗网络(GAN):生成模型
  • 强化学习:决策问题

常见问题与解决方案

梯度消失和梯度爆炸

问题描述:

在深层网络中,梯度在反向传播过程中可能变得非常小(梯度消失)或非常大(梯度爆炸),导致训练困难。

梯度消失的原因:

  • 使用 Sigmoid 或 Tanh 激活函数时,导数最大值小于 1
  • 深层网络中,多个小于 1 的导数相乘,梯度指数级衰减

梯度爆炸的原因:

  • 权重初始化过大
  • 深层网络中,梯度指数级增长

解决方案:

  1. 使用 ReLU 激活函数

    # ReLU 的导数在正区间为 1,不会导致梯度消失
    def relu(x):
        return np.maximum(0, x)
    
  2. 梯度裁剪(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
    
  3. 残差连接(Residual Connections)

    class ResidualBlock:
        def forward(self, x):
            # 残差连接:y = f(x) + x
            out = self.layer(x)
            return out + x  # 跳跃连接
    
  4. 批量归一化

    # Batch Normalization 可以稳定梯度
    # 见前面的 BatchNormalization 类
    

权重初始化

好的初始化方法:

  1. 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))
    
  2. 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
    
  3. 改进的初始化

    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)
XavierSigmoid, Tanhw ~ U(-√6/(n_in+n_out), √6/(n_in+n_out))
HeReLU, Leaky ReLUw ~ N(0, √2/n_in)

超参数调优

重要超参数:

  1. 学习率(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
    
  2. 批量大小(Batch Size)

  • 小批量(32-128):更频繁的更新,可能更稳定
  • 大批量(256+):更快的训练,但可能陷入局部最优
  1. 网络深度和宽度
  • 深度:更多层,学习更复杂的特征
  • 宽度:每层更多神经元,增加容量

调优策略:

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)
# 注意:需要处理精度问题

实践建议

开发流程

  1. 从简单开始

    • 先实现单层网络
    • 逐步增加复杂度
    • 验证每一步
  2. 使用小数据集测试

    • 先用小数据集验证代码正确性
    • 再扩展到完整数据集
  3. 逐步调试

    • 检查前向传播
    • 检查反向传播
    • 检查梯度更新
  4. 监控训练

    • 绘制损失曲线
    • 监控验证集性能
    • 使用早停防止过拟合

常见错误

  1. 维度不匹配

    # 错误
    output = X @ weights  # 如果维度不匹配会报错
    
    # 正确:检查维度
    assert X.shape[1] == weights.shape[0], "维度不匹配"
    output = X @ weights
    
  2. 忘记转置

    # 错误
    gradient = X @ delta  # 维度可能不对
    
    # 正确
    gradient = X.T @ delta
    
  3. 激活函数应用错误

    # 错误:在输出层使用错误的激活函数
    output = sigmoid(linear_output)  # 对于回归问题可能不合适
    
    # 正确:根据任务选择
    if task == 'classification':
        output = sigmoid(linear_output)
    elif task == 'regression':
        output = linear_output
    

总结

核心要点回顾

  1. 神经网络基础

    • 神经元模型和感知机
    • 多层感知机(MLP)
    • 前向传播
  2. 反向传播算法

    • 链式法则
    • 梯度计算
    • 权重更新
  3. 激活函数

    • Sigmoid、Tanh、ReLU
    • 选择合适的激活函数
  4. 损失函数和优化器

    • MSE、交叉熵
    • SGD、Adam
  5. 正则化技术

    • L1/L2 正则化
    • Dropout
    • 批量归一化
  6. 常见问题

    • 梯度消失/爆炸
    • 权重初始化
    • 超参数调优

学习路径

  1. 基础阶段:理解神经元、感知机、前向传播
  2. 进阶阶段:掌握反向传播、实现完整 MLP
  3. 高级阶段:学习正则化、优化技巧、解决实际问题

下一步学习方向

  • 卷积神经网络(CNN):图像识别和处理
  • 循环神经网络(RNN/LSTM):序列数据和自然语言处理
  • Transformer:注意力机制和现代 NLP
  • 生成模型:GAN、VAE、扩散模型
  • 强化学习:决策和游戏 AI

参考资料:

Originally published on mlangTse's Blog. View source