Рассчитать косинусное сходство по 2 строкам предложения
С Python: tf-idf-cosine: найти сходство документа , можно рассчитать сходство документов, используя косинус tf-idf. Без импорта внешних библиотек, есть ли какие-либо способы вычисления косинусного сходства между двумя строками?
s1 = "This is a foo bar sentence ."
s2 = "This sentence is similar to a foo bar sentence ."
s3 = "What is this string ? Totally not related to the other two lines ."
cosine_sim(s1, s2) # Should give high cosine similarity
cosine_sim(s1, s3) # Shouldn't give high cosine similarity value
cosine_sim(s2, s3) # Shouldn't give high cosine similarity value
5 ответов
Простая реализация на чистом Python будет выглядеть так:
import re, math
from collections import Counter
WORD = re.compile(r'\w+')
def get_cosine(vec1, vec2):
intersection = set(vec1.keys()) & set(vec2.keys())
numerator = sum([vec1[x] * vec2[x] for x in intersection])
sum1 = sum([vec1[x]**2 for x in vec1.keys()])
sum2 = sum([vec2[x]**2 for x in vec2.keys()])
denominator = math.sqrt(sum1) * math.sqrt(sum2)
if not denominator:
return 0.0
else:
return float(numerator) / denominator
def text_to_vector(text):
words = WORD.findall(text)
return Counter(words)
text1 = 'This is a foo bar sentence .'
text2 = 'This sentence is similar to a foo bar sentence .'
vector1 = text_to_vector(text1)
vector2 = text_to_vector(text2)
cosine = get_cosine(vector1, vector2)
print 'Cosine:', cosine
Печать
Cosine: 0.861640436855
Используемая здесь формула косинуса описана здесь .
Это не включает взвешивание слов с помощью tf-idf, но для использования tf-idf вам необходимо иметь достаточно большой корпус для оценки весов tfidf.
Вы также можете развивать его дальше, используя более сложный способ извлечения слов из фрагмента текста, его обрезки или лемматизации и т. д.
Короткий ответ: «Нет, это невозможно сделать принципиальным образом, который работает даже удаленно». Это нерешенная проблема в исследованиях по обработке естественного языка, и она также является темой моей докторской работы. Я очень кратко опишу, где мы находимся, и укажу на несколько публикаций:
Значение слов
Самое важное предположение здесь состоит в том, что можно получить вектор, который представляет каждое слово в предложении в вопросе. Этот вектор обычно выбирается для захвата контекстов, в которых может появляться слово. Например, если мы рассмотрим только три контекста «есть», «красный» и «пушистый», слово «кошка» может быть представлено как [98, 1 , 87], потому что если бы вы читали очень очень длинный фрагмент текста (несколько миллиардов слов не редкость по сегодняшнему стандарту), слово «кошка» очень часто появлялось бы в контексте «пушистый» и «есть» , но не так часто в контексте "красного". Точно так же «собака» может быть представлена как [87,2,34], а «зонтик» может быть [1,13,0]. Представляя эти векторы как точки в трехмерном пространстве, «кошка» явно ближе к «собаке», чем к «зонтику», поэтому «кошка» также означает нечто более похожее на «собаку», чем на «зонтик».
Эта область работ была исследована с начала 90-х годов (например, эта работа Греффенштетта) и дала удивительно хорошие результаты. Например, вот несколько случайных записей в тезаурусе, который я недавно построил, когда мой компьютер читал википедию:
theory -> analysis, concept, approach, idea, method
voice -> vocal, tone, sound, melody, singing
james -> william, john, thomas, robert, george, charles
Эти списки похожих слов были получены полностью без вмешательства человека - вы вводите текст и возвращаетесь через несколько часов.
Проблема с фразами
Вы можете спросить, почему мы не делаем одно и то же для длинных фраз, таких как «имбирные лисицы любят фрукты». Это потому, что нам не хватает текста. Чтобы надежно установить, на что похож X, нам нужно увидеть много примеров использования X в контексте. Когда X - это одно слово типа «голос», это не так уж сложно. Однако, по мере того как X становится длиннее, шансы на нахождение естественных явлений X экспоненциально уменьшаются. Для сравнения, у Google есть около 1B страниц, содержащих слово «лиса», и ни одной страницы, содержащей «имбирь, лисица, любит фрукты», несмотря на то, что это совершенно правильное английское предложение, и мы все понимаем, что оно означает.
Состав
Чтобы решить проблему разреженности данных, мы хотим выполнить компоновку, то есть взять векторы для слов, которые легко получить из реального текста, и собрать их так, чтобы они уловили их смысл. Плохая новость в том, что до сих пор никто не смог сделать это хорошо.
Самый простой и очевидный способ - сложить или умножить отдельные векторы слов вместе. Это приводит к нежелательному побочному эффекту, который означает, что «кошки преследуют собак» и «собаки преследуют кошек» означают одно и то же для вашей системы. Кроме того, если вы умножаете, вы должны быть очень осторожны, иначе каждое предложение будет представлено как [0,0,0, ..., 0], что побеждает точку.
Дальнейшее чтение
Я не буду обсуждать более сложные методы композиции, которые были предложены до сих пор. Я предлагаю вам прочитать Катрин Эрк «Модели векторного пространства значений слов и значений слов: опрос» , Это очень хороший опрос высокого уровня, с которого можно начать. К сожалению, он не находится в свободном доступе на сайте издателя, напишите автору напрямую, чтобы получить копию. В этой статье вы найдете ссылки на многие более конкретные методы. Наиболее понятные из них - Митчел и Лапата (2008) и Барони и Дзампарелли(2010) .
Изменить после комментария @vpekar: Суть этого ответа состоит в том, чтобы подчеркнуть тот факт, что, хотя наивные методы существуют (например, сложение, умножение, поверхностное сходство и т. Д.), Они являются фундаментальными недостатками и вообще от них не стоит ожидать отличных результатов.
Спасибо @vpekar за вашу реализацию. Это очень помогло. Я только что обнаружил, что при вычислении косинусного сходства он теряет вес tf-idf. Счетчик (слово) возвращает словарь, в котором есть список слов и их вхождение.
cos (q, d) = sim (q, d) = (q · d) /(| q || d |) = (сумма (qi, di) /(sqrt (сумма (qi2)))) * (sqrt (sum (vi2))), где i = 1 до v)
- qi - это вес tf-idf термина i в запросе.
- di - это tf-idf
- вес термина i в документе. | Д | и | д | длины д и д.
- Это косинусное сходство q и d. , , , , , или же, эквивалентно, косинус угла между q и d.
Пожалуйста, не стесняйтесь просматривать мой код . Но сначала вам нужно будет скачать пакет анаконды. Он автоматически установит вам путь к Python в Windows. Добавьте этот интерпретатор Python в Eclipse.
Хорошо, если вам известны вложения слов , такие как Glove /Word2Vec /Numberbatch, ваша работа наполовину выполнена. Если нет, позвольте мне объяснить, как это можно решить. Преобразуйте каждое предложение в жетоны слова и представляйте каждый из этих жетонов как векторы высокой размерности (используя предварительно обученные вложения слов, или вы можете тренируй их даже сам!). Итак, теперь вы просто не фиксируете их поверхностное сходство, а скорее извлекаете значение каждого слова, составляющего предложение в целом. После этого вычислите их косинусное сходство и вы настроены.
Попробуй это. Загрузите файл 'numberbatch-en-17.06.txt' с https://conceptnet.s3.amazonaws.com/downloads/2017/numberbatch/numberbatch-en-17.06.txt.gz и распакуйте его. Функция 'get_sentence_vector' использует простую сумму векторов слов. Однако его можно улучшить, используя взвешенную сумму, где веса пропорциональны Tf-Idf каждого слова.
import math
import numpy as np
std_embeddings_index = {}
with open('path/to/numberbatch-en-17.06.txt') as f:
for line in f:
values = line.split(' ')
word = values[0]
embedding = np.asarray(values[1:], dtype='float32')
std_embeddings_index[word] = embedding
def cosineValue(v1,v2):
"compute cosine similarity of v1 to v2: (v1 dot v2)/{||v1||*||v2||)"
sumxx, sumxy, sumyy = 0, 0, 0
for i in range(len(v1)):
x = v1[i]; y = v2[i]
sumxx += x*x
sumyy += y*y
sumxy += x*y
return sumxy/math.sqrt(sumxx*sumyy)
def get_sentence_vector(sentence, std_embeddings_index = std_embeddings_index ):
sent_vector = 0
for word in sentence.lower().split():
if word not in std_embeddings_index :
word_vector = np.array(np.random.uniform(-1.0, 1.0, 300))
std_embeddings_index[word] = word_vector
else:
word_vector = std_embeddings_index[word]
sent_vector = sent_vector + word_vector
return sent_vector
def cosine_sim(sent1, sent2):
return cosineValue(get_sentence_vector(sent1), get_sentence_vector(sent2))
Я побежал за заданными предложениями и нашел следующие результаты
s1 = "This is a foo bar sentence ."
s2 = "This sentence is similar to a foo bar sentence ."
s3 = "What is this string ? Totally not related to the other two lines ."
print cosine_sim(s1, s2) # Should give high cosine similarity
print cosine_sim(s1, s3) # Shouldn't give high cosine similarity value
print cosine_sim(s2, s3) # Shouldn't give high cosine similarity value
0.9851735249068168
0.6570885718962608
0.6589335425458225