☰
基于DQN的无人机上行NOMA功率分配与干扰管理实战解析
2026/10/20 2:40:00 网站建设 项目流程

最近在折腾一个通信和深度学习交叉的课题:用深度Q网络(DQN)控制无人机上行链路场景里的NOMA功率分配,目标是做干扰管理。这个题目前前后后跑了小两个月,踩了不少坑,也把整套流程理顺了——从通信系统建模到PyTorch的DQN实现,再到训练调参,每一步都有值得记录的细节。这篇就从头到尾梳理一遍,包括关键公式的来龙去脉、代码结构、网络设计和训练过程中真正影响收敛的细节。如果你是研究生或者刚接触深度强化学习在无线通信里应用的同学,这篇应该能帮你省下不少试错时间。

1. 项目背景与问题定位

1.1 无人机NOMA场景里的干扰到底从哪来

先把这个场景说清楚。想象一架无人机充当空中基站,地面有几个用户同时往无人机发送数据。传统做法是给每个用户分配独立的时频资源,也就是正交多址接入(OMA),彼此不干扰,实现简单但频谱效率偏低。而NOMA的核心思路是:让所有用户在同一个资源块上叠加传输,然后用功率差来区分信号。接收端通过串行干扰消除(SIC)依次解码,这就需要在发射端把不同用户的功率拉开差距——功率强的用户先被解出来,解完从混合信号里减去,再解功率弱的用户,依次往下推。

问题就出在这里。上行链路里每个用户的信号经过不同的信道衰落到达无人机,如果功率分配策略不合理,两个用户的到达功率太接近,SIC解码时就会出现严重的差错传播——解错了第一个用户,后面所有用户跟着完蛋。更麻烦的是,无人机作为空中基站,对地链路是LoS主导的,信道变化剧烈,用户位置一变,最优功率分配立刻就不一样了。这就是"干扰管理"要处理的核心矛盾:在动态信道条件下,实时决定每个用户该用多大功率发射,既保证无人机能正确SIC解码,又让系统总吞吐量尽量高。

1.2 为什么传统优化方案在这个场景里不好使

刚开始按老思路做,想用凸优化硬解。但把问题列出来就发现:功率分配的目标函数包含SIC解码的SINR表达式,每个用户的速率是log2(1+SINR),SINR里又嵌套了多个用户的功率变量——这明显是非凸的。虽然可以用连续凸近似(SCA)或者分数规划去逼近,但每算一步都要做多轮迭代,无人机平台本身计算资源和续航有限,根本扛不住这么重的实时计算。

还有个更要命的问题:传统优化需要精确的信道状态信息(CSI)。无人机和地面用户之间的信道是快变的,等你把CSI测量完、优化算完、功率指令发下去,信道早就变了。手里的最优解是上一刻的,适用性大打折扣。

所以思路就转向了强化学习这条路线。DQN的特点是通过离线训练拟合出一个从"信道观测状态"直接映射到"功率动作"的策略网络,训练好的网络在线上做一次前向推理,微秒级就能给出结果,而且不需要精确的模型先验——你只管喂状态给网络,它自己学映射关系。这正是动态场景下做干扰管理更需要的东西。

1.3 深度强化学习和NOMA结合的总体思路

整体技术路线可以概括成三步:

  • 把无人机上行链路NOMA系统写成可交互的仿真环境,环境的核心逻辑是:根据当前信道状态和用户发射功率,计算SIC解码是否成功、每个用户的可达速率是多少;
  • 把干扰管理问题翻译成马尔可夫决策过程(MDP)。状态设为各用户的信道增益和上一时刻的动作,动作设为所有用户的离散功率组合,奖励设为SIC解码成功时的系统加权速率;
  • 用PyTorch实现DQN,让智能体在仿真环境里反复试错,通过经验回放和时序差分学习逼近最优Q函数。训练完成后,把网络部署到"无人机决策模块",实时输出功率分配结果。

这个思路的核心优势在于:一旦训练收敛,SIC解码的模糊规则、信道变化的统计规律都被隐式编码在神经网络权重里了。在线阶段你不需要再关心复杂的信道模型细节,一个前向传播就能拿到可用的功率决策——这在工程上是非常有吸引力的。接下来的几节按实际开发顺序展开,先说系统模型怎么搭,再讲DQN的关键组件,然后是完整的代码实现和调参过程。

