Alink漫谈(二十二) :源码分析之聚类评估

0x00 摘要

Alink 是阿里巴巴基于实时计算引擎 Flink 研发的新一代机器学习算法平台,是业界首个同时支持批式算法、流式算法的机器学习平台。本文和上文将带领大家来分析Alink中 聚类评估 的实现。

0x01 背景概念

1.1 什么是聚类

聚类(Clustering),用通俗的话来说,就是物以类聚,人以群分。

聚类是观察式学习,而不是示例式的学习。聚类能够作为一个独立的工具获得数据的分布状况,观察每一簇数据的特征,集中对特定的聚簇集合作进一步地分析。

聚类分析还可以作为其他数据挖掘任务(如分类、关联规则)的预处理步骤。

1.2 聚类分析的方法

聚类分析可以大致分为如下方法:

划分方法

  • Construct various partitions and then evaluate them by some criterion,e.g.,minimizing the sum of square errors
  • Typical methods:k-means,k-medoids,CLARANS

层次方法:

  • Create a hierarchical decomposition of the set of data (or objects) using some criterion
  • Typical methods: Diana,Agnes,BIRCH,CAMELEON

基于密度的方法:

  • Based on connectivity and density functions
  • Typical methods: DBSCAN,OPTICS,DenClue

基于网格的方法:

  • Based on multiple-level granularity structure
  • Typical methods: STING,WaveCluster,CLIQUE

基于模型的方法:

  • A model is hypothesized for each of the clusters and tries to find the best fit of that model to each other
  • Typical methods: EM,SOM,COBWEB

基于频繁模式的方法:

  • Based on the analysis of frequent patterns
  • Typical methods: p-Cluster

基于约束的方法:

  • Clustering by considering user-specified or application-specific constraints
  • Typical methods: COD(obstacles),constrained clustering

基于链接的方法:

  • Objects are often linked together in various ways
  • Massive links can be used to cluster objects: SimRank,LinkClus

1.3 聚类评估

聚类评估估计在数据集上进行聚类的可行性和被聚类方法产生的结果的质量。聚类评估主要包括:估计聚类趋势、确定数据集中的簇数、测定聚类质量。

估计聚类趋势:对于给定的数据集,评估该数据集是否存在非随机结构。盲目地在数据集上使用聚类方法将返回一些簇,所挖掘的簇可能是误导。数据集上的聚类分析是有意义的,仅当数据中存在非随机结构。

聚类趋势评估确定给定的数据集是否具有可以导致有意义的聚类的非随机结构。一个没有任何非随机结构的数据集,如数据空间中均匀分布的点,尽管聚类算法可以为该数据集返回簇,但这些簇是随机的,没有任何意义。聚类要求数据的非均匀分布。

测定聚类质量:在数据集上使用聚类方法之后,需要评估结果簇的质量。

具体有两类方法:外在方法和内在方法

  • 外在方法:有监督的方法,需要基准数据。用一定的度量评判聚类结果与基准数据的符合程度。
  • 内在方法:无监督的方法,无需基准数据。类内聚集程度和类间离散程度。

0x02 Alink支持的评估指标

Alink文档中如下:聚类评估是对聚类算法的预测结果进行效果评估,支持下列评估指标。但是实际从其测试代码中可以发现更多。

Compactness(CP), CP越低意味着类内聚类距离越近

\[\overline{CP_i}=\dfrac{1}{|C_i|}\sum_{x \in C_i}\|x_i-u_i\|
\]
\[\overline{CP}=\dfrac{1}{k}\sum_{i=1}^{k}\overline{CP_k}
\]

Seperation(SP), SP越高意味类间聚类距离越远

\[SP=\dfrac{2}{k^2-k}\sum_{i=1}^{k}\sum_{j=i+1}^{k}\|u_i-u_j\|
\]

Davies-Bouldin Index(DB), DB越小意味着类内距离越小 同时类间距离越大

\[DB=\dfrac{1}{k}\sum_{i=1}^{k}max(\dfrac{\overline{CP_i}+\overline{CP_j}}{\|u_i-u_j\|}), i \not= j
\]

Calinski-Harabasz Index(VRC), VRC越大意味着聚类质量越好

\[SSB=\sum_{i=1}^{k}n_i\|u_i-u\|^2
\]
\[SSW=\sum_{i=1}^{k}\sum_{x \in C_i}\|x_i-u_i\|
\]
\[VRC=\dfrac{SSB}{SSW}*\dfrac{N-k}{k-1}
\]

从其测试代码中,我们可以发现更多指标:

  1. Assert.assertEquals(metrics.getCalinskiHarabaz(), 12150.00, 0.01);
  2. Assert.assertEquals(metrics.getCompactness(), 0.115, 0.01);
  3. Assert.assertEquals(metrics.getCount().intValue(), 6);
  4. Assert.assertEquals(metrics.getDaviesBouldin(), 0.014, 0.01);
  5. Assert.assertEquals(metrics.getSeperation(), 15.58, 0.01);
  6. Assert.assertEquals(metrics.getK().intValue(), 2);
  7. Assert.assertEquals(metrics.getSsb(), 364.5, 0.01);
  8. Assert.assertEquals(metrics.getSsw(), 0.119, 0.01);
  9. Assert.assertEquals(metrics.getPurity(), 1.0, 0.01);
  10. Assert.assertEquals(metrics.getNmi(), 1.0, 0.01);
  11. Assert.assertEquals(metrics.getAri(), 1.0, 0.01);
  12. Assert.assertEquals(metrics.getRi(), 1.0, 0.01);
  13. Assert.assertEquals(metrics.getSilhouetteCoefficient(), 0.99,0.01);

我们需要介绍几个指标

2.1 轮廓系数(silhouette coefficient):

对于D中的每个对象o,计算:

  • a(o) : o与o所属的簇内其他对象之间的平均距离a(o) 。
  • b(o) : 是o到不包含o的所有簇的最小平均距离。

得到轮廓系数定义为:

\[s(o)=\dfrac{b(o)-a(o)}{max\{a(o),b(o)\}}
\]

轮廓系数的值在-1和1之间。

a(o)的值反映o所属的簇的紧凑性。该值越小,簇越紧凑。

b(o)的值捕获o与其他簇的分离程度。b(o)的值越大,o与其他簇越分离。

当o的轮廓系数值接近1时,包含o的簇是紧凑的,并且o远离其他簇,这是一种可取的情况。

当轮廓系数的值为负时,这意味在期望情况下,o距离其他簇的对象比距离与自己同在簇的对象更近,许多情况下,这很糟糕,应当避免。

2.2 Calinski-Harabaz(CH)

CH指标通过计算类中各点与类中心的距离平方和来度量类内的紧密度,通过计算各类中心点与数据集中心点距离平方和来度量数据集的分离度,CH指标由分离度与紧密度的比值得到。从而,CH越大代表着类自身越紧密,类与类之间越分散,即更优的聚类结果。

CH和轮廓系数适用于实际类别信息未知的情况。

2.3 Davies-Bouldin指数(Dbi)

戴维森堡丁指数(DBI),又称为分类适确性指标,是由大卫戴维斯和唐纳德·Bouldin提出的一种评估聚类算法优劣的指标。

这个DBI就是计算类内距离之和与类外距离之比,来优化k值的选择,避免K-means算法中由于只计算目标函数Wn而导致局部最优的情况。

2.4 Rand index(兰德指数)(RI) 、Adjusted Rand index(调整兰德指数)(ARI)

其中C表示实际类别信息,K表示聚类结果,a表示在C与K中都是同类别的元素对数,b表示在C与K中都是不同类别的元素对数。

RI取值范围为[0,1],值越大意味着聚类结果与真实情况越吻合。RI越大表示聚类效果准确性越高 同时每个类内的纯度越高

为了实现“在聚类结果随机产生的情况下,指标应该接近零”,调整兰德系数(Adjusted rand index)被提出,它具有更高的区分度:

ARI取值范围为[−1,1],值越大意味着聚类结果与真实情况越吻合。从广义的角度来讲,ARI衡量的是两个数据分布的吻合程度。

0x03 示例代码

聚类评估示例代码如下:

  1. public class EvalClusterBatchOpExp {
  2. public static void main(String[] args) throws Exception {
  3. Row[] rows = new Row[] {
  4. Row.of(0, "0,0,0"),
  5. Row.of(0, "0.1,0.1,0.1"),
  6. Row.of(0, "0.2,0.2,0.2"),
  7. Row.of(1, "9,9,9"),
  8. Row.of(1, "9.1,9.1,9.1"),
  9. Row.of(1, "9.2,9.2,9.2")
  10. };
  11. MemSourceBatchOp inOp = new MemSourceBatchOp(Arrays.asList(rows), new String[] {"label", "Y"});
  12. KMeans train = new KMeans()
  13. .setVectorCol("Y")
  14. .setPredictionCol("pred")
  15. .setK(2);
  16. ClusterMetrics metrics = new EvalClusterBatchOp()
  17. .setPredictionCol("pred")
  18. .setVectorCol("Y")
  19. .setLabelCol("label")
  20. .linkFrom(train.fit(inOp).transform(inOp))
  21. .collectMetrics();
  22. System.out.println(metrics.getCalinskiHarabaz());
  23. System.out.println(metrics.getCompactness());
  24. System.out.println(metrics.getCount());
  25. System.out.println(metrics.getDaviesBouldin());
  26. System.out.println(metrics.getSeperation());
  27. System.out.println(metrics.getK());
  28. System.out.println(metrics.getSsb());
  29. System.out.println(metrics.getSsw());
  30. System.out.println(metrics.getPurity());
  31. System.out.println(metrics.getNmi());
  32. System.out.println(metrics.getAri());
  33. System.out.println(metrics.getRi());
  34. System.out.println(metrics.getSilhouetteCoefficient());
  35. }
  36. }

