Come calcolare la somiglianza tra due documenti di testo?


207

Sto cercando di lavorare su un progetto NLP, in qualsiasi linguaggio di programmazione (anche se Python sarà la mia preferenza).

Voglio prendere due documenti e determinare quanto sono simili.


1
Domanda simile qui stackoverflow.com/questions/101569/… streghe alcune belle risposte

Risposte:


292

Il modo comune per farlo è trasformare i documenti in vettori TF-IDF e quindi calcolare la somiglianza del coseno tra loro. Qualsiasi libro di testo sul recupero delle informazioni (IR) copre questo. Vedi esp. Introduzione al recupero delle informazioni , che è gratuito e disponibile online.

Somiglianze computazionali a coppie

TF-IDF (e trasformazioni di testo simili) sono implementati nei pacchetti Python Gensim e scikit-learn . In quest'ultimo pacchetto, calcolare le somiglianze del coseno è facile come

from sklearn.feature_extraction.text import TfidfVectorizer

documents = [open(f) for f in text_files]
tfidf = TfidfVectorizer().fit_transform(documents)
# no need to normalize, since Vectorizer will return normalized tf-idf
pairwise_similarity = tfidf * tfidf.T

o, se i documenti sono semplici stringhe,

>>> corpus = ["I'd like an apple", 
...           "An apple a day keeps the doctor away", 
...           "Never compare an apple to an orange", 
...           "I prefer scikit-learn to Orange", 
...           "The scikit-learn docs are Orange and Blue"]                                                                                                                                                                                                   
>>> vect = TfidfVectorizer(min_df=1, stop_words="english")                                                                                                                                                                                                   
>>> tfidf = vect.fit_transform(corpus)                                                                                                                                                                                                                       
>>> pairwise_similarity = tfidf * tfidf.T 

sebbene Gensim possa avere più opzioni per questo tipo di attività.

Vedi anche questa domanda .

[Dichiarazione di non responsabilità: sono stato coinvolto nell'implementazione TF-IDF di scikit-learn.]

Interpretazione dei risultati

Dall'alto, pairwise_similarityè una matrice sparsa di Scipy di forma quadrata, con il numero di righe e colonne uguale al numero di documenti nel corpus.

>>> pairwise_similarity                                                                                                                                                                                                                                      
<5x5 sparse matrix of type '<class 'numpy.float64'>'
    with 17 stored elements in Compressed Sparse Row format>

È possibile convertire l'array sparse in un array NumPy tramite .toarray()o .A:

>>> pairwise_similarity.toarray()                                                                                                                                                                                                                            
array([[1.        , 0.17668795, 0.27056873, 0.        , 0.        ],
       [0.17668795, 1.        , 0.15439436, 0.        , 0.        ],
       [0.27056873, 0.15439436, 1.        , 0.19635649, 0.16815247],
       [0.        , 0.        , 0.19635649, 1.        , 0.54499756],
       [0.        , 0.        , 0.16815247, 0.54499756, 1.        ]])

Diciamo che vogliamo trovare il documento più simile al documento finale, "I documenti di scikit-learn sono Orange e Blue". Questo documento ha un indice di 4 pollici corpus. Puoi trovare l'indice del documento più simile prendendo l'argmax di quella riga, ma prima dovrai mascherare gli 1, che rappresentano la somiglianza di ciascun documento con se stesso . Puoi fare il secondo np.fill_diagonal()e il primo attraverso np.nanargmax():

>>> import numpy as np     

>>> arr = pairwise_similarity.toarray()     
>>> np.fill_diagonal(arr, np.nan)                                                                                                                                                                                                                            

>>> input_doc = "The scikit-learn docs are Orange and Blue"                                                                                                                                                                                                  
>>> input_idx = corpus.index(input_doc)                                                                                                                                                                                                                      
>>> input_idx                                                                                                                                                                                                                                                
4

>>> result_idx = np.nanargmax(arr[input_idx])                                                                                                                                                                                                                
>>> corpus[result_idx]                                                                                                                                                                                                                                       
'I prefer scikit-learn to Orange'

Nota: lo scopo dell'utilizzo di una matrice sparsa è di risparmiare (una notevole quantità di spazio) per un corpus e un vocabolario di grandi dimensioni. Invece di convertire in un array NumPy, potresti fare:

>>> n, _ = pairwise_similarity.shape                                                                                                                                                                                                                         
>>> pairwise_similarity[np.arange(n), np.arange(n)] = -1.0
>>> pairwise_similarity[input_idx].argmax()                                                                                                                                                                                                                  
3

1
@larsmans Puoi spiegare un po 'l'array, se possibile, come dovrei leggere questo array. Le prime due colonne sono simili tra le prime due frasi?
Add-semi-due punti

1
@ Ipotesi nulla: nella posizione (i, j), si trova il punteggio di somiglianza tra il documento i e il documento j. Quindi, alla posizione (0,2) si trova il valore di somiglianza tra il primo documento e il terzo (usando l'indicizzazione in base zero), che è lo stesso valore che si trova in (2,0), poiché la somiglianza del coseno è commutativa.
Fred Foo,

1
Se dovessi calcolare la media di tutti i valori al di fuori della diagonale di 1, sarebbe un modo valido per ottenere un singolo punteggio di quanto i quattro documenti siano simili tra loro? In caso contrario, esiste un modo migliore per determinare la somiglianza complessiva tra più documenti?
user301752,

2
@ user301752: potresti prendere la media elementare dei vettori tf-idf (come farebbe k- X.mean(axis=0)medie ) , quindi calcolare la distanza euclidea media / massima / mediana (∗) da quella media. (∗) Scegli quello che ti piace.
Fred Foo,

1
@curious: ho aggiornato il codice di esempio con l'API scikit-learn corrente; potresti voler provare il nuovo codice.
Fred Foo,

87

Identico a @larsman, ma con qualche pre-elaborazione

import nltk, string
from sklearn.feature_extraction.text import TfidfVectorizer

nltk.download('punkt') # if necessary...


stemmer = nltk.stem.porter.PorterStemmer()
remove_punctuation_map = dict((ord(char), None) for char in string.punctuation)

def stem_tokens(tokens):
    return [stemmer.stem(item) for item in tokens]

'''remove punctuation, lowercase, stem'''
def normalize(text):
    return stem_tokens(nltk.word_tokenize(text.lower().translate(remove_punctuation_map)))

vectorizer = TfidfVectorizer(tokenizer=normalize, stop_words='english')

def cosine_sim(text1, text2):
    tfidf = vectorizer.fit_transform([text1, text2])
    return ((tfidf * tfidf.T).A)[0,1]


print cosine_sim('a little bird', 'a little bird')
print cosine_sim('a little bird', 'a little bird chirps')
print cosine_sim('a little bird', 'a big dog barks')

@ Renaud, risposta davvero buona e chiara! Ho due dubbi: I) qual è lo [0,1] che incorpori dopo tfidf * tfidf.T) e II) La frequenza inversa del documento è formata da tutti gli articoli o solo due (considerando che ne hai più di 2) ?
Economist_Ayahuasca,

