StreamingLinearRegressionWithSGD测试

Spark Streaming 线性回归算法测试

大家好,我是一拳就能打爆A柱的A柱猛男

上次测试真的不理想,所以我决定去看DStream和RDD的知识,然后再来测试。关于DStream和RDD的内容我已经整理成博客,各位可以去看看。下面流式线性回归算法还是走了一些弯路,但是我将我做的一五一十给大家展现出来,希望对各位有帮助。

一、StreamingLinearRegressionWithSGD测试

重新看过文档后对DStream有了理解,我决定在流式处理的时候采用即时训练即时测试的方法,将数据从kafka读取到后经过清洗,复制出一份用于训练,一份用于测试。在DStream中我知道可以遍历每一个RDD(foreachRDD),而且在RDD中的操作可以打印到终端。所以我写了下面这个案例:

import java.lang

import StreamingLinearRegression_Online_Predict.{labeledPoint2Label, labeledPoint2Vector}
import com.alibaba.fastjson.{JSON, JSONObject}
import org.apache.kafka.clients.consumer.ConsumerRecord
import org.apache.kafka.common.serialization.StringDeserializer
import org.apache.spark.SparkConf
import org.apache.spark.mllib.linalg
import org.apache.spark.mllib.linalg.Vectors
import org.apache.spark.mllib.regression.{LabeledPoint, StreamingLinearRegressionWithSGD}
import org.apache.spark.rdd.RDD
import org.apache.spark.streaming.dstream.{DStream, InputDStream}
import org.apache.spark.streaming.kafka010.{ConsumerStrategies, KafkaUtils, LocationStrategies}
import org.apache.spark.streaming.{Seconds, StreamingContext}

import scala.collection.mutable.ArrayBuffer

/*
 * 做实时训练,然后看看怎么读取数据去预测
 *
 * 因为流式算法,要用DStream[Vector]才能predict,所以读数据也要用streaming来读
 */
object StreamingLinearRegressionWithSGD_Online_TrainAndPredict {

  def string2Vector(uncleanTrainingDS: DStream[String]) = {
    var i: Int = 0
    val value: DStream[(Int, linalg.Vector)] = uncleanTrainingDS.map(line => {
      val json: JSONObject = JSON.parseObject(line)
      val payload: JSONObject = json.getJSONObject("payload")
      val y = payload.getDouble("y")
      val x1 = payload.getDouble("x1")
      val x2 = payload.getDouble("x2")
      val x3 = payload.getDouble("x3")
      val x4 = payload.getDouble("x4")
      val x5 = payload.getDouble("x5")
      val x6 = payload.getDouble("x6")
      val x7 = payload.getDouble("x7")
      val x8 = payload.getDouble("x8")
      val x9 = payload.getDouble("x9")
      val x10 = payload.getDouble("x10")
      i += 1
      (i, Vectors.dense(Array(x1.toDouble, x2.toDouble, x3.toDouble, x4.toDouble, x5.toDouble, x6.toDouble, x7.toDouble, x8.toDouble, x9.toDouble, x10.toDouble)))
    })
    value
  }


  def string2Label(uncleanTrainingDS: DStream[String]) = {
    var i: Int = 0
    uncleanTrainingDS.map(line => {
      val json: JSONObject = JSON.parseObject(line)
      val payload: JSONObject = json.getJSONObject("payload")
      val y = payload.getDouble("y")
      i += 1
      (i, y.toDouble)
    })
  }


  def string2LabeledPoint(uncleanedTrainingDS: DStream[String]) = {
    uncleanedTrainingDS.map(line => {
      val json: JSONObject = JSON.parseObject(line)
      val payload: JSONObject = json.getJSONObject("payload")
      val y = payload.getDouble("y")
      val x1 = payload.getDouble("x1")
      val x2 = payload.getDouble("x2")
      val x3 = payload.getDouble("x3")
      val x4 = payload.getDouble("x4")
      val x5 = payload.getDouble("x5")
      val x6 = payload.getDouble("x6")
      val x7 = payload.getDouble("x7")
      val x8 = payload.getDouble("x8")
      val x9 = payload.getDouble("x9")
      val x10 = payload.getDouble("x10")
      LabeledPoint(y.toDouble, Vectors.dense(Array(x1.toDouble, x2.toDouble, x3.toDouble, x4.toDouble, x5.toDouble, x6.toDouble, x7.toDouble, x8.toDouble, x9.toDouble, x10.toDouble)))
    })
  }