输出为:

  1. 12150.000000000042
  2. 0.11547005383792497
  3. 6
  4. 0.014814814814814791
  5. 15.588457268119896
  6. 2
  7. 364.5
  8. 0.1199999999999996
  9. 1.0
  10. 1.0
  11. 1.0
  12. 1.0
  13. 0.9997530305375205

0x04 总体逻辑

代码整体逻辑如下:

  • label 相关指标计算操作

    • 使用 calLocalPredResult 对每个分区操作

      • flatMap 1 是打散Row,得到 Label y
      • flatMap 2 是打散Row,得到 y_hat,所以前两步是得到 y 和 y_hat 的映射 map。这两个会广播给 CalLocalPredResult 使用。
      • 调用 CalLocalPredResult 建立混淆矩阵
    • 使用 reduce 归并这些分区操作结果。
    • 使用 extractParamsFromConfusionMatrix 根据混淆矩阵计算 purity, NMI等指标
  • Vector相关指标计算操作
    • 对数据按照类别进行分组
    • 分组归并,调用 CalcClusterMetricsSummary分布式计算向量相关的指标
      • 遍历 rows,累积到 sumVector
      • 循环,计算出若干统计信息
    • 调用 ReduceBaseMetrics,再归并,形成一个BaseMetricsSummary
    • 调用 calSilhouetteCoefficient 来计算 SilhouetteCoefficient
    • 把数据存储为Params
  • 合并输出
    • 做了一个 union,把 labelMetrics 和 vectorMetrics 联合起来,再归并输出到最后的表中
    • 分组归并
    • 输出到最后表

具体代码如下:

  1. public EvalClusterBatchOp linkFrom(BatchOperator<?>... inputs) {
  2. BatchOperator in = checkAndGetFirst(inputs);
  3. String labelColName = this.getLabelCol();
  4. String predResultColName = this.getPredictionCol();
  5. String vectorColName = this.getVectorCol();
  6. DistanceType distanceType = getDistanceType();
  7. ContinuousDistance distance = distanceType.getFastDistance();
  8. DataSet<Params> empty = MLEnvironmentFactory.get(getMLEnvironmentId()).getExecutionEnvironment().fromElements(
  9. new Params());
  10. DataSet<Params> labelMetrics = empty, vectorMetrics;
  11. if (null != labelColName) { // 针对 label 操作
  12. // 获取数据
  13. DataSet<Row> data = in.select(new String[] {labelColName, predResultColName}).getDataSet();
  14. // 使用 calLocalPredResult 对每个分区操作
  15. labelMetrics = calLocalPredResult(data)
  16. .reduce(new ReduceFunction<LongMatrix>() { // 使用 reduce 归并这些分区操作结果
  17. @Override
  18. public LongMatrix reduce(LongMatrix value1, LongMatrix value2) {
  19. value1.plusEqual(value2);
  20. return value1;
  21. }
  22. })
  23. .map(new MapFunction<LongMatrix, Params>() {
  24. @Override
  25. public Params map(LongMatrix value) {
  26. // 使用 extractParamsFromConfusionMatrix 根据混淆矩阵计算 purity, NMI等指标
  27. return ClusterEvaluationUtil.extractParamsFromConfusionMatrix(value);
  28. }
  29. });
  30. }
  31. if (null != vectorColName) {
  32. // 获取数据
  33. DataSet<Row> data = in.select(new String[] {predResultColName, vectorColName}).getDataSet();
  34. DataSet<BaseMetricsSummary> metricsSummary = data
  35. .groupBy(0) // 对数据按照类别进行分组
  36. .reduceGroup(new CalcClusterMetricsSummary(distance)) // 分布式计算向量相关的指标
  37. .reduce(new EvaluationUtil.ReduceBaseMetrics());// 归并
  38. DataSet<Tuple1<Double>> silhouetteCoefficient = data.map( // 计算silhouette
  39. new RichMapFunction<Row, Tuple1<Double>>() {
  40. @Override
  41. public Tuple1<Double> map(Row value) {
  42. List<BaseMetricsSummary> list = getRuntimeContext().getBroadcastVariable(METRICS_SUMMARY);
  43. return ClusterEvaluationUtil.calSilhouetteCoefficient(value,
  44. (ClusterMetricsSummary)list.get(0));
  45. }
  46. }).withBroadcastSet(metricsSummary, METRICS_SUMMARY)
  47. .aggregate(Aggregations.SUM, 0);
  48. // 把数据存储为Params
  49. vectorMetrics = metricsSummary.map(new ClusterEvaluationUtil.SaveDataAsParams()).withBroadcastSet(
  50. silhouetteCoefficient, SILHOUETTE_COEFFICIENT);
  51. } else {
  52. vectorMetrics = in.select(predResultColName)
  53. .getDataSet()
  54. .reduceGroup(new BasicClusterParams());
  55. }
  56. DataSet<Row> out = labelMetrics
  57. .union(vectorMetrics) // 把 labelMetrics 和 vectorMetrics 联合起来
  58. .reduceGroup(new GroupReduceFunction<Params, Row>() { // 分组归并
  59. @Override
  60. public void reduce(Iterable<Params> values, Collector<Row> out) {
  61. Params params = new Params();
  62. for (Params p : values) {
  63. params.merge(p);
  64. }
  65. out.collect(Row.of(params.toJson()));
  66. }
  67. });
  68. // 输出到最后表
  69. this.setOutputTable(DataSetConversionUtil.toTable(getMLEnvironmentId(),
  70. out, new TableSchema(new String[] {EVAL_RESULT}, new TypeInformation[] {Types.STRING})
  71. ));
  72. return this;
  73. }

