Liz's Blog

Python學習筆記#20:機器學習之Natural Language Processing(NLP)實作篇

| Comments

自然語言處理(Natural Language Processing, NLP)用途非常廣,講師在這裡也只取一小部分來講,NLP詳細可參考這裡。可用於分類新文章,也可於找到法律文件中找到最相關的部分等。例如:手邊有兩個文件,分別叫做Blue Pen及Red Pen,以字數來區分特徵則,Blue Pen -> (blue,red,pen) -> (1,0,1),而Red Pen則為(0,1,1)。在範例中(blue,red,pen)這種乘載大量字詞的向量,稱為Bag of Words(bow),若想知道Blue Pen及Red Pen的相似度,則可計算兩者間的cosine。若想改善Bag of Words則可藉由調整TF-IDF (Term Frequency - Inverse Document Frequency)來達到目的。

Udemy
課程名稱:Python for Data Science and Machine Learning Bootcamp
講師:Jose Portilla

這個章節講師花了非常多時間在講解每個套件的運用,比起之前在學R同樣主題受限於套件支援,這篇實際運用更廣且更精確。

1.在終端機先安裝nltk(之後要下載stopwords的語料庫)
conda install nltk

2.載入nltk套件並下載stopwords語料庫

import nltk

#在下面框內輸入d來下載,Identifier則輸入stopwords,再按q離開
nltk.download_shell()

3.使用UCI中的SMS Spam Collection資料集,共有五千多條電話訊息

#[line.rstrip('') for line in open('filename')]讀取檔案時會一行一行變成list
messages = [line.rstrip() for line in open('smsspamCollection/SMSSpamCollection')]

#印出messages的數量
print(len(messages))

#看看第五十個message是什麼,同時也可以看到\t(tab)是分開符號,ham是相對spam(垃圾郵件)的說法
messages[50]

#印出前十條訊息,並為他們編號(0-9)
for mess_no,message in enumerate(messages[:10]):
    print(mess_no,message)
    print('\n')
    
#使用pandas的sep來分開即可
import pandas as pd

#第一欄位為label(ham/spam),第二個欄位為message
messages = pd.read_csv('smsspamCollection/SMSSpamCollection',sep='\t',
                      names=['label','message'])
messages.head()

4.探索資料

messages.describe()

#使用groupby將ham及spam分開計算count、unique、top、freq
messages.groupby('label').describe()

5.Feature Engineering
分析的過程中,常會遇到我們到底要使用哪種特徵(feature),因此要是擁有該領域的知識,相對也更能夠建立特徵,使機器學習演算法發揮作用,更多說明可見feature engineering

#新增messages的length欄位
messages['length'] = messages['message'].apply(len)
messages.head()

6.視覺化資料

import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline

#用直方圖畫出messages的長度大多落在0-200字數間,隨著bins增加,字數出現的區間也隨之改變
messages['length'].plot.hist(bins=50)

#describe()可看出最長的的字數長度
messages['length'].describe()

#看看最長的訊息內容
messages[messages['length']== 910]['message'].iloc[0]

#先來看看ham和spam的訊息長度是否有差異,來決定是否適合作為特徵(結果看起來似乎spam的字數似乎比較高)
messages.hist(column='length',by='label',bins=60,figsize=(12,4))

7.文字預處理
之前學到的分類演算法都是靠數字型特徵來做,現在則是都是文字資料卻要做分類,因此需要將這些文字轉成一連串的數字向量,最簡單的方法是bag-of-words

#載入文字預處理套件
import string

#建立範例
mess = 'Sample message! Notice: it has punctuation.'

#字串中的標點符號有哪些
string.punctuation

#確認字串中沒有標點符號
nopunc = [c for c in mess if c not in string.punctuation]
nopunc

#將字串重組成句子
nopunc = ''.join(nopunc)
nopunc

#解釋nopunc = ''.join(nopunc)代表的意思,假如有一個字串x
x = ['a','b','c','d']

#'+++'的符號將會出現在文字間
'+++'.join(x)

#若''中間是空白則也會直接將文字連接在一起
''.join(x)

8.載入nltk中的stopwords來移除常見但沒有意義的字

from nltk.corpus import stopwords

#載入英文的stopwords
stopwords.words('english')

#將句子拆成一個個單字
nopunc.split()

#移除stopwords中的字(在此例中為it和has)
clean_mess = [word for word in nopunc.split() if word.lower() not in stopwords.words('english')]
clean_mess

#依照剛剛的示範練習,先移除所有標點符號,接著移除stopwords,輸出乾淨的字串
def text_process(mess):
    nopunc = [char for char in mess if char not in string.punctuation]
    nopunc = ''.join(nopunc)
    return [word for word in nopunc.split() if word.lower() not in stopwords.words('english')]
    
#原始的messages的資料
messages.head()

#將訊息中的句子,轉成一個一個的文字
messages['message'].head(5).apply(text_process)

