
简介这份PDF文献面向通信工程、认知无线电与机器学习方向的研究生及科研人员聚焦如何利用BP神经网络对电磁频谱状态进行智能预测以提升频谱接入效率与时域、空间域的利用灵活性。全文围绕最速下降法学习的BP网络展开对频谱预测算法进行数学建模通过训练历史频谱数据调节权值与阈值建立输入与输出间的认知映射从而降低预测误差。资源包共1个PDF文件约934KB内容为完整的期刊论文含中英文摘要、引言、算法建模与实验分析等章节便于按学术脉络精读。目前已有181人学习。读者可从中获取BP神经网络用于频谱预测的完整建模思路、参数优化方法及误差评估流程理解其相较自回归、马尔可夫等方法的收敛与时效特性为通信、雷达、导航等领域的频谱预测研究提供可复用的算法框架与实验参考。1. 从一份 2021 年的频谱预测 PDF 说起它到底能帮你省下多少试错时间如果你正在做认知无线电、动态频谱接入或者频谱态势感知相关的课题大概率会遇到一个很现实的问题频谱数据拿到手了但怎么建模、怎么预测、误差怎么压下去网上搜到的要么是纯理论推导要么是调包跑个 demo 就完事。这份《基于BP神经网络的智能认知频谱预测技术研究》是一篇发表在《电子技术应用》2021 年第 47 卷第 1 期的期刊论文作者来自空军工程大学和西安电子科技大学全文围绕一个核心问题展开用 BP 神经网络对电磁频谱状态做预测把“过去一段时间的信道占用情况”映射到“未来某时刻的占用/空闲状态”。它给出的不是泛泛的概念介绍而是一套完整的数学建模过程——从最速下降法的权值更新公式到隐含层偏导数的逐层反传推导再到 MATLAB 仿真验证参数写得非常具体输入层 4 个神经元、隐含层 20 个、输出层 1 个、最大训练 1000 次、学习率 0.01、目标精度 0.0001。适合谁看做频谱预测算法复现的研究生、需要快速搭建 baseline 的通信工程师、以及想理解 BP 网络在真实信号处理场景中怎么落地的人。它不能直接给你一个可部署的系统但能让你少走至少两週的公式推导弯路。2. BP 神经网络做频谱预测数学建模与参数选型逻辑2.1 为什么频谱预测偏偏选了 BP 网络频谱预测这个问题的本质是从历史观测序列中学习一种非线性映射关系。论文里对比了几类主流方法自回归模型对线性平稳序列有效但频谱占用状态本质上是非线性的、突发性的马尔可夫模型预测精度不错但收敛时间长、时效性差对认知无线电这种需要快速决策的场景不太友好而 BP 神经网络的优势在于它不需要提前知道描述输入输出关系的方程式直接通过训练就能建立认知关系。论文里有一句话点得很准神经网络可消除对参数设置和概率计算的需求。这意味着你不需要假设频谱状态服从某种分布也不需要手动设计状态转移矩阵。对于“0/1”这种二值化的信道占用序列BP 网络通过 Sigmoid 激活函数把输出压到 (0,1) 区间再通过阈值判断就能得到预测状态。这个思路在工程上很实用——你拿到的频谱感知数据往往就是这种离散的占用/空闲标记不需要额外做复杂的特征工程。另一个选型理由是 BP 网络的结构灵活性。论文采用的是经典三层结构输入层、单隐含层、输出层。理论上单隐含层就能实现任意维度的非线性映射所以不需要堆深层。隐含层节点数选了 20 个这个数字不是随便拍的——太少会导致欠拟合预测输出几乎恒定太多会过拟合训练集误差很小但验证集一塌糊涂。论文没有展开讲 20 是怎么来的但常见做法是参考经验公式 ( \sqrt{nm} \alpha )n 为输入节点数m 为输出节点数α 取 1~104 输入 1 输出算下来大概在 3~12 之间取 20 是偏保守的选择留了足够的冗余容量。2.2 最速下降法的权值更新从公式到可执行逻辑论文的核心推导集中在第 1 章从误差函数定义到输出层、隐含层的偏导数计算再到权值和阈值的更新公式。如果你直接看论文里的公式可能会觉得跳跃我把它拆成可执行的步骤。误差函数定义为[ e \frac{1}{2} \sum_{o1}^{q} (d_o(k) - yo_o(k))^2 ]其中 ( d_o(k) ) 是期望输出( yo_o(k) ) 是实际输出q 是输出层神经元个数。这里的 1/2 是为了求导时消掉平方的 2纯粹是数学上的便利。输出层权值 ( w_{ho} ) 的更新量[ \Delta w_{ho} -\mu \frac{\partial e}{\partial w_{ho}} \mu \cdot \delta_o(k) \cdot ho_h(k) ]其中 ( \delta_o(k) (d_o(k) - yo_o(k)) \cdot yo_o(k) \cdot (1 - yo_o(k)) )( \mu ) 是学习率。注意 ( yo_o(k) \cdot (1 - yo_o(k)) ) 这一项就是 Sigmoid 函数的导数论文里专门推导了 ( f(x) y(1-y) )这是整个反传算法能跑通的关键。隐含层权值 ( w_{ih} ) 的更新量[ \Delta w_{ih} \mu \cdot \delta_h(k) \cdot x_i(k) ]其中 ( \delta_h(k) \left( \sum_{o1}^{q} \delta_o(k) \cdot w_{ho} \right) \cdot ho_h(k) \cdot (1 - ho_h(k)) )。这里能看到误差是怎么从输出层“反向传递”到隐含层的——先把输出层的误差信号按权值加权求和再乘上隐含层输出的 Sigmoid 导数。用 Python 写出来大概是这样import numpy as np def sigmoid(x): return 1.0 / (1.0 np.exp(-x)) def sigmoid_derivative(y): # y 是 sigmoid 的输出 return y * (1.0 - y) # 网络结构4 输入20 隐含1 输出 n_input, n_hidden, n_output 4, 20, 1 learning_rate 0.01 max_epochs 1000 target_error 0.0001 # 初始化权值和阈值范围 (-1, 1) np.random.seed(42) w_ih np.random.uniform(-1, 1, (n_input, n_hidden)) b_h np.random.uniform(-1, 1, (1, n_hidden)) w_ho np.random.uniform(-1, 1, (n_hidden, n_output)) b_o np.random.uniform(-1, 1, (1, n_output)) # 假设输入是前 4 个时刻的状态输出是下一时刻状态 # X_train: (样本数, 4), y_train: (样本数, 1) # 这里用随机数据演示前向和反向过程 X_sample np.random.randint(0, 2, (1, 4)).astype(float) d_sample np.random.randint(0, 2, (1, 1)).astype(float) # 前向传播 hi np.dot(X_sample, w_ih) - b_h # 隐含层输入 ho sigmoid(hi) # 隐含层输出 yi np.dot(ho, w_ho) - b_o # 输出层输入 yo sigmoid(yi) # 输出层输出 # 反向传播 delta_o (d_sample - yo) * sigmoid_derivative(yo) # 输出层误差信号 delta_h np.dot(delta_o, w_ho.T) * sigmoid_derivative(ho) # 隐含层误差信号 # 权值更新 w_ho learning_rate * np.dot(ho.T, delta_o) w_ih learning_rate * np.dot(X_sample.T, delta_h) b_o - learning_rate * delta_o.sum(axis0, keepdimsTrue) b_h - learning_rate * delta_h.sum(axis0, keepdimsTrue)这段代码里几个参数需要特别注意。learning_rate 0.01是论文明确给出的值学习率太大会导致权值更新震荡甚至发散太小则收敛慢。max_epochs 1000是训练轮数上限实际跑的时候通常会加一个 early stopping——如果连续若干轮误差不再下降就提前停。target_error 0.0001是目标精度达到这个误差就认为收敛了。初始化范围 (-1, 1) 是论文指定的比常见的 (0, 1) 或小方差高斯分布更激进一些目的是让初始权值有正有负避免所有神经元输出同向。2.3 数据集的构造与训练/验证划分论文用的数据集是 500 个仅含“0”和“1”的序列其中“1”代表信道被占用“0”代表空闲。前 250 个作为训练数据后 250 个作为预测验证数据。训练数据内部又做了二次划分前 200 个作为训练输入后 50 个作为训练输出。这个划分方式有一个隐含的逻辑输入是前 4 个时刻的状态输出是第 5 个时刻的状态。所以对于 200 个训练输入实际能构造出的样本对是 196 个因为要留出 4 个时刻的窗口。论文没有明确说滑动窗口的步长但常见做法是步长为 1即每个时刻都作为一次预测的起点。用 Python 构造数据集的代码def make_dataset(sequence, window_size4): sequence: 一维 0/1 序列 window_size: 输入窗口长度 返回 X: (样本数, window_size), y: (样本数, 1) X, y [], [] for i in range(len(sequence) - window_size): X.append(sequence[i:iwindow_size]) y.append(sequence[iwindow_size]) return np.array(X, dtypefloat), np.array(y, dtypefloat).reshape(-1, 1) # 生成 500 个 0/1 随机序列论文中的做法 np.random.seed(0) full_seq np.random.randint(0, 2, 500) # 前 250 训练后 250 验证 train_seq full_seq[:250] test_seq full_seq[250:] X_train, y_train make_dataset(train_seq, window_size4) X_test, y_test make_dataset(test_seq, window_size4) print(f训练样本数: {len(X_train)}, 测试样本数: {len(X_test)})这里有一个容易翻车的地方论文说“前 200 个作为训练输入后 50 个作为训练输出”但实际构造样本对的时候输入和输出是重叠的——第 200 个输入对应的输出是第 204 个时刻的状态已经超出了“前 200”的范围。所以更合理的理解是前 200 个时刻的数据用于构造输入窗口输出取窗口后一个时刻实际可用的样本对数量是 196 左右。这个细节论文没有展开但复现的时候必须搞清楚否则训练集和验证集的边界会对不齐。训练过程中论文提到“实际值与期望值的差距还是比较大的有时候预测值甚至刚好与期望值相反”这是 BP 网络在二值序列上训练的典型现象——因为 Sigmoid 输出是连续值而期望输出是 0 或 1早期网络输出集中在 0.5 附近四舍五入后就会随机偏向 0 或 1。经过足够多轮训练后输出会逐渐向 0 和 1 两端分化预测准确率才会上来。3. MATLAB 仿真复现从训练曲线到误差分布的完整验证3.1 网络初始化与训练参数设置论文的仿真是在 MATLAB 环境下完成的虽然现在很多人用 Python但 MATLAB 的神经网络工具箱在信号处理领域仍然很常见。如果你要用 MATLAB 复现核心步骤和 Python 版本一致但参数设置的方式不同。论文给出的参数清单参数值说明输入层神经元数4前 4 个时刻的历史状态隐含层神经元数20单隐含层输出层神经元数1下一时刻的预测状态最大训练次数1000防止无限循环学习率0.01最速下降法的步长目标精度0.0001均方误差阈值初始化范围(-1, 1)权值和阈值的初始值在 MATLAB 里可以用newff或者更现代的feedforwardnet来建网% 构造训练数据 full_seq randi([0 1], 1, 500); train_seq full_seq(1:250); test_seq full_seq(251:end); window_size 4; X_train []; y_train []; for i 1:(length(train_seq) - window_size) X_train [X_train; train_seq(i:iwindow_size-1)]; y_train [y_train; train_seq(iwindow_size)]; end X_train X_train; y_train y_train; % 建网 net feedforwardnet(20); % 20 个隐含层神经元 net.trainFcn traingd; % 最速下降法 net.trainParam.epochs 1000; net.trainParam.lr 0.01; net.trainParam.goal 0.0001; net.trainParam.showWindow false; % 训练 [net, tr] train(net, X_train, y_train); % 预测 y_pred net(X_train);traingd就是最速下降法对应论文里的学习算法。feedforwardnet(20)默认是单隐含层 20 个神经元和论文一致。net.trainParam.goal 0.0001对应目标精度。注意 MATLAB 的feedforwardnet默认会把数据分成训练/验证/测试三部分如果要严格按论文的划分来需要手动设置net.divideFcn dividetrain把所有数据都用于训练。3.2 训练过程中的预测输出与期望输出对比论文图 6 展示了训练过程中实际预测输出与期望输出的对比。从描述来看训练早期的预测值和期望值差距较大甚至出现预测值刚好与期望值相反的情况。这是正常的——BP 网络在二值序列上训练时初始权值是随机的网络输出一开始集中在 0.5 附近经过 Sigmoid 后就是 0.5 左右四舍五入到 0 或 1 时就会随机偏向。随着训练进行误差反向传播不断调整权值输出逐渐向 0 和 1 两端分化。论文提到“经过一定的训练之后其预测准确度就会有所上升”但没有给出具体的准确率数字。从图 6 的曲线来看50 个样本中大部分预测值在训练后期已经能跟上期望值的趋势但仍有部分样本存在明显偏差。如果你自己复现建议在训练过程中每隔若干轮记录一次训练集和验证集的均方误差画出一条误差下降曲线。正常情况下训练误差会持续下降验证误差先下降后可能回升过拟合的信号。如果验证误差一直不下降说明网络容量不够或者学习率太小如果验证误差剧烈震荡说明学习率太大。3.3 预测结果的误差分布分析论文图 7 和图 8 分别展示了预测结果与期望值的对比、以及预测误差的分布。从描述来看大部分样本的预测误差在 ±0.2 以内少数样本误差在 ±1 以内极个别样本偏差较大。这个误差分布说明网络已经学到了主要的模式但对某些“突变”样本的预测能力有限。频谱序列中如果出现连续的 0 后突然变成 1或者连续的 1 后突然变成 0网络很难从历史窗口中捕捉到这种突变信号——因为输入只有前 4 个时刻的状态如果这 4 个时刻都是 0网络倾向于预测下一个也是 0。用 Python 画误差分布的代码import matplotlib.pyplot as plt # 假设 y_pred 是网络输出y_test 是期望输出 errors y_test.flatten() - y_pred.flatten() plt.figure(figsize(10, 4)) plt.subplot(1, 2, 1) plt.plot(y_test.flatten(), b-, label期望输出) plt.plot(y_pred.flatten(), r--, label预测输出) plt.xlabel(样本) plt.ylabel(状态) plt.legend() plt.title(预测结果与期望值对比) plt.subplot(1, 2, 2) plt.hist(errors, bins20, edgecolorblack) plt.xlabel(预测误差) plt.ylabel(频数) plt.title(误差分布) plt.tight_layout() plt.show()从误差分布图能看出几个问题如果误差集中在 0 附近说明网络预测很准如果误差在 -1 和 1 附近有双峰说明网络在某些样本上完全预测反了如果误差分布很宽说明网络还没收敛好。论文的误差分布是“小部分在 ±0.2 以内其余准确度较高极个别偏差较大”这是一个比较健康的分布形态。提示二值序列预测中准确率不是唯一指标。如果 0 和 1 的比例严重失衡比如 90% 都是 0网络全部预测 0 也能拿到 90% 的准确率但这样的模型没有实用价值。建议同时看召回率和 F1 分数。4. 避坑与排查复现频谱预测模型时最容易翻车的五个地方4.1 现象训练误差一直不下降输出恒定在 0.5 附近原因学习率太小或者初始化范围不合适。论文用的是 (-1, 1) 均匀分布初始化如果你用了 (0, 1) 或者小方差高斯分布初始权值太小会导致隐含层输入接近 0Sigmoid 输出接近 0.5梯度信号很弱权值更新量极小网络几乎不学习。解决检查初始化范围确保权值有正有负。如果还是不行把学习率从 0.01 提高到 0.05 或 0.1 试一下但要注意观察误差是否震荡。另一个常见原因是输入数据没有做归一化——虽然论文的数据是 0/1 序列本身就在 [0,1] 范围内但如果你用的是其他频谱数据比如功率值必须先归一化到 [0,1] 或 [-1,1]。4.2 现象训练集误差很小但验证集误差很大原因过拟合。隐含层 20 个神经元对于 196 个训练样本来说容量偏大网络可能记住了训练样本的噪声而不是学到了通用模式。解决减少隐含层神经元数量比如降到 10 个或者增加训练数据量。另一个方法是加正则化项在误差函数里加上权值的 L2 惩罚[ E \frac{1}{2m} \sum_{k1}^{m} \sum_{o1}^{q} (d_o(k) - yo_o(k))^2 \frac{\lambda}{2} \sum w^2 ]论文没有提正则化但实际工程中这是控制过拟合的常用手段。λ 一般取 0.001 到 0.01 之间。4.3 现象预测结果全是 0 或者全是 1原因训练数据中 0 和 1 的比例严重失衡。论文用的是随机生成的 500 个 0/1 序列理论上 0 和 1 各占一半但如果你用真实频谱数据某些信道可能 90% 的时间都是空闲的网络学到“全部预测为空闲”就能拿到很低的误差。解决对训练样本做重采样让 0 和 1 的样本数量接近。或者改用加权误差函数对少数类样本的误差给更大的权重。另一个思路是调整输出阈值——默认是 0.5如果正样本很少可以把阈值降到 0.3让网络更容易输出 1。4.4 现象训练过程中误差突然变成 NaN原因学习率太大导致权值更新发散Sigmoid 输入变得极大或极小梯度消失或爆炸。解决把学习率降到 0.001 或更低或者改用自适应学习率方法如 Adam。论文用的是固定学习率 0.01在 196 个样本的小数据集上一般不会发散但如果你的数据量更大或者特征维度更高固定学习率可能不够稳。另一个原因是数据里有 NaN 或 Inf检查一下输入数据是否干净。4.5 现象复现结果和论文对不上误差比论文大很多原因随机种子不同。BP 网络的初始权值是随机生成的不同的随机种子会导致完全不同的训练轨迹。论文没有给出随机种子所以你的结果和论文有差异是正常的。解决多跑几次取平均结果。如果每次跑出来的误差都远大于论文检查一下几个关键参数是否一致输入窗口是不是 4、隐含层是不是 20、学习率是不是 0.01、最大训练次数是不是 1000。另外注意论文的误差函数定义是 ( \frac{1}{2m} \sum \sum (d - yo)^2 )有些实现用的是 ( \frac{1}{m} \sum \sum (d - yo)^2 ) 或者均方根误差量级会差一个系数。5. 从单步预测到多步滚动一个能直接套用的验证技巧论文做的是单步预测——用前 4 个时刻预测第 5 个时刻。但在实际频谱接入决策中你往往需要知道未来多个时刻的状态比如未来 10 个时刻哪些是空闲的。这就涉及到多步预测的问题。最直接的做法是滚动预测先用前 4 个时刻预测第 5 个然后把预测出的第 5 个时刻加入输入窗口用第 2 到第 5 个时刻预测第 6 个以此类推。但这样做有一个致命问题误差会累积。第一步预测错了这个错误会作为输入传给第二步导致后续预测越来越偏。我一般会用一个折中方案训练一个多输出网络输出层有 k 个神经元每个对应未来一个时刻的状态。这样只需要一次前向传播就能得到未来 k 个时刻的预测避免了误差累积。代价是输出层神经元数量增加训练难度会上升。用 Python 改造输出层的代码def make_multistep_dataset(sequence, window_size4, horizon5): window_size: 输入窗口长度 horizon: 预测未来多少个时刻 X, y [], [] for i in range(len(sequence) - window_size - horizon 1): X.append(sequence[i:iwindow_size]) y.append(sequence[iwindow_size:iwindow_sizehorizon]) return np.array(X, dtypefloat), np.array(y, dtypefloat) # 生成多步预测数据集 X_multi, y_multi make_multistep_dataset(full_seq, window_size4, horizon5) print(f输入维度: {X_multi.shape}, 输出维度: {y_multi.shape}) # 网络输出层改为 5 个神经元 n_output_multi 5 w_ho_multi np.random.uniform(-1, 1, (n_hidden, n_output_multi)) b_o_multi np.random.uniform(-1, 1, (1, n_output_multi))验证多步预测效果的时候不要只看整体准确率要分时刻看。通常第 1 步的准确率最高随着预测步数增加准确率会逐渐下降。如果第 3 步之后的准确率掉到 50% 以下相当于随机猜说明这个网络的有效预测范围只有 2 步再远就没有参考价值了。另一个验证技巧是画混淆矩阵。对于二值预测混淆矩阵能直观地看出网络是偏向预测 0 还是偏向预测 1from sklearn.metrics import confusion_matrix, classification_report y_pred_binary (y_pred 0.5).astype(int) y_true_binary y_test.astype(int) print(confusion_matrix(y_true_binary, y_pred_binary)) print(classification_report(y_true_binary, y_pred_binary))如果混淆矩阵显示假阳性很高把空闲预测成占用说明网络过于保守如果假阴性很高把占用预测成空闲说明网络过于激进。在频谱接入场景中假阴性比假阳性更危险——把占用信道预测成空闲会导致冲突所以通常希望网络偏向保守宁可少接入也不要冲突。从那以后我每次复现这类论文都会先把数据集的 0/1 比例、训练/验证划分边界、随机种子这三个东西固定下来再开始调参。不然跑出来的结果忽好忽坏根本分不清是模型问题还是数据问题。希望帮到你。本文还有配套的精品资源点击获取