Spark Machine Learning(SparkML):机器学习(部分三)

目录

8.协同过滤(Collaborative Filtering)

8.1交替最小二乘ALS

8.2显式和隐式反馈

8.3缩放正则化参数

8.4冷启动策略

8.5代码示例:

9.频繁模式挖掘(Frequent Pattern Mining)

FP-Growth

PrefixSpan

10.ML优化:模型选择和超参数调优

模型选择(又称超参数调优)

交叉验证(Cross-Validation)

训练验证拆分(Train-Validation Split)

欠/过拟合的模型优化

11.高级主题--线性方法的优化(开发者)

Limited-memory BFGS(L-BFGS)

加权最小二乘法的正规方程求解器

迭代重加权最小二乘(IRLS)


8.协同过滤(Collaborative Filtering)

协同过滤 是一种推荐算法,根据用户对物品的偏好构建稀缺矩阵,并计算其对其他物品的喜好程度,通常用于推荐系统。这些技术旨在填写用户项关联矩阵的缺失条目。 spark.ml目前支持基于模型的协同过滤,其中用户和产品由可用于预测缺失条目的一小组潜在因素描述。 spark.ml使用交替最小二乘(ALS) 算法来学习这些潜在因素。实现中spark.ml包含以下参数:

(1).numBlocks是用户和项目将被分区为多个块的数量,以便并行化计算(默认为10)。
(2).rank是模型中潜在因子的数量(默认为10)。
(3).maxIter是要运行的最大迭代次数(默认为10)。
(4).regParam指定ALS中的正则化参数(默认为1.0)。
(5).implicitPrefs指定是使用显式反馈 ALS变体还是使用适用于隐式反馈数据的变体 (默认为false使用显式反馈)。
(6).alpha是适用于ALS的隐式反馈变量的参数,其控制偏好观察中的 基线置信度(默认为1.0)。
(7).nonnegative指定是否对最小二乘使用非负约束(默认为false)。

注意:基于DataFrame的ALS API目前仅支持用户和项ID的整数。user和item id列支持其他数字类型,但id必须在整数值范围内。

8.1交替最小二乘ALS

最小二乘法(又称最小平方法)是一种数学优化技术。ALS是alternating least squares的缩写 , 意为交替最小二乘法, 它通过最小化误差的平方和寻找数据的最佳函数匹配。利用最小二乘法可以简便地求得未知的数据,并使得这些求得的数据与实际数据之间误差的平方和为最小。而ALS-WR是alternating-least-squares with weighted-λ -regularization的缩写,意为加权正则化交替最小二乘法。该方法常用于基于矩阵分解的推荐系统中最小二乘法还可用于曲线拟合。其他一些优化问题也可通过最小化能量或最大化熵用最小二乘法来表达。

例如:将用户(user)对商品(item)的评分矩阵分解为两个矩阵:一个是用户对商品隐含特征的偏好矩阵,另一个是商品所包含的隐含特征的矩阵。在这个矩阵分解的过程中,评分缺失项得到了填充,也就是说我们可以基于这个填充的评分来给用户最商品推荐了。

  由于评分数据中有大量的缺失项,传统的矩阵分解SVD(奇异值分解)不方便处理这个问题,而ALS能够很好的解决这个问题。对于R(m×n)的矩阵,ALS旨在找到两个低维矩阵X(m×k)和矩阵Y(n×k),来近似逼近R(m×n),即: 

其中R(m×n)代表用户对商品的评分矩阵,X(m×k)代表用户对隐含特征的偏好矩阵,Y(n×k)表示商品所包含隐含特征的矩阵,T表示矩阵Y的转置。实际中,一般取k<<min(m, n), 也就是相当于降维了。这里的低维矩阵,有的地方也叫低秩矩阵。

为了找到使低秩矩阵X和Y尽可能地逼近R,需要最小化下面的平方误差损失函数:

其中xu(1×k)表示示用户u的偏好的隐含特征向量,yi(1×k)表示商品i包含的隐含特征向量, rui表示用户u对商品i的评分, 向量xuyi的内积是用户u对商品i评分的近似。

损失函数一般需要加入正则化项来避免过拟合等问题,我们使用L2正则化,所以上面的公式改造为:

其中λ是正则化项的系数。

  到这里,协同过滤就成功转化成了一个优化问题。由于变量xu和yi耦合到一起,这个问题并不好求解,所以我们引入了ALS,也就是说我们可以先固定Y(例如随机初始化X),然后利用公式(2)先求解X,然后固定X,再求解Y,如此交替往复直至收敛,即所谓的交替最小二乘法求解法。

  具体求解方法说明如下:

先固定Y,  将损失函数L(X,Y)对xu求偏导,并令导数=0,得到: 

同理固定X,可得:

 其中ru(1×n)是R的第u行,ri(1×m)是R的第i列, I是k×k的单位矩阵。

