首页
/ ML-For-Beginners 强化学习实战:从 Q-Learning 到 Gym CartPole 的完整路径

ML-For-Beginners 强化学习实战:从 Q-Learning 到 Gym CartPole 的完整路径

2026-09-06 12:33:15作者:滑思眉Philip

本篇指南基于 ML-For-Beginners 课程的 强化学习章节 编写,系统讲解强化学习(RL)这一与监督学习、无监督学习并列的第三种机器学习范式:你将理解"环境—状态—动作—奖励"的核心要素,掌握 Q-Learning 算法的完整 Python 实现(含 Bellman 方程与探索/利用权衡),并学会在 OpenAI Gym 的 CartPole 环境中训练一个能保持平衡的连续状态 Agent。

ML-For-Beginners 强化学习课程手绘总结图,展示 Agent、状态、动作、奖励等核心概念

该手绘笔记图概括了本章节的核心:Agent 在环境中执行动作并接收奖励,通过与环境的反复交互学习最优策略。

什么是强化学习:决策驱动的学习范式

强化学习(Reinforcement Learning,RL)被视为与监督学习和无监督学习并列的基本机器学习范式之一,它的本质是关于决策的学习——做出正确的决策,或至少从错误中吸取教训。

一个直观的类比是模拟股票市场:当你对市场施加某项监管政策后,如果产生负面影响,就需要接受这种"负强化"(negative reinforcement),从中学习并调整方向;如果结果积极,则应建立在"正强化"(positive reinforcement)之上继续优化。

在 ML-For-Beginners 的课程脉络中,前序章节已经覆盖了两类问题:

而强化学习属于第三类:它不需要标注训练数据,Agent 通过在模拟环境中执行实验来学习行为方式。同类"无需完整标签"的问题还包括半监督学习(Semi-supervised learning),即利用大量未标注数据预训练模型。

为什么下棋不能靠"分类"解决:环境 + 奖励函数

假设你想教电脑下棋或玩 Super Mario。表面上看,"预测每一步该做什么"像是一个分类问题,但实际上不是——因为不存在一个覆盖"状态→正确动作"的完整数据集。你可能有一些历史对局数据或玩家录像,但这些数据很难覆盖足够多的可能状态。

RL 的思路完全不同:不寻找现成数据,而是让电脑自己反复"玩",并观察结果。因此应用强化学习需要两个要素:

  1. 环境(Environment)与模拟器(Simulator):允许游戏被反复运行,模拟器定义所有游戏规则以及可能的状态与动作;
  2. 奖励函数(Reward function):告诉我们在每一步或每局游戏中表现有多好。

RL 与其他机器学习类型的主要区别在于:我们通常直到游戏结束才知道输赢,因此无法单独判断某一步是否好——奖励往往只在终局给出。我们的目标是设计算法,使模型能在这种不确定性条件下完成训练。本课程选择的 RL 算法是 Q-Learning

课程主题:"彼得与狼"(Peter and the Wolf)

彼得与狼主题插画,展示 RL 课程中 Agent 探索环境、收集苹果、躲避狼的场景

彼得和他的朋友们需要从饥饿的狼口中逃脱。插画由 Jen Looper 绘制。

本章节以俄国作曲家谢尔盖·普罗科菲耶夫(Sergei Prokofiev)的音乐童话《彼得与狼》为区域主题故事,训练机器学习算法帮助彼得完成两件事:

  • 探索周围环境并构建最优导航地图;
  • 学习如何玩滑板并保持在滑板上平衡,以便移动得更快。

整个章节分为两课,对应仓库中的两个目录:

  1. 强化学习与 Q-Learning 入门——离散状态环境下的 Q-Learning 完整实现;
  2. 使用 Gym 模拟环境——将 Q-Learning 应用到 CartPole 连续状态问题。

第一课:Q-Learning 的完整实现(离散环境)

