Q-Learning算法详解

Q-Learning是强化学习中最经典的无模型(model-free)算法之一,核心思想是通过学习"状态-动作价值函数"(即Q函数)来指导智能体的决策。它不需要对环境建模,直接从与环境的交互经验中学习最优策略。

Q-Learning的核心概念
  1. Q函数(Q-Value Function)

    • 定义:Q(s,a)表示在状态s下执行动作a的长期价值(即未来可能获得的累积奖励总和)
    • 作用:智能体通过比较不同动作的Q值,选择Q值最高的动作
  2. Q值更新公式

    Q(s,a) ← Q(s,a) + α[r + γ·maxₐ'Q(s',a') - Q(s,a)]
    

    其中:

    • α是学习率(0 < α ≤ 1),控制更新幅度
    • γ是折扣因子(0 ≤ γ ≤ 1),表示未来奖励的重要程度
    • r是执行动作a后获得的即时奖励
    • s’是执行动作a后到达的新状态
    • maxₐ’Q(s’,a’)表示新状态s’下所有可能动作的最大Q值
  3. ε-贪婪策略(ε-greedy)

    • 以ε的概率随机选择动作(探索)
    • 以1-ε的概率选择当前Q值最高的动作(利用)
    • 平衡探索(发现新策略)和利用(获取已知奖励)
Q-Learning算法流程
  1. 初始化Q表(Q(s,a)),通常初始化为0或随机小值
  2. 对于每个回合(episode):
    a. 初始化状态s
    b. 当s不是终止状态时:
    i. 基于ε-贪婪策略,在状态s选择动作a
    ii. 执行动作a,获得奖励r和新状态s’
    iii. 使用Q值更新公式更新Q(s,a)
    iv. 将状态更新为s’
  3. 重复步骤2,直到Q表收敛

机器人控制实例:迷宫导航

让我们以机器人在迷宫中寻找目标为例,具体说明Q-Learning的应用。

假设机器人需要在一个5x5的迷宫中找到目标位置,机器人可以向上、下、左、右四个方向移动:

  • 碰到墙壁或边界时,不能移动且获得-1惩罚
  • 每移动一步获得-0.1惩罚(鼓励最短路径)
  • 到达目标位置获得+10奖励
  • 到达目标后 episode 结束

代码解析

import numpy as np
import matplotlib.pyplot as plt

class MazeEnv:
    """迷宫环境"""
    def __init__(self):
        # 5x5的迷宫,0表示空地,1表示墙壁,2表示目标
        self.maze = [
            [0, 1, 0, 0, 0],
            [0, 1, 0, 1, 0],
            [0, 0, 0, 1, 0],
            [1, 1, 0, 1, 1],
            [0, 0, 0, 0, 2]
        ]
        self.rows = 5
        self.cols = 5
        self.reset()
        
    def reset(self):
        """重置环境,回到起点"""
        self.robot_pos = [0, 0]  # 机器人初始位置
        return tuple(self.robot_pos)
    
    def step(self, action):
        """执行动作,返回新状态、奖励和是否结束"""
        # 动作:0-上,1-右,2-下,3-左
        row, col = self.robot_pos
        new_row, new_col = row, col
        done = False
        
        if action == 0:  # 上
            new_row -= 1
        elif action == 1:  # 右
            new_col += 1
        elif action == 2:  # 下
            new_row += 1
        elif action == 3:  # 左
            new_col -= 1
            
        # 检查是否撞墙或越界
        if (new_row < 0 or new_row >= self.rows or 
            new_col < 0 or new_col >= self.cols or 
            self.maze[new_row][new_col] == 1):
            # 撞墙,位置不变,给予惩罚
            reward = -1
        else:
            # 移动成功
            self.robot_pos = [new_row, new_col]
            new_row, new_col = self.robot_pos
            
            # 检查是否到达目标
            if self.maze[new_row][new_col] == 2:
                reward = 10  # 到达目标,给予奖励
                done = True
            else:
                reward = -0.1  # 每步轻微惩罚,鼓励最短路径
                done = False
                
        return tuple(self.robot_pos), reward, done
    
    def render(self):
        """绘制迷宫和机器人位置"""
        for i in range(self.rows):
            for j in range(self.cols):
                if [i, j] == self.robot_pos:
                    print("R", end=" ")  # 机器人
                elif self.maze[i][j] == 1:
                    print("#", end=" ")  # 墙壁
                elif self.maze[i][j] == 2:
                    print("G", end=" ")  # 目标
                else:
                    print(".", end=" ")  # 空地
            print()
        print()


class QLearningRobot:
    """基于Q-Learning的机器人"""
    def __init__(self, env, learning_rate=0.1, gamma=0.9, epsilon=0.1):
        self.env = env
        self.learning_rate = learning_rate  # 学习率α
        self.gamma = gamma  # 折扣因子γ
        self.epsilon = epsilon  # ε-贪婪策略的ε值
        
        # 初始化Q表,状态是位置(row, col),动作是0-3
        self.q_table = {}
        for i in range(env.rows):
            for j in range(env.cols):
                self.q_table[(i, j)] = [0.0, 0.0, 0.0, 0.0]  # 上下左右四个动作
        
    def choose_action(self, state):
        """基于ε-贪婪策略选择动作"""
        if np.random.uniform(0, 1) < self.epsilon:
            # 随机选择动作(探索)
            return np.random.choice(4)
        else:
            # 选择Q值最大的动作(利用)
            return np.argmax(self.q_table[state])
    
    def learn(self, state, action, reward, next_state):
        """更新Q值"""
        # 当前Q值
        current_q = self.q_table[state][action]
        # 新状态的最大Q值
        max_next_q = max(self.q_table[next_state])
        # Q值更新公式
        new_q = current_q + self.learning_rate * (reward + self.gamma * max_next_q - current_q)
        self.q_table[state][action] = new_q
    
    def train(self, episodes=1000):
        """训练机器人"""
        rewards = []  # 记录每回合的总奖励
        steps = []    # 记录每回合的步数
        
        for episode in range(episodes):
            state = self.env.reset()
            total_reward = 0
            step = 0
            done = False
            
            while not done:
                action = self.choose_action(state)
                next_state, reward, done = self.env.step(action)
                self.learn(state, action, reward, next_state)
                
                total_reward += reward
                state = next_state
                step += 1
                
                # 防止无限循环
                if step > 1000:
                    break
            
            rewards.append(total_reward)
            steps.append(step)
            
            # 每100回合打印一次进度
            if (episode + 1) % 100 == 0:
                print(f"Episode {episode+1}/{episodes}, Total Reward: {total_reward:.2f}, Steps: {step}")
        
        return rewards, steps
    
    def test(self):
        """测试训练好的机器人"""
        state = self.env.reset()
        self.env.render()
        done = False
        step = 0
        
        while not done and step < 100:
            action = np.argmax(self.q_table[state])  # 只使用利用,不探索
            state, _, done = self.env.step(action)
            self.env.render()
            step += 1


# 主程序
if __name__ == "__main__":
    # 创建环境和机器人
    env = MazeEnv()
    robot = QLearningRobot(env, learning_rate=0.1, gamma=0.9, epsilon=0.1)
    
    # 训练机器人
    print("开始训练...")
    rewards, steps = robot.train(episodes=1000)
    
    # 绘制训练曲线
    plt.figure(figsize=(12, 5))
    
    plt.subplot(1, 2, 1)
    plt.plot(rewards)
    plt.title("每回合总奖励")
    plt.xlabel("回合数")
    plt.ylabel("总奖励")
    
    plt.subplot(1, 2, 2)
    plt.plot(steps)
    plt.title("每回合步数")
    plt.xlabel("回合数")
    plt.ylabel("步数")
    
    plt.tight_layout()
    plt.show()
    
    # 测试训练好的机器人
    print("测试训练好的机器人:")
    robot.test()

上述代码实现了一个完整的Q-Learning机器人迷宫导航系统,主要包含两个类:

  1. MazeEnv类:定义了迷宫环境

    • 5x5的网格世界,包含墙壁、空地和目标
    • 提供reset()方法重置环境
    • 提供step()方法执行动作并返回新状态、奖励和是否结束
    • 提供render()方法可视化当前状态
  2. QLearningRobot类:实现Q-Learning算法

    • 维护一个Q表存储每个状态-动作对的价值
    • choose_action()方法基于ε-贪婪策略选择动作
    • learn()方法使用Q值更新公式更新Q表
    • train()方法进行多回合训练
    • test()方法验证训练效果

算法执行过程说明

  1. 初始化阶段

    • 创建5x5的迷宫环境,设置起点(0,0)和终点(4,4)
    • 初始化Q表,所有状态-动作对的Q值都为0
    • 设置超参数:学习率α=0.1,折扣因子γ=0.9,探索率ε=0.1
  2. 训练阶段

    • 机器人从起点开始,根据ε-贪婪策略选择动作
    • 每次移动后,根据获得的奖励和新状态更新Q值
    • 随着训练进行,机器人逐渐学习到哪些动作能带来更高的长期奖励
    • 训练曲线显示:总奖励逐渐增加,到达目标所需步数逐渐减少
  3. 测试阶段

    • 关闭探索(ε=0),只根据Q值选择最优动作
    • 机器人能够找到从起点到目标的最短路径

Q-Learning的特点与适用场景

  1. 优点

    • 算法简单易懂,实现难度低
    • 收敛性有保证,理论上能找到最优策略
    • 不需要环境模型,适用范围广
  2. 缺点

    • 当状态空间和动作空间很大时,Q表会变得非常庞大
    • 对高维状态空间(如视觉输入)处理困难
  3. 适用场景

    • 小型机器人控制任务(如迷宫导航、简单机械臂控制)
    • 游戏AI(如小型网格游戏)
    • 简单的决策系统

在实际复杂的机器人控制任务中,通常会使用Q-Learning的扩展算法,如Deep Q-Network (DQN),它用神经网络替代Q表,能够处理高维状态空间。

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