在Apache Spark上跑Logistic Regression算法及其中的一些错误问题

在spark上跑Logistic Regression算法,主要参考文章http://mp.weixin.qq.com/s?__biz=MzA4Mzc0NjkwNA==&mid=209693842&idx=2&sn=b276dd93fd68e8a948d71a870deb7237&scene=5&srcid=4DbxDWAICfegAjjGyisg#rd这是微信上分享的一篇文章,同时文章http://code.csdn.net/news/2825285中也有相关介绍。尝试了一下,发现里面有几个地方是有错误的,由于对scala不熟悉并且电脑有比较烂,花了一晚上时间来调试其中的错误。

先把整篇文章粘贴在下面:有错的地方我会在相应的地方标记,最下方附有scala中执行的代码。


本文旨在介绍使用机器学习算法,来介绍Apache Spark数据处理引擎。我们一开始会先简单介绍一下Spark,然后我们将开始实践一个机器学习的例子。我们将使用Qualitative Bankruptcy数据集,来自UCI机器学习数据仓库。虽然Spark支持同时Java,Scala,Python和R,在本教程中我们将使用Scala作为编程语言。不用担心你没有使用Scala的经验。练习中的每个代码段,我们都会详细解释一遍。


APACHE SPARK


Apache Spark是一个开源的集群计算框架,用Spark编写的应用程序可以比Hadoop MapReduce范式的速度高100倍以上。Spark的一个主要的特点,基于内存,运行速度快,不仅如此,复杂应用在Spark系统上运行,也比基于磁盘的MapReduce更有效。Spark还旨在更通用,因此它提供了以下库:


  • Spark SQL,处理结构化数据的模块

  • MLlib,可扩展的机器学习库

  • GraphX,图和图的并行计算API

  • Spark Streaming,可扩展的,可容错的流式计算程序


正如已经提到的,Spark支持Java,Scala,Python和R编程语言。它还集成了其他大数据工具。特别是,Spark可以运行在Hadoop集群,可以访问任何数据源,包括Hadoop Cassandra。


Spark核心概念


在一个高的抽象层面,一个Spark的应用程序由一个驱动程序作为入口,在一个集群上运行各种并行操作。驱动程序包含了你的应用程序的main函数,然后将这些应用程序分配给集群成员执行。驱动程序通过SparkContext对象来访问计算集群。对于交互式的shell应用,SparkContext默认可通过sc变量访问。


Spark的一个非常重要的概念是RDD–弹性分布式数据集。这是一个不可改变的对象集合。每个RDD会分成多个分区,每个分区可能在不同的群集节点上参与计算。RDD可以包含任何类型的Java,Scala对象,Python或R,包括用户自定义的类。RDDS的产生有两种基本方式:通过加载外部数据集或分配对象的集合如,list或set。


在创建了RDDs之后,我们可以对RDDs做2种不同类型的操作:


  • Transformations - 转换操作,从一个RDD转换成另外一个RDD

  • Actions - 动作操作,通过RDD计算结果


RDDs通过lazy的方式计算 - 即当RDDs碰到Action操作时,才会开始计算。Spark的Transformations操作,都会积累成一条链,只有当需要数据的时候,才会执行这些Transformations操作。每一次RDD进行Action操作时,RDD都会重新生成。如果你希望某些中间的计算结果能被其他的Action操作复用,那么你需要调用Spark的RDD.persist()来保存中间数据。


Spark支持多种运行模式,你可以使用交互式的Shell,或者单独运行一个standalone的Spark程序。不管哪一种方式,你都会有如下的工作流:


  • 输入数据,用于生成RDD

  • 使用Transformations操作转换数据集

  • 让Spark保存一些中间计算结果,用于复用计算

  • 使用Action操作,让Spark并行计算。Spark内部会自动优化和运行计算任务。


安装Apache Spark


为了开始使用Spark,需要先从官网下载。选择“Pre-built for Hadoop 2.4 and later”版本然后点击“Direct Download”。如果是Windows用户,建议将Spark放进名字没有空格的文件夹中。比如说,将文件解压到:C:\spark。

正如上面所说的,我们将会使用Scala编程语言。进入Spark的安装路径,运行如下命令:


<span style="margin: 0px; padding: 0px; max-width: 100%; font-size: 14px; box-sizing: border-box !important; word-wrap: break-word !important;">// Linux and Mac users

bin/spark-shell

// Windows users

bin\spark shell</span>


然后你可以在控制台中看到Scala:


<span style="margin: 0px; padding: 0px; max-width: 100%; font-size: 14px; box-sizing: border-box !important; word-wrap: break-word !important;">scala></span>


