2009-11-24 44 views
22

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

是否有任何可以直接聚簇文本的Python工具?如果不是,处理这个问题的最好方法是什么?

+1

有关“文本聚类”的信息: http://www2.parc.com/istl/projects/ia/sg-clustering.html – fviktor 2009-11-24 11:18:01

+0

如果您需要关于提取来自各种文档类型的文本内容,那么请添加另一个问题,因为这是另一个任务的一部分,我想。这将允许更好地分离问题恕我直言。 – fviktor 2009-11-24 11:25:20

+0

假如你有一个完整的目录(每个文件只有一个文档),Mallet也可以处理文本文件,而无需执行任何操作。我建议只使用nltk,一个Python库。您需要对这些文件进行一些预处理,但这并不痛苦。 – ealdent 2009-11-24 12:10:18

回答

44

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

  1. 要群集文件之间的相似性的一些概念。例如,很容易通过tfidf-cosine-distance来区分向量空间中关于体育和政治的新闻。根据这个衡量标准,将产品评论集中在“好”或“坏”的情况下要困难得多。

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

没有文本聚类解决方案,在任何情况下都能很好地工作。因此,开发一些集群软件并将数据放在其上可能是不够的。说了这么多,这里有一些我前段时间使用的实验性代码来处理文本聚类。这些文档被表示为归一化的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) 

对于实际应用中,你可以使用一个像样的标记生成器,使用整数代替令牌字符串且不calc的一个为O(n^2)距离矩阵...

0

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