简介:基于UE4与AirSim开发的无人机强化学习自主导航系统源码,面向计算机科学、电子信息、智能科学与技术、控制工程等专业方向的师生及技术人员,解决虚拟环境中无人机自主路径规划与动态目标追踪等智能控制问题。压缩包共775个文件,以Python脚本、C++头文件/源文件为核心,辅以Markdown文档、PDF论文、示意图与构建脚本,整体约149.52MB,目录结构清晰,便于分模块学习。已有60人学习。内容包含完整算法实现代码、毕业设计论文,并附带构建脚本与参考文献,程序均通过完整性测试;该成果在毕业答辩中获得98分,具备较高学术参考价值,既可用于课程实践与毕设课题开发,也可作为科研项目前期验证框架,支持后续在决策模型、传感器融合与轨迹规划等方向深化拓展。
1. 从仿真到飞控:UE4与AirSim在无人机强化学习里到底扮演什么角色
基于UE4与AirSim的无人机强化学习自主导航系统,最常被低估的一环不是深度强化学习算法,而是仿真环境本身的正确性。目标点在几十米外,途经障碍物,无人机要从图像或激光数据里学会“该往哪飞”,AirSim负责把电机响应、重力、碰撞和传感器噪声翻译成状态流,UE4负责让这个状态流里出现真实世界中的光影和纹理。这个方案适合刚好要做视觉导航、又不希望一开始就在真机上反复炸机的团队,或者已经在用PX4飞控、想用硬件在环仿真先把策略验证一遍的人。下面按从搭建、封装环境、训练到迁移的顺序,把能真正落地的那条路径完整走一遍。
2. 搭一套能跑强化学习的UE4+AirSim环境:版本、工程结构与连接验证
2.1 为什么是AirSim而不是Gazebo或凤凰无人机模拟器
网上常有人问,Gazebo生态不是更成熟吗?Gazebo的优势在动力学和传感器插件,但视觉渲染偏弱,做端到端的视觉导航,训练出来的策略一遇到真实光照就失效。凤凰无人机模拟器适合飞手练手感,玩法很好,但它的内核不对外暴露Python API,更别提在训练循环里重置环境、设置目标点、读传感器。AirSim作为UE4插件,提供多旋翼动力学、双目相机、深度图、激光雷达、GPS和IMU仿真,而且Python API足够干净,一个循环里可以完成“设置任务、跑几步、收观测、下发新指令”。这套搭配把“仿真环境”和“强化学习环境”之间的接口成本降得很低。
如果你已经维护着一套ROS生态,AirSim也有ros2接口,但我的建议是训练阶段不要绕一层ROS桥,直接在Python里消费API。绕行的代价是控制周期被拉长,训练一小时后你会发现rosbridge的队列积压比算法收敛还让人头疼。
2.2 最小工程结构:地图、Settings.json与无人机参数
我一般不会直接拿AirSim自带的小地图跑导航,因为地图太小,无人机一加速就到边界。常见做法是新建一个UE4工程,用Block类地面铺一块足够大的区域,再摆上一些立方体或静态网格体作为障碍物。为了让强化学习后期不过拟合,障碍物位置不要贴死,留出随机摆放的空间。
提示:UE4工程版本与AirSim插件版本务必匹配。不要用UE4.26搭配过老的AirSim插件,否则编译通过的插件在打包后会出现连接失败这类奇怪问题。
工程目录按下面这样组织:
UAVNavSim/ ├─ UAVNav.uproject ├─ Config/ │ └─ DefaultEngine.ini ├─ Content/ │ ├─ Maps/ │ │ └─ TrainingMap.umap │ └─ (障碍物模型、地面材质) └─ Plugins/ └─ AirSim/这个结构里最关键的是Settings.json。AirSim默认读取Documents/AirSim/settings.json,但开发中我建议用环境变量AirSimSettingsFile指定到仓库内,方便多人协作时同步无人机参数,也避免改完配置找不到源头。Settings.json里的无人机部分我通常写成这样:
{ "SettingsVersion": 1.2, "SimMode": "Multirotor", "ClockSpeed": 1, "Vehicles": { "UAV": { "VehicleType": "SimpleFlight", "UseSerial": false, "DefaultCamera": { "Pitch": 0, "Roll": 0, "Yaw": 0 }, "Sensors": { "Lidar": { "SensorType": 6, "Enabled": true, "NumberOfChannels": 16, "PointsPerSecond": 100000, "Range": 40, "HorizontalFOVStart": -90, "HorizontalFOVEnd": 90, "VerticalFOVStart": -15, "VerticalFOVEnd": 15, "RotationsPerSec": 10 } } } } }这个配置里SimMode: Multirotor决定飞的是多旋翼模型;ClockSpeed: 1表示仿真时间与真实时间1:1,训练时如果想让数据收集得更快,可以调到2或4,但动作时间步也会被加速,需要同步调整控制频率。Lidar的16通道、40米量程是室内外通用的一组值,通道越多点云越密,但训练耗时也会明显上涨;如果你的导航策略以视觉为主,Lidar这一块可以先关掉,用深度图做避障已经足够。
地图上有一个常被忽略的点:静态光照。UE4默认构建光照后场景里会有一层真实感很足的光影,对视觉类强化学习来说,光源角度不同会导致同一位置的图像差异很大。环境搭建阶段就把阳光角度、天光强度固定下来,并做好lightmass相关配置,后面训练时图像帧之间才不会出现整体亮度的漂移。
2.3 用Python客户端验证连接:起飞、悬停、收数据一条龙
环境搭好后,先用一个最小脚本验证端到端通信,这一步能排掉80%的环境故障。脚本做的事是:连接AirSim仿真器、解锁电机、起飞、悬停、读取一次相机图像和IMU数据。
import airsim import numpy as np import time client = airsim.MultirotorClient() client.confirmConnection() client.enableApiControl(True, "UAV") client.armDisarm(True, "UAV") client.takeoffAsync(timeout_sec=10, vehicle_name="UAV").join() time.sleep(1) client.hoverAsync(vehicle_name="UAV").join() responses = client.simGetImages([ airsim.ImageRequest("0", airsim.ImageType.DepthVis, False, False) ], vehicle_name="UAV") if responses and responses[0].width > 0: depth_bytes = np.frombuffer(responses[0].image_data_uint8, dtype=np.uint8) print("depth image size:", responses[0].width, "x", responses[0].height, "bytes:", len(depth_bytes)) imu_data = client.getImuData("imu", vehicle_name="UAV") print("imu acc:", imu_data.linear_acceleration)这里confirmConnection()会持续尝试与UE4里的AirSim网络通信,直到握手成功。enableApiControl(True)把无人机控制权从简单飞控切换到API,armDisarm(True)模拟解锁,takeoffAsync后面必须join()等待动作完成,否则后续指令会挤在同一个时间片里导致异常。读取深度图时,ImageType.DepthVis返回的是用像素灰度编码的深度,不是直接的距离值,转成以米为单位的深度需要额外的解码步骤,否则奖励函数里算距离会产生肉眼不容易发现的偏差。
连接验证通过后,再在UE4编辑器里跑一遍PIE模拟,确认地图中障碍物的碰撞体确实挂上了。若没有碰撞体,无人机可能会直接穿过墙壁,训练出来的避障策略就完全失真了。这个位置也是一线项目里最常翻车的地方,不要因为代码跑起来了就默认物理世界是对的。
3. 把自主导航包装成强化学习环境:观测、动作、奖励与通信瓶颈
3.1 观测空间:RGB还是深度图,或者两者都上
导航策略的观测空间直接决定网络结构有多复杂。只给RGB图像,网络要同时学会识别地面、墙壁、天空和距离,在UE4的固定场景里很容易过拟合到纹理;只给深度图,避障信息很干净,但没有颜色和纹理信息,遇到透明物体或低纹理墙面时会吃亏。我一般把深度图作为第一优先,因为导航任务的本质是空间感知而非语义识别,深度图把“前面有没有东西、多远”直接编码出来,网络学起来最快。
深度图在AirSim里返回的是视差或欧几里得深度,推荐使用ImageType.DepthPlanar或DepthVis。DepthPlanar给出的是平面深度,类似相机坐标系下的Z值;DepthVis是可视化编码后的8位灰度,方便人眼调试,但不适合直接作为网络输入。做训练时我通常请求DepthPlanar再归一化到[0,1],同时把IMU的加速度和角速度拼接进向量。还有一套更稳的输入组合是“深度图+自身速度+目标相对方位”,这相当于给策略补了一部分状态信息,比单纯堆图像更容易收敛。
def get_observation(client): # 深度图 depth_resp = client.simGetImages([ airsim.ImageRequest("0", airsim.ImageType.DepthPlanar, True, False) ], vehicle_name="UAV")[0] depth_img = np.frombuffer(depth_resp.image_data_float, dtype=np.float32) depth_img = depth_img.reshape(depth_resp.height, depth_resp.width) # 无人机状态 state = client.getMultirotorState(vehicle_name="UAV") vel = state.kinematics_estimated.linear_velocity ang_vel = state.kinematics_estimated.angular_velocity # 目标相对位置在NED系下的分量 target_rel = current_target_pos - state.kinematics_estimated.position obs = { "depth": depth_img / 100.0, # 归一化到 0~1 "velocity": np.array([vel.x_val, vel.y_val, vel.z_val]), "target_rel": np.array([target_rel.x_val, target_rel.y_val, target_rel.z_val]) } return obs这段代码里两个关键点:一是DepthPlanar返回的是float32数组,不能用image_data_uint8去解析,否则图像全是错位的;二是目标相对位置一定要转成无人机坐标系下的分量,怎么转放到3.4节讲。深度除以100是经验值,AirSim的深度单位是厘米,但不同UE4工程可能因为单位缩放出现差异,训练前先打印一帧深度图的最大值确认一下。
3.2 动作空间:速度指令比姿态指令更好学,原因是什么
无人机底层控制有两种常见方式:给姿态角或给速度指令。直接用姿态角做动作空间,策略要额外学习“多大俯仰角会产生多大加速度、多久才能变成期望速度”这一层动力学映射,训练难度会明显增加。速度指令则不同,AirSim内置的SimpleFlight飞控已经帮你做了姿态内环,策略只需要输出“我想往哪边飞、飞多快”,相当于把学习目标从“动力学控制”降维到“运动规划”。
我常用的动作定义是四维向量:[vx, vy, vz, yaw_rate],前三个范围在-1到1,对应最大速度比例,最后一个控制转向角速度。比如vx=0.5代表以最大前向速度的一半向前飞。用moveByVelocityAsync下发指令:
def apply_action(client, action, max_speed=5.0, duration=0.5): vx = float(action[0]) * max_speed vy = float(action[1]) * max_speed vz = float(action[2]) * max_speed yaw_rate = float(action[3]) * 60.0 # 度/秒 client.moveByVelocityAsync( vx, vy, vz, duration, yaw_mode=airsim.YawMode(True, yaw_rate), vehicle_name="UAV" ).join()这里的duration就是决策周期,同时也是强化学习的时间步长。0.5秒是一个兼顾密度和控制精度的值;太短会导致环境交互太频繁、训练速度下降,太长则避障反应迟钝,无人机在高速靠近障碍时来不及转向。max_speed需要结合地图尺寸设定,我一般先设成3到5米每秒,训练稳定后再调大看策略上限。注意moveByVelocityAsync是非阻塞接口,但.join()会等待指令完成,强化学习循环里必须等它执行完再采集下一帧状态,否则你拿到的观测和动作之间有时间差,策略会学到一种“迟缓”的感觉。
3.3 奖励函数:稀疏奖励为什么会让训练直接翻车
第一次跑无人机导航的人最容易把奖励设计成“到达目标点给+1,撞到障碍物给-1,其他时候给0”。这种稀疏奖励在简单二维网格里可行,但放到连续控制的多旋翼里,无人机随机探索几百步都摸不到目标一次,梯度信号几乎为零,训练结果基本是原地打转。想让这个系统真正跑起来,必须把奖励铺成一个连续的“地势图”,让无人机每一步都能感受到自己在变好还是变坏。
我常用的奖励函数是一个加权组合:
r = 1.0 * delta_distance # 靠向目标的正向奖励 + 0.3 * exp(-distance / 5) # 距离目标越近,额外给一份平滑奖励 - 0.02 * |v| # 轻微的速度惩罚,防止高空乱冲 - 0.5 * (collision == 1) # 碰撞大惩罚 - 0.05 # 每步时间惩罚,推动策略走最短路径delta_distance是这一步执行后“无人机到目标点的欧氏距离”的减少量。这样即使没有到达目标,无人机也能从距离缩短中获得正向反馈。exp(-distance/5)的作用是解决距离很远时梯度过小的问题,相当于在目标附近放了一个引力井。速度惩罚不能太大,否则无人机学成“龟速巡航”,看起来避障很稳,但效率极低;0.02这个量级配合5米每秒的最大速度,既不影响机动性,又能压住原地画圈的坏习惯。
还有一个容易被忽略的点:EP回合长度的设限。我一般把单回合最长时间设为30秒到60秒的仿真时间,到时间还没到达目标就强制结束并给一个负奖励。否则无人机卡在墙边时,它会反复试探、累积一堆无意义的小奖励,让价值估计出现偏差。
3.4 环境交互循环:坐标对齐、数据缓存与单步延迟
把观测、动作、奖励串起来,就成了一个标准的Gym式环境。我第一次写这个循环时,踩过最深的坑是坐标系的混用。AirSim内部使用NED坐标系(X朝北、Y朝东、Z朝下),而UE4世界的坐标是Z朝上;如果你直接拿UE4里的目标点位置减去AirSim读出的位置,算出来的距离会差一个Z轴符号,奖励时正时负,训练完全无法收敛。在环境初始化时就要统一约定:所有计算都在NED系下做,UE4坐标只在放置目标点时做一次转换。
import gym from gym import spaces import airsim import numpy as np class AirSimNavEnv(gym.Env): def __init__(self, target_ue4_pos): super().__init__() self.client = airsim.MultirotorClient() self.client.confirmConnection() # 将UE4坐标转为NED坐标 self.target_ned = self._ue4_to_ned(target_ue4_pos) self.action_space = spaces.Box(low=-1.0, high=1.0, shape=(4,), dtype=np.float32) self.observation_space = spaces.Dict({ "depth": spaces.Box(low=0, high=1.0, shape=(84, 84), dtype=np.float32), "velocity": spaces.Box(low=-np.inf, high=np.inf, shape=(3,), dtype=np.float32), "target_rel": spaces.Box(low=-np.inf, high=np.inf, shape=(3,), dtype=np.float32) }) def _ue4_to_ned(self, p): # UE4: X,Y,Z -> AirSim NED: X, Y, -Z return airsim.Vector3r(p[0], p[1], -p[2])target_rel在3.1节已经减过,但那里用的是世界系下的目标位置。更稳的做法是把这个相对向量旋转到无人机机体坐标系:用getMultirotorState读到的四元数做旋转,得到一个“目标在我的前方多少米、左侧多少米”的表示。这个变换对策略来说至关重要,因为策略要学的是“左转右转”,不是“朝世界的东边飞”。旋转四元数可以直接用scipy.spatial.transform.Rotation,每次step里做一次旋转的计算量很小,但收敛速度的差异非常明显。
环境循环里另一个痛点是数据缓存。AirSim的图像请求在训练中如果每个step都实时渲染、实时压缩传输,交互频率会被拖到5Hz以下。我一般会调低图像分辨率到84x84或64x64,同时把ImageRequest的compress=False设为false(直接取原始float数据),减少CPU在解压上的开销。单步延迟控制在0.2到0.5秒是一个合理区间,如果低于这个值,优先怀疑是不是启用了垂直同步或渲染分辨率过高。
4. 训练自主导航策略:PPO、超参、网络结构与飞行指标
4.1 策略网络与价值网络的结构选择,PPO为什么是默认起点
无人机自主导航是一个典型的高维连续控制问题,深度强化学习算法里PPO是最稳的起点。TRPO的理论更漂亮但实现复杂,DDPG和TD3对超参敏感,reward scale稍微一变就容易发散;PPO用clip限制策略更新幅度,稳定性和样本效率之间的平衡在仿真环境里表现得最省心。如果你已经把David Silver那套强化学习基础吃透了,PPO的loss公式理解成本也很低。
网络结构上,图像输入先过一个轻量CNN:三层卷积加ReLU,把84x84深度图压成256维特征,然后和速度向量、目标相对位置拼接,再送入两个共享的MLP层,最后分别输出动作均值、价值估计。为什么不单独做两个网络?因为导航任务的图像特征和状态价值高度相关,共享底层可以加速特征提取的收敛,训练显存也更省。
import torch import torch.nn as nn class NavPolicy(nn.Module): def __init__(self, img_size=84, state_dim=6, action_dim=4): super().__init__() self.cnn = nn.Sequential( nn.Conv2d(1, 16, kernel_size=8, stride=4), nn.ReLU(), nn.Conv2d(16, 32, kernel_size=4, stride=2), nn.ReLU(), nn.Conv2d(32, 32, kernel_size=3, stride=1), nn.ReLU(), nn.Flatten(), ) # 卷积输出维度需要根据输入尺寸调试确定 self.feature_dim = 32 * 4 * 4 + state_dim self.common = nn.Sequential( nn.Linear(self.feature_dim, 256), nn.ReLU(), ) self.mean_head = nn.Linear(256, action_dim) self.log_std_head = nn.Parameter(torch.zeros(action_dim)) self.value_head = nn.Linear(256, 1) def forward(self, depth, state): feat = self.cnn(depth) x = torch.cat([feat, state], dim=1) x = self.common(x) mean = torch.tanh(self.mean_head(x)) value = self.value_head(x) return mean, valuelog_std_head是PPO里学习到的动作标准差,初始全0意味着分布的标准差为1,配合tanh的输出层,动作范围被限制在-1到1,正好匹配Gym环境里action_space的边界。这里有个细节:标准差不通过共用的common层,而是单独一个参数,这样策略网络更新时梯度更新标准差的方式更稳定,不容易因为特征层的抖动导致探索噪声剧烈变化。
4.2 一套能直接起步的超参数表
超参这个东西,不同环境会不一样,但我有一套在AirSim导航场景里能直接起步的默认值。这里的数值不是硬编码的最优方案,而是给把你从“跑不起来”带到“能看曲线变化”的基准。
| 参数 | 推荐值 | 说明 |
|---|---|---|
| learning_rate | 3e-4 | Adam优化器,不需要线性衰减 |
| n_steps | 2048 | 每轮采样步数,约对应17分钟仿真数据 |
| batch_size | 128 | 小批量更新,防止单批图像过于相关 |
| n_epochs | 10 | 每次都把采样数据过模型10遍 |
| gamma | 0.99 | 折扣因子,适配30秒回合的长度 |
| gae_lambda | 0.95 | 平衡偏差和方差,值越大越偏向长期优势 |
| clip_range | 0.2 | PPO剪裁范围,开头训练不用调小 |
| ent_coef | 0.01 | 熵系数,保持探索,压制策略过早固化 |
| max_grad_norm | 0.5 | 梯度裁剪,防止奖励尖峰带来参数爆震 |
训练时建议先关掉随机障碍物,在一个固定地图上跑通全流程,模型学会一条路线以后,再开Domain Randomization,随机化障碍物位置、光照强度和起点方向。这个递进方式的成功率比一开始就全随机高出很多,因为导航策略要先建立“目标相对位置+深度图像→速度指令”的基本映射,而不是同时面对多个维度变化。
4.3 训练中的飞行指标怎么看,导航是否学到还是死记硬背
训练日志里最骗人的是“episode mean reward不断上升”,因为奖励函数里包含距离减少量,无人机只要学会向前飞就能拿正奖励,不一定真的在避障。我一般会额外记录三个核心导航指标:成功率、平均到达时间、碰撞率。成功率是回合结束时距离目标小于1.5米才判定为成功,平均碰撞率则按“本回合是否发生过碰撞”统计,因为一次碰撞就足以让策略在实机时失控。
我习惯在每个评估节点里把无人机重置到固定起点,目标点放在一个从未出现在训练数据里的位置,跑20个回合看统计。如果成功率不错但碰撞率很高,说明策略学会了直线飞行但不擅长避障,需要把障碍物相关奖励加大,或者在奖励里加一个“近障减速”的引导项。
还有一个判断策略是否“学死”的土办法:把深度图输入随机遮挡下半部分再跑同一个回合。如果策略表现骤降,说明它把大量注意力放在地面纹理上而不是空间结构上,这种情况在真实场景迁移时一定要警惕。正常训练下,策略应该对地面纹理变化不敏感,主要依赖深度轮廓。
4.4 传统路径规划与强化学习端到端之间的取舍
团队里通常会有人质疑:这个问题用A*或RRT加一个避障控制器就能解决,为什么还要上强化学习?这个质疑是对的。如果任务是静态环境下的最短路径,无人机三维路径规划用传统算法成熟且可解释;但如果你希望无人机在未知环境中根据传感器输入实时反应,并且环境结构会在飞行中发生变化,强化学习策略的价值就体现出来了。端到端训练的模型可以做到“所见即所得”,从深度图直接映射到期望速度,省去SLAM建图、路径重规划、轨迹跟踪中间那一大串模块。
从工程成本看,我建议采用“先传统后强化”的路线。第一步用A*做全局路径,输出一串路径点;第二步用强化学习学一个小型避障控制器,跟踪路径点同时避开临时出现的障碍。这样全局规划保证方向正确,局部策略负责反应速度,整套系统的成功率要比纯端到端高很多,也更容易定位是哪个环节出了问题。
5. 避坑手册:UE4构建发黑、AirSim连不上、奖励不收敛的排查思路
5.1 UE4构建光照后发黑
现象:在UE4编辑器里按下构建光照,场景模型正常但画面整体发黑,无人机相机图像在AirSim里看过去像夜里没开灯。原因有两类:一类是场景里没有有效的Lightmass重要体积或者光源没有开启静态光照;另一类是AirSim飞行时摄像机处于某个没有光照构建体积覆盖的区域。
解决:先检查UE4的Build Lighting是否报错,重点看Lightmass日志里有没有“Zero lightmap”一类提示。然后把地面和墙壁的材质LightingMode设为静态或固定,重新搭建光照。如果只是某个区域黑,把Lightmass Importance Volume放大到覆盖整个飞行区域。这个坑在训练前不解决,深度图会整体偏暗,归一化之后噪声占比大大增加,策略学到的全是像素噪声。
5.2 AirSim连接超时或直接崩溃
现象:Python脚本执行到confirmConnection()就卡住,或者UE4运行时直接弹窗闪退。原因最常见的三个:AirSim插件没有正确加载、端口被占用、设置文件路径没被读到。
解决:先看UE4的Output Log里有没有“AirSim initialized successfully”字样;再检查防火墙是不是拦截了UDP 14251端口的进程;最后确认环境变量AirSimSettingsFile指向的路径真实存在。如果都不行,把UE4工程从中文路径移动到纯英文路径下,这个看似无关的步骤能解决一大批插件加载失败问题。开发这类仿真系统,我遇到崩溃的第一反应就是看日志,而不是反复重启工程。
5.3 训练loss下降但无人机原地打转
现象:PPO的critic loss和actor loss都在下降,平均reward也在涨,但可视化无人机在起点附近画圈,从来到不了目标点。原因通常是奖励设计里距离奖励和角速度奖励互相冲突,或者目标相对坐标没有转到机体坐标系。
解决:先在环境里做一次“最短路测试”,即把目标放到无人机正前方3米,让策略网络输出一个全为0的动作,看无人机是否直飞目标。如果发现它在原地转,大概率是target_rel方向反了或坐标轴顺序错了。我当初就踩过这个坑:NED坐标系下Y轴向右,但机体坐标系里偏航顺时针为正,直接把Y分量塞进yaw_rate会让无人机朝目标的反方向转向。修正方法是把target_rel用机体四元数旋转时,注意偏航角和向量旋转的左手右手关系。如果排除了坐标问题,再把时间惩罚0.05调大一点,比如0.08,强制策略不要在原地磨蹭。
5.4 仿真飞得好,实机就失灵
现象:仿真成功率95%,换到真实无人机带光流或视觉定位时,策略频繁撞墙或者抖动剧烈。本质是Sim-to-Real的域差异,包括相机畸变、动态模糊、光照条件、甚至电机响应延迟。
解决:在训练时加入域随机化,把深度图加一点高斯噪声,随机化地面材质颜色和光照角度,把无人机的质量参数在10%范围内扰动。AirSim支持通过Settings.json改重力、阻力系数和最大推力,开一个随机炮台跑训练。另一个重要措施是做硬件在环仿真测试:把训练好的策略接到PX4的硬件在环仿真里跑,验证控制器接口的输出频率和飞控协议匹配后再上真机。上真机之前还要注意动作频率不能太高,把策略输出用一阶低通滤波器平滑一下,否则飞控收到的指令频繁跳变,电调会发出尖锐的噪声并很快发热。
5.5 训练到一半仿真器崩溃,怎么留后悔药
现象:训练跑到第40个小时,UE4编辑器突然崩溃,之前训练的权重还没保存,只能从头再来。这个问题在长时间训练里几乎是必然发生的,所以环境里一定要有检查点机制。
解决:在训练循环的每个step里检测进程状态,同时模型权重每1000步保存一次到本地,并在另一个线程里记录训练的reward曲线。对UE4崩溃的场景,建议用AirSim的simPause接口做定时快照,把障碍物位置、无人机坐标和当前回合进度写入JSON;重新启动时,直接从最近一次快照恢复环境。没有后悔药的设计,长训练就是一场赌运气的游戏。
6. 验证与迁移:一张检查表和一个把策略搬上真机的习惯
6.1 在仿真里验证导航策略的三级检查表
第一级是静态验证:固定起始点、固定目标点,跑50回合,记录成功率和平均飞行时间。这一级通过只代表策略在特定场景下有效。第二级是随机化验证:每次回合随机化起始点、目标点和障碍物位置,跑200回合,统计成功率、平均碰撞次数、平均路径长度与最短路径长度的比值。若比值超过1.5,说明策略绕了远路,需要增大距离奖励权重。第三级是鲁棒性验证:在深度图中注入5%到15%的随机椒盐噪声,同时把Lidar数据丢给策略做对比,确认策略在传感器部分失效时不会直接撞墙。三级全部通过之后,这个策略才算有了上真机的资格。
6.2 把策略搬上真机前,先过一遍“传感器替身”测试
我的习惯是在真机测试前先做一轮“传感器替身”测试:把仿真中使用的深度图输入格式、动作频率、动作范围原样映射到真机的视觉感知模块上,用真机采集一段离线数据,在笔记本上实时跑策略推理,观察输出指令是否平滑。如果输出抖动剧烈,就先加滤波器而不是急着起飞。这个习惯帮我避开了很多“仿真一个样、实机另一个样”的尴尬。
导航策略上线真机时,第一架次不要直接跑自动避障,先把垂直速度限制在0.2米每秒、水平速度限制在1米每秒,遥控器常驻急停,给策略划定一个足够空旷的场地。不要相信仿真里的成功率数字,真机的风场、视觉延迟和电机响应差异,都会让策略的“手感”完全不同。我个人的教训是:迁移永远要比仿真多留出一倍的冗余空间。希望帮到你。
本文还有配套的精品资源,点击获取