7  Введение в cg3: разрешение морфологической неоднозначности

7.1 Constraint grammar

Парадигма Constraint grammar (CG) (Karlsson и др. 1995; Bick и Didriksen 2015) — это правиловая процедурная система обработки текста, позволяющая решать достаточно большой набор разнообразных задач, такие как

  • разрешение неоднозначности;
  • приписывание тэгов, например, синтаксических ролей;
  • разрешение анафоры;
  • построение деревьев зависимостей;
  • chunking — выделение границ синтаксических единиц (без внутренней структуры, отношений вершины-зависимое и т. п.) (Bick 2013);
  • и многие другие.

CG разрабатывалась начиная с 1980-ых Фредом Кралссоном и другими в университете Хельсинки. С самого начала эта парадигма задумывалась как модуль, который работает с любым текстом на любом языке, так что каждой входной единице сопоставляется некоторая выходная единица (включая пунктуацию и другие топографические особенности). Слово constraint в описании подчеркивает фильтрующую функцию, которую носит CG модуль: на вход мы подаем некоторый текст и морфологические разборы, а CG модуль удаляет, изменяет или модифицирует морфологические разборы.

Важно отметить, что CG правила легко соединяются вместе с морфологическими трансдьюсерами, но они так же работают и в других фреймворках: пока я готовился к этой лекции, я нашел .cg3 файл в репозитории, в котором использовался uniparser Тимофея Архангельского.

Отмечу неожиданное преимущество правиловых фреймворков: их достаточно легко превращать в текст, объясняющий процедуру. Попытку сделать такое для правил CG можно посмотреть в следующей работе (Swanson 2025).

7.2 cg3

В наших лекциях для разрешения морфологической неоднозначности мы будем использовать cg3 (полное название vislcg3). VISL (Visual Interactive Syntax Learning) — подпроект датской компании GrammarSoft ApS, а CG3 обозначает новую, третью, версию реализации парадигмы. Чтобы установить достаточно следующих команд.

```{shell}
$ curl -s https://apertium.projectjj.com/apt/install-nightly.sh | sudo bash
$ sudo apt-get install cg3
```

Первая команда добавляет в операционную систему новый источник программ, он такой же как и для команд lexd и twol. Вторая команда — стандартный способ установить программу. У программы есть большая документация, но начинается она со следующего предостережения:

This manual should be regarded as a guideline. Some of the features or functionality described is either not implemented yet, or does not work exactly as advertised. A good place to see what is and is not implemented is to run the regression test suite as the test names are indicative of features. The individual tests are also good starting points to find examples on how a specific feature works.

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

  • cg-conv — программа для преобразования входных/выходных типов данных (например, между апертиумовским и CG) ;
  • cg-comp компилирует файл CG правил в бинарный файл ;
  • cg-proc обрабатывает входные данные при помощи файла CG правил или его бинарного варианта ;
  • и некоторые другие.

В программе cg3 достаточно много аргументов (см. cg3 -h), нам важно три:

  • -g — указывает файл с CG правилами ;
  • -I — файл для чтения вместо стандартного stdin ;
  • -O — файл для записи результата работы вместо стандартного stdout.

7.2.1 Первый пример

Рассмотрим такой трансдьюсер, записанный в example_1.lexd:

PATTERNS
Preposition
Noun
Verb

LEXICON Preposition
в<pr>:в

LEXICON Noun
город<n><nom><sg>:город
город<n><acc><sg>:город
поезд<n><nom><sg>:поезд

LEXICON Verb
ехать<v><npst><p3><sg>:едет

Мы его можем скомпилировать в оптимизированный:

```{shell}
$ lexd example_1.lexd | hfst-txt2fst | hfst-invert | hfst-fst2fst -O -o analyzer.hfstol
```

Подадим предложение на вход процессору:

```{shell}
$ echo "Поезд едет в город." | hfst-proc analyzer.hfstol
```
^Поезд/Поезд<n><nom><sg>$ ^едет/ехать<v><npst><p3><sg>$ ^в/в<pr>$ ^город/город<n><acc><sg>/город<n><nom><sg>$.

