python实现基于LDA模型的文献推荐

基本设计思路

  • 对训练用数据集进行批量处理
  • 使用gensim使用LDA模型实现主题分布
  • 训练/测试模型,并持续更新

对训练用数据集进行批量处理

# -*- coding:utf-8 -*-

import logging
# os模块中包含很多操作文件和目录的函数
import os
import types
import re
#import nltk
#nltk.downloader('brown')
from textblob import TextBlob



LOG_FORMAT = "%(asctime)s - %(levelname)s - %(message)s"
logging.basicConfig(filename=u'd:\zlf.log', level=logging.DEBUG, format=LOG_FORMAT)



#从xml中获取标签里的值,写到新的目录中
def etlXML(old_filename,new_filename):
    from xml.etree import ElementTree as ET
    listTag=["./front", "./body", "./back", "floats-group"]
    f1 = open(old_filename, 'r+', encoding="UTF-8")
    lines = f1.readlines()

    nf = ""#要写入的文件名称
    rYear=""#这篇文章的年份
    year =[]
    for line in lines:
        year = re.findall("<copyright-year>.+</copyright-year>", line)
        if(len(year)>0):
            print(new_filename + "这篇论文的年限是:")
            print(year)
            str = year[0]
            rYear = str[16:20]
    if(rYear==""):#如果这篇文章没有年份数据,则直接返回
        print(new_filename + "这篇论文没有年限")
        return ""
    list=[]
    for line in lines:
        # print("gaga:"+line)


        list = re.findall("<abstrac.+</abstract", line)
        if (len(list) > 0):
            print("gaga---")
            print(list[0])
            nf=os.path.join('D:\zlf\\temp\\',rYear,new_filename)#每年的都放在自己的里面
            path=os.path.join('D:\zlf\\temp\\',rYear)
            isExists = os.path.exists(path)

            # 判断结果
            if not isExists:
                # 如果不存在则创建目录
                # 创建目录操作函数
                os.makedirs(path)

            f2 = open(nf, 'w+', encoding="UTF-8")
            f2.write(list[0])
    return nf

#将文本去除停用词
def qingxiFile(file_text):
    print("要预处理的文件是:" + file_text)
    # 停用词表 []   停用词的标准格式应该是一个词一行在txt中展示.如果一行中有多词汇,可以采用本文方法转换成标准格式
    # 文本集 []
    # 处理后文本本 []
    stop = []
    standard_stop = []
    text = []
    after_text = []
    file_stop = r'D:\zlf\CNENstopwords.txt'  # 停用词表
    # file_text = r'D:\PycharmProjects\tufaci20190218\ceshi-word.txt'  # 要处理的文本集合
    with open(file_stop, 'r', encoding='utf-8') as f:
        lines = f.readlines()  # lines是list类型
        for line in lines:
            #lline = line.strip()  # line 是str类型,strip 去掉\n换行符,删除头尾空格
            stop.append(line)  # 将stop 是列表形式

    # stop 的元素是一行一行的 句子,需要进行转化为一个词一行,即下面:
    for i in range(0, len(stop)):
        for word in stop[i].split():
            standard_stop.append(word.upper())
            #print("stop文件为:" )
            #print( standard_stop)
    #print("停止词:")
    #print(standard_stop)
    logging.info("停止词:")
    logging.info(standard_stop)
    # 读取文本集,
    with open(file_text, 'r', encoding='utf-8') as f:
        lines = f.readlines()
        # 牛逼的分词
        logging.info("文本有几行:")
        logging.info(len(lines))
        if len(lines)==0 :#如果文件是空的,则直接跳出
            return
        blob = TextBlob(lines[0])
        logging.info("textBlob 分词后:")
        #logging.info(blob.noun_phrases)
        for i in blob.noun_phrases:
            if i.upper() not in standard_stop:
                jj = re.findall(r"[A-Za-z]+", i.upper())  # 去掉数字
                print(jj)
                for j in jj :
                    #if((j!="ABSTRACT")  &  (j!='')):
                    if (j not in standard_stop) & (j!='') & (len(i)>2) :
                        after_text.append(j.upper())  # 如果不是数字,则转换为大写,插入到数组里
    #print("转换后:")
    logging.info("清洗后:" )
    logging.info(after_text)

    #filename_result = os.path.join('E:\zlf', filename)
    #print("结果文件是:" + filename_result)
    # 将结果保存在txt中
    with open(file_text, 'w+',encoding='utf-8')as f:
        for i in after_text:
            f.write(i+' ')#每个词后面加一个空格