  def ds2ArrayBuffer(DS: DStream[(Int, Double)]) = {
    var AB: ArrayBuffer[(Int, Double)] = ArrayBuffer[(Int, Double)]()
    DS.foreachRDD(rdd => {
      AB ++= rdd.collect()
    })
    AB
  }

  def union2AB(AB1: ArrayBuffer[(Int, Double)], AB2: ArrayBuffer[(Int, Double)]) = {
    var unionAB: ArrayBuffer[(Int, Double, Double)] = ArrayBuffer[(Int, Double, Double)]()
    AB1.foreach(item => {
      AB2.foreach(item2 => {
        if (item._1 == item2._1) unionAB ++= Array((item._1, item._2, item2._2))
      })
    })
    unionAB
  }

  def calRMSE(unionAB: ArrayBuffer[(Int, Double, Double)]) = {
    var diff: Double = 0.0
    unionAB.foreach(i => {
      diff += Math.abs(i._2 - i._3)
    })
    diff /= unionAB.length + 0.0
    val res: Double = Math.sqrt(diff)
    res
  }

  def main(args: Array[String]): Unit = {
    /**
     * 准备工作
     */
    // Spark
    val conf: SparkConf = new SparkConf().setMaster("local").setAppName("StreamingLinearRegressionWithSGD_Online_TrainAndPredict")
    val ssc: StreamingContext = new StreamingContext(conf, Seconds(5))

    // kafka参数
    val kafkaParams: Map[String, Object] = Map[String, Object](
      // 组id
      "group.id" -> "g1",
      // kafka集群
      "bootstrap.servers" -> "kafka ip:9092",
      // key的解析器
      "key.deserializer" -> classOf[StringDeserializer],
      // value的解析器
      "value.deserializer" -> classOf[StringDeserializer],
      // 若没有偏移量,从最新的开始消费
      "auto.offset.reset" -> "latest",
      // 交给kafka自动维护偏移量
      "enable.auto.commit" -> (true: lang.Boolean)
    )

    // 训练模型的topic
    val trainingTopic = Array("test-dm-jc-linearTraining18")

    /**
     * 链接kafka 接收数据
     */
    val trainStream: InputDStream[ConsumerRecord[String, String]] = KafkaUtils.createDirectStream[String, String](
      ssc,
      LocationStrategies.PreferConsistent,
      ConsumerStrategies.Subscribe[String, String](topics = trainingTopic, kafkaParams)
    )
    // 取到record
    val uncleanedTrainingDS: DStream[String] = trainStream.map(record => record.value())
    uncleanedTrainingDS.print()

    /**
     * 清洗数据
     */
    val trainLabeledPointDS: DStream[LabeledPoint] = string2LabeledPoint(uncleanedTrainingDS)
    val predictVectorDS: DStream[(Int, linalg.Vector)] = string2Vector(uncleanedTrainingDS)
    val predictLabelDS: DStream[(Int, Double)] = string2Label(uncleanedTrainingDS)

    /**
     * 创建模型 训练模型
     */
    val lr: StreamingLinearRegressionWithSGD = new StreamingLinearRegressionWithSGD().setInitialWeights(Vectors.zeros(10))
    lr.trainOn(trainLabeledPointDS)
    // 预测
    val resultDS: DStream[(Int, Double)] = lr.predictOnValues(predictVectorDS)

    /**
     * 计算指标
     */
    // label在tup._1 result在tup._2
    val unionDS: DStream[(Int, (Double, Double))] = predictLabelDS.join(resultDS)
    unionDS.print()

    unionDS.foreachRDD(rdd => {
      // rdd的数据转成array
      val rddArray: Array[(Int, (Double, Double))] = rdd.collect()

      // 样本数
      val cnt = rddArray.length
      // 计算y_true_bar
      var y_bar: Double = 0.0
      rddArray.foreach(line => {
        y_bar += line._2._1
      })
      y_bar /= cnt + 0.0

      var mse: Double = 0.0
      rddArray.foreach(line => {
        mse += Math.pow(line._2._1 - line._2._2, 2)
      })
      mse /= cnt + 0.0
      val rmse = Math.sqrt(mse)

      var ybarAndYpredictDiffAvg: Double = 0.0
      rddArray.foreach(line => {
        ybarAndYpredictDiffAvg += Math.pow(y_bar - line._2._2, 2) // (y_bar - y_predict)^2
      })
      ybarAndYpredictDiffAvg /= cnt

      val r2 = 1 - (mse / ybarAndYpredictDiffAvg)

      println("MSE", mse)
      println("RMSE", rmse)
      println("R2", r2)
    })

    ssc.start()
    ssc.awaitTermination()
  }
}

