Як обчислити схожість між двома текстовими документами?


207

Я дивлюся на роботу над проектом NLP, будь-якою мовою програмування (хоча Python буде моїм уподобанням).

Я хочу взяти два документи і визначити, наскільки вони схожі.


1
Подібне запитання тут stackoverflow.com/questions/101569/… відьма кілька приємних відповідей

Відповіді:


292

Загальний спосіб зробити це - перетворення документів у вектори TF-IDF, а потім обчислення косинусної подібності між ними. Будь-який підручник з пошуку інформації (ІЧ) охоплює це. Див. Esp. Ознайомлення з пошуком інформації , яке є безкоштовним та доступним в Інтернеті.

Обчислювальна пара подібності

TF-IDF (і подібні текстові перетворення) реалізовані в пакетах Python Gensim та scikit-learn . В останньому пакеті обчислювати схожість косинусу так само просто

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 у науковому курсі.]

Інтерпретація результатів

Зверху pairwise_similarity- розріджена матриця Scipy, що має квадратну форму, кількість рядків і стовпців дорівнює кількості документів у корпусі.

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

Ви можете перетворити розріджений масив у масив 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-learn - це помаранчевий і синій". Цей документ має індекс 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 Чи можете ви пояснити масив, якщо це можливо, як я повинен читати цей масив. Перші два стовпці схожі між Першими двома реченнями?
доба-напівколони

1
@ Null-Hypothesis: у положенні (i, j) ви бачите схожість між документом i та документом j. Отже, у позиції (0,2) - значення подібності між першим документом та третім (використовуючи нульову індексацію), що є тим самим значенням, яке ви знаходите при (2,0), оскільки косинусна схожість є комутативною.
Фред Фоо

1
Якби я мав середнє значення всіх значень поза діагоналі 1, це був би здоровий спосіб отримати єдиний бал, наскільки подібні чотири документи один до одного? Якщо ні, чи є кращий спосіб визначення загальної подібності між декількома документами?
user301752

2
@ user301752: ви можете взяти середньоелементне середнє значення tf-idf векторів (як k-означає, що це зробить) з X.mean(axis=0), а потім обчислити середнє / максимальне / медіану (∗) евклідову відстань від цього середнього. (∗) Вибирайте, хто має ваші фантазії.
Фред Фоо

1
@curious: я оновив приклад коду до поточного API scikit-learn; ви можете спробувати новий код.
Фред Фу

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

@ Рено, дійсно гарна і чітка відповідь! У мене є два сумніви: I) що таке [0,1], який ви включаєте після tfidf * tfidf.T) та II) Зворотна частота документа формується з усіх статей або лише двох (враховуючи, що у вас більше 2) ?
Economist_Ayahuasca

2
@AndresAzqueta [0,1] - це позиції в матриці для подібності, оскільки два текстові введення створять симетричну матрицю 2x2.
Філіп Бергстрьом

1
@Renaud, Дякую за повний код. Для тих, хто зіткнувся з помилкою з проханням nltk.download (), ви можете легко зробити nltk.download ('punkt'). Не потрібно все завантажувати.
1man

@Renaud У мене немає більш фундаментальної проблеми. Які рядки тексту слід fit, а які transform?
Джон Строд

@JohnStrood Я не розумію вашого запитання, вибачте, ви могли переформулювати?
Рено

45

Це давнє запитання, але я виявив, що це можна зробити легко за допомогою Spacy . Після того, як документ буде прочитаний, similarityдля пошуку схожості косинусів між векторами документа можна використовувати простий api .

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 числа з плаваючою точкою трохи блукають на більшості мов - оскільки вони не можуть мати необмежену точність у цифрових зображеннях. наприклад, операції з плаваючою комою на або створюють ірраціональні числа завжди мають крихітні помилки округлення, в яких потім множиться. Це є недоліком такого гнучкого представлення у масштабі.
вчений пілот

2
що таке функція відстані, що використовується метод подібності у цьому випадку?
ikel

Якщо у вас виникли проблеми з пошуком "en", запустіть наступну програму встановлення spacy && python -m spacy. Ru
Кібернетична


17

Як правило, косинусна схожість між двома документами використовується як міра подібності документів. На Java ви можете використовувати Lucene (якщо ваша колекція досить велика) або LingPipe для цього. Основною концепцією було б підрахунок термінів у кожному документі та обчислення крапкового добутку терміна вектори. Бібліотеки забезпечують декілька вдосконалень щодо цього загального підходу, наприклад, використовуючи зворотні частоти документа та обчислюючи вектори tf-idf. Якщо ви хочете зробити щось copmlex, LingPipe також пропонує методи обчислення подібності LSA між документами, що дає кращі результати, ніж схожість косинусів. Для Python можна використовувати NLTK .


4
Зауважте, що "подібності з LSA" немає. LSA - це метод зменшення розмірності векторного простору (або для прискорення речей, або для моделювання тем, а не термінів). Ті ж показники подібності, які використовуються для BOW та tf-idf, можна використовувати з LSA (подібність косинусу, евклідова подібність, BM25,…).
Вітіко

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"?
Open Food Broker

1
UPD нормально, я встановив numpy, matplotlib, а також систему TK Python прив'язки для сюжету, і це працює !!
Відкритий продовольчий брокер

1
Про всяк випадок (вибачте за відсутність розривів рядків): імпортуйте tensorflow як tf import tensorflow_hub як hub import matplotlib.pyplot як plt import import numpy як 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 дуже повільний, якщо ви збираєтеся працювати з великою кількістю документів.
Пхіо Аркар Лвін

2

Ви можете спробувати цей Інтернет-сервіс щодо подібності документа косинуса http://www.scurtu.it/documentS Схожіity.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 використовує диференціальний послідовний матч? Якщо так, то проста функція в python виконала б роботу ____________________________________ з дифлібного імпорту SequenceMatcher def isStringS Similar (a, b): ratio = SequenceMatcher (None, a, b) .ratio () коефіцієнт повернення ______________________________
Rudresh Ajgaonkar

2

Якщо вас більше цікавить вимірювання смислової подібності двох фрагментів тексту, пропоную поглянути на цей проект із гітлабу . Ви можете запустити його як сервер, також є заздалегідь побудована модель, яку ви можете легко використати для вимірювання подібності двох фрагментів тексту; незважаючи на те, що він здебільшого підготовлений для вимірювання схожості двох речень, ви все одно можете використовувати його у вашому випадку. Це написано на Java, але ви можете запустити його як службу RESTful.

Інший варіант також - подібність DKPro, яка є бібліотекою з різним алгоритмом для вимірювання подібності текстів. Однак це також написано на Java.

Приклад коду:

// 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 як один клас і отримати оцінку на те, наскільки впевненість підходить до всіх прикладів ?
Гуру Тея

1
так, ви можете, спробуйте .batch_predict (BatchFile, NumberOfPrediction), який дасть результат як результат.xls зі стовпцями ['Речення', 'Пропозиція', 'Оцінка']
Шанкар Ганеш Джаяраман

1

Для синтаксичної подібності Існує 3 простих способи виявлення подібності.

  • Word2Vec
  • Рукавичка
  • Tfidf або countvectorizer

Для семантичної подібності можна використовувати BERT Embedding та спробувати іншу стратегію об’єднання слів, щоб отримати вбудовуваний документ, а потім застосувати схожість косинуса при вкладанні документа.

Розширена методологія може використовувати BERT SCORE для отримання подібності. BERT SCORE

Дослідницьке посилання: https://arxiv.org/abs/1904.09675

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.