Рассчитать косинусное сходство по 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
8 ответов
Простая реализация на чистом 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-х годов (например, эта работа Greffenstette) и дала некоторые удивительно хорошие результаты. Например, вот несколько случайных записей в тезаурусе, который я недавно построил, когда мой компьютер читал википедию:
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: Суть этого ответа заключается в том, чтобы подчеркнуть тот факт, что хотя наивные методы существуют (например, сложение, умножение, поверхностное сходство и т. Д.), Они в корне ошибочны, и в целом не следует ожидать высокой производительности от их.
У меня есть аналогичное решение, но может быть полезно для панд
import math
import re
from collections import Counter
import pandas as pd
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 list(vec1.keys())])
sum2 = sum([vec2[x] ** 2 for x in list(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)
df=pd.read_csv('/content/drive/article.csv')
df['vector1']=df['headline'].apply(lambda x: text_to_vector(x))
df['vector2']=df['snippet'].apply(lambda x: text_to_vector(x))
df['simscore']=df.apply(lambda x: get_cosine(x['vector1'],x['vector2']),axis=1)
Попробуй это. Загрузите файл "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
Самый простой ответ, который я могу придумать, включает в себя CounterVectorizer.
Предположим, у нас есть 3 фрагмента текста.
text_1 = """ """
text_2 = """ """
text_3 = """ """
documents = [text_1, text_2, text_3]
- Чтобы вычислить косинусное сходство, нам нужна матрица количества слов из каждого документа.
import pandas as pd
# Create the Document Term Matrix
count_vectorizer = CountVectorizer(stop_words='english')
count_vectorizer = CountVectorizer()
sparse_matrix = count_vectorizer.fit_transform(documents)
# OPTIONAL: Convert Sparse Matrix to Pandas Dataframe if you want to see the word frequencies.
doc_term_matrix = sparse_matrix.todense()
df = pd.DataFrame(doc_term_matrix,
columns=count_vectorizer.get_feature_names(),
index=['text_1', 'text_2', 'text_3'])
df
- И просто функция подобия косинуса делает работу от sklearn.
from sklearn.metrics.pairwise import cosine_similarity
print(cosine_similarity(df, df))
Что ж, если вы знаете о внедрении слов, таких как Glove/Word2Vec/Numberbatch, ваша работа наполовину выполнена. Если нет, позвольте мне объяснить, как это можно решить. Преобразуйте каждое предложение в жетоны слова и представляйте каждый из этих жетонов как векторы высокой размерности (используя предварительно обученные вложения слов, или вы можете даже обучить их сами!). Итак, теперь вы просто не фиксируете их поверхностное сходство, а скорее извлекаете значение каждого слова, составляющего предложение в целом. После этого вычислите их косинусное сходство и вы настроены.
Без использования внешних библиотек вы можете попробовать BLEU или его альтернативы. Вы можете обратиться к его стандартной реализации: SACREBLEU.
Спасибо @vpekar за вашу реализацию. Это очень помогло. Я только что обнаружил, что при вычислении косинусного сходства он теряет вес tf-idf. Счетчик (слово) возвращает словарь, в котором есть список слов и их вхождение.
cos (q, d) = sim (q, d) = (q · d)/(| q || d |) = (сумма (qi, di)/(sqrt (сумма (qi2)))) *(sqrt(сумма (vi2))) где я = 1 до v)
- qi - это вес tf-idf термина i в запросе.
- ди является тф-идф
- вес термина я в документе. | Д | и | д | являются длинами q и d.
- Это косинусное сходство q и d.,,,,, или, что эквивалентно, косинус угла между q и d.
Пожалуйста, не стесняйтесь просматривать мой код здесь. Но сначала вам нужно будет скачать пакет анаконды. Он автоматически установит вам путь к Python в Windows. Добавьте этот интерпретатор Python в Eclipse.