经过测试可以计算出MSE、RMSE、R2这三个指标,但是不知道是我计算错了还是什么问题,R2出来是负数,按道理范围应该是越接近1越好,出现负数就表明预测值的效果还不如直接用均值替代。从另外两个指标来看,RMSE范围普遍在10左右,看起来不大,但是观察数据集,10的误差相当于乱来。而且RMSE的误差跟在离线线性回归的测试中结果一样,但是R2更加离谱。

下面是几轮指标在控制台的输出:

MSERMSER2
第一轮108.7769528941237110.429619019605832-26.590090737398288
第二轮80.998005423002438.999889189484636-13.982834915982915
第三轮117.3484765670855610.832750184836977-19.2631464767166
第四轮108.2655003554942710.405070896226237-10.90529951327861
第五轮80.435607260813928.968590037503883-24.472098918023324

这输出的效果很不正常,所以我决定自己造一个一元函数去测试,我重新写了一个生产者:

import java.sql.*;
import java.util.Random;


public class LinearRegressionDataWriter2 {

    String driver = "dm.jdbc.driver.DmDriver";
    String url = "jdbc:dm://DM ip:5236/xx";
    String username = "xx";
    String password = "xx";

    Connection conn = null;
    Statement stmt = null;

    public void loadJdbcDriver() throws SQLException {
        try {
            System.out.println("loading jdbc Driver ...");
            Class.forName(driver);
        } catch (ClassNotFoundException e) {
            throw new SQLException("Load Jdbc Driver Error :" + e.getMessage());
        }
    }

    public void insertOne(Integer id, Integer x1, Float y) {
        String sql = "INSERT INTO jc.linearTraining20 VALUES (?,?,?)";
        PreparedStatement pstmt = null;

        try {
            pstmt = conn.prepareStatement(sql);
            pstmt.setInt(1, id);
            pstmt.setInt(2, x1);
            pstmt.setFloat(3, y);
        } catch (SQLException e) {
            System.out.println("Build PreparedStatement fail : " + e.getMessage());
        }

        try {
            pstmt.executeUpdate();
            System.out.println("Insert Successfully");
            pstmt.close();
        } catch (SQLException e) {
            System.out.println("Insert Operation fail : " + e.getMessage());
        }
    }

    public static void main(String[] args) {

        /**
         * 建立连接
         */
        LinearRegressionDataWriter2 writer = new LinearRegressionDataWriter2();
        try {
            writer.loadJdbcDriver();
            writer.conn = DriverManager.getConnection(writer.url, writer.username, writer.password);
        } catch (SQLException e) {
            System.out.println(e.getMessage());
        }

        /**
         * 插入数据
         */
        int i = 0;
        while (true) {
            Random random = new Random();
            Integer x = random.nextInt(10) + 1; // [1,10]
            writer.insertOne(i, x, (float) (1 + (Math.random() * 0.1 - 0.05)) * x); // y = [0.95,1.05] * x
            i += 1;
        }

    }

}

