引言:强化学习在智能决策中的核心地位

强化学习(Reinforcement Learning, RL)是机器学习的一个重要分支,它专注于让智能体(Agent)通过与环境的交互来学习最优决策策略。与监督学习和无监督学习不同,强化学习强调通过奖励信号来指导学习过程,这使得它非常适合解决序列决策问题,如游戏AI、机器人控制、自动驾驶和推荐系统等。

在古月居的强化学习课程中,我们将从零开始,系统地讲解强化学习的核心算法和实战应用技巧。本文将详细阐述强化学习的基本概念、核心算法(包括值函数方法和策略梯度方法)、深度强化学习的融合,以及实际项目中的应用技巧。我们将通过完整的代码示例和详细的解释,帮助读者逐步掌握智能决策的核心技术。

强化学习的魅力在于它能够处理不确定环境下的决策问题,通过试错(trial-and-error)来优化行为。例如,在一个迷宫导航任务中,智能体需要学习如何从起点到达终点,避免陷阱,并最大化总奖励。这种框架直接模拟了人类和动物的学习方式,使其在复杂系统中具有巨大潜力。

本文的结构如下:首先介绍强化学习基础;然后深入核心算法,包括Q-Learning、Deep Q-Network (DQN) 和策略梯度;接着讨论实战应用技巧;最后通过一个完整项目示例来巩固知识。每个部分都会提供详细的解释和代码实现,确保读者能够动手实践。

强化学习基础概念

强化学习的定义与要素

强化学习的核心是马尔可夫决策过程(Markov Decision Process, MDP),它定义了以下关键要素:

  • 状态(State, S):环境的当前描述。例如,在一个网格世界中,状态可以是智能体的位置坐标。
  • 动作(Action, A):智能体可以执行的操作。例如,向上、向下、向左、向右移动。
  • 策略(Policy, π):从状态到动作的映射函数。策略可以是确定性的(π(s) = a)或随机性的(π(a|s) = P(A=a|S=s))。
  • 奖励(Reward, R):环境对智能体动作的即时反馈。例如,到达目标获得+1奖励,撞墙获得-1奖励。
  • 价值函数(Value Function):评估状态或状态-动作对的长期累积奖励。状态价值函数 V(s) = E[Σ γ^t R_t | S_t = s],其中 γ 是折扣因子(0 ≤ γ < 1),用于平衡即时和未来奖励。
  • 环境模型(Model):可选的,用于预测状态转移 P(s’|s,a) 和奖励 R(s,a,s’)。有模型方法使用模型规划,无模型方法通过交互学习。

强化学习的目标是找到最优策略 π*,最大化期望累积奖励 J(π) = E[Σ γ^t R_t]。

强化学习与监督学习的区别

监督学习依赖于标注数据(输入-输出对),而强化学习通过奖励信号学习,没有明确的“正确答案”。这使得RL更适合探索未知环境,但学习过程更慢且不稳定。例如,在图像分类中,监督学习直接学习标签;在RL中,智能体必须通过多次尝试来推断最佳动作。

示例:简单环境建模

考虑一个简单的“悬崖行走”环境:一个1x4的网格,从状态0到3,目标是到达状态3,但状态2有悬崖(负奖励)。状态转移是确定性的,动作只有左右移动。

用Python代码表示环境(使用Gym-like接口):

import numpy as np

class CliffWalkingEnv:
    def __init__(self):
        self.n_states = 4
        self.n_actions = 2  # 0: left, 1: right
        self.state = 0
        self.done = False
    
    def reset(self):
        self.state = 0
        self.done = False
        return self.state
    
    def step(self, action):
        if self.done:
            return self.state, 0, True
        
        if action == 0:  # left
            self.state = max(0, self.state - 1)
        else:  # right
            self.state = min(3, self.state + 1)
        
        reward = -1  # 每步负奖励
        if self.state == 3:
            reward = 0  # 目标奖励
            self.done = True
        elif self.state == 2:
            reward = -100  # 悬崖惩罚
            self.done = True
        
        return self.state, reward, self.done

# 测试环境
env = CliffWalkingEnv()
state = env.reset()
print(f"初始状态: {state}")
state, reward, done = env.step(1)  # 向右
print(f"执行动作1后: 状态={state}, 奖励={reward}, 结束={done}")

