深度强化学习进阶:从DQN到PPO

3 阅读1分钟

在前面几节中,我们学习了深度学习的各种架构,包括CNN、RNN和Transformer。今天,我们将回到强化学习领域,深入学习深度强化学习的进阶内容,包括DQN的改进版本、策略梯度方法和近端策略优化(PPO)算法。

深度强化学习概览

深度强化学习结合了深度学习的表征学习能力和强化学习的决策能力,是实现通用人工智能的重要途径之一。

graph TD
    A[深度强化学习] --> B[DQN系列]
    A --> C[策略梯度]
    A --> D[PPO]
    B --> E[Double DQN]
    B --> F[Dueling DQN]
    B --> G[优先经验回放]
    C --> H[REINFORCE]
    C --> I[Actor-Critic]
    D --> J[近端策略优化]
    D --> K[信任域方法]

DQN改进算法

Deep Q-Network (DQN) 是深度强化学习的里程碑式工作,但存在一些问题,后续研究提出了多种改进方法。

Double DQN

Double DQN 解决了DQN中Q值过高估计的问题。

import numpy as np
import matplotlib.pyplot as plt
import random
from collections import deque

# 简单的DQN实现
class DQN:
    """Deep Q-Network基础实现"""
    
    def __init__(self, state_size, action_size, learning_rate=0.001):
        self.state_size = state_size
        self.action_size = action_size
        self.learning_rate = learning_rate
        self.epsilon = 1.0
        self.epsilon_min = 0.01
        self.epsilon_decay = 0.995
        
        # 简化的Q网络(使用线性函数近似)
        self.q_network = np.random.randn(state_size, action_size) * 0.01
    
    def act(self, state):
        """选择动作"""
        if np.random.rand() <= self.epsilon:
            return random.randrange(self.action_size)
        q_values = np.dot(state, self.q_network)
        return np.argmax(q_values)
    
    def remember(self, state, action, reward, next_state, done):
        """存储经验(简化版)"""
        pass
    
    def replay(self, batch_size):
        """经验回放(简化版)"""
        pass

# Double DQN实现
class DoubleDQN(DQN):
    """Double DQN实现"""
    
    def __init__(self, state_size, action_size, learning_rate=0.001):
        super().__init__(state_size, action_size, learning_rate)
        # 目标网络
        self.target_network = np.copy(self.q_network)
        self.update_target_freq = 100
        self.step_count = 0
    
    def update_target_network(self):
        """更新目标网络"""
        self.target_network = np.copy(self.q_network)
    
    def train_step(self, state, action, reward, next_state, done):
        """训练步骤"""
        # 选择动作使用主网络
        next_q_values = np.dot(next_state, self.q_network)
        next_action = np.argmax(next_q_values)
        
        # 评估Q值使用目标网络
        next_target_q_values = np.dot(next_state, self.target_network)
        target_q = reward
        if not done:
            target_q += 0.99 * next_target_q_values[next_action]
        
        # 计算当前Q值
        current_q = np.dot(state, self.q_network)[action]
        
        # 更新Q值
        td_error = target_q - current_q
        self.q_network[:, action] += self.learning_rate * td_error * state
        
        # 更新目标网络
        self.step_count += 1
        if self.step_count % self.update_target_freq == 0:
            self.update_target_network()
        
        # 降低探索率
        if self.epsilon > self.epsilon_min:
            self.epsilon *= self.epsilon_decay

# 比较DQN和Double DQN
def compare_dqn_double_dqn():
    """比较DQN和Double DQN"""
    print("DQN vs Double DQN:")
    print("1. DQN问题: Q值过高估计")
    print("2. Double DQN解决方案: 分离动作选择和价值评估")
    print("   - 动作选择: 使用主网络")
    print("   - 价值评估: 使用目标网络")
    
    # 模拟Q值估计
    true_q_values = np.array([1.0, 1.5, 2.0, 1.2])  # 真实Q值
    dqn_estimates = np.array([1.2, 1.8, 2.5, 1.4])  # DQN估计值(过高估计)
    double_dqn_estimates = np.array([1.1, 1.6, 2.1, 1.3])  # Double DQN估计值
    
    plt.figure(figsize=(12, 6))
    
    x = np.arange(len(true_q_values))
    width = 0.25
    
    plt.bar(x - width, true_q_values, width, label='真实Q值', color='green')
    plt.bar(x, dqn_estimates, width, label='DQN估计', color='red', alpha=0.7)
    plt.bar(x + width, double_dqn_estimates, width, label='Double DQN估计', color='blue', alpha=0.7)
    
    plt.xlabel('动作')
    plt.ylabel('Q值')
    plt.title('DQN与Double DQN Q值估计对比')
    plt.xticks(x, [f'动作{i}' for i in range(len(true_q_values))])
    plt.legend()
    plt.grid(True, alpha=0.3)
    plt.tight_layout()
    plt.show()
    
    # 计算误差
    dqn_error = np.mean((true_q_values - dqn_estimates) ** 2)
    double_dqn_error = np.mean((true_q_values - double_dqn_estimates) ** 2)
    
    print(f"\n均方误差:")
    print(f"DQN: {dqn_error:.4f}")
    print(f"Double DQN: {double_dqn_error:.4f}")