0x05 针对 label 操作

5.1 calLocalPredResult

因为前面有 DataSet<Row> data = in.select(new String[] {labelColName, predResultColName}).getDataSet();,所以这里处理的就是 y 和 y_hat。

有两个 flatMap 串起来。

  • flatMap 1 是打散Row,得到 Label y
  • flatMap 2 是打散Row,得到 y_hat

两个 flatMap 都接了 DistinctLabelIndexMap 和 project(0),DistinctLabelIndexMap 作用是 Give each label an ID, return a map of label and ID.,就是给每一个 ID 一个 label。project(0)就是提取出 label。

所以前两步是得到 y 和 y_hat 的映射 map。这两个会广播给 CalLocalPredResult 使用。

第三步是调用 CalLocalPredResult 建立混淆矩阵。

具体代码如下:

  1. private static DataSet<LongMatrix> calLocalPredResult(DataSet<Row> data) {
  2. // 打散Row,得到 Label y
  3. DataSet<Tuple1<Map<String, Integer>>> labels = data.flatMap(new FlatMapFunction<Row, String>() {
  4. @Override
  5. public void flatMap(Row row, Collector<String> collector) {
  6. if (EvaluationUtil.checkRowFieldNotNull(row)) {
  7. collector.collect(row.getField(0).toString());
  8. }
  9. }
  10. }).reduceGroup(new EvaluationUtil.DistinctLabelIndexMap(false, null)).project(0);
  11. // 打散Row,得到 y_hat
  12. DataSet<Tuple1<Map<String, Integer>>> predictions = data.flatMap(new FlatMapFunction<Row, String>() {
  13. @Override
  14. public void flatMap(Row row, Collector<String> collector) {
  15. if (EvaluationUtil.checkRowFieldNotNull(row)) {
  16. collector.collect(row.getField(1).toString());
  17. }
  18. }
  19. }).reduceGroup(new EvaluationUtil.DistinctLabelIndexMap(false, null)).project(0);
  20. // 前两步是得到 y 和 y_hat 的映射 map。这两个会广播给 CalLocalPredResult 使用
  21. // Build the confusion matrix.
  22. DataSet<LongMatrix> statistics = data
  23. .rebalance()
  24. .mapPartition(new CalLocalPredResult())
  25. .withBroadcastSet(labels, LABELS)
  26. .withBroadcastSet(predictions, PREDICTIONS);
  27. return statistics;
  28. }

CalLocalPredResult 建立混淆矩阵。

  • open函数中,会从系统中获取 y 和 y_hat。
  • mapPartition函数中,建立混淆矩阵。
  1. matrix = {long[2][]@10707}
  2. 0 = {long[2]@10709}
  3. 0 = 0
  4. 1 = 0
  5. 1 = {long[2]@10710}
  6. 0 = 1
  7. 1 = 0

代码是:

  1. static class CalLocalPredResult extends RichMapPartitionFunction<Row, LongMatrix> {
  2. private Map<String, Integer> labels, predictions;
  3. @Override
  4. public void open(Configuration parameters) throws Exception {
  5. List<Tuple1<Map<String, Integer>>> list = getRuntimeContext().getBroadcastVariable(LABELS);
  6. this.labels = list.get(0).f0;
  7. list = getRuntimeContext().getBroadcastVariable(PREDICTIONS);
  8. this.predictions = list.get(0).f0;
  9. }
  10. @Override
  11. public void mapPartition(Iterable<Row> rows, Collector<LongMatrix> collector) {
  12. long[][] matrix = new long[predictions.size()][labels.size()];
  13. for (Row r : rows) {
  14. if (EvaluationUtil.checkRowFieldNotNull(r)) {
  15. int label = labels.get(r.getField(0).toString());
  16. int pred = predictions.get(r.getField(1).toString());
  17. matrix[pred][label] += 1;
  18. }
  19. }
  20. collector.collect(new LongMatrix(matrix));
  21. }
  22. }

5.2 extractParamsFromConfusionMatrix

extractParamsFromConfusionMatrix 这里就是根据混淆矩阵计算 purity, NMI 等一系列指标。

  1. public static Params extractParamsFromConfusionMatrix(LongMatrix longMatrix) {
  2. long[][] matrix = longMatrix.getMatrix();
  3. long[] actualLabel = longMatrix.getColSums();
  4. long[] predictLabel = longMatrix.getRowSums();
  5. long total = longMatrix.getTotal();
  6. double entropyActual = 0.0;
  7. double entropyPredict = 0.0;
  8. double mutualInfor = 0.0;
  9. double purity = 0.0;
  10. long tp = 0L;
  11. long tpFpSum = 0L;
  12. long tpFnSum = 0L;
  13. for (long anActualLabel : actualLabel) {
  14. entropyActual += entropy(anActualLabel, total);
  15. tpFpSum += combination(anActualLabel);
  16. }
  17. entropyActual /= -Math.log(2);
  18. for (long aPredictLabel : predictLabel) {
  19. entropyPredict += entropy(aPredictLabel, total);
  20. tpFnSum += combination(aPredictLabel);
  21. }
  22. entropyPredict /= -Math.log(2);
  23. for (int i = 0; i < matrix.length; i++) {
  24. long max = 0;
  25. for (int j = 0; j < matrix[0].length; j++) {
  26. max = Math.max(max, matrix[i][j]);
  27. mutualInfor += (0 == matrix[i][j] ? 0.0 :
  28. 1.0 * matrix[i][j] / total * Math.log(1.0 * total * matrix[i][j] / predictLabel[i] / actualLabel[j]));
  29. tp += combination(matrix[i][j]);
  30. }
  31. purity += max;
  32. }
  33. purity /= total;
  34. mutualInfor /= Math.log(2);
  35. long fp = tpFpSum - tp;
  36. long fn = tpFnSum - tp;
  37. long totalCombination = combination(total);
  38. long tn = totalCombination - tp - fn - fp;
  39. double expectedIndex = 1.0 * tpFpSum * tpFnSum / totalCombination;
  40. double maxIndex = 1.0 * (tpFpSum + tpFnSum) / 2;
  41. double ri = 1.0 * (tp + tn) / (tp + tn + fp + fn);
  42. return new Params()
  43. .set(ClusterMetrics.NMI, 2.0 * mutualInfor / (entropyActual + entropyPredict))
  44. .set(ClusterMetrics.PURITY, purity)
  45. .set(ClusterMetrics.RI, ri)
  46. .set(ClusterMetrics.ARI, (tp - expectedIndex) / (maxIndex - expectedIndex));
  47. }

0x06 Vector相关

前两步是分布式计算 以及 归并:

  1. DataSet<BaseMetricsSummary> metricsSummary = data
  2. .groupBy(0)
  3. .reduceGroup(new CalcClusterMetricsSummary(distance))
  4. .reduce(new EvaluationUtil.ReduceBaseMetrics());

6.1 CalcClusterMetricsSummary

调用了 ClusterEvaluationUtil.getClusterStatistics 来进行计算。

  1. public static class CalcClusterMetricsSummary implements GroupReduceFunction<Row, BaseMetricsSummary> {
  2. private ContinuousDistance distance;
  3. public CalcClusterMetricsSummary(ContinuousDistance distance) {
  4. this.distance = distance;
  5. }
  6. @Override
  7. public void reduce(Iterable<Row> rows, Collector<BaseMetricsSummary> collector) {
  8. collector.collect(ClusterEvaluationUtil.getClusterStatistics(rows, distance));
  9. }
  10. }

