Spark MLlib 1.6 -- 特徵抽取和變換

·  TF-IDF

·  Word2Vec

·       Model

·       Example

·  StandardScaler

·       Model Fitting

·       Example

·  Normalizer

·       Example

·  ChiSqSelector

·       Model Fitting

·       Example

·  ElementwiseProduct

·       Example

·  PCA

·       Example

 

7.1 TF-IDF

TF-IDF是一種特徵向量化方法,這種方法多用於文本挖掘,通過算法可以反應出詞在語料庫中某個文檔中的重要性。文檔中詞記爲t,文檔記爲d , 語料庫記爲D . 詞頻TF(t,d) 是詞t 在文檔d 中出現的次數。文檔頻次DF(t,D) 是語料庫中包括詞t的文檔數。如果使用詞在文檔中出現的頻次表示詞的重要程度,那麼很容易取出反例,即有些詞出現頻率高反而沒多少信息量, 如,”a” , “the” , “of” 。如果一個詞在語料庫中出現頻率高,說明它在特定文檔集中信息量很低。逆文檔頻次(inverse document frequency)是詞所能提供的信息量的一種度量:

IDF(t,D) = log frac { | D | + 1 } { DF(t,D) + 1 }

此處| D | 是語料庫中總的文檔數,注意到,公式中使用log函數,當詞出現在所有文檔中時,它的IDF值變爲0. IDF加一個防止在此情況下分母爲0. TF-IDF 度量值表示如下:

       TFIDF(t,d,D) = TF(t,d) \Dot  IDF(t,D)

對於TF  IDF 定義有多種,spark.mllib 中,分開定義TF IDF 

 

Spark.mllib 中實現詞頻率統計使用特徵hash的方式,原始的特徵通過hash函數,映射到一個索引值。後面只需要統計這些索引值的頻率,就可以知道對應詞的頻率。這種方式避免設計一個全局11的詞到索引的映射,這個映射在映射大量語料庫時需要花費更長的時間。但需要注意,通過hash的方式可能會映射到同一個值的情況,即不同的原始特徵通過Hash映射後是同一個值。爲了降低這種情況出現的概率,我們只能對特徵向量升維。i.e., hash表的桶數,默認特徵維度是 2^20 = 1,048,576.

注意:spark.mllib 不支持文本分段,詳見 Stanford nlp group http://nlp.stanford.edu/ scalanlp/chalk :https://github.com/scalanlp/chalk

 

TF實際是統計詞hash之後索引值的頻次,可使用HashingTF 方法並傳入RDD[Iterable[_]]  IDF 需要使用IDF方法。需要注意,每條記錄是可iterable的字符串或其它類型。

HashingTF Scaladocs :http://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.mllib.feature.HashingTF

importorg.apache.spark.rdd.RDD

importorg.apache.spark.SparkContext

importorg.apache.spark.mllib.feature.HashingTF

importorg.apache.spark.mllib.linalg.Vector

 

val sc:SparkContext=...

 

// Loaddocuments (one per line).

val documents:RDD[Seq[String]]= sc.textFile("...").map(_.split("").toSeq)

 

val hashingTF=newHashingTF()

val tf:RDD[Vector]= hashingTF.transform(documents)

HashingTF 方法只需要一次數據交互,而IDF需要兩次數據交互:第一次計算IDF向量,第二次需要和詞頻次相乘

importorg.apache.spark.mllib.feature.IDF

 

// ...continue from the previous example

tf.cache()

val idf =newIDF().fit(tf)

val tfidf:RDD[Vector]= idf.transform(tf)

spark.mllib 支持乎略詞頻低於文檔最小數,需要把minDocFreq這個數傳給IDF構架函數。在此情況下,對應的IDF值設置爲0

importorg.apache.spark.mllib.feature.IDF

 

// ...continue from the previous example

tf.cache()

val idf =newIDF(minDocFreq=2).fit(tf)

val tfidf:RDD[Vector]= idf.transform(tf)

 

7.2 Word2Vect (詞到向量)

Word2Vec 計算詞表徵向量的分佈,這樣可以利用相似相近的詞表徵分佈在鄰近的向量空間,好處就是易於產生新型模型,且模型預測的誤差也容易解釋。向量分佈在自然語言處理中是很有用的,特定像命名實體識別,歧義消除,句法分析,詞性標記和機器翻譯。

 

