06.14 用Python語言模型和LSTM做一個Drake饒舌歌詞生成器

用Python語言模型和LSTM做一個Drake饒舌歌詞生成器

大數據文摘出品

編譯:Fei、倪倪、什錦甜、錢天培

未來AI的主要應用是在建立能夠學習數據然後生成原創內容的網絡。這個想法已經充分應用於在自然語言處理(NLP)領域,這也是AI社區能夠搭建出所謂語言模型的原因:語言模型的前提是學習句子在文章段落中的組成結構,從而生成新的內容。

在這篇文章中,我想嘗試生成與很受歡迎的加拿大說唱歌手Drake(a.k.a. #6god)風格類似的說唱歌詞,這肯定是件很有趣的事兒。

另外,我還想分享一下常規的機器學習項目渠道,因為我發現很多同學想做一些小項目,但不知道該從何處入手。

獲取數據

首先,我們開始蒐集Drake的曲庫,為了節省時間我直接寫了個爬蟲,從網頁metrolyrics.com抓取歌詞。

import urllib.request as urllib2
from bs4 import BeautifulSoup

import pandas as pd
import re
from unidecode import unidecode
quote_page = 'http://metrolyrics.com/{}-lyrics-drake.html'
filename = 'drake-songs.csv'
songs = pd.read_csv(filename)
for index, row in songs.iterrows():
page = urllib2.urlopen(quote_page.format(row['song']))
soup = BeautifulSoup(page, 'html.parser')
verses = soup.find_all('p', attrs={'class': 'verse'})
lyrics = ''
for verse in verses:
text = verse.text.strip()
text = re.sub(r"\\[.*\\]\\n", "", unidecode(text))
if lyrics == '':
lyrics = lyrics + text.replace('\\n', '|-|')
else:
lyrics = lyrics + '|-|' + text.replace('\\n', '|-|')
songs.at[index, 'lyrics'] = lyrics
print('saving {}'.format(row['song']))
songs.head()
print('writing to .csv')
songs.to_csv(filename, sep=',', encoding='utf-8')

我用了一個大家都很熟悉的Python包BeautifulSoup來抓取網頁,這裡參考了一位大牛Justin Yek的教程,我只花了五分鐘就學會了使用。說明一下,上面的代碼中我在循環裡使用了songs這一數據格式,是因為我事先定義了想獲得的歌曲。

教程:

https://medium.freecodecamp.org/how-to-scrape-websites-with-python-and-beautifulsoup-5946935d93fe

用Python語言模型和LSTM做一個Drake饒舌歌詞生成器

用DataFrame存儲了所有的歌曲歌詞

運行爬蟲之後,我就得到了以合適的結構存儲歌詞的csv文件,下一步開始對數據進行預處理並且搭建模型。

模型介紹

現在我們來看看模型是如何生成文本的,這部分你要著重理解,因為這是真正的乾貨。我將先從模型設計和生成歌詞模型中的關鍵組成部分講起,然後,我們就可以直接進入實施階段。

搭建語言模型主要有兩種方法:

1.字符級(Character-leve)模型,

2.詞彙級(Word-level)模型。

這兩者的主要區別在於模型的輸入和輸出,接下來就具體講解一下兩個模型的工作原理。

字符級模型

在字符級模型中,輸入是一連串的字符seed(種子),模型負責預測下一個字符,然後用seed + new_char組合來生成再下一個字符,以此類推。注意,因為我們每次輸入的長度應保持一致,所以實際上在每次迭代輸入時都要丟掉一個字符。我們可以看一個簡單的直觀的例子:

用Python語言模型和LSTM做一個Drake饒舌歌詞生成器

字符級模型生成詞的迭代過程

每次迭代時,模型都是在給定種子字符的基礎上預測下一個最可能生成的字符,或者利用條件概率,即找到概率P(new_char|seed)的最大值,其中new_char是字母表中的任一字母。

在此例中,字符表指所有英文字母和間隔符號的集合。(說明,字母表可以根據你的需要包含不同的字母,主要取決於你生成的語言種類)。

詞彙級模型

詞彙級模型和字符級模型非常相似,但是它用來生成下一個單詞而非字符。這裡舉一個簡單的例子來說明這一點:

用Python語言模型和LSTM做一個Drake饒舌歌詞生成器

圖3. 詞彙級模型生成詞彙的迭代過程

現在在這個模型中,我們以一個詞彙為單位向前尋找下一個詞彙,而非字符。因此,我們想找到概率P(new_word|seed)的最大值,其中new_word是任一詞彙。

這裡要注意的是,這裡我們搜索的範圍比字符級要大得多。字符集模型中,我們只需從字符表中查找大概30個字符,但詞彙級中每次迭代搜索的範圍遠遠大於這個數量,因此每次迭代的運行速度更慢,但既然我們生成的是一整個詞而不只是一個字符,所以也不算太糟糕。

關於詞彙級模型,我最後想說明一點,我們可以通過在數據集中搜索獨特的詞彙來生成更加多樣的詞彙(這一步通常在數據預處理階段進行)。由於詞彙量可以無限大,我們其實有很多提高生成詞彙性能的算法,比如詞嵌入,不過關於這個問題可以再寫一篇文章了。

這篇文章主要關注字符級模型,因為它更易於實施和理解,也更容易轉化為複雜的詞彙級模型。

數據預處理

針對字符級模型,我們將按照以下步驟進行數據預處理:

1.標記字符

對字符級模型而言,輸入應該是基於字符而非字符串的形式。所以,我們首先要將歌詞的每一行轉變成字符的集合。

2.定義字符表

上一步,我們獲得了歌詞中所有可能出現的字符,接下來需要找出所有獨特的字符。由於整個數據集並不大(只有140首歌),簡單起見,我只保留所有英文字母以及一些特殊符號(比如空格),而忽略數字和其他的信息(因為數據集很小,我寧願讓模型少預測一些字符)。

3.創建訓練序列

這裡我們會用到滑動窗口的概念。通過沿著句子拖動一個固定長度的窗口,我們將建立用於訓練的數據序列。下面的這張圖很好地展示了滑動窗口的操作:

用Python語言模型和LSTM做一個Drake饒舌歌詞生成器

圖4. 用滑動窗口獲得輸入/輸出

我們通過每次平移一個字符,得到相應長度為20個字符的模型輸入和長度為1個字符的模型輸出。每次只平移一格的額外好處就是大大擴展了數據集的大小。

4.標註編碼訓練序列

最後,我們不想直接處理原始字符(儘管理論上講每個字符都是一個數字,所以你也可以說ASCII碼已經幫我們為每個字符完成了編碼)。我們要做的是用唯一的數字和每個字符一一對應,這一步就是所謂的標籤編碼。同時,我們要建立兩個非常重要的映射:character-to-index (字符到索引)和index-to-character(索引到字符)。有了這兩個映射,我們就能將字母表中任意的字符編碼成對應的數字,同理,也能將模型輸出的數字索引解碼獲得相應的字符。

5.數據集的獨熱編碼

因為我們用的是分類數據,就是說所有字符都可以被歸為某個類別,所以我們要將字符編碼成輸入列的形式。

當我們完成以上五個步驟以後,基本就大功告成了,接下來只需要搭建和訓練模型。如果你想深入更多細節,以下是五個步驟的代碼供參考。

用Python語言模型和LSTM做一個Drake饒舌歌詞生成器

3.建立模型

我們將用循環神經網絡(RNN),更具體的說是長短期記憶網絡(LSTM),基於前面出現的字符集來預測下一個字符。如果這兩個概念都聽著陌生的話,我也提供了相關概念的快速複習:

RNN快速複習

通常,你看到的網絡就是一個網狀,從很多點匯聚到一個單點輸出。如下圖所示:

用Python語言模型和LSTM做一個Drake饒舌歌詞生成器

圖5. 神經網絡示意圖

這裡的神經網絡是單點輸入,單點輸出。它適用於輸入是不連續的情況,因為輸入的順序不會影響到輸出結果。但是在我們的案例中,輸入字符的順序是非常重要的,因為順序決定了對應的單詞。

而RNN可以接收連續的輸入,同時將前一個節點的輸出作為參數輸入下一個節點,從而解決輸入順序的問題。

用Python語言模型和LSTM做一個Drake饒舌歌詞生成器

圖6. 簡易RNN示意圖

例如,基於序列Tryna_keep_it_simple,提取的下一個字符就應該是 _。這正是我們想讓神經網絡做的。神經網絡的輸入將是 T — > x<1>, r -> x<2>, n -> x<3>... e-> x ,對應輸出字符就是一個空格 y -> _ 。

LSTM快速複習

簡單的RNN網絡仍存在一些問題,它不善於將非常前端的元胞信息傳遞到後端元胞。例如,句子Tryna keep it simple is a struggle for me中最後一個詞me,如果不往回看前面出現了什麼單詞,那麼這個單詞是很難預測準確的(很可能就被預測成了Baka,cat,potato之類)。

而LSTM能夠很好地解決這個問題,它在每個元胞中存儲部分前面發生的事件信息(即前面出現的單詞)。如下圖所示:

用Python語言模型和LSTM做一個Drake饒舌歌詞生成器

圖7. LSTM示意圖,摘自Andrew Ng的深度學習課程

不僅傳遞前一個元胞的輸出a ,同時包含之前元胞輸入信息的c 也作為了下一個元胞的輸入的一部分。這使得LSTM能夠更好地保留上下文的信息,並適用於語言建模的預測。

編程建模

我之前學過一點Keras,所以這次就以Keras為框架編程搭建模型。其實也可以選擇自己搭建模型框架,但這樣會花費更多的時間。

# create sequential network, because we are passing activations
# down the network
model = Sequential()
# add LSTM layer
model.add(LSTM(128, input_shape=(maxlen, len(chars))))
# add Softmax layer to output one character
model.add(Dense(len(chars)))
model.add(Activation('softmax'))
# compile the model and pick the loss and optimizer
model.compile(loss='categorical_crossentropy', optimizer=RMSprop(lr=0.01))
# train the model
model.fit(x, y, batch_size=128, epochs=30)

以上可見,我們搭建了LSTM模型並且使用了批處理,利用數據子集分批進行訓練而不是一次輸入所有的數據,這樣可以稍微提高一點訓練的速度。

4、生成歌詞

訓練完模型,接下來介紹如何生成下一個字符。我們首先要用用戶輸入的簡單字符串作為隨機種子。接著,我們把種子作為網絡的輸入來預測下一個字符,重複這個過程直到我們生成了一些新的歌詞,類似於圖2中所示。

以下是一些生成的歌詞的例子。

注意:這些歌詞都沒被審核過,閱讀時請自行甄別。

用Python語言模型和LSTM做一個Drake饒舌歌詞生成器

用Python語言模型和LSTM做一個Drake饒舌歌詞生成器

用Python語言模型和LSTM做一個Drake饒舌歌詞生成器

用Python語言模型和LSTM做一個Drake饒舌歌詞生成器

用Python語言模型和LSTM做一個Drake饒舌歌詞生成器

你可能會注意到,生成的單詞有的是沒有意義的,這是字符級模型的一個常見問題。這是因為輸入序列經常在單詞的中間被切斷,使得神經網絡模型學習並生成對其輸入而言是有意義,但是我們看來很奇怪的新單詞。

這也是在詞彙級模型中可以解決的問題,但是對於僅以200行代碼建立的模型來說,字符級模型所達到的效果仍然令人印象深刻。

其他應用

在這裡演示的字符級模型的歌詞預測功能可以被擴展到其他更有用的應用上。

例如,可以利用相同的原理對iPhone鍵盤上要輸入的下一個單詞進行預測。

用Python語言模型和LSTM做一個Drake饒舌歌詞生成器

圖8. 鍵盤輸入預測下一個單詞

想象假如你建立一個高準確度的Python語言模型,不但能夠自動填充關鍵詞或者變量名,還可以填充大段的代碼,這將幫助碼農們節省多少時間啊!

你可能也注意到了文中的代碼並不完整,遺漏了一些片段。請到我的Github獲取更多的細節,學習搭建你自己的項目模型。

Github鏈接:

https://github.com/nikolaevra/drake-lyric-generator

相關報道:

https://towardsdatascience.com/generating-drake-rap-lyrics-using-language-models-and-lstms-8725d71b1b12


分享到:


相關文章: