Как вычислить сходство между двумя текстовыми документами?

207

Я смотрю на работу над проектом НЛП, на любом языке программирования (хотя Python будет моим предпочтением).

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

Рейли Борн
источник
1
Подобный вопрос здесь stackoverflow.com/questions/101569/… с некоторыми хорошими ответами

Ответы:

292

Обычный способ сделать это - преобразовать документы в векторы TF-IDF и затем вычислить косинусное сходство между ними. Любой учебник по поиску информации (IR) охватывает это. Смотрите особенно Введение в поиск информации , которая бесплатна и доступна онлайн.

Вычисление парных сходств

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 

хотя у Генсима может быть больше вариантов для такого рода задач.

Смотрите также этот вопрос .

[Отказ от ответственности: я принимал участие в реализации TF-IDF scikit-learn.]

Интерпретация результатов

Сверху pairwise_similarity- скудная матрица Сципи, имеющая квадратную форму, с числом строк и столбцов, равным количеству документов в корпусе.

>>> 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
@ Нулевая гипотеза: в позиции (i, j) вы найдете показатель сходства между документом i и документом j. Таким образом, в позиции (0,2) находится значение сходства между первым документом и третьим (с использованием индексации на основе нуля), которое является тем же значением, которое вы найдете в (2,0), потому что косинусное сходство является коммутативным.
Фред Фу
1
Если бы мне нужно было усреднить все значения за пределами диагонали 1, было бы это разумным способом получить единую оценку того, насколько четыре документа похожи друг на друга? Если нет, есть ли лучший способ определения общего сходства между несколькими документами?
user301752
2
@ user301752: вы можете взять поэлементное среднее векторов tf-idf (как и k-means) X.mean(axis=0), а затем вычислить среднее / максимальное / медианное (∗) евклидово расстояние от этого среднего. (*) Выбери, что у тебя есть.
Фред Фу
1
@curious: я обновил пример кода до текущего API scikit-learn; Вы можете попробовать новый код.
Фред Фу
88

Идентичен @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.
Филипп Бергстрем
1
@Renaud, спасибо за полный код. Для тех, кто столкнулся с ошибкой, запрашивающей nltk.download (), вы можете легко сделать nltk.download ('punkt'). Вам не нужно скачивать все.
1
@ Рено Я не понимаю более фундаментальную проблему. Какие строки текста должны fit, а какие transform?
Джон Строуд
@JohnStrood Я не понимаю вашего вопроса, извините, не могли бы вы переформулировать?
Renaud
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
Какую функцию расстояния использует метод подобия в этом случае?
января
Если у вас возникли проблемы с поиском «en», запустите следующий pip install spacy && python -m spacy download en
Cybernetic
1
@Cybernetic. Посмотрите, как вычисляется метод .sigenity в SpaCy
Уолтер
17

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

Пулькит Гоял
источник
4
Обратите внимание, что здесь нет "сходства LSA". LSA - это метод уменьшения размерности векторного пространства (либо для ускорения, либо для моделирования тем, а не терминов). Те же самые метрики подобия, которые используются с BOW и tf-idf, могут использоваться с LSA (косинусное сходство, евклидово сходство, BM25,…).
Витико
16

Если вы ищете что-то очень точное, вам нужно использовать какой-то лучший инструмент, чем tf-idf. Универсальный кодировщик предложений является одним из наиболее точных, чтобы найти сходство между любыми двумя частями текста. Google предоставил предварительно обученные модели, которые вы можете использовать для своего собственного приложения без необходимости тренироваться с нуля. Во-первых, вам нужно установить тензор потока и тензор потока-хаб:

    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
На всякий случай (извините за отсутствие разрывов строк): импортировать тензор потока в виде tf; импортировать в качестве тензорного потока как концентратор; импортировать matplotlib.pyplot в виде plt импортировать 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 работает очень медленно, если вы собираетесь работать с большим количеством документов.
Phyo Arkar Lwin
2

Возможно, вы захотите попробовать этот онлайн-сервис для сходства документов косинуса http://www.scurtu.it/documentSdentifity.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 четкости isStringSimilar (а, б): соотношение = SequenceMatcher (None, а, б) .ratio) возвращает отношение (______________________________
Rudresh Ajgaonkar
2

Если вас больше интересует измерение семантического сходства двух фрагментов текста, я предлагаю взглянуть на этот проект gitlab . Вы можете запустить его в качестве сервера, также есть встроенная модель, которую вы можете легко использовать для измерения сходства двух фрагментов текста; хотя он в основном обучен измерению сходства двух предложений, вы все равно можете использовать его в своем случае. Он написан на Java, но вы можете запустить его как службу RESTful.

Другим вариантом также является DKPro Similarity - библиотека с различным алгоритмом измерения сходства текстов. Тем не менее, это также написано на 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
Шанкар Ганеш Джаяраман
источник
Я только что попробовал это, но это дает сходство каждого предложения с одним основным, но есть ли способ создать все обучающие данные в формате tear.txt как один класс и получить оценку того, насколько достоверно оно соответствует всем примерам ?
Гуру Теджа
1
да, можете, попробуйте .batch_predict (BatchFile, NumberOfPrediction), который выдаст результаты в виде Results.xls со столбцами ['Предложение', 'Предложение', 'Счёт']
Шанкар Ганеш Джаяраман
1

Для синтаксического сходства может быть 3 простых способа обнаружения сходства.

  • Word2Vec
  • Перчатка
  • Tfidf или countvectorizer

Для семантического сходства Можно использовать BERT Embedding и попробовать разные стратегии объединения слов, чтобы получить вложение документа, а затем применить косинусное сходство при встраивании документа.

Продвинутая методология может использовать BERT SCORE для получения сходства. BERT SCORE

Ссылка на исследовательскую статью: https://arxiv.org/abs/1904.09675

Shaurya Uppal
источник