2010-03-13 7 views
1

J'ai une boucle qui calcule la similarité entre deux documents. Il rassemble tous les jetons dans un document et leurs scores, et les place dans un dictionnaire. Il compare ensuite les dictionnairesSimilarité de document: Comparaison efficace de deux documents

C'est ce que j'ai jusqu'à présent, cela fonctionne, mais il est super lent:

# Doc A 
cursor1.execute("SELECT token, tfidf_norm FROM index WHERE doc_id = %s", (docid[i][0])) 
doca = cursor1.fetchall() 
#convert tuple to a dictionary 
doca_dic = dict((row[0], row[1]) for row in doca) 

#Doc B 
cursor2.execute("SELECT token, tfidf_norm FROM index WHERE doc_id = %s", (docid[j][0])) 
docb = cursor2.fetchall() 
#convert tuple to a dictionary 
docb_dic = dict((row[0], row[1]) for row in docb) 

# loop through each token in doca and see if one matches in docb 
for x in doca_dic: 
    if docb_dic.has_key(x): 
     #calculate the similarity by summing the products of the tf-idf_norm 
     similarity += doca_dic[x] * docb_dic[x] 
print "similarity" 
print similarity 

Je suis assez nouveau pour Python, donc ce gâchis. J'ai besoin de l'accélérer, toute aide serait appréciée. Merci.

Répondre

2

Un point Python: adict.has_key(k) est obsolète dans Python 2.X et a disparu dans Python 3.X. k in adict en tant qu'expression est disponible depuis Python 2.2; utilisez-le à la place. Ce sera plus rapide (pas d'appel de méthode).

Un point pratique en toute langue: parcourez le dictionnaire plus court.

résultat combiné:

if len(doca_dic) < len(docb_dict): 
    short_dict, long_dict = doca_dic, docb_dic 
else: 
    short_dict, long_dict = docb_dic, doca_dic 
similarity = 0 
for x in short_dict: 
    if x in long_dict: 
     #calculate the similarity by summing the products of the tf-idf_norm 
     similarity += short_dict[x] * long_dict[x] 

Et si vous ne avez pas besoin des deux dictionnaires pour quoi que ce soit d'autre, vous pouvez créer seulement un et itérer sur la tuples B (clé, valeur) comme ils pop out de votre requête B Après la docb = cursor2.fetchall(), remplacer tout le code ci-dessous par ceci:

similarity = 0 
for b_token, b_value in docb: 
    if b_token in doca_dic: 
     similarity += doca_dic[b_token] * b_value 

alternative au code ci-dessus: Cela fait plus de travail mais il fait plus de itérer en C au lieu de Python et peut être plus rapide.

similarity = sum(
    doca_dic[k] * docb_dic[k] 
    for k in set(doca_dic) & set(docb_dic) 
    ) 

Version finale du code Python

# Doc A 
cursor1.execute("SELECT token, tfidf_norm FROM index WHERE doc_id = %s", (docid[i][0])) 
doca = cursor1.fetchall() 
# Doc B 
cursor2.execute("SELECT token, tfidf_norm FROM index WHERE doc_id = %s", (docid[j][0])) 
docb = cursor2.fetchall() 
if len(doca) < len(docb): 
    short_doc, long_doc = doca, docb 
else: 
    short_doc, long_doc = docb, doca 
long_dict = dict(long_doc) # yes, it should be that simple 
similarity = 0 
for key, value in short_doc: 
    if key in long_dict: 
     similarity += long_dict[key] * value 

Un autre point pratique: vous avez pas dit quelle partie il est lent ... travailler sur les dicts ou faire Selects? Mettez quelques appels de time.time() dans votre script. Envisagez de pousser TOUT le travail sur la base de données. L'exemple suivant utilise une requête SQLite câblée mais le principe est le même.

C:\junk\so>sqlite3 
SQLite version 3.6.14 
Enter ".help" for instructions 
Enter SQL statements terminated with a ";" 
sqlite> create table atable(docid text, token text, score float, 
    primary key (docid, token)); 
sqlite> insert into atable values('a', 'apple', 12.2); 
sqlite> insert into atable values('a', 'word', 29.67); 
sqlite> insert into atable values('a', 'zulu', 78.56); 
sqlite> insert into atable values('b', 'apple', 11.0); 
sqlite> insert into atable values('b', 'word', 33.21); 
sqlite> insert into atable values('b', 'zealot', 11.56); 
sqlite> select sum(A.score * B.score) from atable A, atable B 
    where A.token = B.token and A.docid = 'a' and B.docid = 'b'; 
1119.5407 
sqlite> 

Et il est bon de vérifier que la table de base de données est correctement indexé (par exemple un sur token par lui-même) ... ne pas avoir un indice utile est une bonne façon de faire une requête SQL exécuter très lentement. Explication: Avoir un index sur token peut rendre vos requêtes existantes ou la requête "faire tout le travail dans la base de données" ou les deux s'exécuter plus rapidement, selon les caprices de l'optimiseur de requête dans votre logiciel DB et la phase de la lune. Si vous n'avez pas d'index utilisable, la base de données lira TOUTES les lignes de votre table - ce qui n'est pas bien.

Création d'un index: create index atable_token_idx on atable(token);

suppression d'un index: drop index atable_token_idx;

(mais ne consulter la documentation pour votre DB)

+0

+1 John c'était une grosse erreur! :) –

+0

"Et si vous n'avez pas besoin des deux dictionnaires pour quelque chose d'autre, vous pouvez créer seulement le un et itérer sur les tuples B (clé, valeur) quand ils sortent de votre requête B." Je ne sais pas. Comment puis-je les parcourir au fur et à mesure qu'ils sortent de ma requête? (désolé si c'est évident) Merci beaucoup pour votre aide. Je dois dire que ce site et les gens sont géniaux. – seanieb

+1

@seanieb: voir ma réponse mise à jour. –

1

Qu'en est-il de pousser certains des travaux sur la DB?

Avec une jointure, vous pouvez avoir un résultat qui est essentiellement

Token A.tfidf_norm B.tfidf_norm 
----------------------------------------- 
    Apple  12.2   11.00 
     ... 
    Word  29.87   33.21 
    Zealot  0.00   11.56 
    Zulu  78.56   0.00 

Et il vous suffit de scanner le curseur et faire vos opérations.

Si vous n'avez pas besoin de savoir si un mot est dans un document et manquant dans l'autre, vous n'avez pas besoin d'une jointure externe, et la liste sera l'intersection des deux ensembles. L'exemple que j'ai inclus ci-dessus attribue automatiquement un "0" pour les mots manquants dans l'un des deux documents. Voyez ce que vos fonctions "correspondantes" nécessitent.

0

Une requête SQL peut faire le travail: '?'

SELECT sum(index1.tfidf_norm*index2.tfidf_norm) FROM index index1, index index2 WHERE index1.token=index2.token AND index1.doc_id=? AND index2.doc_id=? 

Il suffit de remplacer le avec l'identifiant de document 2 respectivement.