我們分析了最流行的歌詞,教你用RNN寫詞編曲(附代碼)
此文展示了基于RNN的生成模型在歌詞和鋼琴音樂上的應(yīng)用。
介紹
在這篇博文中,我們將在歌詞數(shù)據(jù)集上訓(xùn)練RNN字符級(jí)語言模型,數(shù)據(jù)集來自最受歡迎以及最新發(fā)布的藝術(shù)家的作品。模型訓(xùn)練好之后,我們會(huì)選出幾首歌曲,這些歌曲將會(huì)是不同風(fēng)格的不同藝術(shù)家的有趣混合。之后,我們將更新模型使之成為一個(gè)條件字符級(jí)RNN,使我們能夠從藝術(shù)家的歌曲中采樣。最后,我們通過對(duì)鋼琴曲的midi數(shù)據(jù)集的訓(xùn)練來總結(jié)。
在解決這些任務(wù)的同時(shí),我們將簡(jiǎn)要地探討一些有關(guān)RNN訓(xùn)練和推斷的有趣概念,如字符級(jí)RNN,條件字符級(jí)RNN,從RNN采樣,經(jīng)過時(shí)間截?cái)嗟姆聪騻鞑ズ吞荻葯z查點(diǎn)。
所有的代碼和訓(xùn)練模型都已在 github 上開源,并通過 PyTorch 實(shí)現(xiàn)。這篇博文同樣也可用jupyter notebook 閱讀。如果你已經(jīng)熟悉字符級(jí)語言模型和循環(huán)神經(jīng)網(wǎng)絡(luò),可以隨意跳過各個(gè)部分或直接進(jìn)入結(jié)果部分。
字符級(jí)語言模型

在選擇模型前,讓我們仔細(xì)看看我們的任務(wù)。基于現(xiàn)有的字母和所有之前的字母,我們將預(yù)測(cè)下一個(gè)字符。在訓(xùn)練過程中,我們只使用一個(gè)序列,除了最后一個(gè)字符外,序列中的其他字符將作為輸入,并從第二個(gè)字符開始,作為groundtruth(見上圖:源)。我們將從最簡(jiǎn)單的模型開始,在進(jìn)行預(yù)測(cè)時(shí)忽略所有前面的字符,然后改善這個(gè)模型使其只考慮一定數(shù)量的前面的字符,最后得到一個(gè)考慮所有前面的字符的模型。
我們的語言模型定義在字符級(jí)別。我們將創(chuàng)建一個(gè)包含所有英文字符和一些特殊符號(hào)(如句號(hào),逗號(hào)和行尾符號(hào))的字典。每個(gè)字符將被表示為一個(gè)獨(dú)熱編碼的張量。有關(guān)字符級(jí)模型和示例的更多信息,推薦此資源。
有了字符后,我們可以生成字符序列。即使是現(xiàn)在,也可以通過隨機(jī)抽樣字符和固定概率p(any letter)=1和字典大小p(any letter)=1dictionary size來生成句子。這是最簡(jiǎn)單的字符級(jí)語言模型。可以做得更好嗎?當(dāng)然可以,我們可以從訓(xùn)練語料庫中計(jì)算每個(gè)字母的出現(xiàn)概率(一個(gè)字母出現(xiàn)的次數(shù)除以我們的數(shù)據(jù)集的大小),并且用這些概率隨機(jī)抽樣。這個(gè)模型更好但是它完全忽略了每個(gè)字母的相對(duì)位置。
舉個(gè)例子,注意你是如何閱讀單詞的:你從第一個(gè)字母開始,這通常很難預(yù)測(cè),但是當(dāng)你到達(dá)一個(gè)單詞的末尾時(shí),你有時(shí)會(huì)猜到下一個(gè)字母。當(dāng)你閱讀任何單詞時(shí),你都隱含地使用了一些規(guī)則,通過閱讀其他文本學(xué)習(xí):例如,你從單詞中讀到的每一個(gè)額外的字母,空格字符的概率就會(huì)增加(真正很長(zhǎng)的單詞是罕見的),或者在字母"r"之后的出現(xiàn)輔音的概率就會(huì)變低,因?yàn)樗ǔ8S元音。有很多類似的規(guī)則,我們希望我們的模型能夠從數(shù)據(jù)中學(xué)習(xí)。為了讓我們的模型有機(jī)會(huì)學(xué)習(xí)這些規(guī)則,我們需要擴(kuò)展它。
讓我們對(duì)模型做一個(gè)小的逐步改進(jìn),讓每個(gè)字母的概率只取決于以前出現(xiàn)的字母(馬爾科夫假設(shè))。所以,基本上我們會(huì)有p(current letter|previous letter)。這是一個(gè)馬爾科夫鏈模型(如果你不熟悉,也可以嘗試這些交互式可視化)。我們還可以從訓(xùn)練數(shù)據(jù)集中估計(jì)概率分布p(current letter|previous letter)。但這個(gè)模型是有限的,因?yàn)樵诖蠖鄶?shù)情況下,當(dāng)前字母的概率不僅取決于前一個(gè)字母。
我們想要建模的其實(shí)是p(current letter|all previous letters)。起初,這個(gè)任務(wù)看起來很棘手,因?yàn)榍懊娴淖帜笖?shù)量是可變的,在長(zhǎng)序列的情況下它可能變得非常大。結(jié)果表明,在一定程度上,利用共享權(quán)重和固定大小的隱藏狀態(tài),循環(huán)神經(jīng)網(wǎng)絡(luò)可以解決這個(gè)問題,因此引出下一個(gè)討論RNNs的部分。
循環(huán)神經(jīng)網(wǎng)絡(luò)

