วิธีคำนวณความคล้ายคลึงกันระหว่างเอกสารข้อความสองฉบับ?


207

ฉันกำลังมองหาที่ทำงานเกี่ยวกับโครงการ NLP ในภาษาการเขียนโปรแกรมใด ๆ (แม้ว่า Python จะเป็นความชอบของฉัน)

ฉันต้องการเอกสารสองฉบับและกำหนดว่าเอกสารเหล่านี้คล้ายกันมากแค่ไหน


1
คำถามที่คล้ายกันที่นี่stackoverflow.com/questions/101569/…แม่มดบางคำตอบที่ดี

คำตอบ:


292

วิธีทั่วไปในการทำเช่นนี้คือการแปลงเอกสารให้เป็นเวกเตอร์ TF-IDF และคำนวณความคล้ายคลึงกันของโคไซน์ระหว่างพวกเขา หนังสือเรียนใด ๆ เกี่ยวกับการดึงข้อมูล (IR) ครอบคลุมสิ่งนี้ ดูทาย การค้นคืนสารสนเทศเบื้องต้นซึ่งเป็นแบบออนไลน์และให้บริการฟรี

การคำนวณความคล้ายคลึงกันแบบคู่

TF-IDF (และการแปลงข้อความที่คล้ายกัน) จะดำเนินการในหลามแพคเกจGensimและscikit เรียนรู้ ในแพ็คเกจหลังการคำนวณความเหมือนโคไซน์เป็นเรื่องง่ายเหมือน

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

หรือถ้าเอกสารเป็นสตริงธรรมดา

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

แม้ว่า Gensim อาจมีตัวเลือกเพิ่มเติมสำหรับงานประเภทนี้

ดูคำถามนี้ด้วย

[ข้อจำกัดความรับผิดชอบ: ฉันมีส่วนร่วมในการใช้ TF-IDF แบบ Scikit-Learn]

การตีความผลลัพธ์

จากด้านบนpairwise_similarityเป็นเมทริกซ์กระจัดกระจาย Scipy ที่มีรูปร่างเป็นสี่เหลี่ยมโดยมีจำนวนแถวและคอลัมน์เท่ากับจำนวนเอกสารในคลังข้อมูล

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

คุณสามารถแปลง array sparse เป็น array NumPy ผ่าน.toarray()หรือ.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.        ]])

สมมติว่าเราต้องการค้นหาเอกสารที่คล้ายกับเอกสารสุดท้าย "เอกสารการเรียนรู้ scikit คือส้มและน้ำเงิน" เอกสารนี้มีดัชนี 4 corpusใน คุณสามารถค้นหาดัชนีของเอกสารที่คล้ายกันมากที่สุดโดยการ argmax ของแถวนั้น แต่แรกที่คุณจะต้องหน้ากาก 1 ซึ่งเป็นตัวแทนของความคล้ายคลึงกันของเอกสารแต่ละกับตัวเอง คุณสามารถทำสิ่งหลังผ่านnp.fill_diagonal()และอดีตผ่าน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'

หมายเหตุ: วัตถุประสงค์ของการใช้เมทริกซ์แบบกระจัดกระจายคือการบันทึก (พื้นที่จำนวนมาก) สำหรับคลังข้อมูลขนาดใหญ่และคำศัพท์ แทนที่จะแปลงเป็นอาร์เรย์ NumPy คุณสามารถทำได้:

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

1
@ larsmans คุณช่วยอธิบายเกี่ยวกับอาเรย์หน่อยได้ไหมฉันควรจะอ่านอาเรย์นี้ได้อย่างไร สองคอลัมน์แรกมีความคล้ายคลึงกันระหว่างสองประโยคแรก?
เพิ่ม semi-colons

1
@ Null-Hypothesis: ที่ตำแหน่ง (i, j) คุณจะพบคะแนนความคล้ายคลึงกันระหว่างเอกสาร i และเอกสาร j ดังนั้นที่ตำแหน่ง (0,2) คือค่าความคล้ายคลึงกันระหว่างเอกสารแรกและฉบับที่สาม (ใช้การทำดัชนีแบบ zero-based) ซึ่งเป็นค่าเดียวกับที่คุณพบที่ (2,0) เพราะความคล้ายคลึงโคไซน์เป็นคอมมิเตต
Fred Foo