生成的x在[0,10]之间,系数k大概在1的上下0.05范围波动。

打印出来的数很奇怪,RMSE是1.76*10^126:

IDMSERMSER2
13.10297680506421E2521.7615268391552284E1260.0

难道真的我算错数?我再复盘一下每个指标的代码:

unionDS.foreachRDD(rdd => {
    // rdd的数据转成array
    val rddArray: Array[(Int, (Double, Double))] = rdd.collect()

    // 样本数
    val cnt = rddArray.length
    // 计算y_true_bar
    var y_bar: Double = 0.0
    rddArray.foreach(line => {
        y_bar += line._2._1
    })
    y_bar /= cnt + 0.0

    var mse: Double = 0.0
    rddArray.foreach(line => {
        mse += Math.pow(line._2._1 - line._2._2, 2)
    })
    mse /= cnt + 0.0
    val rmse = Math.sqrt(mse)

    var ybarAndYpredictDiffAvg: Double = 0.0
    rddArray.foreach(line => {
        ybarAndYpredictDiffAvg += Math.pow(y_bar - line._2._2, 2) // (y_bar - y_predict)^2
    })
    ybarAndYpredictDiffAvg /= cnt

    val r2 = 1 - (mse / ybarAndYpredictDiffAvg)

    println("MSE", mse)
    println("RMSE", rmse)
    println("R2", r2)

})

计算MSE的代码在这里:

// 样本数
val cnt = rddArray.length
var mse: Double = 0.0
rddArray.foreach(line => {
    mse += Math.pow(line._2._1 - line._2._2, 2)
})
mse /= cnt + 0.0

我觉得应该是没问题的,那么按照RMSE是MSE的开方来看,RMSE应该也没问题:

val rmse = Math.sqrt(mse)

那这两个数为什么那么大?可能我要打印一下差值,添加了打印差值的代码,重新跑一遍:

打印出的差值如下:
0.08002367232335317
0.03811180111069434
0.233659837503307
....
0.002285847737412583
0.14767864924167373
0.04050945274629103

这次所有指标都正常了:

IDMSERMSER2
10.027855453288039060.166899530520726920.9955250175769127
20.0281589216999915920.167806202805473180.9959743402203547
30.0430845952160867560.20756829048794220.9945786765892641

这回指标全都很正常,而且很准,可能是之前我用的x的范围是[0,200]太大了,然后停掉之后改了范围重新监听原来的topic,而原来的topic还有之前的数据所以才会那么大,甚至我看到inf无穷大。总之这回指数都很正常,而且整体的代码基本没有改动,只是清洗数据的步骤根据数据源做了相应的调整,所以也就反向印证了在官方流式线性回归案例中用的案例是乱来的。

二、StreamingLogisticRegressionWithSGD测试

接下来测流式逻辑回归的算法指标,回归算法的指标都一样,但是由于用途不同所以区分了,所以还是用RMSE、MSE、R2来做指标。虽然在做批式逻辑回归的测试的时候做过相关的测试,但是我还是先看一下Spark的文档:

2.1 Spark官方文档

2.1.1 数学公式

许多标准机器学习方法都可以归结为凸优化问题,比如去找到带有权重向量w的凸方程f的最小值的问题,一般来说目标函数可以写成这样:

f ( W ) : = λ R ( W ) + 1 n ∑ i = 1 n L ( W , x i , y i ) f(W) := \lambda R(W) + \frac{1}{n} \sum_{i=1}^n L(W,x_i,y_i) f(W):=λR(W)+n1i=1nL(W,xi,yi)

  • x i 是 训 练 样 本 , y i 是 样 本 对 应 的 标 签 。 当 一 个 方 程 可 以 用 y 和 W T x 来 表 达 的 时 候 我 们 称 之 为 线 性 方 程 。 x_i是训练样本 , y_i是样本对应的标签。当一个方程可以用y和 W^Tx 来表达的时候我们称之为线性方程。 xiyiyWTx线
