Wie berechnet man die Ähnlichkeit zwischen zwei Textdokumenten?


207

Ich möchte an einem NLP-Projekt in einer beliebigen Programmiersprache arbeiten (obwohl Python meine Präferenz sein wird).

Ich möchte zwei Dokumente nehmen und feststellen, wie ähnlich sie sind.


1
Ähnliche Frage hier stackoverflow.com/questions/101569/… mit einigen netten Antworten

Antworten:


292

Die übliche Methode hierfür besteht darin, die Dokumente in TF-IDF-Vektoren umzuwandeln und dann die Kosinusähnlichkeit zwischen ihnen zu berechnen. Jedes Lehrbuch zum Thema Information Retrieval (IR) behandelt dies. Siehe esp. Einführung in Information Retrieval , das kostenlos und online verfügbar ist.

Paarweise Ähnlichkeiten berechnen

TF-IDF (und ähnliche Texttransformationen ) sind in den Python-Paketen Gensim und scikit-learn implementiert . Im letzteren Paket ist die Berechnung von Kosinusähnlichkeiten so einfach wie

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

oder, wenn die Dokumente einfache Zeichenfolgen sind,

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

obwohl Gensim möglicherweise mehr Optionen für diese Art von Aufgabe hat.

Siehe auch diese Frage .

[Haftungsausschluss: Ich war an der Implementierung von Scikit-Learn TF-IDF beteiligt.]

Interpretation der Ergebnisse

Von oben pairwise_similarityist eine quadratische Scipy- Matrix mit einer quadratischen Form zu sehen, bei der die Anzahl der Zeilen und Spalten der Anzahl der Dokumente im Korpus entspricht.

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

Sie können das Sparse-Array über .toarray()oder in ein NumPy-Array konvertieren .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.        ]])

Angenommen, wir möchten das Dokument finden, das dem endgültigen Dokument am ähnlichsten ist: "Die Scikit-Lerndokumente sind Orange und Blau". Dieses Dokument hat Index 4 in corpus. Sie können den Index des ähnlichsten Dokuments finden, indem Sie den Argmax dieser Zeile verwenden. Zuerst müssen Sie jedoch die Einsen maskieren, die die Ähnlichkeit jedes Dokuments mit sich selbst darstellen . Sie können das letztere durch np.fill_diagonal()und das erstere durch tun 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'

Hinweis: Der Zweck der Verwendung einer spärlichen Matrix besteht darin, (viel Platz) für ein großes Korpus und Vokabular zu sparen. Anstatt in ein NumPy-Array zu konvertieren, können Sie Folgendes tun:

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

1
@larsmans Kannst du das Array ein wenig erklären, wenn möglich, wie soll ich dieses Array lesen? Die ersten beiden Spalten sind Ähnlichkeiten zwischen den ersten beiden Sätzen?
Add-Semikolons

1
@ Null-Hypothese: An Position (i, j) finden Sie die Ähnlichkeitsbewertung zwischen Dokument i und Dokument j. An Position (0,2) befindet sich also der Ähnlichkeitswert zwischen dem ersten und dem dritten Dokument (unter Verwendung der nullbasierten Indizierung). Dies ist derselbe Wert, den Sie bei (2,0) finden, da die Kosinusähnlichkeit kommutativ ist.
Fred Foo

1
Wenn ich alle Werte außerhalb der Diagonale von 1 mitteln würde, wäre das eine gute Möglichkeit, eine einzige Bewertung zu erhalten, wie ähnlich die vier Dokumente einander sind? Wenn nicht, gibt es eine bessere Möglichkeit, die allgemeine Ähnlichkeit zwischen mehreren Dokumenten zu bestimmen?
user301752

2
@ user301752: Sie könnten den elementweisen Mittelwert der tf-idf-Vektoren (wie es k-means tun würde) mit nehmen X.mean(axis=0)und dann den euklidischen Durchschnitt / Maximum / Median (∗) Abstand von diesem Mittelwert berechnen. (∗) Wählen Sie, was Ihnen gefällt.
Fred Foo

1
@curious: Ich habe den Beispielcode auf die aktuelle Scikit-Learn-API aktualisiert. Vielleicht möchten Sie den neuen Code ausprobieren.
Fred Foo

87

Identisch mit @larsman, jedoch mit Vorverarbeitung

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, wirklich gute und klare Antwort! Ich habe zwei Zweifel: I) Was ist die [0,1], die Sie nach tfidf * tfidf.T) und II) einfügen? Die inverse Dokumenthäufigkeit wird aus allen oder nur zwei Artikeln gebildet (wenn man bedenkt, dass Sie mehr als 2 haben). ?
Economist_Ayahuasca

