1.前言
bert是非常出名的预训练模型,它在很少的数据也能有很好的表现。
在我们将要引出bert模型时,先来简单diss其他常见模型的缺点吧!!
-
diss Word2vec
word2vec 不能解决一词多义,也不能解决OOV问题,生成的句子和文档向量也差强人意 -
diss RNN
最出名的缺点是:不能并行,训练速度太慢了 -
diss CNN
虽然可以并行,但太适用于分类任务了,用在其他NLP任务上,效果还不如RNN好 -
diss seq2seq
很适合做翻译任务,但encoder和decoder仍然用的RNN,不能并行,所以不能用太深层网络;并且语义向量也只能记住靠近它的一些单词
-
diss Attention
仍然用的RNN结构,不能用多层,否则速度很慢
总结:要想改善模型,则不能使用
- word2vec
- RNN 及其变种
所以出现了如下发展路径:
2. 简单了解Transformer
这里只是简单描述什么是Transformer,至于详细的理解敬请期待 ^^
Transformer 本质仍然是 seq2seq 模型,只不过它叠加了多层的 encoder和decoder
其中每个encoder 和 decoder 结构如下,核心是 self-Attention 机制
3. Bert 模型简介
3.1 bert模型结构
根据这张图:bert训练的两个主要任务是
- 预测被 [mask] 的单词, 相当于完形填空
- 预测输入的两个句子是否相邻
这两个任务就对应了两个损失,因此 bert 是最小化这两个损失函数的和来训练模型的。
3.2 bert的输入
bert将输入句子转化为词向量,是经过了3 个Embedding 的加和, 即
input_embed = Token_embed + Sentence_embed + Position_embed.
源码是:
调用模型的话,我们只需要输入:
1) input_ids:一个形状为[batch_size, sequence_length]的 torch.LongTensor,在词汇表中包含单词的token索引, 注意 在句子首尾分别加了 [cls] 和 [sep] 的 索引
2) segment_ids :形状[batch_size, sequence_length]的可选 torch.LongTensor,在0, 1中选择token类型索引。类型0对应于句子A,类型1对应于句子B。如 [0,0,0,0,0,1,1,1,1,1], 0代表第一个句子A, 1代表第二个句子B,默认全为0
3) input_mask:一个可选的 torch.LongTensor,形状为[batch_size, sequence_length],索引在0, 1中选择。0 是 padding 的位置,1是没有padding的字
用其他博客的图:
其中input_ids = seq_ids
segment_ids = token_type_ids
input_mask = mask
3.3 bert的下游任务
- 加一个线性层就可以分类了
3.4 使用bert提取Embedding向量
可以看到每个Encoder 的隐层输出我们都可以当成是Ebedding, 但哪一层的Embedding更能代表上下文信息呢??
第一层效果通常不太好,最后4层隐层输出的拼接效果是最好的
4. Bert学习路径
推荐学习路径:
5.体验bert操作
5.1 准备下载模型
我们需要这3个模型:
vocab.txt 分字表:
贴心的我,已经帮大家整理好了,这里用到是中文bert模型。
链接:https://pan.baidu.com/s/1phZVYv2G-GZXnkWzAhtdeQ
提取码:gjd1
复制这段内容后打开百度网盘手机App,操作更方便哦
下载完之后:再 pip install pytorch_pretrained_bert
5.2 代码
import torch
from pytorch_pretrained_bert import BertTokenizer, BertModel, BertConfig, BertForMaskedLM
import torch.nn as nn
- 1
- 2
- 3
- 4
# 设置bert模型路径
import os
bert_path = r"D:\Bert\bert-base-chinese"
bert_config_path = os.path.join(bert_path, r"bert_config.json")
bert_vocab_path = os.path.join(bert_path, r"vocab.txt")
bert_model_path = os.path.join(bert_path, r"pytorch_model.bin")
- 1
- 2
- 3
- 4
- 5
- 6
# 加载模型配置
bert_config = BertConfig.from_json_file(bert_config_path)
# 加载中文词库
bert_vocab = BertTokenizer(vocab_file= bert_vocab_path)
# 加载模型
bert_model = BertModel.from_pretrained(bert_path) # 这个函数的输入要求a path or url to a pretrained model archive containing:
#. bert_config.json
a configuration file for the model
#. pytorch_model.bin
a PyTorch dump of a BertForPreTraining instance
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
# Tokenize input
text = "乌兹别克斯坦议会立法院主席获连任"
# 利用bert自带的分字工具分字
tokenized_text = bert_vocab.tokenize(text) # list
#print("type of tokenized:", type(tokenized_text))
# 在句子首尾分别加上[CLS],[SEP]. [CLS] 只有一个,而[SEP]在每句话的末尾,当输入两个句子时,就有两个[SEP]
tokenized_text = ["[CLS]"] + tokenized_text + ["[SEP]"]
#将 字 转化为词库中的索引
input_ids = bert_vocab.convert_tokens_to_ids(tokenized_text) # list, len:18
#print(“type of input_ids :”, type(input_ids), len(input_ids))
## input_ids.shape : [batch, sequence_lenth]
# Define sentence A and B indices ,由于输入只有一个句子,那么就全定义为0
segment_ids = [0] * len(input_ids)
# input_mask,由于没有填充padding, 就全定义为1
input_mask = [1] * len(input_ids)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
# Bert 的输入 只支持 长整型,因此必须转化为 torch.long tensor 形式
input_ids = torch.LongTensor(input_ids)
print("input_ids: ", input_ids.size()) # input_ids: torch.Size([18])
segment_ids = torch.tensor([segment_ids], dtype = torch.long)
input_mask = torch.tensor([input_mask], dtype = torch.long)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
# bert 模型调用
all_encoder_layer, pooled_output = bert_model(input_ids, input_mask,segment_ids)
## all_encoder_layers: 一个包含 12 个 transfomer encoder 层输出的列表list
### 每一层的输出大小是: [batch, seq_lenth, hiddern_size], 对于bert-base 是12, bert-large是24
print("all_encoder_layer:\n ", type(all_encoder_layer), len(all_encoder_layer), all_encoder_layer[0].size())
## pooled_output: 最后一个 transfomer encoder ,且输入句子的第一个字[CLS]位置上的隐层输出
### 大小:[batch, hidden_size]
### 这个输出就可以看做是输入句子的语义信息了
print(“pooled_output: \n”, type(pooled_output), len(pooled_output), pooled_output.size())
######################################
all_encoder_layer:
<class ‘list’> 12 torch.Size([1, 18, 768])
pooled_output:
<class ‘torch.Tensor’> 1 torch.Size([1, 768])
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
-
如果我们想要得到输入句子的 embedding 表示,那么我们只要 all_encoder_layer 的最后几层向量
-
如果我们想要做分类,那么只要Pooled_output 向量,因为它代表了整个输入句子的语义信息
考虑padding时,代码有什么变化??
为什么要padding呢,因为我们训练时候是用batch_size样本的,但是每个样本即句子的长度都不一致,因此我们必须设置一个最大句子的长度,当其他句子小于此长度时就padding,大于此长度就截断
# padding
# 设置最长句子长度
max_seq_lenth = 300
new_text = “乌兹别克斯坦议会立法院主席获连任”
tokenized_text = bert_vocab.tokenize(new_text) # list
tokenized_text = ["[CLS]"] + tokenized_text + ["[SEP]"] # list
input_ids = bert_vocab.convert_tokens_to_ids(tokenized_text) # list
input_mask = [1] * len(input_ids)
# define padding, 填充的地方置为0,未填充的地方是 1
padding = [0] * (max_seq_lenth - len(input_ids))
input_ids += padding # 300
input_mask += padding
# input_ids 后面填充部分都要置0
# 这里只有一个输入句子,因此可以不用segment_ids,因为默认就有全0的segment_ids
#转化为 torch.LongTensor 形式
input_ids = torch.tensor([input_ids], dtype = torch.long) #300
input_mask = torch.tensor([input_mask], dtype = torch.long) # 300
print("padding intput_ids: ", input_ids.size()) # 300
## bert_model 的测试模式,不用梯度更新
bert_model.eval()
with torch.no_grad():
all_encoder_layer, pooled_output = bert_model(input_ids, attention_mask= input_mask)
print("padding all_encoder_layers: ", all_encoder_layer[0].shape)
print("padding pooled_output : ", pooled_output.size())
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32