Практические советы по реализации систем извлечения информации

понедельник, ноября 20, 2006

Multiword Features

В ситуациях когда различиях между категориями проявляются не на уровне различных слов, а на уровне сочетаний этих слов, классические классификаторы Байеса могут показывать не очень удовлетворительные результаты. Справиться с этой проблемой можно используя шинглы (shingles) или multiword features.

В общем случае, шинглы — это уникальные последовательности символов одинаковой длины выделенные из документа. Для задач классификации можно рассматривать последовательности не символов, а слов. Например, для документа "a rose is a rose is a rose" шинглами длиной в четыре слова будут: { (a,rose,is,a), (rose,is,a,rose), (is,a,rose,is) }. Построить все шинглы для документа можно за O(w(n-w)), где w — длина шингла. Дальше шинглы используются как обычные слова в классификаторе Байеса, за счет чего заметно вырастает точность, особенно когда мы работаем с иерархической структурой категорий.

К сожалению шинглы нам не помогут в случае, когда слова из характерных сочетаний идут в документах не подряд, а на различном расстоянии друг от друга. В таком случае можно использовать обобщение идеи шинглов: multiword features. Собственно, что это такое уже понятно, весь вопрос в том, как эффективно эффективно найти характерные для категории multiword features. Действительно, наивный алгоритм, который находит все возможные сочетания слов, скорее всего просто не влезет в память. Очевидно, что нам нужны не все возможные сочетания слов, а только наиболее характерные для категории, то есть которые встречаются, например, как минимум в 10% документов. Это позволяет нам последовательно вычислять корреляцию для очередного набора слов и хранить в памяти только удовлетворяющие условию. Тем не менее, временная сложность такого алгоритма все равно будет O(mk), где m — число слов в категории, k — число слов в наборе. Очевидно, что вычислять наборы более чем из двух слов неэффективно, да и, как правило, это не даст большого выигрыша в точности.

Таким образом, нам осталось решить задачу вычисления корреляции между двумя словами в наборе документов, то есть число документов, в которых эти слова встречаются одновременно. Опять же, самый простой способ, когда мы проходим все документы и считаем вхождения слов в них будет достаточно медленным. Можно использовать следующий алгоритм вычисления корреляции:
  1. Каждое слово представляется вектором, элементы которого равны нулю или единице, в зависимости от присутствия слова в документе с соответствующим номером.
  2. Этот вектор хранится в виде long[], длина массива равна [n / sizeof(long)] + 1, где n — число документов.
  3. Вычисление корреляции между двумя документами сводится к последовательному побитовому умножению элементов двух массивов и вычислению числа единиц в результате.
Хороший практический результат на многих задачах может дать совместное использование шинглов длиной в 3-5 слов и 2-word features.

4 комментария:

Unknown комментирует...

///...за счет чего заметно вырастает точность, особенно когда мы работаем с иерархической структурой категорий.

А есть уже какие то оценки?

krondix комментирует...

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

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

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

В моем классификаторе точность выросла с 85-90% до 95-97%. Но у меня задача сильно проще чем веб-классификация, документы меньше и получше. Плюс для веба с его гигантским словарем вычисление корреляции будет очень долгим, но шинглы в качестве features должны, по ощущениям, заметно помочь.

Анонимный комментирует...

наверное, все-таки имеется в виду битовый вектор размером [(n / sizeof(long)+7)/8]
хотя все равно в память он для разумной коллекции не полезет и нужно его делать разреженным, что в моей практике, позволяло уменьшить подобные массивы в десятки раз для слов из "средних" ранков и "засунуть" в 256 Мб памяти (а дело было лет 6 назад) гиговые коллекции.

krondix комментирует...

я на Java пишу, поэтому приходится использовать long[], а не битовый массив

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