Я провел небольшой эксперимент по написанию кода на Python, чтобы проверить, могу ли я найти семантическое сходство между документами, используя базовые стратегии безумных слов, также известные как неструктурированные, с подсчетом слов. Моя попытка здесь не особенно сложна, но я хотел практический эксперимент, который помог бы мне быстрее освоить некоторые из основ неструктурированного семантического анализа, который предоставляет структура bag-o'-words. . Я надеюсь подготовиться психологически, прежде чем мой учебный курс по науке о данных вскоре перейдет к гораздо более продвинутым сложным фреймворкам НЛП, предлагаемым в таких модулях, как пакеты sklearn и gensim на Python.

Как вы увидите, большинство фактических результатов моих сравнительных экспериментов неоднозначны, граничат с провалом, но ...

Очевидно, разочарование, но, опять же, это было больше для практики, чем для реальных результатов. Ключевыми точками уязвимости, по-видимому, является мое понимание алгоритмов для векторного пространства, которые я пытался создать по образцу Основы статистической обработки естественного языка [ Manning and Schütze, 1999]. Однако другие уязвимые места могут быть более банальными, например, обработка текста и даже мои манипуляции с пандой в Python.

Тем не менее, я в целом доволен, так как его главная цель заключалась в том, чтобы заложить фундамент для понимания анализа набора слов. Надеюсь, я смогу вернуться к этим алгоритмам (и пандам) с большим пониманием по мере продвижения в программе.

Семантическая неоднозначность

Рассмотрим следующий набор из трех терминов: хит, джон и мэри.

Порядок слов чрезвычайно важен в английском языке, поэтому есть огромный инстинкт навести порядок в этих словах. Но пока попробуйте рассмотреть эти три термина совершенно независимо и понять, что есть некоторая двусмысленность в отношении того, кто может быть жертвой, а кто может быть обидчиком, если все, что нам нужно, - это мешок слов.

Базовые структуры: синтаксис и морфология

Но в естественном языке есть десятки методов, которые мы применяем для устранения неоднозначности. Контекст, невербальные сигналы, интонация и так далее. Но мы также обращаемся к основным структурам английского языка, нашему синтаксису (и интонации), которые могут помочь очень легко устранить неоднозначность.

Рассмотрим три однозначных высказывания, использующих эти термины.

Джон ударил Мэри [синтаксис]

Мэри ударила Джона [синтаксис]

Мэри, ударь Джона [контекст, интонация (устная), пунктуация (письменная)]

Точно так же, чтобы проиллюстрировать уровни сложности, с которыми могут столкнуться нижележащие структуры, рассмотрим пассивный залог:

Джона ударила Мэри [синтаксис]

Или рассмотрим следующий рекурсивный пример:

Джон (…) ударил Мэри. [синтаксис]

… Парень, который…

… Это парень, которого я встретил в поезде, который…

… Это парень, которого я встретил в поезде из Квинса, который…

… Парень, которого я встретил в поезде из Квинса, где я живу, который…

и так далее.

Надеюсь, эти примеры иллюстрируют, как основная структура, синтаксис может устранять неоднозначность значений в естественном языке. (Любая остаточная двусмысленность допускает юмористические каламбуры и мультфильмы жителей Нью-Йорка.)

Устранение семантической неоднозначности без обращения к структуре: набор слов

В отличие от сложных систем предложений и структуры слов, теперь рассмотрим эти два набора слов.

Надеюсь, этот пример иллюстрирует, что очень быстро семантическое устранение неоднозначности можно также почерпнуть из документа просто по составу слов, с которыми он появляется - без обращения к какой-либо сложной базовой структуре, иначе говоря, синтаксис и морфология. В этом случае мы можем предположить скрытое ощущение насилия или преступления для удара слева, тогда как значение удара справа, скорее всего, будет интерпретировано как успешное песня по радио.

Морфология, основы и леммы

Согласие относительно того, что делает слово словом, может варьироваться в академическом плане. Например, полезно ли рассматривать следующие слова как один корень? Или лучше их рассматривать как рут с фичами? или просто как есть?

строить: строить, строит, строить, строить, строить.

Часто в компьютерной лингвистике наиболее полезно сгладить вариации слова с его корнем. Двумя наиболее распространенными практиками являются выделение и лемматизация, когда морфемы и символы удаляются, чтобы свести токен к его «корневому» значению, или основе, или лемме. у лингвистов-теоретиков основные структуры также можно найти в границах слов. И представление о том, что делает слово словом в алфавитных языках, иногда может быть неясным. Одним из ключевых допущений моделей мешков со словами является определение слова. В своем эксперименте, чтобы не усложнять, я не вырезал и не лемматизировал ни одно из слов, но легко увидеть, как это будет иметь огромный эффект. Например,

