使用Pytorch和BERT进行多标签文本分类

介绍

自然语言处理(NLP)是一种将非结构化文本处理成有意义的知识的人工智能技术。NLP解决了分类、主题建模、文本生成、问答、推荐等业务问题。虽然TF/IDF矢量化或其他高级词嵌入(如GLOVE和Word2Vec)在此类NLP业务问题上表现出了良好的性能,但这些模型存在局限性就是使用一个向量对词进行编码而不考虑上下文的不同含义。因此,当试图解决理解用户意图所需的问题时,这些模型可能不能很好地执行。一个例子是,当用户与自动聊天机器人交互时,它试图理解用户查询的意图并准确地提供响应。

对于这种情况,NLP中的另一个例子是从下面两个句子中解码上下文意义。

  1. A thieve robbed a bank.

  2. He went to river bank.

从以上两种表述中,人们很容易就能看出“bank”有两种不同的含义;然而,机器不能区分,因为上面提到的词嵌入使用相同的标记“bank”,而不管他们的上下文意义。为了克服这一挑战,谷歌从Transformers (BERT)模型开发了最先进的双向编码器表示。

BERT是什么?

BERT是在8亿单词的图书语料库和2500万单词的英语维基百科上训练的预训练模型。在BERT中,“bank”将有两个不同的含义,因为它们的上下文差异。在保持NLP任务的高性能的同时并不会降低模型构建的训练时间。并且可以从BERT中提取新的语言特征用于模型预测。与RNN、LSTM、CNN等深度学习模型相比,BERT的发展速度要快得多。作为高层次的理解,BERT有两种不同的架构变体:BERT base和BERT large。第一个变型有12个Transformers 块,12个注意头,1.1亿参数,后一个变型有24个Transformers ,16个注意头,3.4亿参数。它在使用过程中完成了两个NLP的任务:遮蔽语言建模和下一句预测。

数据集