QUALITATIVE破产分类


现实生活中的问题是可以用机器学习算法来预测的。我们将试图解决的,通过一个公司的定性信息,预测该公司是否会破产。数据集可以从UCI机器学习库https://archive.ics.uci.edu/ml/datasets/qualitative_bankruptcy下载。在Spark的安装文件夹中,创建一个新的文件夹命名为playground。复制qualitative_bankruptcy.data.txt文件到这里面。这将是我们的训练数据。

这儿需要将文件上传到hdfs上,命令为:

hadoop dfs -copyFromLocal qualitative_bankruptcy.data.txt ./

数据集包含250个实例,其中143个实例为非破产,107个破产实例。


每一个实例数据格式如下:


  • 工业风险

  • 管理风险

  • 财务灵活性

  • 信誉

  • 竞争力

  • 经营风险


这些被称为定性参数,因为它们不能被表示为一个数字。每一个参数可以取下以下值:


  • P positive

  • A average

  • N negative


数据集的最后一个列是每个实例的分类:B为破产或NB非破产。


鉴于此数据集,我们必须训练一个模型,它可以用来分类新的数据实例,这是一个典型的分类问题。


解决问题的步骤如下:


  • 从qualitative_bankruptcy.data.txt文件中读取数据

  • 解析每一个qualitative值,并将其转换为double型数值。这是我们的分类算法所需要的

  • 将数据集划分为训练和测试数据集

  • 使用训练数据训练模型

  • 计算测试数据的训练误差


SPARK LOGISTIC REGRESSION


我们将用Spark的逻辑回归算法训练分类模型。如果你想知道更多逻辑回归算法的原理,你可以阅读以下教程http://technobium.com/logistic-regression-using-apache-mahout。


在Spark的Scala Shell中粘贴以下import语句:


<span style="margin: 0px; padding: 0px; max-width: 100%; font-size: 14px; box-sizing: border-box !important; word-wrap: break-word !important;"><span style="color:#ff0000;">import org.apache.spark.mllib.classification.{LogisticRegressionWithLBFGS, LogisticRegressionModel}</span><span style="color:#3e3e3e;">
</span><span style="color:#000099;">这找了半天没有找到<span style="line-height: 25.6000003814697px; font-family: 'Helvetica Neue', Helvetica, 'Hiragino Sans GB', 'Microsoft YaHei', Arial, sans-serif;">LogisticRegressionWithLBFGS,所以一直报错,最后改用LinearRegressionWithSGD,因此命令变为:</span></span></span>
<span style="margin: 0px; padding: 0px; max-width: 100%; font-size: 14px; box-sizing: border-box !important; word-wrap: break-word !important;"><span style="font-family:Helvetica Neue, Helvetica, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;"></span></span><pre name="code" style="margin-top: 0px; margin-bottom: 0px; font-size: 16px; line-height: 25.6000003814697px; font-family: 'Helvetica Neue', Helvetica, 'Hiragino Sans GB', 'Microsoft YaHei', Arial, sans-serif; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; background-color: rgb(255, 255, 255);"><span style="margin: 0px; padding: 0px; max-width: 100%; font-size: 14px; box-sizing: border-box !important; word-wrap: break-word !important;"><span style="color:#000099;">import org.apache.spark.mllib.classification.{LogisticRegressionWithSGD, LogisticRegressionModel}</span></span>

import org.apache.spark.mllib.regression.LabeledPointimport org.apache.spark.mllib.linalg.{Vector, Vectors}
 
 


这将导入所需的库。


接下来我们将创建一个Scala函数,将数据集中的qualitative数据转换为Double型数值。键入或粘贴以下代码并回车,在Spark Scala Shell。


<span style="margin: 0px; padding: 0px; max-width: 100%; font-size: 14px; box-sizing: border-box !important; word-wrap: break-word !important;">def getDoubleValue( input:String ) : Double = {

    var result:Double = 0.0

    if (input == "P")  result = 3.0 

    if (input == "A")  result = 2.0

    if (input == "N")  result = 1.0

    if (input == "NB") result = 1.0

    if (input == "B")  result = 0.0

    return result

   }</span>


如果所有的运行都没有问题,你应该看到这样的输出:


<span style="margin: 0px; padding: 0px; max-width: 100%; font-size: 14px; box-sizing: border-box !important; word-wrap: break-word !important;">getDoubleValue: (input: String)Double</span>


现在,我们可以读取到qualitative_bankruptcy.data.txt文件中的数据。从Spark的角度来看,这是一个Transformation操作。在这个阶段,数据实际上不被读入内存。如前所述,这是一个lazy的方式执行。实际的读取操作是由count()引发,这是一个Action操作。

