• 25.07.2003

Oracle Magazine, Июнь/Июль 2003
В статье рассматриваются вопросы организации контекстного поиска в СУБД Oracle при помощи компоненты Oracle Text и продукта Russian Context Optimizer (RCO).

Аннотация

В статье рассматриваются вопросы организации контекстного поиска в СУБД Oracle при помощи компоненты Oracle Text и продукта Russian Context Optimizer (RCO). Статья состоит из двух частей.

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

Вторая часть полностью посвящена примерам. Читателю предлагаются варианты решения следующих задач:

  • поиск с учетом словоформ (два подхода),
  • подсветка искомых слов при выдаче документа,
  • построение реферата по запросу.

Все примеры предназначены для запуска в SQL*Plus.

Организация поиска по массиву текстовых документов

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

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

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

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

Oracle Text

Oracle Text представляет собой поисковую машину, встроенную в Oracle Database. Это позволяет задавать в запросах одновременно контекстные и реляционные ограничения, а также выполнять администрирование реляционных и полнотекстовых индексов едиными средствами. Краткий обзор Oracle Text содержится в статье Изучение основ Oracle Text (Oracle Magazine RE – 2001 – май).

Oracle Text предлагает пользователю широкий спектр возможностей при построении поисковых запросов. Перечислим операторы, относящиеся к контекстным ограничениям: ACCUMulate (,), AND (&), EQUIValence (=), MINUS (-), NEAR (;), NOT (~), OR (|), weight (*), wildcard (%, _), WITHIN. Все эти операторы поддерживаются для текстов на всех языках, поддерживаемых Oracle Database. Подробное описание операторов приведено в документе Text Reference, входящим в поставку СУБД Oracle.

Кроме того, Oracle Text содержит процедуры для выдачи документов ctx_doc.markup и ctx_doc.highlight. Ниже будут даны примеры использования этих функций для решения задачи выдачи текста документа с выделенными искомыми словами и выдачи фрагментов документа, содержащих искомые слова.

Существуют также операторы, поддержка которых ограничена несколькими языками, в число которых русский язык не входит. В частности, оператор stem ($), служащий для поиска всех грамматических форм слова поддерживается только для шести европейских языков (английский, французский, испанский, итальянский, немецкий и датский).

Russian Context Optimizer

RCO является программным продуктом компании “Гарант-Парк-Интернет” и предлагает лингвистическое обеспечение и алгоритмы, необходимые для выполнения поисковых операций на массивах русскоязычных текстов. Первая версия RCO вышла в ноябре 1997 года. Текущая версия, 4.0.1, датируется ноябрем 2002 года.

С точки зрения разработчика, RCO является пакетом PL\SQL процедур. Пакет называется rco_context. Упоминание этого пакета в примерах в качестве квалификатора имени процедуры означает использование RCO.

При использовании RCO существенным является понятие “настройка” (setting). Настройки, аналогично предпочтениям (preference), связываются с полнотекстовым индексом и используются при поиске и обновлении индекса. В Приложении 1 приведен пример создания настройки RCO. Для выполнения всех примеров необходимо создать две настройки: одну без использования фильтра RCO (далее будем на нее ссылаться как на settingAsIs), другую – с использованием фильтра (settingFilter).

Русский язык в цифрах

Большинство слов русского языка имеют десятки различных грамматических форм, поэтому для их поиска в тексте необходимо использовать морфологический анализатор. Морфологический анализатор RCO позволяет обрабатывать более 110 тысяч слов, заданных в морфологическом словаре, а также любые неизвестные слова, анализируя их по аналогии с похожими известными словами. Используемый при этом словарь лингвистических данных для русского языка включает в себя около 110.000 словооснов, 6.000 окончаний, 1.500 моделей словоизменения, 200 суффиксов, что соответствует 2.5 миллионам распознаваемых словоформ. Примеры словоформ, порождаемых одним словом:

  • ПРЕЗИДЕНТ=ПРЕЗИДЕНТА=ПРЕЗИДЕНТУ… – всего более 10-ти форм;
  • ПРЕЗИДЕНТСКИЙ=ПРЕЗИДЕНТСКОГО=ПРЕЗИДЕНТСКОМУ… – всего более 20-ти форм;
  • КУЗДРЯЧИТЬ=КУЗДРЯЧИЛ=КУЗДРЯЧИЛА… – всего более 100 форм, включая склоняемые формы причастий “куздрячащий”, “куздрячивший”, “куздряченный”.

