6  Операции с трансдьюсерами

Посмотрим на следующий трансдьюсер.

```{lexd}
PATTERNS
Noun [<N>:] Suffix[-adj] | (Suffix[adj] AdjInflection)
Noun [<N>:] NounInflection

LEXICON Noun
ночь
печь

LEXICON Suffix
<dim>:ка
<adj>:н[adj]

LEXICON AdjInflection
<m><sg><nom>:ой

LEXICON NounInflection
<nom>:
<acc>:
```
```{twol}
Alphabet
  а е й к н о п ч ь ь:0;

Rules

"чк чн пишется без ь"
! например, ночьной -> ночной или печька -> печка

ь:0 <=> _ к;
        _ н;
```
```{shell}
$ lexd example.lexd | hfst-txt2fst -o lexd.hfst
$ hfst-twolc -q example.twol -o twol.hfst
$ hfst-compose-intersect lexd.hfst twol.hfst -o generator.hfst
```

6.1 Некоторые команды hfst

В большинстве команд ниже работают флаги -i — входящий файл; -o — исходящий файл. Если -o отсутствует, то результат печатается в консоль.

6.1.1 hfst-fst2strings

Печатает формы:

```{shell}
$ hfst-fst2strings generator.hfst
```
ночь<N><dim>:ночка
ночь<N><adj><m><sg><nom>:ночной
ночь<N><nom>:ночь
ночь<N><acc>:ночь
печь<N><dim>:печка
печь<N><adj><m><sg><nom>:печной
печь<N><nom>:печь
печь<N><acc>:печь

Также существует программа hfst-expand, которая делает то же самое.

6.1.2 hfst-fst2txt

Печатает трансдьюсер в AT&T формате:

```{shell}
$ hfst-fst2txt generator.hfst
```
0   1   @0@ @0@ 0.000000
1   2   н   н   0.000000
1   3   п   п   0.000000
2   4   о   о   0.000000
3   4   е   е   0.000000
4   5   ч   ч   0.000000
5   6   ь   @0@ 0.000000
5   7   ь   ь   0.000000
6   8   <N> @0@ 0.000000
7   9   <N> @0@ 0.000000
8   10  <nom>   @0@ 0.000000
8   10  <acc>   @0@ 0.000000
8   11  <dim>   к   0.000000
8   12  <adj>   н   0.000000
9   13  <nom>   @0@ 0.000000
9   13  <acc>   @0@ 0.000000
11  14  @0@ а   0.000000
12  15  <m> о   0.000000
13  16  @0@ @0@ 0.000000
14  16  @0@ @0@ 0.000000
15  17  <sg>    й   0.000000
16  0.000000
17  14  <nom>   @0@ 0.000000

6.1.3 hfst-summarise

Данная программа печатает саммари для трансдьюсера.

```{shell}
$ hfst-summarise generator.hfst
```
name: "compose(text(<stdin>), intersect(examples/twol.hfst))"
fst type: OpenFST
arc type: tropical
input symbol table: yes
output symbol table: yes
# of states: 18
# of arcs: 22
initial state: 0
# of final states: 1
# of input/output epsilons: 3
# of input epsilons: 4
# of output epsilons: 11
# of ... accessible states: ???
# of ... coaccessible states: ???
# of ... connected states: ???
# of ... strongly conn components: ???
expanded: ???
mutable: yes
acceptor: no
input deterministic: no
output deterministic: no
input label sorted: ???
output label sorted: ???
weighted: yes
cyclic: no
cyclic at initial state: no
topologically sorted: ???
accessible: ???
coaccessible: ???
string: ???
minimised: ???

Read 1 transducers in total.

Если вам не хочется смотреть на выдачу целиком, можно совмещать с grep:

```{shell}
$ hfst-summarise generator.hfst | grep cyclic
```
cyclic: no
cyclic at initial state: no

6.1.4 hfst-name

Если хочется, чтобы у трансдьюсера было осмысленное имя, то можно при создании дать ему имя при помощи функции hfst-name или hfst-edit-metadata. Посмотреть имя трансдьюсера можно при помощи аргумента -p (и некоторых других):

```{shell}
$ hfst-compose-intersect lexd.hfst twol.hfst | hfst-name -n 'the best fst ever' -o generator.hfst
$ hfst-name -p generator.hfst
```
"the best fst ever"