compare_dqn_double_dqn()

Dueling DQN

Dueling DQN 将Q值分解为状态价值和优势函数,提高了学习效率。

# Dueling DQN概念
class DuelingDQN:
    """Dueling DQN概念演示"""
    
    def __init__(self, state_size, action_size):
        self.state_size = state_size
        self.action_size = action_size
        
        # 状态价值网络
        self.value_stream = np.random.randn(state_size, 64) * 0.1
        self.value_output = np.random.randn(64, 1) * 0.1
        
        # 优势函数网络
        self.advantage_stream = np.random.randn(state_size, 64) * 0.1
        self.advantage_output = np.random.randn(64, action_size) * 0.1
    
    def compute_q_values(self, state):
        """计算Q值"""
        # 状态价值
        value_hidden = np.maximum(0, np.dot(state, self.value_stream))  # ReLU
        value = np.dot(value_hidden, self.value_output)
        
        # 优势函数
        adv_hidden = np.maximum(0, np.dot(state, self.advantage_stream))  # ReLU
        advantage = np.dot(adv_hidden, self.advantage_output)
        
        # 组合Q值: Q(s,a) = V(s) + A(s,a) - mean(A(s,·))
        mean_advantage = np.mean(advantage)
        q_values = value + (advantage - mean_advantage)
        
        return q_values.flatten()

# 可视化Dueling DQN结构
def visualize_dueling_dqn():
    """可视化Dueling DQN结构"""
    fig, ax = plt.subplots(figsize=(12, 8))
    
    # 绘制网络结构
    # 输入层
    input_nodes = [(0, i) for i in range(4)]
    # 共享层
    shared_nodes = [(1, i) for i in range(4)]
    # 价值流
    value_nodes = [(2, 1), (3, 1), (4, 1)]
    # 优势流
    advantage_nodes = [(2, 0), (2, 2), (3, 0), (3, 2), (4, 0), (4, 2)]
    # 输出层
    output_nodes = [(5, i) for i in range(3)]
    
    # 绘制节点
    all_nodes = input_nodes + shared_nodes + value_nodes + advantage_nodes + output_nodes
    
    for x, y in all_nodes:
        color = 'lightblue' if (x, y) in input_nodes else \
                'lightgreen' if (x, y) in shared_nodes else \
                'lightcoral' if (x, y) in value_nodes else \
                'lightyellow' if (x, y) in advantage_nodes else \
                'lightpink'
        ax.add_patch(plt.Circle((x, y), 0.2, color=color, ec='black'))
        if (x, y) in output_nodes:
            ax.text(x, y, f'A{output_nodes.index((x, y))}', ha='center', va='center')
        elif (x, y) == (4, 1):
            ax.text(x, y, 'V', ha='center', va='center')
    
    # 绘制连接线
    # 输入到共享层
    for i in input_nodes:
        for j in shared_nodes:
            ax.plot([i[0], j[0]], [i[1], j[1]], 'k-', alpha=0.5)
    
    # 共享层到价值流
    for node in shared_nodes:
        ax.plot([node[0], 2], [node[1], 1], 'k-', alpha=0.5)
    
    # 共享层到优势流
    for node in shared_nodes:
        ax.plot([node[0], 2], [node[1], 0], 'k-', alpha=0.5)
        ax.plot([node[0], 2], [node[1], 2], 'k-', alpha=0.5)
    
    # 价值流内部连接
    ax.plot([2, 3], [1, 1], 'k-', alpha=0.5)
    ax.plot([3, 4], [1, 1], 'k-', alpha=0.5)
    
    # 优势流内部连接
    for y in [0, 2]:
        ax.plot([2, 3], [y, y], 'k-', alpha=0.5)
        ax.plot([3, 4], [y, y], 'k-', alpha=0.5)
    
    # 到输出层的连接
    # 价值到所有输出
    for out_node in output_nodes:
        ax.plot([4, 5], [1, out_node[1]], 'k-', alpha=0.5)
    
    # 优势到对应输出
    ax.plot([4, 5], [0, 0], 'k-', alpha=0.5)
    ax.plot([4, 5], [0, 1], 'k-', alpha=0.5)
    ax.plot([4, 5], [0, 2], 'k-', alpha=0.5)
    ax.plot([4, 5], [2, 0], 'k-', alpha=0.5)
    ax.plot([4, 5], [2, 1], 'k-', alpha=0.5)
    ax.plot([4, 5], [2, 2], 'k-', alpha=0.5)
    
    ax.set_xlim(-0.5, 5.5)
    ax.set_ylim(-0.5, 3.5)
    ax.set_aspect('equal')
    ax.axis('off')
    ax.set_title('Dueling DQN网络结构', fontsize=16)
    
    # 添加标签
    ax.text(0, -0.5, '输入层', ha='center', fontsize=12)
    ax.text(1, -0.5, '共享层', ha='center', fontsize=12)
    ax.text(3, -0.5, '价值流 + 优势流', ha='center', fontsize=12)
    ax.text(5, -0.5, '输出层(Q值)', ha='center', fontsize=12)
    
    plt.tight_layout()
    plt.show()
    
    print("Dueling DQN核心思想:")
    print("1. 将Q值分解为两部分: Q(s,a) = V(s) + A(s,a)")
    print("2. V(s): 状态价值,衡量当前状态的好坏")
    print("3. A(s,a): 优势函数,衡量在当前状态下各动作的相对优势")
    print("4. 优势: 更好地学习状态价值,提高样本效率")

visualize_dueling_dqn()

策略梯度方法

策略梯度方法直接优化策略函数,而不是学习价值函数。

REINFORCE算法

REINFORCE是最基础的策略梯度算法。

# 简单的REINFORCE实现
class REINFORCE:
    """REINFORCE算法实现"""
    
    def __init__(self, state_size, action_size, learning_rate=0.01):
        self.state_size = state_size
        self.action_size = action_size
        self.learning_rate = learning_rate
        
        # 策略网络参数
        self.policy_weights = np.random.randn(state_size, action_size) * 0.01
    
    def policy(self, state):
        """策略函数(softmax)"""
        logits = np.dot(state, self.policy_weights)
        # 数值稳定
        logits = logits - np.max(logits)
        exp_logits = np.exp(logits)
        return exp_logits / np.sum(exp_logits)
    
    def select_action(self, state):
        """根据策略选择动作"""
        probs = self.policy(state)
        return np.random.choice(self.action_size, p=probs)
    
    def update(self, states, actions, rewards):
        """更新策略"""
        # 计算折扣回报
        discounted_returns = self.compute_discounted_returns(rewards)
        
        # 标准化回报
        discounted_returns = (discounted_returns - np.mean(discounted_returns)) / (np.std(discounted_returns) + 1e-8)
        
        # 更新策略
        for i in range(len(states)):
            state = states[i]
            action = actions[i]
            G = discounted_returns[i]
            
            # 计算策略梯度
            probs = self.policy(state)
            dlog_prob = np.zeros(self.action_size)
            dlog_prob[action] = 1 / (probs[action] + 1e-8)
            
            # 简化的梯度计算
            gradient = np.outer(state, dlog_prob)
            self.policy_weights += self.learning_rate * G * gradient
    
    def compute_discounted_returns(self, rewards, gamma=0.99):
        """计算折扣回报"""
        discounted_returns = np.zeros_like(rewards, dtype=float)
        running_return = 0
        for t in reversed(range(len(rewards))):
            running_return = rewards[t] + gamma * running_return
            discounted_returns[t] = running_return
        return discounted_returns

# 策略梯度优势
def policy_gradient_advantages():
    """策略梯度方法的优势"""
    print("策略梯度方法的优势:")
    print("1. 直接优化目标: 策略梯度方法直接优化期望回报")
    print("2. 适用于连续动作空间: 不需要对连续动作空间进行离散化")
    print("3. 随机策略: 可以学习随机策略,在某些环境中更优")
    print("4. 无偏差: 策略梯度是期望回报梯度的无偏估计")
    
    # 比较价值方法和策略方法
    methods = ['价值方法', '策略方法']
    characteristics = {
        '优化目标': [0.6, 0.9],  # 策略方法更直接
        '动作空间': [0.5, 0.9],  # 策略方法更适合连续空间
        '确定性策略': [0.8, 0.4], # 价值方法通常产生确定性策略
        '样本效率': [0.7, 0.5],  # 价值方法通常样本效率更高
        '方差': [0.4, 0.6]       # 策略梯度方差通常更高
    }
    
    fig, ax = plt.subplots(figsize=(12, 8))
    
    x = np.arange(len(characteristics))
    width = 0.35
    
    for i, method in enumerate(methods):
        values = [characteristics[key][i] for key in characteristics]
        ax.bar(x + i*width, values, width, label=method, alpha=0.8)
    
    ax.set_xlabel('特性')
    ax.set_ylabel('相对优势 (0-1)')
    ax.set_title('价值方法 vs 策略方法')
    ax.set_xticks(x + width/2)
    ax.set_xticklabels(characteristics.keys())
    ax.legend()
    ax.grid(True, alpha=0.3)
    plt.tight_layout()
    plt.show()

