基于tacotron2的galgame角色语音合成+galgame解包 保姆级教程(千恋万花版)

您所在的位置:网站首页 二次元的galgame游戏 基于tacotron2的galgame角色语音合成+galgame解包 保姆级教程(千恋万花版)

基于tacotron2的galgame角色语音合成+galgame解包 保姆级教程(千恋万花版)

#基于tacotron2的galgame角色语音合成+galgame解包 保姆级教程(千恋万花版)| 来源: 网络整理| 查看: 265

(注:本专栏只讨论相关技术,不涉及任何其他目的,如侵删)

(生成数据集的代码有更新!!! 2023.3.14)前言

前段时间在b站上看到了基于TTS模型合成喜爱的二次元语音的一系列教程,觉得非常有意思,也想利用寒假复现一下。但当时忙着复习期末考试,一直拖到了过年这几天才有时间好好研究了一番。

我复现的方式是基于最开始的tacotron2模型,使用NVIDIA官方的预训练模型进行微调,训练语料来自于《千恋*万花》中的芳乃,使用Google Colab + Kaggle的GPU进行训练。因此本教程适合那些有基本的Python编程和深度学习知识、同样想要复现的xdm。

(我本人也不是科班出身学人工智能的,Python和深度学习都是半路出家,所以这里不会涉及过于复杂的实现细节。本教程尽量一步步详细地描述实现过程,希望大家看过之后能大概知道整个流程到底是怎么样的)

tacotron2模型基本介绍

本教程采用原up最开始的思路,具体可以看下面这个视频。

关于tacotron2模型的介绍,感兴趣的同学可以看下面这个视频(不是必要的,下面这个视频有差不多一个小时),讲得比较清楚。

由于我们是复现,不要求对模型具体的结构有过于深入的了解,所以我们只需要大概知道,tacotron2模型实现的效果是我们给定一段文本输入,模型会输出相应的Mel谱,通过后续的WaveGlow声码器得到最终的声音。

提取数据之前,我们在原up的github链接https://github.com/CjangCjengh/tacotron2-japanese中,下载相应的代码压缩包,解压后得到tacotron2-japanese-master文件夹。

文本

有了模型,接下来是最重要的部分:获取数据。这里获取数据的方式借鉴了下面的两个教程:

下面我会一步步展示如何提取文本和生成filelist文件,后文有我写的一个简单的Python脚本,可以生成最终的filelist。

我这次提取的是《千恋*万花》中的芳乃语音。游戏文件夹中有data.xp3(不是data1080.xp3)和voice.xp3两个文件,分别存储了角色台词和对应语音。

角色台词角色语音使用GARBro提取

进入GARBro软件,路径设置为游戏文件夹,界面如下:

打开data.xp3,界面如下:

再打开scn文件夹:

这些scn后缀的文件就是我们需要的文本文件。从头到尾全部选中,右键,提取:(注意不要只提取芳乃开头的,因为在共同线和其他角色线中也有不少芳乃语音)

2023.2.19更新:

目前来看,上面的提取方法应该适用于所有后缀是.scn的文本文件的提取,基本上用GARBro提取之后,使用Freemote都能转换成前文所述的json文件。由于我电脑上现有的galgame不多,所以只试了9nine。根据下面这个视频介绍的GARBro方法,正确打开.xp3文件需要加密,加密方式应该选择游戏名称对应的罗马音。但我之前提取千恋万花的时候选择的是“没有加密”,也成功提取了,所以具体需不需要加密因游戏而异。

GARBro内置的加密方案只涉及部分游戏,经过我的观察,9nine的前三部(九九九、天天天、春之风/春春春)可以用同一个加密方案成功提取:(如果选错加密方案,退至上一级文件夹,点击文件夹栏右侧的刷新,重新进入xp3文件时可以再次选择加密方案)

(注:选错加密方案之后同样可以打开.xp3文件、完成scn的提取,但无法按照下文使用Freemote将scn转换成json文件,会提示Your PSB is encrypted。这里建议大家无脑使用KrKrExtract!!!)

使用KrkrExtract提取(2023.3.14更新)

使用GARBro时,有些加密的游戏无法正常提取,同时提取出来可能也会有乱码,总是就是各种各样的问题。我后面尝试了KrKrExtract,确实好用,具体做法如下。

首先,在https://github.com/xmoezzz/KrkrExtract/releases下载下面三个文件(使用Chrome浏览器会报病毒,建议用IE,下载完的杀毒软件警告也不要管):

然后,把两个后缀是dll的文件复制到游戏文件夹,即.exe文件所在的文件夹:

把日文原版游戏对应的exe文件拖到KrkrExtract.Lite.exe上,注意一定是日文原版游戏,汉化版的不行:

(如果打不开,可能是原版的游戏exe文件出了问题,即无法正常启动日文原版游戏)

