merlin主要源码解析(一)

run_merlin.py详解

run_merlin.py是Merlin框架的核心脚本,配套(.conf)的参数文件使用,其中包括了时延模型训练,声学模型训练和音频文件的生成等众多的功能,其参数文件中的也众多,没有去读相应的源码很难解析其内部的工作原理和训练机制,本博客也算对merlin的代码做一个记录,方便以后查阅。

1、先从1220行的'__main__':开始:

既然是配套参数文件使用,第一步自然是创建解析参数文件的对象:

cfg = configuration.cfg

这里调用了src/configuration文件夹下的configuration类,这里的(.cfg)看是指该文件夹下的__init__.py文件中的

cfg = configuration.configuration()

其实就是调用了configuration.py来创建一个类。
继续往下看:

logging
logger

这些都是创建运行过程中的日志文件,这里就不详解了
直接看1244行的:

cfg.configure(config_file)

configuration类调用configure()函数,传入参数文件路径,初始化configuration类。

2、cfg.configure(config_file)内部操作:

定位到configuration.py文件到82行self.initial_configuration(),这里可以初始化一些你自己设计的参数,这些参数在运行过程中不能改变。且在导入参数文件之前导入。
然后是86行的self.user_configuration(config_file) 导入参数文件,这里参数文件会按照所要完成的任务不同而有不同的参数个数,参数文件的格式为:

[参数类别]
参数名: 参数值

即:
[Outputs]
mgc: 60
dmgc: 180
bap: 5
既然用到user_configuration(config_File) 函数,那就查看下该函数,先看123行:

cfgparser = configparser.ConfigParser()
cfgparser.readfp(open(configFile))

这里是利用configparser包创建一了一个专门读取上述参数文件的对象,读取方式为cfgparser.get(参数类别, 参数名)
然后是171行的 user_options 是所有可选参数,以元组列表的形式表示。一些参数说明见下面列表

work_dir工作的文件的主目录
data_dir训练数据文件位置
inter_data_dir中间数据文件位置(包括处理后的标准化后的矩阵数据和去静音数据)
gen_dir生成数据文件位置
model_dir模型文件位置
inp_dim每帧输入的维度 默认425
out_dim每帧输入的维度 默认187
label_type前端生成标签特征的格式(state_align和phone_align)
question_file_name将标签特征文件转化为特征矩阵的字典文件位置
output_features生成的声学特征选择[‘mgc’, ‘lf0’, ‘vuv’, ‘bap’,‘stepw’,‘sp’,‘seglf0’,‘F0’,‘Gain’]

后续继续添加

该函数507行利用 for (variable, default, section, option) in user_options: 将参数文件中的参数初始化到configuration类中。user_configuration(config_File) 函数完成,主要任务是读取参数文件。
完成user_configuration(config_File) 函数继续看95行的**self.complete_configuration()**函数
该函数主要功能是:
1 补充各工具包的路径,主要工具包有(SPTK,STRAIGHT,WORLD,GLOTTHMM,GLOTTDNN,HMPD)
2 根据参数,导入完整的路径,如选择不同模型,导入不同的模型路径
3 根据选择的特征确定输入输出的特征维度。
里面还有一些细节需要自己去查看源码

这样cfg.configure(config_file)函数操作结束。回到run_merlin.py 继续查看 ‘main’:函数,然后你会发现一堆日志函数,只是方便运行时找错,真正运行的有用的只有一个**main_function(cfg)**函数,看了半天你会发现其实整个main函数主要作用就是读取参数。真正运行的在main_function(cfg)

3、 518行main_function(cfg)函数 , 这才是Merlin中真正的大boss

读取参数类中的所有文件路径的参数

519 file_paths = FilePaths(cfg)

将模型隐藏层传入数据格式为列表:[1024, 1024]

537 hidden_layer_size = cfg.hyper_params['hidden_layer_size']

获取保存了训练文件的文件名列表

541 file_id_list = read_file_list(cfg.file_id_scp)

这里比对训练集+验证集+测试集是否等于数据文件夹下的文件总数