1
ถ้าฉันเฉลี่ยค่าทั้งหมดนอกเส้นทแยงมุมของ 1 นั่นจะเป็นวิธีที่ดีในการรับคะแนนเดียวของเอกสารสี่ฉบับที่มีความคล้ายคลึงกันหรือไม่? ถ้าไม่มีวิธีที่ดีกว่าในการพิจารณาความคล้ายคลึงโดยรวมระหว่างเอกสารหลาย ๆ
user301752

2
@ user301752: คุณสามารถใช้ค่าเฉลี่ยองค์ประกอบที่ชาญฉลาดของเวกเตอร์ tf-idf (เช่น k- หมายถึงจะทำ) ด้วยX.mean(axis=0)จากนั้นคำนวณค่าเฉลี่ย / สูงสุด / ค่ามัธยฐาน (∗) ระยะทางแบบยุคลิดจากค่าเฉลี่ยนั้น (∗) เลือกสิ่งที่มีจินตนาการของคุณ
Fred Foo

1
@curious: ฉันได้อัปเดตโค้ดตัวอย่างเป็น API scikit-Learn ปัจจุบัน คุณอาจต้องการลองรหัสใหม่
Fred Foo

87

เหมือนกันกับ @larsman แต่มีการประมวลผลล่วงหน้าบางส่วน

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 คำตอบที่ดีและชัดเจนจริงๆ! ฉันมีสองข้อสงสัย: I) อะไรคือ [0,1] ที่คุณรวมหลังจาก tfidf * tfidf.T) และ II) ความถี่เอกสารผกผันเกิดขึ้นจากบทความทั้งหมดหรือเพียงสองบทความ (พิจารณาว่าคุณมีมากกว่า 2) ?
Economist_Ayahuasca

2
@AndresAzqueta [0,1] เป็นตำแหน่งในเมทริกซ์สำหรับความคล้ายคลึงกันเนื่องจากอินพุตข้อความสองรายการจะสร้างเมทริกซ์สมมาตร 2x2
Philip Bergström

1
@Renaud ขอขอบคุณสำหรับรหัสที่สมบูรณ์ของคุณ สำหรับผู้ที่พบข้อผิดพลาดที่ขอให้ nltk.download () คุณสามารถทำได้อย่างง่ายดาย nltk.download ('punkt') คุณไม่จำเป็นต้องดาวน์โหลดทุกสิ่ง
1man

@ เรโนลต์ฉันไม่ได้รับปัญหาพื้นฐานมากขึ้น สตริงข้อความใดควรfitและที่transformใด
John Strood

@JohnStrood ฉันไม่เข้าใจคำถามของคุณขอโทษคุณช่วยปฏิรูปได้ไหม
Renaud

45

มันเป็นคำถามที่เก่า แต่ผมพบว่านี้สามารถทำได้อย่างง่ายดายด้วยใจลอย เมื่ออ่านเอกสารแล้ว API ที่เรียบง่ายsimilarityสามารถใช้ในการค้นหาความคล้ายคลึงที่เป็นโคไซน์ระหว่างเวกเตอร์เอกสาร

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
ฉันสงสัยว่าทำไมความคล้ายคลึงกันระหว่าง doc1 และ doc2 คือ 0.999999954642 และไม่ใช่ 1.0
JordanBelf

4
@JordanBelf ตัวเลขจุดลอยตัวเร่ร่อนไปรอบ ๆ ในภาษาส่วนใหญ่ - เนื่องจากพวกเขาไม่มีความแม่นยำอย่างไม่ จำกัด ในการเป็นตัวแทนดิจิทัล เช่นการดำเนินการเกี่ยวกับจำนวนจุดลอยตัวหรือการผลิตจำนวนอตรรกยะมักมีข้อผิดพลาดในการปัดเศษเล็ก ๆ ซึ่งจะทวีคูณซึ่งจะทวีคูณ มันเป็นข้อเสียของการเป็นตัวแทนที่ยืดหยุ่นในแง่ของสเกล
scipilot

2
ฟังก์ชั่นระยะทางที่ใช้วิธีคล้ายคลึงกันในกรณีนี้คืออะไร?
ikel

หากคุณมีปัญหาในการค้นหา "en" ให้เรียกใช้ pip ติดตั้ง spacy && python -m spacy download en
Cybernetic


