python数据分析:内容数据化运营(中)——基于潜在狄利克雷分配(LDA)的内容主体挖掘

版权声明:本文为博主原创文章,如若转载请注明出处 https://blog.csdn.net/tonydz0523/article/details/85127034

案例背景

本案例是从一堆新闻文件中建立相应的主题模型,然后得到不同模型的主题特点,并通过对新文本数据集的预测得到其可能的主题分类。

相关知识

TF-IDF

TF-IDF(term frequency–inverse document frequency)是一种针对关键字的统计分析方法,用来评估关键字或词语对于文档、语料库和文件集合的重要程度。关键字的重要程度跟它在文档中出现的次数成正比,但同时跟它出现的频率呈反比。这种加权的形式能有效避免常用词对于关键字搜索结果的影响,提高搜索结果与关键字之间的相关性。
TF-IDF的主要思想是:如果某个关键字在一篇文档中出现的频率(TF, Term Frequ-ency)高,并且在其他文档中很少出现,那么认为该关键字具有良好的区分不同文档的能力,也就越重要。

词性标注

所谓词性标注是给每个词语确定一个词性分类。例如,“我爱Python数据分析与数据化运营”分词后的结果可能是“我|爱|Python|数据分析|与|数据|化|运营”,其中每个词都有专属的分类:

  • 我:代词
  • 爱:动词
  • Python:英语
  • 数据分析:习用语
  • 与:介词
  • 数据:名词
  • 化:名词
  • 运营:名动词,具有名词功能的动词

在运营中,有很多场景需要做词性标注,然后基于标注的词性可以做进一步应用。例如统计竞争对手新闻稿的主要词语分布、分词结果筛选和过滤、配合文章标签的提取等。

案例数据

案例数据来自搜狐新闻2012年6月到7月的部分新闻数据,涵盖了新闻、家居、体育等主题频道。压缩包中有10个新闻文件,每个文件中包含多条新闻,每条新闻的格式相同,包含新闻URL、页面ID、页面标题和页面内容4部分。以下是文件内容格式示例:

<doc>
<url>页面URL</url>
<docno>页面ID</docno>
<contenttitle>页面标题</contenttitle>
<content>页面内容</content>
</doc>

python实现

导入模块

# tar压缩包库
import tarfile
import os
# 带词性标注的分词模块
import jieba.posseg as pseg
# gensim的词频统计和主题建模模块
from gensim import corpora, models
# 用于处理xml格式文件
from bs4 import BeautifulSoup

解压文件

#### 解压缩文件 #####
if not os.path.exists('./news_data'):  # 如果不存在数据目录,则先解压数据文件
    tar = tarfile.open('news_data.tar.gz')  # 打开tar.gz压缩包对象
    names = tar.getnames()  # 获得压缩包内的每个文件对象的名称
    for name in names:  # 循环读出每个文件
        tar.extract(name, path='./')  # 将文件解压到指定目录
    tar.close()  # 关闭压缩包对象

内容汇总

# 全角转半角
def str_convert(content):
    '''
    将内容中的全角字符,包含英文字母、数字键、符号等转换为半角字符
    :param content: 要转换的字符串内容
    :return: 转换后的半角字符串
    '''
    new_str = ''
    for each_char in content:  # 循环读取每个字符
        code_num = ord(each_char)  # 读取字符的ASCII值或Unicode值
        if code_num == 12288:  # 全角空格直接转换
            code_num = 32
        elif (code_num >= 65281 and code_num <= 65374):  # 全角字符(除空格)根据关系转化
            code_num -= 65248
        new_str += chr(code_num)
    return new_str

# 解析文件内容
def data_parse(data):
    '''
    从原始文件中解析出文本内容数据
    :param data: 包含代码的原始内容
    :return: 文本中的所有内容,列表型
    '''
    raw_code = BeautifulSoup(data, "lxml")  # 建立BeautifulSoup对象
    content_code = raw_code.find_all('content')  # 从包含文本的代码块中找到content标签
    content_list = []  # 建立空列表,用来存储每个content标签的内容
    for each_content in content_code:  # 循环读出每个content标签
        if len(each_content) > 0:  # 如果content标签的内容不为空
            raw_content = each_content.text  # 获取原始内容字符串
            convert_content = str_convert(raw_content)  # 将全角转换为半角
            content_list.append(convert_content)  # 将content文本内容加入列表
    return content_list

# 汇总所有内容
all_content = []  # 总列表,用于存储所有文件的文本内容
for root, dirs, files in os.walk('./news_data'):  # 分别读取遍历目录下的根目录、子目录和文件列表
    for file in files:  # 读取每个文件
        file_name = os.path.join(root, file)  # 将目录路径与文件名合并为带有完整路径的文件名
        with open(file_name, encoding='utf-8') as f:  # 以只读方式打开文件
            data = f.read()  # 读取文件内容
        all_content.extend(data_parse(data))  # 从文件内容中获取文本并将结果追加到总列表

使用jieba对内容进行分词

耗时比较长

# 中文分词
def jieba_cut(text):
    '''
    将输入的文本句子根据词性标注做分词
    :param text: 文本句子,字符串型
    :return: 符合规则的分词结果
    '''
    rule_words = ['z', 'vn', 'v', 't', 'nz', 'nr', 'ns', 'n', 'l', 'i', 'j', 'an',
                  'a']  # 只保留状态词、名动词、动词、时间词、其他名词、人名、地名、名词、习用语、简称略语、成语、形容词、名形词
    words = pseg.cut(text)  # 分词
    seg_list = []  # 列表用于存储每个文件的分词结果
    for word in words:  # 循环得到每个分词
        if word.flag in rule_words:
            seg_list.append(word.word)  # 将分词追加到列表
    return seg_list

# 获取每条内容的分词结果
words_list = []  # 分词列表,用于存储所有文件的分词结果
for each_content in all_content:  # 循环读出每个文本内容
    words_list.append(list(jieba_cut(each_content)))  # 将文件内容的分词结果以列表的形式追加到列表

建立主题模型

通过LDA进行主题建模,分为3个主题

# 文本预处理
def text_pro(words_list, tfidf_object=None, training=True):
    '''
    gensim主题建模预处理过程,包含分词类别转字典、生成语料库和TF-IDF转换
    :param words_list: 分词列表,列表型
    :param tfidf_object: TF-IDF模型对象,该对象在训练阶段生成
    :param training: 是否训练阶段,用来针对训练和预测两个阶段做预处理
    :return: 如果是训练阶段,返回词典、TF-IDF对象和TF-IDF向量空间数据;如果是预测阶段,返回TF-IDF向量空间数据
    '''
    # 分词列表转字典
    dic = corpora.Dictionary(words_list)  # 将分词列表转换为字典形式
    print ('{:*^60}'.format('token & word mapping review:'))
    for i in range(5):  # 循环读出字典前5条的每个key和value,对应的是索引值和分词
        print ('token:%s -- word:%s' % (i, dic[i]))
    # 生成语料库
    corpus = []  # 建立一个用于存储语料库的列表
    for words in words_list:  # 读取每个分词列表
        corpus.append(dic.doc2bow(words))  # 将每个分词列表转换为语料库词袋(bag of words)形式的列表
    print ('{:*^60}'.format('bag of words review:'))
    print (corpus[0])  # 打印输出第一条语料库
    # TF-IDF转换
    if training == True:
        tfidf = models.TfidfModel(corpus)  # 建立TF-IDF模型对象
        corpus_tfidf = tfidf[corpus]  # 得到TF-IDF向量稀疏矩阵
        print ('{:*^60}'.format('TF-IDF model review:'))
        for doc in corpus_tfidf:  # 循环读出每个向量
            print (doc ) # 打印第一条向量
            break  # 跳出循环
        return dic, corpus_tfidf, tfidf
    else:
        return tfidf_object[corpus]

# 建立主题模型
print ('train topic model...')
dic, corpus_tfidf, tfidf = text_pro(words_list, tfidf_object=None, training=True)  # 训练集的文本预处理
num_topics = 3  # 设置主题个数
lda = models.LdaModel(corpus_tfidf, id2word=dic, num_topics=num_topics)  # 通过LDA进行主题建模
print ('{:*^60}'.format('topic model review:'))
for i in range(num_topics):  # 输出每一类主题的结果
    print (lda.print_topic(i))  # 输出对应主题

结果如下:

****************token & word mapping review:****************
token:0 -- word:仇恨
token:1 -- word:侮辱
token:2 -- word:侵害
token:3 -- word:凶杀
token:4 -- word:危害
********************bag of words review:********************
[(0, 1), (1, 1), (2, 1), (3, 1), (4, 1), (5, 1), (6, 1), (7, 1), (8, 1), (9, 1), (10, 1), (11, 1), (12, 1), (13, 1), (14, 1), (15, 1), (16, 2), (17, 1), (18, 1), (19, 2), (20, 1), (21, 1), (22, 1), (23, 1), (24, 1), (25, 2), (26, 1), (27, 1), (28, 1), (29, 1), (30, 1), (31, 1)]
********************TF-IDF model review:********************
[(0, 0.16762633852828174), (1, 0.16660204914253687), (2, 0.1643986382302142), (3, 0.168282481745965), (4, 0.16197667368712637), (5, 0.14602961468426073), (6, 0.16282320045073903), (7, 0.10154448591145282), (8, 0.12365275311464316), (9, 0.12399080729729553), (10, 0.16703117734810868), (11, 0.163124879458702), (12, 0.16844765669812112), (13, 0.16409043499326897), (14, 0.1662290891913951), (15, 0.1685028172752526), (16, 0.332245916102828), (17, 0.16383481532598135), (18, 0.16681622559479037), (19, 0.30849126342177313), (20, 0.1677351934753784), (21, 0.16778969587205647), (22, 0.15736459689355045), (23, 0.15266091940783724), (24, 0.11609101090194619), (25, 0.2636835311342954), (26, 0.14576561774317554), (27, 0.16762633852828174), (28, 0.16751768276692697), (29, 0.1653853043789113), (30, 0.16501988564410103), (31, 0.16833748902827933)]
********************topic model review:*********************
0.005*"散布" + 0.004*"民族" + 0.004*"稳定" + 0.004*"标题" + 0.002*"谣言" + 0.002*"赌博" + 0.002*"教唆" + 0.002*"封建迷信" + 0.002*"邪教" + 0.002*"凶杀"
0.002*"比赛" + 0.002*"是" + 0.001*"搜狐" + 0.001*"小区" + 0.001*"有" + 0.001*"编号" + 0.001*"时间" + 0.001*"北京" + 0.001*"欧洲杯" + 0.001*"人"
0.003*"登录" + 0.002*"删除" + 0.001*"企业" + 0.001*"访问" + 0.001*"房源" + 0.001*"房价" + 0.001*"存在" + 0.001*"市场" + 0.001*"土地" + 0.001*"个人" 

新文本归类

#### 新文本归类###
with open('article.txt', encoding='utf-8') as f:  # 打开新的文本
    text_new = f.read()  # 读取文本数据
text_content = data_parse(data)  # 解析新的文本
words_list_new = jieba_cut(text_new)  # 将文本转换为分词列表
corpus_tfidf_new = text_pro([words_list_new], tfidf_object=tfidf, training=False)  # 新文本数据集的预处理
corpus_lda_new = lda[corpus_tfidf_new]  # 获取新的分词列表(文档)的主题概率分布
print ('{:*^60}'.format('topic forecast:'))
print (list(corpus_lda_new))

结果如下:

****************token & word mapping review:****************
token:0 -- word:一鸣惊人
token:1 -- word:三剑客
token:2 -- word:上演
token:3 -- word:不败
token:4 -- word:专业培训
********************bag of words review:********************
[(0, 1), (1, 1), (2, 1), (3, 1), (4, 1), (5, 3), (6, 1), (7, 1), (8, 1), (9, 1), (10, 2), (11, 1), (12, 1), (13, 1), (14, 1), (15, 2), (16, 2), (17, 1), (18, 1), (19, 3), (20, 1), (21, 1), (22, 1), (23, 1), (24, 1), (25, 1), (26, 1), (27, 2), (28, 3), (29, 2), (30, 1), (31, 1), (32, 1), (33, 1), (34, 1), (35, 2), (36, 1), (37, 2), (38, 1), (39, 1), (40, 2), (41, 2), (42, 1), (43, 1), (44, 1), (45, 1), (46, 1), (47, 1), (48, 1), (49, 1), (50, 2), (51, 1), (52, 1), (53, 1), (54, 2), (55, 3), (56, 1), (57, 1), (58, 1), (59, 2), (60, 1), (61, 1), (62, 2), (63, 1), (64, 1), (65, 1), (66, 1), (67, 1), (68, 1), (69, 1), (70, 2), (71, 1), (72, 1), (73, 4), (74, 1), (75, 1), (76, 1), (77, 7), (78, 5), (79, 2), (80, 1), (81, 1), (82, 1), (83, 1), (84, 1), (85, 1), (86, 1), (87, 1), (88, 1), (89, 4), (90, 4), (91, 1), (92, 1), (93, 7), (94, 1), (95, 1), (96, 2), (97, 3), (98, 1), (99, 2), (100, 2), (101, 1), (102, 1), (103, 1), (104, 1), (105, 1), (106, 8), (107, 1), (108, 3), (109, 1), (110, 1), (111, 3), (112, 2), (113, 1), (114, 1), (115, 1), (116, 2), (117, 1), (118, 1), (119, 1), (120, 7), (121, 2), (122, 4), (123, 1), (124, 1), (125, 1), (126, 1), (127, 1), (128, 1), (129, 17), (130, 1), (131, 4), (132, 1), (133, 1), (134, 1), (135, 1), (136, 1), (137, 1), (138, 1), (139, 2), (140, 2), (141, 2), (142, 1), (143, 1), (144, 1), (145, 2), (146, 1), (147, 2), (148, 1), (149, 2), (150, 1), (151, 1), (152, 1), (153, 1), (154, 1), (155, 4), (156, 1), (157, 1), (158, 2), (159, 2), (160, 1), (161, 1), (162, 5), (163, 1), (164, 2), (165, 1), (166, 6), (167, 1), (168, 1), (169, 1), (170, 1), (171, 2), (172, 1), (173, 1), (174, 1), (175, 1), (176, 2), (177, 1), (178, 1), (179, 3), (180, 1), (181, 1), (182, 1), (183, 3), (184, 1), (185, 1), (186, 2)]
**********************topic forecast:***********************
[[(0, 0.1855535), (1, 0.6704695), (2, 0.14397693)]]

根据概率来看应该归属于第二个主题

结论

从主题建模得到的结果看,三个主题是:

  1. 0.005*“散布” + 0.004*“民族” + 0.004*“稳定” + 0.004*“标题” + 0.002*“谣言” + 0.002*“赌博” + 0.002*“教唆” + 0.002*“封建迷信” + 0.002*“邪教” + 0.002*“凶杀”
  2. 0.002*“比赛” + 0.002*“是” + 0.001*“搜狐” + 0.001*“小区” + 0.001*“有” + 0.001*“编号” + 0.001*“时间” + 0.001*“北京” + 0.001*“欧洲杯” + 0.001*“人”
  3. 0.003*“登录” + 0.002*“删除” + 0.001*“企业” + 0.001*“访问” + 0.001*“房源” + 0.001*“房价” + 0.001*“存在” + 0.001*“市场” + 0.001*“土地” + 0.001*“个人”

可见:

  • 主题一中,权重比较高的词语是散步、民族、稳定、标题等,权重在0.004以上,其次是赌博、教唆等,因此大体可以判断该主体与政治、社会、新闻等主题相关。
  • 主题二中,权重高的词语为比赛、是,其次是欧洲杯、编号等因此大体可以判断该主题是与比赛、体育相关。
  • 主题三中,权重高的词语有登入、企业、房源、房价之类,因此大体可以判断该主题是与房地产相关。

对新主题的预测上,通过结果可以发现,该数据集属于第一、二、三主题的概率是18.6%,67%,14.4%。因此新数据集更偏向于第二主题,即比赛、体育类话题。读者可查看源文件,该文件确实属于该内容方向。

案例应用

对于此类以文本挖掘为主的分析类案例,后期的主要应用方向包括:

  • 从各个类别的主题中提取关键字,并将关键字作为各个主题的SEO关键字优化主题;
  • 不断增加新的文本数据,然后将每次的主题关键字做对比,查找分析新出现的主题关键字,建立对于特定主题的分析和后续内容运营机制;
  • 基于历史主题建模结果做对比,发现各个主题中的新词、新趋势和新话题点;
  • 基于主题关键字,建立或优化主题页的自动关键字和自动摘要信息;
  • 基于具有显著性的关键字(较高权重),将所有文章进行重新类别划分或优化,使主题间的关联性和话题紧密型更强。

补充

  • 在做预测集应用时,所有的文本预处理过程都需要重新做一遍,但TF-IDF模型不需要重新做,只需要应用训练好的模型即可。
  • 主题类别的划分,严重依赖于中文分词的结果。本案例中,只保留具有特定意义的词语,而“有意义”的认知依赖于具体应用环境;通常情况下,名词、动词等类型的词语在主题提炼中更具有显著性意义。
  • 新的预测集在做文本预处理时,需要保持跟训练集相同的格式(此案例中都是嵌套列表)。
  • 如果有大量的文本数据集,可以将训练好的LDA(以及其他模型)持久性保存到硬盘,使用save和load方法可以实现。这样可以避免所有数据对象都存储在内存中,由于数据计算容量的增加导致内存溢出等问题。
  • 案例中的结巴分词模块,对于大文本下的分词效率比较低。结巴分词支持并行分词,它可以将目标文本按行分隔后,把各行文本分配到多个Python进程并行分词然后归并结果,从而获得分词速度的提升。并行分词基于Python自带的multiprocessing模块,但是目前暂不支持Windows。
  • 主题模型中关于主题个数的确定,跟KMeans算法中的K非常类似,如果是针对新的文本集,在没有任何先验经验的前提下要获得有意义的主题是比较困难的,这需要读者通过多次实验获的。

参考:

《python数据分析与数据化运营》 宋天龙

猜你喜欢

转载自blog.csdn.net/tonydz0523/article/details/85127034
今日推荐