现代神经网络架构:CNN与RNN详解

5 阅读1分钟

在上一节中,我们学习了神经网络的基础知识,包括感知机、多层感知机和反向传播算法。今天,我们将深入学习两种重要的现代神经网络架构:卷积神经网络(CNN)和循环神经网络(RNN),它们分别在图像处理和序列数据处理方面表现出色。

现代神经网络架构概览

现代深度学习主要依赖几种核心神经网络架构,每种架构都针对特定类型的数据和任务进行了优化。

graph TD
    A[神经网络架构] --> B[CNN]
    A --> C[RNN]
    A --> D[Transformer]
    B --> E[图像处理]
    B --> F[卷积层]
    B --> G[池化层]
    C --> H[序列数据]
    C --> I[循环层]
    C --> J[LSTM/GRU]
    D --> K[注意力机制]
    D --> L[自注意力]

卷积神经网络(CNN)

卷积神经网络专门用于处理具有网格结构的数据,如图像。它通过卷积层、池化层和全连接层的组合来提取特征。

CNN核心概念

CNN的核心组件包括:

  1. 卷积层(Convolutional Layer) - 提取局部特征
  2. 池化层(Pooling Layer) - 降低维度和参数数量
  3. 全连接层(Fully Connected Layer) - 进行分类或其他任务
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split

# 简单的CNN实现
class SimpleCNN:
    """简化版CNN实现"""
    
    def __init__(self):
        # 初始化卷积核 (3x3 filters)
        self.conv_filters = [np.random.randn(3, 3) * 0.1 for _ in range(4)]
        # 初始化全连接层权重
        self.fc_weights = np.random.randn(4 * 6 * 6, 10) * 0.1
        self.fc_bias = np.zeros(10)
    
    def relu(self, x):
        """ReLU激活函数"""
        return np.maximum(0, x)
    
    def softmax(self, x):
        """Softmax函数"""
        exp_x = np.exp(x - np.max(x, axis=1, keepdims=True))
        return exp_x / np.sum(exp_x, axis=1, keepdims=True)
    
    def conv2d(self, image, kernel, stride=1):
        """2D卷积操作"""
        image_h, image_w = image.shape
        kernel_h, kernel_w = kernel.shape
        
        output_h = (image_h - kernel_h) // stride + 1
        output_w = (image_w - kernel_w) // stride + 1
        
        output = np.zeros((output_h, output_w))
        
        for i in range(0, output_h):
            for j in range(0, output_w):
                region = image[i:i+kernel_h, j:j+kernel_w]
                output[i, j] = np.sum(region * kernel)
        
        return output
    
    def max_pool2d(self, image, pool_size=2, stride=2):
        """2D最大池化操作"""
        image_h, image_w = image.shape
        output_h = (image_h - pool_size) // stride + 1
        output_w = (image_w - pool_size) // stride + 1
        
        output = np.zeros((output_h, output_w))
        
        for i in range(0, output_h):
            for j in range(0, output_w):
                region = image[i*stride:i*stride+pool_size, j*stride:j*stride+pool_size]
                output[i, j] = np.max(region)
        
        return output
    
    def forward(self, image):
        """前向传播"""
        # 卷积层
        conv_outputs = []
        for filter in self.conv_filters:
            conv_out = self.conv2d(image, filter)
            conv_outputs.append(conv_out)
        
        # 激活函数
        activated_outputs = [self.relu(out) for out in conv_outputs]
        
        # 池化层
        pooled_outputs = []
        for out in activated_outputs:
            pooled = self.max_pool2d(out)
            pooled_outputs.append(pooled)
        
        # 展平
        flattened = np.concatenate([out.flatten() for out in pooled_outputs])
        
        # 全连接层
        logits = np.dot(flattened, self.fc_weights) + self.fc_bias
        output = self.softmax(logits.reshape(1, -1))
        
        return output.flatten(), flattened
    
    def cross_entropy_loss(self, predictions, targets):
        """交叉熵损失"""
        return -np.sum(targets * np.log(predictions + 1e-15))
    
    def train_step(self, image, label, learning_rate=0.01):
        """训练步骤"""
        # 前向传播
        predictions, features = self.forward(image)
        
        # 计算损失
        target = np.zeros(10)
        target[label] = 1
        loss = self.cross_entropy_loss(predictions, target)
        
        # 简化的反向传播(这里只展示概念)
        # 实际实现会更复杂
        output_error = predictions - target
        self.fc_weights -= learning_rate * np.outer(features, output_error)
        self.fc_bias -= learning_rate * output_error
        
        return loss

