Статьи

В поисках Лавкрафта, часть 1: введение в НЛП и драгоценность лакомства

Концепция развития личности с использованием НЛП Мы только что начали ООО «Непознаваемый ужас». Наш план — просеять через огромный океан байтов, который является Интернетом, чтобы найти следующий HP Lovecraft, чтобы мы могли заработать целое состояние, продавая мастерский космический ужас.

В качестве альтернативы, если мы не сможем найти следующий HP Lovecraft, мы могли бы просто написать свои собственные истории и продолжать запускать нашу программу против них, дорабатывая, пока нас не смогут отличить от Lovecraft. Тогда сделайте бочки с деньгами.

В этом проекте мы будем делать следующее:

  • Часть 1
    • Узнайте, как анализировать естественный язык с помощью лакомства
  • Часть 2
    • Визуализируйте различия между разными авторами и историями, чтобы увидеть, как мы можем выявить авторов
    • Создайте систему, которая определит, была ли история написана HP Lovecraft.

Думающая машина? Обнаруживаете его работу? Если бы он только знал.

Следуя вдоль

Данные для этого проекта доступны по адресу http://github.com/rlqualls/author-identification-tutorial .

Получение историй о Лавкрафте

Все истории о Лавкрафте, используемые в этом руководстве, доступны в репозитории github. Они находятся в открытом доступе и были получены из проекта Гутенберга. Они были предварительно отформатированы для достижения наилучших результатов, удалены из специального форматирования, которое может отбросить текстовые процессоры лечения.

Тем не менее, ведутся споры о том, подпадают ли многие из произведений HP Лавкрафта под авторское право в Соединенных Штатах, поэтому они были опущены, за исключением «Избежанного дома». Вам нужно будет получить свои собственные копии остальных его работ. истории, но их легко получить в таких местах, как:

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

Обработка естественного языка

Его голос звучал отвратительно, и он мог говорить на всех языках.

НЛП — это объединенная область искусственного интеллекта и лингвистики, связанная со способностью понимать естественный язык в вычислительном отношении. Он представляет собой один из самых серьезных барьеров, с которыми сталкиваются компьютеры при навигации по человеческому миру. Таким образом, участие в любой задаче НЛП, безусловно, не будет тривиальным мероприятием, включая это. Прежде чем мы пойдем дальше, важно, чтобы мы поняли, что происходит.

Возьмите эту строку, например:

"the world is indeed comic, but the joke is on mankind." 

На самом низком уровне компьютер видит строку как массив байтов. Затем он берет эти байты и ассоциирует с ними какое-то представление, такое как ASCII или UTF-8. Например, вот строка, закодированная в ASCII через шестнадцатеричное. Вы можете использовать Asciitohex, чтобы убедиться в этом.

 74 68 65 20 77 6F 72 6C 64 20 69 73 20 69 6E 64 65 65 64 20 63 6F 6D 69 63 2C 20 62 75 74 20 74 68 65 20 6A 6F 6B 65 20 69 73 20 6F 6E 20 6D 61 6E 6B 69 6E 64 2E 

В этот момент компьютер видит символы, но не слова. Чтобы получить слова из предыдущего предложения, нам нужно сообщить компьютеру, какие символы составляют его слова. Этот процесс называется токенизацией . Если бы мы разбили строку на массив токенов, они бы выглядели так:

 ['the', 'world', 'is', 'indeed', 'comic', ',', 'but', 'the', 'joke', 'is', 'on', 'mankind', '.'] 

После токенизации компьютер знает, какие символы являются словами, а какие — пунктуацией, но не знает, что означает любой из них. Вот где все идет от довольно простого к чрезвычайно сложному.

Если бы мы писали язык программирования, это та точка, в которой мы бы создали парсер. Мы могли бы придумать простые правила, такие как «все операторы заканчиваются символом новой строки» или «что-нибудь между токенами« do »и« end »является блоком».

Теперь создание синтаксического анализатора вряд ли можно назвать тривиальным, но именно здесь мы начинаем понимать разницу между языком программирования и естественным языком.

Посмотрите, с какими проблемами мы сталкиваемся с первыми двумя словами нашего предложения: «мир». Какой мир? Весь мир? Мир ссылался на несколько предложений назад? Что если мы все время говорим об определенном мире, но до сих пор мы избегали символического «мира»? Далее, что значит для мира быть комическим? Возможно, автор имел в виду «комикс». Кроме того, «шутка» ранее не упоминалась, а теперь, похоже, физически находится над «человечеством».

В этот момент наша система видит «комические» и «шутки», определяет предложение как шутливое и беззаботное и сообщает нам, что все поняла.

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

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

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

Удовольствие — The Ruby NLP Toolkit

Согласно странице проекта,

Проект Treat направлен на создание языковой и независимой от алгоритмов среды для Ruby с поддержкой поиска документов, разбиения на фрагменты текста, сегментации и токенизации, синтаксического анализа естественного языка, тегов части речи, извлечения ключевых слов и распознавания именованных сущностей.

Вы получили все это?

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

Цели

  • Успешно установить драгоценность лечения
  • У некоторых базовых НЛП
  • Узнайте об организации дерева удовольствия
  • Узнайте, как получить некоторые приятные метрики, используя удовольствие

Установка

Установка угощения может быть сложной. Для начала нам нужно установить гем лечения с:

 gem install treat --version 2.0.7 

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

Перед этим, однако, убедитесь, что Java установлена, а переменная среды $JAVA_HOME установлена ​​в папку установки Java (та, в которой есть bin и include).

Затем откройте оболочку irb или напишите скрипт на Ruby и выполните следующее:

 require 'treat' Treat::Core::Installer.install 'english' 

Это может занять некоторое время, в зависимости от вашей системы. Смотрите, чтобы убедиться, что он устанавливает пакет stanford-core-nlp .

Если Java не установлена ​​или $JAVA_HOME не настроен должным образом, этот шаг завершится неудачно, но в противном случае установка будет выполнена успешно . Эффект заключается в том, что некоторые функции Treat будут давать ошибки, когда этот пакет необходим.

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

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

Как только лакомство установлено, мы можем начать играть с ним. Давайте посмотрим на некоторые примеры.

 require 'treat' include Treat::Core::DSL 'darkness'.category # => "noun" 'abyss'.plural # => "abysses" 'dreaming'.stem # => "dream" 'think'.present_participle# => "thinking" 'towering'.synonyms # => ["eminent", "lofty", "soaring", "towering"] 'perfection'.hypernyms # => ["state", "ideal", "improvement"] 

Навигация по Дереву Лечения

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

Давайте продолжим и обработаем нашу первую историю. Удовольствие предоставляет нам метод «документ».

 story = document('collections/h_p_lovecraft/the_shunned_house.txt') 

Теперь история находится в объекте документа, но она не была обработана. Если вы попробуете story.paragraphs или story.sentences , вы получите пустой массив. Это потому, что, хотя дерево документа существует, оно не имеет узлов. Лечить предоставляет различные текстовые процессоры для создания узлов.

  • Chunkers — разбивает документ на разделы и абзацы
  • Сегментеры — разбивает абзацы и разделы на предложения и заголовки
  • Токенизаторы — разбивает предложения и названия на слова

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

 # Run the chunking processor story.chunk # Now we can access an array of all of the story's paragraphs story.paragraphs #Or the text of any paragraph story.paragraphs.first.to_s story.paragraphs[3].to_s 

ЧАЕВЫЕ

Важно, чтобы блок выполнял свою работу правильно, потому что каждый другой процессор зависит от его результатов. Если вы разбиваете на части документ с именем story , и story.paragraphs[0].to_s не останавливается на границе предложения (точка), возможно, что узлы предложения и фразы не будут содержать правильный текст.

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

