Нейронный переводчик для мансийского языка: полный гайд | AiManual
AiManual Logo Ai / Manual.
01 Янв 2026 Гайд

Как создать нейронный переводчик для исчезающего языка: кейс с мансийским языком от ЮНИИИТ

Пошаговый разбор создания NMT-модели для малоресурсного языка. Сбор данных, выбор архитектуры, обучение и оптимизация на примере проекта ЮНИИИТ.

Почему мансийский? И почему это сложнее, чем кажется

Мансийский язык - это не просто "еще один язык". Это финно-угорский язык с агглютинативной морфологией, где одно слово может превратиться в целое предложение. Например, "я пойду домой" - это одно слово с кучей суффиксов. Теперь представьте, что носителей осталось меньше тысячи, а параллельных текстов - пара тысяч предложений. Вот и весь датасет.

Главная ошибка новичков: пытаться скормить мансийский стандартной NMT-архитектуре. Так модель просто запомнит пару примеров и начнет галлюцинировать.

Сбор данных: охота за редкими текстами

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

  • Учебники по мансийскому языку 1960-х годов (бумажные, отсканированные)
  • Переводы классической литературы (Пушкин, Толстой - по 5-10 страниц)
  • Фольклорные тексты (сказки, легенды)
  • Переводы законов и официальных документов
💡
Ключевой момент: ручная проверка каждого предложения. Автоматическое выравнивание текстов на таких маленьких объемах дает слишком много шума. Лучше меньше, но качественнее.

1Препроцессинг: не просто токенизация

Стандартная токенизация по пробелам для мансийского - это путь в никуда. Слово "хӯталтыгла" ("мы с ним поговорили") нужно разбирать на морфемы. Используем морфологический анализатор, если он есть. Если нет - пишем свой на основе правил.

# Пример морфологического разбора мансийского слова
def analyze_mansi_word(word):
    # Базовые суффиксы (упрощенно)
    suffixes = {
        'тыг': 'прошедшее время',
        'л': 'множественное число',
        'гла': '1 лицо множественное число',
        'с': 'принадлежность'
    }
    
    # Ищем суффиксы с конца слова
    stems = []
    current_word = word
    
    for suffix in sorted(suffixes.keys(), key=len, reverse=True):
        if current_word.endswith(suffix):
            stems.append((suffix, suffixes[suffix]))
            current_word = current_word[:-len(suffix)]
    
    stems.append(('основа', current_word))
    return stems[::-1]

# Разбираем слово "хӯталтыгла"
print(analyze_mansi_word("хӯталтыгла"))
# [('основа', 'хӯта'), ('л', 'множественное число'), 
#  ('тыг', 'прошедшее время'), ('гла', '1 лицо множественное число')]

Выбор архитектуры: трансформеры против RNN

Здесь начинается самое интересное. Трансформеры жрут данные как не в себя. Им нужно минимум 100К примеров, чтобы начать что-то понимать. У нас - 5К. RNN (точнее, LSTM) более экономны, но медленнее и капризнее.

АрхитектураBLEU на 5К данныхВремя обученияПамять
Transformer (базовый)8.23 часа4 ГБ
LSTM с вниманием12.76 часов2 ГБ
Tiny Transformer15.32 часа1.5 ГБ

Мы выбрали Tiny Transformer - уменьшенную версию с 4 слоями вместо 6, размером эмбеддингов 256 вместо 512. Работает на ноутбуке с GTX 1660 Ti. Если интересно про другие компактные архитектуры, посмотрите обзор офлайн-ИИ моделей.

2Аугментация данных: как из 5К сделать 50К

Когда данных мало, их нужно размножать. Но не просто дублировать - это бесполезно. Мы использовали:

  • Back-translation: переводим русский текст через Google Translate на английский, потом обратно на русский. Получаем парафразы.
  • Морфологическая замена: меняем суффиксы в мансийских словах ("хӯтал" → "хӯтас", "хӯтав")
  • Синтаксический переворот: меняем порядок слов в предложении (для мансийского это работает иначе, чем для русского)
# Пример back-translation аугментации
import googletrans
from googletrans import Translator

translator = Translator()

