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在某文檔中出現的次數 / 該文檔中的所有詞的個數。這其實是一種標準化的方式:由於每個文檔的長度都不同,一個詞語在長文檔中的出現次數可能會比短文檔中出現的次數更多,所以詞頻要除以文檔長度(單詞總數)。
         TFw,Di=count(w)Di\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平滑處理。
         IDFw=logN1+i=1NI(w,Di)\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 = count(w)DilogN1+i=1NI(w,Di)\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函數映射到一個索引(即詞),通過這個索引計算詞頻。這個方法避免計算全局的詞-索引映射,因爲全局的詞-索引映射在大規模語料中花費較大。
但是,它會出現哈希衝突,這是因爲不同的元素特徵可能得到相同的哈希值。爲了減少碰撞衝突,我們可以增加目標特徵的維度,例如哈希表的桶數量。默認的特徵維度是218=262,1442^{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]
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章