迭代步骤:首先随机初始化Y,利用公式(3)更新得到X,  然后利用公式(4)更新Y,  直到均方根误差变RMSE化很小或者到达最大迭代次数。

上文提到的模型适用于解决有明确评分矩阵的应用场景,然而很多情况下,用户没有明确反馈对商品的偏好,也就是没有直接打分,我们只能通过用户的某些行为来推断他对商品的偏好。比如,在电视节目推荐的问题中,对电视节目收看的次数或者时长,这时我们可以推测次数越多,看得时间越长,用户的偏好程度越高,但是对于没有收看的节目,可能是由于用户不知道有该节目,或者没有途径获取该节目,我们不能确定的推测用户不喜欢该节目。ALS-WR通过置信度权重来解决这些问题:对于更确信用户偏好的项赋以较大的权重,对于没有反馈的项,赋以较小的权重。ALS-WR模型的形式化说明如下:

ALS-WR的目标函数:

其中α是置信度系数。

求解方式还是最小二乘法:

其中Cu是n×n的对角矩阵,Ci是m×m的对角矩阵;

8.2式和反馈

基于矩阵分解的协同过滤的标准方法将用户项矩阵中的条目视为用户对项目给出的显式偏好,例如,给予电影评级的用户。

在许多现实世界的用例中,通常只能访问隐式反馈(例如观看,点击,购买,喜欢,分享等)。用于spark.ml处理此类数据的方法来自隐式反馈数据集的协过滤。本质上,这种方法不是试图直接对评级矩阵进行建模,而是将数据视为代表强度的数字观察用户行为(例如点击次数或某人观看电影的累积持续时间)。然后,这些数字与观察到的用户偏好的置信水平相关,而不是与项目的明确评级相关。然后,该模型试图找到可用于预测用户对项目的预期偏好的潜在因素。

8.3缩放正则化参数

我们regParam通过用户在更新用户因素时产生的评级数或在更新产品因子时收到的产品评级数来缩小正则化参数以解决每个最小二乘问题。这种方法被命名为“ALS-WR”,并在“ Netflix奖的大规模并行协同过滤 ”一文中进行了讨论。它regParam更少依赖于数据集的规模,因此我们可以将从采样子集中学习的最佳参数应用于完整数据集,并期望获得类似的性能。

8.4冷启动策略

在使用a进行预测时ALSModel,通常会遇到测试数据集中的用户和/或项目,这些用户和/或项目在训练模型期间不存在。这通常发生在两种情况中:

(1).在生产中,对于没有评级历史且未对模型进行过训练的新用户或项目(这是“冷启动问题”)。
(2).在交叉验证期间,数据在训练和评估集之间分配。当Spark中的使用简单随机拆分为CrossValidator或者TrainValidationSplit,它实际上是非常普遍遇到的评估组不是在训练组用户和/或项目。

默认情况下,Spark会在模型中不存在用户和/或项目因子时指定NaN预测ALSModel.transform。这在生产系统中很有用,因为它表示新用户或项目,因此系统可以决定使用某些后备作为预测。

然而,这在交叉验证期间是不期望的,因为任何NaN预测值将导致NaN评估度量的结果(例如,在使用RegressionEvaluator)。这使得模型选择不可能。

Spark允许用户将coldStartStrategy参数设置为“drop”,以便删除DataFrame包含NaN值的预测中的任何行。然后将根据非NaN数据计算评估度量并且该评估度量将是有效的。以下示例说明了此参数的用法。

注意:目前支持的冷启动策略是“nan”(上面提到的默认行为)和“drop”。将来可能会支持进一步的战略。

8.5代码示例:

在以下示例中,我们从MovieLens数据集加载评级数据 ,每行包含用户,电影,评级和时间戳。然后,我们训练一个ALS模型,默认情况下假设评级是明确的(implicitPrefsfalse)。我们通过测量评级预测的均方根误差来评估推荐模型。

Scala代码:

import org.apache.spark.ml.evaluation.RegressionEvaluator
import org.apache.spark.ml.recommendation.ALS

case class Rating(userId: Int, movieId: Int, rating: Float, timestamp: Long)

def parseRating(str: String): Rating = {
  val fields = str.split("::")
  assert(fields.size == 4)
  Rating(fields(0).toInt,fields(1).toInt,fields(2).toFloat,fields(3).toLong)
}
val ratings = spark.read.textFile("/sample_movielens_ratings.txt")
  .map(parseRating)
  .toDF()
val Array(training, test) = ratings.randomSplit(Array(0.8, 0.2))
// 利用ALS训练数据建立推荐模型
val als = new ALS()
  .setMaxIter(5)
  .setRegParam(0.01)
  .setUserCol("userId")
  .setItemCol("movieId")
  .setRatingCol("rating")