Апертиумовский формат неудобно читать, так что добавим флаг -C, чтобы hfst-proc преобразовал получившееся в CG формат:

```{shell}
$ echo "Поезд едет в город." | hfst-proc -C analyzer.hfstol
```
"<Поезд>"
    "поезд" n nom sg
"<едет>"
    "ехать" v npst p3 sg
"<в>"
    "в" pr
"<город>"
    "город" n acc sg
    "город" n nom sg

Мы видим, что форма город имеет два возможных разбора. Кроме того мы видим, что один из разборов точно неправильный. Для того чтобы убрать ненужный разбор, создадим следующий файл example_1.cg3:

REMOVE (nom) IF (-1C (pr)) ;

Применим написанное правило к нашему трансдьюсеру:

```{shell}
$ echo "Поезд едет в город." | hfst-proc -C analyzer.hfstol | cg3 -g example_1.cg3
```
Warning: No soft or hard delimiters defined in grammar. Hard limit of 500 cohorts may break windows in unintended places.
"<Поезд>"
    "поезд" n nom sg
"<едет>"
    "ехать" v npst p3 sg
"<в>"
    "в" pr
"<город>"
    "город" n acc sg

Таким образом мы избавились от ненужного разбора. В следующих разделах мы углубимся в возможности cg3.

7.2.2 Структура .cg3 файла

Файлы с CG правилами принято записывать в файлы с расширениями .cg3 или .rlx. В принципе никакой необходимой структуры такие файлы не предполагают, однако для читаемости имеет смысл делить логические фрагменты на разделы SECTION. Такие разделы могут иметь названия. Кроме того можно вводить специальные разделы BEFORE-SECTIONS и AFTER-SECTIONS, которые будут запускаться до и перед разделами SECTION. Также для отлаживания кода полезно знать команду END, после которой правила из файла не читаются. Содержание файла обычно заключается в наборе команд для декларации переменных при помощи команд LIST и SET и манипуляции

  • с тегами;
  • чтениями;
  • зависимостями;
  • отношениями;
  • единицами анализа (в формализме CG их называют когортами, cohort);
  • и др.

Комментарии отделяются при помощи знака #.

Кроме того, есть некоторый набор настроек для чтения грамматики, который добавляется при помощи команды OPTIONS.

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

```{cg3}
DELIMITERS = "<.>" "<!>" "<?>" "<...>";
OPTIONS += addcohort-attach ;

<...>

# важный комментарий

LIST Det = art ;
LIST PRON = prde prps prn ;
SET Sem/Time = Month | Months | Year | Century | Season | Seasons | TimeOfDay ; 

<...>

1SECTION

REMOVE Dat IF (NOT 0 Anim OR Cog OR Ant) (NOT 0 Pron) ;
SELECT Gen IF (0C N) (-1C A OR Det) ;

<...>

2SECTION some-other-forms ;

REMOVE Imper IF (1C Fin) ;

<...>
```
1
Если раздел SECTION не поименована, то можно не ставить точки с запятой.
2
Если раздел SECTION поименован, то а) в названии не должно быть пробелов б) строка должна заканчиваться точкой с запятой.

7.2.3 Списки и операции над ними

Списки это поименованные переменные, содержащие некоторые объекты. Из примера выше понятно, что объекты в списке разделяются пробелом, а конец списка обозначается точкой с запятой.

Один из первых списков, который даже имеет отдельное название — раздел DELIMITERS. В данном разделе перечисляются единицы, которые останавливают парсинг. Списки DELIMITERS в файлах .cg3 на GitHub не отличаются креативностью и обычно содержат вполне ожидаемые

```{cg3}
DELIMITERS = "<.>" "<!>" "<?>" "<...>" ;
```

Однако, может быть для ваших задач будет иметь смысл добавить какой-то знак переноса строки, на случай, если будет отсутвовать пунктуация в конце абзаца. Важно отметить, что при отсутствии раздела DELIMITERS в файле с правилами программа cg3 выдает предупреждение.

