Spark應用HanLP對中文語料進行文字挖掘--聚類詳解教程
軟體: IDEA2014 、 Maven 、 HanLP 、 JDK ;
用到的知識: HanLP 、 Spark TF-IDF 、 Spark kmeans 、 Spark mapPartition;
用到的資料集: http://www.threedweb.cn/thread-1288-1-1.html (不需要下載,已經包含在工程裡面);
工程下載: https://github.com/fansy1990/hanlp-test 。
1 、 問題描述
現在有一箇中文文字資料集,這個資料集已經對其中的文字做了分類,如下:
其中每個資料夾中含有個數不等的檔案,比如環境有 200 個,藝術有 248 個;同時,每個檔案的內容基本上就是一些新聞報導或者中文描述,如下:
現在需要做的就是,把這些文件進行聚類,看其和原始給定的類別的重合度有多少,這樣也可以反過來驗證我們聚類演算法的正確度。
2. 、解決思路:
2.1 文字預處理:
1. 由於檔案的編碼是 GBK 的,讀取到 Spark 中全部是亂碼,所以先使用 Java 把程式碼轉為 UTF8 編碼;
2. 由於文字存在多個檔案中(大概 2k 多),使用 Spark 的 wholeTextFile 讀取速度太慢,所以考慮把這些檔案全部合併為一個檔案,這時又結合 1. 的轉變編碼,所以在轉變編碼的時候就直接把所有的資料存入同一個檔案中;
其儲存的格式為: 每行: 檔名 .txt\t 檔案內容
如: 41.txt 【 日 期 】 199601....
這樣子的話,就可以通過 .txt\t 來對每行文字進行分割,得到其檔名以及檔案內容,這裡每行其實就是一個檔案了。
2.2 分詞
分詞直接採用 HanLP 的分詞來做, HanLP 這裡選擇兩種: Standard 和 NLP( 還有一種就是 HighSpeed ,但是這個木有使用者自定義詞典,所以前期考慮先用兩種 ) ,具體參考: https://github.com/hankcs/HanLP ;
2.3 詞轉換為詞向量
在 Kmeans 演算法中,一個樣本需要使用數值型別,所以需要把文字轉為數值向量形式,這裡在 Spark 中有兩種方式。其一,是使用 TF-IDF ;其二,使用 Word2Vec 。這裡暫時使用了 TF-IDF 演算法來進行,這個演算法需要提供一個 numFeatures ,這個值越大其效果也越好,但是相應的計算時間也越長,後面也可以通過實驗驗證。
2.4 使用每個文件的詞向量進行聚類建模
在進行聚類建模的時候,需要提供一個初始的聚類個數,這裡面設定為 10 ,因為我們的資料是有 10 個分組的。但是在實際的情況下,一般這個值是需要通過實驗來驗證得到的。
2.5 對聚類後的結果進行評估
這裡面採用的思路是:
1. 得到聚類模型後,對原始資料進行分類,得到原始檔名和預測的分類 id 的二元組 (fileName,predictId) ;
2. 針對( fileName , predictId ),得到( fileNameFirstChar ,fileNameFirstChar.toInt - predictId )的值,這裡需要注意的是 fileNameFirstChar 其實就是代表這個檔案的原始所屬類別了。
3. 這裡有一個一般假設,就是使用 kmeans 模型預測得到的結果大多數是正確的,所以 fileNameFirstChar.toInt-predictId 得到的眾數其實就是分類的正確的個數了(這裡可能比較難以理解,後面會有個小李子來說明這個問題);
4. 得到每個實際類別的預測的正確率後就可以去平均預測率了。
5. 改變 numFeatuers 的值,看下是否 numFeatures 設定的比較大,其正確率也會比較大?
3 、具體步驟:
3.1 開發環境 --Maven
首先第一步,當然是開發環境了,因為用到了 Spark 和 HanLP ,所以需要在 pom.xml 中加入這兩個依賴:
1. <!-- 中文分詞框架 -->
2.<dependency>
3.<groupId>com.hankcs</groupId>
4.<artifactId>hanlp</artifactId>
5.<version>${hanlp.version}</version>
6.</dependency>
7.<!-- Spark dependencies -->
8.<dependency>
9.<groupId>org.apache.spark</groupId>
10.<artifactId>spark-core_2.10</artifactId>
11.<version>${spark.version}</version>
12.</dependency>
13.<dependency>
14.<groupId>org.apache.spark</groupId>
15.<artifactId>spark-mllib_2.10</artifactId>
16.<version>${spark.version}</version>
17.</dependency>
其版本為:
<hanlp.version>portable-1.3.4</hanlp.version> 、 <spark.version>1.6.0-cdh5.7.3</spark.version> 。
3.2 檔案轉為 UTF-8 編碼及儲存到一個檔案
這部分內容可以直接參考: src/main/java/demo02_transform_encoding.TransformEncodingToOne 這裡的實現,因為是 Java 基本的操作,這裡就不加以分析了。
3.3 Scala 呼叫 HanLP 進行中文分詞
Scala 呼叫 HanLP 進行分詞和 Java 的是一樣的,同時,因為這裡有些詞語格式不正常,所以把這些特殊的詞語新增到自定義詞典中,其示例如下:
1.import com.hankcs.hanlp.dictionary.CustomDictionary
2.import com.hankcs.hanlp.dictionary.stopword.CoreStopWordDictionary
3.import com.hankcs.hanlp.tokenizer.StandardTokenizer
4.import scala.collection.JavaConversions._
5./**
6.* Scala 分詞測試
7.* Created by fansy on 2017/8/25.
8.*/
9.object SegmentDemo {
10.def main(args: Array[String]) {
11.val sentense = "41, 【 日 期 】 19960104 【 版 號 】 1 【 標 題 】合巢蕪高速公路巢蕪段竣工 【 作 者 】彭建中 【 正 文 】 安徽合(肥)巢(湖)蕪(湖)高速公路巢蕪段日前竣工通車並投入營運。合巢蕪 高速公路是國家規劃的京福綜合運輸網的重要幹線路段,是交通部確定1995年建成 的全國10條重點公路之一。該條高速公路正線長88公里。(彭建中) "
12.CustomDictionary.add(" 日 期 ")
13.CustomDictionary.add(" 版 號 ")
14.CustomDictionary.add(" 標 題 ")
15.CustomDictionary.add(" 作 者 ")
16.CustomDictionary.add(" 正 文 ")
17.val list = StandardTokenizer.segment(sentense)
18.CoreStopWordDictionary.apply(list)
19.println(list.map(x => x.word.replaceAll(" ","")).mkString(","))
20.}
21.}
執行完成後,即可得到分詞的結果,如下:
考慮到使用方便,這裡把分詞封裝成一個函式:
1./**
2.* String 分詞
3.* @param sentense
4.* @return
5.*/
6.def transform(sentense:String):List[String] ={
7.val list = StandardTokenizer.segment(sentense)
8.CoreStopWordDictionary.apply(list)
9.list.map(x => x.word.replaceAll(" ","")).toList
10.}
11.}
輸入即是一箇中文的文字,輸出就是分詞的結果,同時去掉了一些常用的停用詞。
3.4 求 TF-IDF
在 Spark 裡面求 TF-IDF ,可以直接呼叫 Spark 內建的演算法模組即可,同時在 Spark 的該演算法模組中還對求得的結果進行了維度變換(可以理解為特徵選擇或“降維”,當然這裡的降維可能是提升維度)。程式碼如下:
1.val docs = sc.textFile(input_data).map{x => val t = x.split(".txt\t");(t(0),transform(t(1)))}
2..toDF("fileName", "sentence_words")
3.
4.// 3. 求 TF
5.println("calculating TF ...")
6.val hashingTF = new HashingTF()
7..setInputCol("sentence_words").setOutputCol("rawFeatures").setNumFeatures(numFeatures)
8.val featurizedData = hashingTF.transform(docs)
9.
10.// 4. 求 IDF
11.println("calculating IDF ...")
12.val idf = new IDF().setInputCol("rawFeatures").setOutputCol("features")
13.val idfModel = idf.fit(featurizedData)
14.val rescaledData = idfModel.transform(featurizedData).cache()
變數 docs 是一個 DataFrame[fileName, sentence_words] , 經過 HashingTF 後,變成了變數 featurizedData , 同樣是一個 DataFrame[fileName,sentence_words, rawFeatures] 。這裡通過 setInputCol 以及 SetOutputCol 可以設定輸入以及輸出列名(列名是針對 DataFrame 來說的,不知道的可以看下 DataFrame 的 API )。
接著,經過 IDF 模型,得到變數 rescaledData ,其 DataFrame[fileName,sentence_words, rawFeatures, features] 。
執行結果為:
3.5 建立 KMeans 模型
直接參考官網給定例子即可:
1.println("creating kmeans model ...")
2.val kmeans = new KMeans().setK(k).setSeed(1L)
3.val model = kmeans.fit(rescaledData)
4.// Evaluate clustering by computing Within Set Sum of Squared Errors.
5.println("calculating wssse ...")
6.val WSSSE = model.computeCost(rescaledData)
7.println(s"Within Set Sum of Squared Errors = $WSSSE")
這裡有計算 cost 值的,但是這個值評估不是很準確,比如我 numFeature 設定為 2000 的話,那麼這個值就很大,但是其實其正確率會比較大的。
3.6 模型評估
這裡的模型評估直接使用一個小李子來說明:比如,現在有這樣的資料:
其中, 1 開頭, 2 開頭和 4 開頭的屬於同一類文件,後面的 0,3,2,1 等,代表這個文件被模型分類的結果,那麼可以很容易的看出針對 1 開頭的文件,
其分類正確的有 4 個,其中 ("123.txt",3) 以及(“ 126.txt ” ,1 )是分類錯誤的結果,這是因為,在這個類別中預測的結果中 是最多的,所以 是和 1 開頭的文件對應起來的,這也就是前面的假設。
1. 把同一類文件分到同一個 partition 中;
1.val data = sc.parallelize(t)
2.val file_index = data.map(_._1.charAt(0)).distinct.zipWithIndex().collect().toMap
3.println(file_index)
4.val partitionData = data.partitionBy(MyPartitioner(file_index))
這裡的 file_index ,是對不同類的文件進行編號,這個編號就對應每個 partition ,看 MyPartitioner 的實現:
1.case class MyPartitioner(file_index:Map[Char,Long]) extends Partitioner
2.override def getPartition(key: Any): Int = key match {
3.case _ => file_index.getOrElse(key.toString.charAt(0),0L).toInt
4.}
5..override def numPartitions: Int = file_index.size
6.}
2. 針對每個 partition 進行整合操作:
在整合每個 partition 之前,我們先看下我們自定義的 MyPartitioner 是否在正常工作,可以列印下結果:
1.val tt = partitionData.mapPartitionsWithIndex((index: Int, it: Iterator[(String,Int)]) => it.toList.map(x => (index,x)).toIterator)
2.tt.collect().foreach(println(_))
執行如下:
其中第一列代表每個 partition 的 id ,第二列是資料,發現其資料確實是按照預期進行處理的;接著可以針對每個 partition 進行資料整合:
1.// firstCharInFileName , firstCharInFileName - predictType
2.val combined = partitionData.map(x =>( (x._1.charAt(0), Integer.parseInt(x._1.charAt(0)+"") - x._2),1) )
3..mapPartitions{f => var aMap = Map[(Char,Int),Int]();
4.for(t <- f){
5.if (aMap.contains(t._1)){
6.aMap = aMap.updated(t._1,aMap.getOrElse(t._1,0)+1)
7.}else{
8.aMap = aMap + t
9.}
10.}
11.val aList = aMap.toList
12.val total= aList.map(_._2).sum
13.val total_right = aList.map(_._2).max
14.List((aList.head._1._1,total,total_right)).toIterator
15.// aMap.toIterator // 列印各個 partition 的總結
16. }
在整合之前先執行一個 map 操作,把資料變成( (fileNameFirstChar, fileNameFirstChar.toInt - predictId), 1 ),其中 fileNameFirstChar 代表檔案的第一個字元,其實也就是檔案的所屬實際類別,後面的 fileNameFirstChar.toInt-predictId 其實就是判斷預測的結果是否對了,這個值的眾數就是預測對的;最後一個值程式碼前面的這個鍵值對出現的次數,其實就是統計屬於某個類別的實際檔案個數以及預測對的檔案個數,分別對應上面的 total 和 total_right 變數;輸出結果為:
(4,6,3)
(1,6,4)
(2,6,4)
發現其列印的結果是正確的,第一列代表檔名開頭,第二個代表屬於這個檔案的個數,第三列代表預測正確的個數
這裡需要注意的是,這裡因為文字的實際類別和檔名是一致的,所以才可以這樣處理,如果實際資料的話,那麼 mapPartitions 函式需要更改。
3. 針對資料結果進行統計:
最後只需要進行簡單的計算即可:
1.for(re <- result ){
2.println(" 文件 "+re._1+" 開頭的 文件總數: "+ re._2+", 分類正確的有: "+re._3+", 分類正確率是: "+(re._3*100.0/re._2)+"%")
3.}
4.val averageRate = result.map(_._3).sum *100.0 / result.map(_._2).sum
5.println(" 平均正確率為: "+averageRate+"%")
輸出結果為:
4. 實驗
設定不同的 numFeature ,比如使用 200 和 2000 ,其對比結果為:
所以設定 numFeatures 值越大,其準確率也越高,不過計算也比較複雜。
5. 總結
1. HanLP 的使用相對比較簡單,這裡只使用了分詞及停用詞,感謝開源;
2. Spark 裡面的 TF-IDF 以及 Word2Vector 使用比較簡單,不過使用這個需要先分詞;
3. 這裡是在 IDEA 裡面執行的,如果使用 Spark-submit 的提交方式,那麼需要把 hanpl 的 jar 包加入,這個有待驗證
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31524777/viewspace-2219569/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 利用transformer進行中文文字分類(資料集是復旦中文語料)ORM文字分類
- 利用TfidfVectorizer進行中文文字分類(資料集是復旦中文語料)文字分類
- EM 演算法-對鳶尾花資料進行聚類演算法聚類
- pyhanlp 文字聚類詳細介紹HanLP聚類
- 應用聚類模型獲得聊天機器人語料聚類模型機器人
- 中文自然語言處理工具hanlp隱馬角色標註詳解自然語言處理HanLP
- 文字挖掘和文字分析的九大應用場景
- Hanlp分詞之CRF中文詞法分析詳解HanLP分詞CRF詞法分析
- Spark構建聚類模型(二)Spark聚類模型
- Kotlin 程式語言詳解:特點、應用領域及語法教程Kotlin
- unit3 文字聚類聚類
- Spark中的聚類演算法Spark聚類演算法
- Spring Boot中對自然語言處理工具包hanlp的呼叫詳解Spring Boot自然語言處理HanLP
- Java使用Collections對中文字元進行首字母排序Java字元排序
- hanlp原始碼解析之中文分詞演算法詳解HanLP原始碼中文分詞演算法
- 第一個spark應用開發詳解(java版)SparkJava
- 文字挖掘之語料庫、分詞、詞頻統計分詞
- 自然語言處理入門基礎之hanlp詳解自然語言處理HanLP
- Solaris中對tar.z進行安裝解除安裝教程詳解
- 如何使用Python、Transformers和scikit-learn對文字進行分類?PythonORM
- 【python資料探勘課程】二十四.KMeans文字聚類分析互動百科語料Python聚類
- FCM聚類演算法詳解(Python實現iris資料集)聚類演算法Python
- Java利用hanlp完成語句相似度分析的方法詳解JavaHanLP
- .Net for Spark 實現 WordCount 應用及除錯入坑詳解Spark除錯
- [Tools] 使用 Charles 對 Android 應用進行 HTTPS 資料抓包AndroidHTTP
- 乾貨分享!手把手教你構建用於文字聚類任務的大規模、高質量語料聚類
- NLPIR平臺的文字聚類模組完美契合行業需求聚類行業
- 【微信小程式開發(6)--- 長按文字進行賦值 selectable, 對文字內容進行解碼】微信小程式賦值
- 文字圖Tranformer在文字分類中的應用ORM文字分類
- Go 語言 sync 包的應用詳解Go
- hanlp中文智慧分詞自動識別文字提取例項HanLP分詞
- [大資料] Spark架構詳解大資料Spark架構
- 基於Spark對消費者行為資料進行資料分析開發案例Spark
- 推薦系統中的產品聚類:一種文字聚類的方法聚類
- 系統學習NLP(二十)--文字聚類聚類
- 對傳統應用進行容器化改造
- 如何使用Nginx對Artifactory進行http應用NginxHTTP
- 用Spark進行實時流計算Spark