基于深度学习的语音识别Python源码:MFCC+CNN实现中文数字命令词识别

发布时间:2026/10/25 12:46:27
基于深度学习的语音识别Python源码:MFCC+CNN实现中文数字命令词识别 简介这是一套基于深度学习的语音识别课程设计Python源码面向计算机、人工智能、数据科学等专业在校生以及需要完成课程设计或期末大作业的初学者。项目以语音识别为核心实现了从音频数据预处理、特征提取到CRNN-CTC模型构建与训练的一整套流程并包含自定义网络层与数据处理工具代码模块划分清晰便于理解深度学习模型在语音任务中的应用方式。压缩包共13个文件主要由Python源码、工程配置文件、说明文档及示例图片组成整体大小仅1.29MB轻量且可直接运行。已有743人学习/下载资源经过测试可稳定运行遇到问题可与作者沟通。读者可基于该工程快速搭建语音识别实验也可在现有代码上修改扩展适合作为毕业设计、课程设计或项目立项演示的基础参考。1. 基于深度学习的语音识别 Python 源码课程设计到底该交付什么很多课程设计拿到「基于深度学习的语音识别」这个题目第一反应是去啃端到端大模型结果卡在环境、数据和训练时间上最后只能交一个跑不动的空壳。我做过类似的语音识别 Python 源码工程后最大的感受是这个题目真正的难点不在模型结构而在从原始录音到可演示结果的整条数据管线。所谓「基于深度学习的语音识别 python 源码」最稳妥的交付形态是一套能识别中文数字命令词的完整闭环——数据集组织、MFCC 特征提取、CNN 模型训练、实时录音预测每一步都能在普通笔记本上跑完。这篇文章就按这条路径拆开讲适合正在做课程设计或毕业设计的学生也适合想快速跑通声学模型验证想法的开发者。2. 语音识别四条技术路线摆上桌为什么课程设计我选 MFCC CNN2.1 GMM-HMM、DNN-HMM、CTC 和注意力模型课程设计的交付难度对比语音识别的技术路线从传统到端到端大致分四类课程设计选型时先别急着追新要看交付周期和答辩能讲清楚的程度。第一条是 GMM-HMM 混合模型用高斯混合模型对每个音素建模再靠隐马尔可夫模型处理时序。这条路数学门槛高需要大量领域知识纯手写不现实调库又容易变成黑匣子答辩时老师追问一个转移概率就答不上来。第二条是 DNN-HMM用神经网络替换 GMM 做声学打分。这是语音识别从传统走向深度学习的代表性方案工程组件多需要对齐、解码器配合做起来更像系统集成而非建模练习。第三条是 CTC 端到端直接把音频序列映射到文本序列不需要逐帧标注这对课程设计比较友好但训练不稳定、对齐机制抽象很多同学在 loss 曲线翻车后不知道从哪排查。第四条是 Transformer 加注意力机制的端到端模型。效果最好但对数据量、算力和调试经验的要求也最高课程设计周期内很难训出一个像样的中文模型。综合来看如果目标是「有源码、能复现、答辩能自圆其说」最划算的做法是用梅尔频率倒谱系数加 CNN 做命令词分类本质上属于音频特征到文本标签的映射既具备深度学习的核心要素又能把工程复杂度控制在一个晚上能跑完训练的程度。这是《动手深度学习》式「先跑通再调优」思路的直接落地。2.2 梅尔频谱图加 CNN 分类为什么这是性价比最高的闭环选 MFCC CNN 不是因为它先进而是因为这个组合把语音识别拆成了两个都能被课程设计接受的阶段。特征阶段把 wav 转成 MFCC 矩阵相当于把声音变成一张二维图模型阶段用 CNN 在这张图上做分类和图像分类的套路完全一致。这样模型命名好写输入 39 维的 MFCC 三件套矩阵经过卷积层提取局部时频特征全连接层输出「零到九」十个类别的概率。这个方案能识别的是语音指令不是连续自由文本但这恰好符合课程设计的题目边界。「基于深度学习的语音识别」并不强制要求达到大模型那样的通用识别能力在限定词表上的高准确率加上完整的源码工程比一个跑不起来的大模型更有说服力。另一个原因是训练成本可控十个类别、每类几十条录音、每条一秒钟左右数据总量只有几分钟CNN 在 CPU 上几分钟就能收敛。相比 LSTM 或 CTC 动辄几百轮迭代这个方案可以反复改参数验证给调参留出余地。2.3 整体数据流从 wav 文件到中文数字标签经过了什么我一般会把整个系统拆成五个环节每个环节都对应一个能在代码里看到的模块原始 wav 经过重采样和静音切除得到干净音频然后提取 MFCC 和它的一阶、二阶差分拼成特征矩阵特征矩阵按固定帧数截断或补零变成统一形状CNN 模型接收特征矩阵输出十个类别的概率最后取概率最大的类别对应到中文数字标签。这五个环节串起来就是一条完整的数据流水线。写作时建议把这张数据流图放在课程设计报告的第一页因为面试官和评审老师第一眼就能看出你是否理解整个系统。后面四章就按这五个环节逐一展开第三章解决数据和特征第四章解决模型和训练第五章讲过程中最容易翻车的坑最后一章做实时预测和进阶延伸。3. 数据与特征才是真正的工作量MFCC 提取与数据集准备脚本3.1 数据集怎么组织中文数字命令词的录制与目录划分语音识别课程设计最容易犯的一个错误是把所有音频堆在一个文件夹里训练代码里临时 split最后验证集和训练集混进了同一个人的同一条录音识别率虚高到 99%。我建议严格按照「按说话人划分」的方式组织目录每采集完一个人才划分数据而不是先混合再随机切分。data/ train/ speaker01/ling.wav speaker01/si.wav speaker02/yi.wav ... val/ speaker05/er.wav speaker06/san.wav ... test/ speaker09/qi.wav speaker10/ba.wav ...目录结构设定为「说话人为第一级、中文数字标签为文件名」有两个原因一是后续代码按标签读取时可以同时保留说话人信息方便做分组验证二是如果发现某个说话人的录音噪声特别大可以快速定位到具体目录做清洗。录制时每人每类词录 10 到 20 条10 个数字就是 100 到 200 条。三个真实说话人加两个自己模拟的不同口音总量控制在 500 到 1000 条就能有不错的效果不用去网上找大型数据集。如果不想自己录制也可以从开源语音数据集中裁剪但要注意许可证和词表匹配问题。常见做法是下载 THCHS-30 或 Free ST 中文数据集的子集用ffmpeg切出包含目标数字的片段这个操作的踩坑点在第三章末尾和第五章里细说。3.2 提取 MFCC 的 Python 实现与参数选取特征提取脚本是整个工程里最值得精雕细琢的部分。我惯用的参数组合是采样率 16000、帧长 512 点、帧移 160 点保留 13 维 MFCC 并补充一阶和二阶差分最终得到 39 维特征。采样率设为 16000 是因为语音识别领域的主流模型几乎都以 16k 为基准在这个采样率下 512 点帧长对应 32 毫秒符合常见语音帧长度。# prepare_features.py import librosa import numpy as np SR 16000 # 目标采样率 N_FFT 512 # 帧长 32ms HOP_LENGTH 160 # 帧移 10ms N_MFCC 13 # 基础 MFCC 维数 MAX_FRAMES 32 # 固定帧数约 320ms def extract_features(wav_path): y, sr librosa.load(wav_path, srSR) # 去掉首尾静音避免空白帧干扰分类 y, _ librosa.effects.trim(y, top_db20) mfcc librosa.feature.mfcc( yy, srsr, n_mfccN_MFCC, n_fftN_FFT, hop_lengthHOP_LENGTH ) delta1 librosa.feature.delta(mfcc, order1) delta2 librosa.feature.delta(mfcc, order2) feat np.vstack([mfcc, delta1, delta2]) # (39, T) # 统一长度超长截断不足补零 if feat.shape[1] MAX_FRAMES: feat feat[:, :MAX_FRAMES] else: pad_width MAX_FRAMES - feat.shape[1] feat np.pad(feat, ((0, 0), (0, pad_width)), modeconstant) return feat这段脚本里最关键的是MAX_FRAMES 32这个参数。中文数字录音普遍在 300 到 600 毫秒之间32 帧对应 320 毫秒截断后保留的是发音起始段丢失的尾音对于命令词判别影响不大。补零放在尾部而不放在头部是为了让卷积核先看到发声起点。top_db20的静音切除阈值也需要留意如果录音环境噪声大阈值可以放大到 25 或 30如果录音本身很安静20 就够调大了会把词尾的弱音误伤。3.3 标签编码与 DataLoader给训练循环喂数据特征提取完就要把数据组织成 PyTorch 能直接消费的 Dataset。这里有两个细节一是标签和特征严格对齐读取文件名时直接把中文数字映射成整数避免单独维护一个 list 造成错位二是在__getitem__里返回特征的拷贝防止后续数据增强修改了特征矩阵。# dataset.py import os import torch from torch.utils.data import Dataset import numpy as np LABEL_TO_IDX { ling: 0, yi: 1, er: 2, san: 3, si: 4, wu: 5, liu: 6, qi: 7, ba: 8, jiu: 9 } class SpeechCommandDataset(Dataset): def __init__(self, root_dir): self.samples [] for speaker_dir in sorted(os.listdir(root_dir)): speaker_path os.path.join(root_dir, speaker_dir) if not os.path.isdir(speaker_path): continue for fname in sorted(os.listdir(speaker_path)): label_part fname.split(_)[0].split(.)[0] # 兼容 ling_01.wav 和 ling.wav 两种命名 if label_part not in LABEL_TO_IDX: continue self.samples.append( (os.path.join(speaker_path, fname), LABEL_TO_IDX[label_part]) ) def __len__(self): return len(self.samples) def __getitem__(self, idx): wav_path, label self.samples[idx] feat extract_features(wav_path) # (39, 32) feat feat[np.newaxis, :, :] # (1, 39, 32) 当作单通道图 return torch.tensor(feat, dtypetorch.float32), label注意这里的标签读取逻辑文件名如果用ling_01.wav这种带序号的形式split(_)[0]拿到的是拼音标签直接命名为ling.wav也能兼容因为split(.)[0]得到了ling。数据加载时不要用np.load缓存全部特征到内存数据量不大可以实时提取还能顺便避免缓存文件路径不一致的问题。训练主循环里再配合DataLoader的shuffleTrue打乱顺序至此数据准备环节就完整了。4. 训练一个能演示的 CNN 声学模型网络定义、调参记录与模型保存4.1 网络结构三层卷积加一个全连接参数量刚好不虚胖语音特征矩阵是 39×32 的单通道图尺寸远小于真实图像因此网络不需要很深。我用三层卷积加一层全连接的结构每一层卷积后面接批归一化和 ReLU中间穿插两次最大池化最后展平进全连接分类头。这个结构有两个优点对课程设计来说参数量大约只有几十万CPU 上几分钟一轮不会因为训练时间被答辩质疑同时每一层都有明确的物理含义便于在报告里解释「卷积核在学什么特征」。# model.py import torch.nn as nn class MfccCNN(nn.Module): def __init__(self, n_classes10): super(MfccCNN, self).__init__() self.features nn.Sequential( nn.Conv2d(1, 32, kernel_size3, padding1), nn.BatchNorm2d(32), nn.ReLU(), nn.MaxPool2d(2), nn.Conv2d(32, 64, kernel_size3, padding1), nn.BatchNorm2d(64), nn.ReLU(), nn.MaxPool2d(2), nn.Conv2d(64, 128, kernel_size3, padding1), nn.BatchNorm2d(128), nn.ReLU(), ) # 输入 39x32两次池化后变成 9x8 self.classifier nn.Sequential( nn.Flatten(), nn.Linear(128 * 9 * 8, 256), nn.ReLU(), nn.Dropout(0.3), nn.Linear(256, n_classes) ) def forward(self, x): x self.features(x) x self.classifier(x) return x输入尺寸为什么是 39×32 而不是别的数字这个对应关系必须在代码注释里写清楚。39 是 MFCC 三件套的维数32 是固定帧数经过两次核大小为 2 的最大池化后39 变成 932 变成 8所以全连接输入维度是 128×9×8。改任何一个特征参数这里的计算都要跟着改。这是整个模型里最容易「改了特征忘了改全连接维度」的地方报错信息通常是一长串线性层的维度不匹配第一次看到别慌按上面公式推一遍就能定位。4.2 训练脚本损失函数、优化器、学习率与 L2 正则化的实际设置分类任务的损失函数没有悬念直接选交叉熵。优化器我选 Adam但关键是把weight_decay显式设置为一个不为零的值。weight_decay在 PyTorch 里的实现就是在梯度里加一个参数自身的 L2 范数惩罚项对应深度学习中常说的 L2 正则化作用是把权重拉向零抑制过拟合。课程设计的数据量在几百条这个量级模型很容易把训练集背下来不设正则化的话验证集准确率会明显低于训练集。# train.py import torch import torch.nn as nn from torch.utils.data import DataLoader from model import MfccCNN from dataset import SpeechCommandDataset EPOCHS 30 BATCH_SIZE 32 LR 1e-3 WEIGHT_DECAY 1e-4 train_set SpeechCommandDataset(data/train) val_set SpeechCommandDataset(data/val) train_loader DataLoader(train_set, batch_sizeBATCH_SIZE, shuffleTrue) val_loader DataLoader(val_set, batch_sizeBATCH_SIZE, shuffleFalse) model MfccCNN(n_classes10) criterion nn.CrossEntropyLoss() optimizer torch.optim.Adam(model.parameters(), lrLR, weight_decayWEIGHT_DECAY) scheduler torch.optim.lr_scheduler.StepLR(optimizer, step_size15, gamma0.5) best_acc 0.0 for epoch in range(EPOCHS): model.train() total_loss 0.0 for feats, labels in train_loader: optimizer.zero_grad() logits model(feats) loss criterion(logits, labels) loss.backward() optimizer.step() total_loss loss.item() * feats.size(0) # 每个 epoch 后在验证集上评估 model.eval() correct, total 0, 0 with torch.no_grad(): for feats, labels in val_loader: logits model(feats) preds torch.argmax(logits, dim1) correct (preds labels).sum().item() total labels.size(0) val_acc correct / total scheduler.step() if val_acc best_acc: best_acc val_acc torch.save(model.state_dict(), best_model.pt) print(fEpoch {epoch1}, Loss: {total_loss / total:.4f}, Val Acc: {val_acc:.4f})这里有三个参数值得单独说明。BATCH_SIZE设 32 是因为特征图本身很小显存占用低如果你只有 CPU这个 batch 依旧是安全的不需要刻意调低。LR取 1e-3 是 Adam 的常见起始点遇到损失值反复震荡时把它降到 3e-4大概率能稳下来。step_size15配合gamma0.5表示在第 15 轮把学习率打对折用来在训练后期做精细化收敛这是手动调参最常见的经验做法比无限循环跑固定轮数更节省时间。每轮打印的损失和验证集准确率不是给你看的进度条是供你判断该早停还是该改参数的依据。4.3 训练完成后的模型保存与导出不是 .pth 存下来就完事训练结束拿到best_model.pt很多课程设计到这里就收工了但部署到演示环节时会发现模型经常加载失败或识别结果错乱。原因多半是保存方式的问题。上面代码用torch.save(model.state_dict(), ...)保存的是参数字典加载时需要先实例化模型结构再load_state_dict。另一种常见做法是直接torch.save(model, ...)把整个模型对象序列化但这种方式会绑定 Python 类的路径换目录、换运行环境就报错非常脆弱。我建议一律保存state_dict并且在模型定义文件里提供对应的加载函数# inference.py import torch from model import MfccCNN from prepare_features import extract_features device torch.device(cuda if torch.cuda.is_available() else cpu) def load_model(ckpt_path, n_classes10): model MfccCNN(n_classesn_classes) model.load_state_dict( torch.load(ckpt_path, map_locationdevice) ) model.to(device) model.eval() return model def predict_one_wav(model, wav_path): feat extract_features(wav_path) feat_tensor torch.tensor(feat[np.newaxis, np.newaxis, :, :], dtypetorch.float32, devicedevice) with torch.no_grad(): logits model(feat_tensor) pred_idx torch.argmax(logits, dim1).item() return pred_idx还有一个容易被忽视的点torch.load时设置map_locationdevice否则在 GPU 上训练的模型会在 CPU 机器加载时报显存相关错误。model.eval()也必须在推理前调用否则 BatchNorm 层的统计量还在按训练模式更新同一个 wav 每次预测结果都可能不一样不少同学在这里误以为是模型没训练好。5. 避坑记录语音识别训练中五个最容易翻车的地方5.1 训练损失怎么都不降先别调模型把 wav 读出来听听现象训练跑了十几轮损失值始终在 2.3 上下晃动验证集准确率接近十分之一和随机猜测差不多。原因排查了很久最后发现录音文件本身有问题——设备静音或者有人声但电平极低librosa.load读进来后几乎全是静音top_db20的静音切除把整个音频都切掉了特征矩阵全是零。解决每次采集完一批录音先用librosa.load读一条并播放确认内容再打印y.abs().max()检查峰值电平。如果最大值小于 0.01说明录音基本无效。这类问题一旦混进训练集模型会在所有样本里寻找规律而这些样本根本没有可学习的信息。除此之外还要检查是不是录到了环境噪声比如风扇声、键盘声这类声音的特征和数字发音搅在一起模型学到的可能是噪声模式。这份血泪经验的教训是数据质量检查必须走在任何训练参数调整之前。5.2 验证集准确率虚高到 99%检查训练验证划分是不是按人分的现象训练集和验证集是同一个文件夹里随机切分的验证准确率达到 99%但是拿到面试官电脑上现场录音识别率却掉到 70% 以下。原因是同一个人的同一条录音一部分进了训练集一部分进了验证集。由于静音切除后数据没有打乱相邻文件往往来自同一段连续录音模型见过了验证集中的「同类音频」指标自然虚高。解决划分数据集的第一原则是「按说话人划分而不是按文件划分」。也就是说同一个人的所有录音要么全在训练集要么全在验证集绝不能同时出现在两边。语音识别领域还有个更严格的要求是「相同文本不跨集合」防止模型记住了文本到特征的偶然对应。课程设计做到按说话人划分就足够这一点可以在论文的数据章节明确写出来反而是加分项。5.3 模型加载报错或者结果全错保存方式与运行环境的隐性问题现象训练本机的验证集准确率是 95%把best_model.pt和代码拷贝到另一台电脑后加载时报出Missing key(s)或者加载成功但预测结果全是同一个类别。原因往往是保存时用了torch.save(model, ...)整个对象保存新电脑的 Python 版本或类路径不同导致反序列化失败另一种情况是保存的是state_dict但加载时忘了先实例化模型。解决统一使用torch.save(model.state_dict(), ...)加载时先model MfccCNN(n_classes10)再model.load_state_dict(torch.load(ckpt_path))。如果还有 Key 名称对不上使用strictFalse来打印缺失的键名但打印后必须手动判断到底是谁的结构变了。最后提醒一点保存文件名不要用中文路径某些平台上会引发编码错误这是已知的低级翻车点。5.4 中文路径与文件名导致读取失败现象torchaudio.load或者librosa.load在读取带中文目录的 wav 时报错或者在 Windows 上读取正常、换到 Linux 服务器就崩溃报错信息往往是没有权限或文件不存在但文件明明在那里。原因在于系统编码对中文路径的处理不一致Windows 的默认编码是 GBKLinux 是 UTF-8部分音频库内部不做路径转码。解决工程目录和文件命名全部使用英文字母、数字和下划线这是最省事的方案。同时所有 wav 路径在传给librosa.load前统一用os.path.abspath转成绝对路径并确认存在。如果必须处理中文文件名可以尝试在调用前把路径编码转换为 UTF-8但这类问题往往还会潜伏在其他环节不推荐在课程设计里冒险。5.5 显存溢出与训练速度过慢batch size 和输入长度的取舍现象GPU 显存不足报错或者 CPU 训练一个 epoch 要五分钟。原因可能是特征矩阵太大了某些代码把原始 wav 波形直接喂给模型也可能是DataLoader的num_workers设置过高在 Windows 上反而导致数据加载阻塞。解决确认输入模型的是 39×32 的特征矩阵而不是原始音频。BATCH_SIZE从 32 往下调到 16 或 8观察显存占用和训练损失变化显存溢出通常调整这一个参数就能解决。num_workers在 Windows 上设置为 0 或 1 即可过高的num_workers只会让多进程反复 fork拖慢训练。如果 CPU 训练时间实在无法接受把MAX_FRAMES从 32 降到 24 也能明显提速代价是可能截断较长的词尾建议只在时间紧迫时使用。6. 实时录音预测与三个进阶方向让答辩演示更有底气课程设计答辩场合现场用麦克风说一句「三」电脑上立刻显示预测结果这个演示效果比任何 PPT 都有说服力。实时预测脚本可以借助sounddevice库几秒钟录音后直接调用已经训练好的模型代码比训练逻辑简单得多# record_and_predict.py import io import numpy as np import sounddevice as sd import soundfile as sf from inference import load_model, predict_one_wav SR 16000 DURATION 1.0 model load_model(best_model.pt) # 阻塞式录制一秒 raw sd.rec(int(SR * DURATION), samplerateSR, channels1, dtypefloat32) sd.wait() sf.write(_tmp.wav, raw[:, 0], SR) pred_idx predict_one_wav(model, _tmp.wav) print(识别结果:, IDX_TO_LABEL[pred_idx])这里有一个容易被忽略的关键点实时麦克风的采样率通常由声卡驱动决定而模型训练时用的录音是脚本重采样到 16000 的如果麦克风返回的是 44100必须把数据重采样到 16000 后再喂给特征函数否则识别率会大幅下降。一个我习惯用的查错方式是输入前打印刚才录到的音频峰值如果极低就提醒说话人靠近麦克风再试不要每次都拿模型参数开刀。做完这个闭环如果想在课程设计里多拿亮点分数有三个方向可以选。第一个是数据增强给训练集加一点点高斯白噪声或做时间拉伸能有效提升模型在陌生环境下的鲁棒性代码改动只需要在__getitem__里多两行。第二个方向是把词表从十个中文数字扩展到日常命令词比如「开灯、关灯、播放、暂停」多语种语音识别也是当前行业里的热门趋势如果你有精力可以再加一组普通话和方言对照数据这个点放到答辩中会显得有前瞻性。第三个方向是更换模型尾部为 CTC 损失把「命令词分类」升级为「数字串识别」让系统能识别连续的多个数字发音比如一句「一二三四」输出 1234。回顾我自己的课程设计经历最开始我拿到课题就直接去下载大型预训练模型想一步到位做通用语音识别最终花了一周时间都没有把环境调通。后来老老实实从录制十个数字开始半天时间就拿到了 95% 以上的准确率答辩效果反而更好。希望这篇基于深度学习的语音识别的 Python 源码拆解能帮到你让你在同样的题目上少走一圈弯路。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询