在Python中聚类文本

时间:2009-11-24 10:43:39

标签: python cluster-analysis nlp

我需要对一些文本文档进行聚类,并且一直在研究各种选项。看起来LingPipe可以在没有事先转换的情况下对纯文本进行聚类(到向量空间等),但它是我见过的唯一明确声称可以处理字符串的工具。

是否有可以直接对文本进行聚类的Python工具?如果没有,最好的办法是什么?

3 个答案:

答案 0 :(得分:44)

文本聚类的质量主要取决于两个因素:

  1. 您想要群集的文档之间的相似性概念。例如,通过tfidf-cosine-distance可以很容易地区分向量空间中关于体育和政治的新闻文章。根据这一衡量标准,将产品评论分为“好”或“坏”要困难得多。

  2. 聚类方法本身。你知道有多少个集群?好的,使用kmeans。你不关心准确性,但想要显示一个漂亮的树形结构来导航搜索结果?使用某种层次聚类。

  3. 没有文本群集解决方案,在任何情况下都可以正常运行。因此,开箱即用一些集群软件并将数据丢给它可能还不够。

    话虽如此,这里是我前段时间使用的一些实验代码,用于文本聚类。文档表示为标准化的tfidf向量,并且相似性被测量为余弦距离。聚类方法本身是majorclust

    import sys
    from math import log, sqrt
    from itertools import combinations
    
    def cosine_distance(a, b):
        cos = 0.0
        a_tfidf = a["tfidf"]
        for token, tfidf in b["tfidf"].iteritems():
            if token in a_tfidf:
                cos += tfidf * a_tfidf[token]
        return cos
    
    def normalize(features):
        norm = 1.0 / sqrt(sum(i**2 for i in features.itervalues()))
        for k, v in features.iteritems():
            features[k] = v * norm
        return features
    
    def add_tfidf_to(documents):
        tokens = {}
        for id, doc in enumerate(documents):
            tf = {}
            doc["tfidf"] = {}
            doc_tokens = doc.get("tokens", [])
            for token in doc_tokens:
                tf[token] = tf.get(token, 0) + 1
            num_tokens = len(doc_tokens)
            if num_tokens > 0:
                for token, freq in tf.iteritems():
                    tokens.setdefault(token, []).append((id, float(freq) / num_tokens))
    
        doc_count = float(len(documents))
        for token, docs in tokens.iteritems():
            idf = log(doc_count / len(docs))
            for id, tf in docs:
                tfidf = tf * idf
                if tfidf > 0:
                    documents[id]["tfidf"][token] = tfidf
    
        for doc in documents:
            doc["tfidf"] = normalize(doc["tfidf"])
    
    def choose_cluster(node, cluster_lookup, edges):
        new = cluster_lookup[node]
        if node in edges:
            seen, num_seen = {}, {}
            for target, weight in edges.get(node, []):
                seen[cluster_lookup[target]] = seen.get(
                    cluster_lookup[target], 0.0) + weight
            for k, v in seen.iteritems():
                num_seen.setdefault(v, []).append(k)
            new = num_seen[max(num_seen)][0]
        return new
    
    def majorclust(graph):
        cluster_lookup = dict((node, i) for i, node in enumerate(graph.nodes))
    
        count = 0
        movements = set()
        finished = False
        while not finished:
            finished = True
            for node in graph.nodes:
                new = choose_cluster(node, cluster_lookup, graph.edges)
                move = (node, cluster_lookup[node], new)
                if new != cluster_lookup[node] and move not in movements:
                    movements.add(move)
                    cluster_lookup[node] = new
                    finished = False
    
        clusters = {}
        for k, v in cluster_lookup.iteritems():
            clusters.setdefault(v, []).append(k)
    
        return clusters.values()
    
    def get_distance_graph(documents):
        class Graph(object):
            def __init__(self):
                self.edges = {}
    
            def add_edge(self, n1, n2, w):
                self.edges.setdefault(n1, []).append((n2, w))
                self.edges.setdefault(n2, []).append((n1, w))
    
        graph = Graph()
        doc_ids = range(len(documents))
        graph.nodes = set(doc_ids)
        for a, b in combinations(doc_ids, 2):
            graph.add_edge(a, b, cosine_distance(documents[a], documents[b]))
        return graph
    
    def get_documents():
        texts = [
            "foo blub baz",
            "foo bar baz",
            "asdf bsdf csdf",
            "foo bab blub",
            "csdf hddf kjtz",
            "123 456 890",
            "321 890 456 foo",
            "123 890 uiop",
        ]
        return [{"text": text, "tokens": text.split()}
                 for i, text in enumerate(texts)]
    
    def main(args):
        documents = get_documents()
        add_tfidf_to(documents)
        dist_graph = get_distance_graph(documents)
    
        for cluster in majorclust(dist_graph):
            print "========="
            for doc_id in cluster:
                print documents[doc_id]["text"]
    
    if __name__ == '__main__':
        main(sys.argv)
    

    对于实际应用程序,你可以使用一个像样的标记化器,使用整数而不是标记字符串,并且不计算O(n ^ 2)距离矩阵......

答案 1 :(得分:2)

似乎可以通过使用简单的UNIX命令行工具将这些文档的文本内容提取到文本文件中,然后使用纯Python解决方案进行实际的聚类。

我找到了一般用于群集数据的代码段:

http://www.daniweb.com/code/snippet216641.html

用于此的Python包:

http://python-cluster.sourceforge.net/

另一个python包(主要用于生物信息学):

http://bonsai.ims.u-tokyo.ac.jp/~mdehoon/software/cluster/software.htm#pycluster

答案 2 :(得分:0)

Python库NLTK支持语言分析,包括聚类文本