《Python數據分析與機器學習實戰-唐宇迪》讀書筆記第11章--貝葉斯算法項目實戰 ——新聞分類

python數據分析個人學習讀書筆記-目錄索引

第11章貝葉斯算法項目實戰——新聞分類

   本章介紹機器學習中非常經典的算法——貝葉斯算法,相信大家都聽說過貝葉斯這個偉大的數學家,接下來看一下貝葉斯算法究竟能解決什麼問題。在分類任務中,數值特徵可以直接用算法來建立模型,如果數據是文本數據該怎麼辦呢?本章結合貝葉斯算法通過新聞數據集的分類任務來探索其中每一步細節。

 

11.1貝葉斯算法

   貝葉斯(Thomas Bayes,1701—1761年),英國數學家。所謂的貝葉斯定理源於他生前爲解決一個“逆概”問題而寫的一篇文章。先通過一個小例子來理解一下什麼是正向和逆向概率。假設你的口袋裏面有N個白球、M個黑球,你伸手進去隨便拿一個球,問拿出黑球的概率是多大?

  這個問題可以輕鬆地解決,但是,如果把這個問題反過來還那麼容易嗎?如果事先並不知道袋子裏面黑白球的比例,而是閉着眼睛摸出一個(或好幾個)球,觀察這些取出來的球的顏色之後,要對袋子裏面的黑白球的比例作推測。好像有一點繞,這就是逆向概率問題。接下來就由一個小例子帶大家走進貝葉斯算法。

11.1.1貝葉斯公式

   直接看貝葉斯公式可能有點難以理解,先通過一個實際的任務來看看貝葉斯公式的來歷,假設一個學校中男生佔總數的60%,女生佔總數的40%。並且男生總是穿長褲,女生則一半穿長褲、一半穿裙子,接下來請聽題(見圖11-1)。

   邀月工作室

圖11-1 貝葉斯公式場景實例

  • 1.正向概率。隨機選取一個學生,他(她)穿長褲和穿裙子的概率是多大?這就簡單了,題目中已經告訴大家男生和女生對於穿着的概率。
  • 2.逆向概率。迎面走來一個穿長褲的學生,你只看得見他(她)穿的是否是長褲,而無法確定他(她)的性別,你能夠推斷出他(她)是女生的概率有多大嗎?這個問題似乎有點難度,好像沒辦法直接計算出來,但是是否可以間接求解呢?來試一試吧。

  下面通過計算這個小任務推導貝葉斯算法,首先,假設學校裏面的總人數爲U,這個時候大家可能有疑問,原始條件中,並沒有告訴學校的總人數,只告訴了男生和女生的比例,沒關係,可以先進行假設,一會能不能用上還不一定呢。

  此時穿長褲的男生的個數爲:

   邀月工作室

  式中,P(Boy)爲男生的概率,根據已知條件,其值爲60%;P(Pants|Boy)爲條件概率,即在男生這個條件下穿長褲的概率是多大,根據已知條件,所有男生都穿長褲,因此其值是100%。條件都已知,所以穿長褲的男生數量是可求的。

  同理,穿長褲的女生個數爲:

   邀月工作室

  式中,P(girl)爲女生的概率,根據已知條件,其值爲40%;P(Pants|Girl)爲條件概率,即在女生這個條件下穿長褲的概率是多大,根據已知條件,女生一半穿長褲、一半穿裙子,因此其值是50%,所以穿長褲的女生數量也是可求的。

  下面再來分析一下要求解的問題:迎面走來一個穿長褲的學生,你只看得見他(她)穿的是長褲,而無法確定他(她)的性別,你能夠推斷出他(她)是女生的概率是多大嗎?這個問題概括起來就是,首先是一個穿長褲的學生,這是第一個限定條件,接下來這個人還得是女生,也就是第二個條件。總結起來就是:穿長褲的人裏面有多少是女生。

爲了求解上述問題,首先需計算穿長褲的學生總數,應該是穿長褲的男生和穿長褲的女生總數之和:

   邀月工作室

  此例類別只有兩種,所以只需考慮男生和女生即可,二分類這麼計算,多分類也是如此,舉一反三也是必備的基本功。

  要想知道穿長褲的人裏面有多少女生,可以用穿長褲的女生人數佔穿長褲學生總數的比例確定:

   邀月工作室

  其中穿長褲總數在式(11.3)中已經確定,合併可得:

   邀月工作室

  回到最開始的假設問題中,這個計算結果與總人數有關嗎?觀察式(11.5),可以發現分子和分母都含有總人數U,因此可以消去,說明計算結果與校園內學生的總數無關。因此,穿長褲的人裏面有多少女生的結果可以由下式得到:

   邀月工作室

  分母表示男生中穿長褲的人數和女生中穿長褲的人數的總和,由於原始問題中,只有男生和女生兩種類別,既然已經把它們都考慮進來,再去掉總數U對結果的影響,就是穿長褲的概率,可得:

   邀月工作室

  現在這個問題似乎解決了,不需要計算具體的結果,只需觀察公式的表達即可,上面的例子中可以把穿長褲用A表示,女生用B表示。這就得到貝葉斯公式的推導過程,最終公式可以概括爲:邀月工作室