这个代码定义了一个简单的MDP环境。智能体需要学习避免状态2,优先向右移动。通过多次交互,RL算法会逐步优化策略。

核心算法:值函数方法

值函数方法通过估计状态价值或动作价值来指导决策。最著名的是Q-Learning,它学习动作价值函数 Q(s,a),表示在状态s执行动作a的长期期望奖励。

Q-Learning算法

Q-Learning是一种无模型、off-policy算法,更新规则为: Q(s,a) ← Q(s,a) + α [R + γ max_a’ Q(s’,a’) - Q(s,a)] 其中 α 是学习率,γ 是折扣因子。

它通过ε-贪婪策略探索:以概率ε随机选择动作,否则选择Q值最大的动作。

完整代码实现:Q-Learning on Cliff Walking

我们将实现Q-Learning来解决上述环境。使用表格表示Q函数(适用于小状态空间)。

import numpy as np
import random

class QLearningAgent:
    def __init__(self, n_states, n_actions, alpha=0.1, gamma=0.99, epsilon=0.1):
        self.n_states = n_states
        self.n_actions = n_actions
        self.alpha = alpha  # 学习率
        self.gamma = gamma  # 折扣因子
        self.epsilon = epsilon  # 探索率
        self.q_table = np.zeros((n_states, n_actions))  # Q表初始化为0
    
    def choose_action(self, state):
        if random.random() < self.epsilon:
            return random.randint(0, self.n_actions - 1)  # 随机探索
        else:
            return np.argmax(self.q_table[state])  # 利用
    
    def update(self, state, action, reward, next_state):
        best_next_action = np.argmax(self.q_table[next_state])
        td_target = reward + self.gamma * self.q_table[next_state][best_next_action]
        td_error = td_target - self.q_table[state][action]
        self.q_table[state][action] += self.alpha * td_error
    
    def get_policy(self):
        return np.argmax(self.q_table, axis=1)

# 训练循环
env = CliffWalkingEnv()
agent = QLearningAgent(env.n_states, env.n_actions)

n_episodes = 1000
for episode in range(n_episodes):
    state = env.reset()
    done = False
    while not done:
        action = agent.choose_action(state)
        next_state, reward, done = env.step(action)
        agent.update(state, action, reward, next_state)
        state = next_state

# 测试策略
policy = agent.get_policy()
print("学习到的策略 (0:左, 1:右):", policy)
print("Q表:\n", agent.q_table)

详细解释

  • 初始化:Q表是一个4x2矩阵,初始全0。每个条目Q(s,a)表示期望累积奖励。
  • 动作选择:ε-贪婪确保探索。例如,episode 1中,智能体可能随机左右移动,撞到悬崖获得-100,学习避免。
  • 更新:使用TD误差更新。假设在状态1向右到状态2(悬崖),奖励-100,next_state=2,max Q(2,a)=0(因为done),则更新Q(1,1) = 0 + 0.1*(-100 + 0.99*0 - 0) = -10,降低该动作价值。
  • 训练后:策略应为[1,1,?,1],即从状态0和1向右,避免状态2。运行多次episode,Q表收敛,智能体学会最优路径(0→1→3)。

这个例子展示了Q-Learning如何从零开始学习决策。对于更大状态空间,表格方法不可行,需要函数逼近。

Deep Q-Network (DQN)

DQN用神经网络逼近Q函数,解决高维状态问题(如Atari游戏)。核心创新:

  • 经验回放(Experience Replay):存储转移(s,a,r,s’)到缓冲区,随机采样训练,打破数据相关性。
  • 目标网络:单独的网络计算目标Q值,稳定训练。

DQN代码示例:CartPole环境(Gym库)

假设安装gym:pip install gym。CartPole是一个平衡杆任务,状态是4维向量(位置、速度等),动作是左右推。

import gym
import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
from collections import deque
import random

class DQN(nn.Module):
    def __init__(self, state_dim, action_dim):
        super(DQN, self).__init__()
        self.fc1 = nn.Linear(state_dim, 128)
        self.fc2 = nn.Linear(128, 128)
        self.fc3 = nn.Linear(128, action_dim)
    
    def forward(self, x):
        x = torch.relu(self.fc1(x))
        x = torch.relu(self.fc2(x))
        return self.fc3(x)

