Jak obliczyć podobieństwo między dwoma dokumentami tekstowymi?

207

Patrzę na pracę nad projektem NLP w dowolnym języku programowania (choć wolę Python).

Chcę wziąć dwa dokumenty i ustalić ich podobieństwo.

Reily Bourne
źródło
1
Podobne pytanie tutaj stackoverflow.com/questions/101569/... z kilkoma fajnymi odpowiedziami

Odpowiedzi:

292

Częstym sposobem na to jest przekształcenie dokumentów w wektory TF-IDF, a następnie obliczenie podobieństwa cosinus między nimi. Każdy podręcznik dotyczący wyszukiwania informacji (IR) to obejmuje. Zobacz esp. Wprowadzenie do wyszukiwania informacji , które jest bezpłatne i dostępne online.

Obliczanie podobieństw par

TF-IDF (i podobne transformacje tekstu) są zaimplementowane w pakietach Python Gensim i scikit-learn . W drugim pakiecie obliczanie podobieństw cosinusa jest równie łatwe jak

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

lub, jeśli dokumenty są zwykłymi ciągami,

>>> 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 

chociaż Gensim może mieć więcej opcji dla tego rodzaju zadań.

Zobacz także to pytanie .

[Oświadczenie: Byłem zaangażowany w implementację TF-IDF scikit-learn.]

Interpretacja wyników

Z góry pairwise_similarityjest rzadka macierz Scipy , która ma kwadratowy kształt, a liczba wierszy i kolumn jest równa liczbie dokumentów w korpusie.

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

Możesz przekonwertować rzadką tablicę na tablicę NumPy za pomocą .toarray()lub .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.        ]])

Powiedzmy, że chcemy znaleźć dokument najbardziej podobny do dokumentu końcowego: „Dokumenty scikit-learn są pomarańczowe i niebieskie”. Ten dokument ma indeks 4 cali corpus. Możesz znaleźć indeks najbardziej podobnego dokumentu, biorąc argmax tego wiersza, ale najpierw musisz zamaskować jedynki, które reprezentują podobieństwo każdego dokumentu do siebie . Możesz zrobić to drugie np.fill_diagonal(), a pierwsze przez 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'

Uwaga: celem użycia rzadkiej macierzy jest zaoszczędzenie (znaczna ilość miejsca) na dużym korpusie i słownictwie. Zamiast konwertować na tablicę NumPy, możesz:

>>> n, _ = pairwise_similarity.shape                                                                                                                                                                                                                         
>>> pairwise_similarity[np.arange(n), np.arange(n)] = -1.0
>>> pairwise_similarity[input_idx].argmax()                                                                                                                                                                                                                  
3
Fred Foo
źródło
1
@larsmans Czy możesz wyjaśnić tablicę, jeśli to możliwe, jak powinienem ją czytać. Pierwsze dwie kolumny to podobieństwo między pierwszymi dwoma zdaniami?
dodaj średniki
1
@ Hipoteza zerowa: w pozycji (i, j) znajduje się wynik podobieństwa między dokumentem i a dokumentem j. Tak więc w pozycji (0,2) znajduje się wartość podobieństwa między pierwszym dokumentem a trzecim (przy użyciu indeksowania zerowego), która jest tą samą wartością, którą można znaleźć w (2,0), ponieważ podobieństwo kosinusowe jest przemienne.
Fred Foo,
1
Gdybym miał uśrednić wszystkie wartości poza przekątną 1, czy byłby to dobry sposób na uzyskanie pojedynczej oceny tego, jak podobne są te cztery dokumenty? Jeśli nie, czy istnieje lepszy sposób ustalenia ogólnego podobieństwa między wieloma dokumentami?
user301752,
2
@ user301752: możesz wziąć pod względem elementarnym średnią wektorów tf-idf (jak by to zrobił k-średnich) X.mean(axis=0), a następnie obliczyć średnią / maksymalną / medianę (∗) odległość euklidesową od tej średniej. (∗) Wybierz, co ci się podoba.
Fred Foo
1
@curious: Zaktualizowałem przykładowy kod do bieżącego API scikit-learn; możesz wypróbować nowy kod.
Fred Foo
87