Теперь, когда наша история разбита на абзацы, мы можем разбить первый абзац на предложения. Обратите внимание, что, поскольку мы выборочно сегментируем первый абзац, story.sentences покажет только предложения в первом абзаце.

 # Run the segmenter on the first paragraph story.paragraphs.first.segment #=> An array of sentences in the first paragraph story.paragraphs.first.sentences story.sentences 

Использование Применить

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

 # Instantiate a fresh document (we can't re-process documents) story = document '/path/to/story' # Run the chunker, segmenter, and tokenizer in succession. story.apply(:chunk, :segment, :tokenize) #=> An array containing all of the story's Word objects story.words #=> The first Word object in the first paragraph story.paragraphs.first.words.first #=> The number of words in the first sentence of the first paragraph story.paragraphs[0].sentences[0].word_count 

ЧАЕВЫЕ

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

 # This gets an object, not a string story.paragraphs.first # This gets the actual text of the entire paragraph story.paragraphs.first.to_s # This will print nothing puts story.words.first # This prints the first word puts story.words.first.to_s 

Для Лавкрафта «Избегающий дом» story.sentences[8].to_s возвращает:

 "The house was -- and for that matter still is -- of a kind to attract the attention of the curious." 

Добавление частей речевых узлов

Пока у нас есть доступ к абзацам, предложениям и словам. Но не было бы неплохо узнать, например, сколько существительных, глаголов и прилагательных в каждом абзаце? Мы могли бы написать вспомогательную функцию, которая делает что-то вроде этого:

 node_nouns = node.words.select { |word| word.category == "noun" } 

Однако это не обязательно. #apply :category к #apply , мы можем украсить дерево узлами частей речи (Примечание: это предполагает, что мы ранее применяли :chunk :tokenize :segment и :tokenize ).

 story.apply(:category) 

Теперь можно сделать что-то вроде следующего:

 # Get an array of all Word objects that are nouns story.nouns # Get an array of the lengths of all the verbs story.verbs.map { |v| v.to_s.length } # Get the number of conjuctions used story.conjunction_count 

Больше чем одна история — Коллекции

Лечить коллекции служат двум целям. Во-первых, они позволяют получить доступ к узлам из разных документов через одно и то же дерево. Во-вторых, они позволяют нам получать доступ к документам в каталоге, не зная их имен файлов. Все, что вам нужно сделать, — это передать путь к каталогу историй в Kernel#collection (при условии, что относящийся к нему DSL уже включен).

 stories = collection 'collections/edgar_a_poe' # Process the entire collection stories.apply(:chunk, :segment, :tokenize, :category) # Gets the noun percentage out of all the stories to two decimal places (stories.noun_count.to_f / stories.word_count).round(2) # Prints out the path of every story in the collection stories.each_document do |story| puts story.file end 

Метрики — Популярность слова

Различные метрики могут быть использованы при попытке дактилоскопии авторов. Например, мы можем использовать среднее количество предложений в абзаце.

К сожалению, с простыми метриками мы рискуем запутать двух авторов, которые пишут в похожих стилях. На Лавкрафта оказали влияние Натаниэль Хоторн и Эдгар По, и некоторые из его простых метрик показывают это. Чтобы избежать путаницы с похожими авторами, нам необходимо сравнить реальное содержание авторских рассказов.

Легко доступная нам метрика контента — популярность слова. Если первые 100 слов истории в основном входят в нижние 100 слов другой истории, то эти истории, вероятно, не очень похожи.

