神经网络基础:从感知机到反向传播

1 阅读1分钟

在前面几节中,我们学习了传统机器学习方法、知识表示与检索以及强化学习。今天,我们将进入深度学习的世界,从最基础的感知机开始,逐步深入到多层感知机和反向传播算法,这些是现代深度学习的基石。

神经网络发展概览

神经网络的发展经历了从简单到复杂、从浅层到深层的过程,每一次突破都推动了AI技术的巨大进步。

timeline
    title 神经网络发展史
    1943年 : McCulloch-Pitts神经元模型提出
    1957年 : 感知机(Perceptron)诞生
    1969年 : 感知机局限性被指出,第一次AI寒冬
    1986年 : 反向传播算法被重新发现
    1998年 : LeNet-5在手写数字识别上取得突破
    2006年 : 深度学习概念提出,神经网络复兴
    2012年 : AlexNet在ImageNet竞赛中大获成功
    2017年 : Transformer架构改变NLP领域

感知机详解

感知机是最早的人工神经网络模型,由Frank Rosenblatt在1957年提出。

感知机结构

感知机由输入层和输出层组成,通过学习权重和偏置来实现线性分类。

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

# 感知机实现
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
        
        # 将标签转换为-1和1
        y_ = np.where(y <= 0, -1, 1)
        
        # 训练过程
        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 = np.where(linear_output >= 0, 1, -1)
                
                # 更新权重和偏置
                update = self.learning_rate * (y_[idx] - y_predicted)
                self.weights += update * x_i
                self.bias += update
    
    def predict(self, X):
        """预测"""
        linear_output = np.dot(X, self.weights) + self.bias
        return np.where(linear_output >= 0, 1, 0)
    
    def decision_boundary(self, x):
        """计算决策边界"""
        return -(self.weights[0] * x + self.bias) / self.weights[1] if self.weights[1] != 0 else 0

# 生成线性可分数据
X_linear, y_linear = make_classification(
    n_samples=100, 
    n_features=2, 
    n_redundant=0, 
    n_informative=2,
    n_clusters_per_class=1,
    random_state=42
)

# 训练感知机
perceptron = Perceptron(learning_rate=0.01, n_iterations=1000)
perceptron.fit(X_linear, y_linear)

# 预测
predictions = perceptron.predict(X_linear)
accuracy = np.mean(predictions == y_linear)

print(f"感知机在训练集上的准确率: {accuracy:.4f}")

# 可视化感知机分类结果
plt.figure(figsize=(12, 5))

plt.subplot(1, 2, 1)
# 绘制原始数据
scatter = plt.scatter(X_linear[:, 0], X_linear[:, 1], c=y_linear, cmap='viridis', alpha=0.7)
plt.xlabel('特征 1')
plt.ylabel('特征 2')
plt.title('原始数据')
plt.colorbar(scatter)

plt.subplot(1, 2, 2)
# 绘制分类结果
scatter = plt.scatter(X_linear[:, 0], X_linear[:, 1], c=predictions, cmap='viridis', alpha=0.7)
# 绘制决策边界
x_range = np.linspace(X_linear[:, 0].min()-1, X_linear[:, 0].max()+1, 100)
y_range = perceptron.decision_boundary(x_range)
plt.plot(x_range, y_range, 'r-', linewidth=2, label='决策边界')
plt.xlabel('特征 1')
plt.ylabel('特征 2')
plt.title(f'感知机分类结果 (准确率: {accuracy:.4f})')
plt.legend()
plt.colorbar(scatter)

plt.tight_layout()
plt.show()

# 感知机权重可视化
print(f"学习到的权重: {perceptron.weights}")
print(f"学习到的偏置: {perceptron.bias}")

感知机的局限性

感知机只能解决线性可分问题,无法解决XOR等非线性问题。

