跳转至

代码实践

📖 阅读信息

阅读时间:21 分钟 | 中文字符:8475

目标

首先明确一下整个的流程

  • 强化学习最终的目的:

    • 对于某一特定的驾驶员,当其控制车辆的输入不变(电门开度,方向盘转角等不变)时,通过智能体学习得到的策略,控制

    • 通过控制以上策略中的其中几个(至少一个电机扭矩)

    • 达到——与未加入此策略相比,在尽量不影响车速、车辆运行轨迹等等(对比完全没有使用策略而言)的情况下,能耗减少,达到节能的目的

解释

  • 在同一段道路上,驾驶员的操作(电门开度、方向盘转角等)完全固定不变的前提下,让训练出的智能体基于你提供的「激进 / 温和 / 谨慎」驾驶风格策略(至少控制电机扭矩),在不改变车速、车辆运行轨迹等核心驾驶体验的前提下,降低车辆能耗,最终达成节能效果
  • 让智能体成为「驾驶员操作的 “节能优化层”」—— 驾驶员的输入决定了 “驾驶意图”(比如要加速、要直行),智能体在不违背这个意图(车速 / 轨迹不变)的前提下,优化扭矩、动能回收等策略细节,减少无效能耗。结合你提供的策略表(以「温和 / 经济型」为核心优化方向)

代码实现

简单轨迹 + 电机扭矩控制

先针对某一驾驶员简单的直线加减速情况下,通过智能体学习,扭矩控制实现轨迹、速度的跟踪与节能
总之核心目标有两个——节能和速度跟踪


有着如下的四个大的部分

受控对象的仿真

对应代码类: StraightRoadScenario

这部分是在构建一个虚拟的新能源汽车以及它行驶的道路环境 。为了让 RL 智能体学到有用的策略,仿真必须符合基本的车辆纵向动力学。

  • 纵向动力学模型:step_vehicle 方法中,代码计算了车辆的实时加速度。它考虑了电机输出扭矩转化来的驱动力,并扣除了线性空气阻力、二次空气阻力、坡度阻力以及弯道超速带来的额外阻力损耗。
    a = c_{t} \cdot T - c_{v1} \cdot v - c_{v2} \cdot v^{2} - c_{g} \cdot \theta - c_{c} \cdot v_{over}
    (其中 T 是扭矩,v 是车速,θ 是坡度)
  • 电机效率图 (Motor Map): 核心在 efficiency_load_motor_map_csv 方法。代码支持读取真实的电机 MAP 图(CSV 格式),通过双线性插值 (_interp_bilinear) 来查表获取当前转速和扭矩下的电机效率。如果没有 CSV,它会用一个基于二维高斯分布的解析函数来模拟电机的高效区(中等转速和扭矩区域)。
  • 电池能耗模型: 结合算出的机械功率和电机效率,计算电池的实际充放电功率 (batt_power),进而实时更新 SOC(荷电状态)。它还特别对动能回收 (regen_eff) 做了效率打折。

控制器接口与目标设定

也就是整个强化学习训练的环境是什么(环境的状态空间,智能体会做出的动作空间,对智能体的奖励机制)
对应代码类: DriverReferenceEnergyEnv

这是控制算法的舞台。它将物理仿真封装成 RL 智能体可以交互的马尔可夫决策过程(MDP)。

  • 状态空间 (State - 传感与预测数据):_encode_state 中,控制器不仅读取当前车速、SOC、扭矩,还会读取预测视界 (Preview Horizon) 内的道路坡度、限速,以及驾驶员未来的预期车速和扭矩 (ref_speed_p1, ref_torque_p1 等)。这相当于车辆配备了地形预测(如 eHorizon)和驾驶员意图识别。
  • 动作空间 (Action - 执行器干预): 智能体输出三个连续动作:
    1. residual扭矩修正值。智能体输出的不是绝对扭矩,而是叠加在驾驶员原始指令上的微调量(代码中有严格的限幅 residual_limit,保证驾驶权始终在人类手中)。
    2. regen_gain动能回收增益。控制松油门或刹车时的能量回收强度。
    3. coast_gain滑行增益。决定是否切断动力进行滑行。
  • 奖励函数 (Reward - 控制目标): 这是整个系统最复杂的地方(在 step 方法后半段)。它包含了:
    • 跟踪惩罚 (Track Penalty): 速度和距离偏离驾驶员意图越远,扣分越狠。
    • 能耗奖励 (Energy Term): 相比于纯人类驾驶基线,每节省一点能量就能获得奖励。
    • 平顺性惩罚 (Smooth Penalty): 惩罚扭矩的剧烈波动,保证 NVH(振动噪声)和驾驶平顺性。
    • 拉格朗日惩罚 (Lagrange Penalty): 对超越安全边界的行为施加硬约束惩罚。

智能决策大脑

对应代码类: ValueNet, PolicyNetContinuous, PPOContinuous

这是底层策略优化器。

  • Actor-Critic 架构: ValueNet(Critic)负责评估当前车辆状态的好坏(预估未来还能拿多少分);PolicyNetContinuous(Actor)负责输出动作的高斯分布(均值 μ 和标准差 σ)。
  • 安全探索: Actor 网络在输出时使用了 explore_decay,随着训练的进行,标准差会逐渐减小。这意味着控制器在初期会大胆尝试各种扭矩组合,后期则收敛到最优策略,减少随机动作。
  • PPO 更新逻辑:update 方法中,使用了 PPO 算法特有的裁剪机制(Clip)。这保证了控制器在更新策略时,新策略不会偏离老策略太远,防止因为一次糟糕的采样导致整个控制律崩溃。

说明

也就是神经网络和算法的方面的内容

标定与训练过程

对应代码片段: train_stage 函数及 if __name__ == "__main__": 的主流程。