# 可视化卷积和池化操作
def visualize_conv_pool():
    """可视化卷积和池化操作"""
    # 创建示例图像
    image = np.random.rand(8, 8)
    
    # 创建卷积核
    kernel = np.array([[1, 0, -1],
                       [1, 0, -1],
                       [1, 0, -1]])
    
    # 执行卷积
    cnn = SimpleCNN()
    conv_result = cnn.conv2d(image, kernel)
    pool_result = cnn.max_pool2d(conv_result, pool_size=2)
    
    # 可视化
    fig, axes = plt.subplots(1, 4, figsize=(16, 4))
    
    axes[0].imshow(image, cmap='gray')
    axes[0].set_title('原始图像 (8x8)')
    axes[0].axis('off')
    
    axes[1].imshow(kernel, cmap='RdBu', vmin=-1, vmax=1)
    axes[1].set_title('卷积核 (3x3)')
    axes[1].axis('off')
    
    axes[2].imshow(conv_result, cmap='gray')
    axes[2].set_title(f'卷积结果 ({conv_result.shape[0]}x{conv_result.shape[1]})')
    axes[2].axis('off')
    
    axes[3].imshow(pool_result, cmap='gray')
    axes[3].set_title(f'池化结果 ({pool_result.shape[0]}x{pool_result.shape[1]})')
    axes[3].axis('off')
    
    plt.tight_layout()
    plt.show()

visualize_conv_pool()

print("卷积神经网络核心概念:")
print("1. 卷积层: 通过卷积核提取局部特征")
print("2. 池化层: 降低特征图维度,保留重要信息")
print("3. 全连接层: 将提取的特征用于分类等任务")

CNN在图像分类中的应用

# 使用手写数字数据集演示CNN
def cnn_digit_classification():
    """CNN手写数字分类演示"""
    # 加载数据
    digits = load_digits()
    X, y = digits.data, digits.target
    
    # 重塑为8x8图像
    X_images = X.reshape(-1, 8, 8)
    
    # 分割数据
    X_train, X_test, y_train, y_test = train_test_split(
        X_images, y, test_size=0.3, random_state=42
    )
    
    print(f"训练集大小: {X_train.shape}")
    print(f"测试集大小: {X_test.shape}")
    
    # 创建简单CNN
    cnn = SimpleCNN()
    
    # 训练几个样本(简化演示)
    print("\n开始训练CNN...")
    losses = []
    
    # 只训练前50个样本进行演示
    for i in range(min(50, len(X_train))):
        loss = cnn.train_step(X_train[i], y_train[i], learning_rate=0.1)
        losses.append(loss)
        
        if (i + 1) % 10 == 0:
            print(f"样本 {i+1}, 损失: {loss:.4f}")
    
    # 测试几个样本
    print("\n测试结果:")
    correct = 0
    total = min(20, len(X_test))
    
    for i in range(total):
        predictions, _ = cnn.forward(X_test[i])
        predicted_label = np.argmax(predictions)
        if predicted_label == y_test[i]:
            correct += 1
        
        if i < 5:  # 显示前5个测试样本
            plt.figure(figsize=(10, 2))
            plt.subplot(1, 5, 1)
            plt.imshow(X_test[i], cmap='gray')
            plt.title(f'真实: {y_test[i]}')
            plt.axis('off')
            
            for j in range(min(4, len(predictions))):
                plt.subplot(1, 5, j+2)
                plt.bar(range(10), [predictions[k] if k < len(predictions) else 0 for k in range(10)])
                plt.title(f'预测分布')
                plt.xlabel('数字')
                plt.ylabel('概率')
                break  # 只显示一次
            
            plt.tight_layout()
            plt.show()
    
    accuracy = correct / total
    print(f"测试准确率: {accuracy:.4f} ({correct}/{total})")
    
    # 损失曲线
    plt.figure(figsize=(10, 6))
    plt.plot(losses)
    plt.xlabel('训练样本')
    plt.ylabel('损失')
    plt.title('CNN训练损失曲线')
    plt.grid(True, alpha=0.3)
    plt.show()

