La qualité du texte dépend regroupement principalement de deux facteurs:
une certaine notion de similitude entre les documents que vous souhaitez regrouper. Par exemple, il est facile de faire la distinction entre les articles sur les sports et la politique dans l'espace vectoriel via tfidf-cosine-distance. Il est beaucoup plus difficile de regrouper les revues de produits en «bonnes» ou «mauvaises» sur la base de cette mesure.
La méthode de regroupement elle-même. Vous savez combien de cluster il y aura? Ok, utilisez les kmeans. Vous ne vous souciez pas de la précision, mais souhaitez afficher une arborescence pour la navigation dans les résultats de recherche? Utilisez une sorte de clustering hiérarchique.
Il n'y a pas de solution en cluster texte, qui fonctionnerait bien en aucun cas. Et par conséquent, il ne suffit probablement pas de prendre un logiciel de clustering prêt à l'emploi et d'y jeter vos données. Cela dit, voici un code expérimental que j'ai utilisé il y a quelques temps pour jouer avec le groupement de texte. Les documents sont représentés comme des vecteurs tfidf normalisés et la similarité est mesurée en tant que distance en cosinus. La méthode de regroupement elle-même est 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)
Pour des applications réelles, vous utilisez un tokenizer décent, entiers d'utilisation au lieu de cordes jeton et ne CALC pas un O (n^2) distance matrice ...
Informations sur les « clusters texte » est: http://www2.parc.com/istl/projects/ia/sg-clustering.html – fviktor
Si vous avez besoin d'aide sur l'extraction le contenu du texte de divers types de documents, alors s'il vous plaît ajouter une autre question, car c'est une autre partie de la tâche, je pense. Cela permettrait une meilleure séparation des problèmes à mon humble avis. – fviktor
Mallet travaillera également sur des fichiers texte sans que vous ayez à faire quoi que ce soit, en supposant que vous avez un répertoire plein d'entre eux (avec un doc par fichier). Je recommande simplement d'utiliser nltk, une bibliothèque python.Vous aurez besoin de faire un peu de pré-traitement sur les fichiers, mais ce n'est pas douloureux. – ealdent