Джон ударил Мэри

Джон бьет Мэри

Поисковые запросы, рейтинг страниц и индексирование

Чтобы выразить это более конкретными терминами, представьте, что «попадание» было поисковым запросом, введенным в поисковую систему. Можем ли мы определить, какие документы могут иметь семантическое отношение к поисковому запросу, не обращаясь к синтаксису (базовой структуре языка)?

Что ж, Google может. И крепко. И быстро.

Какова вероятность того, что веб-сайт релевантен поисковому запросу? Как поисковая система знает, как устранить неоднозначность? И как Google делает это так хорошо и так быстро? И так языко-агностически?

Теперь это может показаться знакомым в отношении моделей условной вероятности и наивного Байеса.

Какова вероятность термина-x0 с учетом частотности терминов в документе {x1, x2, x3,…}?

Понятие обработки документов (веб-сайтов, электронной почты, книг, журналов и т. Д.) Для быстрого поиска включает индексацию и ранжирование страниц. И искусство и магия, которые воплощают в жизнь детали этих методов, - вот что делает Google тем, чем он является сегодня.

Мой эксперимент

16 документов

Для начала я проанализировал следующие шестнадцать заголовков с помощью следующей интуиции человека, все из вариантов обычного текста UTF-8 из проекта Gutenberg (https://www.gutenberg.org/wiki/Main_Page).

Я хотел проверить следующие интуиции:

Контроль: один и тот же документ, анализируемый сам с собой, должен показывать наивысшую степень сходства.

  • Документы на одном языке следует рассматривать как более похожие, чем документы на разных языках,
  • Документы, имеющие одинаковую тематику и язык, должны рассматриваться как более похожие, чем документы с несвязанной тематикой.
  • Документы одного автора…?

Английский язык (9)

Истории о лодках

  • Сердце тьмы
  • Моби Дик
  • Человек-Джек-Нож
  • В красном и золотом

Тот же автор (Диккенс)

  • Рождественский гимн
  • Дэвид копперфильд
  • Оливер Твист

Истории о Луне

  • Автор: Earthlight
  • С Земли

Немецкий (3)

  • Смерть в Венеции

Тот же автор (Гессен)

  • Демиан
  • Сиддхартха

Греческий (4)

  • Агамемнон
  • Гитаврос

Тот же автор (Аристотель)

  • Никомахова этика, Vol. 1
  • Никомахова этика, Vol. 2

Жетоны, типы, остановки и грязные слова

Первым шагом было разобрать тексты на слова или токены. Я был очень рад наконец попробовать модуль Python tokenize, Natural Language Took Kit.

from nltk.tokenize import sent_tokenize, word_tokenize, RegexpTokenizer
from nltk.corpus import stopwords

Итак, мои точные шаги были:

  1. Нашел основной основной текст в большом файле. Это было вручную, но проект Gutenberg предоставляет довольно четкое представление о запуске и остановке основного текста.
  2. Разбор абзацев с помощью базового Python str.split ()
  3. Разобрал предложения с помощью nltk.sent_tokenlize.
  4. Слово токены разобрано с помощью nltk.word_tokenize.
  5. Никакого стемминга или лемматизатина, но я «очистил» каждое слово своей собственной функцией «clean ()», просто в качестве теста.
    «def clean (word):
    root = word.strip (). lower ()»
    if root == 'n \' t ':
    root =' not '
    вернуть корень »
  6. Игнорируются общие функциональные слова в соответствии с языком с nltk.stopwords
  7. Подсчет типов слов и построение векторных пространств с помощью базовых слов Python,

например, Сердце тьмы

{'nellie': 1, 'cruising': 1, 'yawl': 1, 'swung': 4, 'anchor': 3, 'without': 51, 'flutter': 1, 'sails': 2, 'rest': 19, 'flood': 2, 'made': 67, 'wind': 6, 'nearly': 13, 'calm': 5, 'bound': 4, 'river': 60, 'thing': 35, 'come': 32, 'wait': 6, 'turn': 9, 'tide': 2, 'sea': 23, 'reach': 11, 'thames': 3, 'stretched': 3, 'us': 54, 'like': 120, 'beginning': 1, 'interminable': 2, 'waterway': 4, 'offing': 2, 'sky': 11, 'welded': 1, 'together': 18, 'joint': 1, 'luminous': 5, 'space': 6, 'tanned': 1, 'barges': 1, 'drifting': 1, 'seemed': 69, 'stand': 7, 'still': 40, 'red': 14, 'clusters': 1, 'canvas': 1, 'sharply': 2, 'peaked': 3, 'gleams': 4, 'varnished': 2, 'sprits': 1, 'haze': 2, 'rested': 3, 'low': 16, 'shores': 3, 'ran': 9, 'vanishing': 2, 'flatness': 1, 'air': 30, 'dark': 25, 'gravesend': 2, 'farther': 7, 'back': 65, 'condensed': 1, 'mournful': 5, 'gloom': 10, 'brooding': 9, 'motionless': 4, 'biggest': 3, 'greatest': 5, 'town': 3, 'earth': 39, 'director': 4, 'companies': 1, 'captain': 4,

Облака слов

Быстрая проверка облаков слов показала, что моя реализация стоп-слов nltk для английского и немецкого текстов выглядит хорошо.

Heart of Darkness [английский]

Смерть в Венеции [немецкий]

Но быстрая проверка моих словесных облаков показала, что моя реализация стоп-слов для греческого языка nltk могла не работать. Мне нужно покопаться в фактических словах в наборе nltk, чтобы подтвердить, однако греческий язык, моя интуиция подсказывает, что проблемы с кодировкой типов или несовместимые диакритические маркеры могут быть проблемой.

Гитаврос [греческий]

Векторы и меры сходства

Чтобы оценить меры сравнения, я создал класс для сравнения двух документов со следующими методами подобия, основанными на моем понимании Основ статистической обработки естественного языка [Manning and Schütze, 1999].

Методы, которые я реализовал на основе наборов уникальных слов, двоичных векторов, которые фактически представляют собой всего лишь кардинальный набор слов. в математике, без счетных значений.

  • Игра в кости: длина общих уникальных слов, деленная на сумму всех уникальных слов в обоих документах.
  • Жаккар: длина общих уникальных слов, деленная на объединение обоих документов.
  • Перекрытие: длина общих уникальных слов, деленная на меньший из двух документов.
  • Косинус: количество совпадающих уникальных слов, деленное на квадратный корень из произведения двух документов.

Методы, которые я реализовал, основаны на подсчете слов или векторном пробеле, которые в моем случае были просто dicts Python.

  • Евклидово расстояние: квадратный корень из суммы квадратов счетных разностей.
  • Косинусный вектор: произведение количества слов в каждом векторе, деленное на квадратный корень из произведения каждого квадрата подсчета.
  • Скрытое размещение Дирихле: TK!

См. Также методы в Python ниже:

class Similarity():
   def __init__(self, leftDocument, rightDocument):
     self.Left = leftDocument
     self.Right = rightDocument
 
   def get_matching_coefficient(self):
     return self.Left.get_set().intersection(self.Right.get_set())
 
   def get_dice_coefficient(self):
     num = 2*len(self.get_matching_coefficient())
     denom = len(self.Left.get_set()) + len(self.Right.get_set())
     return num/denom
 
   def get_jaccard_coefficient(self):
     num = len(self.get_matching_coefficient())
     denom = (len(self.Left.get_set().union(self.Right.get_set())))
     return num/denom
 
   def get_overlap_coefficient(self):
     num = len(self.get_matching_coefficient())
     denom = min( len(self.Left.get_set()),     len(self.Right.get_set()))
     return num/denom
 
   def get_cosine(self):
     num = len(self.get_matching_coefficient())
     denom = (len(self.Left.get_set())*len(self.Right.get_set()))**0.5
     return num/denom
 
   def get_vector_cosine(self):
     num = 0
     denom_left = 0
     denom_right = 0
     if len(self.get_matching_coefficient()):
        for word in self.get_matching_coefficient():
            L_count = self.Left.get_word_count(word)
            R_count = self.Right.get_word_count(word)
            num += L_count*R_count
            denom_left += L_count**2
            denom_right += R_count**2
        return num/((denom_left)**0.5 * (denom_right)**0.5)
     else:
        return 0
 
   def get_euclidian_distance(self):
     dist = 0
     for word in self.get_matching_coefficient():
         dist += (self.Left.get_word_count(word) —     self.Right.get_word_count(word))**2
     return (dist)**0.5

Полученные результаты

Игральные кости

Жаккар

Перекрытие

Косинус

Евклидово расстояние

Косинусные векторы