一文读懂强化学习:从Q-learning到PPO

一文读懂强化学习:从Q-learning到PPO

强化学习作为机器学习的核心分支之一,凭借"试错学习"的特性,成为实现智能体自主决策的关键技术。从经典的Q-learning到工业界主流的PPO(Proximal Policy Optimization),算法的演进始终围绕"更稳定、更高效、更易落地"展开。本文将从基础原理入手,结合实战代码,带你打通从Q-learning到PPO的学习链路。


一、强化学习核心概念速览

在深入算法前,先理清强化学习的核心要素,这是理解所有算法的基础:

  • 智能体(Agent):做出决策的主体(如游戏AI、机器人);
  • 环境(Environment):智能体交互的外部场景(如迷宫、Atari游戏);
  • 状态(State):环境的当前特征(如CartPole中杆子的角度、小车位置);
  • 动作(Action):智能体的决策(如向左/向右推动小车);
  • 奖励(Reward):环境对动作的反馈(如小车保持平衡则奖励+1,倒下则奖励0);
  • 策略(Policy):智能体选择动作的规则(核心优化目标);
  • 价值函数(Value Function):评估某个状态/动作的长期收益。

简单来说,强化学习的目标就是让智能体通过不断与环境交互,学习最优策略,最大化累计奖励。

二、经典入门:Q-learning算法

Q-learning是基于值函数的经典强化学习算法,核心是学习"状态-动作对"的价值(即Q值),表示在某个状态下执行某个动作能获得的长期奖励。

1. Q-learning核心原理