17

โดยทั่วไปแล้วความคล้ายคลึงของโคไซน์ระหว่างเอกสารทั้งสองนั้นจะใช้เป็นตัวชี้วัดความคล้ายคลึงกันของเอกสาร ใน Java คุณสามารถใช้Lucene (ถ้าคอลเลกชันของคุณค่อนข้างใหญ่) หรือLingPipeเพื่อทำสิ่งนี้ แนวคิดพื้นฐานคือการนับเงื่อนไขในเอกสารทุกฉบับและคำนวณผลคูณดอทของพจน์เวกเตอร์ ห้องสมุดมีการปรับปรุงหลายอย่างเกี่ยวกับวิธีการทั่วไปเช่นการใช้ความถี่เอกสารผกผันและการคำนวณเวกเตอร์ tf-idf หากคุณกำลังมองหาที่จะทำอะไร copmlex, LingPipe ยังมีวิธีการคำนวณความคล้ายคลึงกันของ LSA ระหว่างเอกสารที่ให้ผลลัพธ์ที่ดีกว่าความคล้ายคลึงโคไซน์ สำหรับงูหลามคุณสามารถใช้NLTK


4
โปรดทราบว่าไม่มี "ความคล้ายคลึงกัน LSA" LSA เป็นวิธีการลดขนาดของพื้นที่เวคเตอร์ (เพื่อเร่งความเร็วในสิ่งต่างๆหรือหัวข้อแบบจำลองแทนที่จะเป็นคำศัพท์) ตัวชี้วัดความเหมือนกันเดียวกันที่ใช้กับ BOW และ tf-idf สามารถใช้กับ LSA (ความเหมือนโคไซน์, ความคล้ายคลึงแบบยูคลิด, BM25, …)
Witiko

16

หากคุณกำลังมองหาบางอย่างที่ถูกต้องคุณต้องใช้เครื่องมือที่ดีกว่า tf-idf ตัวเข้ารหัสประโยคสากลเป็นหนึ่งในเครื่องมือที่แม่นยำที่สุดในการค้นหาความคล้ายคลึงกันระหว่างข้อความสองส่วน Google จัดเตรียมแบบจำลองที่ได้รับการฝึกอบรมซึ่งคุณสามารถใช้สำหรับแอปพลิเคชันของคุณเองโดยไม่จำเป็นต้องฝึกฝนอะไรเลย ก่อนอื่นคุณต้องติดตั้ง tensorflow และ tensorflow-hub:

    pip install tensorflow
    pip install tensorflow_hub

รหัสด้านล่างช่วยให้คุณสามารถแปลงข้อความใด ๆ ให้เป็นตัวแทนเวกเตอร์ที่มีความยาวคงที่และจากนั้นคุณสามารถใช้ผลิตภัณฑ์ดอทเพื่อค้นหาความคล้ายคลึงกันระหว่างพวกเขา

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)

และรหัสสำหรับการลงจุด:

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

ผลลัพธ์จะเป็น: เมทริกซ์ความคล้ายคลึงกันระหว่างคู่ของข้อความ

ในขณะที่คุณสามารถเห็นความคล้ายคลึงกันมากที่สุดคือระหว่างข้อความกับตัวเองและจากนั้นด้วยข้อความที่ใกล้ชิดของพวกเขาในความหมาย

สำคัญ : ครั้งแรกที่คุณเรียกใช้รหัสมันจะช้าเพราะมันจำเป็นต้องดาวน์โหลดรุ่น หากคุณต้องการป้องกันไม่ให้ดาวน์โหลดโมเดลอีกครั้งและใช้โมเดลโลคัลคุณต้องสร้างโฟลเดอร์สำหรับแคชและเพิ่มลงในตัวแปรสภาพแวดล้อมจากนั้นหลังจากใช้งานเส้นทางครั้งแรก:

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)

ข้อมูลเพิ่มเติม: https://tfhub.dev/google/universal-sentence-encoder/2


สวัสดีขอบคุณสำหรับตัวอย่างนี้กระตุ้นให้ฉันลองใช้ TF - วัตถุ "np" ควรมาจากที่ใด
โบรกเกอร์อาหารเปิด