2. 系统建模:把通信问题翻译成强化学习能懂的语言

2.1 场景参数与信道模型设定

我用的场景是经典的单无人机多用户上行链路模型。无人机悬停在某个固定高度,覆盖范围内随机分布K个地面用户。仿真参数可以按这套来:用户数K=4,无人机高度120米,覆盖半径300米,用户均匀撒在地面上。系统带宽1MHz,噪声功率谱密度-174dBm/Hz,加上接收机噪声系数5dB,综合噪声功率算下来大约是2.51×10⁻¹³ W量级。

信道模型这一块值得多说两句。无人机对地信道和地面蜂窝信道不一样,LoS概率很高,但又不是纯LoS。如果完全用自由空间路径损耗,仿真结果会偏乐观;如果完全用瑞利衰落,又忽略了无人机LoS主导的特性。交易所工程师的惯用做法是用"概率LoS模型"——根据无人机到用户之间的仰角和距离,计算LoS概率,LoS和NLoS分别用不同的路径损耗指数。不过在我的实现里第一版做了简化:用自由空间路径损耗加上10dB的对数正态阴影衰落。原因是DQN训练阶段状态量一定要稳定,如果用随机衰落每种姿态重新采样,智能体很难学习,因为同一状态对应的最优动作不断变化。简化后每个episode里大尺度衰落固定,小尺度扰动控制在合理范围内,训练收敛明显变快。

2.2 NOMA上行链路信号与干扰建模

模型搭好之后信号关系是这样的:假设有K个用户,第k个用户到无人机的信道系数记为h_k,发射功率为P_k,发射符号为x_k。无人机天线接收到的叠加信号就是:

y = Σ √(P_k) h_k x_k + n

其中n是均值为0、方差为σ²的高斯白噪声。

NOMA的关键在于SIC解码顺序。上行链路的SIC解码一般按信道增益从强到弱排序。信道最强的用户最先解码,因为它最容易被解出来;解完从y里减去它的贡献,再解下一个,直到所有用户都被恢复。

这里要特别注意一个细节:在解码第k个用户时,信道比它弱的用户信号还没被消除,会作为干扰叠加进来;而信道比它强的用户已经解完了,干净消除。所以第k个用户的SINR表达式是:

SINR_k = P_k |h_k|² / (Σ_{j>k} P_j |h_j|² + σ²)

其中求和号里是信道弱于第k个用户的所有用户。这个表达式是整个系统模型里最重要的一个公式——奖励函数、SIC成功判断、速率计算全都要靠它。写代码的时候建议单独封装一个函数,避免项目大了在多个地方重复计算导致口径不一致。

2.3 干扰管理目标函数与约束条件

目标函数我定义为系统效用最大化,但不是单纯最大化所有用户速率之和,而是带权重的。原因很简单:如果只最大化总速率,DQN有可能会把某些用户的功率压到极低,优先保障强用户,导致弱用户饿死。这在通信系统里是不可接受的,毕竟用户被调度进来就该有基本服务质量。

所以目标函数写成:

max Σ (w_k · R_k)

其中R_k = B · log2(1 + SINR_k)是第k个用户的可达速率,w_k是业务优先级权重。约束有两类:一是每个用户的最大发射功率不能超过P_max(我这里设的是0.2W,大约23dBm);二是每个用户的SINR不能低于预设门限γ_th(设为3dB),低于门限就判定该用户通信中断,这一轮动作直接给大惩罚。

把目标函数翻译成DQN的奖励函数时,我来回调整过好多次,这部分后面单独聊。总之模型层面定好了:动作给一组功率向量,环境算每个用户的SINR,然后算速率、算惩罚、返回奖励。通信问题到这一步已经被完整封装成了环境的输入输出接口。

3. DQN核心组件设计:状态、动作、奖励与网络结构

3.1 状态空间设计

DQN的状态(state)设计决定了智能体"看到什么"。第一版我贪多,把信道增益、用户位置、上一时隙功率、信噪比全部塞进状态向量,结果训练效果反而不好。原因很容易理解:状态维度高,网络需要大量样本才能拟合,样本量有限时反而学不到有效映射。

最后精简成三块信息,状态向量维度是2K+1:

  • 各用户到无人机的信道增益归一化值(K维);
  • 上一时隙各用户的实际发射功率归一化值(K维);
  • 当前系统的总干扰功率归一化值(1维),即所有非目标信号功率之和的近似度量。

归一化非常重要,我单独提出来强调一下。信道增益的数值范围可能跨越5到6个数量级,不归一化直接输入全连接网络,梯度很容易被大数值维度主导,小数值维度直接废掉。我的做法是把信道增益除以参考值,再经过一次log压缩,把动态范围压到大约[-5, 5]区间;功率则除以P_max映射到[0,1]。

3.2 动作空间设计:离散功率组合

动作空间是DQN设计里比较棘手的一个点。理论上功率是连续变量,但经典DQN只能处理离散动作。有两种路线:一种是用DDPG/TD3这类连续动作算法,另一种就是把功率离散化,用DQN做。

我的选择是离散化,原因有二:第一,NOMA的SIC解码本身就对功率差有明确要求,离散功率等级更容易保证"功率差可辨识"这个硬条件;第二,离散动作和Q值表的匹配逻辑简单,调试起来直观,作为第一步研究工作也更容易发表结果。

功率等级离散化的粒度是个平衡问题。每个用户设M个功率等级,K个用户时总动作数就是M的K次方。K=4时M=5意味着625个动作,网络输出层规模尚可接受。如果K增大到6、M还是5,动作空间就变成15625个,Q值网络学起来就费力了。

我试过好几组设置,最终K=4时M=5比较合适,功率从0.02W到0.2W按对数间隔分档——对数间隔比线性间隔更符合无线通信的功率感知规律,因为信道和噪声都是对数域的。

3.3 奖励函数设计:从崩溃到收敛的关键

奖励函数是这次项目里倒下最多的地方。一开始我用的是"求和速率直接作为奖励",结果训练曲线全程上涨,看起来好像收敛了,但分解每个用户速率后发现:DQN学会了把全部功率分配给信道最好的用户,其他用户速率几乎为0。因为在纯求和速率目标下,放弃弱用户是理性的——所有用户的速率之和反而最大。

但这显然不是我们想要的干扰管理。SIC解码可以容忍功率差异,但总不能真的把一个用户完全饿死。

后来改成了分段奖励结构:

  • 如果所有用户SINR ≥ γ_th(SIC全部成功):reward = Σ w_k · R_k;
  • 如果存在用户SINR < γ_th但≥0(部分成功):reward = Σ w_k · R_k - α · N_fail,其中N_fail是失败用户数;
  • 如果SIC解码彻底失败(第一个用户都解错):reward = -β,直接给大盘子惩罚。

α和β的经验取值是α=5、β=3,对你当前速率量级稍作调整即可。这个结构的思想是:先保证用户间的基本公平(谁也别掉队),再在公平基础上追求总速率提升。经过这个改动,训练出的策略才是真正"懂NOMA"的策略,而不是单纯贪心调度的策略。

3.4 网络结构与PyTorch实现

DQN网络结构本身并不复杂,我用了一个三层的全连接网络:输入层(2K+1维) → 128 → 128 → 输出层(action_dim维)。激活函数用ReLU,最后一层不加激活直接输出Q值。对于动作空间625个、状态维度9的情况,这个网络规模完全够用。

核心代码长这样:

import torch import torch.nn as nn class DQNNet(nn.Module): def __init__(self, state_dim, action_dim, hidden_sizes=[128, 128]): super(DQNNet, self).__init__() layers = [] in_dim = state_dim for h in hidden_sizes: layers.append(nn.Linear(in_dim, h)) layers.append(nn.ReLU()) in_dim = h layers.append(nn.Linear(in_dim, action_dim)) self.net = nn.Sequential(*layers) def forward(self, x): return self.net(x)

