1. 隱含狄利克雷分佈(Latent Dirichlet allocation,LDA)
1)LDA 屬於無監督學習,所有的主題並不需要事先指定,是在聚類過程中逐漸形成的 。
2)MLlib 的 LDA 使用了 GraphX 來提高計算效率,儘管它的輸入和輸出都不是圖。
3)LDA 是基於隱含變量的,在這裏隱含變量指的是算法自動推斷出來的“主題”。這些主題由一些與之關聯的單詞描述,但並不具有明確的主題名稱 。
4)一旦主題被推斷出來,每篇文檔在不同主題上會有不 同的分數。這是 LDA 的一個基本原則和假設:每篇文檔同時會在所有不同的主題上有所涉及,而不僅僅是其中一兩個。
2. LDA實戰——路透社電報新聞分類
1)下載數據:
在如下網址:
https://archive.ics.uci.edu/ml/machine-learning-databases/
下載數據:
reuters21578.tar.gz
2)使用命令解壓其中單個文件reut2-000.sgm
tar -xzvf reuters21578.tar.gz reut2-000.sgm
3)使用命令對文件數據進行清洗,文件名爲rcorpus
cat reut2-000.sgm | tr '\n' ' ' | sed -e 's/<\/BODY>/\n/g' | sed -e 's/^.*<BODY>//' | tr -cd '[[:alpha:]] \n' >rcorpus
4)生成詞袋
(1)過濾停用詞(stop words)
停用詞是指過於普遍,而攜帶的信息量有限
一般使用停用詞詞表,但本次實驗中只是簡單地將一些較短(少於或等於 5 個字母〉的單詞過濾掉,同時也會過濾掉 “ Reuter”這個特殊單詞。
(2)在函數 bagsFrornDocurnentPerLine()裏,所有內容經過 split()操作後會轉化成傳統的 Scala 集合(而不是 ROD )。因此,後續採用的是 groupBy()這種 Scala 集合上的操作方法,而不是 ROD 上的操作方法 groupByKey()
(3)實現代碼:
import org.apache.spark.mllib.linalg._
import org.apache.spark.mllib.clustering._
import org.apache.spark.rdd._
def bagsFromDocumentPerLine(filename:String) =
sc.textFile(filename)
// 對每一行,按空格分割出每個單詞
.map(_.split(" ")
// 過濾操作,過濾掉長度小於等於5和等於reuter的單詞
.filter(x => x.length > 5 && x.toLowerCase != "reuter")
// 全部轉化爲小寫
.map(_.toLowerCase)
// 根據單詞進行分組
.groupBy(x => x)
.toList
// 轉化得到一個(單詞,出現次數)的集合
.map(x => (x._1, x._2.size)))
// 將文件rcorpus的路徑傳入
val rddBags:RDD[List[Tuple2[String,Int]]] =
bagsFromDocumentPerLine("file:///usr/local/spark/input/rcorpus")
val vocab:Array[Tuple2[String,Long]] =
rddBags.flatMap(x => x)
.reduceByKey(_ + _)
.map(_._1)
// zipWithIndex 給每篇文檔附上文檔ID
.zipWithIndex
.collect
// 前半部分是將詞典裏字符串形式的單詞轉換成對應的索引下標
// 後半部分是將它轉換爲LDA可處理的稀疏向量(SparseVector)形式
// 後半部分:先將詞袋轉換成稀疏向量,然後使用zipWithIndex()給每篇文檔附上文檔ID
def codeBags(rddBags:RDD[List[Tuple2[String,Int]]]) =
rddBags.map(x => (x ++ vocab).groupBy(_._1)
.filter(_._2.size > 1)
.map(x => (x._2(1)._2.asInstanceOf[Long].toInt, x._2(0)._2.asInstanceOf[Int].toDouble))
.toList)
.zipWithIndex.map(x => (x._2, new SparseVector(
vocab.size, x._1.map(_._1).toArray, x._1.map(_._2).toArray)
.asInstanceOf[Vector]))
// setK(5)是將文檔聚類到5個主題上
// run()返回的是一個機器學習模型,裏面提供了需要的多種信息:每個主題的描述單詞清單,每篇文檔屬於不同主題的程度。
val model = new LDA().setK(5).run(codeBags(rddBags))
// 查看每個主題的最相關的前6個單詞
model.describeTopics(6).map(_._1.map(vocab(_)._1))
執行結果:
res1: Array[Array[String]] = Array(
Array(billion, exchange, trading, market, system, credit),
Array(tonnes, billion, production, market, exports, january),
Array(billion, president, government, american, economic, analysts),
Array(company, shares, common, quarter, management, profit),
Array(billion, prices, agreement, report, coffee, september))
觀察第一篇文檔的主題分佈情況。由於 topicDistributions()函數會改變文檔的順序,所以需要用 filter()來獲得文檔 ID 爲 0 的文章:
model.asInstanceOf[DistributedLDAModel].topicDistributions.filter(_._1 == 0).collect
執行結果:一個Array,前者是文檔ID,後者是和5個主題的相關性
res2: Array[(Long, org.apache.spark.mllib.linalg.Vector)] = Array((0,[0.05294417725510889,0.1639072664938587,0.05902558780467674,0.026385449794035468,0.6977375186523203]))
3. 使用上述訓練好的模型對沒有見過的文章進行主題預測
1)首先將存儲在集羣中的 DistributedLDAModel (分佈式 LDA 模型),轉換成完全存儲在driver上的 LocalLDAModel (本地 LDA 模型),因爲預測函數僅可使用LocalLDAModel 。
2)選擇了reut2-001.sgm中的第一篇文章進行test。
3)將模型轉換爲 LocalLDAModel 並進行主題預測 :
model.asInstanceOf[DistributedLDAModel].toLocal.topicDistributions(codeBags(bagsFromDocumentPerLine("file:///usr/local/spark/input/test"))).collect
執行結果:
res3: Array[(Long, org.apache.spark.mllib.linalg.Vector)] = Array((0,[0.18995659732316772,0.24368239811339498,0.18208886519362716,0.21355056631476005,0.1707215730550501]))
4. 如何在LDA實現中使用圖(用圖來代替LDA中的稀疏舉證)
1)在 Spark 1.6 中, LDA 可以使用兩個不同的算法來實現:默認的爲基於圖的最大期望( EM )算法 ,或者是在線變分貝葉斯算法。在這裏我們會着重討論 EM 算法 。EM 算法被用於解決存在有未知或隱藏變量的概率問題,正如 LDA 模型裏的隱含變量。 LDA 建立了一個概率方程組 , 如一個單詞屬於一個特定主題的概率和一個單詞在一篇特定文檔中 的概率。
2)求解上述概率方程組時, EM 算法的兩個主要步驟是,期望和最大化,運作的方式與 K-Means 方法類似。在期望步驟中,我們會對一些變量進行猜測或估計。然後,在最大化步驟中 , 計算對應的誤差,這些變量會被相應地進行調整。這樣的過程經過多次迭代後,在期望步驟中做出的猜測會越來越接近真實的概率值 。
代碼語言:scala
參考書籍:Spark GraphX in Action