2009-06-03 6 views
16

Per calcolare la somiglianza tra due documenti, creo un vettore di funzionalità contenente il termine frequenze. Ma poi, per il prossimo passo, non riesco a decidere tra "Cosine similarity" e "Hamming distance".somiglianza cosine vs distanza di Hamming

La mia domanda: hai esperienza con questi algoritmi? Quale ti dà risultati migliori?

In aggiunta a ciò: Potrebbe dirmi come codificare la similarità del coseno in PHP? Per distanza di Hamming, ho già il codice:

function check ($terms1, $terms2) { 
    $counts1 = array_count_values($terms1); 
    $totalScore = 0; 
    foreach ($terms2 as $term) { 
     if (isset($counts1[$term])) $totalScore += $counts1[$term]; 
    } 
    return $totalScore * 500/(count($terms1) * count($terms2)); 
} 

Non voglio usare qualsiasi altro algoritmo. Mi piacerebbe solo avere un aiuto per decidere tra entrambi.

E forse qualcuno può dire qualcosa su come migliorare gli algoritmi. Otterrete risultati migliori se filtrate le parole di arresto o le parole comuni?

Spero che tu possa aiutarmi. Grazie in anticipo!

risposta

16

A distanza di Hamming dovrebbe essere fatto tra due stringhe di uguale lunghezza e con l'ordine preso in considerazione.

Dato che i tuoi documenti sono sicuramente di lunghezza diversa e se i posti delle parole non contano, la somiglianza del coseno è migliore (tieni presente che a seconda delle tue esigenze esistono soluzioni migliori). :)

Ecco una funzione coseno di similitudine di 2 array di parole:

function cosineSimilarity($tokensA, $tokensB) 
{ 
    $a = $b = $c = 0; 
    $uniqueTokensA = $uniqueTokensB = array(); 

    $uniqueMergedTokens = array_unique(array_merge($tokensA, $tokensB)); 

    foreach ($tokensA as $token) $uniqueTokensA[$token] = 0; 
    foreach ($tokensB as $token) $uniqueTokensB[$token] = 0; 

    foreach ($uniqueMergedTokens as $token) { 
     $x = isset($uniqueTokensA[$token]) ? 1 : 0; 
     $y = isset($uniqueTokensB[$token]) ? 1 : 0; 
     $a += $x * $y; 
     $b += $x; 
     $c += $y; 
    } 
    return $b * $c != 0 ? $a/sqrt($b * $c) : 0; 
} 

'veloce (isset() anziché in_array() è un killer su grandi array).

Come potete vedere, i risultati non tengono conto della "magnitudine" di ciascuna parola.

Lo uso per rilevare messaggi multi-postato di "quasi" testi copiati. Funziona bene. :)

Il miglior collegamento su metriche di similarità stringa: http://www.dcs.shef.ac.uk/~sam/stringmetrics.html

Per letture più interessanti:

http://www.miislita.com/information-retrieval-tutorial/cosine-similarity-tutorial.html http://bioinformatics.oxfordjournals.org/cgi/content/full/22/18/2298

+0

Grazie mille. :) Ma la soluzione di Mike (risposta selezionata) non è migliore? Il codice è più breve e sembra essere veloce come il tuo. Quali sono le differenze? – caw

+1

La funzione di Mike non è esattamente esatta. Prova 'echo check (array ('a', 'b', 'c'), array ('a', 'b', 'c'));' Dovrebbe restituire 1 (cos (0)) ma la sua funzione restituisce 0,33. :( – Toto

+0

La tua funzione è veramente corretta? Fornisce 0,71 per [1, 1, 1] e [1, 1, 0]. Ma http://www.miislita.com/searchito/binary-similarity-calculator.html dà 0,82?! È ancora necessario dividere il valore di somiglianza per la lunghezza del documento? – caw

5

Mi scuso per aver ignorato il fatto che hai detto che non volevi utilizzare altri algoritmi, ma seriamente, Levenshtein distance e Damerau-Levenshtein distance sono molto più utili della distanza di Hamming. Ecco un D-L distance implementation in PHP, e se non ti piace levenshtein() funzione nativa di PHP, che penso che non lo farò perché ha un limite di lunghezza, ecco una versione non lunghezza limitata:

function levenshtein_distance($text1, $text2) { 
    $len1 = strlen($text1); 
    $len2 = strlen($text2); 
    for($i = 0; $i <= $len1; $i++) 
     $distance[$i][0] = $i; 
    for($j = 0; $j <= $len2; $j++) 
     $distance[0][$j] = $j; 
    for($i = 1; $i <= $len1; $i++) 
     for($j = 1; $j <= $len2; $j++) 
      $distance[$i][$j] = min($distance[$i - 1][$j] + 1, $distance[$i][$j - 1] + 1, $distance[$i - 1][$j - 1] + ($text1[$i - 1] != $text2[$j - 1])); 
    return $distance[$len1][$len2]; 
} 
+0

Grazie. Penso che tu abbia frainteso qualcosa. NON VOGLIO utilizzare solo la distanza di Hamming. Vorrei applicarlo al vettore di funzionalità del testo, non al testo stesso. Quindi direi che è più utile di levenshtein, non è vero? ;) Ma grazie per il codice, sono sicuro che è utile per molti utenti per altri scopi. – caw

