Scala 如何用对象变换的方式编写for循环?

Scala 如何用对象变换的方式编写for循环?,scala,Scala,我需要在Scala中编写一个带有对象变异的for循环。在机器学习中,当进行聚类(将样本分配到最优分离的组中)时,为了确定集合中的最优组数,聚类算法使用不同的组数运行,为每个组数计算一些误差度量。最佳组数是组数图相对于误差度量的弯头。 在Spark ML库中,一个KMeans对象用于集群,其中组号作为参数传递。因此,我计算误差度量以绘制弯头图,如下所示: var baseClusterer = new KMeans() .setFeaturesCol("sca

我需要在Scala中编写一个带有对象变异的for循环。在机器学习中,当进行聚类(将样本分配到最优分离的组中)时,为了确定集合中的最优组数,聚类算法使用不同的组数运行,为每个组数计算一些误差度量。最佳组数是组数图相对于误差度量的弯头。 在Spark ML库中,一个
KMeans
对象用于集群,其中组号作为参数传递。因此,我计算误差度量以绘制弯头图,如下所示:

var baseClusterer = new KMeans()
                   .setFeaturesCol("scaledFeatures")
                   .setPredictionCol("clusters")
                   .setSeed(0)


2 to 10 map {
   baseClusterer = baseClusterer.setK(k)
   baseClusterer.fit(scaledDF).computeCost(scaledDF)
}

我必须将clusterer对象声明为一个var,并在每次迭代中对其进行修改。有没有更适合scala的编写方法?

您可以避免var这样做:

2 to 10 map { k =>
     baseClusterer.setK(k).fit(scaledDF).computeCost(scaledDF)
}

您可以通过以下方式避免var:

2 to 10 map { k =>
     baseClusterer.setK(k).fit(scaledDF).computeCost(scaledDF)
}

如果我正确理解您的逻辑,也许您可以使用foldLeft,其中每个循环都将返回修改/更新的对象,如下所示:

val finalClusterer = (2 to 10).foldLeft(baseClusterer) { (accum, elem) =>
    val newClusterer = accum.copy(k = k)
    newClusterer.fit(scaledDF).computeCost(scaledDF)
}
implicit class ClustererWrapper {
    def copy {
    ...
    }
}
这样,您将得到一个“最终聚类器”,在该聚类器中,您始终以基本聚类器为原点进行操作

编辑:我的代码使用baseClusterer作为case类,因此使用copy方法。如果您没有它,因为它似乎是一个java类,那么您可以创建一个充当包装器的隐式类,并在其中定义这样的方法,如下所示:

val finalClusterer = (2 to 10).foldLeft(baseClusterer) { (accum, elem) =>
    val newClusterer = accum.copy(k = k)
    newClusterer.fit(scaledDF).computeCost(scaledDF)
}
implicit class ClustererWrapper {
    def copy {
    ...
    }
}

如果我正确理解您的逻辑,也许您可以使用foldLeft,其中每个循环都将返回修改/更新的对象,如下所示:

val finalClusterer = (2 to 10).foldLeft(baseClusterer) { (accum, elem) =>
    val newClusterer = accum.copy(k = k)
    newClusterer.fit(scaledDF).computeCost(scaledDF)
}
implicit class ClustererWrapper {
    def copy {
    ...
    }
}
这样,您将得到一个“最终聚类器”,在该聚类器中,您始终以基本聚类器为原点进行操作

编辑:我的代码使用baseClusterer作为case类,因此使用copy方法。如果您没有它,因为它似乎是一个java类,那么您可以创建一个充当包装器的隐式类,并在其中定义这样的方法,如下所示:

val finalClusterer = (2 to 10).foldLeft(baseClusterer) { (accum, elem) =>
    val newClusterer = accum.copy(k = k)
    newClusterer.fit(scaledDF).computeCost(scaledDF)
}
implicit class ClustererWrapper {
    def copy {
    ...
    }
}
注:此版本是根据注释从原始版本修改而来

如果您打算在不同的数据上重复这个操作,您可能需要考虑创建一个聚类器列表,然后使用:

val clusterers = (2 to 10).map(k =>
  new KMeans()
    .setFeaturesCol("scaledFeatures")
    .setPredictionCol("clusters")
    .setSeed(0)
    .setk(k)
)

val costs = clusterers.map(_.fit(scaledDF).computeCost(scaledDF))
但是,请参阅@BogdanVakulenko的答案,以获得重新编写原始版本的好方法

还请注意,使用相同的
k
多次使用不同的
setSeed
值可能是一个好主意,以避免局部极小值。

注意:此版本是根据注释从原始版本修改而来的

如果您打算在不同的数据上重复这个操作,您可能需要考虑创建一个聚类器列表,然后使用:

val clusterers = (2 to 10).map(k =>
  new KMeans()
    .setFeaturesCol("scaledFeatures")
    .setPredictionCol("clusters")
    .setSeed(0)
    .setk(k)
)

val costs = clusterers.map(_.fit(scaledDF).computeCost(scaledDF))
但是,请参阅@BogdanVakulenko的答案,以获得重新编写原始版本的好方法


还请注意,使用相同的
k
多次使用不同的
setSeed
值可能是一个好主意,以避免出现局部极小值。

感谢您的回复。KMeans是SparkML库中的scala类。感谢您的回复。KMeans是SparkML库中的scala类。感谢您的回复。这更像scala。我仍然必须将baseClusterer声明为var。我知道经验法则是尽可能地声明val。对象突变看起来不像scala。如果没有对象突变,我就无法编写它,这让我很烦恼,这就是为什么我问这个问题。你可以将baseClusterer声明为val。但是Tim的方法更像scala-way@BogdanVakulenko不,你的答案比蒂姆的好(事实上,他的根本不起作用)。。。我解释了为什么对他的回答发表评论。@Dima,这是否意味着baseCluster将发生变异,并且baseCluster可用结果可用,foreach将更加清楚,因为foreach用于副作用。foreach用于预期副作用。这里的结果是
computeCost
返回的值。副作用只是spark库实现的一个不幸产物。感谢您的回复。这更像scala。我仍然必须将baseClusterer声明为var。我知道经验法则是尽可能地声明val。对象突变看起来不像scala。如果没有对象突变,我就无法编写它,这让我很烦恼,这就是为什么我问这个问题。你可以将baseClusterer声明为val。但是Tim的方法更像scala-way@BogdanVakulenko不,你的答案比蒂姆的好(事实上,他的根本不起作用)。。。我解释了为什么对他的回答发表评论。@Dima,这是否意味着baseCluster将发生变异,并且baseCluster可用结果可用,foreach将更加清楚,因为foreach用于副作用。foreach用于预期副作用。这里的结果是
computeCost
返回的值。副作用只是spark的库实现的一个不幸产物。(2到10)。map(basecluster.setK)-它将更具伸缩性。我不确定
KMeans
是如何实现的,但
setK
听起来像是对实际对象进行了变异,而不是复制。如果是这样的话,这种方法最终只会做同样的事情10次。我认为@BogdanVakulenko的答案更好,因为它没有对
的实现细节做出假设。setK
只是查找了一下,它确实改变了对象,所以这不起作用@Dima感谢您的评论,我已经更新了答案以反映您的评论,因此现在应该可以正常工作了!(2到10).map(basecluster.setK)-这将是一种更具伸缩性的方式。我不确定
KMeans
是如何实现的,但
setK
听起来像是对实际对象进行了变异,而不是复制。如果是这样的话,这种方法最终只会做同样的事情10次。我认为@BogdanVakulenko的答案更好,因为它没有对
的实现细节做出假设。setK
只是查找了一下,它确实改变了对象,所以这不起作用@迪玛:谢谢你的评论,我已经更新了回复