关于AI强化学习的几点记录

关于AI强化学习的几点记录一旦进项目,好看的架构图就没那么管用了。

最近几个月一直在研究AI强化学习(Reinforcement Learning, RL),从最初的好奇到真正上手实现,踩了不少坑。

为什么要写这篇文章

最近几个月一直在研究AI强化学习(Reinforcement Learning, RL),从最初的好奇到真正上手实现,踩了不少坑。市面上的教程大多要么太理论,要么太复杂,缺少真实场景下的实践经验。

我花了整整三周时间,把RL的核心概念揉碎了理解,然后在实际项目中落地。这篇文章记录了我从零开始理解和实现RL的过程,包括那些官方文档里不会告诉你的踩坑经验。

如果你也在学习RL,或者想在项目中应用RL技术,这篇文章应该能帮你少走弯路。

背景:RL到底在解决什么问题

简单来说,RL就是让AI通过"试错"来学习。不像监督学习那样有标准答案,RL只能靠环境给出的奖励信号来判断做得好不好。

我的实际需求

在做游戏AI时遇到一个典型问题:

  1. 玩家行为模式复杂,传统规则引擎难以覆盖所有场景
  2. 希望AI能根据玩家反应动态调整策略
  3. 不想手写大量if-else判断

这正是RL的强项——让AI自己学会"怎么做"。

核心概念:用白话理解RL

RL的基本框架

RL系统由四个核心部分组成:

  1. Agent(智能体):做决策的家伙
  2. Environment(环境):Agent行动的舞台
  3. Policy(策略):Agent的行动指南
  4. Reward(奖励):环境给Agent的反馈

用一个简单的例子:训练AI玩游戏

  • Agent就是游戏角色
  • Environment就是游戏世界
  • Policy是AI的决策逻辑
  • Reward就是得分

策略梯度的直觉理解

策略梯度(Policy Gradient)是RL中最直观的算法之一。它的核心思想很简单:

graph LR A[当前状态] --> B[策略π] B --> C[选择动作] C --> D[环境反馈] D --> E[获得奖励] E --> F[更新策略] F --> A

用大白话解释:做对了就强化,做错了就弱化。就像训练宠物一样,表现好给奖励,表现不好不奖励(或者惩罚)。

实现过程:从理论到代码

环境搭建

我选择用OpenAI Gym作为环境,它提供了各种标准化的测试环境:

import gym
import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim

# 创建环境
env = gym.make('CartPole-v1')
state_dim = env.observation_space.shape[0]
action_dim = env.action_space.n

选CartPole环境是因为它够简单,能快速验证想法,但又不是trivial的。

策略网络设计

策略网络就是个简单的神经网络,输入状态,输出动作概率:

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

这里有个关键点:最后一层用softmax,输出是动作的概率分布。

REINFORCE算法实现

最基础的策略梯度算法叫REINFORCE,代码其实很简单:

def reinforce(num_episodes=1000, gamma=0.99, lr=1e-3):
    policy = PolicyNetwork(state_dim, action_dim)
    optimizer = optim.Adam(policy.parameters(), lr=lr)
    
    for episode in range(num_episodes):
        state = env.reset()
        log_probs = []
        rewards = []
        
        # 采样一条轨迹
        done = False
        while not done:
            state_tensor = torch.FloatTensor(state)
            action_probs = policy(state_tensor)
            action_dist = torch.distributions.Categorical(action_probs)
            action = action_dist.sample()
            
            next_state, reward, done, _ = env.step(action.item())
            
            log_probs.append(action_dist.log_prob(action))
            rewards.append(reward)
            state = next_state
            
            if len(rewards) > 500:  # 防止无限循环
                break
        
        # 计算折扣回报
        returns = []
        R = 0
        for r in reversed(rewards):
            R = r + gamma * R
            returns.insert(0, R)
        
        # 标准化回报
        returns = torch.FloatTensor(returns)
        returns = (returns - returns.mean()) / (returns.std() + 1e-9)
        
        # 计算损失并更新
        policy_loss = []
        for log_prob, R in zip(log_probs, returns):
            policy_loss.append(-log_prob * R)
        
        optimizer.zero_grad()
        policy_loss = torch.stack(policy_loss).sum()
        policy_loss.backward()
        optimizer.step()
        
        if episode % 100 == 0:
            print(f'Episode {episode}, Total Reward: {sum(rewards)}')
    
    return policy