class DQNAgent:
    def __init__(self, state_dim, action_dim, gamma=0.99, lr=0.001, epsilon=0.1, buffer_size=10000, batch_size=64):
        self.action_dim = action_dim
        self.gamma = gamma
        self.epsilon = epsilon
        self.batch_size = batch_size
        self.memory = deque(maxlen=buffer_size)
        
        self.policy_net = DQN(state_dim, action_dim)
        self.target_net = DQN(state_dim, action_dim)
        self.target_net.load_state_dict(self.policy_net.state_dict())
        self.target_net.eval()
        
        self.optimizer = optim.Adam(self.policy_net.parameters(), lr=lr)
        self.criterion = nn.MSELoss()
    
    def choose_action(self, state):
        if random.random() < self.epsilon:
            return random.randint(0, self.action_dim - 1)
        else:
            with torch.no_grad():
                state_tensor = torch.FloatTensor(state).unsqueeze(0)
                q_values = self.policy_net(state_tensor)
                return q_values.argmax().item()
    
    def store_transition(self, state, action, reward, next_state, done):
        self.memory.append((state, action, reward, next_state, done))
    
    def update(self):
        if len(self.memory) < self.batch_size:
            return
        
        batch = random.sample(self.memory, self.batch_size)
        states, actions, rewards, next_states, dones = zip(*batch)
        
        states = torch.FloatTensor(np.array(states))
        actions = torch.LongTensor(actions).unsqueeze(1)
        rewards = torch.FloatTensor(rewards)
        next_states = torch.FloatTensor(np.array(next_states))
        dones = torch.FloatTensor(dones)
        
        # 当前Q值
        current_q = self.policy_net(states).gather(1, actions).squeeze()
        
        # 目标Q值
        with torch.no_grad():
            next_q = self.target_net(next_states).max(1)[0]
            target_q = rewards + self.gamma * next_q * (1 - dones)
        
        loss = self.criterion(current_q, target_q)
        self.optimizer.zero_grad()
        loss.backward()
        self.optimizer.step()
    
    def update_target_net(self):
        self.target_net.load_state_dict(self.policy_net.state_dict())

# 训练
env = gym.make('CartPole-v1')
state_dim = env.observation_space.shape[0]
action_dim = env.action_space.n
agent = DQNAgent(state_dim, action_dim)

n_episodes = 500
for episode in range(n_episodes):
    state = env.reset()
    if isinstance(state, tuple): state = state[0]  # Gym新版本处理
    done = False
    total_reward = 0
    while not done:
        action = agent.choose_action(state)
        next_state, reward, done, truncated, info = env.step(action)
        if truncated: done = True
        agent.store_transition(state, action, reward, next_state, done)
        agent.update()
        state = next_state
        total_reward += reward
    
    if episode % 10 == 0:
        agent.update_target_net()
    
    print(f"Episode {episode}: Total Reward = {total_reward}")

# 测试
env = gym.make('CartPole-v1', render_mode='human')
state = env.reset()[0]
done = False
while not done:
    action = agent.choose_action(state)
    state, _, done, _, _ = env.step(action)
env.close()

详细解释

  • 网络结构:简单MLP,输入状态(4维),输出每个动作的Q值。
  • 经验回放:存储转移,采样batch计算损失。目标Q使用目标网络,避免自举偏差。
  • 训练过程:每个episode收集数据,更新网络。CartPole目标是平衡>195步,DQN通常在200episode内收敛。
  • 可视化:测试时渲染环境,观察杆平衡。代码使用PyTorch,易于扩展到CNN处理图像状态。

DQN是深度RL的基石,适用于视觉输入,如Atari游戏。

核心算法:策略梯度方法

值函数方法间接优化策略,而策略梯度直接优化策略参数θ,通过梯度上升最大化J(θ) = E[Σ R_t]。

REINFORCE算法

REINFORCE是蒙特卡洛策略梯度,更新规则: ∇θ J(θ) ≈ Σ_t ∇θ log π(a_t|s_t; θ) * G_t 其中G_t是从t开始的累积回报。