估計貝葉斯公式給大家的印象是,只要把要求解的問題調換了一下位置,就能解決實際問題,但真的有這麼神奇嗎?還是通過兩個實際任務分析一下吧。

 

11.1.2拼寫糾錯實例

   貝葉斯公式能解決哪類問題呢?下面就以一個日常生活中經常遇到的問題爲例,我們打字的時候是不是經常出現拼寫錯誤(見圖11-2),但是程序依舊會返回正確拼寫的字或者語句,這時候程序就會猜測:“這個用戶真正想輸入的單詞是什麼呢?”

    邀月工作室

  圖11-2 打字時的拼寫錯誤

  例如,用戶本來想輸入“the”,但是由於打字錯誤,輸成“tha”,那麼程序能否猜出他到底想輸入哪個單詞呢?可以用下式表示:

  P(猜測他想輸入的單詞|他實際輸入的單詞)(11.9)

  例如,用戶實際輸入的單詞記爲D(D代表一個具體的輸入,即觀測數據),那麼可以有很多種猜測:猜測1,P(h1|D);猜測2,P(h2|D);猜測3,P(h3|D)等。例如h1可能是the,h2可能是than,h3可能是then,到底是哪一個呢?也就是要比較它們各自的概率值大小,哪個可能性最高就是哪個。

  先把上面的猜想統一爲P(h|D),然後進行分析。直接求解這個公式好像難度有些大,有點無從下手,但是剛剛不是得到貝葉斯公式嗎?轉換一下能否好解一些呢?先來試試看:

   邀月工作室

  此時該如何理解這個公式呢?實際計算中,需要分別得出分子和分母的具體數值,才能比較最終結果的大小,對於不同的猜測h1、h2、h3……,分母D的概率P(D)相同,因爲都是相同的輸入數據,由於只是比較最終結果的大小,而不是具體的值,所以這裏可以不考慮分母,也就是最終的結果只和分子成正比的關係,化簡可得:

   邀月工作室

  很多機器學習算法在求解過程中都是隻關心極值點位置,而與最終結果的具體數值無關,這個套路會一直使用下去

  對於給定觀測數據,一個猜測出現可能性的高低取決於以下兩部分。

  • P(h):表示先驗概率,它的大小可以認爲是事先已經計算好了的,比如有一個非常大的語料庫,裏面都是各種文章、新聞等,可以基於海量的文本進行詞頻統計。

  圖11-3用詞雲展示了一些詞語,其中每個詞的大小就是根據其詞頻大小進行設定。例如,給定的語料庫中,單詞一共有10000個,其中候選詞h1出現500次,候選詞h2出現1000次,則其先驗概率分別爲500/10000、1000/10000。可以看到先驗概率對結果具有較大的影響。

 

  邀月工作室

  圖11-3 詞頻統計

  在貝葉斯算法中,一直強調先驗的重要性,例如,連續拋硬幣100次都是正面朝上,按照之前似然函數的思想,參數是由數據決定的,控制正反的參數此時就已經確定,下一次拋硬幣時,就會有100%的信心認爲也是正面朝上。但是,貝葉斯算法中就不能這麼做,由於在先驗概率中認爲正反的比例1︰1是公平的,所以,在下一次拋硬幣的時候,也不會得到100%的信心。

  • P(D|h):表示這個猜測生成觀測數據的可能性大小,聽起來有點抽象,還是舉一個例子。例如猜想的這個詞h需要通過幾次增刪改查能得到觀測結果D,這裏可以認爲通過一次操作的概率值要高於兩次,畢竟你寫錯一個字母的可能性高一些,一次寫錯兩個就是不可能的。

最後把它們組合在一起,就是最終的結果。例如,用戶輸入“tlp”(觀測數據D),那他到底輸入的是“top”(猜想h1)還是“tip”(猜想h2)呢?也就是:已知h1=top,h2=tip,D=tlp,求P(top|tlp)和P(tip|tlp)到底哪個概率大。經過貝葉斯公式展開可得:

  邀月工作室

 這個時候,看起來都是寫錯了一個詞,假設這種情況下,它們生成觀測數據的可能性相同,即P(tlp|top)=P(tlp|tip),那麼最終結果完全由P(tip)和P(top)決定,也就是之前討論的先驗概率。一般情況下,文本數據中top出現的可能性更高,所以其先驗概率更大,最終的結果就是h1:top。

講完這個例子之後,相信大家應該對貝葉斯算法有了一定的瞭解,其中比較突出的一項就是先驗概率,這好像與之前講過的算法有些不同,以前得到的結果完全是由數據決定其中的參數,在這裏先驗概率也會對結果產生決定性的影響。