这个网络相对于任务复杂度来说并不算大,但实践证明足够了。无线通信场景的状态特征相对平滑,不需要图像网络那种超大容量。更大的网络反而更容易过拟合训练样本里的噪声,造成测试时的泛化性能下降。

DQN的另外两个核心机制是经验回放和目标网络。经验回放用deque双端队列实现,容量设为5000,batch size取64;目标网络每50个训练步同步一次主网络参数。训练参数我放在后面第4.3节统一给出。这里先记住一个结论:没有目标网络,Q值更新时用的是同一个网络自己预测的目标,相当于"自己给自己打分",误差会不断放大,训练必定发散。

4. 完整实现流程与仿真代码解析

4.1 仿真环境搭建:把信道和SIC逻辑封装好

整个项目的第一件事是写仿真环境。我把环境封装成一个Python类,接口对齐OpenAI Gym的风格——reset()和step()两个核心方法。环境内部维护真实的信道状态和用户功率,step()接收一个动作索引,把它解码成各用户的功率向量,计算SIC解码结果和速率,最后返回下一状态、奖励和是否结束。

这里有个值得分享的设计:把动作索引和功率向量的映射关系做在环境里,而不是在智能体里。原因是后续如果要换算法(比如从DQN换成PPO),只需要改智能体,环境不用动。动作索引到功率向量的映射我用查表实现,首先生成所有组合再编号,这样保证每个动作的语义清晰,不会出现两个动作映射同一功率向量的情况。

SIC解码函数我建议单独拎出来写,这段逻辑最容易出bug:

def sic_receive(channel_gain, power, noise_power, sinr_threshold): K = len(channel_gain) # 按信道增益降序排列:信道强的先解码 order = np.argsort(channel_gain)[::-1] remaining_power = power.copy() sinr_list = [] success = True for i, user_idx in enumerate(order): received = channel_gain[user_idx] * power[user_idx] # 干扰:尚未解码的用户信号 + 噪声 interference = noise_power for j in order[i+1:]: interference += channel_gain[j] * power[j] sinr = received / interference if sinr < sinr_threshold: success = False sinr_list.append(0) else: sinr_list.append(sinr) # 解码成功则从剩余信号中减掉该用户 remaining_power[user_idx] = 0 return sinr_list, success

注意一个细节:代码里解码成功的判定用的是SINR门限,而SIC"减去信号"的操作我用的是把剩余功率清零来模拟。严格来说,如果信道估计完美,解码成功意味着信号可以被完全消除。这个近似在仿真里是合理的,避免引入信道估计误差那部分复杂度。

4.2 经验回放与DQN训练循环

经验回放是DQN训练稳定性的基石。我实现了一个带deque的ReplayBuffer,容量定义为maxlen=5000,每次训练从中随机采样。

训练循环的伪代码如下,关键步骤和参数都标出来了:

def train_dqn(env, dqn, target_dqn, buffer, config): optimizer = torch.optim.Adam(dqn.parameters(), lr=config['lr']) loss_fn = nn.MSELoss() epsilon = config['epsilon_start'] total_steps = 0 for episode in range(config['max_episodes']): state = env.reset() done = False episode_reward = 0 while not done: # epsilon-greedy探索 if np.random.rand() < epsilon: action = np.random.randint(env.action_space_size) else: with torch.no_grad(): q_vals = dqn(torch.FloatTensor(state)) action = torch.argmax(q_vals).item() next_state, reward, done = env.step(action) buffer.push((state, action, reward, next_state, done)) if len(buffer) >= config['batch_size']: batch = buffer.sample(config['batch_size']) states = torch.FloatTensor(batch.states) actions = torch.LongTensor(batch.actions) rewards = torch.FloatTensor(batch.rewards) next_states = torch.FloatTensor(batch.next_states) dones = torch.FloatTensor(batch.dones) q_values = dqn(states).gather(1, actions.unsqueeze(1)).squeeze() with torch.no_grad(): max_next_q = target_dqn(next_states).max(1).values targets = rewards + config['gamma'] * max_next_q * (1 - dones) loss = loss_fn(q_values, targets) optimizer.zero_grad() loss.backward() optimizer.step() # 周期性同步目标网络 total_steps += 1 if total_steps % config['target_update'] == 0: target_dqn.load_state_dict(dqn.state_dict()) state = next_state episode_reward += reward epsilon = max(config['epsilon_min'], epsilon * config['epsilon_decay']) if episode % 20 == 0: print(f"Episode {episode}, Reward: {episode_reward:.2f}, Epsilon: {epsilon:.3f}")

