Машинный перевод — область технологий, которая успешно приближает будущее. Он разрушает языковой барьер и помогает людям, которые говорят на разных языках, понимать друг друга. Один клик — и можно прочитать и понять статью, написанную на незнакомом языке, или сообщения в мессенджере от людей из любой точки мира. А значит, получить больше информации и найти новых знакомых. Это с точки зрения пользователя. Со стороны разработчиков сервисов тоже, казалось бы, современное машинное обучение уже близко к тому, чтобы достаточно было сделать import model_name from your_favourite_framework — и всё заработало. К сожалению, это не совсем так. Нельзя просто взять готовый претрейн и надеяться, что он будет хорошо переводить все именованные сущности. Нельзя просто обучиться на готовом кусочке WMT‑данных и верить в то, что переводчик будет адекватно работать на специфичном домене. Нельзя просто взять обычный токенизатор и трансформер — и рассчитывать на корректный перевод текстов с шумами и опечатками. Поэтому этот гайд будет немного более сложным, чем импорт моделей. Под катом вы не найдёте серебряной пули — только реальный опыт и подходы, которые помогли нам ВКонтакте справиться со всеми нюансами и запустить собственный переводчик.  ВКонтакте переводчик пригодится во многих направлениях. Например, мы можем мгновенно переводить сообщения в мессенджере. Или посты с русского на английский и другие языки: это помогает иностранцам знакомиться с уникальным для нашей соцсети контентом и находить для себя что‑то потенциально интересное. Кроме того, мы можем привлечь новых пользователей с помощью автоматических переводов постов, отдавая их в выдачу поисковиков. При этом переводчик — не только удобный инструмент для рядового пользователя. Машинный перевод стал базой для всей современной обработки естественного языка. Именно для него изначально придумывали рекуррентные сети, sequence‑to‑sequence модели, аттеншен, трансформеры, которые теперь применяются в самых разных сферах машинного обучения. Сейчас существует множество решений, чтобы затащить машинный перевод в свой продукт. У каждого свои плюсы и минусы. Прежде чем решить, что использовать, необходимо взвесить все за и против. Самый простой и надёжный вариант — купить готовое облачное решение у опытных вендоров. Это может быть Яндекс, Google, DeepL — все они умеют хорошо переводить на большое количество языков. У них вы найдёте большинство популярных языковых пар, быстро подключитесь к удобному API и ни о чём не будете переживать. Но любое удобство стоит денег: нужно заранее оценить количество запросов и возможные всплески нагрузки — они могут обойтись вам очень дорого. Такой вариант отлично подойдёт тем, кто собирается переводить тексты общих тематик и имеет достаточно ресурсов, чтобы хостить эти модели. В репозиториях Hugging Face’a можно найти множество самых разных моделей машинного перевода: начиная с классических en-ru моделей и заканчивая современными мультиязычными, способными переводить тексты с 200 языков. В качестве быстрого продакшен-решения можно воспользоваться готовым движком Argos, который тоже поддерживает много языковых пар и поставляется вместе с готовой API-обёрткой LibreTranslate. По мере использования таких моделей могут проявляться недостатки. Например, качество переводов может сильно отличаться от коммерческих решений. Это становится заметнее на специфических и нестандартных доменах. Например, если переводчик обучался на legal-текстах, вряд ли он сможет хорошо переводить мемы и повседневную речь. Дело здесь не только в различии лексики. Модели могут впитать смещённые знания о мире и начать некорректно переводить некоторые сущности в заданных контекстах. Все эти ошибки можно пытаться исправлять костылями и хардкодингом. Но в какой-то момент оказывается, что проблем настолько много, что легче всё сделать с нуля. Третий вариант требует глубоких технических знаний, ресурсов на сбор данных и разработку моделей. Но в результате вы получаете огромный опыт и удобный инструмент, с помощью которого сможете вносить любые правки в модели и точнее управлять поведением переводчика. Для нас именно разработка собственного решения стала оптимальным вариантом, так как: у нас огромное количество данных для перевода и высокая нагрузка — так что покупка готового решения оказалась бы дороже, чем разработка собственного; открытые решения, которые мы тестировали в ранних версиях продукта, в наших условиях и на нашем домене быстро проявили свои недостатки, проблемы и уязвимости.  Поэтому вскоре мы начали разработку собственного решения. Дальше расскажу об опыте, полученном в процессе. Сейчас в задачах машинного перевода чаще всего используются решения на основе нейронных сетей. Для их работы нужны два базовых компонента: токенайзер и нейросетевая модель. Токенайзер разбивает текст на маленькие сущности — токены, которые потом передаются на обработку в нейросетевую модель. Токенами могут быть целые слова, их небольшие части или отдельные символы. Существует несколько алгоритмов токенизации, каждый из которых обладает своими достоинствами и недостатками. Чуть больше про них можно почитать в хорошем обзоре от Hugging Face. В качестве нейросетевой модели мы используем трансформер типа «энкодер‑декодер». Энкодер вычисляет внутреннее представление исходного текста, на основе которого декодер генерирует перевод. За деталями работы этой архитектуры можно обратиться к оригинальной статье или подробному объяснению с картинками. Упростить разработку переводчика можно с помощью больших предобученных мультиязычных моделей. Для задач перевода лучше всего подходят архитектуры типа «энкодер‑декодер», например mBART и mT5. Эти модели можно взять за основу и дообучить их на своём небольшом корпусе. Плюсы такого подхода — быстрая сходимость во время обучения и более высокое качество, так как эти модели уже знают структуру языка и им достаточно выучить взаимосвязь между языками. Другие предобученные модели, такие как LaBSE и LASER, могут использоваться для аугментации и фильтрации данных, речь о которых пойдёт дальше. Мультиязычные претрейны типа M2M и NLLB могут стать хорошей отправной точкой для ваших исследований. Они покрывают большой набор языковых пар, плюс в оригинальных работах можно найти множество нюансов и тонкостей обучения нейросетевых переводчиков. Какую бы вспомогательную модель мы ни использовали, важно обращать внимание на то, под какими лицензиями они распространяются и какие есть ограничения на их использование. Данные — очень важная часть любой задачи машинного обучения. Итоговое качество переводов чаще зависит от количества и чистоты данных, чем от каких-то архитектурных излишеств моделей. Усилия по разметке и очистке данных приносят гораздо больше пользы, чем аналогичный объём работы по улучшению модели. Секрет хорошего нейросетевого переводчика — большой и чистый корпус. Основной тип данных для обучения переводчика — это bitext-корпусы, состоящие из пар текстов «оригинал — перевод». Такие данные можно размечать самим, но для этого потребуется штат профессиональных переводчиков, много времени и ресурсов. Часть данных можно найти в открытом доступе: например, есть хорошие большие агрегаторы OPUS и Statmt.org. Для автоматизации поиска данных можно пользоваться утилитой mtdata. Однако открытые корпусы часто страдают от большого количества шумов и сильного смещения домена. Например, один из крупнейших корпусов UNPC состоит из официальных и юридических текстов. Используя только такой корпус, сложно научиться хорошо переводить повседневную речь или художественную литературу. Другой полезный источник — это mono-корпусы, состоящие из большого объёма обычных текстов. Чаще всего нас интересуют данные, близкие к нашему домену, которые относительно легко найти в открытом доступе. На основе mono-корпусов мы предобучаем разные вспомогательные модели, начиная с токенизаторов и заканчивая большими языковыми претрейнами типа BART и T5. Самые популярные и доступные источники таких данных: mC4 и Common Crawl. Bitext mining может пригодиться, если у вас есть ещё один источник данных — полупараллельные тексты. Например, русско-английский перевод книги «Война и мир» Льва Толстого. Очевидно, что профессиональный переводчик при работе с текстом адаптирует его, меняет порядок предложений и их структуру. Если бы мы захотели собрать из такого книжного перевода параллельный корпус, последовательно сопоставляя предложения, то ничего хорошего бы не получилось. Мы сможем решить эту проблему, если воспользуемся энкодерами типа LASER или LaBSE. Особенность их работы заключается в том, что чем ближе два предложения на разных языках по смыслу, тем ближе будут их эмбеддинги в векторном пространстве. Посчитав эмбеддинги для каждого предложения из оригинала, сможем поискать ближайшие эмбеддинги предложений в переводе книги. Затем по некоторому порогу близости векторов выбрать пары, которые являются переводом друг друга. Авторы, предложившие такой подход, делали свой поиск внутри огромного корпуса Common Crawl и снапшотов Википедии, и поделились с нами новыми крупными датасетами CCMatrix и WikiMatrix. Для поиска пар переводов на таких обширных корпусах требуются большие вычислительные мощности. Мы, в свою очередь, можем немного упростить задачу и искать пары в заведомо известных полупараллельных корпусах: ими могут быть переводы отдельных книг, статей, новостей и публикаций. Улучшить качество переводов и немного адаптировать домен помогает back-translation. В основе этого метода лежит очень простая идея: «давайте переведём обратным переводчиком текст на другой язык, после чего развернём получившуюся пару». Тогда у нас появятся семплы «плохой перевод» — «хороший оригинал», которые можно добавить к основной обучающей выборке. Такой подход может помочь нам значительно увеличить обучающий корпус и улучшить обучение модели: чистые target-side тексты помогут нам качественнее обучать языковую модель на стороне декодера, а грязные source-side тексты помогут сделать энкодер более устойчивым к разным шумам. Важно правильно подбирать соотношение между back-translated и основной частью обучающих данных. Кроме этого следует добавлять source-side токен к back-translated данным, показывающий модели, что эти данные являются сгенерированными. Такие лайфхаки помогают стабильнее учить переводчик и снижать вероятность переобучения. В любом корпусе текстов для переводов могут присутствовать шумы. Чтобы улучшить качество переводчика и стабилизировать обучение, нужно фильтровать плохие пары переводов. Чаще всего это делается с помощью разных эвристик: соотношения длины исходного и переведённого текста или словарных символов к символам пунктуации; степенью word-alignment связности или величиной энтропии языковой модели; близостью эмбеддингов мультиязычных кодировщиков типа LaBSE или LASER. В открытых корпусах часто можно встретить синтетические переводы, появившиеся в результате работы старых автоматических переводчиков. Обучение на таких текстах плохо влияет на качество переводчика, поэтому их тоже важно фильтровать. Чаще всего это делают с помощью отдельного классификатора, который обучают на заранее собранном датасете. Большая часть описанных фильтров есть в репозитории OpusFilter — там их можно изучить или вдохновиться на написание собственных скриптов фильтрации. С одной стороны, когда мы обучаем нейронную модель, стараемся использовать достаточно чистые и структурированные данные, чтобы получать качественный перевод целого текста. С другой — в реальности с текстом может произойти что угодно и работать модели придётся совсем в других условиях. Пользователь может писать текст с ошибками или опечатками, периодически нажимать Caps Lock, вставлять ссылки, даты, произвольные числа. Такие сущности плохо влияют на нейронную модель: она может начать игнорировать часть сущностей или, наоборот, их чрезмерно повторять. Помимо этого, в тексте могут присутствовать элементы разметки и меншены, которые нужно переводить с учётом контекста и оставлять разметку целостной. Поэтому, чтобы наш автоматический перевод хорошо работал в реальных приложениях, нужен дополнительный препроцессинг и постпроцессинг данных. Сделать препроцессинг помогут два подхода: с использованием word-alignment’ов и служебных токенов. Word-alignment модели находят взаимосвязь между словами в исходном и переведённом тексте. На вход они получают два текста, а на выходе отдают пары лексически связанных слов. Из готовых решений наиболее известны статистические fast-align, eflomal и нейросетевая SimAlign. Алгоритм следующий: Нормализуем все сущности и запоминаем их положение в исходном тексте: слова, написанные заглавными буквами, привести к нижнему регистру, именованные сущности заменить на служебные, избавиться от разметки. Очищенный текст переводим нейросетевой моделью. Вычисляем word alignments. Восстанавливаем сущности: на основе полученных word alignments ищем связанные токены и их позиции в переведённом тексте. Далее придаём им исходный вид: восстанавливаем заглавное написание, именованные сущности и вставляем разметку. Служебные токены — это токены, которые остаются неизменными и занимают подходящее место в переводе. Например, токен  может использоваться для маскирования ссылок. Заменив в исходном тексте ссылку на , мы ожидаем встретить этот токен в переводе, чтобы после поставить на его место исходную ссылку. Точно так же можно решить задачу частичного перевода с помощью глоссариев. Для этого оставим служебный токен на месте слова, который мы потом переведём с помощью статичного глоссария. После того как нейронная модель завершит свою работу, мы найдём в переведённом тексте этот служебный токен и вставим на его место словарный перевод. Для вложенных сущностей и CAPS’а можно ввести токены границ  и . Будем отмечать ими нужные регионы в исходном тексте, чтобы затем восстановить разметку в переводе. Чтобы служебные токены выполняли свою задачу, обучающий корпус необходимо аугментировать. Для этого можно использовать модели word-alignment’а и маскировать случайный набор связанных слов служебными токенами. После успешного обучения моделей стоит задуматься о том, как эти модели использовать в продакшен-среде. Для ускорения и оптимизации инференса PyTorch- / Flax- / Tensorflow-модели следует сконвертировать в ONNX, TensorRT или CTranslate2 в зависимости от рабочего окружения. Последний — удобный фреймворк для инференса трансформеров типа «энкодер-декодер» и «декодер» (encoder-decoder transformer & decoder-only transformer) с поддержкой различных ускорений. В некоторых случаях может пригодиться дистилляция и квантизация моделей. Для хостинга моделей лучше всего использовать микросервисную архитектуру, где каждый отдельный сервис отвечает за инференс отдельной языковой пары, и отдельная часть занимается препроцессингом и определением исходного языка. В таком случае будет гораздо проще масштабировать систему в зависимости от языковых потребностей пользователей. Сперва необходимо понять, какую продуктовую задачу будет решать сервис автоматического перевода. В зависимости от этого принимаем решение: покупать ли облачный сервис, использовать открытые модели или разрабатывать что-то своё. Начинаем собирать всевозможные данные. Ищем открытые корпусы, занимаемся bitext minig’ом и разметкой собственных датасетов. Анализируем свой текстовый домен и подбираем наиболее подходящие mono-корпусы. Подготавливаем всё для обучения: очищаем корпусы, готовим логику препроцессинга, аугментируем данные и обучаем токенизаторы. На основе собранных данных обучаем первые версии нейронных моделей. Следим за метриками, подбираем гиперпараметры. Делаем несколько итераций аугментации данных с помощью back-tranlsation’а. Дописываем препроцессинг и постпроцессинг данных. Тестируем финальное качество переводов и исследуем возникающие corner-кейсы. Подготавливаем модели к выкатке в продакшен. Занимаемся конвертацией и оптимизацией моделей, разработкой микросервисов, собираем воедино весь пайплайн переводов. После выкатки моделей работа не заканчивается. Дальше нас ждёт бесконечный процесс доразметки данных, оптимизаций, правки багов перевода и переобучения моделей. Получается, что если разложить всё по полочкам и разобраться, то окажется, что сделать собственный переводчик вполне реально. Всегда есть много разных вариантов: и облака, и открытые решения, и что-то самописное. Разные продуктовые задачи требуют разных решений. Для нас расчёты показали, что разработка собственного решения окупается примерно за неделю, если сравнивать с использованием облачных сервисов, и позволяет получить потенциально неограниченную пропускную способность и гибкость.  nlp at vk.com