11.1.3垃圾郵件分類

   接下來再看一個日常生活中的實例——垃圾郵件分類問題。這裏不只要跟大家說明其處理問題的算法流程,還要解釋另一個關鍵詞——樸素貝葉斯。貝葉斯究竟是怎麼個樸素法呢?從實際問題出發還是很好理解的。

  當郵箱接收一封郵件時,如何判斷它是一封正常的郵件還是垃圾郵件呢?在機器學習任務中就是一個經典的二分類問題(見圖11-4)。

   邀月工作室

  圖11-4 郵件判斷

  本例中用D表示收到的這封郵件,注意D並不是一個大郵件,而是由N個單詞組成的一個整體。用h+表示垃圾郵件,h−表示正常郵件。當收到一封郵件後,只需分別計算它是垃圾郵件和正常郵件可能性是多少即可,也就是P(h+|D)和P(h−|D)。

根據貝葉斯公式可得:

   邀月工作室

  P(D)同樣是這封郵件,同理,既然分母都是一樣的,比較分子就可以。

  其中P(h)依舊是先驗概率,P(h+)表示一封郵件是垃圾郵件的概率,P(h−)表示一封郵件是正常郵件的概率。這兩個先驗概率都是很容易求出來的,只需要在一個龐大的郵件庫裏面計算垃圾郵件和正常郵件的比例即可。例如郵件庫中包含1000封郵件,其中100封是垃圾郵件,剩下的900封是正常郵件,則P(h+)=100/1000=10%,P(h−)=900/1000=90%。

  P(D|h+)表示這封郵件是垃圾郵件的前提下恰好由D組成的概率,而P(D|h−)表示正常郵件恰好由D組成的概率。感覺似乎與剛剛說過的拼寫糾錯任務差不多,但是這裏需要對D再深入分析一下,因爲郵件中的D並不是一個單詞,而是由很多單詞按順序組成的一個整體。

  D既然是一封郵件,當然是文本語言,也就有先後順序之分。例如,其中含有N個單詞d1,d2…dn,注意其中的順序不能改變,就像我們不能倒着說話一樣,因此:

   邀月工作室

  式中,P(d1,d2,…,dn|h+)爲在垃圾郵件當中出現的與目前這封郵件一模一樣的概率是多大。這個公式涉及這麼多單詞,看起來有點棘手,需要對其再展開一下:

   邀月工作室

  式(11.15)表示在垃圾郵件中,第一個詞是d1;恰好在第一個詞是d1的前提下,第二個詞是d2;又恰好在第一個詞是d1,第二個詞是d2的前提下,第三個詞是d3,以此類推。這樣的問題看起來比較難以解決,因爲需要考慮的實在太多,那麼該如何求解呢?

  這裏有一個關鍵問題,就是需要考慮前後之間的關係,例如,對於d2,要考慮它前面有d1,正因爲如此,才使得問題變得如此煩瑣。爲了簡化起見,如果di與di −1是相互獨立的,就不用考慮這麼多,此時d1這個詞出現與否與d2沒什麼關係。特徵之間(詞和詞之間)相互獨立,互不影響,此時P(d2|d1,h+)=P(d2|h+)。

  這個時候在原有的問題上加上一層獨立的假設,就是樸素貝葉斯,其實理解起來還是很簡單的,它強調了特徵之間的相互獨立,因此式(11.15)可以化簡爲:

   邀月工作室

  對於式(11.16),只需統計di在垃圾郵件中出現的頻率即可。統計詞頻很容易,但是一定要注意,詞頻的統計是在垃圾郵件庫中,並不在所有的郵件庫中。例如P(d1|h+)和P(d1|h−)就要分別計算d1在垃圾郵件中的詞頻和在正常郵件中的詞頻,其值是不同的。像“銷售”“培訓”這樣的詞在垃圾郵件中的詞頻會很高,貝葉斯算法也是基於此進行分類任務。計算完這些概率之後,代入式(11.16)即可,通過其概率值大小,就可以判斷一封郵件是否屬於垃圾郵件。

 

11.2新聞分類任務

   下面要做一個新聞分類任務,也就是根據新聞的內容來判斷它屬於哪一個類別,先來看一下數據:

 

 1 import pandas as pd
 2 import jieba
 3 #pip install jieba
 4 
 5 df_news = pd.read_table('./data/SohusiteData.txt',names=['category','theme','content'],encoding='utf-8',delimiter='^')
 6 df_news = df_news.dropna()
 7 df_news.tail()
 8 
 9 # df_news_small=df_news.head(1000)
