LDA主題模型原理解析與python實(shí)現(xiàn)

文章轉(zhuǎn)自:

wind_blast

LDA(Latent dirichlet allocation)[1]是有Blei于2003年提出的三層貝葉斯主題模型暖侨,通過(guò)無(wú)監(jiān)督的學(xué)習(xí)方法發(fā)現(xiàn)文本中隱含的主題信息纵竖,目的是要以無(wú)指導(dǎo)學(xué)習(xí)的方法從文本中發(fā)現(xiàn)隱含的語(yǔ)義維度-即“Topic”或者“Concept”。隱性語(yǔ)義分析的實(shí)質(zhì)是要利用文本中詞項(xiàng)(term)的共現(xiàn)特征來(lái)發(fā)現(xiàn)文本的Topic結(jié)構(gòu),這種方法不需要任何關(guān)于文本的背景知識(shí)。文本的隱性語(yǔ)義表示可以對(duì)“一詞多義”和“一義多詞”的語(yǔ)言現(xiàn)象進(jìn)行建模,這使得搜索引擎系統(tǒng)得到的搜索結(jié)果與用戶的query在語(yǔ)義層次上match规哪,而不是僅僅只是在詞匯層次上出現(xiàn)交集。文檔塌衰、主題以及詞可以表示為下圖:

LDA參數(shù):

K為主題個(gè)數(shù)诉稍,M為文檔總數(shù),是第m個(gè)文檔的單詞總數(shù)最疆。 是每個(gè)Topic下詞的多項(xiàng)分布的Dirichlet先驗(yàn)參數(shù)杯巨, 是每個(gè)文檔下Topic的多項(xiàng)分布的Dirichlet先驗(yàn)參數(shù)。是第m個(gè)文檔中第n個(gè)詞的主題肚菠,是m個(gè)文檔中的第n個(gè)詞舔箭。剩下來(lái)的兩個(gè)隱含變量和分別表示第m個(gè)文檔下的Topic分布和第k個(gè)Topic下詞的分布,前者是k維(k為T(mén)opic總數(shù))向量蚊逢,后者是v維向量(v為詞典中term總數(shù))层扶。

LDA生成過(guò)程:

所謂生成模型,就是說(shuō)烙荷,我們認(rèn)為一篇文章的每個(gè)詞都是通過(guò)“以一定概率選擇了某個(gè)主題镜会,并從這個(gè)主題中以一定概率選擇某個(gè)詞語(yǔ)”這樣一個(gè)過(guò)程得到。文檔到主題服從多項(xiàng)式分布终抽,主題到詞服從多項(xiàng)式分布戳表。每一篇文檔代表了一些主題所構(gòu)成的一個(gè)概率分布,而每一個(gè)主題又代表了很多單詞所構(gòu)成的一個(gè)概率分布昼伴。

Gibbs Sampling學(xué)習(xí)LDA:

Gibbs Sampling 是Markov-Chain Monte Carlo算法的一個(gè)特例匾旭。這個(gè)算法的運(yùn)行方式是每次選取概率向量的一個(gè)維度,給定其他維度的變量值Sample當(dāng)前維度的值圃郊。不斷迭代价涝,直到收斂輸出待估計(jì)的參數(shù)。初始時(shí)隨機(jī)給文本中的每個(gè)單詞分配主題,然后統(tǒng)計(jì)每個(gè)主題z下出現(xiàn)term t的數(shù)量以及每個(gè)文檔m下出現(xiàn)主題z中的詞的數(shù)量持舆,每一輪計(jì)算色瘩,即排除當(dāng)前詞的主題分配,根據(jù)其他所有詞的主題分配估計(jì)當(dāng)前詞分配各個(gè)主題的概率逸寓。當(dāng)?shù)玫疆?dāng)前詞屬于所有主題z的概率分布后居兆,根據(jù)這個(gè)概率分布為該詞sample一個(gè)新的主題。然后用同樣的方法不斷更新下一個(gè)詞的主題竹伸,直到發(fā)現(xiàn)每個(gè)文檔下Topic分布和每個(gè)Topic下詞的分布收斂泥栖,算法停止,輸出待估計(jì)的參數(shù)和,最終每個(gè)單詞的主題也同時(shí)得出聊倔。實(shí)際應(yīng)用中會(huì)設(shè)置最大迭代次數(shù)晦毙。每一次計(jì)算的公式稱為Gibbs updating rule.下面我們來(lái)推導(dǎo)LDA的聯(lián)合分布和Gibbs updating rule生巡。

