大数据时代,数据量与日俱增,其中不乏大量非结构化数据,例如文本、音频、图像、视频等。本章主要介绍中文文本数据分析的基本流程及相应流程下的知识点,并结合R语言实现部分知识点,从而可以方便地应用于实际的工作或学习中。
1 文本挖掘
文本挖掘并不是一个新鲜的词汇,事实上自从自然语言处理技术发展之时,文本挖掘技术就得以迅速地受到学者关注与研究。现今随着大量非结构化、半结构化的文本数据在企业数据环境中产生,文本挖掘也得到了越来越多的商业运用。本节简要介绍关于文本挖掘的需要知道的一些基本背景、知识。
1.1 文本挖掘简介
在计算机诞生之时,就有学者提出将计算机技术运用在自然语言处理上,例如机器翻译。但限于种种因素,文本分析与挖掘技术在20世纪90年代才开始被广泛研究与应用。如今的自然语言处理技术,是融合了计算机科学、数据挖掘、机器学习、统计学、语言学的多学科交叉领域。而文本挖掘可以说是自然语言处理所研究问题的一个分支。
文本挖掘(TextMining ),又被称为文本知识发现(Knowledge Discovery in Text ),指从文本数据中抽取出信息与知识的过程。从定义上看,文本挖掘与数据挖掘有着相似之处,但也有不同之处。文档不同于结构化数据,常用的一些数据挖掘技术往往不能直接移植到文本挖掘中,其需要根据一些合适的文档模型转换为结构化数据,从而便于应对不同的分析场景并产生商业价值。
综上,文本挖掘既被视为自然语言处理的一个分支,也被视为数据挖掘技术的一种拓展。在实践中,文本挖掘与数据挖掘一样,是由业务与应用驱动的,例如其广泛运用于商业智能、信息检索、推荐系统、机器翻译、语音识别等领域。
1.2 文本挖掘的流程
本章介绍的文本挖掘主要涉及文本分类,其流程可以分为五个环节,即文本数据获取、预处理、特征选择、建模、评估与运用。
文本数据的获取环节中,主要是根据需求从各种途径中获取文本类型数据,并构建语料库,其获取的途径有互联网、企业的数据环境、数字化文档等。获取文本数据后以一定的方式存储,例如服务器的文件系统、关系数据库、大数据平台等。预处理过程即对非结构化的数据进行有目的的转换,包括文本数据清洗、整合、分词、结构化等过程,换言之预处理过程需要进一步处理文本数据的噪音,并按照指定方式转换为适用于分析环境的结构化数据。一般初始的结构化文本数据的特征非常多,且数据极其稀疏。此时就涉及对文本数据进行特征选择。特征选择的目的在于有效减少数据的不重要维度,促进建模的效果,并减少模型的计算量。这与数据挖掘的特征选择是类似的。建模过程即使用特定的规则或数据挖掘模型进行文本挖掘。实践中,有一些数据挖掘方法能够较好地应用于文本分类,例如KNN、朴素贝叶斯等。当然,除了数据挖掘方法,基于规则进行分类也不失为一种运用广泛的文本分类方法。评估与运用环节中,对建立好的文本分类模型进行评估,若不能达到实际需求则需要返回初始环节并进行改进,达到要求后部署到生产环境中进行运用。
1.3 R语言与文本挖掘
本书主要实现工具为R语言,在之前的章节中已经进行过系统介绍。不过在文本挖掘的主题下,需要补充一些知识。
R语言本身非常适合结构化数据的统计分析、数据挖掘建模与数据可视化。对于文本数据这类非结构化数据,R是无法直接进行处理的。但是随着自然语言处理的火热,R的开发者也贡献了很多工具包,使得R能够处理文本数据。从数据获取、数据清洗、数据处理、数据挖掘与文本可视化的几个维度上说,可使用的工具包如表1所示。
表1
功能 库/包
数据获取 RCurl、XML等
数据清洗 base、stringr、reshape2、tidyr等
数据处理 tm、JiebaR、Rwordseg等
分析挖掘 base、nnet、arules、fpc、e1071、LDA等
可视化 wordcloud2、ggplot2、echarts
其中,RCurl、XML包主要用于实现爬虫与网页解析。base、stringr等包提供了很多有用的处理字符串数据的函数。tm包能够使用户在R中轻松地创建语料库并对文本数据进行结构化转换。JiebaR包支持在R中对数据进行高效、灵活地分词。而诸如arules、bnlearn等包提供了关联规则、朴素贝叶斯等在R中的实现,使得建模任务易于完成。wordcloud2包提供了强大、可定制的创建词云的方法,且对中文非常友好。总而言之,在R中可以方便地处理文本类型的分析任务。后续的章节中将具体介绍这些工具包的使用。
2 文本清洗
本节主要介绍文本清洗的概念及其在R中的实现,内容包括中文编码知识、正则表达式简介以及在R中的实现。本节内容偏重基础与实践,若能掌握则有利于在实际工作和学习中处理文本类型数据。
2.1 中文编码知识简介
如今文本数据基本上都存储在计算机系统中,而本书所探讨的文本分析主要是中文文本分析。在计算机上进行文本处理时,无论使用什么分析工具,都绕不开汉字编码的问题。下面叙述计算机汉字编码的基础知识。
在计算机中,汉字信息处理系统一般包括编码、输入、存储、编辑、输出和传输这几个部分。编码则是关键环节,汉字常见的编码形式有很多,比如GB2312、GBK、UTF8、CP936、Big5等。
GBK、GB2312和CP936是我国制定的一套汉字编码标准,每一个汉字占用两个字节,基本能够表示所有的汉字。UTF8编码又称为万国码,其设计初衷是为了统一拉丁语言与非拉丁语言,使所有语言能够通用,UTF8编码的优势在于其几乎包容了世界上所有语言,其更加通用,对于汉字来说,每个汉字占用3到4个字节,在互联网上,该编码为默认汉字编码,在很多数据库中也使用该编码作为汉字编码。Big5编码是流行于中国香港、台湾等地区的汉字繁体字编码标准,该编码主要针对繁体字。
综上,国标系列的编码只适合汉字,在一些更通用的场景中可能会产生问题,例如这种编码的数据在不同操作系统传输时容易出现乱码的情况。而UTF8编码则通用得多,也是Linux、Mac系统的默认编码,但在简体中文的Windows系统下,汉字默认编码为GB2312。若出现乱码,应首先考虑根据操作系统类型,将原始文本数据转换为与系统兼容的编码。在使用一些分析工具进行文本处理时也需要了解该工具所支持的编码是否与汉字文本的编码兼容。例如在R中的汉字字符的情况。
这里,a.txt是一个以UTF8形式编码的汉字文本文件,内容为字符“文本挖掘文本分析”,首先以GBK编码按行读取,结果出现乱码,如下所示。
>readLines('a.txt',encoding = 'GBK') [1] "锘挎枃鏈寲鎺樻枃鏈垎鏋\x90"
当以UTF8编码读取时,编码正常,如下所示。
>readLines('a.txt',encoding = 'UTF-8') [1] "文本挖掘文本分析"
上述例子说明,熟悉文本自身的编码是保障不产生乱码问题的前提。当然若不熟悉原文本编码,则可以尝试可用的汉字编码,直到不出现乱码问题为止。
2.2 正则表达式简介
正则表达式是一种匹配文本字符串的特定模式,其能够完成多种多样的字符串匹配任务,在进行文本处理时,正则表达式可以免去很多繁杂的代码。下面是一个简单的例子:
原文本中的信息为“T文本Mi挖掘1234”,此时希望匹配原文本中的英文字符。一般来说,这种问题需要首先找到英文字符在文本中的起始位置与终止位置,再根据位置提取出来。显然这种方法非常麻烦。而使用正则表达式时,解决问题就简单了很多,这里需要使用stringr包中的str_extract_all函数对字符对象eg_extract进行抽取,而抽取规则为“[A-z]”,这一规则在正则表达式中的含义为所有大小写英文字母。最终将字符中的英文提取出来。
>library(stringr) >eg_extract<-' T文本Mi挖掘1234 ' >str_extract_all(eg_extract,'[A-z]') [[1]] [1] "T" "M" "i"
除了这种简单的匹配,正则表达式也可以匹配更加复杂的形式,在如下向量中,第二个元素不是电子邮箱格式,希望匹配到第一个和第三个邮箱。
>eg<-c('asd@sina.com','asd@pinggu','asd@163.cn')
显然,邮箱以“@”符号分割,前后都可以为数值或英文,再接点号,后面会跟上cn或com,正则表达式如下:
>pattern<-'[A-z0-9]+@[A-z0-9]+\\.(com|cn)' >gsub(pattern,'*',eg) [1] "*" "asd@pinggu" "*"
该正则表达式含义为:大小写字母或数字(一个或多个)@大小写字符与数字(一个或多个).com或cn。虽然形式有些复杂,但可以精准匹配到希望匹配的模式。gsub函数将匹配到模式替换为“*”。
显然,正则表达式在进行文本数据的处理时会非常方便,常用的正则表达式如表2所示。
表2
模式 匹配 模式 匹配
\\d 数字 i+ i重复至少一次
\\D 非数字 i* i重复0或多次
\\s 空格 i? i重复0或一次
\\S 非空格 i{n} i重复n次
\\w 字符 i{n1,n2} i重复n次
续表
模 式 匹 配 模 式 匹 配
\\W 非字符 i{n1,n2}? i重复n1到n2次
\\t 制表符 i{n,} i重复大于等于n次
\\n 新的行 [:alnum:] 字母与数字
^ 字符串起始 [:alpha:] 大小写字母
$ 字符串末尾 [:cntrl:] 控制字符
\ 特殊字符的转义符 [:digit:] 数字
| 轮流匹配 [:graph:] 图像字符
• 任意字符 [:lower:] 小写字母
[ab] a或b [:upper:] 大写字母
[^ab] 非ab字符 [:print:] 可打印字符
[0-9] 0-9 [:punct:] 标点符号
[A-Z] 大写字母 [:space:] 空格字符
[a-z] 小写字母 [:xdigit:] 十六进制字符
[A-z] 大小写字母 [\u4E00-\u9FA5] 汉字
下面列出一些在文本处理时常用的正则表达式例子。
1.匹配数字
一般可以使用“[0-9]”或“\\d”匹配数字。例如匹配字符串“1.文本挖掘(Test Mining),可以发掘海量文本中有价值的信息”中的数字。
> eg='1.文本挖掘(Test Mining),可以发掘海量文本中有价值的信息' > gsub('[0-9]','*',eg) [1] "*.文本挖掘(Test Mining),可以发掘海量文本中有价值的信息"
2.匹配英文
一般可以使用“[A-z]”匹配大小写英文,“[A-Z]”匹配大写英文,“[a-z]”匹配小写英文。例如匹配字符串“1.文本挖掘(Test Mining),可以发掘海量文本中有价值的信息”中的大小写英文。
>gsub('[A-z]','*',eg) [1] "1.文本挖掘(**** ******),可以发掘海量文本中有价值的信息"
3.匹配汉字
汉字可以使用“[\u4E00-\u9FA5]”这种机械匹配的方式,例如匹配“1.文本挖掘(Test Mining),可以发掘海量文本中有价值的信息”中的汉字。
> gsub('[\u4E00-\u9FA5]','*',eg) [1] "1.****(Test Mining),***************"
4.多条件匹配
可以直接将正则表达式叠加起来进行多条件匹配,例如匹配“1.文本挖掘(Test Mining),可以发掘海量文本中有价值的信息”中的汉字、英文和数字。
> gsub('[A-z0-9\u4E00-\u9FA5]','*',eg) [1] "*.****(***********),***************"
当然也可以使用轮流匹配,使用|代表或条件。
>gsub('([A-z]|[0-9]|[\u4E00-\u9FA5])','*',eg) [1] "*.****(***********),***************"
5.“非”的使用
“非”可以在正则表达式中灵活使用,其一般以“^”符号表示,例如匹配“1.文本挖掘(Test Mining),可以发掘海量文本中有价值的信息”中的汉字“非”。
>gsub('[^\u4E00-\u9FA5]','*',eg) [1] "**文本挖掘**************可以发掘海量文本中有价值的信息"
3 中文分词与文档模型
中文分词、文档模型作为文本处理的基础,是进行文本分析的必要前提。本节主要介绍中文分词技术及分词包JiebaR的使用、文档模型介绍及在R中使用tm包对文本数据进行结构化转换。
3.1 中文分词
在文本分析中,一篇文档是由很多词组成,词作为表述语义的基本元素,能够充分地表达文档的特征与含义(见图1),例如体育新闻文本中出现“篮球”这个词是非常正常的,但政治类新闻中不太可能出现“篮球”这一词。
图1
所谓分词,即将文档中的句子按照词进行切分。分词是很多中文自然语言处理的基础,分词有助于提取文档的特征,也对分类模型的效果有很大影响。对于英文类拉丁语系文本,其具有天然的分隔符,分词并不是太大的问题,但对于中文文本来说,分词则变得困难了很多。不过经过学者多年的研究,这一难题可以通过一些方法得以解决,例如基于字典的机械匹配方法,基于语法的分词方法,基于统计的分词方法,基于规则的分词方法。这里并不深入探讨这些方法,下面主要介绍一款优秀的分词工具包JiebaR。
Jieba是基于Python的一款优秀的中文分词工具,后来被开发者改写成多种语言,其R版本为JiebaR。JiebaR支持用户自定义分词词典、自定义停用词词典、支持多种模式分词,且能够进行词性标注与关键词抽取。可以说JiebaR功能强大,使用灵活,能够应对各种实际问题。JiebaR的帮助文档可以参考链接: http://qinwenfeng.com/jiebaR/。
1.JiebaR工具包的使用
首先需要在CRAN上安装JiebaR,如下所示。
>install.packages("jiebaR") >library("jiebaR")
在JiebaR中进行分词前,需要通过函数worker设置分词引擎的参数初始参数,例如:
>worker(type = "mix",user = USERPATH, stop_word = STOPPATH, topn = 5,encoding = "UTF-8", user_weight = "max")
这里列出一些实用的参数,如表3所示:
表3
月号 参数 解释
1 type 切换分词方法和功能,默认mix表示进行混合方法分词,tag表示进行词性标注,keywords表示进行关键词抽取
2 user 设定用户自定义词典路径,默认无
3 stop_word 设定停用词词典路径,默认无
4 topn 在关键词提取中有用,默认输出文档中前5个最有特征的词
5 encoding 默认输入数据编码为UTF8
6 user_weight 用户自定义字典权重设定,当使用自定义词典时,默认权重为最高
2.分词示例
若只需要分词,那么可以保持所有参数为默认状态。若希望标注词性,则需要通过type参数指定tag进行词性标注;若希望提取关键词,type参数则需要设定为keywords。
下面是对字符串“宋仲基真是太帅了”进行分词的结果。
>library(jiebaR)
>engine=worker()
>engine<="宋仲基真是太帅了"
对于一条字符串向量,JiebaR会将向量中的字符串进行分词再输出,例如:
>egvector=c('天气真好','我好像没带钱包') >engine<=egvector [1] "天气" "真好" "我" "好像" "没带" "钱包"
3.词性标注
词性即汉语语法中各个词汇的词性,比如动词、名称、形容词等。jiebaR可以对分词后的结果进行词性标注,这里列出中文词性标识以及含义,如表4所示:
表4
代码 名称 解释
Ag 形语素 形容词性语素。形容词代码为a,语素代码g前面置以A
a 形容词 取英语形容词adjective的第1个字母
ad 副形词 直接作状语的形容词。形容词代码a和副词代码d并在一起
an 名形词 具有名词功能的形容词。形容词代码a和名词代码n并在一起
b 区别词 取汉字“别”的声母的第1个字母
c 连词 取英语连词conjunction的第1个字母
Dg 副语素 副词性语素。副词代码为d,语素代码g前面置以D
d 副词 取adverb的第2个字母,因其第1个字母已用于形容词
e 叹词 取英语叹词exclamation的第1个字母
f 方位词 取汉字“方”的声母的第1个字母
g 语素 绝大多数语素都能作为合成词的“词根”,取汉字“根”的声母
h 前接成分 取英语head的第1个字母
i 成语 取英语成语idiom的第1个字母
j 简称略语 取汉字“简”的声母的第1个字母
l 习用语 习用语尚未成为成语,有点“临时性”,取“临”的声母
m 数词 取英语numeral的第3个字母,n,u已有他用
Ng 名语素 名词性语素。名词代码为n,语素代码g前面置以N
n 名词 取英语名词noun的第1个字母
nr 人名 名词代码n和“人(ren)”的声母并在一起
ns 地名 名词代码n和处所词代码s并在一起
nt 机构团体 “团”的声母为t,名词代码n和t并在一起
nz 专有名词 “专”的声母的第1个字母为z,名词代码n和z并在一起
o 拟声词 取英语拟声词onomatopoeia的第1个字母
p 介词 取英语介词prepositional的第1个字母
q 量词 取英语quantity的第1个字母
续表
代码 名称 解 释
r 代词 取英语代词pronoun的第2个字母,因p已用于介词
s 处所词 取英语space的第1个字母
Tg 时语素 时间词性语素。时间词代码为t,在语素的代码g前面置以T
t 时间词 取英语time的第1个字母
u 助词 取英语助词auxiliary 的第2个字母,因a已用于形容词
Vg 动语素 动词性语素。动词代码为v。在语素的代码g前面置以V
v 动词 取英语动词verb的第一个字母
vd 副动词 直接作状语的动词。动词和副词的代码并在一起
vn 动名词 指具有名词功能的动词。动词和名词的代码并在一起
x 非语素字 非语素字只是一个符号,字母x通常用于代表未知数、符号
y 语气词 取汉字“语”的声母的第1个字母
z 状态词 取汉字“状”的声母的第1个字母
JiebaR在标注词性时,首先会对字符串进行分词处理,再根据词的词性进行标注,如下所示。
> cutter=worker(type = "tag") >cutter_words<-cutter<="我爱北京天安门" >cutter_words r v ns ns "我" "爱" "北京" "天安门"
“我”为代词、“爱”为动词、“北京”和“天安门”为专有名词。
实际进行文本分析时,可以利用词性标注的结果进行停用词过滤,例如过滤助词、连词、介词、语气词、副词等,这类词一般可以在任意文档中出现,所以需要进行剔除。
4.停用词处理
文本中没有实际含义或需要排除的词,汉语虚词,包含助词、连词、介词、语气词、副词等,例如,“的”“哦”“哎呦”“而已”“往往”一般被视为停用词。此外在特定场合下的一些词也可以被视为停用词,例如关于大学官方文稿的分类,“北京大学”“清华大学”等词可以依据需要排除,因为其特征不明显。
在JiebaR中,可以通过词性标注去除停用词,也可以通过指定停用词词典去除停用词,示例如下。
文本“这几天生活与工作貌似都不太顺利”中,希望过滤掉代词与介词,那么具体实现如下。
>stop=c('r','p')
> cutter=worker(type = "tag")
>cutter_words<-cutter<="这几天生活与工作貌似都不太顺利"
>cutter_words[!(names(cutter_words) %in%stop)]
[1]"几天" "生活" "工作" "貌似" "都" "不" "太" "顺利"
这里过滤掉了代词“我”和介词“与”。当然可以在stop中指定信用更多的词性,或者指定需要保留的词性。
此外,也可以在worker参数中设定停用词词典路径完成停用词过滤。停用词词典需要为一个txt文本文件指定停用词,以“这几天生活与工作貌似都不太顺利”为例,假设停用词词典txt文本文件如下:
这
与
貌似
都
不##
太
在worker中设定停用词词典效果如下:
> cutter=worker(stop_word='stop.txt')
>cutter_words<-cutter<="这几天生活与工作貌似都不太顺利"
>cutter_words
[1]"几天" "生活" "工作" "顺利"
停用词典的设定非常灵活,读者可以自由设定停用词内容,也可以依据需求在网上下载已经做好的一些停用词词典。
5.自定义分词词典
JiebaR的默认分词词典主要来源于新闻文本,在一些特定情况下,默认词典中无法兼顾到领域词、生僻词或较新的流行词,此时分词的偏差就可以灵活使用自定义词典来弥补。例如文本“段玉波和李红军在红岭大厦玩杀人游戏呢”中,包含了专有的人名(李红军)、地名(红岭大厦)、和领域词(杀人游戏)。此时默认词典的分词效果不佳。
>system_dict=worker(type = "mix") >system_dict<="段玉波和李红军在红岭大厦玩杀人游戏呢" [1] "段玉波" "和" "李" "红军" "在" "红岭" "大厦" "玩" "杀人" "游戏" "呢"
显然,人名、地名、领域词都没有分好。此时可以加入自定义分词词典。JiebaR的自定义分词词典格式包含词、词频、词性,示例如下:
云计算 12 n 韩玉鉴赏 23 nz 蓝翔 12 nz
以“云计算”为例,12表示“云计算”的词频,该参数值的大小关系到该词被分词的概率,简单来说,词频数值越大被分词的可能性越高。最后的n表示词性,用语词性标注。在设定自定义词典时,可以忽略后两个参数设定,接下来设定自定义分词词典user.txt文本文件:
段玉波
李红军
红岭大厦
杀人游戏
下面在worker参数中设定自定义词典:
>user_dict=worker(type ="mix",user="user.txt")
>user_dict<="段玉波和李红军在红岭大厦玩杀人游戏呢"
[1] "段玉波" "和" "李红军" "在" "红岭大厦" "玩" "杀人游戏" "呢"
经过自定义词典的修正,分词效果变好了。在实际中,文本内容一旦涉及某些专业领域时,可以考虑创建自定义词典,避免分词的偏差。在构建时,一种方法是人为添加,另一种方法是寻找领域辞典。对于后者来说,一些输入法提供了领域词典是可以参考的,例如搜狗细胞词库等。
本节介绍了JiebaR包的基本功能,至于如何使用这些功能会在最后的案例中体现。
4 文本的特征选择及相关性度量
经过分词后,文本数据仍旧是一堆文档与词的集合。所以需要通过一些方法将词语与相应文档的关系表示出来,这就是文档相关性度量。学者们提出了一些文档建模的方法度量文档相关性,比如概率模型、布尔模型及向量空间模型,这里主要介绍向量空间模型(VSM)。模型最终会生成一个词文档矩阵。
词文档矩阵中,词作为文档的特征对文档的分类起决定性作用,文本挖掘中一般特征的个数非常多,若能找到对分类有较大贡献的特征,则会有效提升模型的效果并减少计算量。本节主要涉及一些常用的文本特征选择方法。
4.1 词频-逆向文档频率(TF-IDF)
1.定义
在TF-IDF中,TF代表词频,计算方式为词 在文档 中的出现的频数除以所有词在文档 出现的频数和,如下所示:
这里 表示计频数。IDF则表示逆向文档频率,计算方式为文档总数 除以包含词 的文档个数加1的对数,如下所示:
这里,词频代表了词语出现的频繁程度,逆向文档频率代表了词语的普遍程度。TFIDF的思想在于若某词在某篇文档中出现次数较多而在其他文档中次数较少(不普遍),那么这个词就是特征词,所以对于文档 中的词 ,其TFIDF值定义如下:
TFIDF值如图2所示:
图2
TFIDF值越大则说明词越重要。
2.在R中计算TFIDF值
在R中,介绍两种计算TFIDF值的方式。
JiebaR分词包可以支持计算TFIDF,代码示例如下:
>library(jiebaR)
>cutter <- worker(type ="keywords",topn=n)
>cutter <= str_object
Topn参数指定返回TFIDF值最高的前n个特征词。
这里也可以用tm工具包实现词的特征度量,tm工具包是一款针对文本挖掘的实现框架,其功能非常完善,包括语料库处理、文本预处理、元数据管理、文档模型处理。可在Cran上下载安装。(关于tm包如何根据TF-IDF权重产生词文档矩阵的内容将在4.2节中讲解。)
>install.package('tm')
4.2 文档相关性度量
这里我们先介绍向量空间模型,再近一步根据这个模型生成词文档矩阵。
1.向量空间模型
首先看一个简单的例子,语料库中有两篇文档,文档A为“机器人越来越智能”,文档B为“工业现代化需要普及智能机器人”,经过分词后,文档A为(机器人、越来越、智能),文档B为(工业、现代化、需要、普及、智能、机器人)。对于向量空间模型,首先都会将文档A与文档B的词进行集合化为(机器人、越来越、智能、工业、现代化、需要、普及),其次将文档A与文档B按照集合中的词使用相应的词权重替代,这个权重可以是词频,即每一个词在每一篇文档中出现了多少次;也可以是TF-IDF权重。(图3中使用词频作为权重)
图3
2.词文档矩阵
如此,每一篇文档与其中的词就被格式化为向量的形式,就可以组成词文档矩阵(DocumentTermMatrix)。在词文档矩阵中,列代表语料库中多篇文档的集合出现词语的集合,行代表语料库中的文档,如图4所示。
图4
对于单元格内的观测值,一般可以为:
(1)词频:由向量空间模型产生,代表某词在某文档出现的次数,整数形式。
(2)词频-逆向文档频率(TF-IDF):根据文档、词构建的TFIDF值,数值形式。
词文档矩阵将文本数据转化为二维表形式的数据,使分析更加直观简单。与典型结构化二维表数据相比,二维表的观测变为语料库的文档,变量变成词。不过与典型二维表相比,当语料库中文档很多,词很多时,词文档矩阵会存在大量的0值,换言之文档矩阵非常稀疏。例如如下的词文档矩阵(见图5)。
矩阵中词(特征)非常多,但有大量的0存在,原因在于有大量的词只在少数文档中出现,在多数文档不出现。此时的矩阵是稀疏的(0值的比例非常高)。在R中,相应工具包会将词文档矩阵数据进行存储与运算的优化,这一点和一般结构化数据有所不同。
图5
4.相关性度量
文本相关性度量可以被认为是度量文本之间的距离,而文本分析中常见的三大距离有:欧氏距离、余弦距离以及杰卡德相似系数。
(1)欧氏距离:源自欧氏空间中两点间的距离公式,就是对应维度下差值的平方和开根号。
(2)余弦距离:是比较常用的一种相关性度量方式,计算方法: ,分子点乘,这是计算两向量夹角的公式。
(3)杰卡德相似系数:两个集合A和B的交集元素在A和B的并集所占的比例,用符号J(A,B)表示。
有了上面的词文档矩阵,我们就可以通过上述度量方法计算两两文档之间的相关性。下一节会介绍其在R中的实现过程。
4.3 在R中实现特征选择和相关性度量
1.创建语料库
tm包中的文档管理结构为Corpus(语料库),是一堆文档的集合。语料库分为VCorpus和PCorpus。VCorpus为临时语料库,创建后将以R对象形式存储在工作空间中,当删除该对象后语料库立即被删除;PCorpus为永久语料库,创建后将存储在本地中,如数据库中。tm包为语料库提供了三种形式的源:DirSource表示目录源,VectorSource表示向量源。DataframeSource表示数据框源。
这里以VectorSource较为常用,例如这里生成两篇文档的临时语料库:
>library(tm)
>A='三星中国公司宣布召回在中国大陆地区销售的全部Note7手机'
>B='三星宣布全球停售Note7,全部销毁处理三星Note7的起火召回事件已经告一段落。’
先使用JiebaR完成分词,再将分词后的文档对象加入向量源中,生成语料库words_corpus,words_corpus是一个语料库类。
>library(jiebaR)
>engine<-worker()
>vector=list(engine<=A,engine<=B)
>words_corpus=VCorpus(VectorSource(vector))
>words_corpus
<<VCorpus>>
Metadata: corpus specific: 0, document level (indexed): 0
Content: documents: 2
接下来可以使用lapply查看语料库中的文档情况:
>lapply(words_corpus[1:2],as.character)
$`1`
[1] "三星" "中国" "公司" "宣布" "召回" "在" "中国" "大陆"
[9] "地区" "销售" "的" "全部" "Note7" "手机"
$`2`
[1] "三星" "宣布" "全球" "停售" "Note7" "全部"
[7] "销毁" "处理" "三星" "Note7" "的" "起火"
[13] "召回" "事件" "已经" "告一段落"
2.生成词文档矩阵
使用tm包的DocumentTermMatrix函数,可以将语料库对象转化为词文档矩阵对象。该函数的control参数中可以进行一些进阶设定。
>control<-list(stopwords=F,removePunctuation=F,removeNumbers=F,
wordLengths=c(3, Inf),...)
这四个参数解释如表5所示:
表5
参数 解释
1 removePunctuation 去除标点,默认FASLE
2 removeNumbers 去除数字,默认FASLE
3 stop_word 停用词向量,默认FALSE
4 wordLengths 词长度上下限向量,默认为3到正无穷
这四个参数都可以按照需要控制词文档矩阵的维度个数,例如对于之前words_corpus语料库中的两篇文档,转化为词文档矩阵如下:
> jieba_tokenizer=function(d){unlist(segment(d[[1]],engine))}
>control<-list(stopwords='国土资源部',
removePunctuation=F,
removeNumbers=F,
wordLengths=c(3, Inf),tokenize=jieba_tokenizer)
>inspect(DocumentTermMatrix(words_corpus,control=control))
<<DocumentTermMatrix (documents: 2, terms:2)>>
Non-/sparse entries: 3/1
Sparsity : 25%
Maximal term length: 5
Weighting : term frequency (tf)
Sample :
Terms
Docs note7 告一段落
1 1 0
2 2 1
其中第一行代码是由于3.1.1版本到成书时的3.3.2版本时,tm包在Windows环境下会产生Bug,Bug源自Scan函数。解决办法就是如上先定义一个jieba_tokenizer函数,并且赋值到control参数的tokenize参数中。
这里设定了停用词stopwords为“国土资源部”,矩阵的词长度为3到正无穷。那么使用inspect命令后查看词文档矩阵对象后,长度为1和2的词和停用词被过滤。矩阵一共2列两行。默认生成词频-文档矩阵。
tm包生产TFIDF权重的实现方法如下:
>control<-list(stopwords='国土资源部',removePunctuation=F,removeNumbers=F,
wordLengths=c(3, Inf),tokenize=jieba_tokenizer,
weighting = function(x)weightTfIdf(x))
>inspect(DocumentTermMatrix(words_corpus,control=control))
<<DocumentTermMatrix (documents: 2, terms:2)>>
Non-/sparse entries: 1/3
Sparsity : 75%
Maximal term length: 5
Weighting : term frequency - inverse document frequency (normalized) (tf-idf)
Sample :
Terms
Docs note7 告一段落
1 0 0.0000000
2 00.3333333
注意:JiebaR包在计算TFIDF时使用的是内置语料库的IDF,而tm包则是根据临时语料库的IDF计算,前者更为一般,后者更为精确。在实际中可以依据需要选择合适的计算方法。
3.相关性度量
由于新版本的tm包中没有余弦距离函数,而自带的findAssocs函数只能查找欧氏距离,而且需要指定变量和距离的阈值,不太方便查看所有变量的相似度矩阵,故这里我们自定义余弦相似度矩阵函数,用来度量每个变量之间的余弦距离,函数输入词文档函数生成的矩阵,返回余弦距离函数生成的矩阵,如下所示。
> ##自定义余弦距离矩阵函数
> cosmatrix=function(data){
name=colnames(data);temp=NULL
for (jin name){
x=data[,j];cosin=NULL
for (i in name){
y=data[,i]
cosin[i]=(x%*%y)/(sqrt(x%*%x)*sqrt(y%*%y))
}
temp=cbind(temp,cosin);
}
colnames(temp)=name
return(temp)
}
>tdm<-TermDocumentMatrix(words_corpus,control=list(wordLengths=c(1,Inf),
tokenize=jieba_tokenizer))
>tdm_m<-as.matrix(tdm)
>cosmatrix(tdm_m)
1 2
1 1.00000000.4472136
2 0.44721361.0000000
5 文本分类
文本分类是将文本分成预先定义的类别。很多地方都需要用到文本分类技术,例如:垃圾电子邮件检测、文本归类、评论的情感分析和广告投放等领域。文本分类通常有两种方法:一种是采用规则的方法,利用词频、正则等实现规则分类系统;另一种是采用机器学习的方法,通过在已经标注好的语料上训练好分类器,对以后的测试集文本进行分类。本节将用一个例子来演示如何使用这两种方法进行分类。
5.1 基于规则分类
基于规则的方法通常是通过观察数据,自己定义一些规则,来对文本进行分类。这些规则可以是词频的统计,例如:出现汽车的次数大于2次,也可以使用正则表达式进行精确的匹配然后进行分类。例如:在这个分类问题中,我们定义了下面几条规则进行分类。
规则1:title中包含:“汽车”或者“轿车”,则该文档分类为汽车。
规则2:正文中同时包含:“轿车”“变速箱”“保险”这三个词,则该文档分类为汽车。
5.2 基于分类器进行分类
基于分类器的方法,通过在已经标注好的语料上训练好分类器,对以后的测试集文本进行分类。常用的分类器有朴素贝叶斯、Logistic Regression、SVM等。在下面的例子中,我们将使用SVM对这个问题进行分类。解决一般分类问题的实现步骤如图6所示。
图6
分类器实现的步骤如下。
1.产生训练集
这里分为以下若干种情况。
(1)已标注好的分类,常见于新闻等文本。
(2)人工标注分类,如情感分析等文本需要人工标注,常见于评论分析,工作量大。
(3)不标注,用于无监督方法,例如文本聚类
2.构造词文档矩阵
如前所述,这里可以构建以下两种矩阵。
(1)词频/文档矩阵。
(2)词频-逆向文档矩阵。
3.使用分类器,
(1)有监督:SVM/决策树(随机森林)/Logistic/KNN/朴素贝叶斯等。
(2)无监督:聚类。
关于分类器在R中的实现将会在下面的综合案例中详细说明。
6 主题模型
目前常用的主题模型主要是LDA。LDA通常指的是概率主题模型,即隐含狄里克雷分布(LatentDirichlet Allocation,简称LDA)。LDA是由Blei、David M、Ng Andrew Y、Jordan等于2003年提出的主题模型,用输入输出的方式来理解就是输入一个文档集,即若干个文档,输出每篇文档中的主题,这些主题以概率分布的形式给出。得到每篇文档的主题以后,就可以根据这些主题分布进行主题聚类或者文本分类。
同时,LDA是一种典型的词袋模型,即一篇文档是由一组词组成,这些词被无序地袋装在这篇文档中,彼此之间没有先后顺序关系。
6.1 PLSA模型
之所以先介绍PLSA模型,是因为PLSA(Probabilistic Latent SemanticAnalysis)模型加上一个两维的贝叶斯框架,就是LDA,且PLSA更容易理解。PLSA是一种词袋方法,该模型假设每一个词都关联着一个隐含的主题类别。为了方便描述,这里先定义一些基本变量。
基本假设为,给定一个文档集D,对一个文档集而言,存在一个主题集T,每个文档对应的主题上的分布 ,每个主题对应到词典V上的分布 ,基本概念如下所示。
D={d}已知,文档集。一般比较多,经常是上百万的量级。
V={v}已知,词典,V词典中的词。几万的数量级。
已知,文档d当中第l个词,文档中的某个词可以出现很多次。
未知,文档d中第l个词所属的主题。每个词都要求主题标记,几万的数量级,一般比文档数小一到两个数量级。
T={t}为假设主题集(隐藏的,不知道的),文档全体讨论的主题集。一般不会太多,几个到几百个。
未知,每一个主题反映在词上的分布。第t个主题某个词典的词的概率会不同。需要满足 。主题用词典上一组词的分布来反映。反映每个主题对词汇的选择倾向。每个文档可能关心某几个主题,根据某个主题选择不同的词,例如数学出现概率几何等,所以不同的主题反映在出现的词汇以及词汇频率的不同上。大小是T´V。
未知,每篇文档对应到主题t上的一个分布,每篇文档选择若干个主题,对这若干个主题进行阐述。大小是D´T。
文档生成的过程可以理解为先学习训练给定主题的词语,然后以一定的概率选取某个给定的主题,再以一定的概率选取那个主题下的某个单词。不断地重复这两个通过概率选取的步骤,就可以生成一篇文档。当然在实际操作中,会去除一些副词、介词等排除干扰,生成文档时需要加入这些词使生成的文档看起来更像一篇文章。
和 均服从多项分布,多项分布指的是单次试验中随机变量可取的值有多个离散值( )。可以类比二项分布来理解,即二项分布是可取值只有两个值的特殊的多项分布。多项分布的概率密度函数为:
6.2 PLSA算法
我们知道了文档产生过程,而现实的情况是我们拥有的是文档集,需要探索的是文档中的主题,而主题是一个隐藏的分布。主题建模的目的就在于:自动地发现文档集中的主题分布。
我们得到的是文档集以及对应的词,PLSA属于频率派思想,即样本随机,参数虽未知但固定(作为区分:LDA属于贝叶斯派思想,即样本固定,参数未知但是不固定,服从一定的分布)。
这里对于任意文档来说,基于文档的词的分布 是已知的,对于海量文档,计算 的方法是通过分词、降噪(去除停用词)后得到一个词语的集合,对于每个词语,在文档中出现的次数除以词语的总数就是这个概率 。同理文档集中第 个文档被选中的概率 也可以事先求出。
未知的概率有:给定文档d后,某个主题 出现的概率 ;在给定主题 后,某个词 出现的概率 ,词与主题关系越密切这个概率就越大。
通过已知的信息,训练未知信息的公式如下:
文档d中每个词的生成概率为:
其中 就是我们要估计的参数值,目标就是最大化这个 ,由于待估计的参数中含有隐变量 ,故可以使用EM算法。这里我们省略EM算法的求解过程,直接给出结果。
6.3 LDA主题模型
一篇文档可以包含多个主题,一篇文档是由词语按一定顺序组成的,而每个词都会分配到一个主题(这里忽略了词序)。
LDA要做的其实就是根据给定的一个文档集,推断每篇文档的主题分布(主题在一篇文档中出现的概率)。
这里我们先介绍一些数学概念来详细说明LDA的文档生成方式,之后会对这些概念做详细讲解。在LDA模型中,一篇文档的生成方式如下。
1.从参数为 的狄里克雷分布中取样生成文档d的主题分布 。
2.从主题的多项式分布 中取样生成文档d第l个词的主题 。
3.从参数为 的狄里克雷分布中取样生成主题t对应的词的分布 。
4.从词语的多项式分布 中采样,在每个主题 下最终生成对应的词语 。
其中提到的狄里克雷分布(Dirichlet分布)是多项式分布的共轭先验概率分布,类似的Beta分布是二项式分布的共轭先验概率分布。Dirichlet共轭的推导可以通过Beta分布类比。对于从事数据挖掘的人而言,理解参数对Dirichlet分布的影响是很重要的。图7是三项分布时,参数对分布的影响。当参数 较小时,每篇文档中主题的分布比较极端,基本上是在关注于某一个主题,其他两个只是提及一下,网购客户的评论、时事新闻一般具有这类特征。当参数 较大时,每篇文档中主题的分布比较均匀,小说、散文一般具有这类特征。由于参数是先验的,简单来说就是由做文本分析的人自己设定的。因此需要我们先抽样读一些文档,根据主题分布情况,自己设定一个参数值。
a=0.1
a=1
a=10
图7
图8是目前比较流行的LDA的图模型结构,方框中的右下角标表示循环次数。
图8
也可以表示为下面的形式,从D篇文章到每个词。这个模型的局限在于把词序扔掉了。
这里包含四个变量: , , , 。其中: 相互独立, 相互独立。
联合概率:
其中,可定义: 文档d中分到主题t中的词的个数,一个文档
文档集中词v分到主题t中的个数,整个文档集中
总的来说就是LDA在PLSA的基础上增加了假设: 即两个分布均服从狄里克雷分布。 是狄里克雷分布随机变量的一个样本,同时也是一个多项分布的参数,狄里克雷分布本身有参数。这里狄里克雷分布,是因为取值正好是Z和W的多项分布的参数。
6.4 求解联合分布
LDA就是之前联合分布中将 的分布用狄里克雷分布代入。定下来分布意味着文档之间变得有关联了,被同一个 或 控制。得到的LDA模型如下。
把 通过积分消除,得到
上面转换后的式子即是: ,等号右边分别对应上面两个累乘。有了关于主题和文档的边际分布以后,就可以近一步用过Gibbs采样求解相应的参数。在求解之前先介绍两个基本的概念。
1.马氏链
马氏链的数学定义: ,也就是说状态转移的概率只依赖于前一个状态。从初始概率 出发,在马氏链上做状态转移,记 的概率为 ,则有 。假设到了第n步的时候马氏链收敛,即 都是同分布的随机变量(彼此间不独立),如果我们从一个具体的初始状态 开始,沿着马氏链按照概率转移矩阵做跳转,那么我们得到一个转移序列 ,在n后 都将是平稳分布 的样本。
2.蒙特卡罗方法
当所求解问题是某种随机事件出现的概率,或者是某个随机变量的期望值时,通过某种“实验”的方法,以这种事件出现的频率估计这一随机事件的概率,或者得到这个随机变量的某些数字特征,并将其作为问题的解。
例如求随机变量函数的期望,分两步。
(1)产生服从 分布的k个n维样本: 。
依据大数定理: 。(独立同分布随机变量,n个独立变量的均值就趋向于分布均值)。K越大精度越高,精度依据中心极限定理,大体上是 。
至此,我们就可以用MCMC方法/Gibbs采样方法来求解,Gibbs采样的具体做法是:变量一共有n个 ,随机给一个初始化,然后一个一个改,即改 时,其他的固定不动,然后根据全条件概率来产生一个随机数修改 值,修改完以后往后走,直到n为止,不停地循环。
for k=1to K ##K轮
for i =1 to n
根据 产生一个新的
output
理论上说所有产生的 是一个马尔科夫链 。马尔科夫链的平稳分布就是P(x),即n很大的时候就是平稳分布,所以前面的k(例如1000)都要扔掉,k轮后可以接着转若干次,到了平稳分布后一直都是平稳分布。多转几次是因为刚开始平稳的时候还不独立,拉开一些就会独立。综上,所以要产生任意一个n维分布的随机变量样本,方法就是用Gibbs采样转,前面若干次(例如1000)扔掉,取一个样,再转少一点,比如100次,再拿一个,再转100次再拿一个。实现随机样本还有其他方法,最简单得用的最多的是Gibbs方法(这里Gibbs采样用的是最常见的Metropolis-Hastings算法,有兴趣的读者可以阅读靳志辉先生写的《LDA数学八卦》)。
之前所述的 就可以通过进一步简化得到:
其中 ,即当前d、l的计数z、w拿掉,其他都不变,跟d、l、t、v有关的数减1,然后产生一个样本,产生一个新的t后再加进去。整个过程就是:
6.5 建模过程及参数选择
所有文档每个词组成 (对应V),每个词对应一个主题 (对应T)。
输入的数据:整个文档 ,隐含的多少篇文档D,每篇文档长度 ,词典V={v}。
输入的参数:主题T, , 。这里参数的选择正规的方法是最大似然,但是在试验中一般取 , , 。对于不用的文档对象, 和 值也要做出适当的调整。
初始化: ,随机生成,从1到T的均匀分布中随机取个数。数量级很大,对每个词都要初始化。计算M,N。
开始Gibbs采样循环:
for k=1to K
for d=1 to D##对每一篇文档
for l=1 to ##对文档里每个词
计算
修改:
生成: ##s随机生成
修改:
修改:M,N
一般来说先跑K=1000次,跑完以后输出Z。得到后就可以估计出 , 。
加号后面的项是一个平滑,如果没有则相当于直接用频率代替概率。 、 一般取得很小(小于1)。
7 综合案例
本节通过一个案例来详细讲解文本挖掘的全过程,
数据文件来自搜狗新闻,在十个类别下各包含十个TXT文件,每个文件一个新闻。每个类别一个文件夹,这十个类别分别是:汽车、财经、IT、健康、体育、旅游、教育、招聘、文化、军事。
本案例文本挖掘的过程包括数据的读取(这里忽略了取得数据的过程)、预处理、根据人工标记训练分类模型及预测,通过LDA主题模型查看主题分布和词分布。
7.1 读取文件及预处理
1.读取文件
由于包含十个文件夹,每个文件夹下有十个文件,故去读的时候要先将文件名全部读入到一个变量内,再按照文件名将所有文件读到一个列表向量中。
##列出所有文件夹
>files_dir=list.files("./Sample-Sougou",full.names=T)
##根据情况删去最后的说明文档
> filedirs=files_dir[1:10]
##遍历子文件
> readsubfiles=function(x){list.files(filedirs[x],full.names=T)}
> fullfiles=lapply(1:length(filedirs),readsubfiles)
##最终全部的TXT文件路径
> fullfiles_final=unlist(fullfiles)
##查看前六个
> head(fullfiles_final)
[1] "./Sample-Sougou/C000007/10.txt""./Sample-Sougou/C000007/11.txt"
[3] "./Sample-Sougou/C000007/12.txt""./Sample-Sougou/C000007/13.txt"
[5] "./Sample-Sougou/C000007/14.txt""./Sample-Sougou/C000007/15.txt"
##读取所有文件
> readdata=function(y){readLines(fullfiles_final[y],encoding="ANSI")}
>datafinal=lapply(1:length(fullfiles_final),readdata)
#随机查看任意一篇文档第一段前25个字符
> substr(unlist(datafinal[runif(1,1,100)])[1],1,25)
[1] "全国治理医药购销领域商业贿赂专项工作正在轰轰烈"
2.初步清理及JiebaR分词
先去掉噪音,主要是nbsp、数字和字母,然后对列表中每个向量做分词。
##去除数字及nbsp
> datafinal1=gsub("[0-9 nbsp a-zA-Z]","",datafinal)
##利用jiebaR包分词
> engine=worker()
> wordsall=list()
##分词
> for(i in 1:100){wordsall[[i]]=engine<=datafinal1[[i]]}
##随机查看某个文档的前八个词
> head(wordsall[[runif(1,1,100)]],8)
[1] " " " " "这" "是" "一次" "跨越""世纪" "的"
3.创建文档-词条矩阵
这里仅提取前两个标签即汽车和财经的20篇文档进行分析,去除长度为0和1的词(这些词我们认为是噪声),得到文档-词条矩阵,并且将结果转化为数据框。
创建标签,为之后运行有监督学习的分类模型做准备,并且将最终生成的数据框写入CSV文件word_matrix.csv。
#去除0和1长度的词
>wordsfinal=list()
> for(j in1:20){
wordsfinal[[j]]=unlist(wordsall[[j]])
wordsfinal[[j]]=wordsfinal[[j]][nchar(wordsfinal[[j]])!=1 &nchar(wordsfinal[[j]])!=0]
}
#随机查看
> head(wordsall[[runif(1,1,20)]],8)
[1] " " " " "如果""你" "周围" "的" "不少""人"
#将文本转换为语料库
>words_corpus=Corpus(VectorSource(wordsfinal))
#将语料库转化为文档-词条矩阵
>jieba_tokenizer=function(d){unlist(segment(d[[1]],engine))}
> control<-list(wordLengths=c(2, Inf),tokenize=jieba_tokenizer)
>words_dtm=DocumentTermMatrix(words_corpus,control=control)
#将文档-词条矩阵转换为普通矩阵
> words_matrix=as.matrix(words_dtm)
> dim(words_matrix)
[1] 203571
> words_matrix<-data.frame(words_matrix)
#创建标签
> labels=list(rep("汽车",10),rep("财经",10))
> labels1=unlist(labels)
> words_matrix$Class<-as.factor(labels1)
>write.csv(words_matrix,"words_matrix.csv")
7.2 分类模型
这里我们先构建训练集和测试集,再通过SVM算法和KNN算法分别进行预测,并且对比预测结果。
#构造训练集&测试集
> set.seed(10)
>select<-sample(1:nrow(words_matrix),nrow(words_matrix)*0.6)
> train=words_matrix[select,]
> test=words_matrix[-select,]
##SVM算法
> library(e1071)
> head(names(train))
[1] "奥克斯" "拔高" "半挂车" "保持" "保温车" "保证体系"
>SVM<-svm(Class~.,data=train,kernel="sigmoid",cost=30,gamma=0.01)
> Predictions<-predict(SVM,test)
> Predictions
1 3 9 10 13 16 17 20
财经汽车汽车财经财经汽车汽车汽车
Levels: 财经汽车
#结果对比
>pred=as.data.frame(cbind(test$Class,Predictions))
> library(mlearning)
> mats=confusion(x=pred, vars =c("V1", "Predictions"))
> summary(mats, type = c("Fscore","Recall", "Precision"))
8 items classified with 3 true positives (error =62.5%)
Global statistics on reweighted data:
Error rate: 62.5%, F(micro-average): 0.371,F(macro-average): 0.365
FscoreRecall Precision
2 0.4444444 0.50 0.4000000
1 0.2857143 0.25 0.3333333
##KNN算法
> library(class)
> head(names(train))
[1] "奥克斯" "拔高" "半挂车" "保持" "保温车" "保证体系"
>Predictions=knn(train=train[,1:3571],test=test[,1:3571],cl=train$Class,k=12)
> Predictions
[1] 汽车汽车汽车汽车汽车财经汽车财经
Levels: 财经汽车
#结果对比
>pred=as.data.frame(cbind(test$Class,Predictions))
> library(mlearning)
> mats=confusion(x=pred, vars =c("V1", "Predictions"))
> summary(mats, type = c("Fscore","Recall", "Precision"))
8 items classified with 6 true positives (error =25%)
Global statistics on reweighted data:
Error rate: 25%, F(micro-average): 0.789,F(macro-average): 0.733
FscoreRecall Precision
2 0.8000000 1.0 0.6666667
1 0.6666667 0.5 1.0000000
可见SVM和KNN的预测效果都不好,这和数据本身的性质有关,只有12条数据很难做出比较好的预测,故在文档数较小的情况下,分类模型的效果并不理想。
7.3 LDA主题模型
LDA主题模型在文档集已知归类的情况下最好分类建模,这里我们仅分析汽车栏目的新闻。数据清理的过程如下。
#去除0和1长度的词
> wordsfinal<-list()
> for(j in 1:10){
wordsfinal[[j]]=unlist(wordsall[[j]])
wordsfinal[[j]]=wordsfinal[[j]][nchar(wordsfinal[[j]])!=1& nchar(wordsfinal[[j]])!=0]
}
#随机查看
> head(wordsall[[runif(1,1,10)]],8)
[1] " " " " "消息" "传来" "强制" "缴纳" "元" "年"
>words_corpus=Corpus(VectorSource(wordsfinal))
> #将语料库转化为文档-词条矩阵
>jieba_tokenizer=function(d){unlist(segment(d[[1]],engine))}
> control<-list(wordLengths=c(2, Inf),tokenize=jieba_tokenizer)
>words_dtm=DocumentTermMatrix(words_corpus,control=control)
> #将文档-词条矩阵转换为普通矩阵
> words_matrix=as.matrix(words_dtm)
> dim(words_matrix)
[1] 102393
> words_matrix<-data.frame(words_matrix)
> write.csv(words_matrix,"words_matrix.csv",row.names=F)
##开始建立LDA模型,输入为词频-文档矩阵
> test=words_matrix
>train.lda=LDA(test,3,control=list(alpha=0.1))
#返回后验概率/文档-主题分布/文档-主题分布
> test.topics=posterior(train.lda,test)$topics
> names(posterior(train.lda,test))
[1] "terms" "topics"
#文档-主题分布
> head(test.topics,10)
1 2 3
1 5.310943e-05 5.310943e-05 9.998938e-01
2 9.996580e-01 1.710248e-04 1.710248e-04
3 9.999119e-01 4.403299e-05 4.403299e-05
4 3.597646e-05 3.597646e-05 9.999280e-01
5 9.999555e-01 2.225845e-05 2.225845e-05
6 2.574533e-04 2.574533e-04 9.994851e-01
7 1.925641e-05 1.925641e-05 9.999615e-01
8 7.114593e-05 9.998577e-01 7.114593e-05
9 3.580407e-05 9.999284e-01 3.580407e-05
10 1.566798e-04 9.996866e-01 1.566798e-04
#词—主题
> terms(train.lda,5)
Topic1 Topic 2 Topic 3
[1,] "雷克萨斯" "奇瑞" "黑车"
[2,] "中国" "汽车" "出租车"
[3,] "强制" "销量" "花冠"
[4,] "记者" "已经" "司机"
[5,] "服务" "公司" "新飞"
至此,就得到了每个文档的主题分布和每个主题的词分布。