【问题标题】:tf-idf document clustering with K-means in Apache Spark putting points into one clustertf-idf 文档聚类与 Apache Spark 中的 K-means 将点放入一个聚类中
【发布时间】:2017-10-07 09:13:30
【问题描述】:

我正在尝试通过预处理、生成 tf-idf 矩阵,然后应用 K-means 来完成对文本文档进行聚类的经典工作。但是,在经典的 20NewsGroup 数据集上测试此工作流程会导致大多数文档被聚集到一个集群中。 (我最初尝试将 20 个组中的 6 个中的所有文档聚类 - 因此希望聚类为 6 个聚类)。

我在 Apache Spark 中实现了这一点,因为我的目的是在数百万个文档中使用这种技术。这是在 Databricks 上用 Pyspark 编写的代码:

#declare path to folder containing 6 of 20 news group categories
path = "/mnt/%s/20news-bydate.tar/20new-bydate-train-lessFolders/*/*" % 
MOUNT_NAME

#read all the text files from the 6 folders. Each entity is an entire 
document. 
text_files = sc.wholeTextFiles(path).cache()

#convert rdd to dataframe
df = text_files.toDF(["filePath", "document"]).cache()

from pyspark.ml.feature import HashingTF, IDF, Tokenizer, CountVectorizer 

#tokenize the document text
tokenizer = Tokenizer(inputCol="document", outputCol="tokens")
tokenized = tokenizer.transform(df).cache()

from pyspark.ml.feature import StopWordsRemover

remover = StopWordsRemover(inputCol="tokens", 
outputCol="stopWordsRemovedTokens")
stopWordsRemoved_df = remover.transform(tokenized).cache()

hashingTF = HashingTF (inputCol="stopWordsRemovedTokens", outputCol="rawFeatures", numFeatures=200000)
tfVectors = hashingTF.transform(stopWordsRemoved_df).cache()    

idf = IDF(inputCol="rawFeatures", outputCol="features", minDocFreq=5)
idfModel = idf.fit(tfVectors)

tfIdfVectors = idfModel.transform(tfVectors).cache()

#note that I have also tried to use normalized data, but get the same result
from pyspark.ml.feature import Normalizer
from pyspark.ml.linalg import Vectors

normalizer = Normalizer(inputCol="features", outputCol="normFeatures")
l2NormData = normalizer.transform(tfIdfVectors)

from pyspark.ml.clustering import KMeans

# Trains a KMeans model.
kmeans = KMeans().setK(6).setMaxIter(20)
km_model = kmeans.fit(l2NormData)

clustersTable = km_model.transform(l2NormData)

ID number_of_documents_in_cluster
0    3024
3    5
1    3
5    2
2    2
4    1

正如您所见,我的大多数数据点都聚集到集群 0 中,我无法弄清楚我做错了什么,因为我在网上遇到的所有教程和代码都指向使用这种方法。

此外,我还尝试在 K-means 之前对 tf-idf 矩阵进行归一化,但这也会产生相同的结果。我知道余弦距离是一种更好的测量方法,但我希望在 Apache Spark 中使用标准 K-means 会提供有意义的结果。

谁能帮助我了解我的代码中是否存在错误,或者我的数据集群管道中是否缺少某些内容?

提前谢谢你!

这是python中的实现,即使具有大量最大特征,它也不会将所有文档组合在一起:

#imports
import pandas as pd
import os
import nltk
from nltk.tokenize import RegexpTokenizer
from nltk.corpus import stopwords

import numpy as np

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.cluster import KMeans, MiniBatchKMeans 

vectorizer = TfidfVectorizer(max_features=200000, lowercase=True,
                             min_df=5, stop_words='english',
                             use_idf=True)

X = vectorizer.fit_transform(df['document'])

#Apply K-means to create cluster
from time import time

km = KMeans(n_clusters=20, init='k-means++', max_iter=20, n_init=1,
            verbose=False)

km.fit(X)

#result
3     2634
6     1720
18    1307
15     780
0      745
1      689
16     504
8      438
7      421
5      369
11     347
14     330
4      243
13     165
10     136
17     118
9      113
19     106
12      87
2       62

我原以为我们可以在 pyspark 中使用具有欧几里得距离的 KMeans 复制类似的东西,然后再在 KMeans 中尝试余弦或 Jaccard 距离。有什么解决方案或cmets?

【问题讨论】:

  • 您是否尝试过将您的 epsilon 更改为更高的数字?这将有助于限制集群合并在一起 .setEpsilon apache-spark-user-list.1001560.n3.nabble.com/… 有一点解释
  • Apache spark 有方法:kmeans.getTol()/setTol 用于获取和设置 tol 值。默认设置为 0.0001。我相信这个值是需要设置的,因为我找不到对方法 setEpsilon 的任何引用。但是,由于相同的方法和 tol 值可以使用 sklearn 在 python 中对文档进行集群 - 我认为修改它不会有帮助。
  • 我已经在 python 中使用 Sklearn 解决了这个问题,使用构建 tf-idf 矩阵和使用 K-means 算法的相同方法。任何人都可以建议为什么它在 spark 中失败 - 我的 pyspark 代码中有错误吗?
  • 你用的是什么版本的python/spark?
  • 我使用的是 spark 2.1。目前我在 Databricks 社区版上实现了这一点。