7.2.1 模型

Word2vec 的實現中,我們使用skip-gram模型。Skip-gram的訓練目標是學習詞表徵向量分佈,這個分佈可以用來預測句子所在的語鏡。數學上,給定一組訓練詞w_1, … w_T ,skip-gram模型的目標是最大化平均log-似然。

\Frac{1}{T}  \Sigma|_{t= 1} ^{T}  \Sigma|_{j = -k} ^{j = k}  log {p(w_{t+j} | w_{t})}

此處 k 是訓練樣本窗口。

skip-gram模型中,每個單詞關聯兩個向量u_w v_w ,其中u_w是單詞w的向量表示,v_w是單詞對應的語境。對於給定的單詞w_j 計算預測結果的正確概率由以下softmax 模型。

   P(w_i|w_j) = \Frac{exp(u |_{w_i} |^Tv_{w_j})} {\Sigma|_{ l =1} |^V exp( u |_l |^T v_{w_j})}

此處V  是詞組總數

 

使用softmax計算skip-gram模型的很耗時,因爲log{ p(w_i | w_j ) } 正比於的大小,並且很容易就達到上百萬計算。爲了加速Word2Vec我們使用分層softmax , 此方法可以降低計算複雜度,從原來的log p(w_i | w_j)O(log(V)).

                   

 

7.2.2 例子

下例子列舉如何加載文本文件,將文本內容存放到RDD[Seq[String]]RDD構造一個Word2Vec實例,將輸入數據送入此實例訓練得到Word2VecModel模型。最終,我們展示特定詞的前40個同義詞。爲了運行這個例子,首先下載text8(http://mattmahoney.net/dc/text8.zip數據,解壓到特定的目錄下。此處我們假設解壓出來的文件還叫text8,並且在當前目錄。

Word2Vec ScalaDocs API :http://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.mllib.feature.Word2Vec

importorg.apache.spark._

importorg.apache.spark.rdd._

importorg.apache.spark.SparkContext._

importorg.apache.spark.mllib.feature.{Word2Vec,Word2VecModel}

 

val input = sc.textFile("text8").map(line => line.split("").toSeq)

 

val word2vec =newWord2Vec()

 

val model = word2vec.fit(input)

 

val synonyms = model.findSynonyms("china",40)

 

for((synonym,cosineSimilarity)<- synonyms){

 println(s"$synonym$cosineSimilarity")

}

 

// Saveand load model

model.save(sc,"myModelPath")

val sameModel=Word2VecModel.load(sc,"myModelPath")

 

7.3 standardscaler標準化

標準化是通過變化將原始數據放縮到單位方差,通過平移數據得到均值爲0(如果原數據均值不爲0,需要對採樣數據求出樣本均值,將原始數據減雲樣本均值,即得到均值爲0的新數據)。

例如,支持向量機的RBF 核,L1L2空間的正則線性模型,這兩個例子很能說明問題,經過標準化所有特徵的計算能得到更好的結果。

標準化後的數據,在最優化過程中會更快的收斂,同時也會在模型訓練時防止方差大的數據對整體數據的影響。

 

7.3.1 模型擬合

標準化需要配置以下參數:

1 withMean 默認是假(false)在標準化之前將原始數據以均值爲中心,這樣會使標準化後的數據分佈相對緊密些,這種方法不適合於稀鬆的數據集,否則會觸發異常。

2 withStd 默認是真(true) , 意味將數據標準化到單位方差。

 

StandardScaler 中提供一個擬合方法將RDD[Vector]作爲輸入,學習輸入的統計信息,將輸入集合變換成單位標準差,變換結果可能(也可能不是)均值爲,通過配置StandardScaler 來實現。

 

模型支持VectorTransformer 可以將標準向量變換成新的向量,或者將RDD[Vector] 變換到新的RDD[Vector]

如果特徵向量某個維度的方差爲0,則特徵向量這個維度的變換結果仍然是0.0

 

7.3.2 例子

下例展示如何加載libsvm格式數據,將數據標準化後得到新的向量,此新向量的標準差是1,均值可能(也可能不是) 0 

StandardScalerScala docs API :http://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.mllib.feature.StandardScaler

importorg.apache.spark.SparkContext._

importorg.apache.spark.mllib.feature.StandardScaler

importorg.apache.spark.mllib.linalg.Vectors

importorg.apache.spark.mllib.util.MLUtils

 

val data =MLUtils.loadLibSVMFile(sc,"data/mllib/sample_libsvm_data.txt")

 

val scaler1 =newStandardScaler().fit(data.map(=> x.features))

val scaler2 =newStandardScaler(withMean =true, withStd =true).fit(data.map(=> x.features))

// scaler3is an identical model to scaler2, and will produce identical transformations

val scaler3 =newStandardScalerModel(scaler2.std, scaler2.mean)

 

// data1will be unit variance.

val data1 = data.map(=>(x.label, scaler1.transform(x.features)))

 

// Withoutconverting the features into dense vectors, transformation with zero mean willraise

//exception on sparse vector.

// data2will be unit variance and zero mean.

val data2 = data.map(=>(x.label, scaler2.transform(Vectors.dense(x.features.toArray))))

 

7.4 正規化

將個別樣本正規化爲單位L^p 範數,  在文本分類和聚類中經常使用。例如, L^2 空間正規化 TF-IDF向量的點積,可以看作兩個向量的cos-相似度.s

 

正規化可配置參數:

1) p L^p 空間向量正規化,默認p = 2

