一、引言apache
最近,在作用戶畫像,利用文本分類方法挖掘用戶興趣模型。雖然文本分類不是很難,可是簡單的事情,細節倒是至關的重要。這篇文章我主要是想記錄一下,我在作分類的時候,使用到的特徵選擇的方法,以及相關的是實現方法。ide
二、特徵選擇的方法spa
(1)信息增益scala
信息增益這一詞來自通訊領域,香濃提出的信息熵理論。信息熵的定義以下:rest
它的本質是衡量一個事件的不肯定性的大小。而信息增益則是相對於某一個特定的特徵而言的:例如,對與某個特徵X,其對應的取值有n種(x1,x2,x3...xn),分別計算特徵X在x1,x2,x3...xn的取值下的信息熵,並根據每一個取值的機率,計算出全部取值信息熵的平均值:(以下所示)
code
而信息增益就能夠表示爲原信息熵-條件熵。以下所示:orm
(2)信息增益率blog
特徵取值的個數對信息增益有較大的干擾,爲了不有些特徵取值個數較多,有些特徵取值取值較小,形成信息增益沒法公正的衡量一個特徵的好壞。因而,信息增益率是在原特徵的基礎之上,至關與對信息增益作了歸一化。其表達式是:事件
IGR=IG(T)/H(C)ci
(3)相關係數
相關係數是判斷兩個變量之間的相關性,比較經常使用的是person相關係數。
(4)Gini指數
基尼指數是表示一個變量的重要程度,其值越小越重要。
(5)卡方檢驗
卡方檢驗是檢驗兩個變量之間是否相互獨立的,在特徵選擇中的應用就是檢驗特徵與目標變量之間的是否獨立。在這裏原價假設是特徵與目標變量是相互獨立的。統計計算特徵的值與指望值之間的誤差,因爲誤差不能取負值,因此計算特徵的值與指望值之間的誤差的平方,並求和。最終結果做爲判斷原假設是否成立的標準。其公式以下:(這裏須要注意一點就是指望值是咱們本身計算獲得的)
可是,在特徵選擇的過程當中,咱們須要選擇的是原假設不成立的特徵,即誤差較大的特徵。如下介紹一下卡方檢驗在文本分類中的應用的例子:
假設如今有N篇文檔,其中有M篇是關於體育的,咱們想考察一個詞「籃球」與類別「體育」之間的相關性。咱們有四個觀察值可使用:
1. 包含「籃球」且屬於「體育」類別的文檔數,命名爲A
2. 包含「籃球」但不屬於「體育」類別的文檔數,命名爲B
3. 不包含「籃球」但卻屬於「體育」類別的文檔數,命名爲C
4. 既不包含「籃球」也不屬於「體育」類別的文檔數,命名爲D
那麼如何計算特徵「籃球」的指望值呢?由於A+B是包含「籃球」的文章數,除以總文檔數就是「籃球」出現的機率,固然,這裏認爲在一篇文章中出現便可,而無論出現了幾回,而屬於體育類的文章數爲A+C,在這些個文檔中,應該有
此時,咱們已經獲得了「籃球」這個特徵的指望值,接下來就是計算實際值與指望值之間的誤差了。以下所示:
代入上是公式,能夠獲得:
可是,咱們在實際工程上是經過其大小來選擇特徵詞的,對於全部的特徵詞,A+C和B+D都是同樣的,因此,上面的公式只須要計算以下式子便可:
最後咱們選擇其值較大的K個特徵。
(6)經過建模的方式,來選擇特徵
該方法主要是經過模型的方式來選擇特徵,具體的作法:
1)首先根據原始數據,進行數據的預處理(如缺失值,異常值,歸一化)
2)對原始數據進行建模,通常採用邏輯迴歸模型。
3)根據模型的評價指標(通常用正確率或AUC)來對模型進行預測調優。
4)若上述模型的評價指標較好,根據模型的權值的大小來肯定特徵的重要性。對應權值越大的特徵,其重要性越大。
三、特徵選擇方法的實現
(1)信息增益率和相關係數:
1 package com.welab.BDL.UserInterests 2 3 import org.apache.spark.mllib.linalg.Vectors 4 import org.apache.spark.mllib.linalg.Vector 5 import org.apache.spark.SparkContext 6 import org.apache.spark.rdd.RDD 7 import org.apache.spark.SparkConf 8 import org.apache.spark.mllib.stat.Statistics 9 import org.apache.spark.mllib.linalg.Matrix 10 11 /** 12 * @author LJY 13 */ 14 object InformationGain { 15 16 //計算某個屬性的信息熵 17 def inforEntropy(target_attribute: Array[Double]): Double = { 18 var temp = scala.collection.mutable.HashMap[Double, Int]() 19 for (item <- target_attribute) { 20 if (temp.contains(item)) { 21 temp(item) = temp(item) + 1 22 } else { 23 temp.put(item, 1) 24 } 25 } 26 var Entropy = 0.0 27 for (item <- temp) { 28 Entropy += (-1) * (item._2.toDouble / target_attribute.length) * log2(item._2.toDouble / target_attribute.length) 29 } 30 31 Entropy 32 } 33 34 def log2(x: Double): Double = scala.math.log(x) / scala.math.log(2) 35 36 //計算特徵與目標特徵之間的信息增益 37 def inforGain(sc: SparkContext, feature_attribute: RDD[(Double, Double)]): (Double, Double) = { 38 val target = feature_attribute.map { x => x._2 }.toArray() 39 val Entropy1 = inforEntropy(target) 40 41 val all_Entropy = sc.accumulator(0.0) 42 feature_attribute.groupBy(x => x._1).foreach { x => all_Entropy += (x._2.size.toDouble / target.length) * inforEntropy(x._2.map(x => x._2).toArray) 43 } 44 45 val X = feature_attribute.map { x => x._1 } 46 val Y = feature_attribute.map { x => x._2 } 47 48 val correlation: Double = Statistics.corr(X, Y, "pearson") 49 /* // calculate the correlation matrix using Pearson's method. Use "spearman" for Spearman's method. 50 // If a method is not specified, Pearson's method will be used by default. 51 val correlMatrix: Matrix = Statistics.corr(data, "pearson")*/ 52 53 // println(Entropy1) 54 // println(all_Entropy.value) 55 ((Entropy1 - all_Entropy.value), correlation) 56 } 57 58 //計算特徵與目標特徵之間的信息增益率 59 def inforGainRate(sc: SparkContext, feature_attribute: RDD[(Double, Double)]): (Double, Double) = { 60 val target = feature_attribute.map { x => x._2 }.toArray() 61 val Entropy1 = inforEntropy(target) 62 63 val all_Entropy = sc.accumulator(0.0) 64 feature_attribute.groupBy(x => x._1).foreach { x => all_Entropy += (x._2.size.toDouble / target.length) * inforEntropy(x._2.map(x => x._2).toArray) 65 } 66 67 val X = feature_attribute.map { x => x._1 } 68 val Y = feature_attribute.map { x => x._2 } 69 70 val correlation: Double = Statistics.corr(X, Y, "pearson") 71 /* // calculate the correlation matrix using Pearson's method. Use "spearman" for Spearman's method. 72 // If a method is not specified, Pearson's method will be used by default. 73 val correlMatrix: Matrix = Statistics.corr(data, "pearson")*/ 74 75 // println(Entropy1) 76 // println(all_Entropy.value) 77 ((Entropy1 - all_Entropy.value).toDouble/inforEntropy(X.toArray()), correlation) 78 } 79 80 def main(args: Array[String]): Unit = { 81 // Vectors.dense() 82 val conf = new SparkConf().setAppName("OneLevelClassification").setMaster("local") 83 val sc = new SparkContext(conf) 84 85 val data = sc.textFile("/user/hive/warehouse/bairong_summary") 86 87 val result = data.take(10).foreach { x => 88 val fields = x.split("\t") 89 fields.foreach { x => print(x + " ") } 90 } 91 92 } 93 }
(2)卡方檢驗自定義實現:
1 def my_chiSqTest3(sc: SparkContext, filename: String, p_thr: Double): Array[String] = { 2 val data = sc.textFile(filename, 10000).cache() 3 val allnum = data.count() 4 val result = data.map { x => 5 val fields = x.split("::") 6 val wds = fields(0).split(",") 7 val label = fields(1).split("#")(0) 8 var words = scala.collection.mutable.HashSet[String]() //準備對每一個文檔中的關鍵詞進行去重 9 for (word <- wds) { 10 words.add(word) 11 } 12 var ss = "" 13 for (word <- words) { 14 if (ss.isEmpty()) { 15 ss = word + ":" + label 16 } else { 17 ss = ss + "," + word + ":" + label 18 } 19 } 20 ss 21 }.flatMap { x => x.split(",") }.map { x => 22 val fields = x.split(":") 23 (fields(0), fields(1)) //(word,label) 24 }.reduceByKey { (x, y) => x + "," + y }.cache() 25 26 println("全部的特徵詞的總數爲:" + result.count()) 27 28 /* 29 * A ——表示包含某個關鍵詞T,且屬於某類X的文檔數 30 * B ——表示包含某個關鍵詞T,但不屬於某類X的文檔數 31 * C ——表示不包含某個關鍵詞T,但屬於某類X的文檔數 32 * D ——表示即不包含某個關鍵詞T,也不屬於某類X的文檔數 33 * A+C ——表示屬於某類X的全部文檔數 34 * C+D ——表示不包含關鍵詞T的全部文檔數 35 */ 36 37 val result_A_B = result.map { x => 38 val word = x._1 39 var label_occur = scala.collection.mutable.HashMap[String, Int]() //(label,occur) 40 val labels = x._2.split(",") //包含該關鍵詞全部類別 41 var A = 0 42 var B = 0 43 //統計關鍵詞在每一個類別中出現的次數 44 for (label <- labels) { 45 label_occur.get(label) match { 46 case Some(a) => label_occur.put(label, a + 1) 47 case None => label_occur.put(label, 1) 48 } 49 } 50 //相對每一個類來講,對應的A和B分別以下 51 var word_A_B = "" 52 for (label_num <- label_occur) { 53 A = label_num._2 54 B = labels.length - label_num._2 55 if (word_A_B.isEmpty()) { 56 word_A_B = word + ":" + label_num._1 + "," + A + "#" + B 57 } else { 58 word_A_B = word_A_B + "@" + word + ":" + label_num._1 + "," + A + "#" + B 59 } 60 61 } 62 word_A_B 63 }.flatMap { x => x.split("@") }.map { x => 64 val fields = x.split(",") 65 val word_label = fields(0) 66 val A = fields(1).split("#")(0).toInt 67 val B = fields(1).split("#")(1).toInt 68 (word_label, (A, B)) //(word:label,(A,B)) 69 } 70 71 val result_CplusD = result.map { x => 72 val word = x._1 73 val C_and_D = allnum - x._2.split(",").length 74 (word, C_and_D) //相對於關鍵詞T來講,C+D 75 } 76 77 val result_Aplus_C = data.map { x => 78 val fields = x.split("::") 79 val label = fields(1).split("#")(0) 80 (label, 1) 81 }.reduceByKey(_ + _) 82 83 val res = result_A_B.map { x => 84 val word_label = x._1.split(":") 85 val word = word_label(0) 86 val label = word_label(1) 87 (word, (label, x._2)) //(word,(label,(A,B))) 88 }.leftOuterJoin(result_CplusD).map { x => 89 val word = x._1 90 val label_A_B = x._2._1 91 var C_plus_D = 0 92 x._2._2 match { 93 case Some(a) => C_plus_D = a.toInt 94 case None => 95 } 96 (label_A_B._1, (word, label_A_B._2, C_plus_D)) //(label,(word,(A,B),C+D)) 97 }.leftOuterJoin(result_Aplus_C).map { x => 98 val label = x._1 99 val word_A_B_CplusD = x._2._1 //(word,(A,B),C+D) 100 var AplusC = 0 101 x._2._2 match { 102 case Some(a) => AplusC = a 103 case None => 104 } 105 val A = word_A_B_CplusD._2._1 106 val B = word_A_B_CplusD._2._2 107 val C = AplusC - A 108 val D = word_A_B_CplusD._3 - C 109 (word_A_B_CplusD._1, ((A * D - B * C) * (A * D - B * C)).toDouble / ((A + B) * word_A_B_CplusD._3)) 110 }.reduceByKey { (x, y) => 111 var maxvalue = 0.0 112 if (x > y) { 113 maxvalue = x 114 } else { 115 maxvalue = y 116 } 117 maxvalue 118 }.map(x => (x._2, x._1)).sortByKey(false).map { x => (x._2, x._1) } 119 120 val res1 = res.take((res.count() * p_thr).toInt).map(x => x._1) 121 res1 122 }