第一课让彼得在棋盘世界中探索、收集苹果、躲避狼。RL 的三要素在此落地:Agent(彼得)、状态(棋盘上彼得的位置与盘面)、动作(每个状态下的可选移动),执行动作后 Agent 会获得奖励

环境:一个 width × height 的棋盘

为了简化,彼得的世界是一个 width × height 的方形棋盘,每个格子可以是:

  • 地面(ground):彼得和其他生物可以行走;
  • 水(water):不能行走;
  • 树或草(tree/grass):可以休息的地方;
  • 苹果(apple):彼得乐于找到的食物;
  • 狼(wolf):危险,必须避开。

环境由独立的 Python 模块 rlboard.py 实现,它封装了棋盘类 Board(含 Cell 枚举:empty=0, water=1, wolf=2, tree=3, apple=4)、随机地图生成 randomize()、移动逻辑 move() 以及 matplotlib 绘图 plot()。创建样例棋盘的代码为:

from rlboard import *

width, height = 8, 8
m = Board(width, height)
m.randomize(seed=13)
m.plot()

运行后会打印出与示意图类似的环境画面。对应可运行的 notebook 见 notebook.ipynb(若从云端打开,需将 rlboard.py 放到 notebook 同一目录,因为它依赖 images/ 下的素材与相对路径加载)。

动作与策略(Policy)

彼得在任何位置都只能选择四个动作之一:上、下、左、右。动作被定义为"动作 → 坐标增量"的字典:

actions = { "U": (0,-1), "D": (0,1), "L": (-1,0), "R": (1,0) }
action_idx = { a: i for i, a in enumerate(actions.keys()) }

场景的策略与目标可以概括为:

  • 策略(Policy):Agent(彼得)的策略由所谓"策略函数"定义——给定任意状态返回一个动作。本例中状态由包含玩家当前位置的棋盘表示;
  • 目标:最终学到一个好策略,高效地解决问题。作为基线,课程先实现最简单的策略——随机游走(Random Walk)

基线:随机游走

随机游走策略在每一步从允许的动作中随机选一个,直到到达苹果:

def random_policy(m):
    return random.choice(list(actions))

def walk(m, policy, start_position=None):
    n = 0  # number of steps
    # 设置初始位置
    if start_position:
        m.human = start_position
    else:
        m.random_start()
    while True:
        if m.at() == Board.Cell.apple:
            return n   # 成功!
        if m.at() in [Board.Cell.wolf, Board.Cell.water]:
            return -1  # 被狼吃掉或淹死
        while True:
            a = actions[policy(m)]
            new_pos = m.move_pos(m.human, a)
            if m.is_valid(new_pos) and m.at(new_pos) != Board.Cell.water:
                m.move(a)  # 执行实际移动
                break
        n += 1

walk(m, random_policy)

walk 的返回值是对应路径的长度,每次运行可能不同。把实验重复 100 次并输出统计:

def print_statistics(policy):
    s, w, n = 0, 0, 0
    for _ in range(100):
        z = walk(m, policy)
        if z < 0:
            w += 1
        else:
            s += z
            n += 1
    print(f"Average path length = {s/n}, eaten by wolf: {w} times")

print_statistics(random_policy)

课程实测平均路径长度约 30~40 步——考虑到到最近苹果的平均距离只有约 5~6 步,随机游走效率很低,这正是需要学习的理由。

奖励函数:延迟奖励问题

为了让策略更智能,必须理解哪些移动"更好"。目标用奖励函数定义,它为每个状态返回一个分值,数值越高奖励越好:

move_reward = -0.1   # 普通移动的小惩罚
goal_reward = 10     # 吃到苹果的大奖励
end_reward = -10     # 掉水/遇狼的终局惩罚

def reward(m, pos=None):
    pos = pos or m.human
    if not m.is_valid(pos):
        return end_reward
    x = m.at(pos)
    if x == Board.Cell.water or x == Board.Cell.wolf:
        return end_reward
    if x == Board.Cell.apple:
        return goal_reward
    return move_reward

