项目地址:https://github.com/auspicious3000/autovc
论文链接:https://arxiv.org/abs/1905.05879
1 论文大致内容
非并行数据的语音风格迁移,且实现了零镜头语音转换(仅迁移音色
AUTOVC用一个非常简单的自动编码器框架解决了语音转换问题。该框架由三个模块组成,内容编码器Ec从语音中生成内容嵌入,说话人编码器Es从语音中生成说话人嵌入,解码器D从内容和说话人嵌入中合成语音。训练和实际转换时的输入不同,在实际转换过程中,将源语音X1送入内容编码器,提取内容信息。目标语音输入到说话人编码器以提供目标说话人信息。解码器根据源语音中的内容信息和目标语音中的说话人信息产生转换后的语音。
训练:在整篇论文中,将假设说话人编码器已经经过了预先训练来提取某种形式的说话人嵌入,所以通过训练我们指的是内容编码器和解码器的训练。model_bl.py就是用于提取说话人嵌入,这段代码定义了一个名为D_VECTOR的 PyTorch 模型,这个模型使用了 LSTM 层和线性层来实现嵌入的计算过程。
每个语音片段包含两种类型的信息:说话人嵌入(灰体)和内容嵌入(斜线)
(a)当瓶颈太宽时,内容嵌入会包含一些源说话人信息。(b)当瓶颈太窄时,会丢失内容信息,导致重建不完全。(c)当瓶颈正好时,可以实现完美重构,内容嵌入不包含源说话人信息。(d)在实际的转换过程中,输出不应包含源扬声器的信息,因此转换质量应与自重构时一样高。
信息瓶颈实现了解纠缠(说话人嵌入和说话内容)
2 实验部分
英文数据集(论文使用的数据集):VCTK
尝试使用的中文数据集:AIshell-2
2.1文件解释
make_spect.py:生成mel谱,npy文件
make_metadata.py:计算说话人嵌入,生成训练用数据,生成train.pkl
train.pkl:每一行代表一个说话人信息,每一个说话人信息的第一个元素为说话人,第二个元素为说话人嵌入的平均值,后面的为npy文件的路径
metadata.pkl:作者制作的测试集,每一行代表一个说话人信息,第一个元素是说话人id,第二个元素是说话人嵌入,第三个元素是此说话人的某句话的语音mel谱,生成方式见make_spect.py。作者的metadata.pkl中每个说话人的语音应该都是please call stella.
2.2 跑测试集
速度很慢,如果电脑只有单卡的话,需要改一行代码
将conversation.ipynb里的
g_checkpoint = torch.load('autovc.ckpt')
改为
g_checkpoint = torch.load('autovc.ckpt',map_location='cpu')
生成的音频会直接放在主目录下,需要的话自行修改。每一条语音的命名都是源说话人+目标说话人的形式
可以自己制作metadata.pkl,由于是零镜头转换,因此对目标说话人没有要求
不能直接用中文语音制作测试集,不然跑出来的语音根本没有可懂度
2.2 训练
如果你需要保存中间的训练模型,请在main.py添加以下代码到对应位置:
parser.add_argument('--model_save_dir', type=str, default='run/models')
parser.add_argument('--sample_step', type=int, default=1000)
parser.add_argument('--model_save_step', type=int, default=50)
在solver_encoder.py添加以下内容到对应位置:
self.sample_step = config.sample_step #MARK
self.model_save_step = config.model_save_step
self.model_save_dir = config.model_save_dir
def restore_model(self, resume_iters): #mark
print('Loading the trained models from step {}...'.format(resume_iters))
G_path = os.path.join(self.model_save_dir, '{}-G.ckpt'.format(resume_iters))
g_checkpoint = torch.load(G_path, map_location=lambda storage, loc: storage)
self.G.load_state_dict(g_checkpoint['model'])
self.g_optimizer.load_state_dict(g_checkpoint['optimizer'])
self.g_lr = self.g_optimizer.param_groups[0]['lr']
def print_optimizer(self, opt, name):#mark
print(opt)
print(name)
这一段添加到最后
if (i+1) % self.model_save_step == 0:
G_path = os.path.join(self.model_save_dir, '{}-G.ckpt'.format(i+1))
torch.save({'model': self.G.state_dict(),
'optimizer': self.g_optimizer.state_dict()}, G_path)
print('Saved model checkpoints into {}...'.format(self.model_save_dir))
这一段添加到solver_encoder.py的def train(self):中:
if self.resume_iters: #从保存的模型开始训练
print('Resuming ...')
start_iters = self.resume_iters
self.num_iters += self.resume_iters
self.restore_model(self.resume_iters)
self.print_optimizer(self.g_optimizer, 'G_optimizer')
并修改for i in range(self.num_iters):为:
for i in range(start_iters, self.num_iters):
这些代码是参考的作者的speechsplit,其他的步骤参考原作者就行。
2.3 用中文数据集训练
main.py里面的参数要调,或者调G = Generator(16,256,512,16).eval().to(device)
跑出来的模型损失降到0.0001为止
跑出来的模型测试时生成了一堆噪音,查找发现是损失函数的输入有问题
x_identic, x_identic_psnt, code_real = self.G(x_real, emb_org, emb_org)
g_loss_id = F.mse_loss(x_real, x_identic)
g_loss_id_psnt = F.mse_loss(x_real, x_identic_psnt)
尝试:删除x_identic_psnt和x_identic(2,1,128,80)的第二维,使其tensorsize相同
这种做法可以使损失收敛在0.0007左右,无法降到0.0001,但是相比之前,合成的音频至少能听出来是语音。
在github找到一些回复: