medium根源李雷、睡不着的iris、Aileen编译

搞定NLP范畴的“变形金刚”!手把手教你用BERT举行众标签文天职类

过去的一年,深度神经收集的运用开启了自然言语处理的新时代。预教练模子研讨范畴的运用曾经令许众NLP项目标最新效果发生了庞大的奔驰,比如文天职类,自然言语推理和问答。

ELMo,ULMFiT 和OpenAI Transformer是此中几个要害的里程碑。所有这些算法都容许我们大型数据库(比如所有维基百科作品)上预先教练无监视言语模子,然后卑鄙义务上对这些预先教练的模子举行微调。

这一年里,这一范畴中最冲感人心的事情只怕要数BERT的发布,这是一种基于众言语转换器的模子,它曾经种种NLP项目中取得了令人注目标效果。BERT是一种基于transformer架构的双向模子,它以一种速率更速的基于Attention的方法替代了RNN(LSTM和GRU)的sequential属性。

该模子还两个无监视义务(“掩盖言语模子”和“下一句预测”)上举行了预教练。这让我们可以通过对卑鄙特定义务(比如心情分类,企图检测,问答等)举行微调来运用预先教练的BERT模子。

本文将手把手教你,用BERT完毕一个Kaggle竞赛。

本文中,我们将要点先容BERT众标签文天职类题目中的运用。古板的分类题目假定每个文档都分派给一个且只分派给一个种别,即标签。这有时也被称为众元分类,比如种别数目是2的话,就叫做二元分类。

而众标签分类假设文档可以同时独立即分派给众个标签或种别。众标签分类具有许众实行运用,比如营业分类或为影戏分派众个类型。客户效劳范畴,此技能可用于识别客户电子邮件的众种企图。

我们将运用Kaggle的“恶意评论分类挑衅”来权衡BERT众标签文天职类中的外现。

本次竞赛中,我们将实验构修一个可以将给文本片断分派给同恶评种另外模子。我们设定了恶意评论种别举措模子的目标标签,它们包罗一般恶评、告急恶评、污言秽语、要挟、欺侮和身份仇视。

竞赛链接:https://www.kaggle.com/c/jigsaw-toxic-comment-classification-challenge

从哪开端?

Google Research近来公然了BERT 的tensorflow安排代码,并发布了以下预教练模子:

  • BERT-Base, Uncased: 12层,768个躲藏单位,自当心力的 head数为12,110M参数

  • BERT-Large, Uncased:24层,1024个躲藏单位,自当心力的 head数为16,340M参数

  • BERT-Base, Cased:12层,768个躲藏单位,自当心力的 head数为12,110M参数

  • BERT-Large, Cased:24层,1024个躲藏单位,自当心力的 head数为16,340M参数

  • BERT-Base, Multilingual Cased (最新引荐):104种言语,12层,768个躲藏单位,自当心力的 head数为12,110M参数

  • BERT-Base, Chinese:中文(简体和繁体),12层,768个躲藏单位,自当心力的 head数为12,110M参数

编者注:这里cased和uncased的意义是举行WordPiece分词之前是否区分大小写。uncased外示通通会调解成小写,且剔除所有的重音标记;cased则外示文本的实状况和重音标记都会保管下来。

我们将运用较小的Bert-Base,uncased模子来完毕此义务。Bert-Base模子有12个attention层,所有文本都将由标记器转换为小写。我们亚马逊云 p3.8xlarge EC2实例上运转此模子,该实例包罗4个Tesla V100 GPU,GPU内存总共64 GB。

因为我私人更喜爱TensorFlow上运用PyTorch,以是我们将运用来自HuggingFace的BERT模子PyTorch端口,这可从https://github.com/huggingface/pytorch-pretrained-BERT下载。我们曾经用HuggingFace的repo脚本将预先教练的TensorFlow反省点(checkpoints)转换为PyTorch权重。

我们的完成很洪流平上是以BERT原始完成中供应的run_classifier示例为根底的。

数据展现

数据用类InputExample来外示。

  • text_a:文本评论

  • text_b:未运用

  • 标签:来自教练数据集的评论标签列外(很分明,测试数据集的标签将为空)

class InputExample(object): """A single training/test example for sequence classification.""" def __init__(self, guid, text_a, text_b=None, labels=None): """Constructs a InputExample. Args: guid: Unique id for the example. text_a: string. The untokenized text of the first sequence. For single sequence tasks, only this sequence must be specified. text_b: (Optional) string. The untokenized text of the second sequence. Only must be specified for sequence pair tasks. labels: (Optional) [string]. The label of the example. This should be specified for train and dev examples, but not for test examples. """ self.guid = guid self.text_a = text_a self.text_b = text_b self.labels = labels

class InputFeatures(object): """A single set of features of data.""" def __init__(self, input_ids, input_mask, segment_ids, label_ids): self.input_ids = input_ids self.input_mask = input_mask self.segment_ids = segment_ids self.label_ids = label_ids

我们将InputExample转换为BERT能了解的特征,该特征用类InputFeatures来外示。

  • input_ids:标记化文本的数字id列外

  • input_mask:关于实标记将修立为1,关于填充标记将修立为0

  • segment_ids:关于我们的状况,这将被修立为全1的列外

  • label_ids:文本的one-hot编码标签

标记化(Tokenisation)

BERT-Base,uncased模子运用包罗30,522个单词的词汇外。标记化进程涉及将输入文本拆分为词汇外中可用的标记列外。为了处理不词汇外中的单词,BERT运用一种称为基于双字节编码(BPE,Byte-Pair Encoding)的WordPiece标记化技能。

这种方法将不词汇外之中的词一步步剖析成子词。因为子词是词汇外的一部分,模子曾经进修了这些子词上下文中的外示,而且该词的上下文仅仅是子词的上下文的组合,于是这个词就可以由一组子词外示。要了解关于此方法的更众精细新闻,请参阅作品《运用子词单位的稀有单词的神经收集板滞翻译》。

作品链接:https://arxiv.org/pdf/1508.07909

我看来,这与BERT本身相同都是一种打破。

模子架构

我们将改写BertForSequenceClassification类以使其满意众标签分类的请求。

class BertForMultiLabelSequenceClassification(PreTrainedBertModel): """BERT model for classification. This module is composed of the BERT model with a linear layer on top of the pooled output. """ def __init__(self, config, num_labels=2): super(BertForMultiLabelSequenceClassification, self).__init__(config) self.num_labels = num_labels self.bert = BertModel(config) self.dropout = torch.nn.Dropout(config.hidden_dropout_prob) self.classifier = torch.nn.Linear(config.hidden_size, num_labels) self.apply(self.init_bert_weights) def forward(self, input_ids, token_type_ids=None, attention_mask=None, labels=None): _, pooled_output = self.bert(input_ids, token_type_ids, attention_mask, output_all_encoded_layers=False) pooled_output = self.dropout(pooled_output) logits = self.classifier(pooled_output) if labels is not None: loss_fct = BCEWithLogitsLoss() loss = loss_fct(logits.view(-1, self.num_labels), labels.view(-1, self.num_labels)) return loss else: return logits def freeze_bert_encoder(self): for param in self.bert.parameters(): param.requires_grad = False def unfreeze_bert_encoder(self): for param in self.bert.parameters(): param.requires_grad = True

这里主要的改动是用logits举措二进制交叉熵的耗损函数(BCEWithLogitsLoss),替代用于众元分类的vanilla交叉熵耗损函数(CrossEntropyLoss)。二进制交叉熵耗损可以让我们的模子为标签分派独立的概率。

下面的模子摘要阐清楚模子的各个层及其维度。

BertForMultiLabelSequenceClassification( (bert): BertModel( (embeddings): BertEmbeddings( (word_embeddings): Embedding(28996, 768) (position_embeddings): Embedding(512, 768) (token_type_embeddings): Embedding(2, 768) (LayerNorm): FusedLayerNorm(torch.Size([768]), eps=1e-12, elementwise_affine=True) (dropout): Dropout(p=0.1) ) (encoder): BertEncoder( (layer): ModuleList( # 12 BertLayers (11): BertLayer( (attention): BertAttention( (self): BertSelfAttention( (query): Linear(in_features=768, out_features=768, bias=True) (key): Linear(in_features=768, out_features=768, bias=True) (value): Linear(in_features=768, out_features=768, bias=True) (dropout): Dropout(p=0.1) ) (output): BertSelfOutput( (dense): Linear(in_features=768, out_features=768, bias=True) (LayerNorm): FusedLayerNorm(torch.Size([768]), eps=1e-12, elementwise_affine=True) (dropout): Dropout(p=0.1) ) ) (intermediate): BertIntermediate( (dense): Linear(in_features=768, out_features=3072, bias=True) ) (output): BertOutput( (dense): Linear(in_features=3072, out_features=768, bias=True) (LayerNorm): FusedLayerNorm(torch.Size([768]), eps=1e-12, elementwise_affine=True) (dropout): Dropout(p=0.1) ) ) ) ) (pooler): BertPooler( (dense): Linear(in_features=768, out_features=768, bias=True) (activation): Tanh() ) ) (dropout): Dropout(p=0.1) (classifier): Linear(in_features=768, out_features=6, bias=True) )

  • BertEmbeddings:输入嵌入层

  • BertEncoder: 12个BERT模子attention层

  • 分类器:我们的众标签分类器,out_features = 6,每个分类符对应6个标签

模子教练

教练轮回与原始BERT完成中供应的run_classifier.py里的轮回相同。我们的模子教练了4个epoch(一个完备的数据集通过了神经收集一次而且返回了一次,这个进程称为一个 epoch),每批数据大小为32,序列长度为512,即预教练模子的最大可以性。依据原始论文的倡议,进修率保持3e-5。

因为有时机运用众个GPU,以是我们将Pytorch模子封装DataParallel模块中,这使我们可以所有可用的GPU上举行教练。

我们没有运用半精度FP16技能,因为运用logits 耗损函数的二进制交叉熵不支撑FP16处理。但这并不会影响最终结果,只是需求更长的时间教练。

评估目标

def accuracy_thresh(y_pred:Tensor, y_true:Tensor, thresh:float=0.5, sigmoid:bool=True): "Compute accuracy when `y_pred` and `y_true` are the same size." if sigmoid: y_pred = y_pred.sigmoid() return np.mean(((y_pred>thresh)==y_true.byte()).float().cpu().numpy(), axis=1).sum()

from sklearn.metrics import roc_curve, auc # Compute ROC curve and ROC area for each class fpr = dict() tpr = dict() roc_auc = dict() for i in range(num_labels): fpr[i], tpr[i], _ = roc_curve(all_labels[:, i], all_logits[:, i]) roc_auc[i] = auc(fpr[i], tpr[i]) # Compute micro-average ROC curve and ROC area fpr["micro"], tpr["micro"], _ = roc_curve(all_labels.ravel(), all_logits.ravel()) roc_auc["micro"] = auc(fpr["micro"], tpr["micro"])

我们为精度器量函数添加了一个阈值,默认修立为0.5。

关于众标签分类,更主要的目标是ROC-AUC弧线。这也是Kaggle竞赛的评分目标。我们区分盘算每个标签的ROC-AUC,并对单个标签的roc-auc分数举行微平均。

假如念深化了解roc-auc弧线,这里有一篇很不错的博客。

博客链接:https://towardsdatascience.com/understanding-auc-roc-curve-68b2303cc9c5。

评估分数

我们重复举行了几次实行,每次都有少许输入上的改造,但都取得了相似的结果,如下所示:

教练耗损:0.022,验证耗损:0.018,验证准确度:99.31%。

各个标签的ROC-AUC分数:

  • 一般恶评:0.9988

  • 告急恶评:0.9935

  • 污言秽语:0.9988

  • 要挟:0.9989

  • 欺侮:0.9975

  • 身份仇视:0.9988

  • 微观平均ROC-AUC得分:0.9987

如许的结果仿佛十分令人饱舞,因为我们看上去曾经创立了一个近乎完美的模子来检测文本评论的狠毒程度。现看看我们Kaggle排行榜上的得分。

Kaggle竞赛结果

我们Kaggle供应的测试数据集上运转推理逻辑,并将结果提交给竞赛。以下是结果:




我们的roc-auc评分抵达了0.9863,所有逐鹿者中排名前10%。为了使竞赛结果更具说服力,此次Kaggle竞赛的奖金为35000美元,而一等奖得分为0.9885。

最高分的团队由专业的高武艺数据科学家和从业者构成。除了我们所做的义务除外,他们还运用种种技能来举行数据集成,数据增强(data augmentation)和测试时增强(test-time augmentation)。

结论和后续

我们运用强大的BERT预教练模子完成了众标签分类模子。正如我们所展现的那样,模子已熟知的公然数据集上取得了相当不错的结果。我们可以修立一个天下级的模子生产运用于各行业,特别是客户效劳范畴。

关于我们来说,下一步将是运用“掩盖言语模子”和“下一句预测”对卑鄙义务的文本语料库来微调预教练的言语模子。这将是一项无监视的义务,期望该模子可以进修少许我们自定义的上下文和术语,这和ULMFiT运用的技能相似。

材料链接: 

  • https://nbviewer.jupyter.org/github/kaushaltrivedi/bert-toxic-comments-multilabel/blob/master/toxic-bert-multilabel-classification.ipynb
  • https://github.com/kaushaltrivedi/bert-toxic-comments-multilabel/blob/master/toxic-bert-multilabel-classification.ipynb

原始BERT论文:https://arxiv.org/pdf/1810.04805

相关报道:https://medium.com/huggingface/multi-label-text-classification-using-bert-the-mighty-transformer-69714fa3fb3d

大数据文摘
大数据文摘

承袭“普及数据思念,传达数据文明,帮财产开展”的企业化,我们笃志于数据范畴的资讯、案例、技能,变成“媒体+蕉蔟+才效劳”的良性态,致于打制精准数据科学社区。

工程Kaggle深度神经收集众标签分类文天职类自然言语处理BERT
3
暂无评论
暂无评论~