ios移动网站开发工具,营销型网站有哪些,新闻发布平台,赚钱软件排行榜前十名多Transformer的双向编码器表示法
Bidirectional Encoder Representations from Transformers#xff0c;即Bert#xff1b;
第3章 Bert实战
学习如何使用预训练的BERT模型#xff1a;
如何使用预训练的BERT模型作为特征提取器#xff1b;探究Hugging Face的Transforme…多Transformer的双向编码器表示法
Bidirectional Encoder Representations from Transformers即Bert
第3章 Bert实战
学习如何使用预训练的BERT模型
如何使用预训练的BERT模型作为特征提取器探究Hugging Face的Transformers库学习如何使用Transformers库从预训练的BERT模型中提取嵌入如何从BERT的所有编码器层提取嵌入如何为下游任务微调预训练的BERT模型文本分类、情感分析如何将预训练的BERT模型应用于自然语言推理任务、问答任务以及命名实体识别等任务
预训练的BERT模型
从头开始预训练BERT模型是很费力的因此可以下载预训练的BERT模型并直接使用从Github仓库直接下载
L表示编码器层数H表示隐藏神经元的数量特征大小BERT-base的L12H768
预训练模型可以使用不区分大小写BERT-uncased的格式和区分大小写BERT-cased的格式
不区分大小写时所有标记都转化为小写区分大小写是标记大小写不变直接用于训练 不区分大小写的模型是最常用的模型但对一些特定任务如命名实体识别则必须保留大小写就需要使用区分大小写模型 预训练模型的应用场景
作为特征提取器提取嵌入针对文本分类任务、问答等下游任务对BERT模型进行微调
使用预训练BERT模型作为特征提取词嵌入
示例句子 - 标记句子 - 送入模型 - 返回每个标记的词级嵌入 以及 句级特征
构建分类数据集作正反向观点的二分类
每一个句子都有对应的标签1表示正面0表示负面
训练分类器做情感分类
通过模型或算法对文本进行向量化可以使用预训练的BERT模型对数据集的句子进行向量化这说的就是提嵌入
标记句子
在句子开始添加[CLS]标记在结尾添加[SEP]为统一所有句子的标记长度假设是512那么不足512的会使用标记[PAD]来重复填充为了让模型理解标记[PAD]只是用于匹配长度而不是实际标记的一部分需要引入注意力掩码将所有位置的注意力掩码值设为1再将标记[PAD]的位置设为0最后将所有标记映射为一个唯一的标记ID[CLS]标记对应的ID为101标记[PAD]的仍为0
接下来把 token_ids和 attention_mask一起输入预训练的BERT模型并获得每个标记的特征向量
最终输出的 R[CLS]就是标记[CLS]的嵌入它可以代表整个句子的总特征如果使用的是BERT-base模型配置那么每个标记的特征向量大小为768
采用类似的方法就可计算出训练集所有句子的特征向量一旦有了训练集所有句子的特征就可以把这些特征作为输入训练一个分类器 值的注意的是使用[CLS]标记的特征代表整个句子的特征并不总是一个好主意要获得一个句子的特征最好基于所有标记的特征进行平均或者汇聚 Hugging Face的Transformers库
Hugging Face是一个致力于通过自然语言将AI技术大众化的组织它提供了开源的Transformers库对一些自然语言处理任务和自然语言理解NLU任务非常有效Transformers库包含了百余种语言的数千个预训练模型而且还可以与Pytroch和TF兼容
安装命令pip install Transformers3.5.1
!pip install Transformers4.27.4 --ignore-installed PyYAML
# 问题cannot import name is_tokenizers_available from transformers.utils
# 参考https://discuss.huggingface.co/t/how-to-resolve-the-hugging-face-error-importerror-cannot-import-name-is-tokenizers-available-from-transformers-utils/23957/2# 示意代码 仅做指示
from transformers import BertModel, BertTokenizer
import torchdef get_tokens_and_attention_mask(tokens_a):# 标记更新tokens []# 上下句掩码当前示例任务 没什么用segment_ids []tokens.append([CLS])segment_ids.append(0)for token in tokens_a:tokens.append(token)segment_ids.append(0)if tokens[-1] ! [SEP]:tokens.append([SEP])segment_ids.append(0)# 将标记转换为它们的标记ID token_ids tokenizer.convert_tokens_to_ids(tokens)# 输入的注意力掩码attention_mask [1] * len(token_ids)while len(token_ids) max_seq_length:token_ids.append(0)attention_mask.append(0) # 这里加的0 实际对应的就是标记 [PAD]这里补0实际就不用处理添加[PAD]标记的逻辑segment_ids.append(0)assert len(token_ids) max_seq_length return token_ids, attention_mask# 下载并加载预训练模型
model BertModel.from_pretrained(bert-tiny-uncased)
# 下载并加载用于预训练模型的词元分析器
tokenizer BertTokenizer.from_pretrained(bert-tiny-uncased)sentence I am Good# 分词并获取标记
tokens_a tokenizer.tokenize(sentence)
token_ids, attention_mask get_tokens_and_attention_mask(tokens_a)
# 转为张量
token_ids torch.tensor(token_ids).unsqueeze(0)
attention_mask torch.tensor(attention_mask).unsqueeze(0)# 送入模型
hidden_rep, cls_head model(token_ids, attention_maskattention_mask)
hidden_rep
hidden_rep 包含了所有标记的嵌入特征我们使用的是bert-tiny假设max_seq_length10那么hidden_rep的形状就是torch.size([1, 10, 128])[1, 10, 128]分别对应 [batch_size, sequence_length, hidden_size]隐藏层的大小等于特征向量大小 第1个标记[CLS]的特征hidden_rep[0][0]第2个标记I的特征hidden_rep[0][1]
cls_head
它包含了[CLS]标记的特征shape为torch.size([1, 128])
可以用cls_head作为句子的整句特征
从BERT的所有编码器层中提取嵌入
前面介绍的是如何从预训练的BERT模型的顶层编码器提取嵌入此外也可以考虑从所有的编码器层获得嵌入
使用h0表示输入嵌入层h1则表示第一个编码器层第一个隐藏层研究人员使用预训练的BERT-base模型的不同层编码器的嵌入作为特征应用在命名实体识别任务所得的F1分数调和均值发现将最后4个编码器的嵌入 连接起来可以得到最高的F1这说明可以使用其他层所提取的嵌入而不必只用顶层编码器的嵌入
# 示意代码 仅做指示
from transformers import BertModel, BertTokenizer
import torch# output_hidden_states 可以控制输出所有编码器的嵌入
model BertModel.from_pretrained(bert-base-uncased, output_hidden_states True)
tokenizer BertTokenizer.from_pretrained(bert-base-uncased)sentence I am Good
tokens_a tokenizer.tokenize(sentence)token_ids, attention_mask get_tokens_and_attention_mask(tokens_a)# 转为张量
token_ids torch.tensor(token_ids).unsqueeze(0)
attention_mask torch.tensor(attention_mask).unsqueeze(0)# 送入模型
last_hidden_state, pooler_output, hidden_states model(token_ids, attention_maskattention_mask)
last_hidden_state: 包含从最后编码器获得所有标记的特征shape[1, 10 ,768]对应[batch_size, sequence_length, hidden_size]pooler_output 表示来自最后的编码器的[CLS]标记的特征它被一个线性激活函数和tanh激活函数进一步处理shape[1, 768]可被用作句子的特征hidden_states包含从所有编码器获得的所有标记的特征这是一个包含了13个值的元组包含了从输入层h0到最后的编码器层h2的特征 hidden_states[0]输入嵌入层h0获得的所有标记的特征hidden_states[12]最后一个编码器层h12获得的所有标记的特征shape[1, 10 ,768]
通过对返回值的解析就可以获得所有编码器层的标记嵌入
为下游任务微调预训练BERT模型
到目前为止我们已经学会了如何使用BERT模型再看如何针对下游任务进行微调 在提取句子的嵌入 R[CLS]后可以将其送入一个分类器并训练其进行分类类似的在微调过程中也可以这样做对R[CLS]使用softmax激活函数的前馈网络层 微调的两种调整权重的方式
与分类器层一起更新预训练的BERT模型参数仅更新分类器层的权重可以冻结预训练的BERT模型权重这类似于使用预训练的BERT模型作为特征提取器的情况也可以直接使用预训练的BERT模型作为特征提取器
安装必要的库pip install nlp
# 示意代码 仅做指示当时实际用的是pytorch_pretrained_bert的库也是hugging face的
# from pytorch_pretrained_bert.tokenization import BertTokenizer
# from pytorch_pretrained_bert.modeling import BertForSequenceClassification
# from pytorch_pretrained_bert.optimization import BertAdam
from transformers import BertForSequenceClassification, BertTokenizerFast, Trainer, TrainingArguments
from nlp import load_dataset
from torch
import numpy as np# 使用nlp库加载并下载数据集
dataset dataset.train_test_split(test_size0.3)# {
# test: Dataset(text, label),
# train: Dataset(text, label)
# }train_set dataset[train]
test_set dataset[test]model BertForSequenceClassification.from_pretrained(bert-base-uncased)
# 下载并加载用于预训练模型的词元分析器注意这里使用BertTokenizerFast类
tokenizer BertTokenizerFast.from_pretrained(bert-tiny-uncased)# 这里词元分析器会帮我们完成get_tokens_and_attention_mask函数的功能
tokenizer(I am Good)# 返回
# {
# input_ids:[101, 1000, 1001, 1002, 102],
# token_type_ids:[0, 0, 0, 0, 0],
# attention_mask: [1, 1, 1, 1, 1]
# } # 词元分析器可以输入任意数量的句子并动态地进行补长和填充只需将paddingTrue, max_length10
# tokenizer([I am Good,I am boy], paddingTrue, max_length10)# 返回输入两个句子
# {
# input_ids:[[], []],
# token_type_ids:[[], []],
# attention_mask: [[], []]
# }# 使用词元分析器预处理数据集
# 可以定义一个名为 preprocess的函数来处理数据集
def preprocess(data):return tokenizer(data[text], paddingTrue, truncationTrue) # 截断train_set.set_format(torch, columns[input_ids, attention_mask, label])
test_set.set_format(torch, columns[input_ids, attention_mask, label])
# 训练模型
batch_size 8 # 批量大小
epochs 2 # 迭代次数warmup_steps 500 # 预热步骤
weight_decay 0.01 # 权重衰减# 设置训练参数
training_args TrainingArguments(output_dir ./results,num_train_epochs epochs,per_device_train_batch_size batch_size,per_device_eval_batch_size batch_size,warmip_steps warmup_steps,weight_decay weight_decay,evaluate_during_training True,logging_dir ./logs
)trainer Trainer(model model,args training_args,train_dataset train_set,eval_dataset test_set)# 开始模型训练
trainer.train()# 训练结束后 评估模型
trainer.evaluate()# {epoch: 1.0, eval_loss: 0.68}
# {epoch: 2.0, eval_loss: 0.50}
# ...自然语言推理任务的BERT微调
在该任务中在确定的“前提”下推定假设是“真”、“假”还是“未定的”即模型的目标是确定一个句子对前提-假设对是真、是假、还是中性
对句子进行标记第一句开头加[CLS]标记每句结尾添加[SEP]标记送入预训练模型 得到[CLS]标记的特征即整个句子对的特征将R[CLS]送入分类器
问答任务的BERT微调
问答任务重对一个问题模型会返回一个答案目标是让模型返回正确答案 虽然这个任务可以使用生成式的任务模型但这里是基于微调BERT实现的思路不同 BERT模型输入是一个问题和一个段落这个段落需要是一个含有答案的段落BERT必须从该段落中提取答案
要通过微调BERT模型来完成这项任务模型必须要了解给定段落中包含答案的文本段的起始索引和结束索引要找到这两个索引模型应该返回“该段落中每个标记是答案的起始标记和结束标记的概率”
这里引入两个向量
起始向量S结束向量E 两个向量的值将通过训练获得 为了计算这个概率
对于每个标记i计算标记特征Ri和起始向量S之间的点积然后将softmax函数应用于点积得到概率接下来选择其中具有最高概率的标记并将其索引值作为起始索引结束索引的计算方式类似这样就可以使用起始索引和结束索引选择包含答案的文本段了
# 微调BERT模型用于问答任务
from transformers import BertForQuestionAnswering, BertTokenizer# 下载模型
model BertForQuestionAnswering.from_pretrained(bert-large-uncased-whole-word-masking-fine-tuned-squad) # 该模型基于斯坦福问答数据集SQyAD微调而得# 下载并加载词元分析器
tokenizer BertTokenizer.from_pretrained(bert-large-uncased-whole-word-masking-fine-tuned-squad)question A
paragraph AAABBBBBBAAAAquestion [CLS] question [SEP]
paragraph paragraph [SEP]# 转为 input_ids
question_tokens tokenizer.tokenize(question)
paragraph_tokens tokenizer.tokenize(paragraph)# 设置segment_ids
segment_ids [0] * len(question_tokens)
segment_ids segment_ids [1] * len(paragraph_tokens)# 转张量
input_ids torch.tensor([input_ids])
segment_ids torch.tensor([segment_ids])start_scores, end_scores model(input_ids, token_type_ids segment_ids)start_index torch.argmax(start_scores)
end_index torch.argmax(end_scores)# 答案.join(tokens[start_index: end_index 1]) 关于问答任务BERT微调的一些疑问 问题1起始向量和结束向量 是怎么来的R[CLS]和R[SEP]是否就是这两个向量问题2上面的示例代码段很明显并不是一个微调而是一个应用虽然是应用一个已经微调好的模型但如何训练却未讲明 可以展开思考待查明 对执行命名实体识别任务的BERT模型微调
任务目标是将命名实体划分到预设的类别中如某一句子种出现了人名和地名还有其他词的词汇能将相应词汇进行准确归类
对句子进行标记送入预训练BERT模型获得每个标记特征在将这些标记特征送入一个分类器使用softmax激活函数的前馈网络层最后分类器返回每个命名实体对应的类别 好吧这里说的依旧很简略但至少上我们知道了大致的实现逻辑 此前分类任务的实践代码梳理
todo