
1. 为什么我要把强化学习从Q-learning重新学一遍做机器学习这行的人大概都有个共同的毛病工具用顺手了就不想回头看底层。我最早接触强化学习是在一个推荐策略的小项目里当时直接上了DQN调了几天参数跑通了奖励曲线看着也还行就觉得自己“会了”。后来遇到离线数据场景——手里只有一批历史日志没法跟环境实时交互——才发现DQN那一套根本用不上于是被迫回头补课从Q-learning的表格法一路啃到CQLConservative Q-Learning。这个过程踩了不少坑也纠正了很多之前似是而非的理解。这篇内容就是把我重新学习这条路径的笔记和实操心得整理出来。核心关键词是强化学习、Q-learning、DQN、离线强化学习、CQL、分布偏移。适合两类人看一类是刚入门强化学习、被各种算法名字绕晕的新手另一类是有一定基础、但在离线场景下不知道怎么落地的从业者。我会尽量用大白话把每个算法“为什么长这样”讲清楚而不是只丢公式。先说结论性的判断Q-learning是理解一切值函数方法的根DQN是把表格变成神经网络的工程化桥梁而CQL解决的是“不能和环境交互时怎么学”这个现实问题。这三者是一条清晰的演进线理解了这条线再看别的算法就不会觉得是天上掉下来的。下面我按自己的学习顺序一层层拆开讲。2. Q-learning的表格本质一张会自我更新的记账表2.1 用一个具体场景理解Q表到底在记什么很多人第一次看Q-learning的公式就懵了其实它的本质特别朴素。想象你在一栋陌生的楼里找出口每走到一个路口状态你有几个方向可以选动作每选一次会得到一点反馈奖励。Q-learning要做的事情就是维护一张表记录“在哪个路口往哪个方向走长期来看能拿到多少好处”。这张表就是Q表行是状态列是动作格子里填的是Q值——在某个状态下采取某个动作的长期期望回报。一开始表全是零什么都不懂。每走一步就用实际拿到的奖励加上对下一步最优价值的估计去修正当前格子的值。这个修正过程就是那个著名的更新公式# Q-learning 核心更新伪代码 # Q(s,a) - Q(s,a) alpha * [r gamma * max(Q(s,:)) - Q(s,a)] q_current Q[state][action] q_target reward gamma * max(Q[next_state]) Q[state][action] q_current alpha * (q_target - q_current)这里的alpha是学习率控制每次修正多少gamma是折扣因子控制未来奖励的权重。我第一次真正理解这个公式是在纸上手动算了一个三状态的小例子之后——那种“原来就是不断用新估计去逼近旧估计”的感觉比看十遍推导都管用。2.2 学习率和折扣因子两个最容易被随手填错的参数新手最容易犯的错就是alpha和gamma随手填0.1和0.9然后发现要么学不动要么震荡。我踩过的坑是在一个奖励稀疏的环境里gamma设成0.9结果智能体永远看不到远处的奖励因为折扣衰减太快远处的信号传到起点时已经接近于零了。经验上alpha的选择要看状态空间的稳定性。如果环境随机性大alpha要小一点比如0.01到0.1让更新平滑如果环境确定性强alpha可以大一些加快收敛。gamma则取决于任务的时间跨度——任务越长、奖励越靠后gamma越要接近1。但gamma太接近1也有问题会让Q值估计的方差变大收敛变慢。提示判断gamma是否合理的一个土办法是看gamma的若干次方衰减到0.1需要多少步。如果这个步数远小于任务实际需要的步数说明折扣太狠了。2.3 探索与利用epsilon-greedy不是万能药Q-learning必须解决一个矛盾是选当前已知最好的动作利用还是试试没走过的路探索。最常用的是epsilon-greedy以epsilon的概率随机选否则选当前最优。我一开始固定epsilon0.1结果训练后期还在乱走收敛很差。后来改成衰减策略前期epsilon大比如1.0鼓励探索随着训练逐步降到0.01左右。这个衰减曲线怎么设计其实挺讲究。降太快很多状态没探索到就固化了降太慢收敛遥遥无期。我一般用指数衰减配合一个最小下限实测下来比线性衰减更稳。还有一个细节在状态空间巨大的问题里很多状态可能只被访问过一两次这些状态的Q值估计极不可靠。这时候可以考虑给访问次数少的状态更高的探索优先级这就是后来各种计数探索方法的动机。表格法时代这些还能手动处理到了神经网络时代就变成了更复杂的探索策略。3. 从表格到神经网络DQN到底解决了什么工程难题3.1 状态爆炸表格法撞墙的那一刻Q-learning的表格法有个致命限制状态必须是离散且有限的。一旦状态是连续的比如图像、传感器读数或者状态组合爆炸比如围棋的棋盘表格就存不下了。我最早意识到这个问题是在一个用传感器数据做控制的小项目里——传感器有十几个维度每个维度离散成10档状态数就是10的十几次方表根本建不起来。自然的想法是用一个函数来近似Q值输入状态和动作输出Q值。神经网络就是最好的函数近似器。但直接把Q-learning套上神经网络会非常不稳定甚至发散。为什么因为神经网络训练假设样本是独立同分布的而强化学习的样本是高度相关的时间序列而且目标值Q-target本身还在不断变化相当于“追着一个移动的靶子打”。3.2 经验回放和目标网络两个让训练稳定的关键设计DQN的两个核心贡献恰好对应上面两个不稳定因素。第一个是经验回放Experience Replay把交互产生的(状态, 动作, 奖励, 下一状态)存进一个缓冲区训练时从中随机采样。这样打破了样本的时间相关性让数据接近独立同分布。第二个是目标网络Target Network用一个结构相同但参数更新很慢的网络来计算Q-target避免“自己追自己”导致的震荡。# DQN 训练循环的关键片段伪代码 # 1. 与环境交互存入回放缓冲区 buffer.push(state, action, reward, next_state, done) # 2. 从缓冲区随机采样一个batch batch buffer.sample(batch_size) # 3. 用目标网络计算Q-target q_next target_network(next_states).max(dim1) q_target rewards gamma * q_next * (1 - dones) # 4. 用在线网络计算当前Q值算损失反向传播 q_current online_network(states).gather(actions) loss mse_loss(q_current, q_target.detach()) loss.backward() optimizer.step() # 5. 每隔若干步把在线网络的参数同步给目标网络 if step % sync_interval 0: target_network.load_state_dict(online_network.state_dict())我实测下来目标网络的同步间隔是个敏感参数。同步太频繁等于没有目标网络训练不稳同步太慢目标网络太滞后学习效率低。常见的做法是软更新每次只更新一点点或者硬更新每隔固定步数全量同步具体选哪个要看任务。3.3 过估计问题DQN的一个隐藏缺陷DQN还有一个容易被忽略的问题Q值过估计。因为Q-target里用的是max操作而估计误差有正有负取最大值会系统性地偏向正误差导致Q值被高估。这个问题在动作空间大、奖励噪声大的场景下尤其明显。我第一次遇到这个问题是发现训练出来的策略在仿真里表现很好一到真实环境就拉胯——因为Q值虚高策略对某些动作过于乐观。后来用了Double DQN的思路用在线网络选动作用目标网络评估这个动作的价值把选择和评估解耦过估计就缓解了很多。这个改动很小但效果立竿见影属于性价比极高的技巧。4. 离线强化学习的真正难点不能试错时怎么办4.1 分布偏移离线场景下最致命的暗礁前面讲的Q-learning和DQN都有一个隐含前提智能体可以和环境交互边试边学。但现实里很多场景根本不允许试错——比如医疗决策、金融交易、工业控制你不可能让一个没训练好的策略去真实环境里乱来。这时候只能用一批已经收集好的历史数据来训练这就是离线强化学习。离线场景的核心难题叫分布偏移Distribution Shift。简单说历史数据是由某个旧策略行为策略产生的它覆盖的状态和动作分布和当前正在学习的策略想要探索的分布不一样。当学习到的策略想选一个历史数据里从没出现过的动作时Q函数对这个动作的估计完全是外推出来的可能错得离谱。而Q-learning的max操作偏偏喜欢挑这些被高估的、没见过的动作于是策略越学越偏最后崩掉。我踩过的最典型的坑用一个离线数据集训练DQN训练损失看着一直在降但评估时策略表现极差。排查了很久才发现是Q值在数据分布之外被严重高估策略被“骗”到了那些虚假的高价值区域。4.2 保守思想给Q值加一个“别太乐观”的惩罚解决分布偏移的思路核心是“保守”——对数据分布之外的动作别让Q值估计得太高。CQLConservative Q-Learning就是沿着这个思路设计的。它的做法是在标准Q-learning损失之外加一个正则项压低当前策略倾向选择的动作的Q值同时抬高历史数据里出现过的动作的Q值。用大白话说CQL在告诉Q函数“你没见过的动作别给太高分数据里见过的动作可以给合理的高分”。这样策略就不会被外推出来的虚假高价值吸引学出来的策略更贴近数据能支撑的范围。# CQL 损失的核心结构伪代码简化版 # 标准TD损失 td_loss mse_loss(q_current, q_target) # CQL正则项压低策略动作的Q值抬高数据动作的Q值 q_random online_network(states, random_actions) # 策略可能选的动作 q_data online_network(states, data_actions) # 数据里实际的动作 cql_loss (logsumexp(q_random) - q_data).mean() # 总损失 total_loss td_loss alpha * cql_loss这里的alpha是保守程度系数控制正则项的相对权重。alpha越大越保守策略越贴近数据alpha太小则退化成普通离线Q-learning分布偏移问题又会冒出来。这个参数的调节是CQL实操中最花时间的部分。4.3 CQL的保守系数怎么调我的实测经验alpha的调节没有万能公式但有几个经验可以参考。首先看数据质量如果历史数据覆盖的状态动作空间比较广、质量高alpha可以小一点让策略有更多学习空间如果数据窄且噪声大alpha要大一些宁可保守也别跑偏。其次看评估指标的变化。我一般会同时盯两个量一个是训练损失一个是离线评估的策略回报用历史数据做重要性采样估计。如果训练损失降但评估回报不升反降多半是alpha太小Q值过估计了如果评估回报一直上不去、策略几乎不更新可能是alpha太大保守过头了。还有一个实用技巧CQL的alpha可以随训练动态调整。前期数据利用不充分时用小一点的alpha后期策略稳定了再加大保守力度。这个调度策略在几个项目里都帮我省了不少调参时间。5. 一条可复现的学习与实操路径5.1 环境搭建与最小可跑示例如果你也想走一遍这条路我建议按“表格Q-learning → DQN → CQL”的顺序每个阶段都亲手跑通一个最小例子。环境用经典的CartPole就够它状态简单、奖励清晰适合验证算法是否正确。# 依赖安装以常见科学计算栈为例 pip install numpy gymnasium torch matplotlib先写一个纯表格的Q-learning解CartPole——虽然CartPole状态是连续的但你可以手动离散化比如把每个维度分成若干档。这一步的目的是把更新公式、探索策略、折扣因子这些基础概念用手感记住。跑通之后你会对“Q表怎么收敛”有直观认识。然后换成DQN用神经网络替换Q表。这一步的重点是理解经验回放和目标网络的作用——你可以做个对比实验去掉目标网络看训练曲线怎么抖去掉经验回放看收敛怎么变慢。这种“破坏性实验”比看论文印象深得多。最后上CQL用DQN跑出来的数据或者随机策略产生的数据作为离线数据集对比普通离线DQN和CQL的表现。你会亲眼看到普通离线DQN怎么被分布偏移坑掉而CQL怎么稳住。5.2 每个阶段该重点盯什么指标不同阶段关注的指标不一样这点很多人没意识到。表格Q-learning阶段重点看Q表是否收敛、策略是否稳定可以打印几个关键状态的Q值变化。DQN阶段重点看训练损失、平均回报、Q值量级——Q值如果爆炸式增长多半是过估计或者学习率太大。CQL阶段重点看离线评估回报和保守项的大小判断保守程度是否合适。我习惯用一个简单的表格记录每次实验的关键参数和结果方便回溯阶段关键参数观察指标常见异常表格Q-learningalpha, gamma, epsilonQ值收敛性、策略稳定性不收敛、震荡DQN学习率、回放缓冲区大小、目标网络同步间隔训练损失、平均回报、Q值量级Q值爆炸、训练不稳CQL保守系数alpha、数据质量离线评估回报、保守项大小过保守、策略不更新这张表是我踩了无数坑之后总结的每次开新实验先照着填一遍能省很多瞎调的时间。5.3 几个让我印象深刻的踩坑记录第一个坑在DQN里忘了对Q-target做detach导致梯度回传到了目标网络训练直接崩。这个错误很隐蔽因为代码能跑只是结果不对。后来我养成了习惯凡是计算target的地方都显式加detach。第二个坑离线数据集是用随机策略收集的覆盖太窄CQL怎么调都学不出好策略。这让我明白一个道理——离线强化学习的效果很大程度上取决于数据质量算法再好也救不了烂数据。后来我学会了先分析数据集的覆盖范围再决定用多保守的策略。第三个坑CQL的保守系数一开始设得太大策略几乎不更新评估回报一直卡在行为策略的水平。调小之后才慢慢有提升。这提醒我保守是为了防止跑偏不是为了不学习度很重要。6. 这套方法在实际项目里的边界与取舍6.1 什么时候该用CQL什么时候别硬上CQL不是万能的。如果你的场景可以和环境交互那老老实实用在线方法DQN、PPO之类就行没必要上离线方法因为离线方法天生受限于数据质量。只有当交互成本极高、或者交互有风险时离线强化学习才有价值。另外CQL对数据质量的要求不低。如果历史数据覆盖的状态动作空间太窄或者数据里充满了次优甚至错误的决策CQL也只能在数据支撑的范围内学学不出超越数据的策略。我一般会先做数据覆盖度分析如果发现关键状态几乎没数据就会先考虑补数据而不是硬调算法。6.2 从CQL再往前走还有哪些值得关注的方向CQL之后离线强化学习还有很多演进方向比如基于扩散模型的策略、基于Transformer的序列建模方法等。但我的建议是别急着追新先把Q-learning到CQL这条线吃透。因为后面那些方法本质上还是在解决“分布偏移”和“保守估计”这两个核心问题只是用了不同的数学工具。基础打牢了看新论文会快很多。我个人在实际项目里的体会是算法选型永远要服务于数据和场景。离线强化学习看起来高大上但如果你的数据量小、覆盖窄可能一个简单的行为克隆直接模仿历史动作效果反而更稳。CQL的价值在于当数据足够支撑、又需要超越行为策略时它能给你一个相对可靠的框架。理解它的边界比会调它的参数更重要。最后分享一个小技巧每次实验前先花十分钟想清楚“我的数据能支撑什么样的策略”再决定用多保守的算法。这个习惯帮我避免了很多次“算法调了半天其实是数据不行”的无效劳动。