奖励函数的一个关键特性:大多数情况下,我们只会在游戏结束时才获得实质性奖励。这意味着算法必须能"记住"那些最终导向正奖励的好步骤并提升其重要性,同时抑制导向坏结果的移动——这一延迟奖励(delayed reward)问题是 RL 区别于分类/回归的核心难点。

Q-Learning 与 Bellman 方程

课程采用的算法是 Q-Learning:策略由一个称为 **Q 表(Q-Table)**的数据结构定义,记录每个状态下每个动作的"好坏程度"。由于棋盘尺寸为 width × height,Q 表可用形状为 width × height × len(actions) 的 numpy 数组表示:

Q = np.ones((width, height, len(actions)), dtype=np.float) * 1.0 / len(actions)

注意所有值初始化为相等的 0.25,这正好对应"随机游走"策略——每个状态下的所有移动同样好。m.plot(Q) 可以在棋盘上可视化 Q 表:每个格子中心的箭头指示偏好的移动方向(初始时各方向相等,显示为圆点)。

要处理延迟奖励,需要用动态规划的思想递归地思考问题。假设当前在状态 s,想移动到下一个状态 s':执行动作 a 后会获得即时奖励 r(s,a) 加上未来奖励。若 Q 表正确反映了各动作的"吸引力",则在 s' 处会选择使 Q(s',a') 最大的动作,因此在 s 处能获得的最佳未来奖励为 max_{a'} Q(s',a')。这给出 Bellman 公式

Q(s,a) ← (1-α)·Q(s,a) + α·( r(s,a) + γ · max_{a'} Q(s',a') )

其中 γ 是折扣因子(discount factor),决定当前奖励与未来奖励的相对偏好程度。

学习算法流程

基于 Bellman 方程,Q-Learning 的伪代码如下:

  1. 用相等数值初始化所有状态和动作的 Q 表;
  2. 设学习率 α ← 1;
  3. 重复多次模拟:
    1. 从随机位置开始;
    2. 循环执行:
      • 在状态 s 处选择一个动作 a
      • 执行动作,移动到新的状态 s'
      • 若遇到终局条件或总奖励过小——结束模拟;
      • 在新状态计算奖励 r
      • 按 Bellman 方程更新:Q(s,a) ← (1-α)Q(s,a) + α(r + γ max_{a'}Q(s',a'))
      • s ← *s' *;更新总奖励并减小 α。

探索 vs 利用(Explore vs Exploit)

上述流程中"选择一个动作"这一步有两种极端:完全随机选择会不断探索环境,但也会经常"死掉"并进入不会主动前往的区域;反之,总是利用(exploit) Q 表中已知值选择最优动作,则永远无法探索新状态,很可能错过最优解。

最佳方案是在探索与利用之间取得平衡:按 Q 表中数值成比例的概率选择动作。训练初期 Q 表各值相同,等价于随机选择;随着对环境的了解加深,Agent 更可能走最优路线,同时仍偶尔尝试未探索的路径。

Python 实现:5000 个 epoch 的训练

首先需要把 Q 表中任意数值转换为对应动作的概率向量:

def probs(v, eps=1e-4):
    v = v - v.min() + eps
    v = v / v.sum()
    return v

加一个小量 eps 是为了避免初始所有分量相同时的除零问题。然后运行 5000 次实验(epoch):

for epoch in range(5000):
    # 选取初始点
    m.random_start()
    # 开始行走
    n = 0
    cum_reward = 0
    while True:
        x, y = m.human
        v = probs(Q[x, y])
        a = random.choices(list(actions), weights=v)[0]
        dpos = actions[a]
        m.move(dpos, check_correctness=False)  # 允许走出棋盘,从而终止本局
        r = reward(m)
        cum_reward += r
        if r == end_reward or cum_reward < -1000:
            lpath.append(n)
            break
        alpha = np.exp(-n / 10e5)   # 学习率随步数指数衰减
        gamma = 0.5                 # 折扣因子
        ai = action_idx[a]
        Q[x, y, ai] = (1 - alpha) * Q[x, y, ai] + alpha * (r + gamma * Q[x+dpos[0], y+dpos[1]].max())
        n += 1

几个值得注意的实现细节:

  • m.move(dpos, check_correctness=False) 允许 Agent 走出棋盘边界,此时 reward 返回 end_reward,本局(episode)终止;
  • 学习率 alpha = np.exp(-n / 10e5) 随步数衰减,训练后期对 Q 表只做小幅修正,避免新值覆盖已经学到的好值;
  • 更新式中 Q[x+dpos[0], y+dpos[1]].max() 正是 Bellman 方程中的 max_{a'} Q(s',a') 项。

训练完成后,Q 表已编码每个状态下各动作的吸引力,可视化时每个格子里的小圆圈/箭头指向期望移动方向,可明显看到朝苹果汇聚的"流场"结构。

检验策略:严格 Q 策略与概率采样策略

严格策略——直接选 Q 值最大的动作:

def qpolicy_strict(m):
    x, y = m.human
    v = probs(Q[x, y])
    a = list(actions)[np.argmax(v)]
    return a

walk(m, qpolicy_strict)

课程特别提醒:多次运行上述代码可能观察到它"卡住"(需要按 notebook 的 STOP 按钮中断)。原因是可能存在两个状态在最优 Q 值上"互相指向"的情况,Agent 会在这两个状态间无限往返。

更优的导航策略是训练时使用的"探索+利用"混合策略——按 Q 表值成比例地采样动作:

def qpolicy(m):
    x, y = m.human
    v = probs(Q[x, y])
    a = random.choices(list(actions), weights=v)[0]
    return a

print_statistics(qpolicy)

运行后平均路径长度会显著缩短到 3~6 步的量级(对比随机游走的 30~40 步),验证了学习的有效性。

课程还给出两个挑战(Challenge):Task 1 要求修改 walk 函数限制最大路径长度(如 100 步),观察严格策略何时"挂起";Task 2 要求 walk 不回到已走过的格子以消除循环(但 Agent 仍可能被困在无法逃出的位置)。

观察学习过程:路径长度的三阶段行为

把每个 epoch 的路径长度画成曲线(课程的 lpathlen1.png 图),可以看到三个特征,课程将其总结为:

  • 平均路径长度先上升:一开始对一无所知时,Agent 容易陷入坏状态(水或狼);学会一些知识后能探索得更久,但还不知道苹果在哪;
  • 路径长度随学习进展下降:学得足够多后,达成目标更容易,路径变短;但探索性仍会让我们偏离最佳路径,使路径略长于最优;
  • 长度突然跳升:表明过程的随机性——在某个时刻新值可能"污染" Q 表系数。理想上应通过降低学习率来缓解(训练末期只对小幅调整 Q 表值)。

课程由此引出概念区分:学习率、学习率衰减、折扣因子等被称为超参数(hyperparameters),区别于训练中被优化的参数(parameters)(如 Q 表系数);寻找最优超参数值的过程称为超参数优化,值得单独成题。

第一课的课后作业是"一个更真实的世界":移动消耗能量并增加疲劳,吃苹果补充能量,在树下/草地上休息消除疲劳,且彼得必须拥有足够能量和合适疲劳度才能战胜狼。这意味着状态从"棋盘位置"扩展为 (Board, energy, fatigue) 的组合,奖励函数需要重写,且由于"战斗成功"是稀有事件,训练时长(epochs)需大幅调整——这是对课程主体内容的深度延伸。

第二课:Gym 环境中的 CartPole——连续状态下的 Q-Learning

第一课的棋盘问题可能看起来像"玩具问题",但许多真实问题(如下棋、下围棋)共享同样的结构:有规则、有离散状态的棋盘。第二课则把同样的 Q-Learning 原则应用到连续状态问题——状态由一个或多个实数给定。

问题设定:如果彼得想逃脱狼的追捕,他需要移动得更快。本课展示彼得如何用 Q-Learning 学习滑板平衡。

我们使用一个简化的平衡问题——CartPole:一个可在水平轨道上左右移动的滑块(小车),目标是保持立在滑块上的竖直杆不倒。

OpenAI Gym 环境

上一课中游戏规则与状态由我们自己定义的 Board 类给出;本课则使用模拟环境来仿真平衡杆背后的物理。最流行的 RL 模拟环境之一是 OpenAI 维护的 Gym,它提供从 CartPole 到 Atari 游戏等多种环境。安装与导入:

import sys
!{sys.executable} -m pip install gym

import gym
import matplotlib.pyplot as plt
import numpy as np
import random

初始化 CartPole 环境,并观察两个核心空间:

env = gym.make("CartPole-v1")
print(env.action_space)
print(env.observation_space)
print(env.action_space.sample())

每个环境关联:

  • 观察空间(Observation space):定义从环境接收的信息结构。对 CartPole,我们收到杆的位置、速度等 4 个值;
  • 动作空间(Action space):定义可能的动作。此处是离散空间,仅两个动作——向左向右

运行 100 步的短模拟(每步从 action_space 随机选一个动作),可以看到小车-杆很快倒下:

env.reset()
for i in range(100):
    env.render()
    env.step(env.action_space.sample())
env.close()

✅ 建议在本机 Python(如 VS Code)中运行此代码,渲染窗口会在新窗口打开;在云端运行可能需要按课程文档提到的方式做一些渲染适配。

step 函数返回四个值:当前观察 obs、奖励 rew、结束标志 doneinfo

env.reset()
done = False
while not done:
    env.render()
    obs, rew, done, info = env.step(env.action_space.sample())
    print(f"{obs} -> {rew}")
env.close()

输出形如:

[ 0.03403272 -0.24301182  0.02669811  0.2895829 ] -> 1.0
[ 0.02917248 -0.04828055  0.03248977  0.00543839] -> 1.0
...

观察向量的四个分量依次是:小车位置、小车速度、杆的角度、杆的旋转角速度。用 env.observation_space.low / env.observation_space.high 可以查看各分量的边界。

每个模拟步的奖励恒为 1.0,因为目标就是"尽可能久地存活"——让杆保持近似竖直越久越好。✅ CartPole 的官方判据是:100 次连续试验的平均奖励达到 195 即视为解决

状态离散化:Q 表的前提

Q-Learning 需要为每个状态建 Q 表,因此状态必须是离散的(有限的取值集合)。观察值是连续实数,必须离散化映射到有限状态集。课程给出两种方法:

方法一:分箱(bins)。若知道某数值的取值区间,可把区间分成若干箱,用 numpy.digitize 把数值替换为其所属箱号。此法能精确控制状态空间大小(取决于选的箱数)。

方法二:线性映射后取整。把值线性插值到某个有限区间(如 -20 到 20),再四舍五入取整。此法对状态大小的控制略弱,尤其在不了解输入值精确范围时——本例 4 个分量中有 2 个没有上下界,理论上可能导致无限状态数;但实践中这些极端值很少出现。

课程选择第二种方法:

def discretize(x):
    return tuple((x / np.array([0.25, 0.25, 0.01, 0.1])).astype(np.int))

同时提供分箱法供对比:

def create_bins(i, num):
    return np.arange(num+1) * (i[1]-i[0]) / num + i[0]

print("Sample bins for interval (-5,5) with 10 bins\n", create_bins((-5,5),10))

ints = [(-5,5), (-2,2), (-0.5,0.5), (-2,2)]  # 各参数的取值区间
nbins = [20, 20, 10, 10]                       # 各参数的箱数
bins = [create_bins(ints[i], nbins[i]) for i in range(4)]

def discretize_bins(x):
    return tuple(np.digitize(x[i], bins[i]) for i in range(4))

✅ 两者的差异细节:discretize_bins 返回 0 起始的箱号,输入值接近 0 时返回区间中间的编号(如 10);discretize 则允许输出为负、不关心取值范围,0 就对应 0。跑一遍短模拟并打印两种离散化结果即可直观比较。✅ 训练过程中建议注释掉 env.render() 让模拟"隐形"后台运行,速度更快。

Q 表结构:为什么这里用字典

第一课的离散状态是 0~7 的坐标对,Q 表可以用 8×8×2 的 numpy 张量;若采用分箱离散化,状态空间大小已知(20×20×10×10×2),也可以照搬张量方案。但由于 discretize 的状态维度不精确(部分原值无界,无法保证状态始终在边界内),课程改用字典表示 Q 表,以 (state, action) 元组为键:

Q = {}
actions = (0, 1)

def qvalues(state):
    return [Q.get((state, a), 0) for a in actions]

qvalues() 返回某状态下所有动作对应的 Q 值列表;缺失条目默认返回 0。这一细节体现了实现选择与状态空间性质之间的对应关系:状态空间有界用数组,无界/未知则用字典。

训练循环:超参数与两个改进

先设定超参数:

# hyperparameters
alpha = 0.3      # 学习率:每步对 Q 表当前值的调整幅度
gamma = 0.9      # 折扣因子:未来奖励相对当前奖励的权重
epsilon = 0.90   # 探索/利用因子
  • alpha学习率):定义每步在多大程度上调整 Q 表现有值。第一课从 1 开始再逐渐衰减;本例为简单起见保持恒定,可自行实验调整。
  • gamma折扣因子):表示多大程度上优先未来奖励而非当前奖励。
  • epsilon探索/利用因子):算法中以 epsilon 比例的概率按 Q 表值选动作(利用),其余情况执行随机动作(探索),从而覆盖从未见过的搜索空间区域。就平衡问题而言,随机动作相当于"朝错误方向随机推一把",杆必须学会从这些"错误"中恢复平衡。

课程还做了两个工程改进:

  • 计算平均累计奖励:对一段模拟(5000 次)取平均,每 5000 次迭代打印一次进度;
  • 记录最佳模型 Qbest:保存达到最大平均累计奖励 Qmax 时对应的 Q 表副本,因为训练后期 Q 表值有时会被更差的值破坏。
def probs(v, eps=1e-4):
    v = v - v.min() + eps
    v = v / v.sum()
    return v

Qmax = 0
cum_rewards = []
rewards = []
for epoch in range(100000):
    obs = env.reset()
    done = False
    cum_reward = 0
    # == 执行模拟 ==
    while not done:
        s = discretize(obs)
        if random.random() < epsilon:
            # 利用——按 Q 表概率选动作
            v = probs(np.array(qvalues(s)))
            a = random.choices(actions, weights=v)[0]
        else:
            # 探索——随机选动作
            a = np.random.randint(env.action_space.n)

        obs, rew, done, info = env.step(a)
        cum_reward += rew
        ns = discretize(obs)
        Q[(s, a)] = (1 - alpha) * Q.get((s, a), 0) + alpha * (rew + gamma * max(qvalues(ns)))
    cum_rewards.append(cum_reward)
    rewards.append(cum_reward)
    # == 定期打印结果并计算平均奖励 ==
    if epoch % 5000 == 0:
        print(f"{epoch}: {np.average(cum_rewards)}, alpha={alpha}, epsilon={epsilon}")
        if np.average(cum_rewards) > Qmax:
            Qmax = np.average(cum_rewards)
            Qbest = Q
        cum_rewards = []

仓库中随课程附带的 Gym notebook 保存了真实训练日志,前 25000 次迭代的平均累计奖励依次为 22.0 → 70.14 → 121.86 → 149.64 → 168.28 → 196.74(alpha=0.3, epsilon=0.9),与判据"195/100 次连续试验"相印证:平均奖励已经接近或达到目标,但课程同时提醒,由于这里是对 5000 次取平均而正式判据只要求 100 次,数字接近时仍需谨慎解读。训练日志还显示后期奖励有时会下降——已经学到的 Q 表值可能被更差的新值覆盖,这解释了为何需要保存 Qbest

绘制训练进度:运行平均

每次迭代的累计奖励收集在 rewards 向量中。直接 plt.plot(rewards) 得到的原始曲线几乎无法解读——随机训练过程中每局时长差异很大。用 np.convolve 计算 100 次实验的**运行平均(running average)**后,趋势清晰可见:

def running_average(x, window):
    return np.convolve(x, np.ones(window) / window, mode='valid')

plt.plot(running_average(rewards, 100))

超参数调节策略

为使学习更稳定,可以在训练过程中动态调整超参数:

  • 学习率 alpha:可从接近 1 的值开始并持续减小——Q 表逐渐形成好的数值后,应只做小幅修正而非用新值完全覆盖;
  • epsilon:可以缓慢增大,使 Agent 探索更少、利用更多(从较低值逐渐升到接近 1)。

课程给出两个任务:Task 1 尝试调整超参数争取更高的累计奖励(能否超过 195?);Task 2 要求按正式判据(100 次连续运行平均 195 分)在训练中测量并确认"正式解决"问题。

观察训练成果:让杆真正平衡起来

训练完成后,按训练时的同一动作选择策略(按 Q 表概率分布采样)运行模拟:

obs = env.reset()
done = False
while not done:
    s = discretize(obs)
    env.render()
    v = probs(np.array(qvalues(s)))
    a = random.choices(actions, weights=v)[0]
    obs, _, done, _ = env.step(a)
env.close()

渲染窗口中可以看到小车左右移动、杆保持竖直相当长的时间。课后挑战还有两项:Task 3 用训练时保存的最佳 Q 表 Qbest(覆盖 Q)重跑,观察差异;Task 4 把"按概率采样"改为始终选 Q 值最大的动作(np.argmax),比较是否改善平衡效果。

第二课的课后作业是训练一辆 Mountain Car,将同样的方法迁移到另一经典控制环境。

小结:离散与连续状态下的 Q-Learning 全景

维度 第一课(彼得迷宫) 第二课(CartPole)
状态类型 离散(棋盘坐标) 连续(4 个实数观察值)
动作空间 4 个方向 左 / 右 2 个
Q 表实现 numpy 张量 width×height×4 字典,键为 (state, action)
状态预处理 无需 离散化(线性取整或分箱)
奖励结构 移动 -0.1 / 苹果 +10 / 终局 -10 每步恒为 1,追求存活时长
学习率 从 1 指数衰减 恒定 0.3(可实验调整)
判据 平均路径 3~6 步(随机基线 30~40 步) 100 次连续试验平均奖励 ≥ 195

两课共同展示了 Q-Learning 的完整闭环:定义环境 → 设计奖励函数 → Bellman 方程更新 → 探索/利用平衡 → 超参数调优 → 策略评估。课程的结论是:我们成功地让 Agent 仅凭"定义期望终局的奖励函数 + 智能探索搜索空间的机会"学会了好策略,并已在离散与连续环境(但动作离散)两类问题上验证了 Q-Learning。当动作空间也变为连续、或观察空间复杂到 Atari 游戏画面时,往往需要更强的技术——如神经网络——这正是后续更高级 AI 课程的主题。

延伸阅读

登录后查看全文
热门项目推荐
相关项目推荐