+0

Oops. Non sono riuscito ad assorbire la parte del tratto vettoriale. Non importa. :) Dato che ti piace il codice, lascerò la risposta non cancellata. Spero che i downvoters avranno pietà. :) – chaos

+0

Sì, hanno. Ci sono più uptoters che downvoters. ;) – caw

9

A meno che io sono errato, penso che tu abbia un algoritmo a metà strada tra i due algoritmi. Per distanza di Hamming, uso:

function check ($terms1, $terms2) { 
    $counts1 = array_count_values($terms1); 
    $totalScore = 0; 
    foreach ($terms2 as $term) { 
     if (isset($counts1[$term])) $totalScore += 1; 
    } 
    return $totalScore * 500/(count($terms1) * count($terms2)); 
} 

(. Si noti che si sta solo aggiungendo 1 per ogni elemento abbinati nei vettori gettone)

E per coseno di similitudine, uso:

function check ($terms1, $terms2) { 
    $counts1 = array_count_values($terms1); 
    $counts2 = array_count_values($terms2); 
    $totalScore = 0; 
    foreach ($terms2 as $term) { 
     if (isset($counts1[$term])) $totalScore += $counts1[$term] * $counts2[$term]; 
    } 
    return $totalScore/(count($terms1) * count($terms2)); 
} 

(Si noti che si sta aggiungendo il prodotto dei conteggi di token tra i due documenti.)

La differenza principale tra i due è che la somiglianza del coseno sarà quella Un indicatore più forte quando due documenti hanno la stessa parola più volte nei documenti, mentre nella distanza di Hamming non interessa la frequenza con cui vengono visualizzati i token individuali.

: ho appena notato la tua domanda sulla rimozione delle parole di funzione ecc. Ti consiglio se usi la somiglianza del coseno - dato che le parole di funzione sono abbastanza frequenti (in inglese, almeno), potresti distorcere un risultato non filtrandoli. Se usi la distanza di Hamming, l'effetto non sarà altrettanto grande, ma potrebbe comunque essere apprezzabile in alcuni casi. Inoltre, se si ha accesso a un lemmatizer, ridurrà le mancanze quando un documento contiene "galassie" e l'altro contiene "galassia", per esempio.

In qualsiasi modo, buona fortuna!

+0

Grazie mille! Quindi, se sto usando una combinazione di entrambi gli algoritmi, combina anche i loro vantaggi? Di quanto sarebbe meglio di questi algoritmi, giusto? :) O dovrei usare meglio uno dei tuoi esempi di codice? La tua ultima frase è abbastanza interessante. Quindi la somiglianza del coseno sarebbe migliore per il mio scopo, giusto? Dal momento che esprime una relazione più forte tra due testi se una parola appare spesso, non è vero? – caw

+0

@ marco92w: penso che la somiglianza del coseno sia la migliore in questo caso - vedi anche la mia recente modifica sulle parole di funzione. la tua intuizione era morta lì. – Mike

+0

Thx, la modifica è anche informativa. Ultima domanda: :) Qual è la differenza tra la somiglianza del coseno e il mio algoritmo (codice in questione)? Qual è il migliore? – caw

2

Ecco il mio codice corretto per la funzione coseno Distanza postato da Toto

function cosineSimilarity($tokensA, $tokensB) 
{ 
    $a = $b = $c = 0; 
    $uniqueTokensA = $uniqueTokensB = array(); 

    $uniqueMergedTokens = array_unique(array_merge($tokensA, $tokensB)); 

    foreach ($tokensA as $token) $uniqueTokensA[$token] = 0; 
    foreach ($tokensB as $token) $uniqueTokensB[$token] = 0; 

    foreach ($uniqueMergedTokens as $token) { 
     $x = isset($uniqueTokensA[$token]) ? 1 : 0; 
     $y = isset($uniqueTokensB[$token]) ? 1 : 0; 
     $a += $x * $y; 
     $b += pow($x,2); 
     $c += pow($y,2); 
    } 
    return $b * $c != 0 ? $a/sqrt($b * $c) : 0; 
} 
+1

$ x (e $ y) sarà sempre 1 (il token esiste) o 0 (il token non esiste). In questo caso, POW ($ x, 2) restituirà sempre $ x. Quindi l'ho rimosso per salvare la cpu. :) – Toto

+0

Quindi le tue versioni sono entrambe corrette, Lorenzo e Totò? Entrambi funzionano? – caw