551 assert cfg.train_file_number+cfg.valid_file_number+cfg.test_file_number == total_file_number

555~559行将一些训练需要的文件参数传给当前变量。

获取训练需要的字典特征文件列表,file_id_list文件名列表,cfg.in_dir_dict[feature_name] 具体特征数据所在的文件路径,cfg.file_extension_dict[feature_name] 文件的后缀名。

564 in_file_list_dict[feature_name] = prepare_file_path_list(file_id_list, cfg.in_dir_dict[feature_name], cfg.file_extension_dict[feature_name], False)

这里是综合所有音频特征数据的文件路径, nn_cmp_norm_file_list 是进行了归一化的,有一点要注意,这里的这些操作只是建立好各种文件的存放路径,并没有完成数据的处理。

566 nn_cmp_file_list         = file_paths.get_nn_cmp_file_list()
567 nn_cmp_norm_file_list    = file_paths.get_nn_cmp_norm_file_list()
570 norm_info_file = file_paths.norm_info_file

578行开始处理前端生成的文本特征文件。

578 label_normaliser = HTSLabelNormalisation(question_file_name=cfg.question_file_name, add_frame_features=cfg.add_frame_features, subphone_feats=cfg.subphone_feats)

这里创建了HTSLabelNormalisation类,方便后面数据生成,其中question_file_name 是将标签文件对应到矩阵的字典文件,这里的字典和文本特征都有对应的格式,add_frame_features 自己要将的额外特征,一般不作处理, subphone_feats 音素本身的状态特征,有多个维度选择。这里简单介绍下question 字典文件和标签文件:
question 字典文件:

QS  "L-Nasal"  {*^m-*,*^n-*}
CQS "Pos_C-Syl_in_C-Word(Fw)"  {@(\d+)^}

这里QS 代表数据类型,只能是0和1, “L-Nasal” 是特征的名字,{*^m-*,*^n-*} 是利用正则表达式如果文本特征里匹配到这个,向量中该处的值为1。同理CQS “Pos_C-Syl_in_C-Word(Fw)” {@(\d+)^} 代表同样的含义只是这里CQS 是实数的含义。

文本特征文件:

2500000 4000000 xx^m-k+a2=er2@a@/A:xx-2^2@/B:0.0000+1.0000@0.0000^1.0000^0.0000+1.0000#0.0000-1.0000-/C:xx_n^v#xx+3+1&/D:xx=3!3@0.0000-1.0000&/E:xx|9-xx@xx#3&xx!0.0000-1.0000=/F:d^9=5_3-1!

前两个是该音素开始结束的时间,后面是该因素完整的文本特征文件。像这个音素标签就能匹配到*^m-* ,这里的 * 是边间表示。
还有一个要注意的地方是:如果subphone_feats == ‘coarse_coding’ 会做一步:

self.cc_features = self.compute_coarse_coding_features(3)

这里是利用正态概率密度函数,创建了 [3,600] 的特征,这里为什么要这么操作要看论文:Context Adaptive Training with Factorized Decision Trees for HMM-Based Speech Synthesis
591~607行主要作用就是创建中间数据的文件路径和文件列表
610~612行判断要不提取音频特征。
619~653 生成数据

622 label_normaliser.perform_normalisation(in_label_align_file_list, binary_label_file_list, label_type=cfg.label_type)

这里in_label_align_file_list 文本特征文件所在的路径,binary_label_file_list 生成的矩阵保存的路径, label_type 生成方式有phone_align和state_align两种。这里要注意perform_normalisation 是一个继承函数,最终主要还是调用了frontend/label_normalistion.py 中的368行的load_labels_with_phone_alignment(self, file_name, dur_file_name) 函数
这里load_labels_with_phone_alignment(self, file_name, dur_file_name) 函数还是有必要详解下的:
这里dur_file_name 主要是表示每个音素时长的文件,可有可无,没有的化在标签中表现出来, file_name 标签文件路径。以下行数表示frontend/label_normalistion.py 中的行数:
375~378行, 这里及以下行数均代表frontend/label_normalistion.py 的行数

 if dur_file_name:
      io_funcs = BinaryIOCollection()
      dur_dim = 1 ## hard coded for now
      manual_dur_data = io_funcs.load_binary_file(dur_file_name, dur_dim)

