Оптимизация гиперпараметров для классификации текста с чутьем

Это продолжение нашей предыдущей публикации о Современной классификации текстов. Мы объясняем, как выполнить оптимизацию гиперпараметров с помощью библиотеки Flair Python NLP для достижения оптимальных результатов в классификации текста, превосходящих Google AutoML Natural Language.

Что такое оптимизация гиперпараметров и почему мы не можем просто сделать это вручную?

Оптимизация (или настройка) гиперпараметров - это процесс выбора набора оптимальных параметров для алгоритма машинного обучения. Препроцессоры данных, оптимизаторы и алгоритмы машинного обучения - все получают набор параметров, которые определяют их поведение. Для достижения оптимальной производительности их необходимо настроить в соответствии со статистическими свойствами, типом функций и размером используемого набора данных. Наиболее типичные гиперпараметры в глубоком обучении включают скорость обучения, количество скрытых слоев в глубокой нейронной сети, размер пакета, выпадение…

В НЛП мы также сталкиваемся с рядом других гиперпараметров, часто связанных с предварительной обработкой и встраиванием текста, такими как тип встраивания, размер встраивания, количество слоев RNN…

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

Наше пространство поиска растет экспоненциально с увеличением количества настраиваемых параметров.

Предполагая дискретные варианты, это означает, что если у нас есть 8 параметров, каждый из которых имеет 10 дискретных вариантов, мы получим 10⁸ возможных комбинаций гиперпараметров. Это делает невозможным ручной подбор параметров, если для обучения модели обычно требуется значительное количество времени и ресурсов.

Существует множество методов оптимизации гиперпараметров, таких как поиск по сетке, случайный поиск, байесовская оптимизация, градиентные методы и, наконец, TPE. Древовидная структура Parzen Estimator (TPE) - это метод, который мы использовали в оболочке Flair для Hyperopt - популярной библиотеки оптимизации гиперпараметров Python.

Настройка гиперпараметров с помощью Flair

Flair предоставляет простой API для настройки параметров классификатора текста. Однако нам нужно сообщить ему, какие типы гиперпараметров ему нужно настроить и какие значения он должен учитывать для них.
Запуск оптимизатора не сложнее, чем обучение самого классификатора, но он требует значительно больше времени и ресурсов, поскольку по сути, он выполняет обучение большое количество раз. Поэтому рекомендуется запускать это на оборудовании с ускорением на GPU.

Мы выполним гиперпараметрическую оптимизацию модели текстового классификатора, обученной на Kaggle SMS Spam Collection Dataset, которая научится различать спам и не спам-сообщения.

Готовиться

Чтобы подготовить набор данных, обратитесь к разделу Предварительная обработка - Создание набора данных в Современной классификации текстов, где мы получаем train.csv, test.csv и dev.csv. Убедитесь, что наборы данных хранятся в том же каталоге, что и скрипт, запускающий Flair.

Вы можете проверить, доступен ли у вас графический процессор для обучения, выполнив:

import torch
torch.cuda.is_available()

Он возвращает логическое значение, указывающее, доступен ли CUDA для PyTorch (поверх которого написан Flair).

Настройка параметров

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

Для дискретных параметров используйте:

search_space.add(Parameter.PARAMNAME, hp.choice, options=[1, 2, ..])

А для единообразных непрерывных параметров используйте:

search_space.add(Parameter.PARAMNAME, hp.uniform, low=0.0, high=0.5)

Список всех возможных параметров можно увидеть здесь.

Затем вам нужно будет указать некоторые параметры, относящиеся к типу классификатора текста, который мы хотим использовать, и сколько training_runs и epochs нужно запустить.

param_selector = TextClassifierParamSelector(
    corpus=corpus, 
    multi_label=False, 
    base_path='resources/results', 
    document_embedding_type='lstm',
    max_epochs=10, 
    training_runs=1,
    optimization_value=OptimizationValue.DEV_SCORE
)

