将TensorFlow自动编码器与音乐配合使用
自动编码器是深度学习的一个令人惊奇的部分,有许多令人敬畏的用途。它们允许我们做任何事情,从数据压缩到重构输入。
为什么这很重要?
创建一个能够“清理”或重建信号的神经网络的能力是非常有用的。从在线流媒体压缩音乐到通信信号进行去噪,这都有其用途。
例如,假设您想创建一个流媒体音乐服务,它可以在不使用大量带宽的情况下提供无损的音乐。实际上,您可以使用编码器压缩服务器上的音乐,将压缩的音频流到客户端,然后使用解码器在客户端解压音频。您需要一个更复杂的深度学习模型来处理这样的事情,但是它显示了这种技术的强大。
autoencoder是一种神经网络,它利用无监督学习来学习如何表示给定的数据集。
自动编码器由编码和解码机制组成。编码器负责减少(或压缩)我们的输入数据。解码器负责获取缩减(或压缩)的数据并将其重建为输入的最接近的可能表示。
我深度学习模型使用的数据集是ÁngelFaraldo,你可以在这里得到它(https://zenodo.org/record/1101082#.W5v6BBRlBhE)。对于这种情况,我只需要audio.zip文件(大约2.1 GB)。
下载zip文件后,我继续将其解压缩到一个文件夹中。
由于音频文件是MP3格式,因此需要将它们转换为WAV文件,以便更容易使用。为此,我使用了以下Python代码片段:
from pydub import AudioSegment from glob import iglob DATA_FILES_MP3 = 'audio' DATA_FILES_WAV = 'audio_wav' def convert_mp3_to_wav(): index = 0 for file in iglob(DATA_FILES_MP3 + '/*.mp3'): mp3_to_wav = AudioSegment.from_mp3(file) mp3_to_wav.export(DATA_FILES_WAV + '/' + str(index) + '.wav', format='wav') index += 1
我并不担心维护音频文件的名称。除非您想要使用特定歌曲,否则在此处命名任何特定的WAV文件并不是非常重要。
一旦我将所有MP3文件转换为WAV文件,我就必须弄清楚如何加载WAV数据。将数据放入神经网络可以使用的格式是最困难的部分之一。
为了帮助我处理WAV文件,使用TensorFlow的audio_ops库。我选择使用audio_ops将WAV文件解码为一个样本数组,然后可以对其进行处理和批处理。
数据集中的WAV文件包含两个通道(左和右 - 立体声)。我发现将每个通道放在自己的数组中使数据更容易使用。
WAV文件包含一组样本,这些样本表示某个时刻的信号幅度。根据我的经验,这些数据对于网络来说非常难以学习。因此,我对每个通道进行了离散傅立叶变换,以便将音频信号分解为神经网络可以学习的组件。通过这样做,网络的训练大大改善。
完成所有操作后,我的Python批处理方法如下:
from scipy.fftpack import rfft, irfft from tensorflow.contrib.framework.python.ops import audio_ops ''' curr_batch - The current batch of the training data we are looking at. songs_per_batch - How songs we want to load in per batch sess - Our TensorFlow session object ''' def get_next_batch(curr_batch, songs_per_batch, sess): wav_arr_ch1 = [] wav_arr_ch2 = [] if (curr_batch) >= (len(file_arr)): curr_batch = 0 start_position = curr_batch * songs_per_batch end_position = start_position + songs_per_batch for idx in range(start_position, end_position): audio_binary = tf.read_file(file_arr[idx]) wav_decoder = audio_ops.decode_wav( audio_binary, desired_channels=2) sample_rate, audio = sess.run( [wav_decoder.sample_rate, wav_decoder.audio]) audio = np.array(audio) # We want to ensure that every song we look at has the same # number of samples! if len(audio[:, 0]) != 5292000: continue wav_arr_ch1.append(rfft(audio[:,0])) wav_arr_ch2.append(rfft(audio[:,1])) print("Returning File: " + file_arr[idx]) return wav_arr_ch1, wav_arr_ch2, sample_rate
每次提取新批次时,我都没有创建新的TensorFlow会话,而是发现将会话对象传递给方法更容易。
通过处理和准备数据,让我们进入网络架构以及如何提供数据。
在这种情况下,我使用深度自动编码器来处理信号数据。这意味着自动编码器由具有不同数量节点的多个层组成。确定每层的正确节点数可能是一项繁琐的工作。我发现数据中有一个最佳点,太多的神经元会导致明显的噪音而太少会阻止网络学习。我发现最小的层应该是输入大小的四分之一左右。
旁注:重要的是要知道网络的大小将受到你需要使用多少内存的限制(在我的情况下,16GB)。
经过几天调整我的神经网络后,我最终得到了以下超参数:
inputs = 12348 hidden_1_size = 8400 hidden_2_size = 3440 hidden_3_size = 2800 batch_size = 50 lr = 0.0001 l2 = 0.0001
我使用ReLU作为激活函数,使用Adam Optimizer作为我的优化器。
我获取了每批音频文件,并reshape数据以适应网络。我通过获取一首歌曲的两个声道并将它们分成大小为12348(输入层的大小)的子数组来完成此操作。然后我把第二个通道数组放在第一个通道后面。创建的数组如下:[ch1_samples_1,ch1_samples_2,ch1_samples _ .. N,ch2_samples_1,ch2_samples_2,ch2_samples_ ... N]。这是有效的,因为当定义占位符时,它的形状为[?,12348],我们的数据shape为[num_sample_arrs,12348]。
总的来说,Python处理看起来像这样:
def next_batch(c_batch, batch_size, sess): ch1_arr = [] ch2_arr = [] wav_arr_ch1, wav_arr_ch2, sample_rate = process_data.get_next_batch(c_batch, batch_size, sess) for sub_arr in wav_arr_ch1: batch_size_ch1 = math.floor(len(sub_arr)/inputs) sub_arr = sub_arr[:(batch_size_ch1*inputs)] ch1_arr.append(np.array(sub_arr).reshape( batch_size_ch1, inputs)) for sub_arr in wav_arr_ch2: batch_size_ch2 = math.floor(len(sub_arr)/inputs) sub_arr = sub_arr[:(batch_size_ch2*inputs)] ch2_arr.append(np.array(sub_arr).reshape( batch_size_ch2, inputs)) # Carry through sample_rate for reconstructing the WAV file return np.array(ch1_arr), np.array(ch2_arr), sample_rate
利用shaped arrays,数据可以准备好输入网络。我这样做是通过定义我正在使用的批次数量,以及我想要的epochs数。由于我没有运行强大的装备,我只在10,000个epochs内训练了50首歌曲。然后我会每1000个epochs运行一首测试歌曲并绘制结果图。我的测试歌曲是网络从未见过的(它不是训练集的一部分)。
提供网络数据的逻辑如下:
epochs = 10000 batches = 50 with tf.Session() as sess: init.run() ch1_song, ch2_song, sample_rate = next_batch(0, batch_size, sess) for epoch in range(epochs): epoch_loss = [] print("Epoch: " + str(epoch)) for i in range(batches): total_songs = np.hstack([ch1_song, ch2_song]) batch_loss = [] for j in range(len(total_songs)): x_batch = total_songs[j] _, l = sess.run([training_op, loss], feed_dict = {X:x_batch}) batch_loss.append(l) print("Song loss: " + str(l)) print("Curr Epoch: " + str(epoch) + " Curr Batch: " + str(i) + "/"+ str(batches)) print("Batch Loss: " + str(np.mean(batch_loss))) epoch_loss.append(np.mean(batch_loss)) print("Epoch Avg Loss: " + str(np.mean(epoch_loss))) ... Then feed in test songs to be graphed and converted back to WAV files ...
让自动编码器训练四天后,我停止了训练。
有10,000个epochs,我每1000个epochs的平均批量损失如下:
Epoch: 1000 Avg Loss: 45610.113
Epoch: 2000 Avg Loss: 16638.918
Epoch: 3000 Avg Loss: 14414.36
Epoch: 4000 Avg Loss: 12988.543
Epoch: 5000 Avg Loss: 12236.773
Epoch: 6000 Avg Loss: 11735.502
Epoch: 7000 Avg Loss: 11429.168
Epoch: 8000 Avg Loss: 11221.186
Epoch: 9000 Avg Loss: 11068.95
Epoch: 10000 Avg Loss: 10956.46
还不错!如果我让它训练的时间更长,结果会好得多。
让我们来看看原始波形和频谱图与重建的歌曲。
下面是歌曲在训练中的波形的GIF动画。蓝色是原始波形,橙色是重建波形。
每1000个epochs的数据波形
我们可以肯定地看到神经网络能够很好地重建原始波形。这里仍有一些损失,但可以通过更多训练来纠正。
我们来看一下音频信号的频谱图。下面是网络训练过程中频谱图的另一个动画GIF。在它下面是原始未触动的音频的频谱图。
每1000个测试歌曲epochs的频谱图
测试歌曲的原始频谱图
我们可以看到神经网络肯定能够理解和映射音频的许多特征。