Кроме того, существует раздел SOFT-DELIMITERS, который я видел заполненный только запятой "<,>".

Остальные списки задаются при помощи команды LIST, после которой следует дать переменной некоторое имя, например,

```{cg3}
LIST number = sg pl ;
1LIST number += du ;
```
1
Операция += добавляет тег или несколько тегов к уже созданному списку, хотя и не понятно, что мешало сразу все задать.

Функция SET также позволяет задать списки, однако делает это на основании одной из следующих операций или их сочетаний

  • объединение OR и |;
```{cg3}
LIST A = a b c d ;
LIST B = c d e f ;
SET new_set = a OR b ;
```
a b c d e f
  • прямое произведение +;
```{cg3}
LIST A = N ;
LIST B = sg du pl ;
SET new_set = a + b ;
```
(N sg) (N du) (N pl)
  • исключение -;
```{cg3}
LIST A = a b c d ;
LIST B = c d e f ;
SET new_set = a - b ;
```
a b
  • разность \;
```{cg3}
LIST A = a b c d ;
LIST B = c d e f ;
SET new_set = a \ b ;
```
a b
  • симмитричная разность (U+2206);
```{cg3}
LIST A = a b c d ;
LIST B = c d e f ;
SET new_set = a ∆ b ;
```
a b e f
  • пересечение ;
```{cg3}
LIST A = a b c d ;
LIST B = c d e f ;
SET new_set = a ∩ b ;
```
c d

7.2.4 Операции с чтениями

  • SELECT удаляет все чтения из когорты, кроме тех, что подходят к контексту.
SELECT <target> [contextual_tests] ;
```{shell}
$ cat example_SELECT.cg3
```
SELECT ("город" n acc sg) ;
```{shell}
$ echo "Поезд едет в город." | hfst-proc -C analyzer.hfstol | cg3 -g example_SELECT.cg3
```
Warning: No soft or hard delimiters defined in grammar. Hard limit of 500 cohorts may break windows in unintended places.
"<Поезд>"
    "поезд" n nom sg
"<едет>"
    "ехать" v npst p3 sg
"<в>"
    "в" pr
"<город>"
    "город" n acc sg
  • REMOVE удаляет чтение из когорты. Может удалить даже последнее чтение.
REMOVE <target> [contextual_tests] ;
```{shell}
$ cat example_REMOVE.cg3
```
REMOVE ("город" n nom sg) ;
```{shell}
$ echo "Поезд едет в город." | hfst-proc -C analyzer.hfstol | cg3 -g example_REMOVE.cg3
```
Warning: No soft or hard delimiters defined in grammar. Hard limit of 500 cohorts may break windows in unintended places.
"<Поезд>"
    "поезд" n nom sg
"<едет>"
    "ехать" v npst p3 sg
"<в>"
    "в" pr
"<город>"
    "город" n acc sg
  • RESTORE восстанавливает чтение удаленное при помощи SELECT или REMOVE. Не работает, если все чтения были удалены.
RESTORE <restore_target> <target> [contextual_tests] ;
```{shell}
$ cat example_RESTORE.cg3
```
SELECT ("город" n acc sg) ;
RESTORE ("город" n nom sg) ("город" n);
```{shell}
$ echo "Поезд едет в город." | hfst-proc -C analyzer.hfstol | cg3 -g example_RESTORE.cg3
```
Warning: No soft or hard delimiters defined in grammar. Hard limit of 500 cohorts may break windows in unintended places.
"<Поезд>"
    "поезд" n nom sg
"<едет>"
    "ехать" v npst p3 sg
"<в>"
    "в" pr
"<город>"
    "город" n acc sg
    "город" n nom sg

7.2.5 Операции с тегами

Первая группа команд, которую мы рассмотрим, измененяет теги.

  • ADD — добавляет тэг к набору тегов, разрешая в дальнейшем одной из операций MAP, ADD или REPLACE работать с анализируемым чтением ;
