语音转换之CycleGan-VC2:原理与实战

news2025/1/12 13:40:46

非平行语音转换CycleGAN

之前学习了传统统计学习里的经典的语音转换模型GMM。随着深度学习的发展,出现了更好的语音转换方法,今天学习较为经典的CycleGan。

平行语音转换一般流程

典型代表就是基于GMM的语音转换。平行数据就是说源语音和目标语音一一对应,这里对应就是指每句话的内容必须一样。非平行数据就是说话内容无需完全一样。

所以,平行语音转换中最关键的就是特征配准(特征对齐),对齐效果好坏直接影响最终的转换效果。平行语音一般用DTW算法基于最小距离原则进行对齐。
在这里插入图片描述

特征映射模型总结

根据输入数据的类型,可将特征映射模型分为基于平行和非平行数据的模型。

基于平行数据

最早期使用基于规则的方法,后来引入概率统计模型,典型的如GMM,到现在深度学习的方法,主要基于神经网络。

  • DNN:帧到帧
    在这里插入图片描述

  • RNN:帧到帧
    在这里插入图片描述

  • seq-seq
    在这里插入图片描述

基于非平行数据

  • 通过切分、聚类,来设法构造并行数据

  • 使用ASR系统,辅助VC。
    提取与说话人无关的信息,构造这些信息与目标说话人之间的关联,在文本层级上进行对齐。
    在这里插入图片描述

  • 使用变分自编码VAE与竞争网络
    Encoder可以看作特征降维。
    对齐:让源特征和目标特征经过Encoder后,在浅变量概率分布层级上进行对齐。
    在这里插入图片描述

训练方法:在这里插入图片描述

可以看到,这些非平行数据的VC算法中,还是离不开“对齐”操作。要么在音频特征切分后,在特征维度上进行对齐;要么利用ASR系统,在文本层级对齐;要么利用深度神经网络进行编码,在编码的潜变量或分布上进行对齐。

基于CycleGan的VC,真正意义上摆脱了“对齐”操作。

CycleGan最早用于图片风格转换。如下图CycleGAN 斑马与马转换。
在这里插入图片描述

2019年,有人将CycleGan用到语音转换,并作了一系列的工作。下面这篇是最经典的,第二代CycleGan-VC2,目前被引了两百多次了。
在这里插入图片描述

CycleGan-VC

Gan网络的原理,可以看这篇文章:GAN

下图是CycleGan结构图:
既可以A 到B,也可以B到A。
在这里插入图片描述

和上面图表达的类似,可以对比着看。
在这里插入图片描述

目标函数:
在这里插入图片描述

损失函数

1.对抗损失 Adversial loss
用来衡量如何区分由x生成的y和真实y
在这里插入图片描述
注意这个对抗损失是最大最小化,即训练G时,希望能骗过D,希望损失最小化;训练D时,希望D很厉害能识别生成的假数据,最大化损失。

2.循环不变性损失Cycle-consistency loss
用来保证GX-Y和GY-X得到(X,Y)对的文本内容相同。
在这里插入图片描述
即循环一圈后生成的和原来的越相似越好。

CycleGan-VC2的改进,多了如下。

3.映射一致性损失Identity-mapping loss
Gx-y的网络应该输入x,希望生成y,现在直接就输入y,希望它能不变的输出y,为了保证模型只转换风格,不转换内容。
在这里插入图片描述

上面几个组成总目标函数:

在这里插入图片描述

4.对抗损失2 Adversial loss2
在这里插入图片描述

从下图可以看到,给生成的x又加了个判别器Dx。按理说刚才说的损失循环不变性损失Cycle-consistency loss,也就是下图的(a)已经能保证说话内容不变了,为什么还有这个损失函数?
因为Cycle-consistency loss中的L1范式的度量仍然会导致过平滑,为了减轻这种负面影响才提出这个损失函数。
在这里插入图片描述
实际使用中不需要重新建一个Dx,用已有的Dx就行。

总结上面4个损失函数:
1是为了输出的语音是一句目标语音的人的声音,而不是其他声音,至于说的内容它无法约束。
2和3损失就是为了保证说的内容和原说话人一致,如源说话人说你好,转换后也得内容是你好。
4是为了缓解过平滑,让循环一圈以后的声音仍和原声更接近。

