TF-IDF原理及spark使用

TF-IDF(Term Frequency/Inverse Document Frequency,词频-逆文档频率)是一种统计方法,旨在反映关键词(Term)对集合或语料库中的文档的重要程度。它经常被用作搜索信息检索,文本挖掘和用户建模的加权因子。tf-idf值按比例增加一个单词出现在文档中的次数,并被包含该单词的语料库中的文档数量所抵消,这有助于调整某些单词在一般情况下更频繁出现的事实。搜索引擎经常使用tf-idf加权方案的变体作为在给定用户查询的情况下对文档的相关性进行评分和排序的中心工具。tf-idf可以成功地用于各种主题领域的停用词过滤,包括文本摘要和分类。

词的重要性随着它在文件中出现的次数成正比增加,但同时会随着它在语料库中出现的频率成反比下降。

TF-IDF 算法用于中文时首先要分词,分词后要解决多词一义、一词多义的问题。这两个问题通过简单的tf-idf方法不能很好的解决,于是就有了后来的词嵌入(word2vector)方法,用向量来表征一个词。

如何计算

  • TF: 反映how frequently a term occurs in a document. TF(t) = 词语t在某文档中出现的次数 / 该文档中的所有词的个数。这其实是一种标准化的方式:由于每个文档的长度都不同,一个词语在长文档中的出现次数可能会比短文档中出现的次数更多,所以词频要除以文档长度(单词总数)。
         T F w , D i = c o u n t ( w ) D i \large TF_{w,D_i} = \dfrac {count(w)}{|D_i|}
    其中,count(w)为关键词w的出现次数,|Di|为文档Di中所有词的数量。

  • IDF: IDF反映关键词的不普遍程度。IDF(t)=log(文档总数/包含词语t的文档数)。
    取对数来达到抑制线性增长的目的。假设有100个文档,包含词语A的有30个 100/30=3.3,包含词语B的有60个,100/60=1.67,那么能不能说的B普遍度是A的二倍呢?可能跟别的大部分词比较而言,就不尽然。所以取对数更合理。
    另外,如果某单词在所有文档中均未出现,则IDF中分母为0,需要做+1平滑处理。
         I D F w = l o g N 1 + i = 1 N I ( w , D i ) \large IDF_{w} = log \dfrac{N}{1+\sum_{i=1}^{N}I(w,D_i)}
    其中,N为所有的文档总数,I(w,Di)表示文档Di是否包含关键词,若包含则为1,若不包含则为0。

那么,TF-IDF = TF*IDF = c o u n t ( w ) D i l o g N 1 + i = 1 N I ( w , D i ) \dfrac {count(w)}{|D_i|} * log \dfrac{N}{1+\sum_{i=1}^{N}I(w,D_i)}

sklearn中的例子
from sklearn.feature_extraction.text import TfidfVectorizer
TfidfVectorizer().fit_transform()返回值表示:文档-单词的tf-idf值 的矩阵,例如
(2, 3) 0.267103787642168


spark-scala的例子
spark-2.4.3\examples\src\main\scala\org\apache\spark\examples\ml\TfIdfExample.scala

import org.apache.spark.sql.SparkSession
import org.apache.spark.ml.feature.{HashingTF, IDF, Tokenizer}

object TFidfTest {
  def main(args: Array[String]) {
    val randomSort = new ScalaSort
    val randList = randomSort.RandomDiffList(15)
    println("The seq: " + randList)

    val spark = SparkSession.builder.appName("TfIdfExample")
                .master("local").getOrCreate()
    val sentenceData = spark.createDataFrame(Seq(
      (0.0, "Hi I heard about Spark and I love Spark"),
      (0.0, "I wish Java could use case classes"),
      (1.0, "Logistic regression models are neat")
    )).toDF("label", "sentence")

    val tokenizer = new Tokenizer().setInputCol("sentence").setOutputCol("words")
    val wordsData = tokenizer.transform(sentenceData)
//    wordsData.foreach(x => println(x))

    val hashingTF = new HashingTF()
      .setInputCol("words").setOutputCol("rawFeatures").setNumFeatures(20)
    val featurizedData = hashingTF.transform(wordsData)
    featurizedData.foreach(x => println(x))

    val idf = new IDF().setInputCol("rawFeatures").setOutputCol("features")
    val idfModel = idf.fit(featurizedData)

    val rescaledData = idfModel.transform(featurizedData)
    rescaledData.select("features", "label")
      .collect().foreach(println)

    spark.stop()
  }
}

HashingTF 是一个Transformer:
它使用hashing trick实现词频。元素的特征应用一个hash函数映射到一个索引(即词),通过这个索引计算词频。这个方法避免计算全局的词-索引映射,因为全局的词-索引映射在大规模语料中花费较大。
但是,它会出现哈希冲突,这是因为不同的元素特征可能得到相同的哈希值。为了减少碰撞冲突,我们可以增加目标特征的维度,例如哈希表的桶数量。默认的特征维度是 2 18 = 262 , 144 2^{18} = 262,144

输出如下:

[(20,[0,5,9,13,17],[1.3862943611198906,1.3862943611198906,0.5753641449035617,0.0,1.3862943611198906]),0.0]
[(20,[2,7,9,13,15],[0.6931471805599453,0.6931471805599453,0.8630462173553426,0.0,0.28768207245178085]),0.0]
[(20,[4,6,13,15,18],[0.6931471805599453,0.6931471805599453,0.0,0.28768207245178085,0.6931471805599453]),1.0]

猜你喜欢

转载自blog.csdn.net/rover2002/article/details/106555957