Всем привет привет, месяц назад закончилась тренировка Data dojo 2026 по малоресурсному переводу, которая проходила в рамках масштабного мероприятия Яндекса YOUNG CON. Здесь я бы хотел рассказать про само соревнование, ключевые идеи и, конечно, про моё решение, которое обеспечило мне топ-23 из 400+ участников
Мой план
-
Описание соревнования
-
Метрика
-
Описание обеих задач:
-
Условия
-
Идеи и хронология
-
Валидация и тюнинг
-
Итоговая стратегия и результат
-
-
Как это видели авторы: итоги
-
Полезные ссылки
Описание соревнования
Как я уже сказал ранее, соревнование проводилось в рамках тренировки DATA DOJO с 21 мая по 4 июня. Участником предлагалось решить 2 задачи: задача A — машинный перевод с английского на русский, задача B — малоресурсный перевод с русского на абхазский. Для обоих задач в качестве бейзлайна предоставлялась Gemma-4-E2B, решение проверялось на серверах Яндекса на GPU L4 (24 GB VRAM); 16 GB RAM без доступа к интернету на закрытом тесте. Разрешалось отправлять до 4 посылок ежедневно. Кстати, приватного теста не было, поэтому итоговый результат считался по публичному лидерборду по лучшему решению участников.
Метрика
Обе задаче оценивались по метрике BLEU, конкретно использовалась библиотека scarebleu. Что конкретно она означает? Алгоритм ищет совпадения слов и фраз (n‑грамм). Оценка варьируется в диапазоне от 0 до 1, но конкретно здесь умножалась на 100 для удобства подсчёта. Более подробно про неё почитать можно здесь.
Задача A: Перевод с английского на русский
Условия: задача A
Современные LLM уже отлично справляются с переводом отдельных слов и предложений, но вот при переводе больших абзацев или даже текстов могут возникать проблемы:
-
Теряется согласованность текста: например, глаголы, относящиеся к одному объекту, могут иметь разный род в разных частях текста
-
Нарушается общая стилистика текста
-
Неверно переводятся термины в разных частях: например, в тексте про Github предложение «Button „Fork“ will help» может быть переведено как «Кнопка „Вилка“ поможет»
Именно исходя из этих недостатков, главной задачей было обеспечить максимально качественный перевод английских абзацев на русский язык в относительно маленьких моделях. Тестовый набор содержал 69 примеров, средняя длина составляла 846 символов (162 слова, или в районе 10 предложений). Самый длинный пример содержал 2229 символов (401 слово, 34 предложения).
Также были следующие ограничения на инференс решения:
-
Время работы скрипта генерации ответов на 69 запросов должно занимать не более 45 минут
-
Образ должен занимать менее 20GB.
Идеи и хронология: задача A
Первым делом я, как и все другие участники, залил бейзлайн, тем самым выбив скор 26.6. Также я сразу поднял некую локальную валидацию, чтобы проверять десятки гипотез вместо 4, про неё отдельно написал далее.
Теперь к решению: я начал с чего‑то несложного, конкретно я начал с промпт‑инжениринга и few‑shot примеров. В двух словах, это когда я меняю промпт для LLM, чтобы дать ей более понятные инструкции, как работать. И тут сразу моя роковая ошибка из‑за неопытности в соревнованиях: базовое решение было недетерминированным, то есть при инференсе у него была некая случайность, а я этого не заметил и делал выводы по лидерборду, на котором разброс был +‑ 0.5.
Заметил я это только через несколько дней, исправил, и после перезалива подобрал оптимальный промпт, который давал +0.1 по сравнению с детерминированным бейзлайном. Кстати, few‑shot по итогу только вредил модели, скорее всего потому что так она «переобучалась» под него.
Далее я стал пробовать другие опен‑сорсные модели: были проверены NLLB-200, Riva‑Translate-4B‑Instruct и Opus‑MT, не помню конкретно скоры для каждой, но BLEU более 22 не выбила ни одна из них.
Тогда я остановился на Gemma и решил попробовать PEFT (Parameter‑Efficient Fine‑Tuning: адаптация больших моделей, в т.ч. нейросетей, под конкретные задачи, без переобучения всех их параметров), по таймлайну уже была вторая неделя: конкретно я попробовал дообучить LoRA (Low‑Rank Adaption: метод дообучения больших моделей, при котором в исходную модель не вносятся тяжелые изменения. Вместо этого обучаются небольшие вспомогательные матрицы). Ключевое слово здесь — «попробовал», потому что поднять его у меня так и не получилось).
Помню, что обучал на каком‑то опен‑сорсном параллельном корпусе на kaggle, но вот соединить всё вместе у меня так и не дошли руки. Смотря на это свежим взглядом, я практически уверен, что метрика получилась бы ниже, чем на дефолтных весах, да и в чате люди были такого же мнения, но я всё равно жалею, что не проверил до конца.
Валидация и тюнинг: задача A
Для локальной валидации я скачал параллельный корпус абзацев от НИУ ВШЭ, который, как мне казалось, мог достаточно неплохо отражать тестовые данные. И да, корреляция и вправду была, но со смещением: когда на лидерборде скор был ~26, на валидации получалось ~20. Но мне и этого хватало для проверки гипотез и экспериментов с промптом, поэтому я оставил так. Отдельный прикол конкретно про эту задачу: веса модели не помещались на мою видеокарту (GTX 1650), поэтому приходилось ждать по 15 минут на CPU:).
Также под конец соревнования я использовал обычный grid search (перебор комбинаций гиперпараметров) для подбора параметров инференса, но особого прироста это не дало
Итоговая стратегия и результат: задача A
Так как приватной части теста не было, было принято решение убрать детерминизм и отправлять решение с лучшими параметрами, основанное на Gemma, до тех пор пока не сложатся все звёзды). Таким образом спустя ~16 посылок я выбил свой лучший скор 27.14
Я немного пообщался с другими участниками после завершения соревнования, и практически все (в том числе и топы) также использовали только промпт‑инжениринг и тюнинг, ничего более. К слову, лучший скор по этой задаче был 29.07
Задача B: Перевод с русского на абхазский
Условия: задача B
Абхазский язык — один из самых интересных и сложных языков Кавказа. Это сверхмалоресурсный язык: для него мало параллельных корпусов, ограниченная языковая поддержка в популярных сервисах перевода и немного готовых решений в области машинного обучения. Помимо этого, большинство языковых моделей даже не знают его уникальных букв алфавита — ӷӡқҟԥҭҳҵҷҽҿҩџьәҕҧ.
На этот раз максимизировать BLEU надо было по небольшим текстам: тестовый набор содержал 515 примеров, средняя длина составляла 78 символов (12 слов), в каждом примере было не больше трёх предложений. Также были даны 2 корпуса: один параллельный русский‑абхазский (~200к текстов) и один моноабхазский ( ~1.5млн абхазских текстов). Далее я буду их называть меньшим и большим соответственно.
Ограничения на инференс были следующими:
-
Время работы скрипта генерации ответов на 515 запросов должно занимать не более 30 минут.
-
Образ должен занимать менее 20GB.
Идеи и хронология: задача B
Начать, наверное, стоит с того, что бейзлайн просто не запускался и падал по TL). Насколько я знаю, организаторы вскоре это исправили, но я уже выкрутился, уменьшив максимальное количество новых токенов с 1024 до 256, после чего я увидел скор 4.4 на лидерборде. Ничего лучше я и не рассчитывал увидеть, так как Gemma не знает ни абхазский алфавит, ни тем более лексику и грамматику.
Сразу после этого я стал искать модели, которые можно дообучить на абхазских корпусах. Мой выбор пал на NLLB-200 1.2B, и вот тут уже я решил обучать LoRA на меньшом корпусе. Но перед этим я расширил токенайзер, словарь и эмбеддинги на абхазский алфавит, чтобы модель училась предсказывать их.
Обучал поначалу понемногу, чтобы понять скорость и оценить, стоит ли обучать дальше. Обычно обучал 10–15к итераций (здесь итерация — это один проход по одному мини‑батчу). И в моменте пришёл к печальному выводу: лоры снова не будет. Обучение быстро выходило на плато, да и при расширении словаря PEFT не обучается полноценно. Поэтому экспериментально было принято решение перейти на NLLB-200 0.6B и полный файн‑тюн.
Таким образом я обучил около 50к итераций, и дальше понял, что скорость обучения начала замедляться (ранее скор поднимался на ~2.5 каждые 10к итераций), и решил расширять обучающие данные. Во‑первых, я решил перевести часть моноабхазского корпуса. Вначале я думал обучить модель в обратную сторону (переводить с абхазского на русский), нагенерить синтетики и потом на ней обучить уже в правильную сторону, но я прикинул по времени и решил переводить через Yandex Translate API. И это было заметно дороже, чем я ожидал. С учётом стартового гранта я перевёл ~120к абхазских текстов, что было довольно неплохо.
Также я нашёл 3 тома русско‑абхазского словаря (~50к слов), которые я распарсил и получил сильный прирост в лексике (я замечал на валидации, что текущая версия модели просто никогда не видела некоторых слов, поэтому галлюцинирует). После чего я просто смёржил получившиеся корпуса с определёнными коэффициентами и получил итоговый датасет на ~600к текстов.
Кроме NLLB я также пробовал обучение MT5 1.2B. Идея была простой: делать предсказания обеими моделями, смотреть на уверенность и подобрать threshold, чтобы там, где NLLB не уверена совсем, вторая модель выдавала лучший результат. Но эта идея себя не оправдала: получалось так, что вторая декоррелированная модель показывала максимальную уверенность, хотя NLLB была менее уверена и выдавала лучший скор. В общем, корреляции между уверенностью модели и BLEU получить не удалось, и эту идею пришлось отбросить.
Валидация и тюнинг: задача B
Для валидации я просто использовал первые 200 объектов из итогового обучающего датасета, которые обученная модель не видела. Таким образом получилось получить неплохую корреляцию с лидербордом и тестировать идеи локально. NLLB в отличие от Gemma прекрасно влезала на GPU, поэтому спустя 5 минут я уже видел результат. Также по сравнению с задачей A я добавил графики уверенности модели, а также пирог распределения между NLLB и MT5 (когда я ещё верил в него)
Тюнинг — это мой последний туз в рукаве. Прогонял Optuna в ночь перед дедлайном на 8.5 часов, чтобы оптимизировать гиперпараметры инференса. Это дало мне +0.6 BLEU, что на тот момент стало решающим изменением, обучение к тому моменту уже вышло на плато и это стало моим финальным рычагом.
Итоговая стратегия и результат: задача B
В итоге лучшим решением стал полный файн‑тюн NLLB-200 0.6B с расширенным токенайзером на скомбинированном датасете, я обучал до полного плато, которое настало на 180к итерации. К тому моменту модель обучалась всё медленнее и медленнее, а на 190к скор поплыл вниз. Да, я знаю, что стратегия, которая заключается в железе и надежде, что плато никогда не наступит, не очень хорошая, но это лучшее, что я на тот момент придумал. И тут ещё важно добавить, что все 2 недели я стабильно входил в топ-5, а откинуло меня вниз только в последний день, поэтому на тот момент меня вполне устраивал и такой темп обучения.
Как это видели авторы: итоги
Разбор задачи B можно посмотреть здесь, но если вкратце, то я был крайне близко к правильному решению. Авторы предлагали и расширять токенизатор, и генерить синтетику, и использовать сторонние API, а большинство лучших решений в итоге использовали BYT5: это MT5, но с другим подходом к токенизации, подробнее здесь. А также отдельно отметили Encoder‑Decoder модели, которые лучше справляются с подобными задачами, ко мне они вообще не приходили в голову, и никто из топа их не применял, но я обязательно попробую их в будущем. Были и другие идеи, такие как дистилляция, Beam search и P‑tune, но мне они показались менее интересными.
Как видно из названия, я столкнулся с машинным переводом впервые, но это не помешало мне войти в топ-6% участников. Я однозначно рекомендую всем участвовать в подобных соревнованиях, в том числе на kaggle, так как это отлично место чтобы освоить новую область ML или погрузиться в новые алгоритмы и интересные идеи. Кстати, по итогу у меня сложилось впечатление, что соревнование решалось приват‑шерингом. Ничего не могу гарантировать, но 8 победителей из 10 были знакомы до тренировки, и за это конечно огромный минус Яндексу. А в остальном большое спасибо Яндексу, что он проводит такие классные события)
Полезные ссылки
-
Исходный код моего решения можно посмотреть здесь
-
Исходный код обучения NLLB здесь
-
Финальный лидерборд
-
Мой телеграм, если захотите обсудить со мной программирование, то велкоме)
Автор: wertiba


