在Apache Spark中使用K-means进行tf-idf文档聚类将点放入一个集群

时间:2017-05-09 07:10:56

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

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

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

#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)

output showing most documents get clustered into cluster 0

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

我原本以为我们可以在使用具有欧几里德距离的KMeans之前在pyspark中复制类似的东西,然后在KMeans中尝试余弦或Jaccard距离。任何解决方案或评论?

2 个答案:

答案 0 :(得分:0)

只需几点快速评论:

  • K-Means一般不是文本分析的最佳算法,因为它在高维度上表现不佳。我推荐使用LDA。
  • 使用K-Means,如果将功能数量减少到2000,那么您更有可能获得多个不同的群集。 (我在Databricks CE中提供的20news数据集上快速尝试了这一点/databricks-datasets/news20.binary/data-001/training,并且能够得到不同的集群。)
  • 不相关:如果将所有变换器和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

答案 1 :(得分:0)

@Nassir, 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版本)进行实验,结果继续看起来正确,而不是像火花k-means那样倾斜。

figure 1 and 2