ARTICLE DETAIL

资讯详情

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

中文语音识别毕设项目从环境配置到解码实战指南

中文语音识别毕设项目从环境配置到解码实战指南 简介这套资源是基于PyTorch框架实现的中文语音识别系统包含完整源码、预训练模型与配套使用说明面向毕业设计、课程设计及深度学习语音识别入门人群。系统支持对wav格式音频进行识别并自动播放识别文字预置了多个测试音频可直接运行验证效果。资源包共85个文件约93.44MB核心内容包括7个Python源码文件模型、注意力机制、位置编码、特征提取等模块、预训练模型文件以及中文语音数据集样本文本与音频同时附带录音库安装包、格式转换说明、环境依赖列表等辅助文件便于快速搭建运行环境。包内以音频、文本、脚本、配置等类型为主目录结构清晰。目前已有615人学习使用。借助这套资源可完整体验中文语音识别的数据准备、模型推理与结果输出流程还能基于源码开展二次开发适合作为算法研究或项目演示的基础。1. 基于深度学习的中文语音识别毕设项目包能跑和识别是两个门槛这类 zip 解压之后通常有两种走向改两条路径就听到测试 wav 被转成汉字或者环境依赖连环报错卡在装 torchaudio 之前连模型文件都没见到。我理解为什么很多人把它当成开箱即用的软件但中文语音识别系统本质是一个最小可复现的教学工程你得先问对问题用 Fbank 还是 MFCC声学模型是 CTC 还是 Attention解码时 beam 开多大语言模型权重给多少。中文跟英文最大的差别在同音字、多音字和没有空格的分词边界所以不是把音频丢进深度学习模型就能出文本后面还有解码、纠错和标点处理。这篇文章写给要做毕设、要跑通源码、要改参数做演示的人路径从环境到解码逐层拆开。2. 端到端框架与 Fbank 特征中文语音识别模型的选型和特征流程2.1 DNN-HMM 已是过去式CTC / Attention 为什么是这类包的主流在中文语音识别这个题目下项目包选哪条技术路线基本决定了你后面要折腾多少东西。早期的 Kaldi 方案是 GMM-HMM 对齐再用 DNN 做音素状态的后验概率需要音素集、词典、决策树状态绑定、强制对齐这一整条链路每一样都在消耗毕设时间。而现在把建模对象换成汉字之后整个声学模型可以只用音频文件和汉字序列两个目录就端到端训练起来开源实现多排错也容易所以近几年 Python 源码包里出现的基本都是 PyTorch 写出来的端到端模型。声学模型方案训练链路复杂度直接对汉字建模典型实现DNN-HMMKaldi 式高需要音素状态绑定和对齐否需音素词典Kaldi thchs30 脚本CTC 字模型低帧级自动对齐是PyTorch warp-ctcAttention Encoder-Decoder中自回归解码慢是Transformer、ConformerCTC Attention 联合中两个损失同时训练是开源中文语音项目最常见CTC 最大的好处是训练时不需要音素级的标签对齐它自己学会在 blank 符号和重复字之间做 collapse这让源码包的预处理简单很多。Attention 单独用来生成汉字时长句会出现注意力漂移很多包的做法是把 CTC 作为辅助损失加上解码时再选择 CTC 贪心输出或注意力自回归输出。这个框架你不用改但要能看懂 loss 里的ctc_loss alpha * attention_loss是在干什么答辩时被问到是大概率事件。2.2 词表设计中文语音识别为什么以“字”为基本单位英文语音识别的词表是 subword中文项目包常见做法是用汉字表建模而不是词。原因很直接中文常用汉字 3000 到 6000 个词表却需要几十万到上百万普通数据量下 OOV 问题比英文严重得多。字级建模把词典固定住多音字问题则抛给语言模型和后处理去解决。常见的开源数据集是 THCHS-30 和 AISHELL-1前者约 30 小时适合跑通流程后者约 178 小时适合认真训练一版毕设项目包里多数用的是前者。python -c open(dict.txt,w,encodingutf-8).write(\n.join(sorted(set(open(train.txt,encodingutf-8).read()))))这条命令把训练文本里的所有字符取出去重后写入 dict.txt生成的词表还需要手动补充blank、sos、eos、pad、unk这几个特殊 token。注意要把空格、换行和英文标点过滤掉否则模型会浪费概率去建模无意义的符号。特殊 token 在解码时不会输出所以使用说明里一般会强调“词表和 checkpoint 配套”换词表基本等于换模型。2.3 Fbank 特征80 维 Mel 滤波器的参数和采样率选择特征部分在源码包里一般是一个compute_fbank脚本它负责把所有 wav 批量转成 numpy 数组或 .ark 文件。为什么选 Fbank 而不是 MFCC很多教科书解释是 MFCC 的 DCT 压缩丢掉了相邻滤波器组之间的相关性而深度学习可以自己从全量能量中学特征保留完整信息往往更有利。参数上中文语音识别的开源实现基本是一套统一配置16k 采样率、25ms 窗长、10ms 帧移、80 维 Fbank再加 mean-variance 归一化。import torch import torchaudio def extract_fbank(wav_path, sample_rate16000, num_mel_bins80): waveform, sr torchaudio.load(wav_path) if sr ! sample_rate: waveform torchaudio.functional.resample(waveform, sr, sample_rate) fbank torchaudio.compliance.kaldi.fbank( waveform, num_mel_binsnum_mel_bins, frame_length25.0, frame_shift10.0, dither0.0, energy_floor0.0, ) return fbankdither0.0是关闭抖动因为后面通常还要做速度扰动和 SpecAugment 数据增强前置加噪没有意义energy_floor0.0避免过低能量被强制拉高。torchaudio.load读进来如果是双声道经验做法是先转单声道很多源码包没有做这一步导致训练输入尺寸对不上时的报错信息非常隐蔽。批量处理前我会先用 ffmpeg 统一转成 16k、16bit、单声道 wav再进特征提取这个操作能省掉至少一小时查 bug 的时间。归一化时注意是按全局均值和方差做而不是按每句话做后者会把不同音量的差异抹掉。3. Python 环境、源码结构与推理最小验证把“包运行”落实3.1 PyTorch 版本组合与 conda 环境先跑 CPU 再上 GPU拿到任何标注“包运行”的 Python 项目别急着pip install -r requirements.txt。先创建一个独立 conda 环境避免把你日常环境炸了。Python 版本有个经验判断代码里还有distutils、直接import librosa的老接口就选 3.8 或 3.9源码出现torch.compile、dataclass、zipfile.Path选 3.10/3.11 更省事。中文语音识别项目包的代码风格通常滞后于框架版本所以我一般默认 Python 3.9。conda create -n asr python3.9 -y conda activate asr conda install pytorch torchaudio cpuonly -c pytorch -y pip install librosa editdistance tqdm tensorboard webrtcvad先用 CPU 版把模型加载和推理跑通再装 CUDA 版本。CUDA 版本的问题集中在 torch 与显卡驱动的匹配上conda install cudatoolkit会自带 toolkit不需要单独装系统驱动。torchaudio的 CPU 版和 GPU 版要一起统一否则会出现libtorch_cuda.so缺失这种进程直接崩溃的问题。Python 版本兼容性常见报错3.8~3.9兼容大部分开源语音项目无明显坑3.10多数可运行老代码偶发 warning3.11需要检查依赖版本torchaudio旧版装不上3.12不推荐distutils缺失、编译型依赖安装失败3.2 目录结构与三跳运行先看懂源码包怎么组织好的源码包一般都遵循 data / models / tools / exp 的分层。不要直接点运行脚本先打开目录树把数据和 checkpoint 的路径全部弄清楚. ├── data/ │ ├── thchs30/ # 源音频和标注 │ ├── list/ # train/dev/test 文件列表 │ └── dict.txt # 字表 ├── models/ │ ├── encoder.py │ ├── decoder.py │ └── asr_model.py ├── tools/ │ ├── prepare_data.py │ └── compute_fbank.py ├── exp/ │ └── checkpoints/ # 训练产物 └── infer.py在这类包里跑最小流程有三个命令顺序固定。prepare 负责生成数据列表遍历原始文件夹输出 train.list 和 dev.list并把 wav 路径与标注文本对齐compute_fbank 把每个 wav 转成特征文件这一步通常最慢看到进度条卡住先检查是否有样本损坏infer 加载已有 checkpoint 做单条推理。python tools/prepare_data.py --data_root data/thchs30 --output_dir data/list python tools/compute_fbank.py --config configs/train.json python infer.py --checkpoint exp/checkpoints/model.pt --wav data/test.wavprepare 这步报错最多的原因是 THCHS-30 原始目录下放了 .wav 和 .trn 两个文件如果数据集只有 .wav列表生成后标注会变成空行。compute_fbank 里常见参数是--num_workers在 Windows 上必须设置为 0否则多进程在数据处理阶段会卡死这个坑在 PyTorch 语音项目里非常经典。infer 的输出如果全是空白先看 checkpoint 路径对不对再看词表和模型头文件是否匹配。3.3 环境自检五分钟定位是缺包还是路径问题python -c import torch, torchaudio; print(torch.__version__, torchaudio.__version__) python -c import sys; sys.path.insert(0, .); from models.asr_model import ASRModel; print(ok)第一行确认 torch 与 torchaudio 的版本配对第二行确认模型代码本身能导入。能走到这两步报错就不可能再出现在框架层面接下来按 traceback 查路径和文件编码。Windows 上还要注意中文路径导致的 UnicodeDecodeError运行前把项目放在纯英文路径下是最省事的做法。4. 模型加载、Beam Search 与中文热词纠偏解码侧的关键参数4.1 加载本地模型 checkpointcpu 还是 gpu先解决键名不匹配加载本地模型值得单独讲因为按经验八成人卡在这一步。下载好的模型文件一般以 .pt 或 .pth 结尾里面不一定只放 state_dict也可能是打包了model_state_dict、optimizer 和 epoch 的字典。不要直接model torch.load(path)除非你有把握它是完整序列化的 Module。稳妥做法是手动取出状态字典再加载import torch from models.asr_model import ASRModel checkpoint torch.load(exp/checkpoints/model.pt, map_locationtorch.device(cpu)) state checkpoint.get(model_state_dict, checkpoint) model ASRModel(vocab_size6000, encoder_dim256) model.load_state_dict(state) model.eval()map_location决定加载发生在哪个设备。CPU 机器上忘写这个参数一个几百 MB 的 checkpoint 可能触发 CUDA 初始化然后崩溃GPU 机器上写map_locationcuda:0则更省内存。如果报size mismatch八成是vocab_size对不上数一下 dict.txt 的行数改掉就行。如果报missing key(s)把model.state_dict().keys()和 checkpoint 的 keys 打出来对比常见原因是保存时套了nn.DataParallel需要去掉前缀module.再加载state {k.replace(module., ): v for k, v in state.items()}4.2 贪心解码到 Beam Searchbeam 开多大lm_weight 给多少模型输出的 log_probs 经过 softmax 后是一条帧级标记序列要得到中文句子还需要合并连续相同 token、去掉 blank。经典 CTC 贪心解码实现很短def ctc_greedy_decode(log_probs, blank0): tokens log_probs.argmax(dim1).tolist() result, prev [], blank for idx in tokens: if idx ! prev and idx ! blank: result.append(idx) prev idx return result这个逻辑体现了 CTC 最重要的 collapse 规则相邻重复的标记只保留一个blank 作为分隔符但不能出现在输出里。blank0对应词表中的blank不同项目可能放在索引 0 或最后不对齐就表现为整段识别结果里频繁出现blank字样。贪心只有一条路径遇到同音字、多音字时后验概率接近一次 argmax 就决定了胜负所以生产中常用 Beam Search 保留候选序列。一个便于理解的最小实现如下工程上可以换用 pyctcdecode 这类库import heapq def beam_search_decode(log_probs, beam_size10, blank0): seqs [(0.0, [], blank)] for t in range(len(log_probs)): new_seqs [] for score, seq, prev in seqs: for idx, p in enumerate(log_probs[t]): if idx blank: new_seqs.append((score p.item(), seq, idx)) elif idx prev: new_seqs.append((score p.item(), seq, idx)) else: new_seqs.append((score p.item(), seq [idx], idx)) seqs heapq.nlargest(beam_size, new_seqs, keylambda x: x[0]) return seqs[0][1]这里每一步只保留累计概率最高的 beam_size 条候选idx prev时不做 append对应 CTC 的重复合并近似。完整实现还要考虑路径合并和语言模型分数但参数直觉已经够用了。毕设演示一般把 beam_size 设在 10 到 30 之间CPU 上开到 30 会明显变慢。解码参数推荐值作用beam_size10~30保留候选路径数越大越慢lm_weight0.2~0.6语言模型打分权重length_penalty1.0~1.2长句截断补偿blank_threshold0.9blank 概率阈值很少动语言模型权重不是越大越好。有的项目包内置了 KenLM 加载的 n-gram 模型lm_weight过高时声学概率被语言模型盖过输出的是语言模型里最高频的句子而不是音频内容。调试时从 0.0 开始以小步长加每次观察同一段音频的输出变化比一次设 0.5 更有效。4.3 热词纠偏不重训模型用后处理把姓名和品牌拉回来中文识别即使在字级做得不错姓名、品牌、专业术语也常常因为语料里出现少被识别成同音常见字。立竿见影的方法是热词纠偏它不是重新训练而是对解码结果做替换。如果模型输出字级拼音差异是最直接的匹配键先给热词建拼音表再对候选文本转拼音用编辑距离判断是否命中最后替换。def apply_hotwords(text, hotwords): for name in hotwords: for seg in hotwords[name]: if seg not in text: text text.replace(seg, name) return text hotwords { 商汤科技: [商汤科技, 商汤科技公司], 李现: [李现], } print(apply_hotwords(现在和李现一起参加会议, hotwords))这个例子说明两点热词表不是简单一个词替换要写出常见错句替换顺序重要先长词后短词否则“商汤科技”会被“科技”拆掉。注意不要对全文做全局 replace万一错句和正句同时存在替换会把原本正确的部分改坏。落地时我会把“命中拼音但文本不同”和“文本本就相同”分开处理前者用候选概率对比决定是否替换后者不动。这一节的本质是中文语音系统里最容易被忽视的字到句兜底逻辑。源码包里很可能没有这个模块你补一份热词表并在使用说明里写清楚替换阈值的作用答辩时就是实实在在的改进点比只贴一个识别率数字更有说服力。5. 长音频切分兜底技巧用 VAD 和重拼接保住中文语音识别演示效果5.1 为什么长音频整句识别会越到后面越差CTC 模型对长序列并不友好logits 序列超过几百帧时重复合并和 blank 的累积误差会让输出断句错乱Attention 解码器也有位置漂移说话人停顿稍长就可能在句尾重复或漏字。演示时不要直接跑整段三分钟音频先做语音活动检测把静音段切开再逐段识别。5.2 基于 webrtcvad 的切分与重拼接import webrtcvad def split_wav_vad(pcm, sample_rate16000, aggressiveness2, min_speech_ms250, min_silence_ms600): vad webrtcvad.Vad(aggressiveness) frame_ms, frame_len 20, int(sample_rate * 0.02) frames [pcm[i:iframe_len] for i in range(0, len(pcm), frame_len)] active [vad.is_speech(f, sample_rate) for f in frames] segments, start [], -1 silence_cnt 0 for i, a in enumerate(active): if a: if start 0: start i silence_cnt 0 elif start 0: silence_cnt frame_ms if silence_cnt min_silence_ms: segments.append((start * frame_ms / 1000, i * frame_ms / 1000)) start, silence_cnt -1, 0 return segmentsaggressiveness2是稳定的档位1 会放过更多静音导致切得太碎3 在背景噪声下容易把语音尾部吃掉min_silence_ms600控制断句低于这个值的停顿不会切开防止一句话因为换气被斩成两半。webrtcvad 接收 16bit PCM 字节流调用前要把 float 数组转成 int16 再 tobytes()。没有 webrtcvad 时用能量阈值也可以但在空调声、翻书声存在的环境里WebRTC 的误判明显更少。切分后的识别结果不要直接拼成一个大字符串按段记录时间和文本更合理每段单独过模型按时间顺序拼成带时间戳的字幕式文本。我这里说的拼接是句级拼接不是字级直接拼否则会在切割处出现吞字或重复。演示前我会拿一段三分钟录音先跑整句再跑 VAD 切分后的版本把两个文本连同对齐时间戳打在同一张页面里这个对比本身就是最有说服力的稳定性说明。本文还有配套的精品资源点击获取
返回列表