Identyczny z @larsman, ale z pewnym wstępnym przetwarzaniem

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
źródło
@ Renaud, naprawdę dobra i jasna odpowiedź! Mam dwie wątpliwości: I) jaki jest [0,1], który włączasz po tfidf * tfidf.T) i II) Odwrotna częstotliwość dokumentów jest tworzona ze wszystkich artykułów lub tylko dwóch (biorąc pod uwagę, że masz więcej niż 2) ?
Economist_Ayahuasca
2
@AndresAzqueta [0,1] to pozycje w macierzy dla podobieństwa, ponieważ dwa wprowadzania tekstu utworzą macierz symetryczną 2x2.
Philip Bergström
1
@Renaud, Dziękujemy za Twój pełny kod. Dla tych, którzy napotkali błąd z pytaniem o nltk.download (), możesz łatwo zrobić nltk.download ('punkt'). Nie musisz pobierać wszystkiego.
1man
@Renaud Nie mam bardziej podstawowego problemu. Które ciągi tekstu powinny fit, a które transform?
John Strood,
@JohnStrood Nie rozumiem twojego pytania, przepraszam, czy mógłbyś przeformułować?
Renaud
45

To stare pytanie, ale stwierdziłem, że można to łatwo zrobić za pomocą Spacy . Po odczytaniu dokumentu można użyć prostego interfejsu API, similarityaby znaleźć podobieństwo cosinus między wektorami dokumentów.

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
Koustuv Sinha
źródło
2
Zastanawiam się, dlaczego podobieństwo między doc1 i doc2 wynosi 0,999999954642, a nie 1,0
JordanBelf
4
@ Liczby zmiennoprzecinkowe JordanaBelfa wędrują trochę w większości języków - ponieważ nie mogą mieć nieograniczonej precyzji w reprezentacjach cyfrowych. np. operacje zmiennoprzecinkowe na liczbach niewymiernych lub generowanie liczb nieracjonalnych zawsze powodują niewielkie błędy zaokrąglania, w których następnie mnożą się. To wada tak elastycznej reprezentacji pod względem skali.
scipilot
2
jaką funkcję odległości stosuje ta metoda podobieństwa w tym przypadku?
ikel
Jeśli masz problemy ze znalezieniem „en”, uruchom następującą instalację pip pip spacy && python -m spacy download pl
Cybernetic
1
@Cybernetic Spójrz na Jak obliczana jest metoda .similarity w SpaCy
Walter,
17

Zasadniczo podobieństwo cosinus między dwoma dokumentami jest stosowane jako miara podobieństwa dokumentów. W Javie możesz do tego użyć Lucene (jeśli twoja kolekcja jest dość duża) lub LingPipe . Podstawową koncepcją byłoby policzenie terminów w każdym dokumencie i obliczanie iloczynu kropek wektorów terminów. Biblioteki zapewniają kilka ulepszeń w stosunku do tego ogólnego podejścia, np. Wykorzystują odwrotne częstotliwości dokumentów i obliczają wektory tf-idf. Jeśli chcesz zrobić coś copmlex, LingPipe zapewnia również metody obliczania podobieństwa LSA między dokumentami, co daje lepsze wyniki niż podobieństwo cosinus. W przypadku Pythona możesz użyć NLTK .

Pulkit Goyal
źródło
4
Zauważ, że nie ma „podobieństwa LSA”. LSA to metoda zmniejszania wymiarów przestrzeni wektorowej (w celu przyspieszenia rzeczy lub modelowania tematów, a nie terminów). Te same wskaźniki podobieństwa, które są używane z BOW i tf-idf, mogą być stosowane z LSA (podobieństwo cosinus, podobieństwo euklidesowe, BM25,…).
Witiko
16

Jeśli szukasz czegoś bardzo dokładnego, musisz użyć lepszego narzędzia niż tf-idf. Uniwersalny koder zdań jest jednym z najdokładniejszych w celu znalezienia podobieństwa między dowolnymi dwoma fragmentami tekstu. Google dostarczyło wstępnie przeszkolone modele, których można używać do własnej aplikacji bez potrzeby trenowania od zera. Najpierw musisz zainstalować tensorflow i tensorflow-hub:

    pip install tensorflow
    pip install tensorflow_hub

Poniższy kod pozwala przekonwertować dowolny tekst na reprezentację wektora o stałej długości, a następnie można użyć iloczynu kropkowego, aby znaleźć podobieństwo między nimi

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)