代码示例:REINFORCE on CartPole

import gym
import torch
import torch.nn as nn
import torch.optim as optim
from torch.distributions import Categorical
import numpy as np

class PolicyNetwork(nn.Module):
    def __init__(self, state_dim, action_dim):
        super(PolicyNetwork, self).__init__()
        self.fc1 = nn.Linear(state_dim, 128)
        self.fc2 = nn.Linear(128, action_dim)
    
    def forward(self, x):
        x = torch.relu(self.fc1(x))
        return torch.softmax(self.fc2(x), dim=-1)

class REINFORCEAgent:
    def __init__(self, state_dim, action_dim, lr=0.001, gamma=0.99):
        self.policy = PolicyNetwork(state_dim, action_dim)
        self.optimizer = optim.Adam(self.policy.parameters(), lr=lr)
        self.gamma = gamma
        self.log_probs = []
        self.rewards = []
    
    def choose_action(self, state):
        state_tensor = torch.FloatTensor(state).unsqueeze(0)
        probs = self.policy(state_tensor)
        dist = Categorical(probs)
        action = dist.sample()
        self.log_probs.append(dist.log_prob(action))
        return action.item()
    
    def update(self):
        # 计算累积回报
        returns = []
        R = 0
        for r in reversed(self.rewards):
            R = r + self.gamma * R
            returns.insert(0, R)
        returns = torch.FloatTensor(returns)
        returns = (returns - returns.mean()) / (returns.std() + 1e-8)  # 归一化
        
        policy_loss = []
        for log_prob, R in zip(self.log_probs, returns):
            policy_loss.append(-log_prob * R)
        
        self.optimizer.zero_grad()
        policy_loss = torch.stack(policy_loss).sum()
        policy_loss.backward()
        self.optimizer.step()
        
        self.log_probs = []
        self.rewards = []

# 训练
env = gym.make('CartPole-v1')
state_dim = env.observation_space.shape[0]
action_dim = env.action_space.n
agent = REINFORCEAgent(state_dim, action_dim)

n_episodes = 500
for episode in range(n_episodes):
    state = env.reset()[0]
    done = False
    while not done:
        action = agent.choose_action(state)
        next_state, reward, done, truncated, _ = env.step(action)
        if truncated: done = True
        agent.rewards.append(reward)
        state = next_state
    agent.update()
    total_reward = sum(agent.rewards)
    print(f"Episode {episode}: Total Reward = {total_reward}")

详细解释

  • 策略网络:输出动作概率,采样动作。
  • 更新:计算每个时间步的回报G_t,使用对数概率梯度更新。归一化回报减少方差。
  • 优势:直接处理连续动作或随机策略。缺点:高方差,需要大量episode。
  • 改进:Actor-Critic结合值函数(如A2C),减少方差。

实战应用技巧

1. 超参数调优

  • 学习率α/γ:从小值开始(0.001-0.1),使用学习率衰减。
  • 探索ε:从1.0衰减到0.01,避免过早收敛。
  • 技巧:使用网格搜索或贝叶斯优化。监控Q值或回报曲线,避免发散。

2. 处理稀疏奖励

  • 奖励塑形:添加中间奖励,如接近目标的奖励。
  • 好奇心驱动:添加内在奖励,鼓励探索未知状态。
  • 示例:在机器人路径规划中,如果仅终点有奖励,添加距离负奖励。

3. 稳定训练

  • 经验回放:DQN中,缓冲区大小10k-100k,采样均匀。
  • 目标网络更新:每C步同步,C=100-1000。
  • 梯度裁剪:防止爆炸,torch.nn.utils.clip_gradnorm
  • 并行化:使用多环境实例加速数据收集。

4. 评估与调试

  • 指标:平均回报、学习曲线、策略熵(探索度)。
  • 可视化:TensorBoard记录损失和奖励。
  • 常见问题:Q值爆炸(降低α)、策略崩溃(增加探索)。

5. 工具与库

  • Gymnasium:标准环境接口(原Gym)。
  • Stable Baselines3:预实现算法(PPO, SAC)。
  • RLlib:分布式RL,支持多智能体。

