2021.1.23基于Spark MLlib训练回归算法模型

上节课讲的重点是 : 梯度下降法

目的:优化损失函数

调整w参数,让误差达到最小,可以称,梯度下降法是损失函数的优化函数

让w尽快的找到一个最合适的,以至于让我们的误差达到最小。

梯度下降法是怎么让我们尽快的找到一个最合适的w的呢?

会通过导数,来决定w参数调整的 方向,
使用α学习率以及导数的乘积,作为调整幅度,关于导数起了两部分作用

一:指挥w参数的调整方向
二:参与w的调整,因为每次w调整多大,是学习率乘以导数,

为什么来使用这样一个公式调整w参数?
为什么在调整w参数的时候让导数也参与进来?
为什么不让导数只是来指导w参数的调整方向?

这个知识点聊了挺长时间,如果导数只起了w参数调整方向的作用,这么做会来回震荡,一直到不了山谷,如果一步跃过了山谷了,那么下一次它还会震荡回来。是个永动机的原理,在调整w参数的时候,让导数参与进来,如果一步跨过来,高度是低于原先高度的话会盘旋下降,如果一步跨过来的高度和原先的高度是一样的,它也会来回震荡,因为这两个点的导数的绝对值是相同的。如果α调的很大,一步跨过来,它的高度是高于原先位置的,它会盘旋上升。

关于α的调整也是艺术活,度要把控好了。

损失函数为什么要取一个平方?

如果误差很大,这个平方它会起到一个放大误差的效果。它能把我们的误差放大,当我们寻找这根直线的时候,它就能站在一个全局的角度去考虑,会离数据的密集区非常近,但不会完全穿过。如果只是实打实的给误差取一个绝对值,它在寻找这根直线的时候,它可能不会站在全局的角度,它会完全的穿过数据的密集区。

如果面试官问到这个问题,也可以通过画图的方式。

2021.1.23基于Spark MLlib训练回归算法模型_第1张图片

这张图也可以这么去理解,上面两个点和下面一个点分别是两队在进行拔河比赛,这根线可以理解为绳上的标记,当这个标记越靠近上面两个点的时候,下面这个点的力量会呈平方式的去暴增,上面两个点的力量反而减少很多。

所以我们选择了比较好的误差函数,使用平方而不是绝对值。

无论是梯度下降法还是选取的误差函数,我们最终的目的只有一个,在空间中找到一根比较合适的直线

什么是比较好的直线?
距离空间中的点的误差最小,并且还能很好的代表当前数据的规律。
如果只是达到了第一个条件,距离空间中的点的误差非常非常的小,如果只具备这样一个特点,我们说它可能发生了过拟合。

如果现在能够拿一根曲线来拟合空间中所有的点, 这跟曲线不好,没有任何的规律,类似上学时候的学痴,他每道题可能都是背过的 ,在考试的时候,如果换一下题目,他就不会做了。那是因为学痴是在背题,而不是从他做的这N道题目中,寻找规律,学霸不一样,是从中寻找规律。这根曲线就非常像学痴没有规律,这根直线就非常像学霸。
未来来了一条业务数据,必须x=10,那么他预测出来的y就不准,因为这跟曲线没有规律,怎么去控制它?

把准备的数据集,这N多条数据要分成3块,验证集,训练集和测试集,我们通过训练集来训练这个模型,通过验证集来验证这个模型,把错误标记一下,再迭代出一个模型,再验证一下又得到一个err2,又迭代又得到一个err3,如果此时的err3比err2大了非常多,我们说它可能发生了过拟合。

因为在不断迭代模型的时候,我们可以保证训练集的误差不断的减小,测试集的误差和验证集的误差,有可能在某一个点,它们会急剧上升,在急剧上升的点可以认为就在这个位置发生了过拟合。

所有在训练模型的时候,可以借住训练集来验证每一次迭代出来的模型,防止出现过拟合。还有一个办法,可以使用正则化的方式 L1 L2 正则化,放到后面去讲。

经验:α一般取值0.2~0.3

上节课留下了一个问题,

如果特征有两个,应该使用 y=w0+w1x1+w2x2来拟合
放到三元空间中就是一个面,问w1和w2是什么关系?

2021.1.23基于Spark MLlib训练回归算法模型_第2张图片

现在又复制了一个特征 x2
x2和x1是一模一样的,所有需要拿这样一个公式来拟合: y=w0+w1x1+w2x2
又因为已知条件:x1=x2
上面的 y=w0+w1x1+w2x2
通过结合律,可以写成 y=w0+x1(w1+w2)

