简介:本资源是一套面向高校本科生与研究生的毕业设计级无人机智能控制项目,聚焦UE4+AirSim仿真环境中基于强化学习的自主导航与目标跟踪算法实现,适用于人工智能、机器人学、飞行控制等方向的学习与课题开发。压缩包共645个文件,涵盖191个Python脚本(含DQN/PPO训练逻辑、环境封装与策略推理)、167个C++/hpp头文件(AirSim插件扩展与底层通信模块)、116张PNG图像(仿真场景截图、训练曲线与检测结果可视化)、65份Markdown文档(含环境搭建指南、参数说明与实验记录),整体大小为133.66MB。已有1411人学习下载,资源结构完整,包含build_docs.bat等自动化构建脚本、references.bib文献索引、AirSim_FrSkyTaranis.bin固件支持文件及sln工程配置,便于快速复现训练流程、调试控制策略并拓展多目标跟踪与避障功能。
1. 项目背景与整体设计思路
1.1 为什么选 UE4 + AirSim 这套组合
先说说这个项目的起源。毕业设计题目是“UE4和AirSim环境下无人机自主导航和目标跟踪的强化学习算法”,核心就是让无人机在虚拟环境里自己学会“找目标、跟目标”这件事。市面上能用来做无人机仿真的平台不算少,Gazebo、AirSim、FlightGoggles、甚至直接用ROS里的stage仿真器,但AirSim在“视觉真实性”和“接口友好度”上确实有独到优势。
UE4作为渲染引擎,能提供接近真实的光照、纹理和物理效果。AirSim是微软开源的仿真平台,它本质上是UE4的一个插件,在UE4的工程里挂载一个模块,就能获得完整的无人机动力学模型、传感器模型(相机、IMU、GPS、激光雷达等)以及Python/ROS/C++ API接口。这对做强化学习来说非常关键,因为强化学习最耗费时间的事情之一就是环境交互,而AirSim的Python API让“环境重置”、“取图”、“执行动作”这些操作变得非常直接,非常适合训练循环。
我当时对比过Gazebo,Gazebo在机器人领域生态更大,但视觉渲染的真实程度跟UE4不在一个级别。做深度强化学习,尤其是要接视觉输入(比如第一人称视角相机图像)的算法,渲染质量直接决定训练出来的策略能不能迁移到真实场景,所以最终选了AirSim。另外一个现实原因:写毕业设计论文需要大量可视化截图,UE4的渲染效果放在论文里也确实好看,答辩时演示的冲击力比Gazebo的灰盒子强很多。
1.2 系统总体架构设计
整个系统的设计可以分为三层:环境层、算法层、控制层。环境层就是UE4 + AirSim,负责渲染和物理仿真;算法层是整个项目的核心,包含状态提取、动作决策、奖励计算以及训练循环;控制层则是AirSim内置的飞行控制器接口,我们通过API下发速度指令或角度指令,由底层PID或姿态控制器去稳定无人机。
我采用的架构是经典的分层式结构,没有把底层控制放到强化学习里学。原因很直接,强化学习的动作空间越大、越接近底层控制,训练难度就指数级上升。无人机底层飞控本来就有成熟控制器,让强化学习去学油门和角速度是没必要的浪费。我把动作空间限制在水平速度、垂直速度和偏航角速度这四个维度上,这样既保留了飞行灵活性,又大幅降低了探索难度。
这里重点说一下“局部规划”的思路。目标跟踪问题里,无人机需要在环境中寻找目标并持续跟随,如果直接用一整张大地图来做端到端学习,状态空间太大,训练效率极低。我的方案是把导航拆成两个阶段:第一阶段是全局引导,也就是给无人机一个大致的目标方向;第二阶段是局部避障和目标跟踪,这是强化学习重点优化的部分。全局引导可以使用简单的路径点或者人工势场法来生成,而局部控制交给强化学习策略网络。
这套设计的核心逻辑是:让强化学习做它最擅长的事情——在局部范围内,根据视觉和传感器信息做出快速反应;把计算密集但逻辑简单的全局规划交给传统算法。这个思路在工业级方案里也很常见,类似的方案在波士顿动力的Atlas以及大疆的智能飞行模式中都有体现。毕业设计如果能把这种混合架构说清楚,答辩时是很加分的。
2. 强化学习核心要素设计
2.1 状态空间与动作空间的界定
强化学习里最讲究的就是“状态怎么定义、动作怎么定义、奖励怎么给”。这三个定义好坏,直接决定了你后面一个月是轻松调参还是痛苦debug。我在这个项目里踩了不少坑,先从状态空间说起。
我采用的状态空间分为三部分:
- 无人机自身状态:位置(x, y, z)、速度(vx, vy, vz)、姿态角(roll, pitch, yaw)
- 目标相对状态:目标在无人机机体坐标系下的相对位置(dx, dy, dz)、相对速度
- 环境感知信息:激光雷达或深度相机的距离数据,以及可选的视觉图像
在AirSim里获取这些数据非常方便,client.getMultirotorState()获取无人机位置速度,client.getDetectionInfo()或自己用图像处理来定位目标,client.getLidarData()获取激光雷达点云。这里有一个值得注意的细节:AirSim返回的坐标是UE4的世界坐标系,需要转换到机体坐标系才能用于强化学习状态。因为策略网络需要学习的是“在当前相对位置下该怎么做”,而不是“在绝对坐标(100, 200, 30)处该怎么做”,相对坐标的泛化性远好于绝对坐标。
刚开始我没注意这点,直接用世界坐标作为状态输入,结果模型在训练地图上效果还行,换了一张地图之后直接傻眼。后来改成相对坐标,泛化能力好了很多。这是第一个可复用的经验:状态空间尽量用相对量、归一化量,少用绝对量。
动作空间我刚才提到是四维连续动作:水平前进速度、水平侧向速度、垂直升降速度、偏航角速度。AirSim API支持直接下发速度控制指令,client.moveByVelocityAsync()就可以传入速度向量。但是用连续动作空间,训练时探索会很慢。我试过DDPG和SAC这类连续控制算法,在仿真环境里跑起来效率还可以,但收敛过程比较波折。后来又试了离散化动作:每个维度取几个固定档位,比如水平速度分为前进、后退、静止三档,垂直分为上升、下降、静止三档,偏航分为左转、右转、直行三档。离散动作空间让DQN系算法可以直接应用,收敛速度快了不少。
2.2 奖励函数设计思路
奖励函数是整个项目的灵魂。我前前后后改了很多版本,这里直接分享一个相对成熟的方案。
基础奖励由三部分组成:
- 距离惩罚/奖励:无人机与目标之间的距离变化量。如果本轮执行动作后距离缩小,给予正奖励;距离拉大,给予负奖励。这个叫做“势能奖励”,它能给智能体一个明确的梯度方向,让无人机朝着目标靠近。
- 目标跟踪保持奖励:当目标出现在无人机相机视野中心区域时,给予额外的持续正奖励。这个奖励的梯度是“让目标不掉出视野”,对目标跟踪任务非常关键。
- 碰撞和坠落惩罚:如果无人机与障碍物碰撞、落地或者飞出了规定区域,给予一个大的负奖励并终止本回合。
简单来说,奖励函数就是这五个字的平衡:靠近、看住、别撞。
| 奖励项 | 具体公式 | 权重 |
|---|---|---|
| 距离变化奖励 | alpha * (distance_t - distance_{t+1}) | 1.0 |
| 视野保持奖励 | beta * (1 - target_offset/视野半径) | 0.5 |
| 碰撞惩罚 | gamma * (-10) | 10 |
| 到达奖励 | delta * (+5) | 5 |
| 时间惩罚 | epsilon * (-0.01) | 0.01 |
这里面实际很有讲究的是权重比例。如果距离奖励权重过高,飞机会疯狂靠近目标,导致跟目标撞上;如果视野保持奖励过高,飞机会原地悬停,只盯着目标看但不敢靠近。我最终的比例是距离奖励权重为1.0,视野保持为0.5,碰撞惩罚为10,这样无人机既能靠近目标,也能保持视野,但不会太鲁莽。
另外,有一个非常容易踩的坑:奖励尺度不统一。距离变化量可能是几米,视野偏移量是像素,两者量纲完全不同。如果直接相加,大尺度的量会把小尺度的量淹没。我采用的解决方案是,所有奖励项都归一化到 -1 到 +1 的范围内,然后用权重来平衡相对重要性。
2.3 算法选型与实现细节
算法方面,我实验了三种主流算法:DQN、DDPG、以及近端策略优化(PPO)。最终在项目里主要采用的是改进的DQN,这里把选择过程分享一下。
刚开始我图新鲜,直接上了DDPG,因为当时觉得连续动作更炫。但DDPG对超参数极其敏感,学习率、噪声系数、软更新系数稍微变一点,训练结果就可能从收敛变成发散。我大概花了两周时间在跟DDPG的震荡做斗争,但效果一直不稳定。后来冷静下来分析:虽然动作空间是连续的,但我们的四维控制,每一维其实不需要无限连续,分成三到五个档位完全够用。因此,DQN采用了离散-动作的模型。
DQN实现时我做了两个重要改进:
第一是竞争DQN网络结构(Dueling DQN)。常规DQN直接输出每个动作的Q值,Dueling DQN把Q值分解为状态价值V(s)和动作优势A(s,a),Q(s,a) = V(s) + A(s,a)。这个改进对无人机导航任务特别有效,因为很多状态下无论选择哪个动作,价值都差不多,把“状态本身的好坏”和“动作相对的好坏”分开学习,收敛速度明显加快。
第二是加入了优先经验回放(Prioritized Experience Replay)。这个设计的思路很简单:不是所有历史经验都值得学习,有些经验(比如碰撞前的最后一帧)信息量巨大,应该更频繁地采样。我实现时用了一个sum tree结构来维护优先级,采样复杂度是O(log n),效率没问题。这个技巧让训练前期学习速度提升了至少30%。
关键超参数如下:
| 参数 | 值 |
|---|---|
| 学习率 | 3e-4 |
| 折扣因子 gamma | 0.99 |
| 经验池容量 | 100000 |
| 批量大小 | 64 |
| 目标网络更新频率(软更新系数) | 0.995 |
| 初始探索率 epsilon | 1.0 |
| 最小探索率 epsilon | 0.05 |
| 探索率衰减步数 | 50000 |
这里要特别提一下探索率衰减曲线,我采用的是指数衰减,公式是 epsilon = epsilon_end + (epsilon_start - epsilon_end) * exp(-step / decay_steps)。其实试过线性的衰减,不太好用,指数衰减前期探索充分、后期利用稳定,训练曲线更平滑。这也是调参过程中比较有价值的经验总结。
3. 环境搭建与训练流程
3.1 AirSim 环境配置要点
AirSim的安装其实网上教程很多,但版本配套问题能坑掉一半人。我整理几个关键点。
首先,UE4版本和AirSim版本必须严格对应。我使用的是UE4.27 + AirSim v1.6.0。注意UE4从4.27之后就转向了UE5,如果下载的是新版AirSim,它可能会要求UE5(虽然AirSim后来也支持了UE5)。但UE5的内存占用和编译时间都更夸张,我为了稳定,还是选了经典的4.27。另外还需要注意,AirSim的源码需要自己编译,官方Release版不全,编译需要Visual Studio 2019或2022,以及.NET Framework开发工具。
编译过程不赘述,重点说一下settings.json的配置。AirSim的仿真环境参数都写在Documents/AirSim/settings.json里。我使用的配置关键部分如下:
{ "SettingsVersion": 1.2, "SimMode": "Multirotor", "ClockSpeed": 1.0, "CameraDefaults": { "CaptureSettings": [ { "ImageType": 0, "Width": 320, "Height": 240, "FOV_Degrees": 90, "AutoExposureMethod": 1, "MotionBlurAmount": 0 } ] }, "Vehicles": { "Drone1": { "VehicleType": "SimpleFlight", "DefaultVehicleState": "Armed", "ManualControl": false, "RC": { "RemoteControlID": -1 }, "Sensors": { "Lidar": { "SensorType": 6, "Enabled": true, "NumberOfChannels": 16, "PointsPerSecond": 20000 }, "IMU": { "SensorType": 2, "Enabled": true } } } } }这里面用到了几个注意事项。ManualControl设为false,这样训练过程中用户手柄或者键盘输入不会干扰训练。CameraDefaults中分辨率设成320x240,这个尺寸是综合考虑了“图像信息足够”和“训练速度够快”的折中,再大模型输入参数量太大,训练慢;太小了目标在图像中只有几个像素,没法识别。ClockSpeed设为1.0,就是仿真时间和真实时间同步。有人会把这个值设成3或5来加速训练,但实测下来,AirSim的物理仿真在高倍速下会不稳定,容易导致无人机动力学计算出现异常,得不偿失。
3.2 训练平台与网络结构
训练这个项目,硬件要求其实不低。我用的配置是i7-12700K CPU + RTX 3080显卡 + 32GB内存。AirSim本身的渲染和物理仿真耗费了大量CPU资源,而神经网络推理和训练在GPU上。如果硬件资源有限,建议把仿真的复杂程度降低,比如减少场景中物体的数量、关闭后期特效、降低阴影质量。
网络结构方面,如果状态输入是向量(相对位置+速度+雷达数据),我使用的是一个三层全连接网络:
class DuelingDQN(nn.Module): def __init__(self, state_dim, action_dim): super(DuelingDQN, self).__init__() self.feature_layer = nn.Sequential( nn.Linear(state_dim, 256), nn.ReLU(), nn.Linear(256, 256), nn.ReLU() ) self.value_stream = nn.Sequential( nn.Linear(256, 128), nn.ReLU(), nn.Linear(128, 1) ) self.advantage_stream = nn.Sequential( nn.Linear(256, 128), nn.ReLU(), nn.Linear(128, action_dim) ) def forward(self, state): features = self.feature_layer(state) value = self.value_stream(features) advantage = self.advantage_stream(features) # Dueling DQN 的经典聚合方式 q_values = value + advantage - advantage.mean(dim=1, keepdim=True) return q_values这里有个细节:Dueling DQN不能简单地把V和A相加,需要对优势函数做中心化处理,减去均值,不然会出现“V和A相互抵消、无法唯一辨识”的问题。这个在论文里也有提到,但实际写代码时很容易忽略。
如果输入是视觉图像(相机第一视角画面),网络结构会改成卷积神经网络,类似DQN论文里的Nature CNN结构。三个卷积层+两个全连接层,卷积核分别是8x8、4x4、3x3,步长为4、2、1。图像输入尺寸统一resize到84x84,灰度化处理。视觉输入的训练难度比向量输入大很多,需要更多的训练时间,对GPU的显存要求也更高。我最终在项目里主要用的是“向量为主、视觉为辅”的方案:主状态用向量(相对位置、速度、雷达),同时把视觉图像作为辅助信息输入,提高在复杂场景下的感知能力。
3.3 训练脚本与数据流
训练逻辑可以概括为一个典型的强化学习循环。我写了一个训练管理器,负责协调AirSim环境与强化学习代理之间交互:
for episode in range(total_episodes): # 随机初始化场景位置和目标位置 drone_start_pos = random_position() target_start_pos = random_position() reset_env(drone_start_pos, target_start_pos) state = get_state() episode_reward = 0 for step in range(max_steps_per_episode): # 选择动作(带探索噪声) action = agent.choose_action(state) # 执行动作,获得反馈 reward, done, info = execute_action(action) next_state = get_state() # 存储经验 agent.store_transition(state, action, reward, next_state, done) # 更新网络 agent.learn() state = next_state episode_reward += reward if done: break # 定期评估模型效果 if episode % evaluate_interval == 0: evaluate(agent)这个训练循环看起来很简单,但有几个实现上的关键细节。random_position()随机初始化很重要,如果每次都从同一位置起飞,模型会过拟合,换个起点就不会飞了。我一开始在地图里固定了5个起点、5个目标点,训练得到的模型15个组合里能完成12个,后来增加到20个随机起点之后,几乎所有组合都能完成了。
还有一个细节值得注意:训练期间的开始阶段,为了让无人机学会基本飞行,我会先关闭碰撞检测或者把碰撞惩罚设得很小,让它能自由探索。等到模型学会了基本靠近目标的能力后,再逐步加强碰撞惩罚。这个属于“课程学习”的思路,简单有效。如果不做这个,训练初期无人机会频繁碰撞结束回合,根本没有机会学到“怎么靠近目标”的正向经验。
4. 目标跟踪与自主导航的实现
4.1 目标检测与状态耦合
目标跟踪任务里面,除了强化学习本身,还需要一个前置模块:目标检测。在AirSim环境中识别目标有两种思路:第一种是用AirSim自带的SimDetectAPI获取目标的位置信息,这是仿真器直接提供的,简单可靠,但不够真实;第二种是模拟真实的场景,在相机图像上用视觉识别算法(比如YOLOv5)来检测目标,然后转换成相对坐标。
如果做真实项目,我只推荐第二种方案,因为第一种方案在仿真里完全没问题,但到了真实无人机上,你没有精确的位置数据可用,必须靠视觉感知。毕竟毕业设计的一大得分点是“实用性论证”,你得说明你的方法离真实应用有多远。不过考虑到训练效率,我采用了“真实训练,混合监督”的方案:先用仿真器提供的目标精确位置训练强化学习的导航策略,同时加一个视觉目标检测分支来提供感知线索,两个模块一起构成最终的系统。
这里有一个实际工程中很常见的尴尬问题:目标检测的频率和强化学习控制频率不一致。目标检测如果用YOLOv5在GPU上跑,一帧可能需要30毫秒到100毫秒,而强化学习控制频率可能需要10Hz甚至更高。如果每次控制都要等待检测结果,带宽肯定跟不上。我的解法是状态缓存机制:控制循环从最近一次的目标检测结果中读取目标位置,如果检测结果超过一定时间(比如0.3秒),就使用卡尔曼滤波预测目标当前位置。这个方案在实践里效果很好,既保证了实时性,又不会因为检测延迟导致追踪失效。
卡尔曼滤波的预测更新我也简单说一下。对目标的位置和速度建模为线性运动模型,观测方程直接映射到位置坐标。预测步骤是x_pred = F * x_prev,更新步骤根据检测到的位置来修正。噪声方差我根据实际情况调了很久,目标运动越剧烈,过程噪声就应该设置得越大,否则滤波预测会跟不上目标运动。
4.2 端到端收敛与调参
训练过程中最折磨人的就是“不收敛”和“收敛后震荡”。我自己训练时观察曲线有几个典型阶段,这里总结一下方便大家对照。
第一阶段是探索期,大概前1000个回合。这个阶段奖励曲线一直趴在负区间,偶尔有小的波动。这是正常的,模型在疯狂探索环境,了解“撞墙会死”、“靠近目标有奖励”这些基本规律。很多同学在这里就慌了,以为代码写错了,急于调参,其实应该耐心等待。判断这一阶段是否正常的标准是:回合时长是否在逐渐增加。如果回合时长曲线在上升,说明智能体活得久了,虽然奖励还没转正,但已经有了学习迹象。
第二阶段是进步期,大概1000到5000个回合。奖励曲线开始波动上升,无人机从最初一飞就撞,到自己能悬停,再到能靠近目标。这个阶段最忌讳的是随机奖励中夹杂过一次特别高的奖励,就急着保存模型。我一般看最近100个回合的平均奖励,这个指标比单回合奖励稳定得多。
第三阶段是稳定期,5000个回合之后。奖励曲线在高位震荡,但偶尔还会出现断崖式下跌。这个通常是目标位置和初始位置的特殊组合导致的,比如目标出现在障碍物后面,无人机需要绕过去。这个问题其实不能完全靠强化学习解决,因为局部避障能力和全局规划能力在这个模型里是耦合的。
训练过程最常用的监控指标是三个:训练图(平均奖励)、损失曲线、以及策略可视化效果(每50回合保存一次无人机视角的跟踪视频)。损失曲线不要过分关注数值大小,更重要的是一段时间内的下降趋势。我见过很多代码里损失先下降后上升的情况,这不一定是发散,可能恰恰是探索率下降后模型在细化策略,我之前因为误判浪费了不少时间。
4.3 防止过拟合和泛化策略
在仿真环境里做强化学习,一个非常大的陷阱是模型记住了地图,而不是学会了飞行。我特意做了一个实验来验证过拟合程度:在训练地图上,模型的跟踪成功率达到95%,但是我新建了一个UE4场景,把地形和建筑布局完全换掉,成功率直接掉到40%。这个结果说明模型很大程度上在利用训练地图的特有特征,比如特定的障碍物布局、地面纹理颜色等。
提升泛化的几个有效手段:
场景随机化。训练时随机选择起点、终点、障碍物位置,甚至可以随机调节光照条件、天气条件。AirSim支持天气控制API,client.simSetWeatherParameter()可以调节雾、雨、光照。我在后期加入了随机天气训练,模型的视觉特征不再依赖某个特定光影环境,泛化能力明显提升。
域随机化。在强化学习训练中,对状态数据添加噪声是一个简单但很有用的技巧。对雷达数据加高斯噪声,对位置数据添加小幅随机偏移,这相当于一种数据增强,让策略网络对传感器误差不敏感。AirSim的传感器数据太“干净”了,真实环境中绝对没有这么完美的测量值,所以训练时的加噪处理是很有必要的。
定期评估换图。我准备了两张差异较大的地图,一张用于训练,另一张只用于评估。训练过程中每100个回合在评估地图上测试当前策略,如果发现评估曲线突然下降而训练曲线正常,那就是过拟合了,需要加强随机化或者降低网络容量。
5. 常见问题排查与避坑经验
5.1 AirSim 连接与版本兼容问题
关于训练中遇到的技术问题,这里整理一个速查表。
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
TimeoutError: no ping response | AirSim环境未启动或端口占用 | 确认UE4项目已运行,等待加载完成;检查41419端口占用 |
AttributeError: 'MultirotorClient' has no attribute 'moveByVelocityAsync' | AirSim Python API版本不匹配 | 更新pip install airsim或从源码编译Python API |
| 传感器数据全为零 | settings.json配置错误 | 检查传感器ID、类型,确认传感器是否被禁用 |
| 无人机原地不动 | 未调用enableApiControl或armDisarm | 初始化时必须依次调用enableApiControl(True)、armDisarm(True) |
| 训练速度极慢 | ClockSpeed过高导致物理不稳定 | 将ClockSpeed降回1.0,或者降低画面分辨率 |
no ping response这个问题很多同学都碰到过。第一次跑AirSim的完整流程是:先启动UE4工程,等待场景完全加载(看画面出来了不算,要等控制台稳定),然后另开一个Python脚本运行client = MultirotorClient()和client.confirmConnection()。如果你确定过程没问题但就是连不上,可以用netstat -ano | findstr 41419查一下Windows上端口占用情况,把占用端口的残留进程清掉。
ARM解锁这个问题更要单独说一下。AirSim模拟的是PX4或SimpleFlight的真实飞行流程,第一步调用enableApiControl(True),表示把无人机的控制权从“手动”切到“API”,第二步调用armDisarm(True),相当于解锁电机。如果没解锁就下发速度指令,飞机会无响应。很多时候我因为忘了执行这个初始化顺序,导致训练数据里全是一堆废话(无人机没动的记录),白白浪费了训练时间。
5.2 算法训练不收敛的排查路径
训练不收敛是项目中最常见的问题,这里按照经验概率排序,给出排查路径。
第一优先级:检查状态-动作-奖励的定义是否有逻辑漏洞。最常见的问题是奖励函数在某些特殊状态下给出错误信号。比如,无人机在悬停时,由于噪声导致状态中“距离变化量”计算为负,模型会误以为“不动也在靠近目标”,从而倾向于悬停。这个问题设计之初就要注意,奖励计算一定要用原始数据,不要在奖励计算链路里引入过多的滤波器或平滑。
第二优先级:检查训练数据流。将训练中每一步的状态,动作,奖励打印出来,逐帧检查是否合理。我见过一个问题:动作执行后,下一帧状态变化的延迟产生了“旧状态配新动作”的错位,这个在AirSim的异步控制中很容易出现。原因在于moveByVelocityAsync()是异步指令,代码会立刻返回下一帧的状态,但无人机的实际运动可能还在上个回合的速度控制指令影响下。解决方案是执行动作后加一个time.sleep()等待0.05到0.1秒(大约对应2-3个仿真帧),再获取下一状态。这个细节很重要,如果你的状态动作对错位了,整个优化方向都是错的,训练再怎么调都不收敛。
第三优先级:调超参数。如果前两步没问题,就开始动超参数。常用的几个调整方向(按推荐顺序):
- 学习率:1e-3 -> 3e-4。学习率太大,Q值震荡发散;太小,收敛很慢。
- 奖励权重:尤其是碰撞惩罚的权重。如果过大,模型的策略会变成“原地不动”,因为不动就不撞。
- 探索率衰减速度:如果衰减太快,模型过早进入利用阶段,没探索完环境就固化了策略;衰减太慢,后期模型一直抖。
5.3 训练资源占用与效率优化
AirSim + UE4程序的资源占用相当可观。我实测过,UE4渲染进程占用约4GB内存,GTX 3080约1GB显存用于渲染(取决于分辨率),训练网络占用另外2~3GB,总共内存占用达到15GB以上,显存占用到8GB。如果你的显卡显存只有6GB,建议把采集分辨率降到160x120,或者不使用视觉输入,只使用向量输入。
训练速度优化上有几个经验:
减少AirSim帧率。在settings.json中,可以将CaptureSettings中的帧率设为10Hz左右,这个频率勉强够用,能显著降低CPU负载。控制频率和渲染帧率可以不同步,没必要让UE4渲染60fps的动画给一个学习算法看。
批量更新参数。训练过程中不用每步都更新神经网络参数。我采用的方案是每4步采集经验,累计到4条样本后统一做一次批量更新。这样既保证了训练稳定性,又减少了GPU往返延迟。
经验池复用。如果你做多个对比实验(比如DQN vs Dueling DQN),不要傻乎乎重新采集经验。可以先跑一份随机策略或者某个固定策略,采集一批经验存成numpy文件,之后所有算法都用这批经验初始化经验池,这样不同算法之间的对比更公平,也节省了大量环境交互时间。
5.4 无人机仿真到真机迁移的预期管理
最后说一个很多做这个方向的同学都会忽略的问题:仿真训练出来的策略,到底能不能直接上真机?答案是:直接复制上去一定会失败,但思路可以迁移。
仿真到现实(Sim-to-Real)的鸿沟主要来自三个方面。第一是动力学差异,AirSim的SimpleFlight模型和真实无人机的电机响应、风速影响、惯量都不完全一致,再逼真的仿真也有误差。第二是传感器差距,仿真中的雷达数据是完美解析的,真实雷达受噪声、反射、遮挡影响很大。第三是视觉差异,UE4渲染再真实,也不能完全模拟真实世界的光学特性。
我的建议是,如果你真的想往真机方向走,可以做三件事:在校验阶段,用真实无人机的飞行记录来拟合空气动力参数,将AirSim的模型校准到接近真机;训练时加入加噪和域随机化实验,观察策略对扰动的鲁棒性如何;在策略输出的末端加一个平滑滤波,避免突然的速度跳变把真实无人机飞控搞崩溃。
做毕业设计的话,最务实的方式是做清楚“策略的迁移可行性分析”,通过仿真和半物理仿真(比如用仿真环境处理视觉,用真实飞控处理控制)来论证思路是可行的,不必执着于真实飞行实拍。毕竟硬件的风险和成本是做项目时应该认真考虑的。
6. 写在最后的经验总结
这次做UE4 + AirSim强化学习毕业设计,整体下来最深的感触是:强化学习项目70%的工作量不在算法本身,而在环境、数据、调参和问题排查。算法原理大家都能讲得头头是道,但真正把一个算法在仿真环境里跑通、跑稳、跑到可以展示效果,中间有大量的工程细节。
回头看,有几个决定性的选择算是走对的路:一是选了AirSim而不是Gazebo,渲染效果和API设计在视觉导航任务上确实省心;二是用了课程学习的思路,先学靠近目标再学避障,训练效率提升了非常多;三是状态空间用了相对坐标和归一化,泛化能力好了一大截。
如果说这篇文章里最有价值的经验,我排前三的是:奖励函数要设计成“有梯度”的形式,不要只给稀疏的最终奖励,否则训练基本上要跑到天荒地老;控制循环里一定要加入等待时间保证状态-动作对的正确对齐;以及训练过程要保留定期可视化评估的习惯,只看曲线很容易被表面的统计数据误导。
最后再分享一个有用的技巧:训练跑到后期,可以把探索率固定在一个很小的非零值,比如0.02,不让它衰减到零。这样做的好处是,即使模型已经学到了一套策略,它仍然会做少量随机尝试,偶然发现更好的路径。我实测下来,这种“永不停止的探索”让模型在训练后期还能缓慢稳定地提升性能,这是我比较推荐的小技巧。
这个项目的源代码、配置文件、训练曲线可视化工具已经整理成压缩包,整个项目的结构在压缩包的README里有清晰的说明。如果你也想做类似方向,建议直接拿这份代码跑通一次基础训练,先在仿真里感受一下整个流程,再根据自己的任务做修改。
本文还有配套的精品资源,点击获取