La qualità del testo di clustering dipende principalmente da due fattori:
qualche nozione di somiglianza tra i documenti che si desidera inserire nel cluster. Ad esempio, è facile distinguere tra articoli di cronaca sullo sport e la politica nello spazio vettoriale via tfidf-cosine-distance. È molto più difficile raggruppare recensioni di prodotti in "buono" o "cattivo" in base a questa misura.
Il metodo di raggruppamento stesso. Sai quanti cluster ci saranno? Ok, usa i kmea. Non ti interessa la precisione ma vuoi mostrare una bella struttura ad albero per la navigazione dei risultati di ricerca? Utilizzare una sorta di clustering gerarchico.
Non esiste una soluzione di cluster di testo, che funzioni bene in qualsiasi circostanza. E quindi probabilmente non è abbastanza per prendere alcuni software di clustering fuori dalla scatola e lanciare i tuoi dati su di esso.
Detto questo, ecco un codice sperimentale che ho utilizzato qualche tempo fa per giocare con il clustering del testo. I documenti sono rappresentati come vettori tfidf normalizzati e la somiglianza è misurata come distanza coseno. Il metodo di cluster stesso è 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)
Per le applicazioni reali, si potrebbe utilizzare un tokenizzatore decente, utilizzare numeri interi, invece di token-stringhe e non CALC un O (n^2) distanza-matrix ...
Informazioni su cosa "clustering di testo" è: http://www2.parc.com/istl/projects/ia/sg-clustering.html – fviktor
Se hai bisogno di aiuto sull'estrazione il contenuto del testo da vari tipi di documenti, quindi si prega di aggiungere un'altra domanda, dal momento che è un'altra parte del compito, penso. Permetterebbe una migliore separazione dei problemi IMHO. – fviktor
Mallet funzionerà anche sui file di testo senza che tu debba fare nulla, assumendo che tu abbia una directory piena di essi (con un documento per file). Ti consiglio semplicemente di usare nltk, una libreria python.Avrai bisogno di fare un po 'di pre-elaborazione sui file, ma non è doloroso. – ealdent