LDA主题模型之算法实现

Posted by SkyHigh on October 20, 2016

LDA主题模型之算法实现

写在前面

希望看这篇文章的读者对LDA主题模型的原理有所了解,不然可能会比较吃力。具体的原理解析可以参考:

如果文章有错误的地方,希望能得到您的指正,谢谢。

模型比较

LDA模型(Latent Dirichlet Allocation)用于寻找多个文档内存在的多个主题的模型。和LSA不同的是,LSA需要使用SVD找到的隐形变量是未知属性的。也就是说,LSA后文档或词的每一维所代表的含义不明确。
而对于PLSA模型,它主要是考虑到了文档和词汇之间的关系是通过主题来联系的。即一篇文档的生成过程为:

  • 按概率选择多个主题中的一个主题(多项分布)
  • 在该主题下,按概率选择该主题中的某个词(另一个多项分布)

那么,生成的文档概率正比于两个多项分布的乘积。可以通过EM算法求解局部最优解。而LDA则是在PLSA的思路上进行扩充,给每个多项分布设定一个参数的先验分布。这样我们可以求得参数的后验分布,进一步可以求得w和z的联合分布。

原理概述

具体原理之后有时间另开一章,现在说明大致过程。

LDA的算法是基于Gibbs Sampling算法的,Gibbs Sampling算法又是基于MCMC的。LDA的目的是获得满足w和z的联合分布的样本点(词的主题)。而Gibbs Sampling就是通过迭代某个维度的条件概率(每个维度对应某个文档的某个位置的词)获得平稳状态,而这平稳状态的分布即这条件概率对应的联合概率。所以我们可以通过这种方法,得到稳定分布的样本点。
我们需要迭代的条件分布为:

其中:
$n^{(k)}_{m,\neg i}$表示文档m中主题为k的词数(不包含当前词i)

$\sum^K_{k=1}n_{m,\neg i}^{(k)}$表示文档m的总词数(不包含当前词i)

$n^{(t)}_{k,\neg i}$表示主题k下词汇t的词频(不包含当前词i)

$\sum^V_{t=1}n_{k,\neg i}^{(t)}$表示主题k的总词数(不包含当前词i)

预测公式:

Perplexity计算公式:

其中

在给出代码前,需要注意:

  • 一篇文档对应多个词,多个主题
  • 一个词汇对应多个主题(注意词和词汇的区别)
  • 某篇文档下的某个词对应一个主题
  • 一个主题对应多个词汇

代码

下面给出完整的代码,注释已给出。数据和代码地址为:
l11x0m7.github:LDA

语料库数据:
社会新闻20篇,军事新闻18篇,国际新闻20篇。共58篇文档。

# -*- encoding:utf-8 -*-
import os
import time
import sys
import re
import random
import numpy as np
import copy
import json
reload(sys)
sys.setdefaultencoding('utf-8')

# 新闻爬虫
# 爬取新闻的内容有:社会新闻18篇,军事新闻20篇,国际新闻20篇
class NewsScrapy():
    def __init__(self):
        user_agent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_3) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.54 Safari/536.5'
        self.headers = {'User-Agent': user_agent}


    def download(self, url, num, savepath, l_url):
        import urllib
        import urllib2
        from lxml import etree

        if not os.path.exists(savepath[:savepath.rfind('/')]):
                os.mkdir(savepath[:savepath.rfind('/')])

        if not os.path.exists(l_url) or os.stat(l_url).st_size < 10:
            fw = open(l_url, 'w')
            url_list = set()
            try:
                req = urllib2.Request(url, headers=self.headers)
                res = urllib2.urlopen(req)
                content = res.read().decode('gbk', errors='ignore')
            except Exception as e:
                print '[Error]', e.message
            root = etree.HTML(content)
            href = root.xpath('//@href')
            for h in href:
                # if len(url_list) >= num:
                #     break
                if re.match(r'http://news.qq.com/.*', h):
                    url_list.add(h)
            for url in url_list:
                fw.write(url+'\n')
            fw.close()
            print '\n'.join(url_list)
            print len(url_list)
            url_list = list(url_list)
        else:
            fr = open(l_url)
            url_list = list()
            for line in fr:
                url_list.append(line.strip())
            print 'Load successful!'
            fr.close()

        time.sleep(1)

        with open(savepath, 'w') as fw:
            try:
                count = 0
                for url in url_list:
                    req = urllib2.Request(url, headers=self.headers)
                    res = urllib2.urlopen(req)
                    content = res.read().decode('gbk', errors='ignore')
                    # r = etree.HTML(content)
                    title = re.findall(r'<h1>(.*?)</h1>', content, re.S)
                    if len(title)<1:
                        continue
                    title = title[0].strip()
                    body = re.findall(r'<div id="Cnt-Main-Article-QQ" .*?<P.*?>(.*?)</div>',
                            content, re.S)
                    if len(body)<1:
                        continue
                    body = body[0].replace('</P>', "").replace('<P>', "").replace(' ', "").strip()
                    body = re.sub(r'<.*?>', "", body)
                    print title
                    print body
                    fw.write(title+'\t'+body+'\n')
                    count += 1
                    if count>=num:
                        break
                    time.sleep(5)
            except Exception as e:
                print '[Error2]', e.message if e.message!="" else url