2.1.2 损失函数

下面这个表格整理了Spark支持的损失函数、梯度和次梯度:

  • h i n g e l o s s : 损 失 函 数 L ( w ; x , y ) = m a x ( 0 , 1 − y w T x ) , y ∈ ( − 1 , + 1 ) 梯 度 或 次 梯 度 = { − y x i f   y w T x < 1 , 0 o t h e r w i s e . hinge loss :\\ 损失函数L(w;x,y) = max(0,1−yw^Tx),y∈({−1,+1})\\梯度或次梯度=\begin{cases} -yx & if\ yw^Tx < 1, \\ 0 & otherwise. \end{cases} hingelossL(w;x,y)=max(0,1ywTx),y(1,+1)={yx0if ywTx<1,otherwise.

  • l o g i s t i c l o s s : 损 失 函 数 = l o g ( 1 + exp ⁡ ( − y w T x ) ) , y ∈ ( − 1 , + 1 ) 梯 度 或 次 梯 度 = − y ( 1 − 1 1 + exp ⁡ ( − y w T x ) ) x logistic loss : \\损失函数 = log(1+ \exp(-yw^Tx)),y∈(-1,+1)\\梯度或次梯度=-y(1-\frac{1}{1+\exp(-yw^Tx)})x logisticloss:=log(1+exp(ywTx)),y(1,+1)=y(11+exp(ywTx)1)x

  • s q u a r e d l o s s : 损 失 函 数 = 1 2 ( w T x − y ) 2 , y ∈ R 梯 度 或 次 梯 度 = ( w T x − y ) x squared loss :\\损失函数 =\frac{1}{2}(w^Tx-y)^2,y∈\R \\ 梯度或次梯度=(w^Tx - y )x squaredloss:=21(wTxy)2,yR=(wTxy)x

2.1.3 正则化器

正则化器的目的就是为了让模型简化并且避免过拟合,Spark支持下面几种正则化:zero(不正则化)、L1、L2、elastic net。

2.1.4 优化

在Spark,线性模型使用SGD和L-BFGS两种优化器去优化目标方程。

2.1.5 分类

分类有二分类和多分类两种,在spark.mllib中有两种线性分类方法: linear Support Vector Machines (SVMs) 和 logistic regression 。线性SVMs只支持二分类,逻辑回归支持二分类和多分类。而且对于这两个方法都支持L1、L2的正则化变体。

2.2 逻辑回归

在做批式逻辑回归的评价指标的时候我忽略了一个很严重的问题,这次在流式计算的指标计算中表现出来了。这个问题就是,因为逻辑回归做二分类预测只有两种取值(0,1),所以在计算MSE的时候是小于等于1的,故RMSE会比MSE大。而且二分类问题其实也不需要用到这些指标,其实需要做准确率、精准率、召回率。下面是使用MSE、RMSE、R2这三个指标做出来的结果:

IDMSERMSER2
10.30.5477225575051661-0.19999999999999996
20.30.5477225575051661-0.19999999999999996
30.294117647058823540.5423261445466404-0.20472440944881765
40.31372549019607840.5601120336112039-0.22522522522522426
50.196078431372549020.442807442770047630.22727272727272796
60.20.44721359549995790.19999999999999996

2.3 逻辑回归二分类指标

2.3.1 准确率(Accuracy)

准确率顾名思义就是在样本n中预测正确的概率,其公式是:
a c c = 预 测 正 确 的 样 本 数 总 样 本 数 acc = \frac{预测正确的样本数}{总样本数} acc=
但是这个指标有缺陷,用疾病预测来说,若发病率只有0.1%,预测所有人健康,则acc也会高达99.9%。

2.3.2 精准率(Precision)

精准率表示在预测为正的样本中真正为正的样本比例,公式是:
P r e c i s i o n = T P T P + F P T P 是 本 身 为 正 预 测 也 为 正 的 样 本 数 F P 是 本 身 为 错 预 测 为 正 的 样 本 数 Precision = \frac{TP}{TP+FP} \\ TP是本身为正预测也为正的样本数 \\FP是本身为错预测为正的样本数 Precision=TP+FPTPTPFP

2.3.3 召回率(Recall)

召回率关注原来数据集本身,表示的是原来数据集中的正样本有多少预测正确,公式是:
R e c a l l = T P T P + F N F N 是 原 来 是 正 的 样 本 预 测 成 负 的 样 本 数 Recall = \frac{TP}{TP + FN}\\ FN是原来是正的样本预测成负的样本数 Recall=TP+FNTPFN
以上三个指标都是在[0,1]内的,越接近1效果越好。

2.4 用二分类的指标测试

下面是我修改过的完整代码:

import java.lang

import StreamingLinearRegressionWithSGD_Online_TrainAndPredict.{string2Label, string2LabeledPoint, string2Vector}
import com.alibaba.fastjson.{JSON, JSONObject}
import org.apache.kafka.clients.consumer.ConsumerRecord
import org.apache.kafka.common.serialization.StringDeserializer
import org.apache.spark.SparkConf
import org.apache.spark.mllib.classification.StreamingLogisticRegressionWithSGD
import org.apache.spark.mllib.linalg
import org.apache.spark.mllib.linalg.Vectors
import org.apache.spark.mllib.regression.LabeledPoint
import org.apache.spark.streaming.dstream.{DStream, InputDStream}
import org.apache.spark.streaming.kafka010.{ConsumerStrategies, KafkaUtils, LocationStrategies}
import org.apache.spark.streaming.{Seconds, StreamingContext}



object StreamingLogisticRegressionWithSGD_Online_TrainAndPredict {

  def string2Vector2(uncleanTrainingDS: DStream[String]) = {
    var i: Int = 0
    val value: DStream[(Int, linalg.Vector)] = uncleanTrainingDS.map(line => {
      val json: JSONObject = JSON.parseObject(line)
      val payload: JSONObject = json.getJSONObject("payload")
      val y = payload.getDouble("y")
      val x1 = payload.getDouble("x1")
      val x2 = payload.getDouble("x2")
      i += 1
      (i, Vectors.dense(Array(x1.toDouble, x2.toDouble)))
    })
    value
  }


  def string2Label2(uncleanTrainingDS: DStream[String]) = {
    var i: Int = 0
    uncleanTrainingDS.map(line => {
      val json: JSONObject = JSON.parseObject(line)
      val payload: JSONObject = json.getJSONObject("payload")
      val y = payload.getDouble("y")
      i += 1
      (i, y.toDouble)
    })
  }


  def string2LabeledPoint2(uncleanedTrainingDS: DStream[String]) = {
    uncleanedTrainingDS.map(line => {
      val json: JSONObject = JSON.parseObject(line)
      val payload: JSONObject = json.getJSONObject("payload")
      val y = payload.getDouble("y")
      val x1 = payload.getDouble("x1")
      val x2 = payload.getDouble("x2")

      LabeledPoint(y.toDouble, Vectors.dense(Array(x1.toDouble, x2.toDouble)))
    })
  }

  def main(args: Array[String]): Unit = {
    /**
     * 准备工作
     */
    // Spark
    val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("StreamingLinearRegressionWithSGD_Online_TrainAndPredict2")
    val ssc: StreamingContext = new StreamingContext(conf, Seconds(5))

    // kafka参数
    val kafkaParams: Map[String, Object] = Map[String, Object](
      // 组id
      "group.id" -> "g1",
      // kafka集群
      "bootstrap.servers" -> "kafka ip:9092",
      // key的解析器
      "key.deserializer" -> classOf[StringDeserializer],
      // value的解析器
      "value.deserializer" -> classOf[StringDeserializer],
      // 若没有偏移量,从最新的开始消费
      "auto.offset.reset" -> "latest",
      // 交给kafka自动维护偏移量
      "enable.auto.commit" -> (true: lang.Boolean)
    )

    // 训练模型的topic
    val trainingTopic = Array("test-dm-jc-logisticTraining4")

    /**
     * 链接kafka 接收数据
     */
    val trainStream: InputDStream[ConsumerRecord[String, String]] = KafkaUtils.createDirectStream[String, String](
      ssc,
      LocationStrategies.PreferConsistent,
      ConsumerStrategies.Subscribe[String, String](topics = trainingTopic, kafkaParams)
    )
    // 取到record
    val uncleanedTrainingDS: DStream[String] = trainStream.map(record => record.value())
    uncleanedTrainingDS.print()

    /**
     * 清洗数据
     */
    val trainLabeledPointDS: DStream[LabeledPoint] = string2LabeledPoint2(uncleanedTrainingDS)
    val predictVectorDS: DStream[(Int, linalg.Vector)] = string2Vector2(uncleanedTrainingDS)
    val predictLabelDS: DStream[(Int, Double)] = string2Label2(uncleanedTrainingDS)
    trainLabeledPointDS.print()

    val model: StreamingLogisticRegressionWithSGD = new StreamingLogisticRegressionWithSGD().setInitialWeights(Vectors.zeros(2))
    model.trainOn(trainLabeledPointDS)
    val resultDS: DStream[(Int, Double)] = model.predictOnValues(predictVectorDS)

    /**
     * 计算指标
     * ACC、Precision、Recall
     */
    val unionDS: DStream[(Int, (Double, Double))] = predictLabelDS.join(resultDS)
    println("*******************")
    unionDS.print()
    println("*******************")
    unionDS.foreachRDD(rdd => {
      val rddArray: Array[(Int, (Double, Double))] = rdd.collect()
      val cnt = rddArray.length
      var tp = 0 // 本身为正 预测为正
      var tn = 0 // 本身为负 预测为负
      var fp = 0 // 本身为负 预测为正
      var fn = 0 // 本身为正 预测为负
      var predictCorrect = 0 // 预测正确 无论01
      rddArray.foreach(line => {
        if (line._2._1.equals(1.0)) {
          if (line._2._2.equals(1.0)) {
            tp += 1
            predictCorrect += 1
          }
          else fn += 1
        } else {
          if (line._2._2.equals(1.0)) fp += 1
          else {
            tn += 1
            predictCorrect += 1
          }
        }
      })
      val acc = predictCorrect / (cnt + 0.0)
      val precision = (tp + 0.0) / (tp + fp + 0.0)
      val recall = (tp + 0.0) / (tp + fn + 0.0)
      println("ACC", acc)
      println("Precision", precision)
      println("Recall", recall)

    })
    ssc.start()
    ssc.awaitTermination()
  }
}

计算出的指标如下:

IDAccuracyPrecisionRecall
10.79591836734693881.00.5833333333333334
20.81.00.6
30.81632653061224491.00.625
40.79591836734693881.00.6
50.81.00.6
60.79591836734693881.00.6

可以看到准确率(Accuracy)一直维持在80%左右,精准率全都是100%,而召回率在60%左右波动。从ACC的角度看效果属于中等,从Precision的角度看效果很好,从Recall来看也属于一般水平。

上面这两个回归的测试训练模型的时间都不长,大概只有5~10分钟左右,所以可能训练出来的模型还不是很好。但是我二分类的数据集只有十来条,五分钟应该也够了吧。下次可能要找大一点的数据集,然后训练时间拉长到一小时以上看看才行。

三、总结

之前对DStream和RDD的理解还是不正确,实际上我好像没有认认真真去看过官方文档,所以我又走了弯路。这回总结也就不总结上面的问题了,整个测试过程、思路都写的明明白白,哪怕我做错了我也贴上来并且附上我的思考,大家应该看得懂。这次实验还是让我明白一个道理,无论做什么事,一定不能急,像这次哪怕我拖一个礼拜也能看完整个Spark文档,到时候再做其实很轻松。但是我性子急,上来就开干,干完算法干数据结构,缺哪补哪,但是不成体系,理解也不正确,最终导致完不成任务。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值