AUTOVC 代码解析 —— make_metadata.py
简介
本项目一个基于 AUTOVC 模型的语音转换项目,它是使用 PyTorch 实现的(项目地址)。
AUTOVC 遵循自动编码器框架,只对自动编码器损耗进行训练,但它引入了精心调整的降维和时间下采样来约束信息流,这个简单的方案带来了显著的性能提高。(详情请参阅 AUTOVC 的详细介绍)。
由于 AUTOVC 项目较大,代码较多。为了方便学习与整理,将按照工程文件的结构依次介绍。
本文将介绍项目中的 make_metadata.py 文件:为训练生成说话人嵌入和元数据。
函数解析
main
该函数的作用是: 为训练生成说话人嵌入和元数据
输入参数: 无
输出参数: 无
代码详解:
# 调用 model_bl 中的 D_VECTOR 创建说话人嵌入网络模型,输入数据大小为 80 ,LSTM 隐藏单元数为 768 , 输出数据大小为 256 ;
# 使用 model.eval 方法,将模块设置为评估模式。该设置仅对模型中某些模块有影响,例如 Dropout , BatchNorm 等;
# 使用 model.cuda 方法,移动所有的模型参数并缓存到 GPU ;
# 需注意:这也会生成相关的参数并缓冲不同的对象。因此,如果模块将在GPU上运行,那么它应该在构建优化器之前被调用。
C = D_VECTOR(dim_input=80, dim_cell=768, dim_emb=256).eval().cuda()
# 加载预训练的说话人编码器模型
c_checkpoint = torch.load('3000000-BL.ckpt')
# 创建一个有序字典
new_state_dict = OrderedDict()
# 遍历加载的预训练说话人编码模型对应部分
for key, val in c_checkpoint['model_b'].items():
# 截取 key 值,使命名一致
new_key = key[7:]
# 添加训练好的 Tensor 至有序字典中
new_state_dict[new_key] = val
# 将顺序字典中的 Tensor 加载到上文创建的说话人编码模型中
C.load_state_dict(new_state_dict)
# 为语句数量赋值 10
num_uttrs = 10
# 为修建长度赋值 128
len_crop = 128
# 提供含有梅尔谱图的文件目录
rootDir = './spmel'
# 获取含有梅尔谱图文件目录的子目录列表
dirName, subdirList, _ = next(os.walk(rootDir))
# 打印含有梅尔谱图文件目录名
print('Found directory: %s' % dirName)
# 创建说话人列表
speakers = []
# 遍历含有梅尔谱图文件目录的子目录列表
for speaker in sorted(subdirList):
# 打印当前说话人
print('Processing speaker: %s' % speaker)
# 创建说话列表
utterances = []
# 将当前说话人插入说话列表
utterances.append(speaker)
# 查找当前说话人目录下的所有文件名
_, _, fileList = next(os.walk(os.path.join(dirName,speaker)))
# 构建说话人嵌入
# 当存在的语句数量少于设定的语句数量时警告
assert len(fileList) >= num_uttrs
# 打乱顺序:从 [0, 10) 中随机抽取 10 个数字组成数组,要求数字不能重复
idx_uttrs = np.random.choice(len(fileList), size=num_uttrs, replace=False)
embs = []
# 循环次数为设定的语句数量
for i in range(num_uttrs):
# 按上文生成的随机顺序加载该说话人的语句(梅尔谱图数据)
tmp = np.load(os.path.join(dirName, speaker, fileList[idx_uttrs[i]]))
# 源代码有问题,确少维度参数,会报错
# 这里是将选择的语句编号删除,留下未选择的语句编号
# candidates = np.delete(np.arange(len(fileList)), idx_uttrs)
candidates = np.delete(np.arange(len(fileList)), idx_uttrs[i], None)
# 如果当前的语句太短,选择另一个语句
while tmp.shape[0] < len_crop:
# 从剩下的语句编号中随机抽取一个
idx_alt = np.random.choice(candidates)
# 将选择的语句加载,覆盖 tmp
tmp = np.load(os.path.join(dirName, speaker, fileList[idx_alt]))
# 源代码有误,缺少维度参数,处理方式与上文一致
# 将选择的语句编号从剩余语句编号数组中删除
# candidates = np.delete(candidates, np.argwhere(candidates==idx_alt))
candidates = np.delete(candidates, np.argwhere(candidates == idx_alt), None)
# 取左边界,建议第二个参数写成 high=tmp.shape[0]-len_crop
left = np.random.randint(0, tmp.shape[0]-len_crop)
# 新增一个维度(据说是要求以批量的形式输入),从左边界截取设定的修建长度,保留其他维度全部数据
# 使用上述截取出的 numpy 数组创建 Tensor,并将参数移至 GPU
melsp = torch.from_numpy(tmp[np.newaxis, left:left+len_crop, :]).cuda()
# 将得到的 Tensor 作为输入,使用先前创建的说话人嵌入模型计算说话人编码
emb = C(melsp)
# 将计算的说话人编码从当前计算图分离,并压缩掉维数为 1 的维度
# 接着将 GPU Tensor 转换为 CPU Tensor ,再转换为 numpy 数组
# 最后,将得到的说话人编码添加进说话人编码列表
embs.append(emb.detach().squeeze().cpu().numpy())
# 将说话人编码列表中的说话人编码取平均值,得到该说话人的平均说话人编码,并添加进说话列表中
utterances.append(np.mean(embs, axis=0))
# 创建文件列表
# 按顺序遍历该说话人语句文件(梅尔频谱数据)
for fileName in sorted(fileList):
# 将该说话人的语句文件名添加进说话列表中
utterances.append(os.path.join(speaker,fileName))
# 在说话人列表中添加该说话人的说话列表(说话信息:人名,说话人编码,说话文件)
speakers.append(utterances)
# 创建训练文件,以二进制打开文件,并写入
with open(os.path.join(rootDir, 'train.pkl'), 'wb') as handle:
# Python中的Pickle模块实现了基本的数据序列与反序列化
# dump()方法能序列化对象,将对象保存到文件中去
pickle.dump(speakers, handle)