# LDA模型
class LDAModel():
    def __init__(self, K, copora, alpha=None, beta=None, iteration=None):
        # K个主题
        self.K = K
        # alpha工程取值一般为0.1
        self.alpha = alpha if alpha else 0.1
        # beta工程取值一般为0.01
        self.beta = beta if beta else 0.01
        # 迭代次数一般取值为1000
        self.iteration = iteration if iteration else 1000
        self.nw = object    # K*V   每个主题下的每个词的出现频次
        self.nd = object    # D*K   每个文档下每个主题的词数
        self.nwsum = object # K     每个主题的总词数
        self.ndsum = object # D     每个文档的总词数
        self.theta = object # doc->topic    D*K
        self.phi = object   # topic->word   K*V
        self.z = object     # D*V  (m,w)对应每篇文档的每个词的具体主题
        # 3个语料整合到一起
        self.corpora = list()
        for theme in copora:
            with open(theme) as fr:
                for line in fr:
                    body = line.strip().split('\t')[1].decode()
                    self.corpora.append(body)

        # 文档数
        self.D = len(self.corpora)
        cut_docs = self.cut(self.corpora)
        # 分词并且id化的文档
        self.word2id, self.id2word, self.id_cut_docs, self.wordnum = self.createDict(cut_docs)
        self.V = len(self.id2word)
        # 初始化参数
        self.initial(self.id_cut_docs)
        # gibbs采样,进行文本训练
        self.gibbsSamppling()

        # 保存word2id,id_cut_docs,z,theta,phi,以便应用的时候使用
        with open('data/result/word2id', 'w') as fw:
            for word, id in self.word2id.iteritems():
                fw.write(word+'\t'+str(id)+'\n')

        with open('data/result/id_cut_docs', 'w') as fw:
            for doc in self.id_cut_docs:
                for vocab in doc:
                    fw.write(str(vocab)+'\t')
                fw.write('\n')

        with open('data/result/z', 'w') as fw:
            for doc in self.z:
                for vocab in doc:
                    fw.write(str(vocab)+'\t')
                fw.write('\n')

        with open('data/result/theta', 'w') as fw:
            for doc in self.theta:
                for topic in doc:
                    fw.write(str(topic)+'\t')
                fw.write('\n')

        with open('data/result/phi', 'w') as fw:
            for topic in self.phi:
                for vocab in topic:
                    fw.write(str(vocab)+'\t')
                fw.write('\n')



    # 文档分词,去无用词
    # 可以考虑去除文本低频词
    def cut(self, docs):
        from jieba import cut
        stop_words = self.loadStopWords()
        cut_docs = list()
        for doc in docs:
            cut_doc = cut(doc)
            new_doc = list()
            for word in cut_doc:
                if len(word.decode())>=2 and word not in stop_words and not word.isdigit():
                    new_doc.append(word)
            cut_docs.append(new_doc)
        return cut_docs

    # 创建word2id,id2word和document字典
    def createDict(self, cut_docs):
        word2id = dict()
        wordnum = 0
        for i, doc in enumerate(cut_docs):
            for j, word in enumerate(doc):
                wordnum += 1
                if not word2id.has_key(word):
                    word2id[word] = len(word2id)
                cut_docs[i][j] = word2id[word]
        return word2id, dict(zip(word2id.values(), word2id.keys())), cut_docs, wordnum

    # 初始化参数
    def initial(self, id_cut_docs):
        self.nd = np.array(np.zeros([self.D, self.K]), dtype=np.int32)
        self.nw = np.array(np.zeros([self.K, self.V]), dtype=np.int32)
        self.ndsum = np.array(np.zeros([self.D]), dtype=np.int32)
        self.nwsum = np.array(np.zeros([self.K]), dtype=np.int32)
        self.z = np.array(np.zeros([self.D, self.V]), dtype=np.int32)
        self.theta = np.ndarray([self.D, self.K])
        self.phi = np.ndarray([self.K, self.V])
        # 给每篇文档的每个词随机分配主题
        for i, doc in enumerate(id_cut_docs):
            for j, word_id in enumerate(doc):
                theme = random.randint(0, self.K-1)
                self.z[i,j] = theme
                self.nd[i,theme] += 1
                self.nw[theme,word_id] += 1
                self.ndsum[i] += 1
                self.nwsum[theme] += 1

    # gibbs采样
    def gibbsSamppling(self):
        for iter in range(self.iteration):
            for i, doc in enumerate(self.id_cut_docs):
                for j, word_id in enumerate(doc):
                    theme = self.z[i,j]
                    nd = self.nd[i,theme] - 1
                    nw = self.nw[theme,word_id] - 1
                    ndsum = self.ndsum[i] - 1
                    nwsum = self.nwsum[theme] - 1
                    # 重新给词选择新的主题
                    new_theme = self.reSamppling(nd, nw, ndsum, nwsum)

                    self.nd[i,theme] -= 1
                    self.nw[theme,word_id] -= 1
                    self.nwsum[theme] -= 1

                    self.nd[i,new_theme] += 1
                    self.nw[new_theme,word_id] += 1
                    self.nwsum[new_theme] += 1
                    self.z[i,j] = new_theme
            sys.stdout.write('\rIteration:{0} done!'.format(iter+1))
            sys.stdout.flush()


            # 计算perplexity,比较耗时
            if (iter+1)%100 == 0:
                pp = 0.
                for m in range(self.D):
                    for w in range(self.V):
                        pdzzmulpzw = np.sum((self.nd[m,:]/float(np.sum(self.nd[m,:]))).flatten()*\
                                    (self.nw[:,w]/map(float,np.sum(self.nw, 1))).flatten())
                        pdzzmulpzw = 1. if pdzzmulpzw == 0. else pdzzmulpzw
                        # print pdzzmulpzw
                        pp -= np.log2(pdzzmulpzw)
                        # print pp
                pp /= self.wordnum
                pp = np.exp(pp)

                sys.stdout.write('\rIteration:{0} done!\tPerplexity:{1}'.format(iter+1, pp))
                sys.stdout.flush()

        # 更新theta和phi
        self.updatePara()

    # 更新theta和phi
    def updatePara(self):
        for d in range(self.D):
            for k in range(self.K):
                self.theta[d,k] = float(self.nd[d,k] + self.alpha)/\
                                (self.ndsum[d] + self.alpha*self.K)

        for k in range(self.K):
            for v in range(self.V):
                self.phi[k,v] = float(self.nw[k,v] + self.beta)/\
                                (self.nwsum[k] + self.beta*self.K)

    # 重新选择主题
    def reSamppling(self, nd, nw, ndsum, nwsum):
        pk = np.ndarray([self.K])
        for i in range(self.K):
            # gibbs采样公式
            pk[i] = float(nd + self.alpha)*(nw +self.beta)/\
                    ((ndsum + self.alpha*self.K)*(nwsum + self.beta*self.V))
            if i > 0:
                pk[i] += pk[i-1]

        # 轮盘方式随机选择主题
        u = random.random()*pk[self.K-1]
        for k in range(len(pk)):
            if pk[k]>=u:
                return k

    # new_doc应该为list
    # 预测新的文档的主题
    def predict(self, new_doc, isupdate=False):
        new_cut_doc = self.cut(new_doc)[0]
        new_cut_id_doc = list()
        for word in new_cut_doc:
            if word in self.word2id:
                new_cut_id_doc.append(self.word2id[word])

        # 原为D*K,现在为1*K
        new_nd = np.zeros([self.K], dtype=np.int32)
        # 原为K*V,现在依然为K*V
        new_nw = copy.deepcopy(self.nw)
        # 原为1*D,现为1
        new_ndsum = 0
        # 原为1*K,现为1*K
        new_nwsum = copy.deepcopy(self.nwsum)
        # 当前文档的各个词的主题,1*V
        new_z = np.zeros([self.V]);

        # 类似于initial函数
        for j, word_id in enumerate(new_cut_id_doc):
            theme = random.randint(0, self.K-1)
            new_nd[theme] += 1
            new_nw[theme, word_id] += 1
            new_ndsum += 1
            new_nwsum[theme] += 1
            new_z[j] = theme

        # 类似于gibbsSampling函数
        for iter in range(self.iteration):
            for j, word_id in enumerate(new_cut_id_doc):
                theme = new_z[j]
                cur_nd = new_nd[theme] -1
                cur_nw = new_nw[theme,word_id] - 1
                cur_ndsum = new_ndsum - 1
                cur_nwsum = new_nwsum[theme] - 1

                new_theme = self.reSamppling(cur_nd, cur_nw, cur_ndsum, cur_nwsum)

                new_z[j] = new_theme
                new_nd[theme] -= 1
                new_nw[theme, word_id] -= 1
                new_nwsum[theme] -= 1

                new_nd[new_theme] += 1
                new_nw[new_theme,word_id] += 1
                new_nwsum[new_theme] += 1

            sys.stdout.write('\rIteration:{0} done!'.format(iter+1))
            sys.stdout.flush()

        # 更新theta参数,即再加1行
        new_theta = np.ndarray([self.K])
        for k in range(self.K):
            new_theta[k] = float(new_nd[k] + self.alpha)/\
                            (new_ndsum + self.alpha*self.K)

        # 若更新phi参数,即更新phi矩阵中所有的参数
        # 则需要同时更新theta,z,nw,nd,nwsum,ndsum
        # 也可以不更新,即参数和原来没预测之前一样
        if isupdate:
            for k in range(self.K):
                for v in range(self.V):
                    self.phi[k,v] = float(new_nw[k,v] + self.beta)/\
                                    (new_nwsum[k] + self.beta*self.K)

        # 返回该文档的各个主题出现概率,以及每个词对应的主题
        return new_theta, new_z


    # 返回各个主题的top词汇
    def getTopWords(self, top_num=20):
        with open('./data/result/topwords', 'w') as fw:
            for k in range(self.K):
                top_words = np.argsort(-self.phi[k,:])[:top_num]
                top_words = [self.id2word[word] for word in top_words]
                top_words = '\t'.join(top_words)
                res = 'topic{0}\t{1}'.format(k, top_words)
                fw.write(res+'\n')
                print res



    # 返回文档前几个topic中的前几个词
    def getTopTopics(self, top_topic=5, top_word=5):
        with open('./data/result/toptopics', 'w') as fw:
            for d in range(self.D):
                top_topics = np.argsort(-self.theta[d,:])[:top_topic]
                print 'document{0}:'.format(d)
                for topic in top_topics:
                    top_words_id = np.argsort(-self.phi[topic,:])[:top_word]
                    top_words = [self.id2word[word] for word in top_words_id]
                    top_words = '\t'.join(top_words)
                    res = 'topic{0}\t{1}'.format(topic, top_words)
                    fw.write(res+'\n')
                    print res
                fw.write('\n')


    # 载入停用词
    def loadStopWords(self):
        # 停用词:融合网络停用词、哈工大停用词、川大停用词
        root_path = '..'
        stop_words = set()
        with open(root_path + u'/Dict/StopWords/file/中文停用词库.txt') as fr:
            for line in fr:
                item = line.strip().decode()
                stop_words.add(item)
        with open(root_path + u'/Dict/StopWords/file/哈工大停用词表.txt') as fr:
            for line in fr:
                item = line.strip().decode()
                stop_words.add(item)
        with open(root_path + u'/Dict/StopWords/file/四川大学机器智能实验室停用词库.txt') as fr:
            for line in fr:
                item = line.strip().decode()
                stop_words.add(item)
        with open(root_path + u'/Dict/StopWords/file/百度停用词列表.txt') as fr:
            for line in fr:
                item = line.strip().decode()
                stop_words.add(item)
        with open(root_path + u'/Dict/StopWords/file/stopwords_net.txt') as fr:
            for line in fr:
                item = line.strip().decode()
                stop_words.add(item)
        with open(root_path + u'/Dict/StopWords/file/stopwords_net2.txt') as fr:
            for line in fr:
                item = line.strip().decode()
                stop_words.add(item)
        stop_words.add('')
        stop_words.add(' ')
        stop_words.add(u'\u3000')
        stop_words.add(u'日')
        stop_words.add(u'月')
        stop_words.add(u'时')
        stop_words.add(u'分')
        stop_words.add(u'秒')
        stop_words.add(u'报道')
        stop_words.add(u'新闻')
        return stop_words




