ML-For-Beginners 强化学习实战:从 Q-Learning 到 Gym CartPole 的完整路径
本篇指南基于 ML-For-Beginners 课程的 强化学习章节 编写,系统讲解强化学习(RL)这一与监督学习、无监督学习并列的第三种机器学习范式:你将理解"环境—状态—动作—奖励"的核心要素,掌握 Q-Learning 算法的完整 Python 实现(含 Bellman 方程与探索/利用权衡),并学会在 OpenAI Gym 的 CartPole 环境中训练一个能保持平衡的连续状态 Agent。
该手绘笔记图概括了本章节的核心:Agent 在环境中执行动作并接收奖励,通过与环境的反复交互学习最优策略。
什么是强化学习:决策驱动的学习范式
强化学习(Reinforcement Learning,RL)被视为与监督学习和无监督学习并列的基本机器学习范式之一,它的本质是关于决策的学习——做出正确的决策,或至少从错误中吸取教训。
一个直观的类比是模拟股票市场:当你对市场施加某项监管政策后,如果产生负面影响,就需要接受这种"负强化"(negative reinforcement),从中学习并调整方向;如果结果积极,则应建立在"正强化"(positive reinforcement)之上继续优化。
在 ML-For-Beginners 的课程脉络中,前序章节已经覆盖了两类问题:
- 监督学习(Supervised):拥有标注数据集给出样本解。课程中的分类(Classification)与回归(Regression)都是监督学习任务。
- 无监督学习(Unsupervised):没有标注训练数据,典型代表是聚类(Clustering)。
而强化学习属于第三类:它不需要标注训练数据,Agent 通过在模拟环境中执行实验来学习行为方式。同类"无需完整标签"的问题还包括半监督学习(Semi-supervised learning),即利用大量未标注数据预训练模型。
为什么下棋不能靠"分类"解决:环境 + 奖励函数
假设你想教电脑下棋或玩 Super Mario。表面上看,"预测每一步该做什么"像是一个分类问题,但实际上不是——因为不存在一个覆盖"状态→正确动作"的完整数据集。你可能有一些历史对局数据或玩家录像,但这些数据很难覆盖足够多的可能状态。
RL 的思路完全不同:不寻找现成数据,而是让电脑自己反复"玩",并观察结果。因此应用强化学习需要两个要素:
- 环境(Environment)与模拟器(Simulator):允许游戏被反复运行,模拟器定义所有游戏规则以及可能的状态与动作;
- 奖励函数(Reward function):告诉我们在每一步或每局游戏中表现有多好。
RL 与其他机器学习类型的主要区别在于:我们通常直到游戏结束才知道输赢,因此无法单独判断某一步是否好——奖励往往只在终局给出。我们的目标是设计算法,使模型能在这种不确定性条件下完成训练。本课程选择的 RL 算法是 Q-Learning。
课程主题:"彼得与狼"(Peter and the Wolf)
彼得和他的朋友们需要从饥饿的狼口中逃脱。插画由 Jen Looper 绘制。
本章节以俄国作曲家谢尔盖·普罗科菲耶夫(Sergei Prokofiev)的音乐童话《彼得与狼》为区域主题故事,训练机器学习算法帮助彼得完成两件事:
- 探索周围环境并构建最优导航地图;
- 学习如何玩滑板并保持在滑板上平衡,以便移动得更快。
整个章节分为两课,对应仓库中的两个目录:
- 强化学习与 Q-Learning 入门——离散状态环境下的 Q-Learning 完整实现;
- 使用 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 的伪代码如下:
- 用相等数值初始化所有状态和动作的 Q 表;
- 设学习率 α ← 1;
- 重复多次模拟:
- 从随机位置开始;
- 循环执行:
- 在状态 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、结束标志 done 与 info:
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 课程的主题。
延伸阅读
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00