2
@AndresAzqueta [0,1] sono le posizioni nella matrice per la somiglianza poiché due input di testo creeranno una matrice simmetrica 2x2.
Philip Bergström,

1
@Renaud, grazie per il tuo codice completo. Per coloro che hanno riscontrato l'errore che chiede di nltk.download (), è possibile eseguire facilmente nltk.download ('punkt'). Non è necessario scaricare tutto.
uomo,

@Renaud Non ho un problema più fondamentale. Quali stringhe di testo dovrebbero fite quali transform?
John Strood,

@JohnStrood Non capisco la tua domanda, scusa potresti riformulare?
Renaud,

45

È una vecchia domanda, ma ho scoperto che questo può essere fatto facilmente con Spacy . Una volta letto il documento, è similaritypossibile utilizzare una semplice API per trovare la somiglianza del coseno tra i vettori del documento.

import spacy
nlp = spacy.load('en')
doc1 = nlp(u'Hello hi there!')
doc2 = nlp(u'Hello hi there!')
doc3 = nlp(u'Hey whatsup?')

print doc1.similarity(doc2) # 0.999999954642
print doc2.similarity(doc3) # 0.699032527716
print doc1.similarity(doc3) # 0.699032527716

2
Mi chiedo perché la somiglianza tra doc1 e doc2 sia 0.999999954642 e non 1.0
JordanBelf

4
I numeri a virgola mobile di @JordanBelf vagano un po 'nella maggior parte delle lingue, poiché non possono avere una precisione illimitata nelle rappresentazioni digitali. ad esempio, le operazioni in virgola mobile o la produzione di numeri irrazionali hanno sempre piccoli errori di arrotondamento che si moltiplicano. È il lato negativo di una rappresentazione così flessibile in termini di scala.
scipilot,

2
qual è la funzione di distanza che utilizza il metodo di somiglianza in questo caso?
Ikel

Se hai problemi a trovare "en" esegui la seguente pip install spacy && python -m spacy download it
Cybernetic


17

Generalmente una somiglianza del coseno tra due documenti viene utilizzata come misura di somiglianza dei documenti. In Java, puoi usare Lucene (se la tua collezione è piuttosto grande) o LingPipe per farlo. Il concetto di base sarebbe quello di contare i termini in ogni documento e calcolare il punto prodotto dei termini vettori. Le biblioteche offrono numerosi miglioramenti rispetto a questo approccio generale, ad esempio utilizzando frequenze inverse di documenti e calcolando vettori tf-idf. Se stai cercando di fare qualcosa di copmlex, LingPipe fornisce anche metodi per calcolare la somiglianza LSA tra i documenti che fornisce risultati migliori rispetto alla somiglianza del coseno. Per Python, puoi usare NLTK .


4
Si noti che non esiste una "somiglianza LSA". LSA è un metodo per ridurre la dimensionalità di uno spazio vettoriale (per accelerare le cose o modellare argomenti anziché termini). Le stesse metriche di somiglianza utilizzate con BOW e tf-idf possono essere utilizzate con LSA (somiglianza del coseno, somiglianza euclidea, BM25, ...).
Witiko

16

Se stai cercando qualcosa di molto preciso, devi usare uno strumento migliore di tf-idf. Il codificatore di frasi universale è uno dei più precisi per trovare la somiglianza tra due parti di testo. Google ha fornito modelli predefiniti che è possibile utilizzare per la propria applicazione senza la necessità di allenarsi da zero. Innanzitutto, devi installare tensorflow e tensorflow-hub:

    pip install tensorflow
    pip install tensorflow_hub

Il codice seguente consente di convertire qualsiasi testo in una rappresentazione vettoriale a lunghezza fissa e quindi è possibile utilizzare il prodotto punto per scoprire la somiglianza tra loro

import tensorflow_hub as hub
module_url = "https://tfhub.dev/google/universal-sentence-encoder/1?tf-hub-format=compressed"

# Import the Universal Sentence Encoder's TF Hub module
embed = hub.Module(module_url)

# sample text
messages = [
# Smartphones
"My phone is not good.",
"Your cellphone looks great.",

# Weather
"Will it snow tomorrow?",
"Recently a lot of hurricanes have hit the US",

# Food and health
"An apple a day, keeps the doctors away",
"Eating strawberries is healthy",
]

similarity_input_placeholder = tf.placeholder(tf.string, shape=(None))
similarity_message_encodings = embed(similarity_input_placeholder)
with tf.Session() as session:
    session.run(tf.global_variables_initializer())
    session.run(tf.tables_initializer())
    message_embeddings_ = session.run(similarity_message_encodings, feed_dict={similarity_input_placeholder: messages})

    corr = np.inner(message_embeddings_, message_embeddings_)
    print(corr)
    heatmap(messages, messages, corr)

e il codice per la stampa:

def heatmap(x_labels, y_labels, values):
    fig, ax = plt.subplots()
    im = ax.imshow(values)

    # We want to show all ticks...
    ax.set_xticks(np.arange(len(x_labels)))
    ax.set_yticks(np.arange(len(y_labels)))
    # ... and label them with the respective list entries
    ax.set_xticklabels(x_labels)
    ax.set_yticklabels(y_labels)

    # Rotate the tick labels and set their alignment.
    plt.setp(ax.get_xticklabels(), rotation=45, ha="right", fontsize=10,
         rotation_mode="anchor")

    # Loop over data dimensions and create text annotations.
    for i in range(len(y_labels)):
        for j in range(len(x_labels)):
            text = ax.text(j, i, "%.2f"%values[i, j],
                           ha="center", va="center", color="w", 
fontsize=6)

    fig.tight_layout()
    plt.show()

il risultato sarebbe: la matrice di somiglianza tra coppie di testi

come vedi la maggior somiglianza è tra i testi con se stessi e poi con i loro testi vicini nel significato.

IMPORTANTE : la prima volta che si esegue il codice sarà lento perché deve scaricare il modello. se si desidera impedire che scarichi nuovamente il modello e utilizzare il modello locale, è necessario creare una cartella per la cache e aggiungerla alla variabile di ambiente, quindi dopo la prima esecuzione utilizzare quel percorso:

tf_hub_cache_dir = "universal_encoder_cached/"
os.environ["TFHUB_CACHE_DIR"] = tf_hub_cache_dir

# pointing to the folder inside cache dir, it will be unique on your system
module_url = tf_hub_cache_dir+"/d8fbeb5c580e50f975ef73e80bebba9654228449/"
embed = hub.Module(module_url)

Ulteriori informazioni: https://tfhub.dev/google/universal-sentence-encoder/2


ciao grazie per questo esempio che mi incoraggia a provare TF - da dove dovrebbe provenire l'oggetto "np"?
Open Food Broker,

1
UPD ok, ho installato numpy, matplotlib e anche il sistema TK Python vincolante per la trama e funziona !!
Open Food Broker,

1
Per ogni evenienza (scusate la mancanza di interruzioni di riga): import tensorflow come tf import tensorflow_hub come hub import matplotlib.pyplot come plt import numpy come np
dinnouti

5

Ecco una piccola app per iniziare ...

import difflib as dl

a = file('file').read()
b = file('file1').read()

sim = dl.get_close_matches

s = 0
wa = a.split()
wb = b.split()

for i in wa:
    if sim(i, wb):
        s += 1

n = float(s) / float(len(wa))
print '%d%% similarity' % int(n * 100)

4
difflib è molto lento se hai intenzione di lavorare con un gran numero di documenti.
Phyo Arkar Lwin,

2

Potresti provare questo servizio online per la somiglianza dei documenti del coseno http://www.scurtu.it/documentSimilarity.html

import urllib,urllib2
import json
API_URL="http://www.scurtu.it/apis/documentSimilarity"
inputDict={}
inputDict['doc1']='Document with some text'
inputDict['doc2']='Other document with some text'
params = urllib.urlencode(inputDict)    
f = urllib2.urlopen(API_URL, params)
response= f.read()
responseObject=json.loads(response)  
print responseObject

l'API utilizza il Matcher sequenziale differenziale? Se sì, allora una funzione semplice in Python farebbe il lavoro ____________________________________ dall'importazione diffusa SequenceMatcher def isStringSimilar (a, b): ratio = SequenceMatcher (None, a, b) .ratio () return ratio ______________________________
Rudresh Ajgaonkar

2

Se sei più interessato a misurare la somiglianza semantica di due pezzi di testo, ti suggerisco di dare un'occhiata a questo progetto gitlab . Puoi eseguirlo come un server, c'è anche un modello pre-costruito che puoi usare facilmente per misurare la somiglianza di due pezzi di testo; anche se è principalmente addestrato per misurare la somiglianza di due frasi, puoi comunque usarlo nel tuo caso. È scritto in Java ma puoi eseguirlo come servizio RESTful.

Un'altra opzione è anche la somiglianza DKPro che è una libreria con vari algoritmi per misurare la somiglianza dei testi. Tuttavia, è anche scritto in Java.

esempio di codice:

// this similarity measure is defined in the dkpro.similarity.algorithms.lexical-asl package
// you need to add that to your .pom to make that example work
// there are some examples that should work out of the box in dkpro.similarity.example-gpl 
TextSimilarityMeasure measure = new WordNGramJaccardMeasure(3);    // Use word trigrams

String[] tokens1 = "This is a short example text .".split(" ");   
String[] tokens2 = "A short example text could look like that .".split(" ");

double score = measure.getSimilarity(tokens1, tokens2);

System.out.println("Similarity: " + score);

2

Per trovare la somiglianza delle frasi con un set di dati molto minore e per ottenere un'elevata precisione, puoi utilizzare il pacchetto Python di seguito che utilizza modelli BERT pre-addestrati,

pip install similar-sentences

L'ho appena provato, ma dà la somiglianza di ogni frase con quella principale, ma c'è un modo per creare tutti i dati di allenamento di prono.txt come una classe e ottenere il punteggio su quanta fiducia viene abbinata a tutti gli examplese ?
Guru Teja,

1
sì, puoi provare .batch_predict (BatchFile, NumberOfPrediction) che fornirà output come Results.xls con colonne ['Frase', 'Suggerimento', 'Punteggio']
Shankar Ganesh Jayaraman

1

Per la somiglianza sintattica Esistono 3 semplici modi per rilevare la somiglianza.

  • Word2Vec
  • Guanto
  • Tfidf o countvectorizer

Per la somiglianza semantica Si può usare l'incorporamento BERT e provare diverse strategie di raggruppamento di parole per ottenere l'incorporamento dei documenti e quindi applicare la somiglianza del coseno sull'incorporamento dei documenti.

Una metodologia avanzata può utilizzare BERT SCORE per ottenere la somiglianza. PUNTEGGIO BERT

Link al documento di ricerca: https://arxiv.org/abs/1904.09675

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.