# XOR问题演示
def xor_problem_demo():
    """XOR问题演示感知机的局限性"""
    # XOR数据
    X_xor = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
    y_xor = np.array([0, 1, 1, 0])
    
    # 训练感知机
    xor_perceptron = Perceptron(learning_rate=0.1, n_iterations=1000)
    xor_perceptron.fit(X_xor, y_xor)
    
    # 预测
    xor_predictions = xor_perceptron.predict(X_xor)
    
    print("XOR问题:")
    print("输入\t\t实际输出\t感知机输出")
    for i in range(len(X_xor)):
        print(f"{X_xor[i]}\t\t{y_xor[i]}\t\t{xor_predictions[i]}")
    
    print("\n注意:感知机无法正确解决XOR问题,因为XOR不是线性可分的!")
    
    # 可视化
    plt.figure(figsize=(10, 6))
    
    # 绘制数据点
    colors = ['blue', 'red', 'red', 'blue']  # XOR模式
    markers = ['s', 'o', 'o', 's']
    for i in range(len(X_xor)):
        plt.scatter(X_xor[i, 0], X_xor[i, 1], c=colors[i], s=100, marker=markers[i],
                   label=f'类别 {y_xor[i]}' if i in [0, 1] else "")
    
    # 绘制决策边界
    x_range = np.linspace(-0.5, 1.5, 100)
    y_range = xor_perceptron.decision_boundary(x_range)
    plt.plot(x_range, y_range, 'g--', linewidth=2, label='感知机决策边界')
    
    plt.xlabel('输入1')
    plt.ylabel('输入2')
    plt.title('感知机尝试解决XOR问题(线性不可分)')
    plt.legend()
    plt.grid(True, alpha=0.3)
    plt.xlim(-0.5, 1.5)
    plt.ylim(-0.5, 1.5)
    plt.show()

xor_problem_demo()

多层感知机(MLP)

多层感知机通过增加隐藏层来解决非线性问题,是现代神经网络的基础。

MLP结构

MLP由输入层、一个或多个隐藏层和输出层组成,每层包含多个神经元。

# 神经元实现
class Neuron:
    """神经元实现"""
    
    def __init__(self, n_inputs):
        self.weights = np.random.randn(n_inputs) * 0.1
        self.bias = np.random.randn() * 0.1
    
    def activate(self, inputs):
        """激活函数(使用Sigmoid)"""
        z = np.dot(inputs, self.weights) + self.bias
        return 1 / (1 + np.exp(-np.clip(z, -500, 500)))  # 防止溢出

# 多层感知机实现
class MLP:
    """多层感知机实现"""
    
    def __init__(self, layer_sizes, learning_rate=0.1):
        self.layer_sizes = layer_sizes
        self.learning_rate = learning_rate
        self.layers = []
        
        # 初始化网络层
        for i in range(1, len(layer_sizes)):
            layer = []
            for _ in range(layer_sizes[i]):
                layer.append(Neuron(layer_sizes[i-1]))
            self.layers.append(layer)
    
    def forward(self, inputs):
        """前向传播"""
        self.activations = [inputs]
        
        for layer in self.layers:
            layer_outputs = []
            for neuron in layer:
                output = neuron.activate(self.activations[-1])
                layer_outputs.append(output)
            self.activations.append(np.array(layer_outputs))
        
        return self.activations[-1]
    
    def backward(self, targets):
        """反向传播"""
        # 计算输出层误差
        output = self.activations[-1]
        output_error = targets - output
        output_delta = output_error * output * (1 - output)
        
        # 计算隐藏层误差
        deltas = [output_delta]
        for i in range(len(self.layers) - 2, -1, -1):
            layer = self.layers[i]
            layer_delta = []
            for j, neuron in enumerate(layer):
                error = 0
                next_layer = self.layers[i + 1]
                for k, next_neuron in enumerate(next_layer):
                    error += next_neuron.weights[j] * deltas[0][k]
                delta = error * self.activations[i+1][j] * (1 - self.activations[i+1][j])
                layer_delta.append(delta)
            deltas.insert(0, np.array(layer_delta))
        
        # 更新权重和偏置
        for i, layer in enumerate(self.layers):
            for j, neuron in enumerate(layer):
                for k in range(len(neuron.weights)):
                    neuron.weights[k] += self.learning_rate * deltas[i][j] * self.activations[i][k]
                neuron.bias += self.learning_rate * deltas[i][j]
    
    def train(self, X, y, epochs=1000):
        """训练网络"""
        losses = []
        
        for epoch in range(epochs):
            total_loss = 0
            for i in range(len(X)):
                # 前向传播
                output = self.forward(X[i])
                # 计算损失
                loss = 0.5 * np.sum((y[i] - output) ** 2)
                total_loss += loss
                # 反向传播
                self.backward(y[i])
            
            avg_loss = total_loss / len(X)
            losses.append(avg_loss)
            
            if epoch % 100 == 0:
                print(f"Epoch {epoch}, Loss: {avg_loss:.6f}")
        
        return losses

