В одном из моих последних сообщений в блоге Как настроить Bert для задачи классификации текста я объяснил тонкую настройку BERT для задача многоклассовой классификации текста. В этом посте я объясню, как настроить DistilBERT для задачи классификации текста с несколькими метками. Я также сделал репозиторий GitHub, содержащий полный код, который описан ниже. Вы можете перейти по приведенной ниже ссылке, чтобы увидеть его, а также разветвить и использовать.
Вступление
Модель 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. Но обычно потери Хэмминга и оценка Хэмминга являются лучшими показателями для расчета потерь и точности для задач классификации с несколькими метками. Я буду обсуждать это в своем следующем посте.
Так что это на данный момент. Следите за обновлениями, чтобы узнать больше о потерях, счетах и других вещах в следующем посте. Если вы хотите узнать больше, вы можете посетить мой профиль, чтобы увидеть больше сообщений.