В одном из моих последних сообщений в блоге Как настроить Bert для задачи классификации текста я объяснил тонкую настройку BERT для задача многоклассовой классификации текста. В этом посте я объясню, как настроить DistilBERT для задачи классификации текста с несколькими метками. Я также сделал репозиторий GitHub, содержащий полный код, который описан ниже. Вы можете перейти по приведенной ниже ссылке, чтобы увидеть его, а также разветвить и использовать.

Https://github.com/DhavalTaunk08/Transformers_scripts

Вступление

Модель DistilBERT (https://arxiv.org/pdf/1910.01108.pdf) была выпущена Huggingface.co, которая представляет собой дистиллированную версию BERT, выпущенную Google. (https://arxiv.org/pdf/1810.04805.pdf).

По мнению авторов: -

Они используют дистилляцию знаний на этапе подготовки к обучению и показывают, что можно уменьшить размер модели BERT на 40%, сохранив при этом 97% возможностей понимания языка и работая на 60% быстрее.

Итак, давайте начнем с деталей и процесса настройки модели.

Классификация Multi-Class по сравнению с Multi-Label

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

Мультиклассовая классификация. Допустим, у нас 10 фруктов. Они могут принадлежать к одному из трех классов - «яблоко», «манго» и «банан». Если нас попросят классифицировать фрукты по этим данным классам, они могут принадлежать только к одному из этих классов. Следовательно, это проблема классификации нескольких классов.

Многоканальная классификация. Допустим, у нас мало названий фильмов, и наша задача - классифицировать эти фильмы по жанрам, к которым они относятся, например, «боевик», «комедия», «ужасы», «научная фантастика». -fi ',' драма 'и т. д. Эти фильмы могут принадлежать более чем к одному жанру. Например: «Серия фильмов« Матрица »относится как к категории« боевик », так и к категории« фантастика ». Таким образом, это называется классификацией с несколькими метками.

Форматирование данных

Прежде всего, необходимо отформатировать данные. Требуемые данные могут содержать 2 столбца. Один столбец, содержащий текст для классификации. Другой столбец, содержащий метки, относящиеся к этому образцу. На изображении ниже показан пример фрейма данных: -

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

Но вопрос в том, как конвертировать метки в этот формат? Здесь на помощь приходит scikit-learn !!!

Ниже приведен пример того, как преобразовать эти метки в требуемый формат.

>>> from sklearn.preprocessing import MultiLabelBinarizer
>>> mlb = MultiLabelBinarizer()
>>> mlb.fit_transform([{'sci-fi', 'thriller'}, {'comedy'}])
array([[0, 1, 1],
       [1, 0, 0]])
>>> list(mlb.classes_)
['comedy', 'sci-fi', 'thriller']

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

https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.MultiLabelBinarizer.html

Код

Теперь перейдем к части кода о необходимых библиотеках, о том, как написать DataLoader, и о классе модели для этой задачи.

Необходимые библиотеки

трансформаторы == 3.0.2

факел

scikit-learn

тупой

панды

tqdm

Их можно установить с помощью команды pip install’.

Импорт библиотек

import numpy as np
import pandas as pd
import transformers
import torch
from torch.utils.data import Dataset, DataLoader, RandomSampler, SequentialSampler
from transformers import DistilBertModel, DistilBertTokenizer
from tqdm import tqdm
from sklearn.preprocessing import MultiLabelBinarizer
from torch import cuda
device = 'cuda' if cuda.is_available() else 'cpu'

Вышеупомянутый шаг предназначен для настройки устройства для работы с графическим процессором.

Параметры тренировки

MAX_LEN = 256
TRAIN_BATCH_SIZE = 8
VALID_BATCH_SIZE = 4
EPOCHS = 1
LEARNING_RATE = 1e-05

Эти параметры можно настроить по своему усмотрению. Но здесь следует отметить один важный момент:

DistilBERT принимает max_sequence_length из 512 токенов.

Мы не можем указать max_sequence_length больше, чем это. Если вы хотите задать длину последовательности более 512 токенов, вы можете попробовать модель длинного формата (https://arxiv.org/pdf/2004.05150)

DataLoader

tokenizer = DistilBertTokenizer.from_pretrained('distilbert-base-uncased')
class MultiLabelDataset(Dataset):
    def __init__(self, dataframe, tokenizer, max_len):
        self.tokenizer = tokenizer
        self.data = dataframe
        self.text = dataframe.text
        self.targets = self.data.labels
        self.max_len = max_len

    def __len__(self):
        return len(self.text)

    def __getitem__(self, index):
        text = str(self.text[index])
        text = " ".join(text.split())

        inputs = self.tokenizer.encode_plus(
            text,
            None,
            add_special_tokens=True,
            max_length=self.max_len,
            pad_to_max_length=True,
            return_token_type_ids=True
        )
        ids = inputs['input_ids']
        mask = inputs['attention_mask']
        token_type_ids = inputs["token_type_ids"]


        return {
            'ids': torch.tensor(ids, dtype=torch.long),
            'mask': torch.tensor(mask, dtype=torch.long),
            'token_type_ids': torch.tensor(token_type_ids, dtype=torch.long),
            'targets': torch.tensor(self.targets[index], dtype=torch.float)
        }

Вызов токенизатора и загрузка набора данных. Здесь train_dataset и val_dataset будут наборами данных для обучения и проверки в формате фрейма данных pandas с именами столбцов как [«текст», «метки»].

training_set = MultiLabelDataset(train_dataset, tokenizer, MAX_LEN)
testing_set = MultiLabelDataset(test_dataset, tokenizer, MAX_LEN)
train_params = {'batch_size': TRAIN_BATCH_SIZE,
                'shuffle': True,
                'num_workers': 0
                }

test_params = {'batch_size': VALID_BATCH_SIZE,
                'shuffle': True,
                'num_workers': 0
                }

training_loader = DataLoader(training_set, **train_params)
testing_loader = DataLoader(testing_set, **test_params)

Вышеупомянутый шаг преобразует данные в требуемый формат с помощью класса MultiLabelDataset и DataLoader PyTorch. Вы можете узнать больше о DataLoader, перейдя по приведенной ниже ссылке: -

Https://pytorch.org/tutorials/beginner/data_loading_tutorial.html

Модель Класс

class DistilBERTClass(torch.nn.Module):
    def __init__(self):
        super(DistilBERTClass, self).__init__()
        self.l1 = DistilBertModel.from_pretrained("distilbert-base-uncased")
        self.pre_classifier = torch.nn.Linear(768, 768)
        self.dropout = torch.nn.Dropout(0.3)
        self.classifier = torch.nn.Linear(768, num_classes)

    def forward(self, input_ids, attention_mask):
        output_1 = self.l1(input_ids=input_ids, attention_mask=attention_mask)
        hidden_state = output_1[0]
        pooler = hidden_state[:, 0]
        pooler = self.pre_classifier(pooler)
        pooler = torch.nn.ReLU()(pooler)
        pooler = self.dropout(pooler)
        output = self.classifier(pooler)
        return output

Здесь я использовал 2 линейных слоя поверх модели DistilBERT с выпадающим блоком и ReLu в качестве функции активации. num_classes - количество классов, доступных в вашем наборе данных. Модель вернет логит-баллы для каждого класса. Класс можно вызвать следующим методом: -

model = DistilBERTClass()
model.to(device)

Функция потерь и оптимизатор

def loss_fn(outputs, targets):
    return torch.nn.BCEWithLogitsLoss()(outputs, targets)
optimizer = torch.optim.Adam(params =  model.parameters(), lr=LEARNING_RATE)

Здесь используется BCEWithLogitsLoss, который обычно используется для классификации по нескольким меткам. Можно узнать больше, перейдя по ссылке ниже: -

Https://pytorch.org/docs/stable/generated/torch.nn.BCEWithLogitsLoss.html

Функция обучения

def train_model(epoch):
    model.train()
    for _, data in enumerate(training_loader, 0):
        ids = data['ids'].to(device, dtype = torch.long)
        mask = data['mask'].to(device, dtype = torch.long)
        token_type_ids = data['token_type_ids'].to(device, dtype = torch.long)
        targets = data['targets'].to(device, dtype = torch.float)
        
        outputs = model(ids, mask, token_type_ids)

        optimizer.zero_grad()
        loss = loss_fn(outputs, targets)
        if _%1000==0:
            print(f'Epoch: {epoch}, Loss:  {loss.item()}')
        
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
for epoch in range(EPOCHS):
    train_model(epoch)

Вышеупомянутая функция используется для обучения модели на указанное количество эпох.

Проверка

def validation(testing_loader):
    model.eval()
    fin_targets=[]
    fin_outputs=[]
    with torch.no_grad():
        for _, data in enumerate(testing_loader, 0):
            ids = data['ids'].to(device, dtype = torch.long)
            mask = data['mask'].to(device, dtype = torch.long)
            token_type_ids = data['token_type_ids'].to(device, dtype = torch.long)
            targets = data['targets'].to(device, dtype = torch.float)
            outputs = model(ids, mask, token_type_ids)
            fin_targets.extend(targets.cpu().detach().numpy().tolist())
            fin_outputs.extend(torch.sigmoid(outputs).cpu().detach().numpy().tolist())
    return fin_outputs, fin_targets
outputs, targets = validation(testing_loader)
outputs = np.array(outputs) >= 0.5
accuracy = metrics.accuracy_score(targets, outputs)
f1_score_micro = metrics.f1_score(targets, outputs, average='micro')
f1_score_macro = metrics.f1_score(targets, outputs, average='macro')
print(f"Accuracy Score = {accuracy}")
print(f"F1 Score (Micro) = {f1_score_micro}")
print(f"F1 Score (Macro) = {f1_score_macro}")

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

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