用Gibbs Sampling 學(xué)習(xí)LDA參數(shù)的算法偽代碼如下:

python實(shí)現(xiàn):

-- coding:utf-8 --

import logging
import logging.config
import ConfigParser
import numpy as np
import random
import codecs
import os

from collections import OrderedDict

獲取當(dāng)前路徑

path = os.getcwd()

導(dǎo)入日志配置文件

logging.config.fileConfig("logging.conf")

創(chuàng)建日志對(duì)象

logger = logging.getLogger()

loggerInfo = logging.getLogger("TimeInfoLogger")

Consolelogger = logging.getLogger("ConsoleLogger")

導(dǎo)入配置文件

conf = ConfigParser.ConfigParser()
conf.read("setting.conf")

文件路徑

trainfile = os.path.join(path,os.path.normpath(conf.get("filepath", "trainfile")))
wordidmapfile = os.path.join(path,os.path.normpath(conf.get("filepath","wordidmapfile")))
thetafile = os.path.join(path,os.path.normpath(conf.get("filepath","thetafile")))
phifile = os.path.join(path,os.path.normpath(conf.get("filepath","phifile")))
paramfile = os.path.join(path,os.path.normpath(conf.get("filepath","paramfile")))
topNfile = os.path.join(path,os.path.normpath(conf.get("filepath","topNfile")))
tassginfile = os.path.join(path,os.path.normpath(conf.get("filepath","tassginfile")))

模型初始參數(shù)

K = int(conf.get("model_args","K"))
alpha = float(conf.get("model_args","alpha"))
beta = float(conf.get("model_args","beta"))
iter_times = int(conf.get("model_args","iter_times"))
top_words_num = int(conf.get("model_args","top_words_num"))
class Document(object):
def init(self):
self.words = []
self.length = 0

把整個(gè)文檔及真的單詞構(gòu)成vocabulary(不允許重復(fù))

