五、Clustering
聚类是一个无监督的学习问题,我们旨在基于相似性的概念将实体的子集彼此分组。聚类通常用于探索性分析和/或作为分级监督学习管道的组成部分(在该学习管道中,针对每个聚类训练不同的分类器或回归模型)。
k-means
K均值是最常用的聚类算法之一,它将数据点聚集成预定数量的聚类。 spark.mllib实现包括k-means ++方法的并行变体,称为kmeans ||。 spark.mllib中的实现具有以下参数:
- k是所需集群的数量。请注意,有可能返回少于k个聚类,例如,如果要聚类的独特点少于k个。
- maxIterations是要运行的最大迭代次数。
- initializationMode指定随机初始化或通过k-means ||初始化。
- runs 自Spark 2.0.0起,此参数无效。
- initializationSteps确定k均值的步数||算法。
- epsilon确定我们认为k均值收敛的距离阈值。
- initialModel是用于初始化的一组可选的群集中心。如果提供此参数,则仅执行一次运行。
示例代码
以下代码段可以在spark-shell中执行。
在下面的示例中,在加载和解析数据之后,我们使用KMeans对象将数据分为两个集群。所需聚类的数量传递给算法。然后,我们计算平方误差的集合和内(WSSSE)。您可以通过增加k来减少此错误度量。实际上,最优k通常是WSSSE图中存在“肘”的那个。
有关API的详细信息,请参考KMeans Scala文档和KMeansModel Scala文档。
import org.apache.spark.mllib.clustering.{KMeans, KMeansModel}
import org.apache.spark.mllib.linalg.Vectors
// Load and parse the data
val data = sc.textFile("data/mllib/kmeans_data.txt")
val parsedData = data.map(s => Vectors.dense(s.split(' ').map(_.toDouble))).cache()
// Cluster the data into two classes using KMeans
val numClusters = 2
val numIterations = 20
val clusters = KMeans.train(parsedData, numClusters, numIterations)
// Evaluate clustering by computing Within Set Sum of Squared Errors
val WSSSE = clusters.computeCost(parsedData)
println(s"Within Set Sum of Squared Errors = $WSSSE")
// Save and load model
clusters.save(sc, "target/org/apache/spark/KMeansExample/KMeansModel")
val sameModel = KMeansModel.load(sc, "target/org/apache/spark/KMeansExample/KMeansModel")
Gaussian mixture(高斯混合)
高斯混合模型表示一种复合分布,其中从k个高斯子分布之一中抽取点,每个子分布都有自己的概率。 spark.mllib实现使用期望最大化算法在给定一组样本的情况下得出最大似然模型。该实现具有以下参数:
- k是所需集群的数量。
- ConvergenceTol是对数似然率的最大变化,在此我们认为已实现收敛。
- maxIterations是要达到收敛而要执行的最大迭代次数。
- initialModel是启动EM算法的可选起点。如果省略此参数,将从数据中构造一个随机起点。
示例代码
import org.apache.spark.mllib.clustering.{GaussianMixture, GaussianMixtureModel}
import org.apache.spark.mllib.linalg.Vectors
// Load and parse the data
val data = sc.textFile("data/mllib/gmm_data.txt")
val parsedData = data.map(s => Vectors.dense(s.trim.split(' ').map(_.toDouble))).cache()
// Cluster the data into two classes using GaussianMixture
val gmm = new GaussianMixture().setK(2).run(parsedData)
// Save and load model
gmm.save(sc, "target/org/apache/spark/GaussianMixtureExample/GaussianMixtureModel")
val sameModel = GaussianMixtureModel.load(sc,
"target/org/apache/spark/GaussianMixtureExample/GaussianMixtureModel")
// output parameters of max-likelihood model
for (i <- 0 until gmm.k) {
println("weight=%f\nmu=%s\nsigma=\n%s\n" format
(gmm.weights(i), gmm.gaussians(i).mu, gmm.gaussians(i).sigma))
}
power iteration clustering(幂迭代聚类)
幂迭代聚类(PIC)是一种可扩展且高效的算法,用于对图形的顶点进行聚类,并赋予成对相似性作为边缘属性,如Lin and Cohen的Power Iteration Clustering中所述。 它通过幂迭代计算图的归一化亲和矩阵的伪特征向量,并将其用于对顶点进行聚类。 spark.mllib包括使用GraphX作为后端的PIC实现。它采用(srcId,dstId,相似性)元组的RDD,并输出具有聚类分配的模型。相似性必须是非负的。 PIC假设相似性度量是对称的。一对(srcId,dstId)与顺序无关,应该在输入数据中最多出现一次。如果输入中缺少一对,则将它们的相似性视为零。 spark.mllib的PIC实现采用以下(超级)参数:
- k: 簇数
- maxIterations: 最大功率迭代次数
- initializationMode: 初始化模型。这可以是默认值“随机”(使用随机向量作为顶点属性),也可以是“度数”(使用标准化的和相似度)。
PowerIterationClustering实现PIC算法。它采用表示亲和力矩阵的(srcId:Long,dstId:Long,相似性:Double)元组的RDD。调用PowerIterationClustering.run返回PowerIterationClusteringModel,其中包含计算出的群集分配。
有关API的详细信息,请参阅PowerIterationClustering Scala文档和PowerIterationClusteringModel Scala文档。
import org.apache.spark.mllib.clustering.PowerIterationClustering
val circlesRdd = generateCirclesRdd(sc, params.k, params.numPoints)
val model = new PowerIterationClustering()
.setK(params.k)
.setMaxIterations(params.maxIterations)
.setInitializationMode("degree")
.run(circlesRdd)
val clusters = model.assignments.collect().groupBy(_.cluster).mapValues(_.map(_.id))
val assignments = clusters.toList.sortBy { case (k, v) => v.length }
val assignmentsStr = assignments
.map { case (k, v) =>
s"$k -> ${v.sorted.mkString("[", ",", "]")}"
}.mkString(", ")
val sizesStr = assignments.map {
_._2.length
}.sorted.mkString("(", ",", ")")
println(s"Cluster assignments: $assignmentsStr\ncluster sizes: $sizesStr")
latent Dirichlet allocation(LDA潜在狄利克雷分配)
潜在狄利克雷分配(LDA)是一种主题模型,可以从文本文档集合中推断出主题。可以将LDA视为聚类算法,如下所示:
- 主题对应于聚类中心,文档对应于数据集中的示例(行)。
- 主题和文档都存在于特征空间中,其中特征向量是字数(词袋)的向量。
- LDA不会使用传统的距离来估计聚类,而是使用基于文本文件生成方式的统计模型的功能。
LDA通过setOptimizer函数支持不同的推理算法。 EMLDA Optimizer使用对似然函数的期望最大化学习聚类并产生综合结果,而OnlineLDAOptimizer使用迭代小批量采样进行在线变异推断,并且通常对内存友好。
LDA接收文档集合作为单词计数和以下参数的向量(使用构建器模式设置):
- k:主题数(即群集中心)
- optimizer: 用于学习LDA模型的优化器,即EMLDA Optimizer或OnlineLDA Optimizer
- docConcentration: Dirichlet参数,用于优先于主题的文档分布。较大的值鼓励更平滑的推断分布。
- topicConcentration: Dirichlet参数,用于表示优先主题在单词(单词)上的分布。较大的值鼓励更平滑的推断分布。
- maxIterations: 限制迭代次数。
- checkpointInterval: 如果使用检查点(在Spark配置中设置),则此参数指定创建检查点的频率。如果maxIterations大,则使用检查点可以帮助减少磁盘上的随机文件大小,并有助于故障恢复。
spark.mllib的所有LDA模型均支持:
- describeTopics: 以最重要的术语和术语权重的数组形式返回主题
- topicsMatrix: 返回vocabSize by k矩阵,其中每一列都是一个主题
注意:LDA仍处于积极开发中的实验功能。结果,某些功能仅在优化器生成的两个优化器/模型之一中可用。当前,可以将分布式模型转换为本地模型,但反之则不能。
以下讨论将分别描述每个优化器/模型对。
期望最大化
在EMLDAOptimizer和DistributedLDAModel中实现。
对于提供给LDA的参数:
- docConcentration: 仅支持对称先验,因此提供的k维向量中的所有值都必须相同。所有值也必须> 1.0。提供Vector(-1)会导致默认行为(值(50 / k)+1的均匀k维向量)
- topicConcentration: 仅支持对称先验。值必须> 1.0。提供-1会导致默认值为0.1 + 1。
- maxIterations: EM迭代的最大次数。
Note: 进行足够的迭代很重要。在早期迭代中,EM通常没有用的主题,但是在进行更多迭代之后,这些主题会显着改善。根据您的数据集,通常至少合理使用20次甚至50-100次迭代。EMLDAOptimizer生成一个DistributedLDAModel,该模型不仅存储推断出的主题,还存储完整的训练语料库和训练语料库中每个文档的主题分布。 DistributedLDAModel支持:
- topTopicsPerDocument: 训练语料库中每个文档的主要主题及其权重
- topDocumentsPerTopic: 每个主题的顶部文档以及文档中相应主题的权重。
- logPrior: 给定超参数docConcentration和topicConcentration的估计主题和文档主题分布的对数概率
- logLikelihood:在推断出主题和文档主题分布的情况下,训练语料库的对数可能性
在线变分贝叶斯
在OnlineLDAOptimizer和LocalLDAModel中实现。
对于提供给LDA的参数:
- docConcentration: 通过在k个维度中的每个维度中传递值等于Dirichlet参数的向量,可以使用非对称先验。值应> = 0。提供Vector(-1)会导致默认行为(值(1.0 / k)的均匀k维向量)
- topicConcentration: 仅支持对称先验。值必须> = 0。提供-1结果默认值为(1.0 / k)。
- maxIterations: 提交的迷你批的最大数量。
此外,OnlineLDAOptimizer接受以下参数:
- miniBatchFraction: 在每次迭代中采样和使用的语料库分数
- optimizeDocConcentration: 如果设置为true,则在每个最小批处理之后对超参数docConcentration(又名alpha)执行最大似然估计,并在返回的LocalLDAModel中设置优化的docConcentration。
- tau0 and kappa: 用于学习率衰减,它由(τ0+ iter)-κ计算得出,其中iter是当前迭代次数。
OnlineLDAOptimizer生成一个LocalLDAModel,该模型仅存储推断出的主题。 LocalLDAModel支持:
- logLikelihood(documents): 给定推断的主题,计算提供的文档的下限。
- logPerplexity(documents): 给定推断的主题,计算所提供文档的困惑度的上限。
示例代码
import org.apache.spark.mllib.clustering.{DistributedLDAModel, LDA}
import org.apache.spark.mllib.linalg.Vectors
// Load and parse the data
val data = sc.textFile("data/mllib/sample_lda_data.txt")
val parsedData = data.map(s => Vectors.dense(s.trim.split(' ').map(_.toDouble)))
// Index documents with unique IDs
val corpus = parsedData.zipWithIndex.map(_.swap).cache()
// Cluster the documents into three topics using LDA
val ldaModel = new LDA().setK(3).run(corpus)
// Output topics. Each is a distribution over words (matching word count vectors)
println(s"Learned topics (as distributions over vocab of ${ldaModel.vocabSize} words):")
val topics = ldaModel.topicsMatrix
for (topic <- Range(0, 3)) {
print(s"Topic $topic :")
for (word <- Range(0, ldaModel.vocabSize)) {
print(s"${topics(word, topic)}")
}
println()
}
// Save and load model.
ldaModel.save(sc, "target/org/apache/spark/LatentDirichletAllocationExample/LDAModel")
val sameModel = DistributedLDAModel.load(sc,
"target/org/apache/spark/LatentDirichletAllocationExample/LDAModel")
bisecting k-means(均等k均值)
平分K均值通常会比常规K均值快得多,但通常会产生不同的聚类。
均分k均值是一种层次聚类。分层聚类是寻求建立聚类层次的最常用聚类分析方法之一。层次集群的策略通常分为两种:
- Agglomerative: 这是一种“自下而上”的方法:每个观察都在其自己的群集中开始,并且随着一个群集向上移动,合并成对的群集。
- Divisive: 这是一种“自上而下”的方法:所有观察都从一个群集开始,并且随着一个向下移动到层次结构而递归执行拆分。
二等分k均值算法是一种分裂算法。 MLlib中的实现具有以下参数:
- k: 所需数量的叶簇(默认值:4)。如果没有可分割的叶簇,则实际数目可能会更小。
- maxIterations: 分裂簇的最大k均值迭代次数(默认值:20)
- minDivisibleClusterSize: 可分割簇的最小点数(如果> = 1.0)或最小点数比例(如果<1.0)(默认值:1)
- seed: 随机种子(默认值:类名称的哈希值)
import org.apache.spark.mllib.clustering.BisectingKMeans
import org.apache.spark.mllib.linalg.{Vector, Vectors}
// Loads and parses data
def parse(line: String): Vector = Vectors.dense(line.split(" ").map(_.toDouble))
val data = sc.textFile("data/mllib/kmeans_data.txt").map(parse).cache()
// Clustering the data into 6 clusters by BisectingKMeans.
val bkm = new BisectingKMeans().setK(6)
val model = bkm.run(data)
// Show the compute cost and the cluster centers
println(s"Compute Cost: ${model.computeCost(data)}")
model.clusterCenters.zipWithIndex.foreach { case (center, idx) =>
println(s"Cluster Center ${idx}: ${center}")
}
streaming k-means(流式均值)
当数据到达流中时,我们可能希望动态估计群集,并在新数据到达时对其进行更新。 spark.mllib提供对流式k均值聚类的支持,并带有控制估计值衰减(或“健忘”)的参数。该算法使用了小批量k均值更新规则的概括。对于每批数据,我们将所有点分配给它们最近的聚类,计算新的聚类中心,然后使用以下方法更新每个聚类:
其中ct是群集的先前中心,nt是到目前为止分配给群集的点数,xt是当前批次中新的群集中心,mt是在当前批次中添加到群集的点数。衰减因子α可用于忽略过去:α= 1时,将从头开始使用所有数据;当α= 0时,将仅使用最新数据。这类似于指数加权移动平均值。
可以使用halfLife参数指定衰减,该参数确定正确的衰减因子a,以便对于在时间t采集的数据,其在时间t + halfLife的贡献将降至0.5。可以将时间单位指定为批次或点,并且将相应地调整更新规则。
示例代码
import org.apache.spark.mllib.clustering.StreamingKMeans
import org.apache.spark.mllib.linalg.Vectors
import org.apache.spark.mllib.regression.LabeledPoint
import org.apache.spark.streaming.{Seconds, StreamingContext}
val conf = new SparkConf().setAppName("StreamingKMeansExample")
val ssc = new StreamingContext(conf, Seconds(args(2).toLong))
val trainingData = ssc.textFileStream(args(0)).map(Vectors.parse)
val testData = ssc.textFileStream(args(1)).map(LabeledPoint.parse)
val model = new StreamingKMeans()
.setK(args(3).toInt)
.setDecayFactor(1.0)
.setRandomCenters(args(4).toInt, 0.0)
model.trainOn(trainingData)
model.predictOnValues(testData.map(lp => (lp.label, lp.features))).print()
ssc.start()
ssc.awaitTermination()
在添加带有数据的新文本文件时,群集中心将更新。每个训练点的格式应为[x1,x2,x3],每个测试数据点的格式应为(y,[x1,x2,x3]),其中y是一些有用的标签或标识符(例如,真实的类别分配) )。任何时候将文本文件放在/ training / data / dir中,模型都会更新。任何时候将文本文件放在/ testing / data / dir中,您都会看到预测。有了新数据,集群中心将发生变化!