本文為你展示,如何使用 fasttext 詞嵌入預訓練模型和循環(huán)神經(jīng)網(wǎng)絡(RNN), 在 Keras 深度學習框架上對中文評論信息進行情感分類。 疑問回顧一下,之前咱們講了很多關于中文文本分類的內(nèi)容。 你現(xiàn)在應該已經(jīng)知道如何對中文文本進行分詞了。 你也已經(jīng)學習過,如何利用經(jīng)典的機器學習方法,對分詞后的中文文本,做分類。 你還學習過,如何用詞嵌入預訓練模型,以向量,而不是一個簡單的索引數(shù)值,來代表詞語,從而讓中文詞語的表征包含語義級別的信息。 但是,好像還差了點兒什么。 對,基于深度學習的中文文本分類方法,老師是不是忘了講? 其實沒有。 我一直惦記著,把這個重要的知識點,給你詳細講解一下。但是之前這里面一直有一條鴻溝,那就是循環(huán)神經(jīng)網(wǎng)絡(Recurrent Neural Network, RNN)。 如果你不知道 RNN 是怎么回事兒,你就很難理解文本作為序列,是如何被深度學習模型來處理的。 好在,我已經(jīng)為你做了視頻教程,用手繪的方式,給你講了這一部分。 既然現(xiàn)在這道鴻溝,已被跨越了。本文咱們就來嘗試,把之前學過的知識點整合在一起,用 Python 和 Keras 深度學習框架,對中文文本嘗試分類。 數(shù)據(jù)為了對比的便捷,咱們這次用的,還是《如何用Python和機器學習訓練中文文本情感分類模型?》一文中采用過的某商戶的點評數(shù)據(jù)。 我把它放在了一個 github repo 中,供你使用。 請點擊這個鏈接,訪問咱們的代碼和數(shù)據(jù)。 我們的數(shù)據(jù)就是其中的 每一行是一條評論。評論內(nèi)容和情感間,用逗號分隔。 1 代表正向情感,0 代表負面情感。 環(huán)境要運行深度學習,你需要有 GPU 或者 TPU 的支持。 我知道,它們不便宜。 好在,Google 為咱們提供了免費的云端運行環(huán)境,叫做 Google Colab 。我曾經(jīng)在《如何免費云端運行Python深度學習框架?》一文中,為你介紹過它?,F(xiàn)在,它不止支持 GPU 了,還包含了 TPU 的選項。 注意,請使用 Google Chrome 瀏覽器來完成以下操作。 因為你需要安裝一個瀏覽器插件插件,叫做 Colaboratory ,它是 Google 自家的插件,只能在 Chrome 瀏覽器中,才能運行。 點擊這個鏈接,安裝插件。 把它添加到 Google Chrome 之后,你會在瀏覽器的擴展工具欄里面,看見下圖中間的圖標: 安裝它做什么用? 它的好處,是讓你可以直接把看到的 Github 源代碼,一鍵挪到 Google Colab 深度學習環(huán)境中來使用。 回到本范例的github repo 主頁面,打開其中的 然后,點擊剛剛安裝的 Colaboratory 擴展圖標。Google Chrome 會自動幫你開啟 Google Colab,并且裝載這個 ipynb 文件。 點擊菜單欄里面的“代碼執(zhí)行程序”,選擇“更改運行時類型”。 在出現(xiàn)的對話框中,確認選項如下圖所示。 點擊“保存”即可。 下面,你就可以依次執(zhí)行每一個代碼段落了。 注意第一次執(zhí)行的時候,可能會有警告提示。 出現(xiàn)上面這個警告的時候,點擊“仍然運行”就可以繼續(xù)了。 如果再次出現(xiàn)警告提示,反勾選“在運行前充值所有代碼執(zhí)行程序”選項,再次點擊“仍然運行”即可。 環(huán)境準備好了,下面我們來一步步運行代碼。 預處理首先,我們準備好 Pandas ,用來讀取數(shù)據(jù)。 import pandas as pd 我們從前文介紹的github repo里面,下載代碼和數(shù)據(jù)。 !git clone https://github.com/wshuyi/demo-chinese-text-classification-lstm-keras.git 下面,我們調(diào)用 pathlib 模塊,以便使用路徑信息。 from pathlib import Path 我們定義自己要使用的代碼和數(shù)據(jù)文件夾。 mypath = Path('demo-chinese-text-classification-lstm-keras') 下面,從這個文件夾里,把數(shù)據(jù)文件打開。 df = pd.read_csv(mypath/'dianping.csv') 看看頭幾行數(shù)據(jù): df.head() 讀取正確,下面我們來進行分詞。 我們先把結巴分詞安裝上。 !pip install jieba 安裝好之后,導入分詞模塊。 import jieba 對每一條評論,都進行切分: df['text'] = df.comment.apply(lambda x: ' '.join(jieba.cut(x))) 因為一共只有2000條數(shù)據(jù),所以應該很快完成。 Building prefix dict from the default dictionary ... 再看看此時的前幾行數(shù)據(jù)。 df.head() 如圖所示, 我們舍棄掉原始評論文本,只保留目前的分詞結果,以及對應的情感標記。 df = df[['text', 'sentiment']] 看看前幾行: df.head() 好了,下面我們讀入一些 Keras 和 Numpy 模塊,為后面的預處理做準備: from keras.preprocessing.text import Tokenizer 系統(tǒng)提示我們,使用的后端框架,是 Tensorflow 。 Using TensorFlow backend. 下面我們要設置一下,每一條評論,保留多少個單詞。當然,這里實際上是指包括標點符號在內(nèi)的“記號”(token)數(shù)量。我們決定保留 100 個。 然后我們指定,全局字典里面,一共保留多少個單詞。我們設置為 10000 個。 maxlen = 100 下面的幾條語句,會自動幫助我們,把分詞之后的評論信息,轉換成為一系列的數(shù)字組成的序列。 tokenizer = Tokenizer(num_words=max_words) 看看轉換后的數(shù)據(jù)類型。 type(sequences) 顯示為: list 可見, 我們看看第一條數(shù)據(jù)是什么。 sequences[:1] 評論語句中的每一個記號,都被轉換成為了一個大字典中對應的序號。字典的長度我們前面已經(jīng)規(guī)定了,最多10000條。 但是這里有個問題——評論句子有長有短,其中包含的記號個數(shù)不同啊。 我們探索一下,只看最前面5句話,包含多少個記號(token)。 for sequence in sequences[:5]: 150 果然,不僅長短不一,而且有的還超出我們想要的句子長度。 沒關系,用 data = pad_sequences(sequences, maxlen=maxlen) 再看看這次的數(shù)據(jù): data array([[ 2, 1, 74, ..., 4471, 864, 4], 那些長句子,被剪裁了;短句子,被從頭補充了若干個 0 。整齊規(guī)范。 我們還希望知道,這些序號分別代表什么單詞,所以我們把這個字典保存下來。 word_index = tokenizer.word_index 看看索引的類型。 type(word_index) dict 類型驗證通過。看看內(nèi)容: print(word_index) 沒問題了。 中文評論數(shù)據(jù),已經(jīng)被我們處理成一系列長度為 100 ,其中都是序號的序列了。下面我們要把對應的情感標記,存儲到標記序列 labels = np.array(df.sentiment) 看一下其內(nèi)容: labels array([0, 1, 0, ..., 0, 1, 1]) 全部數(shù)據(jù)都已經(jīng)備妥了。下面我們來劃分一下訓練集和驗證集。 我們采用的,是把序號隨機化,但保持數(shù)據(jù)和標記之間的一致性。 indices = np.arange(data.shape[0]) 看看此時的標記: labels array([0, 1, 1, ..., 0, 1, 1]) 注意順序已經(jīng)發(fā)生了改變。 我們希望,訓練集占 80% ,驗證集占 20%。根據(jù)總數(shù),計算一下兩者的實際個數(shù): training_samples = int(len(indices) * .8) 其中訓練集包含多少數(shù)據(jù)? training_samples 1600 驗證集呢? validation_samples 400 下面,我們正式劃分數(shù)據(jù)。 X_train = data[:training_samples] 看看訓練集的輸入數(shù)據(jù): X_train array([[ 0, 0, 0, ..., 963, 4, 322], 至此,預處理部分就算完成了。 詞嵌入下面,我們安裝 gensim 軟件包,以便使用 Facebook 提供的 fasttext 詞嵌入預訓練模型。 !pip install gensim 安裝后,我們讀入加載工具: from gensim.models import KeyedVectors 然后我們需要把 github repo 中下載來的詞嵌入預訓練模型壓縮數(shù)據(jù)解壓。 myzip = mypath / 'zh.zip' 以 !unzip $myzip Archive: demo-chinese-text-classification-lstm-keras/zh.zip 解壓完畢。 下面我們讀入詞嵌入預訓練模型數(shù)據(jù)。 zh_model = KeyedVectors.load_word2vec_format('zh.vec') 看看其中的第一個向量是什么: zh_model.vectors[0] 這么長的向量,對應的記號是什么呢? 看看前五個詞匯: list(iter(zh_model.vocab))[:5] ['的', '', '在', '是', '年'] 原來,剛才這個向量,對應的是標記“的”。 向量的維度是多少?也就是,一個向量中,包含多少個數(shù)字? len(zh_model[next(iter(zh_model.vocab))]) 300 看來, fasttext 用 300 個數(shù)字組成一個向量,代表一個記號(token)。 我們把這個向量長度,進行保存。 embedding_dim = len(zh_model[next(iter(zh_model.vocab))]) 然后,以我們規(guī)定的字典最大長度,以及每個標記對應向量長度,建立一個隨機矩陣。 embedding_matrix = np.random.rand(max_words, embedding_dim) 看看它的內(nèi)容: embedding_matrix 這個隨機矩陣建立的時候,因為使用了 Numpy 的 然而,我們剛才已經(jīng)看過了“的”的向量表示, 請注意,其中的數(shù)字,在 -1 到 1 的范圍中間。為了讓我們隨機產(chǎn)生的向量,跟它類似,我們把矩陣進行一下數(shù)學轉換: embedding_matrix = (embedding_matrix - 0.5) * 2 embedding_matrix 這樣看起來,隨機產(chǎn)生的數(shù)據(jù),就和真正的預訓練結果更相似了。 為什么做這一步呢?一會兒你就知道了。 我們嘗試,對某個特定標記,讀取預訓練的向量結果: zh_model.get_vector('的') 但是注意,如果你指定的標記,出現(xiàn)在自己任務文本里,卻在預訓練過程中沒有出現(xiàn),會如何呢? 試試輸入我的名字: zh_model.get_vector('王樹義') 不好意思,因為我的名字,在 fasttext 做預訓練的時候沒有,所以獲取詞嵌入向量,會報錯。 因此,在我們構建適合自己任務的詞嵌入層的時候,也需要注意那些沒有被訓練過的詞匯。 這里我們判斷一下,如果無法獲得對應的詞向量,我們就干脆跳過,使用默認的隨機向量。 for word, i in word_index.items(): 這也是為什么,我們前面盡量把二者的分布調(diào)整成一致。這樣咱們對于沒見過的詞匯,也可以做成個以假亂真的分布,一起參加后面的模型訓練過程。 看看我們產(chǎn)生的“混合”詞嵌入矩陣: embedding_matrix 模型詞嵌入矩陣準備好了,下面我們就要搭建模型了。 from keras.models import Sequential 注意這里的模型,是最簡單的順序模型,對應的模型圖如下: 如圖所示,我們輸入數(shù)據(jù)通過詞嵌入層,從序號轉化成為向量,然后經(jīng)過 LSTM (RNN 的一個變種)層,依次處理,最后產(chǎn)生一個32位的輸出,代表這句評論的特征。 這個特征,通過一個普通神經(jīng)網(wǎng)絡層,然后采用 Sigmoid 函數(shù),輸出為一個0到1中間的數(shù)值。 Sigmoid 函數(shù),大概長成這個樣子: 這樣,我們就可以通過數(shù)值與 0 和 1 中哪個更加接近,進行分類判斷。 但是這里注意,此處搭建的神經(jīng)網(wǎng)絡里,Embedding 只是一個隨機初始化的層次。我們需要把剛剛構建的詞嵌入矩陣導入。 model.layers[0].set_weights([embedding_matrix]) 我們希望保留好不容易獲得的單詞預訓練結果,所以在后面的訓練中,我們不希望對這一層進行訓練,因而, 因為是二元分類,因此我們設定了損失函數(shù)為 我們訓練模型,保存輸出為 好了,開始訓練吧: model.compile(optimizer='rmsprop', 機器認認真真,替我們跑了10個來回。 因為有 TPU 的幫助,所以這個過程,應該很快就能完成。 討論對于這個模型的分類效果,你滿意嗎? 如果單看最終的結果,訓練集準確率超過 90%, 驗證集準確率也超過 80%,好像還不錯嘛。 但是,我看到這樣的數(shù)據(jù)時,會有些擔心。 我們把這些訓練中獲得的結果數(shù)值,用可視化的方法,顯示一下: import matplotlib.pyplot as plt 上圖是準確率曲線。虛線是訓練集,實線是驗證集。我們看到,訓練集準確率一路走高,但是驗證集準確率在波動——即便最后一步剛好是最高點。 看下面的圖,會更加清晰。 上圖是損失數(shù)值對比。我們可以看到,訓練集上,損失數(shù)值一路向下,但是,從第2個 epoch 開始,驗證集的損失數(shù)值,就沒有保持連貫的顯著下降趨勢。二者發(fā)生背離。 這意味著什么? 這就是深度學習中,最常見,也是最惱人的問題——過擬合(overfitting)。 《如何用機器學習處理二元分類任務?》一文中,我曾經(jīng)就這個問題,為你做過詳細的介紹。這里不贅述了。 但是,我希望你能夠理解它出現(xiàn)的原因——相對于你目前使用的循環(huán)神經(jīng)網(wǎng)絡結構,你的數(shù)據(jù)量太小了。 深度學習,可以讓你端到端操作,不需要手動繁復去做特征工程。但是,它對于數(shù)據(jù)數(shù)量和質(zhì)量的需求,都很高。 有沒有辦法,可以讓你不需要這么多的數(shù)據(jù),也能避免過擬合,取得更好的訓練結果呢? 這個問題的答案,我在《如何用 Python 和深度遷移學習做文本分類?》一文中已經(jīng)為你介紹過,如果你忘記了,請復習一下吧。 小結本文,我們探討了如何用 Python 和循環(huán)神經(jīng)網(wǎng)絡處理中文文本分類問題。讀過本文并且實踐之后,你應該已經(jīng)能夠把下列內(nèi)容融會貫通了:
希望這份教程,可以在你的科研和工作中,幫上一些忙。 祝(深度)學習愉快! |
|
來自: LibraryPKU > 《機器學習》