val model = als.fit(training)
// 通过计算测试数据上的RMSE来评估模型
// 注意,我们将冷启动策略设置为“drop”,以确保我们没有得到NaN评估指标
model.setColdStartStrategy("drop")
val predictions = model.transform(test)
val evaluator = new RegressionEvaluator()
  .setMetricName("rmse")
  .setLabelCol("rating")
  .setPredictionCol("prediction")
val rmse = evaluator.evaluate(predictions)
println(s"Root-mean-square error = $rmse")
// 为每个用户生成十大电影推荐
val userRecs = model.recommendForAllUsers(10)
// 为每部电影生成十大用户推荐
val movieRecs = model.recommendForAllItems(10)
// 为指定的用户组生成十大电影推荐
val users = ratings.select(als.getUserCol).distinct().limit(3)
val userSubsetRecs = model.recommendForUserSubset(users, 10)
// 为指定的一组电影生成十大用户推荐
val movies = ratings.select(als.getItemCol).distinct().limit(3)
val movieSubSetRecs = model.recommendForItemSubset(movies, 10)

如果评级矩阵是从另一个信息源(即它是从其他信号推断)得出,可以设置implicitPrefs为true以获得更好的效果:

val als = new ALS()
  .setMaxIter(5)
  .setRegParam(0.01)
  .setImplicitPrefs(true)
  .setUserCol("userId")
  .setItemCol("movieId")
  .setRatingCol("rating")

Python代码:

from pyspark.ml.evaluation import RegressionEvaluator
from pyspark.ml.recommendation import ALS
from pyspark.sql import Row

lines = spark.read.text("/sample_movielens_ratings.txt").rdd
parts = lines.map(lambda row: row.value.split("::"))
ratingsRDD = parts.map(lambda p: Row(userId=int(p[0]), movieId=int(p[1]), rating=float(p[2]), timestamp=long(p[3])))
ratings = spark.createDataFrame(ratingsRDD)
(training, test) = ratings.randomSplit([0.8, 0.2])
# 在训练数据# Note上使用ALS构建推荐模型,我们将冷启动策略设置为“drop”,以确保我们没有获得NaN评估指标
als = ALS(maxIter=5, regParam=0.01, userCol="userId", itemCol="movieId", ratingCol="rating", coldStartStrategy="drop")
model = als.fit(training)
# Evaluate the model by computing the RMSE on the test data
predictions = model.transform(test)
evaluator = RegressionEvaluator(metricName="rmse", labelCol="rating",predictionCol="prediction")
rmse = evaluator.evaluate(predictions)
print("Root-mean-square error = " + str(rmse))
# Generate top 10 movie recommendations for each user
userRecs = model.recommendForAllUsers(10)
# Generate top 10 user recommendations for each movie
movieRecs = model.recommendForAllItems(10)
# Generate top 10 movie recommendations for a specified set of users
users = ratings.select(als.getUserCol()).distinct().limit(3)
userSubsetRecs = model.recommendForUserSubset(users, 10)
# Generate top 10 user recommendations for a specified set of movies
movies = ratings.select(als.getItemCol()).distinct().limit(3)
movieSubSetRecs = model.recommendForItemSubset(movies, 10)

 

9.频繁模式挖掘(Frequent Pattern Mining)

挖掘频繁项目,项目集,子序列或其他子结构通常是分析大规模数据集的第一步,这是数据挖掘多年来一直活跃的研究课题。我们将用户引荐到维基百科的关联规则学习 以获取更多信息。

FP-Growth

FP-growth算法Han等人提出:频繁模式挖掘没有候选生成,其中“FP”表示频繁模式。给定交易数据集,FP-growth的第一步是计算项目频率并识别频繁项目。与为同一目的而设计类似Apriori的算法不同,FP-growth的第二步使用后缀树(FP-tree)结构来编码事务而不显式生成候选集,这通常很难生成。在第二步之后,可以从FP树中提取频繁项集。在spark.mllib,我们实现了一个名为PFP的FP-growth的并行版本(比如PFP:Parallel FP-growth for query recommendation),PFP基于事务的后缀分配增长FP树的工作,因此比单机实现更具可扩展性。

spark.mlFP-growth实现采用以下(超)参数:

  • minSupport:对项目集的最小支持被识别为频繁。例如,如果一个项目出现在5个交易中的3个中,则它具有3/5 = 0.6的支持。
  • minConfidence:生成关联规则的最小置信度。置信度表明关联规则经常被发现的频率。例如,如果在事务项集中X出现4次,X 并且Y只出现2次,则规则的置信度为X => Y2/4 = 0.5。该参数不会影响频繁项集的挖掘,但会指定从频繁项集生成关联规则的最小置信度。
  • numPartitions:用于分发工作的分区数。默认情况下,不设置参数,并使用输入数据集的分区数。