Программа hfst-edit-metadata позволяет настроить, например, следующие поля:

  • --add=author=...
  • --add=licence=...
  • --add=name='...'

6.1.5 hfst-dump-alphabets

Печатает алфавит:

```{shell}
$ hfst-dump-alphabets generator.hfst
```
<N>
<acc>
<adj>
<dim>
<m>
<nom>
<sg>
@_EPSILON_SYMBOL_@
@_IDENTITY_SYMBOL_@
@_UNKNOWN_SYMBOL_@
а
е
й
к
н
о
п
ч
ь
<N>
<acc>
<adj>
<dim>
<m>
<nom>
<sg>
@_EPSILON_SYMBOL_@
а
е
й
к
н
о
п
ч
ь

Мне не очень понятно, почему он печатает все два раза, однако этот фрагмент документации намекает мне, что это решаемая проблема:

Alphabet dump options:
  -1, --exclude-seen       Ignore alphabets seen in automaton
  -2, --exclude-metadata   Ignore alphabets from headers
```{shell}
$ hfst-dump-alphabets -1 generator.hfst
```
<N>
<acc>
<adj>
<dim>
<m>
<nom>
<sg>
@_EPSILON_SYMBOL_@
@_IDENTITY_SYMBOL_@
@_UNKNOWN_SYMBOL_@
а
е
й
к
н
о
п
ч
ь

Также при помощи программы grep полезно проверить какие-нибудь символы:

```{shell}
$ hfst-dump-alphabets -1 generator.hfst | grep ъ
```
```{shell}
$ hfst-dump-alphabets -1 generator.hfst | grep к
```
к

6.1.6 hfst-substitute

Заменяет единицы в трансдьюсере (-f from; -t to):

```{shell}
$ hfst-substitute -f "ч" -t "Ч" -i generator.hfst | hfst-fst2strings
```
ноЧь<N><dim>:ноЧка
ноЧь<N><adj><m><sg><nom>:ноЧной
ноЧь<N><nom>:ноЧь
ноЧь<N><acc>:ноЧь
пеЧь<N><dim>:пеЧка
пеЧь<N><adj><m><sg><nom>:пеЧной
пеЧь<N><nom>:пеЧь
пеЧь<N><acc>:пеЧь

Можно заменять конкретные соответствия (строчки в AT&T записи).

```{shell}
$ hfst-substitute -f "ь:@0@" -t "ъ:ъ" -i generator.hfst | hfst-fst2strings
```
ночъ<N><dim>:ночъка
ночъ<N><adj><m><sg><nom>:ночъной
ночь<N><nom>:ночь
ночь<N><acc>:ночь
ночъ<N><dim>:ночъка
ночъ<N><adj><m><sg><nom>:ночъной
печъ<N><dim>:печъка
печъ<N><adj><m><sg><nom>:печъной
печь<N><nom>:печь
печь<N><acc>:печь
печъ<N><dim>:печъка
печъ<N><adj><m><sg><nom>:печъной

Важно отметить, что в таком случае не работает расписывание ъ -> ъ:ъ.

Если хочется чего-то удалить, то следует использовать запись @0@ или @_EPSILON_SYMBOL_@:

```{shell}
$ hfst-substitute -f "ч" -t "@0@" -i generator.hfst | hfst-fst2strings
```
ноь<N><dim>:нока
ноь<N><adj><m><sg><nom>:ноной
ноь<N><nom>:ноь
ноь<N><acc>:ноь
пеь<N><dim>:пека
пеь<N><adj><m><sg><nom>:пеной
пеь<N><nom>:пеь
пеь<N><acc>:пеь

Замены можно делать по списку, для этого нужно иметь .tsv файл. Ниже содержимое файла label_file.tsv:

<N> @_EPSILON_SYMBOL_@
<acc>   @_EPSILON_SYMBOL_@
<adj>   @_EPSILON_SYMBOL_@
<dim>   @_EPSILON_SYMBOL_@
<m> @_EPSILON_SYMBOL_@
<nom>   @_EPSILON_SYMBOL_@
<sg>    @_EPSILON_SYMBOL_@

Мы можем удалить все теги и получить соответствие основы-формы:

```{shell}
$ hfst-substitute -F label_file.tsv -i generator.hfst | hfst-fst2strings
```
ночь:ночка
ночь:ночной
ночь
ночь
печь:печка
печь:печной
печь
печь

Опытным путем я выяснил, что это команда не будет работать с записью @0@.

6.1.7 hfst-lookup

```{shell}
$ echo "ночь<N><dim>" | hfst-lookup generator.hfst
```
hfst-lookup: Warning: It is not possible to perform fast lookups with OpenFST, std arc, tropical semiring format automata.

Using HFST basic transducer format and performing slow lookups

> ночь<N><dim>   ночка   0,000000



> 

Кроме того, можно на вход подавать несколько форм, разделяя их переносом строки (\n), или программой cat читать из файла:

```{shell}
$ echo "ночь<N><adj><m><sg><nom>\nночь<N><dim>" | hfst-lookup generator.hfst
```
hfst-lookup: Warning: It is not possible to perform fast lookups with OpenFST, std arc, tropical semiring format automata.

Using HFST basic transducer format and performing slow lookups

> ночь<N><adj><m><sg><nom> ночной  0,000000



> ночь<N><dim>   ночка   0,000000



> 

Важно также отметить, что при помощи флага -O можно задать тип, в котором нужно печатать результат:

  • xerox
```{shell}
$ echo "ночь<N><adj><m><sg><nom>\nночь<N><dim>" | hfst-lookup -O xerox generator.hfst
```
hfst-lookup: Warning: It is not possible to perform fast lookups with OpenFST, std arc, tropical semiring format automata.

Using HFST basic transducer format and performing slow lookups

> ночь<N><adj><m><sg><nom> ночной  0,000000



> ночь<N><dim>   ночка   0,000000



> 
  • cg — Constraint Grammar
```{shell}
$ echo "ночь<N><adj><m><sg><nom>\nночь<N><dim>" | hfst-lookup -O cg generator.hfst
```
hfst-lookup: Warning: It is not possible to perform fast lookups with OpenFST, std arc, tropical semiring format automata.

Using HFST basic transducer format and performing slow lookups

> "<ночь<N><adj><m><sg><nom>>"

    ""ночной    0,000000



> "<ночь<N><dim>>"

    ""ночка 0,000000



> 
  • apertium
```{shell}
$ echo "ночь<N><adj><m><sg><nom>\nночь<N><dim>" | hfst-lookup -O apertium generator.hfst
```
hfst-lookup: Warning: It is not possible to perform fast lookups with OpenFST, std arc, tropical semiring format automata.

Using HFST basic transducer format and performing slow lookups

