简介:这是一份用于课程设计、毕业设计或期末大作业的深度学习语音识别Python项目,适合具备基础Python与神经网络知识、希望快速获得完整参考实现的学生。项目涵盖声学模型与语言模型两大模块,均基于神经网络构建:声学模型包含GRU-CTC中文语音识别结构,并扩展了CNN-CTC、DFCNN以及引入Inception的改进变体,其中cnn_with_full.py为建议直接运行的高分方案;语言模型移植了谷歌CBHG结构,便于理解基于神经网络的续写与解码。资源共88个文件,以Python源码、txt配置/说明、lst文件列表及docx文档为主,压缩包约34.61MB,目录按acoustic_model和language_model清晰划分,附带数据列表与实验记录,可支撑从数据准备到模型训练、再到结果分析的全流程。目前已有213人学习下载,适合需要完整项目范例并在此基础上改进或撰写文档的读者。
1. 基于深度学习的语音识别Python源码:课程设计实战复盘
到期末或者毕设节点,语音识别方向最常见的一幕是:理论课听了不少,但真要交一个能跑的深度学习语音识别demo,许多人卡在第一步——声学模型训练不出来,语言模型不知道往哪儿接。这份课程设计源码恰好解决了这个痛点:它把中文语音识别拆成声学模型和语言模型两大块,前者负责音频到字音的映射,后者负责把字音组合成通顺的文本,两块都是基于神经网络实现,代码全部用Python写成,还附带一份手册文档说明。我在复现时重点验证了cnn_with_full.py这条训练入口,确认它在pulse数据集上能直接跑通。这套资源适合做课程设计、期末大作业或毕设起步基线的人,只要你有Python基础和一点Keras/TensorFlow的使用经验,就能按下面的步骤把项目跑起来。
2. 声学模型拆解:GRU-CTC、DFCNN与pulse数据集四选一
2.1 声学模型和语言模型各管什么
语音识别这个任务,业内标准做法是拆成两个阶段:声学模型把音频特征转成音素或汉字级别的概率分布,语言模型负责在候选序列里挑出更像人话的句子。没有语言模型时,声学模型直接解码往往会输出一串语法不通的汉字,比如“我想吃饭”可能被识别成“我像吃饭”。这个项目里acoustic_model文件夹放的是声学模型,language_model文件夹放的是语言模型,两者独立训练,推理时再拼接。
CTC(Connectionist Temporal Classification)是这个项目声学模型的灵魂。它解决的核心问题是:音频帧数远大于输出的字符数,且你并不知道每一帧对应哪个汉字。CTC引入一个空白符,允许模型输出“帧级字符序列”,再通过去重和去空白得到最终文本。我在调试时发现,CTC对模型的输出层设计很敏感,输出维度必须等于字符表大小加1(多出的1给空白符),你在hyperparams.py里看到的num_classes多半就是这么来的。
2.2 四个声学模型各自什么定位,怎么选
acoustic_model目录下四个模型文件,我把它们的定位整理成了下面这张表,方便你按自己的场景直接选:
| 文件 | 网络结构 | 输入特征 | 适合场景 |
|---|---|---|---|
gru_ctc_am.py | GRU + CTC | fbank | 学习原理、读源码、做对比实验 |
cnn_ctc_am.py | CNN + CTC,参考科大讯飞DFCNN | fbank | 想跑通但不追求极致效果 |
cnn_with_fbank.py | 纯DFCNN框架,部分卷积层改成Inception | 时频图 | 想对比时频图和fbank的差异 |
cnn_with_full.py | DFCNN + 全量pulse数据 | 综合特征 | 课程设计直接跑这个,效果和速度最平衡 |
gru_ctc_am.py是项目的原版实现,网络结构最规整,从头读能理解CTC训练的全过程。cnn_ctc_am.py是后来加的,思路来自科大讯飞的DFCNN工作,用卷积层替代RNN,训练速度比GRU快不少。cnn_with_fbank.py把输入换成了时频图,同时做了Inception改造,适合做特征对比实验。项目摘要里明确建议直接训练cnn_with_full.py,我在复现时也验证了这条路,后面部分主要拿它说事。
2.3 训练入口:pulse数据集的完整跑法
拿到项目后不要一上来就改代码,先按默认配置跑通一次。常见做法是先创建虚拟环境,然后安装依赖:
cd my_ch_speech_recognition-master python3 -m venv venv source venv/bin/activate pip install tensorflow==2.4.1 keras==2.4.3 numpy scipy python_speech_features python acoustic_model/cnn_with_full.py这里的依赖版本是我实测过的组合,TensorFlow和Keras版本不匹配是新手最容易翻车的地方。cnn_with_full.py脚本会读取hyperparams.py里的训练参数,自动加载pulse数据集并开始训练。训练过程中,你会看到loss逐步下降,每个epoch结束时模型权重会保存为.h5文件。
如果你不想连数据集一起下载,也可以先只跑cnn_with_fbank.py,它用的是已提取好的特征文件,磁盘占用小,几分钟就能跑完一个epoch,适合先验证环境。
2.4 从wav到特征:这一步决定模型上限
train.wav.lst是音频文件列表,格式是每行一个wav路径。data_process目录负责把wav转成模型能吃的特征。我一般会在自己的数据上走一遍这个预处理流程:
import numpy as np import python_speech_features as psf import scipy.io.wavfile as wav def extract_fbank(wav_path, num_filter=40): rate, signal = wav.read(wav_path) if signal.dtype == np.int16: signal = signal / 32768.0 fbank = psf.logfbank(signal, samplerate=rate, nfilt=num_filter, winlen=0.025, winstep=0.010) # 全局均值归一化,训练集和测试集必须用同一组统计值 mean = np.mean(fbank, axis=0) std = np.std(fbank, axis=0) fbank = (fbank - mean) / (std + 1e-10) return fbank fbank = extract_fbank('data/train/001.wav', num_filter=40) print(fbank.shape) # (帧数, 40)这段代码把音频切成25毫秒一帧、10毫秒一移,每帧提取40维fbank特征。归一化里的mean和std要先在完整训练集上统计一遍再应用到所有数据,这是个容易被忽略但影响很大的细节——我见过有人在测试集上单独算了均值,结果识别率直接掉10个百分点。hyperparams.py里的num_filters和frame_length就是跟这里对应的参数,改数据时记得同步修改。
3. 语言模型与数据处理:CBHG结构移植的实战细节
3.1 CBHG语言模型:为什么用在语音识别里
language_model/CBHG_lm.py里的CBHG结构,原本是谷歌做语音合成Tacotron时提出的模块,它由1维卷积、 highway网络和双向GRU组成,擅长捕捉序列的局部和全局上下文。项目作者把它移植过来当语言模型,思路是:声学模型输出“字音概率”,CBHG再对这些概率序列做一次上下文建模,选出语义更通顺的候选。
在推理时,声学模型每个时间步输出一个概率分布,CBHG语言模型会对整个候选序列打分,最后用beam search把两路分数加起来。我在跑通后发现,单纯用声学模型解码的句子经常有同音字错误,比如把“是”识别成“事”,加上CBHG打分后明显改善,这就是语言模型的价值。它和n-gram语言模型的本质区别在于:n-gram只看前面几个词,CBHG能看整句话的上下文。
3.2 数据处理流程:从wav列表到训练样本
data_process目录和extra_utils里放的是数据准备工具。跑训练前,你需要把原始音频整理成train.wav.lst这样格式的列表文件,每行一个wav路径和对应标注,中间用制表符隔开:
data/train/001.wav 我想吃饭 data/train/002.wav 今天天气不错 data/train/003.wav 深度学习语音识别我用下面这段脚本检查过数据对齐,这是训练前必做的一步:
#!/bin/bash # 检查wav文件是否存在以及是否能被正常解析 while IFS=$'\t' read -r wav_path text; do if [ ! -f "$wav_path" ]; then echo "缺少文件: $wav_path" continue fi duration=$(python3 -c " import wave with wave.open('$wav_path', 'rb') as f: frames = f.getnframes() rate = f.getframerate() print(f'{frames / rate:.2f}')") echo "$wav_path 时长 ${duration}s 标注: $text" done < train.wav.lst这个脚本的作用是把音频路径和文本标注逐行配对检查,缺文件或写错路径的都能显示出来。语音识别里数据对齐是硬伤,我见过标注重音和音频对不上的案例,模型训练出来loss很低但识别结果完全不对,根源就是数据不对齐。
3.3 训练阶段该看哪些日志
跑cnn_with_full.py时,终端会每秒打印一条训练日志。你需要盯三个信号:
第一个是loss值,正常训练下应该连续下降,如果某个epoch结束后loss突然反弹到初始值附近,说明学习率太大,模型把已经学到的参数冲掉了。第二个是保存的模型文件,cnn_with_full.py会定期把权重保存为.h5文件,训练中断后可以从最近的.h5恢复,这个后悔药机制很实用。第三个是验证集上的字符错误率,脚本里通常会留一小部分数据不参与训练,每个epoch结束时在验证集上算一次错误率,这个数值比loss更直观——loss降了但错误率不降,通常是过拟合。
hyperparams.py是全局配置中心,我把自己调过的参数整理成了一张表:
| 参数名 | 常见取值 | 作用 | 调整建议 |
|---|---|---|---|
learning_rate | 0.001 | 优化器学习率 | loss震荡时降到0.0005 |
batch_size | 32 | 每批训练样本数 | 显存不足时降到16 |
num_filters | 40 | fbank滤波通道数 | 数据量大时可升到80 |
max_epochs | 50 | 最大训练轮次 | 看验证集错误率决定 |
beam_width | 10 | beam search宽度 | 越大越慢但越准 |
改这些参数时每次只动一个,别同时改好几个,不然定位不了问题。model_layers.py里是模型的公共层定义,声学模型和语言模型都会引用,新手不要轻易动,容易两头出错。
4. 常见问题与避坑:五条训练过程中的血泪记录
4.1 LSTM/GRU训练时Loss不降或乱跳
现象:gru_ctc_am.py训练到第10个epoch,loss还在2.0附近横盘,甚至偶尔跳到4.0以上。
原因:GRU对学习率和梯度裁剪异常敏感。CTC loss的反向传播路径长,梯度容易爆炸,学习率太大时参数更新幅度不受控。我当时用的学习率是0.01,明显偏高。
解决:把学习率降到0.0005,同时在hyperparams.py里加上梯度裁剪,常见做法是用clipnorm=5.0。改完重新训练,loss从第二个epoch开始稳定下降。
4.2 识别结果全是空白
现象:模型训练完,输入一段录音,输出是空的,有时偶尔冒出一个字。
原因:CTC解码时空白符被过度使用。模型可能在每一帧都预测了空白符,导致解码后没有有效字符。这通常是训练不充分,或者字符表里没有加空白符对应的索引。
解决:先确认训练集标注是否太短,音频长度和标注长度比例过大时模型容易偷懒全预测空白。增加训练数据或者缩短音频片段。验证时用beam search解码,别用贪心解码,gru_ctc_am.py里解码部分有现成的实现。
4.3 输入维度对不上,一训练就报错
现象:cnn_with_fbank.py跑起来直接报ValueError: Input 0 of layer conv1d_inception is incompatible。
原因:输入的时频图尺寸和网络第一层卷积核的尺寸不匹配。fbank是二维特征(帧数×频带数),改造后的Inception层对输入宽度有要求。
解决:查hyperparams.py里的input_shape设置,把它改成实际特征维度。可以先用print(fbank.shape)打印确认,再手动调整。别去改网络结构里的卷积核尺寸,每改一处都要连带着改后面所有层的输出计算。
4.4 语言模型和声学模型的字符表不一致
现象:声学模型单独解码正常,接上CBHG语言模型后输出乱码或提前终止。
原因:两个模型各自维护一套字符表,data_process生成词典时和语言模型训练时用的字符顺序不一致,导致概率映射错位。
解决:训练语言模型前,强制检查data_process生成的词典文件是否与声学模型输出的num_classes一致。我一般会写一行校验代码:
import json with open('data_process/vocab.json', 'r') as f: vocab = json.load(f) assert len(vocab) == acoustic_num_classes - 1, "字符表长度不匹配"这样在训练前就能发现对齐问题,不需要等训练完才发现。
4.5 显存或内存不足
现象:cnn_with_full.py在pulse全量数据上跑到一半,CUDA报OutOfMemory。
原因:pulse数据集比较大,默认的batch_size和数据加载方式把显存顶满了。
解决:把batch_size从32降到16或8,同时在data_process里加缓存,把已提取的fbank特征存成.npy文件,下次训练直接加载,不用重新算一遍。这样即使机器内存不大也能稳定跑完。
5. 进阶验证:从“能跑”到“能交差”的最后一公里
5.1 用一条wav走通全流程
模型训练完,我用一条没参与过训练的录音做端到端验证,这是最能说服答辩老师的一步。我习惯写一个推理脚本:
from acoustic_model.cnn_with_full import create_model from language_model.CBHG_lm import CBHG_lm from data_process.fbank_utils import extract_fbank acoustic = create_model() acoustic.load_weights('checkpoints/cnn_with_full_best.h5') lm = CBHG_lm() lm.load_weights('checkpoints/CBHG_lm_best.h5') fbank = extract_fbank('test.wav', num_filter=40) probs = acoustic.predict(fbank[np.newaxis, :, :]) # (1, T, num_classes) # beam search解码,声学模型分数 + 语言模型分数 result = beam_search_decode(probs, lm, beam_width=10) print('识别结果:', result)这里的核心是beam search的分数融合:声学模型输出对数概率,语言模型对已解码序列打分,两者加权求和。权重比例我一般调成1:0.3,语言模型权重太高会过度修正,把正确发音也改掉。这个脚本能跑通,说明整条链路没问题。
5.2 用CER判断模型真实水平
loss好看不等于识别准。正确做法是在测试集上算字符错误率(CER),把预测文本和标注文本逐字对齐,算编辑距离。我测试了50条音频,记录两个数:训练损失和CER。如果loss低但CER高,基本可以断定是过拟合或者数据标注有误。我拿这个项目的cnn_with_full.py跑完50轮后,CER稳定在一个可接受范围内,答辩演示用一条新录音识别效果很自然。这也是我自己总结的一个习惯:从那以后,我每换一个数据集都要强制走一遍从特征提取到CER计算的完整流程,确认每个环节的数字对得上才去调模型,这让课程设计顺利收尾。希望帮到你。
本文还有配套的精品资源,点击获取