标签: python apache-spark k-means tf-idf


【解决方案1】:

@纳西尔, Spark k-means(scala mllib api) 在我的实验中也一直在产生高度倾斜的集群大小分布(见图 1)。大多数数据点分配给一个集群。该实验是使用 20 个具有基本事实的新闻组数据进行的:大约 10K 数据点被手动分类为相当平衡的 20 个组。 http://qwone.com/~jason/20Newsgroups/

最初我怀疑向量创建步骤(使用 Spark 的 HashingTF 和 IDF 库)是导致不正确聚类的原因。然而,即使在实现了我自己版本的基于 TF-IDF 的向量表示之后,我仍然得到了相似的聚类结果,但大小分布高度偏斜。

最终我在 spark 上实现了我自己的 k-means 版本,它使用标准 TF-IDF 矢量表示和 (-ve) 余弦相似度作为距离度量。这个 k-means 的结果看起来是正确的。请参见下图 2。

此外,我通过插入欧几里德距离作为相似性度量(与我自己的 kmean 版本)进行了实验,结果看起来仍然正确,不像 spark k-means 那样倾斜。

figure 1 and 2

【讨论】:

  • 很高兴看到其他人能够重现该错误。我无法让 spark 中的人们正确查看或解决此问题 - 我认为这是非常重要的,因为 spark mllib 库函数被大量使用。我将在未来实现你的代码,看看我是否得到类似的结果,然后它是否适用于原始问题空间所在的 Twitter 数据集。
  • @Nassir,当然。实现的自定义 k-mean 在私有 repo 上,我可以与你分享,让我知道你的 git 句柄。你指的是哪个推特数据集?它有集群的黄金标准吗?
【解决方案2】:

只需几个快速的 cmets:

  • K-Means 通常不是文本分析的最佳算法,因为它在高维度上表现不佳。我会推荐 LDA。
  • 使用 K-Means,如果您将特征数量减少到大约 2000 个,那么您更有可能获得多个不同的集群。 (我在 Databricks CE 中 /databricks-datasets/news20.binary/data-001/training 提供的 20news 数据集上快速尝试了这个,并且能够获得不同的集群。)
  • 不相关:如果将所有转换器和 K-Means 放入流水线,然后只调用一次 fit() 和 transform(),MLlib 代码可以不那么冗长。 :)

这是我从你修改的代码可以运行。警告:我根本没有调整它,所以集群目前非常无用(但它确实找到了不同的集群)。

df = spark.read.parquet("/databricks-datasets/news20.binary/data-001/training")
df.cache().count()

from pyspark.ml.feature import HashingTF, IDF, Tokenizer, CountVectorizer, StopWordsRemover
tokenizer = Tokenizer(inputCol="text", outputCol="tokens")
remover = StopWordsRemover(inputCol="tokens", outputCol="stopWordsRemovedTokens")
hashingTF = HashingTF(inputCol="stopWordsRemovedTokens", outputCol="rawFeatures", numFeatures=2000)
idf = IDF(inputCol="rawFeatures", outputCol="features", minDocFreq=5)

from pyspark.ml.clustering import KMeans
kmeans = KMeans(k=20)

from pyspark.ml import Pipeline
pipeline = Pipeline(stages=[tokenizer, remover, hashingTF, idf, kmeans])

model = pipeline.fit(df)

results = model.transform(df)
results.cache()

display(results.groupBy("prediction").count())  # Note "display" is for Databricks; use show() for OSS Apache Spark

【讨论】:

  • 感谢您的回复。我了解 K Means 不是最好的,但我要求在申请 LDA 之前在微文本文档上完成。我也知道具有欧几里得距离的标准 Kmeans 并不是最好的,但我正试图让它发挥作用,因为研究论文提到他们将其用作基线。
  • 虽然将特征数量减少到 2000 确实会减少维度,但我在原始问题中添加的 Python 实现不会导致所有文档在一个集群中组合在一起 - 即 max_features=200000 – 你知道为什么使用 sklearn 的 python 实现产生的输出更符合预期吗?
  • 嗯,这是个好问题。 :) 我自己没有时间去探索它,但我猜这两条管道正在以不同的方式进行特征处理。我建议通过在 1 个库中进行特征预处理然后将这些特征向量传递给 2 个库来隔离差异。检查各种转换器和 K-Means 的默认参数也很好。如果这仍然显示出主要差异,则可能是在初始化中,这两种算法之间不同的(因为执行本地算法与分布式算法)。
  • 我无法将 sklearn 的预处理输出输入到 spark k-means 中,或者将 spark 的预处理输出输入到 sk-learn kmeans。但是由于两种情况下的预处理是相同的,我不得不说我更倾向于认为kmeans的spark实现对于文本聚类不能正常工作
  • 我目前的解决方案是采用 pyspark 数据框,将其转换为 pandas 数据框,然后使用 sklearn 库进行聚类——效果很好!但是,这当然不是我可以在不久的将来在大数据集上使用的并行解决方案。希望 apache spark 的人能解决这个问题。
猜你喜欢
  • 2014-12-31
  • 2016-12-16
  • 2015-01-16
  • 2018-01-25
  • 2017-11-01
  • 2015-04-11
  • 2019-03-16
  • 2016-05-29
  • 2013-01-11
相关资源
最近更新 更多