Bagaimana cara menghitung kesamaan antara dua dokumen teks?

207

Saya melihat bekerja pada proyek NLP, dalam bahasa pemrograman apa pun (meskipun Python akan menjadi pilihan saya).

Saya ingin mengambil dua dokumen dan menentukan seberapa miripnya.

Reily Bourne
sumber
1
Pertanyaan serupa di sini stackoverflow.com/questions/101569/... menyihir beberapa jawaban yang bagus

Jawaban:

292

Cara umum untuk melakukan ini adalah mengubah dokumen menjadi vektor TF-IDF dan kemudian menghitung kesamaan cosinus di antara mereka. Buku teks tentang pencarian informasi (IR) apa pun mencakup ini. Lihat esp. Pengantar Pengambilan Informasi , yang gratis dan tersedia secara online.

Menghitung Kesamaan Berpasangan

TF-IDF (dan transformasi teks serupa) diimplementasikan dalam paket Python Gensim dan scikit-learn . Dalam paket terakhir, menghitung persamaan cosinus semudah

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

atau, jika dokumennya berupa string biasa,

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

meskipun Gensim mungkin memiliki lebih banyak opsi untuk tugas semacam ini.

Lihat juga pertanyaan ini .

[Penafian: Saya terlibat dalam implementasi scikit-learn TF-IDF.]

Menafsirkan Hasil

Dari atas, pairwise_similarityadalah matriks jarang Scipy yang berbentuk persegi, dengan jumlah baris dan kolom sama dengan jumlah dokumen dalam korpus.

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

Anda bisa mengonversi array jarang ke array NumPy melalui .toarray()atau .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.        ]])

Katakanlah kita ingin menemukan dokumen yang paling mirip dengan dokumen terakhir, "Dokumen scikit-belajar adalah Oranye dan Biru". Dokumen ini memiliki indeks 4 in corpus. Anda dapat menemukan indeks dokumen yang paling mirip dengan mengambil argmax dari baris itu, tetapi pertama-tama Anda harus menutupi 1, yang mewakili kesamaan dari setiap dokumen dengan dirinya sendiri . Anda dapat melakukan yang terakhir melalui np.fill_diagonal(), dan yang pertama melalui 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'

Catatan: tujuan menggunakan matriks jarang adalah untuk menghemat (jumlah ruang yang besar) untuk korpus besar & kosakata. Alih-alih mengonversi ke array NumPy, Anda bisa melakukan:

>>> n, _ = pairwise_similarity.shape                                                                                                                                                                                                                         
>>> pairwise_similarity[np.arange(n), np.arange(n)] = -1.0
>>> pairwise_similarity[input_idx].argmax()                                                                                                                                                                                                                  
3
Fred Foo
sumber
1
@ Larsmans Bisakah Anda menjelaskan array sedikit jika mungkin, bagaimana saya harus membaca array ini. Dua kolom pertama adalah kesamaan antara dua kalimat pertama?
tambahkan-semi-titik dua
1
@ Null-Hipotesis: pada posisi (i, j), Anda menemukan skor kesamaan antara dokumen i dan dokumen j. Jadi, pada posisi (0,2) adalah nilai kesamaan antara dokumen pertama dan ketiga (menggunakan pengindeksan berbasis nol), yang merupakan nilai yang sama dengan yang Anda temukan di (2,0), karena kesamaan cosinus adalah komutatif.
Fred Foo
1
Jika saya rata-rata semua nilai di luar diagonal 1's, apakah itu cara yang baik untuk mendapatkan skor tunggal seberapa mirip keempat dokumen satu sama lain? Jika tidak, apakah ada cara yang lebih baik untuk menentukan kesamaan keseluruhan antara banyak dokumen?
user301752
2
@ user301752: Anda bisa mengambil mean elemen-bijaksana dari vektor tf-idf (seperti k-means akan lakukan) dengan X.mean(axis=0), kemudian menghitung rata-rata / maksimum / median (∗) jarak Euclidean dari rata-rata itu. (∗) Pilih yang mana yang Anda sukai.
Fred Foo
1
@Curious: Saya memperbarui kode contoh ke API scikit-learn saat ini; Anda mungkin ingin mencoba kode baru.
Fred Foo
87

