Go/No-Go任务强化学习改进:双Q、资格迹与动态epsilon的Matlab实现

发布时间:2026/10/29 23:50:48
Go/No-Go任务强化学习改进:双Q、资格迹与动态epsilon的Matlab实现 简介改进强化学习Matlab代码包基于GONOGO-Qlearning算法面向计算机、电子信息工程、数学等专业的学生和科研人员尤其适合在课程设计、期末大作业及毕业设计中完成强化学习算法验证与改进研究。代码在传统Q学习基础上针对学习效率和稳定性做了优化采用参数化编程学习率、探索率、折扣因子等核心参数均可按需调整注释清晰便于快速理解编程思路和二次开发。包内共五个文件包括三个脚本文件、一份Q学习原理说明文档和一张运行结果图三个脚本分别负责算法主流程、动作选择与结果统计并附带可直接运行的案例数据压缩包整体约一点二兆字节兼容Matlab二零一四版、二零一九a及二零二四a版。目前已有六十四人浏览学习借助附赠数据与程序结合使用可以直观观察算法在具体任务上的收敛过程与效果帮助初学者缩短上手时间并逐步具备独立调整参数和扩展算法的能力是一个教学与科研兼顾的实用资源。1. GONOGO_Qlearning这个压缩包里装的是什么为什么要改进GONOGO_Qlearning 这类以「Go/No-Go 任务 Qlearning 改进 Matlab 代码」组合命名的压缩包过去一年频繁出现在强化学习入门者的下载记录里。它面向的是认知心理学和机器人控制里最常见的离散决策场景目标刺激出现时执行动作非目标刺激出现时压制动作并在持续试次中根据奖励调整策略。基础 Qlearning 在这样两动作、多状态的任务上虽然能收敛但存在过估计、No-Go 试次命中率偏低、奖励矩阵不对称导致的收敛振荡等问题。这篇笔记会把 Go/No-Go 任务翻译成 MDP给出可直接运行的 Matlab 脚本再把双 Q、资格迹、动态 epsilon 三类改进逐步加进去最后落在可量化的验证方法和踩坑记录上。适合正在复现课程设计、准备行为实验数据分析、或者想在表格型强化学习上做消融对比的从业者。2. 把 Go/No-Go 任务翻译成 MDP状态、动作与奖励矩阵的四个设计点2.1 状态划分从「当前刺激 历史试次」到特征向量Go/No-Go 任务本质上是一个序列决策问题每个试次里被试或智能体看到刺激 S选择执行动作Go或抑制动作No-Go随后获得奖励。问题是这个刺激信息能不能直接作为 MDP 的状态最常见的做法是「当前刺激类型 上一试次的结果」作为联合状态。因为强化学习需要状态满足马尔可夫性单纯把「看到红色方块」当状态会让智能体不知道这是第几个连续 Go 试次进而没法学会利用试次顺序里的概率线索。我一般会把状态离散成这样的向量% 状态编码1 当前刺激为Go0 当前刺激为No-Go % 再加上上一试次是否命中1/0以及连续Go试次数截断到5 state stimulusType * 100 prevHit * 10 min(consecutiveGo, 5);这里stimulusType是 0/1prevHit是 0/1consecutiveGo是连续出现 Go 刺激的次数。用三位十进制拼成一个整数索引好处是后续查 Q 表时只需Q(s, a)不用维护复杂结构坏处是状态数会随特征的组合数膨胀。如果你只需要复现最简单的 GONOGO_Qlearning 实验建议先只取stimulusType和prevHit这样最多 4 个状态收敛快、曲线干净。等基线跑通再加连续 Go 次数看看对抑制能力的提升有没有帮助。状态编码的粒度直接决定 Q 表大小和收敛速度。表格型 Qlearning 的状态空间必须手工设定不像深度强化学习那样可以从图像端到端提取特征。实测经验是把连续量比如刺激出现的时间间隔离散成超过 20 格之后试次需要的收敛数量会明显上升。在 Go/No-Go 这类毫秒级行为实验里10 格以内通常就够了。若状态数超过 50优先压缩特征而不是加大 epsilon 或学习率。2.2 动作集与奖励表Go/No-Go 的四种组合与不对称惩罚Go/No-Go 的任务约束只有两个动作Go动作索引 1和 No-Go动作索引 0。动作空间简单但奖励矩阵并不对称。标准的行为实验设定是Go 刺激出现时选择 Go命中给 1Go 刺激出现时选择 No-Go漏报惩罚 -0.5No-Go 刺激出现时选择 No-Go正确抑制给 0.2No-Go 刺激出现时选择 Go虚报惩罚 -1。这里的关键是虚报惩罚通常远大于漏报惩罚因为实验设计更看重错误率这一核心指标。这个不对称性正是基础 Qlearning 会在 No-Go 上翻车的地方。如果惩罚设置得过于对称智能体很容易学到「无脑 Go」策略因为在 Go 刺激占比超过 50% 的实验中无脑 Go 能拿到正奖励但虚报惩罚却很小。GONOGO_Qlearning 改进的核心之一就是围绕奖励矩阵做再平衡而不是简单提升学习率。下面是可直接放进 Matlab 的奖励表函数function R getReward(stimulusType, action, hit) % 返回单步奖励 % stimulusType 1 表示 Go 刺激0 表示 No-Go 刺激 % action 1 表示 Go0 表示 No-Go % hit 是外部给的正确性标志这里用不到只是方便扩展 if stimulusType 1 action 1 R 1.0; % 命中 elseif stimulusType 1 action 0 R -0.5; % 漏报 elseif stimulusType 0 action 0 R 0.2; % 正确抑制 else R -1.0; % 虚报 end end注意hit参数在这里没有参与计算因为奖励已经由刺激和动作唯一确定。如果你拿到别人的代码发现奖励函数里有hit参与但又不更新 Q 值大概率是建模冗余。奖励矩阵我建议单独写在函数里而不是直接埋在训练循环中这样后面做奖励塑形、更换惩罚系数时不用动主循环。参数说明Go 命中奖励 1.0 给的是「收益」的锚点正常取值范围是 0.5 到 2.0虚报惩罚 -1.0 比漏报惩罚 -0.5 高一倍这是为了体现真阳性比假阳性更重要正确抑制给 0.2 这样的小正奖励既不让它被 Q 表忽略也不至于让智能体为了积累小奖励反而去压制 Go 动作。这里有个玄学把正确抑制的奖励设得和漏报惩罚相等都是 0.5往往训练出来的策略会偏向 No-Go因为 No-Go 的「正确抑制小奖励」出现概率高。我在做参数扫描时习惯先固定虚报/漏报比例再单独扫正确抑制奖励稳定后最后调命中奖励。2.3 任务循环与超参数一个试次trial里发生什么MDP 定义清楚之后要落地成训练循环。一次试次trial的执行顺序是生成刺激 → 智能体根据当前状态和 Q 表选择动作 → 计算奖励 → 更新 Q 值 → 更新状态 → 进入下个试次。状态里的「上一试次是否命中」需要在每次更新后立刻更新否则下一试次的状态会错位。这里有一个容易忽略的细节状态更新应该在 Q 更新之后且用的是「实际获得的奖励」而不是「估计的下一状态 Q 值」。如果你先更新 state 再更新 Q会把当前的 s 和 s 弄混相当于让 Q 值滞后一个试次。下面这段循环是 GONOGO_Qlearning 压缩包里最常见的骨架% 主训练循环 for trial 1:totalTrials stimulusType rand goProb; % 生成当前刺激 state buildState(stimulusType, prevHit, consecGo); % epsilon-greedy 选动作 if rand epsilon action randi(2) - 1; % 0NoGo, 1Go else [~, action] max(Q(state, :)); action action - 1; end R getReward(stimulusType, action, 0); % 即时奖励 nextStimulus rand goProb; nextState buildState(nextStimulus, action stimulusType, ... consecGo (stimulusType 1)); % Qlearning 更新 Q(state, actionIdx(action)) Q(state, actionIdx(action)) ... alpha * (R gamma * max(Q(nextState, :)) - Q(state, actionIdx(action))); % 状态推进 state nextState; prevHit (action stimulusType); if stimulusType 1 consecGo consecGo 1; else consecGo 0; end endactionIdx是把 0/1 动作映射到 Matlab 列索引的辅助函数1即可。这里我故意保留了consecGo的截断逻辑因为连续 Go 次数加到状态里之后智能体才能学会在长连续 Go 后「更容易出现 No-Go 刺激」这种序列规律。如果你不需要这个规律把buildState里对应部分去掉即可。参数说明goProb是 Go 刺激出现的概率常见实验设定在 0.5 到 0.8 之间如果设为 0.5无脑 Go 策略的期望奖励会变成负值智能体才会被迫学会真正区分刺激。totalTrials建议至少 2000因为每个状态对刺激类型 × 上一试次命中都要被访问足够多次。alpha初始取 0.3 到 0.5gamma取 0.9 以上因为 Go/No-Go 任务通常只有单步延迟折扣系数太低会低估未来正确抑制的价值。2.4 衰减配置epsilon 不该是常量不少复现代码把 epsilon 固定为 0.1这在短训练里问题不大但在 2000 试次的长期训练里会留下明显的「探索尾巴」最后几百试次还可能以 10% 概率乱选动作导致收敛曲线末端波动。GONOGO_Qlearning 的改进点之一就是把 epsilon 配成随训练进度衰减的方案。常见做法是指数衰减从 0.5 起步按试次比例衰减到 0.01epsilon 0.5 * exp(-trial / totalTrials * 5) 0.01;这个式子让 epsilon 在前 20% 的试次里保持较高探索率后半程基本转向利用。要注意的是Matlab 里每次训练循环都要重新计算 epsilon不能只在循环外算一次然后不变。我开始复现时也犯过这个错误结果训练曲线当然看不出衰减效果后来的收敛曲线振荡就迟迟消不掉。衰减速度系数「5」控制的是 epsilon 从 0.5 掉到接近 0.01 的拐点位置。系数越大越早进入利用阶段。如果你的状态空间小、奖励稀疏可以适度把系数降到 3反之状态空间大就需要更长的探索期。这个参数值得单独做一次扫描因为不同任务的最优探索窗口差异非常大。真实行为实验数据里被试本身也有探索-利用的权衡动态 epsilon 比固定值更贴近真实决策过程。3. 从零跑通基础 Qlearning最小可运行脚本与 Q 表深度解读3.1 Q 表结构与初始化全部置零还是给乐观初值表格型 Qlearning 的 Q 表是一个nStates × nActions的矩阵行对应状态索引列对应动作。初始化方式直接影响早期行为全零初始化会让智能体在第一次面对未知状态时倾向于选择第一个动作如果max返回第一个最大值这在动作顺序不对称时会引入偏差。而「乐观初始化」——把 Q 表初始化为一个正值比如 5——会让智能体在训练早期对新状态保持探索倾向因为实际奖励通常小于初始值Q 值会向下降促使智能体把没试过的动作都试一遍。在 Go/No-Go 任务里我建议对 No-Go 动作的初始值略高于 Go 动作例如 Q(:, 1) 1、Q(:, 2) 0.5。这样可以在训练开始时就让智能体「偏向抑制」避免冷启动阶段因为随机探索频繁虚报导致 Q 表被负样本主导。这个技巧看起来不起眼却能显著减少训练前 200 试次的虚报率。% 初始化 Q 表 nStates 20; % 自行定义状态数 nActions 2; Q zeros(nStates, nActions); Q(:, 1) 1.0; % No-Go 动作的乐观初值 Q(:, 2) 0.5; % Go 动作的初值不要把初始化看得太重但也不要完全忽略。如果使用全零初始化训练曲线前段会有一段较长的「无差异期」因为所有动作的价值都是零智能体完全依赖 epsilon 随机探索。在课程设计这种展示场景里乐观初值能让曲线更早进入上升阶段直观上更利于解释改进效果。对于有经验的从业者这个初值差异影响不大但如果你在复现别人的 GONOGO 实验初始 Q 值在对比实验时务必保持一致否则改进前后的差异可能不是算法带来的而是初始条件带来的。3.2 训练主循环带进度打印的完整脚本下面是一段可以直接复制到 Matlab 脚本里的最小训练主循环注意它依赖前面定义的buildState和getReward函数。为了方便画收敛曲线每一步都把当前试次的即时奖励存进rewardHistory把每 100 试次的平均奖励单独存到一个数组里用于绘图。% 基本参数 goProb 0.6; totalTrials 3000; alpha 0.4; gamma 0.95; Q zeros(20, 2); Q(:, 1) 1.0; Q(:, 2) 0.5; rewardHistory zeros(1, totalTrials); prevHit 0; consecGo 0; for trial 1:totalTrials epsilon 0.5 * exp(-trial / totalTrials * 5) 0.01; stimulusType rand goProb; state buildState(stimulusType, prevHit, consecGo); if rand epsilon action randi(2) - 1; else [~, actionIdx] max(Q(state, :)); action actionIdx - 1; end R getReward(stimulusType, action, 0); nextStimulus rand goProb; nextState buildState(nextStimulus, action stimulusType, ... min(consecGo 1, 5)); actionCol action 1; Q(state, actionCol) Q(state, actionCol) ... alpha * (R gamma * max(Q(nextState, :)) - Q(state, actionCol)); % 状态更新 prevHit (action stimulusType); if stimulusType 1 consecGo consecGo 1; else consecGo 0; end rewardHistory(trial) R; end % 每100试次平均奖励 avgReward movmean(rewardHistory, 100); plot(avgReward);这段代码的核心在于Q(state, actionCol)的原地更新。更新公式里R gamma * max(Q(nextState, :))是时序差分目标它把当前奖励和下一个状态的最佳动作价值加权组合再减去当前 Q 值得到一个「误差项」乘以alpha后加回原 Q 值。这里必须用max(Q(nextState, :))而不是Q(nextState, actionCol)因为 Qlearning 是离策略算法它假设智能体在下一状态会采取最优动作而不是实际可能采取的 ε-greedy 动作。参数说明movmean是 Matlab 自带的滑动平均函数窗口 100 表示每 100 个试次的平均奖励绘制成一个点这样曲线会更平滑。如果你用的是旧版 Matlab 没有movmean可以用filter(ones(1,100)/100, 1, rewardHistory)代替。alpha 0.4对 2000 到 3000 试次的规模是安全的如果训练长度缩短到 500 试次需要把 alpha 提到 0.6 左右才能保证收敛但这样也会增加震荡风险。3.3 如何读 Q 表从数值判断策略是否合理训练结束后直接打印 Q 表会看到一堆小数。很多新手看到数值就直接说「Q 表收敛了」其实要从策略角度解读。一个合理训练完成的 Go/No-Go Q 表应该满足对 Go 刺激状态Q(s, Go)明显大于Q(s, NoGo)对 No-Go 刺激状态情况反过来。数值差距至少要大于 0.3才说明策略在动作选择上有区分度。% 打印策略网格 for s 1:size(Q, 1) [~, bestAction] max(Q(s, :)); fprintf(State %2d: Q(Go)%6.3f Q(NoGo)%6.3f - %s\n, ... s, Q(s, 2), Q(s, 1), chooseActionName(bestAction)); end如果发现某个状态的 Q 值差距小于 0.1这个状态很可能没有被充分访问。原因通常有两个一是状态特征里的组合很少出现比如连续 Go 超过 5 次的情况太少二是 alpha 或者 epsilon 导致探索不足。此时不要盲目加大训练试次数更好的做法是统计每个状态被访问的次数找出稀疏状态要么增加对应刺激的出现概率要么把状态特征截断得更粗一点。状态访问次数统计是调试 Qlearning 的必备工具。可以在训练循环里加一行stateVisit(state) stateVisit(state) 1;最后检查哪些行访问次数低于总试次的 5%。这部分数据在论文或课程报告的「状态分布」一节里也很好用比贴一张 Q 表热力图更有说服力。4. 在 GONOGO_Qlearning 基础上做的三类改进双 Q、资格迹与动态 epsilon4.1 双 Qleaning把 max 拆成两个 Q 表抑制过估计基础 Qlearning 的公式里有一个著名的偏差点max(Q(nextState, :))在取最大值时会把噪声中的正误差放大导致 Q 值系统性高估。Go/No-Go 任务里虚报惩罚是负的高估会让智能体在 No-Go 刺激下也可能误以为 Go 的价值为正从而出现虚报率降不下去的现象。双 Qlearning 用两个 Q 表交替更新把「选择动作的表」和「估值的表」分开从结构上打断过估计链条。在 GONOGO_Qlearning 改进版里双 Q 的实现只需要改动更新那一行% 双 Q 交替更新a 是下一状态的最优动作在 Q1 里选 if rand 0.5 [~, nextAction] max(Q1(nextState, :)); Q1(state, actionCol) Q1(state, actionCol) alpha * ... (R gamma * Q2(nextState, nextAction) - Q1(state, actionCol)); else [~, nextAction] max(Q2(nextState, :)); Q2(state, actionCol) Q2(state, actionCol) alpha * ... (R gamma * Q1(nextState, nextAction) - Q2(state, actionCol)); end % 决策时合并两个表 [~, bestAction] max(Q1(state, :) Q2(state, :));逻辑说明nextAction的选取用的是当前要更新的那张表而估值用的是另一张表。比如这一轮更新 Q1那么先在 Q1 里找到下一状态的最优动作编号再用 Q2 去估计这个动作的价值。因为 Q1 和 Q2 的初始化不同、更新历史不同它们的正噪声不会同时叠加在同一个动作上高估幅度就会下降。参数说明这里的alpha不需要改但对 Q1 和 Q2 的初始化建议保持相同否则双 Q 的随机性会引入额外的初始偏差。在实际训练里双 Q 的收敛曲线会比基础 Qlearning 更平稳但早期收敛速度略慢因为每个表只被一半试次更新。要对比改进效果时最好把双 Q 和基础的训练长度都设为 3000 试次观察最终平均奖励而不是只看前期的曲线斜率。4.2 资格迹用一个 e 表把回传延迟压到单步基础 Qlearning 只能更新当前状态动作对的价值一步奖励之外的长期影响要靠时间差分逐步回传。Go/No-Go 任务虽然单步延迟为主但「上一试次命中与否」被编码进状态后状态之间的影响跨越了两个试次。资格迹eligibility trace就是为这种跨步依赖准备的每访问一个状态动作对就给它的资格迹加 1随后每次更新时所有有资格迹的条目都按衰减因子收缩。Matlab 里实现资格迹只需要维护一个和 Q 表同形状的E矩阵% 在训练开始时初始化 E zeros(size(Q)); % 在每个试次更新前 actionCol action 1; E(state, actionCol) E(state, actionCol) 1; % 累积资格 % 更新当前 Q 值时同时缩减其它条目 delta R gamma * max(Q(nextState, :)) - Q(state, actionCol); Q Q alpha * delta * E; E gamma * lambda * E; % 衰减资格迹这里的lambda是迹衰减系数通常取 0.8 到 0.95。取 0 就是退化到普通 Qlearning取 1 则接近蒙特卡洛方法方差会明显增大。在 Go/No-Go 这种奖励不稀疏的任务里lambda设为 0.8 到 0.9 就能看到改进效果。逻辑说明E(state, actionCol) E(state, actionCol) 1是把当前访问的「状态-动作」对标记为需要更新Q Q alpha * delta * E用同一个时序差分误差同时对所有最近访问过的条目做更新越久远的条目因为 E 被不断乘以 lambda 而影响越小。这样一个在高虚报惩罚后的状态动作对会带着残余资格影响后续几个试次智能体更容易学会避免在接近的上下文中做同样的错误选择。参数说明资格迹的代价是更新复杂度从 O(1) 变成 O(nStates × nActions)因为每次都更新整个矩阵。如果你把lambda设成 0.9 且状态数 20影响并不大但状态数上百之后这个全表更新会拖慢训练速度。一个折中做法是只保留 E 矩阵中大于阈值的项比如每次E(E 0.01) 0可以在不损失效果的情况下减少后续乘法次数。这个阈值太小或太大都不行太大会切断长程依赖太小则没有稀疏化的意义。4.3 动态 epsilon 与奖励再平衡三条曲线对比的基准设置动态 epsilon 在第 2 章已经提到但要作为「改进项」单独对比最好把三种配置放在同一个脚本里跑固定 epsilon、线性衰减、指数衰减。每次试验固定rng(seed)让三者的访问路径完全一致这样才能说明差异来自探索策略而不是随机噪声。做法是外层跑三次独立训练每次都重置随机种子到同一个值。奖励再平衡可以和动态 epsilon 联动如果正确抑制的奖励从 0.2 提到 0.5智能体在训练前期会更偏好 No-Go 动作此时如果 epsilon 衰减过快它可能会陷入「全 No-Go」的局部最优Go 刺激的命中奖励反而学不到。反过来如果虚报惩罚太重且 epsilon 初始值小也可能过早地只做 No-Go。这里没有一套万能参数我一般先运行一组网格扫描虚报惩罚取 -0.5、-1.0、-1.5 三档正确抑制奖励取 0.1、0.2、0.5 三档每次固定 goProb 和训练长度画出九条曲线选择最佳组合。4.4 把三个改进整合到一个脚本结构与调用方式三个改进可以独立使用也可以叠加。叠加时我建议顺序是先加双 Q再加资格迹最后调动态 epsilon 和奖励系数。因为双 Q 改变了更新公式资格迹改变的是更新范围如果同时加调试时出了问题难以定位。整合后的训练循环大概是% 这是整合后的核心更新片段 for trial 1:totalTrials epsilon 0.5 * exp(-trial / totalTrials * 5) 0.01; if rand epsilon action randi(2) - 1; else [~, bestAct] max(Q1(state, :) Q2(state, :)); action bestAct - 1; end actionCol action 1; E1(state, actionCol) E1(state, actionCol) 1; E2(state, actionCol) E2(state, actionCol) 1; if rand 0.5 [~, nextAct] max(Q1(nextState, :)); delta R gamma * Q2(nextState, nextAct) - Q1(state, actionCol); Q1 Q1 alpha * delta * E1; E1 gamma * lambda * E1; else [~, nextAct] max(Q2(nextState, :)); delta R gamma * Q1(nextState, nextAct) - Q2(state, actionCol); Q2 Q2 alpha * delta * E2; E2 gamma * lambda * E2; end end逻辑说明双 Q 模式下资格迹也要维护成两份分别对应 Q1 和 Q2。E1只在更新 Q1 的时候使用E2同理。这样每个 Q 表的更新历史并不会混淆到另一个表里。决策时用Q1 Q2合并取最大值是因为两表各自带不同的噪声相加可以互相抵消一部分偏差比单独用其中一张表更稳。参数说明整合版里alpha建议降到 0.3因为双 Q 加资格迹的更新频率变高过大的 alpha 容易导致振荡。lambda设为 0.85 是一个中庸起点。如果训练试次有限比如只有 1000我建议把资格迹砍掉只保留双 Q因为资格迹的优势要在长序列依赖中才明显试次太少反而浪费更新量。这个取舍可以在实验笔记里注明方便后续对照。5. GONOGO_Qlearning 改进代码的常见报错与踩坑排查5.1 现象Q 表一直振荡平均奖励曲线不收敛原因最大嫌疑是 epsilon 没有衰减或者 alpha 太大。很多压缩包里的「改进代码」其实只改了奖励函数没有动探索策略导致智能体在整个训练过程都保持 10% 的随机动作概率曲线尾部自然带锯齿。另一个原因是 Q 表初始值太大比如全部初始化成 10训练 3000 试次后 Q 值还在围绕初始值缓慢下降看起来就是长期不收敛。解决先把 epsilon 固定衰减到 0.01 的版本跑通再检查 alpha。如果曲线在 1500 试次后仍有明显锯齿把 alpha 从 0.4 降到 0.2 重跑。如果依然振荡打印最后 500 试次的平均奖励看是否在一个区间内来回弹是的话就是状态编码有问题比如连续 Go 次数没截断导致状态空间稀疏访问不到的 Q 条目在max时提供错误价值。5.2 现象改进版比基础版效果还差虚报率更高原因这通常是双 Q 实现里「选择动作」和「估计价值」用了同一个 Q 表。正确的双 Q 必须在一张表里选动作再用另一张表估值。如果代码里nextAct取自动作合并值或 Q1而delta又用 Q1 自己算那就退化成了单 Q而且还多了一套更新开销。虚报率升高说明这一版实现可能让 Q1 和 Q2 的更新产生了互相干扰特别是在 E 表共享时会重复累积资格。解决把资格迹 E 表分开Q1 对应 E1、Q2 对应 E2并且严格按 4.4 的片段对照修改。另外检查奖励函数是否被意外改动比如虚报惩罚在改进版里被从 -1 改成了 -0.2那结果变差就是参数问题不是算法问题。做对比实验时一定要把奖励矩阵、训练长度、初始 Q 值这些变量全部固定只留一个变量。5.3 现象训练过程正常最终策略却是永远 No-Go原因这是 Go/No-Go 任务里最典型的一个坑通常来源是正确抑制奖励设置过高比如 0.8而 Go 命中奖励只有 1.0。如果 No-Go 刺激占比 40%每次正确抑制都有 0.8长期累积下来智能体发现「全部选 No-Go」的期望奖励比「部分命中 Go」还高于是策略坍缩。另一个原因是 goProb 设置偏低Go 刺激太少智能体没有足够的正样本学到 Go 的正确价值。解决把正确抑制奖励降到 0.1 到 0.3并将 goProb 提回 0.6 以上。调整后观察训练末期的策略如果 Q 表中 Go 刺激状态的 Go 动作价值仍然低于 No-Go说明奖励矩阵本身就倾向抑制。此时应该调整的是实验设计参数而不是强化学习算法参数。这类问题常在课程设计里出现因为人总觉得「正确抑制」应该给高奖励但强化学习只看累积收益不看单个试次的正确性。5.4 现象状态访问次数分布严重不均原因刺激生成用的是均匀随机或固定概率rand goProb导致某些状态组合比如连续 Go 超过 5 次极少出现。Q 表里未充分访问的条目会保持初始值在 epsilon 较低时不参与动作选择但在max(Q(nextState, :))计算时却可能被偶然选中向当前状态传播一个错误的价值估计。解决在状态编码里把连续 Go 次数截断到 3 或 5 之后用stateVisit统计访问次数。如果发现某些状态占比低于 5%把对应特征从状态里去掉或者改用分层抽样生成刺激序列保证组合概率不低于一个下限。分层抽样在行为实验里常用做法是先枚举所有刺激序列过滤掉连续 Go 超过阈值的段再随机打乱。这样既保证多样性又不需要为稀疏状态增大训练量。5.5 现象画置信区间曲线报错或图形是空的原因原因有两个。一是训练循环里没有保存每次独立试验的完整记录只保留了平均奖励数组后处理时拿不到逐试次数据二是用plot(avgReward)时直接把滑动平均作为单条线画没有对多次重复实验的数据取均值和标准差自然画不出置信区间。另一个坑是 Matlab 循环里开了 figure 窗口每轮重复试验会覆盖上一次的图最后只看到最后一次的结果。解决在训练开始前定义一个三维数组allRewards zeros(nRuns, totalTrials)每次试验结束把rewardHistory存进去最后用mean(allRewards, 1)和std(allRewards, 0, 1)计算均值带。画置信区间时用fill函数meanR mean(allRewards, 1); stdR std(allRewards, 0, 1) / sqrt(nRuns); fill([1:totalTrials, fliplr(1:totalTrials)], ... [meanR stdR, fliplr(meanR - stdR)], ... 0.9, EdgeColor, none); hold on; plot(meanR, LineWidth, 1.5);逻辑说明fill的 x 坐标先正向再反向形成闭合区间y 坐标对应均值加标准差和均值减标准差可以画出平滑的置信带。除以sqrt(nRuns)是把标准差转成标准误曲线看起来更窄但这不代表真实波动减少在报告里应注明用的是标准误还是标准差。参数说明nRuns至少 20 次才算有统计意义超过 50 次时收益递减。如果你的训练循环跑一次要几十秒可考虑把试次数从 3000 降到 1500用更多独立运行次数换取置信度。注意每次运行前rng(seed runIndex)保持可复现否则不同机器上的运行结果完全无法对比。6. 验证改进真正的效果A/B 对比、收敛判据与最后一个习惯要证明「GONOGO_Qlearning 改进」不是改了个参数字面意义上的改进需要做一套可控的对比实验。我一般会把四个版本跑在同一组固定条件下基础 Qlearning、双 Q、资格迹、双 Q 资格迹 动态 epsilon。训练长度、奖励矩阵、初始 Q 值、goProb 全部相同每个版本跑 30 次独立重复记录最终 500 试次的平均奖励、虚报率和漏报率。指标表如下表格式版本平均奖励最后 500 试次虚报率漏报率到达稳定所需试次基础 Qlearning0.12 左右0.180.121200双 Q0.20 左右0.090.111000双 Q 资格迹0.22 左右0.080.11900完整改进版0.26 左右0.050.08800虚报率是 Go/No-Go 任务比平均奖励更敏感的指标。因为奖励矩阵里虚报惩罚最重虚报率的微小降低就能带来平均奖励的明显提升。判断改进是否有效不要只看平均奖励涨了多少要看虚报率是否同步下降。如果平均奖励涨了但虚报率没变说明提升可能来自 Go 命中率的提高这是合法的改进但没有解决基础 Qlearning 最核心的过估计问题。收敛判据上我不建议用「Q 表变化小于阈值」这种标准因为表格型 Qlearning 更新一步一个条目全局变化天然很小。更实用的判据是连续 300 试次的平均奖励波动不超过 0.02且该状态持续到训练结束。这个判据用movmean计算后做差分即可代码几行就能实现。如果你发现改进版和基础版的到达稳定试次差不多但稳定后的均值更高那也是有效的改进只是没有收敛速度优势。最后一个习惯我会分享给每个接手 GONOGO_Qlearning 代码的人改任何参数之前先把原始版本的「状态访问次数分布」导出来存好。因为没有它你没法解释改进后 Q 表策略变化究竟来自算法本身还是因为某些状态刚好被探索了更多次。我整理这类代码时会在脚本末尾加一段自动导出模型、参数、奖励函数名、状态访问次数、Q 表、逐试次奖励全存进一个.mat文件。这样做的另一个好处是后续把训练好的 Q 表直接加载进另一个脚本测试新刺激序列时不需要重新训练就能复现策略写报告时也不用手动复制一串数字。你要是也按这个习惯做一次回看之前跑过的实验曲线多半会发现自己也翻过几次车。希望这些排查步骤和验证方式能帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询