Нейронный машинный перевод (НМП, англ. Neural Machine Translation, NMT) развивается очень быстро. Сегодня, чтобы собрать свой переводчик, не нужно иметь два высших образования. Но чтобы обучить модель, нужен большой параллельный корпус (корпус, в котором предложению на исходном языке сопоставлен его перевод). На практике речь идет хотя бы об одном миллионе пар предложений. Есть даже отдельная большая область НМП, исследующая методы обучения языковых пар с малым количеством данных в электронном виде (англ. Low Resource NMT).
Мы собираем чувашско-русский корпус и параллельно смотрим, что можно сделать с имеющимся объемом данных. В этом примере использовали корпус из 90 000 пар предложений. Самый хороший результат на данный момент дал метод передачи знания (англ. Transfer Learning), о нем и пойдет речь в статье. Цель статьи — дать практический пример реализации, который легко можно было бы воспроизвести.
План обучения таков. Нужно взять большой (родительский) корпус, обучить на нем нейронную модель, а затем дообучить нашу дочернюю модель. Причем целевой язык перевода будет один и тот же: русский. Интуитивно это можно сравнить с изучением второго языка. Его учить легче, зная один иностранный язык. Еще это похоже на изучение узкой области иностранного языка, например, медицинской терминологии английского языка: сначала нужно выучить английский в общем.
В качестве родительского корпуса пробовали брать 1 миллион пар предложений из англо-русского параллельного корпуса и 1 миллион из казахско-русского корпуса. В казахских данных 5 миллионов предложений. Из них взяли только те, у которых коэффициент соответствия (третья колонка) больше 2. Казахский вариант дал результаты чуть лучше. Интуитивно кажется, что это понятно, поскольку чувашский и казахский языки более похожи друг на дурга. Но на самом деле это не доказано, а также сильно зависит от качества корпуса. Более подробно про подбор родительского корпуса можно прочесть в этой статье. Про дочерний корпус из 90 000 пар предложений узнать и запросить пример данных можно тут.
Теперь к коду. Если нет своей быстрой видеокарты, можно тренировать модель на площадке Colab. Для обучения мы использовали библиотеку Sockeye. Предполагается, что уже установлен Python3.
Также возможно отдельно придется повозиться с MXNet , которая отвечает за работу с видеокартой. В Colab нужно дополнительно установить библиотеку
Про нейронные сети принято считать, что им достаточно скормить данные как есть, а они сами разберутся. Но на самом деле это не всегда так. Вот и в нашем случае корпус нужно предобработать. Сначала его токенизируем, чтобы модели легче было понимать, что «кот!» и «кот» — это примерно про одно и тоже. Для примера подойдет просто питоновский токенайзер.
В результате, подаем на вход пары предложений вида
и
На выходе получаются такие токенизированные предложения:
и на русском
В нашем случае понадобятся объединенные словари родительского и дочернего корпусов, поэтому создадим общие файлы:
так как дообучение дочерней модели происходит на том же словаре.
Теперь небольшое, но важное отступление. В МП предложения разбивают на атомы в виде слов и далее оперируют предложениями как последовательностями слов. Но этого как правило недостаточно, потому что образуется огромный хвост из слов, которые встречаются в корпусе по одному разу. Построить для них вероятностную модель сложно. Особенно это актуально для языков с развитой морфологией (падеж, род, число). И русский, и чувашский именно такие языки. Но есть решение. Можно разбить предложение на более низкий уровень, на подслова. Мы использовали Byte pair encoding.
Получим примерно такие последовательности подслов
и
Видно, что из слов хорошо вычленяются аффиксы: Не@@ давно и добро@@ той.
Для этого нужно подготовить словари bpe
И применить их к токенам, например:
По аналогии нужно сделать для всех файлов: обучающих, валидационных и тестовых родительской и дочерней моделей.
Теперь перейдем непосредственно к обучению нейронной модели. Сначала нужно подготовить общие модельные словари:
Далее обучим родительскую модель. Более подробно простой пример описан на странице Sockeye. Технически процесс состоит из двух шагов: подготовки данных с использованием созданных ранее модельных словарей
и самого обучения
Обучение на мощностях Colab занимает около суток. Когда тренировка модели завершена, переводить с ее помощью можно так
Для обучения дочерней модели выполним
Код запуска обучения выглядит так
Добавляются параметры, указывающие, что нужно использовать конфигурацию и веса родительской модели в качестве точки старта. Детали в примере с дообучением от Sockeye. Обучение дочерней модели сходится примерно за 12 часов.
Подводя итоги, сравним результаты. Обычная модель машинного перевода дала качество 24,96 BLEU, тогда как модель с передачей знания 32,38 BLEU. Разница видна и визуально на примерах переводов. Поэтому, пока продолжаем собирать корпус, будем пользоваться этой моделью.
Мы собираем чувашско-русский корпус и параллельно смотрим, что можно сделать с имеющимся объемом данных. В этом примере использовали корпус из 90 000 пар предложений. Самый хороший результат на данный момент дал метод передачи знания (англ. Transfer Learning), о нем и пойдет речь в статье. Цель статьи — дать практический пример реализации, который легко можно было бы воспроизвести.
План обучения таков. Нужно взять большой (родительский) корпус, обучить на нем нейронную модель, а затем дообучить нашу дочернюю модель. Причем целевой язык перевода будет один и тот же: русский. Интуитивно это можно сравнить с изучением второго языка. Его учить легче, зная один иностранный язык. Еще это похоже на изучение узкой области иностранного языка, например, медицинской терминологии английского языка: сначала нужно выучить английский в общем.
В качестве родительского корпуса пробовали брать 1 миллион пар предложений из англо-русского параллельного корпуса и 1 миллион из казахско-русского корпуса. В казахских данных 5 миллионов предложений. Из них взяли только те, у которых коэффициент соответствия (третья колонка) больше 2. Казахский вариант дал результаты чуть лучше. Интуитивно кажется, что это понятно, поскольку чувашский и казахский языки более похожи друг на дурга. Но на самом деле это не доказано, а также сильно зависит от качества корпуса. Более подробно про подбор родительского корпуса можно прочесть в этой статье. Про дочерний корпус из 90 000 пар предложений узнать и запросить пример данных можно тут.
Теперь к коду. Если нет своей быстрой видеокарты, можно тренировать модель на площадке Colab. Для обучения мы использовали библиотеку Sockeye. Предполагается, что уже установлен Python3.
pip install sockeye
Также возможно отдельно придется повозиться с MXNet , которая отвечает за работу с видеокартой. В Colab нужно дополнительно установить библиотеку
pip install mxnet-cu100mkl
Про нейронные сети принято считать, что им достаточно скормить данные как есть, а они сами разберутся. Но на самом деле это не всегда так. Вот и в нашем случае корпус нужно предобработать. Сначала его токенизируем, чтобы модели легче было понимать, что «кот!» и «кот» — это примерно про одно и тоже. Для примера подойдет просто питоновский токенайзер.
from nltk.tokenize import WordPunctTokenizer
def tokenize(src_filename, new_filename):
with open(src_filename, encoding="utf-8") as src_file:
with open(new_filename, "w", encoding="utf-8") as new_file:
for line in src_file:
new_file.write("%s" % ' '.join(WordPunctTokenizer().tokenize(line)))
new_file.write("\n")
В результате, подаем на вход пары предложений вида
Нумаях пулмасть вӗсене те укҫа тӳлеме пӑрахнӑ.
Республикӑра пурӑнакансем хӑйсен хастарлӑхӗпе, ырӑ кӑмӑлӗпе, чунтан тухакан йӑл куллипе тӗлӗнмелле лару-тӑру йӗркелерӗҫ, ют ҫӗршыври пирӗн ӗҫтешсем палӑртнӑ тӑрӑх, ҫавнашкалли вӗсен патӗнче Раштав уявне паллӑ тунӑ вӑхӑтра кӑна пулать.
и
Недавно им тоже перестали платить деньги.
Своим оптимизмом, добротой, приветливыми улыбками жители республики создали удивительную атмосферу, которая, по словам зарубежных партнеров, бывает у них только во время празднования Рождества.
На выходе получаются такие токенизированные предложения:
Нумаях пулмасть вӗсене те укҫа тӳлеме пӑрахнӑ .
Республикӑра пурӑнакансем хӑйсен хастарлӑхӗпе , ырӑ кӑмӑлӗпе , чунтан тухакан йӑл куллипе тӗлӗнмелле лару - тӑру йӗркелерӗҫ , ют ҫӗршыври пирӗн ӗҫтешсем палӑртнӑ тӑрӑх , ҫавнашкалли вӗсен патӗнче Раштав уявне паллӑ тунӑ вӑхӑтра кӑна пулать .
и на русском
Недавно им тоже перестали платить деньги .
Своим оптимизмом , добротой , приветливыми улыбками жители республики создали удивительную атмосферу , которая , по словам зарубежных партнеров , бывает у них только во время празднования Рождества .
В нашем случае понадобятся объединенные словари родительского и дочернего корпусов, поэтому создадим общие файлы:
cp kk.parent.train.tok kkchv.all.train.tok
cat chv.child.train.tok >> kk.parent.train.tok
cp ru.parent.train.tok ru.all.train.tok
cat ru.child.train.tok >> ru.all.train.tok
так как дообучение дочерней модели происходит на том же словаре.
Теперь небольшое, но важное отступление. В МП предложения разбивают на атомы в виде слов и далее оперируют предложениями как последовательностями слов. Но этого как правило недостаточно, потому что образуется огромный хвост из слов, которые встречаются в корпусе по одному разу. Построить для них вероятностную модель сложно. Особенно это актуально для языков с развитой морфологией (падеж, род, число). И русский, и чувашский именно такие языки. Но есть решение. Можно разбить предложение на более низкий уровень, на подслова. Мы использовали Byte pair encoding.
git clone https://github.com/rsennrich/subword-nmt.git
Получим примерно такие последовательности подслов
Ну@@ маях пулмасть вӗсене те укҫа тӳ@@ леме пӑрахнӑ .
Республи@@ кӑра пурӑнакансем хӑйсен хастар@@ лӑхӗпе , ырӑ кӑмӑ@@ лӗпе , чунтан тухакан йӑ@@ л кул@@ липе тӗлӗнмелле лару - тӑ@@ ру йӗркеле@@ рӗҫ , ют ҫӗршыв@@ ри пирӗн ӗҫ@@ те@@ шсем палӑртнӑ тӑрӑх , ҫав@@ наш@@ ка@@ лли вӗсен патӗнче Ра@@ шта@@ в уя@@ вне паллӑ тунӑ вӑхӑтра кӑна пулать .
и
Не@@ давно им тоже пере@@ стали пла@@ тить деньги .
Сво@@ им о@@ пти@@ ми@@ з@@ мом , добро@@ той , привет@@ ли@@ выми улыб@@ ками жители республики соз@@ дали уди@@ витель@@ ную ат@@ мо@@ с@@ фер@@ у , которая , по сло@@ вам за@@ ру@@ бе@@ жных па@@ рт@@ не@@ ров , бывает у них только во время празд@@ но@@ вания Ро@@ ж@@ де@@ ства .
Видно, что из слов хорошо вычленяются аффиксы: Не@@ давно и добро@@ той.
Для этого нужно подготовить словари bpe
python subword-nmt/subword_nmt/learn_joint_bpe_and_vocab.py --input kkchv.all.train.tok ru.all.train.tok -s 10000 -o bpe.codes --write-vocabulary bpe.vocab.kkchv bpe.vocab.ru
И применить их к токенам, например:
python subword-nmt/subword_nmt/apply_bpe.py -c bpe.codes --vocabulary bpe.vocab.kkchv --vocabulary-threshold 50 < kkchv.all.train.tok > kkchv.all.train.bpe
!python subword-nmt/subword_nmt/apply_bpe.py -c bpe.codes --vocabulary bpe.vocab.ru --vocabulary-threshold 50 < ru.all.train.tok > ru.all.train.bpe
По аналогии нужно сделать для всех файлов: обучающих, валидационных и тестовых родительской и дочерней моделей.
Теперь перейдем непосредственно к обучению нейронной модели. Сначала нужно подготовить общие модельные словари:
python -m sockeye.prepare_data -s kk.all.train.bpe -t ru.all.train.bpe -o kkru_all_data
Далее обучим родительскую модель. Более подробно простой пример описан на странице Sockeye. Технически процесс состоит из двух шагов: подготовки данных с использованием созданных ранее модельных словарей
python -m sockeye.prepare_data -s kk.parent.train.bpe -t ru.parent.train.bpe -o kkru_parent_data --source-vocab kkru_all_data/vocab.src.0.json --target-vocab kkru_all_data/vocab.trg.0.json
и самого обучения
python -m sockeye.train -d kkru_parent_data -vs kk.parent.dev.bpe -vt ru.parent.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 kkru_parent_model --num-layers 6 --disable-device-locking --batch-size 1024 --optimized-metric bleu --max-num-checkpoint-not-improved 10
Обучение на мощностях Colab занимает около суток. Когда тренировка модели завершена, переводить с ее помощью можно так
python -m sockeye.translate --input kk.parent.test.bpe -m kkru_parent_model --output ru.parent.test_kkru_parent.bpe
Для обучения дочерней модели выполним
python -m sockeye.prepare_data -s chv.child.train.bpe -t ru.child.train.bpe -o chvru_child_data --source-vocab kkru_all_data/vocab.src.0.json --target-vocab kkru_all_data/vocab.trg.0.json
Код запуска обучения выглядит так
python -m sockeye.train -d chvru_child_data -vs chv.child.dev.bpe -vt ru.child.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 ruchv_150K_skv_dev19_model --num-layers 6 --disable-device-locking --batch-size 1024 --optimized-metric bleu --max-num-checkpoint-not-improved 10 --config kkru_parent_model/args.yaml --params kkru_parent_model/params.best
Добавляются параметры, указывающие, что нужно использовать конфигурацию и веса родительской модели в качестве точки старта. Детали в примере с дообучением от Sockeye. Обучение дочерней модели сходится примерно за 12 часов.
Подводя итоги, сравним результаты. Обычная модель машинного перевода дала качество 24,96 BLEU, тогда как модель с передачей знания 32,38 BLEU. Разница видна и визуально на примерах переводов. Поэтому, пока продолжаем собирать корпус, будем пользоваться этой моделью.
Комментарии (3)
AlexAntonov Автор
29.11.2019 18:55BPE считается в пределах одной минуты. То есть это не узкое место в процессе. Версию, которые вы предложили, не смотрел, спасибо за ссылку.
rouge считать sockeye умеет, но я на него не смотрю: принимаю по bleu и по визуальному сравнению. Проверил по логам: на валидации растет примерно с 0.38 до 0.47vtrokhymenko
05.12.2019 18:57+1на самом деле rouge (и его модификации) больше показывает чем bleu, проверено на практике
но глазами все равное лучше тоже просматривать
vtrokhymenko
а сколько по времени у Вас заняло сделать bpe? и не пробовали это сделать с помощью github.com/VKCOM/YouTokenToMe?
также подскажите плз, не измеряли ли Вы еще и rouge?