循環(huán)神經(jīng)網(wǎng)絡(luò)是一族用于處理序列數(shù)據(jù)的神經(jīng)網(wǎng)絡(luò),與前饋神經(jīng)網(wǎng)絡(luò)不同,RNNs可以使用其內(nèi)部存儲(chǔ)器來處理任意輸入序列。
由于任意大小的輸入序列,它們被簡(jiǎn)潔地描述為一個(gè)具有循環(huán)周期的圖(見上圖:源)。但是如果已知輸入序列的大小,則可以"展開"。定義一個(gè)非線性映射,從當(dāng)前輸入xt 和先前隱藏狀態(tài) st−1 到輸出ot 和隱藏狀態(tài) st。隱藏狀態(tài)大小具有預(yù)定義的大小,存儲(chǔ)在每一步更新的特征,并影響映射的結(jié)果。
現(xiàn)在,將字符級(jí)語言模型的前一張圖片與已折疊的RNN圖片對(duì)齊,以了解我們?nèi)绾问褂肦NN模型來學(xué)習(xí)字符級(jí)語言模型。
雖然圖片描繪了Vanilla RNN,但是我們?cè)诠ぷ髦惺褂肔STM,因?yàn)樗菀子?xùn)練,通常可以獲得更好的結(jié)果。
為了更詳細(xì)地介紹RNNs,推薦以下資源。
歌詞數(shù)據(jù)集
在實(shí)驗(yàn)中,我們選擇了55000+ Song Lyrics Kaggle dataset,其中包含了很多近期的藝術(shù)家和更多經(jīng)典的好作品。它存儲(chǔ)為pandas文件,并用python包裝,以便用于培訓(xùn)。為了使用我們的代碼,你需要自行下載。
為了能夠更好地解釋結(jié)果,我選擇了一些我稍微熟悉的藝術(shù)家:
- 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']
訓(xùn)練無條件的字符級(jí)語言模型
第一個(gè)實(shí)驗(yàn)是在整個(gè)語料庫上訓(xùn)練我們的字符級(jí)語言模型RNN,在訓(xùn)練時(shí)沒有考慮藝術(shù)家的信息。
從RNN采樣
在訓(xùn)練完模型之后,我們?cè)囍槌鰩资赘琛;旧希琑NN每一步都會(huì)輸出logits,我們可以利用softmax函數(shù)從分布中取樣。或者可以直接使用Gumble-Max技巧采樣,這和直接使用logits是等價(jià)的。
抽樣的一個(gè)有趣之處是,我們可以對(duì)輸入序列進(jìn)行部分定義,并在初始條件下開始采樣。舉個(gè)例子,我們采樣以"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
采樣過程中使用的"temperature"參數(shù)控制采樣過程的隨機(jī)性。當(dāng)這個(gè)參數(shù)接近零時(shí),采樣等于argmax,當(dāng)它接近無窮時(shí),采樣等同于均勻分布的采樣。可以看一下相關(guān)論文中的圖(Jang et al):