cnn_digit_classification()

循环神经网络(RNN)

循环神经网络专门用于处理序列数据,如文本、时间序列等。它通过在时间步之间共享参数来捕获序列中的依赖关系。

RNN核心概念

RNN的核心特点是具有"记忆"能力,当前时间步的输出不仅依赖于当前输入,还依赖于之前时间步的信息。

# 简单RNN实现
class SimpleRNN:
    """简化版RNN实现"""
    
    def __init__(self, input_size, hidden_size, output_size):
        self.input_size = input_size
        self.hidden_size = hidden_size
        self.output_size = output_size
        
        # 初始化权重
        self.W_hh = np.random.randn(hidden_size, hidden_size) * 0.1  # 隐藏层到隐藏层
        self.W_xh = np.random.randn(input_size, hidden_size) * 0.1   # 输入到隐藏层
        self.W_hy = np.random.randn(hidden_size, output_size) * 0.1  # 隐藏层到输出
        self.b_h = np.zeros(hidden_size)  # 隐藏层偏置
        self.b_y = np.zeros(output_size)  # 输出层偏置
    
    def tanh(self, x):
        """Tanh激活函数"""
        return np.tanh(x)
    
    def softmax(self, x):
        """Softmax函数"""
        exp_x = np.exp(x - np.max(x))
        return exp_x / np.sum(exp_x)
    
    def forward(self, inputs):
        """前向传播"""
        hidden_states = []
        outputs = []
        
        # 初始化隐藏状态
        h = np.zeros(self.hidden_size)
        
        # 对每个时间步进行处理
        for x in inputs:
            # 计算隐藏状态
            h = self.tanh(np.dot(x, self.W_xh) + np.dot(h, self.W_hh) + self.b_h)
            hidden_states.append(h)
            
            # 计算输出
            y = np.dot(h, self.W_hy) + self.b_y
            output = self.softmax(y)
            outputs.append(output)
        
        return outputs, hidden_states
    
    def predict(self, inputs):
        """预测"""
        outputs, _ = self.forward(inputs)
        return np.argmax(outputs[-1])  # 返回最后一个时间步的预测

# 文本序列处理示例
def text_sequence_demo():
    """文本序列处理演示"""
    # 简单的字符级文本处理
    text = "hello world"
    
    # 创建字符到索引的映射
    chars = list(set(text))
    char_to_idx = {ch: i for i, ch in enumerate(chars)}
    idx_to_char = {i: ch for i, ch in enumerate(chars)}
    
    print("字符映射:")
    for ch, idx in char_to_idx.items():
        print(f"  '{ch}' -> {idx}")
    
    # 将文本转换为索引序列
    sequence = [char_to_idx[ch] for ch in text]
    print(f"\n文本序列: {sequence}")
    
    # 转换为one-hot编码
    def to_one_hot(indices, vocab_size):
        one_hot = np.zeros((len(indices), vocab_size))
        for i, idx in enumerate(indices):
            one_hot[i, idx] = 1
        return one_hot
    
    one_hot_sequence = to_one_hot(sequence, len(chars))
    print(f"序列形状: {one_hot_sequence.shape}")
    
    # 创建简单RNN
    rnn = SimpleRNN(input_size=len(chars), hidden_size=10, output_size=len(chars))
    
    # 前向传播
    outputs, hidden_states = rnn.forward(one_hot_sequence)
    
    print(f"\n输出数量: {len(outputs)}")
    print(f"隐藏状态数量: {len(hidden_states)}")
    print(f"每个输出形状: {outputs[0].shape}")
    print(f"每个隐藏状态形状: {hidden_states[0].shape}")
    
    # 可视化隐藏状态
    plt.figure(figsize=(12, 6))
    
    # 绘制隐藏状态热力图
    hidden_matrix = np.array(hidden_states)
    plt.subplot(1, 2, 1)
    plt.imshow(hidden_matrix, cmap='viridis', aspect='auto')
    plt.xlabel('隐藏单元')
    plt.ylabel('时间步')
    plt.title('RNN隐藏状态演化')
    plt.colorbar(label='激活值')
    
    # 绘制最后一个时间步的输出分布
    plt.subplot(1, 2, 2)
    plt.bar(range(len(chars)), outputs[-1])
    plt.xlabel('字符索引')
    plt.ylabel('概率')
    plt.title('最后一个时间步的输出分布')
    plt.xticks(range(len(chars)), [idx_to_char[i] for i in range(len(chars))])
    
    plt.tight_layout()
    plt.show()