Лечить предоставляет Countable#frequency_of который возвращает количество раз, когда слово встречается. Если мы создадим хэш, в котором каждый ключ является словом, а каждое значение — это число раз, которое оно появляется, мы можем отсортировать его, чтобы получить популярность слова.

 # Start off with an empty hash object word_hash = {} # Assign the word's frequency to its key in word_hash # Note: frequency_of does perform #downcase internally, # but iterating over a uniq downcased array prevents iterating over # unnecessary instances in the story downcased_words = story.words.map { |word| word.to_s.downcase }.uniq downcased_words.each do |w| word_hash[w] = story.frequency_of(w) end # Create an array of [key, value] arrays, sorted greatest-to-least word_popularity = word_hash.sort_by { |key, value| value }.reverse 

Это получит слово популярность всех слов в истории, но на самом деле нас могут не интересовать все из них. Такие слова, как «или» и «и» не являются специфическими для любого типа истории, и они занимают много места в верхних рейтингах. Поскольку прилагательные помогают задать тон истории, мы можем получить много информации, оценивая их в одиночку.

 adjective_popularity = word_popularity.select do |key, value| key.category == "adjective" end 

Вот лучшие 30 прилагательных, которые я получил от Избеганного Дома:

 [["one", 35], ["more", 30], ["other", 19], ["two", 17], ["most", 16], ["old", 15], ["new", 14], ["many", 14], ["first", 12], ["great", 12], ["certain", 11], ["last", 11], ["strange", 10], ["such", 10], ["french", 9], ["same", 7], ["white", 7], ["next", 7], ["hideous", 7], ["light", 7], ["few", 6], ["ancient", 6], ["own", 6], ["sinister", 5], ["broken", 5], ["proper", 5], ["evil", 5], ["thin", 5], ["horrible", 5], ["terrible", 5], ["peculiar", 5]] 

Из этих слов видно, что произведение, скорее всего, представляет собой ужасную работу со словами «странный» и «зловещий» в верхнем рейтинге.

Быстрый анализ — проценты существительных

Прежде чем закончить это введение, давайте посмотрим, сможем ли мы найти какую-либо отличительную информацию между новостной статьей и художественным произведением. Мы сравним существительные проценты коллекций. В папке сценариев есть небольшой скрипт с именем noun_percentages.rb .

 # !/usr/bin/env ruby require 'pathname' require 'treat' include Treat::Core::DSL def process_collection(path) puts "Author: #{Pathname.new(path).basename}" paths = Dir.glob(path + "/*") paths.each do |story_path| story = document story_path story.apply(:chunk, :segment, :tokenize, :category) noun_percentage = (story.noun_count / story.word_count.to_f).round(2) puts "#{Pathname.new(story_path).basename}: #{noun_percentage}" end puts "" end process_collection 'collections/edgar_a_poe' process_collection 'collections/nathaniel_hawthorne' process_collection 'collections/h_p_lovecraft' process_collection 'collections/philip_k_dick' process_collection 'collections/news' 

Этот скрипт входит в каждую папку автора, обрабатывает каждую историю и печатает процент слов, которые являются существительными в этой истории. Запустите это с:

 ruby scripts/noun_percentages.rb 

Вы должны получить такие результаты:

 Author: edgar_a_poe the_masque_of_red_death.txt: 0.2 the_fall_of_the_house_of_usher.txt: 0.2 the_pit_and_the_pendulum.txt: 0.19 the_black_cat.txt: 0.2 the_tell_tale_heart.txt: 0.16 the_premature_burial.txt: 0.19 Author: nathaniel_hawthorne the_man_of_adamant.txt: 0.21 the_maypole_of_merry_mount.txt: 0.24 the_birth_mark.txt: 0.22 young_goodman_brown.txt: 0.24 the_minister's_black_veil.txt: 0.22 Author: h_p_lovecraft the_shunned_house.txt: 0.24 Author: philip_k_dick beyond_the_door.txt: 0.19 beyond_lies_the_wub.txt: 0.22 second_variety.txt: 0.24 the_variable_man.txt: 0.27 Author: news obama_egypt.txt: 0.34 lab_mice.txt: 0.3 cambodian_vote.txt: 0.41 syria_war.txt: 0.37 

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

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

Ресурсы