class DataPreProcessing(object):
def init(self):
self.docs_count = 0
self.words_count = 0
#保存每個(gè)文檔d的信息(單詞序列耙蔑,以及l(fā)ength)
self.docs = []
#建立vocabulary表,照片文檔的單詞
self.word2id = OrderedDict()
def cachewordidmap(self):
with codecs.open(wordidmapfile, 'w','utf-8') as f:
for word,id in self.word2id.items():
f.write(word +"\t"+str(id)+"\n")
class LDAModel(object):
def init(self,dpre):
self.dpre = dpre #獲取預(yù)處理參數(shù)
#
#模型參數(shù)
#聚類(lèi)個(gè)數(shù)K孤荣,迭代次數(shù)iter_times,每個(gè)類(lèi)特征詞個(gè)數(shù)top_words_num,超參數(shù)α(alpha) β(beta)
#
self.K = K
self.beta = beta
self.alpha = alpha
self.iter_times = iter_times
self.top_words_num = top_words_num
#
#文件變量
#分好詞的文件trainfile
#詞對(duì)應(yīng)id文件wordidmapfile
#文章-主題分布文件thetafile
#詞-主題分布文件phifile
#每個(gè)主題topN詞文件topNfile
#最后分派結(jié)果文件tassginfile
#模型訓(xùn)練選擇的參數(shù)文件paramfile
#
self.wordidmapfile = wordidmapfile
self.trainfile = trainfile
self.thetafile = thetafile
self.phifile = phifile
self.topNfile = topNfile
self.tassginfile = tassginfile
self.paramfile = paramfile
# p,概率向量 double類(lèi)型甸陌,存儲(chǔ)采樣的臨時(shí)變量
# nw,詞word在主題topic上的分布
# nwsum,每各topic的詞的總數(shù)
# nd,每個(gè)doc中各個(gè)topic的詞的總數(shù)
# ndsum,每各doc中詞的總數(shù)
self.p = np.zeros(self.K)
# nw,詞word在主題topic上的分布
self.nw = np.zeros((self.dpre.words_count,self.K),dtype="int")
# nwsum,每各topic的詞的總數(shù)
self.nwsum = np.zeros(self.K,dtype="int")
# nd,每個(gè)doc中各個(gè)topic的詞的總數(shù)
self.nd = np.zeros((self.dpre.docs_count,self.K),dtype="int")
# ndsum,每各doc中詞的總數(shù)
self.ndsum = np.zeros(dpre.docs_count,dtype="int")
self.Z = np.array([ [0 for y in xrange(dpre.docs[x].length)] for x in xrange(dpre.docs_count)]) # M*doc.size(),文檔中詞的主題分布

    #隨機(jī)先分配類(lèi)型盐股,為每個(gè)文檔中的各個(gè)單詞分配主題
    for x in xrange(len(self.Z)):
        self.ndsum[x] = self.dpre.docs[x].length
        for y in xrange(self.dpre.docs[x].length):
            topic = random.randint(0,self.K-1)#隨機(jī)取一個(gè)主題
            self.Z[x][y] = topic#文檔中詞的主題分布
            self.nw[self.dpre.docs[x].words[y]][topic] += 1
            self.nd[x][topic] += 1
            self.nwsum[topic] += 1

    self.theta = np.array([ [0.0 for y in xrange(self.K)] for x in xrange(self.dpre.docs_count) ])
    self.phi = np.array([ [ 0.0 for y in xrange(self.dpre.words_count) ] for x in xrange(self.K)]) 
def sampling(self,i,j):
    #換主題
    topic = self.Z[i][j]
    #只是單詞的編號(hào)钱豁,都是從0開(kāi)始word就是等于j
    word = self.dpre.docs[i].words[j]
    #if word==j:
    #    print 'true'
    self.nw[word][topic] -= 1
    self.nd[i][topic] -= 1
    self.nwsum[topic] -= 1
    self.ndsum[i] -= 1

    Vbeta = self.dpre.words_count * self.beta
    Kalpha = self.K * self.alpha
    self.p = (self.nw[word] + self.beta)/(self.nwsum + Vbeta) * \
             (self.nd[i] + self.alpha) / (self.ndsum[i] + Kalpha)

    #隨機(jī)更新主題的嗎
    # for k in xrange(1,self.K):
    #     self.p[k] += self.p[k-1]
    # u = random.uniform(0,self.p[self.K-1])
    # for topic in xrange(self.K):
    #     if self.p[topic]>u:
    #         break

    #按這個(gè)更新主題更好理解,這個(gè)效果還不錯(cuò)
    p = np.squeeze(np.asarray(self.p/np.sum(self.p)))
    topic = np.argmax(np.random.multinomial(1, p))

    self.nw[word][topic] +=1
    self.nwsum[topic] +=1
    self.nd[i][topic] +=1
    self.ndsum[i] +=1
    return topic
def est(self):
    # Consolelogger.info(u"迭代次數(shù)為%s 次" % self.iter_times)
    for x in xrange(self.iter_times):
        for i in xrange(self.dpre.docs_count):
            for j in xrange(self.dpre.docs[i].length):
                topic = self.sampling(i,j)
                self.Z[i][j] = topic
    logger.info(u"迭代完成疯汁。")
    logger.debug(u"計(jì)算文章-主題分布")
    self._theta()
    logger.debug(u"計(jì)算詞-主題分布")
    self._phi()
    logger.debug(u"保存模型")
    self.save()