# 使用MLP解决XOR问题
def mlp_xor_demo():
    """MLP解决XOR问题"""
    # XOR数据
    X_xor = np.array([[0, 0], [0, 1], [1, 0], [1, 1]], dtype=float)
    y_xor = np.array([[0], [1], [1], [0]], dtype=float)
    
    # 创建MLP (2输入 -> 4隐藏 -> 1输出)
    mlp = MLP([2, 4, 1], learning_rate=5.0)
    
    # 训练
    print("训练MLP解决XOR问题:")
    losses = mlp.train(X_xor, y_xor, epochs=2000)
    
    # 测试
    print("\nMLP解决XOR问题结果:")
    print("输入\t\t目标输出\t实际输出")
    for i in range(len(X_xor)):
        output = mlp.forward(X_xor[i])
        print(f"{X_xor[i]}\t\t{y_xor[i][0]}\t\t{output[0]:.4f}")
    
    # 绘制损失曲线
    plt.figure(figsize=(12, 5))
    
    plt.subplot(1, 2, 1)
    plt.plot(losses)
    plt.xlabel('Epoch')
    plt.ylabel('Loss')
    plt.title('训练损失曲线')
    plt.grid(True, alpha=0.3)
    
    # 可视化决策边界
    plt.subplot(1, 2, 2)
    # 创建网格点
    xx, yy = np.meshgrid(np.linspace(-0.5, 1.5, 100),
                         np.linspace(-0.5, 1.5, 100))
    grid_points = np.c_[xx.ravel(), yy.ravel()]
    
    # 预测网格点
    Z = np.array([mlp.forward(point)[0] for point in grid_points])
    Z = Z.reshape(xx.shape)
    
    # 绘制决策边界
    plt.contourf(xx, yy, Z, levels=50, alpha=0.8, cmap='viridis')
    plt.colorbar(label='输出值')
    
    # 绘制原始数据点
    colors = ['blue', 'red', 'red', 'blue']
    markers = ['s', 'o', 'o', 's']
    for i in range(len(X_xor)):
        plt.scatter(X_xor[i, 0], X_xor[i, 1], c=colors[i], s=100, marker=markers[i],
                   edgecolors='white', linewidth=2)
    
    plt.xlabel('输入1')
    plt.ylabel('输入2')
    plt.title('MLP解决XOR问题的决策边界')
    plt.grid(True, alpha=0.3)
    
    plt.tight_layout()
    plt.show()

mlp_xor_demo()

反向传播算法详解

反向传播是训练神经网络的核心算法,通过链式法则计算梯度并更新网络参数。

反向传播原理

反向传播通过以下步骤计算梯度:

  1. 前向传播计算输出
  2. 计算输出误差
  3. 反向传播误差
  4. 计算参数梯度
  5. 更新参数
