-
Notifications
You must be signed in to change notification settings - Fork 23
Модель данных
Здесь будет описание устройства базы данных 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.