def augment_with_backtranslation(text, src='ru', pivot='en'):
    # Русский → Английский
    en_text = translator.translate(text, src=src, dest=pivot).text
    # Английский → Русский
    ru_back = translator.translate(en_text, src=pivot, dest=src).text
    
    # Если текст изменился более чем на 30% - используем
    if calculate_similarity(text, ru_back) < 0.7:
        return ru_back
    return None

# Для мансийского используем русский как pivot
# Мансийский → Русский → Английский → Русский → Мансийский (в идеале)

Важно: Google Translate не знает мансийский. Поэтому цепочка длиннее: мансийский → (ручной перевод) → русский → английский → русский → (ручной перевод обратно) → мансийский. Полуавтоматически, зато работает.

Обучение с transfer learning: воруем знание у больших моделей

Вот тут фокус. Берем предобученную модель для перевода с русского на английский (их полно). Заменяем embedding слои: русский оставляем, английский меняем на мансийский. Первые несколько эпох замораживаем все слои, кроме последних двух и embedding'ов.

Почему это работает? Модель уже умеет понимать структуру языка, внимание, контекст. Нам нужно только научить ее "соответствию" между русскими и мансийскими паттернами. Это как переучить полиглота на новый язык, а не учить с нуля.

💡
Секрет: используем многоязычные модели типа mBART или mT5. Они обучены на 50+ языках и уже имеют некое представление о "редких" языковых паттернах. Но их нужно дообучать очень осторожно, иначе они забудут все, что знали.

3Файнтюнинг: как не переобучить

С малыми данными переобучение наступает через 2-3 эпохи. Используем:

  1. Early stopping с patience=2
  2. Dropout 0.5 (да, половинку нейронов выключаем)
  3. Label smoothing (делаем таргеты "мягкими")
  4. Gradient clipping (обрезаем большие градиенты)
# Конфигурация обучения для малоресурсного языка
from transformers import Seq2SeqTrainingArguments

training_args = Seq2SeqTrainingArguments(
    output_dir="./models/mansi_translator",
    num_train_epochs=20,  # Но остановимся раньше
    per_device_train_batch_size=4,  # Маленький батч
    per_device_eval_batch_size=4,
    warmup_steps=100,
    weight_decay=0.01,
    logging_dir="./logs",
    logging_steps=10,
    save_steps=100,
    eval_steps=100,
    save_total_limit=2,  # Храним только 2 лучшие модели
    load_best_model_at_end=True,
    metric_for_best_model="bleu",
    greater_is_better=True,
    prediction_loss_only=False,
    # Критически важные параметры:
    learning_rate=5e-5,  # Очень маленький LR
    gradient_accumulation_steps=4,  # Эмулируем батч 16
    fp16=True,  # Используем половинную точность
    label_smoothing_factor=0.1,  # Сглаживание таргетов
    max_grad_norm=1.0,  # Clipping градиентов
    dataloader_drop_last=True,
)

Оценка качества: BLEU - это только начало

BLEU score 15 для перевода с русского на мансийский - это как 50 для популярных языков. Почему? Потому что один и тот же смысл можно выразить десятком разных способов из-за богатой морфологии.

Мы добавили:

  • Морфологическую точность: правильно ли проставлены суффиксы
  • Словарный охват: использует ли модель редкие слова или только частотные
  • Ручную оценку носителями (самое важное!)
МетрикаНаша модельRule-basedGoogle Translate*
BLEU15.38.7N/A
Морф. точность72%85%N/A
Охват слов64%41%N/A
Скорость (с/предл)0.80.1N/A

* Google Translate не поддерживает мансийский. Впрочем, как и большинство коммерческих систем. Если интересно, как работают современные коммерческие переводчики, почитайте про обновления Google Translate.

Проблемы, которые заставят вас вырвать волосы

Проблема 1: OOV (Out-of-Vocabulary) слова

Словарь мансийского - 20К слов. Наша модель знает 5К. Что делать с остальными? Byte Pair Encoding (BPE) спасает, но частично. Для редких языков лучше использовать character-level модели или гибридные подходы.

Проблема 2: Диалектные различия