2
@AndresAzqueta [0,1] ist die Position in der Matrix für die Ähnlichkeit, da durch zwei Texteingaben eine symmetrische 2x2-Matrix erstellt wird.
Philip Bergström

1
@Renaud, vielen Dank für Ihren vollständigen Code. Für diejenigen, die auf den Fehler gestoßen sind, nach nltk.download () zu fragen, können Sie einfach nltk.download ('punkt') ausführen. Sie müssen nicht alles herunterladen.
1man

@Renaud Ich bekomme kein grundlegenderes Problem. Welche Textzeichenfolgen sollten fitund welche transform?
John Strood

@ JohnStrood Ich verstehe deine Frage nicht. Entschuldigung, könntest du sie neu formulieren?
Renaud

45

Es ist eine alte Frage, aber ich fand, dass dies mit Spacy leicht gemacht werden kann . Sobald das Dokument gelesen wurde, kann eine einfache API similarityverwendet werden, um die Kosinusähnlichkeit zwischen den Dokumentvektoren zu ermitteln.

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
Ich frage mich, warum die Ähnlichkeit zwischen doc1 und doc2 0,999999954642 und nicht 1,0 ist
JordanBelf

4
@ JordanBelf-Gleitkommazahlen wandern in den meisten Sprachen ein wenig herum - da sie in digitalen Darstellungen keine unbegrenzte Genauigkeit aufweisen können. Beispielsweise haben Gleitkommaoperationen auf oder das Erzeugen irrationaler Zahlen immer winzige Rundungsfehler, die sich dann multiplizieren. Es ist der Nachteil einer solchen flexiblen Darstellung in Bezug auf den Maßstab.
Scipilot

2
Was ist die Distanzfunktion, die diese Ähnlichkeitsmethode in diesem Fall verwendet?
Ikel

Wenn Sie Probleme haben, "en" zu finden, führen Sie die folgende Pip-Installation aus: spacy && python -m spacy download en
Cybernetic

1
@Cybernetic Schauen Sie sich an, wie die .similarity-Methode in SpaCy berechnet wird
Walter

17

Im Allgemeinen wird eine Kosinusähnlichkeit zwischen zwei Dokumenten als Ähnlichkeitsmaß für Dokumente verwendet. In Java können Sie dazu Lucene (wenn Ihre Sammlung ziemlich groß ist) oder LingPipe verwenden. Das Grundkonzept wäre, die Terme in jedem Dokument zu zählen und das Punktprodukt der Termvektoren zu berechnen. Die Bibliotheken bieten gegenüber diesem allgemeinen Ansatz verschiedene Verbesserungen, z. B. die Verwendung inverser Dokumenthäufigkeiten und die Berechnung von tf-idf-Vektoren. Wenn Sie etwas Copmlexes tun möchten, bietet LingPipe auch Methoden zur Berechnung der LSA-Ähnlichkeit zwischen Dokumenten, die bessere Ergebnisse als die Cosinus-Ähnlichkeit liefern. Für Python können Sie NLTK verwenden .


4
Beachten Sie, dass es keine "LSA-Ähnlichkeit" gibt. LSA ist eine Methode, um die Dimensionalität eines Vektorraums zu reduzieren (entweder um Dinge zu beschleunigen oder um Themen anstelle von Begriffen zu modellieren). Dieselben Ähnlichkeitsmetriken, die mit BOW und tf-idf verwendet werden, können mit LSA verwendet werden (Kosinusähnlichkeit, euklidische Ähnlichkeit, BM25,…).
Witiko

16

Wenn Sie nach etwas sehr Genauem suchen, müssen Sie ein besseres Werkzeug als tf-idf verwenden. Der universelle Satzcodierer ist einer der genauesten, um die Ähnlichkeit zwischen zwei beliebigen Textteilen zu ermitteln. Google hat vorgefertigte Modelle bereitgestellt, die Sie für Ihre eigene Anwendung verwenden können, ohne dass Sie von Grund auf neu trainieren müssen. Zuerst müssen Sie Tensorflow und Tensorflow-Hub installieren:

    pip install tensorflow
    pip install tensorflow_hub

Mit dem folgenden Code können Sie jeden Text in eine Vektordarstellung fester Länge konvertieren und dann das Punktprodukt verwenden, um die Ähnlichkeit zwischen ihnen herauszufinden

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)

und der Code zum Plotten:

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

