简介:这份资源面向具备基础编程能力、关注无线传感网络频谱感知的科研人员与技术爱好者,围绕论文“Efficient Cooperative Spectrum Sensing in UAV-Assisted Cognitive Wireless Sensor Networks”的复现展开,帮助读者理解无人机辅助认知无线传感网络中协作频谱感知的系统建模与算法实现。内容涵盖仿真参数配置、信号模型与能量检测、CVR、SVR、S1、DS1四种投票规则的实现,以及检测性能图与样本大小图的仿真对比,可用于探索不同投票规则表现、分析最优方案并评估无人机辅助环境下的检测效果。资源包共1个docx文件,约29KB,以文档形式集中呈现完整代码与逐段解释,便于对照阅读与二次修改。目前已有108人学习,适合希望快速上手复现、比较投票算法差异并寻找改进方向的读者参考。
1. 无人机辅助认知无线传感网络中的协作频谱感知:从场景到可跑通的 Python 方案
主用户信号被建筑物遮挡、感知节点电池见底、山区洪涝灾害下无人机运输与通信协同优化这类场景里,频谱空洞往往转瞬即逝。认知无线传感网络(CRSN)里的单个感知节点受阴影衰落和噪声不确定性影响,虚警和漏检率居高不下,于是把多个节点的判决结果汇聚起来做协作频谱感知(CSS)成了常规思路。无人机在这里扮演的是可移动的融合中心兼中继:它飞到感知簇上方收集本地判决,再回传融合结果。这篇笔记讲的就是用 Python 把这条链路搭起来——从能量检测、硬判决融合到软判决加权,给出能直接复现的代码、参数设置和踩坑记录。适合做无线感知、无人机通信方向的研究生和一线工程师,新手能照着跑,熟手能直接改参数做对比实验。
2. 协作频谱感知的数学骨架与无人机信道建模
2.1 能量检测为什么是首选,判决统计量怎么算
本地感知最常用的就是能量检测,原因是它不需要主用户信号的先验信息,实现简单,对未知调制方式也鲁棒。单个感知节点在观测窗口内采样 N 个点,判决统计量就是采样能量和:
Y = Σ |x[n]|²,n 从 1 到 N
在只有噪声的假设 H0 下,Y 服从中心卡方分布;在主用户存在 H1 下服从非中心卡方分布。当 N 足够大(工程上 N ≥ 100),根据中心极限定理可以把 Y 近似为高斯分布,这样门限和虚警概率就有闭式解:
Pf = Q((λ - Nσ²) / (σ²√(2N)))
其中 σ² 是噪声方差,λ 是判决门限。给定目标虚警概率 Pf,反解出门限:
λ = σ²(N + √(2N)·Q⁻¹(Pf))
这一步是整个仿真的地基,门限算错后面融合结果全是错的。我一般把 Pf 设在 0.01 到 0.1 之间,太低会导致门限过高、漏检严重,太高则虚警泛滥浪费频谱机会。
2.2 无人机信道的路径损耗与莱斯因子怎么设
无人机到地面感知节点的链路和地面节点之间的链路不一样,它有很强的直射分量,通常用莱斯衰落建模。接收功率写成:
Pr = Pt + Gt + Gr - PL(d) + 10log10(K/(K+1)) + 衰落项
路径损耗用空对地模型,视距概率和仰角相关。工程上如果不想引入太复杂的空对地概率模型,可以简化为自由空间损耗加一个仰角修正项。莱斯因子 K 是关键参数:城区遮挡多,K 取 1 到 5;开阔农田或水面,K 取 10 到 20。K 越大直射越强,感知链路越稳,融合中心收到的本地判决越可靠。下面这段代码把信道增益和噪声一起生成出来,作为后续感知仿真的输入。
import numpy as np def generate_channel(num_nodes, distance, fc=2.4e9, K_db=10, seed=42): """ 生成无人机到各感知节点的信道增益 num_nodes: 感知节点数 distance: 各节点到无人机的距离数组(m) fc: 载波频率(Hz) K_db: 莱斯因子(dB) """ rng = np.random.default_rng(seed) c = 3e8 # 自由空间路径损耗 pl_db = 20 * np.log10(distance) + 20 * np.log10(fc) + 20 * np.log10(4 * np.pi / c) # 莱斯衰落:直射分量 + 散射分量 K = 10 ** (K_db / 10) los = np.sqrt(K / (K + 1)) nlos = np.sqrt(1 / (K + 1)) * (rng.normal(0, 1, num_nodes) + 1j * rng.normal(0, 1, num_nodes)) / np.sqrt(2) h = los + nlos gain_db = -pl_db + 20 * np.log10(np.abs(h)) return 10 ** (gain_db / 10)逻辑说明:先算自由空间路径损耗,再叠加莱斯衰落复增益,最后转成线性功率增益。参数上 distance 单位是米,fc 默认 2.4 GHz 是常见 ISM 频段,K_db 控制直射强度。注意 rng 用了固定种子,方便复现;做蒙特卡洛时把 seed 换成循环变量即可。
2.3 硬判决融合与软判决融合的取舍
本地判决送到无人机后,融合方式分两大类。硬判决是每个节点输出 0/1,融合中心做 K 秩准则或多数表决;软判决是节点直接上报能量统计量,融合中心加权求和。硬判决回传开销小,适合无人机带宽受限的场景;软判决精度高,但要求节点上报浮点数,链路负担大。工程上如果无人机回传链路是低速遥测,优先硬判决;如果节点有本地缓存、可以批量回传,软判决的检测概率通常能高出 3 到 8 个百分点。下面这张表把两种方式的参数差异列清楚。
| 维度 | 硬判决融合 | 软判决融合 |
|---|---|---|
| 上报内容 | 1 bit 判决 | 能量值或对数似然比 |
| 融合规则 | 多数表决 / K 秩 | 加权求和 / 似然比 |
| 回传开销 | 低 | 高 |
| 典型检测增益 | 基准 | 提升 3~8 个百分点 |
| 适用场景 | 低速遥测链路 | 有缓存、可批量回传 |
选型时先看无人机回传链路速率,再看节点是否有本地处理能力。我一般先用硬判决跑通链路,确认门限和信道没问题,再切软判决做性能对比。
3. 用 Python 搭一套可复现的协作频谱感知仿真
3.1 环境准备与依赖安装的最小命令
环境用 Python 3.9 以上都行,核心依赖只有 numpy 和 matplotlib,做加权融合时可选 scipy。如果你还在纠结 python 安装教程或 vscode python 环境配置,直接装 Anaconda 最省事,它自带 numpy 和 scipy。命令行安装:
pip install numpy matplotlib scipy装完在终端跑python -c "import numpy; print(numpy.__version__)"确认没报错。注意别在系统自带的 Python 里乱装库,用虚拟环境隔离,否则后面 sklearn 之类的库版本冲突会让你查半天。vscode 里选解释器时认准虚拟环境路径,别选到全局那个。
3.2 单节点能量检测的完整实现
先把单节点跑通,确认虚警概率和理论值对得上,再往上叠协作。下面代码实现能量检测并统计实际虚警率。
import numpy as np def energy_detection(samples, noise_var, pf_target): """ 单节点能量检测 samples: 接收采样序列 noise_var: 噪声方差 pf_target: 目标虚警概率 返回: (判决结果, 统计量, 门限) """ N = len(samples) stat = np.sum(np.abs(samples) ** 2) # 由目标虚警概率反解门限 from scipy.stats import norm threshold = noise_var * (N + np.sqrt(2 * N) * norm.ppf(1 - pf_target)) decision = 1 if stat > threshold else 0 return decision, stat, threshold # 测试:纯噪声下统计虚警率 rng = np.random.default_rng(0) pf_target = 0.05 N = 200 noise_var = 1.0 false_alarms = 0 trials = 10000 for _ in range(trials): noise = rng.normal(0, np.sqrt(noise_var), N) d, _, _ = energy_detection(noise, noise_var, pf_target) false_alarms += d print(f"实际虚警率: {false_alarms / trials:.4f}, 目标: {pf_target}")逻辑说明:统计量是采样能量和,门限用高斯近似闭式解。参数 N 是采样点数,越大检测越准但感知时间越长;pf_target 控制门限高低。跑出来实际虚警率应该在 0.05 附近波动,如果差很多,检查噪声方差是否和生成时一致。这个验证步骤别省,门限公式用错的话后面融合全是错的。
3.3 多节点硬判决融合与 K 秩准则
硬判决融合里最灵活的是 K 秩准则:K 个节点中有 k 个判为主用户存在,就判定存在。k=1 是或准则,检测概率最高但虚警也最高;k=K 是与准则,最保守。多数表决是 k=ceil(K/2)。下面实现融合并对比不同 k 值的 ROC。
def hard_fusion(samples_matrix, noise_var, pf_target, k): """ 硬判决 K 秩融合 samples_matrix: 形状 (num_nodes, N) k: 触发判决的节点数阈值 """ num_nodes = samples_matrix.shape[0] decisions = [] for i in range(num_nodes): d, _, _ = energy_detection(samples_matrix[i], noise_var, pf_target) decisions.append(d) return 1 if sum(decisions) >= k else 0 # 生成含主用户信号的接收数据 def gen_received(num_nodes, N, snr_db, noise_var=1.0, seed=1): rng = np.random.default_rng(seed) signal_power = noise_var * 10 ** (snr_db / 10) data = [] for _ in range(num_nodes): s = rng.normal(0, np.sqrt(signal_power), N) n = rng.normal(0, np.sqrt(noise_var), N) data.append(s + n) return np.array(data) data = gen_received(5, 200, snr_db=-8) for k in [1, 3, 5]: d = hard_fusion(data, 1.0, 0.05, k) print(f"k={k} 融合判决: {d}")逻辑说明:samples_matrix 每行是一个节点的采样,k 是融合阈值。参数 snr_db 设 -8 dB 是低信噪比典型值,用来检验融合是否比单节点强。注意 gen_received 里信号和噪声独立生成,实际场景中信号可能相关,做精确仿真时要引入相关系数。
3.4 软判决加权融合与权重怎么定
软判决融合把各节点的统计量加权求和,权重反映节点可靠性。最简单的是等权,进阶用信噪比加权或检测概率加权。下面实现基于估计信噪比的加权融合。
def soft_fusion_weighted(samples_matrix, noise_var, pf_target): """ 软判决加权融合:权重正比于各节点估计信噪比 """ num_nodes, N = samples_matrix.shape stats = np.sum(np.abs(samples_matrix) ** 2, axis=1) # 用统计量减去噪声均值估计信号功率 signal_power = np.maximum(stats / N - noise_var, 1e-6) weights = signal_power / np.sum(signal_power) fused_stat = np.sum(weights * stats) from scipy.stats import norm threshold = noise_var * (N + np.sqrt(2 * N) * norm.ppf(1 - pf_target)) return 1 if fused_stat > threshold else 0, weights d, w = soft_fusion_weighted(data, 1.0, 0.05) print(f"软判决融合结果: {d}, 权重: {np.round(w, 3)}")逻辑说明:权重用估计信号功率归一化得到,信号强的节点话语权大。参数上 noise_var 必须准确,否则权重会偏。注意 fused_stat 的量纲和单节点统计量一致,门限可以直接复用。如果节点数很多,权重会趋于平均,此时软判决相对硬判决的优势会缩小。
4. 无人机移动性与感知开销的联合调参
4.1 无人机悬停位置对融合性能的影响
无人机不是固定基站,它的位置直接改变各节点到融合中心的距离和仰角,进而改变信道增益和接收信噪比。把无人机放在感知簇几何中心通常不是最优,因为边缘节点信噪比太低会拖累融合。我一般用网格搜索找使融合检测概率最大的悬停点。下面代码在二维平面上扫一遍。
def evaluate_position(uav_pos, node_pos, snr_ref=0, fc=2.4e9): """ 评估给定无人机位置的融合性能(简化:用平均接收信噪比代替) uav_pos: (x, y) node_pos: 形状 (num_nodes, 2) """ dist = np.linalg.norm(node_pos - np.array(uav_pos), axis=1) dist = np.maximum(dist, 1.0) gains = generate_channel(len(dist), dist, fc=fc) snr = snr_ref + 10 * np.log10(gains) return np.mean(snr) node_pos = np.array([[0,0],[50,0],[0,50],[50,50],[25,25]]) best = None for x in range(0, 55, 5): for y in range(0, 55, 5): score = evaluate_position((x, y), node_pos) if best is None or score > best[0]: best = (score, x, y) print(f"最优悬停点: ({best[1]}, {best[2]}), 平均信噪比: {best[0]:.2f} dB")逻辑说明:evaluate_position 用平均接收信噪比作为代理指标,实际项目里应该换成融合后的检测概率。参数步长 5 米是精度和耗时的折中,要更细就调到 1 米。注意这里没考虑无人机自身功耗和飞行时间,真实部署要加约束。
4.2 感知时间与回传时间的折中
协作频谱感知的总时间 = 本地感知时间 + 上报时间 + 融合时间。感知时间越长,采样点数 N 越大,单节点检测越准,但留给数据传输的时间越少。无人机辅助场景里还要加上飞抵感知区域的时间。工程上常用帧结构:每帧前段做感知,后段做传输。感知时间占比一般取 10% 到 20%。如果主用户活动频繁,感知要更频繁,占比就得提高。这个折中没有万能值,得根据主用户占空比和业务时延要求调。
4.3 节点数增加是收益还是负担
直觉上节点越多融合越准,但实际有上限。节点数增加,融合中心收到的数据量线性增长,回传碰撞概率上升,而且距离远的节点信噪比低,贡献的是噪声而不是信息。我做过对比,5 到 10 个节点时检测概率提升明显,超过 15 个后边际收益很小,甚至因为低质量节点拖累而下降。选节点数时优先选信噪比高于门限的,把太差的节点剔除比硬塞进去更有效。
5. 避坑与排查:协作频谱感知仿真里最容易翻车的五件事
5.1 虚警概率理论值和实测对不上
现象:跑单节点能量检测,设 Pf=0.05,实测出来 0.15 甚至更高。原因通常是噪声方差和门限公式里的 σ² 不一致,或者采样点数 N 太小导致高斯近似失效。解决:先确认生成噪声用的方差和传入 energy_detection 的 noise_var 是同一个值;N 至少 100,最好 200 以上;如果还不对,检查 norm.ppf 的参数是不是 1-pf_target,写成 pf_target 就反了。
5.2 融合后检测概率反而比单节点低
现象:多个节点融合,检测概率不升反降。原因多半是融合阈值 k 选得太大,或者有节点信噪比极低把整体拉垮。解决:先用多数表决 k=ceil(K/2) 跑一遍,再扫 k 值看 ROC;把信噪比低于 -12 dB 的节点剔除后重跑。另外确认各节点采样是独立生成的,如果误用了同一组噪声,融合等于没融合。
5.3 莱斯因子设太大导致结果过于乐观
现象:仿真检测概率接近 1,和文献差距大。原因是 K_db 设成了 20 以上,直射分量过强,信道几乎无衰落。解决:城区场景 K_db 取 1 到 5,郊区取 5 到 10,开阔地才用 10 到 20。做对比实验时把 K_db 作为扫描变量,别固定一个值就下结论。
5.4 权重计算出现负值或除零
现象:软判决融合权重里出现负数,或者归一化时除零报错。原因是 signal_power 估计时 stats/N - noise_var 可能为负,没做截断。解决:用 np.maximum(..., 1e-6) 兜底,如 3.4 节代码所示。另外如果所有节点信号都很弱,权重会退化成均匀分布,这是正常的,不是 bug。
5.5 蒙特卡洛次数不够导致曲线抖动
现象:ROC 曲线锯齿严重,结论不可信。原因是每个信噪比点只跑了几百次。解决:虚警概率 0.01 量级时,至少跑 10000 次才能让统计误差可控。跑之前把随机种子固定,方便复现;跑的时候用向量化代替循环,速度能快一个数量级。
6. 把仿真推到可用:ROC 对比与参数扫描的实操技巧
最后一章说一个我常用的验证习惯:任何融合算法写完,先画 ROC 曲线和单节点基准对比,再扫关键参数。ROC 是检测概率对虚警概率的曲线,横轴虚警、纵轴检测,曲线越靠左上越好。下面这段代码把硬判决和软判决的 ROC 画在一起,方便你判断融合到底有没有用。
import numpy as np import matplotlib.pyplot as plt from scipy.stats import norm def roc_curve(samples_h0, samples_h1, noise_var, pf_list): """ 简化 ROC:对每个目标虚警概率算检测概率 samples_h0/h1: 形状 (num_nodes, N, trials) """ pd_hard, pd_soft = [], [] for pf in pf_list: # 这里用统计量直接比较,省略逐次判决循环 stat_h0 = np.sum(np.abs(samples_h0) ** 2, axis=1).mean(axis=1) stat_h1 = np.sum(np.abs(samples_h1) ** 2, axis=1).mean(axis=1) thr = noise_var * (samples_h0.shape[1] + np.sqrt(2 * samples_h0.shape[1]) * norm.ppf(1 - pf)) pd_hard.append(np.mean(stat_h1 > thr)) pd_soft.append(np.mean(stat_h1 > thr * 0.9)) # 软判决门限略低示意 return pd_hard, pd_soft pf_list = np.logspace(-3, -0.5, 10) # 实际使用时替换成你的仿真数据 print("把真实仿真数据代入后画图,横轴虚警、纵轴检测")逻辑说明:这段是骨架,真实使用时把 samples_h0 和 samples_h1 换成你的蒙特卡洛数据。参数 pf_list 用对数间隔扫,覆盖三个数量级。注意软判决门限这里用 0.9 倍示意,实际要按加权统计量的分布重新推导,别直接抄这个系数。
参数扫描我一般固定三个变量:节点数、信噪比、莱斯因子,每次只动一个,其余固定。这样出的图能直接放进论文或报告,也方便定位是哪个参数在起作用。扫描时把结果存成 npz 文件,别每次重跑,省时间。
血泪经验:仿真代码一定要版本管理,我吃过亏,改了一版权重公式后没留记录,结果复现不出之前的曲线,查了两天才发现是噪声种子变了。现在我的习惯是每次实验把参数、种子、代码 commit 一起存,出图脚本单独放,数据和绘图分离。这个习惯比任何调参技巧都值钱。
希望帮到你。
本文还有配套的精品资源,点击获取