手撕PPO(二)代码实现记录
PPO 代码实现记录(Pendulum)这篇是 train.py 和 agent.py 的实现记录,原理部分在 NOTE.md 里已经写过了,这里只记代码怎么搭起来的、每块在干嘛、哪里踩过坑。环境是 Pendulum-v1,连续动作、单环境,固定 200 步 truncate。先看 train.py 的主循环,再拆 agent.py 里 actor/critic/buffer/update 那几块。 后面加 advantage 标准化和梯度裁剪两个 trick 是因为一开始训不稳,跑到后面会崩,这部分单独放最后记。 一、train.py:主循环导包、建倒立摆环境先导包。gymnasium 给环境,torch 存模型,time/os 拼日志路径,matplotlib 最后画曲线。PPOAgent 和 device 从自己写的 agent.py 里拿,device 是 agent.py 里统一判好的 cuda/cpu。 建环境用 gym.make,Pendulum 的 reward 全是负的,立得越稳越接近 0,所以 0 是上限。 123456789import gymnasium...
手撕PPO(一)论文框架
这份笔记是为了复现 PPO 整理的,按照采样、优势估计、损失计算、参数更新的顺序走一遍。公式记号尽量和 Schulman 的两篇原文(PPO 2017、GAE 2015)保持一致。这篇文章主要记 PPO 论文和实现里容易对不上的地方,代码复现部分下一篇再讲。 原论文链接: [1707.06347] Proximal Policy Optimization Algorithms 前置知识原本的策略梯度DQN 是学一个“动作价值函数” $Q(s,a)$,然后选 $Q$ 最大的动作。Policy Gradient 是直接学一个策略 $\pi_\theta(a \mid s)$,让网络直接输出动作概率,或者输出连续动作分布的参数。 DQN 里网络做的是:输入 state,输出每个动作的 $Q$。训练目标是让 $Q$ 满足 Bellman 方程: Q(s,a) \approx r + \gamma \max_{a'} Q(s', a')所以 DQN 本质上是: 先学“每个动作有多好”。 再根据 $Q$ 值选动作。 Policy Gradient 的思路Policy Gradient ...
CPP树
\theta=5description和keywords都是给爬虫看的 map myMap键值对,键唯一,按照键的大小自动排序 12345678910111213//插入,访问,查找,删除myMap.insert(make_pair(1, 100)) #make_pair打包键值对myMap[2] = 200;myMap.insert({{3, 300}, {4, 400}}); auto it = myMap.find(3);if (it != myMap.end()) { a= it->first; b= it->second ;} else {}myMap.erase(1); str.size()获取列表长度 string定义字符串 unordered_map:适合对元素顺序没有要求,且需要快速访问的场景,尤其是在元素数量较多且频繁进行查找、插入和删除操作时。 .push_back(),在vector后面添加元素 vector
贪心算法
分发饼干大饼干优先给大胃口的学生,遍历学生,满足条件后再更新饼干 摆动序列多种情况,记录上一个对差值,遇到平坡不更新差值,只在变化时更新差值(last带等号,now不带等号) 最大子序和当当前值为负数的时候,放弃掉当前值,重新开始,因为负数加上任意一个数都会更小int result = INT32_MIN;表示32位有符号整数的最小值 买股票的最佳时机II只计算上升段的和写法优化:大于0,+=value:+=max(value,0); 1result += max(prices[i] - prices[i - 1], 0); 跳跃游戏最大覆盖值,一直更新for循环的限制值,只在限制值范围内循环,找到答案提前return 跳跃游戏II(重做没思路)在当前覆盖范围内,找最大的跳跃距离 K次取反后最大化数组和先尽量吧负数全变成正数,再针对最小的数疯狂取反代码优化:cmp排序: 1234static bool cmp(int a, int b) {return abs(a) > abs(b);}sort(A.begin(), A.end(), cmp...
Legged gym 使用笔记
Legged gym 使用笔记入门案例1.倒立摆 首先在 envs 目录下创建自己的环境 cartpole。 这篇主要是看自定义 Cartpole2Task 环境时做的源码笔记。整体流程大致是这样: 在 envs 目录下放自己的任务代码和 config,比如 cartpole2.py、cartpole2_config.py。 在 envs/__init__.py 中注册任务名、环境类、环境配置和 PPO 配置。训练脚本通过任务名找到对应环境。 环境类继承 BaseTask,核心是实现仿真创建、step、reset、reward、observation 这些函数。 初始化时先解析配置,再调用父类创建 sim/env,随后初始化 PyTorch buffer,最后准备奖励函数列表。 训练时算法会不断调用 env.step(actions)。一次 step 里先裁剪 action,再计算 torque,接着推进物理仿真,刷新状态,然后检查终止条件,计算奖励,重置需要结束的环境,最后重新计算 observation 并返回给算法。 下面按函数继续记录。 导入LEGGED_G...
卡尔曼滤波(一)公式推导
首先这里非常感谢drcan提供的非常详细且通俗的教程。这篇文章也记录一下我学习卡尔曼滤波的过程。主要是记录一下公式的推到过程。 均值的增量式写法 \begin{aligned} \hat{x}_k &=\frac{1}{k}\sum_{i=1}^{k}z_i \\ &=\frac{1}{k}\left(z_1+z_2+\cdots+z_k\right) \\ &=\frac{1}{k}\left(z_1+z_2+\cdots+z_{k-1}\right)+\frac{1}{k}z_k \\ \hat{x}_{k-1} &=\frac{1}{k-1}\left(z_1+z_2+\cdots+z_{k-1}\right) \\ \hat{x}_k &=\left(1-\frac{1}{k}\right)\hat{x}_{k-1}+\frac{1}{k}z_k \end{aligned}最后整理为: \boxed{ \hat{x}_k = \hat{x}_{k-1} + \frac{1}{k} \left( z_k-\hat{x}_{k-1} \right) }可以看到随着测量次数的...
FreeRTOS中断管理
中断是 MCU 的硬件机制。STM32 通过 NVIC 管理外设中断,每个中断对应一个中断服务例程,也就是 ISR。以 STM32F4 这类 Cortex-M4 为例,中断优先级数字越小,硬件优先级越高;数字越大,硬件优先级越低。 FreeRTOS 运行以后,任务调度和中断并不是两套互不相关的东西。任务切换依赖 SysTick、PendSV 这些异常,中断里也经常需要释放信号量、写队列、发送任务通知,把外设事件交给任务处理。因此中断优先级和 FreeRTOS API 的调用规则需要先明确。 一、STM32 中断优先级STM32 的中断由 NVIC 管理。优先级通常分成抢占优先级和子优先级,抢占优先级决定能不能互相嵌套,子优先级用于同抢占优先级下的响应顺序。 在 FreeRTOS 工程里,一般会把优先级分组配置成所有有效位都用于抢占优先级,比如 STM32 HAL 里常见的 NVIC_PRIORITYGROUP_4。这样 FreeRTOS 用 BASEPRI 屏蔽中断时,判断逻辑比较直接。以 STM32F4 常见的 4 个有效优先级位为例,中断优先级数值范围是 0 到 15,其中 ...
FreeRTOS任务调度
FreeRTOS 里的任务,就像是一个不会随便返回的 C 函数。基本形式如下: 12345678void TaskA(void *argument){ for (;;) { /* 做自己的事 */ vTaskDelay(pdMS_TO_TICKS(10)); }} 任务函数可以写得像普通函数,但不能像普通函数那样执行完以后直接 return。任务是被调度器管理的执行单元,函数返回后,任务栈和任务控制块仍然在内核的管理范围里,结果不可控。结束任务通常有两种方式:任务自己在退出前调用 vTaskDelete(NULL),或者另一个任务拿到它的句柄后调用 vTaskDelete(taskHandle)。 vTaskDelete() 只是把任务从调度器的各种链表里移除,让它不再参与调度。动态创建任务占用的 TCB 和栈内存,一般由空闲任务后续回收。因此空闲任务需要有运行机会。如果系统里一直有高优先级任务死循环运行,不阻塞、不让出 CPU,被删除任务的内存就可能迟迟无法回收。 一、任务状态FreeRTOS...
FreeRTOS空闲任务与低功耗
FreeRTOS 低功耗这块,最容易混在一起的是三个东西:HAL 的基础时钟、FreeRTOS 的 Tick、CPU 进入低功耗的时机。原笔记里提到 SysTick、TIM6、空闲钩子和 Tickless,本质上都围绕这几个问题。 一、HAL 时基和 __weakSTM32 HAL 里很多函数依赖一个毫秒级时基,最典型的是 HAL_Delay() 和 HAL_GetTick()。HAL 默认通常使用 SysTick 作为时基,每 1ms 进入一次 SysTick 中断,在中断里增加 HAL 的 tick 计数。 HAL 里有不少函数使用 __weak 修饰。弱函数的意思是:库里提供一个默认实现,用户如果在自己的工程里写了同名强定义函数,就可以覆盖这个默认实现。HAL 时基相关函数就是典型例子。 比如 HAL 默认会有类似这样的弱函数: 1234__weak void HAL_IncTick(void){ uwTick += uwTickFreq;} 用户可以通过 CubeMX 或手动代码,把 HAL 的时基从 SysTick 改成 TIM6、TIM7 等...
FreeRTOS软件定时器
FreeRTOS 软件定时器适合对精度要求不高、但又需要周期触发或延时触发的场景。它不是硬件定时器,不会占用某个 TIM 外设,也不会在硬件中断里直接执行回调。软件定时器依赖 FreeRTOS 的 Tick 和一个专门的定时器服务任务。 如果要做 PWM、输入捕获、精确采样这类强实时功能,还是应该用硬件定时器。如果只是 500ms 翻转一次 LED、3s 后超时处理、周期性检查状态,用软件定时器更方便。 一、软件定时器的运行机制软件定时器启用后,FreeRTOS 内核会创建两个核心对象: 定时器服务任务:也叫 Timer Service Task 或 Daemon Task。它本身也是一个普通 FreeRTOS 任务,由调度器调度运行。 定时器命令队列:任务或 ISR 调用 xTimerStart()、xTimerStop()、xTimerReset() 等函数时,并不是直接操作定时器链表,而是把命令发送到这个队列里,由定时器服务任务取出后执行。 因此,软件定时器的操作路径大致是: 12345用户任务/ISR 调用 xTimerStart() -> 命...










