BERT实战(上)

上半部分介绍如何从BERT模型提取嵌入,下半部分介绍如何针对下游任务进行微调

1. 预训练的BERT模型

使用16GB的数据从头开始训练104M(1.04亿)参数量的BERT-base模型是很费算力的,Google发布了各种配置的BERT模型,可以基于这些模型对下游任务进行微调。L是Transformer层数,H是隐藏层维度。


BERT模型配置

2. 从预训练BERT模型中提取嵌入

  1. 标记级(词级)的特征
  2. 句级的特征。通常情况可以使用全部标记的特征平均或聚合而不单纯只用[CLS]标记产生的特征
    特征

2.1 安装Hugging Face的Transformers库

pip install Transformers

2.2 BERT嵌入的生成

  1. 预处理句子
  2. 调用模型获得嵌入

2.2.1 预处理句子

2.2.1.1 引入模型和词元分析器

from transformers import BertModel, BertTokenizer
# 创建模型
model = BertModel.from_pretrained('bert-base-uncased')
# 创建tokenizer
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')

2.2.1.2 手动处理一个句子

步骤:

  1. 分词:tokenizer.tokenize(sentence)
  2. 添加[CLS][SEP]标记
  3. 使用[PAD]补齐
  4. 创建注意力掩码:不注意[PAD]部分的句子
  5. 将标记转化为标记id:tokenizer.convert_tokens_to_ids(tokens) / 标记id解码成标记:tokenizer.decode(input_ids)
  6. 将标记id和注意力掩码转化为张量
sentence = 'I love China'
print('句子: {}'.format(sentence))
# 句子: I love China

tokens = tokenizer.tokenize(sentence)
print('分词: {}'.format(tokens))
# 分词: ['i', 'love', 'china']

tokens = ['[CLS]'] + tokens + ['[SEP]']
print('添加[CLS]和[SEP]标记: {}'.format(tokens))
# 添加[CLS]和[SEP]标记: ['[CLS]', 'i', 'love', 'china', '[SEP]']

tokens = tokens + ['[PAD]'] + ['[PAD]']
print('使用[PAD]补齐: {}'.format(tokens))
# 使用[PAD]补齐: ['[CLS]', 'i', 'love', 'china', '[SEP]', '[PAD]', '[PAD]']

attention_mask = [1 if i != '[PAD]' else 0 for i in tokens]
print('创建注意力掩码: {}'.format(attention_mask))
# 创建注意力掩码: [1, 1, 1, 1, 1, 0, 0]

input_ids = tokenizer.convert_tokens_to_ids(tokens)
print('将标记转化为标记id: {}'.format(input_ids))
# 将标记转化为标记id: [101, 1045, 2293, 2859, 102, 0, 0]
decode_ids = tokenizer.decode(input_ids)
print('标记id解码成标记: {}'.format(decode_ids))
# 标记id解码成标记: [CLS] i love china [SEP] [PAD] [PAD]

attention_mask = torch.tensor(attention_mask).unsqueeze(0)
input_ids = torch.tensor(input_ids).unsqueeze(0)
print('注意力掩码张量: {}'.format(attention_mask))
# 注意力掩码张量: tensor([[1, 1, 1, 1, 1, 0, 0]])
print('标记id张量: {}'.format(input_ids))
# 标记id张量: tensor([[ 101, 1045, 2293, 2859,  102,    0,    0]])

2.2.1.3 编码一个句子

  • tokenizer(sentence)
sentence = 'I love China'
inputs = tokenizer(sentence)
print('句子: {}'.format(sentence))
# 句子: I love China
print('input_ids: {}'.format(inputs['input_ids']))
# input_ids: [101, 1045, 2293, 2859, 102]
print('attention_mask: {}'.format(inputs['attention_mask']))
# attention_mask: [1, 1, 1, 1, 1]
print('token_type_ids: {}'.format(inputs['token_type_ids']))
# token_type_ids: [0, 0, 0, 0, 0]

2.2.1.4 编码两个句子并补齐

  • tokenizer([sentence_a, sentence_b], padding=True)
sentence_a = 'This is a short sentence.'
sentence_b = 'This is a rather long sequence. It is at least longer than the sequence A.'
print('句子a: {}'.format(sentence_a))
print('句子b: {}'.format(sentence_b))

outputs = tokenizer([sentence_a, sentence_b], padding=True)
print('input_ids: {}'.format(outputs['input_ids']))
# input_ids: [[101, 2023, 2003, 1037, 2460, 6251, 1012, 102, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [101, 2023, 2003, 1037, 2738, 2146, 5537, 1012, 2009, 2003, 2012, 2560, 2936, 2084, 1996, 5537, 1037, 1012, 102]]
print('attention_mask: {}'.format(outputs['attention_mask']))
# attention_mask: [[1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]
print('token_type_ids: {}'.format(outputs['token_type_ids']))
# token_type_ids: [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]

2.2.1.5 编码两个拼接的句子

  • tokenizer(sentence_a, sentence_b)