Обратите внимание, что DEV_SCORE установлено в качестве значения оптимизации. Это чрезвычайно важно, потому что мы не хотим оптимизировать наши гиперпараметры на основе набора тестов, поскольку это может привести к переобучению.

Наконец, мы запускаем param_selector.optimize(search_space, max_evals=100), который выполнит 100 вычислений оптимизатора и сохранит наши результаты в resources/results/param_selection.txt

Полный исходный код для запуска всего процесса выглядит следующим образом:

from flair.hyperparameter.param_selection import TextClassifierParamSelector, OptimizationValue
from hyperopt import hp
from flair.hyperparameter.param_selection import SearchSpace, Parameter
from flair.embeddings import WordEmbeddings, FlairEmbeddings
from flair.data_fetcher import NLPTaskDataFetcher
from pathlib import Path
corpus = NLPTaskDataFetcher.load_classification_corpus(Path('./'), test_file='test.csv', dev_file='dev.csv', train_file='train.csv')
word_embeddings = [[WordEmbeddings('glove'), FlairEmbeddings('news-forward'), FlairEmbeddings('news-backward')]]
search_space = SearchSpace()
search_space.add(Parameter.EMBEDDINGS, hp.choice, options=word_embeddings)
search_space.add(Parameter.HIDDEN_SIZE, hp.choice, options=[32, 64, 128, 256, 512])
search_space.add(Parameter.RNN_LAYERS, hp.choice, options=[1, 2])
search_space.add(Parameter.DROPOUT, hp.uniform, low=0.0, high=0.5)
search_space.add(Parameter.LEARNING_RATE, hp.choice, options=[0.05, 0.1, 0.15, 0.2])
search_space.add(Parameter.MINI_BATCH_SIZE, hp.choice, options=[16, 32, 64])
param_selector = TextClassifierParamSelector(
    corpus=corpus, 
    multi_label=False, 
    base_path='resources/results', 
    document_embedding_type='lstm',
    max_epochs=10, 
    training_runs=1,
    optimization_value=OptimizationValue.DEV_SCORE
)
param_selector.optimize(search_space, max_evals=100)

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

Полученные результаты

Оптимизатор проработал около 6 часов на графическом процессоре и выполнил 100 оценок. Окончательные результаты были записаны в resources/results/param_selection.txt.

Последние несколько строк отображают лучшую комбинацию параметров, как показано ниже:

--------evaluation run 97
dropout: 0.19686569599930906
embeddings: ./glove.gensim, ./english-forward-v0.2rc.pt, lm-news-english-backward-v0.2rc.pt
hidden_size: 256
learning_rate: 0.05
mini_batch_size: 32
rnn_layers: 2
score: 0.009033333333333374
variance: 8.888888888888905e-07
test_score: 0.9923
...
----------best parameter combination
dropout: 0.19686569599930906
embeddings: 0 
hidden_size: 3
learning_rate: 0 <- *this means 0th option*
mini_batch_size: 1
rnn_layers: 1

На основе ontest_score результатов настройки, подтвержденных несколькими дополнительными оценками, мы достигли тестового показателя f1 на уровне 0,9923 (99,23%)!

Это означает, что мы ненамного превзошли AutoML от Google.

подсказка: если precision = recall thenf-score = precision = recall

Означает ли это, что я всегда смогу достичь самых современных результатов, следуя этому руководству?

Короткий ответ: нет. Руководство должно дать вам хорошее представление о том, как использовать оптимизатор гиперпараметров Flair, и не является исчерпывающим сравнением структур классификации текста НЛП. Использование описанных подходов, безусловно, даст результаты, сопоставимые с другими современными структурами, но они будут различаться в зависимости от набора данных, используемых методов предварительной обработки и определенного пространства поиска гиперпараметров.

Обратите внимание, что при выборе наилучшего сочетания параметров Flair учитывает как потерю, так и дисперсию полученных результатов. Следовательно, модель с наименьшими потерями и наибольшим значением f1 не обязательно будет выбрана как лучшая.

Итак, как мне теперь использовать параметры для обучения реальной модели?

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

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