模型支持VectorTransformer 可以將標準向量變換成新的向量,或者將RDD[Vector] 變換到新的RDD[Vector]

如果輸入向量範數爲0,則直接返回輸入向量

 

7.4.1 例子

下例展示如何加載libsvm格式數據,將數據正規化爲L^2 範數, L^\{Infinit} 範數

Normalizer ScalaDocs API :http://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.mllib.feature.Normalizer

importorg.apache.spark.SparkContext._

importorg.apache.spark.mllib.feature.Normalizer

importorg.apache.spark.mllib.linalg.Vectors

importorg.apache.spark.mllib.util.MLUtils

 

val data =MLUtils.loadLibSVMFile(sc,"data/mllib/sample_libsvm_data.txt")

 

val normalizer1=newNormalizer()

val normalizer2=newNormalizer(=Double.PositiveInfinity)

 

// Eachsample in data1 will be normalized using $L^2$ norm.

val data1 = data.map(=>(x.label, normalizer1.transform(x.features)))

 

// Eachsample in data2 will be normalized using $L^\infty$ norm.

val data2 = data.map(=>(x.label, normalizer2.transform(x.features)))

 

7.5ChiSqSelector(ChiSq選擇器)

在模型構造階段,特徵選擇從特徵向量中剔除相關的維度,即對特徵空間進行降維,這樣可以加速迭代過程,並提升學習效率。

 

ChiSqSelector 實現基於chi-squared 的特徵選擇器,它處理歸類特徵的類標籤,ChiSqSelector 基於Chi-Squared檢驗對特徵進行排序,而不直接考慮特徵向量的類別,選取排序靠前的特徵向量,因爲這些特徵向量能很好的決定類別標籤。這就好比選取對分類有決定意義的特徵向量。

在實際中,選取檢驗集可以優化特徵的數量。(?)

 

7.5.1 模型擬合

ChiSqSelector 算法配置 numTopFeatures 參數來確定選取排名前多少個特徵向量。

擬合方法的輸入是歸類特徵的RDD[LabeledPoint],通過學習統計信息,返回ChiSqSelectorModel模型,這個模型可以用於對特徵空間進行降維。這個模型可以處理輸入Vector,得到降維後的Vector , 或者對RDD[Vector] 進行降維。

 

當然,也可以構造一個特徵索引(索引按升序排列)對這個索引的數組訓練ChiSqSelectorModel模型。

 

7.5.2例子

下例展現ChiSqSelector的基礎應用,輸入矩陣的每個元素的範圍 0 ~ 255 

ChiSqSelectorScala Docs :http://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.mllib.feature.ChiSqSelector

importorg.apache.spark.SparkContext._
importorg.apache.spark.mllib.linalg.Vectors
importorg.apache.spark.mllib.regression.LabeledPoint
importorg.apache.spark.mllib.util.MLUtils
importorg.apache.spark.mllib.feature.ChiSqSelector
 