上面没有加第二列x2的时候,拿y=w0+w1x就可以来拟合了
上面的w1等于下边的w1+w2

使用的开发工具是pycharm
python的运行环境叫Anaconda,按照文档去安装,然后需要在pycharm里设置一下

Anaconda安装文档:
1.什么是Anaconda?
Anaconda是一个开源的Python发行版本,python是一个编译器,如果不使用Anaconda那么安装起来会比较痛苦,各个库之间的依赖性就很难连接的很好。Anaconda可以看做Python的一个集成安装,里面集成了很多关于python科学计算的第三方库,安装它后就默认安装了python、IPython、集成开发环境Spyder和众多的包和模块,包含了conda(conda 是开源包(packages)和虚拟环境(environment)的管理系统。)、Python等180多个科学包及其依赖项。因为包含了大量的科学包,Anaconda 的下载文件比较大(约 515 MB),如果只需要某些包,或者需要节省带宽或存储空间,也可以使用Miniconda这个较小的发行版(仅包含conda和 Python)。
2.下载Anaconda
Anaconda官网,下载Anaconda:
https://www.anaconda.com
下载Anaconda也可以在清华镜像下载:https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/
3.windows 安装Anaconda
1)官网下载安装Anaconda,安装路径中不要有中文和空格
安装过程中会有下图所示,直接将Anaconda加入到系统环境变量中:
✔ Add Anaconda to the system PATH environment variable
✔ Register Anaconda as the system Python 3.5
如果不选中也可以安装完成后自己配置环境变量:
a)我的电脑->右键属性->高级系统设置->高级->环境变量->系统变量
找到Path,加入(以自己的路径为准):
C:\Anaconda
C:\Anaconda\Scripts
C:\Anaconda\Library\bin
b)加入完成之后重启cmd命令即可。
2)安装完成后,进入cmd,检验安装是否成功
conda --version
3)通过python –version 命令查看发行版默认的Python版本
python --version
4.Anaconda应用介绍及使用
Anaconda Navigator :用于管理工具包和环境的图形用户界面,后续涉及的众多管理命令也可以在 Navigator 中手工实现。
Jupyter notebook :基于web的交互式计算环境,可以编辑易于人们阅读的文档,用于展示数据分析的过程。
QTconsole :一个可执行 IPython 的仿终端图形界面程序,相比 Python Shell 界面,qtconsole 可以直接显示代码生成的图形,实现多行代码输入执行,以及内置许多有用的功能和函数。
spyder :一个使用Python语言、跨平台的、科学运算集成开发环境。
#创建一个名为python34的环境,指定Python版本是3.5(不用管是3.5.x,conda会为我们自动寻找3.5.x中的最新版本)
conda create --name python35 python=3.5
#安装好后,使用activate激活某个环境
activate python35 # for Windows
source activate python35 # for Linux & Mac
#激活后,会发现terminal输入的地方多了python35 的字样,实际上,此时系统做的事情就是把默认2.7环境从PATH中去除,再把3.5对应的命令加入PATH
#此时,再次输入
python --version
#可以得到Python 3.5.5 :: Anaconda 4.1.1 (64-bit),即系统已经切换到了3.5的环境
#如果想返回默认的python 2.7环境,运行
deactivate python35 # for Windows
source deactivate python35 # for Linux & Mac
#删除一个已有的环境
conda remove --name python35 --all

刚才的问题会使用MLlib去验证
有这样一组样本数据:

-9.490009878824548 1:0.4551273600657362 2:0.36644694351969087 3:-0.38256108933468047 4:-0.4458430198517267 5:0.33109790358914726 6:0.8067445293443565 7:-0.2624341731773887 8:-0.44850386111659524 9:-0.07269284838169332 10:0.5658035575800715
0.2577820163584905 1:0.8386555657374337 2:-0.1270180511534269 3:0.499812362510895 4:-0.22686625128130267 5:-0.6452430441812433 6:0.18869982177936828 7:-0.5804648622673358 8:0.651931743775642 9:-0.6555641246242951 10:0.17485476357259122
-4.438869807456516 1:0.5025608135349202 2:0.14208069682973434 3:0.16004976900412138 4:0.505019897181302 5:-0.9371635223468384 6:-0.2841601610457427 7:0.6355938616712786 8:-0.1646249064941625 9:0.9480713629917628 10:0.42681251564645817
-19.782762789614537 1:-0.0388509668871313 2:-0.4166870051763918 3:0.8997202693189332 4:0.6409836467726933 5:0.273289095712564 6:-0.26175701211620517 7:-0.2794902492677298 8:-0.1306778297187794 9:-0.08536581111046115 10:-0.05462315824828923
-7.966593841555266 1:-0.06195495876886281 2:0.6546448480299902 3:-0.6979368909424835 4:0.6677324708883314 5:-0.07938725467767771 6:-0.43885601665437957 7:-0.608071585153688 8:-0.6414531182501653 9:0.7313735926547045 10:-0.026818676347611925