# 详细的反向传播实现
class DetailedMLP:
    """详细MLP实现,展示反向传播过程"""
    
    def __init__(self, input_size, hidden_size, output_size, learning_rate=0.1):
        self.input_size = input_size
        self.hidden_size = hidden_size
        self.output_size = output_size
        self.learning_rate = learning_rate
        
        # 初始化权重和偏置
        self.W1 = np.random.randn(self.input_size, self.hidden_size) * 0.1
        self.b1 = np.zeros((1, self.hidden_size))
        self.W2 = np.random.randn(self.hidden_size, self.output_size) * 0.1
        self.b2 = np.zeros((1, self.output_size))
    
    def sigmoid(self, x):
        """Sigmoid激活函数"""
        return 1 / (1 + np.exp(-np.clip(x, -500, 500)))
    
    def sigmoid_derivative(self, x):
        """Sigmoid函数的导数"""
        return x * (1 - x)
    
    def forward(self, X):
        """前向传播"""
        self.z1 = np.dot(X, self.W1) + self.b1
        self.a1 = self.sigmoid(self.z1)
        self.z2 = np.dot(self.a1, self.W2) + self.b2
        self.a2 = self.sigmoid(self.z2)
        return self.a2
    
    def backward(self, X, y, output):
        """反向传播"""
        m = X.shape[0]
        
        # 输出层误差
        dz2 = output - y
        dW2 = (1/m) * np.dot(self.a1.T, dz2)
        db2 = (1/m) * np.sum(dz2, axis=0, keepdims=True)
        
        # 隐藏层误差
        da1 = np.dot(dz2, self.W2.T)
        dz1 = da1 * self.sigmoid_derivative(self.a1)
        dW1 = (1/m) * np.dot(X.T, dz1)
        db1 = (1/m) * np.sum(dz1, axis=0, keepdims=True)
        
        # 更新参数
        self.W2 -= self.learning_rate * dW2
        self.b2 -= self.learning_rate * db2
        self.W1 -= self.learning_rate * dW1
        self.b1 -= self.learning_rate * db1
    
    def train(self, X, y, epochs=1000):
        """训练网络"""
        losses = []
        
        for epoch in range(epochs):
            # 前向传播
            output = self.forward(X)
            
            # 计算损失 (均方误差)
            loss = np.mean((output - y) ** 2)
            losses.append(loss)
            
            # 反向传播
            self.backward(X, y, output)
            
            if epoch % 200 == 0:
                print(f"Epoch {epoch}, Loss: {loss:.6f}")
        
        return losses
    
    def predict(self, X):
        """预测"""
        return self.forward(X)

# 使用详细MLP进行分类任务
def detailed_mlp_demo():
    """详细MLP演示"""
    # 生成分类数据
    X, y = make_classification(
        n_samples=200,
        n_features=2,
        n_redundant=0,
        n_informative=2,
        n_clusters_per_class=1,
        random_state=42
    )
    
    # 转换标签格式
    y = y.reshape(-1, 1)
    
    # 分割数据
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
    
    # 标准化数据
    X_mean, X_std = X_train.mean(axis=0), X_train.std(axis=0)
    X_train = (X_train - X_mean) / X_std
    X_test = (X_test - X_mean) / X_std
    
    # 创建并训练MLP
    mlp = DetailedMLP(input_size=2, hidden_size=10, output_size=1, learning_rate=1.0)
    
    print("训练详细MLP:")
    losses = mlp.train(X_train, y_train, epochs=1000)
    
    # 预测
    train_pred = (mlp.predict(X_train) > 0.5).astype(int)
    test_pred = (mlp.predict(X_test) > 0.5).astype(int)
    
    train_accuracy = np.mean(train_pred == y_train)
    test_accuracy = np.mean(test_pred == y_test)
    
    print(f"\n训练集准确率: {train_accuracy:.4f}")
    print(f"测试集准确率: {test_accuracy:.4f}")
    
    # 可视化结果
    plt.figure(figsize=(15, 5))
    
    # 损失曲线
    plt.subplot(1, 3, 1)
    plt.plot(losses)
    plt.xlabel('Epoch')
    plt.ylabel('Loss')
    plt.title('训练损失曲线')
    plt.grid(True, alpha=0.3)
    
    # 训练集分类结果
    plt.subplot(1, 3, 2)
    scatter = plt.scatter(X_train[:, 0], X_train[:, 1], c=train_pred.ravel(), cmap='viridis', alpha=0.7)
    plt.xlabel('特征 1')
    plt.ylabel('特征 2')
    plt.title(f'训练集分类结果 (准确率: {train_accuracy:.4f})')
    plt.colorbar(scatter)
    plt.grid(True, alpha=0.3)
    
    # 测试集分类结果
    plt.subplot(1, 3, 3)
    scatter = plt.scatter(X_test[:, 0], X_test[:, 1], c=test_pred.ravel(), cmap='viridis', alpha=0.7)
    plt.xlabel('特征 1')
    plt.ylabel('特征 2')
    plt.title(f'测试集分类结果 (准确率: {test_accuracy:.4f})')
    plt.colorbar(scatter)
    plt.grid(True, alpha=0.3)
    
    plt.tight_layout()
    plt.show()

detailed_mlp_demo()

神经网络的挑战与解决方案

神经网络在训练过程中面临梯度消失和梯度爆炸等问题,现代技术提供了多种解决方案。