FPGrowthModel规定:

  • freqItemsets:频繁的项目集格式为DataFrame(“items”[Array],“freq”[Long])
  • associationRules:上面以置信度生成的关联规则minConfidence,格式为DataFrame(“antecedent”[Array],“consequent”[Array],“confidence”[Double])。
  • transform:对于每个事务itemsCol,该transform方法将其项目与每个关联规则的前提进行比较。如果记录包含特定关联规则的所有前提,则该规则将被视为适用,并且其结果将被添加到预测结果中。变换方法将所有适用规则的结果总结为预测。预测列具有相同的数据类型,itemsCol并且不包含现有项目itemsCol

例子:

Scala代码:

import org.apache.spark.ml.fpm.FPGrowth

val dataset = spark.createDataset(Seq("1 2 5","1 2 3 5","1 2")).map(t => t.split(" ")).toDF("items")
val fpgrowth = new FPGrowth().setItemsCol("items").setMinSupport(0.5).setMinConfidence(0.6)
val model = fpgrowth.fit(dataset)
model.freqItemsets.show() // 显示器频繁项集.
model.associationRules.show() // 显示生成的关联规则.
// transform根据所有关联规则检查输入项,并将结果总结为预测
model.transform(dataset).show()

Java代码:

import java.util.Arrays;
import java.util.List;
import org.apache.spark.ml.fpm.FPGrowth;
import org.apache.spark.ml.fpm.FPGrowthModel;
import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.RowFactory;
import org.apache.spark.sql.SparkSession;
import org.apache.spark.sql.types.*;

List<Row> data = Arrays.asList(
  RowFactory.create(Arrays.asList("1 2 5".split(" "))),
  RowFactory.create(Arrays.asList("1 2 3 5".split(" "))),
  RowFactory.create(Arrays.asList("1 2".split(" ")))
);
StructType schema = new StructType(new StructField[]{ new StructField(
  "items", new ArrayType(DataTypes.StringType, true), false, Metadata.empty())
});
Dataset<Row> itemsDF = spark.createDataFrame(data, schema);
FPGrowthModel model = new FPGrowth()
  .setItemsCol("items")
  .setMinSupport(0.5)
  .setMinConfidence(0.6)
  .fit(itemsDF);
// Display frequent itemsets.
model.freqItemsets().show();
// 显示生成的关联规则.
model.associationRules().show();
//transform根据所有关联规则检查输入项,并将结果总结为预测
model.transform(itemsDF).show();

PrefixSpan

PrefixSpan是Pei等人提出的序列模式挖掘算法 ,Mining Sequential Patterns by Pattern-Growth:The PrefixSpan Approach。我们将读者引用参考文献来形式化序列模式挖掘问题。

spark.ml的PrefixSpan实现采用以下参数:

(1).minSupport:需要被视为频繁顺序模式的最小支持。
(2).maxPatternLength:频繁序列模式的最大长度。任何超过此长度的频繁模式都不会包含在结果中。
(3).maxLocalProjDBSize:在投影数据库的本地迭代处理开始之前,前缀投影数据库中允许的最大项目数。应根据执行程序的大小调优此参数
(4).sequenceCol:数据集中序列列的名称(默认为“序列”),此列中具有空值的行将被忽略。

例子:

Scala代码:

import org.apache.spark.ml.fpm.PrefixSpan

val smallTestData = Seq(
  Seq(Seq(1, 2), Seq(3)),
  Seq(Seq(1), Seq(3, 2), Seq(1, 2)),
  Seq(Seq(1, 2), Seq(5)),
  Seq(Seq(6))
)
val df = smallTestData.toDF("sequence")
val result = new PrefixSpan()
  .setMinSupport(0.5)
  .setMaxPatternLength(5)
  .setMaxLocalProjDBSize(32000000)
  .findFrequentSequentialPatterns(df)
  .show()

Java代码:

import org.apache.spark.ml.fpm.PrefixSpan;
import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.RowFactory;
import org.apache.spark.sql.SparkSession;
import org.apache.spark.sql.types.*;
import java.util.Arrays;
import java.util.List;

List<Row> data = Arrays.asList(
  RowFactory.create(Arrays.asList(Arrays.asList(1, 2), Arrays.asList(3))),
  RowFactory.create(Arrays.asList(Arrays.asList(1), Arrays.asList(3, 2), Arrays.asList(1,2))),
  RowFactory.create(Arrays.asList(Arrays.asList(1, 2), Arrays.asList(5))),
  RowFactory.create(Arrays.asList(Arrays.asList(6)))
);
StructType schema = new StructType(new StructField[]{ new StructField(
  "sequence", new ArrayType(new ArrayType(DataTypes.IntegerType, true), true),false, Metadata.empty())
});
Dataset<Row> sequenceDF = spark.createDataFrame(data, schema);
PrefixSpan prefixSpan = new PrefixSpan().setMinSupport(0.5).setMaxPatternLength(5);
// Finding frequent sequential patterns
prefixSpan.findFrequentSequentialPatterns(sequenceDF).show();

 

