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

Я хотел обучить модель перевода с японского на английский и японский с использованием токенизаторов предложений, но было сложно найти предварительно обученные токенизаторы для больших данных (более 10 миллионов предложений). Я обнаружил, что Наборы данных Huggingface предоставляют простой способ использовать общий набор данных обхода, поэтому я решил использовать эти наборы данных и обучить несколько моделей для совместного использования.

Репозиторий кода содержит все необходимое для настройки среды и обучения любому языку, который входит в число 100 языков в общем обходе 100. Следует отметить, что обучение с большими данными требует большого объема памяти (512 ГБ +) RAM , так что это не то, что вы должны ожидать от своего ноутбука. Репозиторий кода содержит предварительно обученные модели как для японского, так и для английского языков с объемом словарного запаса 8000, 16000, 32000 и 48000. Количество предложений, используемых для каждой модели, составляет около 75 миллионов.

Код обучения

Блокнот jupyter в репозитории кода будет содержать те же фрагменты кода, что и здесь, но я добавлю сюда еще несколько комментариев. Функции, о которых я говорю в этой статье, можно найти здесь.

Сначала мы собираемся использовать наборы данных huggingface и загрузить в него общий набор данных для сканирования 100 языков и японскую часть. Набор данных разделен на поезд и тестовую часть. Один только раздел поезда содержит сотни миллионов предложений, чего более чем достаточно для обучения модели. Ожидайте, что вы потратите до десятков часов на загрузку данных вначале.

import datasets

# load the japanese dataset
dataset_ja = datasets.load_dataset("cc100", lang="ja")

# sample a data entry
dataset_ja["train"][961563]

Модуль фраза поставляется с обучающим API Python, который использует предложения в файле, по одному предложению в строке. Сценарий разделит набор данных и запишет содержимое в файлы, 10 миллионов записей данных (около 15 миллионов предложений) на файл. В результате будет около 50 файлов по 1,5 ГБ каждый.

from src.make_datasets import make_sentence_files
# split dataset into files
make_sentence_files(dataset_ja["train"])

На самом деле нам не нужны все данные, поэтому давайте рассмотрим 5 файлов, объединим их в один большой файл и вернем путь к файлу:

import cfg
from src.make_datasets import sample_and_make_tempfile

tempfile_path = sample_and_make_tempfile(
    sentences_dir = cfg.JP_SENTENCES_DIR
    , num_files = 5)

Теперь, когда данные обучения преобразованы в подходящий формат, мы можем вызвать API обучения части предложения, обучить модель и сохранить:

import sentencepiece as spm


def train_jp(vocab_size):
  model_prefix = "cc100_jp" + "_vocab_" + str(vocab_size)
  spm.SentencePieceTrainer.train(input=tempfile_path
      , model_prefix=model_prefix
      , vocab_size=vocab_size
      , character_coverage = 0.9995
      , num_threads=60
      , train_extremely_large_corpus=True
  )
train_jp(8000)

Есть несколько параметров, которые можно настроить, но наиболее важными из них являются размер словарного запаса и охват символов. Для нейронных машинных переводов обычно используется размер словаря 32000. Для более простых задач и если требуется небольшая модель, следует использовать меньшие размеры словарного запаса. Для английского языка покрытие символов должно быть установлено на 100% (1), что означает, что каждый символ должен присутствовать в словаре. Для языков с большим количеством символов, таких как японский или особенно китайский, некоторые редкие символы могут быть опущены.

Похоже, что train_extremely_large_corpus = True предотвращает сбои при больших объемах данных, но сбои все равно происходят, если было использовано достаточно данных для обучения. Num_threads будет контролировать, сколько параллельных процессов используется для обучения, настраивая в соответствии с количеством доступных ядер ЦП. Для обучения я использовал экземпляр AWS r5.16xlarge с 64 ядрами и 512 ГБ. Одно обучение длилось несколько часов.

Некоторые проблемы, с которыми я столкнулся во время обучения

Нагрузка и множество тихих сбоев. После начала обучения выходов не будет слишком много, поэтому трудно увидеть, что происходит, и я не нашел способа сделать обучение более подробным. В какой-то момент экземпляр AWS больше не отвечал, поэтому это было признаком сбоя экземпляра. Я предполагаю, что это связано с нехваткой памяти для процесса. После экспериментов мне показалось, что каждый раз около 512 ГБ достаточно для используемых мной параметров.

Иногда мне удавалось обучить модель с выборкой 7 файлов, а не 5, описанных выше. Мне также не удалось обучить модель, передав несколько файлов в виде списка в SentencePieceTrainer.train (), хотя, согласно документации, это должно поддерживаться. Я решил объединить файлы в один большой файл, который, казалось, работал лучше.

При желании можно было бы копнуть глубже и заставить обучение работать с еще большими данными. Однако обучающих данных из 75 миллионов предложений из различных доменов должно хватить для любых целей, и я не думаю, что большие данные каким-либо образом повлияют на токенизацию.

Некоторые комментарии об использовании модели фразы-предложения

Модуль Python readme предложит несколько примеров, но основное его использование:

import sentencepiece as spm
tokenizer = spm.SentencePieceProcessor(model_file="blabla.model")
tokenizer.encode("This is an example sentence", out_type=str)
...
['▁This', '▁is', '▁an', '▁example', '▁sentence']

Основным отличием предложения от других токенизаторов является регуляризация подслова. В качестве примера рассмотрим слово лев. Вероятно, само слово не настолько распространено, чтобы у нас в лексиконе была лексема leoh, но наверняка у нас должны быть отдельные буквы, обозначающие l, e, o, h. Но также le и oh наверняка присутствуют во многих словах и в конечном итоге тоже являются токенами. Таким образом, мы могли бы обозначить leoh как le, ой или, может быть, le, o, h или…

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

for _ in range(5):
  print(tokenizer.encode('Hello Medium', out_type=str,
      enable_sampling=True, alpha=0.1, nbest_size=-1))
...
['▁', 'Hello', '▁', 'M', 'ed', 'ium']
['▁', 'H', 'ello', '▁Me', 'di', 'u', 'm']
['▁', 'He', 'l', 'lo', '▁', 'Me', 'd', 'i', 'um']
['▁Hello', '▁Me', 'd', 'i', 'um']
['▁Hello', '▁Medium']

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

UNK_IDX = tokenizer.piece_to_id('<unk>')
BOS_IDX = tokenizer.piece_to_id('<s>')
EOS_IDX = tokenizer.piece_to_id('</s>')
PAD_IDX = len(tokenizer)
print(UNK_IDX, PAD_IDX, BOS_IDX, EOS_IDX)
...
0 32000 1 2

Вот и все, я надеюсь, вы найдете применение предварительно обученным моделям, или мое репозиторий окажется полезным при обучении токенизаторов для вашего языка. Спасибо за чтение!