# 梯度消失问题演示
def gradient_vanishing_demo():
    """梯度消失问题演示"""
    print("梯度消失问题说明:")
    print("在深层网络中,梯度在反向传播过程中会逐层减小,导致前面层的权重更新缓慢。")
    print("这使得深层网络难以训练。")
    
    # 模拟深层网络中的梯度变化
    depths = range(1, 21)
    # 假设每层的梯度衰减因子为0.8
    gradient_factors = [0.8 ** d for d in depths]
    
    plt.figure(figsize=(10, 6))
    plt.plot(depths, gradient_factors, 'bo-')
    plt.xlabel('网络深度')
    plt.ylabel('梯度相对大小')
    plt.title('梯度消失问题演示')
    plt.grid(True, alpha=0.3)
    plt.yscale('log')
    plt.show()
    
    print("解决方案:")
    print("1. 使用ReLU等激活函数替代Sigmoid")
    print("2. 批量归一化(Batch Normalization)")
    print("3. 残差连接(Residual Connections)")
    print("4. 合适的权重初始化(Xavier/He初始化)")

gradient_vanishing_demo()

# 现代神经网络技术
def modern_nn_techniques():
    """现代神经网络技术"""
    techniques = {
        '激活函数': ['ReLU', 'Leaky ReLU', 'ELU', 'Swish'],
        '正则化': ['Dropout', 'BatchNorm', 'LayerNorm'],
        '优化器': ['SGD', 'Adam', 'RMSprop', 'AdaGrad'],
        '初始化': ['Xavier', 'He', 'LeCun'],
        '架构': ['ResNet', 'DenseNet', 'EfficientNet']
    }
    
    fig, axes = plt.subplots(2, 3, figsize=(15, 10))
    axes = axes.flatten()
    
    for i, (category, items) in enumerate(techniques.items()):
        if i < len(axes):
            ax = axes[i]
            y_pos = np.arange(len(items))
            ax.barh(y_pos, [1]*len(items), color=plt.cm.Set3(np.linspace(0, 1, len(items))))
            ax.set_yticks(y_pos)
            ax.set_yticklabels(items)
            ax.set_title(category)
            ax.set_xlabel('重要性')
            ax.grid(True, alpha=0.3)
    
    # 隐藏多余的子图
    for i in range(len(techniques), len(axes)):
        axes[i].set_visible(False)
    
    plt.tight_layout()
    plt.show()

modern_nn_techniques()

print("现代神经网络的关键技术:")
print("1. ReLU激活函数: 解决梯度消失问题")
print("2. 批量归一化: 加速训练并提高稳定性")
print("3. Dropout: 防止过拟合")
print("4. Adam优化器: 自适应学习率")
print("5. 残差连接: 允许训练更深的网络")

本周学习总结

今天我们深入学习了神经网络的基础知识:

  1. 感知机

    • 理解了感知机的工作原理
    • 认识了感知机的局限性(只能解决线性可分问题)
  2. 多层感知机

    • 学习了MLP的结构和工作原理
    • 实现了MLP解决XOR问题
  3. 反向传播算法

    • 掌握了反向传播的数学原理
    • 实现了详细的反向传播过程
  4. 现代技术

    • 了解了梯度消失问题及其解决方案
    • 学习了现代神经网络的关键技术
graph TD
    A[神经网络基础] --> B[感知机]
    A --> C[多层感知机]
    A --> D[反向传播]
    A --> E[现代技术]
    B --> F[结构]
    B --> G[局限性]
    C --> H[隐藏层]
    C --> I[非线性]
    D --> J[链式法则]
    D --> K[梯度计算]
    E --> L[梯度问题]
    E --> M[解决方案]

课后练习

  1. 运行本节所有代码示例,理解神经网络的工作原理
  2. 修改MLP的结构(增加隐藏层、改变神经元数量),观察性能变化
  3. 实现不同的激活函数(如tanh、ReLU),比较它们的效果
  4. 研究批量归一化和Dropout的实现原理

下节预告

下一节我们将学习现代神经网络架构,包括卷积神经网络(CNN)和循环神经网络(RNN),这些是处理图像和序列数据的重要工具,敬请期待!


有任何疑问请在讨论区留言,我们会定期回复大家的问题。