10.ML优化:模型选择和超参数调优

本节介绍如何使用MLlib的工具来优化ML算法和管道。内置的交叉验证和其他工具允许用户优化算法和管道中的超参数。

模型选择(又称超参数调优)

ML中的一个重要任务是模型选择,或者使用数据为给定的任务找到最佳模型或参数。这也称为调优。可以对单个估计器(如逻辑回归)进行调优,也可以对包含多种算法、特性化和其他步骤的整个管道进行调优。用户可以一次优化整个管道,而不是单独优化管道中的每个元素。

MLlib支持使用CrossValidator和TrainValidationSplit等工具选择模型。这些工具需要以下项目:

  • Estimator用户调优的算法或Pipeline.
  • ParamMap集合用于参数选择支持多参数如:迭代次数,正则化等.
  • Evaluator:衡量模型在测试数据上的拟合程度,给出评估结果.

从较高的层面来看,这些模型选择工具的工作原理如下:

  • 将输入数据分成训练和测试数据集。
  • 对于每个(训练,测试)对,他们遍历ParamMap集合对于每个参数映射,它们都使用这些参数来拟合Estimator,得到拟合的模型,并使用Evaluator来评估模型的性能
  • 选择表现最好的参数集合生成的模型。

针对回归问题,Evaluator可以是一个RegressionEvaluator;针对二进制数据,可以是BinaryClassificationEvaluator,或是对于对分类问题的MulticlassClassificationEvaluator。用于选择最佳ParamMap的默认度量方式可以通过评估器的setMetricName方法进行覆盖。

为了帮助构建参数网格,用户可以使用该ParamGridBuilder实用程序。默认情况下,参数网格中的参数集将按顺序进行评估。在parallelism使用CrossValidator或运行模型选择之前,可以通过设置值为2或更大(值为1的串行)来并行完成参数评估TrainValidationSplit。parallelism应谨慎选择值,以在不超出群集资源的情况下最大化并行性,并且较大的值可能并不总是导致性能提高。一般来说,对于大多数集群而言,高达10的值应该足够了。

交叉验证(Cross-Validation)

CrossValidator(交叉验证器)将数据集拆分为k折数据集,并被用作训练和测试数据集。例如, k=3折时,CrossValidator 将生成3对(训练,测试)数据集,每对数据集使用2/3的数据进行训练,1/3的数据进行测试。为了评估一个特定的参数映射,交叉验证器通过在3个不同的(训练、测试)数据集对上拟合估计值,为生成的3个模型计算平均评估度量。

在找出最好的ParamMap后,CrossValidator 会使用这个ParamMap和整个的数据集来重新拟合Estimator。

注意:在整个参数网格中进行交叉验证是比较耗时的。例如,在下面的例子中,参数网格有3个hashingTF.numFeatures值和2个lr.regParam值,CrossValidator使用2折切分数据。最终将有(3 * 2) * 2 = 12个不同的模型将被训练。在真实场景中,很可能使用更多的参数和进行更多折切分(k=3和k=10都很常见)。换句话说,使用CrossValidator的代价可能会异常的高。然而,对比启发式的手动调优,这是选择参数的行之有效的方法

示例:通过交叉验证选择模型

使用CrossValidator从整个网格的参数中选择合适的参数:

Scala代码:

import org.apache.spark.ml.Pipeline
import org.apache.spark.ml.classification.LogisticRegression
import org.apache.spark.ml.evaluation.BinaryClassificationEvaluator
import org.apache.spark.ml.feature.{HashingTF, Tokenizer}
import org.apache.spark.ml.linalg.Vector
import org.apache.spark.ml.tuning.{CrossValidator, ParamGridBuilder}
import org.apache.spark.sql.Row

// Prepare training data from a list of (id, text, label) tuples.
val training = spark.createDataFrame(Seq(
  (0L, "a b c d e spark", 1.0),(1L, "b d", 0.0),(2L, "spark f g h", 1.0),
  (3L, "hadoop mapreduce", 0.0),(4L, "b spark who", 1.0),(5L, "g d a y", 0.0),
  (6L, "spark fly", 1.0),(7L, "was mapreduce", 0.0),
  (8L, "e spark program", 1.0),(9L, "a e c l", 0.0),
  (10L, "spark compile", 1.0),
  (11L, "hadoop software", 0.0))
).toDF("id", "text", "label")
// 配置ML管道,它由三个阶段组成:tokenizer、hashingTF和lr
val tokenizer = new Tokenizer().setInputCol("text").setOutputCol("words")
val hashingTF = new HashingTF().setInputCol(tokenizer.getOutputCol).setOutputCol("features")
val lr = new LogisticRegression().setMaxIter(10)
val pipeline = new Pipeline().setStages(Array(tokenizer, hashingTF, lr))
// 我们使用ParamGridBuilder构造一个参数网格来搜索。带有3个hashingTF值。numFeatures和两个lr值。这个网格将有3 x 2 = 6个参数设置供交叉验证器选择.
val paramGrid = new ParamGridBuilder()
  .addGrid(hashingTF.numFeatures, Array(10, 100, 1000))
  .addGrid(lr.regParam, Array(0.1, 0.01))
  .build()
