Обратный перевод для Нейронного машинного перевода

    Привет. Некоторое время назад я рассказывал про способ сделать хороший машинный перевод на нейронных сетях, когда в наличии мало данных. Всего таких способа два. И наступило время рассказать про второй. Встречайте: обратный перевод.

    Что это такое? Допустим, мы хотим обучить переводчик с чувашского на русский. Берем некоторое небольшое количество параллельных данных: предложений, которые есть на двух языках. В нашем случае это 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 подходит, когда нет времени и ресурсов переводить обратно, помогает быстро улучшить базовую модель. Остальные методы дают результаты чуть хуже, их надо применять аккуратно, сравнивать на вашем корпусе.

    Средняя зарплата в IT

    111 111 ₽/мес.
    Средняя зарплата по всем IT-специализациям на основании 6 720 анкет, за 2-ое пол. 2020 года Узнать свою зарплату
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 6

      +1
      Спасибо за статью! Однозначно в закладки)
      А не пробовали на больших моноязычных текстах обучить экодер-декодер (декодер потом выбросить), после чего энкодер заморозить и учить только декодер уже на аугментированых данных? Потом, конечно, можно слегка оттюнить и энкодер, но только совсем слегка)
      У меня, когда занимался машинным переводом, как-то до этого руки не дошли, потому и интересуюсь)
        0
        Именно так не пробовал. Записал себе идею на посмотреть) Из относительно похожего сейчас рекомендуют копать в сторону github.com/facebookresearch/XLM
        +1

        А на каком подкорпусе BLEU считался? 30+ для бейзлайна это сильно.

          +1
          Дев и тест корпуса автоматически рандомно собрали из ранней версии датасета, когда было еще несколько десятков тысяч предложений. В обучающей выборке их, конечно, нет. Хотя мы осознаем, что они далеки от идеала). На самом деле абсолютные значения BLEU практически ничего не значат, важнее сравнение их между собой, примерно +2-3 BLEU видно глазами, но тоже не 100% правило. Вот к примеру тут github.com/masakhane-io/masakhane-mt/tree/master/benchmarks/en-yo/jw300-baseline обучение на автоматически выровненных данных, то есть на более грязном корпусе, и тоже высокие показатели, причем тест лучше, чем дев существенно.
            0
            Согласен, для относительного сравнения, думаю, нормально так делать.
            Сейчас над универсальным автоматическим выравнивателем на основе ембеддингов по предложениям работаю, — для распространенных языков мультиязыковые модели типа USE и sentence-transformers хорошо работают, а для малоресурсных типа чувашского можно выравнивать через прокси-текст (машинный перевод на русский), а потом делать обратное сопоставление. Тоже будет дополнительный ресурс для обучения.
              0
              Интересно! Мы у себя делаем выравнивание вручную, чтобы качество корпуса было выше, а то он итак небольшой

        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

        Самое читаемое