if __name__ == '__main__':
    crawer = NewsScrapy()
    # crawer.download('http://news.qq.com/society_index.shtml', 20, './data/society', './data/society_urls')
    # crawer.download('http://mil.qq.com/mil_index.htm', 20, './data/military', './data/military_urls')
    # crawer.download('http://news.qq.com/world_index.shtml', 20, './data/world', './data/world_urls')

    corpus = ['./data/society', './data/military', './data/world']
    time1 = time.time()
    ldamodel = LDAModel(20, corpus, iteration=300)
    time2 = time.time()
    print 'Training time:{0}'.format(time2-time1)
    # 各个主题的top20词汇
    print '每个主题的top words'
    ldamodel.getTopWords(20)
    # 各个文档的top5话题,每个话题的top5词汇
    print '每篇文档的top topics中的top words'
    ldamodel.getTopTopics()

    time1 = time.time()
    # 预测一篇文档的top5主题,返回每个主题的top5词汇
    pred_doc_id = 0
    pred_doc = [ldamodel.corpora[pred_doc_id]]
    new_theta, new_z = ldamodel.predict(pred_doc)
    time2 = time.time()
    print 'Predict time per document:{0}'.format(time2-time1)

    print '预测文档{0}:'.format(pred_doc_id)
    top_topics = np.argsort(-new_theta)[:5]
    for topic in top_topics:
        top_words_id = np.argsort(-ldamodel.phi[topic,:])[:5]
        top_words = [ldamodel.id2word[word] for word in top_words_id]
        top_words = '\t'.join(top_words)
        print 'topic{0}\t{1}'.format(topic, top_words)

