По данным IMDb, в нем более полумиллиона названий фильмов и почти 5,5 миллионов обзоров пользователей, каждый из которых имеет рейтинг от 1 до 10 и текстовый обзор; Я хочу использовать эти обзоры для обучения модели, которая может угадывать настроения будущих обзоров. Тип обработки естественного языка называется текстовой классификацией. Большая часть работы здесь взята из Урока 8 курса FastAI, который я не могу рекомендовать всем, кто интересуется глубоким обучением. Следуйте вместе с блокнотом, используемым для обучения модели.
Предварительная обработка
Текст — довольно абстрактное понятие для компьютера, который понимает только единицы и нули, — его нужно поработать, чтобы подготовить его к пониманию человеческого языка. Каждое слово в корпусе похоже на категориальную переменную с конечным числом уровней; уровни корпуса называются словарным запасом.
Есть два шага:
- Токенизация: создайте список слов/подслов для корпуса.
- Нумеризация: преобразуйте слова в числа, а их положение — в индекс.
Токенизация
Кажется довольно простым: передать текст токенизатору и начать обучение? К сожалению, нам нужно обрабатывать такие вещи, как пунктуация и переносы. Существуют разные способы разбиения текста на слова, но я сосредоточусь здесь на том, который основан на Word. К счастью, существуют токенизаторы слов, один из которых называется spacy
. Он знает, как обрабатывать слова, такие как «это».
first(spacy(["It's really sunny on Monday"])) >> ['It', "'s", 'really', 'sunny', 'on', 'Monday']
FastAI добавляет свои собственные специальные правила токенов, чтобы добавить контекст к слову, например, начало отзыва или заглавную букву.
tok = Tokenizer(spacy)(["It's really sunny on Monday"]); tok >> ['xxbos', 'xxmaj', 'it', "'s", 'really', 'sunny', 'on', 'xxmaj', monday']
xxbos
означает начало потока (bos), а xxmaj
— заглавную букву. Такие слова, как «Оно» и «оно», для нас одно и то же, но для модели они уникальны. Эти специальные токены анализируют их как одинаковые, но с токеном с заглавной буквы, чтобы различать их.
Нумерация
Теперь модель знает, как разбить каждое слово в обзоре, но нам все еще нужно преобразовать их в нечто числовое, понятное компьютеру. Нумерация работает как переменные с горячим кодированием, где Numericalize
составляет список всех возможных уровней для категориальной переменной, заменяя каждый индексом в словаре.
num = Numericalize(min_freq=3, max_vocab=60000) num.setup(tok) num(tok) >> tensor([ 4, 11, 435, 434, ... ])
Сохранение всех слов нецелесообразно, потому что словарный запас будет слишком большим, его ограничивает свойство max_vocab
, а min_freq
требует, чтобы слова появлялись несколько раз перед добавлением. Этот тензор теперь может быть загружен непосредственно в слой внедрения сети.
RNN
Рекуррентные нейронные сети используются в НЛП, потому что они взвешивают положение текста в последовательности. Основная идея заключается в том, что первый уровень сети использует встраивание первого слова; второй использует встраивание второго слова в сочетании с выходными активациями первого слова; третий использует вложение третьего слова с активациями второго слова. Активации предыдущего слова называются скрытым состоянием.
Вместо того, чтобы явно объявлять каждый уровень, они реорганизуют их в цикл for — это то, что повторяется. Ограничение цикла for
— это количество слоев.
Первый класс выше, LanguageModel
, явно передает следующему слою каждой сети предыдущие активации; LanguageModelRecurrent
использует цикл для реализации того же самого. Приведенная выше модель обеспечивает точность 50 %.
Состояние и сигнал
В forward()
скрытое состояние h
сохраняет предыдущие активации; затем он сбрасывается на ноль, отбрасывая потенциально важную информацию о словах, которые дают больше контекста обзору. Перемещение сброса в init()
решает эту проблему за счет сброса только при создании экземпляра объекта. Это сохраняет состояние, но открывает другую проблему: взрыв градиента. Сохранение состояния создает слой для каждого токена в корпусе, которых может быть 60 000, что требует вычислений градиента для каждого слоя, что замедляет обучение. Эту проблему решает отбрасывание всех градиентов, кроме первых трех; это называется усеченным обратным распространением во времени (tBPTT).
Вторая вышеприведенная реализация RNN добавляет сигнал, перебирая длину последовательности sl
вместо трех слов. Увеличение длины предложения дает больше контекста; потенциально повышая точность — это называется сигнализацией. Добавление состояния увеличило точность до 57 %, а добавление дополнительного сигнала — до 64 %.
Многослойные и LSTM
Многослойные RNN берут выходные данные первого слоя и вводят их в следующий, предоставляя модели более длительный временной горизонт для обучения и улучшая понимание текста. Учитывая это, точность должна значительно улучшиться, но вместо этого она снизилась до 48%, что на 16% меньше, чем в нашей последней однослойной модели. Причиной этого падения являются явления, называемые взрывным и исчезающим градиентами. Многократное умножение матриц приводит к снижению их точности из-за того, что каждое число с плавающей запятой имеет только 32 бита; поскольку числа с плавающей запятой отклоняются от 1, они теряют точность, поскольку для хранения их значения требуется больше битов. Прохождение матрицы через два слоя приводит к тому, что ее числа расходятся с ее фактическим значением при каждом умножении. Если градиенты слишком малы, алгоритм не обновляется; слишком большие, и их обновления слишком радикальны.
Архитектура долговременной кратковременной памяти (LSTM) решает эту проблему, сохраняя больше памяти предложения, вводя другое скрытое состояние, называемое состоянием ячейки. Скрытое состояние фокусируется на текущей лексеме слова, в то время как состояние ячейки учитывает активацию слов ранее в последовательности слов.
Оранжевые прямоугольники на диаграмме выше представляют слои в сети, tanh – гиперболический tan, а другой – сигмоидальный. Масштабирование выходных данных от 0 до 1 для sigmoid и от -1 до +1 для tanh решает проблему взрывающихся градиентов. состояние ячейкиуправляет LSTM,который обновляется с помощью желтых кружков на диаграмме; произведение их входов определяет состояние: обновить тех, кто ближе к 1; отбросить ближе к 0. Сеть теперь может поддерживать долговременную память слов, что облегчает понимание длинных предложений.
init()
определяет каждый вентиль, используемый в LSTM, а forward()
реализует их. Удобно, что в PyTorch есть встроенный класс, так что не нужно все это писать. Благодаря LSTM точность многослойной модели увеличилась с 48% до 81% — довольно большой скачок! Потери при проверке модели были намного выше, чем при обучении, что свидетельствует о переобучении данных.
Регуляризация
В традиционных моделях ML этот шаг достаточно прост: к функции потерь добавляется только член регуляризации:
В НЛП процесс немного сложнее: некоторые подходы используют увеличение данных, перевод текста на другой язык и его повторный перевод в фразовые предложения другим способом; это открытая область исследований, которая выходит за рамки данной статьи.
Выпадение
Идея исключения заключается в случайном удалении случайных нейронов в сети, чтобы помочь сети работать для достижения общей цели. Исключение этих нейронов вносит шум в сеть, делая модель более надежной и менее склонной к переобучению.
Отбрасывание нейронов контролируется вероятностью, p
, меняющейся от слоя к слою с соответствующим отбрасыванием в слоях сложной сети. Отсев следует распределению Бернулли, определенному ниже.
Dropout реализован в PyTorch:
class Dropout(Module): def __init__(self): self.p = p def forward(self, x): if not self.training: return x mask.new(*x.shape).bernoulli_(1-p) return x * mask.div_(1-p)
Привязка веса
Входные вложения языковой модели (первый слой) сопоставляют английские слова с активациями, а выходные активации — с английскими словами. Установка одинаковых значений может повысить точность. Вот бумага.
self.h_o.weight = self.i_h.weight
FastAI предоставляет класс TextLearner
, который сделает за нас большую часть работы:
learn = TextLearner(dls, LanguageModelLSTM(len(vocab), 64, 2, 0.4), loss_func=CrossEntropyLossFlat(), metrics=accuracy) learn.fit_one_cycle(15, 1e-2, wd=0.1)
Точность увеличилась с 81 % до 87 %; это было бы лучше всего в мире пять лет назад! Создатели FastAI обучили модель, используя те же методы, что и выше, чтобы достичь точности 94% — только недавно побитой.
Прогноз
FastAI предоставляет набор данных IMDb с 25 000 поляризованных обзоров, доступ к которым можно получить с помощью довольно простого синтаксиса:
path = untar_data(URLs.IMDB)
DataBlock
использует path
для загрузки примеров в модель.
dls_clas = DataBlock( blocks=(TextBlock.from_folder(path), CategoryBlock), get_y=parent_label, get_items=partial(get_text_files, folders=['train', 'test']), splitter=GrandparentSplitter(valid_name='test') ).dataloaders(path, path=path, bs=128, seq_len=72)
При создании модели используются DataBlock
,AWD_LSTM
: регуляризованная архитектура LSTM и drop_multi
: глобальное отсев. to_fp16()
преобразует все 32-битные числа с плавающей запятой в 16-битные, что ускоряет обучение.
l3 = text_classifier_learner( dls_clas, AWD_LSTM, drop_mult=0.5, metrics=accuracy ).to_fp16()
Я рекомендую сохранить обученную модель и словарный запас в pickle
файлах, так как на их обучение на графических процессорах Colab ушло более двух часов. Загрузка модели и создание прогнозов выполняется ниже:
l3 = l3.load('/path/to/my_saved_model') l3.predict('That was terrible!') >> ('neg', tensor(0), tensor([0.8067, 0.1933]))
Хостинг
Обычно я помещал эту модель в веб-приложение Streamlit для логического вывода, как я делал это в своих предыдущих проектах, но поскольку сохраненная модель очень велика, мне нужно будет использовать облачный хостинг.