让 AI 直接既兼顾安全又兼顾节能是不可能的,所以代码采用了课程学习(分阶段训练)

  1. Track 阶段 (跟随阶段): energy_weight=0.0。首先让控制器学会做个“老实人”,什么都不干,或者只做极小的微调,完全复刻人类驾驶员的速度和轨迹,确保绝对安全。
  2. Energy 阶段 (节能阶段): energy_weight 从 0.9 逐渐增加到 2.35。在保证跟随误差不越界的前提下,逼迫控制器去“抠”能量。比如在下坡前提前一点点松油门,或者在电机低效区主动调整扭矩。
  3. 自适应修复 (Repair/Rescue): 代码里有大段的 Rescue 和 Repair 逻辑。如果在节能阶段,控制器为了省电导致车速跟不上(偏离太大),系统会自动调低 energy_weight,退回到上一个安全的检查点重新训练。这非常符合工业界安全第一的标定思路。

说明

就是整个训练的流程

第二个流程的解释(整个马尔科夫过程)

智能体的输入(环境给智能体的“状态观察”)

在每次智能体需要做决定前,环境会调用 _encode_state 方法,将当前汽车的所有内外部信息打包成一个 24 维的浮点数向量(数组) 交给智能体。这相当于智能体的“眼睛”。

这个 24 维的输入被巧妙地归一化(取值尽量在 0 到 1 之间,方便神经网络学习),具体包含以下几大类信息:

  • 车辆当前自身状态(3 维):
    • 当前车速 (vehicle["speed"] / max_speed)
    • 当前电池剩余电量 SOC (vehicle["soc"] / max_soc)
    • 当前电机实际输出扭矩 (vehicle["torque"] / max_torque)
  • 人类驾驶员指令及跟踪误差(4 维):
    • 人类驾驶员在这一步期望的车速和期望扭矩。
    • 速度误差:当前实际车速减去人类期望车速(如果落后了是负数,超速了是正数)。
    • 距离误差:实际行驶距离减去人类期望行驶距离(用来判断有没有掉队)。
  • 道路环境“传感器”信息(3 维):
    • 当前的道路坡度(slope_profile,上坡还是下坡)。
    • 当前的弯道限速(curve_speed_limit)。
    • 当前的行驶进度(step_idx / horizon,告诉智能体这条路走到百分之几了)。
  • “天眼”预览信息(8 维):
    • 智能体可以“看”到未来第 1 步、第 3 步、第 6 步的人类期望车速和期望扭矩。
    • 还能看到未来第 1 步的速度和扭矩相较于当前的变化率(delta)。这相当于告诉智能体:“前面马上要减速了,你可以提前准备”。(实现了预测的功能)
  • 自身动作历史与上下文(6 维):
    • 上一步的扭矩修正值、上一步的动能回收增益、上一步的滑行增益(让智能体知道自己刚才干了什么,保持动作连贯)。
    • 人类驾驶风格的 One-hot 编码(代表 Eco 节能、Normal 普通、Sport 运动中的一种)。

环境接收到的输入(智能体的“动作”)

当智能体看完上述 24 维状态后,它的神经网络(PolicyNetContinuous)会输出一个 3 维的连续动作向量 给环境的 step(action) 方法。

这三个维度并不是直接去踩油门或刹车,而是对人类驾驶员指令的“微调系数”

  1. action[0] -> 扭矩修正值 (Residual Torque)

    • 智能体会输出一个调整量。如果人类想输出 50 Nm 的扭矩,智能体可能会输出 −5 Nm(也就是劝系统只输出 45 Nm 稍微省点电)。
    • 安全机制: 环境会对其进行极其严格的限幅(_compute_action_limits)。如果当前速度误差很小,允许的修正范围就大一点;如果车子已经快跟不上人类的意图了,环境会直接把智能体的修正权限收窄,甚至强制其修正值为正(帮着加速追赶)。
    • action[1] -> 动能回收增益 (Regen Gain)

    • 控制范围在 −1.0 到 1.0 之间,映射后决定动能回收的强度。如果系统判断人类在减速,智能体可以调高这个值,尽可能多地把动能转化为电能存入电池。

    • action[2] -> 滑行增益 (Coast Gain)

    • 控制是否允许车辆切断电机输出进入“滑行(Coasting)”状态。在下坡或准备减速的空旷地带,切断扭矩让车子自己滑行是非常省电的高级技巧。

解释

现在只有这三个方面的调整
必须在驾驶员操纵的范围内进行调整

环境的变化(物理引擎的步进)

环境接收到这 3 个动作后,在 step 方法中会经历一个复杂且严谨的融合过程,然后推进物理状态:

第一步:指令融合与底层保护

  • 环境首先拿出人类驾驶员的原始扭矩期望ref_torque)。
  • 叠加上智能体刚才给出的扭矩修正值residual)。
  • 底层兜底规则(Heuristics): 这是为了应对极端情况加的安全锁。如果车子严重掉队,环境会自己生成一个 drift_corr_torque (防掉队补偿) 和 bias_corr_torque (误差积分补偿) 强行加到扭矩里。如果检测到前方急减速,还会加入 preview_corr_torque
  • 合并以上所有项,得到最终要发给电机的指令 torque_cmd(并做物理极限截断,不能超过电机的最大扭矩)。

第二步:调用物理仿真更新状态

  • 环境带着 torque_cmd(最终扭矩)、regen_gain(动能回收倍率)和当前的 step_count(时间步),去调用底层的物理模型:self.scenario.step_vehicle(...)
  • 状态突变发生在这里:step_vehicle 里,物理引擎根据牛顿第二定律,算出身上的阻力、电机的驱动力,更新出下一时刻的新车速新行驶距离,并根据电机效率 Map 计算耗电量,更新电池剩余电量(SOC)
  • 时间推进:self.step_count += 1

第三步:计算“成绩单”(Reward 计算)

  • 环境拿到更新后的新状态,开始跟人类原本应有的状态做对比。
  • 计算跟踪惩罚(如果新车速和新距离偏离人类意图太多,扣大分)。
  • 计算节能奖励(如果耗电量比纯人类驾驶时少,且没有掉队,加分)。
  • 计算平顺度惩罚(如果智能体上一步和这一步给的修正值跳跃太大,扣分,因为这会让乘客晕车)。

第四步:返回下一轮循环

  • 根据新的 self.step_count,环境再次调用 _encode_state(idx),把变化后的新车速、新 SOC、新的误差等信息重新打包成 24 维向量。
  • [新状态, 这步的奖励分数 Reward, 是否结束当前回合 Done, 附加信息 Info] 返回给智能体。

自然语言描述学习的过程

