
做相控阵和雷达方向图设计的同志应该都有体会稀疏阵列的位置优化是个让人又爱又恨的问题。爱是因为它确实能在阵元数量不变的情况下大幅压低峰值旁瓣电平恨是因为传统的全局优化算法——不管是遗传算法还是粒子群——跑起来慢不说还容易陷入局部最优。最近我把双深度Q网络DDQN用在了稀疏圆阵的非均匀阵列位置优化上用MATLAB把整套仿真跑通了效果比预期好不少。这篇文章就把整个项目的建模思路、网络设计、MATLAB工程实现和调试过程完整分享一下给做阵列优化的同行一个可参考的实践方案。这个项目解决的是一个很具体的工程问题在圆阵孔径固定、阵元数量固定的前提下通过优化阵元在圆周上的非均匀分布位置使阵列方向图的峰值旁瓣电平PSLL尽可能低。它适合雷达、通信、声呐系统里做阵列设计的工程师也适合正在学强化学习和智能优化算法的研究生。整套仿真完全在MATLAB里完成不需要GPUCPU跑十几分钟就能看到收敛趋势门槛不算高但背后涉及的建模细节和调参经验确实不少。下面我从头到尾把方案讲清楚。1. 项目整体设计与思路拆解1.1 问题的数学本质从均匀圆阵到稀疏非均匀圆阵均匀圆形阵列UCA有个很直观的问题阵元在圆周上等间隔排布布阵简单但方向图旁瓣水平是固定死的。以8元均匀圆阵为例半径取1.5个波长时方位面方向图的峰值旁瓣大概在-8dB上下这个水平在很多低截获或者抗干扰场景里是不够用的。要想继续压低旁瓣常规思路是增加阵元数量但工程上孔径、通道数、成本全是约束这条路经常走不通。稀疏圆阵的思路就变成我不让阵元均匀占满圆周而是把阵元放到一组候选位置中的某些位置上让阵元之间的间距出现冗余通过位置的非均匀分布来破坏方向图叠加时的同相条件把能量从旁瓣区域摊薄或者转移到主瓣附近。这个问题的本质是一个组合优化问题如果圆周上一共有N个候选格点要从里面选出K个位置放阵元候选组合数是C(N,K)。N等于24、K等于8的时候组合数已经到73万量级而每一次组合都要算一遍方向图暴力枚举完全不现实。半径的选择也值得说一嘴。半径太小阵元之间的空间耦合严重而且波束宽度太宽稀疏优化的收益不明显半径太大均匀阵列会出现栅瓣旁瓣周期性抬高。经验上半径取1到2个波长之间比较合适。非均匀分布本身对栅瓣有一定的抑制能力因为它把阵元间距打乱了栅瓣能量不再集中到少数几个方向上这也是这个题目特别适合用稀疏非均匀方式做的原因之一。1.2 为什么选DDQN对比遗传算法、粒子群与普通DQN在做这个项目之前我先用遗传算法GA跑过一轮对比。GA在阵列位置优化上确实是老牌选手经典论文里经常看到但它有两个让我不太舒服的地方一是每一代都要维护一个种群每个个体都要算一遍方向图计算量很大二是收敛结果对初始种群、交叉概率、变异概率这些参数非常敏感换一个阵元数就得重新调一遍。粒子群PSO也类似迭代后期容易聚到一个局部解上早熟问题严重。这也是我转用强化学习的核心原因强化学习学出来的不是当前这组位置本身而是在给定阵列布局状态下应该往哪个方向调整位置的操作策略。策略网络训练好之后对同口径、同类型的阵列优化问题有迁移能力换个阵元数或者换一组随机初值策略可以快速给出一组合理的位置结果不需要从头跑完整的优化流程。这一点对工程上反复迭代阵列方案非常有用。至于为什么用DDQN而不是普通DQN这个要说到DQN的过估计问题。DQN在计算目标Q值时用了求最大值的操作而神经网络拟合误差不可能为零对最大值取期望会系统性高估真实Q值。在阵列优化这种奖励数值差异并不大的场景里过估计会直接导致算法把旁瓣降低一点误判成降低很多最后收敛出来的位置布局根本不够好。DDQN的做法是把动作选择和价值评估两个环节拆开用评估网络选择最优动作用目标网络计算该动作对应的价值从机制上避免过估计被不断放大。这部分到第三章我会详细展开。1.3 系统流程与模块划分整个仿真系统在功能上拆成三个模块阵列方向图计算模块、强化学习环境模块、DDQN训练模块。阵列方向图计算模块负责输入阵元位置坐标输出归一化方向图和PSLL是整条链路里计算量最大的部分所以我在实现的时候用矩阵运算做了向量化没有写for循环逐点计算。强化学习环境模块负责维护当前阵列状态、执行动作、计算奖励、返回下一状态相当于把稀疏阵列位置优化问题包装成了一个标准的强化学习环境。DDQN训练模块负责网络定义、经验回放、ε-greedy探索、目标网络更新和训练过程记录。这样分层有一个明显好处如果以后想把DDQN换成PPO、SAC或者其他算法只需要把第三个模块换掉前面两个模块完全不用动。代码层面我用的是MATLAB脚本加函数的方式组织主程序只管调用每个模块单独成函数调试时可以在命令行单独测某一个层非常方便。还有一个附加好处是方向图计算函数在GA、PSO等其他算法里也能复用做对比实验时不用重复写底层代码。2. 稀疏圆阵方向图建模与强化学习要素设计2.1 圆阵阵因子与峰值旁瓣电平计算先把圆阵的几何模型定下来。假设有一个半径为r的圆环布阵平面在xOy平面观察方向限制在xOy平面内也就是只看方位面方向图方位角记为φ。如果第i个阵元所在的方位角是φi那么这个阵元的位置向量就是(r·cosφi, r·sinφi)。对窄带信号阵因子可以写成AF(φ) Σ wi · exp[j·k·r·cos(φ − φi)]其中k 2π/λ是波数wi是第i个阵元的幅度加权。在位置优化阶段通常先假设等幅激励也就是wi1这样方向图完全由阵元位置决定。归一化方向图是F(φ) |AF(φ)| / max|AF(φ)|再转到dB刻度也就是20·log10|F(φ)|。峰值旁瓣电平PSLL的定义是在主瓣区域以外的角度范围内方向图dB值的最大值。这里有一个必须统一的细节主瓣区域的宽度要怎么界定。我一般先找到峰值位置然后向两侧扩展直到方向图第一次降到峰值以下某个阈值的位置再向外扩一点保护带这个区间内的方向图在统计PSLL时排除掉。做所有对比实验时主瓣区域的定义必须一致否则不同算法算出来的PSLL没有可比性。再说说为什么非均匀分布能压低旁瓣。均匀阵列的阵元间距固定方向图叠加时会有一部分分量在特定方向上同相叠加形成较高的栅瓣或者旁瓣。把阵元间距随机化之后各个阵元辐射分量在旁瓣区域很难再次同相叠加能量被分散到更大的角度范围里所以旁瓣峰值会明显下降。这也是稀疏阵列位置优化的物理直觉通过扰乱阵元间距的周期性换取更低的旁瓣水平代价是阵元位置不再整齐工程安装时需要单独设计结构件。2.2 MDP四要素状态、动作、奖励、转移把组合优化问题包装成马尔可夫决策过程MDP是整个项目里最需要动脑筋的一步。我的建模方式如下。状态把圆周均匀划分成N个候选格点用一个长度为N的0/1向量表示当前阵列布局1表示该格点上放了阵元0表示空位。阵元总数K固定所以状态向量中1的数量恒为K。这种编码方式直接、可解释性强网络输入维度就是N维不需要额外做特征工程。我这里N取24K取8。动作为了让阵元数守恒动作定义为把一个阵元从一个候选格点移动到另一个空置格点。如果当前有K个阵元、N-K个空位那么合法动作数是K×(N-K)个。每个动作可以给一个全局编号。由于不同状态的合法动作集合不一样我在网络输出层后面加了一个Mask操作把所有非法动作对应的Q值置为负无穷这样无论是ε-greedy采样还是argmax选择都不会选到非法动作。奖励我试验过两种奖励。第一种是稀疏奖励只有到达终止状态才给奖励奖励值等于当前PSLL的负值第二种是密集奖励每一步移动后都计算PSLL变化量ΔPSLL PSLL_new - PSLL_old然后奖励取-ΔPSLL也就是旁瓣下降给正奖励、旁瓣抬升给负奖励。实测下来密集奖励的收敛速度快得多因为每一步移动的效果立刻反馈给了网络梯度信号更充分。转移采用固定步数的episode设计每个episode最多执行L步到达步数上限或者方向图性能连续多步没有改善时截断。初始状态我试验过两种一种是完全随机的稀疏布局另一种是从均匀圆阵出发做少量随机扰动。随机初始化的好处是探索范围大缺点是训练前期总是在乱调所以我干脆在前几个episode用比较大的ε强制探索后面的episode再慢慢降下来。2.3 奖励设计里的两个关键选择第一个关键选择是奖励稀疏度。我一开始用的是稀疏奖励结果训练了一两百个episode网络基本没有学到任何结构损失函数一直在震荡位置布局改善很不明显。后来改成密集奖励大概几十个episode就出现了PSLL明显下降的趋势。这个经验我觉得可以复用对于阵列位置优化这种每一步都是局部小调整的决策过程用相邻两步PSLL的差值作为即时奖励比在最终状态才给奖励有效得多。稀疏奖励更适合走到终点才见分晓的任务不适合这种逐步优化的任务。第二个关键选择是奖励要不要做归一化。PSLL的数值范围一般在-20dB到0dB之间而神经网络对输入输出的尺度很敏感。我把奖励缩放到[-1,1]区间r -ΔPSLL / 10这样训练的时候学习率不用调得很小收敛也更稳。这里有个绕不开的成本问题方向图计算本身是一个慢过程如果每一步都用PSLL差值做奖励那每执行一步动作就要重算一次方向图。所以方向图计算函数必须写成向量化形式不能天真地逐点嵌套循环否则训练时间会翻几十倍。我第一次写的时候没注意这一点一个episode跑下来慢到怀疑人生后来把方向图函数改成矩阵运算才把单次方向图计算压到毫秒级。3. DDQN算法原理与网络结构3.1 DQN过估计问题是怎么产生的要理解DDQN先要搞清楚DQN到底哪里出了问题。DQN的目标值公式是y r γ · max_a Q_target(s, a; θ⁻)这个求max的操作在数学期望意义下会对真实Q值产生正向偏置。打个比方你手里有一堆股票的估计收益每只股票的真实收益都是0但你的评估方法有随机误差有的高估有的低估。如果你每次都按最大估计值去买长期下来你一定会觉得自己买的股票都是赚钱的但实际收益其实接近0。动作选择越多这个偏差被放得越大。放在阵列优化场景里这种现象表现得很隐蔽训练过程中你会看到Q值曲线一直在涨但实际算出来的PSLL并没有同步改善。有一次训练到后期网络输出的Q值已经到十几可是我拿当前策略去仿真验证PSLL还是只有-9dB左右这时候基本可以断定过估计已经严重影响策略质量了。如果继续用这种带偏的目标值训练策略会越来越倾向于选择那些被高估的动作最后收敛出来的位置布局很可能是错的。3.2 DDQN如何解耦动作选择与价值评估DDQN的核心修改只有一处目标值计算时不再用目标网络直接对下一状态求max而是先用评估网络选出最优动作再用目标网络计算这个动作的Q值。公式如下a* argmax_a Q_main(s, a; θ) y r γ · Q_target(s, a*; θ⁻)换成文字描述就是评估网络负责选动作目标网络负责给这个动作打分。因为动作选择权交给了评估网络目标网络只做评估所以当评估网络因为拟合误差高估了某个动作时目标网络给它的分数不一定跟着高估这样就切断了选择偏差直接进入目标值链路的通路。数学上可以证明DDQN得到的Q值估计虽然还是有偏但偏置方向是可控的。在实际工程里最能感受到的差异是DDQN训练出来的Q值曲线更平稳损失函数不会出现突然爆掉的情况最终策略的质量也更稳。在MATLAB里实现DDQN我没有用强化学习工具箱因为状态转移、奖励计算、动作掩码这些都是自定义的用工具箱反而要花大量时间适配环境接口。我的做法是直接用深度学习工具箱定义两个结构相同的多层感知机用dlnetwork构建网络训练时用adamupdate做参数更新。这样代码虽然多写几行但每一步做什么完全可控排查问题也更直接。3.3 神经网络结构与训练超参数网络结构不复杂我用的是三层全连接网络输入层维度是N也就是候选格点数这里N24第一个隐藏层128个神经元第二个隐藏层64个神经元激活函数都用ReLU输出层维度是动作空间大小也就是K×(N-K)K8时输出维度为128。这里没有用卷积网络因为状态是稀疏0/1序列没有图像那种局部平移不变性全连接网络直接建模各候选位置之间的组合关系效果更好。训练超参数这里列一组我最终定下来的值学习率1e-4用Adam优化器经验回放池容量5000条每次训练从池里随机采样256条目标网络每200步同步一次ε从1.0线性衰减到0.05衰减步数4000步。这些参数不是一次调出来的是我跑了三轮对比之后确定的相对稳定的一组值。后面我还会给一张完整的参数速查表方便直接做参考。4. MATLAB仿真实现与技术细节4.1 工程目录结构与初始化我的工程目录结构很简单一共四个文件加一个结果文件夹sparse_circle_ddqn/ ├─ main_sparse_circle_ddqn.m % 主程序控制整个训练流程 ├─ array_factor.m % 方向图计算函数 ├─ compute_psll.m % 峰值旁瓣电平计算函数 ├─ ddqn_agent.m % DDQN智能体类 └─ results/ % 输出方向图与训练曲线我把智能体封装成了一个MATLAB类这样评估网络参数、目标网络参数、回放池、优化器状态都可以放在类的属性里主程序调用起来非常干净。MATLAB从R2020a开始对面向对象支持得挺好如果您习惯纯函数式组织也可以但类的方式确实让训练循环更好读。初始化时要注意几个全局参数波长、圆阵半径、候选格点数、阵元数、每个episode最大步数、目标网络同步间隔等。我建议把这类参数集中放在主程序开头的参数区方便后续批量跑对比实验。这次用的MATLAB版本是R2023a深度学习工具箱是必需的其他工具箱没有依赖。4.2 核心函数方向图计算与PSLL方向图计算函数是整条链路的基础我给出了完整代码。核心逻辑是先把阵元方位角转成坐标再用矩阵乘法一次性算出所有观察方向上的阵因子function AF array_factor(theta_el, phi_az, phi_elem, r, lambda) % theta_el: 俯仰角向量(弧度)这里固定为pi/2表示方位面 % phi_az: 方位角采样向量(弧度) % phi_elem: 阵元方位角向量(弧度) % r: 圆阵半径 % lambda: 波长 k 2*pi/lambda; % 阵元坐标 x r*cos(phi_elem); y r*sin(phi_elem); % 观察方向单位向量 u [cos(phi_az(:)).*sin(theta_el); sin(phi_az(:)).*sin(theta_el); cos(theta_el)*ones(size(phi_az(:)))]; % 阵元位置矩阵 pos [x(:), y(:), zeros(size(x(:)))]; % 阵因子AF(phi) sum_i exp(j*k*(u*pos_i)) phase 2*pi/lambda * (pos * u); % K x length(phi) AF sum(exp(1j*phase), 1); endPSLL的计算函数我单独写了一个逻辑是先把方向图转成dB再取旁瓣区域的最大值function psll_db compute_psll(AF_db, mainlobe_idx) % AF_db: 归一化方向图dB值1 x length(phi) % mainlobe_idx: 主瓣区域对应的索引范围 sidelobe_idx setdiff(1:length(AF_db), mainlobe_idx); if isempty(sidelobe_idx) psll_db -100; else psll_db max(AF_db(sidelobe_idx)); end end主瓣区域的确定方法我用的是找到峰值位置之后向两侧延伸直到方向图第一次低于峰值-3dB的位置再向外扩展一排角度作为保护带。这个保护带范围内的方向图在统计PSLL时直接排除避免主瓣区域被误判成旁瓣。主瓣宽度的选择对PSLL数值影响很大所以对比实验里必须统一。4.3 DDQN训练主循环实现训练主循环是整个项目的心脏。下面这段代码展示了一个episode内部的完整交互过程for episode 1:max_episodes state env_reset(); % 随机生成满足阵元数约束的初始0/1向量 for step 1:max_steps % epsilon-greedy选择动作 if rand() epsilon a randi(num_actions); else qv predict(main_net, state); qv mask_invalid_actions(qv, state); % 非法动作置-Inf [~, a] max(qv); end [next_state, reward, done] env_step(state, a); store_experience(state, a, reward, next_state, done); % 从回放池采样训练 if mod(global_step, train_interval) 0 batch sample_replay_buffer(batch_size); update_main_network(batch); end % 定期硬同步目标网络 if mod(global_step, target_update_interval) 0 assign(target_net, main_net); end state next_state; global_step global_step 1; if done break; end end epsilon max(0.05, epsilon * epsilon_decay); % 记录每个episode的最优PSLL record_best_psll(state); end几个实现要点。第一env_step里面执行动作后要立刻重算方向图和奖励这部分最耗时所以方向图计算必须用矩阵运算。第二state虽然是0/1向量但送到网络里之前要转成单列double类型我是用dlarray包装了一下便于调用深度学习函数。第三Mask操作不能原地修改网络输出的Q向量而是生成一个临时加偏置的向量否则会影响后续梯度的计算这个细节很多人会踩坑。4.4 训练过程监控与结果可视化训练过程中我实时记录了三个量当前episode最好PSLL、评估网络的平均Q值、目标网络更新次数。训练结束后我把最优布局下的方向图与均匀圆阵、随机稀疏圆阵的方向图画在同一张图里做对比。下面是我实验中的一组代表性结果布阵方式PSLL备注8元均匀圆阵半径1.5λ约-7.9dB作为基准参考随机稀疏布阵24选8约-4.6dB随机布局旁瓣反而升高GA优化结果约-16.5dB耗时约14分钟DDQN优化结果约-17.3dB训练约9分钟DDQN的最终PSLL比GA还低了0.8dB左右训练时间也更短。更重要的是把训练好的策略拿去初始化另一个半径略小的圆阵优化任务只做了200步微调就达到了不错的效果这种迁移能力是GA不具备的也是我最看重DDQN方案的地方。训练曲线方面PSLL曲线在前几十个episode下降最快后面进入缓慢优化阶段这是正常的不必因为后期收敛慢就调大学习率。5. 训练调试实录与常见问题排查5.1 训练不收敛的表现与排查我踩过的第一个坑是Q值持续增长但PSLL不动。这个问题在DQN系列算法里很典型原因多半是过估计解决办法就是换成DDQN或者检查目标网络更新频率是不是太快。如果目标网络每50步就同步一次等于把评估网络的偏置也快速传染给了目标网络DDQN的优势就消失了。我在实验里把同步间隔定在200到500步之间。第二个坑是训练前期PSLL不仅没降反而比随机初始状态还高。排查后发现是ε衰减太快探索不足智能体早早进入了贪心阶段只顾着在已经发现的几条动作路径上反复走没有继续探索更优的位置组合。把ε从0.9衰减到0.05的步数拉到4000步之后情况明显改善。第三个坑是奖励计算里的方向图重算问题。因为每次只移动一个阵元理论上可以做增量更新但MATLAB里数组索引操作其实很快整体重算比手动增量更新更不容易出错。我一度为了省时间写了一个增量更新版本结果边界条件没处理好导致奖励偶尔算错整个训练被带偏。后来我全部改成整体重算用矩阵运算把单次方向图计算控制在毫秒级反而更省心。5.2 Q值爆炸与数值稳定性问题Q值爆炸是我前期调参时遇到的最头疼的问题表现为Q值从个位数突然跳到几千随后损失函数变成NaN。逐一排查后确认是两个原因叠加一是奖励没有归一化PSLL差值最大可以到十几dB乘以折扣因子γ累加之后Q值很容易被放大二是学习率偏高1e-3对这个问题来说太大了。把奖励归一化到±1量级、学习率降到1e-4之后Q值曲线稳定多了。另外一个数值细节方向图计算用exp(1j*phase)时phase单位必须是弧度MATLAB默认三角函数用弧度但很多初学者会习惯性用角度导致阵因子完全错误方向图看起来是乱的。我把角度采样统一从度转弧度在所有函数入口处检查一遍单位这类低级错误就不容易混进调试环节。5.3 超参数速查表我把一组能稳定复现结果的超参数整理成表方便直接参考参数建议值说明候选格点数 N24兼顾搜索空间规模与计算速度阵元数 K8大约为候选格点数的1/3稀疏度适中圆阵半径 r1.5λ半径太大会出现栅瓣太小则波束太宽episode最大步数50让策略有足够时间完成多轮调整经验回放池容量5000太大训练慢太小样本相关性高batch size256影响梯度稳定性学习率1e-4对应Adam优化器目标网络同步间隔200步硬更新频率ε初始值/终值1.0 / 0.05线性衰减4000步奖励缩放/10将PSLL差值映射到±1量级这些参数在我的项目里是稳定的。如果您换了阵元数或者半径优先调整的是候选格点数和动作空间规模学习率这类训练参数通常不需要动太多。我个人在实际操作中最深的体会是这类组合优化加强化学习的项目真正的瓶颈往往不是模型写不出来而是状态、动作、奖励与问题本身的映射关系设计得是否合理。DDQN只是一个改进后的Q学习框架它的上限很大程度取决于你把稀疏圆阵位置优化表达成MDP的方式。比如奖励用PSLL差值还是稀疏奖励训练效果相差巨大状态用0/1编码还是连续坐标网络学习难度也完全不同。建议大家在动手写代码前先花一两天把建模部分想清楚多跑几个小规模对比实验验证建模方向再进入完整训练。最后再分享一个小技巧我在训练结束后会把最优布局保存成结构体数组包括阵元方位角、PSLL、训练过程曲线等字段后续做批处理对比实验非常方便。把这个项目从GA换成DDQN之后我不但拿到了更低的旁瓣数值还拿到了一个能在同类阵列设计问题上反复使用的策略网络这是传统启发式算法带不来的价值。