Северный мансийский vs южный мансийский - как британский английский vs американский, только хуже. Модель начинает мешать диалекты. Решение: указывать диалект как специальный токен в начале предложения.

Проблема 3: Оценка без референсов

Для многих предложений у нас есть только один эталонный перевод. Модель может выдать грамматически правильный, но лексически другой вариант - и получит низкий BLEU. Используем BERTScore или COMET.

Самый болезненный момент: носители языка критикуют перевод, потому что "так не говорят". Но они сами говорят по-разному! Приходится выбирать "усредненный" вариант, который всех разочаровывает.

Развертывание: чтобы пользоваться могли даже бабушки

Носители мансийского - часто пожилые люди в отдаленных поселках. Им нужен:

  • Простой веб-интерфейс (большие кнопки, крупный шрифт)
  • Офлайн-режим (интернет в тайге - это роскошь)
  • Голосовой ввод/вывод (многие не любят печатать)

Мы сделали Telegram-бота и простое веб-приложение на Flask. Модель весит 300 МБ - помещается на телефон. Для голосового интерфейса использовали легкие TTS-модели.

# Минимальный Flask API для перевода
from flask import Flask, request, jsonify
import torch
from transformers import AutoModelForSeq2SeqLM, AutoTokenizer

app = Flask(__name__)

# Загружаем модель один раз при старте
model = AutoModelForSeq2SeqLM.from_pretrained("./models/mansi_final")
tokenizer = AutoTokenizer.from_pretrained("./models/mansi_final")
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)
model.eval()

@app.route('/translate', methods=['POST'])
def translate():
    data = request.get_json()
    text = data.get('text', '')
    dialect = data.get('dialect', 'northern')  # северный или южный
    
    # Добавляем токен диалекта
    if dialect == 'southern':
        text = " " + text
    else:
        text = " " + text
    
    inputs = tokenizer(text, return_tensors="pt", max_length=128, truncation=True)
    inputs = {k: v.to(device) for k, v in inputs.items()}
    
    with torch.no_grad():
        outputs = model.generate(**inputs, max_length=256, num_beams=4)
    
    translated = tokenizer.decode(outputs[0], skip_special_tokens=True)
    
    return jsonify({
        'original': data['text'],
        'translated': translated,
        'dialect': dialect
    })

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=False)

Что в итоге получилось?

Модель, которая переводит простые бытовые фразы с точностью около 70%. Сложные предложения, идиомы, поэзию - не тянет. Но для:

  • Перевода меню в школе
  • Объявлений в больнице
  • Простых разговорников
  • Оцифровки архивных записей

- работает. Главное - она дает базовый инструмент, который могут дорабатывать лингвисты.

💡
Фишка: модель стала использоваться в школах Ханты-Мансийского округа. Дети вводят русские фразы, получают мансийские - и наоборот. Это не идеальный перевод, но это работает. И главное - показывает, что язык "живой", у него есть цифровое присутствие.

Советы, если беретесь за свой исчезающий язык

  1. Начинайте со сбора данных, а не с выбора модели. Без данных - ничего.
  2. Привлекайте носителей с самого начала. Не делайте "для них", делайте "с ними".
  3. Используйте transfer learning. Не пытайтесь обучить с нуля на 1000 примеров.
  4. Ожидайте низких метрик. BLEU 10-15 - это успех для малоресурсного языка.
  5. Сделайте простой интерфейс. Если людям сложно пользоваться - проект умрет.
  6. Планируйте постоянное обновление. Каждый новый текст улучшает модель.

И последнее: не надейтесь сделать идеальный переводчик. Цель - не заменить человека, а создать инструмент, который поможет сохранить язык. Даже если он ошибается в 30% случаев - это лучше, чем ничего.

Кстати, похожие подходы работают и для других задач - например, для создания RAG-систем на специфичных доменах или для перевода кода между языками. Мало данных - не приговор, это вызов.

P.S. Через год после запуска проекта в мансийский корпус добавили 2000 новых предложений - школьные сочинения, переписки в соцсетях. Модель дообучили - и BLEU вырос с 15.3 до 18.7. Мораль: такие проекты должны быть живыми. Как и языки, которые они пытаются сохранить.