ADD <tags> [BEFORE|AFTER <tags>] <target> [contextual_tests] ;
```{shell}
$ cat example_ADD.cg3
```
ADD (NEW_TAG) (v) ;
ADD (ANOTHER_TAG) AFTER (npst) (v) ;
```{shell}
$ echo "Поезд едет в город." | hfst-proc -C analyzer.hfstol | cg3 -g example_ADD.cg3
```
Warning: No soft or hard delimiters defined in grammar. Hard limit of 500 cohorts may break windows in unintended places.
"<Поезд>"
    "поезд" n nom sg
"<едет>"
    "ехать" v npst ANOTHER_TAG p3 sg NEW_TAG
"<в>"
    "в" pr
"<город>"
    "город" n acc sg
    "город" n nom sg
  • MAP — добавляет тэг к набору тегов, запрещая в дальнейшем одной из операций MAP, ADD или REPLACE работать с анализируемым чтением ;
MAP <tags> [BEFORE|AFTER <tags>] <target> [contextual_tests] ;
```{shell}
$ cat example_MAP.cg3
```
MAP (NEW_TAG) (v) ;
ADD (ANOTHER_TAG) AFTER (npst) (v) ;
```{shell}
$ echo "Поезд едет в город." | hfst-proc -C analyzer.hfstol | cg3 -g example_MAP.cg3
```
Warning: No soft or hard delimiters defined in grammar. Hard limit of 500 cohorts may break windows in unintended places.
"<Поезд>"
    "поезд" n nom sg
"<едет>"
    "ехать" v npst p3 sg NEW_TAG
"<в>"
    "в" pr
"<город>"
    "город" n acc sg
    "город" n nom sg
  • SUBSTITUTE — заменяет один набор тэгов на другой набор тэгов. Может быть сделана нулевая замена, для этого следует использовать знак *.
SUBSTITUTE <locate tags> <replacement tags> <target> [contextual_tests] ;
```{shell}
$ cat example_SUBSTITUTE.cg3
```
SUBSTITUTE (sg) (SG) (v) ;
SUBSTITUTE (nom) (*) ("поезд") ;
```{shell}
$ echo "Поезд едет в город." | hfst-proc -C analyzer.hfstol | cg3 -g example_SUBSTITUTE.cg3
```
Warning: No soft or hard delimiters defined in grammar. Hard limit of 500 cohorts may break windows in unintended places.
"<Поезд>"
    "поезд" n sg
"<едет>"
    "ехать" v npst p3 SG
"<в>"
    "в" pr
"<город>"
    "город" n acc sg
    "город" n nom sg
  • REPLACE — оставляет только выделенный тэг, а остальные теги убирает, запрещая в дальнейшем одной из операций MAP, ADD или REPLACE работать с анализируемым чтением ;
```{shell}
$ cat example_REPLACE.cg3
```
REPLACE (NEW_TAG) (v) ;
```{shell}
$ echo "Поезд едет в город." | hfst-proc -C analyzer.hfstol | cg3 -g example_REPLACE.cg3
```
Warning: No soft or hard delimiters defined in grammar. Hard limit of 500 cohorts may break windows in unintended places.
"<Поезд>"
    "поезд" n nom sg
"<едет>"
    "ехать" NEW_TAG
"<в>"
    "в" pr
"<город>"
    "город" n acc sg
    "город" n nom sg
  • UNMAP снимает блокирующее поведение команд MAP и REPLACE. По-умолчанию UNMAP работает только с единственными чтениями, но если хочется чтобы блокировка была снята с нескольких чтений одной формы, стоит добавить UNSAFE.
UNMAP <target> [contextual_tests] ;
```{shell}
$ cat example_UNMAP.cg3
```
MAP (NEW_TAG) (v) ;
UNMAP (NEW_TAG) ;
ADD (ANOTHER_TAG) AFTER (npst) (v) ;
```{shell}
$ echo "Поезд едет в город." | hfst-proc -C analyzer.hfstol | cg3 -g example_UNMAP.cg3
```
Warning: No soft or hard delimiters defined in grammar. Hard limit of 500 cohorts may break windows in unintended places.
"<Поезд>"
    "поезд" n nom sg
