十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python+Keras实现中文语音识别:声学模型与CBHG语言模型全解析

Python+Keras实现中文语音识别:声学模型与CBHG语言模型全解析 简介面向人工智能与语音识别方向的Python开发者这是一套基于神经网络的中文语音识别系统由声学模型与语言模型两大模块组成适合作为入门实践与工程参考。声学模块提供GRU-CTC、CNN-CTC以及以DFCNN为骨架并加入Inception的改进结构语言模块引入CBHG网络覆盖从语音特征到文本输出的完整建模流程。资源包共88个文件其中29个Python脚本承载主要算法实现29个txt文件多为训练文本与配置说明22个lst文件用于数据列表索引另有少量pyc缓存、md文档及pkl数据文件整体约34.52MB目录按acoustic_model、language_model、data等模块划分便于检索复用和二次开发。项目还整合STC、Primewords、Aishell、THCHS30四个常用中文数据集的加载与处理脚本覆盖常见中文语音数据格式可直接用于训练和对比实验。目前已有4040人学习使用适合希望深入理解神经网络语音识别原理、动手训练中文语音模型的开发者与研究者。1. 为什么是 Python 双层神经网络的语音识别方案中文语音识别和英文有一个明显区别英文有天然的空格分词中文一个 pinyin 对应几十个汉字声学模型输出只有拼音或单字序列时后面的语言模型几乎决定了最终文本是不是人话。所以大部分中文识别系统都会拆成声学模型把音频转成音素或汉字概率和语言模型把候选序列重新评分两层。这个项目正是典型的双层结构底层在 acoustic_model 文件夹里放着 GRU-CTC、CNN-CTC、DFCNN 三种声学模型上层在 language_model 文件夹里放了一个基于 CBHG 结构的神经网络语言模型。从课程设计或者说“人工智能大作业”的角度看它把特征提取、序列建模、解码拼乐高一样串起来了适合想完整看一遍语音识别链路的人。Python 在整个链路里承担了从数据处理到模型训练的几乎全部工作音频解码用 librosa 或 scipy特征用 fbank模型用 Keras 组装最后 CTC 解码用 beam search。比起部署上更重的一套 C 方案这个仓库最大的价值是可以让别人在不到半天内看到“语音怎么变成文本”的全部细节适合学生也适合刚转语音方向的工程师。不过原项目的一些实验脚本和临时文件混在一起拿到手第一件事不是跑而是先理清每个文件属于哪一层。2. 声学模型从 GRU-CTC 到 DFCNN 的演化路径2.1 CTC 损失函数为什么能直接对齐语音帧和汉字传统 HMM 声学模型需要先给每帧音频标注状态中文标注成本极高。CTC 的思路是允许预测序列中出现一个特殊标记 blank模型可以自由地在输出中插入“停顿”符号然后用动态规划把相同输出映射到同一条路径上。这样我们只需要给音频对应的汉字序列即可训练不需要逐帧对齐。对中文来说一个汉字在音频里持续大约 0.2~0.3 秒但一帧 fbank 特征是 10 毫秒所以模型输出频率远高于字符频率。CTC 就是来解决这种“输入长、输出短”的不等长对齐问题。在 CTC 解码阶段最常见的做法是把模型输出的帧概率递交给 beam search加上语言模型权重重新打分。这在项目中表现为声学模型和语言模型在解码时才真正融合。2.2 GRU-CTC 的 Keras 实现与参数说明gru_ctc_am.py 是项目里最先实现的声音模型。GRU 与 LSTM 相比参数量少、训练速度快在时序建模中足够捕获语音帧之间的依赖。典型的 Keras 实现如下from keras.layers import Input, GRU, Dense, Lambda from keras import backend as K def ctc_lambda_func(args): y_pred, y_true, input_length, label_length args return K.ctc_batch_cost(y_true, y_pred, input_length, label_length) inputs Input(nameinputs, shape(max_frames, num_features)) gru1 GRU(units256, return_sequencesTrue, namegru_1)(inputs) gru2 GRU(units256, return_sequencesTrue, namegru_2)(gru1) dense Dense(unitsnum_classes 1, activationsoftmax, namedense)(gru2) labels Input(namelabels, shape[max_label_len], dtypefloat32) input_length Input(nameinput_length, shape[1], dtypeint64) label_length Input(namelabel_length, shape[1], dtypeint64) loss_out Lambda(ctc_lambda_func, output_shape(1,), namectc)([dense, labels, input_length, label_length]) model Model(inputs[inputs, labels, input_length, label_length], outputsloss_out)这段代码里max_frames是单条音频最多能容纳的时间帧数超过的部分要么切段要么截断。num_features是 fbank 维度通常取 80过高会导致计算量明显增大但识别率提升有限。密集层输出num_classes 1多出来的那个节点对应 CTC blank 符号。训练时模型接收的不是手工对齐的 label而是三样东西原始 label、实际输入帧长度和实际 label 长度CTC loss 在训练时会遍历所有对齐路径。在 Keras 中 CTC 是一个自定义 Lambda 层没有可训练参数反向传播的梯度会自动被 K.ctc_batch_cost 接住。这里要注意labels要 padding 成统一长度否则没法组成 batch同时必须在label_length里记录真实的汉字个数否则损失会异样收敛。2.3 CNN-CTC 与 DFCNN 变体把卷积放进序列建模GRU 直接处理原始 fbank 表示所有时间步对模型状态敏感容易丢掉局部频段突变特征。CNN 有两个天然优势一是卷积核在时间维上滑动能像滤波器一样消除语音速率变化带来的影响二是在频率维上共享参数能减少训练数据的需求。项目里把讯飞 DFCNN 的结构简化后放进了 cnn_ctc_am.py先用卷积层从频谱图上提取局部特征再通过全连接层映射到文字概率空间。cnn_with_fbank.py则更进一步输入不只给原始频谱而是把 fbank 特征直接当成单通道图像喂进 2D 卷积层。这在我的理解里更接近图像分类每一帧是频率维上的特征向量多帧堆叠后组合成一张二维图。一个典型的 DFCNN 声学模型片段如下from keras.layers import Input, Conv2D, BatchNormalization, Dense, Flatten input_feature Input(shape(num_frames, num_fbank_bins, 1), namefbank_input) x Conv2D(filters32, kernel_size(3, 3), paddingsame, activationrelu)(input_feature) x BatchNormalization()(x) x Conv2D(filters32, kernel_size(3, 3), paddingsame, activationrelu)(x) x BatchNormalization()(x) x Flatten()(x) x Dense(units256, activationrelu)(x) output Dense(unitsnum_classes 1, activationsoftmax, namectc_output)(x)这里 kernel_size 的 (3,3) 共用同一套卷积核处理时间帧和频率维度每个卷积层后接 BatchNorm 是语音任务常规操作因为不同说话人的音量差异会造成频带能量分布差距较大归一化能加速收敛。cnn_with_full_data.py是项目注释里建议直接训练的那份我实际跑下来感觉它更适合数据量充分的场景。fbank 版本对环境噪声更敏感但训练速度更快full 数据版本使用更大的卷积核和更多通道需要显存也更高普通 8G 显存下 batch 只能压到 4~8。初学者建议先用 fbank 版本跑通再把网络加深。3. 语言模型CBHG 结构如何从 TTS 反哺 ASR3.1 为什么纯声学模型不够需要语言模型融合声学模型输出的是每个时间步的概率分布第一步只是拿到一个候选字符序列例如语音“zhi shi”既可能是“知识”也可能是“指示”。这时语言模型的价值就是对候选序列打分让符合中文语义习惯的句子得到更高分。经典的 N-gram 语言模型覆盖不了长距离依赖比如“北京和上海的...补贴政策”中间的“补贴”与“北京和上海”之间的关联跨度远超过三元文法窗口。神经网络语言模型用嵌入向量和循环结构建模上下文CBHG 就是其中一种结构。3.2 CBHG 的结构解读与 Keras 实现CBHG 最早用在 Google 的 Tacotron 语音合成器里用来把字符序列编码为语义向量。它由三块组成不同卷积核大小的通道拼接、Highway 网络、双向 GRU。前一部分捕捉局部语法信息后两部分负责长距离依赖。语言模型场景下输入是 pinyin 或汉字序列结构完全可以直接复用。from keras.layers import Conv1D, MaxPooling1D, concatenate, Bidirectional, GRU def cbhg(inputs, num_banks16, conv_filters128): conv_list [] for k in range(1, num_banks 1): conv Conv1D(filtersconv_filters, kernel_sizek, paddingsame, activationrelu)(inputs) conv_list.append(conv) conv_bank concatenate(conv_list, axis-1) pool MaxPooling1D(pool_size2, strides1, paddingsame)(conv_bank) conv_proj Conv1D(filtersconv_filters, kernel_size3, paddingsame)(pool) highway_input concatenate([conv_proj, inputs], axis-1) bi_gru Bidirectional(GRU(units128, return_sequencesTrue))(highway_input) return bi_gru这里num_banks决定用多少种不同长度的卷积核16 表示从 1 到 16 个窗口并行提取语法特征。pool_size2, strides1在保序的同时压缩通道避免序列长度减半。最后把卷积输出和原始输入拼接再送进 BiGRU能让模型同时看到局部细节和全局语义。实际项目中language_model/CBHG_lm.py还会在卷积后接两层 Highway 网络Highway 的作用是让梯度能直接穿过较深的网络层。3.3 把 CBHG 从 TTS 移植到 ASR 的注意点语音合成里 CBHG 输入是字符嵌入输出用于生成频谱。移植到语音识别做语言模型时输入需要换成声学模型解码产生的拼音序列或候选汉字序列。这意味着输入序列长度和声学输出帧长不再是固定的训练时需要对序列做 mask避免把 padding 当作真实数据。另一个注意点是学习率。CBHG 中卷积 bank 的种卷积核同时训练参数起伏比简单 GRU 大我一般会在前 10 个 epoch 用 0.0005 的较低学习率之后每 20 个 epoch 按 0.5 倍衰减否则 loss 会先大幅下降再突然反弹。如果发现语言模型在验证集上过拟合优先减少 num_banks 和 highway 层数不要急着加 dropout。4. 数据预处理与训练策略让四套数据集统一跑起来4.1 四套数据集的格式归一化stc、primewords、Aishell、thchs30 四个数据集原始目录各不相同有的下好了是一堆 mp3有的附带了 XML 标注有的只给 wav 和单独的 txt。项目把它们统一整理到 acoustic_model/data 下格式是每行一个 wav 路径加对应文本隔开成一个 tsv 文件。通常处理办法是写一个 data_process 脚本python gen_data.py --input_dir data/thchs30 --format wav --output_corpus data/combined.txt# gen_data.py 内部对四个数据集做统一遍历 import os def build_corpus(data_dir, ext.wav): with open(data/combined.txt, w, encodingutf-8) as f: for root, _, files in os.walk(data_dir): for name in files: if name.endswith(ext): wav_path os.path.join(root, name) txt_path wav_path.replace(ext, .txt) if os.path.exists(txt_path): text open(txt_path, encodingutf-8).read().strip() f.write(f{wav_path}\t{text}\n)这段脚本把四个数据集的 wav 路径和文本统一写入一个 combined.txt。使用场景是两套数据集的 txt 编码可能一个是 gbk一个是 utf-8统一前我会先写一段小代码转成 utf-8。另一个容易踩的坑是 wav 采样率不统一原始 stc 可能是 44.1kHzAishell 是 16kHz后续特征提取前必须重采样否则 fbank 维度对不上。数据集的选择对训练影响很大我常用下面的标准来预筛数据集语音类型推荐场景训练速度参考thchs30明读新闻快速跑通流程最快primewords短句朗读命令词识别中等Aishell多场景普通话正式实验较慢stc听说混合实验对比较慢4.2 特征提取配置与超参数对照语音识别里有两种常用特征MFCC 和 fbank。MFCC 做离散余弦变换去掉一些音素无关信息早期 HMM 系统常用神经网络模型普遍直接用 fbank保留更全的语音信息。这个项目里 fbank 版本和 full 版本都依赖同一份 hyperparams.py里面核心参数如下sample_rate 16000 n_fft 1024 hop_length 160 num_fbank 80 pre_emphasis 0.97 max_frames 800sample_rate设为 16000 是语音识别最常见的折中既能覆盖大部分语音频率又不会让特征维度过大。n_fft和hop_length决定每帧覆盖的时间范围n_fft 是 102410 毫秒下移动 160 个采样点也就是 10ms 一帧。num_fbank越少训练越快80 维是精度和速度的平衡点。pre_emphasis是预加重系数用来提升高频分量这项对 fbank 影响不算大但如果把特征改成 MFCC 就必须保留。4.3 训练策略序列 padding、学习率衰减和 OOM 处理CTC 模型训练时最关键的是序列 padding。同一个 batch 里有的句子 3 秒有的 10 秒需要把 3 秒的尾部补零到 10 秒长度但同时告诉 CTC 层的input_length真实的帧数是多少。项目里常用这种方式import numpy as np # feats 是 (batch, time, feat) 特征 # text 是 (batch, max_label_len) 标签 def padding_batch(batch_feats, batch_text): max_t max([f.shape[0] for f in batch_feats]) feat_dim batch_feats[0].shape[1] pad_feats np.zeros((len(batch_feats), max_t, feat_dim), dtypenp.float32) input_len [] for i, f in enumerate(batch_feats): pad_feats[i, :f.shape[0], :] f input_len.append(f.shape[0]) return pad_feats, np.array(input_len, dtypenp.int64)出错率最高的地方是input_length和label_length的数据类型。CTC 层要求它们必须是 int32 或 int64否则会在求 loss 时抛出类型错误。Keras 模型里这两个输入通常设成 int64但在 numpy 里生成时默认可能是 int32尽量显式转型。OOM 是另一个常见问题。当训练 10 秒长音频时max_frames800会直接撑爆显存。我先建议把max_frames降到 400配合hop_length从 160 提升到 320这样时间维减半但注意频率分辨率不变实际有效信息损失不大。如果还想更激进可以在数据预处理阶段用声学特征均值归一化把每个样本的帧数压缩到统一范围同时保证 batch 的 padding 比例低于 0.2否则模型大部分时间在学 padding。5. 实证技巧如何用这个项目做中文语音识别 demo 并调出更快效果先把 thchs30 里一小部分数据拆出来当作开发集。不要用全部30 小时语音放进 8G 显存里训练一次要将近两小时我用前 2000 条音频做快速验证再决定是否全量。这里的第一步是确保 test.wav 和对应文本都整理好命令行ffmpeg -i input.wav -ar 16000 -ac 1 -sample_fmt s16 temp.wav这条命令把任意格式音频统一换成 16k 单声道 PCM与训练数据一致。很多同学训练时识别率还行一到自己录音就狂涨错字多半是音频格式没对齐你看 librosa 会自动重采样但项目里拿音频路径直接读的话就会遇到采样率不匹配的问题。模型推理阶段通常是加载 cnn_with_fbank 模型对单条音频提取 fbank 后向前传播再用 beam search 解码。如果只想看中间结果可以用model.predict打印出最后一层 softmax 的峰值正常情况下中文文字的位置会对应几帧连续高概率输出其他地方接近 0。这能帮你判断是声学模型没学好还是语言模型把分数拉偏了。评估不用等整个训练结束。我一般每 10 个 epoch 跑一次开发集用字符错误率 CER 作为指标计算逻辑很简单把所有识别出的汉字序列与标注计算编辑距离除以标注长度。小于 15% 说明声学模型结构没大问题超过 30% 时先去查四个数据集的文本是否有错或者特征是否出现了 NaN。如果发现 full 数据版本的 DFCNN 模型在显存受限环境下训练不稳可以把它当作特征提取器冻结前面卷积层只训练最后一层 BiGRU显存占用能降到三分之一。这个方案虽然损失一定精度但训练速度提升明显尤其适合需要快速交付课程设计或演示的时候用。实际跑完 thchs30 full 数据版本并 open 一次能明显感受到声学模型输出和 CBHG 语言模型重构后文本的流畅度差异。本文还有配套的精品资源点击获取
返回列表