从此处(https://datahack.analyticsvidhya.com/contest/janatahack-independence-day-2020-ml-hackathon/#ProblemStatement)获取数据集,该数据集可用于研究论文的主题建模的多标签分类对比。 对比的目的是从大型的科学文章在线存档中尽可能地容易找到相关的文章。 我选择此数据集的原因是,尽管有许多关于二进制分类的Twitter情绪讨论BERT和Pytorch的文章,但很少找到有关处理多类问题的。 并且有很多共享代码可能无法正常工作。

查看如下的代码我建议具备python,NLP,深度学习和Pytorch框架的基础知识。 必须使用Google帐户才能使用Google Colab帐户。

处理数据的方法

在传统的NLP机器学习问题中,我们倾向于清除不需要的文本,例如删除停用词,标点符号,删除符号和数字等。但是,在BERT中,不需要执行此类预处理任务,因为BERT使用了这些 单词的顺序和位置,以了解用户输入的意图。

ML / DL工程师应该从不同方面探索数据集,以真正了解他们手中的数据类型,这是一个好习惯。 NLP的典型功能是单词计数,动词计数,形容词计数,数字计数,标点符号计数,双字母组计数,三字组计数等。 为简便起见,我已展示了如何对单词计数列进行计数,其中单个标题中使用的总单词数将被计算在内。 您可能还需要处理类似于TITLE的Abstract列,以及ABSTRACT和TITLE的组合。

下面的命令创建“ WORD_COUNT”列。

df_raw['WORD_COUNT'] = df_raw['TITLE'].apply(lambda x: len(x.split())

这将生成“ WORD_COUNT”的分布图,即标题的长度。

如您所见,文章标题的大部分以10个单词为中心,这是预期的结果,因为TITLE应该简短,简洁且有意义。

由于我将仅使用“ TITLE”和“ target_list”,因此我创建了一个名为df2的新数据框。 df2.head()命令显示训练数据集中的前五个记录。 如您所见,两个目标标签被标记到最后的记录,这就是为什么这种问题称为多标签分类问题的原因。

df2 = df_raw[['TITLE', 'target_list']].copy()

df2.head()

同时,设置将用于模型训练的参数。 由于我更喜欢使用2*base数字,因此最大长度设置为16,这涵盖了大部分“ TITLE”长度。 训练和有效批处理大小设置为32。epoch为4,因为它很容易在几个epoch上过度拟合。 我从lr=0.00001开始学习。 您可以随意尝试不同的值以提高准确性。

# Sections of config
# Defining some key variables that will be used later on in the training
MAX_LEN = 16
TRAIN_BATCH_SIZE = 32
VALID_BATCH_SIZE = 32
EPOCHS = 4
LEARNING_RATE = 1e-05
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')

让我们创建一个称为“ CustomDataset”的通用类。 Class从我们的原始输入特征生成张量,并且Pytorch张量可以接受class的输出。 它期望具有上面定义的“ TITLE”,“ target_list”,max_len,并使用BERT toknizer.encode_plus函数将输入设置为数字矢量格式,然后转换为张量格式返回。

class CustomDataset(Dataset):
    def __init__(self, dataframe, tokenizer, max_len):
        self.tokenizer = tokenizer
        self.data = dataframe
        self.title = dataframe['TITLE']
        self.targets = self.data.target_list
        self.max_len = max_len

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



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

        inputs = self.tokenizer.encode_plus(
            title,
            None,
            add_special_tokens=True,
            max_length=self.max_len,
            padding='max_length',
            return_token_type_ids=True,
            truncation=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)

        }

数据集的80%用于模型训练,而20%用于验证。 测试数据集完全用于测试目的。

train_size = 0.8
train_dataset = df2.sample(frac=train_size,random_state=200)
valid_dataset = df2.drop(train_dataset.index).reset_index(drop=True)
train_dataset = train_dataset.reset_index(drop=True)


print("FULL Dataset: {}".format(df2.shape))
print("TRAIN Dataset: {}".format(train_dataset.shape))
print("TEST Dataset: {}".format(valid_dataset.shape))


training_set = CustomDataset(train_dataset, tokenizer, MAX_LEN)
validation_set = CustomDataset(valid_dataset, tokenizer, MAX_LEN)

我们已经讨论了将张量准备为输入特征的大部分基础工作。 现在,构建BERT模型很容易。 由于来自模型的冗长输出,我已简化为仅显示模型。 我已使用dropout 0.3来随机减少特征,以最大程度地减少第2层的过拟合。第3层采用了768维特征,这些特征是从使用BERT的第2层输出的。 它返回6个特征,这是对目标列表的最终预测。

class BERTClass(torch.nn.Module):
    def __init__(self):
        super(BERTClass, self).__init__()
        self.l1 = transformers.BertModel.from_pretrained('bert-base-uncased')
        self.l2 = torch.nn.Dropout(0.3)
        self.l3 = torch.nn.Linear(768, 6)

    def forward(self, ids, mask, token_type_ids):
        _, output_1= self.l1(ids, attention_mask = mask, token_type_ids = token_type_ids)
        output_2 = self.l2(output_1)
        output = self.l3(output_2)
        return output

model = BERTClass()
model.to(device)

BCE损失函数用于找出模型预测值与实际目标值之间的误差。 使用Adam优化器。 损失功能请参见下文。

def loss_fn(outputs, targets):
    return torch.nn.BCEWithLogitsLoss()(outputs, targets)

optimizer = torch.optim.Adam(params =  model.parameters(), lr=LEARNING_RATE)

同时我还创建了检查点,可以在训练期间保存最佳模型。 当需要从停下来的地方继续训练时,这将有助于减少训练时间。 创建检查点可以节省时间,以便从头开始进行重新训练。 如果您对从最佳模型生成的输出感到满意,则不需要进一步的微调,则可以使用模型进行推断。

def load_ckp(checkpoint_fpath, model, optimizer):

    """
    checkpoint_path: path to save checkpoint
    model: model that we want to load checkpoint parameters into       
    optimizer: optimizer we defined in previous training

    """
    # load check point
    checkpoint = torch.load(checkpoint_fpath)
    # initialize state_dict from checkpoint to model
    model.load_state_dict(checkpoint['state_dict'])
    # initialize optimizer from checkpoint to optimizer
    optimizer.load_state_dict(checkpoint['optimizer'])
    # initialize valid_loss_min from checkpoint to valid_loss_min
    valid_loss_min = checkpoint['valid_loss_min']
    # return model, optimizer, epoch value, min validation loss 
    return model, optimizer, checkpoint['epoch'], valid_loss_min.item()
import shutil, sys   

def save_ckp(state, is_best, checkpoint_path, best_model_path):

    """
    state: checkpoint we want to save
    is_best: is this the best checkpoint; min validation loss
    checkpoint_path: path to save checkpoint
    best_model_path: path to save best model
    """
    f_path = checkpoint_path
    # save checkpoint data to the path given, checkpoint_path
    torch.save(state, f_path)
    # if it is a best model, min validation loss
    if is_best:
        best_fpath = best_model_path
        # copy that checkpoint file to best path given, best_model_path
        shutil.copyfile(f_path, best_fpath)
def train_model(start_epochs,  n_epochs, valid_loss_min_input, 
                training_loader, validation_loader, model, 
                optimizer, checkpoint_path, best_model_path):

  # initialize tracker for minimum validation loss
  valid_loss_min = valid_loss_min_input 
  for epoch in range(start_epochs, n_epochs+1):
    train_loss = 0
    valid_loss = 0
    model.train()
    print('############# Epoch {}: Training Start   #############'.format(epoch))
    for batch_idx, data in enumerate(training_loader):
        #print('yyy epoch', batch_idx)
        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 batch_idx%5000==0:
         #   print(f'Epoch: {epoch}, Training Loss:  {loss.item()}')
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        #print('before loss data in training', loss.item(), train_loss)
        train_loss = train_loss + ((1 / (batch_idx + 1)) * (loss.item() - train_loss))
        #print('after loss data in training', loss.item(), train_loss)
    print('############# Epoch {}: Training End     #############'.format(epoch))
    print('############# Epoch {}: Validation Start   #############'.format(epoch))
    ######################    
    # validate the model #
    ######################
    model.eval()
    with torch.no_grad():
      for batch_idx, data in enumerate(validation_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)
            loss = loss_fn(outputs, targets)
            valid_loss = valid_loss + ((1 / (batch_idx + 1)) * (loss.item() - valid_loss))
            val_targets.extend(targets.cpu().detach().numpy().tolist())
            val_outputs.extend(torch.sigmoid(outputs).cpu().detach().numpy().tolist())

      print('############# Epoch {}: Validation End     #############'.format(epoch))
      # calculate average losses
      #print('before cal avg train loss', train_loss)
      train_loss = train_loss/len(training_loader)
      valid_loss = valid_loss/len(validation_loader)
      # print training/validation statistics 
      print('Epoch: {} \tAvgerage Training Loss: {:.6f} \tAverage Validation Loss: {:.6f}'.format(
            epoch, 
            train_loss,
            valid_loss
            ))
      # create checkpoint variable and add important data
      checkpoint = {
            'epoch': epoch + 1,
            'valid_loss_min': valid_loss,
            'state_dict': model.state_dict(),
            'optimizer': optimizer.state_dict()
      }

        # save checkpoint
      save_ckp(checkpoint, False, checkpoint_path, best_model_path)
      ## TODO: save the model if validation loss has decreased
      if valid_loss <= valid_loss_min:
        print('Validation loss decreased ({:.6f} --> {:.6f}).  Saving model ...'.format(valid_loss_min,valid_loss))
        # save checkpoint as best model
        save_ckp(checkpoint, True, checkpoint_path, best_model_path)
        valid_loss_min = valid_loss
    print('############# Epoch {}  Done   #############\n'.format(epoch))
  return model

“train_model”被创建来训练模型,“checkpoint_path”是训练模型的参数将被保存为每个epoch,“best_model”是最好的模型将被保存的地方。

checkpoint_path = '/content/drive/My Drive/NLP/ResearchArticlesClassification/checkpoint/current_checkpoint.pt'

best_model = '/content/drive/My Drive/NLP/ResearchArticlesClassification/best_model/best_model.pt'

trained_model = train_model(1, 4, np.Inf, training_loader, validation_loader, model, 
                      optimizer,checkpoint_path,best_model)

训练结果如下:

############# Epoch 1: Training Start   #############
############# Epoch 1: Training End     #############
############# Epoch 1: Validation Start   #############
############# Epoch 1: Validation End     #############
Epoch: 1 	Avgerage Training Loss: 0.000347 	Average Validation Loss: 0.001765
Validation loss decreased (inf --> 0.001765).  Saving model ...
############# Epoch 1  Done   #############

############# Epoch 2: Training Start   #############
############# Epoch 2: Training End     #############
############# Epoch 2: Validation Start   #############
############# Epoch 2: Validation End     #############
Epoch: 2 	Avgerage Training Loss: 0.000301 	Average Validation Loss: 0.001831
############# Epoch 2  Done   #############

############# Epoch 3: Training Start   #############
############# Epoch 3: Training End     #############
############# Epoch 3: Validation Start   #############
############# Epoch 3: Validation End     #############
Epoch: 3 	Avgerage Training Loss: 0.000263 	Average Validation Loss: 0.001896
############# Epoch 3  Done   #############

############# Epoch 4: Training Start   #############
############# Epoch 4: Training End     #############
############# Epoch 4: Validation Start   #############
############# Epoch 4: Validation End     #############
Epoch: 4 	Avgerage Training Loss: 0.000228 	Average Validation Loss: 0.002048
############# Epoch 4  Done   #############

因为我只执行了4个epoch,所以完成得很快,我将threshold设置为0.5。你可以试试这个阈值,看看是否能提高结果。

val_preds = (np.array(val_outputs) > 0.5).astype(int)
val_preds
array([[0, 0, 1, 0, 0, 0],
       [0, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0, 0],
       ...,
       [0, 0, 1, 0, 0, 0],
       [0, 1, 0, 0, 0, 0],
       [1, 0, 0, 0, 0, 0]])

让我们将精确度和F1得分定义为模型性能的指标。F1将被用于评估。

accuracy = metrics.accuracy_score(val_targets, val_preds)
f1_score_micro = metrics.f1_score(val_targets, val_preds, average='micro')
f1_score_macro = metrics.f1_score(val_targets, val_preds, average='macro')
print(f"Accuracy Score = {accuracy}")
print(f"F1 Score (Micro) = {f1_score_micro}")
print(f"F1 Score (Macro) = {f1_score_macro}")

使用混淆矩阵和分类报告,以可视化我们的模型如何正确/不正确地预测每个单独的目标。

from sklearn.metrics import multilabel_confusion_matrix as mcm, classification_report
cm_labels = ['Computer Science', 'Physics', 'Mathematics',
       'Statistics', 'Quantitative Biology', 'Quantitative Finance']
cm = mcm(val_targets, val_preds)
print(classification_report(val_targets, val_preds))

模型预测的准确率为76%。F1得分低的原因是有六个类的预测,通过结合“TITLE”和“ABSTRACT”或者只使用“ABSTRACT”来训练可以提高它。我对这两个案例都进行了训练,发现“ABSTRACT”特征本身的F1分数比标题和标题与抽象相结合要好得多。在没有进行超参数优化的情况下,我使用测试数据进行推理,并在private score中获得0.82分。

有一些事情可以做,以提高F1成绩。一个是微调模型的超参数,你可能想要实验改变学习速率,退出率和时代的数量。在对模型微调的结果满意之后,我们可以使用整个训练数据集,而不是分成训练和验证集,因为训练模型已经看到了所有可能的场景,使模型更好地执行。

你可以在谷歌Colab查看这个项目源代码

https://colab.research.google.com/drive/1SPxxEW9okgnbMdk1ORlfSQI4rjV2tVW_#scrollTo=EJQRHd7VVMap

作者:Kyawkhaung

deephub翻译组

  • 2
    点赞
  • 55
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
### 回答1: PyTorch-BERT可以用于多标签任务。多标签任务是指一个样本可以同时被分配多个标签,而不是只有一个唯一的标签PyTorch-BERT可以通过微调(fine-tuning)来处理多标签任务,具体步骤如下: 1. 数据预处理:首先将原始数据转换为特定的输入格式,即将每个样本编码为输入序列。对于文本分类任务,可以使用tokenizer将输入文本转换为BERT模型对应的输入格式。同时,每个样本的标签也需要进行处理,通常使用独热编码或多标签编码的方式表示多个标签。 2. 模型微调:使用经过预训练的BERT模型,将其权重加载到PyTorch模型中。然后将加载的模型与多标签分类器(如全连接层)结合,以适应多标签任务的需求。微调的目标是让BERT模型能够更好地适应特定的多标签任务。 3. 训练与评估:使用经过微调的模型对训练数据进行训练,并在验证集上进行评估。在训练过程中,通常使用交叉熵损失函数来计算模型的损失,并使用优化算法(如Adam)来更新模型的参数。 4. 预测:在模型训练完成后,可以使用经过微调的模型对新的未标记样本进行预测。模型将输出一个概率分布,表示每个标签是否存在的可能性。可以根据设定的阈值,将概率高于阈值的标签作为模型的预测结果。 总而言之,PyTorch-BERT可以通过微调的方式来处理多标签任务。在微调过程中,需要将BERT模型与多标签分类器结合,并进行相应的训练和评估。通过这种方式,PyTorch-BERT可以应用于各种多标签分类任务,如文本分类、图像标注等。 ### 回答2: PyTorch是一个开源的机器学习框架,它提供了一种强大的编程环境,可以用于构建和训练各种深度学习模型。BERT(Bidirectional Encoder Representations from Transformers)是一种预训练的自然语言处理模型,它能够有效地处理各种自然语言任务。 在PyTorch使用BERT进行标签分类任务,需要进行以下几个步骤: 1. 数据预处理:将文本数据转换为适合BERT模型输入的格式。首先,需要将文本分词并添加特殊标记(如"[CLS]"和"[SEP]")来标记句子的开头和结束。然后,将分词后的文本转换为词向量,可以使用BERT的预训练模型来获取词向量。 2. 构建模型:使用PyTorch构建多标签分类模型。可以使用BERT作为基本模型,然后添加适当的全连接层来实现多标签分类。这些全连接层可以将BERT模型的输出映射到具体的标签。在模型的训练过程中,可以使用交叉熵损失函数和梯度下降方法来优化模型的参数。 3. 模型训练:使用标注好的数据集对构建的模型进行训练。可以使用PyTorch提供的优化器(如AdamOptimizer)和内置的训练循环来简化训练过程。 4. 模型评估:使用测试集评估训练得到的模型的性能。可以使用各种指标(如准确率、精确率、召回率和F1分数)来评估模型的多标签分类性能。 总结起来,使用PyTorchBERT进行标签分类任务,需要进行数据预处理、模型构建、模型训练和模型评估等步骤。通过合理设计模型结构和使用适当的优化算法,可以实现高效准确的多标签分类。 ### 回答3: PyTorch是一个很流行的深度学习框架,而BERT是一个非常强大的预训练模型,可以用于自然语言处理任务。当我们要处理多标签分类问题时,可以使用PyTorchBERT的组合来解决。 多标签分类是指一个样本可以被分配到多个类别中,而不仅仅是一个类别。在使用PyTorchBERT进行标签分类时,我们首先需要对文本数据进行处理。我们可以使用BERT模型的tokenizer将文本转换为对应的token,然后将其转化为PyTorch的张量。 接下来,我们可以使用BERT模型进行特征提取。BERT模型可以将输入的token序列编码成固定长度的向量表示,这样可以保留输入句子的语义信息。通过BERT模型的输出,我们可以获取每个token的向量表示。 对于多标签分类问题,我们可以使用全连接层或者其他一些分类器来预测每个类别的概率。我们可以将BERT模型的输出连接到一个全连接层中,然后使用激活函数(如sigmoid函数)将输出的概率限制在0和1之间。 接着,我们可以使用交叉熵损失函数来计算模型的损失,并使用反向传播算法来更新模型的参数。在训练过程中,我们可以使用一些评估指标(如精确率、召回率、F1分数等)来评估模型在多标签分类任务上的性能。 为了优化模型的训练,我们可以使用一些技巧,如学习率调整、正则化、批量归一化等。此外,还可以使用数据增强技术来增加训练数据的多样性,从而提升模型的泛化能力。 总结来说,通过使用PyTorchBERT的组合,我们可以很方便地解决多标签分类问题。PyTorch提供了灵活的深度学习框架,而BERT则是一个强大的预训练模型,它们的结合可以帮助我们构建准确度高且性能优良的多标签分类模型。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值