一共有500组,对于这样一个样本数据,要使用多元线性回归算法来拟合。
之前讲过简单的线性回归, y = w0 + w1x 只有一个自变量和一个因变量
如果有多个自变量,10个,刚才看的x特征,就要使用这样一个公式:
y=w0+w1x1+w2x2+…+wnxn 这个公式可以叫多元线性回归。
正常情况下,我们用的都是多元线性回归。

简单线性回归的基础上多几个维度的事。

把这样一个样本加载过来,

package com.msb.lr_new

import org.apache.spark.SparkConf
import org.apache.spark.ml.linalg.{
     DenseVector, SparseVector}
import org.apache.spark.ml.regression.{
     LinearRegression, LinearRegressionModel}
import org.apache.spark.sql.SparkSession


object LinearRegression02 {
     
  //0.5395881831013476
  def main(args: Array[String]): Unit = {
     
    val conf = new SparkConf()
    conf.setMaster("local")

    val spark = SparkSession.builder().config(conf).appName("LinearRegression").getOrCreate()

    var data = spark.read.format("libsvm")
      .load("data/sample_linear_regression_data.txt")

   //增加一列与第一个特征一模一样 -0.294192922737251,-0.294192922737251
    //未增加:   -0.5883852628595317


    /**
      *randomSplit 是随机切割的方法  Array(0.8,0.2)
      *
      *  data拆分成两个df
      *  第一个df的数据量是data  的  80%   随机
      *  第一个df的数据量是data  的  20%
      *
      *  seed种子如果每次都是一样的
      *  那么每次随机出来的数据都是一样的
      *
      *  测试环境为什么随机出来的数据一样?
      *  便于排查错误
      *
      *
      *  为什么要把data切成两份?
      *  1、第一份做训练集   --   model
      *  2、第二份做测试集
      */

    val DFS = data.randomSplit(Array(0.8,0.2),1)

    val (training,test) = (DFS(0),DFS(1))

    val lr = new LinearRegression()
      .setMaxIter(10)
      //L1+L2系数之和    0代表不使用正则化
//      .setRegParam(0.3)
    /**
      * 用于调整L1、L2之间的比例,简单说:调整L1,L2前面的系数
      * For alpha = 0, the penalty is an L2 penalty.
      * For alpha = 1, it is an L1 penalty.
      * For alpha in (0,1), the penalty is a combination of L1 and L2.
      */
//      .setElasticNetParam(0.8)


    // Fit the model
    val lrModel = lr.fit(training)

    // 打印模型参数w1...wn和截距w0
    println(s"Coefficients: ${lrModel.coefficients} Intercept: ${lrModel.intercept}")

    // 基于训练集数据,总结模型信息
    val trainingSummary = lrModel.summary
    println(s"RMSE: ${trainingSummary.rootMeanSquaredError}")

    spark.close()

  }
}

有一些算法是有bug的,所有讲算法的时候会讲两个版本,一个是基于spark MLlib去实现,还有一个版本是基于python的sklearn,是python的一个模块,这个模块里面集成了一堆的算法,可以理解为一个机器学习库。MLlib只不过是spark生态圈的机器学习库。会讲基于这两个框架的算法实现。

SparseVector稀疏向量
创建的时候指定向量的长度,在哪些位置有值,分别是多少,其他位置都为0
可以把这个值 sparseVector.toDense 变成一个密集向量
val sparseVector = new SparseVector(6,Array(1,3,5),Array(1,1,1))
如果本来创建的就是一个密集向量:这个向量里大部分的位置都不是0,都是有值的,所谓的稀疏向量就是这个向量里面大部分的位置为0,所以采用这个方式去创建,比较方便。

如果要创建一个稠密向量,传进来一个数据,为0就写0,非0就写对应的值
val denseVector = new DenseVector(Array(1,1,0,1))

不管是稠密向量还是稀疏向量都属于Vector 向量,只不过表达形式不一样而已。

你可能感兴趣的:(大数据)