得知李航老師的《統計學習方法》出了第二版,我第一時間就買了。看了這本書的目錄,非常高興,好傢伙,居然把主題模型都寫了,還有pagerank。一路看到了馬爾科夫蒙特卡羅方法和LDA主題模型這裡,被打擊到了,滿滿都是數學公式。LDA是目前為止我見過最複雜的模型了。
找了培訓班的視訊看,對LDA模型有了大致的認識。下面總結一點東西。
1、LDA與PLSA的聯絡
LDA模型和PLSA的聯絡非常緊密,都是概率模型(LSA是非概率模型),是利用概率生成模型對文字集合進行主題分析的無監督學習方法。
不同在於,PLSA是用了頻率學派的方法,用極大似然估計進行學習,而LDA是用了貝葉斯學派的方法,進行貝葉斯推斷,所以LDA就是在pLSA的基礎上加了⻉葉斯框架,即LDA就是pLSA的⻉葉斯版本 。
LDA和PLSA都假設存在兩個多項分佈:話題是單詞的多項分佈,文字是話題的多項分佈。不同在於,LDA認為多項分佈的引數也服從一個分佈,而不是固定不變的,使用狄利克雷分佈作為多項分佈的先驗分佈,也就是多項分佈的引數服從狄利克雷分佈。
為啥引入先驗分佈呢?因為這樣能防止過擬合。為啥選擇狄利克雷分佈呢作為先驗分佈呢?因為狄利克雷分佈是多項分佈的共軛先驗分佈,那麼先驗分佈和後驗分佈的形式相同,便於由先驗分佈得到後驗分佈。
2、LDA的文字集合生成過程
首先由狄立克雷分佈得到話題分佈的引數的分佈,然後隨機生成一個文字的話題分佈,之後在該文字的每個位置,依據該文字的話題分佈隨機生成一個話題;
然後由狄利克雷分佈得到單詞分佈的引數的分佈,再得到話題的單詞分佈,在該位置依據該話題的單詞分佈隨機生成一個單詞,直到文字的最後一個位置,生成整個文字;
最後重複以上過程,生成所有的文字。
下面是兩個小案例,用gensim訓練LDA模型,進行新聞文字主題抽取,還有一個是希拉蕊郵件的主題抽取。
github:https://github.com/DengYangyong/LDA_gensim
一、LDA新聞文字主題抽取
第一步:對新聞進行分詞
這次使用的新聞文件中有5000條新聞,有10類新聞,['體育', '財經', '房產', '家居', '教育', '科技', '時尚', '時政', '遊戲', '娛樂'],每類有500條新聞。首先對文字進行清洗,去掉停用詞、非漢字的特殊字元等。然後用jieba進行分詞,將分詞結果儲存好。
#!/usr/bin/python # -*- coding:utf-8 -*- import jieba,os,re from gensim import corpora, models, similarities """建立停用詞列表""" def stopwordslist(): stopwords = [line.strip() for line in open('./stopwords.txt',encoding='UTF-8').readlines()] return stopwords """對句子進行中文分詞""" def seg_depart(sentence): sentence_depart = jieba.cut(sentence.strip()) stopwords = stopwordslist() outstr = '' for word in sentence_depart: if word not in stopwords: outstr += word outstr += " " # outstr:'黃蜂 湖人 首發 科比 帶傷 戰 保羅 加索爾 ...' return outstr """如果文件還沒分詞,就進行分詞""" if not os.path.exists('./cnews.train_jieba.txt'): # 給出文件路徑 filename = "./cnews.train.txt" outfilename = "./cnews.train_jieba.txt" inputs = open(filename, 'r', encoding='UTF-8') outputs = open(outfilename, 'w', encoding='UTF-8') # 把非漢字的字元全部去掉 for line in inputs: line = line.split('\t')[1] line = re.sub(r'[^\u4e00-\u9fa5]+','',line) line_seg = seg_depart(line.strip()) outputs.write(line_seg.strip() + '\n') outputs.close() inputs.close() print("刪除停用詞和分詞成功!!!")
第二步:構建詞頻矩陣,訓練LDA模型
gensim所需要的輸入格式為:['黃蜂', '湖人', '首發', '科比', '帶傷', '戰',...],也就是每篇文件是一個列表,元素為詞語。
然後構建語料庫,再利用語料庫把每篇新聞進行數字化,corpus就是數字化後的結果。
第一條新聞ID化後的結果為corpus[0]:[(0, 1), (1, 1), (2, 1), (3, 1), (4, 1),...],每個元素是新聞中的每個詞語的ID和頻率。
最後訓練LDA模型。LDA是一種無監督學習方法,我們可以自由選擇主題的個數。這裡我們做了弊,事先知道了新聞有10類,就選擇10個主題吧。
LDA模型訓練好之後,我們可以檢視10個主題的單詞分佈。
第6個主題(從0開始計數)的單詞分佈如下。還行,從“拍攝、電影、柯達”這些詞,可以大致看出是娛樂主題。
(5, '0.007*"中" + 0.004*"拍攝" + 0.004*"說" + 0.003*"英語" + 0.002*"時間" + 0.002*"柯達" + 0.002*"中國" + 0.002*"國泰" + 0.002*"市場" + 0.002*"電影"')
從第10個主題的單詞分佈也大致可以看出是財經主題。
(9, '0.085*"基金" + 0.016*"市場" + 0.014*"公司" + 0.013*"投資" + 0.012*"股票" + 0.011*"分紅" + 0.008*"中" + 0.007*"一季度" + 0.006*"經理" + 0.006*"收益"')
但效果還是不太令人滿意,因為其他的主題不太看得出來是什麼。
"""準備好訓練語料,整理成gensim需要的輸入格式""" fr = open('./cnews.train_jieba.txt', 'r',encoding='utf-8') train = [] for line in fr.readlines(): line = [word.strip() for word in line.split(' ')] train.append(line) # train: [['黃蜂', '湖人', '首發', '科比', '帶傷', '戰',...],[...],...] """構建詞頻矩陣,訓練LDA模型""" dictionary = corpora.Dictionary(train) # corpus[0]: [(0, 1), (1, 1), (2, 1), (3, 1), (4, 1),...] # corpus是把每條新聞ID化後的結果,每個元素是新聞中的每個詞語,在字典中的ID和頻率 corpus = [dictionary.doc2bow(text) for text in train] lda = models.LdaModel(corpus=corpus, id2word=dictionary, num_topics=10) topic_list = lda.print_topics(10) print("10個主題的單詞分佈為:\n") for topic in topic_list: print(topic)
10個主題的單詞分佈為: (0, '0.008*"中" + 0.005*"市場" + 0.004*"中國" + 0.004*"貨幣" + 0.004*"託管" + 0.003*"新" + 0.003*"債券" + 0.003*"說" + 0.003*"公司" + 0.003*"做"') (1, '0.081*"基金" + 0.013*"公司" + 0.011*"投資" + 0.008*"行業" + 0.007*"中國" + 0.007*"市場" + 0.007*"中" + 0.007*"億元" + 0.006*"規模" + 0.005*"新"') (2, '0.013*"功能" + 0.009*"採用" + 0.008*"機身" + 0.007*"設計" + 0.007*"支援" + 0.007*"中" + 0.005*"玩家" + 0.005*"拍攝" + 0.005*"擁有" + 0.005*"倍"') (3, '0.007*"中" + 0.006*"佣金" + 0.006*"企業" + 0.004*"考" + 0.004*"萬家" + 0.003*"市場" + 0.003*"單詞" + 0.003*"櫥櫃" + 0.003*"說" + 0.003*"行業"') (4, '0.012*"拍攝" + 0.007*"中" + 0.007*"萬" + 0.006*"鏡頭" + 0.005*"搭載" + 0.005*"英寸" + 0.005*"高清" + 0.005*"約" + 0.004*"擁有" + 0.004*"元"') (5, '0.007*"中" + 0.004*"拍攝" + 0.004*"說" + 0.003*"英語" + 0.002*"時間" + 0.002*"柯達" + 0.002*"中國" + 0.002*"國泰" + 0.002*"市場" + 0.002*"電影"') (6, '0.024*"考試" + 0.010*"相機" + 0.008*"套裝" + 0.007*"拍攝" + 0.005*"萬" + 0.005*"玩家" + 0.005*"中" + 0.004*"英寸" + 0.004*"索尼" + 0.004*"四級"') (7, '0.019*"贖回" + 0.007*"基金" + 0.007*"淨" + 0.006*"中" + 0.004*"市場" + 0.004*"資產" + 0.004*"收益" + 0.003*"中國" + 0.003*"債券" + 0.003*"說"') (8, '0.010*"基金" + 0.010*"中" + 0.006*"公司" + 0.005*"產品" + 0.005*"市場" + 0.004*"元" + 0.004*"中國" + 0.004*"投資" + 0.004*"資訊" + 0.004*"考試"') (9, '0.085*"基金" + 0.016*"市場" + 0.014*"公司" + 0.013*"投資" + 0.012*"股票" + 0.011*"分紅" + 0.008*"中" + 0.007*"一季度" + 0.006*"經理" + 0.006*"收益"')
第三步:抽取新聞的主題
我們還可以利用訓練好的LDA,得到一條新聞的主題分佈,也就是一條新聞屬於各主題的可能性的概率分佈。
找了三條新聞,分別是體育,娛樂和科技新聞:
體育 馬曉旭意外受傷讓國奧警惕 無奈大雨格外青睞殷家軍記者傅亞雨瀋陽報導 來到瀋陽,國奧隊依然沒有擺脫雨水的困擾 ...
娛樂 尚雯婕籌備回滬獻演□晨報記者 郭翔鶴 北京攝影報導 3月在北京舉行了自己的首唱“尚佳分享·尚雯婕2008北京演唱會”後 ...
科技 摩托羅拉:GPON在FTTH中比EPON更有優勢作 者:魯義軒2009年,在國內光進銅退的火熱趨勢下,摩托羅拉攜其在...
然後同樣進行分詞、ID化,通過lda.get_document_topics(corpus_test) 這個函式得到每條新聞的主題分佈。得到新聞的主題分佈之後,通過計算餘弦距離,應該也可以進行文字相似度比較。
從結果中可以看到體育新聞的第6個主題的權重最大:(5, 0.60399055),可惜從第6個主題的單詞分佈來看,貌似這是個娛樂主題。
娛樂新聞的主題分佈中,第5個主題的權重最大:(4, 0.46593386),而科技新聞的主題分佈中,第3個主題的權重最大:(2, 0.38577113)。
"""抽取新聞的主題""" # 用來測試的三條新聞,分別為體育、娛樂和科技新聞 file_test = "./cnews.test.txt" news_test = open(file_test, 'r', encoding='UTF-8') test = [] # 處理成正確的輸入格式 for line in news_test: line = line.split('\t')[1] line = re.sub(r'[^\u4e00-\u9fa5]+','',line) line_seg = seg_depart(line.strip()) line_seg = [word.strip() for word in line_seg.split(' ')] test.append(line_seg) # 新聞ID化 corpus_test = [dictionary.doc2bow(text) for text in test] # 得到每條新聞的主題分佈 topics_test = lda.get_document_topics(corpus_test) labels = ['體育','娛樂','科技'] for i in range(3): print('這條'+labels[i]+'新聞的主題分佈為:\n') print(topics_test[i],'\n') fr.close() news_test.close()
這條體育新聞的主題分佈為: [(2, 0.022305986), (3, 0.20627314), (4, 0.039145608), (5, 0.60399055), (7, 0.1253269)] 這條娛樂新聞的主題分佈為: [(3, 0.06871579), (4, 0.46593386), (7, 0.23081028), (8, 0.23132402)] 這條科技新聞的主題分佈為: [(2, 0.38577113), (5, 0.14801453), (6, 0.09730849), (7, 0.36559567)]
二、希拉蕊郵件門主題抽取
在美國大選期間,希拉蕊的郵件被洩露出來了,有6000多封郵件,我們可以用LDA主題模型對這些郵件的進行主題抽取,得到每個主題的單詞分佈,和每封郵件的主題分佈。
還可以利用訓練好模型,得到新郵件的主題分佈。
步驟和以上的案例差不多,只是不需要進行分詞。
第一步:用正規表示式清洗資料,並去除停用詞
#!/usr/bin/python # -*- coding:utf-8 -*- import numpy as np import pandas as pd import re from gensim import corpora, models, similarities import gensim """第一步:用正規表示式清洗資料,並去除停用詞""" df = pd.read_csv("HillaryEmails.csv") # 原郵件資料中有很多Nan的值,直接扔了。 df = df[['Id','ExtractedBodyText']].dropna() # 用正規表示式清洗資料 def clean_email_text(text): text = text.replace('\n'," ") # 新行,我們是不需要的 text = re.sub(r"-", " ", text) # 把 "-" 的兩個單詞,分開。(比如:july-edu ==> july edu) text = re.sub(r"\d+/\d+/\d+", "", text) # 日期,對主體模型沒什麼意義 text = re.sub(r"[0-2]?[0-9]:[0-6][0-9]", "", text) # 時間,沒意義 text = re.sub(r"[\w]+@[\.\w]+", "", text) # 郵件地址,沒意義 text = re.sub(r"/[a-zA-Z]*[:\//\]*[A-Za-z0-9\-_]+\.+[A-Za-z0-9\.\/%&=\?\-_]+/i", "", text) # 網址,沒意義 # 以防還有其他除了單詞以外的特殊字元(數字)等等,我們把特殊字元過濾掉 # 只留下字母和空格 # 再把單個字母去掉,留下單詞 pure_text = '' for letter in text: if letter.isalpha() or letter==' ': pure_text += letter text = ' '.join(word for word in pure_text.split() if len(word)>1) return text docs_text = df['ExtractedBodyText'] docs = docs_text.apply(lambda s: clean_email_text(s)) # 得到所有郵件的內容 doclist = docs.values print("一共有",len(doclist),"封郵件。\n") print("第1封郵件未清洗前的內容為: \n",docs_text.iloc[0],'\n') # 去除停用詞,處理成gensim需要的輸入格式 stopwords = [word.strip() for word in open('./stopwords.txt','r').readlines()] # 每一封郵件都有星期和月份,這裡也把他們過濾掉 weeks = ['monday','mon','tuesday','tues','wednesday','wed','thursday','thur','friday','fri','saturday','sat','sunday','sun'] months = ['jan','january','feb','february','mar','march','apr','april','may','jun','june','jul',\ 'july','aug','august','sept','september','oct','october','nov','november','dec','december'] stoplist = stopwords+weeks+months+['am','pm'] texts = [[word for word in doc.lower().split() if word not in stoplist] for doc in doclist] texts = [[word for word in doc.lower().split() if word not in stoplist] for doc in doclist] print("第1封郵件去除停用詞並處理成gensim需要的格式為:\n",texts[0],'\n')
一共有 6742 封郵件。 第1封郵件未清洗前的內容為: B6 Thursday, March 3, 2011 9:45 PM H: Latest How Syria is aiding Qaddafi and more... Sid hrc memo syria aiding libya 030311.docx; hrc memo syria aiding libya 030311.docx March 3, 2011 For: Hillary 第1封郵件去除停用詞並處理成gensim需要的格式為: ['latest', 'syria', 'aiding', 'qaddafi', 'sid', 'hrc', 'memo', 'syria', 'aiding', 'libya', 'docx', 'hrc', 'memo', 'syria', 'aiding', 'libya', 'docx', 'hillary']
第二步:構建語料庫,訓練LDA模型
這個英文的stopwordlist感覺不太行,從最終得到的單詞分佈來看,us、would這種詞居然還有。這些單詞看得眼睛都花了,不容看出來主題是啥。
我們看第8個主題的單詞分佈,裡面的詞有:state,obama,president,government,估計這個主題與當前總統有關。
(7, '0.008*"us" + 0.008*"new" + 0.007*"would" + 0.005*"state" + 0.005*"obama" + 0.004*"one" + 0.004*"said" + 0.004*"president" + 0.003*"first" + 0.003*"government"'),
"""第二步:構建語料庫,將文字ID化""" dictionary = corpora.Dictionary(texts) corpus = [dictionary.doc2bow(text) for text in texts] # 將每一篇郵件ID化 print("第1封郵件ID化後的結果為:\n",corpus[0],'\n') """訓練LDA模型""" lda = gensim.models.ldamodel.LdaModel(corpus=corpus, id2word=dictionary, num_topics=20) # 第10個主題的單詞分佈,取權重最高的前10個詞 print(lda.print_topic(9, topn=10)) # 所有主題的單詞分佈 print(lda.print_topics(num_topics=20, num_words=10))
第1封郵件ID化後的結果為: [(0, 3), (1, 2), (2, 1), (3, 2), (4, 1), (5, 2), (6, 2), (7, 1), (8, 1), (9, 3)] [(0, '0.008*"us" + 0.008*"state" + 0.006*"doc" + 0.006*"afghan" + 0.005*"taliban" + 0.005*"said" + 0.003*"department" + 0.003*"strategic" + 0.003*"diplomacy" + 0.003*"afghanistan"'),
(1, '0.019*"pls" + 0.014*"call" + 0.013*"cheryl" + 0.013*"print" + 0.012*"fw" + 0.011*"mills" + 0.010*"state" + 0.010*"sullivan" + 0.009*"secretary" + 0.008*"huma"'),
(2, '0.012*"get" + 0.010*"see" + 0.009*"call" + 0.008*"good" + 0.008*"im" + 0.007*"thx" + 0.007*"know" + 0.007*"think" + 0.007*"today" + 0.007*"like"'),
(3, '0.069*"fyi" + 0.007*"sbwhoeop" + 0.006*"sid" + 0.005*"waldorf" + 0.005*"talk" + 0.004*"organizing" + 0.004*"fw" + 0.004*"abedin" + 0.004*"agree" + 0.004*"huma"'),
(4, '0.004*"ri" + 0.003*"phil" + 0.003*"yeah" + 0.003*"consulted" + 0.003*"arrange" + 0.003*"mayors" + 0.003*"cloture" + 0.003*"windows" + 0.002*"denis" + 0.002*"miliband"'),
(5, '0.007*"us" + 0.006*"people" + 0.006*"would" + 0.006*"one" + 0.006*"american" + 0.005*"israel" + 0.005*"said" + 0.004*"government" + 0.004*"united" + 0.004*"also"'),
(6, '0.012*"yes" + 0.009*"tomorrow" + 0.007*"boehner" + 0.006*"kurdistan" + 0.006*"still" + 0.005*"message" + 0.005*"talk" + 0.005*"call" + 0.004*"ops" + 0.004*"would"'),
(7, '0.008*"us" + 0.008*"new" + 0.007*"would" + 0.005*"state" + 0.005*"obama" + 0.004*"one" + 0.004*"said" + 0.004*"president" + 0.003*"first" + 0.003*"government"'),
(8, '0.008*"president" + 0.008*"obama" + 0.007*"said" + 0.006*"white" + 0.005*"house" + 0.005*"state" + 0.005*"percent" + 0.005*"ok" + 0.005*"new" + 0.005*"one"'),
(9, '0.024*"office" + 0.017*"secretarys" + 0.013*"meeting" + 0.012*"room" + 0.009*"state" + 0.009*"time" + 0.008*"department" + 0.008*"call" + 0.007*"treaty" + 0.007*"arrive"')]
第三步:檢視郵件的主題分佈
檢視了第一封郵件的主題分佈,然後推測了希拉蕊兩條推特的主題。
"""第三步:檢視某封郵件所屬的主題""" print("第1封郵件的大致內容為:\n",texts[0],'\n') topic = lda.get_document_topics(corpus[0]) print("第1封郵件的主題分佈為:\n",topic,'\n') # 希拉蕊發的兩條推特 # 給大夥翻譯一下這兩句: # 這是選舉的一天!數以百萬計的美國人投了希拉蕊的票。加入他們吧,確定你投給誰。 # 希望今天每個人都能度過一個安樂的感恩節,和家人朋友共度美好時光——來自希拉蕊的問候。 twitter = ["It's Election Day! Millions of Americans have cast their votes for Hillary—join them and confirm where you vote ", "Hoping everyone has a safe & Happy Thanksgiving today, & quality time with family & friends. -H"] text_twitter = [clean_email_text(s) for s in twitter] text_twitter = [[word for word in text.lower().split() if word not in stoplist] for text in text_twitter] corpus_twitter = [dictionary.doc2bow(text) for text in text_twitter] topics_twitter = lda.get_document_topics(corpus_twitter) print("這兩條推特的主題分佈分別為:\n",topics_twitter[0] ,'\n',topics_twitter[1])
第1封郵件的大致內容為: ['latest', 'syria', 'aiding', 'qaddafi', 'sid', 'hrc', 'memo', 'syria', 'aiding', 'libya', 'docx', 'hrc', 'memo', 'syria', 'aiding', 'libya', 'docx', 'hillary'] 第1封郵件的主題分佈為: [(7, 0.9499477)] 這兩條推特的主題分佈分別為: [(0, 0.23324193), (15, 0.6667277)] [(0, 0.34214944), (7, 0.23708023), (9, 0.34343135)]
參考資料:
1、李航:《統計學習方法》(第二版)
2、某培訓班資料