测试结果

每个话题的top20的词

topic0	投票	日本	总统	学生	提供	美国	发生	展示	希望	亿美元	时间	文莱	维生素	希特勒	政策	此事	资产	管理	男孩	土地
topic1	中国	美国	许某	旅客	乘坐	靖国神社	发展	对象	访问	候选人	车辆	并未	情况	事故	实施	两名	日本	紧急	网友	亿美元
topic2	中国	时间	美国	公司	出租车	之间	选择	有人	情况	朝鲜	特朗普	一名	发展	减轻	女孩	形势	时期	洗澡	举办	流落
topic3	中国	日本	中心	支持	杜特	正式	总统	航空	接受	地区	王室	民警	美国	基地	发生	咪咪	土地	人员	当天	收银员
topic4	美国	中国	日本	意大利	民警	军机	征兵	关系	提升	王室	提出	第一	引发	女子	咪咪	树上	导致	安德森	中午	公司
topic5	中国	参拜	记者	投票	活动	国家	目的	情况	孟茹	新纳粹	之间	费用	车道	提出	各国	奥巴马	媒体	合作	发生	接受
topic6	日本	美国	总统	投票	候选人	朝鲜	增加	伊拉克	发布	没想到	账号	国家	提供	对此	领空	医院	计划	民警	许某	未来
topic7	中国	日本	记者	海外	国家	美国	学生	方式	总统	集团	公司	资料	女士	前往	出口	外交部	飞机	战略	警察	实在
topic8	中国	日本	总统	公司	泰国	选举人	菲律宾	支持	三国	美国	两人	目标	孩子	纪律	罗纳德	地铁	标题	王室	医院	自民党
topic9	美国	学生	候选人	记者	泰国	正式	女性	军队	老人	特朗普	王室	发现	基地	总统	许某	日本	选择	俄罗斯	飞机	做法
topic10	记者	奥巴马	中国	民警	时间	学生	协议	日本	泰国	发现	标题	波音	孩子	时期	展开	情况	榆林市	检查	货车	屯田
topic11	中国	美国	大选	男子	一名	资源	盗窃	学生	日本政府总统	事发	对此	日本	时间	发生	记者	现场	有人	网站	儿子
topic12	美国	家长	日本	学生	商品	苏富比	男子	国王	情况	发现	期间	一点	人员	数量	靖国神社	议员	包括	犯罪	这是	驾驶
topic13	总统	网友	中国	日本	作用	影响	男子	时间	标题	基地	特朗普	阮志	位于	战争	实际上	下午	三名	收入	靖国神社发布
topic14	美国	日本	全球	波音	泰国	总统	参拜	战斗机	菲律宾	中心	孩子	国际	建立	影响	资产	奥巴马	增加	日本政府	关系	投票
topic15	中国	美国	日本	记者	老王	王室	投票	网友	司机	政治	三星	公司	监控	介绍	华商报	系统	确实	两个	最终	真的
topic16	日本	中国	美国	记者	男子	调查	市场	训练	经济	财富	当天	民警	相关	分析	危险	希望	媒体	关系	发现	国防部
topic17	日本	美国	总统	中国	男子	告诉	王室	费用	死亡	时间	超过	菲律宾	欧洲	奥巴马	记者	特朗普	民警	遭受	位于	公司
topic18	中国	发现	一名	日本	公司	泰国	经济	国会	尔特	总统	约合	孩子	下午	火枪	为例	特朗普	中国日报	条件	学生	发言人
topic19	中国	王室	美国	日本	泰国	靖国神社	安装	食物	三国	小区	接近	项目	家长	男子	学生	全球	巴基斯坦	进一步	军队	巴尔

