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

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

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

Проблема

Мы будем решать проблему NER (распознавание именованных объектов), применяемую к набору данных рецепта [ссылка здесь: Набор вкусов]. Целью этой задачи является извлечение структурированной информации из необработанного текста в виде помеченных сущностей.

Наша цель — присвоить правильный тег каждому элементу необработанного текста.

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

Модели

Архитектура:

Базовая архитектура нашей модели:

Схема тегирования:

Упомянутые выше метки BIO — это способ кодирования тегов в последовательности токенов, сгенерированных из необработанного текста, как описано здесь:

Стартовый токен каждого объекта {LABEL} помечен как B-{LABEL}, а каждый другой токен этого же объекта помечен как I-{LABEL}. Все остальные токены помечены как O.

Выполнение:

Мы будем использовать библиотеку Hugging Face Transformers для BERT. Мы можем подключить предварительно обученную модель, как и любой другой слой факела, поскольку это экземпляр nn.Module.

В __init__ модели мы можем сделать это -›

from transformers import BertConfig, BertModel
class BertNerModel(BaseModel):
    def __init__(
        self,
        ...
        dropout=0.2,
        bert_path=None,
        ...
    ):
        super().__init__()

        ...
        self.bert = BertModel(config=CONFIG)

        ...

        if bert_path:
            state_dict = torch.load(bert_path)
            self.bert.load_state_dict(state_dict)

        self.do = nn.Dropout(p=dropout)

        self.out_linear = nn.Linear(CONFIG.hidden_size, n_classes)

Где CONFIG — это экземпляр BertConfig, инициализированный из файла JSON.

Затем в прямом методе мы делаем:

def forward(self, x):

    mask = (x != self.pad_idx).int()
    x = self.bert(
        x, attention_mask=mask, encoder_attention_mask=mask
    ).last_hidden_state
    # [batch, Seq_len, CONFIG.hidden_size]

    x = self.do(x)

    out = self.out_linear(x)

    return out

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

Для сравнения мы также будем обучать модель с нуля, используя nn.TransformerEncoder. Реализация следует той же логике, что и в BERT. Все это можно найти здесь.

Результаты

Мы отслеживаем наши показатели потерь с помощью Tensorboard:

Мы видим, что модель, основанная на тонкой настройке BERT, обобщает лучше, чем модель, обученная с нуля. Это дополнительно подтверждается представленными ниже оценками F1:

Оценка F1 на проверочном наборе:

Инициализация BERT: 92,9%

nn.TransformerEncoder: 76,3%

Скорость логического вывода на графическом процессоре 3080 RTX (размер пакета 1):

Инициализация BERT: 43 предложения в секунду

nn.TransformerEncoder: 303 предложения в секунду

Заключение

Мы смогли получить дополнительные 16% очков F1 с помощью предварительно обученной модели. Это было сделано очень легко благодаря библиотеке трансформаторов Hugging Face и PyTorch, но за счет более низкой скорости вывода. Тем не менее, мы все еще можем использовать весовое квантование или дистилляцию модели, чтобы облегчить проблему задержки вывода, сохраняя при этом большую часть производительности F1.

Вы можете найти код для воспроизведения всех этих результатов в этом репозитории: https://github.com/CVxTz/ner_playground.

Набор данных: https://github.com/taisti/TASTEset/tree/main/data (лицензия MIT)

Спасибо за прочтение!