文章目录
前言
非并行多域语音转换(VC)是一种在不依赖于并行数据的情况下学习多个域之间的映射的技术。这是重要的,但具有挑战性,因为需要学习多个映射,并且显式监督不可用。最近,StarGAN-VC因其仅使用单个生成器即可解决此问题而备受关注。然而,真实语音和转换语音之间仍然存在差距。为了弥补这一差距,我们重新思考了StarGAN-VC的条件方法,这是在单个模型中实现非并行多域VC的关键组成部分,并提出了一种改进的变体,称为StarGAN-VC2。特别是,我们从两个方面重新思考了条件方法:训练目标和网络架构。对于前者,我们提出了一种源和目标条件对抗性损失,允许所有源域数据转换为目标域数据。对于后者,我们介绍了一种基于调制的条件方法,该方法可以以特定领域的方式转换声学特征的调制。我们在非并行多说话人VC上评估了我们的方法。客观评估表明,我们提出的方法在全局和局部结构测量方面都提高了语音质量。此外,主观评估表明,StarGAN-VC2在自然度和说话者相似性方面优于StarGAN-VC。
正文
论文: StarGAN-VC2:Rethinking Conditional Methods for StarGAN-Based Voice Conversion
论文连接
变声器
- 变声器的工作原理是什么呢?
- 其实就是把语音特征进行转换,只不过内容不能变!

VC:Voice Conversion
- 如何构建一个变声器呢?思想跟stargan差不多,细节完全不同
- 需要输入什么?1.声音数据;2.标签编码;
- 整体来说还是GAN模型,主要解决数据特征提取,网络模型定义
- stargan-vc2是升级版,前身还有cyclegan-vc和stargan-vc
使用数据集
- VCC2016
- 4个人的声音数据,相当于4个domain,他们之间相互转换【‘SF1’,‘SF2’,‘TM1’,‘TM2’】
- 论文中选择的特征为:MFCCs(梅尔倒谱系数);log F0(基频的对数);APs(声学参数);
- 论文使用输入特征为:batchsize135*128**(35为特征个数,128为指定特征维度或者切分)**
科普:
1.梅尔倒谱系数,用于表示语音信号的特性。MFCCs是语音信号处理中常用的一种参数,通过对语音信号进行傅里叶变换和一系列数学处理,得到一组倒谱系数,用于描述语音信号的频谱特性和声道形状。
2.log F0 表示基频的对数。基频是指声音信号中最低的频率成分,通常与声道的振动频率相关,对于语音信号来说,基频决定了音调的高低。
3.APs(Acoustic Parameters)是声学参数,用于描述语音信号的声学特性。APs 可以包括多种参数,如频谱参数、能量参数、持续时间参数等,用于分析和表示语音信号的不同特征。
输入数据格式
- 频率:每秒钟波峰所发生的数目称之为信号的频率,用单位千赫兹(kHz)表示
- 例如:0.1毫秒完成4.8次采样,则1秒48000次采样,采样率48KHZ

预处理数据
- 16KHZ重采样
- 预加重:补偿高频信号,让高频信号权重更大一些,因为它信息多
- 分帧:类似时间窗口,得到多个特征段
论文中的特征汇总
- 基频特征(F0):声音可以分解成不同频率的正弦波,其中最低的那个波谷。
- 频谱包络:语音是一个时序信号,如采样频率为16kHz的音频文件(每秒包含16000个采样点)分帧后得到了多个子序列,然后对每个子序列进行傅里叶变换操作,就得到了频率-振幅图(也就是描述频率-振幅图变化趋势的)
- Aperiodic参数:基于F0与频谱包络计算得到
科普:
傅里叶变换的作用
1. 信号分析:傅里叶变换可以将语音信号从时域转换到频域,从而提供语音信号的频谱信息。这有助于分析和理解语音信号的频率特性。
2. 特征提取:通过对语音信号进行傅里叶变换,可以提取出语音信号中的频率分量,这些频率分量可以作为语音信号的特征参数,用于后续的语音识别和分类。
3. 降噪和预处理:傅里叶变换还可以用于语音信号的降噪和预处理。通过去除语音信号中的噪声和干扰,可以提高语音识别的准确性和鲁棒性。
Aperiodic参数的作用
在语音合成中,为了获得更自然的声音,激励源不仅包含周期信号,还需要包含一些非周期信号。Aperiodic参数就是用于描述这种非周期信号的特性,它与周期信号一起共同构成了语音信号的激励源。通过对Aperiodic参数的提取和分析,可以更好地理解和表示语音信号的特性,从而提高语音合成的质量和自然度。
MFCC
MFCC(Mel Frequency Cepstral Coefficients)梅尔频率倒谱系数。
流程为:

1. 预处理:对原始语音信号进行预处理,包括预加重、分帧和加窗等操作。
2. 傅里叶变换:对每帧语音信号进行傅里叶变换,得到语音信号的频谱。
3. 梅尔滤波:将语音信号的频谱映射到梅尔刻度上,并通过一组三角滤波器组进行滤波,得到每个滤波器的输出。
4. 对数运算和离散余弦变换(DCT):对每个滤波器的输出取对数,并进行离散余弦变换,得到MFCC系数。
科普:
1. 通俗解释:FFT之后就把语音转换到频域,MEL滤波器变换后相当于得到更符合人类听觉的效果:

2. 最后DCT相当于提取每一帧的包络(这里面特征多),包络是指一个信号的最大值和最小值构成的曲线,它反映了信号幅度的变化。在语音信号处理中,包络通常用于描述语音信号的幅度变化特性。
网络架构
- 生成器:输入就是提取好的特征,输出也就是特征
- 感觉就是编码-解码的过程,其中引入了IN和GLU单元

语音数据包含的成分

图中的Content Encoder是提取声音内容;Speaker Encoder是提取声音特征;然后经过解码器Decoder,拼接成一个新的有内容和新特征的声音,进而实现变声器的效果。
思考:
- 变声器虽然把咱们动静给改了,但是内容没变吧!
- 编码时如何保留住原始内容呢?这就得去掉声音中特性的部分
- 解码时如何放大个性呢?还是需要再处理解码特征
Instance Normalization(IN)
把声音特征去掉,就是声音内容了。
IN是在每个样本的每个通道上单独进行归一化操作。具体来说,IN计算每个样本每个通道的均值和方差,并使用它们对该通道的数据进行标准化。

Adaptive Instance Normalization(AdaIn)
IN是一种用于解决内部协变量偏移问题的归一化方法,它通过对每个通道的激活进行归一化,使得网络的训练更加稳定。然而,IN在风格迁移任务中可能会导致风格的丢失。
为了解决这个问题,AdaIn被提了出来。AdaIn不仅对每个通道的激活进行归一化,还学习了一个缩放因子和偏移因子,这两个因子是根据数据风格计算得出的。这样,AdaIn就可以将内容的激活分布调整为风格的激活分布,从而实现了风格的迁移。


生成器 PS(PixelShuffle)
不会…
判别器
- GSP:global sum pooling:一个特征图压缩成一个点,batch512hw,压缩成batch512的点
- 然后,标签通过embeding编码成512维特征(batch*512),内积得到batch个判别结果