然后会出现下面这个界面:

把对应的xp3文件拖到上面这个对话框上,自动开始提取:

提取完的文件在 ./KrkrExtract_Output/data 文件夹中,scn文件夹中就是我们想要的文件:

转换为json文件

提取完后,使用Freemote软件转换为json文件,具体操作是选中文件后,拖至FreeMoteToolkit文件夹中的PsbDecompile.exe中,会显示“用xxx.exe”打开:

这样,每个scn文件会生成相应的json文件和resx.json后缀的文件。

打开json文件和resx.json文件(可以用记事本打开),我们会发现有用的信息全在json文件中:

json文件resx.json文件

然而,json文件包含众多信息,以及多级字典嵌套,不方便直接生成我们想要的txt文件。在Python中,使用json库读入json文件,得到一个字典。经过我的观察,文本信息在该字典的“scene”元素下的某一个子字典,该字典中有“texts”键,对应的值为一个列表,存储相应的文本信息:

在文件中搜索“voice”,可以找到我们想要的台词文本和相应的音频文件:

生成生成所需的train.txt和val.txt

由于没有找到生成filelist文件的教学,我写了一个简单的py脚本generate_txt.py,可以处理文件夹中所有的json文件,提取特定角色的语音。(下面的代码2023.3.14有更新,之前需要把该py脚本移到语音合成模型所在文件夹,现在不需要了,只用把该文件夹路径加入到系统路径中,即可顺利导入text_to_sequence函数,该函数实际上是在text文件夹的__init__.py中;此外,下面的代码中还加入了speaker_id输入,支持后续VITS模型的Multiple Speakers模式)

完整代码如下:

其中,核心的filelist_build函数有几个参数,具体含义如下:

json_path:json文件所在文件夹

character:需要提取语音的角色,输入是日文汉字,因此需要在json文件中找到对应角色的汉字名,比如香坂春风是“春風”不是“春风”:

speaker_id:有多个speakers时对应speaker的id,默认为-1(即单个speaker),对应后续VITS模型的Multiple speakers模式

json_format:json文件的格式,一般是ks.json或者txt.json,默认为ks.json

根据下面这篇专栏,scn文件一般的扩展名是ks.scn或者txt.scn(千恋万花中是ks.scn,而9nine中是txt.scn,两种文件经Freemote分别转换成ks.json和txt.json)。

collapse_LF:是否清除文本中自带的换行符,默认为True(清除)。我发现在原文本中,有些句子内会自带换行符“\\n”:

(我听了几个语音片段,感觉换行符的加入并没有造成太多语义上的变化,而原作者的cleaner中似乎没有清除行内换行符\\n,而\\n作为非有效文本,不知道是否影响模型的学习,因此我这里将collapse_LF作为可选参数。)

folder_path:在vits目录下存放voice的文件夹,默认是'wav',最后txt文件中的记录格式为“folder_path/xxx.wav”

最后,需要指定filelists文件夹的父文件夹路径,一般设在语音模型对应文件夹下:

代码的最后会print提取的总样本数,如果total samples = 0,可以先检查一下名字或者路径是否输入正确。因为我只试验了部分游戏,难免有考虑不周的地方,有其他问题欢迎私信我或者评论区留言。

在后续训练中,我发现数据集中可能混杂着一些异常样本,在经过cleaner之后得到空字符串,长度为0,导致后续的pack_padded_sequence函数报错。因此在生成数据集文件时,我们需要去除这些异常样本。

这里我采用的是一种比较笨的方法,即对每个样本做一次cleaner,保留长度不为0的样本。(其实可以直接生成cleaner之后的txt,但我懒得改了)

(注:千恋万花没有单独划分h语音,我目前没有特别好的办法自动去除数据集中的h语音,可以尝试去除所有包含消音字符“●”的文本)

由此,我们得到了训练集和测试集,分别对应transcript_train.txt和transcript_test.txt。

语音提取

提取完文本,接下来是提取语音。在GARbro中打开voice.xp3文件(KrkrExtract提取过程同上,音频文件存在 ./KrkrExtract_Output/voice 文件夹中):

前三个字母代表角色(akh我也不知道是谁),后面的一串数字是音频的章节和序号。我们找到“yos”开头的音频,选中后提取出来:

这时候我们提取出来的是ogg文件,需要转换成wav文件。下载格式工厂,这是一个免费的格式转换软件,界面如下:

选择转换成wav文件,在输出配置中设置采样率为22050Hz,单声道:

随后选中所有ogg文件,批量转换成wav文件,完成音频导出:

模型训练(Colab)

提取完所有的数据,我们正式开始训练模型,这里可以直接使用前述教程中原up提供的colab链接,但为了方便调参,我修改了一下相应的colab笔记本,具体链接为https://colab.research.google.com/drive/1MvKoW9h1ul1WTn1WIubAqHMjpXajFQ6a#scrollTo=5mBxgMRL23_x

