Что это такое? Допустим, мы хотим обучить переводчик с чувашского на русский. Берем некоторое небольшое количество параллельных данных: предложений, которые есть на двух языках. В нашем случае это 250 тысяч предложений. Обучаем модель в обратную сторону, с русского на чувашский. Теперь у нас есть относительно неплохой переводчик. Далее возьмем много данных на одном языке, в нашем случае это 1 миллион предложений на русском языке. Переведем их на чувашский язык. В результате у нас появится дополнительный синтетический параллельный корпус из 1 миллиона пар предложений. Дальше смешиваем с оригинальным корпусом и обучаем. Получается неплохой профит, при этом метод достаточно прост и не требует изменений в архитектуре сети.
Чтобы показать этот профит, нужно с чем-то сравниться. В качестве бейзлайна возьмем модель, обученную только на параллельных данных. Здесь и далее используем архитектуру Трансформер, реализованную на Sockeye
pip install sockeye
Обучаем на GPU
pip install mxnet-cu100mkl
Поскольку чувашский и русский языки сложны морфологически, то собрать словарь из уникальных слов проблематично, поэтому используем Byte Pair Encoding (BPE)
git clone https://github.com/rsennrich/subword-nmt.git
python subword-nmt/subword_nmt/learn_joint_bpe_and_vocab.py --input /content/train/chv.train_250K.tok /content/train/ru.train_250K.tok -s 10000 -o bpe.codes --write-vocabulary bpe.vocab.chv bpe.vocab.ru
python subword-nmt/subword_nmt/apply_bpe.py -c bpe.codes --vocabulary bpe.vocab.chv --vocabulary-threshold 50 < chv.train_250K.tok > chv.train_250K.bpe
python subword-nmt/subword_nmt/apply_bpe.py -c bpe.codes --vocabulary bpe.vocab.ru --vocabulary-threshold 50 < ru.train_250K.tok > ru.train_250K.bpe
python subword-nmt/subword_nmt/apply_bpe.py -c bpe.codes --vocabulary bpe.vocab.chv --vocabulary-threshold 50 < chv.dev.tok > chv.dev.bpe
python subword-nmt/subword_nmt/apply_bpe.py -c bpe.codes --vocabulary bpe.vocab.ru --vocabulary-threshold 50 < ru.dev.tok > ru.dev.bpe
Параметры обучения такие:
python -m sockeye.prepare_data -s chv.train_250K.bpe -t ru.train_250K.bpe -o chvru_data
python -m sockeye.train -d chvru_data -vs chv.dev.bpe -vt ru.dev.bpe --encoder transformer --decoder transformer --transformer-model-size 512 --transformer-feed-forward-num-hidden 256 --transformer-dropout-prepost 0.1 --num-embed 512 --max-seq-len 100 --decode-and-evaluate 500 -o chvru_model --num-layers 6 --disable-device-locking --keep-last-params 1 --batch-size 1024 --optimized-metric bleu --max-num-checkpoint-not-improved 15
Получается 30,77 BLEU. Бейзлайн готов.
Классический обратный перевод
Для нейронных сетей эталонная статья Rico Sennrich et al., 2016 про обратный перевод появилась в 2016 году. Обратите внимание, что архитектура сети остается прежней, меняется только входной корпус
python subword-nmt/subword_nmt/learn_joint_bpe_and_vocab.py --input /content/train/chv.train_250K_bt_1M.tok /content/train/ru.train_250K_bt_1M.tok -s 10000 -o bpe.codes --write-vocabulary bpe.vocab.chv bpe.vocab.ru
python subword-nmt/subword_nmt/apply_bpe.py -c bpe.codes --vocabulary bpe.vocab.chv --vocabulary-threshold 50 < chv.train_250K_bt_1M.tok > chv.train_250K_bt_1M.bpe
...
python -m sockeye.prepare_data -s chv.train_250K_bt_1M.bpe -t ru.train_250K_bt_1M.bpe -o chvru_data
python -m sockeye.train ...
Даже такой базовый метод сразу дает хороший рост относительно параллельной модели: 34,38 BLEU.
В дальнейшем было предложено несколько модификаций и альтернатив. Давайте на них тоже посмотрим.
Тегированный обратный перевод
Первый рассматриваемый способ усовершенствования описан в статье Isaac Caswell et al., 2019 Известно, что если объем переведенного обратно корпуса будет значительно превышать параллельный, то постепенно результаты будут деградировать. Чтобы помочь модели справиться, предлагается разметить все синтетические предложения на языке-источнике тегом BT вначале: «BT На улице сегодня очень жарко, но говорят, что будет дождь.». Тогда, говоря по-простому, модель возьмет все самое хорошее от дополнительных данных, а плохого не возьмет.
python subword-nmt/subword_nmt/learn_joint_bpe_and_vocab.py --input /content/train/chv.train_250K_tagbt_1M.tok /content/train/ru.train_250K_tagbt_1M.tok -s 10000 -o bpe.codes --write-vocabulary bpe.vocab.chv bpe.vocab.ru
python subword-nmt/subword_nmt/apply_bpe.py -c bpe.codes --vocabulary bpe.vocab.chv --vocabulary-threshold 50 < chv.train_250K_tagbt_1M.tok > chv.train_250K_tagbt_1M.bpe
...
python -m sockeye.prepare_data -s chv.train_250K_tagbt_1M.bpe -t ru.train_250K_tagbt_1M.bpe -o chvru_data
python -m sockeye.train ...
В результате получилось 34,32 BLEU. Примерно столько же, сколько и на классическом обратном переводе. Делаем вывод, что наш синтетический корпус еще не так велик, и можно обратно переводить еще. К сожалению, это по времени затратный процесс, поэтому оставим для дальнейших исследований.
COPY
Очень простой метод, описанный в статье Anna Currey et al., 2017 Гипотеза здесь такая: что если профит обратного перевода не только в обученной обратной модели, но и в самом дополнительном моно-корпусе. Давайте для этого используем данные целевого языка и просто скопируем в данные исходного языка. Получится как бы русско-русская пара. Далее смешаем с параллельным корпусом.
python subword-nmt/subword_nmt/learn_joint_bpe_and_vocab.py --input /content/train/chv.train_250K_cp_1M.tok /content/train/ru.train_250K_cp_1M.tok -s 10000 -o bpe.codes --write-vocabulary bpe.vocab.chv bpe.vocab.ru
python subword-nmt/subword_nmt/apply_bpe.py -c bpe.codes --vocabulary bpe.vocab.chv --vocabulary-threshold 50 < chv.train_250K_cp_1M.tok > chv.train_250K_cp_1M.bpe
...
python -m sockeye.prepare_data -s chv.train_250K_cp_1M.bpe -t ru.train_250K_cp_1M.bpe -o chvru_data
python -m sockeye.train ...
Такой метод дает 31,19 BLEU на нашем корпусе.
Дальше метод можно чуть усовершенствовать: при подготовке словаря использовать только параллельные данные, чтобы в словаре исходного языка акцент был на чувашский.
python subword-nmt/subword_nmt/learn_joint_bpe_and_vocab.py --input /content/train/chv.train_250K.tok /content/train/ru.train_250K.tok -s 10000 -o bpe.codes --write-vocabulary bpe.vocab.chv bpe.vocab.ru
python subword-nmt/subword_nmt/apply_bpe.py -c bpe.codes --vocabulary bpe.vocab.chv --vocabulary-threshold 50 < chv.train_250K_cp_1M.tok > chv.train_250K_cp_1M.bpe
...
python -m sockeye.prepare_data -s chv.train_250K_cp_1M.bpe -t ru.train_250K_cp_1M.bpe -o chvru_data
python -m sockeye.train ...
И это действительно работает. Подход дает 32,44 BLEU. Хуже чем обратный перевод, но если нет времени на подготовку синтетических данных, то метод хороший.
Дальше авторы статьи предлагают совместить обратный перевод и COPY так, что модель будет обучена на 2 250 000 парах предложений: 250 тысяч — параллельный корпус, 1 миллион — обратный перевод, и еще 1 миллион — COPY. Таким образом предложения на русском языке из моно-корпуса появятся дважды. К сожалению, здесь улучшения на нашем корпусе не получилось: 33,43 BLEU. Авторы показывали, что ухудшение появляется только на больших корпусах. Однако, и у нас на бедном корпусе тоже видно ухудшение. Можно предположить только, что вручную качественно собранные 250 тысяч предложений — это уже достаточно большой корпус
Word-on-Word
Следующую статью Sreyashi Nag et al., 2019 упомянем лишь в ознакомительных целях. Когда совсем мало параллельных данных, авторы используют всю силу имеющихся словарей. Предлагается предложение из моно-корпуса на целевом языке перевести пословно. Так, для английского «I like to sing» получилось бы «Я любить [пропуск] петь». На практике для нашего корпуса это малоприменимо по двум причинам. Во-первых, русский и чувашский языки имеют богатую морфологию, поэтому в данном случае задача усложняется. Помимо непосредственного перевода слова, его нужно сначала привести в неопределенную форму одного языка, а затем в нужную форму другого. Если этого не сделать, то в словаре будет много отсутствующих слов. Во-вторых, метод скорее применим для параллельных корпусов менее 100 тысяч предложений. В нашем случае обратный перевод, скорее всего, будет давать более качественный перевод.
NOISE
Далее рассмотрим еще два способа, описанные в статье Сергея Едунова, который, как он сам говорит, много сидит в фейсбуке, Sergey Edunov et al., 2018 В работе есть и другие варианты, но утверждается, что не все они работают одинаково хорошо. Суть NOISE в том, что в синтетические предложения исходного языка добавляется шум 3 видов: 0,1 слов удаляется, 0,1 слов заменяется условным тегом , часть слов перемешивается, но так, что слово не может сдвинуться дальше, чем на 3 позиции. Реализацию можно посмотреть тут поиском по фразе «noise for auto-encoding loss». Например, это может выглядеть так: если на вход подать предложение «На улице сегодня очень жарко, но говорят, что будет дождь.», то на выходе можно получить «На сегодня жарко, но говорят, дождь что будет.»
python subword-nmt/subword_nmt/learn_joint_bpe_and_vocab.py --input /content/train/chv.train_250K_noise_1M.tok /content/train/ru.train_250K_1M.tok -s 10000 -o bpe.codes --write-vocabulary bpe.vocab.chv bpe.vocab.ru
python subword-nmt/subword_nmt/apply_bpe.py -c bpe.codes --vocabulary bpe.vocab.chv --vocabulary-threshold 50 < chv.train_250K_noise_1M.tok > chv.train_250K_noise_1M.bpe
...
python -m sockeye.prepare_data -s chv.train_250K_noise_1M.bpe -t ru.train_250K_1M.bpe -o chvru_data
python -m sockeye.train ...
В итоге получается 33,25 BLEU. На нашем корпусе проигрывает классическому обратному переводу. Видимо, подразумеваемся, что синтетический корпус должен быть кратно больше.
SAMPLING
Суть второго метода из статьи в том, чтобы иначе сделать обратный перевод, так как он неидеален. Предлагается брать не лучший по мнению модели перевод, а некоторый случайный из предложенных моделью. Получившиеся предложения будут хуже, но более разнообразными, и дадут более богатый сигнал для обучения. Технически это решается добавлением одного параметра в команду перевода. Было
python -m sockeye.translate --input ru.train_1M.bpe -m ruchv_model --output chv.train_1M.bpe
Стало
python -m sockeye.translate --input ru.train_1M.bpe -m ruchv_model --output chv.train_1M.bpe --sample
Результат: 32,67 BLEU. И это тоже хуже, чем классический обратный перевод. Гипотеза, почему так, примерно такая же, как и с методом выше.
Совмещение с Переносом Знания
В заключении обсудим еще один важный вопрос: можно ли совместить обратный перевод и перенос знания, про который я писал в прошлой статье.
В качестве основы для метода переноса знаний на этот раз используем 3 миллиона предложений казахско-русского корпуса: те, для которых коэффициент соответствия (третья колонка) больше 1. Дочерний корпус — те же 250 тысяч предложений. Для этого объема данных получили 35,24 BLEU. Да-да, это чуть лучше, чем результаты обратного перевода.
По всем законам жанра, дальше должна быть история про то, что комбинация двух методов дала еще более высокий результат. Но нет. Эксперименты с использованием разных вариантов словарей (с добавлением данных обратного перевода и без) и разных дочерних корпусов для дообучения (опять же с добавлением данных обратного перевода и без) показали себя либо чуть хуже, либо значительно хуже. Вплоть до того, что если и в подготовке общего словаря, и в качестве дочернего использовать корпус в 250 тысяч параллельных предложений вместе с 1 миллионом обратно-переведенных, то получим весьма заметное ухудшение 24,73 BLEU. Причем сильнее всего на результат влияет общий словарь. Поле для дальнейших исследований.
Выводы
Итого в результате экспериментов пришли к тому, что классический обратный перевод дает наилучший результат. Равно как и метод переноса знаний. Использовать можно любой из них, в зависимости от имеющихся данных. COPY подходит, когда нет времени и ресурсов переводить обратно, помогает быстро улучшить базовую модель. Остальные методы дают результаты чуть хуже, их надо применять аккуратно, сравнивать на вашем корпусе.
jjdeluxe
Спасибо за статью! Однозначно в закладки)
А не пробовали на больших моноязычных текстах обучить экодер-декодер (декодер потом выбросить), после чего энкодер заморозить и учить только декодер уже на аугментированых данных? Потом, конечно, можно слегка оттюнить и энкодер, но только совсем слегка)
У меня, когда занимался машинным переводом, как-то до этого руки не дошли, потому и интересуюсь)
AlexAntonov Автор
Именно так не пробовал. Записал себе идею на посмотреть) Из относительно похожего сейчас рекомендуют копать в сторону github.com/facebookresearch/XLM