#主程序在这里
#file = open('D:\zlf\\result.txt', 'w')
rootdir ='D:\zlf_unzip'
list = os.listdir(rootdir) #列出文件夹下所有的目录与文件
print(list)
for i in range(0,len(list)):
       path = os.path.join(rootdir,list[i])
       #print("文件夹名称"+path)
#       if os.path.isfile(path):
       list_temp=os.listdir(path)
       for i in range(0,len(list_temp)):
            filename=os.path.join(path,list_temp[i])
            print("文件名称"+filename)#要处理的文件的全名
            new_filename=os.path.join('D:\zlf\\temp',list_temp[i])
            # new_filename = os.path.join('D:\zlf_unzip\\temp', list_temp[i])
            nf=etlXML(filename,list_temp[i])#提取xml中的值后,放在D:/zlf/temp下,再后续处理
            if(nf!=""):
                qingxiFile(nf)#从上面zlf/temp中读取文件,放在zlf中,同样的文件名。
            #zlf_lda(target_file)
                # 遍历单个文件,读取行数
                #for line in open(list_temp[0]):
                   # file.writelines(line)
               # file.write('\n')
              #你想对文件的操作
#file.close()


'''
# 获取目标文件夹的路径
meragefiledir = D:\zlf_unzip' #os.getcwd() + '\\MerageFiles'
print (meragefiledir)
# 获取当前文件夹中的文件名称列表
filenames = os.listdir(meragefiledir)
print(filenames)
# 打开当前目录下的result.txt文件,如果没有则创建
file = open('D:\zlf\\result.txt', 'w')
# 向文件中写入字符

# 先遍历文件名
for filename in filenames:
    filepath = meragefiledir + '\\'
    filepath = filepath + filename
    # 遍历单个文件,读取行数
    for line in open(filepath):
       file.writelines(line)
    file.write('\n')
# 关闭文件
file.close()
'''

使用gensim使用LDA模型实现主题分布

# coding=utf-8
import codecs
import logging
import os
from gensim import corpora
from gensim.models import LdaModel
from gensim.similarities  import docsim

from mpltools import style
import numpy as np
import matplotlib.pyplot as plt
#from gensim.corpora import Dictionary


LOG_FORMAT = "%(asctime)s - %(levelname)s - %(message)s"
logging.basicConfig(filename=u'd:\zlf_lda.log', level=logging.DEBUG, format=LOG_FORMAT)

train = []
rr=[]
rootdir = 'D:\zlf\\temp1\\2013'
list = os.listdir(rootdir) #列出文件夹下所有的目录与文件
print(list)
for i in range(0,len(list)):
    path = os.path.join(rootdir,list[i])
    fr = open(path, 'r',encoding='utf-8')
    for line in fr.readlines():
        lline = line.split(' ')
        for l in lline:
            if(len(l)>0):
                if(l=="CELLS"):
                    rr.append("CELL")
                else:
                    rr.append(l)
    train.append(rr)#train里每个是一篇文章
    length=str(len(train))
    #print ('train长度'+length)
#print ('给第一个训练集分词'+ train[0])



dictionary = corpora.Dictionary(train) ## 得到单词的ID,统计单词出现的次数以及统计信息
print ('dictionary:')
print(dictionary)