我把文件放到了hdfs的根目录下,所以下面的命令改为:

<span style="margin: 0px; padding: 0px; max-width: 100%; font-size: 14px; box-sizing: border-box !important; word-wrap: break-word !important;"><span style="color:#000099;">val data = sc.textFile("Qualitative_Bankruptcy.data.txt")</span></span>

<span style="margin: 0px; padding: 0px; max-width: 100%; font-size: 14px; box-sizing: border-box !important; word-wrap: break-word !important;"><span style="color:#cc0000;">val data = sc.textFile("playground/Qualitative_Bankruptcy.data.txt")</span><span style="color:#3e3e3e;">

data.count()</span></span>


用我们val关键字声明一个常量data。它是一个包含输入数据所有行的RDD。读操作被SC或sparkcontext上下文变量监听。count操作应返回以下结果:


<span style="margin: 0px; padding: 0px; max-width: 100%; font-size: 14px; box-sizing: border-box !important; word-wrap: break-word !important;">res0: Long = 250</span>


现在是时候为逻辑回归算法准备数据,将字符串转换为数值型。

下面命令有错误的地方,下面已经更正,注意标红的地方。

<span style="margin: 0px; padding: 0px; max-width: 100%; font-size: 14px; box-sizing: border-box !important; word-wrap: break-word !important;"><span style="color:#3e3e3e;">val parsedData = data.map{line</span><span style="color:#ff0000;"> =></span><span style="color:#3e3e3e;">

    val parts = line.split(",")

    LabeledPoint(getDoubleValue(parts(6)), Vectors.dense(parts.slice(0,6).map(x </span><span style="color:#cc0000;">=></span><span style="color:#3e3e3e;">getDoubleValue(x))))

}</span></span>


在这里,我们声明了另外一个常量,命名为parsedData。对于data变量中的每一行数据,我们将做以下操作:


  • 使用“,”拆分字符串,并获得一个向量,命名为parts

  • 创建并返回一个LabeledPoint对象。每个LabeledPoint包含标签和值的向量。在我们的训练数据,标签或类别(破产或非破产)放在最后一列,数组下标0到6。这是我们使用的parts(6)。在保存标签之前,我们将用getDoubleValue()函数将字符串转换为Double型。其余的值也被转换为Double型数值,并保存在一个名为稠密矢量的数据结构。这也是Spark的逻辑回归算法所需要的数据结构。


Spark支持map()转换操作,Action动作执行时,第一个执行的就是map()。


我们来看看我们准备好的数据,使用take():


<span style="margin: 0px; padding: 0px; max-width: 100%; font-size: 14px; box-sizing: border-box !important; word-wrap: break-word !important;">parsedData.take(10)</span>


上面的代码,告诉Spark从parsedData数组中取出10个样本,并打印到控制台。一样的,take()操作之前,会先执行map()。输出结果如下:


<span style="margin: 0px; padding: 0px; max-width: 100%; font-size: 14px; box-sizing: border-box !important; word-wrap: break-word !important;">res5: Array[org.apache.spark.mllib.regression.LabeledPoint] = Array((1.0,[3.0,3.0,2.0,2.0,2.0,3.0]), (1.0,[1.0,1.0,2.0,2.0,2.0,1.0]), (1.0,[2.0,2.0,2.0,2.0,2.0,2.0]), (1.0,[3.0,3.0,3.0,3.0,3.0,3.0]), (1.0,[1.0,1.0,3.0,3.0,3.0,1.0]), (1.0,[2.0,2.0,3.0,3.0,3.0,2.0]), (1.0,[3.0,3.0,2.0,3.0,3.0,3.0]), (1.0,[3.0,3.0,3.0,2.0,2.0,3.0]), (1.0,[3.0,3.0,2.0,3.0,2.0,3.0]), (1.0,[3.0,3.0,2.0,2.0,3.0,3.0]))</span>


接着我们划分一下训练数据和测试数据,将parsedData的60%分为训练数据,40%分为测试数据。


<span style="margin: 0px; padding: 0px; max-width: 100%; font-size: 14px; box-sizing: border-box !important; word-wrap: break-word !important;">val splits = parsedData.randomSplit(Array(0.6, 0.4), seed = 11L)

val trainingData = splits(0)

val testData = splits(1)</span>


训练数据和测试数据也可以像上面一样,使用take()者count()查看。


激动人心的时刻,我们现在开始使用Spark的LogisticRegressioinWithLBFGS()来训练模型。设置好分类个数,这里是2个(破产和非破产):