我稍微修改了原代码中create_hparams()函数的定义,便于之后修改超参数。

在colab中,我直接clone原up的git,随后我们需要上传相应的数据集文件至filelists文件夹,colab不支持重名文件替代,所以我们需要先删除原来的txt:

(如果提示找不到原来的txt文件,可以刷新一下文件目录)

同时注意后续create_hparams中self.training_files和validation_files的路径:

随后我们需要将自己的数据集以压缩包形式上传到Google drive,最好命名为wav.zip,随后解压:

在第一次训练时,我们需要下载官方提供的预训练模型,然后把warm_start设置为1,这样会重置记录的epoch数。同时我们还需要将checkpoint保存路径设置为drive云盘,这样如果训练中断了,我们的模型不会消失:

在训练开始前,我们需要在create_hparams中修改总迭代epochs和多少次迭代保存一次:

(我个人的经验,100个epoch大概要跑9h左右)

随后可以开始训练:

为了防止colab连接中断,我们可以在睡觉前操作一下:https://blog.csdn.net/Thebest_jack/article/details/124565741

这样睡醒后就可以看到训练好的模型了!!

模型训练(Kaggle)

Colab只能训几个小时就不能用了,而Kaggle提供了35h的GPU使用时间,所以我也想利用Kaggle完成训练,但是在网上好像没有相关的教程,我就自己摸索了一下。

在训练之前,我们需要将我们的模型和数据集上传至Kaggle的Dataset。我们打开Create a New Dataset,建立两个数据集:checkpoint和yoshino,其中checkpoint存放我们现有的模型,yoshino中有两个子文件夹,存放wav和filelists:

注意,当上传文件夹时,Kaggle会自动解压,因此wav.zip中可以包含filelists和wav,方便后续操作:

上传成功后界面如下:

Kaggle的ipynb链接为https://www.kaggle.com/code/littleweakweak/tacotron2,下面简单做一些解释。

在Kaggle的环境中,不能直接安装pyopenjtalk,搜索之后得知需要先uninstall cmake:

Kaggle的ipynb和Colab大同小异,但是需要针对Kaggle的文件路径做一些调整。(根据你自己的dataset名字进行修改)

随后我们就可以开始训练了。对于防止Kaggle中断,网上也有类似的方法,但我尝试过后好像不行。实际上Kaggle提供了更简单高效的方式:save version

具体操作见https://www.cnblogs.com/zgqcn/p/14160093.html,设置完save version后,我们就可以关机睡觉了,等它训练完成后便可以查看结果。由于save version不会中途保存,我们设置迭代epoch时需要估计一下训练时长(100个epoch大概9h),以免超过使用时间限制。

提醒!!!Kaggle每次继续训练时,记得更新checkpoint数据集中的模型,否则还会从上次的模型开始训练,我就是疏忽了这一点白训了十个小时)

训练心得

写到这里,模型训练的过程大概描述完了,下面是个人的一些心得:

训练过程中可以逐步调低学习率,一开始学习率1e-3,等训练一段时间之后可以调低至1e-4、1e-5等。时不时观察一下train loss和val loss的变化趋势,判断模型的状态

我这次大概训练了不到30h,25000次迭代,最终train loss大概0.21,val loss大概0.27。实际上最后几个小时,train loss一直慢慢下降,val loss一直在0.27附近波动,怀疑出现过拟合,最后学习率为1e-5,weight_decay为1e-5,不打算继续训练了。由于之前没有训练神经网络的经验,如果有大佬有比较好的方案,欢迎评论区指出)

最后模型可以发出比较清晰的日语,但是音调有些奇怪;如果用假名表示汉语拼音,某些句子发音还不错,如果每个音发的比较短,可以再加一个音,如:びぇえざいじぇえりいふぁあでん(鳖在这理发店)

在inference时,如果输出mel_outputs_postnet.shape,会发现每次inference的输出都不同。查阅之后得知,这是因为模型的Prenet中,dropout层的training = True,所以model.eval()对其不起作用,而其他dropout层的training = self.training。我在训练完之后才知道这个事情,而训练过程中validate时同样激活了Prenet的dropout层。而当我直接修改prenet时,模型输出异常。目前不知道在训练前调整prenet之后结果如何,感兴趣的xdm可以尝试之后告诉我。

结语

到这里整个模型的训练过程就讲完了。经过这几天的努力,最终实现了一个还算不错的结果。这真的是一个很有意思的项目,非常敬佩原up的创意和后来各位up的改进,由于我是第一次复现项目,也是第一次训练TTS模型,知识有限,还有很多的不理解之处,发出这个简陋的教程也是希望和大家共同学习,希望之后能继续尝试一些有意思的项目。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3