Spark数据挖掘-TF-IDF文档矩阵

原创
2015/11/19 20:47
阅读数 2.7K

Spark数据挖掘-TF-IDF文档矩阵

前言

得到词文档矩阵往往都是文本挖掘算法的第一步,词文档矩阵中行表示语料库中出现过的词(实际代码都是对词进行整数编码),列表示所有的文档,矩阵中的每个值就代表词在文档中的重要程度。目前已经有很多计算词在文档中权重的模型,不过最通用的模型应该就是 词频-逆文档频率(简称:TF-IDF) 矩阵。

TF-IDF

先看一下TF-IDF如何计算每个词在文档中的重要程度,先假设得到了下面几个变量的值:

  • termFrequencyInDoc:Int 词在文档中出现的次数
  • totalTermsInDoc: Int 文档中所有词的个数
  • termFreqInCorpus: Int 语料库中出现这个词的不同文档数
  • totalDocs: Int 整个语料库包含的文档数量

利用上面的几个值就可以计算一个词在文档中的重要程度,代码如下:

def termDocWeight(termFrequencyInDoc: Int, totalTermsInDoc: Int,
  termFreqInCorpus: Int, totalDocs: Int): Double = {
  val tf = termFrequencyInDoc.toDouble / totalTermsInDoc
  val docFreq = totalDocs.toDouble / termFreqInCorpus
  val idf = math.log(docFreq)
  tf * idf
}

TF-IDF含义解读

直观理解,TF-IDF考虑了如下两个方面:

  1. 首先,词在某篇文档中出现的越多越重要
  2. 其次,词在其他文档中尽量少出现,如果每篇文档中它都出现的很多,它就没有区分度了。所以这里采用的是文档频率取逆,也就是取倒数。

但是有个问题,首先词频是近似服从指数分布,一些通用词出现的次数可能是低频词几十或者几百倍,直接采用文档频率取逆会导致稀缺词获得一个巨大的权重,实际上就会忽略其他词的影响,这样显然不妥,为了去掉这个影响,算法采用对文档频率取逆之后再取对数,这样就把乘法差异转化为加法差异。使得稀缺词权重降低了。

TF-IDF的局限

这个算法本质还是词袋模型,它没有考虑词与词之间的顺序、语法结构以及语义。通过对每个不同的词计数,这个模型很难区别一词多义、以及同义词。实际编程中往往每篇文档表示为行向量,每个元素代表词对应的索引位置,这样可以适应语料库中文档变的越来越多,通常情况下,语料库文档的增加速度肯定大过词的增长速度。而且这样的行向量也是稀疏表示,节省空间。

TF-IDF实战

TF-IDF原理非常简单,实际工作中还是有很多需要注意的地方,下面就用 Spark 实战的方式讲解如何将一个语料库转为 TF-IDF 矩阵,首先给出具体的步骤:

  1. 实际工作中对每篇文档往往都有一个唯一的标识主键,这样方便以后的统计
  2. 文档清洗(去掉不符合要求的文档:比如文档内容为空)
  3. 文档分词(去掉停用词、按词性过滤一下词、过滤低频词、词干提取),其中词干提取指的是将比如:“大款、大款们”合并为一个词,中文实现这个难度很大。
  4. 数据加载,词编码、文档编码
  5. 将数据编码之后转为文档词向量再合并为矩阵
  6. 计算IF-IDF

数据清洗、停词库每个项目都会有不同的要求,但是分词、词编码、文档编码等工作还是比较通用的,下面会对通用技术通过 Spark 实战详细讲解(尤其是分布式编程需要注意的地方:)

分布式文本分词

分布式文本分词注意两个地方:

  • 停词库需要广播到所有机器
  • 每个 executor 只需启动一个分词模型即可,分词模型很耗资源,与连接数据库一样,每个executor只需启动一个实例

计算TF-IDF

val documents: RDD[Seq[String]] = sc.textFile("your_data_dir").map(_.split(" ").toSeq)

val hashingTF = new HashingTF()
val tf: RDD[Vector] = hashingTF.transform(documents)

tf.cache()
//没有在两个文档中出现过的词语不要
val idf = new IDF(minDocFreq = 2).fit(tf)
val tfidf: RDD[Vector] = idf.transform(tf)
展开阅读全文
打赏
1
11 收藏
分享
加载中
更多评论
打赏
0 评论
11 收藏
1
分享
返回顶部
顶部