Spark-MLlib分类算法(逻辑回归)实战算法

一、数据来源及开发环境

开发环境:为了方便代码管理这里使用了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))
    }

添加了类别标识特征以后完整代码如下:

扫描二维码关注公众号,回复: 2299007 查看本文章
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中分类算法的应用以及一些算法优化的思路。当然为了提高算法准确度还需要完成的一项重要工作就是参数调优,对于这方面的内容本篇博客未涉及,有兴趣的朋友可以自行查阅相关资料试试不同的参数对正确率的影响。另外这里举例用到的是逻辑回归分类算法,其他大部分分类算法的使用方法类似。

猜你喜欢

转载自blog.csdn.net/bigdata_mining/article/details/81126786