Лабораторная работа №2. Кодирование текста с помощью алгоритма BPE ================================================================== .. toctree:: :maxdepth: 1 :titlesonly: :caption: Full API lab_2_tokenize_by_bpe.api.rst Дано ---- 1. Текст на русском языке (``assets/text.txt``), который загружен и сохранен в переменную ``text`` в ``start.py``. 2. Секретный зашифрованный текст. 3. Обученный на большом корпусе текстов словарь токенов ``assets/vocab.json``. 4. Нейросетевая языковая модель ``assets/nmt_demo>`` (пример обращения - ``assets/nmt_demo/main.py>``) В ходе выполнения лабораторной работы Вы научитесь предобрабатывать текст при помощи алгоритма `Byte-Pair Encoding `__ таким образом, чтобы суметь получить предсказания от нейросетевой лингвистической модели и оценить ее работу. Терминология ------------ В данной лабораторной работе мы будем оперировать следующими понятиями: .. glossary:: :sorted: Слово Последовательность символов между пробельными символами. Предобработанное слово Слово, разбитое на токены. Частота слова Количество вхождений слова в текст. Токен Часть, выделяемая в слове. Идентификатор токена Целочисленное значение, однозначно указывающее на определенный токен. Vocabulary (словарь токенов) Словарь, устанавливающий однозначное соответствие между токенами и идентификаторами токенов. Описание алгоритма BPE (Byte-Pair Encoding) ----------------------------------------------- Целью автоматической обработки естественного языка (Natural Language Processing, NLP) является формализация языка таким образом, чтобы сохранить его смысловое наполнение. Ключевую роль в такой обработке играет токенизация и кодирование текста. Благодаря этому становится возможным использование формальных языковых моделей, которые не способны работать с текстовыми данными в первоначальном виде, но широко используются для выявления скрытых закономерностей в числовых данных. В качестве самого простого способа токенизировать текст можно рассмотреть разбиение текста на слова. Однако у такого подхода есть существенные минусы: 1. Размер словаря (vocabulary), множества всех уникальных токенов, в таком случае получается достаточно большим. 2. Даже при таком очень большом словаре нередко приходится сталкиваться с проблемой `Out-of-Vocabulary `__ слов. Это не позволяет сделать обработку универсальной и масштабируемой на большое количество дискурсов. По этой причине сегодня существует множество техник кодирования текста по частям слов. Одной из них является **Byte-Pair Encoding**, с которой вам и предстоит поработать в рамках данной лабораторной работы. Данный алгоритм был предложен в 1994 Филиппом Гейджем в статье `“Новый метод сжатия данных” `__. Идея алгоритма сводится к замене самых частотных пар символов другим символом, при этом объем используемой памяти снижается в два раза. В контексте токенизации текста BPE представляет собой итеративное слияние наиболее частых пар последовательных токенов в тексте (или корпусе текстов) до тех пор, пока не будет достигнут определенный размер словаря. Для лучшего понимания рассмотрим пример применения этого алгоритма. Допустим, нам дан следующий текст: .. code:: py "It's far, farther, farthest and old, older, oldest" Разобьем текст на слова по пробельным символам и посимвольно токенизируем каждое слово: .. code:: py [ ('I', 't', "'", 's', ''), ('f', 'a', 'r', ',', ''), ('f', 'a', 'r', 't', 'h', 'e', 'r', ',', ''), ('f', 'a', 'r', 't', 'h', 'e', 's', 't', ''), ('a', 'n', 'd', ''), ('o', 'l', 'd', ',', ''), ('o', 'l', 'd', 'e', 'r', ',', ''), ('o', 'l', 'd', 'e', 's', 't', '') ] .. note:: Каждое слово заканчивается специальным токеном ``''``, который обозначает конец слова. Составим таблицу частотности пар токенов. Нас интересуют те токены, которые идут друг за другом: .. code:: py { ('I', 't'): 1, ('t', "'"): 1, ("'", 's'): 1, ('s', ''): 1, ('f', 'a'): 3, ('a', 'r'): 3, ('r', ','): 3, (',', ''): 4, ('r', 't'): 2, ('t', 'h'): 2, ('h', 'e'): 2, ('e', 'r'): 2, ('e', 's'): 2, ('s', 't'): 2, ('t', ''): 2, ('a', 'n'): 1, ('n', 'd'): 1, ('d', ''): 1, ('o', 'l'): 3, ('l', 'd'): 3, ('d', ','): 1, ('d', 'e'): 2 } Обратите внимание, что мы не рассматриваем пары токенов, которые встречаются на границе слов. Поэтому у нас нет пар, начинающихся с ``''``. Теперь нужно выбрать самую часто встречающуюся пару. В нашем примере это пара токенов ``(',', '')``. Давайте объединим ее в один токен: .. code:: py [ ('I', 't', "'", 's', ''), ('f', 'a', 'r', ','), ('f', 'a', 'r', 't', 'h', 'e', 'r', ','), ('f', 'a', 'r', 't', 'h', 'e', 's', 't', ''), ('a', 'n', 'd', ''), ('o', 'l', 'd', ','), ('o', 'l', 'd', 'e', 'r', ','), ('o', 'l', 'd', 'e', 's', 't', '') ] Далее мы пересчитываем встречаемость пар токенов, соединяем самую частую пару и повторяем процесс до тех пор, пока не достигнем нужного размера словаря. Итогом работы алгоритма должно стать следующее разбиение: .. code:: py [ ('I', 't', "'", 's', ''), ('far', ','), ('far', 'th', 'er', ','), ('far', 'th', 'est', ''), ('a', 'n', 'd', ''), ('old', ','), ('old', 'er', ','), ('old', 'est', '') ] Предполагается, что таким образом удается выделить из текста значимые последовательности: это могут быть корни слов либо другие широко употребляемые морфемы. Так, в нашем примере нам удалось выделить два корня (``far`` и ``old``), а также суффиксы сравнительной и превосходной степени ``er`` и ``est``. Иногда в алгоритме вместо специального токена конца слова используется токен, который обозначает, напротив, начало слова. В настоящей лабораторной нам доведется столкнуться и с тем, и с другим. Давайте пошагово рассмотрим реализацию такого алгоритма: 1. Создать частотный словарь корпуса, который отражает количество вхождений каждого из слов. 2. Токенизировать слова (на первой итерации - разделить слова на символы). 3. Составить частотный словарь пар токенов, которые следуют друг за другом в рамках одного слова. 4. Сформировать новый токен из самой часто встречающейся пары. 5. Повторить шаги 2-4 до тех пор, пока не будет достигнуто желаемое количество токенов. Что надо сделать ---------------- Шаг 0. Начать работу над лабораторной (вместе с преподавателем на практике) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1. Измените файлы ``main.py`` и ``start.py`` 2. Закоммитьте изменения и создайте новый Pull request .. important:: Код, выполняющий все требуемые действия, должен быть написан в функции ``main`` в модуле ``start.py``. Для этого реализуйте функции в модуле ``main.py`` и импортируйте их в ``start.py``. Вызов функции в файле ``start.py``: .. code:: py if __name__ == '__main__': main() В рамках данной лабораторной работы **нельзя использовать сторонние модули, а также стандартные модули collections и itertools**. Обратите внимание, что в файле ``target_score.txt`` необходимо выставить желаемую оценку: 4, 6, 8 или 10. Чем выше желаемая оценка, тем большее количество тестов запускается при проверке вашего Pull Request. Шаг 1. Токенизировать одно слово ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Начнем с предобработки слов. Для этого реализуйте функцию :py:func:`lab_2_tokenize_by_bpe.main.prepare_word`. Функция не должна удалять из строки специальные символы или приводить текст к нижнему регистру. В случае, если какой-либо из специальных токенов, который обозначает начало и конец слова, представлен значением ``None``, то добавлять его в кортеж не требуется. Например, строка ``"It's"`` должна быть обработана следующим образом: ``('I', 't', "'", 's', '')``, где ``''`` - токен, который обозначает конец слова. В качестве токена, который обозначает начало слова, было передано ``None``. Шаг 2. Сформировать частотный словарь ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. important:: Выполнение Шага 2 соответствует 4 баллам. Далее нужно собрать частотный словарь, ключами которого выступают предобработанные слова, а значениями - количество вхождений слов в текст. В рамках данной лабораторной работы будем придерживаться мнения, что граница слова - это любой пробельный символ. Например, из текста ``"It's far, farther, farthest and old, older, oldest"`` должен получиться словарь следующего вида: .. code:: py { ('I', 't', "'", 's', ''): 1, ('f', 'a', 'r', ',', ''): 1, ('f', 'a', 'r', 't', 'h', 'e', 'r', ',', ''): 1, ('f', 'a', 'r', 't', 'h', 'e', 's', 't', ''): 1, ('a', 'n', 'd', ''): 1, ('o', 'l', 'd', ',', ''): 1, ('o', 'l', 'd', 'e', 'r', ',', ''): 1, ('o', 'l', 'd', 'e', 's', 't', ''): 1 } Реализуйте функцию :py:func:`lab_2_tokenize_by_bpe.main.collect_frequencies`. При этом токен начала слова может быть представлен ``None``, а токен конца - нет. Функция обязательно должна вызывать функцию :py:func:`lab_2_tokenize_by_bpe.main.prepare_word`. Продемонстрируйте составление частотного словаря в функции ``main()`` модуля ``start.py``, используя текст на русском языке (переменная ``text``). В качестве токена конца слова используйте строку ````. Токен начала слова не используйте, то есть передайте в его качестве ``None``. Шаг 3. Посчитать количество вхождений каждой из пар токенов ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Чтобы сформировать новые токены, нужно выделить самые часто встречающиеся сочетания уже существующих токенов. Для этого нужно сформировать частотный словарь, в котором в качестве ключей используются пары существующих токенов (то есть пока что только пары символов), а значениями - количество случаев, когда эти символы следуют друг за другом в пределах одного слова. Так, для примера из предыдущего шага частотный словарь сочетания токенов имеет следующий вид: .. code:: py { ('I', 't'): 1, ('t', "'"): 1, ("'", 's'): 1, ('s', ''): 1, ('f', 'a'): 3, ('a', 'r'): 3, ('r', ','): 3, (',', ''): 4, ('r', 't'): 2, ('t', 'h'): 2, ('h', 'e'): 2, ('e', 'r'): 2, ('e', 's'): 2, ('s', 't'): 2, ('t', ''): 2, ('a', 'n'): 1, ('n', 'd'): 1, ('d', ''): 1, ('o', 'l'): 3, ('l', 'd'): 3, ('d', ','): 1, ('d', 'e'): 2 } Обратите внимание, что для пары порядок токенов критичен. Кроме этого, сочетания токенов на стыке слов не образуют пару, поэтому в нашем словаре пар нет сочетаний, которые начинаются с токена конца слова. Реализуйте функцию :py:func:`lab_2_tokenize_by_bpe.main.count_tokens_pairs`. Шаг 4. Сформировать новый токен ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Словарь частотности сочетаний токенов позволяет нам выбрать, из чего можно сформировать новый токен. Реализуйте функцию :py:func:`lab_2_tokenize_by_bpe.main.merge_tokens`. Она обновляет частотный словарь слов, заменяя в ключах пары токенов, из которых сформирован новый токен, на этот самый новый объединенный токен. Так, если в частотном словаре слов из предыдущего шага объединить пару токенов ``(',', '')`` в новый токен, то словарь должен измениться следующим образом: .. code:: py { ('I', 't', "'", 's', ''): 1, ('f', 'a', 'r', ','): 1, ('f', 'a', 'r', 't', 'h', 'e', 'r', ','): 1, ('f', 'a', 'r', 't', 'h', 'e', 's', 't', ''): 1, ('a', 'n', 'd', ''): 1, ('o', 'l', 'd', ','): 1, ('o', 'l', 'd', 'e', 'r', ','): 1, ('o', 'l', 'd', 'e', 's', 't', ''): 1 } Шаг 5. Обучить токенизатор ~~~~~~~~~~~~~~~~~~~~~~~~~~ .. important:: Выполнение Шага 5 соответствует 6 баллам. Теперь у нас есть все компоненты для того, чтобы обучить наш токенизатор и сформировать необходимое количество новых токенов. Для этого реализуйте функцию :py:func:`lab_2_tokenize_by_bpe.main.train`. Чтобы сформировать новый токен, нужно выбирать самую часто встречающуюся пару токенов. В случае, если несколько пар встречаются одинаково часто, нужно выбрать ту, которая даст более длинный токен. Если и это не даст однозначного ответа, то необходимо выбрать ту пару, которая дает лексикографически меньший токен. Например, для пар ``{('a', 'b'): 3, ('b', 'cd'): 3, ('b', 'ca'): 3}`` нужно сформировать токен ``'bca'``, так как этот токен длиннее токена ``'ab'`` и лексикографически меньше токена ``'bcd'``. Обратите внимание, что если доступных для слияния пар токенов не осталось, то обучение должно прекратиться, даже если необходимое количество токенов не было достигнуто. Продемонстрируйте обучение токенизатора на материале текста из переменной ``text`` в модуле ``start.py``. Используйте 100 слияний в качестве критерия остановки обучения. .. note:: Подумайте, можно ли установить связь между оптимальным количеством слияний и количеством вхождений в частотный словарь слов? Шаг 6. Присвоить токенам идентификатор ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Известно, что формальные модели, включая лингвистические модели, не способны обрабатывать буквенные данные, поэтому необходимо для каждой текстовой последовательности сформировать числовой вектор. Для этого нужно присвоить каждому из получившихся токенов и символов в них определенный числовой идентификатор. .. note:: При присваивании идентификатора не нужно учитывать повторяющиеся токены и символы. Отсортируйте токены по длине в порядке убывания. Токены, имеющие одинаковую длину, должны быть отсортированы лексикографически в порядке возрастания. Присвойте данной последовательности идентификаторы от 0 до n-1, где n - количество токенов. Например, из набора токенов ``['far', 'old', 'th', 'er', 'est', '', 'a', 'n', 'd']`` должен получиться следующий словарь: .. code:: py {'': 0, 'est': 1, 'far': 2, 'old': 3, '': 4, 'er': 5, 'th': 6, 'a': 7, 'd': 8, 'n': 9} Для этого реализуйте функцию :py:func:`lab_2_tokenize_by_bpe.main.get_vocabulary`. В возвращаемом словаре должны присутствовать специальные токены: * токен конца слова (при наличии); * токен начала слова (при наличии); * неизвестный токен. Присвоение идентификатора данным токенам производится так же после сортировки вместе с остальными токенами. Шаг 7. Декодировать текст ~~~~~~~~~~~~~~~~~~~~~~~~~ .. important:: Выполнение Шага 7 соответствует 8 баллам. Теперь, когда у нас есть выделенные токены и присвоенные им числовые идентификаторы, мы можем декодировать любую последовательность. Для этого реализуйте функцию :py:func:`lab_2_tokenize_by_bpe.main.decode`. Обратите внимание, что в возвращаемой строке не должно быть токенов, которые обозначают конец слова. Вместо них должно быть то, что мы в рамках текущей работы считаем границей слова, то есть пробельный символ. Так, при использовании словаря из предыдущего шага, последовательность ``[2, 6, 1, 4]`` должна быть раскодирована как ``'farthest '``. Создайте словарь вида ``<токен: числовой идентификатор>``, используя ``''`` в качестве токена, который обозначает неизвестную последовательность. Затем продемонстрируйте работу декодера, прочитав и декодировав секретное содержимое любого файла из папки ``assets/secrets`` в модуле ``start.py``. .. note:: В данных файлах есть инструкция о получении бонуса к оценке. При этом, **количество бонусов ограничено**. Один студент может отгадать не более одной загадки. Поэтому только первые 5 студентов, которые справятся с заданием, смогут получить бонус. Решение о применении бонуса принимается ментором и не подлежит оспариванию. Студент получит бонус, только если на момент выполнения задания в его форке опубликован актуальный код, который позволяет воспроизвести результат. Шаг 8. Закодировать одно слово ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Кодирование текста, в отличие от декодирования, является более сложным процессом и подразумевает предварительную токенизацию текста. На текущем шаге Вам нужно реализовать функцию :py:func:`lab_2_tokenize_by_bpe.main.tokenize_word`. Обратите внимание, что при токенизации слова необходимо в первую очередь выбирать более длинные токены. Например, мы токенизируем слово ``('w', 'o', 'r', 'd', '')`` и получаем следующий словарь токенов: .. code:: py { 'w': 0, '': 1, 'o': 2, 'r': 3, 'd': 4, 'wo': 5, 'ord': 6, '': 7 } При корректной реализации слово должно быть токенизировано следующим образом: ``[0, 6, 7]``. Варианты ``[5, 3, 4]`` и ``[0, 2, 3, 4]`` являются неправильными: токен ``ord`` длиннее токенов ``wo`` и тем более ``w``, ``o``, ``r`` и ``d``. В случае, если есть несколько подходящих самых длинных токенов, следует использовать тот токен, который меньше лексикографически. В случае, если встречается последовательность, которую нельзя заменить ни на один из известных токенов, она заменяется на специальный неизвестный токен. Это позволит без ошибок обрабатывать тексты, в которых есть, например, вставки на других языках. Шаг 9. Загрузить словарь ~~~~~~~~~~~~~~~~~~~~~~~~ В дальнейшем нам предстоит работать с нейросетевой языковой моделью, обученной на задачу перевода с русского языка на английский. Для этого нужно использовать специальный словарь токенов, обученный на большом объеме текстов и расположенный в файле ``assets/vocab.json``. На данном шаге мы научимся считывать такие словари из файлов с расширением ``.json``. Для этого реализуйте функицю :py:func:`lab_2_tokenize_by_bpe.main.load_vocabulary`. Мы уже встречались с файлами такого формата в предыдущих лабораторных работах. Больше узнать об этом формате можно `здесь `__. Для работы с такими файлами используется библиотека `json `__. Шаг 10. Закодировать текст ~~~~~~~~~~~~~~~~~~~~~~~~~~ Чтобы формальная модель поняла наш запрос, нужно его закодировать. Для этого реализуйте функцию :py:func:`lab_2_tokenize_by_bpe.main.encode`. Она обязательно должна вызывать функции :py:func:`lab_2_tokenize_by_bpe.main.prepare_word` и :py:func:`lab_2_tokenize_by_bpe.main.tokenize_word`. Шаг 11. Выделить n-граммы ~~~~~~~~~~~~~~~~~~~~~~~~~ Теперь мы готовы перейти к тому, чтобы научиться оценивать качество перевода. Для сравнения полученных предсказаний с истинным ответом, нужно научиться рассчитывать метрику `BLEU (bilingual evaluation understudy) `__. Эта метрика часто используется для оценки предсказаний в задаче машинного перевода текста. Она представляет собой геометрическое среднее метрик `Precision `__, посчитанных для n-грамм различного порядка. В течение следующих нескольких шагов мы реализуем подсчет метрики BLEU. Для начала нужно реализовать функцию для выделения n-грамм из последовательности :py:func:`lab_2_tokenize_by_bpe.main.collect_ngrams`. N-граммами называют такую подпоследовательность, которая включает в себя ровно n последовательных элементов. Например, из буквенной последовательности ``мыть`` можно выделить четыре униграммы (``м``, ``ы``, ``т``, ``ь``), три биграммы (``мы``, ``ыт``, ``ть``), две триграммы (``мыт``, ``ыть``) и, наконец, одну 4-грамму (``мыть``). Например, при вызове функции с последовательностью ``мыть`` и порядком 3, мы ожидаем получить результат ``[('м', 'ы', 'т'), ('ы', 'т', 'ь')]``. Шаг 12. Вычислить метрику Precision ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ В общем случае значение метрики Precision обозначает, сколько положительных предсказаний модели оказались действительно положительными. В нашем случае можно сформулировать это более конкретно: какая доля истинных значений попала в предсказания модели. Иными словами, чем больше предсказанных n-грамм действительно присутствуют в истинной последовательности, тем выше значение Precision. Для вычисления метрики Precision реализуйте функцию :py:func:`lab_2_tokenize_by_bpe.main.calculate_precision`. Рассмотрим пример. Пусть истинными значениями является ``[('м',), ('ы',), ('т',), ('ь',)]``, а предсказанными - ``[('м',), ('ы',), ('т',), ('ь',), ('c',), ('я',)]``. Тогда совпадающими значениями будут следующие 4 элемента: ``[('м',), ('ы',), ('т',), ('ь',)]``. Так как всего нам дано 6 предсказанных значений Precision, то доля совпадающих элементов, равна :math:`\frac{4}{6}=0.(6)`. Шаг 13. Вычислить среднее геометрическое ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ До расчета метрики BLEU нам остался всего один шаг, и это подсчет среднего `геометрического значения `__. Для этого реализуйте функцию :py:func:`lab_2_tokenize_by_bpe.main.geo_mean`. Рассчитывать среднее значение мы будем для метрик Precision, которые посчитаны для n-грамм различного порядка, от 1 до :math:`order_{max}`, где :math:`order_{max}` - максимальный порядок рассматриваемых n-грамм. Рассчитать значение можно по следующей формуле: .. math:: Mean_{geometric} = \frac{1}{order_{max}} \times \sum_{i=1}^{order_{max}}ln(Precision_{i}) Здесь :math:`Precision_{i}` обозначает значение метрики Precision для n-грамм порядка :math:`i`, причем :math:`i` принимает значения от 1 до :math:`order_max`. Шаг 14. Вычислить метрику BLEU ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. important:: Выполнение Шага 14 соответствует 10 баллам. Наконец, мы готовы рассчитать метрику BLEU, которая отражает близость предсказанного значения истинному. Для этого реализуйте функцию :py:func:`lab_2_tokenize_by_bpe.main.calculate_bleu`, которая обязательно вызывает функции :py:func:`lab_2_tokenize_by_bpe.main.collect_ngrams`, :py:func:`lab_2_tokenize_by_bpe.main.calculate_precision` и :py:func:`lab_2_tokenize_by_bpe.main.geo_mean`. Функция должна: 1. Выделить n-граммы из предоставленных последовательностей всех порядков от 1 до обозначенного максимального. 2. Посчитать Precision для выделенных n-грамм каждого порядка. 3. Вычислить среднее геометрическое полученных значений метрики и вернуть это значение, предварительно умножив на 100. Продемонстрируйте оценку работы языковой модели ``Helsinki-NLP/opus-mt-ru-en`` в задаче машинного перевода в ``start.py``. Информацию об этой языковой модели можно получить `здесь `__. Пример работы с данной языковой моделью вы можете увидеть в ``assets/nmt_demo/main.py``. Вам не нужно импортировать модель и генерировать предсказания, это уже сделано за вас. Текст, который использовали для получения предсказания, расположен в файле ``assets/for_translation_ru_raw.txt``. Закодируйте его при помощи специального обученного словаря ``assets/vocab.json``. При кодировании в качестве токена начала слова используйте ``\u2581``, а в качестве неизвестного токена - ````. Токен конца слова использовать не нужно (т.е. передайте ``None``). Чтобы убедиться, что файл удалось закодировать верным образом, можно сравнить его с закодированной версией текста из файла ``assets/for_translation_ru_encoded.txt``. Закодированные предсказания модели (то есть предсказания в том виде, в котором их вернула модель) можно найти в файле ``assets/for_translation_en_encoded.txt``. Необходимо декодировать их, используя реализованные вами функции, и сравнить получившийся текст с эталонным переводом из файла ``assets/for_translation_en_raw.txt``. При декодировании токен конца слова следует передать как ``None``. Сравнение следует производить по метрике BLEU. После декодирования и перед сравнением нужно очистить текст от токена начала слова (``\u2581``), заменив его на пробел. Полезные ссылки --------------- - `Оригинальная статья о BPE алгоритме `__ - `Описание формата хранения данных JSON `__ и `документация библиотеки `__ для работы с такими файлами - `Описание нейросетевой модели `__, чьи предсказания были использованы в настоящей работе - `Оригинальная статья о BLEU метрике `__, используемой для оценки качества машинного перевода