10 
11 # w_filenameCSV = './data/data.txt'
12 # # write to files
13 # with open(w_filenameCSV,'w',newline='',encoding='utf-8') as write_csv:
14 #     write_csv.write(df_news_small.to_csv(sep='^', index=False,encoding='utf-8'))
15     
16 
17 # df_news_small = pd.read_csv('./data/data.txt',names=['category','theme','content'],encoding='utf-8',delimiter='^')
18 # df_news_small.head(5)

   邀月工作室 

        邀月注:最初用89501條數據,python進程佔滿16G內存,瀏覽器進程崩潰,導致所有修改代碼丟失,痛定思痛,決定改用1000條新聞,所以上圖應爲(1001,3)

  由於原始數據都是由爬蟲爬下來的,所以看起來有些不整潔,需要清洗一番。這裏有幾個字段特徵:

  • Category:當前新聞所屬的類別,一會要進行分類任務,這就是標籤。
  • Theme:新聞的主題,這個暫時不用,大家在練習的時候,也可以把它當作特徵。
  • Content:新聞的內容,也就是一篇文章,內容很豐富。

  前5條數據都是與財經有關,我們再來看看後5條數據(見圖11-5)。

   邀月工作室

  圖11-5 財經類別新聞

  這些都與另一個主題——土地相關,任務已經很明確,根據文章的內容進行類別的劃分。那麼如何做呢?之前看到的數據都是數值型,直接傳入算法中求解參數即可。這份數據顯得有些特別,都是文本,計算機可不認識這些文字,所以,首先需要把這些文字轉換成特徵,例如將一篇文章轉換成一個向量,這樣計算機就能識別了。

11.2.1數據清洗

   對於一篇文章來說,裏面的內容很豐富,對於中文數據來說,通常的做法是先把文章進行分詞,然後在詞的層面上去構建文章向量。下面先選一篇文章,然後進行分詞:

1 content = df_news.content.values.tolist() #將每一篇文章轉換成一個list
2 print (content[1000]) #隨便選擇其中一個看看

  邀月工作室

  這裏選擇使用結巴分詞工具包完成這個分詞任務(Python中經常用的分詞工具),首先直接在命令行中輸入“pip install jieba”完成安裝。結巴工具包還是很實用的,主要用來分詞,其實它還可以做一些自然語言處理相關的任務,想具體瞭解的同學可以參考其GitHub文檔。

  分詞的基本原理也是機器學習算法,感興趣的同學可以瞭解一下HMM隱馬爾可夫模型。

   在結果中可以看到將原來的一句話變成了一個list結構,裏面每一個元素就是分詞後的結果,這份數據規模還是比較小的,只有5000條,分詞很快就可以完成(這次耗的不是CPU,是內存,約佔去5-8G內存,和數據量有關)。

1 content_S = []
2 for line in content:
3     current_segment = jieba.lcut(line) #對每一篇文章進行分詞
4     if len(current_segment) > 1 and current_segment != '\r\n': #換行符
5         content_S.append(current_segment) #保存分詞的結果
Building prefix dict from the default dictionary ...
Dumping model to file cache d:\Temp\jieba.cache
Loading model cost 0.962 seconds.
Prefix dict has been built successfully.
1 content_S[900]

  邀月工作室

  完成分詞任務之後,要處理的對象就是其中每一個詞,我們知道一篇文章的主題應該由其內容中的一些關鍵詞決定,例如“訂車”“一汽”“車展”等,一看就知道與汽車相關。但是另一類詞,例如“今年”“在”“3月”等,似乎既可以在汽車相關的文章中使用,也可以在其他文章中使用,它們稱作停用詞,也就是要過濾的目標。

  首先需要選擇一個合適的停用詞庫,網上有很多現成的,但是都沒有那麼完整,所以,當大家進行數據清洗任務的時候,還需要自己添加一些,停用詞如圖11-6所示。

 邀月工作室

  圖11-6中只截取停用詞表中的一部分,都是一些沒有實際主題色彩的詞,如果想把清洗的任務做得更完善,還是需要往停用詞表中加入更多待過濾的詞語,數據清洗乾淨,才能用得舒服。如果添加停用詞的任務量實在太大,一個簡單的辦法就是基於詞頻進行統計,普遍情況下高頻詞都是停用詞。

  對於文本任務來說,數據清洗非常重要,因爲其中每一個詞都會對結果產生影響,在開始階段,還是希望儘可能多地去掉這些停用詞。

  過濾掉停用詞的方法很簡單,只需要遍歷數據集,剔除掉那些出現在停用詞表中的詞即可,下面看一下對比結果。

 1 df_content=pd.DataFrame({'content_S':content_S}) #專門展示分詞後的結果
 2 df_content.head()
 3 
 4 stopwords=pd.read_csv("stopwords.txt",index_col=False,sep="\t",quoting=3,names=['stopword'], encoding='utf-8')
 5 stopwords.head(20)
 6 
 7 def drop_stopwords(contents,stopwords):
 8     contents_clean = []
 9     all_words = []
10     for line in contents:
11         line_clean = []
12         for word in line:
13             if word in stopwords:
14                 continue
15             line_clean.append(word)
16             all_words.append(str(word))
17         contents_clean.append(line_clean)
18     return contents_clean,all_words
19     
20 contents = df_content.content_S.values.tolist()    
21 stopwords = stopwords.stopword.values.tolist()
22 contents_clean,all_words = drop_stopwords(contents,stopwords)
23 
24 #df_content.content_S.isin(stopwords.stopword)
25 #df_content=df_content[~df_content.content_S.isin(stopwords.stopword)]
26 #df_content.head()
27 
28 df_content=pd.DataFrame({'contents_clean':contents_clean})
29 df_content.head()
30 #8萬9千條新聞,運行了半小時,後改爲1000條新聞,邀月注

 

邀月工作室

  顯然,這份停用詞表做得並不十分完善,但是可以基本完成清洗的任務,大家可以酌情完善這份詞表,根據實際數據情況,可以選擇停用詞的指定方法。

  中間來一個小插曲,在文本分析中,現在經常會看到各種各樣的詞雲,用起來還是比較有意思的。在Python中可以用wordcloud工具包來做,可以先參考其github文檔。

1 pip install wordcloud

    

 1 df_all_words=pd.DataFrame({'all_words':all_words})
 2 df_all_words.head()
 3 
 4 import numpy as np
 5 # words_count=df_all_words.groupby(by=['all_words'])['all_words'].agg({"count":np.size})
 6 # SpecificationError: nested renamer is not supported
 7 # print(df_all_words.head(10)
 8 # df_words=df_all_words(['all_words'])
 9 
10 # print(df_all_words.columns)
11 df_all_words.loc[:, 'count'] = 1 #設置一個整列值
12 
13 # print(df_all_words.columns)
14 
15 data_group3 = df_all_words.groupby('all_words').agg({'count':'count'})
16 data_group3.head(10)
17 
18 # help(words_count.reset_index)
19 words_count=data_group3.reset_index().sort_values(by=['count'],ascending=False)
20 # words_count.drop(31733,axis=0)   #'\ue40c'對應的是31733
21 # words_count.drop(index=31733,axis=0)   #'\ue40c'對應的是31733
22 
23 words_count.head(100)

邀月工作室

 1 from wordcloud import WordCloud
 2 import matplotlib.pyplot as plt
 3 %matplotlib inline
 4 import matplotlib
 5 matplotlib.rcParams['figure.figsize'] = (10.0, 5.0)
 6 
 7 wordcloud=WordCloud(font_path="./data/simhei.ttf",background_color="white",max_font_size=80)
 8 word_frequence = {x[0]:x[1] for x in words_count.head(100).values}
 9 wordcloud=wordcloud.fit_words(word_frequence)
10 plt.imshow(wordcloud)

     

{'比賽': 246, '': 227, '北京': 181, '中國': 166, '時間': 165, '市場': 92, '冠軍': 82, '美國': 77, '經濟': 68, '體育': 64, '選手': 64, 

'國際': 61, '昨日': 58, '球員': 57, '公司': 55, '': 55, '女人': 54, '世界': 52, '': 51, '一場': 50, '決賽': 49, '球隊': 48, '爭奪':
47, '主場': 46, '': 45, '公開賽': 45, '': 44, '報道': 43, '搜狐': 43, '網球': 42, '最終': 42, '上海': 42, '全球': 42, '': 40, '賽季': 40,
'': 39, '聯賽': 39, '': 39, '': 39, '種子': 38, '男人': 38, '球迷': 38, '發佈': 38, '對手': 36, '結束': 35, '投資': 35, '正式': 35,

'廣州': 34, '': 34, '全國': 34, '凌晨': 33, '倫敦': 32, '晉級': 32, '數據': 32, '英國': 31, '億元': 31, '顯示': 30, '溫網': 30,
'展開': 30, '戰勝': 30, '公佈': 30, '當日': 30, '': 30, '表現': 29, '擊敗': 29, '溫布爾登': 29, '國內': 29, '發展': 29, '': 29,

'近日': 29, '分鐘': 28, '品牌': 28, '': 28, '迎戰': 28, '昨天': 27, '賽段': 26, '參加': 26, '迎來': 26, '': 26, '企業': 26, '關注': 25,
'情況': 25, '推出': 25, '開獎': 25, '俱樂部': 25, '': 25, '圖爲': 24, '媒體': 24, '': 24, '進一步': 24, '女單': 24, '意大利': 24,

'深圳': 24, '接受': 23, '': 23, '': 23, '日前': 23, '男單': 23, '恆大': 23, '集團': 23}

   邀月提示:你可以修改爲自己的數據試試,效果:

      邀月工作室

 

11.2.2TF-IDF關鍵詞提取

   在文本分析中,經常會涉及打標籤和特徵提取,TF-IDF是經常用到的套路。在一篇文章中,經過清洗之後,剩下的都是稍微有價值的詞,但是這些詞的重要程度相同嗎?如何從一篇文章中找出最有價值的幾個詞呢?如果只按照詞頻進行統計,得到的結果並不會太好,因爲詞頻高的可能都是一些套話,並不是主題,這時候TF-IDF就派上用場了。

  這裏借用一個經典的例子——一篇文章《中國的蜜蜂養殖》。

  當進行詞頻統計的時候,發現在這篇文章中,“中國”“蜜蜂”“養殖”這3個詞出現的次數是一樣的,假設都是10次,這個時候如何判斷其各自的重要性呢?這篇文章講述的應該是與蜜蜂和養殖相關的技術,所以“蜜蜂”和“養殖”這兩個詞應當是重點。而“中國”這個詞,既可以說中國的蜜蜂,還可以說中國的籃球、中國的大熊貓,能派上用場的地方簡直太多了,並不專門針對某一個主題,所以,在這篇文章的類別劃分中,它應當不是那麼重要。

  這樣就可以給出一個合理的定義,如果一個詞在整個語料庫中(可以當作是在所有文章中)出現的次數都很高(這篇文章有它,另一篇還有這個詞),那麼這個詞的重要程度就不高,因爲它更像一個通用詞。如果另一個詞在整體的語料庫中的詞頻很低,但是在這一篇文章中卻大量出現,就有理由認爲它在這篇文章中很重要。例如,“蜜蜂”這個詞,在籃球、大熊貓相關的文章中基本不可能出現,在這篇文章中卻大量出現。TF-IDF計算公式如下:

 邀月工作室

  其中:

 邀月工作室

  詞頻這個概念很好理解,逆文檔頻率就看這個詞是不是哪兒都出現,出現得越多,其值就越低。掌握TF-IDF之後,下面以一篇文章試試效果:

1 import jieba.analyse #工具包
2 index = 345 #隨便找一篇文章就行
3 content_S_str = "".join(content_S[index]) #把分詞的結果組合在一起,形成一個句子
4 print (content_S_str) #打印這個句子
5 print ("  ".join(jieba.analyse.extract_tags(content_S_str, topK=5, withWeight=False)))#選出來5個核心詞

邀月工作室

  關鍵詞結果:伯南克 美國 失業率 經濟 財政

  簡單過一遍文章可以發現,講的大概就是美國失業率,得到的關鍵詞也與文章的主題差不多。關鍵詞提取方法還是很實用的,想一想大家每天使用各種APP都能看到很多廣告,不同的用戶收到的廣告應該不同。接下來還需將重點放回分類任務中,先來看一下標籤都有哪些類別:

 1 # df_news = pd.read_table('./data/data.txt',names=['category','theme','content'],encoding='utf-8',delimiter='^')
 2 # df_news = df_news.dropna()
 3 # df_news.tail()
 4 
 5 # print(df_news['category'][1:1000])
 6 # print(df_news['category'][1:1000])
 7 print(len(contents_clean))
 8 
 9 
10 # df_train=pd.DataFrame({'contents_clean':contents_clean,'label':df_news['category']})
11 # ValueError: array length 1000 does not match index length 1001
12 # ValueError: arrays must all be same length
13 ###***************************************過濾第一行標題*******************************
14 # d = {'contents_clean':contents_clean, 'label':  df_news['category'][0:1000]}
15 d = {'contents_clean':contents_clean, 'label':  df_news['category']}
16 df_train = pd.DataFrame(data=d)
17 # df_train=pd.DataFrame(data={'contents_clean':contents_clean,'label':df_news['category']})
18 
19 #數據異常檢查,邀月注:
20 #*************************************************************************************
21 # 讀取數據
22 # train = pd.read_csv('./data/train.csv/train.csv')
23 
24 # 檢查數據中是否有缺失值
25 # np.isnan(df_train).any()
26 
27 # Flase:表示對應特徵的特徵值中無缺失值
28 # True:表示有缺失值
29 
30 # 2、刪除有缺失值的行
31 # df_train.dropna(inplace=True)
32 
33 # 然後在看數據中是否有缺失值
34 
35 # 也可以根據需要對缺失值進行填充處理:
36 # df_train.fillna(0)
37 print(type(df_train))
38 # print(np.isnan(df_train['category']).any())
39 # print(np.isnan(df_train).any())
40 
41 # from sklearn.impute import SimpleImputer
42 # imp_mean = SimpleImputer(missing_values=np.nan, strategy='mean')
43 # imp_mean.fit(df_train)
44 
45 # np.nan_to_num(df_train, nan=np.nanmean(df_train))
46 
47 #********************************************************************
48 # df_train.tail()
49 # print(df_train.head(1000))

  一直報這個錯:刪除原data.txt文件中的標題行即可。

# df_train=pd.DataFrame(data={'contents_clean':contents_clean,'label':df_news['category']})
# ValueError: array length 1000 does not match index length 1001
1 df_train.label.unique()
2 #array(['category', '財經', '女性', '體育', '奧運'], dtype=object)
1 # label_mapping = {"汽車": 1, "財經": 2, "科技": 3, "健康": 4, "體育":5, "教育": 6,"文化": 7,"軍事": 8,"娛樂": 9,"時尚": 0}
2 label_mapping = {"女性": 1, "財經": 2, "體育":5, "奧運": 6}
3 df_train['label'] = df_train['label'].map(label_mapping) #構建一個映射方法
4 df_train.head()
 1 from sklearn.model_selection import train_test_split
 2 
 3 x_train, x_test, y_train, y_test = train_test_split(df_train['contents_clean'].values, df_train['label'].values, random_state=1)
 4 
 5 #x_train = x_train.flatten()
 6 x_train[0][1]
 7 
 8 words = []
 9 for line_index in range(len(x_train)):
10     try:
11         #x_train[line_index][word_index] = str(x_train[line_index][word_index])
12         words.append(' '.join(x_train[line_index]))
13     except:
14         print (line_index,word_index)
15 words[0]        
1 print (len(words))

   到目前爲止,已經處理了標籤,切分了數據集,接下來就要提取文本特徵了,這裏通過一個小例子給大家介紹最簡單的詞袋模型。

 1 from sklearn.feature_extraction.text import CountVectorizer
 2 texts=["dog cat fish","dog cat cat","fish bird", 'bird'] #爲了簡單期間,這裏4句話我們就當做4篇文章了
 3 cv = CountVectorizer() #詞頻統計
 4 cv_fit=cv.fit_transform(texts) #轉換數據
 5 
 6 print(cv.get_feature_names())
 7 print(cv_fit.toarray())
 8 
 9 
10 print(cv_fit.toarray().sum(axis=0))
['bird', 'cat', 'dog', 'fish']
[[0 1 1 1]
 [0 2 1 0]
 [1 0 0 1]
 [1 0 0 0]]
[2 3 2 2]

  向sklearn中的feature_extraction.text模塊導入CountVectorizer,也就是詞袋模型要用的模塊,這裏還有很豐富的文本處理方法,感興趣的讀者也可以嘗試一下其他方法。爲了簡單起見,構造了4個句子,暫且當作4篇文章就好。觀察發現,這4篇文章中總共包含4個不同的詞:“bird”“cat”“dog”“fish”。所以詞袋模型的向量長度就是4,在結果中打印get_feature_names()可以得到特徵中各個位置的含義,例如,從第一個句子“dog cat fish”得到的向量爲[0 1 1 1],它的意思就是首先看第一個位置’bird’在這句話中有沒有出現,出現了幾次,結果爲0;接下來同樣看“cat”,發現出現了1次,那麼向量的第二個位置就爲1;同理“dog”“fish”在這句話中也各出現了1次,最終的結果也就得到了。

  詞袋模型是自然語言處理中最基礎的一種特徵提取方法,直白地說,它就是看每一個詞出現幾次,統計詞頻即可,再把所有出現的詞組成特徵的名字,依次統計其個數就能夠得到文本特徵。感覺有點過於簡單,只考慮詞頻,而不考慮詞出現的位置以及先後順序,能不能稍微改進一些呢?還可以通過設置ngram_range來控制特徵的複雜度,例如,不僅可以考慮單單一個詞,還可以考慮兩個詞連在一起,甚至更多的詞連在一起的組合。

 1 from sklearn.feature_extraction.text import CountVectorizer
 2 texts=["dog cat fish","dog cat cat","fish bird", 'bird']
 3 cv = CountVectorizer(ngram_range=(1,4)) #設置ngram參數,讓結果不光包含一個詞,還有2個,3個的組合
 4 cv_fit=cv.fit_transform(texts)
 5 
 6 print(cv.get_feature_names())
 7 print(cv_fit.toarray())
 8 
 9 
10 print(cv_fit.toarray().sum(axis=0))
['bird', 'cat', 'cat cat', 'cat fish', 'dog', 'dog cat', 'dog cat cat', 'dog cat fish', 'fish', 'fish bird']
[[0 1 0 1 1 1 0 1 1 0]
 [0 2 1 0 1 1 1 0 0 0]
 [1 0 0 0 0 0 0 0 1 1]
 [1 0 0 0 0 0 0 0 0 0]]
[2 3 1 1 2 2 1 1 2 1]

  這裏只加入ngram_range=(1,4)參數,其他保持不變,觀察結果中的特徵名字可以發現,此時不僅是一個詞,還有兩兩組合或三個組合在一起的情況。例如,“cat cat”表示文本中出現“cat”詞後面又跟了一個“cat”詞出現的個數。與之前的單個詞來對比,這次得到的特徵更復雜,特徵的長度明顯變多。可以考慮上下文的前後關係,在這個簡單的小例子中看起來沒什麼問題。如果實際文本中出現不同詞的個數成千上萬了呢?那使用ngram_range=(1,4)參數,得到的向量長度就太大了,用起來就很麻煩。所以,通常情況下,ngram參數一般設置爲2,如果大於2,計算起來就成累贅了。接下來對所有文本數據構建詞袋模型:

1 from sklearn.feature_extraction.text import CountVectorizer
2 
3 vec = CountVectorizer(analyzer='word', max_features=4000,  lowercase = False)
4 feature = vec.fit_transform(words)
1 feature.shape
2 #(750, 4000)

  在構建過程中,還額外加入了一個限制條件max_features=4000,表示得到的特徵最大長度爲4000,這就會自動過濾掉一些詞頻較小的詞語。如果不進行限制,大家也可以去掉這個參數觀察,會使得特徵長度過大,最終得到的向量長度爲85093,而且裏面很多都是詞頻很低的詞語,導致特徵過於稀疏,這些對建模來說都是不利的,所以,還是非常有必要加上這樣一個限制參數,特徵確定之後,剩下的任務就交給貝葉斯模型吧:

 1 from sklearn.naive_bayes import MultinomialNB #貝葉斯模型
 2 classifier = MultinomialNB() 
 3 classifier.fit(feature, y_train)
 4 
 5 test_words = []
 6 for line_index in range(len(x_test)):
 7     try:
 8         #
 9         test_words.append(' '.join(x_test[line_index]))
10     except:
11          print (line_index,word_index)
12 test_words[0]
13 
14 classifier.score(vec.transform(test_words), y_test)

 

  邀月工作室

   結果是0.936

  貝葉斯模型中導入了MultinomialNB模塊,還額外做了一些平滑處理,主要目的是在求解先驗概率和條件概率的時候避免其值爲0。詞袋模型的效果看起來還湊合,能不能改進一些呢?在這份特徵中,公平地對待每一個詞,也就是看這個詞出現的個數,而不管它重要與否,但看起來還是有點問題。因爲對於不同主題來說,有些詞可能更重要,有些詞就沒有什麼太大價值。還記得老朋友TF-IDF吧,能不能將其應用在特徵之中呢?當然是可以的,下面通過一個小例子來看一下吧:

 1 from sklearn.feature_extraction.text import TfidfVectorizer
 2 
 3 X_test = ['卡爾 敵法師 藍胖子 小小','卡爾 敵法師 藍胖子 痛苦女王']
 4 
 5 tfidf=TfidfVectorizer()
 6 weight=tfidf.fit_transform(X_test).toarray()
 7 word=tfidf.get_feature_names()
 8 print (weight)
 9 for i in range(len(weight)):  
10     print (u"", i, u"篇文章的tf-idf權重特徵")
11     for j in range(len(word)):
12         print (word[j], weight[i][j])
[[0.44832087 0.63009934 0.44832087 0.         0.44832087]
 [0.44832087 0.         0.44832087 0.63009934 0.44832087]]
第 0 篇文章的tf-idf權重特徵
卡爾 0.44832087319911734
小小 0.6300993445179441
敵法師 0.44832087319911734
痛苦女王 0.0
藍胖子 0.448320873199117341 篇文章的tf-idf權重特徵
卡爾 0.44832087319911734
小小 0.0
敵法師 0.44832087319911734
痛苦女王 0.6300993445179441
藍胖子 0.44832087319911734

  簡單寫了兩句話,就是要分別構建它們的特徵。一共出現5個詞,所以特徵的長度依舊爲5,這和詞袋模型是一樣的,接下來得到的特徵就是每一個詞的TF-IDF權重值,把它們組合在一起,就形成了特徵矩陣。觀察發現,在兩篇文章當中,唯一不同的就是“小小”和“痛苦女王”,其他詞都是一致的,所以要論重要程度,還是它們更有價值,其權重值自然更大。在結果中分別進行了打印,方便大家觀察。

  TfidfVectorizer()函數中可以加入很多參數來控制特徵(見圖11-9),比如過濾停用詞,最大特徵個數、詞頻最大、最小比例限制等,這些都會對結果產生不同的影響,建議大家使用的時候,還是先參考其API文檔,價值還是蠻大的,並且還有示例代碼。

  最後還是用同樣的模型對比一下兩種特徵提取方法的結果差異:(結果是0.916)

 

 1 from sklearn.feature_extraction.text import TfidfVectorizer
 2 
 3 vectorizer = TfidfVectorizer(analyzer='word', max_features=4000,  lowercase = False)
 4 vectorizer.fit(words)
 5 
 6 from sklearn.naive_bayes import MultinomialNB
 7 classifier = MultinomialNB()
 8 classifier.fit(vectorizer.transform(words), y_train)
 9 
10 classifier.score(vectorizer.transform(test_words), y_test)

   邀月工作室

  效果比之前的詞袋模型有所提高,這也在預料之中,那麼,還有沒有其他更好的特徵提取方法呢?上一章中曾提到word2vec詞向量模型,這裏當然也可以使用,只不過難點在於如何將詞向量轉換成文章向量,傳統機器學習算法在處理時間序列相關特徵時,效果還是有所欠缺,等弄懂神經網絡之後,再向大家展示如何應用詞向量特徵,感興趣的同學可以先預習gensim工具包,自然語言處理任務肯定會用上它。

   gensim工具包不只有word2vec模塊,主題模型,文章向量等都有具體的實現和示例代碼,學習價值還是很大的。

 

項目小結:

  本章首先講解了貝葉斯算法,通過兩個小例子,拼寫糾錯和垃圾郵件分類任務概述了貝葉斯算法求解實際問題的流程。以新聞文本數據集爲例,從分詞、數據清洗以及特徵提取開始一步步完成文本分類任務。建議大家在學習過程中先弄清楚每一步的流程和目的,然後再完成核心代碼操作,機器學習的難點不只在建模中,數據清洗和預處理依舊是一個難題,尤其是在自然語言處理中。

 

第11章完。

python數據分析個人學習讀書筆記-目錄索引

 

該書資源下載,請至異步社區:https://www.epubit.com

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章