有没有好奇歌星们清唱的声音怎么样?这一讲,我们将歌曲的人声和背景音乐分离出来,使用的网络是RNN。接下来一一讲解怎么实现。
下载数据集
搞机器学习,首先想到的是怎么获取训练的数据,网上有开放的数据集MIR-1k,下载地址如下:
http://mirlab.org/dataset/public/MIR-1K.rar
下载完数据,解压到dataset/下,结构如下图所示
数据集里有很多文件夹,其他的我们不管,我们只用到Wavfile和UndividedWavfile文件夹下的文件。简单介绍一下数据集,打开Wavfile文件夹
里面有1000个音频文件,随便播放一个,请忽略里面五音不全的唱功(这里还是对得向该数据集的制作者致敬,感谢你们的付出),注意听会发现,有一个声道的数据是纯背景音乐,另一个声道的数据是纯人声。有了这个特性,我们就可以很好的利用了,后面详解代码再说。UndividedWavfile文件夹下也是类似的音频文件,只是数量只有110个,正好我们可以使用Wavfile文件夹下的数据做训练集,用UndividedWavfile文件夹下的数据做测试集。
思路
有了数据集,接下来还的有思路,俗称套路。首先,我们现在做的是音频的项目, 做音频的项目,首先就得想到将时域转到频域,再做分析。
而神经网络的套路也基本是下面的几步:
- 创建占位符、变量
- 设置学习率和batch size等参数
- 构建神经网络
- 设置损失函数
- 设置优化器
- 创建会话
- 利用神经网络开始训练数据,一般都是mini-batch的方法
具体到我们的这个项目,我们应该做以下事情
- 导入需要训练的数据集文件路径,存到列表中即可
- 导入训练集数据,每一个训练集文件都是一个双声道的音频文件,其中,第一个声道存的是背景音乐,第二个声道存的是纯人声;我们需要三组数据,第一组是将双声道转成单声道的数据,即让背景音乐和人声混合在一起 第二组数据是纯背景音乐,第三组数据是纯人声数据
- 通过上一步获取的数据都是时域的,我们要通过短时傅里叶变换将声音数据转到频域
- 初始化网络模型
- 获取mini-batch数据,开始进行迭代训练
train主框架
有了套路,就开始撸代码,先来看看训练train部分的代码,代码在train.py文件里
#可以通过命令设置的参数:
#dataset_dir : 数据集路径
#model_dir : 模型保存的文件夹
#model_filename : 模型保存的文件名
#dataset_sr : 数据集音频文件的采样率
#learning_rate : 学习率
#batch_size : 小批量训练数据的长度
#sample_frames : 每次训练获取多少帧数据
#iterations : 训练迭代次数
#dropout_rate : dropout率
def parse_arguments(argv):
parser = argparse.ArgumentParser()
parser.add_argument('--dataset_train_dir', type=str, help='数据集训练数据路径', default='./dataset/MIR-1K/Wavfile')
parser.add_argument('--dataset_validate_dir', type=str, help='数据集验证数据路径', default='./dataset/MIR-1K/UndividedWavfile')
parser.add_argument('--model_dir', type=str, help='模型保存的文件夹', default='model')
parser.add_argument('--model_filename', type=str, help='模型保存的文件名', default='svmrnn.ckpt')
parser.add_argument('--dataset_sr', type=int, help='数据集音频文件的采样率', default=16000)
parser.add_argument('--learning_rate', type=float, help='学习率', default=0.0001)
parser.add_argument('--batch_size', type=int, help='小批量训练数据的长度', default=64)
parser.add_argument('--sample_frames', type=int, help='每次训练获取多少帧数据', default=10)
parser.add_argument('--iterations', type=int, help='训练迭代次数', default=30000)
parser.add_argument('--dropout_rate', type=float, help='dropout率', default=0.95)
return parser.parse_args(argv)
if __name__ == '__main__':
main(parse_arguments(sys.argv[1:]))
上面一些参数都有注释,比较简单就不再解释了,来看main函数做了什么
#训练模型,需要做以下事情
#1. 导入需要训练的数据集文件路径,存到列表中即可
#2. 导入训练集数据,每一个训练集文件都是一个双声道的音频文件,
# 其中,第一个声道存的是背景音乐,第二个声道存的是纯人声,
# 我们需要三组数据,第一组是将双声道转成单声道的数据,即让背景音乐和人声混合在一起
# 第二组数据是纯背景音乐,第三组数据是纯人声数据
#3. 通过上一步获取的数据都是时域的,我们要通过短时傅里叶变换将声音数据转到频域
#4. 初始化网络模型
#5. 获取mini-batch数据,开始进行迭代训练
def main(args):
#先看数据集数据是否存在
if not os.path.exists(args.dataset_train_dir) or not os.path.exists(args.dataset_validate_dir):
raise NameError('数据集路径"./dataset/MIR-1K/Wavfile"或"./dataset/MIR-1K/UndividedWavfile"不存在!')
# 1. 导入需要训练的数据集文件路径,存到列表中即可
train_file_list = load_file(args.dataset_train_dir)
valid_file_list = load_file(args.dataset_validate_dir)
上面做的很简单,将我们要训练和验证的文件路径导入到相应的列表里就可以了。然后设置一些参数
# 数据集的采样率
mir1k_sr = args.dataset_sr
# 用于短时傅里叶变换,窗口大小
n_fft = 1024
# 步幅;帧移对应卷积中的stride;
hop_length = n_fft // 4
# Model parameters
# 学习率
learning_rate = args.learning_rate
# 用于创建rnn节点数
num_hidden_units = [1024, 1024, 1024, 1024, 1024]
# batch 长度
batch_size = args.batch_size
# 获取多少帧数据
sample_frames = args.sample_frames
# 训练迭代次数
iterations = args.iterations
# dropout
dropout_rate = args.dropout_rate
# 模型保存路径
model_dir = args.model_dir
model_filename = args.model_filename
接着,我们就需要读取音频文件了,音频文件里保存的是时域的数据,读取文件后,使用快速傅里叶变换将它们转到频域中
#导入训练数据集的wav数据,
#wavs_mono_train存的是单声道,wavs_music_train 存的是背景音乐,wavs_voice_train 存的是纯人声
wavs_mono_train, wavs_music_train, wavs_voice_train = load_wavs(filenames = train_file_list, sr = mir1k_sr)
# 通过短时傅里叶变换将声音转到频域
stfts_mono_train, stfts_music_train, stfts_voice_train = wavs_to_specs(
wavs_mono=wavs_mono_train, wavs_music=wavs_music_train, wavs_voice=wavs_voice_train, n_fft=n_fft,
hop_length=hop_length)
# 跟上面一样,只不过这里是测试集的数据
wavs_mono_valid, wavs_music_valid, wavs_voice_valid = load_wavs(filenames=valid_file_list, sr=mir1k_sr)
stfts_mono_valid, stfts_music_valid, stfts_voice_valid = wavs_to_specs(
wavs_mono=wavs_mono_valid, wavs_music=wavs_music_valid, wavs_voice=wavs_voice_valid