Если быть более точным, в русском языке существует четыре типа парадигм словообразования: парадигма существительного (14 грамматических форм), парадигма прилагательного (31 грамматическая форма) и парадигма глагола (146 форм, не считая 86 возвратных). К четвертому типу (вырожденному) относятся все неизменяемые слова.

Из 110 тысяч слов русского языка более 2-х тысяч имеет совпадающие грамматические формы. Для обозначения этого явления используется терминомонимия. Примеры слов-омонимов:

  • АРХАНГЕЛЬСКОМ (АРХАНГЕЛЬСК сущ., АРХАНГЕЛЬСКИЙ прил.),
  • БАНКА (БАНК сущ. муж. р., БАНКА сущ. жен. р.),
  • БЕГУ (БЕГ сущ., БЕЖАТЬ гл.),
  • БОРОВ (БОР сущ. неодуш, БОРОВ сущ. одуш.),
  • МАТЕРЕЙ (МАТЕРЕТЬ гл., МАТЕРЫЙ прил., МАТЬ сущ.).

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

Как учет словоформ повышает качество поиска

Для оценки качества поиска на практике чаще всего используются два показателя:полнота и точность.

Полнота – отношение числа полученных релевантных документов к общему числу релевантных документов в базе.

Точность – отношение числа полученных релевантных документов к общему числу полученных документов.

Мнение системы о соответствии документа запросу не всегда совпадает с мнением пользователя. В противном случае точность и полнота были бы 100%. На практике, чем система больше возвращает документов на запрос, тем выше полнота. Если система вернет все документы, хранящиеся в базе, полнота заведомо будет 100%, а точность близка к нулю. Чем больше релевантных документов попадет в начало списка, выдаваемого системой, тем выше точность. В идеальном случае все релевантные документы должны находиться в начале списка. Поэтому при выдаче документы упорядочиваются в порядке убывания релевантности.

В реальных системах требования высокой точности и полноты являются взаимоисключающими. Типичный график зависимости точности от полноты приведен на рис. 1. Участок в левом верхнем углу соответствует просмотру нескольких первых документов, участок в правом нижнем углу – просмотру всего списка. Данные для построения графика взяты из статьи Text Retrieval Quality: A Primer посвященной замерам качества Oracle Text на наборе текстовых данных TREC.

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

По различным оценкам, учет словоформ в зависимости от длины запросов и документов повышает точность и полноту результатов поиска до 40%.

Рисунок 1. Зависимость точности от полноты

Два подхода к организации поиска с учетом словоформ

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

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

При поиске задача системы – извлечь из индекса список пар <документ, позиция словоместа>, для которых индексируемое слово=поисковое слово. Отметим, что в случае использования оператора эквивалентности (EQUIV), индексируемое слово проверяется на принадлежность множеству поисковых слов.

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

Способ 1:

  • Заносить в индекс слова в том виде, в котором они встречаются в тексте.
  • При поиске использовать оператор EQUIV и указывать все возможные грамматические формы слова. Перечислять все формы вручную очень неудобно. RCO позволяет автоматизировать эту операцию.

Способ 2:

  • Заносить в индекс вместо слов их нормальные формы (в литературе также используется термин “лемма”). Это можно сделать при помощи фильтра, предоставляемого в RCO.
  • При поиске заменять слова в запросе их нормальными формами. RCO позволяет сделать это автоматически.

Пример расширения запроса

Код в данном примере использует функцию rco_context.PrepareStringEx, действие которой заключается в замене каждого русского слова в запросе на множество всех его словоформ с использованием оператора EQUIV. При этом ключевые слова, операторы и метасимволы Oracle Text будут оставлены без изменений. При приглашении к вводу настройки используйте имя настройки settingAsIs.

Листинг 1. Расширение запроса

set serveroutput on
declare
  l_query VARCHAR2(128) := 'совет директоров';
  l_query_ex VARCHAR2(4000);
  TYPE rc_t is REF CURSOR;
  l_cursorid rc_t;
  l_rowid rowid;
