La calidad del texto agrupación depende principalmente de dos factores:
alguna noción de similitud entre los documentos que desea agrupar. Por ejemplo, es fácil distinguir entre nuevos artículos sobre deportes y política en el espacio vectorial a través de tfidf-cosine-distance. Es mucho más difícil agrupar los comentarios de los productos en "buenos" o "malos" según esta medida.
El método de agrupación en sí. ¿Sabes cuántos clústers habrá? Ok, usa kmeans. ¿No le importa la precisión, pero quiere mostrar una buena estructura de árbol para la navegación de los resultados de búsqueda? Use algún tipo de agrupamiento jerárquico.
No existe una solución de agrupamiento de texto que funcione bien bajo ninguna circunstancia. Y, por lo tanto, probablemente no sea suficiente tomar algún software de agrupación de la caja y arrojar sus datos sobre él.
Dicho esto, he aquí un código experimental que utilicé hace algún tiempo para jugar con el agrupamiento de texto. Los documentos se representan como vectores tfidf normalizados y la similitud se mide como la distancia del coseno. El método de agrupamiento en sí es 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)
Para aplicaciones reales, se utilizaría un tokenizer decente, el uso de números enteros en lugar de token-cuerdas y no Calc un O (n^2) distancia de matriz ...
Información sobre qué "agrupación de texto" es: http://www2.parc.com/istl/projects/ia/sg-clustering.html – fviktor
Si necesita ayuda en la extracción de los contenidos de texto de varios tipos de documentos, luego agregue otra pregunta, ya que esa es otra parte de la tarea, creo. Permitiría una mejor separación de los problemas en mi humilde opinión. – fviktor
Mallet también funcionará en archivos de texto sin tener que hacer nada, suponiendo que tiene un directorio lleno de ellos (con un documento por archivo). Recomiendo solo usar nltk, una biblioteca de python.Tendrá que hacer un pequeño preprocesamiento en los archivos, pero no es doloroso. – ealdent