文章轉(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)附上博文鏈接!