
简介面向无人艇USV自主避障研究的强化学习工程包集成深度Q网络DQN控制方案与无人艇、巡逻艇建模代码适合学习智能控制、路径规划的水上机器人研究者及高年级本科生。压缩包共12个文件以11个MATLAB脚本.m和1个数据文件.mat构成涵盖环境模拟、状态/动作空间设计、Q网络训练、经验回放、奖励计算及sim_RL主仿真脚本可端到端演示“建模—训练—避障决策”的完整流程。压缩包约24.53MB整体结构精练便于直接阅读和二次开发同时文件命名清晰目标Q值计算、奖励函数、船舶运动方程等模块相互独立方便针对性调试。目前已有326人学习浏览。通过该资源读者能掌握DQN在无人艇避障场景中的落地写法理解目标网络更新、ε-greedy策略等关键技巧并可基于USV动力学模型与预设数据快速复现仿真为后续扩展多障碍物环境或改进算法提供可运行基线。1. 拿到 USV_RL.7z 之后先读懂这个无人艇强化学习结构拿到这份 USV_RL.7z别急着在 MATLAB 里双击文件。这个 7z 压缩包是一个完整的无人艇USV避障强化学习工程入口在 sim_RL.m其余 .m 文件分别实现船体动力学、场景重置、奖励计算和目标 Q 值更新。核心方法是 DQN无人艇作为智能体通过反复试探获得碰撞、接近目标等反馈学出一套从状态到动作直航、左转、右转的映射。我习惯先执行7z l USV_RL.7z看文件列表再7z x USV_RL.7z解压如果要把改好的工程传给别人可以用7z a -p做加密压缩。这里的强化学习和语言模型里的 RL 不一样它没有 SFT 阶段产生的“标准答案”每一步奖励都来自仿真环境的碰撞、距离和到达检测本质上是无模型的序列决策问题。适合已经读过 DQN 理论、想自己手写训练循环而不想只调 MATLAB 内置 rlDQLAgent 的工程师。2. 建环境不只是写方程从 USV_Eqs.m 到 USVReset.m2.1 动力学模型里到底更新了哪些状态USV_Eqs.m 的输入与输出USV_Eqs.m 是环境里最“物理”的一部分。它负责在每次决策步后把当前状态推进一步。常见做法是采用水平面三自由度模型位置 (x, y)、艏向角 psi以及对应的线速度 u、v 和角速度 r。函数签名大概是function dstate USV_Eqs(t, state, thrust, rudder) % state [x; y; psi; u; v; r] x state(1); y state(2); psi state(3); u state(4); v state(5); r state(6); % 运动学地面坐标系下的速度 dx u * cos(psi) - v * sin(psi); dy u * sin(psi) v * cos(psi); dpsi r; % 动力学使用 USV_init_M.m 给出的惯性矩阵 M 和阻尼矩阵 D % 以下为典型数值实际项目在初始化脚本中通过 M、C、D 矩阵传入 m11 25.8; m22 33.8; m33 2.76; d11 0.72; d22 0.86; d33 1.38; du (m22 * v * r - d11 * u thrust) / m11; dv (-m22 * u * r - d22 * v) / m22; dr ((m22 - m11) * u * v - d33 * r rudder) / m33; dstate [dx; dy; dpsi; du; dv; dr]; end这段代码把state按位置、角度、速度的顺序排列输出是状态对时间的导数交给ode45或离散欧拉积分器推进。参数说明thrust是推进力对应动作空间里的“加速”rudder是转艏力矩对应“左转/右转”。在实际工程里USV_init_M.m不只是给一组常量而会读入船长、排水量和附加质量系数生成惯性矩阵 M、科氏向心力矩阵 C 和阻尼矩阵 D。不要跳过这一步DQN 对动力学差异很敏感同一个网络换一条艇的参数往往要重新训练。2.2 状态空间与动作空间划分DQN 要求状态是定长向量动作是有限离散集合。我从 USV_Eqs.m 的状态向量出发一般会在sim_RL.m里拼出这样的观测状态分量含义数值范围示例x, y无人艇在全局坐标系下的位置0 ~ 50 mpsi艏向角-pi ~ piu, v, r纵荡、横荡线速度和偏航角速度-2 ~ 2 m/s, -1 ~ 1 rad/sdist_goal与目标点的欧氏距离0 ~ 60 mbearing_goal目标相对艏向的方位角-pi ~ pidist_obs离最近障碍物的距离0 ~ 20 mobs_angle障碍物相对艏向的方位角-pi ~ pi动作空间我见过两种设计一种是三动作[-30, 0, 30]度舵角配合固定推进力另一种是五动作把左右舵都拆成“小幅度”和“大幅度”。优先级上动作数少训练更快但机动性差动作数多则容易在早期贪吃随机激励。对于这份工程的规模三动作足够验证避障能力。注意不要让网络直接输出连续舵角除非你改写成 DDPG 或 SAC否则离散 DQN 会直接发散。2.3 USVReset.m每次 episode 都换一个出生点USVReset.m 的作用是重置场景。如果每次训练都把无人艇放在同一点障碍物也在同一位置智能体很快就会记住一条固定轨迹而不是学会避障。常见做法是在可航行区域内随机化初始位置和艏向function [state, obstacle] USVReset() % 初始化无人艇位置在 0~10 米区域内随机出生 x0 rand * 10; y0 rand * 10; psi0 -pi/4 rand * pi/2; u0 0; v0 0; r0 0; % 障碍物位置随机半径 1.2~2.0 米 obstacle [15 rand * 20, 15 rand * 20, 1.2 rand * 0.8]; state [x0; y0; psi0; u0; v0; r0]; end这里rand生成的随机起点保证每轮 episode 不完全相同障碍物范围也限制了被随机到无人艇脸上的极端情况。注意重置时不要只随机坐标也要随机艏向角否则网络会把“出生时朝右”当成默认先验训练后期在相反方向的动作几乎学不到。还与 PointerMove.m 配合使用PointerMove.m 负责把当前(x, y, psi)画到演示界面上它不影响决策但我在调试时靠它判断船是否在绕圈。3. DQN 智能体从 AgentInitializer 到目标 Q 值计算的实现细节3.1 AgentInitializer.m 不复杂它只是把状态向量变成动作价值AgentInitializer.m 通常负责创建 Q 网络。这里的 Q 网络不是策略网络它输入状态输出每个离散动作的 Q 值估计。用 MATLAB 手写时可以这么看function agent AgentInitializer(stateDim, actionDim) % stateDim 由观测向量长度决定例如 2.2 节表格里的 10 % actionDim 对应离散动作数例如 3 layers [ featureInputLayer(stateDim, Normalization, none) fullyConnectedLayer(128) reluLayer fullyConnectedLayer(64) reluLayer fullyConnectedLayer(actionDim) ]; agent dlnetwork(layers); end参数说明featureInputLayer输入维度必须和USVReset返回的状态拼接结果一致。我遇到过只改了状态没改这里导致训练到一半维度报错的情况。中间层用 128 和 64 是训练稳定性和拟合能力的折中问题规模不大没必要堆到 512 以上数据量小的时候大网络只会让 Q 值抖动更剧烈。dlnetwork是 MATLAB Deep Learning Toolbox 里的网络容器也可以替换成network对象但dlnetwork对自动微分更友好训练循环里用dlgradient很方便。3.2 CalculationQtarget.m 里的目标值为什么要单独算DQN 有一个经典陷阱如果当前网络既负责选择最优动作、又负责计算目标值Q 值更新时会出现和当前参数“互相追逐”的情况。CalculationQtarget.m 就是解决这个问题的。它接收一批经验中的reward、nextState和done用目标网络计算下一状态的最大 Q 值function targetQ CalculationQtarget(reward, nextState, done, qNetwork, qTarget, gamma) % qNetwork : 在线网络用于选择下一步最优动作 % qTarget : 目标网络用于给出稳定的 Q 估计 % 注意这里使用 double DQN 的思想动作由在线网络选值由目标网络算 nextQ predict(qTarget, nextState); [maxQ, ~] max(nextQ, [], 1); targetQ reward gamma * maxQ .* (1 - done); end逻辑说明reward是环境返回的即时奖励gamma是折扣因子值越小越看重近期回报done是终止标记episode 结束时maxQ会被乘 0避免把终止状态后不存在的未来回报算进去。为什么这里多传一个qNetwork因为传统 DQN 用目标网络直接选动作而 double DQN 用在线网络选动作、目标网络估值能减少过估计。我这边的项目中qNetwork参数实际用于这一步所以即使只实现自然 DQN也建议在接口里保留。3.3 tcegreedy.m探索策略不是简单 epsilon-greedy动作选择策略在tcegreedy.m中实现。单看名字tce容易误解实际它是在 epsilon-greedy 基础上增加了一个类似温度退火的处理。每次决策时function action tcegreedy(qValues, epsilon, temperature) % qValues: 当前状态对应的 Q 值向量维度为 [actionDim, 1] if rand epsilon action randi(size(qValues, 1)); % 全局探索 else prob exp((qValues - max(qValues)) / temperature); prob prob / sum(prob); action randsample(size(qValues, 1), 1, true, prob); end end与只取argmax不同当temperature较大时即使选择利用也会以较大概率选中 Q 值第二、第三的动作。这个特性对避障很有用如果障碍物在正前方左转和右转的 Q 值接近简单 argmax 会因为网络噪声导致频繁换向加入温度后动作会更平滑。训练时epsilon从 0.9 衰减到 0.02temperature从 1.5 衰减到 0.3能让早期探索充分、后期决策更确定性。4. Reward_Cal、sig_t_Cal 与 sim_RL把奖励塑形落到训练循环4.1 避障奖励不能是一个大稀疏量Reward_Cal.m 写的是 DQN 的“价值观”。如果只在碰撞时给 -100、到达时给 100中间过程没有反馈稀疏奖励会让探索非常低效。我一般会把奖励拆成四项到达目标的正奖励、碰撞负奖励、每步时间惩罚、接近/远离障碍物的势场奖励。代码形式如下function r Reward_Cal(state, goal, obstacle, prevDistGoal) distObs norm(state(1:2) - obstacle(1:2)); distGoal norm(state(1:2) - goal); % 碰撞或接近危险半径 if distObs obstacle(3) 1.0 r -100; return; end % 到达目标 if distGoal 2.0 r 100; return; end % 势场项障碍物越近惩罚越大向目标靠近给予正向增量 obsPenalty 0.8 / (distObs 0.1); goalReward 0.3 * (prevDistGoal - distGoal); r -0.02 - obsPenalty goalReward; end逻辑说明prevDistGoal是上一时刻到目标的距离当前时刻比上一时刻近则goalReward为正这个稠密信号能引导船走直线distObs 0.1防止除零。注意obsPenalty的系数不能太大否则船会在离障碍物很远的地方就开始绕路行为会变成“恐惧一切目标点附近的东西”。我在调参时会把三项打印出来观察日志里r的数量级是否稳定在 -1 到 1 之间如果绝对值全部大于 10说明奖励尺度不对训练会很难收敛。4.2 sig_t_Cal.m 和 Fuzzy_set.m时间感知与模糊分段sig_t_Cal.m 看起来是在计算一个随时间变化的信号常见做法是让奖励惩罚在 episode 后期逐渐增强。比如训练早期允许船在目标附近“磨蹭”后期则越拖越扣分。代码可等价于function sig sig_t_Cal(t, maxStep) % t 为当前 episode 内步数 % sig 在 0.1 到 1 之间变化 sig 0.1 0.9 * t / maxStep; end这个sig乘到障碍物惩罚项上可以让船在 episode 最后阶段宁可稍微绕远也要先避开障碍。Fuzzy_set.m 则是把连续状态模糊成安全等级我见过它是把障碍物距离分成“近、中、远”三段输出一个权重。可以理解为function w Fuzzy_set(distObs) if distObs 15 w 0.1; % 远不着急 elseif distObs 6 w 0.6; % 中开始警觉 else w 1.2; % 近优先级拉满 end逻辑说明这个权重和distObs的原始值一起决定了避障的急迫程度。比纯线性惩罚更接近人类操舵习惯距离很远时不额外干预进入危险半径后再强烈反应。这样设置能让 Q 值在安全区域保持在较低水平避免网络因为处处都有惩罚而学到“停在原地”。4.3 sim_RL.m 主循环里的经验回放与超参数sim_RL.m 是训练主脚本它的关键不是调用 DQN 工具箱而是你自己维护经验池和目标网络拷贝。经验回放的顺序会影响稳定我一般把“采样—计算 target—更新”缩到一段循环里% 经验回放采样 batchSize 64; expIdx randi(bufferSize, batchSize, 1); sBatch replayBuffer_s(:, expIdx); aBatch replayBuffer_a(:, expIdx); rBatch replayBuffer_r(:, expIdx); sNextBatch replayBuffer_s2(:, expIdx); doneBatch replayBuffer_d(:, expIdx); % 计算目标 Q 值 targetQ CalculationQtarget(rBatch, sNextBatch, doneBatch, qNetwork, qTarget, gamma); % 使用在线网络预测当前 Q只更新选中的动作 qPred predict(qNetwork, sBatch); for i 1:batchSize qPred(aBatch(i), i) targetQ(i); % 仅让被采样动作的 Q 逼近目标 end参数说明bufferSize我常设为 50000 或 100000太小会让样本相关性高训练像过拟合单条轨迹。batchSize一般为 32 到 64太大对 MATLAB 的循环开销高太小则梯度方向噪声大。gamma设为 0.95因为无人机场景每步时间很短未来几步状态对当前决策影响明显。目标网络更新频率我一般每 500 步同步一次太频繁等于没有目标网络太久则目标值长期失真。下表是常用的初始参数参考值参数含义建议值maxEpisodes最大训练回合数2000maxStep每回合最大步数300epsilon_start探索率初值0.9epsilon_end探索率最低值0.02epsilonDecay探索率衰减系数0.995temperaturetcegreedy 温度初值1.5gamma折扣因子0.95targetUpdateFreq目标网络同步步数5005. 加载 20210802.mat 验证真实避障效果以及几个容易翻车的训练细节5.1 从 .mat 恢复智能体并跑一次确定性评估20210802.mat这个文件是训练好的网络权重快照文件名日期代表保存时间。验证时不能直接拿 sim_RL 继续训练而是加载权重后关闭探索。在 MATLAB 命令窗口可以这样做data load(20210802.mat); agent data.agent; % 变量名以实际保存为准可能是 qNetwork currentState USVReset(); epsilon 0; % 关闭探索 temperature 0.1; % 尽量选取 argmax for t 1:maxStep qValues predict(agent, dlarray(currentState, CB)); action tcegreedy(qValues, epsilon, temperature); nextState stepEnvironment(currentState, action); % 按 USV_Eqs 推进 currentState nextState; PointerMove(currentState); % 可视化 end说明CB是给dlarray指定维度标签C 是通道B 是 batchMATLAB 的predict需要这个格式。验证时要看的是船能否在给定起点和障碍物下连续多轮成功到达目标而不是只看一局。我习惯跑 50 个随机起点统计成功率和平均路径长度只有成功率超过 80% 才认为训练完成。如果加载后发现船直接撞障碍大概率是保存时只存了qTarget而没有把在线网络同步进去。5.2 训练不收敛时先检查这几个点第一奖励尺度。碰撞给 -100、到达给 100 时中间项很容易被淹没。把日志里每回合总奖励打印出来观察曲线是否逐渐上升如果长期在 -1000 附近抖动先减小碰撞惩罚。第二目标网络更新频率太高。我见过有人每步都同步结果 Q 值还是震荡改成 500 步同步后会平稳很多。第三随机重置不能省。USVReset 里的随机初始位置和障碍物是训练稳定的关键如果环境完全没有随机性DQN 会对初始位置过拟合。第四解压 7z 后路径不能有中文。MATLAB 在含中文路径下加载 .mat 和脚本编码可能异常建议把目录改成纯英文。第五检查tcegreedy的返回值维度。randsample默认返回列向量后续索引会让qPred行索引混乱必要时用(:)转成标量。以上每一项我都踩过按排除法查下来比盲目调学习率快得多。本文还有配套的精品资源点击获取