4. 强化学习入门

任务说明

在一个5×5的网格世界中,智能体从左上角(0,0)出发,需要到达右下角(4,4)的目标位置。网格中包含若干障碍物,智能体碰到障碍物或试图越界时会停留在原地并获得惩罚。本案例实现标准的Q-learning算法,通过不断试错学习最优策略,并输出学习过程中的关键指标。

环境与依赖

仅需Python 3.6+,无需第三方库(使用random模块)。代码完全自包含,可直接运行。

完整实现

import random

# 网格世界定义
GRID_SIZE = 5
GOAL = (4, 4)
OBSTACLES = {(1, 1), (2, 2), (3, 1), (1, 3)}

# 动作: 0=上, 1=下, 2=左, 3=右
ACTIONS = [(-1, 0), (1, 0), (0, -1), (0, 1)]
ACTION_NAMES = ['上', '下', '左', '右']

# Q-learning 超参数
ALPHA = 0.1      # 学习率
GAMMA = 0.9      # 折扣因子
EPSILON = 0.1    # 探索率
EPISODES = 500   # 训练回合数

# 初始化Q表: 状态(s) -> 动作(a) 的Q值
Q = {}
for i in range(GRID_SIZE):
    for j in range(GRID_SIZE):
        if (i, j) not in OBSTACLES:
            Q[(i, j)] = [0.0] * 4

def is_valid(state):
    """检查状态是否合法(在网格内且不是障碍物)"""
    i, j = state
    return 0 <= i < GRID_SIZE and 0 <= j < GRID_SIZE and state not in OBSTACLES

def step(state, action_idx):
    """执行动作,返回(新状态, 奖励, 是否终止)"""
    i, j = state
    di, dj = ACTIONS[action_idx]
    new_state = (i + di, j + dj)

    # 越界或撞障碍物:停留原地,给予惩罚
    if not is_valid(new_state):
        return state, -1.0, False

    # 到达目标
    if new_state == GOAL:
        return new_state, 10.0, True

    # 普通移动
    return new_state, 0.0, False

def choose_action(state):
    """ε-greedy策略选择动作"""
    if random.random() < EPSILON:
        return random.randint(0, 3)
    # 贪心选择Q值最大的动作(随机打破平局)
    q_values = Q[state]
    max_q = max(q_values)
    best_actions = [a for a, q in enumerate(q_values) if q == max_q]
    return random.choice(best_actions)

def train():
    """训练Q-learning,返回每回合的步数和总奖励"""
    steps_per_episode = []
    rewards_per_episode = []

    for episode in range(EPISODES):
        state = (0, 0)
        total_reward = 0
        steps = 0
        done = False

        while not done and steps < 100:  # 防止死循环
            action = choose_action(state)
            next_state, reward, done = step(state, action)

            # Q-learning 更新公式
            best_next_q = max(Q[next_state]) if not done else 0
            Q[state][action] += ALPHA * (
                reward + GAMMA * best_next_q - Q[state][action]
            )

            state = next_state
            total_reward += reward
            steps += 1

        steps_per_episode.append(steps)
        rewards_per_episode.append(total_reward)

    return steps_per_episode, rewards_per_episode

def extract_policy():
    """从训练好的Q表中提取确定性策略"""
    policy = {}
    for state in Q:
        if state == GOAL:
            policy[state] = '目标'
            continue
        best_action = max(range(4), key=lambda a: Q[state][a])
        policy[state] = ACTION_NAMES[best_action]
    return policy

def print_policy(policy):
    """打印策略网格"""
    print("学习到的策略(每个格子的最优动作):")
    for i in range(GRID_SIZE):
        row = []
        for j in range(GRID_SIZE):
            if (i, j) in OBSTACLES:
                row.append('■')
            elif (i, j) == GOAL:
                row.append('★')
            else:
                row.append(policy[(i, j)])
        print(' '.join(f'{cell:<2}' for cell in row))

# 主程序
if __name__ == '__main__':
    random.seed(42)  # 固定随机种子,保证可复现

    steps, rewards = train()
    policy = extract_policy()

    # 输出训练结果摘要
    print(f"训练完成:共{EPISODES}回合")
    print(f"最后10回合平均步数: {sum(steps[-10:]) / 10:.1f}")
    print(f"最后10回合平均总奖励: {sum(rewards[-10:]) / 10:.2f}")
    print(f"首次到达目标的回合: {next((i+1 for i, s in enumerate(steps) if s < 100), '未到达')}")
    print()
    print_policy(policy)

    # 验证:用训练好的策略走一遍,确认能到达目标
    state = (0, 0)
    path = [state]
    for _ in range(20):
        if state == GOAL:
            break
        action_idx = max(range(4), key=lambda a: Q[state][a])
        state, _, done = step(state, action_idx)
        path.append(state)
        if done:
            break

    print(f"\n验证路径: {' -> '.join(str(p) for p in path)}")
    print(f"验证结果: {'成功到达目标' if path[-1] == GOAL else '未能到达目标'}")

运行方式

python q_learning_grid.py

验证方法与判定标准

运行程序后,观察以下输出以确认实现正确:

  • 策略合理性:打印的策略网格中,每个非障碍物格子应指向能逐步接近目标的方向,且不存在指向障碍物或明显绕远的动作。
  • 收敛性指标:最后10回合的平均步数应显著小于早期回合(通常小于15步),平均总奖励应为正值(接近10)。
  • 确定性验证:程序末尾会使用训练好的策略(贪心)从起点走一遍,输出路径应以(0,0)开始、以(4,4)结束,并显示“成功到达目标”。

由于设置了随机种子(seed=42),每次运行结果完全一致,便于复现与比对。

学习过程可视化

Q-learning 训练过程示意(5×5网格) 起点 ★ 目标 ■ ■ ■ ■ ↓ ↓ ↓ ↓ ↓ → ■ → ■ ↓ → → ■ → ↓ → ■ → → ★ → → → → ★ 行4 行3 行2 行1 行0 图例 起点 (0,0) 目标 (4,4) 障碍物 ↓ → 学习到的动作 Q-learning 超参数: α=0.1, γ=0.9, ε=0.1 回合数=500 训练收敛趋势 每回合步数(越低越好) 回合 → 步数 收敛后步数↓

图中展示了训练完成后提取的确定性策略:每个非障碍物格子的箭头表示该状态下Q值最大的动作。可以看到智能体学会了绕开障碍物、以最短路径到达目标。训练曲线示意了随着回合增加,到达目标所需的步数逐渐减少并趋于稳定,反映了Q-learning的收敛过程。

常见问题

  • 如果训练不收敛:检查学习率α是否过大(建议0.05~0.2),或增加训练回合数至1000。
  • 如果策略出现振荡:适当降低探索率ε(如从0.1降至0.05),或采用ε衰减策略。
  • 修改网格:直接调整OBSTACLES集合或GRID_SIZE即可适配不同环境。