sentence_a = 'This is a short sentence.'
sentence_b = 'This is a rather long sequence. It is at least longer than the sequence A.'
print('句子a: {}'.format(sentence_a))
print('句子b: {}'.format(sentence_b))

inputs = tokenizer(sentence_a, sentence_b)
print('input_ids: {}'.format(inputs['input_ids']))
# input_ids: [101, 2023, 2003, 1037, 2460, 6251, 1012, 102, 2023, 2003, 1037, 2738, 2146, 5537, 1012, 2009, 2003, 2012, 2560, 2936, 2084, 1996, 5537, 1037, 1012, 102]
print('attention_mask: {}'.format(inputs['attention_mask']))
# attention_mask: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
print('token_type_ids: {}'.format(inputs['token_type_ids']))
# token_type_ids: [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]

2.2.2 调用模型获得嵌入

调用模型:

  1. model(input_ids, attention_mask=attention_mask, token_type_ids=token_type_ids)

输出:

  1. outputs['pooler_output']:句级的嵌入,NSP任务使用这个预测。[CLS]标记经过tanh激活的前馈神经网络获得
  2. outputs['last_hidden_state']:词级的嵌入,MLM任务使用这个预测
sentence_a = 'This is a short sentence.'
sentence_b = 'This is a rather long sequence. It is at least longer than the sequence A.'
inputs = tokenizer(sentence_a, sentence_b)
input_ids = torch.tensor(inputs['input_ids']).unsqueeze(0)
attention_mask = torch.tensor(inputs['attention_mask']).unsqueeze(0)
token_type_ids = torch.tensor(inputs['token_type_ids']).unsqueeze(0)

outputs = model(input_ids, attention_mask=attention_mask, token_type_ids=token_type_ids)
pooler_output = outputs['pooler_output'] 
last_hidden_state = outputs['last_hidden_state']  
print('pooler_output shape: {}'.format(pooler_output.shape))  # [batch_size, hidden_size]
# pooler_output shape: torch.Size([1, 768])
print('last_hidden_state shape: {}'.format(last_hidden_state.shape))  # [batch_size, sequence_length, hidden_size]
# last_hidden_state shape: torch.Size([1, 26, 768])

3. 从BERT的所有编码器层中提取嵌入

原因:单纯使用一个隐藏层的特征处理下游任务并非是结果最好的。BERT的研究人员在命名实体任务中进行了实验,比较了实用不同层特征的F1分数。当串联最后4个隐藏层时,取得了最好的F1分数。

不同层的嵌入的F1分数

3.1 提取方式

  • BertModel.from_pretrained('bert-base-uncased', output_hidden_states=True),设置output_hidden_states=True获得每一层的特征

输出:

  1. outputs['hidden_states']:获得13 * [batch_size, sequence_length, hidden_size] 的张量
  2. hidden_states[0]:输入嵌入层的输出
  3. hidden_states[-1]:最后一个编码器层的输出
model = BertModel.from_pretrained('bert-base-uncased', output_hidden_states=True)
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')

sentence_a = 'This is a short sentence.'
sentence_b = 'This is a rather long sequence. It is at least longer than the sequence A.'
print('句子a: {}'.format(sentence_a))
print('句子b: {}'.format(sentence_b))
outputs = tokenizer(sentence_a, sentence_b)
input_ids = torch.tensor(outputs['input_ids']).unsqueeze(0)
attention_mask = torch.tensor(outputs['attention_mask']).unsqueeze(0)
token_type_ids = torch.tensor(outputs['token_type_ids']).unsqueeze(0)

outputs = model(input_ids, attention_mask=attention_mask, token_type_ids=token_type_ids)
pooler_output = outputs['pooler_output']
last_hidden_state = outputs['last_hidden_state']
hidden_states = outputs['hidden_states']
print('pooler_output shape: {}'.format(pooler_output.shape))  # [batch_size, hidden_size]
# pooler_output shape: torch.Size([1, 768])
print('last_hidden_state shape: {}'.format(last_hidden_state.shape))  # [batch_size, sequence_length, hidden_size]
# last_hidden_state shape: torch.Size([1, 26, 768])
print('hidden_states length: {}'.format(len(hidden_states)))  # 13
# hidden_states length: 13
print('hidden_states[0] shape: {}'.format(hidden_states[0].shape))  # [batch_size, sequence_length, hidden_size]
# hidden_states[0] shape: torch.Size([1, 26, 768])

参考资料

[1]. BERT基础教程Transformer大模型实战
[2]. 如何计算Bert模型的参数量:https://blog.csdn.net/weixin_44402973/article/details/126405946
[3]. Pytorch Transformer Tokenizer常见输入输出实战详解:https://blog.csdn.net/yosemite1998/article/details/122306758

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,546评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,224评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,911评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,737评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,753评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,598评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,338评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,249评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,696评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,888评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,013评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,731评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,348评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,929评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,048评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,203评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,960评论 2 355

推荐阅读更多精彩内容