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

资讯详情

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

AISHELL-3语音数据集全解析:结构、标注格式与TTS预处理实践

AISHELL-3语音数据集全解析:结构、标注格式与TTS预处理实践 我第一次下载AISHELL-3的时候以为它跟AISHELL-1、AISHELL-2差不多解压之后直接拿路径就能开始训练。结果打开train目录一看wav按说话人分好了子目录旁边还躺着label.txt和speaker.info.txt每行的格式既不像Kaldi常用的转录文本也不像LJSpeech那种纯filelist。我翻了半天README才把字段关系理顺。这篇博文就把AISHELL-3从目录结构、标注格式、音频参数到数据读取和预处理中容易踩的坑从头到尾捋一遍。给两类人看一类是打算拿它训练中文多说话人TTS的工程师另一类是刚接触语音语料库、想知道开源数据集到底长什么样的研究者。1. 背景与定位AISHELL-3是给谁用、为什么TTS语料库是独立赛道1.1 AISHELL系列的身份认知AISHELL系列是北京希尔贝壳科技做的开源中文语音数据集。AISHELL-1和AISHELL-2面向ASR任务说话人数多、时长也大但采样率普遍是16kHz对识别够用对合成不够。AISHELL-3是同一家公司专门为TTS准备的多说话人普通话语料音频质量提了一个档次这一点从设计目标开始就不一样。官方说明显示语料库包含218位普通话说话人的约85小时有效录音采样率44.1kHz16bit量化wav格式。和ASR语料相比AISHELL-3的录音更接近“室内安静环境下的自然朗读”每句话音长相对完整标点也保留在文本里目的是让模型能学到真实的停连和韵律。1.2 为什么TTS不能直接拿ASR语料来训我经常被问ASR语料那么多为什么TTS不能直接拿来训原因有三。第一是采样率很多ASR语料只有16kHz而现代神经声码器比如HiFi-GAN、Vocos通常需要24kHz以上才能输出不闷的声音拿16kHz去训TTS形式上可以但音质天花板很低。第二是干净程度ASR语料允许有嘈杂背景、重音字混读、甚至半句话因为识别模型能容忍这些但合成模型会把噪声和异常韵律学进去。第三是标注维度TTS除了要文本最好还要有拼音、韵律边界、标点层级中文尤其依赖拼音来消解同音字。AISHELL-3的label.txt里同时给了汉字和带声调的数字拼音就是冲着TTS训练管线去的。1.3 85小时、218人这两个数字意味着什么218人、85小时这两个数字意味着什么横向看中文开源TTS语料库本来就不多能到百人量级、并且发音人信息公开的几乎没有第二家。LibriTTS有五百多小时、上千人但它是英文VCTK有一百多人、44小时但也是英文并且口音混杂。中文普通话这一档AISHELL-3几乎是唯一能直接拿来练多说话人音色建模的开源选择。大约85小时听上去不多换算成句子数在几万句量级对于单说话人TTS来说可能偏少但多说话人模型要学的是说话人之间的差异每个说话人不需要特别多的句子这个体量刚好能支撑起说话人embedding和音色迁移实验。1.4 开源协议与获取渠道授权方面AISHELL-3使用Apache License 2.0这是开源协议里对商用很友好的一类你可以用于学术研究也可以改进后放到自己的商业产品里前提是保留原始版权声明并注明修改。这一点在语音语料库领域非常难得。下载渠道主要是OpenSLR之类的开源语音资源平台文件名一般是data_aishell3.tgz解压后得到data_aishell3目录。如果你是做商用产品建议下载时额外核对一下当前官网给出的版本和校验值避免拿到第三方转发的旧包。2. 解压后的真实目录结构train/dev/test划分和文件命名规则2.1 顶层目录与脚本里最常用的相对路径解压后顶层结构可以简化成下面这样各split目录下都各自带wav和label.txtdata_aishell3/ ├── train/ │ ├── wav/ │ │ ├── S0001/ │ │ │ ├── S0001_0001.wav │ │ │ └── ... │ │ └── ... │ └── label.txt ├── dev/ │ ├── wav/ │ └── label.txt ├── test/ │ ├── wav/ │ └── label.txt └── speaker.info.txttrain、dev、test是从原语料里预先切好的三个子集。多说话人TTS里数据划分有一条铁律训练集和验证集/测试集必须在说话人维度上隔离不能是同一个人既出现在train又出现在test。AISHELL-3在这方面做得比较规范我拿到的版本里train和dev/test的说话人ID没有重叠。你需要自己验证一次后面写K折或者自定义切分时也别忘了这条。2.2 wav里按说话人分子目录的组织方式wav目录往下会看到很多以说话人ID命名的文件夹这就是整个数据集最核心的组织单位。文件夹内通常放着一个说话人的全部句子录音文件名带说话人ID前缀和句子编号。这种设计的最大好处是你不用额外维护一张“哪个音频属于谁”的映射表从路径里就能拆出说话人ID——这对后面做说话人embedding和按说话人采样太有用了。不同渠道版本的ID前缀可能有差异有的用S0001这种形式有的用SSB0001这种形式但“目录名就是说话人ID”这个逻辑基本不变拿到数据先ls看一眼就好。2.3 和AISHELL-1/2在文件组织上的差异AISHELL-1的解压目录通常是wav/加transcript/两层wav下再按train/dev/test划分子目录转录文件是一整个aishell_transcript_v0.8.txt不在每个split里单独放。AISHELL-3改成把label.txt按split放进每个子集其实是更贴近训练框架的主流习惯train阶段只扫train目录test阶段只扫test目录不用再人工过滤。不要小看这个差异文件多了之后靠glob去全盘扫wav再按文件名过滤split速度和出错率都会让人难受。3. label.txt逐行拆解文件名、汉字文本、拼音序列如何对齐3.1 一行的三个信息段label.txt里每一行描述一个音频文件字段一般可以分成三段wav文件名、汉字文本、带声调的拼音序列。以我手上的train/label.txt为例打开大概长这样S0001_0001.wav 我们开始今天的会议。 wo3 men2 kai1 shi3 jin1 tian1 de5 hui4 yi4 S0001_0002.wav 他在门口等了一会儿。 ta1 zai4 men2 kou3 deng3 le5 yi2 hui3 er4第一列是音频文件名第二列是原始汉字句子第三列开始是拼音。具体分隔符不同版本可能不一样有可能是多个空格也有可能是tab写解析代码时建议统一用split()而不是按固定字符数切因为拼音部分是变长的。有的版本还会把标点从汉字句子里独立出去或者用其他符号标出停顿这点在读完一个文件后一定要先head一下自己确认不要拿来就写死。3.2 拼音标注的声调表示规则拼音标注用的是带数字声调的方案一声到四声对应数字1到4轻声一般用5标注也有版本对轻声不给数字。比如“的”在“的 de5”里是轻声“地”在“土地 tu3 di4”里是四声两个都写得很清楚。这种数字声调方案比直接用带调元音dé、dì更好处理因为模型输入的时候要把拼音token化数字作为一个独立token比从Unicode字符里抠声调符号要稳定得多。如果你要接BERT或Wav2Vec里的文本编码器拼音部分通常要转成音素序列数字声调再拆出来单独做一个feature维度或者直接和音节拼成一个token。3.3 多音字和错标文件里没写但训练时一定会遇到的雷AISHELL-3的拼音标注整体质量不错但也不是百分之百对学生。最典型的是多音字比如“重庆大学”里的“重”应该读chóng但标注偶尔会标成zhòng“觉得”的“觉”应读jué错标成jiào的情况我在清洗时也碰到过。这些错误单看一两条没什么进入训练之后会影响模型学汉字到拼音的对齐尤其在做字到音对齐的时候会跟着错。这里给一个我常用的处理流程先用pypinyin按默认词典把整句重新生成一遍拼音再和label.txt给的拼音逐字比对不一致的字挑出来人工判定。AISHELL-3本身是辅助标注我的态度是“参考但不盲信”这个原则同样适用于所有带拼音的开源语料。4. 音频参数与说话人元数据44.1kHz背后一些容易忽略的细节4.1 用实际代码验证音频编码参数音频参数用代码一读便知。比如soundfile读一个样本import soundfile as sf info sf.info(data_aishell3/train/wav/S0001/S0001_0001.wav) print(info.samplerate, info.subtype, info.channels) # 常见输出44100 PCM_16 1也就是说单个音频文件是44.1kHz、16bit量化的单声道wav/PCM。这个规格在开源TTS语料里属于中等偏上比VCTK的48kHz低但比LJSpeech的22.05kHz高足够让HiFi-GAN级别的神经声码器在24kHz到44.1kHz之间做升采样合成。另外要留意一个容易忽略的点虽然文件后缀是.wav但文件头可能带或不带额外元数据读取时不要自己硬解析RIFF的偏移直接用soundfile/librosa/torchaudio这类库。自己手写wav解析器在这种语料上纯属浪费精力。4.2 时长分布是batch设计的隐藏变量时长分布也是一个值得关注的问题。我用脚本统计过AISHELL-3的句子时长并不均匀短句可能只有一两秒长句在十秒以上的也有一批整体呈左偏分布。这会影响训练时的batch组成如果所有句子直接按文件名顺序打batch同一batch里可能全是长句或全是短句显存和计算效率会忽高忽低。现在主流的TTS训练器比如VITS都支持按时长相似度排序或者动态padding来解决这个问题但你得先把每条音频的时长统计出来喂给dataloader之前做一次排序。时长统计本身不复杂代码放在第5章思路就是读sf.info拿frames除以samplerate。4.3 speaker.info.txt里的字段与用法speaker.info.txt放在语料库根目录是AISHELL-3使用频率被低估的一份元数据。我手上的版本里每一行是一个说话人的基本信息字段至少包含说话人ID和性别后续列取决于版本常见会带年龄段和所属地域/口音区用tab分隔。地域/口音字段对TTS尤其重要虽然218人都被归类为普通话说话人但来自不同方言区的人在声母韵母和调值上会有可感知的差异这在模型里会被当成音色的一部分学走。如果你的目标场景是“标准播音腔”训练前最好按这个文件先筛掉口音色彩重的说话人如果目标是泛化到不同用户反而应该保留甚至在采样时按地域均衡一下。性别字段也很有用训练性别条件模型或者做发音人embedding可视化时能直接拿来当标签。5. 把AISHELL-3转成可训练的filelist完整代码走一遍5.1 解析label.txt的最小脚本先给一个解析label.txt的最小脚本。核心就一句话split()之后第0个字段是音频文件名第1个字段是汉字文本第2个字段到结尾是拼音。import os from collections import defaultdict AISHELL3_ROOT data_aishell3 SPLIT train label_path os.path.join(AISHELL3_ROOT, SPLIT, label.txt) records [] with open(label_path, r, encodingutf-8) as f: for line in f: line line.strip() if not line: continue parts line.split() # 根据实际版本第0列可能是纯文件名也可能是相对路径 wav_name parts[0] text parts[1] pinyin_list parts[2:] records.append((wav_name, text, pinyin_list)) print(f共解析 {len(records)} 条记录)注意两点如果第0列是纯文件名后面拼路径时要自己拼上说话人ID目录如果原文件里第0列已经是相对路径上面的wav_name可以直接喂给os.path.join。我建议先打印前5条记录看一眼再决定路径拼接方式这个习惯能帮你省掉大量因为版本差异导致的路径报错。5.2 路径拼接、时长统计与说话人均衡信息接下来把每条音频的时长、说话人ID都统计出来import soundfile as sf def resolve_wav_path(wav_name: str) - str: # 方案Alabel.txt里已经包含完整相对路径 full os.path.join(AISHELL3_ROOT, SPLIT, wav_name) if os.path.exists(full): return full # 方案B纯文件名需要按说话人ID拼 # 注意如果实际文件名不含下划线请按你手上的命名规则调整 spk_id wav_name.split(_)[0] return os.path.join(AISHELL3_ROOT, SPLIT, wav, spk_id, wav_name) speaker_duration defaultdict(float) speaker_count defaultdict(int) total_duration 0.0 for wav_name, text, pinyin_list in records: wav_path resolve_wav_path(wav_name) info sf.info(wav_path) dur info.frames / info.samplerate total_duration dur spk_id wav_name.split(_)[0] speaker_duration[spk_id] dur speaker_count[spk_id] 1 print(f总时长(小时): {total_duration / 3600:.2f}) print(f说话人数: {len(speaker_count)})这段代码跑完你就知道手上的版本到底有多少有效数据和多少位说话人。我实际跑下来官方宣称的85小时基本能对上说话人数也是218但个别说话人的录音条数很少最少的只有二三十条。这个speaker_count后面有大用做多说话人采样平衡和验证集划分都要用到。5.3 重采样、清洗与filelist输出预处理最后一步通常是生成训练框架能直接吃的filelist。这里按“LJSpeech风格 说话人ID 拼音”的常见组合输出一行一条out_path ffilelist_{SPLIT}.txt with open(out_path, w, encodingutf-8) as out: for wav_name, text, pinyin_list in records: wav_path resolve_wav_path(wav_name) spk_id wav_name.split(_)[0] pinyin_str .join(pinyin_list) out.write(f{wav_path}|{spk_id}|{text}|{pinyin_str}\n)如果你要重采样到24kHz或16kHz建议在生成filelist之后用librosa.load(wav_path, srtarget_sr)单独过一遍然后把重采样结果落成新文件。通常TTS框架在dataloader里直接用44.1kHz原音频也行但会把显存和IO吃得比较重训练速度明显下降。我自己的习惯是如果模型不需要输出44.1kHz就把数据预先重采样到模型目标采样率训练时省下的时间远大于预处理多花的那点功夫。6. 我踩过的几个坑和一条推荐的预处理流水线6.1 编码、分隔符、全角符号这些小问题AISHELL-3最常踩的坑不在模型而在文件读写。第一是编码label.txt基本都是UTF-8但Windows上有些解压软件解出来会带BOM头用utf-8直接open之后第一行会多一个\ufeff逐行比较时对不上。解法很简单open时用encodingutf-8-sig带不带BOM都能兼容。第二是分隔符有的行内部用tab有的用连续空格个别行甚至混了中文字段之间的空白。用split()能扛过绝大多数情况但要小心中文全角空格\u3000或全角逗号混进文本里它们在split()眼里和其他字符一样不会被自动过滤会在后面tokenize的时候报出奇奇怪怪的错。预处理脚本里加一行line.replace(\u3000, )很有必要。6.2 标点清洗别让朗读机丢掉停顿能力第三个坑是文本里标点和特殊符号不统一。AISHELL-3的原始句子以句号、逗号、问号居多但有些句子句末没有标点有些把冒号、引号和省略号也保留下来了。如果训练框架的text前端只覆盖常用标点遇到这些符号会直接崩或者静默丢掉。我建议在进入训练前做一次统一的标点表清洗保留逗号、句号、问号、感叹号、分号、顿号其余一律删除必要时把连续标点合并成一个。不要觉得这种清洗是小题大做中文TTS模型的停顿能力很大程度就来自这些标点子集少了它们句子会合成得像朗读机。6.3 底噪、静音和说话人类别不均衡第四个坑和音频本身有关。AISHELL-3的录音环境不是专业录音棚安静程度大概是普通办公室或家庭房间的级别底噪和轻微混响是存在的个别短句边界还可能有环境杂音。底噪问题在TTS里比ASR更致命因为合成模型会把噪声当作说话人特征的一部分学进去合成时要么整体显得“脏”要么在静音段放出沙沙声。我的预处理管线里一定会加一步静音裁剪用librosa的effects.trim()裁掉首尾静音段再做一次简单的能量门限过滤。如果发现某个wav的信噪比明显低干脆从训练集剔除不要为了凑句数把噪声学进模型。import librosa audio, sr librosa.load(wav_path, srNone) trimmed, _ librosa.effects.trim(audio, top_db30) if len(trimmed) sr * 0.3: # 过滤过短的裁剪结果 print(too short:, wav_path)最后一句说话人维度的数据平衡也很容易忽略。218个说话人的句数天然不均衡训练时如果直接按均匀采样句数少的说话人会被模型逐渐遗忘生成出来的音色会朝高频说话人偏移。常见做法是给每个说话人设置一个采样权重句数越少权重越高。这个细节不是AISHELL-3独有的但任何一个多说话人TTS项目都绕不开它建议在数据管线阶段就把speaker_count统计出来后续做采样时直接用。7. 横向对比与选型边界AISHELL-3到底处于什么位置7.1 开源TTS语料库关键参数对比把AISHELL-3放到更大的坐标系里看很多取舍会清晰很多。下面这张表是我自己整理的开源TTS语料库对比重点看语言、时长、说话人数和采样率语料库语言有效时长说话人数采样率授权LJSpeech英文约24小时122.05 kHz公有领域VCTK英文约44小时11048 kHz开放授权LibriTTS英文约580小时240024 kHzCC BY 4.0AISHELL-3中文普通话约85小时21844.1 kHzApache 2.0LJSpeech胜在单说话人、语音干净适合入门TTS但一句一个情绪泛化很差。VCTK有110位说话人但口音复杂很多说话人不是标准英音。LibriTTS量大、说话人多但源自有声书句子长度和录音风格跟日常语音差别很大。AISHELL-3的中文属性是它最大的差异化优势85小时、218人、44.1kHz采样率在中文多说话人TTS方向几乎没有替代品。7.2 什么时候值得用什么时候果断放弃说完了优势和定位也该泼一泼冷水。AISHELL-3不是万能的以下场景我建议谨慎使用你需要一个固定的、播音级的纯净音色这时候应该找专业录音的单说话人语料而不是含218种音色的多说话人集。你需要口语对话或情感表达AISHELL-3整体是朗读风格句子规整、情绪中性拿去训对话机器人出来的语气会偏“念稿”。你需要特定方言或中英混合输出AISHELL-3只有普通话和中文文本英文单词即使出现也要先做归一化。你对噪声底限要求极高虽然有底噪在可接受范围但和专业棚录还是有差距做商业精品音库时要重新评估。反过来如果你想做多说话人音色建模、说话人embedding研究、中文TTS数据管线练习、或者用户自定义音色的可行性验证AISHELL-3可以直接用了。最后说点个人体会。AISHELL-3我前后用过三轮第一轮觉得label.txt格式太乱第二轮开始用脚本统一清洗之后顺畅了很多到第三轮已经把它当成验证多说话人TTS想法的默认数据。这个语料库最值钱的地方不是85小时这个数字而是它把“多说话人中文普通话”这个在开源世界里稀缺的基本盘给补齐了并且给出了一个相对丰富、有说话人元数据、有拼音标注的规范结构。你真正上手之后会发现格式解读这件事本身不复杂但把它彻底搞透后面跑模型可以省掉一半的调试时间。
返回列表