begin
  -- расширяем строку запроса
  l_query_ex := rco_context.PrepareStringEx( l_query, '&SettingName' );
  dbms_output.put_line(l_query_ex);

  -- формируем запрос
  l_query_ex := 
     'select rowid from &TableName where contains(&FieldName,''' || 
     l_query_ex  || ''') > 0';


  -- выполняем и выводим результат
  open l_cursorid for l_query_ex;
  loop
    fetch l_cursorid into l_rowid;
    exit when l_cursorid%notfound;
    dbms_output.put_line(l_rowid);
  end loop;
  close l_cursorid;
end;
/


Результат:


Enter value for settingname: settingAsIs
Enter value for tablename: tblTest
Enter value for fieldname: text


(СОВЕТ=СОВЕТА=СОВЕТАМ=СОВЕТАМИ=СОВЕТАХ=СОВЕТЕ=
        СОВЕТОВ=СОВЕТОМ=СОВЕТУ=СОВЕТЫ)
(ДИРЕКТОР=ДИРЕКТОРА=ДИРЕКТОРАМ=ДИРЕКТОРАМИ=ДИРЕКТОРАХ=
        ДИРЕКТОРЕ=ДИРЕКТОРОВ=ДИРЕКТОРОМ=ДИРЕКТОРУ)


AAAH09AAIAAAAAWAAA

В случае предоставления пользователю языка запросов, отличного от языка Oracle Text, возможно расширение отдельно взятого слова:

Листинг 2. Получение списка всех грамматических форм слова

select rco_context.WordGetExtensions('президент','&SettingName')
                  from dual; 


Результат:


Enter value for settingname: settingAsIs


RCO_CONTEXT.WORDGETEXTENSIONS('ПРЕЗИДЕНТ','SETTINGASIS')
----------------------------------------------------------------
ПРЕЗИДЕНТ=ПРЕЗИДЕНТА=ПРЕЗИДЕНТАМ=ПРЕЗИДЕНТАМИ=
ПРЕЗИДЕНТАХ=ПРЕЗИДЕНТЕ=ПРЕЗИДЕНТОВ=
ПРЕЗИДЕНТОМ=ПРЕЗИДЕНТУ=ПРЕЗИДЕНТЫ

Пример лемматизации запроса

Для выполнения этого примера требуется использование настройки settingFilter. В соответствии с данной настройкой все индексируемые слова приводятся в нормальную форму. Для поиска по такому индексу необходимо перевыполнением запроса привести в нормальную форму поисковые слова. Код данного примера полностью совпадает с кодом предыдущего примера, за исключением используемой функции rco_context.PrepareString вместо rco_context.PrepareStringEx. Функция rco_context.PrepareString производит лемматизацию всех русских слов запроса, оставляя без изменений ключевые слова, операторы и метасимволы Oracle Text.

В результате работы модифицированной таким образом процедуры будет получена следующая выдача:

Листинг 3. Результат лемматизации запроса

Enter value for settingname: settingFilter
Enter value for tablename: tblTest
Enter value for fieldname: text


СОВЕТ ДИРЕКТОР


AAAH09AAIAAAAAWAAA

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

Листинг 4. Лемматизация поисковых слов

select rco_context.get_normal_form('президенту для подписания?','&SettingName')
                   from dual; 


На данный запрос система выдаст такой результат:


Enter value for AsIsSettingName: settingFilter


RCO_CONTEXT.GET_NORMAL_FORM('ПРЕЗИДЕНТУ ДЛЯ ПОДПИСАНИЯ ?','CLOB_TEXT')
-------------------------------------------------------------------
президент подписание

Сравнение лемматизации и расширения запроса

Таблица 1. Сравнение подходов к поиску с учетом словоформ

Расширение запроса Лемматизация запроса и индекса
Скорость обновления индекса Не влияет. Замедляет, так как используется фильтр.
Скорость выполнения запроса Замедляет, так как требуется большее число обращений к индексам. Не влияет.
Размер индекса Не влияет Сокращает. Число записей в таблице DR$IndexName$I.
Возможность поиска слова как есть Можно. Достаточно не расширять запрос. Нельзя. В индексе хранятся только нормальные формы.
Влияние омонимии Ищет все омонимичные формы. Ищет только одну в соответствии с приоритетами частей речи.

Как видно из таблицы 1, каждый подход имеет свои достоинства и недостатки. И все же, на взгляд автора, предпочтительней выглядит расширение запроса.

Подсветка результатов

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

Листинг 5. Подсветка искомых слов в документе (rco_context.highlightText)

set serveroutput on
declare
  Text CLOB;             -- исходный текст документа
  TextHigh CLOB;         -- текст с выделенными словами
  offset pls_integer;
  DocTxt varchar2(128) := 'Президент РФ подписал указ';
  resStr varchar2(128);
  len pls_integer := length(DocTxt);
begin
  -- инициализируем объекты LOB
  dbms_lob.createtemporary(TextHigh,cache=>FALSE,dur=>DBMS_LOB.CALL);
  dbms_lob.createtemporary(Text,cache=>FALSE,dur=>DBMS_LOB.CALL);
  dbms_lob.writeappend(Text,len,DocTxt);


  -- вызываем функцию подсветки
  rco_context.highlightText('&query',Text,TextHigh, '&SettingName');
  -- печатаем результат
  offset := 1;
  loop
    resStr := dbms_lob.substr(TextHigh,80,offset);
    if resStr is NULL then
      exit;
    end if;
    dbms_output.put_line(resStr);
    offset := offset + length(resStr);
  end loop;


  -- освобождаем объекты LOB
  dbms_lob.freetemporary(Text);
  dbms_lob.freetemporary(TextHigh);
 end;
/


Результат:


Enter value for query: указ
Enter value for settingname: settingAsIs


Президент РФ подписал <font color=red><b>указ</b></font>

Альтернативой является использование процедуры ctx_doc.markup. Данная процедура выделяет искомые слова, причем только те, что удовлетворяют условиям запроса. При получении документа процедура ctx_doc.markup вызывает фильтр, используемый на этапе построения индекса, поэтому индекс должен быть построен с настройкой settingAsIs. В противном случае результат будет состоять из последовательности слов в нормальной форме, разделенных пробелами. Кроме того, в данном коде, для сокращения размера используется in-memory версия функции ctx_doc.markup, доступная только в Oracle 9iR2 Text.

Листинг 6. Подсветка искомых слов в документе (ctx_doc.markup)

set serveroutput on
declare
  mklob clob;            -- объект CLOB для получения результата подсветки
  resStr varchar2(128);
  offset pls_integer;
begin
  -- вызываем функцию подсветки, расширяя при этом запрос
  ctx_doc.markup(
    '&IndexName','&TextKeyValue',
    rco_context.PrepareStringEx('&query','&SettingName'), mklob,
    tagset=>'HTML_DEFAULT', starttag=>'<font color=red><b>',
               endtag=>'</b></font>');


  -- печатаем результат
 offset := 1;
 loop
    resStr := DBMS_LOB.SUBSTR(mklob,80,offset);
    if resStr is NULL then
      exit;
    end if;
    dbms_output.put_line(resStr);
    offset := offset + length(resStr);
  end loop;


  -- освобождаем временный lob, созданный ctx_doc.markup
  dbms_lob.freetemporary(mklob);
end;
/


Результат:
 
Enter value for indexname: clob_text
Enter value for textkeyvalue: 777
Enter value for query: указом
Enter value for settingname: settingAsIs


Президент РФ подписал <font color=red><b>указ</b></font>

Отличия двух подходов приведены в таблице 2.

Таблица 2. Различия в использовании процедур rco_context.highlighttext и ctx_doc.markup

rco_context.highlighttext ctx_doc.markup
Обработка запроса Выделяются все слова запроса. Выделяются только те слова, что соответствуют условиям запроса.
Возможность настройки Настройки разборщика текста RCO. Настройки BASIC_LEXER.
Возможность подсветки неиндексированного текста Можно. Нельзя.

Выдача фрагментов документа по запросу

Все мы уже давно привыкли, что при выдаче списка документов поисковой машиной в интернет вместе с заголовком документа выдаются также его фрагменты, содержащие искомые слова. В литературе также употребляется выражение “построение реферата по запросу”. Следующий пример является доработкой кода, предложенного в статье Using Multilingual Text Search With Oracle9i Text. Код извлекает из документа фрагмент, содержащий первое вхождение слов запроса.

В данном примере более адекватным был бы выбор процедуры ctx_doc.highlight, однако использование in-memory версии процедуры ctx_doc.markup упрощает восприятие кода.

Листинг 7. Построение реферата по запросу

declare
  mklob   CLOB;                 -- буфер типа CLOB 
                                -- для получения документа с разметкой
  offset  INTEGER;              -- смещение фрагмента 
  amt     BINARY_INTEGER := 150;-- число символов, составляющих фрагмент
  prefix  INTEGER := 30;        -- число символов перед поисковым словом
  resStr  VARCHAR2(800);        -- строка с результирующим фрагментом


  -- starttag должен быть уникален в рамках документа
  starttag VARCHAR2(50) := '<b name="9itext">';
  endtag VARCHAR2(50)   := '</b>';
begin


  ctx_doc.markup('&IndexName', '&TextKeyValue',
    rco_context.preparestringex('&query','&&SettingName'),
    mklob,
    tagset=>'HTML_DEFAULT', starttag=>starttag, endtag=>endtag );


  -- получаем смещение первого вхождения искомого слова
  offset := DBMS_LOB.INSTR(mklob, starttag, 1, 1);


  -- вычисляем смещение
  if offset > prefix THEN 
    offset := offset - prefix;
  ELSE
    offset := 1;
  END IF;


  -- считываем результат
  resStr := DBMS_LOB.SUBSTR(mklob,amt,offset);


  -- освобождаем временный CLOB 
  dbms_lob.freetemporary(mklob);


  dbms_output.put_line(resStr);
END;
/


Результат:


Enter value for indexname: clob_text
Enter value for textkeyvalue: 100013
Enter value for query: Россия

Сегодня Президент <b name=”9itext”>России</b> Борис Ельцин по представлению Правительства РФ внес в Государственную Думу на ратификацию

Заключение

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

Приложение 1. Создание настройки RCO

Для создания настройки и соответствующего ей индекса необходимо запустить командный файл $RCO_HOME/config/rco_settings. В версии под Windows NT/2000 существует возможность создания настроек посредством приложения с графическим интерфейсом. В результате работы командного файла будут созданы необходимые файлы и sql скрипт для внесения изменений в схему.

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

В случае успешного завершения будут созданы файлы:

$RCO_HOME/config/rco_$settingId.opt, $RCO_HOME/config/rco_$settingId.stw,

$RCO_HOME/sql/rco_demo_$settingId.sql, $RCO_HOME/sql/rco_drop_$settingId.sql.

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

Отредактируйте при необходимости файлы rco_$settingId.opt (настройки процедуры разбора текста документов и запросов), rco_$settingId.stw (список стоп-слов), rco_demo_$settingId.sql (параметры хранилища и индексов). Для создания настройки выполните скрипт rco_demo_$settingId.sql от владельца схемы. Для удаления настройки выполните скрипт rco_drop_$settingId.sql.

Следующие два листинга дают представление о содержимом файла rco_demo_$settingId.sql.

Листинг 9. Построение индекса с использованием фильтра RCO

begin
  ctx_ddl.create_preference('RCO_FILTER_clob_text', 'USER_FILTER');
  ctx_ddl.set_attribute('RCO_FILTER_clob_text',
    'COMMAND','rco_filter_clob_text.bat');
  ctx_ddl.create_preference('RCO_LEXER_clob_text', 'BASIC_LEXER');
  ctx_ddl.set_attribute('RCO_LEXER_clob_text', 'index_themes','NO');
  execute immediate 'create index clob_text on clob_text(text)
    indextype is ctxsys.context parameters(''filter 
    RCO_FILTER_clob_text lexer RCO_LEXER_clob_text'')';
end;
/

Листинг 10. Построение индекса без использования фильтра RCO

begin
  ctx_ddl.create_preference('RCO_LEXER_clob_text', 'BASIC_LEXER');
  ctx_ddl.set_attribute('RCO_LEXER_clob_text', 'index_themes','NO');
  execute immediate 'create index clob_text on clob_text(text)
    indextype is ctxsys.context parameters(''lexer RCO_LEXER_clob_text'')';
end;
/