简介这份资源面向具备基础编程能力、关注无线传感网络频谱感知的科研人员与技术爱好者围绕论文“Efficient Cooperative Spectrum Sensing in UAV-Assisted Cognitive Wireless Sensor Networks”的复现展开帮助读者理解无人机辅助认知无线传感网络中协作频谱感知的系统建模与算法实现。内容涵盖仿真参数配置、信号模型与能量检测、CVR、SVR、S1、DS1四种投票规则的实现以及检测性能图与样本大小图的仿真对比可用于探索不同投票规则表现、分析最优方案并评估无人机环境下的检测效果。资源包为1个docx文档约29KB以文字与代码说明为主结构紧凑便于查阅。目前已有108人学习适合希望借助具体编程范例快速上手协作频谱感知实现与仿真分析的读者参考。1. 无人机协作频谱感知的 Python 复现从论文公式到可跑通的仿真脚本认知无线传感网络里频谱感知是决定“能不能用这段空闲频段”的第一道关卡。单节点检测受阴影衰落影响大于是把多台无人机组成协作感知阵列用投票规则做融合判决就成了论文Efficient Cooperative Spectrum Sensing in UAV-Assisted Cognitive Wireless Sensor Networks的核心思路。这份 Python 实现把论文里的系统模型、能量检测、四种投票规则CVR、SVR、S1、DS1和仿真对比全部落成了可执行代码跑一遍就能看到不同规则在检测性能和样本开销上的差异。适合做无线传感网、认知无线电方向的研究生和工程师尤其是需要快速复现论文结论、又不想从零推导公式的人。下面按“模型怎么建 → 规则怎么写 → 仿真怎么跑 → 坑在哪”的顺序拆开讲。2. 系统模型与能量检测参数怎么设、信号怎么生成2.1 无人机协作感知的帧结构与时隙划分论文的感知流程建立在一个帧结构上每帧总时长T先划出tau_s做本地感知再划出tau_r做结果上报。N架无人机各自在感知时隙内做能量检测得到 0/1 的本地判决然后在报告时隙把判决送到融合中心。融合中心按某种投票规则给出全局判决主用户存在1还是不存在0。代码里N 100表示无人机数量或 mini-slot 数量K 40是决策门限——累计收到 40 个“1”就判主用户存在。P_u 3是主用户发射功率T 100e-3秒tau_s tau_r 0.4e-3秒。这些数值不是随便填的它们决定了每帧能容纳多少个感知和报告动作也直接影响后续样本大小的统计口径。本地检测概率P_d 0.7、虚警概率P_f 0.4、主用户存在概率P_H1 0.4、不存在概率P_H0 0.6这四个参数是能量检测的输入。P_d越高说明单节点越灵敏P_f越高说明误报越多。实际调参时P_d和P_f通常来自 ROC 曲线上的工作点不能同时拉满。2.2 能量检测函数的实现逻辑与参数含义原始代码里的energy_detection用随机数模拟本地判决先按P_H1决定主用户是否真的存在再按P_d或P_f决定节点报 1 还是 0。这是一种“行为级”建模不涉及具体信号波形适合快速验证投票规则。import numpy as np N 100 # 无人机数量 / mini-slots 数量 K 40 # 决策门限累计 1 的个数达到 K 即判主用户存在 P_u 3 # 主用户发射功率 T 100e-3 # 每帧持续时间秒 tau_s 0.4e-3 # 每个感知时隙持续时间 tau_r 0.4e-3 # 每个报告时隙持续时间 P_d 0.7 # 本地检测概率 P_f 0.4 # 本地虚警概率 P_H0 0.6 # 主用户不存在的先验概率 P_H1 0.4 # 主用户存在的先验概率 def energy_detection(Pu, N, tau_s, P_d, P_f): 行为级能量检测按先验概率生成主用户状态 再按检测/虚警概率生成本地 0/1 判决。 decisions [] for _ in range(N): if np.random.rand() P_H1: # 主用户存在以 P_d 概率正确报 1 decision 1 if np.random.rand() P_d else 0 else: # 主用户不存在以 P_f 概率误报 1 decision 1 if np.random.rand() P_f else 0 decisions.append(decision) return decisions这段代码的关键在于P_H1和P_H0控制的是“真实世界”的状态P_d和P_f控制的是“节点认知”的准确度。两者叠加后实际报 1 的概率是P_H1 * P_d P_H0 * P_f。如果你把P_f设得过高融合中心会收到大量假阳性全局虚警率跟着飙升。常见做法是固定P_f在 0.1 以下再调P_d看检测性能的天花板。提示energy_detection返回的是长度为N的列表顺序对应无人机编号。后续投票规则对这个列表的遍历顺序敏感尤其是 SVR 和 DS1。3. 四种投票规则的代码实现CVR、SVR、S1、DS1 到底差在哪3.1 CVR 与 SVR固定样本 vs 顺序截断CVRConventional Voting Rule是最朴素的规则等所有N个本地判决都到齐数一下“1”的个数大于等于K就判 1否则判 0。它的样本大小恒为N没有提前终止的机会。def CVR(decisions): 固定投票规则收齐所有样本后按门限 K 判决。 total_samples len(decisions) global_decision 1 if sum(decisions) K else 0 return global_decision, total_samplesSVRSequential Voting Rule则引入顺序思想一边收样本一边计数一旦“1”的个数达到K或者“0”的个数达到N - K 1立刻停止接收并给出判决。这样在信道条件好、判决倾向明显时能省下大量样本。def SVR(decisions): 顺序投票规则累计 1 达到 K 或累计 0 达到 N-K1 时提前终止。 count_ones 0 count_zeros 0 for decision in decisions: if decision 1: count_ones 1 else: count_zeros 1 if count_ones K or count_zeros N - K 1: global_decision 1 if count_ones K else 0 return global_decision, count_ones count_zeros global_decision 1 if count_ones K else 0 return global_decision, count_ones count_zerosSVR 的提前终止条件count_zeros N - K 1容易被忽略。它的含义是即使剩下的样本全是 1也无法让 count_ones 达到 K所以不如直接判 0。这个边界条件写错SVR 的样本节省效果会大打折扣。3.2 S1 与 DS1只盯“1”和差分跳变S1 是 SVR 的简化版只累计“1”的个数达到K就判 1 并返回如果遍历完都没达到判 0。它不关心“0”的累计因此在主用户存在概率较低时可能会多收一些样本才放弃。def S1(decisions): 改进顺序规则只累计 1达到 K 即判 1否则遍历完判 0。 count_ones 0 for decision in decisions: if decision 1: count_ones 1 if count_ones K: return 1, count_ones return 0, count_onesDS1Differential Sequential 1更激进它只统计“决策发生跳变”的样本。具体说第一个样本无条件计入之后只有当当前判决与上一个判决不同时才把样本计入sample_count并且如果新判决是 1才增加count_ones。这样做的假设是连续相同的判决携带的信息量低跳变点才是关键。def DS1(decisions): 差分顺序规则只统计跳变样本跳变为 1 时累计 count_ones。 count_ones 0 prev_decision None sample_count 0 for decision in decisions: if prev_decision is None: prev_decision decision sample_count 1 if decision 1: count_ones 1 else: if decision ! prev_decision: sample_count 1 if decision 1: count_ones 1 prev_decision decision if count_ones K: return 1, sample_count return 0, sample_countDS1 的sample_count和count_ones是两套计数前者用于统计实际消耗的样本后者用于判决。代码里prev_decision只在跳变时更新意味着连续相同的判决会被“折叠”。这种设计在本地判决相关性强的场景下能大幅压缩样本但如果判决序列本身随机性很高跳变频繁DS1 的样本节省就不明显了。注意DS1 的sample_count在第一个样本后可能小于count_ones因为跳变为 0 时只增加sample_count不增加count_ones。画图时如果直接拿sample_count当横轴要确认它的物理含义是“消耗的样本数”而不是“收到的 1 的个数”。4. 仿真实验与结果绘制场景构造、循环逻辑与出图4.1 三种场景的连续相同判决约束代码里定义了三个场景(30, 35)、(15, 20)、(1, 5)分别表示“连续相同本地决策的感知帧数范围”。场景 1 要求连续相同判决的帧数落在 30 到 35 之间场景 3 则允许 1 到 5 的短连续。这个约束是为了模拟不同信道相关性连续相同判决越多说明节点之间的相关性越强DS1 的差分机制越有优势。仿真主循环里有一段“重新生成”逻辑如果生成的decisions不满足当前场景的连续长度要求就重新调用energy_detection直到满足为止。这段逻辑用while循环实现内部有一个i -1的重置操作容易写错。scenarios [(30, 35), (15, 20), (1, 5)] def simulation(): detection_performance {r: [] for r in [CVR, SVR, S1, DS1]} sample_size {r: [] for r in [CVR, SVR, S1, DS1]} for scenario in scenarios: scenario_detection {r: [] for r in [CVR, SVR, S1, DS1]} scenario_sample {r: [] for r in [CVR, SVR, S1, DS1]} for _ in range(1000): decisions energy_detection(P_u, N, tau_s, P_d, P_f) # 校验连续相同判决长度是否落在当前场景范围内 consecutive_count 1 i 0 while i N - 1: if decisions[i] decisions[i 1]: consecutive_count 1 else: if consecutive_count scenario[0] or consecutive_count scenario[1]: decisions energy_detection(P_u, N, tau_s, P_d, P_f) i -1 consecutive_count 1 else: consecutive_count 1 i 1 for rule, func in [(CVR, CVR), (SVR, SVR), (S1, S1), (DS1, DS1)]: gd, samples func(decisions) scenario_detection[rule].append(gd) scenario_sample[rule].append(samples) for rule in [CVR, SVR, S1, DS1]: detection_performance[rule].append(np.mean(scenario_detection[rule])) sample_size[rule].append(np.mean(scenario_sample[rule])) return detection_performance, sample_sizei -1的作用是让while循环从头开始重新校验新生成的序列。如果写成i 0会跳过第一个样本的校验导致场景约束不严格。这个细节在论文复现里很关键因为场景约束直接影响 DS1 的样本节省幅度。4.2 检测性能图与样本大小图的绘制仿真结束后用matplotlib画两张图一张是检测性能全局判决的均值随场景变化另一张是平均样本大小随场景变化。检测性能的均值越接近 1 越好样本大小越小越好。import matplotlib.pyplot as plt detection_performance, sample_size simulation() scenario_labels [Scenario 1, Scenario 2, Scenario 3] plt.figure(figsize(12, 6)) for rule in [CVR, SVR, S1, DS1]: plt.plot(scenario_labels, detection_performance[rule], markero, labelrule) plt.xlabel(Scenario) plt.ylabel(Detection Performance) plt.title(Detection Performance of Different Voting Rules) plt.legend() plt.grid(True) plt.show() plt.figure(figsize(12, 6)) for rule in [CVR, SVR, S1, DS1]: plt.plot(scenario_labels, sample_size[rule], markers, labelrule) plt.xlabel(Scenario) plt.ylabel(Average Sample Size) plt.title(Sample Size of Different Voting Rules) plt.legend() plt.grid(True) plt.show()出图后重点看两个地方一是 DS1 在场景 1长连续下的样本大小是否显著低于 CVR二是 S1 和 SVR 的检测性能是否接近 CVR。如果 DS1 的检测性能掉得厉害说明差分机制把太多有效信息折叠掉了需要回头检查count_ones的累计逻辑。提示np.mean(scenario_detection[rule])统计的是全局判决的均值不是检测概率。要算真正的检测概率需要把“主用户真实存在”的样本单独拿出来统计其中判 1 的比例。5. 避坑与排查复现时最容易翻车的五个地方5.1 现象DS1 样本大小比 CVR 还大原因sample_count在跳变时才增加但如果判决序列本身跳变频繁sample_count会接近N而count_ones可能迟迟达不到K导致遍历完才返回。解决检查场景约束是否真的生效尤其是i -1的重置逻辑。如果场景 3 的(1, 5)被误用成(30, 35)DS1 的跳变次数会被人为压低样本大小反而失真。5.2 现象SVR 的样本大小恒等于 N原因提前终止条件写错。常见错误是把count_zeros N - K 1写成count_zeros K导致“0”的累计永远达不到终止门限。解决确认N - K 1的计算N 100、K 40时N - K 1 61。也就是说收到 61 个 0 才能判 0 并终止。5.3 现象检测性能图四条线几乎重合原因P_d和P_f设置得太接近导致本地判决的区分度不够。P_d 0.7、P_f 0.4时报 1 的概率在主用户存在和不存在两种情况下分别是 0.7 和 0.4差距只有 0.3。解决把P_f降到 0.1 左右或者把P_d提到 0.9让本地判决更有信息量。也可以增加仿真次数1000到5000降低统计噪声。5.4 现象场景校验循环卡死或极慢原因while循环里重新生成decisions后i -1让循环从头开始但如果新序列仍然不满足场景约束会反复重新生成。场景 1 的(30, 35)要求连续相同判决至少 30 个而N 100时随机序列很难自然满足导致大量重试。解决要么放宽场景约束要么在生成decisions时直接构造满足连续长度的序列而不是靠拒绝采样。5.5 现象DS1 的count_ones和sample_count对不上原因DS1 在跳变为 0 时只增加sample_count不增加count_ones跳变为 1 时两者都增加。如果误以为sample_count就是“收到的 1 的个数”画图时会把样本大小和检测门限混在一起。解决在 DS1 里加一行调试输出打印每次跳变时的decision、count_ones、sample_count确认计数逻辑符合预期。6. 进阶玩法把行为级模型换成带噪声的能量检测原始代码的energy_detection是行为级建模不涉及具体信号。如果想更贴近论文的物理层可以把本地判决换成基于高斯白噪声的能量检测。下面这个refined_energy_detection引入了噪声功率和阈值计算主用户存在时接收信号为sqrt(Pu) * randn() sqrt(noise_power) * randn()不存在时只有噪声。def refined_energy_detection(Pu, N, tau_s, P_d, P_f, noise_power0.1): 带高斯白噪声的能量检测 主用户存在时信号加噪声不存在时仅噪声 按阈值判 0/1。 decisions [] for _ in range(N): if np.random.rand() P_H1: signal np.sqrt(Pu) * np.random.randn() noise np.sqrt(noise_power) * np.random.randn() received signal noise threshold np.sqrt(noise_power) * np.sqrt(2 * np.log(1 / (1 - P_d))) decisions.append(1 if np.abs(received) threshold else 0) else: noise np.sqrt(noise_power) * np.random.randn() threshold np.sqrt(noise_power) * np.sqrt(2 * np.log(1 / (1 - P_f))) decisions.append(1 if np.abs(noise) threshold else 0) return decisions这个版本里阈值由P_d和P_f反推threshold sqrt(noise_power) * sqrt(2 * ln(1 / (1 - P)))。P越接近 1ln(1 / (1 - P))越大阈值越高判决越保守。把refined_energy_detection替换掉原来的energy_detection再跑一遍仿真你会看到检测性能整体下移因为噪声引入了额外的随机性。另一个进阶方向是参数敏感性分析把K从 20 扫到 50每个K跑一轮仿真画一张“检测性能 vs K”的曲线。代码结构上把simulation()里的K改成外部传入的参数即可。注意K变化时SVR 的提前终止条件N - K 1也要跟着变否则样本大小的对比会失真。我自己的习惯是每次改完投票规则或检测模型先跑一个N 10、K 4的小规模用例打印每个规则的global_decision和samples确认逻辑对了再放大到N 100。这样能避免在大循环里浪费时间。从那以后我每次复现论文代码都强制先跑小规模冒烟测试再上全量仿真。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?