// Load some data in libsvm format
valdata=MLUtils.loadLibSVMFile(sc,"data/mllib/sample_libsvm_data.txt")
// Discretize data in 16 equal bins since ChiSqSelector requires categorical features
// Even though features are doubles, the ChiSqSelector treats each unique value as a category
valdiscretizedData=data.map{lp=>
  LabeledPoint(lp.label,Vectors.dense(lp.features.toArray.map{x=>(x/16).floor}))
}
// Create ChiSqSelector that will select top 50 of 692 features
valselector=newChiSqSelector(50)
// Create ChiSqSelector model (selecting features)
valtransformer=selector.fit(discretizedData)
// Filter the top 50 features from each feature vector
valfilteredData=discretizedData.map{lp=>
  LabeledPoint(lp.label,transformer.transform(lp.features))
}

 

7.6 Hadamard乘積(ElementwiseProduct)

ElementwiseProduct對輸入向量的每個元素乘以一個權重向量的每個元素,對輸入向量每個元素逐個進行放縮。這個稱爲對輸入向量和變換向量scalingVec 使用Hadamard product(阿達瑪積)進行變換,最終產生一個新的向量。用向量 w 表示 scalingVec ,則Hadamard product可以表示爲

 

Vect(v_1, … , v_N)\o Vect(w_1, … , w_N) = Vect(v_1 w_1, … , v_N w_N)

 

Hamard 乘積需要配置一個權向量 scalingVec

1) scalingVec 變換向量

ElementwiseProduct實現 VectorTransformer 方法,就可以對向量乘以權向量,得到新的向量,或者對RDD[Vector] 乘以權向量得到RDD[Vector]

 

7.6.1 例子

下例展示如何對向量進行ElementwiseProduct變換

ElementwiseProductScala Docs API  :http://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.mllib.feature.ElementwiseProduct

importorg.apache.spark.SparkContext._
importorg.apache.spark.mllib.feature.ElementwiseProduct
importorg.apache.spark.mllib.linalg.Vectors
 
// Create some vector data; also works for sparse vectors
valdata=sc.parallelize(Array(Vectors.dense(1.0,2.0,3.0),Vectors.dense(4.0,5.0,6.0)))
 
valtransformingVector=Vectors.dense(0.0,1.0,2.0)
valtransformer=newElementwiseProduct(transformingVector)
 
// Batch transform and per-row transform give the same results:
valtransformedData=transformer.transform(data)
valtransformedData2=data.map(x=>transformer.transform(x))

 

7.7 PCA

PCA可以將特徵向量投影到低維空間,實現對特徵向量的降維。

 

7.7.1 例子

下例展示如何計算特徵向量空間的主成分,使用主成分對向量投影到低維空間,同時保留向量的類標籤。

PCA Scala DocsAPI : http://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.mllib.feature.PCA

importorg.apache.spark.mllib.regression.LinearRegressionWithSGD
importorg.apache.spark.mllib.regression.LabeledPoint
importorg.apache.spark.mllib.linalg.Vectors
importorg.apache.spark.mllib.feature.PCA
 
valdata=sc.textFile("data/mllib/ridge-data/lpsa.data").map{line=>
  valparts=line.split(',')
  LabeledPoint(parts(0).toDouble,Vectors.dense(parts(1).split(' ').map(_.toDouble)))
}.cache()
 
valsplits=data.randomSplit(Array(0.6,0.4),seed=11L)
valtraining=splits(0).cache()
valtest=splits(1)
 
valpca=newPCA(training.first().features.size/2).fit(data.map(_.features))
valtraining_pca=training.map(p=>p.copy(features=pca.transform(p.features)))
valtest_pca=test.map(p=>p.copy(features=pca.transform(p.features)))
 
valnumIterations=100
valmodel=LinearRegressionWithSGD.train(training,numIterations)
valmodel_pca=LinearRegressionWithSGD.train(training_pca,numIterations)
 
valvaluesAndPreds=test.map{point=>
  valscore=model.predict(point.features)
  (score,point.label)
}
 
valvaluesAndPreds_pca=test_pca.map{point=>
  valscore=model_pca.predict(point.features)
  (score,point.label)
}
 
valMSE=valuesAndPreds.map{case(v,p)=>math.pow((v-p),2)}.mean()
valMSE_pca=valuesAndPreds_pca.map{case(v,p)=>math.pow((v-p),2)}.mean()
 
println("Mean Squared Error = "+MSE)
println("PCA Mean Squared Error = "+MSE_pca)

 

發佈了44 篇原創文章 · 獲贊 34 · 訪問量 104萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章