问题描述
在给定的数据库上利用Topic Model做无监督学习,学习到主题的分布。可以在数据库中随机选定K本小说,在每本小说中随机抽出M个段落作为训练数据,并抽出N个段落作为测试,利用topic model和其他的分类器对给定的段落属于哪一本小说进行分类。 其中K至少为3.
实验原理
文本分类简介
文本分类是指在给定分类体系,根据文本内容自动确定文本类别的过程。最基础的分类是归到两个类别中,称为二分类问题,例如电影评论分类,只需要分为“好评”或“差评”。分到多个类别中的称为多分类问题,例如,把名字分类为法语名字、英语名字、西班牙语名字等。
一般来说文本分类大致分为如下几个步骤:
-
定义阶段:定义数据以及分类体系,具体分为哪些类别,需要哪些数据。
-
数据预处理:对文档做分词、去停用词等准备工作。
-
数据提取特征:对文档矩阵进行降维,提取训练集中最有用的特征。
-
模型训练阶段:选择具体的分类模型以及算法,训练出文本分类器。
-
评测阶段:在测试集上测试并评价分类器的性能。
-
应用阶段:应用性能最高的分类模型对待分类文档进行分类。
LDA模型简介
LDA(Latent Dirichlet Allocation)由Blei, David M.、Ng, Andrew Y.、Jordan于2003年提出,用来推测文档的主题分布。它可以将文档集中每篇文档的主题以概率分布的形式给出,从而通过分析一些文档抽取出它们的主题分布后,便可以根据主题分布进行主题聚类或文本分类。LDA 模型是话题模型(topic model)的典型代表,通过概率模型建立了从主题到文档中每个词的关系,从利用贝叶斯概率,能通过文档的词推导出文档的主题。
LDA中的 3 个概念:
词(word):数据中的基本离散单元
文档(document):待处理的数据对象,由词组成,不计顺序。文档对象在话题模型中是“词袋”概念。
话题(topic):每篇文档都有特定的一些话题,根据这些话题产生
在LDA模型中,一篇文章的生成方式如下:
-
从狄利克雷分布 α \alpha α中取样生成文档i的主题分布 θ i \theta _{i} θi
-
从主题的多项式分布 θ i \theta_{i} θi中取样生成文档i第 j 个词的主题 z i , j z_{i,j} zi,j
-
从狄利克雷分布 β \beta β中取样生成主题 z i , j z_{i,j} zi,j对应的词语分布 ϕ z i , j \phi_{z_{i,j}} ϕzi,j
-
从词语的多项式分布 ϕ z i , j \phi_{z_{i,j}} ϕzi,j中采样最终生成的词语 ω i , j \omega_{i,j} ωi,j
其中,类似\beta分布是二项式分布的共轭先验概率分布,而狄利克雷分布(Dirichlet分布)是多项式分布的共轭先验概率分布。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!p1x1⋅⋅⋅pkxk
Gamma分布
Gamma函数的定义:
Γ
(
x
)
=
∫
0
∞
t
x
−
1
e
−
t
d
t
\Gamma(x) = \int_{0}^{\infty} t^{x-1}e^{-t}dt
Γ(x)=∫0∞tx−1e−tdt
β \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(1−x)β−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α1−1⋅⋅⋅θkαk−1
根据该模型可写出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(ωj∣zj)
实验流程
本次实验以第一次实验所提供的金庸先生的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模型