das Ergebnis wäre: die Ähnlichkeitsmatrix zwischen Textpaaren

Wie Sie sehen können, besteht die größte Ähnlichkeit zwischen Texten mit sich selbst und dann mit ihren engen Texten in der Bedeutung.

WICHTIG : Wenn Sie den Code zum ersten Mal ausführen, ist er langsam, da das Modell heruntergeladen werden muss. Wenn Sie verhindern möchten, dass das Modell erneut heruntergeladen wird und das lokale Modell verwendet wird, müssen Sie einen Ordner für den Cache erstellen und zur Umgebungsvariablen hinzufügen. Verwenden Sie dann nach dem ersten Ausführen diesen Pfad:

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)

Weitere Informationen: https://tfhub.dev/google/universal-sentence-encoder/2


Hallo, danke für dieses Beispiel, das mich ermutigt, TF auszuprobieren - woher soll das Objekt "np" kommen?
Open Food Broker

1
UPD ok, ich habe numpy, matplotlib und auch System TK Python Binding für den Plot installiert und es funktioniert !!
Open Food Broker

1
Nur für den Fall (Entschuldigung für das Fehlen von Zeilenumbrüchen): Tensorflow als tf importieren tensorflow_hub als Hub importieren matplotlib.pyplot als plt import numpy als np
dinnouti

5

Hier ist eine kleine App, mit der Sie loslegen können ...

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 ist sehr langsam, wenn Sie mit einer großen Anzahl von Dokumenten arbeiten.
Phyo Arkar Lwin

2

Möglicherweise möchten Sie diesen Onlinedienst für die Ähnlichkeit von Kosinusdokumenten http://www.scurtu.it/documentSimilarity.html ausprobieren

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

Verwendet die API Differential Sequential Matcher? Wenn ja, dann würde eine einfache Funktion in Python die Aufgabe übernehmen ____________________________________ aus dem Difflib-Import SequenceMatcher def isStringSimilar (a, b): ratio = SequenceMatcher (Keine, a, b) .ratio () Rückgabeverhältnis ______________________________
Rudresh Ajgaonkar

2

Wenn Sie mehr daran interessiert sind, die semantische Ähnlichkeit zweier Textteile zu messen, empfehlen wir Ihnen, sich dieses Gitlab-Projekt anzusehen . Sie können es als Server ausführen. Es gibt auch ein vorgefertigtes Modell, mit dem Sie die Ähnlichkeit zweier Textteile leicht messen können. Obwohl es hauptsächlich zum Messen der Ähnlichkeit von zwei Sätzen trainiert wurde, können Sie es in Ihrem Fall dennoch verwenden. Es ist in Java geschrieben, aber Sie können es als RESTful-Dienst ausführen.

Eine weitere Option ist DKPro Similarity , eine Bibliothek mit verschiedenen Algorithmen zur Messung der Ähnlichkeit von Texten. Es ist jedoch auch in Java geschrieben.

Codebeispiel:

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

Um Satzähnlichkeit mit sehr wenig Datensatz zu finden und eine hohe Genauigkeit zu erzielen, können Sie das folgende Python-Paket verwenden, das vorab trainierte BERT-Modelle verwendet.

pip install similar-sentences

Ich habe das gerade versucht, aber es gibt Ähnlichkeit mit jedem Satz mit dem einen Hauptsatz, aber es gibt eine Möglichkeit, alle Trainingsdaten von satz.txt als eine Klasse zu erstellen und eine Bewertung darüber zu erhalten, wie viel Vertrauen es mit allen Beispielen übereinstimmt ?
Guru Teja

1
Ja, Sie können .batch_predict (BatchFile, NumberOfPrediction) ausprobieren, das als Results.xls mit Spalten ['Satz', 'Vorschlag', 'Punktzahl'] ausgegeben wird
Shankar Ganesh Jayaraman

1

Für syntaktische Ähnlichkeit Es gibt drei einfache Möglichkeiten, Ähnlichkeit zu erkennen.

  • Word2Vec
  • Handschuh
  • Tfidf oder Countvectorizer

Für semantische Ähnlichkeit kann man die BERT-Einbettung verwenden und eine andere Wortpooling-Strategie ausprobieren, um die Dokumenteinbettung zu erhalten, und dann die Kosinusähnlichkeit auf die Dokumenteinbettung anwenden.

Eine erweiterte Methodik kann BERT SCORE verwenden, um Ähnlichkeit zu erzielen. BERT SCORE

Link zum Forschungspapier: https://arxiv.org/abs/1904.09675

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.