简介:这份资源面向具备一定编程基础、关注无线传感网络与认知无线电频谱感知的研究人员和技术爱好者,围绕论文“Efficient Cooperative Spectrum Sensing in UAV-Assisted Cognitive Wireless Sensor Networks”的复现展开,帮助读者理解无人机辅助场景下协作频谱感知的系统建模与算法实现。资源包内共1个docx文档,约29KB,集中呈现参数设置、信号模型与能量检测、CVR/SVR/S1/DS1四种投票规则实现以及仿真实验的完整Python代码与解释。读者可据此掌握本地虚警概率与检测概率的计算逻辑,对比不同投票规则在检测性能与样本大小上的差异,并借助检测性能图和样本大小图评估无人机辅助环境下的频谱检测效果。内容还涉及信号模型增强、投票规则复杂度分析、参数灵敏性分析及机器学习引入等改进方向,适合作为协作频谱感知算法复现与扩展研究的参考范例。目前已有108人学习。
1. 无人机辅助认知无线传感网络里的协作频谱感知,到底难在哪
主用户信号忽强忽弱、地面节点被山体遮挡、阴影衰落让单点检测虚警率飙升——这是我在做无人机辅助认知无线传感网络协作频谱感知时最先撞上的三件事。认知无线传感网络的核心诉求是让次级用户在不干扰主用户的前提下复用频谱,而协作频谱感知就是把多个节点的判决汇总,用空间分集换检测可靠性。无人机在这里的价值不是噱头:它机动、视距概率高,能飞到地面节点之间的“感知盲区”上方充当空中融合节点或中继。Python 实现这套链路,难点不在算法公式,而在把信道模型、能量检测、融合规则、无人机轨迹这几块拼成一个能跑、能出 ROC 曲线的闭环。这篇写给已经懂点信号处理、想用 Python 把协作频谱感知从公式落到可复现实验的从业者,也写给做无人机通信协同优化的同学。
2. 协作频谱感知的链路模型与 Python 选型:先立住理论再动手
2.1 从单节点能量检测到多节点协作的判决融合
单节点能量检测的逻辑很朴素:在观测窗内累加接收信号能量,和门限比大小。主用户存在时统计量服从非中心卡方分布,不存在时服从中心卡方分布,样本数足够大时两者都近似高斯。问题在于,地面传感节点常处在深衰落里,单点检测的检测概率在低信噪比下会塌得很快。协作的思路是让 N 个节点各自做本地判决,再送到融合中心。融合规则常见三种:AND 规则(全判 1 才判 1,虚警低但检测概率也低)、OR 规则(有一个判 1 就判 1,检测概率高但虚警高)、K-out-of-N 规则(折中,K 取多数)。无人机辅助场景里,无人机可以当融合中心,也可以当移动的感知节点,把空中和地面的判决一起汇总。选哪种规则取决于你对虚警的容忍度:频谱复用效率优先就偏 OR,保护主用户优先就偏 AND。
2.2 为什么用 NumPy + SciPy 而不是纯 MATLAB 移植
热词里 matlab 无人机仿真出现频率很高,但 Python 做这套仿真有实际优势:NumPy 的向量化能把蒙特卡洛循环压到几秒,SciPy 的scipy.stats直接给卡方和高斯分布,scipy.special里有 Marcum Q 函数,画 ROC 不用自己造轮子。环境上,我一般用 Python 3.10 以上,装 NumPy、SciPy、Matplotlib 三件套就够跑通核心链路。如果你用 vscode python 环境配置,记得选对解释器,别让全局环境和虚拟环境打架。下面是最小依赖安装:
# 建议在虚拟环境里装,避免污染全局 python -m venv cwn_env source cwn_env/bin/activate # Windows 用 cwn_env\Scripts\activate pip install numpy scipy matplotlib参数说明:venv建独立环境,numpy负责向量化运算,scipy提供统计分布和特殊函数,matplotlib出 ROC 和检测概率曲线。装完用python -c "import numpy, scipy; print(numpy.__version__, scipy.__version__)"验证,版本不匹配是后面报错的高频来源。
2.3 信道模型:路径损耗、阴影衰落与无人机的视距优势
协作感知的仿真可信度,八成取决于信道模型。地面节点之间常用对数距离路径损耗加对数正态阴影衰落,无人机到地面节点则要区分视距和非视距概率。我一般用简化的空对地模型:视距概率随仰角增大而升高,仰角由无人机高度和水平距离决定。这一步别偷懒,直接把无人机高度设成固定值再算仰角,否则轨迹一变信道就失真。下面给出信道增益的核心实现:
import numpy as np def path_loss_db(distance, alpha=3.0, d0=1.0): """对数距离路径损耗,alpha 为路径损耗指数""" distance = np.maximum(distance, d0) # 防止 log(0) return 10 * alpha * np.log10(distance / d0) def shadow_fading(num_samples, sigma_db=8.0): """对数正态阴影衰落,sigma_db 是阴影标准差""" return np.random.normal(0.0, sigma_db, num_samples) def los_probability(elevation_deg, a=9.61, b=0.16): """空对地视距概率,a、b 为环境拟合参数""" return 1.0 / (1.0 + a * np.exp(-b * (elevation_deg - a)))逻辑说明:path_loss_db用对数距离模型,alpha在开阔地取 2 到 3,城区取 3 到 4;shadow_fading生成 dB 域高斯样本,sigma_db典型值 6 到 10;los_probability是空对地视距经验式,a、b随环境变,城区和郊区差别明显。参数怎么改:想模拟山区遮挡就把alpha调大、sigma_db调大,想体现无人机视距优势就把仰角算准再代入。
3. 用 Python 跑通协作频谱感知:能量检测、融合与 ROC 曲线
3.1 单节点能量检测的向量化实现
能量检测是整条链路的原子操作,写不好后面蒙特卡洛会慢到怀疑人生。核心是别用 for 循环逐样本累加,直接用矩阵运算一次算完所有节点、所有蒙特卡洛轮次。下面这段是我常用的写法:
import numpy as np def energy_detector(signal_power, noise_power, num_samples, num_nodes, num_mc): """ 向量化能量检测 signal_power: 主用户信号功率(标量或数组) noise_power: 噪声功率 num_samples: 每节点观测样本数 num_nodes: 协作节点数 num_mc: 蒙特卡洛轮次 返回: 每轮每节点的检测统计量 """ # 噪声样本,形状 (num_mc, num_nodes, num_samples) noise = np.random.normal(0, np.sqrt(noise_power), (num_mc, num_nodes, num_samples)) # 信号样本,主用户存在时叠加 signal = np.random.normal(0, np.sqrt(signal_power), (num_mc, num_nodes, num_samples)) received = noise + signal # 沿样本轴累加能量,得到 (num_mc, num_nodes) return np.sum(received ** 2, axis=-1)逻辑说明:noise和signal都按高斯建模,received是叠加后的接收样本,np.sum(..., axis=-1)沿样本维累加能量。参数说明:num_samples越大检测越稳但感知时延越高,典型取 100 到 1000;num_nodes是协作规模,取 3 到 10 比较常见;num_mc建议至少 10000 才能让 ROC 曲线平滑。这里用np.random.normal而不是np.random.randn,是为了让噪声功率可调,别小看这个区别,功率归一化错了整条曲线都会偏。
3.2 门限设定与三种融合规则的代码落地
门限设定有两种路子:一是按目标虚警概率反推,二是固定门限看检测概率。工程上更常用前者,因为虚警直接关系到是否干扰主用户。高斯近似下门限由噪声功率、样本数和目标虚警决定。融合规则用布尔矩阵一次算完:
from scipy.stats import norm def threshold_from_pfa(pfa, noise_power, num_samples): """由目标虚警概率反推能量门限(高斯近似)""" return noise_power * num_samples + \ norm.isf(pfa) * np.sqrt(2 * num_samples) * noise_power def fusion_decision(stats, threshold, rule="K", k=None): """ 融合判决 stats: 形状 (num_mc, num_nodes) 的检测统计量 threshold: 门限 rule: "AND" / "OR" / "K" k: K-out-of-N 里的 K """ local = stats > threshold # 本地判决,布尔矩阵 if rule == "AND": return np.all(local, axis=1) if rule == "OR": return np.any(local, axis=1) if rule == "K": k = k or (local.shape[1] // 2 + 1) return np.sum(local, axis=1) >= k raise ValueError("rule 只支持 AND / OR / K")逻辑说明:threshold_from_pfa用高斯近似的逆生存函数反推门限,norm.isf就是 1 减累积分布;fusion_decision先把统计量和门限比出布尔矩阵,再按规则沿节点轴聚合。参数说明:pfa典型取 0.01 到 0.1;rule="K"时k默认取多数,也可以手动设成 2 或 3 观察折中效果。注意norm.isf在pfa极小时数值会不稳,pfa低于 1e-4 建议改用精确卡方分布。
3.3 蒙特卡洛跑 ROC:把检测概率和虚警概率画出来
有了检测统计量和融合判决,ROC 就是扫一遍门限、统计检测概率和虚警概率。这里要分别跑主用户存在和不存在两组样本,否则虚警概率算不出来。下面给出完整流程:
import numpy as np import matplotlib.pyplot as plt def roc_curve(num_mc=20000, num_nodes=5, num_samples=200, noise_power=1.0, signal_power=0.5): # 主用户存在 stats_h1 = energy_detector(signal_power, noise_power, num_samples, num_nodes, num_mc) # 主用户不存在 stats_h0 = energy_detector(0.0, noise_power, num_samples, num_nodes, num_mc) thresholds = np.linspace(stats_h0.min(), stats_h1.max(), 200) pd_list, pfa_list = [], [] for th in thresholds: pd_list.append(np.mean(fusion_decision(stats_h1, th, "K"))) pfa_list.append(np.mean(fusion_decision(stats_h0, th, "K"))) return np.array(pfa_list), np.array(pd_list) pfa, pd = roc_curve() plt.plot(pfa, pd, label="K-out-of-N") plt.xlabel("虚警概率 Pfa") plt.ylabel("检测概率 Pd") plt.legend() plt.grid(True) plt.show()逻辑说明:stats_h1和stats_h0分别是主用户存在与不存在时的统计量,扫门限时对两组分别算检测概率和虚警概率。参数说明:num_mc越大曲线越平滑,20000 轮在普通笔记本上几秒能跑完;signal_power控制信噪比,想复现低信噪比场景就把它调到 0.1 以下。跑完你会看到 K-out-of-N 的曲线夹在 AND 和 OR 之间,这正是折中的直观体现。
3.4 把无人机节点加进来:空中节点的信道与位置影响
无人机辅助的关键增量在于空中节点的信道和位置。把无人机当成一个额外感知节点,它的路径损耗用空对地模型,视距概率随仰角变。下面把无人机节点拼进协作链路:
def uav_channel_gain(height, horizontal_dist, alpha=2.5, sigma_db=6.0): """无人机到地面节点的信道增益(dB)""" dist = np.sqrt(height ** 2 + horizontal_dist ** 2) elevation = np.degrees(np.arctan2(height, horizontal_dist)) pl = path_loss_db(dist, alpha) p_los = los_probability(elevation) # 视距和非视距用不同阴影,按概率加权 fading = p_los * shadow_fading(1, sigma_db) + \ (1 - p_los) * shadow_fading(1, sigma_db + 4) return -(pl + fading) # 增益为负的损耗逻辑说明:uav_channel_gain先算三维距离和仰角,再用视距概率加权两种阴影。参数说明:height是无人机高度,典型 100 到 300 米;horizontal_dist是水平距离;alpha空中链路取 2 到 2.5,比地面小。把无人机的增益换算成信号功率后替换energy_detector里的signal_power,就能对比“有无人机”和“无无人机”的 ROC 差异。这一步是整篇最值得动手的地方,因为无人机高度和水平位置直接决定它值不值得加。
4. 协作频谱感知仿真避坑:那些让 ROC 曲线失真的细节
4.1 现象:ROC 曲线在高虚警区翘得离谱,原因:门限扫描范围没覆盖两组统计量
第一次跑 ROC 时曲线在高虚警区突然翘起,看着像算法有问题。排查发现是门限扫描范围只取了stats_h1的最小到最大,没覆盖stats_h0的尾部,导致虚警概率被截断。解决:门限范围取两组统计量并集的最小到最大,或者直接用分位数扫。这个坑很隐蔽,因为曲线大部分看着正常,只有尾部失真。
4.2 现象:检测概率随节点数增加反而下降,原因:融合规则和门限没同步调整
有次把节点数从 3 加到 10,检测概率不升反降。原因是门限还是按单节点目标虚警反推的,节点一多 OR 规则下虚警被放大,固定门限下检测概率自然乱。解决:节点数变化时,门限要么按融合后的目标虚警重算,要么固定门限但重新标定。血泪经验是,协作增益不是白来的,规则和门限必须一起调。
4.3 现象:蒙特卡洛结果每次跑都不一样,原因:随机种子没固定
调试阶段最烦的是每次跑曲线都变,没法对比。原因是没设随机种子。解决:在脚本开头加np.random.seed(42),正式出图时再关掉种子多跑几次取平均。注意固定种子只用于调试,最终结论要看多次独立运行的统计。
4.4 现象:低信噪比下检测概率恒为 0 或 1,原因:功率归一化或样本数设置不当
低信噪比场景里检测概率要么全 0 要么全 1,中间没有过渡。排查发现是signal_power和noise_power量级差太多,或者num_samples太小导致统计量方差过大。解决:先把信噪比设成 0 dB 附近验证链路,再逐步降低;样本数至少 100,低信噪比建议 500 以上。这个坑的本质是数值尺度,不是算法错。
4.5 现象:无人机节点加进去没提升,原因:高度和水平距离没调,视距概率接近 0
把无人机当节点加进去,ROC 几乎没变。原因是无人机高度设太低、水平距离太远,仰角小到视距概率接近 0,空中节点退化成地面节点。解决:把高度提到 100 米以上,水平距离控制在高度的一到三倍,让仰角落在视距概率高的区间。这一步调完,无人机辅助的增益才看得出来。
5. 进阶技巧:用无人机轨迹优化把协作检测概率再抬一档
把链路跑通只是起点,真正拉开差距的是无人机轨迹。固定悬停的无人机只能覆盖一个区域,而主用户信号和地面节点分布是变的,轨迹优化能让无人机飞到“边际增益最大”的位置。我一般用简化的贪心策略:把感知区域网格化,每步让无人机朝检测概率提升最大的网格移动,同时约束飞行距离和高度。下面是一个最小可跑的贪心轨迹:
def greedy_trajectory(grid_points, height, start_idx=0, steps=20): """贪心选点:每步选使协作检测概率提升最大的网格""" path = [start_idx] visited = {start_idx} for _ in range(steps - 1): best_idx, best_gain = None, -np.inf for idx in range(len(grid_points)): if idx in visited: continue # 用无人机到该点的信道增益近似边际贡献 gain = uav_channel_gain(height, grid_points[idx]) if gain > best_gain: best_gain, best_idx = gain, idx path.append(best_idx) visited.add(best_idx) return path逻辑说明:greedy_trajectory每步在未访问网格里选信道增益最大的点,uav_channel_gain复用前面的空对地模型。参数说明:grid_points是水平网格坐标数组,height是飞行高度,steps是轨迹点数。这个贪心不是最优,但胜在能跑、能对比。验证方法很直接:把轨迹点对应的无人机位置代入协作感知链路,跑 ROC 对比固定悬停,看检测概率在相同虚警下提升多少。如果提升不明显,先检查网格粒度和高度约束,别急着换算法。
我自己的习惯是,任何轨迹优化都先拿固定悬停当基线,基线打不过就别谈优化。这套 Python 实现从信道到 ROC 再到轨迹,最值钱的部分不是代码本身,而是你能随时改一个参数、重跑一遍、看到曲线怎么动。希望帮到你。
本文还有配套的精品资源,点击获取