#把所有文档根据字典转换成VSM
#corpus语库 计算每个文档中的TF-IDF值:
corpus = [dictionary.doc2bow(text) for text in train]  # 将dictionary转化为一个词袋,得到文档-单词矩阵
'''
for line in corpus:
    logging.info("逐行打印corpus")
    logging.info(line)
'''
'''
α
LDA 模型的 Dirichlet 先验分布, 文档上主题
分布的先验
β
LDA 模型的 Dirichlet 先验分布, 主题上词语
分布的先验
'''
lda = LdaModel(corpus=corpus, id2word=dictionary, alpha='auto', eta=0.01, minimum_probability=0.05, num_topics=60)#β设置成0.1或者0.01 α设置成50/topic



#此段用来确定主题数目的
thetas=[lda[c] for c in corpus]
plt.hist([len(t) for t in thetas], np.arange(60))
plt.ylabel('Nr of documents')
plt.xlabel('Nr of topics')
plt.show()

'''
# 对每篇文档中的每个词都计算tf-idf值
corpus_tfidf = LdaModel.TfidfModel(corpus)

# 逐行打印
logging.info('每个词的TF-IDF:')
for c in corpus_tfidf:
    logging.info(c)

doc_topic = [a for a in lda[corpus_tfidf]]
logging.info('Document-Topic:\n')
logging.info(doc_topic)
'''
# tfidf = LdaModel.TfidfModel(corpus)  # 使用tf-idf 模型得出该评论集的tf-idf 模型
# corpus_tfidf = tfidf[corpus]
# lda.save('zlf_lda.model')
# lda = LdaModel.load('zhwiki_lda.model')

lda.save('zlf_lda.model')
'''
similarity = docsim.MatrixSimilarity(corpus)
logging.info('Similarity:')
print(similarity)
'''
print(len(corpus))
topic_list = lda.print_topics(20)
print (type(lda.print_topics(20)))
print ('长度'+str(len(lda.print_topics(20))))

for topic in topic_list:
    print( topic)
'''
 # # 4. 可视化结果
    # Topic - word  主题中词的分布图
    plt.figure(figsize=(8, 12))
    # f, ax = plt.subplots(5, 1, sharex=True)
    for i, k in enumerate([0, 5, 9, 14, 19]):
        ax = plt.subplot(5, 1, i+1)
        ax.plot(topic_list[k, :], 'r-')
        ax.set_xlim(-50, 4350)   # [0,4258]
        ax.set_ylim(0, 0.08)
        ax.set_ylabel("Prob")
        ax.set_title("topic {}".format(k))
    plt.xlabel("word", fontsize=14)
    plt.tight_layout()
    plt.show()

    # Document - Topic  文档中主题的分布图
    plt.figure(figsize=(8, 12))
    # f, ax= plt.subplots(5, 1, figsize=(8, 6), sharex=True)
    for i, k in enumerate([1, 3, 4, 8, 9]):
        ax = plt.subplot(5, 1, i+1)
        ax.stem(topic_list[k, :], linefmt='g-', markerfmt='ro')
        ax.set_xlim(-1, 60+1)
        ax.set_ylim(0, 1)
        ax.set_ylabel("Prob")
        ax.set_title("Document {}".format(k))
    plt.xlabel("Topic", fontsize=14)
    plt.tight_layout()
    plt.show()

'''

print('给定一个新文档,输出其主题分布')

test_doc = 'D:\zlf\\temp1\\2013\srep00810.nxml' #新文档进行分词

target=[]
rr=[]
fr1 = open(test_doc, 'r',encoding='utf-8')
for line1 in fr1.readlines():
        line2= line1.split(' ')
        for l in line2:
             rr.append(l.upper())
        target.append(rr)
        length=str(len(target))
        print ('target长度'+length)
#print ('给第一个训练集分词'+ train[0])

target_dictionary = corpora.Dictionary(target) ## 得到单词的ID,统计单词出现的次数以及统计信息
print ('target dictionary:')
print(target_dictionary)

#test_doc = train[0]  # 查看训练集中第三个样本的主题分布
doc_bow = target_dictionary.doc2bow(target[0])  # 文档转换成bow
doc_lda = lda[doc_bow]  # 得到新文档的主题分布


# 输出新文档的主题分布
print('得到新文档的主题分布:')
print(doc_lda)
print('新文档主题分布结束')
for topic in doc_lda:
    print("%s\t%f\n" % (lda.print_topic(topic[0]), topic[1]))
'''
'''
'''
gensim 核心概念
gensim的整个package会涉及三个概念:corpus, vector, model.

语库(corpus)
文档集合,用于自动推出文档结构,以及它们的主题等,也可称作训练语料。
向量(vector)

在向量空间模型(VSM)中,每个文档被表示成一个特征数组。例如,一个单一特征可以被表示成一个问答对(question-answer pair):

[1].在文档中单词”splonge”出现的次数? 0个
[2].文档中包含了多少句子? 2个
[3].文档中使用了多少字体? 5种
这里的问题可以表示成整型id (比如:1,2,3等), 因此,上面的文档可以表示成:(1, 0.0), (2, 2.0), (3, 5.0). 如果我们事先知道所有的问题,我们可以显式地写成这样:(0.0, 2.0, 5.0). 这个answer序列可以认为是一个多维矩阵(3维). 对于实际目的,只有question对应的answer是一个实数.

对于每个文档来说,answer是类似的. 因而,对于两个向量来说(分别表示两个文档),我们希望可以下类似的结论:“如果两个向量中的实数是相似的,那么,原始的文档也可以认为是相似的”。当然,这样的结论依赖于我们如何去选取我们的question。

稀疏矩阵(Sparse vector)

通常,大多数answer的值都是0.0. 为了节省空间,我们需要从文档表示中忽略它们,只需要写:(2, 2.0), (3, 5.0) 即可(注意:这里忽略了(1, 0.0)). 由于所有的问题集事先都知道,那么在稀疏矩阵的文档表示中所有缺失的特性可以认为都是0.0.

gensim的特别之处在于,它没有限定任何特定的语料格式;语料可以是任何格式,当迭代时,通过稀疏矩阵来完成即可。例如,集合 ([(2, 2.0), (3, 5.0)], ([0, -1.0], [3, -1.0])) 是一个包含两个文档的语料,每个都有两个非零的 pair。

模型(model)

对于我们来说,一个模型就是一个变换(transformation),将一种文档表示转换成另一种。初始和目标表示都是向量--它们只在question和answer之间有区别。这个变换可以通过训练的语料进行自动学习,无需人工监督,最终的文档表示将更加紧凑和有用;相似的文档具有相似的表示。

'''

训练/测试模型,并持续更新

把每一篇文章的主题*相关系数保存在数据库中,然后再查询

# coding=utf-8
import codecs
import logging
import os
import pymysql.cursors
from gensim import corpora
from gensim.models import LdaModel
import pymysql.cursors

#from gensim.corpora import Dictionary
#设置日志文件
LOG_FORMAT = "%(asctime)s - %(levelname)s - %(message)s"
logging.basicConfig(filename=u'd:\zlf_lda_sql.log', level=logging.DEBUG, format=LOG_FORMAT)

# 连接数据库
connect = pymysql.Connect(
    host='localhost',
    port=3306,
    user='root',
    passwd=' ',
    db='MySql',
    charset='utf8'
)

# 获取游标
cursor = connect.cursor()

lda = LdaModel.load('zlf_lda.model')#加载模型
'''下面是批量向数据库中插入,当前文章-主题的关系
rootdir ='D:\zlf\\temp1\\2013'
list = os.listdir(rootdir) #列出文件夹下所有的目录与文件
print(list)
logging.info(list)
for i in range(0,len(list)):
       path = os.path.join(rootdir,list[i])
       test_doc =path #'D:\zlf\\temp1\\2013\srep00810.nxml' #新文档进行分词
       target = []
       fr1 = open(test_doc, 'r', encoding='utf-8')
       for line1 in fr1.readlines():
           line1 = line1.split(' ')
           target.append(line1)
           length = str(len(target))
           print('target长度' + length)
       # print ('给第一个训练集分词'+ train[0])
       target_dictionary = corpora.Dictionary(target)  ## 得到单词的ID,统计单词出现的次数以及统计信息
       print('target dictionary:')
       print(target_dictionary)
       # test_doc = train[0]  # 查看训练集中第三个样本的主题分布
       doc_bow = target_dictionary.doc2bow(target[0])  # 文档转换成bow
       doc_lda = lda[doc_bow]  # 得到新文档的主题分布
       #用新的文章更新训练模型,提高模型准确度
       new_corpus = [target_dictionary.doc2bow(text) for text in target]
       lda.update(new_corpus)
       # 输出新文档的主题分布
       print('得到新文档的主题分布:')
       print(doc_lda)
       print('新文档主题分布结束')
       for topic in doc_lda:
           print(topic[0], topic[1])
           # 插入数据
           sql = "INSERT INTO zlf_doc_topic (doc_name, topic_id, relativity) VALUES ( '%s', '%s', %.4f )"
           data = (list[i], topic[0], topic[1])
           cursor.execute(sql % data)
           connect.commit()
           print('成功插入', cursor.rowcount, '条数据')
           print("%s\t%f\n" % (lda.print_topic(topic[0]), topic[1]))
'''
#下面是当输入一篇新的文章时,推荐最相关的20篇文章。
test_doc='srep00210.nxml'
path = os.path.join('D:\zlf\\newfile', test_doc)
target = []
fr1 = open(path, 'r', encoding='utf-8')
for line1 in fr1.readlines():
    line1 = line1.split(' ')
    target.append(line1)
    length = str(len(target))
    print('target长度' + length)
# print ('给第一个训练集分词'+ train[0])
target_dictionary = corpora.Dictionary(target)  ## 得到单词的ID,统计单词出现的次数以及统计信息
print('target dictionary:')
print(target_dictionary)
# test_doc = train[0]  # 查看训练集中第三个样本的主题分布
doc_bow = target_dictionary.doc2bow(target[0])  # 文档转换成bow
doc_lda = lda[doc_bow]  # 得到新文档的主题分布
#用新的文章更新训练模型,提高模型准确度
new_corpus = [target_dictionary.doc2bow(text) for text in target]
lda.update(new_corpus)
# 输出新文档的主题分布,并将这篇文章记录在案
print('得到新文档的主题分布:')
print(doc_lda)
print('新文档主题分布结束')
for topic in doc_lda:
    print(topic[0], topic[1])
    # 插入数据
    sql = "INSERT INTO zlf_doc_topic (doc_name, topic_id, relativity) VALUES ( '%s', '%s', %.4f )"
    data = (test_doc, topic[0], topic[1])
    cursor.execute(sql % data)
    connect.commit()
    print('成功插入', cursor.rowcount, '条数据')
    print("%s\t%f\n" % (lda.print_topic(topic[0]), topic[1]))
sql_result="select distinct a.doc_name,b.doc_name,a.topic_id,a.relativity*b.relativity as rel from zlf_doc_topic a  left join zlf_doc_topic b on a.topic_id=b.topic_id and a.doc_name<>b.doc_name  where a.doc_name='%s' order by a.relativity*b.relativity desc limit 20"
cursor.execute(sql_result % test_doc)
connect.commit()
for row in cursor.fetchall():
    print(row)
print('共推荐', cursor.rowcount, '篇文章')
  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值