Если вы когда-либо пытались настроить модель на основе трансформаторов, такую как GPT2, вы, должно быть, столкнулись с множеством проблем, как и я. Будь то подготовка набора данных, создание загрузчика данных, добавление токенов, создание масок внимания или создание пользовательского цикла обучения (если вы такой же стойкий пользователь PyTorch, как и я), нам требуется довольно много времени, чтобы в состоянии сделать это. Кроме того, отсутствие онлайн-ресурсов (на момент написания этого блога), которые точно соответствовали бы нашему варианту использования, является большой проблемой для нас в достижении того, что мы хотим!
Недавно я столкнулся с FastAI и был очень впечатлен его возможностями и простыми в использовании API. В этой статье мы рассмотрим, как можно настроить языковую модель на основе GPT2 для набора данных wikitext-2.
Примечание. Код и несколько пояснений взяты из официальной документации FastAI.
Библиотека Трансформеров
Начнем с установки библиотеки трансформаторов:
!pip install -Uq transformers
Здесь мы настроим предварительно обученную модель GPT2 и настроим викитекст-2. Давайте импортируем GPT2LMHeadModel
и GPT2Tokenizer
для подготовки данных.
from transformers import GPT2LMHeadModel, GPT2TokenizerFast
Давайте загрузим предварительно обученные веса модели GPT2 и токенизатора, используя следующий код:
pretrained_weights = 'gpt2' tokenizer = GPT2TokenizerFast.from_pretrained(pretrained_weights) model = GPT2LMHeadModel.from_pretrained(pretrained_weights)
Если вы ранее не работали с объектом токенизатора, токенизаторы в HuggingFace обычно выполняют токенизацию и числовую обработку за один шаг.
ids = tokenizer.encode('Hi my name is Sahil Sheikh') print(ids) # output: [17250, 616, 1438, 318, 22982, 346, 30843]
Предложение кодируется в список «чисел», где каждое число представляет собой токен в предложении.
Чтобы декодировать предложение обратно в его исходную форму, мы можем использовать:
tokenizer.decode(ids) # output: Hi my name is Sahil Sheikh
Давайте сначала протестируем модель, прежде чем настраивать ее с помощью нашего набора данных. Для тестирования нашей модели мы будем использовать PyTorch (мой любимый :D).
import torch t = torch.LongTensor(ids)[None] preds = model.generate(t)
Модель можно использовать для создания прогнозов, поскольку она предварительно обучена. У него есть метод generate
, который ожидает пакет подсказок, поэтому мы передаем ему наши идентификаторы и добавляем одно пакетное измерение (есть предупреждение о заполнении, которое мы также можем игнорировать).
Строка t = torch.LongTensor(ids)[None]
создает тензор из списка ids
(закодированное предложение) и изменяет его форму. [None]
используется для добавления дополнительного измерения к тензору.
Прогнозы по умолчанию имеют длину 20:
# You can check the shape of the predictions and the predictions itself using # the below code print(preds.shape,preds[0])
Мы можем использовать метод декодирования (который предпочитает массив numpy тензору):
tokenizer.decode(preds[0].numpy()) # output: Hi my name is Sahil Sheikh, I am a Muslim. # This is literally what GPT2 returned xD
Преодоление разрыва с помощью фастая
Теперь давайте посмотрим, как мы можем использовать fastai для тонкой настройки этой модели на wikitext-2, используя все обучающие утилиты (поиск скорости обучения, политика 1cycle и т. д.).
from fastai.text.all import *
Затем мы загружаем набор данных (если его нет), он поставляется в виде двух файлов csv:
path = untar_data(URLs.WIKITEXT_TINY) path.ls()
Вы можете просмотреть данные с помощью метода .head():
df_train = pd.read_csv(path/'train.csv', header=None) df_valid = pd.read_csv(path/'test.csv', header=None) df_train.head()
Далее собираем все тексты в один numpy массив (поскольку с фастаем так будет проще):
all_texts = np.concatenate([df_train[0].values, df_valid[0].values])
Чтобы обработать эти данные для обучения модели, нам нужно построить Transform
, который будет применяться лениво. В этом случае мы могли бы сделать предварительную обработку раз и навсегда и использовать преобразование только для декодирования, но быстрый токенизатор от HuggingFace, как следует из его названия, быстр, поэтому такой способ не сильно влияет на производительность. .
В фасте Transform
можно определить:
- метод
encodes
, который применяется при вызове преобразования - метод
decodes
, который применяется, когда вы вызываете методdecode
преобразования, если вам нужно декодировать что-либо для демонстрации (например, преобразование идентификаторов в текст здесь) - метод
setups
, который устанавливает некоторое внутреннее состояниеTransform
(здесь он не нужен, поэтому мы его пропустим)
class TransformersTokenizer(Transform): def __init__(self, tokenizer): self.tokenizer = tokenizer def encodes(self, x): toks = self.tokenizer.tokenize(x) return tensor(self.tokenizer.convert_tokens_to_ids(toks)) def decodes(self, x): return TitledStr(self.tokenizer.decode(x.cpu().numpy()))
Затем вы можете сгруппировать свои данные с этим Transform
, используя TfmdLists
. Он содержит как набор для обучения, так и набор для проверки. Мы указываем индексы обучающего набора и набора проверки с помощью splits
(здесь все первые индексы до len(df_train)
, а затем все остальные индексы):
Мы можем посмотреть оба декодирования, используя метод «show_at»:
show_at(tls.train, 0)
Библиотека fastai ожидает, что данные будут собраны в объект DataLoaders
. Мы можем получить его, используя метод dataloaders
. Мы должны указать размер пакета и длину последовательности, чтобы определить загрузчик данных. Мы будем тренироваться с последовательностями размером 256 (GPT2 использовал длину последовательности 1024, вы можете увеличить размер с 256 до 1024, если ваш GPU позволяет):
bs,sl = 4,256 dls = tls.dataloaders(bs=bs, seq_len=sl)
Другой способ собрать данные — предварительно обработать тексты раз и навсегда и использовать преобразование только для декодирования тензоров в тексты:
def tokenize(text): toks = tokenizer.tokenize(text) return tensor(tokenizer.convert_tokens_to_ids(toks)) tokenized = [tokenize(t) for t in progress_bar(all_texts)]
Нам также нужно изменить Tokenizer, например:
class TransformersTokenizer(Transform): def __init__(self, tokenizer): self.tokenizer = tokenizer def encodes(self, x): return x if isinstance(x, Tensor) else tokenize(x) def decodes(self, x): return TitledStr(self.tokenizer.decode(x.cpu().numpy()))
В методе encodes
мы по-прежнему учитываем случай, когда мы получаем что-то, что еще не токенизировано, на тот случай, если нам нужно будет создать набор данных с новыми текстами, используя это преобразование.
tls = TfmdLists(tokenized, TransformersTokenizer(tokenizer), splits=splits, dl_type=LMDataLoader) dls = tls.dataloaders(bs=bs, seq_len=sl)
Тонкая настройка модели
Модель HuggingFace вернет кортеж в выходных данных с фактическими прогнозами и некоторыми дополнительными активациями. Чтобы работать внутри тренировочного цикла fastai, нам нужно удалить те, которые используют Callback
: мы используем их, чтобы изменить поведение тренировочного цикла.
Здесь нам нужно написать событие after_pred
и заменить self.learn.pred
(которое содержит прогнозы, которые будут переданы в функцию потерь) только его первым элементом. В обратных вызовах есть ярлык, который позволяет вам получить доступ к любому из базовых атрибутов Learner
, поэтому мы можем написать self.pred[0]
вместо self.learn.pred[0]
. Этот ярлык работает только для чтения, а не для записи, поэтому мы должны написать self.learn.pred
справа (иначе мы бы установили атрибут pred
в Callback
).
class DropOutput(Callback): def after_pred(self): self.learn.pred = self.pred[0]
Теперь мы готовы создать наш Learner
, который представляет собой объект fastai, группирующий данные, модель и функцию потерь и отвечающий за обучение или вывод модели. Поскольку мы находимся в настройках языковой модели, мы передаем недоумение в качестве метрики, и нам нужно использовать только что определенный обратный вызов. Наконец, мы используем смешанную точность, чтобы сохранить каждый бит памяти, который мы можем (и если у вас есть современный графический процессор, это также ускорит обучение):
learn = Learner(dls, model, loss_func=CrossEntropyLossFlat(), cbs=[DropOutput], metrics=Perplexity()).to_fp16()
Мы можем сделать Learn.lr_find(), чтобы найти оптимальную скорость обучения для процесса тонкой настройки.
Кривая скорости обучения предлагает выбрать что-то между 1e-4 и 1e-3.
Чтобы начать процесс тонкой настройки, запустите следующий код:
learn.fit_one_cycle(1, 1e-4)
Теперь, всего за одну эпоху тонкой настройки и небольшой регуляризации, наша модель на самом деле не улучшилась, поскольку она уже была потрясающей.
Вот и все!
Заключение
Нам, специалистам по данным, нелегко оставаться в курсе ВСЕХ существующих библиотек. Моя цель - найти такие библиотеки и API и представить их вам, чтобы вы могли использовать их, когда ПРИХОДИТ ПОДХОДЯЩЕЕ ВРЕМЯ :D
Если вам понравилась статья, подписывайтесь на меня на Medium :D
Спасибо :D