为了损失函数更直观,把上面的这几个损失函数进行了规整,统一成min的,loss最小。
1.对抗损失 Adversial loss

在这里插入图片描述

2.循环不变性损失Cycle-consistency loss
在这里插入图片描述

3.映射一致性损失Identity-mapping loss
在这里插入图片描述

4.对抗损失2 Adversial loss2
在这里插入图片描述

网路结构

生成器G: 输入source的MCEP特征,输出改变音色的MCEP特征。
生成器使用1DCNN,以捕获总体特征之间的关系,同时保留时间结构。同时使用了下采样层,残差层和上采样层,以及Instance Normalization。
这里用到了门控CNN,模拟lstm的是否遗忘门,或者说判断权重的思想。再做一个和CNN卷积一样参数的filter, 取值0-1,判断这个序列的特征哪些应该被关注,哪些应该被忽略。在门控CNN中,门控线性单元(GLUs)被用作一个激活函数,GLU是一个数据驱动的激活函数,并且门控机制允许根据先前的层状态选择性地传播信息。

在这里插入图片描述

鉴别器D: 输入音频的MCEP等特征,进行二分类,输出是否经过转换
使用2D CNN鉴别器来基于2D频谱纹理鉴别数据。使用具有完全连接层作为最后一层的FullGAN,以根据整体输入结构来区分数据。

在这里插入图片描述

注意里面结构PatchGAN:
一般D输出就是一维的,PatchGAN就是输出多维,就还是1-x,取平均。
在这里插入图片描述

代码实现

在GitHub上就有官方代码:CycleGAN-VC2
里面data文件夹有两个人的语音,可以分别新建train和test文件夹,把数据分来。

一般工程代码主要包含下面部分:

在这里插入图片描述

相关参数

hparams.py:超参数

class hparams():
    def __init__(self):
        
        # 数据缓存
        self.train_dir_A = "data/S0913/train"
        self.train_dir_B = "data/gaoxiaosong/train"
        self.catch_A = "catch/train_A"
        self.catch_B = "catch/train_B"
        self.n_frames =128
        
        # 特征提取相关
        self.fs = 16000         # 采样率 
        self.frame_period = 5.0 # 帧移
        self.coded_dim = 36     # mcep 特征维度 
        
        # 训练相关参数
        self.g_lr = 2e-4
        self.d_lr = 1e-4
        self.train_steps = 2e4
        
        # learing rate 衰减
        self.start_decay = 1e4
        self.decay_G = self.g_lr/2e5
        self.decay_D = self.d_lr/2e5
        
        # 丢失 identity_loss
        self.step_drop_identity = 1e4
        
        # lamda
        self.identity_loss_lambda = 10
        self.cycle_loss_lambda = 5
        
        #每个 step_log 进行一次保存
        self.step_save = 2000
        
        # 模型保存路径
        self.path_save = 'save'

数据预处理

提取语音常见特征:F0、MCEP(这里编码成coded-sp)、ap

import numpy as np
import pyworld
import glob
from hparams import hparams
import librosa
import os

# 用world声码器特征提取
def feature_world(wav,para):
    fs = para.fs
    wav = wav.astype(np.float64)
    f0, timeaxis = pyworld.harvest(wav, fs, frame_period=para.frame_period, f0_floor=71.0, f0_ceil=800.0)
    
    sp = pyworld.cheaptrick(wav, f0, timeaxis, fs)
    ap = pyworld.d4c(wav, f0, timeaxis, fs)
    coded_sp =pyworld.code_spectral_envelope(sp, fs, para.coded_dim)
    return f0,timeaxis,sp,ap,coded_sp
   
# 信号正则 
def wav_normlize(wav):
    max_ = np.max(wav)
    min_ = np.min(wav)
    wav_norm = wav*(2/(max_ - min_)) - (max_+min_)/(max_-min_)
    return wav_norm
    
# 处理文件夹中的每条语音
def processing_wavs(file_wavs,para):
    
    f0s = []
    coded_sps = []
    for file in file_wavs:
        print("processing %s"%(file))
        fs = para.fs
        wav, _ = librosa.load(file, sr=fs, mono=True)
        wav = wav_normlize(wav)
        
        # 提取特征f0和coded_sp
        f0,_,_,_,coded_sp=feature_world(wav,para)
        print(coded_sp.shape)
        f0s.append(f0)
        coded_sps.append(coded_sp)

# 计算log_f0的 均值和std
    log_f0s = np.ma.log(np.concatenate(f0s))
    log_f0s_mean = log_f0s.mean()
    log_f0s_std = log_f0s.std()
    
# 计算 coded_sp 的均值和 标准差
    coded_sps_array = np.concatenate(coded_sps,axis=0)  # coded_sp的维度  T * D
    coded_sps_mean = np.mean(coded_sps_array,axis=0,keepdims = True)
    coded_sps_std = np.std(coded_sps_array,axis=0,keepdims = True)

 # 利用 coded_sp 的均值和 标准差 对特征进行正则
    coded_sps_norm = []
    for coded_sp in coded_sps:
        coded_sps_norm.append(  (coded_sp- coded_sps_mean)/ coded_sps_std )
        
    return log_f0s_mean,log_f0s_std,coded_sps_mean,coded_sps_std,coded_sps_norm


if __name__ == "__main__":
    
    para = hparams()
    
    # 提取说话人A的特征
    dir_train_A = para.train_dir_A
    wavs = glob.glob(dir_train_A+'/*wav') 
    f0_mean,f0_std,mecp_mean,mecp_std, mecps = processing_wavs(wavs,para)
    os.makedirs(para.catch_A,exist_ok = True)
    np.save(os.path.join(para.catch_A,'static_f0.npy'),np.array([f0_mean,f0_std],dtype=object))
    np.save(os.path.join(para.catch_A,'static_mecp.npy'),np.array([mecp_mean,mecp_std],dtype=object))
    np.save(os.path.join(para.catch_A,'data.npy'),np.array(mecps,dtype=object))
    
    
    # 提取说话人B的特征
    dir_train_B = para.train_dir_B
    wavs = glob.glob(dir_train_B+'/*wav')
    f0_mean,f0_std,mecp_mean,mecp_std, mecps = processing_wavs(wavs,para)
    os.makedirs(para.catch_B,exist_ok = True)
    np.save(os.path.join(para.catch_B,'static_f0.npy'),np.array([f0_mean,f0_std],dtype=object))
    np.save(os.path.join(para.catch_B,'static_mecp.npy'),np.array([mecp_mean,mecp_std],dtype = object))
    np.save(os.path.join(para.catch_B,'data.npy'),np.array(mecps,dtype=object))

构造Dataloader

import os
import torch
import numpy as np
from torch.utils.data import Dataset,DataLoader
from hparams import hparams


class VC_Dataset(Dataset):
    
    def __init__(self,para):
        
        self.path_A = para.catch_A
        self.path_B = para.catch_B
        self.n_frames = para.n_frames
        
        # 加载数据
        self.train_A = np.load(os.path.join(self.path_A,'data.npy'),allow_pickle=True).tolist()
        self.train_B = np.load(os.path.join(self.path_B,'data.npy'),allow_pickle=True).tolist()
        self.n_samples = min(len(self.train_A),len(self.train_B))
        
        # 生成随机数据对
        self.gen_random_pair_index()
        
    def gen_random_pair_index(self):
        train_data_A_idx = np.arange(len(self.train_A))
        train_data_B_idx = np.arange(len(self.train_B))
        np.random.shuffle(train_data_A_idx)
        np.random.shuffle(train_data_B_idx)
        train_data_A_idx_subset = train_data_A_idx[:self.n_samples].tolist()
        train_data_B_idx_subset = train_data_B_idx[:self.n_samples].tolist()
        self.index_pairs = [(i,j) for i,j in zip(train_data_A_idx_subset,train_data_B_idx_subset)]
     
    def __len__(self):
        return len(self.index_pairs)
        
    
    def __getitem__(self,idx):
        
        # 读取 A 和 B 的数据
        data_A = self.train_A[self.index_pairs[idx][0]]
        data_B = self.train_B[self.index_pairs[idx][1]]
        
        # 从 A 和 B 中 随机截取 n_frames 帧
        start_A = np.random.randint(len(data_A) - self.n_frames + 1)
        sub_data_A = data_A[start_A:start_A + self.n_frames]
        
        start_B = np.random.randint(len(data_B) - self.n_frames + 1)
        sub_data_B = data_B[start_B :start_B + self.n_frames]
       
        return torch.from_numpy(sub_data_A.T),torch.from_numpy(sub_data_B.T)
        
