特征抽取: TF-IDF -- spark.ml
赖永炫   Tue Dec 20 2016 09:45:50 GMT+0800 (中国标准时间) [ 技术 ]     浏览次数:7926
版权声明: 本文发自http://mocom.xmu.edu.cn,为 赖永炫 老师的个人博文,文章仅代表个人观点。无需授权即可转载,转载时请务必注明作者。

返回 [Spark MLlib入门教程](http://mocom.xmu.edu.cn/article/show/5858ab782b2730e00d70fa08/0/1) 这一部分我们主要介绍和特征处理相关的算法,大体分为以下三类: - 特征抽取:从原始数据中抽取特征 - 特征转换:特征的维度、特征的转化、特征的修改 - 特征选取:从大规模特征集中选取一个子集 ## 特征抽取 Feature Extractors ### TF-IDF (HashingTF and IDF) ​ “词频-逆向文件频率”(TF-IDF)是一种在文本挖掘中广泛使用的特征向量化方法,它可以体现一个文档中词语在语料库中的重要程度。 ​ 词语由t表示,文档由d表示,语料库由D表示。词频TF(t,d)是词语t在文档d中出现的次数。文件频率DF(t,D)是包含词语的文档的个数。如果我们只使用词频来衡量重要性,很容易过度强调在文档中经常出现,却没有太多实际信息的词语,比如“a”,“the”以及“of”。如果一个词语经常出现在语料库中,意味着它并不能很好的对文档进行区分。TF-IDF就是在数值化文档信息,衡量词语能提供多少信息以区分文档。其定义如下: $$ IDF(t,D) = log \frac{\left| D \right| + 1}{DF(t,D) + 1} $$ ​ 此处$\left| D \right|$ 是语料库中总的文档数。公式中使用log函数,当词出现在所有文档中时,它的IDF值变为0。加1是为了避免分母为0的情况。TF-IDF 度量值表示如下: $$ TFIDF(t,d,D) = TF(t,d) \cdot IDF(t,D) $$ ​ 在Spark ML库中,TF-IDF被分成两部分:TF (+hashing) 和 IDF。 **TF**: HashingTF 是一个Transformer,在文本处理中,接收词条的集合然后把这些集合转化成固定长度的特征向量。这个算法在哈希的同时会统计各个词条的词频。 **IDF**: IDF是一个Estimator,在一个数据集上应用它的fit()方法,产生一个IDFModel。 该IDFModel 接收特征向量(由HashingTF产生),然后计算每一个词在文档中出现的频次。IDF会减少那些在语料库中出现频率较高的词的权重。 ​ Spark.mllib 中实现词频率统计使用特征hash的方式,原始特征通过hash函数,映射到一个索引值。后面只需要统计这些索引值的频率,就可以知道对应词的频率。这种方式避免设计一个全局1对1的词到索引的映射,这个映射在映射大量语料库时需要花费更长的时间。但需要注意,通过hash的方式可能会映射到同一个值的情况,即不同的原始特征通过Hash映射后是同一个值。为了降低这种情况出现的概率,我们只能对特征向量升维。i.e., 提高hash表的桶数,默认特征维度是 2^20 = 1,048,576. ​ 在下面的代码段中,我们以一组句子开始。首先使用分解器Tokenizer把句子划分为单个词语。对每一个句子(词袋),我们使用HashingTF将句子转换为特征向量,最后使用IDF重新调整特征向量。这种转换通常可以提高使用文本特征的性能。 ​ 首先,导入TFIDF所需要的包: ```scala import org.apache.spark.SparkConf import org.apache.spark.SparkContext import org.apache.spark.sql.SQLContext import org.apache.spark.ml.feature.{HashingTF, IDF, Tokenizer} ``` ​ 接下来,根据SparkContext来创建一个SQLContext,其中sc是一个已经存在的SparkContext;然后导入sqlContext.implicits._来实现RDD到Dataframe的隐式转换。 ```scala scala> val sqlContext = new SQLContext(sc) sqlContext: org.apache.spark.sql.SQLContext = org.apache.spark.sql.SQLContext@225a9fc6 scala> import sqlContext.implicits._ import sqlContext.implicits._ ``` ​ 第三步,创建一个集合,每一个句子代表一个文件。 ```scala scala> val sentenceData = sqlContext.createDataFrame(Seq( | (0, "I heard about Spark and I love Spark"), | (0, "I wish Java could use case classes"), | (1, "Logistic regression models are neat") | )).toDF("label", "sentence") sentenceData: org.apache.spark.sql.DataFrame = [label: int, sentence: string] ``` 第四步,用tokenizer把每个句子分解成单词 ```scala scala> val tokenizer = new Tokenizer().setInputCol("sentence").setOutputCol("words") tokenizer: org.apache.spark.ml.feature.Tokenizer = tok_494411a37f99 scala> val wordsData = tokenizer.transform(sentenceData) wordsData: org.apache.spark.sql.DataFrame = [label: int, sentence: string, words: array] scala> wordsData.foreach {println} [1,Logistic regression models are neat,WrappedArray(logistic, regression, models, are, neat)] [0,I wish Java could use case classes,WrappedArray(i, wish, java, could, use, case, classes)] [0,I heard about Spark and I love Spark,WrappedArray(i, heard, about, spark, and, i, love, spark)] ``` ​ 从打印结果可以看到,tokenizer的transform()方法把每个句子拆分成了一个个单词。 第五步,用HashingTF的transform()方法把句子哈希成特征向量。我们这里设置哈希表的桶数为2000。 ```scala scala> val hashingTF = new HashingTF(). | setInputCol("words").setOutputCol("rawFeatures").setNumFeatures(2000) hashingTF: org.apache.spark.ml.feature.HashingTF = hashingTF_2591ec73cea0 scala> val featurizedData = hashingTF.transform(wordsData) featurizedData: org.apache.spark.sql.DataFrame = [label: int, sentence: string, words: array, rawFeatures: vector] scala> featurizedData.foreach {println} [1,Logistic regression models are neat,WrappedArray(logistic, regression, models, are, neat),(2000,[65,618,852,992,1194],[1.0,1.0,1.0,1.0,1.0])] [0,I wish Java could use case classes,WrappedArray(i, wish, java, could, use, case, classes),(2000,[103,105,192,774,818,1265,1703],[1.0,1.0,1.0,1.0,1.0,1.0,1.0])] [0,I heard about Spark and I love Spark,WrappedArray(i, heard, about, spark, and, i, love, spark),(2000,[105,365,727,1469,1858,1926],[2.0,2.0,1.0,1.0,1.0,1.0])] ``` ​ 我们可以看到每一个单词被哈希成了一个不同的索引值。以"I heard about Spark and I love Spark"为例,输出结果中2000代表哈希表的桶数,“[105,365,727,1469,1858,1926]”分别代表着“i, spark, heard, about, and, love”的哈希值,“[2.0,2.0,1.0,1.0,1.0,1.0]”为对应单词的出现次数。 ​ 第六步,调用IDF方法来重新构造特征向量的规模,生成的idf是一个Estimator,在特征向量上应用它的fit()方法,会产生一个IDFModel。 ```scala scala> val idf = new IDF().setInputCol("rawFeatures").setOutputCol("features") idf: org.apache.spark.ml.feature.IDF = idf_7fcc9063de6f scala> val idfModel = idf.fit(featurizedData) idfModel: org.apache.spark.ml.feature.IDFModel = idf_7fcc9063de6f ``` 同时,调用IDFModel的transform方法,可以得到每一个单词对应的TF-IDF 度量值。 ```scala scala> val rescaledData = idfModel.transform(featurizedData) rescaledData: org.apache.spark.sql.DataFrame = [label: int, sentence: string, words: array, rawFeatures: vector, features: vector] scala> rescaledData.select("features", "label").take(3).foreach(println) [(2000,[105,365,1329,1469,1926],[0.28768207245178085,0.6931471805599453,0.693147 1805599453,0.6931471805599453,0.6931471805599453]),0] [(2000,[103,105,192,774,818,1265,1703],[0.6931471805599453,0.28768207245178085,0 .6931471805599453,0.6931471805599453,0.6931471805599453,0.6931471805599453,0.693 1471805599453]),0] [(2000,[65,618,852,992,1194],[0.6931471805599453,0.6931471805599453,0.6931471805 599453,0.6931471805599453,0.6931471805599453]),1] ``` “[105,365,727,1469,1858,1926]”分别代表着“i, spark, heard, about, and, love”的哈希值。105和365分别代表了“i"和"spark",其TF-IDF值分别是0.2876820724517808和0.6931471805599453。这两个单词都在第一句中出现了两次,而"i"在第二句中还多出现了一次,从而导致"i"的TF-IDF 度量值较低。相对而言,“spark”可以对文档进行更好的区分。 通过TF-IDF得到的特征向量,在接下来可以被应用到相关的机器学习方法中。


自动标签  : spark.ml   特征   抽取   哈希   特征向量   HashingTF   Spark   使用   文档   可以   词语   出现   句子   Tokenizer    

更多 [ 技术 ] 文章

请先 登录, 查看相关评论.