Статьи

Использование Lucene и Cascalog для быстрой обработки текста в масштабе

В этом посте объясняются методы обработки текста и анализа, используемые при запуске 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