由于改用LogisticRegressionWithSGD,所以下面的语句就会变化:

LogisticRegressionWithSGD的使用方法,主要参考

http://spark.apache.org/docs/0.9.0/mllib-guide.html#using-mllib-in-scala

<span style="margin: 0px; padding: 0px; max-width: 100%; font-size: 14px; box-sizing: border-box !important; word-wrap: break-word !important;"><span style="color:#ff0000;">val model = new LogisticRegressionWithLBFGS().setNumClasses(2).run(trainingData)(这句要去掉,改为下面两句)</span></span>

val numIterations = 20
val model =
LogisticRegressionWithSGD.train(parsedData, numIterations)

当模型训练完,我们可以使用testData来检验一下模型的出错率。


<span style="margin: 0px; padding: 0px; max-width: 100%; font-size: 14px; box-sizing: border-box !important; word-wrap: break-word !important;"><span style="color:#3e3e3e;">val labelAndPreds = testData.map { point =

  val prediction = model.predict(point.features)

  (point.label, prediction)

}
</span><span style="color:#000099;">下面这句话有误,注意标红的部分,原来为=,更正为=>:</span><span style="color:#3e3e3e;">
val trainErr = labelAndPreds.filter(r </span><span style="color:#cc0000;">=></span><span style="color:#3e3e3e;"> r._1 != r._2).count.toDouble / testData.count</span></span>


变量labelAndPreds保存了map()转换操作,map()将每一个行转换成二元组。二元组包含了testData的标签数据(point.label,分类数据)和预测出来的分类数据(prediction)。模型使用point.features作为输入数据。


最后一行代码,我们使用filter()转换操作和count()动作操作来计算模型出错率。filter()中,保留预测分类和所属分类不一致的元组。在Scala中_1和_2可以用来访问元组的第一个元素和第二个元素。最后用预测出错的数量除以testData训练集的数量,我们可以得到模型出错率:


<span style="margin: 0px; padding: 0px; max-width: 100%; font-size: 14px; box-sizing: border-box !important; word-wrap: break-word !important;">trainErr: Double = 0.20430107526881722</span>


总结


在这个教程中,你已经看到了Apache Spark可以用于机器学习的任务,如logistic regression。虽然这只是非分布式的单机环境的Scala shell demo,但是Spark的真正强大在于分布式下的内存并行处理能力。


在大数据领域,Spark是目前最活跃的开源项目,在过去几年已迅速获得关注和发展。在过去的几年里。采访了超过2100受访者,各种各样的使用情况和环境。


[参考资料]


“Learning Spark” by HoldenKarau, Andy Konwinski, Patrick Wendell and Matei Zaharia, O’Reilly Media 2015

Lichman, M. (2013). UCI Machine Learning Repository [http://archive.ics.uci.edu/ml]. Irvine, CA: University of California, School of Information and Computer Science


https://spark.apache.org/docs/latest/mllib-linear-methods.html#logistic-regression

https://spark.apache.org/docs/1.1.0/mllib-data-types.html

https://archive.ics.uci.edu/ml/datasets/Qualitative_Bankruptcy

https://databricks.com/blog/2015/01/27/big-data-projects-are-hungry-for-simpler-and-more-powerful-tools-survey-validates-apache-spark-is-gaining-developer-traction.html

附:

执行的代码:




import org.apache.spark.mllib.classification.{LogisticRegressionWithSGD, LogisticRegressionModel}
import org.apache.spark.mllib.regression.LabeledPoint
import org.apache.spark.mllib.linalg.{Vector, Vectors}
def getDoubleValue( input:String ) : Double = {
    var result:Double = 0.0
    if (input == "P")  result = 3.0 
    if (input == "A")  result = 2.0
    if (input == "N")  result = 1.0
    if (input == "NB") result = 1.0
    if (input == "B")  result = 0.0
    return result
   }


val data = sc.textFile("Qualitative_Bankruptcy.data.txt")
data.count()


val parsedData = data.map{line =>
    val parts = line.split(",")
    LabeledPoint(getDoubleValue(parts(6)), Vectors.dense(parts.slice(0,6).map(x =>getDoubleValue(x))))
}
parsedData.take(10)
val splits = parsedData.randomSplit(Array(0.6, 0.4), seed = 11L)
val trainingData = splits(0)
val testData = splits(1)
val numIterations = 20
val model = LogisticRegressionWithSGD.train(parsedData, numIterations)
val labelAndPreds = testData.map { point =>
 val prediction = model.predict(point.features)
 (point.label, prediction)
}
val trainErr = labelAndPreds.filter(r => r._1 != r._2).count.toDouble / testData.count






  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值