实战项目:多臂老虎机(Multi-Armed Bandit)与RL结合

多臂老虎机是RL的简化版,模拟探索-利用权衡。我们用它入门,然后扩展到完整RL。

问题描述

有K=4个老虎机臂,每个臂有未知奖励分布(均值μ_k,标准差1)。目标:最大化1000次拉动的总奖励。

UCB算法(Upper Confidence Bound)

UCB选择臂a = argmax [Q(a) + c * sqrt(ln(t)/N(a))],其中t是总拉动次数,N(a)是臂a拉动次数,c是探索常数。

代码实现

import numpy as np
import matplotlib.pyplot as plt

class BanditEnv:
    def __init__(self, k=4):
        self.k = k
        self.means = np.random.normal(0, 1, k)  # 随机均值
        self.arms = [np.random.normal(m, 1) for m in self.means]  # 奖励分布
    
    def pull(self, arm):
        return np.random.normal(self.means[arm], 1)

class UCBAgent:
    def __init__(self, k, c=2.0):
        self.k = k
        self.c = c
        self.q_values = np.zeros(k)  # 估计均值
        self.counts = np.zeros(k)    # 拉动次数
        self.total_counts = 0
    
    def choose_arm(self):
        if self.total_counts < self.k:  # 初始探索所有臂
            return self.total_counts
        ucb_values = self.q_values + self.c * np.sqrt(np.log(self.total_counts) / (self.counts + 1e-5))
        return np.argmax(ucb_values)
    
    def update(self, arm, reward):
        self.counts[arm] += 1
        self.total_counts += 1
        self.q_values[arm] += (reward - self.q_values[arm]) / self.counts[arm]

# 模拟
env = BanditEnv(k=4)
agent = UCBAgent(4, c=2.0)
n_steps = 1000
rewards = []
optimal_arms = []

for t in range(n_steps):
    arm = agent.choose_arm()
    reward = env.pull(arm)
    agent.update(arm, reward)
    rewards.append(reward)
    optimal_arms.append(1 if arm == np.argmax(env.means) else 0)

# 结果
cumulative_reward = np.cumsum(rewards)
optimal_rate = np.cumsum(optimal_arms) / np.arange(1, n_steps + 1)

print(f"最终估计均值: {agent.q_values}")
print(f"真实均值: {env.means}")
print(f"最优臂选择率: {optimal_rate[-1]:.2f}")

plt.figure(figsize=(12, 4))
plt.subplot(1, 2, 1)
plt.plot(cumulative_reward)
plt.title("Cumulative Reward")
plt.xlabel("Steps")
plt.ylabel("Total Reward")

plt.subplot(1, 2, 2)
plt.plot(optimal_rate)
plt.title("Optimal Arm Selection Rate")
plt.xlabel("Steps")
plt.ylabel("Rate")
plt.show()

详细解释

  • 环境:4个臂,随机均值(如[-0.5, 1.2, 0.3, -1.0])。
  • UCB逻辑:优先高估计臂,但对低N(a)臂增加置信上限,鼓励探索。c=2平衡探索利用。
  • 更新:增量平均更新Q值。
  • 结果分析:初始随机,后期收敛到最优臂(最高均值)。曲线显示累积奖励增长和最优率提升。
  • 扩展到RL:将Bandit视为单步MDP,添加状态(如时间)可扩展为上下文Bandit,再结合DQN处理复杂状态。

这个项目展示了从简单决策到RL的过渡。实际应用中,Bandit用于A/B测试、广告投放。

结论与进一步学习

通过本文,我们从强化学习基础出发,深入核心算法如Q-Learning、DQN和REINFORCE,并提供了详细代码和解释。实战技巧帮助处理真实挑战,如调优和稳定性。项目示例(Bandit)桥接到更复杂任务。

古月居课程强调实践:建议读者运行代码,修改参数观察变化。进一步学习路径:

  • 阅读《Reinforcement Learning: An Introduction》(Sutton & Barto)。
  • 探索高级算法:PPO、SAC、DQN变体(Rainbow)。
  • 项目:使用RL训练Atari游戏AI或机器人模拟(MuJoCo)。

强化学习是AI决策的未来,掌握它将开启无限可能。继续实践,逐步构建智能系统!