Identik dengan @ larsman, tetapi dengan beberapa preprocessing

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
sumber
@ Renaud, jawaban yang sangat bagus dan jelas! Saya memiliki dua keraguan: I) berapa [0,1] yang Anda masukkan setelah tfidf * tfidf.T) dan II) Frekuensi dokumen terbalik dibentuk dari semua artikel atau hanya dua (mengingat Anda memiliki lebih dari 2) ?
Economist_Ayahuasca
2
@AndresAzqueta [0,1] adalah posisi dalam matriks untuk kesamaan karena dua input teks akan membuat matriks simetris 2x2.
Philip Bergström
1
@Renaud, Terima kasih atas kode lengkap Anda. Bagi mereka yang mengalami kesalahan meminta nltk.download (), Anda dapat dengan mudah melakukan nltk.download ('punkt'). Anda tidak perlu mengunduh semuanya.
pria
@Renaud Saya tidak mendapatkan masalah yang lebih mendasar. Teks mana yang harus fit, dan yang mana transform?
John Strood
@ JohnStrood Saya tidak mengerti pertanyaan Anda, maaf bisakah Anda merumuskan kembali?
Renaud
45

Ini pertanyaan lama, tetapi saya menemukan ini dapat dilakukan dengan mudah dengan Spacy . Setelah dokumen dibaca, api sederhana similaritydapat digunakan untuk menemukan kesamaan cosinus antara vektor dokumen.

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
sumber
2
Saya bertanya-tanya mengapa kesamaan antara doc1 dan doc2 adalah 0.999999954642 dan bukan 1.0
JordanBelf
4
@JordanBelf angka floating point memang berkeliaran sedikit di sebagian besar bahasa - karena mereka tidak dapat memiliki presisi tak terbatas dalam representasi digital. misalnya operasi floating point atau menghasilkan bilangan irasional selalu memiliki kesalahan pembulatan kecil yang merambat di mana kemudian berkembang biak. Ini adalah kelemahan dari representasi yang fleksibel dalam skala.
scipilot
2
apa fungsi jarak yang menggunakan metode kesamaan dalam kasus ini?
ikel
Jika Anda mengalami masalah dalam menemukan "en" jalankan pip install spacy && python -m spacy unduh en
Cybernetic
1
@Cybernetic Lihatlah bagaimana metode .similaritas di SpaCy dihitung
Walter
17

Umumnya kesamaan cosinus antara dua dokumen digunakan sebagai ukuran kesamaan dokumen. Di Jawa, Anda dapat menggunakan Lucene (jika koleksi Anda cukup besar) atau LingPipe untuk melakukan ini. Konsep dasar akan menghitung istilah dalam setiap dokumen dan menghitung titik produk dari vektor istilah. Perpustakaan memang memberikan beberapa perbaikan atas pendekatan umum ini, misalnya menggunakan frekuensi dokumen terbalik dan menghitung vektor tf-idf. Jika Anda ingin melakukan sesuatu copmlex, LingPipe juga menyediakan metode untuk menghitung kesamaan LSA antara dokumen yang memberikan hasil yang lebih baik daripada cosine similarity. Untuk Python, Anda bisa menggunakan NLTK .

Pulkit Goyal
sumber
4
Perhatikan bahwa tidak ada "kesamaan LSA". LSA adalah metode untuk mengurangi dimensi ruang vektor (baik untuk mempercepat atau memodelkan topik daripada istilah). Metrik kesamaan yang sama yang digunakan dengan BOW dan tf-idf dapat digunakan dengan LSA (cosinus similarity, euclidean similarity, BM25, ...).
Witiko
16

Jika Anda mencari sesuatu yang sangat akurat, Anda perlu menggunakan beberapa alat yang lebih baik daripada tf-idf. Encoder kalimat universal adalah salah satu yang paling akurat untuk menemukan kesamaan antara dua bagian teks. Google menyediakan model pra-pelatihan yang dapat Anda gunakan untuk aplikasi Anda sendiri tanpa perlu melatih apa pun. Pertama, Anda harus menginstal tensorflow dan tensorflow-hub:

    pip install tensorflow
    pip install tensorflow_hub

Kode di bawah ini memungkinkan Anda mengonversi teks apa pun menjadi representasi vektor dengan panjang tetap dan kemudian Anda dapat menggunakan produk titik untuk mengetahui kesamaan di antara mereka

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)

