SyntaSpeech是2022年5月份浙大提出的,结合图神经网络在TTS声学模型进行的改进,只提供了Ubuntu的版本。
但是手上正好有一台windows,所以看看Windows下面能不能跑
其中有几个需要修改的点(因为是改完了才写的blog,可能有一些小的遗漏)。
1、安装过程中所有的bash都没法用,不过大部分都可以用conda安装,就不赘述了
2、Windows默认编码是gbk,Linux是utf8,这在不同环境下拷数据容易出现问题,在所有open函数的的参数中加入encoding=utf-8
3、在utils/os_utils.py中,使用了大量的bash,Windows肯定是执行不了,小改一下(windows没有很好的ln代替的命令,所以就用复制代替了,我把改完的代码贴上把)
import os
import subprocess
import shutil
def link_file(from_file, to_file):
# subprocess.check_call(
# f'ln -s "`realpath --relative-to="{os.path.dirname(to_file)}" "{from_file}"`" "{to_file}"', shell=True)
shutil.copy(from_file, to_file)
def move_file(from_file, to_file):
# subprocess.check_call(f'mv "{from_file}" "{to_file}"', shell=True)
shutil.move(from_file, to_file)
def copy_file(from_file, to_file):
# subprocess.check_call(f'cp -r "{from_file}" "{to_file}"', shell=True)
shutil.copy(from_file, to_file)
def remove_file(*fns):
# For linux
# for f in fns:
# subprocess.check_call(f'rm -rf "{f}"', shell=True)
for f in fns:
if os.path.exists(f):
if os.path.isdir(f):
shutil.rmtree(f)
elif os.path.isfile(f):
os.remove(f)
else:
pass
else:
pass
4、mfa_usr/run_mfa_train_align.sh没法使用,在处理数据的三步(process,align,binarizer)的第二步中有用到,我的做法是把调用部分注释掉,自己手动完成。mfa_usr/run_mfa_train_align.sh脚本逻辑也很简单,就是一个mfa的train,然后rm掉tmp文件,最后把分文件夹(为了更好的使用多核性能)的TextGrid文件合并到一个文件夹中
5、Windows下的多进程使用的是spwan,而Linux下是fork,fork的时候python的global变量就可以传过去,但是windows下不行,所以处理数据不要用多进程,一定要用process_data_single_processing,不然hparams传不过去
6、其实上面这些都不用特意写一个blog,主要是第五点,SyntaSpeech写配置文件用的一种链式的规则,就是比如有个a.yaml,在a.yaml中可以写我基于b.yaml,一环套一环,这是前提。当时我在Ubuntu跑起来完全没问题,但是windows下面越跑内存越大,显卡不启动,直到32G内存满了也不启动,我启用debug模式开始跟踪,开始我以为是open导致的,把整个二进制的数据集(它自己写的,很有意思)都进来了,经过测试,open不会占用大量内存,跟进线程发现会一直load数据,不会组batch,开始怀疑是不是batch size太大了,但是Linux下也是这个配置,完全没有问题。跟踪一下batch size,设置的值是100000,异常恐怖的值,但是在配置文件明明有写8,于是开始跟踪set_hparams过程,发现一共读了8次config文件,前3次没有max_sentences(也就是batch size,这里面写的叫max_sentences)值,第四次是100000,第五、六次是8,第七次又是100000,第四次应该是基础config,第五次给覆盖掉了,可以为什么第七次又覆盖回来了呢?其实到这已经发现问题,如果只要跑的话可以都写在一个config就避免了这个问题。
但是我们打破沙锅问到底,看看到底问题在哪,经过跟踪,发现为了防止重复读config,作者用loaded_config来记录读过的config,而且为了使用相对路径,还专门写了一下
for c in hparams_['base_config']:
if c.startswith('.'):
c = f'{os.path.dirname(config_fn)}\\{c}'
c = os.path.normpath(c)
if c not in loaded_config:
override_config(ret_hparams, load_config(c))
问题就出来Windows下的/和\\,python可以兼容使用/或者\\,我们写时候习惯写/,但是os.path.normpath会把路径改写成\\,于是一个文件就有了不同路径,比如c:\\egs/1.txt,c:\\egs\\1.txt,这也是为什么之前读过的基础config会覆盖我们写的config,知道问题就很好处理了,在load_config这个函数传进来时候
def load_config(config_fn):
config_fn = config_fn.replace('/','\\')
简单处理一下,完全没问题!
真的是找bug半天,改bug一行
最后还有一个小问题,windows跑跑的给我们显卡降频了。。。虽然我也不知道为什么?可能是累了?推荐个软件MSIAfterburner,可以实时查看显卡频率,也可以超频用。3090正常1400以上,一般能到1800。最后重启电脑,显卡恢复正常频率。
windows比Linux跑神经网络稍微慢一点,Linux 2.3 steps/s,Windows 2.05 steps/s,可能也跟windows要维持我正常使用有关系。
总结就是,能不用windows就不用windows,真的很折腾。。。