zoukankan      html  css  js  c++  java
  • 文本主题抽取:用gensim训练LDA模型

    得知李航老师的《统计学习方法》出了第二版,我第一时间就买了。看了这本书的目录,非常高兴,好家伙,居然把主题模型都写了,还有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('	')[1]
            line = re.sub(r'[^u4e00-u9fa5]+','',line)
            line_seg = seg_depart(line.strip())
            outputs.write(line_seg.strip() + '
    ')
        
        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个主题的单词分布为:
    ")
    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('	')[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]+'新闻的主题分布为:
    ')
        print(topics_test[i],'
    ')
    
    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('
    '," ")                        # 新行,我们是不需要的
        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),"封邮件。
    ")
    print("第1封邮件未清洗前的内容为: 
    ",docs_text.iloc[0],'
    ')
    
    # 去除停用词,处理成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需要的格式为:
    ",texts[0],'
    ')
    一共有 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化后的结果为:
    ",corpus[0],'
    ')
    
    """训练LDA模型"""
    lda = gensim.models.ldamodel.LdaModel(corpus=corpus, id2word=dictionary, num_topics=10)# 所有主题的单词分布
    print(lda.print_topics(num_topics=10, 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封邮件的大致内容为:
    ",texts[0],'
    ')
    topic = lda.get_document_topics(corpus[0])
    print("第1封邮件的主题分布为:
    ",topic,'
    ')
    
    # 希拉里发的两条推特
    # 给大伙翻译一下这两句:
    # 这是选举的一天!数以百万计的美国人投了希拉里的票。加入他们吧,确定你投给谁。
    # 希望今天每个人都能度过一个安乐的感恩节,和家人朋友共度美好时光——来自希拉里的问候。
    
    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("这两条推特的主题分布分别为:
    ",topics_twitter[0] ,'
    ',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.0111170085), (1, 0.011118207), (2, 0.01111913), (3, 0.011116115), (4, 0.89994085), (5, 0.011116263), (6, 0.011116605), (7, 0.011117295), (8, 0.01111973), (9, 0.0111187985)] 
    [(4, 0.9181052)]
    
    

    参考资料:

    1、李航:《统计学习方法》(第二版)

    2、某培训班资料

  • 相关阅读:
    SpringBoot(三)SpringApplication启动类准备阶段
    SpringBoot(四)SpringApplication启动类运行阶段 SpringApplicationRunListener
    201871010104陈园园 《面向对象程序设计(java)》第八周学习总结 陈园园
    201871010104陈园园 《面向对象程序设计(java)》第十周学习总结 陈园园
    201871010104陈园园 《面向对象程序设计(java)》第四周学习总结 陈园园
    201871010104陈园园 《面向对象程序设计(java)》第二周学习总结 陈园园
    《2019面向对象程序设计(java)课程学习进度条》 陈园园
    201871010104陈园园 《面向对象程序设计(java)》第七周学习总结 陈园园
    201871010104陈园园 《面向对象程序设计 (java)》第一周学习总结 陈园园
    201871010104陈园园 《面向对象程序设计(java)》第六——七周学习总结 陈园园
  • 原文地址:https://www.cnblogs.com/Luv-GEM/p/10881838.html
Copyright © 2011-2022 走看看