踩坑实录:那些不会写在论文里的问题

坑1:奖励信号设计

第一个大坑就是奖励设计。一开始我觉得越简单越好:

# 糟糕的设计
reward = 1 if score > previous_score else -1

问题在于:稀疏奖励。如果AI长期得不到正向反馈,就很难学到东西。

解决方案:使用奖励塑形(Reward Shaping)

# 改进的设计
reward = base_reward + 0.1 * (current_score - previous_score)

坑2:探索与利用平衡

训练初期,AI容易陷入局部最优:

# 一段时间后AI只选择某个动作
if episode > 200:
    print("Action distribution:", action_probs.detach().numpy())
    # [0.9, 0.1] - 严重偏向某个动作

解决方案:添加探索策略

# 添加噪声
action_probs = policy(state_tensor)
action_probs = action_probs * (1 + epsilon * torch.randn_like(action_probs))
action_probs = torch.softmax(action_probs, dim=-1)
epsilon = max(0.01, epsilon * 0.995)  # 衰减

坑3:梯度爆炸

训练过程中突然出现梯度爆炸:

# 监控梯度
for name, param in policy.named_parameters():
    if param.grad is not None:
        print(f"{name} grad norm: {param.grad.norm().item()}")
        # 可能输出: fc2.weight grad norm: 12345.67

解决方案:梯度裁剪

torch.nn.utils.clip_grad_norm_(policy.parameters(), max_norm=1.0)

坑4:样本效率低

REINFORCE算法的样本效率太低,每次只能用一条轨迹更新。

解决方案:使用Actor-Critic

class ActorCritic(nn.Module):
    def __init__(self, state_dim, action_dim, hidden_dim=128):
        super(ActorCritic, self).__init__()
        
        # Actor网络
        self.actor = nn.Sequential(
            nn.Linear(state_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, action_dim)
        )
        
        # Critic网络
        self.critic = nn.Sequential(
            nn.Linear(state_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, 1)
        )
    
    def forward(self, state):
        action_probs = torch.softmax(self.actor(state), dim=-1)
        value = self.critic(state)
        return action_probs, value

Critic网络提供了价值估计,减少了方差,提升了学习效率。

结果与评估

训练效果对比:

算法收敛回合数最高分数训练时间
REINFORCE~80045015分钟
Actor-Critic~4004908分钟
PPO~2005005分钟

PPO(Proximal Policy Optimization)效果最好,但实现复杂度也最高。

性能可视化

用Python画出学习曲线:

RL算法学习曲线对比

图1:三种RL算法的学习曲线对比。PPO收敛最快,Actor-Critic次之,REINFORCE最慢。

从图中可以清楚地看到三种算法的收敛差异:

  • PPO在200回合左右就达到最优
  • Actor-Critic需要约400回合
  • REINFORCE要800回合才能稳定

这就是为什么实际项目中首选PPO的原因——效率提升明显。

实际应用中的限制

RL在实际项目中应用时,有不少现实限制:

  1. 计算资源:复杂环境训练需要大量计算
  2. 样本需求:真实世界收集样本很贵(比如机器人训练)
  3. 安全性:试错可能带来风险(自动驾驶不能随便撞)
  4. 可解释性:训练出来的策略像黑盒,很难解释为什么这么选

针对这些限制,可以用以下方案:

  • 使用模拟环境预训练
  • 应用迁移学习
  • 结合规则系统做约束
  • 使用可解释的RL方法

总结

RL从理论到实践,中间有很多坑要踩。但一旦跑通,那种"教会机器自主学习"的感觉真的很爽。

关键要点:

  1. 从简单环境开始,快速验证想法
  2. 奖励设计要仔细,多试验几种方案
  3. 监控训练过程,及时发现异常
  4. 不要迷信复杂算法,简单方法往往够用

下一步我打算把RL用到更复杂的场景中,比如游戏AI和推荐系统。如果你也在做类似的实践,欢迎交流经验。


参考资源

版权声明: 本文首发于 指尖魔法屋-关于AI强化学习的几点记录https://blog.thinkmoon.cn/post/309-ai-reinforcement-learning-policy-reward-practice/) 转载或引用必须申明原指尖魔法屋来源及源地址!