简介:这份资源是面向MATLAB初学者与音频识别方向学习者的BP神经网络猪咳嗽声识别项目源码包,可用于理解如何将机器学习分类方法落地到畜牧业动物健康监测场景。包内共13个文件,以12个wav音频样本和1个m脚本为主,压缩包约306KB,音频文件覆盖猪咳嗽、哼哼、咆哮、尖叫、打呼噜等类别,用于训练与测试;主程序脚本承担网络构建、训练、测试与结果分析等核心流程。项目围绕音频预处理、分帧、去噪与特征提取展开,输入可采用声谱图、MFCC或短时能量与过零率等特征,隐藏层学习复杂模式,输出层对应不同声音类别,并涉及训练函数选择、迭代次数与学习率设置及交叉验证评估。目前已有330人学习,适合希望快速复现声音分类实验、掌握MATLAB神经网络工具箱用法并积累特征工程与模型调参经验的读者参考。
1. 从一段猪舍录音说起:BP神经网络猪咳嗽声识别到底在做什么
猪舍里此起彼伏的咳嗽声,靠人耳去分辨哪头猪病了,几乎是不可能完成的任务。一个存栏几千头的育肥场,饲养员每天巡栏两三次,等发现明显呼吸道症状时,往往已经有一批猪进入感染中期,用药成本上去了,料肉比也垮了。BP神经网络猪咳嗽声识别要解决的,就是把「听咳嗽」这件事从人耳搬到算法上:用麦克风阵列或录音笔采集猪舍音频,切出疑似咳嗽的片段,提取特征后送进BP神经网络做二分类或多分类,判断这段声音是不是猪咳嗽、属于哪种咳嗽。它适合两类人:一类是想把声音监测接入现有养殖管理系统的工程师,另一类是手里有音频数据、想跑通一个完整识别链路的学生或算法初学者。整条链路不复杂,但每一步都有坑,下面按「数据怎么来、特征怎么提、网络怎么搭、结果怎么验」的顺序讲透。
2. 音频采集与标注:猪咳嗽声识别的数据地基怎么打
2.1 猪舍录音的采集方案与设备选型
猪咳嗽声识别第一个翻车点往往不在模型,而在数据。猪舍环境噪声极大:风机低频轰鸣、猪只走动撞击栏位、饲喂器落料、人员走动,信噪比经常低到让人怀疑录音设备坏了。常见做法是用指向性麦克风或小型麦克风阵列,采样率至少 16 kHz,位深 16 bit,单声道即可。如果做定点监测,麦克风吊装在栏位上方 1.5 到 2 米,避开风机正对方向;如果做移动巡检,手持录音笔要加防风毛套,否则风噪会直接淹没咳嗽声。
采集时长上,建议每个栏舍连续录 30 分钟以上,覆盖早中晚三个时段,因为猪的咳嗽在清晨和夜间更频繁。录音文件按「栏舍编号_日期_时段」命名,方便后续追溯。这里有个血泪经验:不要用手机自带录音 App 直接录,很多手机会自动做降噪和增益,把咳嗽声的高频细节抹掉,后面特征提取时你会后悔没有原始波形。
提示:采集前先用一段已知咳嗽样本试录,回放确认能听清咳嗽的爆发段,再开始批量录。
2.2 咳嗽声片段切分与人工标注流程
整段录音不能直接喂给网络,需要先切分成短片段。猪咳嗽声通常持续 0.3 到 0.8 秒,能量集中在中高频。切分可以用短时能量加过零率做初筛:先算帧能量,超过自适应阈值的段标记为候选,再合并相邻候选帧,得到候选片段。候选片段导出后,必须人工听辨标注,标成「咳嗽」和「非咳嗽」两类,非咳嗽里要包含风机噪声、猪叫、撞击声等负样本。
标注工具用 Audacity 或 Praat 都行,导出时统一成 WAV、16 kHz、单声道。每个类别建议至少 500 条片段,正负样本比例控制在 1:1 到 1:2 之间。如果正样本太少,可以用数据增强:加轻微高斯噪声、做时间平移、调整音量,但不要做大幅变速,否则咳嗽的时频结构会变形。
import librosa import numpy as np import os def split_cough_segments(wav_path, out_dir, frame_len=400, hop=200, energy_th=0.02): # 读取音频,统一采样率 y, sr = librosa.load(wav_path, sr=16000, mono=True) # 分帧计算短时能量 frames = librosa.util.frame(y, frame_length=frame_len, hop_length=hop) energy = np.sum(frames ** 2, axis=0) energy = energy / (np.max(energy) + 1e-8) # 能量超过阈值的帧标记为候选 candidates = energy > energy_th segments = [] start = None for i, flag in enumerate(candidates): if flag and start is None: start = i elif not flag and start is not None: s = start * hop e = i * hop + frame_len if 0.2 * sr < (e - s) < 1.2 * sr: # 限制片段长度 segments.append((s, e)) start = None # 导出候选片段 os.makedirs(out_dir, exist_ok=True) for idx, (s, e) in enumerate(segments): seg = y[s:e] librosa.output.write_wav(os.path.join(out_dir, f"seg_{idx}.wav"), seg, sr) return len(segments) if __name__ == "__main__": n = split_cough_segments("pig_house_01.wav", "candidates") print("导出候选片段数:", n)这段代码的逻辑是:先统一采样率到 16 kHz,再分帧算短时能量,能量归一化后用阈值筛出候选帧,合并成片段并限制长度在 0.2 到 1.2 秒之间。参数frame_len=400对应 25 ms 窗,hop=200对应 50% 重叠,energy_th=0.02是经验阈值,实际要根据录音增益调整。导出后的片段还需要人工听辨,把真正的咳嗽挑出来。
3. 特征提取:MFCC、梅尔频谱和时域特征怎么选
3.1 MFCC 与梅尔频谱的提取步骤
BP 神经网络不能直接吃原始波形,需要把音频转成固定维度的特征向量。最常用的是 MFCC(梅尔频率倒谱系数),它模拟人耳对频率的非线性感知,对咳嗽这种中高频爆发声比较敏感。提取流程是:预加重、分帧、加汉明窗、FFT、梅尔滤波器组、取对数、DCT,最后取前 13 维,再加上一阶差分和二阶差分,凑成 39 维。梅尔频谱则是省去 DCT 这一步,直接取对数梅尔谱,维度通常取 64 或 128,适合后面接卷积结构,但纯 BP 网络用 MFCC 更省参数。
import librosa import numpy as np def extract_mfcc(wav_path, n_mfcc=13): y, sr = librosa.load(wav_path, sr=16000, mono=True) # 预加重,增强高频 y = np.append(y[0], y[1:] - 0.97 * y[:-1]) # 提取 MFCC mfcc = librosa.feature.mfcc(y=y, sr=sr, n_mfcc=n_mfcc, n_fft=400, hop_length=200) # 一阶差分和二阶差分 delta = librosa.feature.delta(mfcc) delta2 = librosa.feature.delta(mfcc, order=2) # 拼接后对时间轴取均值和标准差,得到固定维度 feat = np.concatenate([mfcc, delta, delta2], axis=0) feat = np.concatenate([np.mean(feat, axis=1), np.std(feat, axis=1)]) return feat # 维度 39*2 = 78 if __name__ == "__main__": f = extract_mfcc("seg_0.wav") print("特征维度:", f.shape)这里的关键参数是n_mfcc=13,加上差分后每个系数有 3 个版本,再对时间轴做均值和标准差池化,得到 78 维固定向量。池化是为了让不同时长的片段都能送进全连接网络。如果你发现咳嗽和非咳嗽在均值上区分度不够,可以改用最大值池化或分位数池化,保留爆发段的峰值信息。
3.2 时域特征与特征融合的取舍
除了 MFCC,还可以加时域特征:短时能量、过零率、谱质心、谱带宽、谱滚降。这些特征计算快,对冲击性噪声敏感。常见做法是把 MFCC 的 78 维和 5 个时域特征拼成 83 维,送进 BP 网络。但特征不是越多越好,维度太高而样本少时,网络容易过拟合。我一般会先单独用 MFCC 跑一版,再逐步加特征,看验证集准确率有没有提升,没有提升就砍掉。
注意:所有特征在送入网络前必须做标准化,按训练集的均值和标准差做 Z-score,验证集和测试集用同一套参数,否则会出现数据泄露。
4. BP 网络搭建:结构、激活函数和训练参数怎么定
4.1 网络结构设计与 bp 神经网络结构图对应关系
标题里热搜词有「bp神经网络结构图」,说明很多人关心网络到底几层、每层多少节点。猪咳嗽声识别这种任务,输入是 78 到 83 维特征,输出是二分类,结构不需要太深。我一般用三层:输入层、一个隐藏层、输出层。隐藏层节点数按经验公式sqrt(输入维数 + 输出维数) + 5估算,78 维输入二分类大概在 15 到 25 之间,实际取 20 比较稳。激活函数隐藏层用 ReLU,输出层用 Sigmoid(二分类)或 Softmax(多分类)。损失函数用交叉熵,优化器用 Adam,学习率 0.001,批量大小 32,训练 100 到 200 轮,配合早停。
import torch import torch.nn as nn class CoughBPNet(nn.Module): def __init__(self, input_dim=78, hidden_dim=20, num_classes=2): super().__init__() self.net = nn.Sequential( nn.Linear(input_dim, hidden_dim), nn.ReLU(), nn.Dropout(0.3), # 防止过拟合 nn.Linear(hidden_dim, num_classes) ) def forward(self, x): return self.net(x) def train_one_epoch(model, loader, optimizer, criterion, device): model.train() total_loss = 0 for x, y in loader: x, y = x.to(device), y.to(device) optimizer.zero_grad() logits = model(x) loss = criterion(logits, y) loss.backward() optimizer.step() total_loss += loss.item() return total_loss / len(loader) if __name__ == "__main__": model = CoughBPNet() print(model)这段代码定义了一个带 Dropout 的三层 BP 网络。input_dim=78对应 MFCC 加差分的池化特征,hidden_dim=20是隐藏层节点数,Dropout(0.3)在训练时随机丢弃 30% 神经元,降低过拟合。训练循环里先清零梯度,再前向、算损失、反向、更新。如果你用 Softmax 多分类,损失函数换成nn.CrossEntropyLoss,它内部已经包含 Softmax,不要再手动加。
4.2 训练参数调优与早停策略
学习率是最关键的参数。0.001 是 Adam 的常用起点,如果损失震荡,降到 0.0005;如果下降太慢,升到 0.002 试试。批量大小 32 在样本量几百到几千时比较合适,太小梯度噪声大,太大收敛慢。早停的判据是验证集损失连续 10 轮不下降就停,同时保存验证集损失最低的模型权重。这里有个玄学现象:有时候验证集准确率还在涨,但损失已经开始升,说明模型在过拟合,这时候要以损失为准早停。
提示:训练前把数据按 7:1.5:1.5 切成训练、验证、测试,切分时按片段随机打乱,不要按录音文件切,否则同一头猪的相似片段会同时出现在训练和测试里,准确率虚高。
5. 避坑与排查:猪咳嗽声识别落地时最容易翻车的 5 个地方
5.1 现象:验证集准确率 99%,现场部署后惨不忍睹
原因:数据泄露。同一段录音切出来的片段被随机分到了训练集和测试集,片段之间高度相似,模型只是记住了这段录音的噪声底,没有学到咳嗽的本质特征。解决:按原始录音文件划分数据集,训练集和测试集来自不同栏舍、不同日期,再做交叉验证。
5.2 现象:模型把风机噪声也判成咳嗽
原因:负样本里风机噪声太少,或者风机噪声和咳嗽在 MFCC 上区分度不够。解决:增加风机噪声负样本,同时加入谱质心和谱滚降特征,风机噪声能量集中在低频,咳嗽在中高频,这两个特征能拉开差距。
5.3 现象:训练损失不下降,一直卡在 0.69 左右
原因:特征没有标准化,或者标签编码错了。解决:检查输入特征是否做了 Z-score,检查标签是不是 0 和 1 而不是 1 和 2。另外,如果输入特征全是正数且量纲差异大,网络很难收敛。
5.4 现象:模型在测试集上表现好,但换一个猪舍就崩
原因:过拟合到特定猪舍的声学环境。解决:做数据增强时加入不同信噪比的噪声,训练时用 Dropout 和权重衰减,测试时至少在两个不同猪舍的数据上验证。
5.5 现象:推理速度太慢,达不到实时监测要求
原因:特征提取用了太长的窗和太高的重叠,或者网络隐藏层节点太多。解决:特征提取用 25 ms 窗、10 ms hop,网络隐藏层降到 15 以内,推理时用 ONNX 或 TorchScript 导出,批量推理。
6. 进阶技巧:用交叉验证和混淆矩阵把模型可信度摸清楚
6.1 五折交叉验证的落地写法
单次划分的测试结果波动很大,尤其是样本量只有几百条时。我一般用五折交叉验证,把数据按录音文件分成五份,每次取四份训练、一份验证,循环五次,取平均准确率和标准差。这样能看出模型在不同数据子集上的稳定性。如果五折的标准差超过 5 个百分点,说明数据分布不均匀,需要检查是不是某些栏舍的样本太少。
from sklearn.model_selection import KFold import numpy as np def cross_validate(features, labels, model_fn, n_splits=5): kf = KFold(n_splits=n_splits, shuffle=True, random_state=42) accs = [] for train_idx, val_idx in kf.split(features): X_train, X_val = features[train_idx], features[val_idx] y_train, y_val = labels[train_idx], labels[val_idx] model = model_fn() # 这里省略训练循环,实际调用训练函数 acc = 0.0 # 替换为真实验证准确率 accs.append(acc) return np.mean(accs), np.std(accs)这段代码展示了五折交叉验证的骨架,KFold的shuffle=True保证每折数据随机,random_state固定后结果可复现。实际使用时把训练循环填进去,记录每折的验证准确率,最后看均值和标准差。
6.2 混淆矩阵告诉你模型到底错在哪
准确率只给一个总数,混淆矩阵能看出模型是把咳嗽漏判了还是把噪声误判了。猪咳嗽识别里,漏判(假阴性)比误判(假阳性)更严重,因为漏掉一头病猪可能导致整栏感染。所以看混淆矩阵时,重点看假阴性有多少。如果假阴性高,可以调低分类阈值,比如从 0.5 降到 0.3,牺牲一点精确率换召回率。
| 指标 | 含义 | 猪咳嗽识别中的取舍 |
|---|---|---|
| 准确率 | 正确分类比例 | 整体参考,样本不均衡时失真 |
| 精确率 | 判为咳嗽中真正咳嗽的比例 | 误报多时看这个 |
| 召回率 | 真正咳嗽中被判出来的比例 | 漏报多时看这个,优先保证 |
| F1 值 | 精确率和召回率的调和平均 | 综合权衡 |
我自己的习惯是,每次训练完先打印混淆矩阵,再算召回率,如果召回率低于 0.9,不管准确率多高都不上线。现场部署时,把模型输出概率和一段时间的滑动平均结合,连续多个片段都判为咳嗽才报警,能进一步压误报。这套流程跑下来,猪咳嗽声识别从数据到部署的链路就闭环了。希望帮到你。
本文还有配套的精品资源,点击获取