target_dqn的同步这里用的是硬更新。软更新(每步按τ=0.001混合)在训练后期能减小抖动,但硬更新配合较大的更新间隔(50步)完全够用,还能少写代码。

4.3 超参数配置清单

这些参数折腾了很多轮,最终稳定复现的组合是:

参数取值说明
学习率3e-4偏大的学习率会让Q值振荡,3e-4比较稳
折扣因子γ0.99长期奖励重要,时隙够长
经验池容量5000与每个episode的交互量匹配
batch_size64样本量适中,梯度噪声可控
ε初始值1.0前50个episode基本全随机探索
ε最小值0.01后期保留少量探索
ε衰减率0.995每个episode乘一次,衰减不算太快
目标网络更新步数50硬更新间隔,单位是训练步
每个episode最大时隙数100超过强制结束
训练总episode数1000大约5分钟内能跑完一整套

有个细节值得注意:ε衰减率不要设得太激进。如果前200个episode就把ε降到0.01,智能体过早进入"纯利用"模式,很多动作组合永远没有探索机会,最终策略容易陷入局部最优。我试过0.98的衰减率,收敛结果明显比0.995差。这个经验在动作空间大的时候尤其重要。

4.4 训练结果与性能评估

训练1000个episode之后,把训练期间每个episode的平均奖励画出来,可以看到三段式曲线:前100个episode奖励快速上升(策略从乱来变成有基本章法),100到500个episode是稳定爬坡期(SIC失败次数明显减少),500之后进入平台期(小幅震荡但整体平稳)。

评估阶段我做了两组对比。一组是固定功率方案,即所有用户都用0.1W平均功率发射;另一组是随机功率方案,每次从离散功率空间随机选一个组合。跑100轮独立测试,对三种方案计算平均系统加权速率和SIC成功率:

  • 固定功率方案:SIC成功率较低,只有约35%,因为用户信道增益差异大时固定功率没法保证解码顺序稳定;
  • 随机功率方案:平均速率不稳定,方差很大,偶尔SIC成功率高但多数时候惨不忍睹;
  • DQN方案:平均加权速率比固定功率提升约28%,SIC成功率稳定在95%以上。关键是方差小,很少出现极端低值。

这个结果说明DQN真正学到的是"根据信道状态调整用户间功率差"的策略——信道差异大的时候把强用户功率压一点、弱用户功率提一点,让到达功率形成正确的SIC解码梯度。这其实就是在学习NOMA功率域的本质规律,而不只是记住几个固定模式。

5. 调试经验与常见问题

5.1 训练发散的几大原因

DQN在通信环境里比在游戏环境里更容易发散,背后原因很实在:游戏环境的状态转移是固定的,通信环境的状态转移带随机性,而且奖励函数的尺度没有统一标准。遇到发散先查这几项:

  • 奖励值域太大。和速率可能到几十甚至上百,Q值跟着膨胀,梯度更新直接把网络参数打飞。处理办法是把奖励除以一个基准值,让奖励量级落在[-2, 8]区间。这个基准值怎么选?跑几轮随机策略,统计平均奖励的数量级就行。
  • 状态没有归一化。信道增益直接输入几乎是必炸的,先做log压缩再加归一化。
  • 经验池里的数据分布偏斜。如果大部分经验都是SIC失败的负奖励,网络会学到"无论什么状态都给差不多的负Q值",动弹不得。对策是提高ε初期探索的随机性,让智能体尽早看到成功的状态转移。另外可以人为筛选经验:只保留SIC成功和失败各一半,训练效果立竿见影。

5.2 奖励函数设计和SIC成功率的连锁反应

这个坑值得单独讲讲。我调整奖励函数的过程中发现,SIC成功率和奖励函数里的惩罚系数α存在明显的正反馈关系:α太小,智能体不怎么在乎掉线的用户,SIC成功率上不去;α太大,智能体变得过度保守,所有动作都倾向于把功率调得极致拉开,总速率掉得厉害。

α还有一个隐藏作用——它决定了探索的方向优先级。刚开始训练时智能体随机探索,大部分动作都SIC失败,如果惩罚太狠,智能体会认为"任何动作都是灾难"而不敢尝试,Q值长期为负。此时反而先减小α让部分失败动作的奖励不至于太惨,帮助智能体迈过"探索的恐惧期",训练中期再把α调上来。

5.3 随机种子与可复现性管理

研究项目里可复现性是硬要求。我踩过一个大坑:环境里每次reset都重新随机生成用户位置和信道,结果两次训练的最终性能差异达到15%以上——因为训练数据本身都不同。这会导致同一个代码跑出的结论不可复现,论文和课题答辩的时候非常尴尬。

正确的做法是:将一个seed用于用户位置生成,另一个seed用于信道衰落生成,第三个seed用于智能体探索随机性。三个seed分开管理,每个seed各自跑3次训练取平均,作为最终性能指标。这样既保证了对照实验的公平性,又不至于把随机性完全杀死。

5.4 目标网络同步频率对稳定性的影响

目标网络同步频率S这个超参数,很多人直接照抄论文里的1000步,但在通信这种小步长、长episode的场景里不一定最优。我做了个小实验:S=50、S=200、S=1000三档对比。

结果:S=50时Q值估计波动小,训练曲线平稳,最终奖励最高;S=200时略有振荡;S=1000时训练后期出现明显的Q值震荡,损失函数无法收敛。原因是通信场景里相邻状态的相关性比较强(信道缓慢变化),如果目标网络长期不更新,它给出的目标Q值会严重偏离当前策略的真实水平,导致训练方向混乱。所以推荐的做法是在几百步量级做硬更新,具体数值根据你的场景维度微调,敏感性不算太强,几十到几百步之间差别不大。

5.5 状态空间冗余对策略质量的隐性影响

最后聊一个容易被忽视的问题:状态里到底该不该包含"上一时刻动作"。

一开始我以为智能体需要知道上次分配的功率才能做平滑调节,但实验发现引入上一时隙动作后策略有滞后性——智能体倾向于"沿用上一状态",导致信道已经变化了功率却还没跟上。去掉这项之后,纯靠信道状态决策,表现反而更快。

但完全去掉也有代价:动作切换会比较频繁,功率抖动增大。折中方案是把"上一时刻动作"改成"上一时刻动作与当前动作的差值"作为状态,即显式告诉智能体动作的增量信息。这样既能吸收历史信息,又不会因为直接输入动作值让网络学会"偷懒沿用"。这个改法在我的场景里综合效果最好,你可以在自己的代码里试试这个思路。

6. 后续扩展方向与个人体会

这个项目做到目前的程度,解决的是"单无人机、单小区、离散功率、用户固定数量"这个子问题。天然的方向是往更复杂的场景扩:多无人机协作、用户数量动态变化、连续功率动作(需要切换到DDPG或PPO)、新增能量效率作为优化目标。从架构上看,当前的环境封装和DQN解耦设计基本不需要大改,迁移到这些方向都比较顺畅。

我个人体验最深的一点是:做通信和深度学习交叉的项目,千万不要在一开始就追求大而全的模型。先把通信模型封装成一个干净的环境,用最简单的DQN跑通端到端链路,然后逐项加入细节。我在前期把大量时间消耗在状态空间"贪多"和奖励函数设计上,反而忽略了先跑通一个能出对比图的baseline。后来强制自己把主链路做通、再回头优化细节,效率高了很多。

对于正在做类似课题的同学,我的建议是:先把论文里的数学公式彻底搞懂,尤其是SIC的干扰表达式怎么写——这一步错了后面全是白算。然后快速实现一个不调参、随机策略的版本,验证环境逻辑正确。最后再叠加DQN、经验回放和目标网络这些组件。这个过程看起来绕路,实际上是最省时间的路径。

如果你正在搭建类似的项目,希望这份经验能帮你少踩几个坑。等我把多无人机协作下的干扰协调方案跑通,再来分享新的进展。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询