特徵向量的提取請看我的之前的一篇博客:
https://blog.csdn.net/LOG_IN_ME/article/details/103047796
特徵向量提取結果如下圖:
這個DataFrame的“TF-IDF”列就是提取的特徵向量。
我們把該列取出來:
val TFIDFResult: DataFrame = idfModel.transform(featureVec).select("TF-IDF")
TFIDFResult.show()
1、如何理解TFIDFResult?
TFIDFResult字面量即爲最後一列的DataFrame,每一行的數據類型爲GenericRowWithSchema(GenericRowWithSchema繼承自 org.apache.spark.sql.Row)。
關於GenericRowWithSchema的操作可參考:https://blog.csdn.net/Code_LT/article/details/87719115
println(TFIDFResult.first())
我們希望得到一個向量,而上圖所示得到的是一個row,兩者格式有差別,row外面套有中括號[],裏面的()纔是向量。
所以可以使用row的.get()方法按索引取出GenericRowWithSchema裏的值:
println(TFIDFResult.first().get(0))
取出來後發現GenericRowWithSchema裏面是一個org.apache.spark.ml.linalg.SparseVector類型的稀疏向量,纔是我們需要的。
(由於我提取特徵向量是使用 import org.apache.spark.ml.feature.{HashingTF, IDF} ml庫下的類,所以裝在GenericRowWithSchema裏的是ml庫下的向量,並且是稀疏向量SparseVector(因爲我提取特徵哈希的桶數遠大於分得的詞數)。)
【悟】
一開始我一直想要把()裏第3項也就是TFIDF值的列表取出來計算,我一直以爲那個是特徵向量,但等我輸出數據類型的時候才頓悟:
()整體就是SparseVector類型,第一項2000意義是這個稀疏向量是2000維的(哈希的時候桶數爲2000),第二項是該詞袋下每個詞的哈希值,第三項裏面是與第二項一一對應的TFIDF值。整個()描述的就是一個向量整體!
2、使用什麼距離來計算相似度?
有了特徵向量,下面就要計算特徵向量之間的相似。
spark.mllib支持兩種相關係數的計算,一個是皮爾遜係數(pearson),一個是斯皮爾曼係數(spearman)。這兩個係數都是相關係數大於0表示兩個向量正相關,小於0表示兩個向量負相關,0表示兩個向量沒有相關性。
【注意】spark.ml暫時還沒有這個相關係數計算的API,這是一個大麻煩。
關於這兩個係數比較解釋可以參考以下三篇博客:
https://blog.csdn.net/lambsnow/article/details/79972145
https://blog.csdn.net/t15600624671/article/details/77247822
https://blog.csdn.net/qq_36384657/article/details/100117039
pearson:
spearman:
總體來說:皮爾遜係數可以理解爲對兩個向量進行歸一化後,計算其餘弦距離。但是變量的標準差不能爲0(分母不能爲0),也就是說你的兩個變量中任何一個的值不能都是相同的;實驗數據之間的差距不能太大,或者說皮爾森相關性係數受異常值的影響比較大;並且數據是建立在線性相關的基礎上,一般指直線,若是曲線則要求兩變量數據的間距相同或者數據取自於正態分佈數據中。
斯皮爾曼相關性係數,通常也叫斯皮爾曼秩相關係數。“秩”,可以理解成就是一種順序或者排序,那麼它就是根據原始數據的排序位置進行求解,這種表徵形式就沒有了求皮爾森相關性係數時那些限制。對極端數據,異常數據不敏感。
我們將採用斯皮爾曼相關性係數。
由於spark.mllib庫使用RDD,而我們使用spark.ml庫的DataFrame得到特徵向量,要想使用mllib庫下的相關性計算API,就必須要把DataFrame轉換成RDD。
3、怎麼將DataFrame轉換成RDD?
使用DataFrame的.rdd
4、提取特徵向量RDD
//特徵向量rdd
var feature:RDD[Vector] = TFIDFResult
.rdd.map(row=>{
org.apache.spark.mllib.linalg.Vectors.fromML(row.get(0).asInstanceOf[org.apache.spark.ml.linalg.Vector])
})
【注意】
1、首先feature要顯式聲明數據類型:RDD[Vector](意思是RDD裏每一行數據類型都是Vector),否則會被判爲Any類型,在後面調用計算相關係數方法的時候參數類型不符。(一般而言scala變量不需要聲明類型,但是這裏需要明確變量類型,這是一個坑。)
2、.rdd是將TFIDFResult轉換爲RDD類型。
3、.map()操作是對於RDD每一行執行{}裏的函數操作,即將{}(row)裏面的()(向量)提取出來,spark會智能識別函數最後一行作爲返回值。
4、重點來了:
row.get(0)取出的SparseVector前面已經說過是spark.ml.linalg.SparseVector,是ml庫裏的,而feature:RDD[Vector]由於是spark.mllib的RDD,所以對應的要求裏面的Vector是mllib庫裏的spark.mllib.linalg.Vector。所以如何將ml庫的SparseVector轉換成mlllib庫的Vector是一個非常大的問題。
以下一篇文檔挽救了這個數據類型轉換!
http://spark.apache.org/docs/latest/ml-migration-guides.html
該文檔講述的是scala下進行ml與mllib兩個庫之間相應數據類型的轉換,同時也提供了Java和Python下的轉換方法。
按照文檔裏所提供的方法,我使用org.apache.spark.mllib.linalg.Vectors.fromML()方法可以將org.apache.spark.ml.linalg.Vector類型數據轉換成org.apache.spark.mllib.linalg.Vector。
但是row.get(0)目前是org.apache.spark.ml.linalg.SparseVector而非org.apache.spark.ml.linalg.Vector,那我們需要先做ml庫下的數據類型轉換,同一庫下的數據類型強制轉換使用.asInstanceOf[]。
row.get(0).asInstanceOf[org.apache.spark.ml.linalg.Vector]//spark.ml.linalg.SparseVector --> spark.ml.linalg.Vector
注意不可直接這樣寫:
row.get(0).asInstanceOf[Vector]
因爲我們前面引入的是org.apache.spark.mllib.linalg._,所以只寫Vector會被默認爲mllib庫下的Vector,需要顯式聲明哪個包下的類。
5、輸出RDD看看是什麼樣子:
feature.foreach(println) //RDD輸出使用foreach不用print,print輸出的是數據類型
5、計算相似度矩陣
//計算相似度矩陣
val correlMatrix: Matrix = Statistics.corr(feature,"spearman")//這裏選用斯皮爾曼相關係數,皮爾遜係數輸入"pearson"
println(correlMatrix)
【所有代碼(基於上一個程序)】
加入的包:
import org.apache.spark.mllib.linalg._
import org.apache.spark.mllib.stat.Statistics
import org.apache.spark.rdd.RDD
加在main函數裏的代碼:
val TFIDFResult: DataFrame = idfModel.transform(featureVec).select("TF-IDF")
//特徵向量rdd
var feature:RDD[Vector] = TFIDFResult
.rdd.map(row=>{
org.apache.spark.mllib.linalg.Vectors.fromML(row.get(0).asInstanceOf[org.apache.spark.ml.linalg.Vector])
})
println(feature)
//計算相似度矩陣
val correlMatrix: Matrix = Statistics.corr(feature,"spearman")
println(correlMatrix)