Skip to content

Модель данных

Dmitry Granovsky edited this page Nov 27, 2017 · 11 revisions

Здесь будет описание устройства базы данных OpenCorpora для разработчиков.

На данный момент описано 16 таблиц из 87 (+18 перечислены в TODO).

Тексты в корпусе

Корпус -- это коллекция размеченных текстов. Как хранятся тексты?

Самая крупная единица организации контента в корпусе -- это текст (или "книга"). Тексты организованы иерархически и хранятся в таблице books:

+---------------------+-----------------------+------+-----+---------+----------------+
| book_id             | mediumint(8) unsigned | NO   | PRI | NULL    | auto_increment |
| book_name           | varchar(255)          | NO   |     | NULL    |                |
| parent_id           | int(10) unsigned      | NO   | MUL | 0       |                |
| syntax_on           | tinyint(3) unsigned   | NO   | MUL | NULL    |                |
| old_syntax_moder_id | smallint(5) unsigned  | NO   |     | NULL    |                |
+---------------------+-----------------------+------+-----+---------+----------------+

Последние два поля связаны с экспериментальными уровнями разметки и вам неинтересны.

Текст состоит из параграфов (таблица paragraphs), тут всё просто. Колонка pos задаёт порядок следования.

 +---------+-----------------------+------+-----+---------+----------------+
| par_id  | smallint(5) unsigned  | NO   | PRI | NULL    | auto_increment |
| book_id | mediumint(8) unsigned | NO   | MUL | NULL    |                |
| pos     | smallint(5) unsigned  | NO   | MUL | NULL    |                |
+---------+-----------------------+------+-----+---------+----------------+

Аналогично параграф состоит из предложений (таблица sentences). У предложения есть поле source, где для разных целей записывается его исходный текст до токенизации. Последняя колонка полуэкспериментальная, не важна.

+--------------+-----------------------+------+-----+---------+----------------+
| sent_id      | mediumint(8) unsigned | NO   | PRI | NULL    | auto_increment |
| par_id       | smallint(5) unsigned  | NO   | MUL | NULL    |                |
| pos          | smallint(5) unsigned  | NO   | MUL | NULL    |                |
| source       | text                  | NO   |     | NULL    |                |
| check_status | smallint(5) unsigned  | NO   |     | NULL    |                |
+--------------+-----------------------+------+-----+---------+----------------+

Наконец, предложение состоит из токенов (таблица tokens). У токена есть текст. Если взять все токены предложения и слить подряд их тексты, расставляя кое-где пробелы, должен получиться source этого предложения.

+---------+-----------------------+------+-----+---------+----------------+
| tf_id   | int(10) unsigned      | NO   | PRI | NULL    | auto_increment |
| sent_id | mediumint(8) unsigned | NO   | MUL | NULL    |                |
| pos     | smallint(5) unsigned  | NO   | MUL | NULL    |                |
| tf_text | varchar(100)          | NO   |     | NULL    |                |
+---------+-----------------------+------+-----+---------+----------------+

Метаинформация

Метаинформация о текстах хранится в тегах (таблица book_tags). Тег -- это просто строка. В реальности большинство тегов представляют собой строку вида "ключ:значение".

+----------+-----------------------+------+-----+---------+-------+
| book_id  | mediumint(8) unsigned | NO   | MUL | NULL    |       |
| tag_name | varchar(512)          | NO   | MUL | NULL    |       |
+----------+-----------------------+------+-----+---------+-------+

Некоторые из тегов имеют вид url:http://..., они указывают на первоисточник текста. Первоисточники мы скачиваем и храним у себя. Для их учёта существует таблица downloaded_urls, в которой filename -- имя локального файла:

+----------+--------------+------+-----+---------+-------+
| url      | varchar(512) | NO   | MUL | NULL    |       |
| filename | varchar(100) | NO   |     | NULL    |       |
+----------+--------------+------+-----+---------+-------+

Морфологический словарь

Одна из особенностей OpenCorpora -- каждое слово в корпусе либо объявлено неизвестным, либо является ссылкой на словарь. Как устроен словарь?

Единицей словаря является лексема. У нас они по историческим причинам называются леммами, хотя это и лингвистически некорректно. Леммы лежат в таблице dict_lemmata:

+------------+-----------------------+------+-----+---------+----------------+
| lemma_id   | mediumint(8) unsigned | NO   | PRI | NULL    | auto_increment |
| lemma_text | varchar(50)           | NO   |     | NULL    |                |
| deleted    | tinyint(3) unsigned   | NO   | MUL | NULL    |                |
+------------+-----------------------+------+-----+---------+----------------+

Если лемма удаляется из словаря, она продолжает жить в этой таблице со значением поля deleted, равным 1. Это бывает полезно.

