AISHELL-3语音语料库详解:多说话人TTS与ASR数据预处理实践
AISHELL-3这个语料库做语音合成和语音识别的人基本都绕不开。它最大的价值不是“又多了一个开源数据”而是把多说话人的中文语音数据、文本标注、说话人信息一次性打包给了你而且标注格式干净几乎可以直接喂给模型。很长一段时间里中文TTS研究里要找一个发音人数量够多、音质统一、标注规范的语料选择并不多AISHELL-3能成为常客不是没有理由的。这篇文章就围绕AISHELL-3展开从它的数据规模、目录结构、标注格式到实际做预处理时怎么把wav和文本组织成可训练的样本一条线讲清楚。不管你打算做Multi-speaker TTS、声音克隆还是拿它做ASR训练只要涉及到这个语料库这篇文章都能帮你省掉不少踩坑时间。1. AISHELL-3到底是什么定位、体量与适用场景1.1 它的出身与定位AISHELL-3由北京希尔贝壳科技有限公司发布是AISHELL系列里专门面向多说话人场景的开源中文普通话语料库。系列里更早的AISHELL-1大家可能更熟悉但AISHELL-3的定位完全不同——AISHELL-1主打的是大词汇量连续语音识别LVCSR追求的是海量时长和丰富的文本覆盖度AISHELL-3则更偏向语音合成和声学建模研究尤其是多说话人建模。这句话怎么理解语音识别任务关心的是“什么字被读出来了”所以音频可以有口音、可以快、可以慢模型反正要学习鲁棒性。而语音合成任务关心的是“这句话该怎么读”它要求发音清楚、录音环境安静、声学特征稳定。AISHELL-3的录音是专业环境下录制的录音质量统一这决定了它作为TTS训练数据的先天优势。1.2 数据规模与统计官方公布的AISHELL-3核心数据是这样发音人数量218人总时长约85小时音频总量约8.8万条具体数量以你下载到的版本和过滤规则为准多数人会先做静音裁剪裁剪后数量略有变化汉字覆盖约11288个不同字符采样率44.1kHz采样位深16bit声道数单声道音频格式wav需要特别注意的是采样率44.1kHz这个参数。很多做TTS的人习惯用16kHz或22.05kHz的数据AISHELL-3给的是44.1kHz好处是保留了更多高频细节为声码器vocoder提供了充足的信息空间但代价是存储空间更大处理时一般需要重采样。另外这里有个容易误解的点——218位发音人不是平均划分的。每个人的录音时长、句子数量并不一致有的说话人录了几百句有的可能只有几十句。这在多说话人建模场景下会带来一个问题说话人数量够多但部分说话人数据量不足训练时容易让模型把说话人特征和内容特征耦合在一起。需要我在后面第4章讲数据切分时处理这一点。1.3 适合谁不适合谁适合用AISHELL-3的人群我简单分三类。第一类做多说话人TTS的研究者和工程师。无论是经典的Tacotron、FastSpeech系列还是现在流行的VITS、NaturalSpeechAISHELL-3都是很理想的基准数据集因为它在发音人数量上远超很多同类开源数据。第二类做声音克隆、语音风格迁移相关工作的开发者。声音克隆通常需要目标说话人的若干参考语句如果要测评模型在不同说话人上的泛化表现AISHELL-3的218个说话人正好提供了足够多样的评测样本。第三类做中文ASR和语音前端处理的同学。虽然它的定位不是ASR但85小时干净中文标注语音对声学模型训练、数据增强、语音增强实验来说也都是现成可用的资源。不太适合的场景也有。如果你的任务是低资源场景下的ASRAISHELL-3这种干净录音和真实场景差距较大泛化性不如多场景真实数据。如果你需要在极端嘈杂环境下的语音增强研究AISHELL-3几乎没有环境噪声也不算好选择。简单说它的长项是“干净、规范、多说话人”短板是“场景单一、无噪声”。2. 下载、解压与目录结构先把手里的文件彻底摸清2.1 压缩包构成与目录树你从官网或镜像站点下载到的通常是一个data_aishell3.tgz解压后大致是这个样子data_aishell3/ ├── train/ │ ├── wav/ │ │ ├── SSB0001/ │ │ │ ├── SSB00010001.wav │ │ │ ├── SSB00010002.wav │ │ │ └── ... │ │ ├── SSB0002/ │ │ └── ... │ ├── content.txt │ └── speaker.info ├── test/ │ ├── wav/ │ │ ├── SSB0005/ │ │ └── ... │ ├── content.txt │ └── speaker.info注意不同渠道下载到的压缩包内部结构可能略有差异有的版本会把train和test合并得更深一些有的会附带官方的start-kit脚本。我上面给的是最常见的结构。如果你解压后发现自己那份目录结构不同不用慌核心文件就三类wav目录、content.txt、speaker.info。2.2 每个目录和文件的含义先看wav目录。它的二级子目录以说话人ID命名说话人ID是SSB加四位数字从SSB0001到SSB0218。每个说话人目录下面就是对应的wav音频文件文件名也是相同的说话人ID加上该说话人内部句子序号比如SSB00010001.wav表示SSB0001这个人的第0001条录音。这个命名看似简单但非常重要——它在content.txt中就是用来定位音频的唯一标识。接着是content.txt。这是一个纯文本文件每一行对应一条音频的文本转录。它的格式我先在这里铺垫一下下一章仔细说。一句话概括每行由“音频ID 空格 对应中文文本”组成。然后是speaker.info。这个文件记录每个说话人的属性信息字段有说话人ID、性别、方言区、年龄区间等。多说话人语音合成中性别和方言往往是控制条件做条件TTS时会用它来构造说话人Embedding的辅助标签。2.3 speaker.info怎么读speaker.info的每行通常长这样SSB0001 F Z 20-30 SSB0002 M B 20-30含义不严格统一但常见的字段顺序是说话人ID、性别、方言区、年龄段。性别这里F表示FemaleM表示Male方言区用字母缩写表示说话人的普通话地域背景比如B可能表示北方官话区Z可能表示西南官话区具体要看你下载版本里附带的数据说明。我见过有人在读这个文件时直接用行号当说话人索引这是错误做法。因为说话人ID是跳跃的你直接数行数行号和ID对不上后面构建Embedding查找表时很容易串线。正确做法是先把speaker.info解析成字典id到index做一个显式映射。3. 转录文件与格式细节发音文本、标注字段逐行拆解3.1 content.txt格式拆解content.txt的每一行是音频ID加文本格式大概如下SSB00010001 今天天气真不错我们去公园散步吧。 SSB00010002 请把这份文件发给张经理。看起来就两列但有几个细节是很多人刚上手会忽略的。第一分隔符。官方给的通常是空格分隔但也有人下载到的版本用Tab分隔。建议你拿到文件后第一件事就是检查分隔符不要想当然这关系到后续编写的解析脚本是否能用。第二文本里是否带标点。AISHELL-3的文本是带中文标点的逗号、句号都会保留。这个标点不是无意义噪声——对TTS来说标点代表了韵律边界逗号对应短暂停顿句号对应更长的停顿和音高下降这些是训练韵律模型的重要信息。因此不建议把标点全部删掉。就算你做的是纯音素输入也建议在音素序列里保留停顿标记或者在文本标准化阶段把标点映射成专属token。第三文本的数字和英文。AISHELL-3朗读文本以中文汉字为主但偶尔会有阿拉伯数字、英文大小写字母比如年份、型号、缩写词。在TTS预处理必须做文本规范化Text Normalization时这些数字和英文要转成中文发音比如“2023年”要转成“二零二三年”“CPU”要转成“C P U”或“西皮优”取决于你的前端规则。3.2 音频ID与文本的对应关系音频ID看起来就是文件名去掉.wav后缀但如果只是简单截字符串可能会遇到ID和实际文件不匹配的情况。尤其是你在数据增强、筛选、去重之后再用一个生成的新ID去回查原始音频就很容易搞混。我自己的做法是在预处理阶段重建一个“三件套”索引wav_path音频实际路径utt_id从content.txt中解析的音频IDtext原始文本然后以utt_id作为主键确保wav路径和文本都对齐。这看起来很简单但在处理上万条数据时提前把主键关系建好后面每一步的过滤和扩充都会省心很多。3.3 音频格式确认wav参数虽然官方写明44.1kHz/16bit/单声道但“官方写明”不等于“文件一定如此”。我遇到过部分导出数据被人为重采样或压缩过的情况尤其是从某些网盘或社区分享渠道下载的版本。用ffprobe或Python的wave模块批量检查一下最稳妥ffprobe -v quiet -print_format json -show_streams SSB00010001.wav | grep -E sample_rate|channels|bits_per_sample如果发现某个说话人的音频采样率和其他人不一致赶紧隔离处理否则后面提取Mel谱时采样率不一致会导致特征维度和时间步长完全对不上训练直接崩。我把AISHELL-3的音频参数汇总成一张速查表方便对照检查参数值编码格式PCM采样率44100 Hz采样位深16 bit声道数1单声道时长范围1~10秒不等大部分集中在2~6秒语音内容中文普通话语句覆盖新闻、口语、叙事等多类句式4. 数据预处理实操从原始WAV到TTS/ASR可用样本4.1 建立索引文件与元信息表拿到语料库后第一步肯定不是直接训练而是建立一份全量索引。我强烈建议你写一个脚本遍历wav目录、解析content.txt和speaker.info把三份文件的信息合并成一个metadata.csv字段是utt_id, wav_path, text, speaker_id, gender, dialect, duration其中duration可以通过wave模块或者ffprobe读取音频时长。整个索引构建代码不复杂但需要注意两条遍历wav目录时用os.walk自动递归到子目录别手动拼路径。解析content.txt时遇到前后字段数量不一致的行打日志直接跳过并统计不要默默忽略。生成metadata.csv后后续所有操作都在这张表上做源数据别乱动。我见过有同事直接在原语料库上增删改结果数据量一大版本根本收不回来。坚持“原始数据只读索引另建”这是数据工程的基本素养。4.2 重采样44.1kHz降到22.05kHz还是16kHz到底降到多少合适取决于你的模型。如果你做的是HiFi-GAN、Vocoder这种高保真模型保留44.1kHz可以如果你用FastSpeech、VITS这类常见的TTS框架它们一般搭配的声码器在22.05kHz下训练用16kHz的也有不少但音质天花板会低一些。我做TTS时习惯统一降到22.05kHz理由是在音质损失可接受的范围内存储和计算成本比44.1kHz减少了一半而且大多数开源预训练声码器HiFi-GAN v1/v2都匹配22.05kHz。如果你的显存和磁盘都很宽裕想做到更高质量的合成那就保留44.1kHz或者降到一个中间值但一定要全库统一。重采样建议用librosa或sox批量处理时用torchaudio.save注意一下管线调度。重点提醒重采样会导致音频数据发生物理变化如果你后面要做“说话人验证”或者“音频指纹”这类任务请保留原始版本。另外重采样后会引入轻微的振铃效应和混叠尤其在16kHz下对高频信息敏感的任务要斟酌。4.3 静音裁剪与端点检测原始录音里每条音频前后多少会有些静音或底噪TTS训练时这些空白段会对模型学习韵律边界产生干扰。常见做法是做VAD语音活动检测把首尾静音裁掉。我用的是webrtcvad参数设置成mode2严格度适中帧长20ms同时设置一个最小语音长度阈值避免误杀过短的语音。核心代码思路是这样import webrtcvad import wave import struct vad webrtcvad.Vad(2) window_size int(16000 * 0.02) # 20ms at 16k, 实际根据你的采样率调整 with wave.open(wav_path, rb) as wf: rate wf.getframerate() frames wf.readframes(wf.getnframes()) # 切帧并标记是否有语音 # 依据连续的语音帧确定起止位置做VAD时有几个实际坑要提醒采样率对VAD影响极大。webrtcvad只支持8k、16k、32k、48k你如果先重采样到22.05k直接用会报错。先降到16k做VAD得到边界后再映射回原采样率。静音裁剪不要做得太狠保留100~200ms的前后缓冲避免把塞音、擦音的开头部分误切掉。4.4 文本规范化标点、数字、英文处理文本规范化是中文TTS链条里最琐碎、最容易出错的一环。AISHELL-3的文本整体相对干净但依然需要处理以下几个情况全半角统一中文标点使用全角英文符号和数字用半角先把全角数字、字母统一转成半角再把半角标点统一成全角。数字转汉字这是大头。“123”在“编号123”和“气温23度”里的读法完全不同需要上下文规则。常用的开源工具有WeTextProcessing也就是TN库处理效果不错可以直接用在AISHELL-3上。英文单词处理连续大写字母如“CPU”、“AI”一般逐字母读首字母大写的单词如“Google”按英文发音读。到底按哪种取决于你的合成前端和词典覆盖范围建议在预处理时统一策略并在文档里记录。我自己的经验是第一次做文本规范化的同学容易在数字处理上纠结过深。其实先跑一遍默认规则看输出结果有没有明显错误再针对AISHELL-3文本中出现的高频数字模式做补充规则比一开始就堆规则要好得多。4.5 按说话人切分与多说话人训练集构造AISHELL-3有218个说话人但我们不太可能直接把所有人塞进训练集。通常的做法是设定最少句子数阈值。比如某说话人录音少于50条就考虑剔除或者只用于验证因为数据量太少模型很难学到稳定的说话人特征。根据speaker.info做性别均衡。如果某个性别说话人数量远大于另一个全量训练会导致模型合成声音偏向多数的性别后期做条件生成时会出问题。说话人信噪比统一的检查。虽然录音环境一致但个体音量有差异建议对每个说话人计算平均RMS能量做一轮音量归一化避免模型把响度误解成说话人特征。构建多说话人TTS的metadata时一般需要两列speaker_id和speaker_embedding索引。把speaker_id连续化成0~N-1的index并和speaker.info保持映射关系。文件末尾最好留一个speaker2idx.json防止下次用的时候重新映射导致实验无法复现。5. 常见问题与排错实录5.1 文件路径对不上说好的SSB0200怎么少了一整个文件夹下载完整后先统计一下说话人目录数量是否是218。我有一次发现目录只有217个排查半天是解压时某个子目录因为文件名非法没解出来或者传输过程中丢包导致部分文件缺失。建议在建立metadata时直接用代码统计每个说话人的文件数和content.txt里的条目数比对。不一致的马上定位不要等训练到一半才发现数据量不对。5.2 采样率不统一为什么loss在震荡AISHELL-3官方数据统一是44.1kHz但你如果从第三方拿到的处理版很可能已经被人降过采样甚至不同文件采样率不一样。这个现象最坑的地方在于不会报错而是模型训练时loss一直震荡因为你提取特征用的帧长对应的时间长度在不同音频之间不一致。排查方式很简单批量用ffprobe扫一遍所有音频的采样率统计unique值。如果有多种采样率统一重采样再重新提特征。5.3 文本读取乱码content.txt打开全是锟斤拷content.txt虽然以UTF-8编码为主但有些渠道发布的版本可能是GBK或GB2312。你在Linux下用Python默认utf-8读就会出现乱码。我自己遇到过一次文件在Windows上被编辑器转换了编码保存此后在Linux上怎么读都是乱的。标准做法是用chardet或file命令先检测编码再决定用什么解码。如果确定是GBK就这样读with open(content.txt, r, encodinggbk) as f: lines f.readlines()再折腾一点的办法是统一转成UTF-8存一份副本后续都用副本。5.4 说话人ID不连续Embedding查表对不上AISHELL-3的说话人ID从SSB0001到SSB0218看起来连续但实际训练时你可能会过滤掉一部分说话人导致ID出现空洞。如果你直接拿ID字符串去和模型输入取模或者做hash很容易造成Embedding冲突。解决思路是永远维护一个speaker2idx字典只在训练脚本初始化时加载一次。测试时如果有人给你一个生僻的说话人ID查不到就直接返回一个默认的未知说话人向量或者报错不要静默忽略。结尾AISHELL-3的整体使用体验在开源中文语音语料库里算得上非常省心发音人多样、录音干净、标注格式简单几乎不需要做“清洗”就能直接上手。但越是简单的数据越要警惕“简单背后的细节”。就我个人的经验真正花时间的不是把模型跑起来而是把数据管线做得极稳——从编码、采样率到文本规范化和说话人映射每一步都提前定好规则后面实验才能有可复现性。最后分享一个实用小技巧如果你打算长期拿AISHELL-3做各种实验建议在下完原始数据后先做一份“标准预处理版”包含裁剪后的wav、处理好的metadata.csv、统一的采样率和规范化文本存成一个独立目录。以后再跑新实验直接从这个标准版出发不仅省时间还能保证不同实验之间的数据条件一致这才是最有价值的“护城河”。