Bert文本分类和命名实体的模型架构剖析

介绍

文本分类:给一句文本分类;
实体识别:从一句文本中,识别出其中的实体;

做命名实体识别,有2种方式:

  1. 基于Bert-lstm-crf 的Token分类;
  2. 生成式的从序列到序列的文本生成方法。比如:T5、UIE、大模型等;

如果你想体验完整命名实体识别教程请浏览:Huggingface Token 分类官方教程:https://huggingface.co/learn/nlp-course/zh-CN/chapter7/2

若实体识别采取Token分类的做法:
 那么文本分类是给一整句话做分类,实体识别是给一整句话中的每个词做分类。从本质上看,两者都是分类任务;

import torch
from transformers import (
    AutoTokenizer, 
    AutoModel,
    AutoModelForSequenceClassification,
    BertForSequenceClassification,
    AutoModelForTokenClassification,
)

Bert模型架构

基本的Bert模型结构:

model_name = "bert-base-chinese"
bert = AutoModel.from_pretrained(model_name)
bert

Output:

...
        (output): BertOutput(
          (dense): Linear(in_features=3072, out_features=768, bias=True)
          (LayerNorm): LayerNorm((768,), eps=1e-12, elementwise_affine=True)
          (dropout): Dropout(p=0.1, inplace=False)
        )
      )
    )
  )
  (pooler): BertPooler(
    (dense): Linear(in_features=768, out_features=768, bias=True)
    (activation): Tanh()
  )
)

文本分类模型:

seq_cls_model = AutoModelForSequenceClassification.from_pretrained(model_name)
seq_cls_model

Output:

...
          (output): BertOutput(
            (dense): Linear(in_features=3072, out_features=768, bias=True)
            (LayerNorm): LayerNorm((768,), eps=1e-12, elementwise_affine=True)
            (dropout): Dropout(p=0.1, inplace=False)
          )
        )
      )
    )
    (pooler): BertPooler(
      (dense): Linear(in_features=768, out_features=768, bias=True)
      (activation): Tanh()
    )
  )
  (dropout): Dropout(p=0.1, inplace=False)
  (classifier): Linear(in_features=768, out_features=2, bias=True)
)

实体识别 token 分类:

token_cls_model = AutoModelForTokenClassification.from_pretrained(model_name)
token_cls_model

Output:

...
          (output): BertOutput(
            (dense): Linear(in_features=3072, out_features=768, bias=True)
            (LayerNorm): LayerNorm((768,), eps=1e-12, elementwise_affine=True)
            (dropout): Dropout(p=0.1, inplace=False)
          )
        )
      )
    )
  )
  (dropout): Dropout(p=0.1, inplace=False)
  (classifier): Linear(in_features=768, out_features=2, bias=True)
)

经过观察AutoModelForSequenceClassificationAutoModelForTokenClassification的模型架构一模一样,即分类与实体识别的模型架构一模一样。两者都是在基础的Bert模型尾部添加 dropoutclassifier层。

Q:它们是一模一样Bert模型架构,为何能实现不同的任务?
A:因为它们选取Bert输出不同,损失值计算也不同。

损失计算方式

BertForSequenceClassification

from transformers import BertForSequenceClassification

按住 Ctrl + 鼠标左键,查看源码

forward 函数中可以查看到loss的计算方式。

outputs = self.bert(
    input_ids,
    attention_mask=attention_mask,
    token_type_ids=token_type_ids,
    position_ids=position_ids,
    head_mask=head_mask,
    inputs_embeds=inputs_embeds,
    output_attentions=output_attentions,
    output_hidden_states=output_hidden_states,
    return_dict=return_dict,
)

pooled_output = outputs[1]

pooled_output = self.dropout(pooled_output)
logits = self.classifier(pooled_output)

BertForSequenceClassification 使用 pooled_output = outputs[1]

BertForTokenClassification

from transformers import BertForTokenClassification

forward 函数中可以查看到loss的计算方式。

...
outputs = self.bert(
    input_ids,
    attention_mask=attention_mask,
    token_type_ids=token_type_ids,
    position_ids=position_ids,
    head_mask=head_mask,
    inputs_embeds=inputs_embeds,
    output_attentions=output_attentions,
    output_hidden_states=output_hidden_states,
    return_dict=return_dict,
)

sequence_output = outputs[0]

sequence_output = self.dropout(sequence_output)
logits = self.classifier(sequence_output)

BertForTokenClassification 使用 sequence_output = outputs[0]

Bert 输出结果剖析

下述是BertModel的输出结果,既可以使用字典访问,也可以通过下标访问:

BaseModelOutputWithPoolingAndCrossAttentions(
            last_hidden_state=sequence_output,
            pooler_output=pooled_output,
            past_key_values=encoder_outputs.past_key_values,
            hidden_states=encoder_outputs.hidden_states,
            attentions=encoder_outputs.attentions,
            cross_attentions=encoder_outputs.cross_attentions,
        )

outputs[0] 是last_hidden_state, outputs[1]是 pooler_output。

last_hidden_state 是输入到Bert模型每一个token的状态,pooler_output[CLS]的last_hidden_state经过pooler处理得到的状态。

在这里插入图片描述

在图片上,用红色字标出了 last_hidden_state 和 pooler_output 在模型架构的位置。

例子

接下来使用一个例子帮助各位读者深入理解Bert输出结果中的last_hidden_statepooler_output的区别。

from transformers import (
    AutoTokenizer, 
    # BertModel,
    AutoModel,
    DataCollatorForTokenClassification
)
model_name = "bert-base-chinese"
tokenizer = AutoTokenizer.from_pretrained(model_name)
seq_cls_model = AutoModel.from_pretrained(model_name)

data_collator = DataCollatorForTokenClassification(tokenizer=tokenizer)
batch = data_collator([
                        tokenizer("今天天气真好,咱们出去放风筝吧!"),
                        tokenizer("起风了,还是在家待着吧!"),
                        ])
for k, v in batch.items():
    print(k, v.shape)

Output:

input_ids torch.Size([2, 18])
token_type_ids torch.Size([2, 18])
attention_mask torch.Size([2, 18])

Bert 模型推理

output = bert(**batch)
print(
    torch.equal(output[0], output["last_hidden_state"]),
    torch.equal(output[1], output["pooler_output"])
)
last_hidden_state = output["last_hidden_state"]
pooler_output = output["pooler_output"]

Output:

True True
# output[0] == output["last_hidden_state"] 为真
# 这意味着Bert的输出,既可以用下标访问,也可以用字典的键访问
print(
    f"last_hidden_state.shape: {last_hidden_state.shape}",
    f"pooler_output.shape: {pooler_output.shape}"
)

Output:

last_hidden_state.shape: torch.Size([2, 18, 768]) pooler_output.shape: torch.Size([2, 768])

仅仅看它们的shape,也能看出它们的区别

last_hidden_state:包括每一个token的状态;(所以用来做命名实体识别)
pooler_output:只有[CLS]的状态;([CLS]的输出向量被认为是整个序列的聚合表示,故用于分类任务。)

# [CLS] 在第一个token的位置,通过下标获取 `[CLS]`的tensor,再经过pooler处理
# 判断其与output["pooler_output"]是否相等
CLS_tensor = last_hidden_state[:,0,:].reshape(2, 1, -1)
torch.equal(
    bert.pooler(CLS_tensor),
    pooler_output
)

Output:

True

输出为True,这验证了 [CLS]的tensor经过pooler层后,便是output[“pooler_output”]。

参考资料

  • 13
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jieshenai

为了遇见更好的文章

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值