> ^ночь<N><adj><m><sg><nom>/*ночь<N><adj><m><sg><nom>$

> ^ночь<N><dim>/*ночь<N><dim>$

> 

6.1.8 hfst-invert

Это очень полезная функция, так как она позволяет переворачивать трансдьюсер. В прошлом разделе мы видели генератор, а что если мы хотим наоборот - не генератор, а анализатор?

```{shell}
$ hfst-invert generator.hfst -o analyzer.hfst
$ echo "ночка" | hfst-lookup analyzer.hfst
```
hfst-lookup: Warning: It is not possible to perform fast lookups with OpenFST, std arc, tropical semiring format automata.

Using HFST basic transducer format and performing slow lookups

> ночка  ночь<N><dim>    0,000000



> 

Любой трансдьюсер обратим — и это очень классно: вы делаете один объект, а он и анализатор, и генератор.

6.1.9 hfst-fst2fst

Возможно, Вы заметили предупреждение, которое возвращает hfst-lookup. Оптимизированный формат можно сделать при помощи программы hfst-fst2fst, он полезен и для других программ hfst.

```{shell}
$ hfst-fst2fst -O analyzer.hfst -o analyzer.hfstol
$ echo "ночка" | hfst-lookup analyzer.hfstol
```
hfst-lookup: Warning: It is not possible to perform fast lookups with OpenFST, std arc, tropical semiring format automata.

Using HFST basic transducer format and performing slow lookups

> ночка  ночь<N><dim>    0,000000



> 

Программа hfst-lookup больше не выдает предупреждения. Еще более ожидаемый вывод будет, если использовать программу hfst-optimized-lookup:

```{shell}
$ echo "ночка" | hfst-optimized-lookup analyzer.hfstol
```
ночка   ночь<N><dim>

6.1.10 hfst-proc и hfst-proc2

Программа hfst-proc парсит текст. Важно, чтобы трансдьюсер был в оптимизированной форме, т. е. .hfstol. Обратите внимание на то, как экранируется восклицательный знак.

```{shell}
$ echo "какая-то Ночка и ПЕЧЬ\!" | hfst-proc analyzer.hfstol
```
^какая/*какая$-^то/*то$ ^Ночка/Ночь<N><dim>$ ^и/*и$ ^ПЕЧЬ/ПЕЧЬ<N><acc>/ПЕЧЬ<N><nom>$!

Разные аргументы форматируют по-разному выдачу:

  • -C/--cg Constraint Grammar
```{shell}
$ echo "какая-то Ночка и ПЕЧЬ\!" | hfst-proc -C analyzer.hfstol
```
"<какая>"
    "*какая"
"<то>"
    "*то"
"<Ночка>"
    "ночь"  N dim
"<и>"
    "*и"
"<ПЕЧЬ>"
    "печь"  N acc
    "печь"  N nom
  • -x Xerox
```{shell}
$ echo "какая-то Ночка и ПЕЧЬ\!" | hfst-proc -x analyzer.hfstol
```
какая   +?

то  +?

Ночка   Ночь<N><dim>

и   +?

ПЕЧЬ    ПЕЧЬ<N><acc>
ПЕЧЬ    ПЕЧЬ<N><nom>
  • -w возвращать словарный вид слова, а не тот вид, в котором он встретился в тексте.
```{shell}
$ echo "какая-то Ночка и ПЕЧЬ\!" | hfst-proc -xw analyzer.hfstol
```
какая   +?

то  +?

Ночка   ночь<N><dim>

и   +?

ПЕЧЬ    печь<N><acc>
ПЕЧЬ    печь<N><nom>
  • флаг -N позволяет настроить, сколько форм будет возвращаться при анализе, например ниже исчез разбор ПЕЧЬ<N><nom>.
```{shell}
$ echo "какая-то Ночка и ПЕЧЬ\!" | hfst-proc -xN 1 analyzer.hfstol
```
какая   +?

то  +?

Ночка   Ночь<N><dim>

и   +?

ПЕЧЬ    ПЕЧЬ<N><acc>

Кроме того, существует программа hfst-proc2, которая, кажется, возвращает только то, что анализатор может проанализировать:

```{shell}
$ echo "какая-то ночка и печь\!" | hfst-proc2 analyzer.hfstol
```
ночка
печь

Флаг -a позволяет напечатать все:

```{shell}
$ echo "какая-то ночка и печь\!" | hfst-proc2 -a analyzer.hfstol
```
какая-то 
ночка
 и 
печь
\!

Флаг -C печатает в CoNLL-U:

```{shell}
$ echo "какая-то ночка и печь\!" | hfst-proc2  analyzer.hfstol
```
1   ночка   _   _   _   _   _   _   _   ночь<N><dim>
2   печь    _   _   _   _   _   _   _   печь<N><nom>

6.1.11 hfst-pair-test

Эта программа проверяет twol:

```{shell}
$ echo "н о ч к а" | hfst-pair-test twol.hfst
```
Test passed.
```{shell}
$ echo "н о ч ь к а" | hfst-pair-test twol.hfst
```
Rule "чк чн пишется без ь" fails:
#:0 н о ч HERE ---> ь к а #:0 

FAIL: н о ч ь к а REJECTED

Test failed.
```{shell}
$ echo "н о ч ь:0 к а" | hfst-pair-test twol.hfst
```
Test passed.

Тесты могут быть отрицательными:

```{shell}
$ echo "н о ч ь к а" | hfst-pair-test -N twol.hfst
```
Test passed.
```{shell}
$ echo "н о ч к а" | hfst-pair-test -N twol.hfst
```
FAIL: н о ч к а PASSED

Test failed.

Вообще стоит обратить внимание на мануал этой программы: она подробнее многих других программ hfst.

6.1.12 hfst-kill-paths

Убирает все пути, содержащие некоторый символ:

```{shell}
$ hfst-kill-paths -S о generator.hfst | hfst-fst2strings
```
печь<N><acc>:печь
печь<N><nom>:печь
печь<N><dim>:печка

6.2 Некоторые операции с двумя трансдьюсерами

Создадим себе второй очень похожий трансдьюсер:

```{shell}
$ hfst-substitute -f "о" -t "О" -i generator.hfst | hfst-name -n 'transducer 2' -o generator2.hfst
```

6.2.1 hfst-compare

Сравнивает трансдьюсеры

```{shell}
$ hfst-compare generator.hfst generator.hfst
```
the best fst ever == the best fst ever
```{shell}
$ hfst-compare generator.hfst generator2.hfst
```
the best fst ever != transducer 2
```{shell}
$ hfst-compare generator.hfst analyzer.hfst
```
the best fst ever != invert(the best fst ever)

6.2.2 hfst-concatenate

Конкатенация трансдьюсеров - последнее состояние одного трансдьюсера становится начальным состоянием второго.

```{shell}
hfst-concatenate generator.hfst generator2.hfst | hfst-fst2strings | head 
```
ночь<N><dim>нОчь<N><dim>:ночканОчка
ночь<N><dim>нОчь<N><adj><m><sg><nom>:ночканОчнОй
ночь<N><dim>нОчь<N><nom>:ночканОчь
ночь<N><dim>нОчь<N><acc>:ночканОчь
ночь<N><dim>печь<N><dim>:ночкапечка
ночь<N><dim>печь<N><adj><m><sg><nom>:ночкапечнОй
ночь<N><dim>печь<N><nom>:ночкапечь
ночь<N><dim>печь<N><acc>:ночкапечь
ночь<N><adj><m><sg><nom>нОчь<N><dim>:ночнойнОчка
ночь<N><adj><m><sg><nom>нОчь<N><adj><m><sg><nom>:ночнойнОчнОй

6.2.3 hfst-conjunct или hfst-intersect

Пересечение трансдьюсеров: выделяет общее для двух трансдьюсеров.

```{shell}
hfst-conjunct generator.hfst generator2.hfst | hfst-fst2strings
```
печь<N><dim>:печка
печь<N><nom>:печь
печь<N><acc>:печь

6.2.4 hfst-disjunct или hfst-union

Объединение трансдьюсеров.

```{shell}
hfst-disjunct generator.hfst generator2.hfst | hfst-fst2strings
```
ночь<N><dim>:ночка
ночь<N><adj><m><sg><nom>:ночной
ночь<N><nom>:ночь
ночь<N><acc>:ночь
печь<N><dim>:печка
печь<N><adj><m><sg><nom>:печной
печь<N><nom>:печь
печь<N><acc>:печь
нОчь<N><dim>:нОчка
нОчь<N><adj><m><sg><nom>:нОчнОй
нОчь<N><nom>:нОчь
нОчь<N><acc>:нОчь
печь<N><dim>:печка
печь<N><adj><m><sg><nom>:печнОй
печь<N><nom>:печь
печь<N><acc>:печь

6.2.5 hfst-subtract или hfst-minus

Вычитание трансдьюсеров.

```{shell}
hfst-subtract generator.hfst generator2.hfst | hfst-fst2strings
```
ночь<N><dim>:ночка
ночь<N><adj><m><sg><nom>:ночной
ночь<N><nom>:ночь
ночь<N><acc>:ночь
печь<N><adj><m><sg><nom>:печной

6.3 Создание транслитераторов

Для того, чтобы создать транслитерацию, давайте запишем наши соответствия в такой файл:

```{lexd}
PATTERNS
segments

LEXICON segments
а:a
е:e
й:j
к:k
н:n
о:o
п:p
ч:tʃ
ь:
<N>
<acc>
<adj>
<dim>
<m>
<nom>
<sg>
```
```{shell}
$ lexd correspondences.lexd | hfst-txt2fst -o correspondences.hfst
$ hfst-fst2strings correspondences.hfst
```
а:a
е:e
й:j
к:k
н:n
о:o
п:p
ч:tɕ
ь:
<N>
<acc>
<adj>
<dim>
<m>
<nom>
<sg>

Получившийся трансдьюсер делает достаточно скучную вещь: если ему дать один символ он его переведет, а вот большее количество — нет:

```{shell}
echo "ч" | hfst-lookup correspondences.hfst
```
hfst-lookup: Warning: It is not possible to perform fast lookups with OpenFST, std arc, tropical semiring format automata.

Using HFST basic transducer format and performing slow lookups

> ч  tɕ  0,000000



> 
```{shell}
echo "че" | hfst-lookup correspondences.hfst
```
hfst-lookup: Warning: It is not possible to perform fast lookups with OpenFST, std arc, tropical semiring format automata.

Using HFST basic transducer format and performing slow lookups

> че че+?    inf



> 

Чтобы наш трансдьюсер делал преобразования во всей строке, нужно его зациклить при помощи программы hfst-repeat:

```{shell}
$ lexd correspondences.lexd | hfst-txt2fst | hfst-repeat -f 1 -o correspondences.hfst
```
```{shell}
echo "ч" | hfst-lookup correspondences.hfst
```
hfst-lookup: Warning: It is not possible to perform fast lookups with OpenFST, std arc, tropical semiring format automata.

Using HFST basic transducer format and performing slow lookups

> ч  tɕ  0,000000



> 
```{shell}
echo "че" | hfst-lookup correspondences.hfst
```
hfst-lookup: Warning: It is not possible to perform fast lookups with OpenFST, std arc, tropical semiring format automata.

Using HFST basic transducer format and performing slow lookups

> че tɕe 0,000000



> 

Чтобы соединить транслитератор с уже готовыми трансдьюсерами, достаточно сделать композицию:

```{shell}
hfst-compose analyzer.hfst correspondences.hfst | hfst-fst2strings
```
ночка:notɕ<N><dim>
ночной:notɕ<N><adj><m><sg><nom>
ночь:notɕ<N><nom>
ночь:notɕ<N><acc>
печка:petɕ<N><dim>
печной:petɕ<N><adj><m><sg><nom>
печь:petɕ<N><nom>
печь:petɕ<N><acc>
```{shell}
hfst-compose generator.hfst correspondences.hfst | hfst-fst2strings
```
ночь<N><dim>:notɕka
ночь<N><adj><m><sg><nom>:notɕnoj
ночь<N><nom>:notɕ
ночь<N><acc>:notɕ
печь<N><dim>:petɕka
печь<N><adj><m><sg><nom>:petɕnoj
печь<N><nom>:petɕ
печь<N><acc>:petɕ

А что если в ходе транслитерация графема чувствительна к контексту? Например, русское <е> может транскрибироваться как [e] (мел) и как [je] (еда). Я полагаю, у решения этой проблемы есть две стратегии:

  • добавлять все соответствия в файл соответствий, например,
...
че:tɕe
пе:pʲe
е:je
...
  • добавлять еще один twol трансдьюсер, который исправляет какие-то вещи
```{lexd}
PATTERNS
segments

LEXICON segments
а:a
е:{E}
й:j
к:k
н:n
о:o
п:p
ч:tɕ
ь:
```
```{twol}
Alphabet
  %{E%}:je
  %{E%}:e
  %{E%}:ʲe
;

Rules
"E в начале слова"
%{E%}:je <=> .#. _ ;

"E после мягких"
%{E%}:e <=> ɕ _ ;
```
```{shell}
$ hfst-twolc -q correspondences_twol.twol -o correspondences_twol.hfst
$ lexd correspondences2.lexd | hfst-txt2fst | hfst-repeat -f 1 | hfst-compose-intersect correspondences_twol.hfst -o correspondences.hfst
$ echo "ечене" | hfst-lookup correspondences.hfst
```
hfst-lookup: Warning: It is not possible to perform fast lookups with OpenFST, std arc, tropical semiring format automata.

Using HFST basic transducer format and performing slow lookups

> ечене  jetɕenʲe    0,000000



> 

6.4 Перевод основы

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

```{lexd}
PATTERNS
translations

LEXICON translations
печь:stove
ночь:night
<N>
<acc>
<adj>
<dim>
<m>
<nom>
<sg>
```
```{shell}
$ lexd ru_en.lexd | hfst-txt2fst | hfst-repeat -f 1 -o ru_en.hfst
$ hfst-compose analyzer.hfst ru_en.hfst | hfst-fst2strings
```
ночка:night<N><dim>
ночной:night<N><adj><m><sg><nom>
ночь:night<N><nom>
ночь:night<N><acc>
печка:stove<N><dim>
печной:stove<N><adj><m><sg><nom>
печь:stove<N><nom>
печь:stove<N><acc>