基于Topic model的中文文本分类

本文介绍了使用LDA进行文本分类的实验流程,包括数据预处理、构建词频矩阵、训练LDA模型以及进行验证。通过在金庸武侠小说数据集上的实验,展示了LDA模型生成的主题分布,并分析了分类效果。虽然单词分布常见,但实验仍体现出LDA在文本特征提取方面的潜力。
摘要由CSDN通过智能技术生成

问题描述

在给定的数据库上利用Topic Model做无监督学习,学习到主题的分布。可以在数据库中随机选定K本小说,在每本小说中随机抽出M个段落作为训练数据,并抽出N个段落作为测试,利用topic model和其他的分类器对给定的段落属于哪一本小说进行分类。 其中K至少为3.

实验原理

文本分类简介

文本分类是指在给定分类体系,根据文本内容自动确定文本类别的过程。最基础的分类是归到两个类别中,称为二分类问题,例如电影评论分类,只需要分为“好评”或“差评”。分到多个类别中的称为多分类问题,例如,把名字分类为法语名字、英语名字、西班牙语名字等。

一般来说文本分类大致分为如下几个步骤:

  1. 定义阶段:定义数据以及分类体系,具体分为哪些类别,需要哪些数据。

  2. 数据预处理:对文档做分词、去停用词等准备工作。

  3. 数据提取特征:对文档矩阵进行降维,提取训练集中最有用的特征。

  4. 模型训练阶段:选择具体的分类模型以及算法,训练出文本分类器。

  5. 评测阶段:在测试集上测试并评价分类器的性能。

  6. 应用阶段:应用性能最高的分类模型对待分类文档进行分类。

LDA模型简介

LDA(Latent Dirichlet Allocation)由Blei, David M.、Ng, Andrew Y.、Jordan于2003年提出,用来推测文档的主题分布。它可以将文档集中每篇文档的主题以概率分布的形式给出,从而通过分析一些文档抽取出它们的主题分布后,便可以根据主题分布进行主题聚类或文本分类。LDA 模型是话题模型(topic model)的典型代表,通过概率模型建立了从主题到文档中每个词的关系,从利用贝叶斯概率,能通过文档的词推导出文档的主题。

LDA中的 3 个概念:

词(word):数据中的基本离散单元

文档(document):待处理的数据对象,由词组成,不计顺序。文档对象在话题模型中是“词袋”概念。

话题(topic):每篇文档都有特定的一些话题,根据这些话题产生

在LDA模型中,一篇文章的生成方式如下:

  1. 从狄利克雷分布 α \alpha α中取样生成文档i的主题分布 θ i \theta _{i} θi

  2. 从主题的多项式分布 θ i \theta_{i} θi中取样生成文档i第 j 个词的主题 z i , j z_{i,j} zi,j

  3. 从狄利克雷分布 β \beta β中取样生成主题 z i , j z_{i,j} zi,j对应的词语分布 ϕ z i , j \phi_{z_{i,j}} ϕzi,j

  4. 从词语的多项式分布 ϕ z i , j \phi_{z_{i,j}} ϕzi,j中采样最终生成的词语 ω i , j \omega_{i,j} ωi,j
    其中,类似\beta分布是二项式分布的共轭先验概率分布,而狄利克雷分布(Dirichlet分布)是多项式分布的共轭先验概率分布。LDA的图模型结构如下图所示
    LDA模型结构示意图

多项式分布

多项分布,是二项分布扩展到多维的情况. 多项分布是指单次试验中的随机变量的取值不再是0-1的,而是有多种离散值可能(1,2,3…,k).概率密度函数为:
p ( x 1 , x 2 , ⋅ ⋅ ⋅ , x k ; n , p 1 , p 2 , ⋅ ⋅ ⋅ , p k ) = n ! x 1 ! ⋅ ⋅ ⋅ x k ! p 1 x 1 ⋅ ⋅ ⋅ p k x k p(x_1, x_2,\cdot \cdot\cdot,x_k;n,p_1,p_2,\cdot\cdot\cdot,p_{k})=\frac{n!}{x_1!\cdot\cdot\cdot x_k!}p_1 ^{x_1} \cdot\cdot\cdot p_k^ {x_k} p(x1,x2,,xk;n,p1,p2,,pk)=x1!xk!n!p1x1pkxk

Gamma分布

