Spark MLlib實現的中文文本分類–Native Bayes
來自: http://lxw1234.com/archives/2016/01/605.htm
關鍵字:spark mllib、文本分類、樸素貝葉斯、native bayes
文本分類是指將一篇文章歸到事先定義好的某一類或者某幾類,在數據平臺的一個典型的應用場景是,通過爬取用戶瀏覽過的頁面內容,識別出用戶的瀏覽偏好,從而豐富該用戶的畫像。
本文介紹使用Spark MLlib提供的樸素貝葉斯(Native Bayes)算法,完成對中文文本的分類過程。主要包括中文分詞、文本表示(TF-IDF)、模型訓練、分類預測等。
中文分詞
對于中文文本分類而言,需要先對文章進行分詞,我使用的是IKAnalyzer中文分析工具,之前有篇文章介紹過《中文分詞工具-IKAnalyzer下載及使用》,其中自己可以配置擴展詞庫來使分詞結果更合理,我從搜狗、百度輸入法下載了細胞詞庫,將其作為擴展詞庫。這里不再介紹分詞。
中文詞語特征值轉換(TF-IDF)
分好詞后,每一個詞都作為一個特征,但需要將中文詞語轉換成Double型來表示,通常使用該詞語的TF-IDF值作為特征值,Spark提供了全面的特征抽取及轉換的API,非常方便,詳見http://spark.apache.org/docs/latest/ml-features.html,這里介紹下TF-IDF的API:
比如,訓練語料/tmp/lxw1234/1.txt:
0,蘋果 官網 蘋果 宣布
1,蘋果 梨 香蕉
逗號分隔的第一列為分類編號,0為科技,1為水果。
case class RawDataRecord(category: String, text: String)val conf = new SparkConf().setMaster("yarn-client") val sc = new SparkContext(conf) val sqlContext = new org.apache.spark.sql.SQLContext(sc) import sqlContext.implicits._
//將原始數據映射到DataFrame中,字段category為分類編號,字段text為分好的詞,以空格分隔 var srcDF = sc.textFile("/tmp/lxw1234/1.txt").map { x => var data = x.split(",") RawDataRecord(data(0),data(1)) }.toDF()
srcDF.select("category", "text").take(2).foreach(println) [0,蘋果 官網 蘋果 宣布] [1,蘋果 梨 香蕉] //將分好的詞轉換為數組 var tokenizer = new Tokenizer().setInputCol("text").setOutputCol("words") var wordsData = tokenizer.transform(srcDF)
wordsData.select($"category",$"text",$"words").take(2).foreach(println) [0,蘋果 官網 蘋果 宣布,WrappedArray(蘋果, 官網, 蘋果, 宣布)] [1,蘋果 梨 香蕉,WrappedArray(蘋果, 梨, 香蕉)]
//將每個詞轉換成Int型,并計算其在文檔中的詞頻(TF) var hashingTF = new HashingTF().setInputCol("words").setOutputCol("rawFeatures").setNumFeatures(100) var featurizedData = hashingTF.transform(wordsData)</pre>
這里將中文詞語轉換成INT型的Hashing算法,類似于Bloomfilter,上面的setNumFeatures(100)表示將Hash分桶的數量設置為100個,這個值默認為2的20次方,即1048576,可以根據你的詞語數量來調整,一般來說,這個值越大,不同的詞被計算為一個Hash值的概率就越小,數據也更準確,但需要消耗更大的內存,和Bloomfilter是一個道理。
featurizedData.select($"category", $"words", $"rawFeatures").take(2).foreach(println) [0,WrappedArray(蘋果, 官網, 蘋果, 宣布),(100,[23,81,96],[2.0,1.0,1.0])] [1,WrappedArray(蘋果, 梨, 香蕉),(100,[23,72,92],[1.0,1.0,1.0])]結果中,“蘋果”用23來表示,第一個文檔中,詞頻為2,第二個文檔中詞頻為1.
//計算TF-IDF值 var idf = new IDF().setInputCol("rawFeatures").setOutputCol("features") var idfModel = idf.fit(featurizedData) var rescaledData = idfModel.transform(featurizedData) rescaledData.select($"category", $"words", $"features").take(2).foreach(println)[0,WrappedArray(蘋果, 官網, 蘋果, 宣布),(100,[23,81,96],[0.0,0.4054651081081644,0.4054651081081644])] [1,WrappedArray(蘋果, 梨, 香蕉),(100,[23,72,92],[0.0,0.4054651081081644,0.4054651081081644])]
//因為一共只有兩個文檔,且都出現了“蘋果”,因此該詞的TF-IDF值為0.</pre>
最后一步,將上面的數據轉換成Bayes算法需要的格式,如:
https://github.com/apache/spark/blob/branch-1.5/data/mllib/sample_naive_bayes_data.txt
var trainDataRdd = rescaledData.select($"category",$"features").map { case Row(label: String, features: Vector) => LabeledPoint(label.toDouble, Vectors.dense(features.toArray)) }
每一個LabeledPoint中,特征數組的長度為100(setNumFeatures(100)),”官網”和”宣布”對應的特征索引號分別為81和96,因此,在特征數組中,第81位和第96位分別為它們的TF-IDF值。
到此,中文詞語特征表示的工作已經完成,trainDataRdd已經可以作為Bayes算法的輸入了。
分類模型訓練
訓練模型,語料非常重要,我這里使用的是搜狗提供的分類語料庫,很早之前的了,這里只作為學習測試使用。
下載地址在:http://www.sogou.com/labs/dl/c.html,語料庫一共有10個分類:
C000007 汽車
C000008 財經
C000010 IT
C000013 健康
C000014 體育
C000016 旅游
C000020 教育
C000022 招聘
C000023 文化
C000024 軍事每個分類下有幾千個文檔,這里將這些語料進行分詞,然后每一個分類生成一個文件,在該文件中,每一行數據表示一個文檔的分詞結果,重新用0-9作為這10個分類的編號:
0 汽車
1 財經
2 IT
3 健康
4 體育
5 旅游
6 教育
7 招聘
8 文化
9 軍事比如,汽車分類下的文件內容為:
數據準備好了,接下來進行模型訓練及分類預測,代碼:
package com.lxw1234.textclassificationimport scala.reflect.runtime.universe
import org.apache.spark.SparkConf import org.apache.spark.SparkContext import org.apache.spark.ml.feature.HashingTF import org.apache.spark.ml.feature.IDF import org.apache.spark.ml.feature.Tokenizer import org.apache.spark.mllib.classification.NaiveBayes import org.apache.spark.mllib.linalg.Vector import org.apache.spark.mllib.linalg.Vectors import org.apache.spark.mllib.regression.LabeledPoint import org.apache.spark.sql.Row
object TestNativeBayes {
case class RawDataRecord(category: String, text: String)
def main(args : Array[String]) {
val conf = new SparkConf().setMaster("yarn-client") val sc = new SparkContext(conf) val sqlContext = new org.apache.spark.sql.SQLContext(sc) import sqlContext.implicits._ var srcRDD = sc.textFile("/tmp/lxw1234/sougou/").map { x => var data = x.split(",") RawDataRecord(data(0),data(1)) } //70%作為訓練數據,30%作為測試數據 val splits = srcRDD.randomSplit(Array(0.7, 0.3)) var trainingDF = splits(0).toDF() var testDF = splits(1).toDF() //將詞語轉換成數組 var tokenizer = new Tokenizer().setInputCol("text").setOutputCol("words") var wordsData = tokenizer.transform(trainingDF) println("output1:") wordsData.select($"category",$"text",$"words").take(1) //計算每個詞在文檔中的詞頻 var hashingTF = new HashingTF().setNumFeatures(500000).setInputCol("words").setOutputCol("rawFeatures") var featurizedData = hashingTF.transform(wordsData) println("output2:") featurizedData.select($"category", $"words", $"rawFeatures").take(1) //計算每個詞的TF-IDF var idf = new IDF().setInputCol("rawFeatures").setOutputCol("features") var idfModel = idf.fit(featurizedData) var rescaledData = idfModel.transform(featurizedData) println("output3:") rescaledData.select($"category", $"features").take(1) //轉換成Bayes的輸入格式 var trainDataRdd = rescaledData.select($"category",$"features").map { case Row(label: String, features: Vector) => LabeledPoint(label.toDouble, Vectors.dense(features.toArray)) } println("output4:") trainDataRdd.take(1) //訓練模型 val model = NaiveBayes.train(trainDataRdd, lambda = 1.0, modelType = "multinomial") //測試數據集,做同樣的特征表示及格式轉換 var testwordsData = tokenizer.transform(testDF) var testfeaturizedData = hashingTF.transform(testwordsData) var testrescaledData = idfModel.transform(testfeaturizedData) var testDataRdd = testrescaledData.select($"category",$"features").map { case Row(label: String, features: Vector) => LabeledPoint(label.toDouble, Vectors.dense(features.toArray)) } //對測試數據集使用訓練模型進行分類預測 val testpredictionAndLabel = testDataRdd.map(p => (model.predict(p.features), p.label)) //統計分類準確率 var testaccuracy = 1.0 * testpredictionAndLabel.filter(x => x._1 == x._2).count() / testDataRdd.count() println("output5:") println(testaccuracy)
} }</pre>
執行后,主要輸出如下:
output1:(將詞語轉換成數組)
output2:(計算每個詞在文檔中的詞頻)
output3:(計算每個詞的TF-IDF)
output4:(Bayes算法的輸入數據格式)
output5:(測試數據集分類準確率)
準確率90%,還可以。接下來需要收集分類更細,時間更新的數據來訓練和測試了。