policy_gradient_advantages()

近端策略优化(PPO)

PPO是目前最流行的策略优化算法之一,平衡了样本效率和稳定性。

# PPO核心概念
class PPOConcept:
    """PPO核心概念演示"""
    
    def __init__(self):
        pass
    
    def clip_function(self, ratio, epsilon=0.2):
        """PPO裁剪函数"""
        return np.clip(ratio, 1-epsilon, 1+epsilon)
    
    def ppo_loss(self, ratio, advantage, epsilon=0.2):
        """PPO损失函数"""
        clipped_loss = np.minimum(
            ratio * advantage,
            self.clip_function(ratio, epsilon) * advantage
        )
        return -np.mean(clipped_loss)  # 负号因为我们要最大化

# 可视化PPO裁剪机制
def visualize_ppo_clipping():
    """可视化PPO裁剪机制"""
    ppo = PPOConcept()
    
    # 比率范围
    ratios = np.linspace(0, 2.5, 100)
    advantages = [1.0, -1.0]  # 正负优势示例
    
    plt.figure(figsize=(12, 6))
    
    for i, advantage in enumerate(advantages):
        plt.subplot(1, 2, i+1)
        
        # 无裁剪的目标
        unclipped = ratios * advantage
        
        # 裁剪后的目标
        clipped = ppo.clip_function(ratios) * advantage
        
        plt.plot(ratios, unclipped, 'b-', label=f'无裁剪 (优势={advantage})', linewidth=2)
        plt.plot(ratios, clipped, 'r-', label=f'PPO裁剪 (优势={advantage})', linewidth=2)
        
        # 填充裁剪区域
        plt.fill_between(ratios, unclipped, clipped, alpha=0.3, color='gray')
        
        plt.axvline(x=1.0, color='k', linestyle='--', alpha=0.5, label='比率=1.0')
        plt.axhline(y=0, color='k', linestyle='-', alpha=0.3)
        
        plt.xlabel('重要性比率 π(a|s)/π_old(a|s)')
        plt.ylabel('目标函数值')
        plt.title(f'PPO裁剪机制 (优势={advantage})')
        plt.legend()
        plt.grid(True, alpha=0.3)
    
    plt.tight_layout()
    plt.show()
    
    print("PPO核心思想:")
    print("1. 裁剪机制: 限制策略更新幅度,防止过大更新")
    print("2. 重要性比率: ρ(θ) = π_θ(a|s) / π_old(a|s)")
    print("3. 裁剪范围: 通常设置为[1-ε, 1+ε],ε=0.2")
    print("4. 目标函数: L(θ) = E[min(ρ(θ)A, clip(ρ(θ), 1-ε, 1+ε)A)]")

visualize_ppo_clipping()

PPO算法优势

# PPO与其他算法对比
def ppo_comparison():
    """PPO与其他算法对比"""
    
    algorithms = ['DQN', 'A3C', 'TRPO', 'PPO']
    metrics = {
        '样本效率': [0.8, 0.6, 0.7, 0.75],
        '稳定性': [0.6, 0.5, 0.9, 0.95],
        '实现复杂度': [0.7, 0.4, 0.3, 0.8],
        '调参难度': [0.7, 0.5, 0.4, 0.8],
        '实际应用': [0.8, 0.6, 0.7, 0.9]
    }
    
    fig, ax = plt.subplots(figsize=(12, 8))
    
    x = np.arange(len(metrics))
    width = 0.2
    
    for i, algorithm in enumerate(algorithms):
        values = [metrics[key][i] for key in metrics]
        ax.bar(x + i*width, values, width, label=algorithm, alpha=0.8)
    
    ax.set_xlabel('评估指标')
    ax.set_ylabel('相对性能 (0-1)')
    ax.set_title('深度强化学习算法对比')
    ax.set_xticks(x + width * 1.5)
    ax.set_xticklabels(metrics.keys())
    ax.legend()
    ax.grid(True, alpha=0.3)
    plt.tight_layout()
    plt.show()
    
    print("PPO算法特点:")
    print("1. 高样本效率: 通过重要性采样重用经验")
    print("2. 高稳定性: 裁剪机制防止策略更新过大")
    print("3. 易实现: 相比TRPO等算法实现简单")
    print("4. 广泛应用: 在各种RL任务中表现良好")