當(dāng)τ=1時(shí),分布不受影響。如果我們減少 τ,分布變得更加明顯,意味著有更大概率質(zhì)量的值會(huì)增加。當(dāng)τ 接近零時(shí),采樣將相當(dāng)于argmax,因?yàn)樵撝档母怕式咏?。當(dāng)我們開始增加τ 時(shí),分布變得越來越均勻。
之前的樣本是在"temperature"參數(shù)等于0.5的情況下生成的,讓我們看看當(dāng)我們?cè)黾拥?.0時(shí)會(huì)發(fā)生什么:
- Why can't we drop out of time?
- We were born for words to see.
- Won't you love this. You're still so amazing.
- This could be that down on Sunday Time.
- Oh, Caroline, a lady floor.
- I thought of love, oh baby.
讓我們進(jìn)一步增加:
- 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
我們?yōu)槭裁床恢苯右淮涡栽黾幽?
那么,你可以看到這樣的趨勢(shì):當(dāng)我們?cè)黾?quot;temperature"時(shí),采樣的句子變得越來越隨機(jī)。
訓(xùn)練有條件的字符級(jí)語言模型
想象一下,如果我們能夠以某種特定藝術(shù)家的風(fēng)格生成歌詞。讓我們改變我們的模型,以便在訓(xùn)練期間使用這些信息。
我們將通過為我們的RNN增加一個(gè)額外的輸入來做到這一點(diǎn)。到目前為止,我們的RNN模型只接受每步包含one-hot編碼字符的張量。
對(duì)我們的模型的擴(kuò)展將是非常簡(jiǎn)單的:我們將擁有和額外的一個(gè)熱點(diǎn)編碼張量來代表藝術(shù)家。所以每一步RNN都會(huì)接受一個(gè)張量,這個(gè)張量由代表人物和藝術(shù)家的連續(xù)張量組成。更多信息可以點(diǎn)擊此處查看
從有條件語言模型RNN中抽樣
訓(xùn)練結(jié)束后,我們抽取了一些藝術(shù)家的歌曲,部分結(jié)果如下所示:
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
似乎真的有可能,尤其是使用了痛苦這個(gè)詞,這在藝術(shù)家的歌詞中是很常見的事實(shí)。
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
看起來很酷,但請(qǐng)記住,我們沒有跟蹤驗(yàn)證的準(zhǔn)確性,所以一些樣本行可能已經(jīng)被rnn模型記住了。一個(gè)更好的方法是選擇一個(gè)模型,在訓(xùn)練期間給出最好的驗(yàn)證分?jǐn)?shù)(見下一節(jié)我們用這種方式進(jìn)行訓(xùn)練的代碼)。
我們也注意到了一件有趣的事情:當(dāng)你想用一個(gè)指定的起始字符串進(jìn)行采樣時(shí),無條件模型通常更好地表現(xiàn)出來。我們的直覺是,當(dāng)從一個(gè)具有特定起始字符串的條件模型中抽樣時(shí),我們實(shí)際上把兩個(gè)條件放在我們的模型開始字符串和一個(gè)藝術(shù)家之間。而且我們沒有足夠的數(shù)據(jù)來模擬這個(gè)條件分布(每個(gè)歌手的歌曲數(shù)量相對(duì)有限)。
我們正在使代碼和模型可用,并且即使沒有g(shù)pu,也可以從我們訓(xùn)練好的模型中采樣歌曲,因?yàn)樗?jì)算量并不大。
Midi 數(shù)據(jù)集
接下來,我們將使用由大約700首鋼琴歌曲組成的小型midi數(shù)據(jù)集。我們使用了諾丁漢鋼琴數(shù)據(jù)集(僅限于訓(xùn)練分割)。
任何MIDI文件都可以轉(zhuǎn)換為鋼琴鍵軸,這只是一個(gè)時(shí)頻矩陣,其中每一行是不同的MIDI音高,每一列是不同的時(shí)間片。因此,我們數(shù)據(jù)集中的每首鋼琴曲都會(huì)被表示成一個(gè)大小的矩陣88×song_length,88 是鋼琴音調(diào)的個(gè)數(shù)。下圖是一個(gè)鋼琴鍵軸矩陣的例子:

即使對(duì)于一個(gè)不熟悉音樂理論的人來說,這種表現(xiàn)方式也很直觀,容易理解。每行代表一個(gè)音高:高處的行代表低頻部分,低處的行代表高頻部分。另外,我們有一個(gè)代表時(shí)間的橫軸。所以如果我們?cè)谝欢〞r(shí)間內(nèi)播放一定音調(diào)的聲音,我們會(huì)看到一條水平線。總而言之,這與YouTube上的鋼琴教程非常相似。
現(xiàn)在,我們來看看字符級(jí)模型和新任務(wù)之間的相似之處。在目前的情況下,給定以前播放過的所有音調(diào), 我們將預(yù)測(cè)下一個(gè)時(shí)間步將要播放的音調(diào)。所以,如果你看一下鋼琴鍵軸的圖,每一列代表某種音樂字符,給定所有以前的音樂字符,預(yù)測(cè)下一個(gè)音樂字符。我們注意依一下文字字符與音樂字符的區(qū)別。回憶一下,語言模型中的每個(gè)字符都是由one-hot向量表示的(意思是我向量中只有一個(gè)值是1,其他都是0)。對(duì)于音樂字符,可以一次按下多個(gè)鍵(因?yàn)槲覀冋谔幚韽?fù)音數(shù)據(jù)集)。在這種情況下,每個(gè)時(shí)間步將由一個(gè)可以包含多個(gè)1的向量表示。
培養(yǎng)音高水平的鋼琴音樂模型
在開始訓(xùn)練之前,根據(jù)在前面討論過的不同的輸入,需要調(diào)整我們用于語言模型的損失函數(shù)。在語言模型中,我們?cè)诿總€(gè)時(shí)間步上都有one-hot的編碼張量(字符級(jí))作為輸入,用一個(gè)one-hot的編碼張量作為輸出(預(yù)測(cè)的下一個(gè)字符)。由于預(yù)測(cè)的下一個(gè)字符時(shí)使用獨(dú)占,我們使用交叉熵?fù)p失。
但是現(xiàn)在我們的模型輸出一個(gè)不再是one-hot編碼的矢量(可以按多個(gè)鍵)。當(dāng)然,我們可以將所有可能的按鍵組合作為一個(gè)單獨(dú)的類來處理,但是這是比較難做的。相反,我們將輸出向量的每個(gè)元素作為一個(gè)二元變量(1表示正在按鍵,0表示沒有按鍵)。我們將為輸出向量的每個(gè)元素定義一個(gè)單獨(dú)的損失為二叉交叉熵。而我們的最終損失將是求這些二元交叉熵的平均和。可以閱讀代碼以獲得更好的理解。
按照上述的修改后,訓(xùn)練模型。在下一節(jié)中,我們將執(zhí)行采樣并檢查結(jié)果。
從音調(diào)水平的RNN采樣
在優(yōu)化的早期階段,我們采樣了鋼琴鍵軸:

可以看到,模型正在開始學(xué)習(xí)數(shù)據(jù)集中歌曲常見的一種常見模式:1首歌曲由2個(gè)不同的部分組成。第一部分包含一系列獨(dú)立播放的節(jié)奏,非常易辨,通常是可唱(也稱為旋律)。如果看著采樣的鋼琴鍵軸圖,這部分在底部。如果觀察鋼琴卷軸的頂部,可以看到一組通常一起演奏的音高 - 這是伴隨著旋律的和聲或和音(在整個(gè)歌曲中一起播放的部分)的進(jìn)行。
訓(xùn)練結(jié)束后,從模型中抽取樣本如下圖所示:

如圖所示,他們和前面章節(jié)中的所看到的真實(shí)情況相似。
訓(xùn)練結(jié)束后,抽取歌曲進(jìn)行分析。這里有一個(gè)有趣的介紹樣本。而另一個(gè)樣本,是具有很好的風(fēng)格轉(zhuǎn)換。同時(shí),我們生成了一些低速參數(shù)的例子,它們導(dǎo)致歌曲的速度慢了:這里是第一個(gè)和第二個(gè)。可以在這里找到整個(gè)播放列表。
序列長(zhǎng)度和相關(guān)問題
現(xiàn)在讓我們從GPU內(nèi)存消耗和速度的角度來看待我們的問題。
我們通過批量處理我們的序列大大加快了計(jì)算速度。同時(shí),隨著序列變長(zhǎng)(取決于數(shù)據(jù)集),我們的最大批量開始減少。為什么是這種情況?當(dāng)我們使用反向傳播來計(jì)算梯度時(shí),我們需要存儲(chǔ)所有對(duì)內(nèi)存消耗貢獻(xiàn)最大的中間激活量。隨著我們的序列變長(zhǎng),我們需要存儲(chǔ)更多的激活量,因此,我們可以用更少的樣本在批中。
有時(shí)候,我們需要用很長(zhǎng)的序列來工作,或者增加批的大小,而你只有1個(gè)有少量?jī)?nèi)存的GPU。在這種情況下,有多種可能的解決方案來減少內(nèi)存消耗,這里,我們只提到兩種解決方案,它們之間需要取舍。
首先是一個(gè)截?cái)嗟暮笙騻鞑ァ_@個(gè)想法是將整個(gè)序列拆分成子序列,并把它們分成不同的批,除了我們按照拆分的順序處理這些批,每一個(gè)下一批都使用前一批的隱藏狀態(tài)作為初始隱藏狀態(tài)。我們提供了這種方法的實(shí)現(xiàn),以便能更好地理解。這種方法顯然不是處理整個(gè)序列的精確等價(jià),但它使更新更加頻繁,同時(shí)消耗更少的內(nèi)存。另一方面,我們有可能無法捕捉超過一個(gè)子序列的長(zhǎng)期依賴關(guān)系。
第二個(gè)是梯度檢查點(diǎn)。這種方法使我們有可能在使用更少內(nèi)存的同時(shí),在整個(gè)序列上訓(xùn)練我們的模型,以執(zhí)行更多的計(jì)算。回憶,之前我們提到過訓(xùn)練中的大部分內(nèi)存資源是被激活量使用。梯度檢查點(diǎn)的思想包括僅存儲(chǔ)每個(gè)第n個(gè)激活量,并在稍后重新計(jì)算未保存的激活。這個(gè)方法已經(jīng)在Tensorflow和Pytorch中實(shí)現(xiàn)。
結(jié)論和未來的工作
在我們的工作中,我們訓(xùn)練了簡(jiǎn)單的文本生成模型,擴(kuò)展了模型以處理復(fù)調(diào)音樂,簡(jiǎn)要介紹了采樣如何工作以及溫度參數(shù)如何影響我們的文本和音樂樣本 - 低溫提供了更穩(wěn)定的結(jié)果,而高溫增加了更多的隨機(jī)性這有時(shí)會(huì)產(chǎn)生非常有趣的樣本。
未來的工作可以包括兩個(gè)方向 - 用訓(xùn)練好的模型在更多的應(yīng)用或更深入的分析上。例如,可以將相同的模型應(yīng)用于Spotify收聽歷史記錄。在訓(xùn)練完收聽歷史數(shù)據(jù)后,可以給它一段前一小時(shí)左右收聽的歌曲序列,并在當(dāng)天余下時(shí)間為您播放一個(gè)播放列表。那么,也可以為你的瀏覽歷史做同樣的事情,這將是一個(gè)很酷的工具來分析你的瀏覽行為模式。在進(jìn)行不同的活動(dòng)(在健身房鍛煉,在辦公室工作,睡覺)時(shí),從手機(jī)中獲取加速度計(jì)和陀螺儀數(shù)據(jù),并學(xué)習(xí)分類這些活動(dòng)階段。之后,您可以根據(jù)自己的活動(dòng)自動(dòng)更改音樂播放列表(睡眠 - 冷靜的音樂,在健身房鍛煉 - 高強(qiáng)度的音樂)。在醫(yī)學(xué)應(yīng)用方面,模型可以應(yīng)用于基于脈搏和其他數(shù)據(jù)檢測(cè)心臟問題,類似于這項(xiàng)工作。
分析在為音樂生成而訓(xùn)練的RNN中的神經(jīng)元激勵(lì)是非常有趣的,鏈接在這里。看模型是否隱含地學(xué)習(xí)了一些簡(jiǎn)單的音樂概念(就像我們對(duì)和聲和旋律的討論)。 RNN的隱藏表示可以用來聚集我們的音樂數(shù)據(jù)集以找到相似的歌曲。
讓我們從我們無條件的模型中抽取最后一首歌詞來結(jié)束這篇文章:D:
- The story ends
- The sound of the blue
- The tears were shining
- The story of my life
- I still believe
- The story of my life