9.可參考Stemmingpart of speech來把同源字或縮寫字等內容繼續歸類。雖然NLTK有許多內建的工具來做,但由於許多人發訊息時常使用縮寫等字,所以有時候並不太有用,可參考NLTK book)。

10.載入CountVectorizer將文字向量化

from sklearn.feature_extraction.text import CountVectorizer

#在CountVectorizer可調整許多參數,但練習中只指定analyzer為text_process
bow_transformer = CountVectorizer(analyzer=text_process).fit(messages['message'])

#印出總共有多少個字彙
print(len(bow_transformer.vocabulary_))

#取其中一條訊息
mess4 = messages['message'][3]
print(mess4)

#將訊息轉成向量
bow4 = bow_transformer.transform([mess4])

#看訊息中重複文字出現的頻率(共有七個不重複的單字,有兩個字出現兩次)
print(bow4)
print(bow4.shape)

#看第一個重複的字是什麼
bow_transformer.get_feature_names()[4068]

#看第二個重複的字是什麼
bow_transformer.get_feature_names()[9554]

#將整個messages內的訊息轉成data frame
messages_bow = bow_transformer.transform(messages['message'])

#Bag-of-Words (bow)轉換messages_bow,整個電話訊息會變成一個大型sparse matrix(稀疏矩陣)=(總訊息數,不重複單字資料庫)
print('Shape of Sparse Matrix: ',messages_bow.shape)

#nnz=Amount of Non-Zero occurences
messages_bow.nnz

#計算sparsity(稀疏性)
sparsity = (100.0 * messages_bow.nnz / (messages_bow.shape[0] * messages_bow.shape[1]))
print('sparsity: {}'.format(round(sparsity)))

11.TF-IDF(term frequency-inverse document frequency)
TF-IDF權重用來評估單字對於整個文件或語料庫的重要性,常用於資料檢索及資料探勘。當單字出現次數越多,對文件的重要性越高,但對語料庫而言,出現頻率越高則相反。tf-idf權重常用於搜尋引擎評估使用者搜尋和該文件相關度的評分排名。

TF-IDF由兩個部分所組成:
(1)TF(Term Frequency):(在文件中該字詞出現的次數)/(文件中的字詞量)。但文件可能有長有短,所以可以在除以文件長度來作為標準化。
(2)IDF(Inverse Document Frequency):log(文件總數/指定字詞出現在文件中的次數)

範例: 文件中含有100個字詞,cat在該文件中出現三次。總共有10,000,000個文字,cat總共出現1,000次。
TF=3/100=0.03
IDF=log(10,000,000/1,000)=4
IF-IDF=0.03*4=0.12

#載入TfidfTransformer套件
from sklearn.feature_extraction.text import TfidfTransformer

#計算第四條訊息每個字詞的TF-IDF
tfidf_transformer = TfidfTransformer().fit(messages_bow)
tfidf4 = tfidf_transformer.transform(bow4)
print(tfidf4)

#檢查特定字詞的idf
print(tfidf_transformer.idf_[bow_transformer.vocabulary_['university']])

#將之前bow的語料庫轉到TF-IDF的語料庫
messages_tfidf = tfidf_transformer.transform(messages_bow)
print(messages_tfidf.shape)

12.區別訊息是ham還是spam
訓練模型把所有的訊息轉成向量後,終於可以開始分類訊息是ham還是spam。使用的是Naive Bayes做文字分類。

#載入套件
from sklearn.naive_bayes import MultinomialNB

#利用MultinomialNB()來建立模型
spam_detect_model = MultinomialNB().fit(messages_tfidf, messages['label'])

#看模型預測結果
spam_detect_model.predict(tfidf4)[0]
messages['label'][3]

#評估模型表現(在這裡可以呼叫classification_report來評估模型好壞,但用本來用來訓練做出來的模型,又再次拿來測試,似乎有點不合理)
all_pred = spam_detect_model.predict(messages_tfidf)
print(all_pred)

13.將資料分成訓練組及測試組

from sklearn.model_selection import train_test_split

msg_train, msg_test, label_train, label_test = \
train_test_split(messages['message'], messages['label'], test_size=0.2)

print(len(msg_train), len(msg_test), len(msg_train) + len(msg_test))

14.建立Data Pipeline來儲存一連串要做的事(先將文字轉成向量,計算tfidf加權,再來分類)

from sklearn.pipeline import Pipeline

pipeline = Pipeline([
    ('bow', CountVectorizer(analyzer=text_process)),
    ('tfidf', TfidfTransformer()),
    ('classifier', MultinomialNB()),
])

#利用訓練組資料建立模型
pipeline.fit(msg_train,label_train)

#利用測試組來做預測
predictions = pipeline.predict(msg_test)

#評估模型好壞
from sklearn.metrics import classification_report
print(classification_report(predictions,label_test))

Comments

comments powered by Disqus