def _theta(self):
    for i in xrange(self.dpre.docs_count):#遍歷文檔的個(gè)數(shù)詞
        self.theta[i] = (self.nd[i]+self.alpha)/(self.ndsum[i]+self.K * self.alpha)
def _phi(self):
    for i in xrange(self.K):
        self.phi[i] = (self.nw.T[i] + self.beta)/(self.nwsum[i]+self.dpre.words_count * self.beta)
def save(self):
    # 保存theta文章-主題分布
    logger.info(u"文章-主題分布已保存到%s" % self.thetafile)
    with codecs.open(self.thetafile,'w') as f:
        for x in xrange(self.dpre.docs_count):
            for y in xrange(self.K):
                f.write(str(self.theta[x][y]) + '\t')
            f.write('\n')
    # 保存phi詞-主題分布
    logger.info(u"詞-主題分布已保存到%s" % self.phifile)
    with codecs.open(self.phifile,'w') as f:
        for x in xrange(self.K):
            for y in xrange(self.dpre.words_count):
                f.write(str(self.phi[x][y]) + '\t')
            f.write('\n')
    # 保存參數(shù)設(shè)置
    logger.info(u"參數(shù)設(shè)置已保存到%s" % self.paramfile)
    with codecs.open(self.paramfile,'w','utf-8') as f:
        f.write('K=' + str(self.K) + '\n')
        f.write('alpha=' + str(self.alpha) + '\n')
        f.write('beta=' + str(self.beta) + '\n')
        f.write(u'迭代次數(shù)  iter_times=' + str(self.iter_times) + '\n')
        f.write(u'每個(gè)類(lèi)的高頻詞顯示個(gè)數(shù)  top_words_num=' + str(self.top_words_num) + '\n')
    # 保存每個(gè)主題topic的詞
    logger.info(u"主題topN詞已保存到%s" % self.topNfile)

    with codecs.open(self.topNfile,'w','utf-8') as f:
        self.top_words_num = min(self.top_words_num,self.dpre.words_count)
        for x in xrange(self.K):
            f.write(u'第' + str(x) + u'類(lèi):' + '\n')
            twords = []
            twords = [(n,self.phi[x][n]) for n in xrange(self.dpre.words_count)]
            twords.sort(key = lambda i:i[1], reverse= True)
            for y in xrange(self.top_words_num):
                word = OrderedDict({value:key for key, value in self.dpre.word2id.items()})[twords[y][0]]
                f.write('\t'*2+ word +'\t' + str(twords[y][1])+ '\n')
    # 保存最后退出時(shí)牲尺,文章的詞分派的主題的結(jié)果
    logger.info(u"文章-詞-主題分派結(jié)果已保存到%s" % self.tassginfile)
    with codecs.open(self.tassginfile,'w') as f:
        for x in xrange(self.dpre.docs_count):
            for y in xrange(self.dpre.docs[x].length):
                f.write(str(self.dpre.docs[x].words[y])+':'+str(self.Z[x][y])+ '\t')
            f.write('\n')
    logger.info(u"模型訓(xùn)練完成。")

數(shù)據(jù)預(yù)處理幌蚊,即:生成d()單詞序列谤碳,以及詞匯表

