基于BP神经网络的调制信号识别:六类信号分类与特征提取实战
简介基于BP神经网络的调制信号识别资源面向使用MATLAB进行通信信号处理与机器学习教学研究的本硕博学生与工程技术人员。资源聚焦如何利用BP神经网络自动区分2ASK、4ASK、2PSK、4PSK、2FSK、4FSK六种常见数字调制类型覆盖从特征构造、网络训练到结果验证的完整仿真流程可直接用于课程设计、毕业设计或课题预研。包内共2个文件包含MATLAB主程序脚本与配套操作录像压缩包仅348KB轻量便携脚本内实现核心识别算法与清晰注释录像则演示了在MATLAB 2021a及以上版本中的运行步骤、参数调整及常见注意事项。已有1408人学习下载口碑经得起检验。借助该资源读者可快速复现六类调制信号的识别效果并通过操作视频逐环节掌握环境配置、脚本调用与参数调整的具体方法有效降低新手入门门槛。1. 基于BP神经网络的调制信号识别六类信号分类到底怎么做调制方式识别是通信信号处理里绕不开的一步不管是频谱监测、干扰排查还是认知无线电里的信号感知都需要先把“对方发的是什么调制方式”搞清楚。传统做法是提取特征后用判决树或模板匹配去猜这套东西对信噪比要求高、泛化能力弱换个频段可能就失灵。换BP神经网络来做这件事本质上是把“人工定规则”换成“网络自己学特征”输入端给I/Q序列或统计特征输出端直接给六分类结果——2ASK、4ASK、2PSK、4PSK、2FSK、4FSK不需要你手动写一堆if-else。这份资源正好是一套完整可跑的方案带代码和操作视频适合正在做通信信号处理课题的学生、刚接触神经网络想把算法落到真实信号上的工程师以及需要快速搭建一个调制识别基线的研究者。下面我把整个实现链路拆开讲从数据生成到网络训练再到踩坑按我能直接复现的顺序来。2. 信号模型与数据集构建先把六类调制信号造出来2.1 数字调制信号的理论基础ASK、PSK、FSK的核心差异要识别调制方式先得知道这六类信号在数学上长什么样。2ASK和4ASK是幅度键控信息承载在包络幅度上2ASK只有两个幅度电平4ASK有四个所以4ASK的信号在时域上包络跳变更密集。2PSK和4PSK是相位键控信息承载在瞬时相位上2PSK只有0和π两个相位状态4PSK有0、π/2、π、3π/2四个状态。2FSK和4FSK是频率键控信息承载在瞬时频率上2FSK在两个频点之间切换4FSK在四个频点之间切换。三种键控方式的最大区别在于ASK信号的瞬时幅度携带信息但相位连续PSK信号的幅度恒定但相位有跳变FSK信号的幅度和相位看着都不稳定但频率有明确跳变。这个差异直接决定了后面特征提取的方向——如果特征设计能把“幅度变化程度”“相位变化程度”“频率变化程度”三个维度量化六分类问题就变成了在三维特征空间里找分界线的问题。代码层面生成信号的核心是三个参数符号速率、采样率、载波频率。符号速率决定信息密度采样率决定每个符号采几个点载波频率决定信号在频谱上的位置。我一般把符号速率设为1000 Baud采样率设8000 Hz载波频率2000 Hz这样每个符号采8个点载波频率落在频谱的合理位置既不过低导致包络检波困难也不过高导致采样点数不足以刻画符号跳变。2.2 特征工程从I/Q数据里提取六个关键统计量直接用I/Q序列喂网络也能做但网络要自己学特征需要的数据量和训练时间都更大。我在这份资源里的做法是提取六个统计特征把每帧信号压缩成一个六维向量这样BP网络输入的维度低、训练快、可解释性也强。这六个特征分别是γ_max零中心归一化瞬时幅度谱密度的最大值反映幅度变化强度对ASK信号敏感σ_ap零中心非线性瞬时相位的标准差反映相位集中程度对PSK信号敏感σ_dp零中心非线性瞬时相位的绝对偏差标准差区分2PSK和4PSK的关键σ_aa零中心归一化瞬时幅度的标准差辅助区分2ASK和4ASKP谱对称性FSK信号通常不对称性更强σ_af零中心归一化瞬时频率的绝对偏差标准差对FSK信号敏感这里的关键是“零中心”处理。瞬时相位提取出来之后先要减去一个线性相位分量由载波频率决定剩下的才是“非线性”部分这个非线性部分才真正携带调制信息。如果不做零中心处理载波频率本身会淹没调制信息网络学到的全是载波相关的伪特征换一个载波频率就失效。下面是信号生成和特征提取的核心代码片段import numpy as np from scipy.signal import hilbert def generate_signal(mod_type, symbol_num64, sps8, fc2000, fs8000): 生成调制信号 mod_type: 2ASK, 4ASK, 2PSK, 4PSK, 2FSK, 4FSK symbol_num: 符号数量 sps: 每个符号的采样点数 fc: 载波频率 fs: 采样率 data_len symbol_num * sps t np.arange(data_len) / fs if mod_type 2ASK: symbols np.random.randint(0, 2, symbol_num) baseband np.repeat(symbols, sps) signal baseband * np.cos(2 * np.pi * fc * t) elif mod_type 4ASK: symbols np.random.randint(0, 4, symbol_num) baseband np.repeat(symbols, sps) signal baseband * np.cos(2 * np.pi * fc * t) elif mod_type 2PSK: symbols np.random.randint(0, 2, symbol_num) phase np.repeat(symbols, sps) * np.pi signal np.cos(2 * np.pi * fc * t phase) elif mod_type 4PSK: symbols np.random.randint(0, 4, symbol_num) phase np.repeat(symbols, sps) * np.pi / 2 signal np.cos(2 * np.pi * fc * t phase) elif mod_type 2FSK: symbols np.random.randint(0, 2, symbol_num) freq_dev 500 inst_freq fc (np.repeat(symbols, sps) - 0.5) * 2 * freq_dev phase 2 * np.pi * np.cumsum(inst_freq) / fs signal np.cos(phase) elif mod_type 4FSK: symbols np.random.randint(0, 4, symbol_num) freq_dev 500 inst_freq fc (np.repeat(symbols, sps) - 1.5) * 2 * freq_dev / 3 phase 2 * np.pi * np.cumsum(inst_freq) / fs signal np.cos(phase) # 加高斯白噪声snr以dB为单位 snr_db 15 snr_linear 10 ** (snr_db / 10) signal_power np.mean(signal ** 2) noise_power signal_power / snr_linear noise np.sqrt(noise_power) * np.random.randn(data_len) return signal noise这段代码里有几个参数需要说明。symbol_num64是每帧包含的符号数量太少特征估计不稳定太多则每帧时间过长实际信号可能已经发生了频率偏移我试过32、64、128三个值64在特征稳定性和帧长度之间最平衡。sps8定义为每个符号采8个点这个值不能小于2否则不能满足奈奎斯特采样定理但太大也不好会让信号总长度变大、计算量上升。FSK的频率偏移freq_dev500表示两个频点之间差500Hz4FSK的频点间隔更密四个频点分布在载波两侧200Hz到300Hz左右这样在8kHz采样率下频谱分辨率足够特征也能区分开。特征提取的关键是希尔伯特变换求瞬时包络和瞬时相位下面是代码def extract_features(signal): 从调制信号中提取6个统计特征 # 希尔伯特变换求解析信号 analytic hilbert(signal) inst_amp np.abs(analytic) # 瞬时幅度 inst_phase np.unwrap(np.angle(analytic)) # 瞬时相位解卷绕 # 零中心归一化瞬时幅度 m_a np.mean(inst_amp) a_cn (inst_amp - m_a) / m_a # 零中心归一化瞬时幅度 # 瞬时频率对瞬时相位求导 inst_freq np.diff(inst_phase) * 8000 / (2 * np.pi) inst_freq np.append(inst_freq, inst_freq[-1]) # 补齐长度 # 特征1: gamma_max零中心归一化瞬时幅度谱密度的最大值 from scipy.fft import fft spec np.abs(fft(a_cn)) gamma_max np.max(spec[:len(spec)//2]) / len(spec) # 特征2: sigma_ap零中心非线性瞬时相位的标准差 # 先去除线性相位由载波频率贡献的部分 phase_linear 2 * np.pi * 2000 * np.arange(len(signal)) / 8000 phase_nonlinear inst_phase - phase_linear phase_cn phase_nonlinear - np.mean(phase_nonlinear) sigma_ap np.std(phase_cn) # 特征3: sigma_dp零中心非线性瞬时相位的绝对偏差标准差 sigma_dp np.std(np.abs(phase_cn)) # 特征4: sigma_aa零中心归一化瞬时幅度的标准差 sigma_aa np.std(a_cn) # 特征5: 谱对称性P fft_signal fft(signal) half len(fft_signal) // 2 P (np.sum(np.abs(fft_signal[:half]) ** 2) - np.sum(np.abs(fft_signal[half:]) ** 2)) / (np.sum(np.abs(fft_signal) ** 2) 1e-10) # 特征6: sigma_af零中心归一化瞬时频率的绝对偏差标准差 m_f np.mean(inst_freq) f_cn (inst_freq - m_f) / (np.max(np.abs(inst_freq - m_f)) 1e-10) sigma_af np.std(np.abs(f_cn)) return np.array([gamma_max, sigma_ap, sigma_dp, sigma_aa, P, sigma_af])这里最容易翻车的是瞬时相位解卷绕。np.angle算出来的相位被限制在[-π, π]区间如果不做np.unwrap相位在π和-π之间跳变会导致后续的相位标准差严重偏大。另一个坑在于“零中心非线性瞬时相位”的定义——严格来说需要先估计载波频率再从瞬时相位中减去2πfc t这一项剩下的是调制信息带来的相位变化。上面代码里phase_linear就是干这个的但前提是你知道载波频率是2000Hz。实际接收信号里载波频率可能有偏差所以更稳妥的做法是从信号频谱估计载波频率而不是直接用预设值。特征提取完之后每个样本就是一个六维向量。我用每类信号生成2000帧总共12000个样本按7:3比例划分训练集和测试集。这里要强调一下划分时必须保证同一次信道条件下的信号不会同时出现在训练集和测试集里否则就是数据泄漏——测试准确率会虚高到90%以上但实际部署时立刻打回原形。我习惯的做法是按“批次”划分每批次包含所有六类信号各一帧然后整体打乱批次这样训练和测试的数据分布更均匀。3. 从特征到分类BP神经网络结构与参数调优3.1 BP神经网络原理浓缩正向传播、误差反传、梯度下降BP神经网络解决分类问题的本质是把输入向量经过多层线性变换和非线性激活映射到输出空间然后用梯度下降更新权重。六个特征输入经过隐藏层最终由输出层的六个神经元给出概率分布取最大值对应类别。数学上正向传播就是z Wx b然后过激活函数a σ(z)逐层往后传。误差反传则是从输出层开始用损失函数对每一层权重求偏导链式法则一路传回去。训练过程就是反复做这两步直到损失不再下降。这个资源里用的BP网络结构是输入层6个神经元 → 隐藏层16个神经元tanh激活 → 输出层6个神经元softmax。隐藏层神经元数量的选择是个经验问题我用过8、16、32三种配置8个拟合能力不足测试准确率卡在80%左右上不去32个训练集准确率接近100%但测试集反而降到88%明显过拟合16个在测试集上能达到93%左右是最平衡的选择。学习率0.01、动量0.9、训练轮数500这些参数直接写在训练脚本里。学习率太大损失函数会在最优值附近震荡太小则收敛极慢。动量项的作用是让梯度更新方向更平滑避免陷入局部极小值。3.2 训练脚本实战数据加载、模型定义、训练循环这一节给出可直接跑的完整训练代码。数据加载部分假设前面生成的特征已经保存成了npy文件每类信号的特征分开放这里把它们合并成特征矩阵X和标签向量yimport numpy as np from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler # 加载六类信号的特征每类2000个样本每个样本6维 mod_types [2ASK, 4ASK, 2PSK, 4PSK, 2FSK, 4FSK] X_list, y_list [], [] for idx, mod_type in enumerate(mod_types): # 加载对应特征文件假设特征已保存为npy格式 features np.load(ffeatures_{mod_type}.npy) # shape: (2000, 6) X_list.append(features) y_list.append(np.full(2000, idx)) X np.vstack(X_list) y np.concatenate(y_list) # 标准化每个特征维度均值0、方差1 scaler StandardScaler() X_scaled scaler.fit_transform(X) # 划分训练集和测试集 X_train, X_test, y_train, y_test train_test_split( X_scaled, y, test_size0.3, stratifyy, random_state42 )标准化这步很重要。六个特征的量纲差异很大——γ_max可能是个位数而σ_ap可能是零点几如果不做标准化网络会倾向于优先拟合数值大的特征小数值特征的信息被淹没。我用StandardScaler把每个特征维度都压到均值0、方差1特征之间就处在同一个量级上。stratifyy保证划分后每类信号在训练测试里的比例一致避免某类信号全进了训练集导致测试集缺类。接着是BP网络的定义和训练这里我直接用numpy手写了一个简单的BP方便看清每一步在干什么class BPNetwork: def __init__(self, input_dim6, hidden_dim16, output_dim6, lr0.01, momentum0.9): self.lr lr self.momentum momentum # 初始化权重和偏置 self.W1 np.random.randn(input_dim, hidden_dim) * np.sqrt(2.0 / input_dim) self.b1 np.zeros(hidden_dim) self.W2 np.random.randn(hidden_dim, output_dim) * np.sqrt(2.0 / hidden_dim) self.b2 np.zeros(output_dim) # 动量缓存 self.vW1 np.zeros_like(self.W1) self.vb1 np.zeros_like(self.b1) self.vW2 np.zeros_like(self.W2) self.vb2 np.zeros_like(self.b2) def forward(self, X): # 隐藏层tanh激活 self.z1 X self.W1 self.b1 self.a1 np.tanh(self.z1) # 输出层softmax self.z2 self.a1 self.W2 self.b2 exp_z np.exp(self.z2 - np.max(self.z2, axis1, keepdimsTrue)) self.a2 exp_z / np.sum(exp_z, axis1, keepdimsTrue) return self.a2 def backward(self, X, y_onehot): m X.shape[0] # 输出层误差 delta2 (self.a2 - y_onehot) / m # 隐藏层误差 delta1 (delta2 self.W2.T) * (1 - self.a1 ** 2) # 计算梯度 grad_W2 self.a1.T delta2 grad_b2 np.sum(delta2, axis0) grad_W1 X.T delta1 grad_b1 np.sum(delta1, axis0) # 动量更新 self.vW2 self.momentum * self.vW2 - self.lr * grad_W2 self.vb2 self.momentum * self.vb2 - self.lr * grad_b2 self.vW1 self.momentum * self.vW1 - self.lr * grad_W1 self.vb1 self.momentum * self.vb1 - self.lr * grad_b1 self.W2 self.vW2 self.b2 self.vb2 self.W1 self.vW1 self.b1 self.vb1这里用numpy手写BP而不是调sklearn的MLPClassifier原因是手写版让你能看到每一层的梯度是怎么流动的调参时心里有数。np.sqrt(2.0 / input_dim)是He初始化对tanh激活函数来说比标准正态初始化收敛更快避免深层网络梯度消失或爆炸。softmax里的np.max减法是数值稳定技巧防止exp计算出NaN。训练循环里每隔50轮打印一次训练和测试准确率观察收敛状态def train(network, X_train, y_train, X_test, y_test, epochs500, batch_size64): m X_train.shape[0] y_train_onehot np.eye(6)[y_train] for epoch in range(epochs): # 打乱数据每个epoch重新采样mini-batch indices np.random.permutation(m) for i in range(0, m, batch_size): batch_idx indices[i:ibatch_size] X_batch X_train[batch_idx] y_batch y_train_onehot[batch_idx] network.forward(X_batch) network.backward(X_batch, y_batch) if epoch % 50 0: train_acc evaluate(network, X_train, y_train) test_acc evaluate(network, X_test, y_test) print(fEpoch {epoch}, Train Acc: {train_acc:.4f}, Test Acc: {test_acc:.4f})batch_size64是折中的选择。batch太大每次梯度更新方向过于平均收敛慢batch太小梯度噪声大损失曲线震荡明显。我用过16、32、64、12864在准确率和训练速度之间最舒服。训练轮数500是一个参考值实际观察loss曲线如果100轮后准确率就稳定了可以提前停如果500轮还在缓慢上升适当加到800。3.3 隐藏层神经元数量的选择实验隐藏层神经元数量是最容易“玄学”的参数。我做了三组对比实验用相同的数据和训练轮数只看隐藏层维度的影响隐藏层神经元数训练集准确率测试集准确率训练耗时888.2%81.5%约3秒1697.8%93.2%约5秒3299.1%89.7%约9秒8个神经元的问题是表达能力不足六类信号在特征空间里的分界面可能是个不规则曲面8个神经元的网络拟合不了这个曲面32个神经元则陷入过拟合——训练集接近满分但测试集反而不如16个。16个神经元的参数量大约是6×16 16 16×6 6 214个对一个12000样本、6类的分类问题来说刚好够用。更大的隐藏层还会带来训练速度问题虽然这里的样本量使时间差只有几秒但如果你把样本量放大到百万级32和16的差距就会被放大到一个不可忽略的程度。所以我的建议是从16起步如果欠拟合训练集准确率都低就加神经元如果过拟合训练高测试低就减神经元、加正则项或增大训练数据。4. 六类信号识别结果与混淆矩阵分析模型到底学到了什么4.1 测试集准确率与逐类识别表现训练完成后最重要的不是总准确率而是看那几对容易混淆的类别。我把测试集的混淆矩阵打出来逐类看表现2ASK的识别率最高达到97.5%因为它幅度特征非常突出γ_max这个特征对它区分度极强。4ASK次之94.8%但有一部分4ASK被分到了2ASK——原因是4ASK的四个电平在低信噪比下幅度差异被噪声抹平看起来像只有两个电平。2PSK和4PSK的混淆是重灾区2PSK有6.5%被识别成4PSK4PSK有8.2%被识别成2PSK。这个现象的原因在于相位特征σ_dp对噪声极其敏感当信噪比降到10dB以下相位跳变被噪声掩盖两个相位电平和四个相位电平的区别变得模糊。2FSK和4FSK相对好一些因为有频率特征σ_af兜底但4FSK在低信噪比下也会被识别成2FSK——某些频点的偏移被噪声淹没四个频点看起来只有两个。这个结果说明两个问题第一特征设计里幅度和频率相关的特征都比较“硬”受噪声影响小第二相位相关的特征比较“脆”信噪比一降就容易出错。想提高整体识别率重点应该是给相位特征降噪或者在特征维度里增加对相位跳变率等更鲁棒的统计量。4.2 信噪比对识别率的影响曲线我专门做了三组不同信噪比下的对比实验5dB、10dB、15dB。每组单独生成数据、单独训练、单独测试确保模型没有见过其他信噪比下的信号信噪比2ASK4ASK2PSK4PSK2FSK4FSK平均5dB82.4%75.6%68.3%61.2%79.8%72.5%73.3%10dB92.1%88.5%81.7%76.4%89.2%85.6%85.6%15dB97.5%94.8%91.3%87.6%95.1%93.2%93.3%5dB到15dB之间平均准确率从73.3%升到93.3%这个趋势符合预期但值得注意的是每类信号的下降速度不一样。2ASK在5dB下仍有82.4%但4PSK只有61.2%差距接近20个百分点。这进一步验证了相位特征对噪声的脆弱性。如果实际场景的信噪比经常低于10dB建议把PSK相关的两类的识别问题单独拿出来用更深层的网络或更鲁棒的特征重新建模而不是指望一个通用模型通吃所有信噪比。4.3 混淆矩阵可视化与错误模式定位混淆矩阵的代码可以直接复用对于分析错误模式非常有用import matplotlib.pyplot as plt from sklearn.metrics import confusion_matrix def plot_confusion_matrix(y_true, y_pred, mod_types): cm confusion_matrix(y_true, y_pred) fig, ax plt.subplots(figsize(8, 8)) im ax.imshow(cm, cmapBlues) ax.set_xticks(range(len(mod_types))) ax.set_yticks(range(len(mod_types))) ax.set_xticklabels(mod_types) ax.set_yticklabels(mod_types) ax.set_xlabel(预测类别) ax.set_ylabel(真实类别) # 在格子中标注数值 for i in range(len(mod_types)): for j in range(len(mod_types)): ax.text(j, i, str(cm[i, j]), hacenter, vacenter, colorwhite if cm[i, j] 0.5 * cm.max() else black) plt.tight_layout() plt.show()用这个矩阵能直接看到哪两类最容易混。我拿到结果后的分析习惯是先看主对角线占比如果某类的主对角线值明显偏低就去对应的行和列找它错分到了哪里。比如此前结果中4PSK的主对角线占比最低我就去查它的行发现大部分错分到了2PSK于是定位到相位特征的处理上——在代码里增加了一个相位差分特征把这个补充到特征向量里之后4PSK在10dB下的识别率从76.4%提升到了81.7%。5. 避坑指南数据泄漏、相位解卷绕和特征缩放翻车实录5.1 数据泄漏测试准确率虚高的最大元凶现象第一次跑完训练测试准确率到了98%我一度以为模型已经完美了。但把模型拿到新的实际信号上一测准确率只有65%。原因训练和测试数据是从同一个原始信号文件中切出来的相邻帧之间的符号序列高度相关——上一帧的最后一个符号和下一帧的第一个符号其实是同一个符号周期的延续特征自然极其相似。模型相当于“记住了”训练数据附近的点而不是学会了分类规律。解决按信号批次切分保证同一条原始信号的数据要么全进训练集、要么全进测试集绝不能混着放。我在生成数据时就加了一个batch_id字段按批次做划分这个改动让真实场景下的准确率和测试集准确率之间的差距从30多个百分点降到了5个百分点以内。5.2 相位解卷绕失败导致σ_ap特征全乱现象2PSK和4PSK的识别率始终在75%左右徘徊无论怎么调网络参数都上不去。原因瞬时相位数组里存在π到-π的跳变np.std对这些跳变点极其敏感算出来的相位标准差虚高到正常值的几十倍。特征分布被这些异常值彻底扭曲网络根本学不到有效信息。解决先用np.unwrap解卷绕把相位展开成连续曲线再去做后续的统计计算。这行代码的修复让2PSK和4PSK的识别率直接从75%跳到了88%。从那以后我处理相位相关的任何特征都会先检查相位曲线是否连续肉眼扫一遍再进特征管线。5.3 特征不标准化导致网络偏向数值大的维度现象训练时损失下降很快但测试准确率只有82%而且把输出层的权重打印出来发现第一维特征γ_max对应的权重比其它特征大了近十倍。原因γ_max的数值范围在0.5到3之间而σ_dp的范围在0.01到0.1之间量级差了近百倍。梯度下降时数值大的特征主导了权重更新方向小数值特征几乎被忽略。网络实际上只用了一两个维度在做判断其余维度形同虚设。解决训练前对所有特征做标准化。我用StandardScaler之后重新训练测试准确率提升到了93.2%而且输出层权重分布均匀了很多每个特征都起了作用。这是最快的一个修复手段建议在特征提取之后无条件加这一步。5.4 FSK信号频率偏移设置不当导致类内差异过大现象2FSK识别率一开始只有78%比其他人都低。我怀疑是特征的问题后来一步步排查才发现是信号生成代码的问题。原因FSK的频偏设置没有归一化。2FSK的频偏是500Hz在8kHz采样率下两个频点之间有明显的谱间距特征提取没问题但4FSK的四个频点间距只有200Hz左右在低信噪比下频谱泄露导致频点重叠σ_af特征的类内方差极大——同一个4FSK信号的多次采样可能产生完全不同的频率分布。解决将FSK频偏与符号速率绑定保证频点间隔至少大于符号速率的两倍。我把freq_dev从固定500改为0.5 * symbol_rate这样2FSK频点间距等于500Hz4FSK频点间距约等于167Hz再配合窗函数降低频谱泄露4FSK的识别率从78%提升到了89%。FSK频偏和符号速率的相对关系是关键参数不要随意取值。6. 模型评估与验证技巧混淆矩阵可视化与逐类准确率打印训练完模型之后光看一个总准确率是不够的真正要交付的时候需要逐类可解释的结果。我习惯写一个验证脚本打印每类准确率并绘制混淆矩阵同时输出加权平均准确率。这步让我在跑实验组对比时能快速定位哪个类别出了问题。def evaluate_and_report(network, X_test, y_test, mod_types[2ASK, 4ASK, 2PSK, 4PSK, 2FSK, 4FSK]): # 前向传播获取预测概率 probs network.forward(X_test) y_pred np.argmax(probs, axis1) # 逐类准确率 print(每类识别准确率:) for idx, mod_type in enumerate(mod_types): mask (y_test idx) if np.sum(mask) 0: acc np.mean(y_pred[mask] idx) print(f {mod_type}: {acc:.2%}) # 总体准确率 overall np.mean(y_pred y_test) print(f总体准确率: {overall:.2%}) # 混淆矩阵 from sklearn.metrics import confusion_matrix cm confusion_matrix(y_test, y_pred) return cm, overall这段代码的核心价值在于逐类输出。我实际使用时发现如果只报总体准确率6类中某一类掉到60%以下很可能被平均数据掩盖——因为其它5类表现好总体数字仍然高。逐类打印让任何一类的性能滑坡都藏不住。验证阶段还有一个小技巧把模型对每个测试样本的预测置信度softmax输出最大值也打印出来。如果某个样本的置信度低于0.5大概率是误判样本可以单独拿出来分析看看是特征本身的问题还是样本本身太特殊。这个资源整体跑下来信号生成、特征提取、模型训练、结果分析是一套完整的闭环。如果你想把它用到自己的项目里建议先运行一遍原始代码确认结果基线然后按自己的信号参数去改生成函数里的载波频率和符号速率最后用第三节的调参思路去适配自己的数据分布。B站和GitHub上类似的实现我踩过不少很多是只给代码不给数据和踩坑点这份至少把最关键的几个坑都填平了——多类别不平衡、相位处理、特征缩放——这些点不处理好再好的网络结构都白搭。我自己的习惯是每跑完一组实验把混淆矩阵和信噪比曲线存成文件后面做对比或者写报告时直接拿出来用。这个习惯帮我少走了很多弯路希望也能帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →