机器学习 推断
我们在哪? 我们去哪?
在本系列的前四个部分中,您学习了如何:
您可能想知道还有什么要做。 的确,本教程系列涵盖了Apache Spark的许多方面,但是使用此分析平台可以发现更多内容。 现在该探讨Spark的主要功能之一:它对机器学习的支持。
在本系列的前面各部分中,您以商店的销售数据为例。 该想法只是向您展示您可以使用机器学习做什么:根据先前的订单预测未来的订单。
您需要什么:
- Spark ML(用于机器学习)库,位于GitHub上的项目中。
- 每个Informix®数据库附带的stores_demo数据集。
注意:如果您不具备Informix知识,请不要担心。 您不需要它即可阅读和理解本教程。 但是,请随时考虑将IBM Informix用作下一个项目的RDBMS。 - 您用于第1-4部分的代码
- 对于这一部分,实验室已经投入使用。 GitHub上的
net.jgp.labs.informix2spark.l5x0
软件包。
数学
我爱数学。 来自法国的集中式教育系统,如果您想访问最好的工科学校,您最好爱数学。 在其他许多地方也是如此,但法国似乎是一个极端。 对我来说不幸的是,机器学习(ML)背后的数学是大量的统计数据和概率。 尽管我喜欢统计数据,但我并不是最大的概率迷。 也许这是因为它们在某些情况下是随机的。
因此,我总是尝试着将对ML的数学影响最小化。 我发现这使我们大多数人都可以理解ML。
线性回归
线性回归是您将要实现的概念。 想象一下下图:x轴(横坐标)是周数,y轴(纵坐标)是本周的总订单量。 它看起来应如下图所示。
线性回归的原理是绘制一条直线,该直线与图表上所有点的距离最小。
在这种情况下,回归线为:
您现在可以想象,我们将继续此行以查看行进路线。 但是,首先,您需要知道我们如何获得数据。
使用Spark获取数据
使用本系列前面各部分中的示例,使它们适应以获取订单,然后按周对销售额进行分组。 输出应如下所示:
+----------+----------------+
|order_week|sum(total_price)|
+----------+----------------+
| 21| 4387.00|
| 22| 2144.00|
| 23| 940.00|
| 24| 450.00|
| 25| 1366.80|
| 26| 2544.00|
| 28| 3652.97|
| 30| 2670.00|
+----------+----------------+
注意:在本系列的这一部分中,我不会解释代码的每个部分。 到此时,您应该可以轻松阅读代码,而不必将其分成小块。 就是说,如果您有任何问题,请在评论中提出问题。
您的代码应如下所示:
package net.jgp.labs.informix2spark.l500;
import static org.apache.spark.sql.functions.lit;
import static org.apache.spark.sql.functions.weekofyear;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.SparkSession;
import org.apache.spark.sql.jdbc.JdbcDialect;
import org.apache.spark.sql.jdbc.JdbcDialects;
import net.jgp.labs.informix2spark.utils.Config;
import net.jgp.labs.informix2spark.utils.ConfigManager;
import net.jgp.labs.informix2spark.utils.InformixJdbcDialect;
import net.jgp.labs.informix2spark.utils.K;
public class OrdersPerWeekApp {
public static void main(String[] args) {
OrdersPerWeekApp app = new OrdersPerWeekApp();
app.start();
}
private void start() {
SparkSession spark;
spark = SparkSession
.builder()
.appName("Sales per week")
.master("local")
.getOrCreate();
// List of all tables we want to work with
List<String> tables = new ArrayList<>();
tables.add("orders");
tables.add("items");
// Specific Informix dialect
JdbcDialect dialect = new InformixJdbcDialect();
JdbcDialects.registerDialect(dialect);
// Let's connect to the database
Config config = ConfigManager.getConfig(K.INFORMIX);
// Let's build our datalake
Map<String, <Dataset<Row> datalake = new HashMap<>();
for (String table : tables) {
System.out.print("Loading table [" + table + "] ... ");
Dataset<Row> df = spark.read()
.format("jdbc")
.option("url", config.getJdbcUrl())
.option("dbtable", table)
.option("user", config.getUser())
.option("password", config.getPassword())
.option("driver", config.getDriver())
.load();
datalake.put(table, df);
System.out.println("done");
}
System.out.println("We have loaded " + datalake.size()
+ " table(s) in our data lake");
// Let's look at the content
Dataset<Row> ordersDf = datalake.get("orders");
Dataset<Row> itemsDf = datalake.get("items");
// Builds the datasets in 2 steps, first with the week number...
Dataset<Row> allDf = ordersDf
.join(
itemsDf,
ordersDf.col("order_num").equalTo(itemsDf.col("order_num")),
"full_outer")
.drop(ordersDf.col("customer_num"))
.drop(itemsDf.col("order_num"))
.withColumn("order_week", lit(weekofyear(ordersDf.col("order_date"))));
// ... then with
allDf = allDf
.groupBy(allDf.col("order_week"))
.sum("total_price")
.orderBy(allDf.col("order_week"));
allDf.show(50);
}
}
创建allDF
数据allDF
时,此应用程序的“肉”开始。 首先,根据order_date
创建一个名为order_week
的order_date
。 使用weekofyear()
静态方法从日期确定星期数,并使用lit()
静态方法在数据框中从头开始创建列。 两种方法均在代码开头静态导入。
资料品质
查看数据始终很重要。 这样做可能不会捕获所有异常,尤其是对于大数据。 但是,通过查看它,您可以看到缺少第27和29周。 根据这一观察,您至少要做出两个决定:
- 忽略丢失的数据。 也许中央系统尚未更新,这不是第一次发生,或者是前几天使系统崩溃的实习生。
- 假设没有任何订单; 这意味着您必须插入两行,数量为0。
我建议您采用第一个解决方案:不要将其归咎于实习生,而要留意您的决定。
机器学习两秒钟入门
ML算法可能很复杂。 但是,该原理确实很容易。 您建立 (或训练 ) 模型 ,然后将此模型应用于数据集以预测结果。 在这种情况下,您将只执行步骤2,但是您可以轻松地想象不同的情况,其中模型不会更改,并且可以在步骤3、4等中重用。
当然,作为数据专业人员,您可以想象此模型所带来的整个生命周期活动:验证,优化,测试等。但是,这些活动在本入门指南的范围之外。
建立模型
您有数据,也有理论。 现在您可以练习了。
您的第一步是准备数据以供ML训练器摘要。 它根据算法的类型而有所不同,但是线性回归需要特征和标签 。
从本质上讲,标签就是您正在研究的内容,并且功能定义了它。 因此,如果您查看第28周的订单,您的收入为$ 3,652.97,则标签为3652.97
,其功能之一是28
。
您可以添加更多功能,例如:
- 温度
- 降水量
- 前几年同一周的订单总数
- 假期前后的天数等
我记得我的一个朋友卖过游泳池。 他的交货时间大约有六个月。 在阳光明媚的时候,他卖出了更多的游泳池,因此将阳光照到他的模型上是合理的。
一个常见的错误是混淆标签和功能,尤其是在只有一个功能的情况下。
要使用线性回归,即使您的向量仅包含一个元素,Spark仍需要一个特征向量。 基本上,Spark期望以下数据帧:
+----------+----------------+-------+--------+
|order_week|sum(total_price)| label|features|
+----------+----------------+-------+--------+
| 21| 4387.00|4387.00| [21.0]|
| 22| 2144.00|2144.00| [22.0]|
| 23| 940.00| 940.00| [23.0]|
| 24| 450.00| 450.00| [24.0]|
| 25| 1366.80|1366.80| [25.0]|
| 26| 2544.00|2544.00| [26.0]|
| 28| 3652.97|3652.97| [28.0]|
| 30| 2670.00|2670.00| [30.0]|
+----------+----------------+-------+--------+
您可以简单地将sum(total_price)
列重命名为label
,但是由于label和features列均仅是线性回归算法的技术约束,因此我希望将数据与技术约束分开。
要构建矢量,可以使用用户定义的函数(UDF)。 此扩展名从原始值创建向量。
package net.jgp.labs.informix2spark.l520;
import org.apache.spark.ml.linalg.Vector;
import org.apache.spark.ml.linalg.Vectors;
import org.apache.spark.sql.api.java.UDF1;
public class VectorBuilderInteger implements UDF1<Integer, Vector> {
private static final long serialVersionUID = -2991355883253063841L;
@Override
public Vector call(Integer t1) throws Exception {
double d = t1.doubleValue();
return Vectors.dense(d);
}
}
UDF实现了Integer
(输入类型)和Vector
(返回类型)的UDF1。 向量需要double值,因此您需要将整数转换为double。
在使用UDF之前,您必须在Spark会话中注册它。 创建Spark会话后,请确保立即注册UDF。
spark.udf().register("vectorBuilder", new VectorBuilderInteger(), new VectorUDT());
在这种情况下:
-
vectorBuilder
是您要添加到Spark SQL中的函数的名称。 -
VectorBuilderInteger
是实现UDF的类。 -
VectorUDT
是返回类型。
在转换代码中,您可以简单地调用vectorBuilder()
函数来创建列。
Dataset<Row> df = allDf
.withColumn("values_for_features", allDf.col("order_week"))
.withColumn("label", allDf.col("sum(total_price)"))
.withColumn("features", callUDF("vectorBuilder", col("values_for_features")))
.drop(col("values_for_features"));
现在您已经以正确的形式获得了数据,创建模型仅需要两行代码。
LinearRegression lr = new LinearRegression().setMaxIter(20);
LinearRegressionModel model = lr.fit(df);
内省一下呢?
本部分是可选的。 想象一下,我添加了它是为了达到发现未来订单的最终目标,从而增加了悬念。但是,我也为那些想了解确实没有水晶球但有一些方法论和科学的数学爱好者添加了它。
Spark提供了检查模型所需的工具。 首先,将模型应用于您拥有的完整数据框:
model.transform(df).show();
这将添加一个预测列(线性回归线上的值)。
+----------+----------------+-------+--------+------------------+
|order_week|sum(total_price)| label|features| prediction|
+----------+----------------+-------+--------+------------------+
| 21| 4387.00|4387.00| [21.0]|2101.3694797687876|
| 22| 2144.00|2144.00| [22.0]|2144.7183236994233|
| 23| 940.00| 940.00| [23.0]|2188.0671676300585|
| 24| 450.00| 450.00| [24.0]|2231.4160115606937|
| 25| 1366.80|1366.80| [25.0]|2274.7648554913294|
| 26| 2544.00|2544.00| [26.0]|2318.1136994219646|
| 28| 3652.97|3652.97| [28.0]|2404.8113872832355|
| 30| 2670.00|2670.00| [30.0]| 2491.509075144506|
+----------+----------------+-------+--------+------------------+
看一下与模型相关的不同数学计算:
LinearRegressionTrainingSummary trainingSummary = model.summary();
System.out.println("numIterations: " + trainingSummary.totalIterations());
System.out.println("objectiveHistory: " +
Vectors.dense(trainingSummary.objectiveHistory()));
trainingSummary.residuals().show();
System.out.println("RMSE: " + trainingSummary.rootMeanSquaredError());
System.out.println("r2: " + trainingSummary.r2());
此代码返回:
numIterations: 1
objectiveHistory: [0.0]
+-------------------+
| residuals|
+-------------------+
| 2285.6305202312124|
|-0.7183236994233084|
|-1248.0671676300585|
|-1781.4160115606937|
| -907.9648554913294|
| 225.8863005780354|
| 1248.1586127167643|
| 178.4909248554941|
+-------------------+
RMSE: 1246.0139337359603
r2: 0.009719742211204974
让我们看一下其中一个条件。 均方根误差(RMSE)也称为均方根偏差(RMSD),用于测量模型或估计器预测的值(样本和总体值)与观测值之间的差异。 因为这是一个距离,所以数字越小越好。 而且,当您将其与标签的值进行比较时,这意味着您还差得很远,这不好。
虽然这不好,但解释很容易。 标签中的差异很大,因为功能数量有限。 这绝对不是大数据。
其他参数定义线:截距,回归参数和迭代的收敛容限。
double intercept = model.intercept();
System.out.println("Intersection: " + intercept);
double regParam = model.getRegParam();
System.out.println("Regression parameter: " + regParam);
double tol = model.getTol();
System.out.println("Tol: " + tol);
结果是:
Intersection: 1191.0437572254443
Regression parameter: 0.0
Tol: 1.0E-6
没那么神奇的水晶球
现在,您可以预测未来三周的订单了。 您将发现要做的复杂代码。 让悬念增加,代码优先:
for (double feature = 31.0; feature < 34; feature++) {
Vector features = Vectors.dense(feature);
double p = model.predict(features);
System.out.printf("Total orders prediction for week #%d is $%4.2f.\n",
Double.valueOf(feature).intValue(),
p);
}
记住您之前看到的内容:要素存储在向量中。 因此,即使您只有一个要素(星期数),它仍然需要一个向量,因此请记住使用该要素构建此向量。
然后,您可以使用向量从模型中调用predict()
方法。 而已; 您刚刚做出了第一个预测!
将模型应用于新功能(此处为周号)需要七行代码。 在这七行中,三行用于显示,两行用于循环。 结果:
Total orders prediction for week #31 is $2534.86.
Total orders prediction for week #32 is $2578.21.
Total orders prediction for week #33 is $2621.56.
给自己打五分! 您遵循了很长时间(希望不会太痛苦)的教程系列,甚至发现公司的订单正在增加。
你学到了什么
《在Spark中卸载Informix数据》系列的第五部分教您:
- 基于RDBMS数据的ML的用例,而ML并不总是需要大量的数据。
- 有点数学,例如RMSE衡量模型的质量。
- 数据质量的重要性(我可能应该教更多的DQ)。
- 线性回归是ML的一种简单形式。
- ML具有一个简单的过程:训练模型并在新数据上重用模型。
告别
这也是本系列的最后一部分。 我衷心希望您喜欢它。 我非常高兴地编写了本系列的每个部分,并要感谢IBM的支持团队,尤其是Robin Wood,他们表现出了耐心,对我的英语的宽容并提供了帮助。 谢谢,罗宾。
让我们通过Twitter(@jgperrin),电子邮件jgp@jgp.net(我回复所有电子邮件)或下面的评论保持联系。 我们将在2018年的IBM Think中与您联系以获取更多Spark内容!
走得更远
更多阅读信息:
- 要在您的Mac上安装并更好地理解Informix,请阅读“ Mac 10.12上的Informix 12.10,并带有一些Java 8:苹果,咖啡和一个伟大的数据库的故事 ”(自私无耻的插件;我写了这本书)。
- 从我的GitHub存储库下载本教程中的所有代码。 如何使用JDBC将IBM Informix数据传输到Apache Spark 。 别忘了喜欢叉!
- 在我的文章Meet Cactar,古代蒙古数据质量军阀上,阅读有关数据质量和我的朋友Cactar的更多信息。
- 观看视频和幻灯片,了解我在2017年6月的Spark峰会上所做的主题为“ 机器学习的关键是准备正确的数据 ”的演讲。
- 在Wikipedia上了解有关线性回归的更多信息。
- 在IBM网站和国际Informix用户组上了解有关Informix的更多信息。
- 注册参加IBM Think会议。
翻译自: https://www.ibm.com/developerworks/opensource/library/ba-offloading-informix-data-spark-5/index.html
机器学习 推断