在这个基于 PPO 算法的系统中,智能体并不是在开车的同时“实时”修改自己大脑里的规则的。相反,它的学习过程更像是一个“先闭卷考试,考完再集中复盘”**的过程。

在强化学习的术语里,跑完一次完整的路程叫做一个回合(Episode)。让我们用通俗的语言,模拟一下智能体跑完“一次路程”并从中学习的完整生命周期 :

第一阶段:发车前的准备(环境重置)

当一次新的路程开始时,系统会重置车辆状态(比如车速归零或设为初始速度、电池充满、把它放在路的起点)。

此时,智能体(这个 AI 副驾)睁开眼睛,环顾四周,它收到了一份“24 维的当前路况简报”。这份简报里写着:我们当前车速是多少、电池还剩多少、前面有没有下坡、前面那个“人类老司机”打算踩多深的油门。

第二阶段:行驶中的“盲测”与记忆(数据收集)

车子启动了。在接下来的 220 个时间步(Horizon)里,智能体必须连续不断地做出决策:

  1. 大脑直觉输出(Actor 网络): 智能体的策略网络根据当前路况,凭着它现有的经验,给出一个微调建议,比如“人类想输出 50Nm 扭矩,我建议减小 5Nm,并且稍微带点动能回收”。
  2. 加入“好奇心”扰动(Exploration): 为了发现更好的省电技巧,AI 不会每次都死板地输出一样的建议。代码中有一个 explore_decay 参数,系统会在 AI 的建议上加上一点点随机的“高斯噪声”(试探性的盲盒动作)。有时候它会不小心多减了 2Nm,有时候会少减 1Nm。
  3. 物理世界反馈(Environment Step): 车子按照最终的指令往前开了一小段。系统充当了严厉的裁判,根据这一步的表现给出一个即时奖励(Reward)。如果车子没跟上人类的速度,裁判大喊“扣 5 分!”;如果这一步因为下坡滑行省了电,裁判说“加 2 分!”。
  4. 拿小本本记下来(Transition Dict): 智能体会把刚才发生的一切默默记在脑子里:“我在状态 A 下,做了动作 B,裁判给了我分数 R,并且车子开到了状态 C”。

注意:在这个行驶过程中,智能体的大脑(神经网络权重)是锁死的,它只是在拼命地体验和记录,收集“错题本”和“优秀日记”。

第三阶段:到达终点或半路淘汰(回合结束)

车子开到了终点(跑完了设定的时间步),或者因为 AI 乱指挥导致车速偏离人类意图太远,触发了安全底线(Fail Tolerance),系统直接强制结束这次路程并给出巨额惩罚(扣 120 分)。

此时,智能体的小本本里已经密密麻麻记录了这一路上几百次的“状态 - 动作 - 奖励”数据。

第四阶段:赛后闭门复盘(策略更新)

