介绍:
在这篇文章中,我们将在最流行的艺术家歌词数据集上训练RNN字符级语言模型。我们将训练一个训练有素的模型,并且采样几首歌曲,将不同风格且不同艺术家的歌词进行有趣的混合。之后,我们将更新我们的模型,使它成为一个字符级的RNN。最后,我们通过钢琴歌曲midi数据集来训练我们的模型。在解决所有这些任务的同时,我们将简要地探讨一些与RNN训练和推理有关的概念,如字符级RNN,有条件的字符级RNN,从RNN采样,通过时间和梯度检查点后向传播。所有的代码和训练模型都是在Pytorch实现你也可以在github上看到。博客文章也可以用jupyter笔记本格式查看,如果你已经熟悉了字符级语言模型和递归神经网络,请随意跳过相应的部分或直接转到结果部分。
字符级语言模型:
在训练过程中,我们只是采取一个序列,并使用除最后一个字符以外的所有字符作为输入,并将第二个字符作为标记数据:groundtruth(见上图; Source)。我们从最简单的模型开始,在忽略所有以前的字母的同时进行预测,然后再改进这个模型,使其只考虑一定数量的以前的字母,最后用这个模型来考虑所有以前的字母预测。
我们的语言模型是在字母层面上定义的。我们将创建一个包含所有英文字符和一些特殊符号(如句点,逗号和行尾符号)的字典。每个字符将被表示为一个热编码的张量。有关字符级模型的更多信息,我推荐此资源。
有了字符,我们现在可以形成字符序列。我们可以通过用一个固定的概率字符表示随机抽样字符甚至生成的句子只是这是最简单的人物级语言模型。这样我们可以计算我们的训练语料库中每个字母的出现概率(字母出现的次数除以我们的数据集的大小),并使用这些概率随机抽样字母。这个模型比较好,但是它忽略了每个字母的相对位置。当你阅读单词时,你都隐含地使用了你通过阅读其他文本学习的一些规则:例如,你从单词中读出的每个附加字母,空格字符的概率增加,或者在字母“r”之后的任何辅音的概率低,因为它通常跟随元音。有很多类似的规则,我们希望我们的模型能够从数据中学习。
首先我们要对模型做一个小的改进,让每个字母的概率只取决于以前发生的字母(马尔可夫假设)。这是一个马尔可夫链模型(也可以尝试这些交互式可视化)。我们还可以估算的概率分布从我们的训练数据集。这个模型是有限的,因为在大多数情况下,当前字母的概率不仅取决于前一个字母。
为了得到更好的模型,我们需要用到RNN,所以我们这下一节我们专门讨论RNN。
复发神经网络(RNN):
递归神经网络是用于处理顺序数据的一种神经网络,与前馈神经网络不同的是,RNN可以使用其内部存储器来处理任意输入序列。由于任意大小的输入序列,它们被简洁地描述为一个具有周期的图形(参见图片:来源)。但是如果输入序列的大小是已知的,它们可以“展开”。
为了更详细地介绍RNN,我向读者介绍以下资源。
歌词数据集:
我们为我们的试验选择了55000 +歌词Kaggle数据集,其中包含最新的艺术家的歌词。它作为一个pandas文件存储,我们写了一个python包装,以便能够将其用于训练目的。你需要自己下载,以便能够使用我们的代码。
为了能够更好地解释结果,我选择了一些我或多或少熟悉的艺术家:
artists = [
'ABBA',
'Ace Of Base',
'Aerosmith',
'Avril Lavigne',
'Backstreet Boys',
'Bob Marley',
'Bon Jovi',
'Britney Spears',
'Bruno Mars',
'Coldplay',
'Def Leppard',
'Depeche Mode',
'Ed Sheeran',
'Elton John',
'Elvis Presley',
'Eminem',
'Enrique Iglesias',
'Evanescence',
'Fall Out Boy',
'Foo Fighters',
'Green Day',
'HIM',
'Imagine Dragons',
'Incubus',
'Jimi Hendrix',
'Justin Bieber',
'Justin Timberlake',
'Kanye West',
'Katy Perry',
'The Killers',
'Kiss',
'Lady Gaga',
'Lana Del Rey',
'Linkin Park',
'Madonna',
'Marilyn Manson',
'Maroon 5',
'Metallica',
'Michael Bolton',
'Michael Jackson',
'Miley Cyrus',
'Nickelback',
'Nightwish',
'Nirvana',
'Oasis',
'Offspring',
'One Direction',
'Ozzy Osbourne',
'P!nk',
'Queen',
'Radiohead',
'Red Hot Chili Peppers',
'Rihanna',
'Robbie Williams',
'Rolling Stones',
'Roxette',
'Scorpions',
'Snoop Dogg',
'Sting',
'The Script',
'U2',
'Weezer',
'Yellowcard',
'ZZ Top']
训练无条件的字符级语言模型:
我们的第一个实验是在整个语料库上训练我们的字符级语言模型RNN。我们在训练时没有考虑到艺术家的信息。
从RNN中采样:
在训练完模型之后,我试着抽出几首歌。在每一步我们的RNN将输出logits,或者我们可以直接使用Gumble-Max技巧和直接使用logits的样本。
关于采样的一个有趣的事情是,我们可以自己部分定义输入序列及其他初始条件。例如,我们可以采样以“Why”开头的歌曲:
Why do you have to leave me?
I think I know I'm not the only one
I don't know if I'm gonna stay awake
I don't know why I go along
I don't know why I can't go on
I don't know why I don't know
I don't know why I don't know
I don't know why I keep on dreaming of you
我们来看一下以“Well”开头的歌曲:
Well, I was a real good time
I was a rolling stone
I was a rock and roller
Well, I never had a rock and roll
There were times I had to do it
I had a feeling that I was found
I was the one who had to go
采样时使用的“温度”参数来控制采样过程的随机性。当这个参数接近零时,采样等于argmax,当它接近无限时,采样等同于从均匀分布采样:
让我们尝试增加更多:
Why - won't we grow up naked?
We went quietly what we would've still give
That girl you walked before our bedroom room
I see your mind is so small to a freak
Stretching for a cold white-heart of crashing
Truth in the universal daughter
I lose more and more hard
I love you anytime at all
Ah come let your help remind me
Now I've wanted waste and never noticed
I swear I saw you today
You needed to get by
But you sold a hurricane
Well out whispered in store
训练有条件的字符级语言模型:
想象一下,如果我们能够以某个特定艺术家的风格生成歌词。这时我们需要改变我们的模型,以便在训练期间使用这些信息。我们将通过为我们的RNN增加一个额外的输入来做到这一点。到目前为止,我们的RNN模型只接受每步包含一个热点编码字符的张量。
对我们的模型的改进将是非常简单的:我们将会拥有一个代表艺术家的热编码张量。所以每一步RNN都会接受一个张量,这个张量是代表艺术家的连续张量组成的。看这里了解更多。
从条件语言模型RNN抽样:
训练结束后,我们抽取了一些艺术家的歌曲。
Him:
My fears
And the moment don't make me sing
So free from you
The pain you love me yeah
Whatever caused the warmth
You smile you're happy
You sit away
You say it's all in vain
ABBA:
Oh, my love it makes me close a thing
You've been heard, I must have waited
I hear you
So I say
Thank you for the music, that makes me cry
And you moving my bad as me, ah-hang wind in the hell
I was meant to be with you, I'll never be playing up
Bob Marley:
Mercy on judgment, we got so much
Alcohol, cry, cry, cry
Why don't try to find our own
I want to know, Lord, I wanna give you
Just saving it, learned
Is there any more?
All that damage done
That's all reason, don't worry
Need a hammer
I need you more and more
Coldplay:
Look at the stars
Into life matter where you lay
Saying no doubt
I don't want to fly
In my dreams and fight today
I will fall for you
All I know
And I want you to stay
Into the night
I want to live waiting
With my love and always
Have I wouldn't wasted
Would it hurt you
Kanye West:
I'm everywhere for you
The way that it couldn't stop
I mean it too late and love I made in the world
I told you so I took the studs full cold-stop
The hardest stressed growin'
The hustler raisin' on my tears
I know I'm true, one of your love
看起来很酷,但我们没有跟踪验证的准确性,所以那些样本可能已经被我们的rnn记住了。一个更好的方法是选择一个模型,在训练期间给出最好的验证分数(下一节我们将用这种方式进行训练)。我也注意到了一件有趣的事情:当你想用一个指定的起始字符串进行采样时,无条件模型通常更好地表现出来。我的直觉是,当从一个具有特定起始字符串的条件模型中抽样时,我们实际上把两个条件放在我们的模型开始字符串和一个艺术家之间。而且我们没有足够的数据来模拟这个条件分布(每个歌手的歌曲数量相对有限)。
我们正在使用的代码和模型对每个人来说都是可用的,即使没有GPU,也可以从我们训练好的模型中采样歌曲,因为它的计算量不大。
Midi数据集:
即使对于一个不熟悉音乐理论的人来说,这种表现方式也很直观,容易理解。每行代表一个音高:顶行代表低频音高,底行代表高音。另外,我们有一个代表时间的横轴。所以如果我们在一定时间内播放一定音调的声音,我们会看到一条水平线。总体而言,这与YouTube上的钢琴教程非常相似。
训练高音水平的钢琴音乐模型:
在开始训练之前,我们不得不调整我们的语言模型损失,以考虑我们在前面讨论过的不同的输入。在语言模型中,我们在每个时间步上都有一热编码张量(字符)作为输入,而作为输出(预测的下一个字符)我们有一热编码张量。由于我们不得不为预测的下一个字符做出一个单独的选择,我们将使用 交叉熵损失。
在做出上述改变之后,我们训练了我们的模型。在下一节中,我们将执行采样并检查结果。
从音调水平的RNN采样:
在优化的早期阶段,我们采样的钢琴卷筒:
你可以看到我们的模型开始学习一个在我们的数据集的歌曲中很常见的模式:每首歌曲由两个不同的部分组成。第一部分包含一系列独立播放的节奏,非常易辨,通常是旋律。如果你看着采样的钢琴卷,这部分可以清楚地看到在底部。如果你也看看我们的钢琴卷轴的顶部,我们可以看到一组通常一起演奏的音高,这是伴随着旋律的和声或和音的进行。
训练结束后,从我们的模型中抽取样本开始看起来像这样:
正如你所看到的,它们开始看起来更像我们在前面章节中展示的真相钢琴卷的图片。
训练结束后,我们抽取歌曲进行分析。我们有一个有趣的介绍样本,而另一个样本具有很好的风格转换。这里是第一个和第二个。你也可以在这里找到整个播放列表。
序列长度和相关问题:
现在让我们从GPU内存消耗和速度的角度来看待我们的问题。
我们通过批量处理我们的序列大大加快了计算速度。同时,随着序列变长(取决于数据集),我们的最大批量开始减少。为什么是这种情况?当我们使用反向传播来计算梯度时,我们需要存储所有对内存消耗贡献最大的中间活动。随着我们的序列变长,我们需要存储的更多,因此,我们可以在我们的批次中适用更少的例子。
有些时候,我们要么用很长的序列来工作,要么增加批量,或者你只需要一个具有少量内存的GPU。在这种情况下,有多种可能的解决方案来减少内存消耗,但是我只提两种解决方案,这些解决方案会有不同的取舍。
首先是截断后向传播。这个想法是将整个序列拆分成子序列,并把它们分成不同的批次,除了我们按照拆分的顺序处理这些批次,每一个下一批次都使用前一批次的隐藏状态作为初始隐藏状态。我还提供了这种方法的实施,以便你可以更好地理解。这种方法显然不是处理整个序列的最好替代方法,但它使更新更频繁并消耗更少的内存。另一方面,我们有可能无法捕捉到超过一个子序列长度的长期依赖关系。
第二个是梯度检查点。这种方法使我们有可能在使用更少的内存的同时,在整个序列上训练我们的模型,以执行更多的计算。如果你还记得,之前我们提到训练中最多的记忆被激活函数所占据。梯度检查点等思想是只储存每个训练之后的激活函数并重新计算未保存的激活函数。这个方法已经在Tensorflow 中实现,并在Pytorch 中实现。
本文由北邮@爱可可-爱生活老师推荐,阿里云云栖社区组织翻译。
文章原标题《Learning to generate lyrics and music with Recurrent Neural Networks》,作者:Daniil Pakhomov,译者:虎说八道,审阅:。
文章为简译,更为详细的内容,请查看原文