ClusterEvaluationUtil.getClusterStatistics如下

  1. public static ClusterMetricsSummary getClusterStatistics(Iterable<Row> rows, ContinuousDistance distance) {
  2. List<Vector> list = new ArrayList<>();
  3. int total = 0;
  4. String clusterId;
  5. DenseVector sumVector;
  6. Iterator<Row> iterator = rows.iterator();
  7. Row row = null;
  8. while (iterator.hasNext() && !EvaluationUtil.checkRowFieldNotNull(row)) {
  9. // 取出第一个不为空的item
  10. row = iterator.next();
  11. }
  12. if (EvaluationUtil.checkRowFieldNotNull(row)) {
  13. clusterId = row.getField(0).toString(); // 取出 clusterId
  14. Vector vec = VectorUtil.getVector(row.getField(1)); // 取出 Vector
  15. sumVector = DenseVector.zeros(vec.size()); // 初始化
  16. } else {
  17. return null;
  18. }
  19. while (null != row) { // 遍历 rows,累积到 sumVector
  20. if (EvaluationUtil.checkRowFieldNotNull(row)) {
  21. Vector vec = VectorUtil.getVector(row.getField(1));
  22. list.add(vec);
  23. if (distance instanceof EuclideanDistance) {
  24. sumVector.plusEqual(vec);
  25. } else {
  26. vec.scaleEqual(1.0 / vec.normL2());
  27. sumVector.plusEqual(vec);
  28. }
  29. total++;
  30. }
  31. row = iterator.hasNext() ? iterator.next() : null;
  32. }
  33. DenseVector meanVector = sumVector.scale(1.0 / total); // 取mean
  34. // runtime变量,这里示例是第二组的向量
  35. list = {ArrayList@10654} size = 3
  36. 0 = {DenseVector@10661} "9.0 9.0 9.0"
  37. 1 = {DenseVector@10662} "9.1 9.1 9.1"
  38. 2 = {DenseVector@10663} "9.2 9.2 9.2"
  39. double distanceSum = 0.0;
  40. double distanceSquareSum = 0.0;
  41. double vectorNormL2Sum = 0.0;
  42. for (Vector vec : list) { // 循环,计算出几个统计信息
  43. double d = distance.calc(meanVector, vec);
  44. distanceSum += d;
  45. distanceSquareSum += d * d;
  46. vectorNormL2Sum += vec.normL2Square();
  47. }
  48. // runtime变量
  49. sumVector = {DenseVector@10656} "27.3 27.3 27.3"
  50. meanVector = {DenseVector@10657} "9.1 9.1 9.1"
  51. distanceSum = 0.34641016151377424
  52. distanceSquareSum = 0.059999999999999575
  53. vectorNormL2Sum = 745.3499999999999
  54. return new ClusterMetricsSummary(clusterId, total, distanceSum / total, distanceSquareSum, vectorNormL2Sum,
  55. meanVector, distance);
  56. }

6.2 ReduceBaseMetrics

这里是进行归并,形成一个BaseMetricsSummary。

  1. /**
  2. * Merge the BaseMetrics calculated locally.
  3. */
  4. public static class ReduceBaseMetrics implements ReduceFunction<BaseMetricsSummary> {
  5. @Override
  6. public BaseMetricsSummary reduce(BaseMetricsSummary t1, BaseMetricsSummary t2) throws Exception {
  7. return null == t1 ? t2 : t1.merge(t2);
  8. }
  9. }

6.3 calSilhouetteCoefficient

第三步是调用 calSilhouetteCoefficient 来计算 SilhouetteCoefficient。

  1. vectorMetrics = metricsSummary.map(new ClusterEvaluationUtil.SaveDataAsParams()).withBroadcastSet(
  2. silhouetteCoefficient, SILHOUETTE_COEFFICIENT);

这里就是和公式一样的处理

  1. public static Tuple1<Double> calSilhouetteCoefficient(Row row, ClusterMetricsSummary clusterMetricsSummary) {
  2. if (!EvaluationUtil.checkRowFieldNotNull(row)) {
  3. return Tuple1.of(0.);
  4. }
  5. String clusterId = row.getField(0).toString();
  6. Vector vec = VectorUtil.getVector(row.getField(1));
  7. double currentClusterDissimilarity = 0.0;
  8. double neighboringClusterDissimilarity = Double.MAX_VALUE;
  9. if (clusterMetricsSummary.distance instanceof EuclideanDistance) {
  10. double normSquare = vec.normL2Square();
  11. for (int i = 0; i < clusterMetricsSummary.k; i++) {
  12. double dissimilarity = clusterMetricsSummary.clusterCnt.get(i) * normSquare
  13. - 2 * clusterMetricsSummary.clusterCnt.get(i) * MatVecOp.dot(vec, clusterMetricsSummary.meanVector.get(i)) + clusterMetricsSummary.vectorNormL2Sum.get(i);
  14. if (clusterId.equals(clusterMetricsSummary.clusterId.get(i))) {
  15. if (clusterMetricsSummary.clusterCnt.get(i) > 1) {
  16. currentClusterDissimilarity = dissimilarity / (clusterMetricsSummary.clusterCnt.get(i) - 1);
  17. }
  18. } else {
  19. neighboringClusterDissimilarity = Math.min(neighboringClusterDissimilarity,
  20. dissimilarity / clusterMetricsSummary.clusterCnt.get(i));
  21. }
  22. }
  23. } else {
  24. for (int i = 0; i < clusterMetricsSummary.k; i++) {
  25. double dissimilarity = 1.0 - MatVecOp.dot(vec, clusterMetricsSummary.meanVector.get(i));
  26. if (clusterId.equals(clusterMetricsSummary.clusterId.get(i))) {
  27. if (clusterMetricsSummary.clusterCnt.get(i) > 1) {
  28. currentClusterDissimilarity = dissimilarity * clusterMetricsSummary.clusterCnt.get(i) / (clusterMetricsSummary.clusterCnt.get(i) - 1);
  29. }
  30. } else {
  31. neighboringClusterDissimilarity = Math.min(neighboringClusterDissimilarity,
  32. dissimilarity);
  33. }
  34. }
  35. }
  36. return Tuple1.of(currentClusterDissimilarity < neighboringClusterDissimilarity ?
  37. 1 - (currentClusterDissimilarity / neighboringClusterDissimilarity) :
  38. (neighboringClusterDissimilarity / currentClusterDissimilarity) - 1);
  39. }

6.4 SaveDataAsParams

第四步是把数据存储为Params

  1. public static class SaveDataAsParams extends RichMapFunction<BaseMetricsSummary, Params> {
  2. @Override
  3. public Params map(BaseMetricsSummary t) throws Exception {
  4. Params params = t.toMetrics().getParams();
  5. List<Tuple1<Double>> silhouetteCoefficient = getRuntimeContext().getBroadcastVariable(
  6. EvalClusterBatchOp.SILHOUETTE_COEFFICIENT);
  7. params.set(ClusterMetrics.SILHOUETTE_COEFFICIENT,
  8. silhouetteCoefficient.get(0).f0 / params.get(ClusterMetrics.COUNT));
  9. return params;
  10. }
  11. }

0x06 合并输出

这一步做了一个 union,把 labelMetrics 和 vectorMetrics 联合起来,再归并输出到最后的表中。

  1. DataSet<Row> out = labelMetrics
  2. .union(vectorMetrics)
  3. .reduceGroup(new GroupReduceFunction<Params, Row>() {
  4. @Override
  5. public void reduce(Iterable<Params> values, Collector<Row> out) {
  6. Params params = new Params();
  7. for (Params p : values) {
  8. params.merge(p);
  9. }
  10. out.collect(Row.of(params.toJson()));
  11. }
  12. });
  13. this.setOutputTable(DataSetConversionUtil.toTable(getMLEnvironmentId(),
  14. out, new TableSchema(new String[] {EVAL_RESULT}, new TypeInformation[] {Types.STRING})
  15. ));

0xFF 参考

聚类算法及其评估指标

[ML] 聚类评价指标

聚类结果的评价指标

聚类评价指标

如何评价聚类结果的好坏?

聚类评估算法-轮廓系数(Silhouette Coefficient )

聚类效果好坏的评价指标

ARI聚类效果评价指标

聚类算法评价指标——Davies-Bouldin指数(Dbi)

【每周一博】浅说Davies-Bouldin指数(DBI)

聚类算法评价指标

聚类模型性能评价指标

