千家信息网

Spark平台下基于LDA的k-means算法实现是怎样的

发表于:2024-11-19 作者:千家信息网编辑
千家信息网最后更新 2024年11月19日,Spark平台下基于LDA的k-means算法实现是怎样的,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。1.文本挖掘模块设计1.1文
千家信息网最后更新 2024年11月19日Spark平台下基于LDA的k-means算法实现是怎样的

Spark平台下基于LDA的k-means算法实现是怎样的,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。

1.文本挖掘模块设计

1.1文本挖掘流程

文本分析是机器学习中的一个很宽泛的领域,并且在情感分析、聊天机器人、垃圾邮件检测、推荐系统以及自然语言处理等方面得到了广泛应用。

文本聚类是信息检索领域的一个重要概念,在文本挖掘领域有着广泛的应用。文本聚类能够自动地将文本数据集划分为不同的类簇,从而更好地组织文本信息,可以实现高效的知识导航与浏览。

本文选择主题模型LDA(Latent Dirichlet Allocation)算法对文档进行分类处理,选择在Spark平台上通过Spark MLlib实现LDA算法,其中Spark Mllib是Spark提供的机器学习库,该库提供了常用的机器学习算法。

1.2文本挖掘流程分析

首先是数据源部分,主要的数据包括文档数据和互联网爬虫数据。然后是数据抽取部分,将已经收集好的数据通过同步工具上传至分布式文件系统HDFS,作为模型训练的数据源。其次是数据探索与预处理部分,该部分主要是对原始数据集进行预处理,包括分词、停用词过滤、特征向量提取等。再次是模型训练部分,主要包括训练与测试,从而得到一个模型。最后是模型评估,对学得模型进行评估之后,进行线上部署。

2.文本挖掘模块算法研究

2.1LDA主题模型算法

LDA(Latent Dirichlet allocation)由David M. Blei,Andrew Y. Ng,Michael I. Jordan于2003年提出的基于概率模型的主题模型算法,即隐含狄利克雷分布,它可以将文档集中每篇文档的主题以概率分布的形式给出,将文本向量投射到更容易分析处理的主题空间当中,去除文本中存在的噪声,是一种常用的文本分析技术,可以用来识别大规模文档集或语料库中潜在的主题信息,通常被用来对大规模文档数据进行建模。通过主题模型和设定的主题数,可以训练出文档集合中不同的主题所占的比例以及每个主题下的关键词语出现的概率。从文档集合中学习得到主题分布和主题比例,可以进一步在数据挖掘任务中使用。

LDA借用词袋的思想,以某一概率选取某个主题,再以某一概率选出主题中的每个单词,通过不断重复该步骤产生文档中的所有语词。该方法对词汇进行了模糊聚类,聚集到一类的词可以间接地表示一个隐含的主题。LDA对文本信息进行了挖掘,能用来衡量不同文档之间的潜在关系,也能通过某一类词来表达文档中隐藏的主题。

2.2K均值算法

聚类(Clustering)是一种将数据集划分为若干组或类的方法。通过聚类过程将一群抽象的对象分为若干组,每一组由相似的对象构成,称之为一个类别。与分类不同(将数据按照事先定义好的分类标准进行划分),聚类是一种无监督学习(unsupervised learning),训练数据集的标签信息是未知的,目标是通过对无标记训练样本按照特定的测度的形似性程度进行聚合,为进一步数据分析提供基础。

K均值(k-means)算法的基本思想是初始随机给定K 个簇中心,即从n个数据对象中选择k个任意对象作为初始的簇中心,按照最邻近原则把待分类样本点分到各个簇。然后按平均法重新计算各个簇的中心(该类别中的所有数据对象的均值),从而确定新的簇心。一直迭代,直到簇心的移动距离小于某个给定的值。

K均值算法采用了贪心策略,通过迭代优化来近似求解上式E值,算法流程如下图所示

2.3文本挖掘算法优化

LDA主题模型算法应用于文档聚类,计算得出的主题可以看做是文档的聚类中心,利用主题模型进行文档聚类,可以有效地组织文档数据集。同时,由于LDA主题模型可以计算出每篇文档在不同主题下的概率分布,因此可以将此主题的概率分布作为文档的特征向量,从而将高维的文档向量投影到低维的特征空间中。

计算文本之间的距离是传统的K-means算法在进行文本聚类时的关键步骤,而文本通常是非结构化数据,构建的文本向量具有稀疏性和维度高的特点,同时,构建文本特征向量并未考虑到文字之间的语义关系,因此可能会造成位于同一类簇的文本之间具有非相似性。

因此本文基于LDA主题模型改进K-means算法,首先通过LDA主题模型对文档数据集进行建模,挖掘出每篇文档的主题概率分布,既能够达到文档降维和去除噪声的效果,又能弥补通过关键词构建文档特征向量容易造成丢失信息的缺陷。最后每篇文档的主题概率分布作为K-means算法的输入数据集。

3.实验分析

3.1基于Spark的LDA主题模型算法实现

数据集介绍

选择Newsgroups数据集作为该实验的训练集和测试集。Newgroups是一个新闻数据集,该数据集包括大约20000篇新闻文档,总共分为6个大类别,每个大类别又分不同的小类别,小类别共计20个,如下表所示。该新闻数据集已经成为了学界和业界在机器学习的文本挖掘实验中常用的数据集,比如文本分类和文本聚类。

该数据集共包含7个文件,其中3个文件为训练数据(train.data、train.label、train.map),共计11269篇,另外3个文件为测试数据(test.data、test.label、test.map),共计7505篇,另外一个文件为词汇表(vocabulary.txt),其第i行表示编号为i的单词的名称。文件扩展名为.data的文件格式为[docIdx wordIdx count],其中docIdx表示文档编号,wordIdx表示词语的编号,count表示该词语的词频统计。文件扩展名为.label的文件表示文档的主题分类,每行数据代表某篇文档的类别。文件扩展名为.map的文表示类别编号与类别名称的映射关系,其具体格式为[labelName labelId]。

原始数据集处理

原始的数据集格式为[docIdx wordIdx count],例如[1,20,2]表示在编号为1的文档中,编号为20的词语的词频是2。LDA接受的参数格式为:

[label,(vector_ size, [wiIdx,wjIdx,···wnIdx ],[tfi,tfj,···tfn])]

上述格式的数据代表一个带有标签的稀疏向量,其中label表示文档编号,vector_ size表示向量的维度,wnIdx表示词n的索引编号,tfn表示词n的词频。需要将原始数据转换成上述的格式,具体步骤如下:

  • Step1:将原始数据集上传至HDFS
[kms@kms-1 ~]$ hdfs dfs -put /opt/modules/train_data/lda/train.data  /train/lda/
  • Step2:初始化SparkSession并加载数据
val spark = SparkSession           
.builder
.appName(s"${this.getClass.getSimpleName}") .getOrCreate()
//设置日志级别
Logger.getLogger("org.apache.spark").setLevel(Level.OFF) Logger.getLogger("org.apache.hadoop").setLevel(Level.OFF)
//加载原始数据集
val rowDS = spark.read.textFile("/train/lda/train.data")
  • Step3:数据集矩阵变换处理
//创建形如MatrixEntry(row_index, column_index, value)的MatrixEntry
val matrixEntry:RDD[MatrixEntry] = rowDS.rdd.map(_.split(" "))
.map(rowdata => MatrixEntry(rowdata(0).toLong,rowdata(1).toLong,rowdata(2).toDouble))
//创建稀疏矩阵
val sparseMatrix: CoordinateMatrix = new CoordinateMatrix(matrixEntry)
//创建LabeledPoint数据集
val labelPointData = sparseMatrix.toIndexedRowMatrix.rows.map(r => (r.index, r.vector.asML))
val corpusDF = spark.createDataFrame(labelPointData).toDF("label","features")
corpusDF.saveAsTextFile("/tarin/lda/labelPointData")

处理之后的部分数据集如下所示,其中一行代表一篇文档的特征向量

[4551,(53976,[23,27,29,30,44,45,48,314,425,748,767,825,930,969,995,1345,7033,13872,16798,19139,26846,26847,27081,29607,30801,31200,31201,31202],[2.0,1.0,3.0,3.0,1.0,1.0,1.0,1.0,2.0,3.0,1.0,2.0,1.0,1.0,2.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0])]
[2493,(53976,[80,133,754,3699,4066,5190,6138,7327,7361,10267,10344,10949,11390,11683,11759,16206,22708,22709],[1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,2.0,1.0,1.0,2.0,1.0,1.0,1.0,1.0,2.0,1.0])]

k折交叉验证确定训练参数

交叉验证法(cross validation)是将数据集D划分为k个大小相似的互斥子集的一种方法,其中每个子集都尽可能地保持数据分布的一致性,即从数据集D中通过分层采样的方式得到。然后,每次再用k-1个子集的并集作为训练集,剩下的那个子集作为测试集;通过这样的处理,可以得到k组训练集和测试集,进而可以进行k次训练和测试,最终返回的是这k个测试结果的均值。交叉验证法评估结果的稳定性和保真性在很大程度上依赖于k的取值,为突出这一点,通常把交叉验证法称为k折交叉验证(k-fold cross validation)。K通常取值为10,称之为10折交叉验证,下图给出了10折交叉验证的示意图。

困惑度(perplexity)指标是LDA 模型的原作者Blei 等提出的一种反应模型泛化能力的指标, 在评价模型的优劣上具有一定的代表性和普遍性。所谓困惑度就是文档在划分主题时确定性的评判, 反映的是模型对新样本的适用性。困惑度越小表示该模型的泛化能力越好。

十折交叉验证处理过程如下所示

//将数据集分割为10份,每份占10%
val splitData = labelPointData.randomSplit(Array.fill(10)(0.1))
//设定主题的个数为15-25
val rangTopic = 15 to 25
rangTopic.foreach { k =>
var perplexity = 0.0
for (i <- 0 to 9) {
//选择其中9份做训练集
val trainIdx = (0 to 9).toArray.filter(_ != i)
var trainData = spark.sparkContext.union(
splitData(trainIdx(0)),
splitData(trainIdx(1)),
splitData(trainIdx(2)),
splitData(trainIdx(3)),
splitData(trainIdx(4)),
splitData(trainIdx(5)),
splitData(trainIdx(6)),
splitData(trainIdx(7)),
splitData(trainIdx(8)))
//创建DataFrame
val trainDF = spark.createDataFrame(trainData).toDF("label","features")
val testDF = spark.createDataFrame(splitData(i)).toDF("label","features")
//训练主题个数为k时的模型
val lda = new LDA().setK(k).setMaxIter(50)
val ldaModel = lda.fit(trainDF)
perplexity = perplexity + ldaModel.logPerplexity(testDF)
}
val avePerplexity = perplexity / 10
System.out.println("当主题个数为 " + k + "时," + "交叉验证的平均困惑度为 " + avePerplexity)
}

经过十折交叉验证,验证在不同主题下(取值15-25)训练模型的平均困惑度,测试发现在主题k=20时,困惑度的值最小。由于困惑度值越小, 表示该模型具有较好的泛化能力,所以选择k=20作为主题个数。

实验结果

将主题个数设置为20,迭代次数设置为50,使用上述数据集训练LDA模型,具体步骤如下

//训练LDA模型
val lda = new LDA().setK(20).setMaxIter(50)
val model = lda.fit(corpusDF)
val ll = model.logLikelihood(corpusDF)
val lp = model.logPerplexity(corpusDF)
println("当主题个数为20时对数似然为: " + ll)
println("当主题个数为20时困惑度为: " + lp)
//描述主题
val topics = model.describeTopics(5)
println("The topics described by their top-weighted terms:")
topics.show(false)
topics.rdd.saveAsTextFile("/tarin/lda/topics")
// 测试结果
val transformed = model.transform(corpusDF)
transformed.select("label","topicDistribution").rdd.saveAsTextFile("/tarin/lda /testtopic")

通过训练得到LDA模型,其中训练数据的主题-单词概率分布如下表所示,选择权重排名在前5的单词,其中topic表示主题编号,termIndices表示词语编号组成的集合,termWeights表示词语编号对应的权重的集合。

每个主题对应的单词列表如下表所示,其中topic表示主题,termIndices表示词语编号组成的集合,vocabulary表示词汇。

该模型的文档-主题分布如下表所示,由于文档较多,这里仅列出部分文档,其中label表示文档编号,topicDistribution表示文档的主题分布。

通过上面的分析,得到了文本的主题分布。每篇文档将对应一个主题的特征空间,从而达到降维的目的。主题-单词单词概率分布描述了主题的特征空间,其中主题表示聚类中心。

结合词汇表与训练集,将其处理成[word,count]的形式,其中word表示单词,count表示该次出现的频次,具体的处理过程如下。

package com.apache.ml
import org.apache.spark.{SparkConf, SparkContext}

object WordCount {
def main(args: Array[String]): Unit = {
val conf = new SparkConf()
.setMaster("local")
.setAppName("wordcount")
val sc = new SparkContext(conf)

val rawRDD = sc.textFile("e:///lda/train.data")
val vocabulary = sc.textFile("e:///lda/vocabulary.txt")
val word_nums = rawRDD.map(_.split(" ")).map(row => (row(1).toLong, row(2).toInt))
val word_count = word_nums.reduceByKey(_ + _)
val sort_wcnt = word_count.sortByKey(false)
//处理词汇表
val num_vocabulary = vocabulary.zipWithIndex().map(row => (row._2, row._1))
val sort_combination = sort_wcnt.join(num_vocabulary)
.map(row => (row._2._1, row._2._2))
.sortByKey(false)
.map(row => (row._2, row._1))
sort_combination.saveAsTextFile("e:///combination")
}
}

通过使用R语言的wordcloud2包,进行可视化文档词云图展示,见下图

3.2Spark平台下基于LDA的k-means算法实现

数据预处理

将通过LDA主题模型计算的文档-主题分布作为k-means的输入,文档-主题分布的形式为[label, features,topicDistribution],其中features代表文档的特征向量,每一行数据代表一篇文档。由于k-means接受的特征向量输入的形式为[label,features],所以需要将原始的数据集schema转化为[label,features]的形式,即将topicDistribution列名转为features。处理步骤为:

   Val trainDF =   transformed.select("label","topicDistribution").toDF("label",   "features")       
模型训练

Spark ML的K-means算法提供了如下的参数配置:

  • setFeaturesCol(value: String):设置输入的特征向量列,默认值为features
  • setK(value: Int):设置类簇的个数
  • setMaxIter(value: Int):设置最大迭代次数
  • setPredictionCol(value: String):设置输出列名称,默认为prediction
  • setSeed(value: Long):设置随机数种子
  • setTol(value: Double):设置收敛阈值

设置最大迭代次数为200,随机数种子为123,类簇个数为2、4、6、8、10、12、14、16、18、20,其余选择默认值,分别观察评估指标的变化情况。具体代码如下:

(2 to 20 by 2).toList.map {k =>
val kmeans = new KMeans().setK(k).setSeed(123).setMaxIter(200)
val kmeansModel = kmeans.fit(kMeansTrain)
// 预测结果
val predictions = kmeansModel.transform(kMeansTrain)
//计算误差平方和
val wssse = kmeansModel.computeCost(kMeansTrain)
println(s"Within set sum of squared errors = $wssse")
// 计算轮廓系数
val evaluator = new ClusteringEvaluator()
val silhouette = evaluator.evaluate(predictions)
println(s"Silhouette with squared euclidean distance = $silhouette")
//显示聚类结果
println("Cluster Centers: ")
kmeansModel.clusterCenters.foreach(println)
}

经过训练,得到当K为6时聚类效果最好,此时的聚类结果如下表所示:

模型评估
  • 轮廓系数

轮廓系数(Silhouette Coefficient)是评价聚类效果好坏的一种方法,用来衡量簇的密集与分散程度。它结合内聚度和分离度两种因素,可以用来在相同原始数据的基础上用来评价不同算法、或者算法不同运行方式对聚类结果所产生的影响。轮廓系数取值为[-1,1],其值越大表示同类中样本距离最近,不同类中样本距离最远,即该值越接近于1,簇越紧凑,聚类效果越好。

使用K-means算法,将待分类数据集分为了 k 个类簇,对于其中的一个点 i 来说,a(i)表示该向量到它所属类簇中其他点的平均距离,b(i)表示该向量到其他类簇中点的平均距离。对于一个样本集合,所有样本的轮廓系数的平均值即为该聚类结果总的轮廓系数。

  • 误差平方和

误差平方和又称残差平方和、组内平方和等。根据n个观察值拟合适当的模型后,余下未能拟合部份(ei=yi-y平均)称为残差,其中y平均表示n个观察值的平均值,所有n个残差平方之和称误差平方和。

当K取不同值时,计算所得误差平方

计算所得的轮廓系数如下图所示,结合误差平方和和轮廓系数,当k=6时,有着较好的聚类效果。

3.3结果分析

首先,通过LDA主题模型,可以计算出文档数据集的文档-主题分布情况和主题-单词的分布情况,训练得出的主题数即为类簇数。

对LDA训练的文档-主题分布结果,即将文档表示成在不同主题上的分布所组成的向量,由于LDA考虑到了词之间的语义关系,所以该特征向量能够更好地反应文档的信息,因此可以将其作为K-means聚类算法的输入,从而弥补基于空间向量模型的K-means算法的缺点。经过实验发现,在类簇K为6时,轮廓系数为65.9661577458792,误差平方和为0.8266340036962969,聚类效果良好。

主要介绍Spark平台下基于LDA的k-means算法实现。对文本挖掘进行了详细设计,在公开数据集上训练LDA模型,并对文档-主题分布和主题-词语分布进行了详细说明。最后实现了基于LDA的K-means聚类算法,克服了传统K-means算法的缺陷。关于Spark平台下基于LDA的k-means算法实现是怎样的问题的解答就分享到这里了,希望以上内容可以对大家有一定的帮助,如果你还有很多疑惑没有解开,可以关注行业资讯频道了解更多相关知识。

0