1. 程式人生 > >MLlib分類演算法實戰演練--Spark學習(機器學習)

MLlib分類演算法實戰演練--Spark學習(機器學習)

因為自身原因最近再學習spark MLlib,看的教材是《spark機器學習》,感覺這本書偏入門並且有很多實操,非常適合新手。下面就是我在學習到第五章關於分類演算法的一些要點,最要是通過程式碼實操,具體演算法原理就不介紹。

一、資料來源及開發環境

開發環境:為了方便程式碼管理這裡使用了IDEA整合開發環境,單機進行程式碼除錯感覺很方便嘛,主要環境與我前兩篇部落格中部署的環境一致。

資料來源:機器學習實在中資料的獲取很重要,網際網路上要找到類似資料非常容易。本例項使用的是Kaggle競賽資料(相信學習機器學習的都知道這個比賽)。資料是關於網站點選資料,主要用於推薦的頁面是短暫流行還是長久流行。
下載地址
,下載train.tsv的檔案,需要註冊才能下載。

二、資料預處理

大家下載好資料以後可以通過相應的工具開啟看看資料構成。由於資料中第一行為列名,在演算法中是用不到的,因此將其刪除並存為train_noheader.tsv,linux命令如下:

sed 1d train.tsv >train_noheader.tsv

三、程式碼解析

使用IDEA新建一個scala class,鍵入如下程式碼:

//匯入各種類
import org.apache.spark.mllib.classification.LogisticRegressionWithSGD
import
org.apache.spark.mllib.linalg.Vectors import org.apache.spark.mllib.regression.LabeledPoint import org.apache.spark.{SparkContext, SparkConf} /** * Created by luo on 12/12/15. */ object ML_Classification { def main(args:Array[String]){ //程式碼初始化的一些步驟 val conf=new SparkConf().setAppName("classification"
).setMaster("local[2]") val sc=new SparkContext(conf) val rawData=sc.textFile("/home/luo/sparkLearning/MLData/train_noheader.tsv") val records=rawData.map(_.split("\t"))//資料是以\t分割 val data=records.map{point=> //將資料中的引號全部替換為空 val replaceData=point.map(_.replaceAll("\"","")) //本資料的頭四個欄位不會用到,資料的一個欄位代表分類的結果,1為長久,0為短暫 val label=replaceData(replaceData.size-1).toInt val features=replaceData.slice(4,replaceData.size-1).map(x=>if(x=="?") 0.0 else x.toDouble) //label存分類結果,features存特徵,將其轉換為LabeledPoint型別,此型別主要用於監督學習。 LabeledPoint(label,Vectors.dense(features)) } //使用邏輯迴歸演算法,此演算法使用的是隨機梯度下降演算法進行優化, //當然也可以使用其他的優化演算法,對於演算法原理推薦看看Andrew Ng的視訊 val lrModel=LogisticRegressionWithSGD.train(data,10)//迭代次數10次 //預測並檢視有多少預測正確,這裡的測試集與訓練資料集相同, val predictrueData=data.map{point=> if(lrModel.predict(point.features)==point.label) 1 else 0 }.sum() //求正確率 val accuracy=predictrueData/data.count() println(accuracy) } }

執行之後可以看到正確率為0.5146720757268425(各次計算結果不相同屬於正常情況),這個正確率也太低了吧。接下來介紹優化模型的方法用於提高預測的正確率。

三、特徵標準化

當各類特徵取值大小差距比較大時對預測結果會產生不好的影響。於是採用特徵標準化會提高預測精度,這裡的特徵標準化就是我們常說的歸一化。完成歸一化的工作可以自己編寫程式碼計算,公式就是(x-u)/sqrt(variance)。另外一種方法是採用MLlib內建的方法來標準化,我這裡肯定採用的是後者。

特徵標準化的方法如下:

 val vectors=data.map(p=>p.features)
 //呼叫初始化一個StandardScaler物件,具體使用方法檢視Spark api
    val scaler=new StandardScaler(withMean = true,withStd = true).fit(vectors)
 //重新標準化特徵變數
    val scalerData=data.map(point=>
    LabeledPoint(point.label,scaler.transform(point.features))
    )

進行特徵標準化以後的完整程式碼如下:

import org.apache.spark.mllib.classification.LogisticRegressionWithSGD
import org.apache.spark.mllib.feature.StandardScaler
import org.apache.spark.mllib.linalg.Vectors
import org.apache.spark.mllib.regression.LabeledPoint
import org.apache.spark.{SparkContext, SparkConf}

/**
  * Created by luo on 12/12/15.
  */
object ML_Classification {

  def main(args:Array[String]){

    val conf=new SparkConf().setAppName("classification").setMaster("local[2]")
    val sc=new SparkContext(conf)

    val rawData=sc.textFile("/home/luo/sparkLearning/MLData/train_noheader.tsv")
    val records=rawData.map(_.split("\t"))

    val data=records.map{point=>
      val replaceData=point.map(_.replaceAll("\"",""))
      val label=replaceData(replaceData.size-1).toInt
      val features=replaceData.slice(4,replaceData.size-1).map(x=>if(x=="?") 0.0 else x.toDouble)
      LabeledPoint(label,Vectors.dense(features))
    }

    val vectors=data.map(p=>p.features)
    val scaler=new StandardScaler(withMean = true,withStd = true).fit(vectors)
    val scalerData=data.map(point=>
      LabeledPoint(point.label,scaler.transform(point.features))
    )

    val lrModel=LogisticRegressionWithSGD.train(scalerData,10)

    val predictrueData=scalerData.map{point=>
      if(lrModel.predict(point.features)==point.label) 1 else 0
    }.sum()

    val accuracy=predictrueData/data.count()
    println(accuracy)
  }
}

在IDEA中執行檢視結果為0.6204192021636241,結果比之前要好一些,但是然並卵,還要繼續優化。

四、新增類別特徵

如果有詳細看每一行資料的話會發現每行的第四個特徵為網頁類別,例如“weather”、“sports”、“unkown”等,由於此列資料為文字,在之前的訓練之中我們人為捨棄掉了這一列的資料,但是每一個類別可以考慮的因素不一樣,所以類別對預測精度的影響還是蠻大的。通過一個1×n的向量來表示,屬於哪個類別向量的對應位置為1,其餘為0。程式碼如下:

//資料第三列是類別,先計算總類數,然後建立一個類別到序號的map
val category=records.map(r=>r(3)).distinct().collect().zipWithIndex.toMap

//與之前不同的是添加了一個向量categoryFeatures用於標識類別
    val data=records.map{point=>
      val replaceData=point.map(_.replaceAll("\"",""))
      val label=replaceData(replaceData.size-1).toInt
      val categoriesIndex=category(point(3))
      val categoryFeatures=Array.ofDim[Double](category.size)
      categoryFeatures(categoriesIndex)=1.0
      val otherfeatures=replaceData.slice(4,replaceData.size-1).map(x=>if(x=="?") 0.0 else x.toDouble)
 //RDD之間的加運算使用"++",構建添加了類別標識以後的特徵向量
      val features=otherfeatures++categoryFeatures
      LabeledPoint(label,Vectors.dense(features))
    }

添加了類別標識特徵以後完整程式碼如下:

import org.apache.spark.mllib.classification.LogisticRegressionWithSGD
import org.apache.spark.mllib.feature.StandardScaler
import org.apache.spark.mllib.linalg.Vectors
import org.apache.spark.mllib.regression.LabeledPoint
import org.apache.spark.{SparkContext, SparkConf}

/**
  * Created by luo on 12/12/15.
  */
object ML_Classification {

  def main(args:Array[String]){

    val conf=new SparkConf().setAppName("classification").setMaster("local[2]")
    val sc=new SparkContext(conf)

    val rawData=sc.textFile("/home/luo/sparkLearning/MLData/train_noheader.tsv")
    val records=rawData.map(_.split("\t"))

    val category=records.map(r=>r(3)).distinct().collect().zipWithIndex.toMap

    val data=records.map{point=>
      val replaceData=point.map(_.replaceAll("\"",""))
      val label=replaceData(replaceData.size-1).toInt
      val categoriesIndex=category(point(3))
      val categoryFeatures=Array.ofDim[Double](category.size)
      categoryFeatures(categoriesIndex)=1.0
      val otherfeatures=replaceData.slice(4,replaceData.size-1).map(x=>if(x=="?") 0.0 else x.toDouble)
      val features=otherfeatures++categoryFeatures
      LabeledPoint(label,Vectors.dense(features))
    }

    val vectors=data.map(p=>p.features)
    val scaler=new StandardScaler(withMean = true,withStd = true).fit(vectors)
    val scalerData=data.map(point=>
      LabeledPoint(point.label,scaler.transform(point.features))
    )

    val lrModel=LogisticRegressionWithSGD.train(scalerData,10)

    val predictrueData=scalerData.map{point=>
      if(lrModel.predict(point.features)==point.label) 1 else 0
    }.sum()

    val accuracy=predictrueData/data.count()
    println(accuracy)
  }
}

執行之後檢視正確率為0.6657200811359026,在之前的優化基礎之上又有一定的提升。

總結:本篇部落格介紹的內容到這裡就結束了,主要是介紹了MLlib中分類演算法的應用以及一些演算法優化的思路。當然為了提高演算法準確度還需要完成的一項重要工作就是引數調優,對於這方面的內容本篇部落格未涉及,有興趣的朋友可以自行查閱相關資料試試不同的引數對正確率的影響。另外這裡舉例用到的是邏輯迴歸分類演算法,其他大部分分類演算法的使用方法類似。