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

Szukam pracy nad projektem NLP, w dowolnym języku programowania (choć Python będzie moim ulubionym).

Chcę wziąć dwa dokumenty i określić, jak są podobne.

 226
nlp
Author: Trilarion, 2012-01-17

11 answers

Powszechnym sposobem jest przekształcenie dokumentów w wektory TF-IDF, a następnie obliczenie podobieństwa cosinusów między nimi. Każdy podręcznik wyszukiwania informacji (IR) obejmuje to. Zob. esp. Wprowadzenie do wyszukiwania informacji, który jest darmowy i dostępny online.

Computing Pairwise Podobieństwa

TF-IDF (i podobne przekształcenia tekstu) są zaimplementowane w pakietach Pythona Gensim i scikit-learn . W ten ostatni pakiet, obliczanie podobieństw cosinusowych jest tak proste, 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 znaków,

>>> 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 też to pytanie .

[Zastrzeżenie: brałem udział w implementacji scikit-learn TF-IDF.]

Interpretacja wyników

Z góry pairwise_similarity jest macierzą scytyjską o kwadratowym kształcie, z liczbą rzędów i kolumny równe 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ć tablicę sparse 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 w corpus. Indeks najbardziej podobnego dokumentu można znaleźć po biorąc argmax tego wiersza, ale najpierw musisz zamaskować jedynki, które reprezentują podobieństwo każdego z nich dokument do siebie . Możesz zrobić to drugie przez 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 (znacznej ilości miejsca) na duży korpus i słownictwo. Zamiast konwertować do tablicy NumPy, można zrobić:

>>> n, _ = pairwise_similarity.shape                                                                                                                                                                                                                         
>>> pairwise_similarity[np.arange(n), np.arange(n)] = -1.0
>>> pairwise_similarity[input_idx].argmax()                                                                                                                                                                                                                  
3
 325
Author: Fred Foo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-08-02 15:37:56

Identyczny jak @ larsman, ale z pewnym preprocesem

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')
 93
Author: Renaud,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-19 06:48:48

To stare pytanie, ale stwierdziłem, że można to łatwo zrobić za pomocą Spacy . Po odczytaniu dokumentu można użyć prostego api similarity do znalezienia cosinusowego podobieństwa między wektorami dokumentu.

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
 51
Author: Koustuv Sinha,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-09-10 15:36:08

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

    pip install tensorflow
    pip install tensorflow_hub

Poniższy kod umożliwia konwersję dowolnego tekstu na wektor o stałej długości reprezentacji, a następnie można użyć produktu dot, aby dowiedzieć się podobieństwa 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 wykreś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 jest między tekstami ze sobą, a następnie z ich bliskimi tekstami w znaczeniu.

Ważne: przy pierwszym uruchomieniu kodu będzie on powolny, ponieważ musi pobrać model. jeśli chcesz zapobiec ponownemu pobraniu modelu i użyj modelu lokalnego musisz utworzyć folder dla pamięci podręcznej i dodać go do zmiennej środowiskowej, a następnie po pierwszym uruchomieniu użyj 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

 22
Author: Rohola Zandie,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-02-11 02:12:36

Zasadniczo cosinus podobieństwa między dwoma dokumentami jest używany jako miara podobieństwa dokumentów. W Javie możesz użyć Lucene (jeśli Twoja kolekcja jest dość duża) lub LingPipe. Podstawowym pojęciem byłoby policzenie terminów w każdym dokumencie i obliczenie iloczynu punktowego wektorów terminów. Biblioteki zapewniają kilka ulepszeń w stosunku do tego ogólnego podejścia, np. wykorzystanie częstotliwości odwrotnych dokumentów i obliczanie wektorów TF-idf. Jeśli szukasz zrób coś copmlex, LingPipe zapewnia również metody obliczania podobieństwa LSA między dokumentami, co daje lepsze wyniki niż podobieństwo cosinus. W Pythonie możesz użyć NLTK .

 17
Author: Pulkit Goyal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-17 15:59:17

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)
 5
Author: Ben,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-06-30 22:17:49

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

pip install similar-sentences
 3
Author: Shankar Ganesh Jayaraman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-05-13 06:14:59

Możesz wypróbować tę usługę online dla cosinusowego podobieństwa dokumentów 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
 2
Author: Ekaterina Gorchinsky,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-02-12 12:03:16

Jeśli jesteś bardziej zainteresowany pomiarem 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żesz ł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 swoim case.It jest napisany w Javie, ale można go uruchomić jako usługę RESTful.

Inną opcją jest również Dkpro podobieństwo która jest biblioteką z różnych algorytmów do pomiaru podobieństwa tekstów. Jednak jest również napisany w Javie.

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);
 2
Author: Mohammad-Ali,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-09-10 22:10:31

Dla Podobieństwa Składniowego Istnieją 3 proste sposoby wykrywania podobieństwa.

  • Word2Vec
  • Rękawica
  • Tfidf lub countvectorizer

Dla Podobieństwa Semantycznego Można użyć Bert osadzanie i spróbować różnych strategii łączenia słów, aby uzyskać osadzanie dokumentu, a następnie zastosować cosinus podobieństwo na osadzanie dokumentu.

Zaawansowana metodologia może wykorzystać wynik BERT, aby uzyskać podobieństwo. BERT SCORE

Referat Badawczy Link: https://arxiv.org/abs/1904.09675

 2
Author: shaurya uppal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-11-14 10:28:10

Łączę rozwiązania z odpowiedzi @FredFoo i @Renaud. Moje rozwiązanie jest w stanie zastosować wstępne przetwarzanie @Renaud na korpusie tekstowym @FredFoo, a następnie wyświetlić podobieństwa parami, gdzie podobieństwo jest większe niż 0. Uruchomiłem ten kod w systemie Windows, instalując python i pip najpierw. pip jest instalowany jako część Pythona, ale być może będziesz musiał zrobić to jawnie, ponownie uruchamiając pakiet instalacyjny, wybierając modify, a następnie wybierając pip. Używam wiersza poleceń do wykonania mojego Pythona kod zapisany w pliku "similarity.py". musiałem wykonać następujące polecenia:

>set PYTHONPATH=%PYTHONPATH%;C:\_location_of_python_lib_
>python -m pip install sklearn
>python -m pip install nltk
>py similarity.py

Kod dla similarity.py jest następująca:

from sklearn.feature_extraction.text import TfidfVectorizer
import nltk, string
import numpy as np
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]

def normalize(text):
    return stem_tokens(nltk.word_tokenize(text.lower().translate(remove_punctuation_map)))

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(tokenizer=normalize, stop_words='english')
tfidf = vect.fit_transform(corpus)   
                                                                                                                                                                                                                    
pairwise_similarity = tfidf * tfidf.T

#view the pairwise similarities 
print(pairwise_similarity)

#check how a string is normalized
print(normalize("The scikit-learn docs are Orange and Blue"))
 0
Author: Shamsa,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2021-01-21 10:58:36