В этом посте объясняются методы обработки текста и анализа, используемые при запуске Yieldbot. Их технология использует инструменты с открытым исходным кодом, включая Cascalog, Lucene, Hadoop и Clojure Java Interop. Следующая статья была написана Сореном Макбетом , специалистом по данным в Yieldbot.
Здесь, в Yieldbot, мы выполняем много текстовой обработки аналитических данных. Чтобы добиться этого за разумное время, мы используем Cascalog , библиотеку обработки и запросов данных для Hadoop; написано в Clojure. Поскольку Cascalog — это Clojure, вы можете разрабатывать и тестировать запросы прямо в Clojure REPL. Это позволяет итеративно разрабатывать рабочие процессы обработки с предельной скоростью. Поскольку запросы Cascalog — это просто код Clojure, вы можете получить доступ ко всему, что может предложить Clojure, без необходимости реализации каких-либо специфичных для домена API или интерфейсов для пользовательских функций обработки. В сочетании с удивительным Java Interop от Clojure вы можете делать довольно сложные вещи очень просто и лаконично.
Многие большие библиотеки Java уже существуют для обработки текста, например, Lucene , OpenNLP , LingPipe , Stanford NLP . Использование Cascalog позволяет использовать эти существующие библиотеки с минимальными усилиями, что приводит к гораздо более коротким циклам разработки.
В качестве примера я покажу, как легко комбинировать Lucene и Cascalog для некоторой (простой) обработки текста. Вы можете найти весь код, использованный в примерах, на Github .
Наша цель состоит в том, чтобы токенизировать строку текста. Это почти всегда первый шаг в любом виде обработки текста, так что это хорошее место для начала. Для наших целей мы будем широко определять токен как базовую единицу языка, которую мы хотели бы проанализировать; обычно токен — это слово. Есть много разных способов сделать токенизацию. Lucene содержит много различных подпрограмм токенизации, которые я не буду здесь подробно описывать, но вы можете прочитать документы, чтобы узнать больше. Мы будем использовать стандартный анализатор Lucene, который является хорошим базовым токенизатором. Он будет вводить все входные данные в нижнем регистре, удалять основной список стоп-слов и довольно умно обрабатывать знаки препинания и тому подобное.
Во-первых, давайте смоделируем наш запрос Cascalog. Наши входные данные будут состоять из 1 набора строк, которые мы хотели бы разбить на токены.
(defn tokenize-strings [in-path out-path]
(let [src (hfs-textline in-path)]
(?<- (hfs-textline out-path :sinkmode :replace)
[!line ?token]
(src !line)
(tokenize-string !line :> ?token)
(:distinct false))))
Я не буду тратить кучу времени на объяснение синтаксиса Cascalog, так как вики и документы уже очень хороши в этом. Здесь мы читаем в текстовом файле, который содержит строки, которые мы хотим маркировать, по одной строке на строку. Каждая из этих строк будет передана в функцию tokenize-string, которая выдаст 1 или более 1-кортежей; по одному на каждый сгенерированный токен.
Далее давайте напишем нашу функцию tokenize-string. Мы будем использовать удобную функцию Cascalog, которая называется операция с состоянием. Если выглядит так:
(defmapcatop tokenize-string {:stateful true}
([] (load-analyzer StandardAnalyzer/STOP_WORDS_SET))
([analyzer text]
(emit-tokens (tokenize-text analyzer text)))
([analyzer] nil))
В начале 0-арная версия вызывается один раз для каждой задачи. Мы будем использовать это для создания экземпляра нашего анализатора Lucene, который будет выполнять токенизацию. 1 + n-арность передает результат функции 0-арности в качестве первого параметра плюс любые другие параметры, которые мы определяем. Это где фактическая работа произойдет. Последняя функция из 1 арности используется для очистки.
Далее мы создадим остальные служебные функции, которые нам нужны для загрузки анализатора Lucene, получения токенов и их выдачи обратно.
(defn tokenizer-seq
"Build a lazy-seq out of a tokenizer with TermAttribute"
[^TokenStream tokenizer ^TermAttribute term-att]
(lazy-seq
(when (.incrementToken tokenizer)
(cons (.term term-att) (tokenizer-seq tokenizer term-att)))))
(defn load-analyzer [^java.util.Set stopwords]
(StandardAnalyzer. Version/LUCENE_CURRENT stopwords))
(defn tokenize-text
"Apply a lucene tokenizer to cleaned text content as a lazy-seq"
[^StandardAnalyzer analyzer page-text]
(let [reader (java.io.StringReader. page-text)
tokenizer (.tokenStream analyzer nil reader)
term-att (.addAttribute tokenizer TermAttribute)]
(tokenizer-seq tokenizer term-att)))
(defn emit-tokens [tokens-seq]
"Compute n-grams of a seq of tokens"
(partition 1 1 tokens-seq))
Мы интенсивно используем удивительное Java Interop от Clojure, чтобы использовать Java API Lucene для выполнения тяжелой работы. Хотя этот пример очень прост, вы можете взять эту платформу и добавить любое количество различных доступных анализаторов Lucene, чтобы выполнить гораздо более сложную работу с небольшим изменением кода Cascalog.
Опираясь на Lucene, мы получаем закаленную в бою, быструю обработку без необходимости писать тонну клеевого кода благодаря Clojure. Так как код Cascalog — это код Clojure, нам не нужно тратить кучу времени на переключение между различными средами сборки и тестирования, а производственное развертывание — это просто «идеальный вариант».
Источник: http://blog.yieldbot.com/using-lucene-and-cascalog-for-fast-text-proce