Spark网格搜索——训练验证集拆分

前言

Spark内的网格搜索主要有两种评估流程,分别是 交叉验证训练验证集拆分,这篇文章主要介绍训练验证集拆分的具体流程

数据集划分

训练集、验证集、测试集

训练集(train):训练模型
验证集(val):评估模型
测试集(test):一旦找到了最佳参数,就开始最终训练
使用训练集训练多个网络模型,再使用验证集测试这些网络,找到得分最高的那个网络作为我们选择的最佳网络,再将训练集和验证集合并,重新训练这个最佳网络,得到最佳网络参数。

Spark ML模型评估算法

回归评估指标

1.RegressionEvaluator

用于验证回归模型的评估算法,如:ALS,线性回归等……

val metricName: Param[String]

"rmse" (default): root mean squared error
"mse": mean squared error
"r2": R2 metric
"mae": mean absolute error

分类评估指标

1.BinaryClassificationEvaluator

用于验证二分分类模型的评估算法:如判断(1,0)或(是,否)

val metricName: Param[String]
param for metric name in evaluation (supports "areaUnderROC" (default), "areaUnderPR")
2.MulticlassClassificationEvaluator

用于判断多分类,当然适用于上面的二分类

val metricName: Param[String]
param for metric name in evaluation (supports "f1" (default), "weightedPrecision", "weightedRecall", "accuracy")

聚类评估指标

ClusteringEvaluator

用于聚类模型的评估 越接近1,表明效果越好

val metricName: Param[String]
param for metric name in evaluation (supports "silhouette" (default))

模型评估Example

记录一次较为简易的模型训练过程:

​ 训练模型分为三步:

- 1.训练集训练模型
- 2.验证集评估模型
- 3.测试集最终训练
    import org.apache.spark.ml.clustering.KMeans
    import org.apache.spark.ml.evaluation.ClusteringEvaluator

    //数据集
    //dataset:测试集 training:训练集 vali:验证集
    val dataset = spark.read.format("libsvm").load("data/mllib/sample_kmeans_data.txt")
    //将测试集按7:3的比例切分为 训练集和验证集
    val Array(training,vali)=dataset.randomSplit(Array(0.7,0.3))
    // 训练Kmeans模型
    //Kmeans超参数
    val kmeans = new KMeans().setK(2).setSeed(1L)
    // 使用训练集训练模型
    val model = kmeans.fit(training)

    // Kmeans为聚类模型,使用聚类指标评估
    val evaluator = new ClusteringEvaluator()
    // 使用验证集参与评估
    val predictions = model.transform(vali)
    val silhouette = evaluator.evaluate(predictions)
    println(silhouette)
    //若评估效果符合预期,即silhouette接近1
    val real_model=kmeans.fit(dataset)

参数探索

根据上文所说的模型评估Example,我们可以通过变量silhouette的值,来不断调整模型的参数,使其接近于1。这里有个较为方便的方法,快速找到较为合适的参数——网格搜索

网格搜索

网格搜索算法是一种通过遍历给定的参数组合来优化模型表现的方法

  • 为何使用:超参数选择不恰当,就会出现欠拟合或者过拟合的问题

  • 内容: 网格搜索,搜索的是参数,即在指定的参数范围内,按步长依次调整参数,利用调整的参数训练学习器,从所有的参数中找到在验证集上精度最高的参数,这其实是一个训练和比较的过程。

  • Grid Search:一种调参手段;穷举搜索:在所有候选的参数选择中,通过循环遍历,尝试每一种可能性,表现最好的参数就是最终的结果

  • 用法:网格搜索适用于三四个(或者更少)的超参数(当超参数的数量增长时,网格搜索的计算复杂度会呈现指数增长,这时候则使用随机搜索),用户列出一个较小的超参数值域,这些超参数至于的笛卡尔积(排列组合)为一组组超参数。网格搜索算法使用每组超参数训练模型并挑选验证集误差最小的超参数组合

  • 缺点:遍历所有组合,比较耗时

网格搜索Example:
    import org.apache.spark.ml.clustering.KMeans
    import org.apache.spark.ml.evaluation.ClusteringEvaluator
    import org.apache.spark.ml.tuning.{ParamGridBuilder, TrainValidationSplit, TrainValidationSplitModel}

    //数据集
    val dataset = spark.read.format("libsvm").load("data/mllib/sample_kmeans_data.txt")


    // 训练Kmeans模型
    //Kmeans超参数
    val kmeans = new KMeans()

    /**
      * 网格搜索:
      * 对所有addGrid()内的超参数数组进行排列组合,rmse越小,模型精确度越高
      * 排列组合的参数不建议太多,网格搜索相当于所有组合遍历一遍
      *   这里会对maxIter和k进行排列组合 如:
      *      第一次训练 maxIter=200,k=5
      *      第二次训练 maxIter=200,k=10
      *      ……
      *   所有排列组合训练完后,根据评估模型,筛选出最合适的模型
      */
    val paramGrid = new ParamGridBuilder()
      .addGrid(kmeans.maxIter, Array(200, 400, 600))
      .addGrid(kmeans.k, Array(5, 10, 20))
      .build()

    // Kmeans为聚类模型,使用聚类指标评估
    val evaluator = new ClusteringEvaluator()


    val trainValidationSplit = new TrainValidationSplit()
      //设置预测模型
      .setEstimator(kmeans)
      //设置评估模型
      .setEvaluator(evaluator)
      //训练集、验证集划分 训练集为$ratio 验证集为1-$ratio
      .setTrainRatio(0.7)
      //网格搜索参数
      .setEstimatorParamMaps(paramGrid)
      //预测seed
      .setSeed(1L)

    //训练
    //该方法将自动完成`模型评估Example`中的一二步,找到最适合的评估模型后,用测试集dataset训练最终模型
    val final_model=trainValidationSplit.fit(dataset)
    //打印参数列表
    println(final_model.bestModel.parent.extractParamMap())