Вся информация, кроме текста леммы, хранится в истории правок леммы в таблице dict_revisions:

+------------+-----------------------+------+-----+---------+----------------+
| rev_id     | int(10) unsigned      | NO   | PRI | NULL    | auto_increment |
| set_id     | int(10) unsigned      | NO   | MUL | NULL    |                |
| lemma_id   | mediumint(8) unsigned | NO   | MUL | NULL    |                |
| rev_text   | text                  | NO   |     | NULL    |                |
| f2l_check  | tinyint(1) unsigned   | NO   | MUL | NULL    |                |
| dict_check | tinyint(1) unsigned   | NO   | MUL | NULL    |                |
| is_last    | tinyint(1)            | NO   | MUL | NULL    |                |
+------------+-----------------------+------+-----+---------+----------------+
  • set_id -- это id набора правок, о них ниже.
  • rev_text -- это собственно данная ревизия данной леммы, представляет собой, о ужас, XML.
  • f2l_check -- это индикатор наличия ревизии в индексе form2lemma, о нём ниже.
  • dict_check -- индикатор того, была ли ревизия проверена на ошибки, и об этом ниже.
  • is_last -- банально, является ли ревизия текущей для данной леммы (да, это денормализация для ускорения).

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

Очень полезно знать, в парадигмах каких лемм в словаре есть данная форма (а особенно это полезно при разборе текста, нам же надо поставить из каждого токена ссылку на словарь). Для этого есть специальный индекс form2lemma:

+------------+-----------------------+------+-----+---------+-------+
| form_text  | varchar(50)           | NO   | MUL | NULL    |       |
| lemma_id   | mediumint(8) unsigned | NO   | MUL | NULL    |       |
| lemma_text | varchar(50)           | NO   |     | NULL    |       |
| grammems   | text                  | NO   |     | NULL    |       |
+------------+-----------------------+------+-----+---------+-------+

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

Связи между лексемами

TODO (dict_links, dict_links_types, dict_links_revisions)

Проверка словаря на консистентность

TODO (gram, gram_restrictions, dict_errata, dict_errata_exceptions)

Разметка именованных сущностей

Концептуальная модель этого слоя разметки описана здесь. Она довольно непростая.

Первая фундаментальная концепция -- это тегсет (набор тегов и типов для сущностей). Один и тот же текст можно параллельно разметить разными тегсетами. Тегсеты хранятся в таблице ne_tagsets:

+-----------------+----------------------+------+-----+---------+----------------+
| tagset_id       | tinyint(3) unsigned  | NO   | PRI | NULL    | auto_increment |
| tagset_name     | varchar(32)          | NO   |     | NULL    |                |
| annots_per_text | tinyint(3) unsigned  | NO   |     | 4       |                |
| active_texts    | smallint(5) unsigned | NO   |     | 10      |                |
+-----------------+----------------------+------+-----+---------+----------------+

Помимо названия тегсета (tagset_name), здесь есть:

  • annots_per_text -- количество аннотаций от разных людей, которые мы хотим получить для каждого текста,
  • active_texts -- количество текстов, одновременно выставленных на разметку (которое не хочется делать слишком большим, чтобы тексты размечались быстрее).

Тегсет определяет, какие типы объектов в нём допустимы (например, "дата" или "имя человека"). Эти типы хранятся в таблице ne_object_types:

+----------------+-------------+------+-----+---------+----------------+
| object_type_id | int(11)     | NO   | PRI | NULL    | auto_increment |
| tagset_id      | int(11)     | NO   |     | NULL    |                |
| object_name    | varchar(50) | NO   |     | NULL    |                |
| color_number   | int(11)     | NO   |     | 1       |                |
+----------------+-------------+------+-----+---------+----------------+

Имя (object_name) -- это как раз, например, "Person"; color_number нужен для отображения разных типов разными цветами в интерфейсе.

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

+--------------+---------------------+------+-----+---------+-------+
| book_id      | int(11)             | NO   | PRI | NULL    |       |
| tagset_id    | tinyint(3) unsigned | NO   | PRI | NULL    |       |
| moderator_id | int(11)             | NO   |     | 0       |       |
+--------------+---------------------+------+-----+---------+-------+

Здесь видно, что каждой паре (текст, тегсет) можно присвоить модератора. moderator_id -- ссылка на поле user_id таблицы users.

TODO:

  • ne_paragraphs,
  • ne_paragraph_comments,
  • ne_entities,
  • ne_tags,
  • ne_entity_tags,
  • ne_mentions,
  • ne_entities_mentions,
  • ne_objects,
  • ne_object_props,
  • ne_object_prop_vals,
  • ne_event_log.

Не используются: facts, fact_types, fact_fields, fact_field_values.