def preprocessing():
logger.info(u'載入數(shù)據(jù)......')
with codecs.open(trainfile, 'r','utf-8') as f:
docs = f.readlines()
logger.debug(u"載入完成,準(zhǔn)備生成字典對(duì)象和統(tǒng)計(jì)文本數(shù)據(jù)...")
# 大的文檔集
dpre = DataPreProcessing()
items_idx = 0
for line in docs:
if line != "":
tmp = line.strip().split()
# 生成一個(gè)文檔對(duì)象:包含單詞序列(w1,w2,w3,,,,,wn)可以重復(fù)的
doc = Document()
for item in tmp:
if dpre.word2id.has_key(item):# 已有的話,只是當(dāng)前文檔追加
doc.words.append(dpre.word2id[item])
else: # 沒(méi)有的話溢豆,要更新vocabulary中的單詞詞典及wordidmap
dpre.word2id[item] = items_idx
doc.words.append(items_idx)
items_idx += 1
doc.length = len(tmp)
dpre.docs.append(doc)
else:
pass
dpre.docs_count = len(dpre.docs) # 文檔數(shù)
dpre.words_count = len(dpre.word2id) # 詞匯數(shù)
logger.info(u"共有%s個(gè)文檔" % dpre.docs_count)
dpre.cachewordidmap()
logger.info(u"詞與序號(hào)對(duì)應(yīng)關(guān)系已保存到%s" % wordidmapfile)
return dpre
def run():
# 處理文檔集蜒简,及計(jì)算文檔數(shù),以及vocabulary詞的總個(gè)數(shù)漩仙,以及每個(gè)文檔的單詞序列
dpre = preprocessing()
lda = LDAModel(dpre)
lda.est()
if name == 'main':
run()

參考資料:

lda主題模型

概率語(yǔ)言模型及其變形系列

lda八卦

作者:wind_blast
來(lái)源:CSDN
原文:https://blog.csdn.net/wind_blast/article/details/53815757
版權(quán)聲明:本文為博主原創(chuàng)文章搓茬,轉(zhuǎn)載請(qǐng)附上博文鏈接!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末队他,一起剝皮案震驚了整個(gè)濱河市卷仑,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌麸折,老刑警劉巖锡凝,帶你破解...
    沈念sama閱讀 222,590評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異磕谅,居然都是意外死亡私爷,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)膊夹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)衬浑,“玉大人,你說(shuō)我怎么就攤上這事放刨」ぶ龋” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,301評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)助币。 經(jīng)常有香客問(wèn)我浪听,道長(zhǎng),這世上最難降的妖魔是什么眉菱? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,078評(píng)論 1 300
  • 正文 為了忘掉前任迹栓,我火速辦了婚禮,結(jié)果婚禮上俭缓,老公的妹妹穿的比我還像新娘克伊。我一直安慰自己,他們只是感情好华坦,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,082評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布愿吹。 她就那樣靜靜地躺著,像睡著了一般惜姐。 火紅的嫁衣襯著肌膚如雪犁跪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,682評(píng)論 1 312
  • 那天歹袁,我揣著相機(jī)與錄音坷衍,去河邊找鬼。 笑死宇攻,一個(gè)胖子當(dāng)著我的面吹牛惫叛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播逞刷,決...
    沈念sama閱讀 41,155評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼嘉涌,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了夸浅?” 一聲冷哼從身側(cè)響起仑最,我...
    開(kāi)封第一講書(shū)人閱讀 40,098評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎帆喇,沒(méi)想到半個(gè)月后警医,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,638評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡坯钦,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,701評(píng)論 3 342
  • 正文 我和宋清朗相戀三年预皇,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片婉刀。...
    茶點(diǎn)故事閱讀 40,852評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡吟温,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出突颊,到底是詐尸還是另有隱情鲁豪,我是刑警寧澤潘悼,帶...
    沈念sama閱讀 36,520評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站爬橡,受9級(jí)特大地震影響治唤,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜糙申,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,181評(píng)論 3 335
  • 文/蒙蒙 一宾添、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧郭宝,春花似錦辞槐、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,674評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)卜范。三九已至衔统,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間海雪,已是汗流浹背锦爵。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,788評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留奥裸,地道東北人险掀。 一個(gè)月前我還...
    沈念sama閱讀 49,279評(píng)論 3 379
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像湾宙,于是被迫代替她去往敵國(guó)和親樟氢。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,851評(píng)論 2 361