1
UPD ตกลงฉันได้ติดตั้ง numpy, matplotlib และระบบ TK Python เข้ากับพล็อตแล้วและใช้งานได้ !!
โบรกเกอร์อาหารเปิด

1
ในกรณีที่ (ขออภัยสำหรับการขาดการแบ่งบรรทัด): นำเข้า tensorflow เป็น TF นำเข้า tensorflow_hub เป็นการนำเข้าฮับ matplotlib.pyplot เป็น plt นำเข้าจำนวนมากเป็น np
dinnouti

5

นี่เป็นแอพเล็ก ๆ น้อย ๆ เพื่อให้คุณเริ่มต้น ...

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 ช้ามากถ้าคุณจะทำงานกับเอกสารจำนวนมาก
Phyo Arkar Lwin

2

คุณอาจต้องการลองใช้บริการออนไลน์นี้เพื่อดูความคล้ายคลึงกันของเอกสาร cosine 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

Api ใช้ Matcher ต่อเนื่องต่างกันอย่างไร ถ้าใช่แล้วฟังก์ชั่นง่าย ๆ ใน python จะทำงาน ____________________________________ จาก difflib การนำเข้า SequenceMatcher def isStringSimilar (a, b): อัตราส่วน = SequenceMatcher (ไม่มี, a, b) .ratio () อัตราผลตอบแทน ____________________________
Rudresh Ajgaonkar

2

หากคุณสนใจที่จะวัดความคล้ายคลึงกันทางความหมายของข้อความสองส่วนฉันขอแนะนำให้ดูที่โครงการ gitlabนี้ คุณสามารถเรียกใช้เป็นเซิร์ฟเวอร์ได้นอกจากนี้ยังมีโมเดลที่สร้างไว้ล่วงหน้าซึ่งคุณสามารถใช้เพื่อวัดความคล้ายคลึงกันของข้อความสองชิ้นได้อย่างง่ายดาย แม้ว่าส่วนใหญ่แล้วจะได้รับการฝึกฝนให้วัดความคล้ายคลึงกันของสองประโยคคุณยังสามารถใช้ในกรณีของคุณได้มันเขียนด้วยภาษาจาวา แต่คุณสามารถเรียกใช้เป็นบริการสงบได้

อีกตัวเลือกหนึ่งก็คือDKPro Similarityซึ่งเป็นห้องสมุดที่มีอัลกอริธึมต่าง ๆ เพื่อวัดความคล้ายคลึงกันของข้อความ อย่างไรก็ตามมันเขียนด้วยภาษาจาวา

ตัวอย่างรหัส:

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

เพื่อค้นหาความเหมือนประโยคที่มีชุดข้อมูลน้อยมากและเพื่อให้ได้ความแม่นยำสูงคุณสามารถใช้แพ็คเกจ python ด้านล่างซึ่งใช้โมเดล BERT ที่ผ่านการฝึกอบรมมาก่อน

pip install similar-sentences

ฉันแค่ลองมัน แต่มันให้ความคล้ายคลึงกันของแต่ละประโยคกับหนึ่งหลัก แต่มีวิธีใดที่จะสร้างข้อมูลการฝึกอบรมประโยค. txt ทั้งหมดในหนึ่งชั้นเรียนและได้รับคะแนนว่ามีความมั่นใจมากแค่ไหนที่ตรงกับข้อสอบทั้งหมด ?
Guru Teja

1
ใช่คุณสามารถลอง. batch_predict (BatchFile, NumberOfPrediction) ซึ่งจะให้ผลลัพธ์เป็น Results.xls พร้อมคอลัมน์ ['ประโยค', 'คำแนะนำ', 'คะแนน']
Shankar Ganesh Jayaraman

1

สำหรับ Syntactic Similarity สามารถตรวจจับความคล้ายคลึงกันได้ 3 วิธี

  • Word2Vec
  • ถุงมือ
  • Tfidf หรือ countvectorizer

สำหรับ Semantic Similarity หนึ่งสามารถใช้ BERT Embedding และลองใช้กลยุทธ์การรวมคำที่แตกต่างกันเพื่อให้ได้การฝังเอกสาร

วิธีการขั้นสูงสามารถใช้ BERT SCORE เพื่อให้ได้ความคล้ายคลึงกัน คะแนน BERT

ลิงค์รายงานการวิจัย: https://arxiv.org/abs/1904.09675

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.