代码
- preprocess.py
这段代码执行了以下操作:
- 导入所需的库和模块。
- 定义了命令行参数解析器(ArgumentParser)。该解析器用于从命令行输入的参数中解析出指定的参数值。
- 定义了
--dataset参数,用于指定数据集名称,默认值为VCC2016,可选值为VCC2016和VCC2018。 - 定义了
--input_dir参数,用于指定输入数据的目录,默认值为./data/spk。 - 定义了
--output_dir参数,用于指定处理后数据的输出目录,默认值为./data/processed。
- 定义了
- 解析命令行参数,并将解析后的值保存到相应的变量中。
- 创建输出目录(如果不存在)。
- 根据选择的数据集,设置采样率。
- 如果数据集为
VCC2016,则采样率为16000 Hz。 - 如果数据集为
VCC2018,则采样率为22050 Hz。
- 如果数据集为
- 调用
wav_to_mcep_file函数,将输入目录中的音频文件转换为梅尔倒谱系数(MCCs),并保存到输出目录中的文件中。 - 实例化
GenerateStatistics类的对象,并指定数据集的输出目录。 - 调用
generate_stats方法,生成统计特征(均值和标准差)。 - 调用
normalize_dataset方法,对数据集进行归一化处理。 - 输出程序执行的持续时间。
总的来说,这段代码执行了音频数据的预处理过程,包括将音频转换为梅尔倒谱系数(MCCs),计算统计特征,并对数据集进行归一化处理。
if __name__ == "__main__":
start = datetime.now()
parser = argparse.ArgumentParser(description='Convert the wav waveform to mel-cepstral coefficients(MCCs)\
and calculate the speech statistical characteristics.')
input_dir = './data/spk'
output_dir = './data/processed'
dataset_default = 'VCC2016'
parser.add_argument('--dataset', type=str, default=dataset_default, choices=['VCC2016', 'VCC2018'],
help='Available datasets: VCC2016 and VCC2018 (Default: VCC2016).')
parser.add_argument('--input_dir', type=str, default=input_dir, help='Directory of input data.')
parser.add_argument('--output_dir', type=str, default=output_dir, help='Directory of processed data.')
argv = parser.parse_args()
input_dir = argv.input_dir
output_dir = argv.output_dir
os.makedirs(output_dir, exist_ok=True)
"""
Sample rate:
VCC2016: 16000 Hz
VCC2018: 22050 Hz
"""
if argv.dataset == 'VCC2016':
sample_rate = 16000
else:
sample_rate = 22050
wav_to_mcep_file(input_dir, sample_rate, processed_filepath=output_dir)
# 生成统计特征
generator = GenerateStatistics(output_dir)
generator.generate_stats()
generator.normalize_dataset()
end = datetime.now()
print(f"* Duration: {
end-start}.")
这段代码定义了一个名为load_wavs的函数,用于加载音频文件并进行预处理。
函数的参数包括:
dataset:数据集路径,指定包含音频文件的目录。sr:采样率。
函数的流程如下:
- 创建一个空字典
data,用于存储音频文件的路径。 - 使用
os.scandir遍历数据集目录中的文件和子目录。 - 对于每个子目录,将其作为键添加到
data字典中,并创建一个空列表作为对应的值。 - 使用
os.scandir遍历子目录中的音频文件。 - 对于每个音频文件,将其路径添加到相应子目录的列表中。
- 输出加载的键(子目录名称)。
- 创建一个空字典
resdict,用于存储处理后的音频数据。 - 初始化计数器
cnt为0。 - 遍历
data字典的键值对,进行音频文件的预处理。 - 对于每个音频文件路径,提取文件名作为新的键。
- 使用
librosa.load函数加载音频文件,将其转换为浮点数类型的波形数据。 - 使用
librosa.effects.trim函数对波形数据进行修剪,突出高频信号。 - 应用预加重滤波器,将修剪后的波形数据附加到之前的采样点后面。
- 将处理后的波形数据添加到
resdict字典中对应的子目录和文件键中。 - 输出进度点"."(每处理一个音频文件输出一个点)。
- 递增计数器
cnt。 - 输出总音频文件数量。
- 返回处理后的
resdict字典。
总的来说,这段代码实现了对音频文件的加载和预处理操作。它遍历数据集目录并获取每个子目录中的音频文件路径。然后,它使用Librosa库加载音频文件,对其进行修剪和预加重滤波器处理,并将处理后的音频数据保存在一个字典中。最后,返回包含处理后数据的字典。
# 用于加载音频文件。
def load_wavs(dataset: str, sr):
"""
`data`: contains all audios file path.
`resdict`: contains all wav files.
"""
data = {
}
with os.scandir(dataset) as it:
for entry in it:
if entry.is_dir():
data[entry.name] = []
with os.scandir(entry.path) as it_f:
for onefile in it_f:
if onefile.is_file():
data[entry.name].append(onefile.path)
print(f'* Loaded keys: {
data.keys()}')
resdict = {
}
cnt = 0
for key, value in data.items():
resdict[key] = {
}
for one_file in value: #预处理,突出高频信号,因为一般发音的话高频信号能表达更多有用的信息
filename = one_file.split('/')[-1].split('.')[0]
newkey = f'{
filename}'
wav, _ = librosa.load(one_file, sr=sr, mono=True, dtype=np.float64)#sr:采样率 mono:单通道
y, _ = librosa.effects.trim(wav, top_db=15)
wav = np.append(y[0], y[1: ] - 0.97 * y[: -1])
resdict[key][newkey] = wav
print('.', end='')
cnt += 1
print(f'\n* Total audio files: {
cnt}.')
return resdict
# 用于生成指定大小的迭代器块。
# iterable:可迭代对象。
# size:块大小。
# 函数功能:
# 将可迭代对象拆分为指定大小的块。
# 逐块返回生成器。
这段代码定义了一个名为chunks的函数,用于将可迭代对象按照指定大小拆分成连续的子块。
函数的参数包括:
iterable:可迭代对象,即需要拆分的对象。size:块的大小,指定每个子块的元素个数。
函数的流程如下:
- 使用
range函数生成一个以size为步长的索引序列,遍历这个索引序列。 - 在每次迭代中,根据当前索引截取
iterable中的连续子序列,子序列从当前索引开始,长度为size。 - 使用
yield语句将每个子序列作为生成器的输出。 - 持续迭代直到所有的子块都被生成和返回。
总的来说,这段代码实现了将一个可迭代对象拆分为连续的子块,每个子块包含指定数量的元素。它通过生成器的方式返回拆分后的子块,可以在需要的时候逐个获取子块,而不是一次性生成和返回所有的子块。
def chunks(iterable, size):
"""
Yield successive n-sized chunks from iterable.
"""
for i in range(0, len(iterable), size):
yield iterable[i: i + size]
# 用于将音频文件转换为MCEP特征。
# sr: 采样率。
# 进行音频文件转换和特征提取。
这段代码定义了一个名为wav_to_mcep_file的函数,用于将音频文件转换为MCEP特征并保存为文件。
函数的参数包括:
dataset:数据集路径,指定包含音频文件的目录。sr:采样率。processed_filepath:转换后的特征文件保存路径,默认为"./data/processed"。
函数的流程如下:
- 使用
shutil.rmtree函数删除已存在的processed_filepath目录及其内容。 - 使用
os.makedirs函数创建processed_filepath目录,如果目录已存在则不进行任何操作。 - 使用
glob.glob函数计算数据集中所有音频文件的数量,并输出总音频文件数量。 - 调用
load_wavs函数加载和预处理音频数据,将结果存储在字典d中。 - 遍历字典
d中的每个说话者。 - 获取当前说话者的音频数据列表
values_of_one_speaker。 - 使用
chunks函数将音频数据列表拆分为指定大小的块one_chunk。 - 初始化一个空列表
wav_concated用于存储拼接后的音频数据。 - 对于每个块,将其数据拼接到
wav_concated列表中。 - 将
wav_concated列表转换为NumPy数组wav_concated。 - 调用
cal_mcep函数计算MCEP特征,得到基频(f0)、频谱包络(ap)和梅尔频谱倒谱系数(mcep)。 - 根据说话者名称和块索引生成新的名称
newname。 - 构建特征文件路径
file_path_z,使用np.savez函数将f0和mcep保存为NPZ文件。 - 输出保存特征文件的消息。
- 对于每个块中的每个帧,在MCEP矩阵中滑动窗口,每次取连续的
FRAMES帧作为一个音频片段。 - 如果音频片段的帧数等于
FRAMES,则生成临时文件名temp_name并构建保存路径filePath,使用np.save函数将音频片段保存为NPY文件。 - 输出保存音频片段文件的消息。
总的来说,这段代码实现了将音频文件转换为MCEP特征,并将特征数据保存为文件。它遍历数据集中的音频文件,将它们按照指定大小进行拼接,并计算拼接后音频数据的MCEP特征。然后,它将特征数据保存为NPZ格式的文件,以及根据帧窗口滑动的方式,将每个音频片段保存为单独的NPY文件。
def wav_to_mcep_file(dataset: str, sr: int, processed_filepath: str='./data/processed'):
"""
Convert wavs to MCEPs feature using image representation.
"""
shutil.rmtree(processed_filepath)
os.makedirs(processed_filepath, exist_ok=True)
allwavs_cnt = len(glob.glob(f'{
dataset}/*/*.wav'))
print(f'* Total audio files: {
allwavs_cnt}.')
d = load_wavs(dataset, sr)
for one_speaker in d.keys():
values_of_one_speaker = list(d[one_speaker].values())
for index, one_chunk in enumerate(chunks(values_of_one_speaker, CHUNK_SIZE)):
wav_concated = []
temp = one_chunk.copy()
for one in temp:
wav_concated.extend(one)
wav_concated = np.array(wav_concated)
f0, ap, mcep = cal_mcep(wav_concated, sr, FEATURE_DIM, FFTSIZE, SHIFTMS, ALPHA)
newname = f'{
one_speaker}_{
index}'
file_path_z = os.path.join(processed_filepath, newname)
np.savez(file_path_z, f0=f0, mcep=mcep)
print(f'[SAVE]: {
file_path_z}')
for start_idx in range(0, mcep.shape[1] - FRAMES + 1, FRAMES):
one_audio_seg = mcep[:, start_idx: start_idx + FRAMES]
if one_audio_seg.shape[1] == FRAMES:
temp_name = f'{
newname}_{
start_idx}'
filePath = os.path.join(processed_filepath, temp_name)
np.save(filePath, one_audio_seg)
print(f'[SAVE]: {
filePath}.npy')
-
solver.py
- 恢复或加载预训练的模型。
- 从指定的测试目录读取数据,包括源说话者的音频数据和说话者标签。
- 获取目标说话者列表。
- 对于每个目标说话者,进行以下操作:
- 将源说话者和目标说话者的标签进行编码转换。
- 对于给定的音频文件和内容,进行以下操作:
- 提取声音特征,包括基频(f0)、包络(ap)和梅尔倒谱系数(mcep_norm)。
- 创建一个空列表,用于存储转换结果。
- 对于每个特征帧,进行以下操作:
- 将特征帧从源说话者转换为目标说话者。
- 对转换后的特征进行一些后处理操作。
- 将转换结果添加到列表中。
- 将转换结果拼接起来,并进行一些修剪操作。
- 进行音频合成,生成转换后的音频。
- 将转换后的音频保存到文件中。
class Solver(object):
def __init__(self, data_loader, config):
self.config = config
self.data_loader = data_loader
self.num_spk = config.num_spk
if config.dataset == 'VCC2016':
self.sample_rate = 16000
else:
self.sample_rate = 22050
self.lambda_cyc = config.lambda_cyc
self.lambda_gp = config.lambda_gp
self.lambda_id = config.lambda_id
# Training configurations.
self.data_dir = config.data_dir
self.test_dir = config.test_dir
self.batch_size = config.batch_size
self.num_iters = config.num_iters
self.num_iters_decay = config.num_iters_decay
self.g_lr = config.g_lr
self.d_lr = config.d_lr
self.n_critic = config.n_critic
self.beta1 = config.beta1
self.beta2 = config.beta2
self.resume_iters = config.resume_iters
# Test configurations.
self.test_iters = config.test_iters
self.trg_speaker = ast.literal_eval(config.trg_speaker)
self.src_speaker = config.src_speaker
self.use_tensorboard = config.use_tensorboard
self.device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
self.spk_enc = LabelBinarizer().fit(speakers)
self.log_dir = config.log_dir
self.sample_dir = config.sample_dir
self.model_save_dir = config.model_save_dir
self.result_dir = config.result_dir
self.log_step = config.log_step
self.sample_step = config.sample_step
self.model_save_step = config.model_save_step
self

最低0.47元/天 解锁文章
813