Alink漫谈(二十二) :源码分析之聚类评估的更多相关文章

  1. Alink漫谈(十七) :Word2Vec源码分析 之 迭代训练

    Alink漫谈(十七) :Word2Vec源码分析 之 迭代训练 目录 Alink漫谈(十七) :Word2Vec源码分析 之 迭代训练 0x00 摘要 0x01 前文回顾 1.1 上文总体流程图 1 ...

  2. Java 序列化和反序列化(二)Serializable 源码分析 - 1

    目录 Java 序列化和反序列化(二)Serializable 源码分析 - 1 1. Java 序列化接口 2. ObjectOutputStream 源码分析 2.1 ObjectOutputSt ...

  3. [UGUI]图文混排(二):Text源码分析

    UGUI源码: https://bitbucket.org/Unity-Technologies/ui/downloads/?tab=tags 首先下载一份UGUI源码,这里我下载的版本是5.3.2f ...

  4. 跟厂长学PHP7内核(二):源码分析的环境与工具

    本文主要介绍分析源码的方式,其中包含环境的搭建.分析工具的安装以及源码调试的基本操作. 一.工具清单 PHP7.0.12 GDB CLion 二.源码下载及安装 $ wget http://php.n ...

  5. adaptiveThreshold自适应二值化源码分析

    自适应二值化介绍: 二值化算法是用输入像素的值I与一个值C来比较,根据比较结果确定输出值. 自适应二值化的每一个像素的比较值C都不同,比较值C由这个像素为中心的一个块范围计算在减去差值delta得到. ...

  6. Flink源码阅读(二)——checkpoint源码分析

    前言 在Flink原理——容错机制一文中,已对checkpoint的机制有了较为基础的介绍,本文着重从源码方面去分析checkpoint的过程.当然本文只是分析做checkpoint的调度过程,只是尽 ...

  7. DataMatrix二维条码源码分析检测识别图像位置

    发布时间:2014-10-31 DataMatrix的代码结构和QR码基本相同: 其中Detector的功能还是从原始图像中找出符号码的部分,并且进行透视转换纠正扭曲. 其解码流程与QR码差不多,关键 ...

  8. [dpdk] 熟悉SDK与初步使用 (二)(skeleton源码分析)

    接续前节:[dpdk] 熟悉SDK与初步使用 (一)(qemu搭建实验环境) 程序逻辑: 运行参数: 关键API: 入口函数: int rte_eal_init(int argc, char **ar ...

  9. keystone系列二:keystone源码分析

    六 keystone架构 6.1 Keystone API Keystone API与Openstack其他服务的API类似,也是基于ReSTFul HTTP实现的. Keystone API划分为A ...

随机推荐

  1. akka-grpc - 基于akka-http和akka-streams的scala gRPC开发工具

    关于grpc,在前面的scalaPB讨论里已经做了详细的介绍:google gRPC是一种全新的RPC框架,在开源前一直是google内部使用的集成工具.gRPC支持通过http/2实现protobu ...

  2. jsoup中selector的用法及作用

    1.jsoup——selector定义: selector选择器是用于对jsoup解析后document文档的数据筛选操作 2.jsoup——selector操作步骤: 1)先导jsoup架包 2)基 ...

  3. Python输入input、输出print

    1.输入input input是用于输入数据给变量.通过键盘输入的是字符串,如果需要其他格式,需要做转换.比如int.float类型数据,int() 如下是一个例子: 如果a不进行int转换,那么输入 ...

  4. js对象数组新增、修改时的验证是否重复的逻辑

    JS代码: // 定义数据集合 const persons = [ { id: 1, name: '张三' }, { id: 2, name: '李四' } ] console.log('') con ...

  5. python数值运算 四则运算

    数值运算 描述 获得用户输入的一个字符串,格式如下:‪‬‪‬‪‬‪‬‪‬‮‬‫‬‪‬‪‬‪‬‪‬‪‬‪‬‮‬‪‬‫‬‪‬‪‬‪‬‪‬‪‬‮‬‪‬‭‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‮‬‪‬‪‬‪‬‪‬‪‬ ...

  6. 尝试MatCap类型shader

    听说MatCap能在低端机上做出很漂亮的pbr效果,就尝试了一下. MatCap全称MaterailCapture,里面存的是光照信息,通过法线的xy分量去采样matcap,得到在该方向法线的光照信息 ...

  7. .Net在Windows上使用Jenkins做CI/CD的那些事

    背景 最近入职了一家新公司,公司各个方面都让我非常的满意,我也怀着紧张与兴奋的心情入职后,在第一天接到了领导给我的第一个任务——把整个项目的依赖引用重新整理并实施项目的CI/CD. 本篇的重点主要分享 ...

  8. 20190918-03关机重启命令及修改root密码 000 006

    (1)sync   (功能描述:将数据由内存同步到硬盘中) (2)halt (功能描述:关闭系统,等同于shutdown -h now 和 poweroff) (3)reboot (功能描述:就是重启 ...

  9. leetcode刷题-83删除排序链表中的重复元素

    题目 给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次. 示例 1: 输入: 1->1->2 输出: 1->2 实现 # Definition for singly-li ...

  10. 第0课 - 搭建开发环境之安装QT

    第0课 - 搭建开发环境之安装Qt 1. 课程学习的原材料 — Visual Studio 2010 — Qt SDK 4.7.4 — Qt Creator 2.4.1 2. Visual Studi ...