这是一个自由的语音生成软件,主要使用librosa来解析音频,pytorch来机器学习,旨在通过深度学习技术生成音乐作品。
TkTTS 是自由软件,遵循Affero GNU 通用公共许可证第 3 版或任何后续版本。你可以自由地使用、修改和分发该软件,但不提供任何明示或暗示的担保。有关详细信息,请参见 Affero GNU 通用公共许可证。
这里演示的是大致流程,实际可能需要调整,不过一般照着这个来就行了
pip install -r requirements.txtnvidia-smi # 查看 CUDA 版本
pip install -r requirements.txt --index-url https://download.pytorch.org/whl/cu128 --extra-index-url https://pypi.org/simpleTip
根据nvidia-smi的输出CUDA Version把cu128换成你自己的 CUDA 版本,比如输出CUDA Version: 12.1就把cu128替换为cu126
具体来说,PyTorch 的CUDA是向下兼容的,所以选择时只需要选择比自己的 CUDA 版本小一点的版本就行了。
比如 PyTorch 提供了三个版本: 12.6, 12.8, 12.9,然后你的 CUDA 版本是12.7,那么就选择12.8(因为官方提供的12.6 < 你的12.7 < 官方提供的12.8)
准备一个数据集,比如我用游戏提取脚本提取游戏语音作为数据集,这里演示使用 The LJ Speech Dataset 数据集
wget https://data.keithito.com/data/speech/LJSpeech-1.1.tar.bz2 # https://keithito.com/LJ-Speech-Dataset/然后写一个脚本,将数据集转化为这种结构
dataset
metadata.json
select_oblige
fem_kuk_00001.ogg
senren_banka
akh108_001.ogg
而元数据metadata.json内容应类似这样:对于每一个音频,都应该在元数据有一个对应的条目
{
"select_oblige/kuk/fem_kuk_00001.ogg": {
"text": "XXXX",
"positive_prompt": ["sex:female"],
"negative_prompt": ["sex:male"]
},
"senren_banka/akh108_001.ogg": {
"text": "XXXX",
"positive_prompt": ["sex:female"],
"negative_prompt": ["sex:male"]
}
}- 使用GARbro从游戏目录的
xxx.pfs文件提取出sound/vo和script文件夹,分别保存到/path/to/game/sound/vo和/path/to/game/script - 运行
python extract_artemis.py /path/to/game/script /path/to/game/sound/vo /path/to/artemis_pre_dataset,它会输出一个角色ID对应的角色名次数 - 仿照
examples/select_oblige_c2t.json和examples/aikotoba_sss_c2t.json,根据第二步的输出,写你要提取的游戏的角色ID-角色名映射表,保存到/path/to/artemis_c2t.json - 运行
python convert_artemis_to_dataset.py /path/to/artemis_pre_dataset /path/to/artemis_c2t.json /path/to/artemis_dataset -p source:<游戏名> -p <其他你想在所有对话加上的标签> -n <你想在所有对话加上的负面标签> - 你的数据集应该已经在
/path/to/artemis_dataset了,元数据文件在/path/to/artemis_dataset/metadata.json
- 使用GARbro分别从游戏目录的
data.xp3文件和voice.xp3提取出scn和根目录文件夹,分别保存到/path/to/game/script和/path/to/game/voice - 运行
python extract_kirikiriz.py /path/to/game/script /path/to/game/voice /path/to/kirikiriz_pre_dataset,它会输出所有对话出现的角色名 - 仿照
examples/senren_banka_c2t.json,根据第二步的输出,写你要提取的游戏的角色ID-角色名映射表,保存到/path/to/kirikiriz_c2t.json - 运行
python convert_kirikiriz_to_dataset.py /path/to/kirikiriz_pre_dataset /path/to/kirikiriz_c2t.json /path/to/kirikiriz_dataset -p source:<游戏名> -p <其他你想在所有对话加上的标签> -n <你想在所有对话加上的负面标签> - 你的数据集应该已经在
/path/to/kirikiriz_dataset了,元数据文件在/path/to/kirikiriz_dataset/metadata.json
我们都知道,有一些标签是不可能同时存在的,比如sex:male和sex:female就不可能同时出现在一个正面提示里,所以我们可以定义一堆标签互斥组,类似这样
[
{"sex:male", "sex:female"},
{"age:children", "age:teenager", "age:adult", "age:old"},
{"character:白上フブキ", "character:夏色まつり"} # 举这两个例子是因为我喜欢看
]对于一个音频的元数据,我们遍历每一个标签互斥组(类型:集合),然后检测元数据的正面标签是否包含且仅包含了这个标签互斥组中的一个标签,比如
positive_prompt: set[str]
negative_prompt: set[str]
groups = [{"age:children", "age:teenager", "age:adult", "age:old"}, ...]
for group in groups:
if len(intersection := positive_prompt & group) == 1: # 为什么不是只要检测到包含就全部加入负面提示呢?因为可能有些用户想要同时指定两个冲突的标签;鬼知道他们是怎么想的
negative_prompt.update(group - intersection) # 将该组其余所有的互斥标签加入负面提示我写了一个脚本来节省自己造轮子的麻烦,你可以通过运行
python augment_prompt_with_exclusion.py /path/to/dataset/metadata.json /path/to/dataset/metadata_augment.json /path/to/mutually_exclusive_groups.json来实现提示词增强
如果你有若干个数据集,像这样
datasets
shirakami_fubuki
metadata.json
ytb_live01_001.ogg
ytb_live01_002.ogg
...
natsuiro_matsuri
metadata.json
ytb_live01_001.ogg
ytb_live01_002.ogg
...
你可以通过将不同数据集放在一个文件夹,比如/path/to/datasets,然后运行
python merge_datasets.py /path/to/datasets/shirakami_fubuki/metadata.json /path/to/datasets/natsuiro_matsuri/metadata.json -o /path/to/datasets/metadata.json将其元数据合并到/path/to/datasets/metadata.json;此外,你可以将原来的/path/to/datasets/shirakami_fubuki/metadata.json和/path/to/datasets/natsuiro_matsuri/metadata.json删除
你可以在训练前将数据集切割,你可以通过运行
python split_dataset.py /path/to/dataset/metadata.json test.json:1 nul:99这样,你的/path/to/dataset目录应出现总数据1%的test.json。这主要用于与小规模数据集测试。
由于 FastSpeech 系列需要显式文本-语音对齐,第一代往往从训练好的自回归模型(称为“教师模型”)提取编码器-解码器的注意力矩阵,用来对齐文本和音频。
第二代 FastSpeech 改进了这一点,改为使用Montreal Forced Aligner提取时长信息。
这里介绍如何使用Montreal Forced Aligner提取时长信息。
# 参考 https://montreal-forced-aligner.readthedocs.io/en/latest/getting_started.html
# 下载 Anaconda,安装,然后创建 Montreal Forced Aligner 的虚拟环境
conda config --add channels conda-forge
conda create -n aligner montreal-forced-aligner
conda activate aligner
# 测试是否成功创建并使用了虚拟环境
mfa --help
# 以日语为例,下载日语的分词器、词典、声学模型
# 模型一览: https://mfa-models.readthedocs.io/en/latest/index.html
mfa model download tokenizer japanese_mfa
mfa model download dictionary japanese_mfa
mfa model download acoustic japanese_mfa
# 对文本进行分词
python prepare_tokenize.py /path/to/dataset/metadata.json
mfa tokenize /path/to/dataset japanese_mfa /path/to/dataset --clean
# 对齐文本,生成 TextGrid
mfa align /path/to/dataset japanese_mfa japanese_mfa /path/to/dataset --clean
# 提取时长信息,将其注入原本的元数据文件,并清理临时文件
python post_align.py /path/to/dataset/metadata.json /path/to/dataset/metadata.json如果你想使用对齐方式,如果恰好输出格式为TextGrid,你可以尝试用post_align.py提取。
如果不是,你需要写一个脚本手动将对齐结果转换格式,格式示例
{
"hololive/kiryu_coco/ytb-live-2023-09-25.ogg": {
"phones": [
[0, 1, "19"],
[9, 18, "89"],
[18, 89, "0"],
[89, 106, "6"],
[106, 1024, "0"],
[1024, 8964, "4"]
],
"positive_prompt": ["source:hololive", "source:youtube", "character:桐生ココ", "year:2023"],
"negative_prompt": []
}
}from typing import TypedDict, NamedTuple
class PhoneInfo(NamedTuple):
start_time: float
end_time: float
phone: float
class AudioMetadata(TypedDict):
phones: PhoneInfo
positive_prompt: list[str]
negative_prompt: list[str]其中元数据不需要text键(但是为了分享,建议保留作为参考)
对齐前或者对齐后,你都可以把整个/path/to/dataset打包发布为数据集。
python list_phones_from_datasets.py /path/to/dataset/train.json -o /path/to/phones.txt
python list_tags_from_datasets.py /path/to/dataset/train.json -o /path/to/tags.txt
python init_checkpoint.py /path/to/ckpt /path/to/phones.txt -t /path/to/tags.txt由于直接训练时加载音频数据十分缓慢,不能发挥 GPU 训练的快的优势,所以这里我们采用:将训练数据预处理缓存在硬盘,在训练时直接加载而无需处理的方法,加快数据加载速度。
这样的数据集只适用于训练以下超参完全相同的检查点:
- tokenizer
- tag_label_encoder(但是如果是在原始标签后又新增标签的没事,比如说,``{"[UNK]": 0, "tag": 1, "new": 2}
可以兼容{"[UNK]": 0, "tag": 1}`的标签编码器) - sample_rate
- fft_length
- frame_length
- hop_length
- win_length
- num_mels
将通用数据集转化为快速训练数据集,包含几个步骤:
- 文本:用分词器编码为文本序列
- 正面、负面提示词:用标签编码器转换为提示词序列
- 音频:转换为梅尔频谱、音高和能量
考虑到兼容两种加载方式(加载原始通用数据集和快速训练数据集)的代码很难维护(见tkaimidi一直没维护),所以这里强制要求必须转换为快速训练数据集。你可以运行
python prepare_fast_dataset.py /path/to/dataset/metadata.json /path/to/ckpt /path/to/fast_dataset train:8 val:1 test:1数据集将会被分割为 10 份,其中 8 份、两个 1 份分别保存到/path/to/fast_dataset/train.npz、/path/to/fast_dataset/val.npz、/path/to/fast_dataset/test.npz
Tip
值得注意的是,由于音高和能量是全局归一化的,所以
# 正确的做法
python prepare_fast_dataset.py /path/to/dataset/metadata.json /path/to/fast_dataset train:9 val:1和
# 错误的示例
python split_dataset.py /path/to/dataset/metadata.json train.json:9 val.json:1
python prepare_fast_dataset.py /path/to/dataset/train.json /path/to/fast_dataset train:1
python prepare_fast_dataset.py /path/to/dataset/val.json /path/to/fast_dataset val:1的结果是不一样的:因为在第一个命令,train和val共用一个统计数据(pitch/energy的min/max值),而在第二个示例,训练集和验证集的pitch和energy分布不同,所以用这种方式测出的val_pitch_loss和val_energy_loss不具有参考价值。
但是val_postnet_loss和val_audio_loss不会受到影响,仍然有意义。这是因为 Mel 频谱的归一化方式是逐样本进行的,而不是基于全局统计数据。
所以如果实在要用这种方法,请忽略val_pitch_loss和val_energy_loss,只看val_postnet_loss和val_audio_loss。
python train_tktts.py <num_epochs> /path/to/ckpt -t /path/to/dataset/train.npz -v /path/to/dataset/val.npz将<num_epochs>替换为实际的你想训练的轮数
你可以运行
tensorboard --logdir /path/to/ckpt/logdir然后在浏览器内访问http://localhost:6006/查看模型训练过程和状态,以便调整超参数。
python list_phones_from_ckpt.py /path/to/ckpt # 列出所有音素
python list_tags_from_ckpt.py /path/to/ckpt # 列出所有标签
python generate.py /path/to/ckpt 10 output.wav mː b ɯ ŋ k a b e s oː k a ts ɨ t o ɕ -p character:夏色まつり -p source:youtube -p sex:female -p age:teenager -n sex:male # 用`-p`指定正面提示,`-n`指定负面提示- 请在命令行输入
python3 file.py --help获得帮助
文档是不可能写的,这辈子都不可能写的。经验表明,写了文档只会变成“代码一天一天改,文档一年不会动”的局面,反而误导人。
所以我真心推荐:有什么事直接看代码(代码的注释和函数的文档还是会更新的),或者复制代码问ai去吧(记得带上下文)。
欢迎提出问题、改进或贡献代码。如果有任何问题或建议,您可以在 GitHub 上提 Issues,或者直接通过电子邮件联系开发者。
如有任何问题或建议,请联系项目维护者 thiliapr。
- Email: thiliapr@tutanota.com
支持Blue Ribbon Online 言论自由运动!
你可以通过向其捐款以表示支持。
为什么要自由软件: GNU 宣言
你可以通过以下方式支持自由软件运动:
- 向非自由程序或在线敌服务说不,哪怕只有一次,也会帮助自由软件。不和其他人使用它们会帮助更大。进一步,如果你告诉人们这是在捍卫自己的自由,那么帮助就更显著了。
- 帮助 GNU 工程和自由软件运动
- 向 FSF 捐款