ppo_comparison()

深度强化学习应用

# 深度强化学习应用领域
def drl_applications():
    """深度强化学习应用领域"""
    
    applications = {
        '游戏AI': 0.25,
        '机器人控制': 0.2,
        '自动驾驶': 0.15,
        '金融交易': 0.1,
        '资源调度': 0.1,
        '推荐系统': 0.1,
        '其他': 0.1
    }
    
    plt.figure(figsize=(15, 10))
    
    # 饼图
    plt.subplot(2, 2, 1)
    plt.pie(applications.values(), labels=applications.keys(), autopct='%1.1f%%')
    plt.title('深度强化学习应用领域分布')
    
    # 条形图
    plt.subplot(2, 2, 2)
    apps = list(applications.keys())
    values = list(applications.values())
    bars = plt.barh(apps, values, color=plt.cm.viridis(np.linspace(0, 1, len(apps))))
    plt.xlabel('应用比例')
    plt.title('深度强化学习应用领域')
    plt.grid(True, alpha=0.3)
    
    # 发展时间线
    plt.subplot(2, 1, 2)
    years = [1992, 2013, 2015, 2016, 2017, 2020]
    events = ['TD-Gammon', 'DQN', 'AlphaGo', 'AlphaGo Zero', 'PPO', 'AlphaFold']
    plt.hlines(1, min(years)-1, max(years)+1, alpha=0.3)
    plt.scatter(years, [1]*len(years), s=100, color='red')
    
    for i, (year, event) in enumerate(zip(years, events)):
        plt.annotate(event, (year, 1), 
                    xytext=(0, 20 if i % 2 == 0 else -40), 
                    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(min(years)-1, max(years)+1)
    plt.ylim(0.5, 1.5)
    plt.yticks([])
    plt.xlabel('年份')
    plt.title('深度强化学习重要里程碑')
    
    plt.tight_layout()
    plt.show()

drl_applications()

# 现代DRL框架
def modern_drl_frameworks():
    """现代深度强化学习框架"""
    frameworks = {
        'Stable-Baselines3': 'PyTorch',
        'Ray RLlib': 'PyTorch/TensorFlow',
        'OpenAI Baselines': 'TensorFlow',
        'Dopamine': 'JAX',
        'Acme': 'JAX',
        'Tianshou': 'PyTorch'
    }
    
    print("现代深度强化学习框架:")
    for framework, backend in frameworks.items():
        print(f"  {framework}: 基于 {backend}")
    
    print("\n框架特点:")
    print("1. Stable-Baselines3: 简单易用,适合初学者")
    print("2. Ray RLlib: 分布式训练,生产环境")
    print("3. Dopamine: 谷歌开发,研究导向")
    print("4. Acme: DeepMind开发,模块化设计")
    print("5. Tianshou: 清华大学开发,中文文档")

modern_drl_frameworks()

本周学习总结

今天我们深入学习了深度强化学习的进阶内容:

  1. DQN改进算法

    • 学习了Double DQN解决Q值过高估计问题
    • 掌握了Dueling DQN的价值-优势分解思想
  2. 策略梯度方法

    • 理解了REINFORCE算法的原理
    • 认识了策略梯度方法的优势
  3. 近端策略优化(PPO)

    • 掌握了PPO的裁剪机制
    • 了解了PPO在实际应用中的优势
  4. 应用与框架

    • 了解了深度强化学习的应用领域
    • 熟悉了现代DRL框架
graph TD
    A[深度强化学习进阶] --> B[DQN改进]
    A --> C[策略梯度]
    A --> D[PPO]
    B --> E[Double DQN]
    B --> F[Dueling DQN]
    C --> G[REINFORCE]
    C --> H[Actor-Critic]
    D --> I[裁剪机制]
    D --> J[优势函数]

课后练习

  1. 运行本节所有代码示例,理解各种算法的工作原理
  2. 实现一个简单的PPO算法,并在简单环境中测试
  3. 比较不同DQN变体在相同任务上的性能
  4. 研究IMPALA、R2D2等更先进的DRL算法

下节预告

下一节我们将学习AI系统工程师相关内容,包括模型部署与推理优化,这是将AI模型应用到实际生产环境的关键技术,敬请期待!


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