text_sequence_demo()

LSTM和GRU

为了解决RNN的梯度消失问题,LSTM和GRU引入了门控机制。

# LSTM单元实现(简化版)
class SimpleLSTM:
    """简化版LSTM实现"""
    
    def __init__(self, input_size, hidden_size):
        self.input_size = input_size
        self.hidden_size = hidden_size
        
        # 为简化,我们只展示LSTM的概念结构
        self.W = np.random.randn(input_size + hidden_size, 4 * hidden_size) * 0.1
        self.b = np.zeros(4 * hidden_size)
    
    def sigmoid(self, x):
        """Sigmoid函数"""
        return 1 / (1 + np.exp(-np.clip(x, -500, 500)))
    
    def tanh(self, x):
        """Tanh函数"""
        return np.tanh(x)
    
    def forward_step(self, x, h_prev, c_prev):
        """单步前向传播"""
        # 拼接输入和前一隐藏状态
        concat = np.concatenate([x, h_prev])
        
        # 计算门控值
        gates = np.dot(concat, self.W) + self.b
        i, f, o, g = np.split(gates, 4)  # 输入门、遗忘门、输出门、候选值
        
        # 应用激活函数
        i = self.sigmoid(i)  # 输入门
        f = self.sigmoid(f)  # 遗忘门
        o = self.sigmoid(o)  # 输出门
        g = self.tanh(g)     # 候选值
        
        # 计算当前细胞状态
        c = f * c_prev + i * g
        
        # 计算当前隐藏状态
        h = o * self.tanh(c)
        
        return h, c

# 可视化LSTM门控机制
def visualize_lstm_gates():
    """可视化LSTM门控机制"""
    fig, axes = plt.subplots(2, 2, figsize=(12, 10))
    
    # 模拟门控值
    time_steps = range(10)
    forget_gate = np.exp(-np.array(time_steps) * 0.2)  # 遗忘门随时间衰减
    input_gate = 1 - forget_gate  # 输入门与遗忘门互补
    output_gate = 0.5 + 0.3 * np.sin(np.array(time_steps) * 0.5)  # 输出门周期性变化
    candidate_values = np.random.randn(10)  # 候选值
    
    axes[0, 0].plot(time_steps, forget_gate, 'b-o')
    axes[0, 0].set_title('遗忘门')
    axes[0, 0].set_ylabel('门控值')
    axes[0, 0].grid(True, alpha=0.3)
    
    axes[0, 1].plot(time_steps, input_gate, 'g-o')
    axes[0, 1].set_title('输入门')
    axes[0, 1].grid(True, alpha=0.3)
    
    axes[1, 0].plot(time_steps, output_gate, 'r-o')
    axes[1, 0].set_title('输出门')
    axes[1, 0].set_xlabel('时间步')
    axes[1, 0].set_ylabel('门控值')
    axes[1, 0].grid(True, alpha=0.3)
    
    axes[1, 1].plot(time_steps, candidate_values, 'm-o')
    axes[1, 1].set_title('候选值')
    axes[1, 1].set_xlabel('时间步')
    axes[1, 1].grid(True, alpha=0.3)
    
    plt.tight_layout()
    plt.show()

visualize_lstm_gates()

print("LSTM门控机制:")
print("1. 遗忘门: 决定从细胞状态中丢弃什么信息")
print("2. 输入门: 决定在细胞状态中存储什么新信息")
print("3. 输出门: 决定基于细胞状态输出什么")
print("4. 候选值: 创建新的候选值向量")

CNN与RNN的应用对比