每篇文档的top5 topics中的top5 words

document0:
topic19	中国	王室	美国	日本	泰国
topic14	美国	日本	全球	波音	泰国
topic3	中国	日本	中心	支持	杜特
topic7	中国	日本	记者	海外	国家
topic18	中国	发现	一名	日本	公司
document1:
topic18	中国	发现	一名	日本	公司
topic19	中国	王室	美国	日本	泰国
topic17	日本	美国	总统	中国	男子
topic15	中国	美国	日本	记者	老王
topic8	中国	日本	总统	公司	泰国
document2:
topic16	日本	中国	美国	记者	男子
topic11	中国	美国	大选	男子	一名
topic0	投票	日本	总统	学生	提供
topic10	记者	奥巴马	中国	民警	时间
topic15	中国	美国	日本	记者	老王
document3:
topic7	中国	日本	记者	海外	国家
topic9	美国	学生	候选人	记者	泰国
topic13	总统	网友	中国	日本	作用
topic16	日本	中国	美国	记者	男子
topic11	中国	美国	大选	男子	一名
document4:
topic12	美国	家长	日本	学生	商品
topic1	中国	美国	许某	旅客	乘坐
topic4	美国	中国	日本	意大利	民警
topic6	日本	美国	总统	投票	候选人
topic10	记者	奥巴马	中国	民警	时间
document5:
topic19	中国	王室	美国	日本	泰国
topic0	投票	日本	总统	学生	提供
topic9	美国	学生	候选人	记者	泰国
topic15	中国	美国	日本	记者	老王
topic6	日本	美国	总统	投票	候选人
document6:
topic19	中国	王室	美国	日本	泰国
topic15	中国	美国	日本	记者	老王
topic10	记者	奥巴马	中国	民警	时间
topic7	中国	日本	记者	海外	国家
topic13	总统	网友	中国	日本	作用
document7:
topic8	中国	日本	总统	公司	泰国
topic3	中国	日本	中心	支持	杜特
topic4	美国	中国	日本	意大利	民警
topic19	中国	王室	美国	日本	泰国
topic2	中国	时间	美国	公司	出租车
document8:
topic16	日本	中国	美国	记者	男子
topic15	中国	美国	日本	记者	老王
topic8	中国	日本	总统	公司	泰国
topic0	投票	日本	总统	学生	提供
topic3	中国	日本	中心	支持	杜特
document9:
topic0	投票	日本	总统	学生	提供
topic6	日本	美国	总统	投票	候选人
topic4	美国	中国	日本	意大利	民警
topic10	记者	奥巴马	中国	民警	时间
topic17	日本	美国	总统	中国	男子
document10:
topic5	中国	参拜	记者	投票	活动
topic7	中国	日本	记者	海外	国家
topic3	中国	日本	中心	支持	杜特
topic19	中国	王室	美国	日本	泰国
topic14	美国	日本	全球	波音	泰国
document11:
topic2	中国	时间	美国	公司	出租车
topic16	日本	中国	美国	记者	男子
topic17	日本	美国	总统	中国	男子
topic10	记者	奥巴马	中国	民警	时间
topic3	中国	日本	中心	支持	杜特
document12:
topic1	中国	美国	许某	旅客	乘坐
topic11	中国	美国	大选	男子	一名
topic7	中国	日本	记者	海外	国家
topic6	日本	美国	总统	投票	候选人
topic0	投票	日本	总统	学生	提供
document13:
topic4	美国	中国	日本	意大利	民警
topic19	中国	王室	美国	日本	泰国
topic7	中国	日本	记者	海外	国家
topic1	中国	美国	许某	旅客	乘坐
topic13	总统	网友	中国	日本	作用
document14:
topic2	中国	时间	美国	公司	出租车
topic7	中国	日本	记者	海外	国家
topic1	中国	美国	许某	旅客	乘坐
topic6	日本	美国	总统	投票	候选人
topic0	投票	日本	总统	学生	提供
document15:
topic16	日本	中国	美国	记者	男子
topic3	中国	日本	中心	支持	杜特
topic19	中国	王室	美国	日本	泰国
topic1	中国	美国	许某	旅客	乘坐
topic15	中国	美国	日本	记者	老王
document16:
topic7	中国	日本	记者	海外	国家
topic1	中国	美国	许某	旅客	乘坐
topic12	美国	家长	日本	学生	商品
topic0	投票	日本	总统	学生	提供
topic17	日本	美国	总统	中国	男子
document17:
topic19	中国	王室	美国	日本	泰国
topic15	中国	美国	日本	记者	老王
topic2	中国	时间	美国	公司	出租车
topic0	投票	日本	总统	学生	提供
topic9	美国	学生	候选人	记者	泰国
document18:
topic1	中国	美国	许某	旅客	乘坐
topic2	中国	时间	美国	公司	出租车
topic10	记者	奥巴马	中国	民警	时间
topic9	美国	学生	候选人	记者	泰国
topic3	中国	日本	中心	支持	杜特
document19:
topic9	美国	学生	候选人	记者	泰国
topic2	中国	时间	美国	公司	出租车
topic18	中国	发现	一名	日本	公司
topic4	美国	中国	日本	意大利	民警
topic19	中国	王室	美国	日本	泰国
document20:
topic10	记者	奥巴马	中国	民警	时间
topic1	中国	美国	许某	旅客	乘坐
topic13	总统	网友	中国	日本	作用
topic0	投票	日本	总统	学生	提供
topic5	中国	参拜	记者	投票	活动
document21:
topic10	记者	奥巴马	中国	民警	时间
topic15	中国	美国	日本	记者	老王
topic13	总统	网友	中国	日本	作用
topic17	日本	美国	总统	中国	男子
topic4	美国	中国	日本	意大利	民警
document22:
topic6	日本	美国	总统	投票	候选人
topic2	中国	时间	美国	公司	出租车
topic15	中国	美国	日本	记者	老王
topic7	中国	日本	记者	海外	国家
topic17	日本	美国	总统	中国	男子
document23:
topic19	中国	王室	美国	日本	泰国
topic4	美国	中国	日本	意大利	民警
topic18	中国	发现	一名	日本	公司
topic14	美国	日本	全球	波音	泰国
topic13	总统	网友	中国	日本	作用
document24:
topic5	中国	参拜	记者	投票	活动
topic19	中国	王室	美国	日本	泰国
topic2	中国	时间	美国	公司	出租车
topic3	中国	日本	中心	支持	杜特
topic12	美国	家长	日本	学生	商品
document25:
topic2	中国	时间	美国	公司	出租车
topic12	美国	家长	日本	学生	商品
topic8	中国	日本	总统	公司	泰国
topic10	记者	奥巴马	中国	民警	时间
topic14	美国	日本	全球	波音	泰国
document26:
topic13	总统	网友	中国	日本	作用
topic10	记者	奥巴马	中国	民警	时间
topic9	美国	学生	候选人	记者	泰国
topic18	中国	发现	一名	日本	公司
topic8	中国	日本	总统	公司	泰国
document27:
topic10	记者	奥巴马	中国	民警	时间
topic12	美国	家长	日本	学生	商品
topic17	日本	美国	总统	中国	男子
topic0	投票	日本	总统	学生	提供
topic13	总统	网友	中国	日本	作用
document28:
topic17	日本	美国	总统	中国	男子
topic11	中国	美国	大选	男子	一名
topic0	投票	日本	总统	学生	提供
topic14	美国	日本	全球	波音	泰国
topic16	日本	中国	美国	记者	男子
document29:
topic3	中国	日本	中心	支持	杜特
topic12	美国	家长	日本	学生	商品
topic1	中国	美国	许某	旅客	乘坐
topic2	中国	时间	美国	公司	出租车
topic15	中国	美国	日本	记者	老王
document30:
topic2	中国	时间	美国	公司	出租车
topic4	美国	中国	日本	意大利	民警
topic8	中国	日本	总统	公司	泰国
topic12	美国	家长	日本	学生	商品
topic16	日本	中国	美国	记者	男子
document31:
topic3	中国	日本	中心	支持	杜特
topic13	总统	网友	中国	日本	作用
topic17	日本	美国	总统	中国	男子
topic2	中国	时间	美国	公司	出租车
topic14	美国	日本	全球	波音	泰国
document32:
topic6	日本	美国	总统	投票	候选人
topic8	中国	日本	总统	公司	泰国
topic0	投票	日本	总统	学生	提供
topic17	日本	美国	总统	中国	男子
topic11	中国	美国	大选	男子	一名
document33:
topic16	日本	中国	美国	记者	男子
topic14	美国	日本	全球	波音	泰国
topic18	中国	发现	一名	日本	公司
topic3	中国	日本	中心	支持	杜特
topic5	中国	参拜	记者	投票	活动
document34:
topic13	总统	网友	中国	日本	作用
topic2	中国	时间	美国	公司	出租车
topic3	中国	日本	中心	支持	杜特
topic14	美国	日本	全球	波音	泰国
topic12	美国	家长	日本	学生	商品
document35:
topic0	投票	日本	总统	学生	提供
topic15	中国	美国	日本	记者	老王
topic2	中国	时间	美国	公司	出租车
topic16	日本	中国	美国	记者	男子
topic1	中国	美国	许某	旅客	乘坐
document36:
topic9	美国	学生	候选人	记者	泰国
topic1	中国	美国	许某	旅客	乘坐
topic6	日本	美国	总统	投票	候选人
topic13	总统	网友	中国	日本	作用
topic17	日本	美国	总统	中国	男子
document37:
topic17	日本	美国	总统	中国	男子
topic12	美国	家长	日本	学生	商品
topic9	美国	学生	候选人	记者	泰国
topic14	美国	日本	全球	波音	泰国
topic1	中国	美国	许某	旅客	乘坐
document38:
topic1	中国	美国	许某	旅客	乘坐
topic14	美国	日本	全球	波音	泰国
topic15	中国	美国	日本	记者	老王
topic0	投票	日本	总统	学生	提供
topic16	日本	中国	美国	记者	男子
document39:
topic19	中国	王室	美国	日本	泰国
topic11	中国	美国	大选	男子	一名
topic14	美国	日本	全球	波音	泰国
topic3	中国	日本	中心	支持	杜特
topic17	日本	美国	总统	中国	男子
document40:
topic18	中国	发现	一名	日本	公司
topic2	中国	时间	美国	公司	出租车
topic10	记者	奥巴马	中国	民警	时间
topic7	中国	日本	记者	海外	国家
topic17	日本	美国	总统	中国	男子
document41:
topic9	美国	学生	候选人	记者	泰国
topic10	记者	奥巴马	中国	民警	时间
topic17	日本	美国	总统	中国	男子
topic7	中国	日本	记者	海外	国家
topic16	日本	中国	美国	记者	男子
document42:
topic7	中国	日本	记者	海外	国家
topic1	中国	美国	许某	旅客	乘坐
topic3	中国	日本	中心	支持	杜特
topic12	美国	家长	日本	学生	商品
topic15	中国	美国	日本	记者	老王
document43:
topic11	中国	美国	大选	男子	一名
topic5	中国	参拜	记者	投票	活动
topic15	中国	美国	日本	记者	老王
topic2	中国	时间	美国	公司	出租车
topic6	日本	美国	总统	投票	候选人
document44:
topic19	中国	王室	美国	日本	泰国
topic17	日本	美国	总统	中国	男子
topic5	中国	参拜	记者	投票	活动
topic7	中国	日本	记者	海外	国家
topic16	日本	中国	美国	记者	男子
document45:
topic10	记者	奥巴马	中国	民警	时间
topic0	投票	日本	总统	学生	提供
topic14	美国	日本	全球	波音	泰国
topic5	中国	参拜	记者	投票	活动
topic19	中国	王室	美国	日本	泰国
document46:
topic0	投票	日本	总统	学生	提供
topic19	中国	王室	美国	日本	泰国
topic15	中国	美国	日本	记者	老王
topic9	美国	学生	候选人	记者	泰国
topic6	日本	美国	总统	投票	候选人
document47:
topic15	中国	美国	日本	记者	老王
topic1	中国	美国	许某	旅客	乘坐
topic17	日本	美国	总统	中国	男子
topic9	美国	学生	候选人	记者	泰国
topic2	中国	时间	美国	公司	出租车
document48:
topic1	中国	美国	许某	旅客	乘坐
topic17	日本	美国	总统	中国	男子
topic4	美国	中国	日本	意大利	民警
topic5	中国	参拜	记者	投票	活动
topic6	日本	美国	总统	投票	候选人
document49:
topic2	中国	时间	美国	公司	出租车
topic19	中国	王室	美国	日本	泰国
topic17	日本	美国	总统	中国	男子
topic11	中国	美国	大选	男子	一名
topic10	记者	奥巴马	中国	民警	时间
document50:
topic1	中国	美国	许某	旅客	乘坐
topic19	中国	王室	美国	日本	泰国
topic12	美国	家长	日本	学生	商品
topic6	日本	美国	总统	投票	候选人
topic16	日本	中国	美国	记者	男子
document51:
topic18	中国	发现	一名	日本	公司
topic15	中国	美国	日本	记者	老王
topic3	中国	日本	中心	支持	杜特
topic9	美国	学生	候选人	记者	泰国
topic13	总统	网友	中国	日本	作用
document52:
topic3	中国	日本	中心	支持	杜特
topic16	日本	中国	美国	记者	男子
topic9	美国	学生	候选人	记者	泰国
topic2	中国	时间	美国	公司	出租车
topic8	中国	日本	总统	公司	泰国
document53:
topic1	中国	美国	许某	旅客	乘坐
topic9	美国	学生	候选人	记者	泰国
topic17	日本	美国	总统	中国	男子
topic7	中国	日本	记者	海外	国家
topic15	中国	美国	日本	记者	老王
document54:
topic19	中国	王室	美国	日本	泰国
topic15	中国	美国	日本	记者	老王
topic17	日本	美国	总统	中国	男子
topic16	日本	中国	美国	记者	男子
topic4	美国	中国	日本	意大利	民警
document55:
topic13	总统	网友	中国	日本	作用
topic3	中国	日本	中心	支持	杜特
topic4	美国	中国	日本	意大利	民警
topic12	美国	家长	日本	学生	商品
topic10	记者	奥巴马	中国	民警	时间
document56:
topic11	中国	美国	大选	男子	一名
topic12	美国	家长	日本	学生	商品
topic2	中国	时间	美国	公司	出租车
topic4	美国	中国	日本	意大利	民警
topic19	中国	王室	美国	日本	泰国
document57:
topic4	美国	中国	日本	意大利	民警
topic17	日本	美国	总统	中国	男子
topic3	中国	日本	中心	支持	杜特
topic7	中国	日本	记者	海外	国家
topic11	中国	美国	大选	男子	一名

预测文档0的top5的话题的top5的词

topic2	中国	时间	美国	公司	出租车
topic17	日本	美国	总统	中国	男子
topic7	中国	日本	记者	海外	国家
topic4	美国	中国	日本	意大利	民警
topic13	总统	网友	中国	日本	作用

总结

时间复杂度

LDA主题能够较为有效的总结多篇文档的主题,并给出每个主题下的关键词。但是由于每次迭代的时候需要循环所有文档的所有词,因此其效率很低。如果迭代n次,共m篇文档,每篇大约N个词,主题数为k,则时间复杂度为O(nmN*k)。比较好的解决方法是并行化处理。

空间复杂度

如果总共有V个词汇,则空间复杂度为O(kV)。因此如果单机训练要取得较好的效果,需要大量时间。

主题数的选择

可以使用HDP等较为复杂的模型自动确定主题数K,但是模型复杂,计算复杂。可以通过设置不同的K,训练后验证比较求得最佳值,或采用设置不同的topic数量,画出topicnumber-perplexity曲线。

超参数的选择

工程上一般取$\alpha=0.1,\beta=0.01,iteration=1000$。