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更加离谱。
下面是几轮指标在控制台的输出:
MSE | RMSE | R2 | |
---|---|---|---|
第一轮 | 108.77695289412371 | 10.429619019605832 | -26.590090737398288 |
第二轮 | 80.99800542300243 | 8.999889189484636 | -13.982834915982915 |
第三轮 | 117.34847656708556 | 10.832750184836977 | -19.2631464767166 |
第四轮 | 108.26550035549427 | 10.405070896226237 | -10.90529951327861 |
第五轮 | 80.43560726081392 | 8.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:
ID | MSE | RMSE | R2 |
---|---|---|---|
1 | 3.10297680506421E252 | 1.7615268391552284E126 | 0.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
这次所有指标都正常了:
ID | MSE | RMSE | R2 |
---|---|---|---|
1 | 0.02785545328803906 | 0.16689953052072692 | 0.9955250175769127 |
2 | 0.028158921699991592 | 0.16780620280547318 | 0.9959743402203547 |
3 | 0.043084595216086756 | 0.2075682904879422 | 0.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=1∑nL(W,xi,yi)
- x i 是 训 练 样 本 , y i 是 样 本 对 应 的 标 签 。 当 一 个 方 程 可 以 用 y 和 W T x 来 表 达 的 时 候 我 们 称 之 为 线 性 方 程 。 x_i是训练样本 , y_i是样本对应的标签。当一个方程可以用y和 W^Tx 来表达的时候我们称之为线性方程。 xi是训练样本,yi是样本对应的标签。当一个方程可以用y和WTx来表达的时候我们称之为线性方程。
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} hingeloss:损失函数L(w;x,y)=max(0,1−ywTx),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(1−1+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(wTx−y)2,y∈R梯度或次梯度=(wTx−y)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这三个指标做出来的结果:
ID | MSE | RMSE | R2 |
---|---|---|---|
1 | 0.3 | 0.5477225575051661 | -0.19999999999999996 |
2 | 0.3 | 0.5477225575051661 | -0.19999999999999996 |
3 | 0.29411764705882354 | 0.5423261445466404 | -0.20472440944881765 |
4 | 0.3137254901960784 | 0.5601120336112039 | -0.22522522522522426 |
5 | 0.19607843137254902 | 0.44280744277004763 | 0.22727272727272796 |
6 | 0.2 | 0.4472135954999579 | 0.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+FPTPTP是本身为正预测也为正的样本数FP是本身为错预测为正的样本数
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()
}
}
计算出的指标如下:
ID | Accuracy | Precision | Recall |
---|---|---|---|
1 | 0.7959183673469388 | 1.0 | 0.5833333333333334 |
2 | 0.8 | 1.0 | 0.6 |
3 | 0.8163265306122449 | 1.0 | 0.625 |
4 | 0.7959183673469388 | 1.0 | 0.6 |
5 | 0.8 | 1.0 | 0.6 |
6 | 0.7959183673469388 | 1.0 | 0.6 |
可以看到准确率(Accuracy)一直维持在80%左右,精准率全都是100%,而召回率在60%左右波动。从ACC的角度看效果属于中等,从Precision的角度看效果很好,从Recall来看也属于一般水平。
上面这两个回归的测试训练模型的时间都不长,大概只有5~10分钟左右,所以可能训练出来的模型还不是很好。但是我二分类的数据集只有十来条,五分钟应该也够了吧。下次可能要找大一点的数据集,然后训练时间拉长到一小时以上看看才行。
三、总结
之前对DStream和RDD的理解还是不正确,实际上我好像没有认认真真去看过官方文档,所以我又走了弯路。这回总结也就不总结上面的问题了,整个测试过程、思路都写的明明白白,哪怕我做错了我也贴上来并且附上我的思考,大家应该看得懂。这次实验还是让我明白一个道理,无论做什么事,一定不能急,像这次哪怕我拖一个礼拜也能看完整个Spark文档,到时候再做其实很轻松。但是我性子急,上来就开干,干完算法干数据结构,缺哪补哪,但是不成体系,理解也不正确,最终导致完不成任务。