if __name__ == "__main__":
    para = hparams()
    m_Dataset= VC_Dataset(para)
    
    m_DataLoader = DataLoader(m_Dataset,batch_size = 2,shuffle = True, num_workers = 2)
    
    m_Dataset.gen_random_pair_index()
    for n_epoch in range(3):
        for i_batch, sample_batch in enumerate(m_DataLoader):
            train_A = sample_batch[0]
            train_B = sample_batch[1]
            print(train_A.shape)
            print(train_B.shape)
            if i_batch>5:
                break
            
        m_Dataset.gen_random_pair_index()

其他代码太多就不一一列出来了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/129542.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

矢量图斑局部狭长判断和定位局部狭长部分(PostGIS、Java、C#实现)

矢量数据在数据采集过程中由于数据处理导致出现局部狭窄的面状部分,如下图 1. 定义和解决方法 狭长结构是指图斑几何形态上窄而长的部分,符号化后出现图形粘连压盖现象,导致难以在图面上清晰地表达出来。因此,依据地图表达比例尺因素需要对狭长结构进行融解处理。在…

helm部署frps和连接

文章目录一. helm部署frps1.1 下载1.2 部署1.2.1 不开启dashboard界面1.2.2 开启dashboard界面1.2.3 卸载1.3 查看1.4 IP Port 允许端口1.5 Web Ingress二. frpc客户端连接2.1 IP Port 连接2.2 Web 域名连接一. helm部署frps 1.1 下载 mkdir -p /root/i/helm && cd…

并查集专题1_图篇

1.并查集介绍 并查集支持查询和合并操作,只回答两个节点是不是在一个连通分量中,并不回答路径问题。 如果一个问题具有传递性,可以考虑用并查集。并查集最常见的一种设计思想是把在同一个连通分量中的节点组织成一个树形结构。 2.并查集的…

动作捕捉技术应用于地面移动机器人协同

《一千零一夜》故事集中收录的《阿拉丁神灯》深受读者们的喜爱,其中阿拉丁拥有一块神奇的魔毯,它具有运载功能,可以将物体轻松便捷的从一个地方转移到另一个地方。在现实生活中,可变形布作为一种轻量便携且具有良好适应性的载体&a…

redhat9中mysql常用命令(持续更新)

目录 1、查看当前用户 2、查看选择的数据库 3、创建数据库 4、创建数据表 5、插入数据 6、查看表所以字段的内容 7、查看数据库当前密码策略 8、查看密码插件 9、更改密码策略为LOW,改为LOW或0 10、 更改密码长度 11、设置大小写、数字和特殊字符均不要求 …

微服务系列专栏介绍

文章目录一 专栏介绍1.1 微服务行业背景不同行业IT系统更新频率IT系统存在的问题微服务架构在企业中应用情况1.2 什么是微服务1.3 微服务的特点1.4 微服务诞生背景1.5 微服务架构的优势二 专栏目标三 专栏涉及技术四 专栏架构1.微服务架构:2.Go语言3.go-micro架构4.…

入侵无线WiFi的主要方式及防护要点

从攻击形态上看,无线网络攻击主要可以分为三个大类:被动型攻击、主动型攻击以及针对网络组件的攻击: 被动攻击一般发生在攻击者处于无线网络范围内并可以监视无线通信内容时,最常见的被动攻击是数据包嗅探。由于被动攻击者只是监…

HTTPS 的通信加解密过程,证书为什么更安全?

经典面试题 HTTPS 的通信加解密过程,证书为什么更安全? 考察点 《计算机网络》相关知识 了解 HTTPS 协议加解密的过程 了解数字证书认证的过程 技术点 对称加密和非对称加密 HTTPS 协议的加解密过程 数字证书认证过程 对称加密和非对称加密 对称加…

Mysql 小Tips

Mysql 小Tips 目录Mysql 小Tips1.group_concat2.char_length3.locate4.replace5.now6.insert into ... select7.insert into ... ignore8.select ... for update9.on duplicate key update10.show create table11.create table ... select12.explain13.show processlist14.mysq…

企业电子招投标采购系统源码之首页设计

​ 功能模块: 待办消息,招标公告,中标公告,信息发布 描述: 全过程数字化采购管理,打造从供应商管理到采购招投标、采购合同、采购执行的全过程数字化管理。通供应商门户具备内外协同的能力,为外…

字符设备驱动(二)

1. IO模型 (1)阻塞式IO:不能操作就睡觉 (2)非阻塞式IO:不能操作就返回错误 (3)IO复用 (4)信号驱动式IO (5)异步IO 2.阻塞与…

c++算法基础必刷题目——按位贪心

文章目录按位贪心1、毒瘤xor2、兔子的区间密码3、起床困难综合症按位贪心 1、毒瘤xor NC18979 毒瘤xor 题目描述 小a有N个数a1, a2, …, aN&#xff0c;给出q个询问&#xff0c;每次询问给出区间[L, R]&#xff0c;现在请你找到一个数X&#xff0c;使得 1、0⩽X<231 2、∑…

Vue(ref和$refs属性介绍与使用)

一、 Vue(ref和$refs属性介绍与使用) 在Vue中一般很少会用到直接操作DOM&#xff0c;但不可避免有时候需要用到&#xff0c;这时我们可以通过ref和$refs这两个来实现&#xff0c;本文我们就来详细的介绍下这个内容 除了自定义属性外&#xff0c;Vue实例还暴露一些有用的实例属性…

Educational Codeforces Round 140 (Rated for Div. 2)

A. Cut the Triangle 题目链接&#xff1a;Problem - A - Codeforces 样例输入&#xff1a; 44 7 6 8 3 54 5 4 7 6 85 8 1 8 2 53 6 6 6 6 3 样例输出&#xff1a; YES YES YES NO题意&#xff1a;在二维平面上给定一个非退化三角形的三个顶点&#xff0c;问我们能不能用…

NVIDA CUDA和cuDNN安装教程

文章目录一. 查看自己的GPU版本是否支持cuda二 .安装CUDA三. 安装cuDNN一. 查看自己的GPU版本是否支持cuda 打开显卡的控制面板&#xff0c;查看显卡是否支持cuda 二 .安装CUDA 去官网查看cuda版本与所需系统固件的对应关系 从官网下载所需的CUDA Toolkit Archive,这里下载…

04-Css+Nginx

前端入门——CSS 1 CSS入门 1.1 初识CSS 1.1.1 概述 在学习了基本HTML标签和样式后&#xff0c;接下来学习前端开发的第二门技术。 我们发现&#xff0c;虽然标签的属性可以调整一些样式&#xff0c;但是效果不够理想&#xff0c;而我们更愿意把样式编写在<style> 标…

CSS 实现七彩圆环loading动画

前言 &#x1f44f;CSS 实现七彩圆环loading动画&#xff0c;速速来Get吧~ &#x1f947;文末分享源代码。记得点赞关注收藏&#xff01; 1.实现效果 2.实现步骤 定义父容器宽度为–w&#xff0c;每个圆环之间的gap间距为–gap&#xff0c;圆环的border边框宽为–border&am…

7个从Windows计算机上恢复Word文档的方法

你是否有受到Windows上损坏或丢失的Word文件的困扰&#xff1f;好吧&#xff0c;你并不孤单&#xff01; 文件损坏是 Word 文档的常见问题。此外&#xff0c;人们不小心删除了文件或忘记保存最新版本。 由于Word文件通常包含您在个人或职业生涯中所需的重要数据&#xff0c;因…

JavaWeb:Maven创建Web项目

1.1 Web项目结构 Web项目的结构分为&#xff1a;开发中的项目和开发完可以部署的Web项目&#xff0c;这两种项目的结构是不一样的&#xff0c;我们一个个来介绍下&#xff1a; Maven Web项目结构&#xff1a;开发中的项目 开发完成部署的Web项目 开发项目通过执行Maven打包…

python为你画虎迎新年,结束旧年迎新景

前言 2022年是农历壬寅虎年&#xff0c;虎作为十二生肖是家喻户晓的 据学者考证&#xff0c;其应当起源于楚文化中对虎的图腾崇拜&#xff0c;象征力量与威严。 虎者&#xff0c;百兽之王&#xff0c;是威猛的象征&#xff0c;为镇宅辟邪之灵物。 虎在民间习俗中被尊崇为瑞兽…