找到每个kmeans集群的顶级单词

时间:2015-11-08 21:02:08

标签: python apache-spark pyspark apache-spark-mllib

我有以下代码部分,它将TFIDF的一组推文映射到原始单词,然后用于查找每个群集中的顶级单词:<​​/ p>  

#document = sc.textFile("<text file path>").map(lambda line: line.split(" "))
#"tfidf" is an rdd of tweets contained in "document"
#map tfidf to original tweets and cluster similar tweets
clusterIds = clusters.predict(tfidf)
mapped_value = clusterIds.zip(document)
cluster_value = mapped_value.reduceByKey(lambda a,b: a+b).take(cluster_num)


#Fetch the top 5 words from each cluster
topics = []
for i in cluster_value:
    word_count = sc.parallelize(i[1])
    topics.append(
        word_count.map(lambda x: (x,1))
            .reduceByKey(lambda x,y: x+y)
            .takeOrdered(5, key=lambda x: -x[1]))

有更好的方法吗? 我在Spark UI上看到,在4个VM的集群上执行reduceByKey()操作时,我的代码需要大约70分钟,其中包含20.5 Gb的执行程序内存和2 GB的驱动程序内存。推文数量为500K。用于停用词和垃圾字符的文本文件大小为31 Mb。

1 个答案:

答案 0 :(得分:2)

由于您未提供a Minimal, Complete, and Verifiable example,我只能假设document rdd包含标记化文本。所以让我们创建一个虚拟的例子:

mapped_value = sc.parallelize(
    [(1, "aabbc"), (1, "bab"), (2, "aacc"), (2, "acdd")]).mapValues(list)
mapped_value.first()
## (1, ['a', 'a', 'b', 'b', 'c'])

您可以做的一件事是同时聚合所有集群:

from collections import Counter

create_combiner = Counter

def merge_value(cnt, doc):
    cnt.update(Counter(doc))
    return cnt

def merge_combiners(cnt1, cnt2):
    cnt1.update(cnt2)
    return cnt1

topics = (mapped_value
    .combineByKey(create_combiner, merge_value, merge_combiners)
    .mapValues(lambda cnt: cnt.most_common(2)))

topics
## [(1, [('b', 4), ('a', 3)]), (2, [('a', 3), ('c', 3)])]

您可以通过将Counter替换为普通dict并手动计数/更新来进一步改进,但我认为不值得大惊小怪。

有什么好处?

  • 首先,您减少了必须移动的数据量(序列化 - 转移 - 反序列化)。特别是,您不仅仅收集将数据发送回工人。

    收集和发送是昂贵的,所以你应该避免它,除非它是唯一的选择。如果对整个数据集的聚合是昂贵的,那么优选的方法可能是重复filter等效于这样的事情:

    [rdd.filter(lambda (k, v): k == i).map(...).reduce(...)
        for i in range(number_of_clusters)]
    
  • 你只开始一份工作而不是每个集群的工作而且开始工作并不便宜(请参阅我对Spark MLLib's LassoWithSGD doesn't scale?的答案)。你在这里获得多少取决于许多集群。

  • 由于数据没有平整,因此根本没什么可做的。连接列表不需要任何内容​​,需要大量复制。使用词典可以减少存储数据的数量,就地更新不需要副本。您可以通过调整merge_value

    来尝试进一步提高效果
    def merge_value(cnt, doc):
        for v in doc:
            cnt[v] += 1
        return cnt1
    

附注:

  • 有30 MB的数据和20.5 GB的内存,我根本不会打扰Spark。由于k-means只需要很少的额外内存,因此您可以以更低的成本在本地并行创建多个模型。