FEATURED · 精选文章

SARSA算法:强化学习中的同策略决策与安全探索实践

发布时间 / 2026/8/7 9:28:32
来源 / 创域科博编辑部
栏目 / 资讯中心
SARSA算法:强化学习中的同策略决策与安全探索实践 1. 项目概述从“试错”到“学习”的智能决策之路聊到让机器或程序学会自己做决策很多人会立刻想到深度学习、大模型这些听起来就很高大上的词。但在我十多年的算法工程和AI应用落地经验里真正能在资源有限、环境复杂、需要快速响应的场景下稳定发挥作用的往往是一些更“古典”但极其精妙的方法。今天要拆解的SARSA算法就是强化学习领域里这样一位“低调的实力派”。它不像它的“近亲”Q-Learning那样常被挂在嘴边但在许多实际场景中比如机器人避障、游戏AI、工业控制甚至是广告投放策略的实时调整SARSA的表现往往更稳健、更贴近真实交互逻辑。简单来说SARSA是一种用于解决序列决策问题的算法。想象一下你在一个陌生的迷宫里找出口每走一步一个状态你都要决定往哪个方向走一个动作。走对了可能离出口更近走错了可能撞墙。SARSA要做的就是通过不断地“走”和“试”学会一套策略在迷宫的每个位置都知道往哪个方向走最有希望最快找到出口。它的名字很有意思是五个英文单词的首字母State状态- Action动作- Reward奖励- State‘下一状态- Action’下一动作。这个名字本身就揭示了它的核心工作流程我基于当前状态选择一个动作得到奖励并进入新状态后我还会看一下在新状态下“实际会采取”的下一动作是什么然后用这个“动作对”的信息来更新我之前的判断。这一点是它和Q-Learning最本质的区别也决定了它们不同的性格和适用场景。如果你正在研究如何让一个系统具备自适应的决策能力但又对黑盒般的深度模型心存疑虑或者你的应用场景对决策的安全性和在线学习的稳定性要求极高那么SARSA绝对值得你深入了解。它不追求理论上“最优”的激进探索而是强调在“当前策略”下的“踏实”学习这种特性使得它在面对真实、复杂、甚至有风险的环境时常常能表现出更可靠的性能。接下来我会带你彻底拆解SARSA从它的设计哲学、数学原理到一行行代码的实现细节再到实际调参和避坑的经验让你不仅能看懂更能亲手用起来。2. 核心原理与设计哲学为什么是“On-Policy”要真正理解SARSA绝不能只停留在公式记忆上必须搞懂它背后的设计思想。这关系到你未来是生搬硬套还是能根据实际问题灵活选用甚至改进算法。2.1 “同策略”学习言行一致的决策者SARSA被归类为“同策略”算法。这是它最核心的标签。什么叫“同策略”通俗讲就是用于探索环境的行为策略和用于评估、改进的目标策略是同一个策略。让我用一个生活化的例子来解释。假设你在教一个新手司机学习在复杂路口左转。你有两种教法Q-Learning式异策略你告诉学员“你现在按我教的保守方法开行为策略。但你在心里评估每个操作好坏时别管你现在实际怎么开你就假设自己是个老司机每次都选理论上最快最猛的那个操作目标策略来打分。”SARSA式同策略你告诉学员“你就按我教的保守方法开行为策略。同时你评估操作好坏时就看按这个保守方法开下去接下来实际会发生什么。比如你打了转向灯后如果按保守策略你会等三秒再转那你就用‘等三秒’这个实际后续动作来评估刚才‘打灯’这个动作的好坏。”看出区别了吗SARSA在更新对某个“状态-动作”的价值判断时它参考的是在当前策略下真实会发生的后续动作。它评估的是“如果我继续按照现在的开法开结果会怎样”。而Q-Learning评估的是“假设我以后每次都选最好的开法结果会怎样”。因此SARSA学习到的是当前正在执行的这个策略本身的价值它更“脚踏实地”学习过程与最终表现高度一致。2.2 与Q-Learning的关键分野悬崖边的抉择这个差异在著名的“悬崖漫步”网格世界问题中体现得淋漓尽致。想象一个网格左下角是起点右下角是终点但最下面一排除了起点和终点都是“悬崖”掉下去会有很大的负奖励比如-100每走一步也有小的负奖励比如-1鼓励智能体尽快到达终点。Q-Learning由于它在更新时总是假设下一步会选最优动作即使当前策略可能很保守它很快就能“发现”那条贴着悬崖边的最短路径。因为它在心里算账时认为贴着悬崖走只要下一步“假设”自己会往里走一步是价值最高的。这可能导致它在实际探索中因为探索的随机性真的掉下悬崖几次但最终它会学会那条激进的最优路径。SARSA由于它用实际策略来选择下一步动作如果它的策略是ε-贪婪大部分时间贪心小部分时间随机探索那么当它靠近悬崖时它“考虑”到的下一步动作有ε的概率是随机的可能就会掉下悬崖。在更新价值时这个“掉下悬崖”的巨大风险会被考虑进去。因此SARSA学到的策略会更保守它会选择一条离悬崖更远的、更安全的路径尽管这条路可能更长。注意这里的关键不是SARSA学不到最优路径。如果你把探索率ε随着时间衰减到0SARSA最终也会收敛到最优策略。但在学习过程中尤其是在ε还比较大的阶段SARSA学到的策略会更关注当前策略下的实际安全性。这使得它在机器人控制、金融交易等对风险敏感的领域往往更受青睐。2.3 算法流程与更新公式拆解SARSA的目标是学习一个动作价值函数 Q(s, a)它代表了在状态s下采取动作a并且此后一直遵循当前策略π所能获得的累积奖励的期望值。它的核心更新公式如下Q(s, a) ← Q(s, a) α * [ R γ * Q(s, a) - Q(s, a) ]我们来拆解这个公式里的每一个部分s, a当前的状态和动作。R执行动作a后环境返回的即时奖励。s执行动作a后到达的新状态。a根据当前策略π在状态s下选择的下一个动作。这是SARSA的“S”和“A”的来源。γ折扣因子范围在[0, 1]。它决定了未来奖励的现值。γ越接近0智能体越“短视”只在乎眼前奖励越接近1越“有远见”。通常设为0.9或0.99。α学习率范围在[0, 1]。它控制着新信息覆盖旧信息的程度。α0表示完全不学习α1表示完全用新估计替换旧值。通常从一个较大的值如0.1开始随着学习进程逐渐衰减。[ R γ * Q(s, a) - Q(s, a) ]这被称为时序差分误差。它是实际得到的奖励加上对未来价值的折扣估计与原有价值估计之间的差值。这个误差是驱动学习的核心信号。整个算法的伪代码流程可以概括为初始化Q表格状态数 × 动作数通常设为0或小随机数。对于每一个训练回合 a. 初始化状态s。 b. 根据当前Q值和探索策略如ε-贪婪选择动作a。 c. 循环直到回合结束到达终止状态 i. 执行动作a观察奖励R和新状态s‘。 ii.根据当前Q值和同样的探索策略在状态s’下选择动作a‘。关键步骤 iii. 使用公式Q(s, a) ← Q(s, a) α * [R γ * Q(s, a) - Q(s, a)]更新Q值。 iv. 将状态和动作更新为s‘和a’准备下一步s ← s,a ← a。这个“s ← s,a ← a”的滑动窗口正是SARSA名称的生动体现也是它“同策略”特性的代码级表达。3. 核心实现细节与参数调优实战理解了原理我们动手实现一个经典的例子网格世界寻路。我们将通过这个例子把SARSA的每一个细节掰开揉碎并深入讨论那些真正影响效果的参数和技巧。3.1 环境搭建一个简单的网格世界我们设计一个4x4的网格左上角(0,0)是起点右下角(3,3)是终点到达终点奖励10。网格中有一个“陷阱”格子(1,2)踩上去奖励-10并结束回合。其他所有非终点的移动每步奖励-0.1鼓励智能体尽快找到终点。动作空间是上、下、左、右四个方向。import numpy as np import matplotlib.pyplot as plt class GridWorld: def __init__(self): self.width 4 self.height 4 self.start_state (0, 0) self.goal_state (3, 3) self.trap_state (1, 2) self.state self.start_state # 动作映射0:上1:右2:下3:左 self.actions [0, 1, 2, 3] self.action_effects [(-1, 0), (0, 1), (1, 0), (0, -1)] def reset(self): self.state self.start_state return self.state def step(self, action): x, y self.state dx, dy self.action_effects[action] new_x, new_y x dx, y dy # 边界检查 if 0 new_x self.height and 0 new_y self.width: self.state (new_x, new_y) # 如果移动出界则留在原地也可以给负奖励这里简化处理 # 判断终止和奖励 if self.state self.goal_state: reward 10 done True elif self.state self.trap_state: reward -10 done True else: reward -0.1 # 每步小惩罚鼓励快速到达 done False return self.state, reward, done3.2 SARSA智能体实现接下来是SARSA智能体的核心代码。我会在关键位置加上详细注释。class SarsaAgent: def __init__(self, state_space, action_space, learning_rate0.1, discount_factor0.9, epsilon0.1): self.state_space state_space # 状态空间维度例如 (4,4) self.action_space action_space # 动作列表 [0,1,2,3] self.lr learning_rate # 学习率 α self.gamma discount_factor # 折扣因子 γ self.epsilon epsilon # 探索率 ε # 初始化Q表。这是一个字典键是(state, action)元组值是Q值。 # 使用字典比大型数组更灵活尤其对于状态空间很大或连续的情况需配合函数逼近。 self.q_table {} for x in range(state_space[0]): for y in range(state_space[1]): for a in action_space: self.q_table[((x, y), a)] 0.0 # 初始化为0 def choose_action(self, state): 使用ε-贪婪策略选择动作 if np.random.uniform(0, 1) self.epsilon: # 探索随机选择一个动作 action np.random.choice(self.action_space) else: # 利用选择当前状态下Q值最大的动作 # 获取该状态下所有动作的Q值 q_values [self.q_table[(state, a)] for a in self.action_space] # 可能存在多个动作具有相同的最大Q值随机选择一个以避免偏见 max_q max(q_values) # 找出所有等于最大Q值的动作索引 candidates [a for a in self.action_space if self.q_table[(state, a)] max_q] action np.random.choice(candidates) return action def learn(self, state, action, reward, next_state, next_action, done): 执行SARSA更新 current_q self.q_table[(state, action)] if done: # 如果下一状态是终止状态则没有后续的Q值 target_q reward else: # SARSA核心使用下一状态和“实际选择的”下一动作的Q值 next_q self.q_table[(next_state, next_action)] target_q reward self.gamma * next_q # 时序差分更新公式 self.q_table[(state, action)] current_q self.lr * (target_q - current_q)3.3 训练循环与可视化将环境和智能体组合起来进行训练并记录学习过程。def train_sarsa(env, agent, episodes500): episode_rewards [] episode_steps [] for episode in range(episodes): state env.reset() action agent.choose_action(state) # SARSA需要预先为初始状态选一个动作 total_reward 0 steps 0 done False while not done: # 执行动作 next_state, reward, done env.step(action) # 为下一个状态选择动作这是SARSA的a‘ next_action agent.choose_action(next_state) # 学习 agent.learn(state, action, reward, next_state, next_action, done) # 更新状态和动作为下一步做准备 state, action next_state, next_action total_reward reward steps 1 episode_rewards.append(total_reward) episode_steps.append(steps) # 每100回合打印一次进度 if (episode 1) % 100 0: avg_reward np.mean(episode_rewards[-100:]) avg_steps np.mean(episode_steps[-100:]) print(fEpisode {episode1}, Avg Reward (last 100): {avg_reward:.2f}, Avg Steps: {avg_steps:.2f}) return episode_rewards, episode_steps # 创建环境和智能体 env GridWorld() agent SarsaAgent(state_space(4,4), action_space[0,1,2,3], learning_rate0.1, discount_factor0.9, epsilon0.1) # 开始训练 rewards, steps train_sarsa(env, agent, episodes1000) # 绘制学习曲线 fig, (ax1, ax2) plt.subplots(1, 2, figsize(12, 4)) ax1.plot(rewards) ax1.set_xlabel(Episode) ax1.set_ylabel(Total Reward) ax1.set_title(SARSA Learning Curve (Reward)) ax1.grid(True) ax2.plot(steps) ax2.set_xlabel(Episode) ax2.set_ylabel(Steps per Episode) ax2.set_title(SARSA Learning Curve (Steps)) ax2.grid(True) plt.tight_layout() plt.show()3.4 关键参数调优心得代码跑起来了但效果好不好全看参数怎么调。这几个参数不是拍脑袋定的背后有很强的逻辑。学习率 α作用控制新旧知识的更新速度。可以理解为“有多相信这次的新经验”。调优经验初始值通常从0.1或0.2开始尝试。环境动态变化快、奖励噪声大可以设小一点如0.05环境稳定可以设大一点加速学习。衰减策略强烈建议使用衰减的学习率。早期需要快速学习后期需要微调稳定。可以用线性衰减或指数衰减。例如α α_init / (1 decay_rate * episode)。我在很多项目中会设置一个初始α如0.5然后每N个回合乘以一个衰减系数如0.995直到降到下限如0.001。迹象如果总奖励曲线震荡剧烈可能是α太大如果学习速度极其缓慢可能是α太小。折扣因子 γ作用决定智能体是“目光短浅”还是“深谋远虑”。调优经验接近10.99, 0.95适用于决策有长期影响、需要规划多步的场景比如围棋、资源管理游戏。在我们的网格世界里设为0.9或0.99是合理的因为需要为到达终点做多步规划。接近00.1, 0.5适用于即时奖励占主导、未来不确定性极高的场景或者回合很短的问题。一个技巧如果你发现智能体总是卡在局部最优比如在网格里绕圈可以尝试稍微提高γ让它更看重长远回报可能有助于跳出循环。探索率 ε作用平衡“利用已知好方法”和“探索新可能性”。调优经验初始值通常从0.1开始。对于完全未知的环境初期可以设高一点如0.3甚至0.5以充分探索。衰减策略衰减ε比衰减α更重要也更有必要。我们最终希望智能体收敛到一个确定性的最优策略。常用指数衰减ε max(ε_min, ε * decay_factor)。例如从0.5开始每回合乘以0.998最低降到0.01。SARSA的特殊性由于SARSA是同策略的ε直接影响它评估的策略。一个始终保持在0.1的ε意味着智能体永远会保留10%的随机探索它学到的策略也是包含这10%随机性的策略的价值这被称为ε-软策略。如果你想要一个完全确定的最优策略必须让ε衰减到0。Q表初始化零初始化最常见但对于某些有负奖励的环境可能导致智能体初期对探索“畏手畏脚”因为所有动作初始价值为0探索到负奖励会降低Q值让它更不愿尝试。可以尝试小的正数初始化如均匀分布[0, 0.01]这会提供一点“乐观主义”鼓励初期探索。针对陷阱在我们的例子里陷阱奖励是-10。如果Q表初始为0那么第一次掉入陷阱后对应状态动作的Q值会变成负数这能有效帮助智能体快速学会避开。这是一个好的设计。实操心得调参没有银弹。最好的方法是可视化。同时绘制总奖励曲线和每回合步数曲线。理想的曲线应该是奖励从低点或负值开始逐渐上升并最终稳定在一个较高的水平步数从很大的值开始逐渐下降并稳定在一个较小的值最优路径长度。如果曲线后期还在大幅震荡检查α和ε是否衰减到位。如果曲线很早就停滞在一个次优水平尝试增加初期ε或调整γ。4. 高级话题与性能优化当状态空间稍大一些比如10x10的网格Q表的大小就会变成100状态x 4动作 400。这还能应付。但如果状态空间是连续的如机器人关节角度或者是像围棋、电子游戏那样状态数天文数字用表格存储Q值就完全不可能了。这时就需要引入函数逼近。4.1 从表格到大脑引入函数逼近函数逼近的核心思想是用一个参数化的函数来近似表示Q值函数即Q(s, a; θ) ≈ Q(s, a)其中θ是函数的参数比如神经网络的权重。这样我们不再存储巨大的表格而是学习一组参数。最常用的方法就是深度Q网络的变体——深度SARSA。我们用一个神经网络来拟合Q函数。输入是状态s可能是图像、传感器数据等输出是每个动作a对应的Q值。深度SARSA的更新目标需要稍作修改。对于表格型SARSA目标是R γ * Q(s, a)。对于深度SARSA这个目标值在计算时a需要根据当前策略网络在状态s‘下的输出来选择例如ε-贪婪策略。然后我们用这个目标值去和网络对Q(s, a)的预测值计算损失如均方误差通过梯度下降来更新网络参数θ。# 伪代码示意展示与DQN的区别 # DQN (异策略) 目标 y R γ * max_a Q_target(s, a) # 用目标网络计算max # 深度SARSA (同策略) 目标 y R γ * Q_online(s, a) # a 由在线网络按策略选择 # 注意实际实现中为了稳定深度SARSA也常使用目标网络即 y R γ * Q_target(s, a)深度SARSA的实现要点经验回放同样可以使用这打破了序列的相关性提高了数据效率。目标网络也强烈建议使用。用一个更新较慢的目标网络来计算Q(s, a)可以稳定训练。但注意a的选择仍然需要根据在线策略网络或结合探索策略来决定然后从目标网络取出对应的Q值。策略的同步性这是深度SARSA最需要小心的地方。因为策略随着在线网络的更新而不断变化用于计算a的策略和用于计算Q(s, a)的目标网络所代表的策略可能存在不同步。这会在理论上引入一些偏差但在实践中如果目标网络更新得足够慢软更新或定期硬更新这个影响通常是可控的。4.2 收敛性分析与算法局限SARSA作为一个经典的时序差分学习算法在表格型情况下如果满足以下条件可以证明其收敛到最优的ε-软策略当ε衰减到0时收敛到最优策略所有状态-动作对被无限次访问。学习率α满足随机近似条件Σα ∞, Σα² ∞。简单说就是衰减但不能衰减得太快。策略是“温和”的如ε-贪婪保证持续的探索。SARSA的局限性保守性如前所述在同策略框架下它学习的是当前探索策略下的价值。在探索阶段ε较大时它可能会学到一条过于保守的路径。这既是缺点可能慢速收敛到最优也是优点学习过程更安全。对探索策略依赖大算法的性能与探索策略如ε的设置和衰减紧密耦合。一个糟糕的探索策略设计会导致学习失败。样本效率在表格型中它与Q-Learning相当。在函数逼近中由于同策略更新的特性其样本效率有时被认为略低于异策略的Q-Learning/DQN因为后者可以更自由地利用历史数据通过经验回放来学习最优策略而不受数据收集时策略的限制。5. 常见问题、调试技巧与实战案例在实际项目中应用SARSA你一定会遇到各种奇怪的现象。下面是我踩过的一些坑和总结的排查思路。5.1 问题排查清单现象可能原因排查与解决方法奖励不上升智能体摆烂1. 学习率α太大或太小。2. 探索率ε太高智能体一直在随机游走。3. 折扣因子γ太小智能体太“短视”看不到长远收益。4. 奖励设置不合理比如每步惩罚太大掩盖了最终奖励。1. 检查并调整α尝试加入衰减。2. 检查ε确保其有衰减机制并观察探索行为。3. 适当提高γ让智能体更有远见。4. 重新设计奖励函数确保目标导向明确。可以尝试稀疏奖励好奇心驱动等高级技巧。学习曲线震荡剧烈1. 学习率α过高。2. 环境本身随机性大或噪声强。3. ε值波动或衰减策略不合适。1. 降低α或使用自适应学习率方法。2. 考虑使用更平滑的更新方式如使用目标网络在深度SARSA中。3. 使用更平缓的ε衰减或尝试基于置信度的探索。收敛到次优策略1. ε衰减太快过早停止探索陷入局部最优。2. Q表初始化导致偏见如全零初始化在负奖励环境抑制探索。3. 状态表示不够好导致不同状态被错误地等价。1. 放缓ε衰减速度或使用衰减到一个小正数如0.01而非0。2. 尝试乐观初始化小的正数。3. 检查状态编码确保其能充分区分不同情境。在深度SARSA中训练不稳定1. 目标网络更新频率太快。2. 经验回放缓冲区大小或采样方式不当。3. 神经网络结构或优化器不合适。4. 梯度爆炸或消失。1. 增加目标网络更新周期如每1000步同步一次或使用软更新θ_target τ*θ_online (1-τ)*θ_target。2. 调整缓冲区大小确保其足够大且覆盖多样经验。尝试优先经验回放。3. 调整网络层数、节点数尝试不同的激活函数和优化器如Adam。4. 使用梯度裁剪、批归一化等技术。5.2 一个实战案例简易游戏AI假设我们要为一个“飞船避障”游戏写AI。飞船在一条横向卷轴的通道中飞行通道上下有随机生成的障碍物飞船可以执行“上”、“下”、“保持”三个动作。目标是尽可能飞得远而不撞上障碍。状态设计不能直接用原始像素。我们可以提取特征飞船与上方最近障碍物的垂直距离、与下方最近障碍物的垂直距离、障碍物的宽度和速度等。这样状态空间就从百万维的像素空间降到了几个有意义的维度。动作设计{上 下 保持}。奖励设计每存活一帧0.1。撞到障碍-10回合结束。成功通过一个难度区间5。为什么用SARSA在这个场景下安全性至关重要。一次撞击就导致游戏结束。Q-Learning可能会为了追求更高的长期奖励多通过几个区间而学习一些“铤而走险”的贴边飞行策略这在随机性强的环境中更容易失败。SARSA由于其同策略特性在学习过程中就会考虑到探索动作随机上下带来的风险从而更倾向于学习一条相对居中、安全的飞行路径。虽然最终路径可能不是理论上的“最快”但鲁棒性更高在实际游戏中的平均存活时间可能更长。实现要点使用深度SARSA因为状态是连续的距离是实数。网络输入层维度等于状态特征数输出层有3个节点对应三个动作的Q值。采用ε-贪婪策略ε从0.5指数衰减到0.02。使用经验回放和目标网络来稳定训练。每10000帧更新一次目标网络。在训练了几十万帧后你会发现这个AI学会了在通道中央“巡航”当障碍物靠近时会提前做出平稳的规避动作而不是在最后时刻猛拉方向。这种稳健的行为模式正是SARSA优势的体现。最后我想强调的是没有“最好”的算法只有“最合适”的算法。SARSA的“同策略”特性让它成为对安全性、在线学习稳定性有要求场景下的一个优秀选择。当你面临一个决策问题时不妨先问自己我的智能体在学习过程中“试错”的成本高不高如果高也许应该从SARSA开始你的尝试。希望这篇近万字的拆解能帮你不仅理解SARSA的代码更能理解它的灵魂并在合适的项目中让它为你所用。
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