这才是真正发生“学习(Learning)”的时刻。智能体拿着记录本,开始在 agent.update() 方法中进行深度反思:

  1. “神算子”预测对比(Critic 评估): 智能体大脑里的价值网络(Critic)会回放刚才的录像。它会逐帧分析:“在第 50 秒那个下坡前,我原本以为这局只能拿 10 分。但实际上因为当时我偷偷试探性地加大了动能回收(加入了随机噪声的那个动作),最后总成绩比预期多拿了 3 分!”。这多出来的 3 分,就是所谓的优势(Advantage)
  2. “打工人”行为矫正(Actor 更新): 策略网络(Actor)听到 Critic 的汇报后,恍然大悟。对于那些产生了“正优势(比预期好)”的试探性动作,Actor 会修改自己的神经元连接,提高下次在同样路况下做出这个动作的概率;反之,对于那些导致掉队扣分的“负优势”动作,它会降低做这些动作的概率。
  3. PPO 的安全锁(Clip 机制): 智能体在修改大脑时非常谨慎。它遵循 PPO 算法的死规定:每次复盘,行为习惯的改变幅度不能超过 20%(eps = 0.2。绝不能因为一次偶然的成功就完全推翻过去的驾驶习惯,防止“走火入魔”。

总结

这就是智能体在“一次路程”前后的学习全过程。当这套流程在不同的虚拟道路、不同的人类驾驶风格下循环往复执行成千上万次(比如代码里的几百个 Episodes)后,那个原本靠“随机乱猜”的 AI,就会被一点点打磨成一个能精准预判路况、毫厘不差地微调扭矩的“节能大师”。

当前阶段还存在的一些问题

结合刚才我们探讨的“上帝视角”和预测偏差问题,再以汽车电子与控制系统开发的工业标准来审视这段代码,确实还有几个非常关键的优化空间。

虽然这段代码在 PPO 算法实现和环境封装上已经相当完备,但如果目标是将其打造成可以直接在真实的整车控制器(VCU)上运行的策略,以下几个方面是亟待改进的:

1. 将“开环驾驶员模型”升级为“闭环驾驶员模型”

这是目前物理仿真环境中最大的脱节之处。

  • 现状: 代码在 simulate_open_loop 中,让系统完全按照预先录制好的绝对扭矩指令(torque_profile)跑了一圈。在 AI 训练时,无论 AI 怎么微调扭矩导致车速发生变化,人类驾驶员的“历史参考扭矩”依然是死板地照本宣科。
  • 问题: 在现实中,人类驾驶员是一个闭环反馈控制器 (Closed-loop Controller) 。如果 AI 为了省电偷偷减小了 5Nm 的扭矩,导致车速下降,人类驾驶员肯定会感觉到,并本能地把油门踩得更深来补偿。
  • 改进方案: 应该在 step_vehicle 环境中内置一个简单的 PID 驾驶员模型。在每一个仿真步中,PID 驾驶员根据期望车速和当前实际车速计算出动态的踩踏扭矩,AI 再在此基础上输出 residual 进行微调。这样不仅更符合人类行为,也能让 AI 学会如何与人类“博弈”。

2. 引入领域随机化 (Domain Randomization) 应对预测不确定性

刚才我们提到了“天眼”信息在实车上是靠 ADAS 或前馈算法推测出来的,必然带有误差。

  • 现状: 智能体在 _encode_state 中获取的 ref_speed_p1ref_torque_p1 等预览数据是 100% 精准的全局绝对真实值。
  • 改进方案: 在打包这些预测状态输入神经网络之前,人为注入高斯噪声(Gaussian Noise)或有色噪声。 s_{predict} = s_{true} + \mathcal{N}(0, \sigma^2) 通过这种方式,迫使神经网络学习到更加鲁棒的策略,不去过度依赖单一时刻的预测值,从而直接缩小 Sim-to-Real(仿真到现实)的鸿沟。

3. 强化 NVH 考量与更真实的执行器延迟

在新能源汽车技术中,扭矩的频繁波动不仅影响驾驶平顺性,更会激发传动系的扭转共振,带来严重的 NVH(噪声、振动与声振粗糙度)问题。

  • 现状: 代码确实考虑了平顺性,在奖励函数中写了 smooth_penalty = smooth_coeff * abs(residual - prev_residual) 来惩罚相邻两步修正值的跳变,并在执行器层面用了一阶惯性环节模拟响应延迟 (alpha = self.dt / (self.torque_time_const + self.dt))。
  • 改进方案: 1. 通信与机械纯延时: 真实的 CAN 总线通信和电机逆变器响应存在纯延时(Time Delay)。可以在状态更新中加入几步的滞后队列,让控制对象的动力学更贴近真实物理台架。
    1. 更科学的 NVH 惩罚: 仅仅惩罚一阶差分(Jerk)是不够的,代码末尾虽然计算了 torque_jerk_mean,但并未将其纳入深度强化学习的闭环约束。可以针对特定频段的扭矩波动施加基于频谱的惩罚,避免 AI 激发出引起车辆顿挫的特定频率。

4. 奖励函数 (Reward Shaping) 的解耦与精简

  • 现状: 代码里的奖励函数极其庞大,包含了跟踪惩罚、总能耗奖励、平顺惩罚、拉格朗日约束、电机效率 Map 引导惩罚 (efficiency_term) 等十几项。这种把所有目标揉成一个标量标量奖励(Scalar Reward)的做法,在工程调参时是一场噩梦。
  • 改进方案: 使用受限马尔可夫决策过程(CMDP)。代码里已经初步引入了拉格朗日乘子法来控制速度和距离越界 (lagrange_penalty = self.lambda_speed * (speed_violation**2) ...),可以将这种硬约束思想贯彻到底。将“平顺性”、“NVH 要求”、“不掉队”全部设为 CMDP 的硬约束,让 PPO 唯一的目标就是“最大化省电”,把多目标优化问题解耦,这样模型收敛会快得多。

第一版

几个文件的说明
  1. motor_env.py
    • 定义环境动力学
    • 定义状态
    • 定义动作怎么作用到车辆
    • 定义 reward 怎么算
    • 所以 r 的来源在这里
  2. motor_training.py
    • 从环境里拿到 reward
    • 收集轨迹
    • 调 agent.update(...)
    • 做阶段训练、阶段内评估、early stop、checkpoint 维护
  3. motor_agent.py
    • 定义神经网络结构
    • 定义 PPO 更新公式
    • 真正的网络参数更新在 PPOContinuous.update
  4. motor_experiment.py
    • 决定跑哪种风格
    • 决定 seed、回合数、训练阶段参数
    • 决定多 seed 之间怎么选最终模型
    • 决定保存到哪里

说明

说明

现在的目的是什么,就是让智能体能够微调电机的扭矩等等,使得工作更加高效
所以怎么定奖励呢,就是行驶单位距离纯电机的节省能量低即可(即时的奖励)
微调,而且不能使得速度、位移的偏差过大(不能拆东墙补西墙)

现在我的环境的奖励机制中加入了

  • 非对称的能耗奖励惩罚:耗能的惩罚比节能的奖励大很多
  • 在 map 图上利用高校区的奖励
  • 跟踪的奖励(\(\mathrm{Penalty}_{bias}=-w\cdot\left(v_{agent}-v_{ref}\right)^2\))使用平方项,增大大偏差的惩罚,减小小偏差的
    新加了使用排除了风阻、动能影响的作为实际的节能
    剩下的就没有了,还引入了实际的效率图

关于 git 的一些知识

首先

我们可以通过 github 上面的 history 实现查看历史的能力

提示词

你现在是一个资深强化学习代码工程师。请帮我生成一个与我现有项目风格高度一致的 Python 代码工程,要求“框架、模块划分、函数职责、训练流程、评估输出、命名习惯”都尽量接近下面这套现有代码,但不要照抄原文件内容,要生成一套新的、可运行的实现。

目标任务:
构建一个基于 PPO 的连续动作强化学习训练系统,用于“参考驾驶员轨迹上的能量优化控制”。智能体不是直接从零开车,而是在参考控制基础上做连续微调,并支持多风格(eco / normal / sport)训练与评估。

请严格按以下工程结构和设计思想生成代码:

一、项目文件结构
请生成以下文件,并保持模块职责清晰:
1. motor_env.py
2. motor_agent.py
3. motor_training.py
4. motor_experiment.py
5. motor_plotting.py
6. run_motor_ppo.py

二、总体架构要求
1. 环境负责:
- 定义车辆状态、参考轨迹、道路信息、效率图查询
- step/reset 接口 #就是状态更新的接口
- 计算 reward
- 计算 constraint costs(约束成本,通过拉格朗日算法转化为惩罚项,包括速度、距离等等约束惩罚)
- 输出详细 info 字典
- 支持 track / energy 两种 mode(先进行 track,之后进行 energy)
- 支持 eco / normal / sport 三种风格(这三种风格使用代码生成)

  1. 智能体负责:
  2. 连续动作 PPO
  3. Squashed Gaussian policy(压缩高斯策略,在连续动作空间的强化学习中常用的策略,输出连续有界的动作)(通过高斯分布,前期的方差大,鼓励探索;后期策略趋于确定)
  4. actor / critic
  5. 多约束 cost critics(对应环境中的 costs,同样像主奖励一样有策略网络。惩罚项超出阈值的话,就增加其对应的 \(\lambda\),使用更新后的 \(\lambda\) 计算加权的约束惩罚,计入总损失)
  6. PPO update
  7. 自适应 entropy / exploration(使用熵得到策略的随机性,能够自己调节和探索,和上面的压缩高斯策略是比较像的)
  8. 支持动作原始值和执行值区分(能够正确的计算梯度,又能够实现安全隔离)

  9. 训练模块负责:

  10. 单阶段训练 train_stage(...)
  11. 多阶段训练流程衔接
  12. rollout/eval 汇总(rollout:收获交互的数据)(eval:评估当前模型的性能)
  13. Lagrangian multiplier 更新(对应上面两个的 cost 的计算部分,计算是否超过阈值,更新其权重,在更新之后应用惩罚,实现解耦与自适应安全)
  14. checkpoint 保存和恢复(保存训练中一些关键的参数等等,并且实现可以恢复,选择最佳的模型)
  15. 输出评估指标

  16. 实验模块负责:

  17. 构造训练道路 / 评估道路 / stress test 道路
  18. 多 seed 训练
  19. style-specific 配置(不同的风格不一样)
  20. 选择 best seed
  21. 导出图像、json、模型文件

三、环境设计要求
1. 动作空间为 3 维连续动作:
- residual torque adjustment
- regen gain
- coast gain

  1. 观测包含:
  2. 当前车辆状态(速度、SOC、实际扭矩等)
  3. 当前参考值(参考速度、参考距离、参考扭矩)
  4. 误差量(速度误差、距离误差)
  5. 历史动作
  6. 预瞄信息
  7. 驾驶风格 one-hot
  8. 动作语义提示(如 residual scale hint、regen/coast center/range 等)(就是告诉智能体现在可以做哪些动作,范围是什么:比如跟踪良好时,允许扭矩有较大的修正)

  9. 环境需支持:

  10. 随机生成道路
  11. 道路由坡度分布 + 弯道限速分布组成
  12. 训练集、评估集、stress 集可分开生成

四、奖励与约束设计
请按“当前项目风格”生成,而不是简单单一 reward。

  1. track 模式 reward:
  2. 以速度误差、距离误差、平滑惩罚为主
  3. 有 alive reward
  4. 小误差下有 bonus

  5. energy 模式 reward:

  6. 以分段/窗口节能奖励为主
  7. 支持 rolling window saving
  8. 支持窗口正奖励、负惩罚、负 streak 惩罚
  9. 支持效率相关奖励(例如 agent_eta 与 ref_eta 的差值,或 torque efficiency gain)
  10. 支持 coast / regen 的合理奖励
  11. reward 不要只看原始能耗,要支持 accounted energy 概念

  12. constraint costs:
    至少包含:

  13. speed
  14. distance
  15. smoothness
  16. projection(预测约束,基于当前的状态预测未来 N 步的约束违反风险)
  17. window_energy(窗口的耗能的约束成本,防止智能体为了长期的节能而短期的大量耗能)

五、训练流程要求
请实现“分阶段训练”:
1. track 阶段:
- 先训练稳定跟踪
- 强调可驾驶性和误差收敛

  1. energy 阶段:
  2. 逐步拉高 energy_weight
  3. 使用 PPO + 多约束 Lagrangian
  4. 支持 integral / PID 两种 multiplier 更新方式,但默认 integral(就是拉格朗日算子的两种更新方式,用于动态调整约束惩罚的强度)检测到严重违反约束,可以临时切换到 PID 模式快速响应

  5. polish(在主训练完成之后,对策略进行精细化的额外训练的阶段) / bias repair(偏差修复) / save guard(安全防护):

  6. 允许在实验模块中再设计若干可选精修阶段
  7. 但主流程应保持清晰

六、评估指标要求
请实现详细评估,至少输出:
- saving_total_pct
- saving_epd_pct
- saving_net_epd_pct
- saving_isochronous_pct
- saving_net_isochronous_pct
- low_speed_benefit_pct
- speed_mae
- dist_mae
- speed_rel_bias_pct
- dist_rel_bias_pct
- front_half_saving_pct
- back_half_saving_pct
- front_back_saving_gap_pct
- worst_window_saving_pct
- negative_window_ratio
- worst_segment_speed_mae
- worst_segment_dist_mae
- tracking_ok
- smooth_ok
- bias_guard_ok

七、代码风格要求
1. 函数命名、模块划分、参数风格尽量接近工程型 RL 项目,不要写成教学 demo
2. 每个函数都要有简短中文 docstring,说明作用、输入、输出
3. 代码应偏“工程可调参风格”,配置项尽量显式
4. 输出的 info / meta / plot 数据字段要尽量丰富
5. 变量名尽量与下列风格一致:
- saving_isochronous_pct
- constraint_window_energy_cost
- segment_speed_constraint_cost
- effective_energy_weight
- style_energy_scale
- bias_guard_speed_floor
- worst_style_saving_isochronous_pct
- robust_saving_joint_pct

八、绘图要求
请在 plotting 模块中生成以下图:
1. 跟踪与能耗总图
2. 轨迹图
3. energy saving effect 图
4. saving trace 图
5. 10-step / rolling window saving 图

图中应突出:
- Pure Control Saving
- Raw Saving
- Low-speed Benefit

九、输出要求
请直接给出完整代码,不要只讲思路。
生成时按文件分别输出,每个文件单独一个代码块,并标注文件名。(注释清晰)
代码要尽量能直接运行。
可以输出帮助阅读的中文文档.md

十、能耗的计算:
使用距离步长作为计算能耗的依据:
使用这段单位距离内的能量除以距离

\[ e_k=\frac{\Delta E_k}{\Delta s_k} \]

原始驾驶员操作跑一遍,得到同一路段下每个距离单元的基准能耗:\(e_k^\mathrm{ref}=\frac{\Delta E_k^\mathrm{ref}}{\Delta s}\)
则这一段的节能率为:

\[ \rho_k=\frac{e_k^\mathrm{ref}-e_k^\mathrm{agent}}{e_k^\mathrm{ref}+\varepsilon} \]

这里的能量可以直接减去风阻、加速带来的影响,则第 k 段的能耗定义为:

\[ E_k^\mathrm{res}=\sum_{j\in k}\frac{\Delta t_j}{3600}\cdot\frac{u_j}{3600\eta_T}\left(Gf\cos\alpha_j+G\sin\alpha_j\right) \]

或者在已知电池输出功率的情况下:

\[ E_k^{\mathrm{cmp}}=E_k^{\mathrm{bat}}-E_{\mathrm{air},k}-E_{\mathrm{acc},k} \]
\[ E_k^*=\sum_{j\in k}\left(P_{\mathrm{bat},j}-\frac{1}{2}\rho C_dAv_j^3-\delta ma_jv_j\right)\Delta t_j \]

也就是原始的能量减去风阻和加速阻力这些可以通过降速减少的东西


再考虑惩罚项:

\[ r_k=w_e\cdot\rho_k-w_v\left(\frac{u_k-u_k^{\mathrm{ref}}}{u_{\mathrm{tol}}}\right)^2-w_t\left(\frac{\Delta t_k-\Delta t_k^{\mathrm{ref}}}{\Delta t_k^{\mathrm{ref}}+\varepsilon}\right)^2-w_a\left(\frac{a_k-a_k^{\mathrm{ref}}}{a_{\mathrm{tol}}}\right)^2 \]

最后再加上一个终端的奖励:就是全局的、行驶同样的距离、最终的速度相同时的能量节能率(可以直接通过 SOC);再减去时间上的差别:

\[ R_{\mathrm{terminal}}=w_E\cdot\frac{E_{\mathrm{tot}}^{\mathrm{ref}}-E_{\mathrm{tot}}^{\mathrm{agent}}}{E_{\mathrm{tot}}^{\mathrm{ref}}+\varepsilon}-w_T\left(\frac{T_{\mathrm{agent}}-T_{\mathrm{ref}}}{T_{\mathrm{ref}}}\right)^2 \]

第二版

新加的一些东西

首先就是奖励函数的设置:

  • 如果跟踪不合格的话:

    \[ r_k^{track}=8\left(\frac{|v_k-v_k^{ref}|}{v_{tal}}\right)^2+4\left(\frac{|s_k-s_k^{ref}|}{s_{tal}}\right)^2+2\left(\frac{|a_k-a_k^{ref}|}{a_{tal}}\right)^2 \]

    没有奖励,全部是上述的跟踪惩罚
    - 跟踪合格的话:

    $$
    r_k=-w_c\Delta e_k^{loss}-w_u\left(\frac{\Delta T_k}{\Delta T_{max}}\right)^2
    $$
    节能部分,\(r_k=-w_c\Delta e_k^{loss}-w_u\left(\frac{\Delta T_k}{\Delta T_{max}}\right)^2\)

道路部分

  • 添加 traffic_density 或 speed_reduction 数组模拟车流导致的限速下降;
  • 插入离散事件数组(stops, signals, junctions)并在 generate_reference_trajectory 中考虑这些事件;
  • 带时间的交通模型(随时间变化的速度限制)用于模拟动态拥堵。

电机部分

平顺性是怎么计算的?
但是滚动阻力不是和地面附着系数相关吗?

  • 还有就是扭矩剪切的时候能不能和误差相关:
    当前代码把 motor_torque 限幅为绝对物理极限并与 prev_torque 做平滑。若希望限制 agent 的“修正幅度”以与当前跟踪误差挂钩,可以采用动态限制策略,例如把允许的残差尺度按误差放大或缩小:
    • 例子公式:allowed_scale = base_scale * clip(1 + k * (speed_err / speed_tol), min_scale, max_scale);然后 residual_torque = action[0] * allowed_scale。这样当速度误差大时允许更大的修正(更积极纠偏),误差小时则限制突变以保持平顺。
    • 另一种:把 motor_torque 剪切到 [ref_torque - Δ, ref_torque + Δ],其中 Δ = base_scale * (1 + k * error_norm)。

现在的物理建模

电机转速:
$$
\omega_m = \frac{v}{R_w} i_g,\qquad
n_m = \omega_m \frac{60}{2\pi}
$$

恒扭矩/恒功率限幅:

\[ T_{\max}(n)= \begin{cases} T_{\max}, & n \le n_b\\ \min\left(T_{\max}, \frac{P_{\max}}{\omega_m}\right), & n>n_b \end{cases} \]
\[ T_{\regen,\max}(n)= \begin{cases} T_{\regen,\max}, & n \le n_b\\ \min\left(T_{\regen,\max}, \frac{P_{\regen,\max}}{\omega_m}\right), & n>n_b \end{cases} \]
\[ T_k=\mathrm{clip}\!\left(T_k^{cmd},-T_{\regen,\max}(n_k),T_{\max}(n_k)\right) \]

纵向动力学:(这里的步长是在单位距离的基础上更新的)

\[ \begin{gathered}F_{\mathrm{roll}}=mgC_{\pi}\cos\theta\\F_{\mathrm{grade}}=mg\sin\theta\\F_{\mathrm{air}}=\frac{1}{2}\rho C_{d}Av^{2}\\F_{\mathrm{drive}}=\frac{T_ki_g\eta_d}{R_w}\\F_{\mathrm{net}}=F_{\mathrm{drive}}-F_{\mathrm{roll}}-F_{\mathrm{grade}}-F_{\mathrm{air}}\\a_k=\frac{F_{\mathbf{net}}}{\delta m}\\v_{k+1}=\sqrt{\max\left(v_k^2+2a_k\Delta s,0.25\right)}\\\mathrm{v}\mathrm{avg},k=\max\left(\frac{v_{k}+v_{k+1}}{2},0.5\right),\quad\Delta t_{k}=\frac{\Delta s}{v_{\mathrm{avg},k}}\end{gathered} \]

电池功率:
$$
P_{m,k}=T_k \omega_{\mathrm{avg},k}
$$
$$
P_{\mathrm{bat},k}=
\begin{cases}
\dfrac{P_{m,k}}{\eta_k}+P_{\mathrm{aux}}, & P_{m,k}\ge 0\
P_{m,k}\eta_k+P_{\mathrm{aux}}, & P_{m,k}<0
\end{cases}
$$
这里的 \(\eta_k\) 仍然来自效率图:(aux 为辅助系统的功率)
$$
\eta_k=\eta(T_k,n_{\mathrm{avg},k})
$$

单步电池能耗:
$$
E_{\mathrm{bat},k} = P_{\mathrm{bat},k}\frac{\Delta t_k}{3600}
$$

SOC 更新:
$$
SOC_{k+1} = \mathrm{clip}\left(SOC_k-\frac{E_{\mathrm{bat},k}}{C_{\mathrm{bat}}},\ SOC_{\min},\ SOC_{\max}\right)
$$

奖励里用的“损失能量”口径仍然是:
$$
F_{\mathrm{wheel},k} =
\delta m a_k + mg C_{rr}\cos\theta_k + mg\sin\theta_k + \tfrac12 \rho C_d A v_{k+1}^2
$$
$$
c_{\mathrm{loss},k}=
\begin{cases}
\dfrac{1}{\eta_k}-1, & F_{\mathrm{wheel},k}\ge 0\
1-\eta_k, & F_{\mathrm{wheel},k}<0
\end{cases}
$$
$$
E_{\mathrm{loss},k}=|F_{\mathrm{wheel},k}|\,c_{\mathrm{loss},k}\frac{\Delta s}{3600}
$$

如果当前步跟踪合格,能量奖励主项是:
$$
r_k = -w_c\left(\frac{E_{\mathrm{loss},k}^{agent}}{\Delta s}-\frac{E_{\mathrm{loss},k}^{ref}}{\Delta s}\right)
-0.08\left(\frac{\Delta T_k}{\Delta T_{\max}}\right)^2
$$

如果你愿意,我下一步建议直接做一件事:把参考轨迹也改成“用这套限功率动力学反推生成”,这样新 plant 和参考就真正一致了,tracking_ok 大概率会回到正常。


物理定义: 这是把车辆物理上不可避免的做功(如势能 \(mgh\) 的改变、加速所需的初动能 \(\frac{1}{2}mv^2\))全部剔除在外,纯粹只统计“这套电驱系统白白浪费成了热能”的那部分能量。

数学计算公式(对应您代码里的

compute_loss_energy
):

首先计算车辆要达到目标的加速度和克服环境阻力,理论上需要的纯轮端机械推力 \(F_{wheel}\): $$ F_{wheel} = m_{eq}a + mg \cos\theta \cdot f_r + mg \sin\theta + \frac{1}{2}\rho C_d A v^2 $$ 这段 \(\Delta s\) 距离内的纯物理机械做功为:\(W_{mech} = F_{wheel} \cdot \Delta s\)

然后算出被浪费/折损掉的纯发热能量 \(E_{loss}\)

当处于驱动/牵引状态 ( \(F_{wheel} \ge 0\) ) 时: 损耗等于“电池流出的电”减去“实际用在车轮上的机械功”。 $$ E_{loss} = E_{bat} - W_{mech} = \frac{W_{mech}}{\eta} - W_{mech} = W_{mech} \cdot \left(\frac{1}{\eta} - 1\right) $$
当处于制动/回收状态 ( \(F_{wheel} < 0\) ) 时: 损耗等于“车轮原本能提供的回收潜能”减去“实际充入电池的电”。

\[ E_{loss} = |W_{mech}| - |E_{batin}| = |W_{mech}| - |W_{mech}| \cdot \eta = |W_{mech}| \cdot (1 - \eta) \]

以上就是现在的每一步的对能量的奖励——只针对能量的损失的方面


P0 先做
这 3 项最值得先改,顺序不要乱。

  1. 统一参考轨迹和智能体的动力学口径
    文件: motor_env.py
    现在参考轨迹还是“先定速度,再反推扭矩”,智能体却已经用新 plant 在跑。要改成:
  2. 参考驾驶员先输出“目标扭矩/目标加速度”
  3. 再调用同一个 step_longitudinal_dynamics() 滚出 ref_speed/ref_torque/ref_energy
  4. 参考和智能体都走同一套限功率、SOC、电池功率、传动效率

预期效果:
- tracking_ok 更真实
- 节能比较才公平
- 现在“智能体天然吃亏”的问题会先消掉

  1. 把奖励从硬门控改成连续门控
    文件: motor_env.py
    现在跟踪一旦不过线,节能奖励直接没了。建议改成:
    [
    r = -L_{track} + g_{track}\cdot r_{energy}
    ]
    其中
    [
    g_{track}=\exp(-\alpha_v e_v^2-\alpha_s e_s^2-\alpha_a e_a^2)
    ]

预期效果:
- 训练早期也能收到节能信号
- 不会一直只学“保命跟踪”
- PPO 会更容易收敛到“又能跟踪又能省”

  1. 改 best checkpoint 选择规则
    文件: motor_training.py
    现在更像在选“别太极端”的模型,不是在选“最省电”的模型。建议改成:
  2. 先筛 tracking_ok=True
  3. 再筛 bias_guard_ok=True
  4. 在剩下模型里按 saving_total_pct 最大选
  5. 作为辅条件要求 saving_cmp_total_pct > 0

预期效果:
- 最终保存的模型和你的真实目标一致
- 不会再出现“训练里有省电 checkpoint,但最后没选到”

P1 接着做
这两项会明显提高“学到真正节能策略”的概率。

  1. 重做动作空间
    文件: motor_env.py
    现在动作只是参考扭矩的小残差,太容易“修坏参考”。建议二选一:
  2. 保守版: 保留残差,但做课程学习,逐步放大动作范围
  3. 更稳版: 动作改成“目标轮端力修正”或“目标加速度修正”

  4. 奖励分成“局部可学”和“最终目标”两层
    文件: motor_env.py motor_env.py
    建议:

  5. 单步奖励继续用 loss_energy
  6. 终局奖励改成 battery energy + 时间偏差 + 跟踪误差
    这样局部稳定、全局目标也不丢。

P2 诊断增强
这一步不是改模型,是让我们知道它为什么失败。

  1. 评估时固定输出 4 类诊断
    文件: motor_env.py motor_plotting.py
  2. 牵引段 / 回收段分开统计
  3. 按速度区间统计节能率
  4. 按扭矩区间统计节能率
  5. 画 ref/agent 在效率图上的落点热图

这样能直接回答:
- 是高速区差
- 还是上坡差
- 还是回收差
- 还是一直跑在低效率岛外

推荐执行顺序
1. 统一参考轨迹动力学
2. 连续门控奖励
3. 改 checkpoint 选择
4. 再调动作空间
5. 最后再调权重

动作的更新

每一距离步里,速度、位移和能耗的更新顺序现在是这样的,入口在 motor_env.py:474
1. 先把动作变成残差扭矩,再和参考扭矩合成当前步的扭矩命令,在 motor_env.py:477motor_env.py:483
2. 把这个扭矩命令送进同一个动力学步进器 motor_env.py:485
3. 在步进器里,先用当前车速算电机转速,再做恒扭矩/恒功率限幅,在 motor_env.py:228motor_env.py:229
4. 然后算滚阻、坡阻、风阻、驱动力和合力,再得到加速度,在 motor_env.py:231motor_env.py:237
5. 再按距离域公式更新速度:
[
v_{k+1}=\sqrt{\max(v_k^2+2a_k\Delta s,\ 0.25)}
]
motor_env.py:238
6. 用新旧速度平均值得到本步时间:
[
v_{\text{avg},k}=\frac{v_k+v_{k+1}}{2},\qquad \Delta t_k=\frac{\Delta s}{v_{\text{avg},k}}
]
motor_env.py:240motor_env.py:241
7. 用平均转速查效率图,算电池功率和单步电能,在 motor_env.py:243motor_env.py:248
8. 同时算奖励口径里的损失能量 cmp_energy_step,在 motor_env.py:249
9. 最后回到环境里累计:
[
s_{k+1}=s_k+v_{\text{avg},k}\Delta t_k
]
[
E_{k+1}=E_k+E_{\text{step},k}
]
[
SOC_{k+1}=SOC_k-\frac{E_{\text{step},k}}{C_{\text{bat}}}
]
对应 motor_env.py:513motor_env.py:518

你问的连续门控公式
[
g_{\text{track}}=\exp(-\alpha_v e_v^2-\alpha_s e_s^2-\alpha_a e_a^2)
]
意思其实很简单:它是一个 0~1 之间的“跟踪可信度系数”。

  • e_v,e_s,e_a 分别是速度、位移、加速度误差,通常会先做归一化。
  • \alpha_v,\alpha_s,\alpha_a 是权重,谁更重要就把谁调大。
  • 如果误差都很小,指数里的值接近 0,那么
    [
    g_{\text{track}}\approx 1
    ]
    这时节能奖励几乎全额生效。
  • 如果误差变大,指数项会越来越负,那么
    [
    g_{\text{track}}\to 0
    ]
    这时节能奖励会被连续压小,但不会像现在这种硬门控一样直接“啪”地归零。

所以它本质上是在做一件事:把“先跟踪、再节能”从硬切换改成软切换。
不是“过线就有节能奖励,不过线就完全没有”,而是“误差越大,节能奖励越弱”。

步骤
  1. 站在起点,下达指令 (At the start of \(\Delta s\) )
    在当前点(比如第 \(k\) 步),车辆有一个初始速度 \(v_k\)。 此时,环境接收来自策略(Agent)给出的新扭矩命令。这个扭矩命令是准备在接下来的这段 \(\Delta s\) 距离内去执行的,而不是走完再给。

  2. 计算 \(\Delta s\) 过程内的受力与加速度
    拿到扭矩命令后,环境立刻计算各种阻力(这里的风阻是利用刚才的起点速度 \(v_k\) 算出的)。 用电机的推力减去所有阻力,得到一个净合力(f_net),除以等效质量,得到了一个加速度 \(a_k\)。系统假设在接下来的这段 \(\Delta s\) 距离里,这个加速度 \(a_k\) 是保持恒定不变的。

  3. 推演 \(\Delta s\) 结束时的新状态
    既然加速度恒定,车就在这段 \(\Delta s\) 里进行匀加速/匀减速运动。 利用物理学公式 \(v_{end}^2 - v_{start}^2 = 2 a s\),算出走完这段 \(\Delta s\) 时的末端新速度 \(v_{k+1}\)(也就是代码里的 v_new)。 所以:车并不是“保持”某个速度走 \(\Delta s\),而是速度在走的过程中一直在平滑变化。

  4. 计算走完这段 \(\Delta s\) 花了多久
    起点速度是 \(v_k\),终点速度是 \(v_{k+1}\),匀加速运动的平均速度就是两者的平均值:\(v_{avg} = (v_k + v_{k+1}) / 2\)。 用这段距离除以平均速度,就算出了经过这段 \(\Delta s\) 所花费的时间 \(\Delta t_k\)

  5. 计算这段 \(\Delta s\) 内的能耗(核心解答您的问题)
    是“先计算功率,后计算能耗”。

    第一步(先算功率): 利用刚刚得到的平均速度 \(v_{avg}\) 换算出电机的平均转速,结合下达的扭矩,去查表得到效率 \(\eta\)。然后用
    (扭矩 × 平均转速) / 效率
    的公式,算出这段过程中的平均电池输出功率(代码里的 \(p_bat\))。
    第二步(后算能耗): 功率只是个瞬间强度。用这个平均功率 \(\times\) 第 4 步算出的时间 \(\Delta t_k\) (公式原理是 \(E = P \times \Delta t\) ),最终得到了走完这段 \(\Delta s\) 一共消耗掉的电能(代码里的 energy_step)。

  6. 更新并进入新循环
    把算出来的末端速度 \(v_{k+1}\) 记作下一段路程的“初始速度”,往前挪动 \(\Delta s\) 的距离然后把这个最新状态返回给智能体,智能体根据新状态再次给出下一段 \(\Delta s\) 的新扭矩指令,无限重复。

新的改进

  1. 引入了新的奖励机制——对跟踪的惩罚进行了分段,而不是一刀切,而且对跟踪奖励的数值进行了放大
  2. 对于驾驶员初始的输出转矩进行了平滑
  3. 引入了时间债(告诉了智能体现在它已经满了多少时间了)
  4. 新引入了速度的连续惩罚,防止产生长时间的速度的误差

存在的一些问题
1. 还是初期的扭矩为什么相差会这么大,原因是什么
2. 最终的速度还是倾向于慢于原始的速度,这种能解决吗
3. 驾驶员模型和道路模型的生成问题
生成的速度还是不够充分,也没有按照我的收集的数据进行收集
4. 节能的问题