Q-learning的核心是贝尔曼方程 ,用于迭代更新Q值:
Q ( s , a ) ← Q ( s , a ) + α [ r + γ max ⁡ a ′ Q ( s ′ , a ′ ) − Q ( s , a ) ] Q(s,a) \leftarrow Q(s,a) + \alpha \left[ r + \gamma \max_{a'} Q(s',a') - Q(s,a) \right] Q(s,a)←Q(s,a)+α[r+γa′maxQ(s′,a′)−Q(s,a)]

  • α \alpha α:学习率(0<α≤1),控制每次更新的幅度;
  • γ \gamma γ:折扣因子(0≤γ≤1),衡量未来奖励的重要性;
  • r r r:当前动作获得的即时奖励;
  • max ⁡ a ′ Q ( s ′ , a ′ ) \max_{a'} Q(s',a') maxa′Q(s′,a′):下一个状态 s ′ s' s′中所有动作的最大Q值。

Q-learning的核心逻辑:用"当前Q值"和"即时奖励+未来最优Q值"的差值,逐步修正Q值,最终逼近最优Q表。

2. Q-learning实战:迷宫游戏AI

我们用经典的迷宫环境实现Q-learning,让智能体学会从起点走到终点。

环境说明

迷宫大小为4x4,状态0为起点,状态15为终点,灰色格子(如状态1、6)为障碍物,智能体可执行上/下/左/右4个动作,走到障碍物奖励-1,走到终点奖励10,其他位置奖励-0.1(鼓励尽快到达终点)。

完整代码
python 复制代码
import numpy as np
import random

# 1. 构建迷宫环境
class MazeEnv:
    def __init__(self):
        self.size = 4  # 4x4迷宫
        self.state = 0  # 初始状态(起点)
        self.end_state = 15  # 终点
        # 障碍物位置
        self.obstacles = [1, 6, 7, 10]
    
    def reset(self):
        """重置环境,返回初始状态"""
        self.state = 0
        return self.state
    
    def step(self, action):
        """执行动作,返回(next_state, reward, done)"""
        # 动作映射:0-上,1-下,2-左,3-右
        row = self.state // self.size
        col = self.state % self.size
        
        if action == 0:  # 上
            new_row = row - 1
            new_col = col
        elif action == 1:  # 下
            new_row = row + 1
            new_col = col
        elif action == 2:  # 左
            new_row = row
            new_col = col - 1
        elif action == 3:  # 右
            new_row = row
            new_col = col + 1
        else:
            raise ValueError("动作只能是0-3")
        
        # 边界检查
        if new_row < 0 or new_row >= self.size or new_col < 0 or new_col >= self.size:
            return self.state, -1, False  # 撞墙,状态不变,奖励-1
        
        new_state = new_row * self.size + new_col
        
        # 障碍物检查
        if new_state in self.obstacles:
            return self.state, -1, False  # 碰到障碍物,状态不变,奖励-1
        
        # 终点检查
        if new_state == self.end_state:
            return new_state, 10, True  # 到达终点,奖励10,结束
        
        # 普通移动
        self.state = new_state
        return new_state, -0.1, False

# 2. 实现Q-learning智能体
class QLearningAgent:
    def __init__(self, n_states, n_actions, alpha=0.1, gamma=0.9, epsilon=0.1):
        self.n_actions = n_actions
        # 初始化Q表:n_states x n_actions,初始值为0
        self.q_table = np.zeros((n_states, n_actions))
        self.alpha = alpha  # 学习率
        self.gamma = gamma  # 折扣因子
        self.epsilon = epsilon  # 探索率(ε-贪心策略)
    
    def choose_action(self, state):
        """ε-贪心策略选择动作:ε概率探索,1-ε概率利用"""
        if random.uniform(0, 1) < self.epsilon:
            # 探索:随机选动作
            action = random.randint(0, self.n_actions - 1)
        else:
            # 利用:选当前状态下Q值最大的动作
            action = np.argmax(self.q_table[state, :])
        return action
    
    def learn(self, state, action, reward, next_state):
        """更新Q表"""
        # 贝尔曼方程
        q_predict = self.q_table[state, action]
        q_target = reward + self.gamma * np.max(self.q_table[next_state, :])
        self.q_table[state, action] += self.alpha * (q_target - q_predict)

# 3. 训练智能体
if __name__ == "__main__":
    env = MazeEnv()
    agent = QLearningAgent(n_states=16, n_actions=4)
    episodes = 1000  # 训练轮数
    
    for episode in range(episodes):
        state = env.reset()
        done = False
        total_reward = 0
        
        while not done:
            action = agent.choose_action(state)
            next_state, reward, done = env.step(action)
            agent.learn(state, action, reward, next_state)
            state = next_state
            total_reward += reward
        
        # 每100轮打印一次训练进度
        if (episode + 1) % 100 == 0:
            print(f"Episode {episode+1}, Total Reward: {total_reward:.1f}")
    
    # 打印训练后的Q表(重点看起点和终点附近的Q值)
    print("\n训练后的Q表(部分):")
    print("起点(状态0)的Q值:", agent.q_table[0])
    print("终点前一步(状态14)的Q值:", agent.q_table[14])
代码说明
  • 环境类(MazeEnv):模拟迷宫规则,处理动作执行、边界/障碍物检查,返回奖励和新状态;
  • 智能体类(QLearningAgent) :核心是choose_action(ε-贪心策略平衡探索与利用)和learn(贝尔曼方程更新Q表);
  • 训练过程:循环1000轮,每轮从起点重置,直到到达终点,最终Q表会收敛到最优值。
运行结果

训练完成后,起点(状态0)的Q值会明显偏向"向右"或"向下"的最优动作,终点前一步(状态14)的Q值中"向右"动作的Q值会远高于其他动作,说明智能体已学会最优路径。

3. Q-learning的局限性

Q-learning虽然简单易实现,但存在明显短板:

  • 依赖Q表存储,仅适用于离散、小规模状态空间(如4x4迷宫),无法处理连续状态(如自动驾驶的传感器数据);
  • ε-贪心策略的探索效率低,容易陷入局部最优;
  • 对超参数(α、γ、ε)敏感,调参成本高。

三、工业界主流:PPO算法

为解决值函数方法的局限性,策略梯度方法应运而生------直接优化策略函数,而非价值函数。PPO是策略梯度的改进版,凭借"稳定、高效、易实现"成为当前强化学习的主流算法。

1. PPO核心原理

PPO的核心是近端策略优化 ,通过限制策略更新的幅度(Clip机制),避免策略突变导致训练崩溃:
L C L I P ( θ ) = E ^ t [ min ⁡ ( r t ( θ ) A ^ t , clip ( r t ( θ ) , 1 − ϵ , 1 + ϵ ) A ^ t ) ] L^{CLIP}(\theta) = \hat{\mathbb{E}}_t \left[ \min(r_t(\theta) \hat{A}_t, \text{clip}(r_t(\theta), 1-\epsilon, 1+\epsilon) \hat{A}_t) \right] LCLIP(θ)=E^t[min(rt(θ)A^t,clip(rt(θ),1−ϵ,1+ϵ)A^t)]

  • r t ( θ ) r_t(\theta) rt(θ):新旧策略的概率比;
  • A ^ t \hat{A}_t A^t:优势函数(衡量动作的好坏);
  • clip \text{clip} clip:将策略更新幅度限制在 [ 1 − ϵ , 1 + ϵ ] [1-\epsilon, 1+\epsilon] [1−ϵ,1+ϵ](通常ε=0.2)。

简单来说,PPO通过"裁剪"策略更新的幅度,保证每一步更新都在"安全范围"内,既避免训练崩溃,又能稳定提升策略。

2. PPO实战:CartPole平衡

我们用OpenAI Gym的CartPole环境实现PPO,让智能体学会平衡小车和杆子(经典强化学习入门任务)。

前置条件

先安装依赖:

bash 复制代码
pip install gymnasium torch numpy
完整代码
python 复制代码
import torch
import torch.nn as nn
import torch.optim as optim
import numpy as np
import gymnasium as gym

# 1. 定义策略网络(Actor-Critic架构)
class ActorCritic(nn.Module):
    def __init__(self, state_dim, action_dim, hidden_dim=64):
        super(ActorCritic, self).__init__()
        # 策略网络(Actor):输出动作概率
        self.actor = nn.Sequential(
            nn.Linear(state_dim, hidden_dim),
            nn.Tanh(),
            nn.Linear(hidden_dim, hidden_dim),
            nn.Tanh(),
            nn.Linear(hidden_dim, action_dim),
            nn.Softmax(dim=-1)
        )
        # 价值网络(Critic):输出状态价值
        self.critic = nn.Sequential(
            nn.Linear(state_dim, hidden_dim),
            nn.Tanh(),
            nn.Linear(hidden_dim, hidden_dim),
            nn.Tanh(),
            nn.Linear(hidden_dim, 1)
        )
    
    def get_action(self, state):
        """输入状态,输出动作和对数概率"""
        state = torch.tensor(state, dtype=torch.float32)
        probs = self.actor(state)
        dist = torch.distributions.Categorical(probs)
        action = dist.sample()
        return action.item(), dist.log_prob(action)
    
    def evaluate(self, state, action):
        """评估动作的对数概率和状态价值"""
        state = torch.tensor(state, dtype=torch.float32)
        action = torch.tensor(action, dtype=torch.int64)
        probs = self.actor(state)
        dist = torch.distributions.Categorical(probs)
        log_prob = dist.log_prob(action)
        value = self.critic(state)
        return log_prob, value

# 2. 实现PPO智能体
class PPOAgent:
    def __init__(self, state_dim, action_dim, lr=3e-4, gamma=0.99, clip_epsilon=0.2, k_epochs=4):
        self.model = ActorCritic(state_dim, action_dim)
        self.optimizer = optim.Adam(self.model.parameters(), lr=lr)
        self.gamma = gamma
        self.clip_epsilon = clip_epsilon
        self.k_epochs = k_epochs  # 每批数据训练轮数
        
        # 存储轨迹数据
        self.states = []
        self.actions = []
        self.log_probs = []
        self.rewards = []
        self.dones = []
    
    def store_data(self, state, action, log_prob, reward, done):
        """存储单步轨迹数据"""
        self.states.append(state)
        self.actions.append(action)
        self.log_probs.append(log_prob)
        self.rewards.append(reward)
        self.dones.append(done)
    
    def compute_advantages(self):
        """计算优势函数和累计回报"""
        advantages = []
        advantage = 0
        returns = []
        return_ = 0
        
        # 从后往前计算
        for i in reversed(range(len(self.rewards))):
            # 累计回报:r_t + γ*r_{t+1} + ... + γ^n*r_{t+n}
            return_ = self.rewards[i] + self.gamma * return_ * (1 - self.dones[i])
            returns.insert(0, return_)
            
            # 优势函数:A_t = Q_t - V_t = (r_t + γ*V_{t+1}) - V_t
            value = self.model.critic(torch.tensor(self.states[i], dtype=torch.float32)).item()
            next_value = self.model.critic(torch.tensor(self.states[i+1], dtype=torch.float32)).item() if i < len(self.states)-1 else 0
            td_error = self.rewards[i] + self.gamma * next_value * (1 - self.dones[i]) - value
            advantage = td_error + self.gamma * 0.95 * advantage * (1 - self.dones[i])  # GAE
            advantages.insert(0, advantage)
        
        # 标准化优势函数
        advantages = torch.tensor(advantages, dtype=torch.float32)
        advantages = (advantages - advantages.mean()) / (advantages.std() + 1e-8)
        returns = torch.tensor(returns, dtype=torch.float32)
        
        return advantages, returns
    
    def update(self):
        """PPO策略更新"""
        # 准备数据
        old_log_probs = torch.tensor(self.log_probs, dtype=torch.float32)
        states = torch.tensor(self.states, dtype=torch.float32)
        actions = torch.tensor(self.actions, dtype=torch.int64)
        advantages, returns = self.compute_advantages()
        
        # 多轮训练
        for _ in range(self.k_epochs):
            # 计算当前策略的对数概率和价值
            current_log_probs, values = self.model.evaluate(states, actions)
            
            # 策略比率:r_t(θ) = π_θ(a_t|s_t) / π_θ_old(a_t|s_t)
            ratio = torch.exp(current_log_probs - old_log_probs.detach())
            
            # Clip损失
            surr1 = ratio * advantages
            surr2 = torch.clamp(ratio, 1 - self.clip_epsilon, 1 + self.clip_epsilon) * advantages
            actor_loss = -torch.min(surr1, surr2).mean()
            
            # 价值损失(MSE)
            critic_loss = nn.MSELoss()(values.squeeze(), returns)
            
            # 总损失
            total_loss = actor_loss + 0.5 * critic_loss
            
            # 反向传播
            self.optimizer.zero_grad()
            total_loss.backward()
            self.optimizer.step()
        
        # 清空轨迹数据
        self.states = []
        self.actions = []
        self.log_probs = []
        self.rewards = []
        self.dones = []

# 3. 训练PPO智能体
if __name__ == "__main__":
    # 初始化环境(CartPole-v1)
    env = gym.make("CartPole-v1")
    state_dim = env.observation_space.shape[0]  # 状态维度:4
    action_dim = env.action_space.n  # 动作维度:2(左/右)
    
    # 初始化智能体
    agent = PPOAgent(state_dim, action_dim)
    episodes = 200  # 训练轮数
    max_steps = 500  # 每轮最大步数
    
    for episode in range(episodes):
        state, _ = env.reset()
        total_reward = 0
        
        for step in range(max_steps):
            # 选择动作
            action, log_prob = agent.model.get_action(state)
            # 执行动作
            next_state, reward, done, truncated, _ = env.step(action)
            total_reward += reward
            
            # 存储数据
            agent.store_data(state, action, log_prob, reward, done or truncated)
            
            state = next_state
            
            # 每200步更新一次策略(或结束时)
            if (step + 1) % 200 == 0 or done or truncated:
                agent.update()
                break
        
        # 打印训练进度
        print(f"Episode {episode+1}, Total Reward: {total_reward}")
        
        # 当奖励稳定在500时,训练完成
        if total_reward >= 500:
            print("训练完成!智能体已稳定平衡CartPole")
            break
代码说明
  • Actor-Critic网络:Actor输出动作概率(策略),Critic输出状态价值(评估);
  • PPOAgent类 :核心是compute_advantages(计算优势函数,衡量动作好坏)和update(Clip机制限制策略更新幅度);
  • 训练过程:每轮与环境交互,存储轨迹数据,累计一定步数后更新策略,直到奖励稳定在500(CartPole的最大奖励)。
运行结果

训练100-200轮后,智能体可稳定将CartPole平衡500步,说明策略已收敛到最优。

四、Q-learning vs PPO:核心差异

维度 Q-learning PPO
算法类型 值函数方法 策略梯度方法(Actor-Critic)
状态空间 适合离散、小规模 适合连续/离散、大规模
训练稳定性 易震荡,超参数敏感 稳定,Clip机制限制更新幅度
落地难度 简单,仅需Q表 稍复杂,但有成熟框架支持
工业应用 小规模场景(如简单游戏) 主流(如机器人、游戏AI、推荐系统)

五、强化学习学习建议

  1. 先掌握基础:理解马尔可夫决策过程(MDP)、贝尔曼方程、策略/价值函数等核心概念;
  2. 从代码入手:先实现Q-learning,理解"值迭代"的逻辑,再过渡到PPO的"策略优化";
  3. 借助成熟框架:工业界常用Stable Baselines3、RLlib等库,可直接调用PPO等算法,聚焦业务场景;
  4. 关注实际问题:强化学习的核心痛点是样本效率低、泛化能力弱,可重点学习数据增强、预训练等优化方法。

总结

  1. Q-learning是强化学习入门的经典值函数算法,核心是通过贝尔曼方程迭代更新Q表,适合离散、小规模场景;
  2. PPO是工业界主流的策略梯度算法,通过Clip机制限制策略更新幅度,训练稳定、适配复杂场景,是强化学习落地的首选;
  3. 从Q-learning到PPO的学习,本质是从"值迭代"到"策略优化"的思维转变,代码实操是理解算法的关键。

强化学习的学习没有捷径,唯有结合原理和实战,才能真正掌握其核心逻辑。希望本文的代码和解析能帮你打通从入门到实战的链路,后续可尝试将PPO应用到更复杂的场景(如Atari游戏、机器人控制),进一步深化理解。


✨ 坚持用 清晰的图解 +易懂的硬件架构 + 硬件解析, 让每个知识点都 简单明了 !

🚀 个人主页一只大侠的侠 · CSDN

💬 座右铭 : "所谓成功就是以自己的方式度过一生。"

相关推荐
Juicedata6 小时前
JuiceFS 企业版 5.3 特性详解:单文件系统支持超 5,000 亿文件,首次引入 RDMA
大数据·人工智能·机器学习·性能优化·开源
2301_790300966 小时前
Python单元测试(unittest)实战指南
jvm·数据库·python
VCR__6 小时前
python第三次作业
开发语言·python
韩立学长6 小时前
【开题答辩实录分享】以《助农信息发布系统设计与实现》为例进行选题答辩实录分享
python·web
码农水水6 小时前
得物Java面试被问:消息队列的死信队列和重试机制
java·开发语言·jvm·数据结构·机器学习·面试·职场和发展
小白狮ww7 小时前
Ovis-Image:卓越的图像生成模型
人工智能·深度学习·目标检测·机器学习·cpu·gpu·视觉分割模型
2401_838472517 小时前
使用Scikit-learn构建你的第一个机器学习模型
jvm·数据库·python
u0109272717 小时前
使用Python进行网络设备自动配置
jvm·数据库·python
工程师老罗7 小时前
优化器、反向传播、损失函数之间是什么关系,Pytorch中如何使用和设置?
人工智能·pytorch·python
Fleshy数模7 小时前
我的第一只Python爬虫:从Requests库到爬取整站新书
开发语言·爬虫·python