dan kode untuk merencanakan:

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

hasilnya adalah: matriks kesamaan antara pasangan teks

seperti yang Anda lihat, kesamaan yang paling mirip adalah antara teks dengan diri mereka sendiri dan kemudian dengan teks dekat dalam makna.

PENTING : pertama kali Anda menjalankan kode itu akan lambat karena perlu mengunduh model. jika Anda ingin mencegahnya mengunduh model lagi dan menggunakan model lokal Anda harus membuat folder untuk cache dan menambahkannya ke variabel lingkungan dan kemudian setelah pertama kali berjalan gunakan jalur itu:

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)

Informasi lebih lanjut: https://tfhub.dev/google/universal-sentence-encoder/2

Rohola Zandie
sumber
hai terima kasih untuk contoh ini yang mendorong saya untuk mencoba TF - dari mana benda "np" berasal?
Open Food Broker
1
UPD ok, saya telah menginstal numpy, matplotlib dan juga sistem TK Python yang mengikat untuk plot dan berfungsi !!
Open Food Broker
1
Untuk jaga-jaga (maaf karena tidak ada jeda baris): impor tensorflow sebagai tf impor tensorflow_hub sebagai hub impor matplotlib.pyplot sebagai plt impor numpy as np
dinnouti
5

Berikut ini sedikit aplikasi untuk membantu Anda memulai ...

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
sumber
4
difflib sangat lambat jika Anda akan bekerja dengan sejumlah besar dokumen.
Phyo Arkar Lwin
2

Anda mungkin ingin mencoba layanan online ini untuk kesamaan dokumen 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
sumber
itu Api menggunakan Pencocokan berurutan Diferensial? Jika ya, maka fungsi sederhana dalam python akan melakukan pekerjaan ____________________________________ dari diff impor SequenceMatcher def isStringSimilar (a, b): rasio = SequenceMatcher (Tidak, a, b) .rasio () rasio pengembalian ______________________________
Rudresh Ajgaonkar
2

Jika Anda lebih tertarik mengukur kesamaan semantik dari dua bagian teks, saya sarankan lihat proyek gitlab ini . Anda dapat menjalankannya sebagai server, ada juga model pra-dibangun yang dapat Anda gunakan dengan mudah untuk mengukur kesamaan dua potong teks; meskipun sebagian besar dilatih untuk mengukur kemiripan dua kalimat, Anda masih dapat menggunakannya dalam kasus Anda. Ini ditulis dalam java tetapi Anda dapat menjalankannya sebagai layanan RESTful.

Pilihan lain juga adalah DKPro Similarity yang merupakan perpustakaan dengan berbagai algoritma untuk mengukur kesamaan teks. Namun, ini juga ditulis dalam java.

contoh kode:

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

Untuk menemukan kesamaan kalimat dengan dataset yang sangat sedikit dan untuk mendapatkan akurasi tinggi Anda dapat menggunakan paket python di bawah ini yang menggunakan model BERT yang telah dilatih sebelumnya,

pip install similar-sentences
Shankar Ganesh Jayaraman
sumber
Saya baru saja mencobanya, tetapi memberikan kemiripan dari setiap kalimat dengan yang utama, tetapi apakah ada cara untuk membuat semua data pelatihan kalimat.txt sebagai satu kelas dan mendapatkan skor pada seberapa banyak kepercayaan itu cocok dengan semua contoh ?
Guru Teja
1
ya Anda bisa, coba .batch_predict (BatchFile, NumberOfPrediction) yang akan memberikan hasil sebagai Results.xls dengan Kolom ['Kalimat', 'Saran', 'Skor']
Shankar Ganesh Jayaraman
1

Untuk Kesamaan Sintaksis Ada 3 cara mudah untuk mendeteksi kesamaan.

  • Word2Vec
  • Sarung tangan
  • Tfidf atau countvectorizer

Untuk Semantic Similarity One dapat menggunakan BERT Embedding dan mencoba strategi kumpulan kata yang berbeda untuk mendapatkan penyematan dokumen dan kemudian menerapkan kesamaan cosinus pada penyematan dokumen.

Metodologi canggih dapat menggunakan BERT SCORE untuk mendapatkan kesamaan. SKOR BERT

Tautan Makalah Penelitian: https://arxiv.org/abs/1904.09675

shaurya uppal
sumber