Gamma函数的定义:
Γ ( x ) = ∫ 0 ∞ t x − 1 e − t d t \Gamma(x) = \int_{0}^{\infty} t^{x-1}e^{-t}dt Γ(x)=0tx1etdt

β \beta β分布

β \beta β分布的定义:对于参数\alpha>0,\beta>0, 取值范围为[0, 1]的随机变量x的概率密度函数为
f ( x ; α , β ) = 1 B ( α , β ) x α − 1 ( 1 − x ) β − 1 f(x;\alpha, \beta) = \frac{1}{B(\alpha,\beta)}x^{\alpha-1}(1-x)^{\beta-1} f(x;α,β)=B(α,β)1xα1(1x)β1
其中
1 B ( α , β ) = Γ ( α + β ) Γ ( α ) Γ ( β ) \frac{1}{B(\alpha,\beta)} = \frac{\Gamma(\alpha+\beta)}{\Gamma(\alpha)\Gamma(\beta)} B(α,β)1=Γ(α)Γ(β)Γ(α+β)

Dirichlet分布

Dirichlet分布是多项式分布的共轭先验分布。这两个分布模型在文档生成模型中的应用是非常自然的想法——因为后验分布和先验分布具有相同形式,只是参数有所不同,这意味着当我们获得新的观察数据时,我们就能直接通过更新参数,获得新的后验分布。参数为的Dirichlet分布的密度函数为
p ( θ ∣ α ) = Γ ( ∑ i = 1 K α i ) ∏ i = 1 K Γ ( α i ) θ 1 α 1 − 1 ⋅ ⋅ ⋅ θ k α k − 1 p(\theta |\alpha) = \frac {\Gamma (\sum_{i=1}^{K} \alpha_{i})} {\prod\nolimits_{i=1}^K \Gamma(\alpha_{i}) } \theta_{1}^{\alpha_{1}-1}\cdot \cdot\cdot \theta_{k}^{\alpha_{k}-1} p(θα)=i=1KΓ(αi)Γ(i=1Kαi)θ1α11θkαk1
根据该模型可写出LDA的联合概率分布为
p ( w , z , θ , β ∣ α , η ) = p ( θ ∣ α ) p ( β ∣ η ) ∏ j = 1 N p ( z j ∣ θ ) p ( ω j ∣ z j ) p(w,z,\theta, \beta\vert \alpha, \eta ) = p(\theta \vert \alpha)p(\beta \vert\eta) \prod\nolimits_{j=1}^N p(z_{j}\vert \theta) p(\omega_{j}\vert_{z_{j}}) p(w,z,θ,βα,η)=p(θα)p(βη)j=1Np(zjθ)p(ωjzj)

实验流程

本次实验以第一次实验所提供的金庸先生的16本武侠小说作为数据集,利用LDA进行文本分类。

数据预处理

与第一次实验相同,删去所有的隐藏符号,删除所有的非中文字符,不考虑上下文关系的前提下删去所有标点符号。以jieba库对中文语料进行分词。由实验要求得,需对数据库进行训练集和测试集划分。在该实验当中,将测试集化为2000行语料,其余行列为训练集。

def get_data():
    """如果文档还没分词,就进行分词"""
    outfilename_1 = "./cnews.train_jieba.txt"
    outfilename_2 = "./cnews.test_jieba.txt"
    if not os.path.exists('./cnews.train_jieba.txt'):
        outputs = open(outfilename_1, 'w', encoding='UTF-8')
        outputs_test = open(outfilename_2, 'w', encoding='UTF-8')
        datasets_root = "./datasets"
        catalog = "inf.txt"

        test_num = 10
        test_length = 20
        with open(os.path.join(datasets_root, catalog), "r", encoding='utf-8') as f:
            all_files = f.readline().split(",")
            print(all_files)

        for name in all_files:
            with open(os.path.join(datasets_root, name + ".txt"), "r", encoding='utf-8') as f:
                file_read = f.readlines()
                train_num = len(file_read) - test_num
                choice_index = np.random.choice(len(file_read), test_num + train_num, replace=False)
                train_text = ""
                for train in choice_index[0:train_num]:
                    line = file_read[train]
                    line = re.sub('\s', '', line)
                    line = re.sub('[\u0000-\u4DFF]', '', line)
                    line = re.sub('[\u9FA6-\uFFFF]', '', line)
                    if len(line) == 0:
                        continue
                    seg_list = list(jieba.cut(line, cut_all=False))  # 使用精确模式
                    line_seg = ""
                    for term in seg_list:
                        line_seg += term + " "
                        # for index in range len(line_seg):
                    outputs.write(line_seg.strip() + '\n')


                for test in choice_index[train_num:test_num + train_num]:
                    if test + test_length >= len(file_read):
                        continue
                    test_line = ""
                    # for i in range(test, test + test_length):
                    line = file_read[test]
                    line = re.sub('\s', '', line)
                    line = re.sub('[\u0000-\u4DFF]', '', line)
                    line = re.sub('[\u9FA6-\uFFFF]', '', line)
                    seg_list = list(jieba.cut(line, cut_all=False))  # 使用精确模式
                    #line_seg = ""
                    for term in seg_list:
                        test_line += term + " "
                    outputs_test.write(test_line.strip()+'\n')

        outputs.close()
        outputs_test.close()
        print("得到训练集与测试集!!!")

运用LDA进行训练并进行验证

本文用了gensim中的corpora和其中自带的lda模型来进行训练。corpora能够构建词频矩阵。

    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)


    """构建词频矩阵,训练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=16)
    # lsi = models.LsiModel(corpus=corpus,id2word=dictionary,num_topics=16)
    topic_list_lda = lda.print_topics(16)
    # topic_list_lsi = lsi.print_topics(16)
    print("以LDA为分类器的16个主题的单词分布为:\n")
    for topic in topic_list_lda:
        print(topic)

    """测试"""
    file_test = "./cnews.test_jieba.txt"
    news_test = open(file_test, 'r', encoding='UTF-8')
    test = []
    # 处理成正确的输入格式
    for line in news_test:
       # line = line.split('\t')[1]
        #line = re.sub(r'[^\u4e00-\u9fa5]+', '', line,)
        line = [word.strip() for word in line.split(' ')]
        test.append(line)

    for text in test:
        corpus_test = dictionary.doc2bow((text))
        # print(corpus_test)



    corpus_test = [dictionary.doc2bow(text) for text in test]
    # print(corpus_test)
    # 得到每本小说的主题分布
    print("以LDA为分类器的十六本小说的主题分布为:\n")
    topics_test = lda.get_document_topics(corpus_test)

    for i in range(10):
        print(i)
        print('的主题分布为:\n')
        print(topics_test[i], '\n')

    fr.close()
    news_test.close()

实验结果

16个主题的单词分布为:

(0, ‘0.074*“即” + 0.045*“一掌” + 0.024*“正要” + 0.023*“劈” + 0.021*“长老” + 0.015*“相见” + 0.013*“瞧见” + 0.013*“位” + 0.012*“占” + 0.012*“足”’)
(1, ‘0.069*“内力” + 0.028*“这次” + 0.013*“伤人” + 0.013*“火焰” + 0.011*“尽” + 0.011*“万” + 0.011*“心惊” + 0.010*“说出” + 0.010*“可要” + 0.010*“刀法”’)
(2, ‘0.023*“了” + 0.022*“的” + 0.016*“哪” + 0.012*“於” + 0.012*“在” + 0.011*“又” + 0.010*“或” + 0.010*“怎能” + 0.009*“因” + 0.009*“如何”’)
(3, ‘0.060*“的” + 0.028*“他” + 0.027*“了” + 0.022*“是” + 0.019*“这” + 0.016*“弟子” + 0.015*“在” + 0.012*“武功” + 0.010*“也” + 0.009*“和”’)
(4, ‘0.035*“了” + 0.028*“在” + 0.023*“他” + 0.021*“得” + 0.021*“的” + 0.021*“听” + 0.015*“便” + 0.015*“中” + 0.014*“但” + 0.012*“见”’)
(5, ‘0.033*“他” + 0.032*“如此” + 0.022*“了” + 0.018*“兄弟” + 0.016*“剑法” + 0.015*“却” + 0.014*“的” + 0.013*“与” + 0.011*“但” + 0.011*“到”’)
(6, ‘0.056*“我” + 0.052*“你” + 0.049*“了” + 0.044*“道” + 0.030*“的” + 0.027*“他” + 0.022*“是” + 0.016*“也” + 0.014*“那” + 0.014*“不”’)
(7, ‘0.022*“的” + 0.022*“人物” + 0.018*“尽” + 0.014*“逼” + 0.013*“银子” + 0.012*“只好” + 0.011*“胡说八道” + 0.010*“哈哈” + 0.009*“显得” + 0.009*“为”’)
(8, ‘0.033*“前来” + 0.021*“小小” + 0.016*“啷” + 0.014*“同门” + 0.013*“如何是好” + 0.012*“呛” + 0.011*“嫌” + 0.010*“出招” + 0.010*“跌” + 0.009*“来不及”’)
(9, ‘0.066*“的” + 0.035*“在” + 0.028*“了” + 0.024*“他” + 0.018*“上” + 0.018*“是” + 0.013*“这” + 0.012*“中” + 0.009*“一” + 0.009*“将”’)
(10, ‘0.020*“来” + 0.019*“张” + 0.017*“一名” + 0.014*“站” + 0.013*“一面” + 0.013*“汉子” + 0.012*“起身” + 0.011*“声响” + 0.010*“那” + 0.010*“到”’)
(11, ‘0.038*“了” + 0.027*“将” + 0.025*“的” + 0.017*“长剑” + 0.016*“他” + 0.016*“已” + 0.015*“在” + 0.014*“一” + 0.013*“向” + 0.011*“从”’)
(12, ‘0.038*“两位” + 0.026*“不必” + 0.019*“招数” + 0.018*“走出” + 0.013*“中土” + 0.012*“拜” + 0.011*“毒手” + 0.011*“敌手” + 0.011*“目光” + 0.010*“擒”’)
(13, ‘0.072*“的” + 0.037*“是” + 0.037*“了” + 0.023*“道” + 0.020*“她” + 0.017*“这” + 0.016*“他” + 0.016*“在” + 0.013*“也” + 0.013*“有”’)
(14, ‘0.064*“的” + 0.061*“是” + 0.026*“也” + 0.022*“他” + 0.020*“和” + 0.020*“人” + 0.019*“有” + 0.016*“都” + 0.015*“说” + 0.012*“这”’)
(15, ‘0.044*“武林” + 0.042*“居然” + 0.034*“一位” + 0.026*“撞” + 0.019*“房中” + 0.016*“颤” + 0.013*“声道” + 0.013*“力道” + 0.012*“横” + 0.010*“外号”’)

0
的主题分布为:

[(6, 0.34669572), (13, 0.6095521)] 可以看出属于第14个主题

1
的主题分布为:

[(0, 0.08241061), (3, 0.4389208), (6, 0.4161207)] 可以看出属于第3个主题的概率大

2
的主题分布为:

[(0, 0.012508655), (1, 0.012508655), (2, 0.012508656), (3, 0.012508665), (4, 0.012508665), (5, 0.012508658), (6, 0.60123813), (7, 0.22364065), (8, 0.012508655), (9, 0.012508662), (10, 0.012508655), (11, 0.012508661), (12, 0.012508655), (13, 0.012508659), (14, 0.012508705), (15, 0.012508655)] 可以看出属于第七个主题

3
的主题分布为:

[(6, 0.65538734), (12, 0.087329924), (14, 0.2064464)] 可以看出属于第七个主题

4
的主题分布为:

[(6, 0.8660557)] 可以看出属于第七个主题

5
的主题分布为:

[(4, 0.1860438), (5, 0.2250588), (9, 0.16105035), (11, 0.37007958)] 可以看出属于第十二个主题

6
的主题分布为:

[(3, 0.11233538), (9, 0.07748296), (12, 0.07419315), (13, 0.5327368), (14, 0.16874391)] 可以看出属于第十三个主题

7
的主题分布为:

[(0, 0.015625179), (1, 0.015625179), (2, 0.015625188), (3, 0.015625188), (4, 0.015625205), (5, 0.015625186), (6, 0.015625183), (7, 0.015625183), (8, 0.015625179), (9, 0.0156252), (10, 0.01562518), (11, 0.015625192), (12, 0.01562518), (13, 0.015625196), (14, 0.7656222), (15, 0.015625179)] 可以看出属于第十五个主题

8
的主题分布为:

[(6, 0.75041765), (9, 0.16206965)] 可以看出属于第七个主题

9
的主题分布为:

[(6, 0.70361245), (9, 0.10615072), (13, 0.13601784)] 可以看出属于第七个主题

总结

在理解了LDA主题模型原理的基础上,使用了gensim中封装的LDA模型,提取了金庸小说全集的主题分布特征,得到了如上所示的效果。由实验结果可知,分类的效果并不是特别好,原因可以由16个主题的单词分布可以看出,这些单词都是一些十分常见的单词,并不具有特殊性,而在此基础上还能有不错的效果也证实了LDA的独到之处。

附录

#!/usr/bin/python
# -*- coding:utf-8 -*-

import jieba, os, re
import numpy as np
from gensim import corpora, models

def get_data():
    """如果文档还没分词,就进行分词"""
    outfilename_1 = "./cnews.train_jieba.txt"
    outfilename_2 = "./cnews.test_jieba.txt"
    if not os.path.exists('./cnews.train_jieba.txt'):
        outputs = open(outfilename_1, 'w', encoding='UTF-8')
        outputs_test = open(outfilename_2, 'w', encoding='UTF-8')
        datasets_root = "./datasets"
        catalog = "inf.txt"

        test_num = 10
        test_length = 20
        with open(os.path.join(datasets_root, catalog), "r", encoding='utf-8') as f:
            all_files = f.readline().split(",")
            print(all_files)

        for name in all_files:
            with open(os.path.join(datasets_root, name + ".txt"), "r", encoding='utf-8') as f:
                file_read = f.readlines()
                train_num = len(file_read) - test_num
                choice_index = np.random.choice(len(file_read), test_num + train_num, replace=False)
                train_text = ""
                for train in choice_index[0:train_num]:
                    line = file_read[train]
                    line = re.sub('\s', '', line)
                    line = re.sub('[\u0000-\u4DFF]', '', line)
                    line = re.sub('[\u9FA6-\uFFFF]', '', line)
                    if len(line) == 0:
                        continue
                    seg_list = list(jieba.cut(line, cut_all=False))  # 使用精确模式
                    line_seg = ""
                    for term in seg_list:
                        line_seg += term + " "
                        # for index in range len(line_seg):
                    outputs.write(line_seg.strip() + '\n')


                for test in choice_index[train_num:test_num + train_num]:
                    if test + test_length >= len(file_read):
                        continue
                    test_line = ""
                    # for i in range(test, test + test_length):
                    line = file_read[test]
                    line = re.sub('\s', '', line)
                    line = re.sub('[\u0000-\u4DFF]', '', line)
                    line = re.sub('[\u9FA6-\uFFFF]', '', line)
                    seg_list = list(jieba.cut(line, cut_all=False))  # 使用精确模式
                    #line_seg = ""
                    for term in seg_list:
                        test_line += term + " "
                    outputs_test.write(test_line.strip()+'\n')

        outputs.close()
        outputs_test.close()
        print("得到训练集与测试集!!!")


if __name__ == "__main__":
    get_data()
    """准备好训练语料,整理成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)


    """构建词频矩阵,训练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=16)
    # lsi = models.LsiModel(corpus=corpus,id2word=dictionary,num_topics=16)
    topic_list_lda = lda.print_topics(16)
    # topic_list_lsi = lsi.print_topics(16)
    print("以LDA为分类器的16个主题的单词分布为:\n")
    for topic in topic_list_lda:
        print(topic)

    """测试"""
    file_test = "./cnews.test_jieba.txt"
    news_test = open(file_test, 'r', encoding='UTF-8')
    test = []
    # 处理成正确的输入格式
    for line in news_test:
       # line = line.split('\t')[1]
        #line = re.sub(r'[^\u4e00-\u9fa5]+', '', line,)
        line = [word.strip() for word in line.split(' ')]
        test.append(line)

    for text in test:
        corpus_test = dictionary.doc2bow((text))
        # print(corpus_test)



    corpus_test = [dictionary.doc2bow(text) for text in test]
    # print(corpus_test)
    # 得到每个测试集的主题分布
    
    topics_test = lda.get_document_topics(corpus_test)

    for i in range(10):
        print(i)
        print('的主题分布为:\n')
        print(topics_test[i], '\n')

    fr.close()
    news_test.close()

[1] 我是这样一步步理解–主题模型(Topic Model)、LDA(案例代码) - 简书
[2] 一文详解LDA主题模型 - 知乎
[3] 主题模型-LDA浅析_Stay hungry, Stay foolish-CSDN博客_lda模型
[4] 隐狄利克雷主题模型(Latent Dirichlet Allocation, LDA Topic Model) - 知乎
[5] 文本主题抽取:用gensim训练LDA模型

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值