// 现在,我们将管道视为一个估计器,将其封装在一个交叉验证器实例中。这将使我们能够为所有管道阶段共同选择参数。交叉验证器需要一个估计器、一组估计器参数和一个求值器。注意这里的评估器是一个BinaryClassificationEvaluator,它的默认度量是areaUnderROC
val cv = new CrossValidator()
  .setEstimator(pipeline)
  .setEvaluator(new BinaryClassificationEvaluator)
  .setEstimatorParamMaps(paramGrid)
  .setNumFolds(2)  // 在实践中使用3+
  .setParallelism(2)  // 并行计算最多2个参数设置
// 运行交叉验证,并选择最佳参数集.
val cvModel = cv.fit(training)
// 准备测试文档,这些文档没有标记(id, text)元组.
val test = spark.createDataFrame(Seq((4L, "spark i j k"),(5L, "l m n"),(6L, "mapreduce spark"),(7L, "apache hadoop"))).toDF("id", "text")
// 对测试文档进行预测。cvModel使用找到的最佳模型(lrModel).
cvModel.transform(test)
  .select("id", "text", "probability", "prediction")
  .collect()
  .foreach { case Row(id:Long,text:String,prob:Vector,prediction:Double) =>
    println(s"($id, $text) --> prob=$prob, prediction=$prediction")
  }

Python代码:

from pyspark.ml import Pipeline
from pyspark.ml.classification import LogisticRegression
from pyspark.ml.evaluation import BinaryClassificationEvaluator
from pyspark.ml.feature import HashingTF, Tokenizer
from pyspark.ml.tuning import CrossValidator, ParamGridBuilder

# Prepare training documents, which are labeled.
training = spark.createDataFrame(
  [(0, "a b c d e spark", 1.0),(1, "b d", 0.0),(2, "spark f g h", 1.0),
   (3, "hadoop mapreduce", 0.0),(4, "b spark who", 1.0),(5, "g d a y", 0.0),
   (6, "spark fly", 1.0),(7, "was mapreduce", 0.0),(8, "e spark program", 1.0),
   (9, "a e c l", 0.0),(10, "spark compile", 1.0),(11, "hadoop software", 0.0)], 
  ["id", "text", "label"]
)
# 配置ML管道,它由三个阶段组成:tokenizer、hashingTF和lr.
tokenizer = Tokenizer(inputCol="text", outputCol="words")
hashingTF=HashingTF(inputCol=tokenizer.getOutputCol(),outputCol="features")
lr = LogisticRegression(maxIter=10)
pipeline = Pipeline(stages=[tokenizer, hashingTF, lr])
# 现在,我们将管道视为一个估计器,将其封装在一个交叉验证器实例中。这将使我们能够为所有管道阶段共同选择参数。交叉验证器需要一个估计器、一组估计器参数和一个求值器。注意这里的评估器是一个BinaryClassificationEvaluator,它的默认度量是areaUnderROC.
paramGrid = ParamGridBuilder().addGrid(hashingTF.numFeatures, [10, 100, 1000])
    .addGrid(lr.regParam, [0.1, 0.01])
    .build()
crossval = CrossValidator(estimator=pipeline,estimatorParamMaps=paramGrid,evaluator=BinaryClassificationEvaluator(),numFolds=2)  
# 在实践中使用3+折叠
# 运行交叉验证,并选择最佳参数集.
cvModel = crossval.fit(training)
# Prepare test documents, which are unlabeled.
test = spark.createDataFrame([(4, "spark i j k"),(5, "l m n"),(6, "mapreduce spark"),(7, "apache hadoop")], ["id", "text"])
# 对测试文档进行预测。cvModel使用找到的最佳模型(lrModel).
prediction = cvModel.transform(test)
selected = prediction.select("id", "text", "probability", "prediction")
for row in selected.collect():
    print(row)

训练验证拆分(Train-Validation Split)

除了CrossValidator,Spark还提供了用于超参数调优的TrainValidationSplit。

 TrainValidationSplit仅评估每个参数组合一次,而不是k次CrossValidator。因此,它耗时,但是当训练数据集不够大时,不会产生可靠的结果。