oraz kod do kreślenia:

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()

wynik byłby: macierz podobieństwa między parami tekstów

jak widać, największe podobieństwo występuje między tekstami ze sobą, a następnie z ich bliskimi tekstami w znaczeniu.

WAŻNE : przy pierwszym uruchomieniu kod będzie działał powoli, ponieważ musi pobrać model. jeśli chcesz zapobiec ponownemu pobieraniu modelu i korzystaniu z modelu lokalnego, musisz utworzyć folder pamięci podręcznej i dodać go do zmiennej środowiskowej, a następnie po pierwszym uruchomieniu użyć tej ścieżki:

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)

Więcej informacji: https://tfhub.dev/google/universal-sentence-encoder/2

Rohola Zandie
źródło
cześć dzięki za ten przykład zachęcający mnie do wypróbowania TF - skąd powinien pochodzić obiekt „np”?
Open Food Broker
1
UPD ok, zainstalowałem numpy, matplotlib, a także systemowe powiązanie TK Python dla fabuły i działa !!
Open Food Broker
1
Na wszelki wypadek (przepraszam za brak podziałów linii): import tensorflow jako tf import tensorflow_hub jako hub import matplotlib.pyplot jako plt import numpy jako np
dinnouti
5

Oto mała aplikacja na początek ...

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)
Ben
źródło
4
difflib działa bardzo wolno, jeśli zamierzasz pracować z dużą liczbą dokumentów.
Phyo Arkar Lwin,
2

Możesz wypróbować tę usługę online pod kątem podobieństwa dokumentów cosinus 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
Ekaterina Gorchinsky
źródło
czy interfejs API używa mechanizmu różnicowego sekwencyjnego? Jeśli tak, to prosta funkcja w pythonie wykonałaby zadanie ____________________________________ z importu difflib SequenceMatcher def isStringSimilar (a, b): ratio = SequenceMatcher (None, a, b) .ratio () return return ______________________________
Rudresh Ajgaonkar
2

Jeśli jesteś bardziej zainteresowany mierzeniem semantycznego podobieństwa dwóch fragmentów tekstu, proponuję rzucić okiem na ten projekt gitlab . Możesz uruchomić go jako serwer, istnieje również wstępnie zbudowany model, którego można łatwo użyć do pomiaru podobieństwa dwóch fragmentów tekstu; nawet jeśli jest on głównie przeszkolony do pomiaru podobieństwa dwóch zdań, nadal możesz go używać w twoim przypadku. Jest napisany w Javie, ale możesz go uruchomić jako usługę RESTful.

Inną opcją jest również podobieństwo DKPro, które jest biblioteką z różnymi algorytmami do pomiaru podobieństwa tekstów. Jednak jest również napisane w języku Java.

przykład kodu:

// 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);
Mohammad-Ali
źródło
2

Aby znaleźć podobieństwo zdań z bardzo mniejszym zestawem danych i uzyskać wysoką dokładność, możesz użyć poniższego pakietu python, który używa wstępnie przeszkolonych modeli BERT,

pip install similar-sentences
Shankar Ganesh Jayaraman
źródło
Właśnie tego próbowałem, ale daje to podobieństwo każdego zdania do jednego głównego, ale czy istnieje sposób, aby utworzyć wszystkie dane treningowe zdania.txt jako jedną klasę i uzyskać ocenę, ile pewności dopasowuje się do wszystkich przykładów ?
Guru Teja
1
tak, możesz spróbować .batch_predict (BatchFile, NumberOfPrediction), który da wynik w postaci Results.xls z kolumnami [„Zdanie”, „Sugestia”, „Wynik”]
Shankar Ganesh Jayaraman
1

Podobieństwo składniowe Istnieją 3 proste sposoby wykrywania podobieństwa.

  • Word2Vec
  • Rękawica
  • Tfidf lub Countvectorizer

W przypadku podobieństwa semantycznego Można użyć osadzania BERT i wypróbować inne strategie łączenia słów, aby uzyskać osadzanie dokumentów, a następnie zastosować podobieństwo cosinus do osadzania dokumentów.

Zaawansowana metodologia może wykorzystywać BERT SCORE, aby uzyskać podobieństwo. WYNIK BERT

Link do artykułu naukowego: https://arxiv.org/abs/1904.09675

shaurya uppal
źródło