TrainValidationSplit

ALS模型网格调参时遇到了一些坑,这里列举一下有坑的地方(其实都是同一个原因造成的)

  • 1.模型的最优参数,每次都是网格搜索排列组合的第一个

    如:

    val paramGrid = new ParamGridBuilder()
          .addGrid(als.maxIter, Array(500,800,1000))
          .addGrid(als.rank, Array(5,10,15))
          .build()
    

    上述代码设置的网格参数,在使用网格搜索遍历后,最优参数必是 maxIter=500,rank=5

  • 2.查看rmse时,全是NaN

    model.validationMetrics=Array(NaN,NaN,Nan)
    

先说结论:

造成这些结果的主要原因,还是ALS冷启动策略设置错误的缘故。ALS模型默认遇到未知UserCol的用户时(即没参与过运算的userId),会将prediction置为NaN。而评估模型进行计算时,若prediction的值有Nan数据,会导致最后的评估结果值也为NaN。如上述第二点所示。

设一个评分表,有userCol,itemCol,rating三个字段,且全表数据不会重复。

UserColItemColRating
Aa5.0
Bb5.0
Cc1.0
Dd2.0
Ee2.0

TrainValidationSplit方法在遍历最优参数时,是将训练集和验证集是按照setTrainRatio($ratio)的比例随机分配,假设ratio=0.8,则训练集与验证集的比例则为8:2,上表将有四条数据(ABCD)参与训练,一条数据(E)参与验证。因ALS模型只能预测参与计算的数据,验证集用户E的prediction=NaN。

TrainValidationSplit遍历过程的大致代码:

    ……
    val est = $(estimator)
    val eval = $(evaluator)
    val epm = $(estimatorParamMaps)
    val Array(trainingDataset, validationDataset) =
      dataset.randomSplit(Array($(trainRatio), 1 - $(trainRatio)), $(seed))
    trainingDataset.cache()
    validationDataset.cache()
    ……
    val metricFutures = epm.zipWithIndex.map { case (paramMap, paramIndex) =>
      Future[Double] {
        val model = est.fit(trainingDataset, paramMap).asInstanceOf[Model[_]]

        if (collectSubModelsParam) {
          subModels.get(paramIndex) = model
        }
        // TODO: duplicate evaluator to take extra params from input
        val metric = eval.evaluate(model.transform(validationDataset, paramMap))
        logDebug(s"Got metric $metric for model trained with $paramMap.")
        metric
      }(executionContext)
    }
    ……
    val (bestMetric, bestIndex) =
      if (eval.isLargerBetter) metrics.zipWithIndex.maxBy(_._1)
      else metrics.zipWithIndex.minBy(_._1)
    logInfo(s"Best set of parameters:\n${epm(bestIndex)}")
    logInfo(s"Best train validation split metric: $bestMetric.")
    val bestModel = est.fit(dataset, epm(bestIndex)).asInstanceOf[Model[_]]
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
在机器学习中,通常会将数据集划分为训练集、验证集和测试集三部分。可以使用 Apache Spark 中的 `randomSplit()` 方法和 `sample()` 方法来实现这一目标。 以下是一个简单的示例代码: ```python # 导入需要的库 from pyspark.sql.functions import * from pyspark.sql.types import * # 创建一个 DataFrame data = spark.createDataFrame([(1, "John", 0), (2, "Jane", 1), (3, "Alice", 1), (4, "Bob", 0), (5, "Charlie", 1)], ["id", "name", "label"]) # 划分数据集 weights = [0.6, 0.2, 0.2] train_data, val_data, test_data = data.randomSplit(weights, seed=1234) # 打印训练集、验证集和测试集的数据量 print("Training data count:", train_data.count()) print("Validation data count:", val_data.count()) print("Test data count:", test_data.count()) # 从训练集中抽取一部分数据作为验证集 sample_fraction = 0.5 sampled_train_data = train_data.sample(False, sample_fraction, seed=1234) remaining_train_data = train_data.subtract(sampled_train_data) # 打印抽样后的训练集和剩余的训练集的数据量 print("Sampled training data count:", sampled_train_data.count()) print("Remaining training data count:", remaining_train_data.count()) ``` 在这个示例代码中,我们创建了一个包含 5 条数据的 DataFrame。然后使用 `randomSplit()` 方法将数据集按 6:2:2 的比例划分为训练集、验证集和测试集。接着,我们使用 `sample()` 方法从训练集中抽取一部分数据作为验证集。最后,我们打印了训练集、验证集和测试集的数据量,以及抽样后的训练集和剩余的训练集的数据量。 需要注意的是,划分数据集时,需要指定一个随机种子(seed),以保证每次划分的结果都是一致的。同时,抽样时也需要指定一个随机种子。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值