如果有dur_file_name 利用BinaryIOCollection()类直接导入音素时长,这个时长是时延模型时的输出值,不是输入特征。
380~385行,表示有自己要加的特征,则加入特征维度上。
389~423行是对将输入的标签文件转化为特征矩阵,标签文件的格式就是如上所示。其中还有几行代码要说明下:

412 frame_number = int(end_time/50000) - int(start_time/50000)

这里表示每一帧长度为5毫秒,frame_number 为一个音素所对应的帧的个数

415 cc_feat_matrix = self.extract_coarse_coding_features_relative(frame_number)

这个就是上面所提到的利用正态概率密度函数,创建了 [3,600] 的特征矩阵后,每一帧从这个矩阵中选出3个点,来代表产生该帧状态的可能,从而作为特征向量中的3维。这个要注意下。

419 label_binary_vector = self.pattern_matching_binary(full_label)
422 label_continuous_vector = self.pattern_matching_continous_position(full_label)
423 label_vector = numpy.concatenate([label_binary_vector, label_continuous_vector], axis = 1)

前两句函数就是利用qusetion字典文件产生向量的函数(一个0/1,一个实数),第三句合并两个向量。
425~455 行就是处理subphone_featsadd_frame_features

 if self.add_frame_features:
      current_block_binary_array = numpy.zeros((frame_number, self.dict_size+self.frame_feature_size))
      for i in range(frame_number):
           current_block_binary_array[i, 0:self.dict_size] = label_vector

           if self.subphone_feats == 'minimal_phoneme':
            ## features which distinguish frame position in phoneme
                current_block_binary_array[i, self.dict_size] = float(i+1)/float(frame_number) # fraction through phone forwards
                current_block_binary_array[i, self.dict_size+1] = float(frame_number - i)/float(frame_number) # fraction through phone backwards
                current_block_binary_array[i, self.dict_size+2] = float(frame_number) # phone duration

           elif self.subphone_feats == 'coarse_coding':
                ## features which distinguish frame position in phoneme using three continous numerical features
                current_block_binary_array[i, self.dict_size+0] = cc_feat_matrix[i, 0]
                current_block_binary_array[i, self.dict_size+1] = cc_feat_matrix[i, 1]
                current_block_binary_array[i, self.dict_size+2] = cc_feat_matrix[i, 2]
                current_block_binary_array[i, self.dict_size+3] = float(frame_number)

           elif self.subphone_feats == 'none':
                pass

           else:
                sys.exit('unknown subphone_feats type')

     label_feature_matrix[label_feature_index:label_feature_index+frame_number,] = current_block_binary_array
     label_feature_index = label_feature_index + frame_number
           
elif self.subphone_feats == 'none':
     current_block_binary_array = label_vector
     label_feature_matrix[label_feature_index:label_feature_index+1,] = current_block_binary_array
     label_feature_index = label_feature_index + 1

代码比较简单这里就不解释了。
然后就是461行返回label_feature_matrix 就是每句话对应的二维特征矩阵,矩阵的每行代表每个音素的特征,merlin里是将这个矩阵保存到文件中,形成中间的数据文件
回到run_merlin.py 的624~636行,判断有没有其他输入特征要加

然后是638行,删除静音帧
之所以要删除,因为
1 一般静音帧数在生成音频时是固定的,一般不用时延模型预测
2 静音帧数的个数是非静音帧数的4倍左右,容易影响归一化。

remover = SilenceRemover(n_cmp = lab_dim, silence_pattern = cfg.silence_pattern, label_type=cfg.label_type, remove_frame_features = cfg.add_frame_features, subphone_feats = cfg.subphone_feats)

SilenceRemover() 是一个类,传入参数lab_dim 输入文本特征维度,silence_pattern 的静音的格式:[’*-sil+*’]label_type 对齐方式,

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值