与交叉验证器不同,TrainValidationSplit创建一个(训练、测试)数据集对。通过使用trainRatio参数将数据集分割成两个部分。例如,当trainRatio=0.75时,TrainValidationSplit将生成一个训练和测试数据集对,其中75%的数据用于训练,25%用于验证。

与交叉验证器类似,TrainValidationSplit最终使用最佳ParamMap和整个数据集对Estimator进行拟合

例子:通过训练验证分离的模型选择

Scala代码:

import org.apache.spark.ml.evaluation.RegressionEvaluator
import org.apache.spark.ml.regression.LinearRegression
import org.apache.spark.ml.tuning.{ParamGridBuilder, TrainValidationSplit}

val data = spark.read.format("libsvm").load("/linear_regression_data.txt")
val Array(training, test) = data.randomSplit(Array(0.9, 0.1), seed = 12345)
val lr = new LinearRegression().setMaxIter(10)
// 我们使用ParamGridBuilder构造一个参数网格来搜索。TrainValidationSplit将尝试所有值的组合,并使用求值器确定最佳模型.
val paramGrid = new ParamGridBuilder()
  .addGrid(lr.regParam, Array(0.1, 0.01))
  .addGrid(lr.fitIntercept)
  .addGrid(lr.elasticNetParam, Array(0.0, 0.5, 1.0))
  .build()
// 在这种情况下,估计量就是简单的线性回归。一个TrainValidationSplit需要一个估计器、一组估计器ParamMaps和一个求值器.
val trainValidationSplit = new TrainValidationSplit()
  .setEstimator(lr)
  .setEvaluator(new RegressionEvaluator)
  .setEstimatorParamMaps(paramGrid)
  .setTrainRatio(0.8) // 80%的数据将用于训练,剩下的20%用于验证.
  .setParallelism(2) // 并行计算最多2个参数设置
// 运行训练验证分离,并选择最佳参数集.
val model = trainValidationSplit.fit(training)
// 对测试数据进行预测。模型是组合了性能最好的参数的模型
model.transform(test).select("features", "label", "prediction").show()

Java代码:

import org.apache.spark.ml.evaluation.RegressionEvaluator;
import org.apache.spark.ml.param.ParamMap;
import org.apache.spark.ml.regression.LinearRegression;
import org.apache.spark.ml.tuning.ParamGridBuilder;
import org.apache.spark.ml.tuning.TrainValidationSplit;
import org.apache.spark.ml.tuning.TrainValidationSplitModel;
import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;

Dataset<Row> data = spark.read().format("libsvm").load("/linear_regression_data.txt");
Dataset<Row>[] splits = data.randomSplit(new double[] {0.9, 0.1}, 12345);
Dataset<Row> training = splits[0];
Dataset<Row> test = splits[1];
LinearRegression lr = new LinearRegression();
ParamMap[] paramGrid = new ParamGridBuilder()
  .addGrid(lr.regParam(), new double[] {0.1, 0.01})
  .addGrid(lr.fitIntercept())
  .addGrid(lr.elasticNetParam(), new double[] {0.0, 0.5, 1.0})
  .build();
TrainValidationSplit trainValidationSplit = new TrainValidationSplit()
  .setEstimator(lr)
  .setEvaluator(new RegressionEvaluator())
  .setEstimatorParamMaps(paramGrid)
  .setTrainRatio(0.8)  // 80% for training and the remaining 20% for validation
  .setParallelism(2);  // Evaluate up to 2 parameter settings in parallel
TrainValidationSplitModel model = trainValidationSplit.fit(training);
model.transform(test).select("features", "label", "prediction").show();

欠/过拟合的模型优化

在监督学习中,我们想要在训练数据上构建模型,然后能够对没见过的新数据(这些新数据与训练集具有相同的特性)做出准确预测。如果一个模型能够对没见过的数据做出准确预测,我们就说它能够从训练集泛化(generalize)到测试集。

构建一个对现有信息量来说过于复杂的模型,正如我们的新手数据科学家做的那样,这被称为过拟合(overfitting)。如果你在拟合模型时过分关注训练集的细节,得到了一个在训练集上表现很好、但不能泛化到新数据上的模型,那么就存在过拟合。与之相反,如果你的模型过于简单——比如说,“有房子的人都买船”——那么你可能无法抓住数据的全部内容以及数据中的变化,你的模型甚至在训练集上的表现就很差。选择过于简单的模型被称为欠拟合(underfitting)。

我们的模型越复杂,在训练数据上的预测结果就越好。但是,如果我们的模型过于复杂,我们开始过多关注训练集中每个单独的数据点,模型就不能很好地泛化到新数据上。二者之间存在一个最佳位置,可以得到最好的泛化性能。这就是我们想要的模型。下图给出了过拟合与欠拟合之间的权衡:

进行模型训练后,发现效果不能满足预期需求,怎么优化呢?可以先分析模型是过拟合还是欠拟合,从而针对性优化。