# 应用场景对比
def compare_cnn_rnn_applications():
    """对比CNN和RNN的应用场景"""
    
    applications = {
        'CNN': [
            '图像分类',
            '目标检测',
            '人脸识别',
            '医学图像分析',
            '风格迁移',
            '图像生成'
        ],
        'RNN': [
            '文本分类',
            '机器翻译',
            '语音识别',
            '时间序列预测',
            '情感分析',
            '文本生成'
        ]
    }
    
    plt.figure(figsize=(15, 6))
    
    # CNN应用
    plt.subplot(1, 2, 1)
    y_pos = np.arange(len(applications['CNN']))
    plt.barh(y_pos, [1]*len(applications['CNN']), color='skyblue')
    plt.yticks(y_pos, applications['CNN'])
    plt.title('CNN主要应用场景')
    plt.xlabel('适用性')
    plt.grid(True, alpha=0.3)
    
    # RNN应用
    plt.subplot(1, 2, 2)
    y_pos = np.arange(len(applications['RNN']))
    plt.barh(y_pos, [1]*len(applications['RNN']), color='lightcoral')
    plt.yticks(y_pos, applications['RNN'])
    plt.title('RNN主要应用场景')
    plt.xlabel('适用性')
    plt.grid(True, alpha=0.3)
    
    plt.tight_layout()
    plt.show()

compare_cnn_rnn_applications()

# 现代架构发展
def modern_architecture_evolution():
    """现代架构发展时间线"""
    architectures = {
        '1980s': ['LeNet (CNN)'],
        '1990s': ['LSTM (RNN)'],
        '2012': ['AlexNet (CNN)'],
        '2014': ['GRU (RNN)'],
        '2015': ['ResNet (CNN)'],
        '2017': ['Transformer'],
        '2018': ['BERT (Transformer)'],
        '2020': ['GPT-3 (Transformer)']
    }
    
    plt.figure(figsize=(12, 8))
    
    years = list(architectures.keys())
    year_nums = range(len(years))
    
    plt.hlines(1, 0, len(years)-1, alpha=0.3)
    plt.scatter(year_nums, [1]*len(year_nums), s=100, color='red')
    
    for i, (year, archs) in enumerate(architectures.items()):
        plt.annotate(f"{year}\n{', '.join(archs)}", (i, 1), 
                    xytext=(0, 30 if i % 2 == 0 else -60), 
                    textcoords='offset points',
                    ha='center', va='bottom' if i % 2 == 0 else 'top',
                    bbox=dict(boxstyle='round,pad=0.3', fc='lightgreen', alpha=0.7),
                    arrowprops=dict(arrowstyle='->', connectionstyle='arc3,rad=0'))
    
    plt.xlim(-0.5, len(years)-0.5)
    plt.ylim(0.5, 1.5)
    plt.yticks([])
    plt.xlabel('时间')
    plt.title('神经网络架构发展时间线')
    plt.tight_layout()
    plt.show()

modern_architecture_evolution()

本周学习总结

今天我们深入学习了两种重要的现代神经网络架构:

  1. 卷积神经网络(CNN)

    • 理解了卷积层、池化层和全连接层的作用
    • 实现了简单的CNN进行图像分类
    • 学习了CNN在计算机视觉中的应用
  2. 循环神经网络(RNN)

    • 掌握了RNN处理序列数据的原理
    • 实现了简单的RNN处理文本序列
    • 了解了LSTM和GRU的门控机制
  3. 应用对比

    • 比较了CNN和RNN的不同应用场景
    • 了解了现代神经网络架构的发展历程
graph TD
    A[现代神经网络] --> B[CNN]
    A --> C[RNN]
    B --> D[卷积层]
    B --> E[池化层]
    B --> F[应用]
    C --> G[循环层]
    C --> H[LSTM/GRU]
    C --> I[应用]
    F --> J[图像处理]
    I --> K[序列处理]

课后练习

  1. 运行本节所有代码示例,理解CNN和RNN的工作原理
  2. 修改SimpleCNN,增加更多的卷积层和池化层,观察效果变化
  3. 实现一个简单的文本生成器,使用RNN生成字符序列
  4. 研究现代CNN架构(如ResNet、EfficientNet)和Transformer架构的原理

下节预告

下一节我们将学习Transformer和大语言模型,包括自注意力机制、BERT、GPT等现代NLP技术,这些是当前AI领域最热门的研究方向,敬请期待!


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