把强化学习单独放在一门课的第三周来讲,第一次接触的人多半会有点措手不及——前两周还在跟监督学习的损失函数、正则化项较劲,思路是"给定输入预测标签,错了就调参数",突然画风一转,变成了智能体在环境里反复试错、拿奖励、改策略。这个跳跃感是真实的,也正因如此,这一周的内容往往是整门课里最容易被反复回看的一节。强化学强化学习这件事,说到底不是背下几个公式就完事,而是要建立一套"用试错来逼近最优决策"的思维方式,这个思维方式和之前所有学过的内容都不一样。
这篇东西是把我自己啃这一周内容的过程完整摊开:从"这一周到底想讲什么"开始,把马尔可夫决策过程、状态价值函数、动作价值函数、贝尔曼方程这几个概念逐个拆开,再落到能直接跑起来的 Q-learning 代码,最后补上深度强化学习和离线强化学习的延伸方向,以及我在调试过程中踩过的那些坑。适合两类人看:一类是刚学完监督学习、准备跨进强化学习大门的新手,另一类是学完一遍但感觉概念还是一团浆糊、想重新理顺逻辑的人。代码部分用的都是 Python 加 NumPy,没有任何重型依赖,复制粘贴就能跑。
1. 从监督学习跨到试错学习:这一周到底想教会你什么
1.1 为什么强化学习被安排在第三周
很多课程体系把强化学习放在比较靠后的位置,是有讲究的。前面几周铺垫的东西——梯度下降、损失函数、过拟合与正则化、模型评估——本质上都在解决"给定一批带标签的数据,怎么把映射关系学准"这个问题。这套思路的前提是有标准答案,你算出来的预测值和真实标签一减,误差就出来了,剩下的就是优化。
强化学习没有这个前提。智能体在环境里走一步,得到的反馈不是一个"正确答案",而是一个标量奖励,甚至这个奖励可能延迟到几十步之后才出现。你根本不知道当前这一步走得对不对,只知道最后结果好不好。这种"信用分配"的难题,才是强化学习真正区别于前面所有内容的地方。
把它放在第三周,我觉得意图是让你在已经熟悉"用数值优化驱动模型改进"这套逻辑之后,再去看一种完全不同的驱动方式:不靠标签,靠奖励;不靠一次前向传播就得到反馈,靠和环境交互一整个回合才拿到反馈。理解了这个差异,后面价值迭代、Q-learning 那些公式才不会显得像天书。
1.2 五个基本要素搭起一个闭环
不管教材怎么写,强化学习的骨架始终是五个东西在转圈:智能体、环境、状态、动作、奖励。智能体是决策者,环境是它面对的世界,状态是它此刻看到的信息,动作是它能做的选择,奖励是环境给它的反馈信号。
这五者组成的闭环是这样的:智能体观察到当前状态,选一个动作丢给环境,环境根据内部规则转移到新状态,同时吐出一个奖励,智能体拿到新状态和奖励后继续决策,如此往复,直到某个终止条件触发。整个过程叫一个 episode。
这个闭环听起来简单,但真正写代码时会立刻遇到几个绕不开的问题:状态怎么表示?动作空间是离散的还是连续的?奖励什么时候给、给多少?终止条件怎么定义?这四件事定不下来,后面的算法全是空中楼阁。我在做机械臂相关的练习时最深的一个体会就是,环境建模花掉的时间远远超过调算法的时间,而环境建模里最耗神的又恰恰是奖励设计。
提示:不要一上来就冲着算法去。先把环境的状态、动作、奖励、终止条件四件事写清楚,写在纸上都行,这一小时的投入能省掉后面十小时的调试。
1.3 和另外两类学习范式的边界
监督学习、无监督学习、强化学习这三者的边界,很多人背得出概念但说不清区别,我换个角度讲会更直观。监督学习是"有老师批改作业",每道题都有标准答案;无监督学习是"没有老师,自己找规律",比如把一堆数据分成几堆;强化学习是"没有标准答案,只有一个期末总评",你每一步怎么走没人管,最后考砸了得自己倒推是哪一步出了问题。
边界清晰之后,适用场景也就清晰了。有大量标注数据、任务边界明确、每次预测都是独立的问题,用监督学习。数据没有标签但需要发现内部结构,用无监督学习。决策序列之间有强依赖、当前动作会影响未来状态、反馈延迟且稀疏,那才是强化学习的地盘。游戏 AI、机器人控制、资源调度、推荐系统的长期收益优化,都属于这一类。
这里有个常见的误用:有人拿强化学习去做本来用监督学习就能解决得很好的分类任务,结果训练又慢又不稳定。原因很简单,强化学习的样本效率天生就低,它需要大量交互才能学到东西,如果你有现成的标签,用它纯属自找麻烦。搞清楚边界,比多背几个算法有用得多。
2. 马尔可夫决策过程:把"边走边决策"翻译成可计算的数学结构
2.1 五元组里的每一项对应现实中的什么
马尔可夫决策过程,通常写成五元组 (S, A, P, R, γ),也就是状态集合、动作集合、状态转移概率、奖励函数、折扣因子。这五个符号第一次看会觉得抽象,我把它们一一对到现实场景里,就好理解了。假设你在做一个仓储机器人调度系统:S 是所有可能的位置组合与任务状态,A 是前进、后退、转弯、抓取这些动作,P 是从当前状态执行某动作后转移到各新状态的概率,R 是每完成一个任务给的奖励,γ 是衡量你多看重未来收益的一个系数。
P 这个符号最容易被忽略,但它其实是 MDP 的核心。它表达的是不确定性——同样的动作,在不同情况下可能通向不同结果。真实系统里几乎不存在完全确定的转移,地面打滑、传感器噪声、其他机器人抢道,都会让 P 变成一个概率分布而不是一个确定值。也正因为有 P,我们才需要讨论"期望回报"而不是"确定回报"。
马尔可夫性这个前提值得单独说一句:它假设下一个状态只取决于当前状态和当前动作,跟更早的历史无关。这个假设在现实中当然不总是成立,但它极大地简化了问题,让价值函数可以写成只关于当前状态的函数。绝大多数入门内容都建立在这个假设上,真要处理部分可观测的情况,那是 POMDP 的范畴,属于进阶话题。
2.2 回报、折扣因子与"眼前的奖励要不要贪"
从某一时刻开始,往后所有奖励的累加就是回报。但直接累加有个问题:如果任务是持续进行的,奖励序列无限长,总和会发散。所以引入了折扣因子 γ,把未来的奖励按 γ 的幂次打折。回报的定义是 G_t = R_{t+1} + γR_{t+2} + γ²R_{t+3} + …,γ 取 0 到 1 之间。
γ 的取值直接决定智能体的"性格"。γ 接近 0,它极度短视,只看眼前这一步的奖励;γ 接近 1,它愿意为了远期收益牺牲当下。我在做一个路径规划的小实验时对比过 γ = 0.5 和 γ = 0.99 的区别,前者学出来的策略会在终点附近绕圈,因为它看不到远处的目标奖励;后者才会老老实实走最短路径。这个参数的物理含义其实是"未来一步奖励在当前时刻值多少钱",γ = 0.9 意味着十步之后的奖励大约值当前的 0.35 倍。
至于为什么用几何衰减而不是线性衰减,如果认真推导一遍会有个漂亮的结论:只有当折扣是几何形式时,回报的递归关系 G_t = R_{t+1} + γG_{t+1} 才能成立。而这个递归关系正是后面贝尔曼方程的基础。换句话说,γ 这个设计不是为了方便,而是为了让整个数学框架自洽。
2.3 策略的两种表示方式
策略就是智能体的行为规则,记作 π。它有两种常见形式,搞清这两种形式是理解后面算法分歧的钥匙。
第一种是确定性策略,写作 a = π(s),给定状态直接输出唯一动作。第二种是随机性策略,写作 π(a|s),给定状态输出一个动作上的概率分布。听起来确定性策略更简洁,但为什么很多算法偏偏用随机策略?两个原因。一是探索需要,随机性能让智能体在训练阶段尝试不同动作,避免一开始就锁死在某个次优路径上。二是有些场景下最优策略本身就是随机的,比如石头剪刀布这类博弈问题,确定性策略一定会被对手针对。
这里有个容易被忽略的细节:策略是我们要优化的对象,但绝大多数算法并不直接优化策略,而是先算出价值函数,再从价值函数里反推出策略。这个"绕一圈"的做法,就是所谓的基于价值的方法,Q-learning 属于这一派。另有一派直接对策略参数做梯度上升,叫策略梯度方法。两条路线的分歧根源,就在策略的表示形式上。
2.4 贝尔曼方程:用一个等式把无限未来折叠起来
贝尔曼方程是这一周最核心的一个式子,也是很多人第一次看会卡住的地方。它的思想其实很朴素:一个状态的价值,等于"立刻拿到的奖励"加上"折扣后的下一个状态的价值"。
写成期望形式就是 V(s) 等于在策略 π 下对动作求期望、再对下一状态求期望,得到 r 加上 γ 倍的 V(s')。这个式子的威力在于,它把"从现在到无限远的累加"折叠成了"一步加下一个状态的价值",无限问题变成了递归问题。递归意味着可以用迭代法求解,这才是后面价值迭代、时序差分这些算法能成立的根本原因。
我第一次看这个推导时的疑惑是:这不是循环定义吗?V(s) 用 V(s') 定义,V(s') 又用 V(s'') 定义,怎么解?答案在于终止状态。总有些状态的价值是已知的,比如终点的价值就是最终奖励,从这个锚点出发,往回一层层传,整个价值表就能收敛。这也解释了为什么强化学习的问题必须要有明确的终止条件——没有终点,这个递归就没有落脚点。
3. 状态价值函数的作用:这一周最容易被追问的考点
3.1 V(s) 衡量的到底是什么
状态价值函数 V(s) 的定义是:从状态 s 出发,按照某个策略 π 继续走下去,能获得的期望回报。注意三个关键词——"从 s 出发""按照 π""期望"。第一个词说明它只跟当前状态有关,跟你怎么到达这个状态无关;第二个词说明它依赖于策略,同一个状态在不同策略下价值不同;第三个词说明它是个期望值,因为转移和策略都可能是随机的。
它解决的问题是"这个局面好不好"。举个下棋的例子,棋盘上某个局面对你有利还是不利,这就是状态价值的直观含义。有了它,你甚至不需要预先知道每一步怎么走,只要在每个状态看一眼四个方向里哪个下一状态价值最高,往那边走就行了。
V(s) 在实际中的用途比想象中广。除了用来做决策,它还能用来做信用分配——如果一个状态的价值很高,说明从这里往后大概率能拿到好结果,这个状态就值得被保护。另外,在优势演员评论家这类算法里,V(s) 被当作基准线来降低梯度的方差,这是它一个非常实用但入门时很少被强调的作用。
3.2 Q(s,a) 与 V(s) 的互相推导
动作价值函数 Q(s,a) 的定义是:在状态 s 下先执行动作 a,之后再按照策略 π 走,能获得的期望回报。它比 V 多了一个维度,多出来的就是"当前这一步选了什么"。
两者的关系可以这样理解:V(s) 是对所有可能动作的加权平均,权重就是策略在该状态下选择每个动作的概率,即 V(s) = Σ π(a|s) Q(s,a)。反过来,如果你知道所有动作的 Q 值,那最优策略就是直接选 Q 最大的那个动作,所以 V*(s) = max_a Q*(s,a)。
为什么要有 Q 而不只用 V?关键在于,光有 V(s) 你还没法直接选动作,你得知道环境的转移概率 P 才能算出每个动作之后的期望价值。而在很多真实问题里,P 是未知的,你只能通过实际尝试来估计。这就是无模型方法存在的理由:Q 值可以直接通过试错估计出来,V 值在不知道 P 的情况下反而不好用。所以我个人的经验是,只要环境模型未知,就从 Q 入手;如果模型完全已知,动态规划用 V 会更省内存。
3.3 优势函数与"这个动作比平均水平好多少"
优势函数是 A(s,a) = Q(s,a) − V(s),含义是"在这个状态下,选这个动作比按当前策略的平均表现好多少"。它是个相对量,正值表示这个动作优于平均,负值表示劣于平均。
这个量为什么重要?因为它把"绝对好坏"变成了"相对好坏"。同一个 Q 值,在一个整体价值很低的局面里可能是很好的选择,在一个整体价值很高的局面里却可能是拖后腿的。用优势函数做策略更新,梯度信号的方差会小很多,训练也稳定得多。
入门阶段不一定会重点讲优势函数,但它是连接基于价值方法和策略梯度方法的桥梁。理解它之后,再去看那些评论家网络、广义优势估计的内容,就不会觉得突兀。我建议在学完 V 和 Q 之后立刻把优势函数补上,它的思维成本很低,但收益很大。
3.4 手算一个四格世界的价值迭代
光看公式容易飘,我用手算一个极简例子把它落地。假设一条四个格子的直线通道,位置记作 1、2、3、4,4 是终点。每一步可以往左或往右,走出边界就停在原地并得到 −1 的惩罚。到达终点得 +10,并且游戏结束。折扣因子 γ 取 0.9,所有状态初始价值设为 0。
第一轮迭代,我们从终点反推。位置 4 是终止状态,价值直接是 10。位置 3 的策略是往右走,价值是 10 × 0.9 = 9。位置 2 往右走到 3,价值是 9 × 0.9 = 8.1。位置 1 同理是 8.1 × 0.9 = 7.29。这是一次完整的价值回传。
第二轮再检查一遍,位置 2 如果往左走到 1 再走回来,得到的期望价值更低,所以最优动作仍然是往右。整个过程重复几轮,价值表就稳定了。这个手算过程能让你直观看到"奖励从终点向起点一层层传播"的机制,而这个机制正是后面所有迭代算法的内核。你可以把这个例子推广到带惩罚的版本,比如每走一步扣 0.1,看看价值分布怎么变——会发现离终点越远价值越低,而且这个衰减是指数的。
提示:手算这一步千万别跳过。我在带新人时发现,能自己把四格世界的价值迭代算一遍的人,后面理解时序差分几乎没有障碍;跳过这步的人往往卡在"为什么更新目标是 r + γV(s')"上很久。
4. 从动态规划到 Q-learning:能跑起来的完整实现
4.1 动态规划两条路线:策略迭代与价值迭代
在环境模型已知的前提下,求解最优策略有两条经典路线。第一条叫策略迭代,分两步走:先做策略评估,把当前策略的价值函数算准;再做策略改进,对每个状态贪心地选当前看起来最好的动作。这两步交替进行,直到策略不再变化。
第二条叫价值迭代,把评估和改进揉在一起,每轮只做一次更新,更新式是 V(s) 取所有动作里"立刻奖励加折扣后下个状态价值"的最大值。它相当于把多次策略评估压缩成了一次,收敛速度往往更快,代码也更短。
两者怎么选?如果动作空间大、每次评估都很贵,价值迭代更划算;如果希望每一步都有明确的策略含义,策略迭代更直观。实测下来,小规模问题上两者的差距可以忽略,直接用价值迭代省事。价值迭代的伪代码只有几行:初始化价值表,循环每个状态,用最大值更新,反复直到变化量小于阈值。
4.2 蒙特卡洛与时序差分的取舍
当环境模型未知时,动态规划用不了,因为它需要 P。这时候有两类替代方案。
蒙特卡洛方法是等一整个 episode 跑完,用实际拿到的回报来更新价值。它无偏,但方差很大,而且必须等到回合结束才能更新,不适合连续任务。时序差分方法则是走一步就更新,用"立刻奖励加上下一个状态的估计价值"来近似真实回报。它引入了偏差,但方差小得多,也能在线学习。
这个取舍在实践中的表现是:蒙特卡洛收敛慢但目标稳定,时序差分快但前期估计不准容易震荡。绝大多数现代算法都站在时序差分这一边,因为在线学习的能力太重要了。我做过一个对比实验,同样的迷宫环境,时序差分大约用三分之一的回合数就能达到相同的成功率。
4.3 Q-learning 更新公式逐项拆解
Q-learning 的更新式是:Q(s,a) 增加 α 乘以(r 加上 γ 乘以下一状态所有动作里最大的 Q 值,再减去当前的 Q(s,a))。
逐项看。α 是学习率,控制每次更新迈多大步子。r 是这一步实际拿到的奖励。γ 是折扣因子。max Q(s',a') 是对下一个状态所有可能动作取最大值,这个"最大"是 Q-learning 最标志性的地方,它意味着它学的是最优策略的价值,跟你当前实际采用的行为策略无关。
括号里那一整块叫时序差分误差,可以理解成"我原来以为值多少"和"实际观测加上后续估计告诉我值多少"之间的差距。乘以学习率就是按这个差距的一部分去修正。这个结构和梯度下降的更新形式几乎一样,只不过"梯度"换成了 TD 误差。
这个"最大"也带来一个问题:Q-learning 是离策略算法,它敢用贪心的目标来更新,而如果行为策略长期探索不到某些动作,这些动作的 Q 值可能被高估。这就是著名的最大化偏差问题,后面发展的双 Q 学习就是为了缓解它。入门时不用急着处理,但心里要有这个数。
4.4 一段可以直接跑的 Q-table 迷宫代码
下面这段代码是一个完整的 Q-learning 实现,五乘五的网格,起点左上角,终点右下角,中间有几个障碍格。每一步撞墙或者走到普通格子扣少量分,到达终点给正奖励。整个流程不依赖任何强化学习库,只要 NumPy。
import random import numpy as np GRID = 5 START = (0, 0) GOAL = (4, 4) BLOCKS = {(1, 1), (1, 2), (2, 2), (3, 3)} ACTIONS = [(-1, 0), (1, 0), (0, -1), (0, 1)] # 上 下 左 右 N_ACTIONS = len(ACTIONS) def is_valid(r, c): return 0 <= r < GRID and 0 <= c < GRID and (r, c) not in BLOCKS def step(state, action): """返回 (下一状态, 奖励, 是否终止)""" r, c = state dr, dc = ACTIONS[action] nr, nc = r + dr, c + dc if not is_valid(nr, nc): return state, -1.0, False # 撞墙或撞障碍,原地不动 if (nr, nc) == GOAL: return (nr, nc), 10.0, True # 到达终点 return (nr, nc), -0.1, False # 普通移动给小惩罚 def q_learning(episodes=3000, alpha=0.1, gamma=0.95, eps_start=1.0, eps_end=0.05): Q = np.zeros((GRID, GRID, N_ACTIONS)) eps = eps_start decay = (eps_start - eps_end) / (episodes * 0.8) for ep in range(episodes): state = START for _ in range(200): # 单回合步数上限 # epsilon-贪心选动作 if random.random() < eps: action = random.randrange(N_ACTIONS) else: action = int(np.argmax(Q[state[0], state[1]])) next_state, reward, done = step(state, action) # 终止状态未来价值为 0 future = 0.0 if done else gamma * np.max(Q[next_state[0], next_state[1]]) td_target = reward + future td_error = td_target - Q[state[0], state[1], action] Q[state[0], state[1], action] += alpha * td_error state = next_state if done: break eps = max(eps_end, eps - decay) # 探索率逐渐衰减 return Q if __name__ == "__main__": Q = q_learning() # 从起点出发,按贪心策略走一遍,打印路径 state, path = START, [START] for _ in range(50): action = int(np.argmax(Q[state[0], state[1]])) state, reward, done = step(state, action) path.append(state) if done: break print("学到的路径:", path)跑完之后打印出来的路径应该是一条避开障碍、尽量短的路线。如果跑出来还在原地打转或者撞墙,先检查 epsilon 衰减是不是太快,或者单回合步数上限设得太小导致走不到终点。
代码里有两个细节值得说。一是终止状态的处理,done为真时未来价值取 0,这不是偷懒,而是因为终点之后没有未来了,如果这里还去查 Q 表,会把终点后面的估计值错误地算进来。二是 epsilon 的衰减速度,我用的是线性衰减到 0.05 为止,保留一点探索是为了防止策略完全锁死。
4.5 三个核心参数怎么定
学习率 α、折扣因子 γ、探索率 ε 是三个必须调的旋钮,它们各自的作用和常见取值我用一张表对比一下。
| 参数 | 作用 | 常见取值 | 调大了会怎样 | 调小了会怎样 |
|---|---|---|---|---|
| α 学习率 | 每次修正的步长 | 0.05 ~ 0.2 | 更新快但震荡,难收敛 | 收敛稳但极慢 |
| γ 折扣因子 | 对远期奖励的重视程度 | 0.9 ~ 0.99 | 看得远但方差大、易过估计 | 短视,学不到长期策略 |
| ε 探索率 | 随机尝试的概率 | 从 1.0 衰减到 0.05 | 一直乱走,学不会 | 早早锁死次优路径 |
这三者的联动很有意思。γ 调大之后,回报的方差会显著上升,这时候如果 α 还开得很大,训练曲线会剧烈抖动,解决办法是把 α 降下来,同时多跑一些回合。反过来,如果 α 很小而 γ 也小,智能体会学得非常慢而且短视,看起来像是什么都没学到。
我自己的经验速记:迷宫这类离散小问题,α 从 0.1 起、γ 从 0.95 起、ε 从 1.0 线性衰减到 0.05,基本不用怎么改就能跑通。遇到连续控制或者状态空间大的问题,再针对性调整。
5. 表格装不下状态之后:深度强化学习与离线强化学习
5.1 函数逼近为什么是必然选择
Q-table 的方式有个硬伤:它要求每个状态单独占一行。状态数量少的时候没问题,一旦状态变成图像、连续坐标、高维传感器读数,表格就彻底装不下了,这就是维度灾难。而且表格方式没有泛化能力,见过状态 A 学到的东西,对相邻的状态 B 一点用都没有。
解决办法是用一个参数化函数来近似 Q 值,输入状态输出各动作的价值,函数的形式可以是线性模型,也可以是神经网络。用了神经网络就是深度强化学习。它的好处是相邻状态会得到相似的输出,学到的经验能自动泛化,而且参数量不随状态数指数增长。
代价也很明显:不再有任何收敛保证。表格方法在合适条件下能证明收敛,换成神经网络之后,训练稳定性成了主要难题,各种技巧基本都是围绕这一点发展出来的。
5.2 DQN 稳住训练的两个关键设计
深度 Q 网络把 Q 学习和神经网络结合起来,但直接拼起来会失败,原因有两个。一是样本之间高度相关,相邻时刻的样本几乎是同一场景的变体,用它做梯度下降会破坏独立同分布假设。二是目标值和预测值同时更新,相当于追着自己的尾巴跑。
对应的两个设计是经验回放和目标网络。经验回放把交互产生的转移存进一个大缓冲区,训练时从中随机采样一批,这样样本之间的相关性被打散了,而且一条经验可以被反复利用,样本效率提升明显。目标网络是另外复制一个结构相同但参数更新缓慢的网络,专门用来算更新目标,隔一段时间才把主网络的参数同步过去,这样目标不会每一步都变。
这两个技巧的组合效果非常显著,可以说是深度强化学习能落地的基石。实现上有几个细节要注意:缓冲区容量通常在十万量级,太小起不到打散作用;目标网络的同步周期一般是几百到几千步;训练时先让缓冲区积累一定数量的数据再开始学,不然一开始样本太少没意义。
5.3 奖励设计与稀疏奖励的破解思路
奖励设计是实战中最容易翻车的地方,也是书本上很少讲透的部分。稀疏奖励指的是只有达成目标才有奖励,中间过程没有任何反馈,智能体相当于在一个巨大的空间里盲找,几乎学不到东西。
破解思路有几条。第一是奖励塑形,在中间过程加一些引导性奖励,比如离目标更近就给一点正反馈、每消耗一步给一点负反馈。这个做法要注意尺度,如果引导奖励给得太大,智能体可能学会"拿奖励但不完成任务",比如原地打转刷分。第二是分层思想,把大任务拆成若干子目标,每个子目标单独给奖励。第三是用内在好奇心类的方法,鼓励智能体探索状态空间中新奇的区域。
我在做机械臂抓取练习时遇到过典型症状:机械臂在原地抖动不抓取,原因是每步惩罚设得比抓取奖励还重,它干脆选择不动来少扣分。后来把步惩罚调小,同时给"爪子靠近物体"一个小的中间奖励,训练立刻就有起色。这个坑很典型,如果你发现智能体学会了"什么都不做",先去检查奖励的量级关系。
5.4 离线强化学习适合什么场景
前面讲的都是在线方法,智能体一边交互一边学。但有些场景根本不允许你随便试,比如医疗决策、工业生产参数调整、真实机器人操作,试错成本太高,甚至不可逆。离线强化学习解决的就是这个问题:只给你一批已经采集好的历史数据,要求从中学会策略,全程不再和环境交互。
难点在于分布偏移。历史数据是用某个行为策略采集的,学出来的策略会倾向于选择数据里出现很少的动作,而这些动作的价值估计极不可靠,容易被高估,最后策略跑偏到数据分布之外。常见的应对方法是给更新目标加约束,限制新策略偏离行为策略太远。
如果你的场景是仿真环境、可以随时重置、试错成本为零,那就老老实实在线学。只有当真机操作代价高、数据只能靠历史积累的时候,离线方法才是必须的。判断标准很简单:你能不能承受一万次失败?能,就在线;不能,就离线。
6. 实操踩坑实录与问题速查
6.1 训练不收敛时的排查顺序
遇到训练不收敛,不要急着改算法,按固定顺序排查效率最高。
先看环境。奖励函数有没有量级失衡?终止条件能不能被触发?动作执行之后状态有没有真的变化?这三个问题里任何一个出问题,再强的算法也救不回来。我遇到过最离谱的一次,是碰撞检测写错了一个坐标,导致智能体永远撞不到障碍,自然学不会避障。
再看数据流。Q 值有没有在更新?打印几个状态下的 Q 值看看,如果全是零或者全是 NaN,说明更新逻辑有问题。NaN 通常来自学习率过大或者除以零。
然后看超参数。α 是不是太大导致震荡?γ 是不是太小导致短视?ε 衰减是不是太快导致没探索够?
最后才看算法本身是不是适合当前问题。虽然理论上 Q-learning 能处理任意离散 MDP,但状态空间大的时候它确实力不从心,这时候要换函数逼近方法,而不是继续调参数。
6.2 常见现象与成因对照表
下面这些症状我在调试过程中基本都遇到过,整理成表方便对号入座。
| 现象 | 可能的成因 | 处理办法 |
|---|---|---|
| 智能体原地不动 | 步惩罚大于动作收益 | 调小每步惩罚,增加中间引导奖励 |
| 前期学到策略后期崩掉 | 学习率过大或探索率衰减过快 | 降低 α,放缓 ε 衰减 |
| Q 值持续膨胀不收敛 | 未处理终止状态,γ 接近 1 | 终止状态未来价值置 0,适度降低 γ |
| 回合长度越来越长却总失败 | 陷入绕圈循环 | 增加步上限,加入每步负奖励 |
| 训练几百轮毫无起色 | 奖励过于稀疏 | 做奖励塑形或引入中间目标 |
| 学习曲线剧烈抖动 | 样本相关性过强 | 引入经验回放,增大批量 |
| 表现远好于实际 | 测试时仍在探索 | 评估阶段关闭探索,用贪心策略 |
这张表不是万能的,但能覆盖大部分入门阶段的问题。我建议每次遇到异常先在这张表里找找有没有对应项,往往比盲目改参数高效得多。
6.3 几条我自己总结的经验
第一条,先跑通再优化。不要一上来就追求 SOTA 效果,先用最小环境加最小算法跑出能动的策略,确认整条链路通了,再去处理性能问题。很多新手的卡点其实在于环境代码本身有 bug,而不是算法不行。
第二条,把可视化做在前面。训练过程中的 Q 值分布、平均回报曲线、智能体的行动轨迹,这三样东西能解决八成的调试问题。纯看数字打日志效率太低,我在关键实验里都会顺手把轨迹画出来。
提示:在评估阶段一定要把探索关掉。开着 ε 去评估,测出来的成功率会明显偏低,你会误以为算法没学好,白白折腾半天。
第三条,学会用极端参数定位问题。想确认某段逻辑有没有生效,就把相关参数设成极端值看行为有没有明显变化。比如把 γ 设成 0.01,如果策略毫无变化,说明你的折扣因子根本没进到更新式里。这个技巧能快速排除"改了但没生效"这类隐蔽 bug。
第四条,把随机种子固定下来。强化学习方差天然就大,不固定种子的话,两次跑出来的结果差异可能比改算法的差异还大,导致你无法判断改动到底有没有用。固定种子之后,再刻意用多个种子做对比,看均值而不是看单次结果。
第五条,也是我个人体会最深的一条:强化学习的门槛不在算法,在环境。把环境设计清楚了,奖励给对了,剩下的算法部分其实是整个流程里最标准化的环节。我见过太多人花两周调算法,最后发现问题出在奖励函数的一个符号上。
回过头看这一周的内容,V 函数、Q 函数、贝尔曼方程这三样东西是贯穿全场的骨架,其余内容基本都是从它们派生出来的。真正学透的标志不是能背下更新公式,而是看到一个新问题时,能条件反射地想清楚:状态怎么定义、动作空间长什么样、奖励该在哪里给、折扣因子取多少。这四个问题回答好了,选什么算法反而是水到渠成的事。这套框架用在游戏、机器人、调度系统上是同一套,差别只在细节。