(1).欠拟合

训练集和测试集的准确率都比较低,模型没有很好的学到内在的关系,可以考虑:

​​​​​​​1)训练样本优化,可能训练样本中存在一些噪声样本,进行数据清洗;
2)增加有效特征;
3)调低正则项的惩罚系数;
4)更换相对复杂的模型,如把线性模型换成非线性模型;
5)模型融合投票。

(2).过拟合

训练过程中训练集的准确率较高,但是测试集准确率较低,可以考虑:

1)增加训练样本数据;
2)提高正则项的惩罚系数;
3)减少迭代训练次数;
4)更换相对简单的模型,如把非线性模型换为线性模型。

 

11.高级主题--线性方法的优化(开发者)

Limited-memory BFGS(L-BFGS

L-BFGS牛顿法族中的一种优化算法,用于解决minw∈Rdf(w)形式的优化问题。L-BFGS方法将目标函数局部近似为二次方,而不评估目标函数的第二偏导数以构造Hessian矩阵。Hessian矩阵通过先前的梯度评估来近似,因此不存在垂直可伸缩性问题(训练特征的数量),这与在牛顿法中明确地计算Hessian矩阵不同。因此与其他一阶优化相比,L-BFGS通常实现更快的收敛。

L-BFGS是解决无约束非线性规划问题最常用的方法,具有收敛速度快、内存开销少等优点,在机器学习各类算法中常有它的身影。简单的说,L-BFGS和GD(Gradient Descent,梯度下降)、SGD(Stochastic Gradient Descent ,随机梯度下降)作用类似,但大多数情况下收敛速度更快,这点在大规模计算中很重要。

由于L-BFGS是建立在目标函数的2阶泰勒展开基础上的,其前提条件就是函数的2阶导不为0。在机器学习中一般如果用L2正则都是可以满足这个条件的。如果用的是L1正则,则目标函数可能出现2阶导为0的情况。对于使用L1正则的情况,可以使用OWL-QN方法。OWL-QN(Orthant-Wise Limited-memory Quasi-Newton)是L-BFGS的扩展,可以有效地处理L1和弹性混合正则化。

L-BFGS用作LinearRegression, LogisticRegression, AFTSurvivalRegression 和MultilayerPerceptronClassifier的求解器。

MLlib L-BFGS求解器在breeze中调用相应的实现。

加权最小二乘法的正规方程求解器

MLlib 通过WeightedLeastSquares实现加权最小二乘的正规方程求解器。

每次观测的特征个数为m,我们采用加权最小二乘公式:

λ是正则化参数,α是elastic-net混合参数,δ的总体标准偏差的总体标准偏差是标签和σj特征列。

此目标函数只需要一次传递数据就可以收集解决此问题所需的统计信息。对于一个n×m的数据矩阵,这些统计量只需要 的存储,因此当m(特征数)相对较小时,可以存储在一台机器上。然后,我们可以用直接切列斯基分解或迭代优化程序等局部方法在一台机器上求解法方程。

Spark MLlib目前支持两种求解常规方程的方法:Cholesky分解法和准牛顿法(L-BFGS/OWL-QN)。Cholesky因子分解依赖于一个正定协方差矩阵(即数据矩阵的列必须是线性无关的),如果违反了这个条件,就会失败。即使协方差矩阵不是正定的,拟牛顿方法仍然能够提供一个合理的解,所以在这种情况下,正规方程求解器也可以回归到拟牛顿方法。当前总是为LinearRe启用此回退.

WeightedLeastSquares支持L1、L2和elastic-net正则化,并提供启用或禁用正则化和标准化的选项。如果没有L1正规化应用(即α= 0),存在一个解析解和柯列斯基或拟牛顿解算器可以使用。当α> 0不存在解析解,我们用拟牛顿迭代解算器找到系数。

为了使普通方程方法更有效,加权最小二乘要求特征数不超过4096。对于较大的问题,使用L-BFGS算法

迭代重加权最小二乘(IRLS)

MLlib 通过IterativelyReweightedLeastSquares实现迭代重加权最小二乘(IRLS)。它可用于找到广义线性模型(GLM)的最大似然估计,在稳健回归中找到M估计量和其他优化问题。有关更多信息请参阅迭代重新加权最小二乘法以获得最大似然估计,以及一些强大且可抵抗的替代方案

它通过以下过程迭代地解决了某些优化问题:

  • 在当前解决方案中线性化目标并更新相应的权重。
  • 通过WeightedLeastSquares解决加权最小二乘(WLS)问题。
  • 重复上述步骤直到收敛。

由于它涉及WeightedLeastSquares在每次迭代中求解加权最小二乘(WLS)问题,因此它还要求特征的数量不超过4096.目前IRLS用作GeneralizedLinearRegression的默认求解器。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值