"<едет>"
    "ехать" v npst ANOTHER_TAG p3 sg NEW_TAG
"<в>"
    "в" pr
"<город>"
    "город" n acc sg
    "город" n nom sg
```{shell}
$ cat example_UNSAFE.cg3
```
MAP (NEW_TAG) ("город" n) ;
UNMAP UNSAFE (NEW_TAG) ;
ADD (ANOTHER_TAG) ("город" n) ;
```{shell}
$ echo "Поезд едет в город." | hfst-proc -C analyzer.hfstol | cg3 -g example_UNSAFE.cg3
```
Warning: No soft or hard delimiters defined in grammar. Hard limit of 500 cohorts may break windows in unintended places.
"<Поезд>"
    "поезд" n nom sg
"<едет>"
    "ехать" v npst p3 sg
"<в>"
    "в" pr
"<город>"
    "город" n acc sg NEW_TAG ANOTHER_TAG
    "город" n nom sg NEW_TAG ANOTHER_TAG
  • APPEND добавляет новое прочтение к некоторой единице. Следите, чтобы среди добавляемого была начальная форма.
APPEND <tags> <target> [contextual_tests] ;
```{shell}
$ cat example_APPEND.cg3
```
APPEND ("поезд" n nom sg NEW_READING) ("поезд" n) ;
```{shell}
$ echo "Поезд едет в город." | hfst-proc -C analyzer.hfstol | cg3 -g example_APPEND.cg3
```
Warning: No soft or hard delimiters defined in grammar. Hard limit of 500 cohorts may break windows in unintended places.
"<Поезд>"
    "поезд" n nom sg
    "поезд" n nom sg NEW_READING
"<едет>"
    "ехать" v npst p3 sg
"<в>"
    "в" pr
"<город>"
    "город" n acc sg
    "город" n nom sg
  • COPY копирует прочтение и добавляет к нему тег или набор тегов.
COPY <extra tags> [EXCEPT <except tags>] [BEFORE|AFTER <tags>] <target> [contextual_tests] ;
```{shell}
$ cat example_COPY.cg3
```
COPY (NEW) AFTER (n) ("поезд" n nom sg);
```{shell}
$ echo "Поезд едет в город." | hfst-proc -C analyzer.hfstol | cg3 -g example_COPY.cg3
```
Warning: No soft or hard delimiters defined in grammar. Hard limit of 500 cohorts may break windows in unintended places.
"<Поезд>"
    "поезд" n nom sg
    "поезд" n NEW nom sg
"<едет>"
    "ехать" v npst p3 sg
"<в>"
    "в" pr
"<город>"
    "город" n acc sg
    "город" n nom sg

7.2.6 Контекстные тесты

Большинство примеров операций выше имели в своих шаблонах раздел [contextual_tests], который делает инструменты значительно функциональнее. Во-первых, можно задать контекст справа или слева:

```{cg3}
1... (1 N)
2... (-2* V)
```
1
Есть ли хотя бы одно чтение с тегом N в следующей когорте?
2
Есть ли хотя бы одно чтение с тегом V две или более когорты назад?

После номера можно добавить C, тогда тест будет исполнятся только в случае если все чтения в когорте соответствуют условию.

```{cg3}
1... (1С N)
2... (-2С* V)
```
1
Все ли чтения имеют тег N в следующей когорте?
2
Все ли чтения имеют тег V две или более когорты назад?

Результат теста можно перевернуть, добавив NOT:

```{cg3}
1... (NOT 1 N)
2... (NOT -2* V)
```
1
В следующей когорте не должно быть чтения с тегом N.
2
Две и более когорты назад не должно быть чтения с тегом V.
Задание 07_01

Напишите cg3 правила, которые в нашем примере удаляют лишнее чтение и разметят синтаксические роли S, O и V. Избавьтесь также от предупреждения про разделители.

"<Поезд>"
    "поезд" n nom sg S
"<едет>"
    "ехать" v npst p3 sg V
"<в>"
    "в" pr
"<большой>"
    "*большой"
"<город>"
    "город" n acc sg O

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