《自然语言处理——基于预训练模型的方法》——车万翔、郭江、崔一鸣
自然语言处理——基于预训练模型的方法——第7章 预训练语言模型
动态词向量方法CoVe和ELMo将词表示从静态转变到动态,同时也在多个自然语言处理任务中显著地提升了性能。随后,以GPT和BERT为代表的基于大规模文本训练出的预训练语言模型(Pre-trained Language Model,PLM)已成为目前主流的文本表示模型。
7.1 概述
预训练模型并不是自然语言处理领域的“首创”技术。在计算机视觉(Com-puter Vision,CV)领域,以ImageNet为代表的大规模图像数据为图像识别、图像分割等任务提供了良好的数据基础。
在CV领域,通常会使用ImageNet进行一次预训练,让模型从海量图像中充分学习如何从图像中提取特征。然后根据具体的目标任务,使用相应的领域数据精调,使模型进一步“靠近”目标任务的应用场景,起到领域适配和任务适配的作用。“预训练+精调”模式
在自然语言处理领域,预训练模型通常指代预训练语言模型。
2018年,以GPT和BERT为代表的基于深层Transformer的表示模型出现,预训练语言模型被大家广泛熟知。
预训练语言模型三大特点:
- 大数据
- 大模型
- 大算力
7.1.1 大数据
预训练数据需要讲究“保质”和“保量”。
- “保质”是希望预训练语料的质量要尽可能高,避免混入过多的低质量语料。
- “保量”是希望预训练语料的规模要尽可能大,从而获取更丰富的上下文信息。
7.1.2 大模型
数据规模和模型规模在一定程度上正相关。在小数据上训练模型时,通常模型的规模不会太大,避免过拟合。在大数据上训练模型时,若不增大模型规模,可能会造成新的知识无法存放的情况,从而无法完全涵盖大数据中丰富的语义信息。
容量大 → 参数量大
如何设计考虑:
- 模型需要具有较高的并行程度,以弥补大模型带来的训练速度下降的问题
- 模型能够捕获并构建上下文信息,以充分挖掘大数据文本中丰富的语义信息。
基于Transformer的神经网络模型成为目前构建预训练语言模型的最佳选择。
7.1.3 大算力
深度学习计算设备——图形处理单元(Graphics Processing Unit,GPU)
后起之秀——张量处理单元(TensorProcessing Unit,TPU)
- 图形处理单元(GPU,俗称显卡)
随着GPU核心的不断升级,计算能力和计算速度得到大幅提升,不仅可以作为常规的图形处理设备,同时也可以成为深度学习领域的计算设备。
CPU擅长处理串行运算以及逻辑控制和跳转,而GPU更擅长大规模并行运算。
英伟达生产的GPU依靠与之匹配的统一计算设备架构(Compute Unified Device Archi-tecture,CUDA)能够更好地处理复杂的计算问题,同时深度优化多种深度学习基本运算指令。
英伟达Volta系列硬件 - 张量处理单元(TPU)
谷歌公司近年定制开发的专用集成电路(Appli-cation Specific Integrated Circuit,ASIC)。
目前,TPU主要支持TensorFlow深度学习框架,只能通过谷歌云服务器访问使用。
7.2 GPT
OpenAI 公司在2018年提出了一种生成式预训练(Generative Pre-Training,GPT)模型用来提升自然语言理解任务的效果,正式将自然语言处理带入“预训练”时代。
自然语言处理新范式:生成式预训练 + 判别式任务精调
- 生成式预训练:在大规模文本数据上训练一个高容量的语言模型,从而学习更加丰富的上下文信息。
- 判别式任务精调:将预训练好的模型适配到下游任务中,并使用有标注数据学习判别式任务。
7.2.1 无监督预训练
GPT的整体结构是一个基于Transformer的单向语言模型,即从左至右对输入文本建模,使用多层Transformer作为模型的基本结构。
7.2.2 有监督任务精调
在预训练阶段,GPT利用大规模数据训练出基于深层Transformer的语言模型,已经掌握了文本的通用语义表示。精调(Fine-tuning)的目的是在通用语义表示的基础上,根据下游任务(Downstream task)的特性进行领域适配,使之与下游任务的形式更加契合,以获得更好的下游任务应用效果。
下游任务精调通常是由有标注数据进行训练和优化的。为了进一步提升精调后模型的通用性以及收敛速度,可以在下游任务精调时加入一定权重的预训练任务损失。这样做是为了缓解在下游任务精调的过程中出现灾难性遗忘(Catastrophic Forgetting)问题。
7.2.3 适配不同的下游任务
- 单句文本分类
输入由单个文本构成,输出由对应的分类标签构成。 - 文本蕴含
输入由两段文本构成,输出由分类标签构成,用于判断两段文本之间的蕴含关系。 - 相似度计算
相似度计算任务也由两段文本构成。但与文本蕴含任务不同的是,参与相似度计算的两段文本之间不存在顺序关系。 - 选择性阅读理解
要将〈篇章,问题,选项〉作为输入,以正确选项编号作为标签,任务是让机器阅读一篇文章,需要从多个选项中选择出问题对应的正确选项。
7.3 BERT
BERT(Bidirectional Encoder Representation from Transformers)由Devlin等人在2018年提出的基于深层Transformer的预训练语言模型。BERT不仅充分利用了大规模无标注文本来挖掘其中丰富的语义信息,同时还进一步加深了自然语言处理模型的深度。
7.3.1 整体结构
BERT的基本模型结构由多层Transformer构成,包含两个预训练任务:掩码语言模型(Masked Language Model,MLM)和下一个句子预测(Next Sentence Prediction,NSP)
在预训练阶段的输入形式统一为两段文本拼接的形式。
7.3.2 输入表示
BERT的输入表示由词向量、块向量和位置向量之和组成。
- 词向量
通过词向量矩阵将输入文本转换成实值向量表示。 - 块向量
编码当前词属于哪一个块,输入序列中每个词对应的块编码(Segment Encoding)为当前词所在块的序号(从0开始计数)。 - 位置向量
来编码每个词的绝对位置。
7.3.3 基本预训练任务
引入基于自编码(Auto-Encoding)的预训练任务进行训练。BERT的基本预训练任务由掩码语言模型和下一个句子预测构成。
- 掩码语言模型
为了真正实现文本的双向建模,即当前时刻的预测同时依赖于“历史”和“未来”,BERT采用了一种类似完形填空(Cloze)的做法,并称之为掩码语言模型(MLM)。
MLM预训练任务直接将输入文本中的部分单词掩码(Mask),并通过深层Transformer模型还原为原单词,从而避免了双向语言模型带来的信息泄露问题,迫使模型使用被掩码词周围的上下文信息还原掩码位置的词。
建模方法:
- 输入层
- BERT编码层
- 输出层
- 代码实现
def create_masker_lm_predictions(tokens,masked_lm_prob,max_predictions_per_seq,vocab_words,rng):
"""
创建MLM任务的训练数据
:param tokens: 输入文本
:param masked_lm_prob:掩码语言模型的掩码概率
:param max_predictions_per_seq: 每个序列的最大预测数目
:param vocab_words: 词表列表
:param rng: 随机数生成器
:return:
"""
cand_indexes = []
for ( i , token) in enumerate(tokens):
# 掩码时跳过[CLS]和[SEP]
if token == "[CLS]" or token == "[SEP]":
continue
cand_indexes.append([i])
rng.shuffle(cand_indexes) # 随机打乱所有下标
output_tokens = list(tokens)
# 计算预测数目
num_to_predict = min(max_predictions_per_seq,max(1,int(round(len(tokens) * masked_lm_prob))))
masked_lms = [] # 存储掩码实例
covered_indexes = set() # 存储已经处理过的下标
for index in cand_indexes:
if len(masked_lms) >= num_to_predict:
break
if index in covered_indexes:
continue
covered_indexes.add(index)
masked_token = None
# 80%概率替换为[MASK]
if rng.random() < 0.8:
masked_token = "[MASK]"
else:
# 以10%的概率不进行任何替换
if rng.random() < 0.5:
masked_token = tokens[index]
# 以10%的概率替换成词表中的随机词
else:
masked_token = vocab_words[rng.randint(0,len(vocab_words) - 1)]
output_tokens[index] = masked_token # 设置为被掩码的token
masked_lms.append(MaskedLmInstance(index = index ,label = tokens[index]))
masked_lms = sorted(masked_lms,key=lambda x:x.index)
masked_lm_positions = [] # 存储需要掩码的下标
masked_lm_labels = [] # 存储掩码前的原词,还原目标
for p in masked_lms:
masked_lm_positions.append(p.index)
masked_lm_labels.append(p.label)
return (output_tokens,masked_lm_positions,masked_lm_labels)
- 下一个句子预测(NSP)
构建两段文本之间的关系。NSP任务是一个二分类任务,需要判断句子B是否是句子A的下一个句子。
训练样本:
- 正样本:来自自然文本中相邻的两个句子“句子A”和“句子B”,即构成“下一个句子”关系。
- 负样本:将“句子B”替换为语料库中任意一个其他句子,即构成“非下一个句子”关系。
建模方法
- 输入层
对于给定的经过掩码处理后的输入文本→BERT的输入表示。 - BERT编码层
输入表示v经过L层Transformer的编码,借助自注意力机制充分学习文本中每个词之间的语义关联,最终得到输入文本的上下文语义表示。 - 输出层
判断输入文本x(2)是否是x(1)的下一个句子
7.3.4 更多预训练任务
可将MLM任务替换为如下两种进阶预训练任务,以进一步提升预训练难度,从而挖掘出更加丰富的文本语义信息。
- 整词掩码
在MLM任务中,最小的掩码单位是WordPiece子词,存在一定的信息泄露。
整词掩码(Whole Word Masking,WWM)预训练任务的提出解决了Word-Piece子词信息泄露的问题。最小掩码单位由WordPiece子词变为整词。
- 正确理解整词掩码
在整词掩码中,当发生掩码操作时:
- 整词中的各个子词均会被掩码处理
- 子词的掩码方式并不统一,并不是采用一样的掩码方式
- 子词各自的掩码方式受概率控制
- 中文整词掩码
可将中文的字(Character)类比为英文中的WordPiece子词,进而应用整词掩码技术。
- N-gram掩码
将连续的N-gram文本进行掩码,并要求模型还原缺失内容。
具体操作流程:
- 首先根据掩码概率判断当前标记(Token)是否应该被掩码
- 当被选定为需要掩码时,进一步判断N-gram的掩码概率
- 对该标记及其之后的N−1个标记进行掩码。当不足N−1个标记时,以词边界截断
- 在掩码完毕后,跳过该N-gram,并对下一个候选标记进行掩码判断
- 三种掩码策略的区别
7.3.5 模型对比
7.4 预训练语言模型的应用
7.4.1 概述
BERT模型的两种应用方式
- 特征提取
仅利用BERT提取输入文本特征,生成对应的上下文语义表示,而BERT本身不参与目标任务的训练,即BERT部分只进行解码(无梯度回传)。 - 模型精调
利用BERT作为下游任务模型基底,生成文本对应的上下文语义表示,并参与下游任务的训练,在下游任务学习过程中,BERT对自身参数进行更新。
7.4.2 单句文本分类
- 建模方法
单句文本分类(Single Sentence Classification,SSC)任务是最常见的自然语言处理任务,需要将输入文本分成不同类别。
- 输入层
对于一个给定的经过WordPiece分词后的句子x1x2··· xn,进行如下处理得到BERT的原始输入X。 - BERT编码层
输入表示v经过多层Transformer的编码,借助自注意力机制充分学习句子中每个词之间的语义关联,并最终得到句子的上下文语义表示。 - 分类输出层
在得到[CLS]位的隐含层表示h0后,通过一个全连接层预测输入文本对应的分类标签。
- 代码实现
import numpy as np
from datasets import load_dataset, load_metric
from transformers import BertTokenizerFast, BertForSequenceClassification, TrainingArguments, Trainer
# 加载训练数据、分词器、预训练模型以及评价方法
dataset = load_dataset('glue', 'sst2')
tokenizer = BertTokenizerFast.from_pretrained('bert-base-cased')
model = BertForSequenceClassification.from_pretrained('bert-base-cased', return_dict=True)
metric = load_metric('glue', 'sst2')
# 对训练集进行分词
def tokenize(examples):
return tokenizer(examples['sentence'], truncation=True, padding='max_length')
dataset = dataset.map(tokenize, batched=True)
encoded_dataset = dataset.map(lambda examples: {'labels': examples['label']}, batched=True)
# 将数据集格式化为torch.Tensor类型以训练PyTorch模型
columns = ['input_ids', 'token_type_ids', 'attention_mask', 'labels']
encoded_dataset.set_format(type='torch', columns=columns)
# 定义评价指标
def compute_metrics(eval_pred):
predictions, labels = eval_pred
return metric.compute(predictions=np.argmax(predictions, axis=1), references=labels)
# 定义训练参数TrainingArguments,默认使用AdamW优化器
args = TrainingArguments(
"ft-sst2", # 输出路径,存放检查点和其他输出文件
evaluation_strategy="epoch", # 定义每轮结束后进行评价
learning_rate=2e-5, # 定义初始学习率
per_device_train_batch_size=16, # 定义训练批次大小
per_device_eval_batch_size=16, # 定义测试批次大小
num_train_epochs=2, # 定义训练轮数
)
# 定义Trainer,指定模型和训练参数,输入训练集、验证集、分词器以及评价函数
trainer = Trainer(
model,
args,
train_dataset=encoded_dataset["train"],
eval_dataset=encoded_dataset["validation"],
tokenizer=tokenizer,
compute_metrics=compute_metrics
)
# 开始训练!(主流GPU上耗时约几小时)
trainer.train()
7.4.3 句对文本分类
- 建模方法
句对文本分类(Sentence Pair Classification,SPC)任务与单句文本分类任务类似,需要将一对文本分成不同类别。应用BERT处理句对文本分类任务的模型与单句文本分类模型类似,仅在输入层有所区别。
- 输入层
对于一对给定的经过 WordPiece 分词后的句子,将其拼接得到BERT的原始输入X和输入表示v。 - BERT层
- 分类输出层
- 代码实现
import numpy as np
from datasets import load_dataset, load_metric
from transformers import BertTokenizerFast, BertForSequenceClassification, TrainingArguments, Trainer
# 加载训练数据、分词器、预训练模型以及评价方法
dataset = load_dataset('glue', 'rte')
tokenizer = BertTokenizerFast.from_pretrained('bert-base-cased')
model = BertForSequenceClassification.from_pretrained('bert-base-cased', return_dict=True)
metric = load_metric('glue', 'rte')
# 对训练集进行分词
def tokenize(examples):
return tokenizer(examples['sentence1'], examples['sentence2'], truncation=True, padding='max_length')
dataset = dataset.map(tokenize, batched=True)
encoded_dataset = dataset.map(lambda examples: {'labels': examples['label']}, batched=True)
# 将数据集格式化为torch.Tensor类型以训练PyTorch模型
columns = ['input_ids', 'token_type_ids', 'attention_mask', 'labels']
encoded_dataset.set_format(type='torch', columns=columns)
# 定义评价指标
def compute_metrics(eval_pred):
predictions, labels = eval_pred
return metric.compute(predictions=np.argmax(predictions, axis=1), references=labels)
# 定义训练参数TrainingArguments,默认使用AdamW优化器
args = TrainingArguments(
"ft-rte", # 输出路径,存放检查点和其他输出文件
evaluation_strategy="epoch", # 定义每轮结束后进行评价
learning_rate=2e-5, # 定义初始学习率
per_device_train_batch_size=16, # 定义训练批次大小
per_device_eval_batch_size=16, # 定义测试批次大小
num_train_epochs=2, # 定义训练轮数
)
# 定义Trainer,指定模型和训练参数,输入训练集、验证集、分词器以及评价函数
trainer = Trainer(
model,
args,
train_dataset=encoded_dataset["train"],
eval_dataset=encoded_dataset["validation"],
tokenizer=tokenizer,
compute_metrics=compute_metrics
)
trainer.train()
7.4.4 阅读理解
- 建模方法
抽取式阅读理解主要由篇章(Passage)、问题(Question)和答案(Answer)构成,要求机器在阅读篇章和问题后给出相应的答案,而答案要求是从篇章中抽取出的一个文本片段(Span)。
- 输入层
对问题Q= q1q2··· qn和篇章P= p1p2··· pm (P和Q均经过WordPiece分词后得到)拼接得到BERT的原始输入序列X - BERT编码层
输入表示v经过多层Transformer的编码,借助自注意力机制充分学习篇章和问题之间的语义关联,并最终得到上下文语义表示。 - 答案输出层
在得到输入序列的上下文语义表示h后,通过全连接层,将每个分量(对应输入序列的每个位置)压缩为一个标量,并通过Softmax函数预测每个时刻成为答案起始位置的概率Ps以及终止位置的概率Pe。 - 解码方法
在得到起始位置以及终止位置的概率后,使用简单的基于Top-k 的答案抽取方法获得最终答案。
- 代码实现
import numpy as np
from datasets import load_dataset, load_metric
from transformers import BertTokenizerFast, BertForQuestionAnswering, TrainingArguments, Trainer, default_data_collator
# 加载训练数据、分词器、预训练模型以及评价方法
dataset = load_dataset('squad')
tokenizer = BertTokenizerFast.from_pretrained('bert-base-cased')
model = BertForQuestionAnswering.from_pretrained('bert-base-cased', return_dict=True)
metric = load_metric('squad')
# 准备训练数据并转换为feature
def prepare_train_features(examples):
tokenized_examples = tokenizer(
examples["question"], # 问题文本
examples["context"], # 篇章文本
truncation="only_second", # 截断只发生在第二部分,即篇章
max_length=384, # 设定最大长度为384
stride=128, # 设定篇章切片步长为128
return_overflowing_tokens=True, # 返回超出最大长度的标记,将篇章切成多片
return_offsets_mapping=True, # 返回偏置信息,用于对齐答案位置
padding="max_length", # 按最大长度进行补齐
)
# 如果篇章很长,则可能会被切成多个小篇章,需要通过以下函数建立feature到example的映射关系
sample_mapping = tokenized_examples.pop("overflow_to_sample_mapping")
# 建立token到原文的字符级映射关系,用于确定答案的开始和结束位置
offset_mapping = tokenized_examples.pop("offset_mapping")
# 获取开始和结束位置
tokenized_examples["start_positions"] = []
tokenized_examples["end_positions"] = []
for i, offsets in enumerate(offset_mapping):
# 获取输入序列的input_ids以及[CLS]标记的位置(在BERT中为第0位)
input_ids = tokenized_examples["input_ids"][i]
cls_index = input_ids.index(tokenizer.cls_token_id)
# 获取哪些部分是问题,哪些部分是篇章
sequence_ids = tokenized_examples.sequence_ids(i)
# 获取答案在文本中的字符级开始和结束位置
sample_index = sample_mapping[i]
answers = examples["answers"][sample_index]
start_char = answers["answer_start"][0]
end_char = start_char + len(answers["text"][0])
# 获取在当前切片中的开始和结束位置
token_start_index = 0
while sequence_ids[token_start_index] != 1:
token_start_index += 1
token_end_index = len(input_ids) - 1
while sequence_ids[token_end_index] != 1:
token_end_index -= 1
# 检测答案是否超出当前切片的范围
if not (offsets[token_start_index][0] <= start_char and offsets[token_end_index][1] >= end_char):
# 超出范围时,答案的开始和结束位置均设置为[CLS]标记的位置
tokenized_examples["start_positions"].append(cls_index)
tokenized_examples["end_positions"].append(cls_index)
else:
# 将token_start_index和token_end_index移至答案的两端
while token_start_index < len(offsets) and offsets[token_start_index][0] <= start_char:
token_start_index += 1
tokenized_examples["start_positions"].append(token_start_index - 1)
while offsets[token_end_index][1] >= end_char:
token_end_index -= 1
tokenized_examples["end_positions"].append(token_end_index + 1)
return tokenized_examples
# 通过函数prepare_train_features,建立分词后的训练集
tokenized_datasets = dataset.map(prepare_train_features, batched=True, remove_columns=dataset["train"].column_names)
# 定义训练参数TrainingArguments,默认使用AdamW优化器
args = TrainingArguments(
"ft-squad", # 输出路径,存放检查点和其他输出文件
evaluation_strategy="epoch", # 定义每轮结束后进行评价
learning_rate=2e-5, # 定义初始学习率
per_device_train_batch_size=16, # 定义训练批次大小
per_device_eval_batch_size=16, # 定义测试批次大小
num_train_epochs=2, # 定义训练轮数
)
# 定义Trainer,指定模型和训练参数,输入训练集、验证集、分词器以及评价函数
trainer = Trainer(
model,
args,
train_dataset=tokenized_datasets["train"],
eval_dataset=tokenized_datasets["validation"],
data_collator=default_data_collator,
tokenizer=tokenizer,
)
trainer.train()
7.4.5 序列标注
- 建模方法
命名实体识别(NER)需要针对给定输入文本的每个词输出一个标签,以此指定某个命名实体的边界信息。
- 输入层
对给定的输入文本x1x2···xn进行处理,得到BERT的原始输入X和输入层表示v - BERT编码层
得到输入文本中每个词对应的BERT隐含层表示。 - 序列标注层
针对每个词给出“BIO”标注模式下的分类预测。
- 代码实现
import numpy as np
from datasets import load_dataset, load_metric
from transformers import BertTokenizerFast, BertForTokenClassification, TrainingArguments, Trainer, DataCollatorForTokenClassification
# 加载CoNLL-2003数据集、分词器
dataset = load_dataset('conll2003')
tokenizer = BertTokenizerFast.from_pretrained('bert-base-cased')
# 将训练集转换为可训练的特征形式
def tokenize_and_align_labels(examples):
tokenized_inputs = tokenizer(examples["tokens"], truncation=True, is_split_into_words=True)
labels = []
for i, label in enumerate(examples["ner_tags"]):
word_ids = tokenized_inputs.word_ids(batch_index=i)
previous_word_idx = None
label_ids = []
for word_idx in word_ids:
# 将特殊符号的标签设置为-100,以便在计算损失函数时自动忽略
if word_idx is None:
label_ids.append(-100)
# 把标签设置到每个词的第一个token上
elif word_idx != previous_word_idx:
label_ids.append(label[word_idx])
# 对于每个词的其他token也设置为当前标签
else:
label_ids.append(label[word_idx])
previous_word_idx = word_idx
labels.append(label_ids)
tokenized_inputs["labels"] = labels
return tokenized_inputs
tokenized_datasets = dataset.map(tokenize_and_align_labels, batched=True, load_from_cache_file=False)
# 获取标签列表,并加载预训练模型
label_list = dataset["train"].features["ner_tags"].feature.names
model = BertForTokenClassification.from_pretrained('bert-base-cased', num_labels=len(label_list))
# 定义data_collator,并使用seqeval进行评价
data_collator = DataCollatorForTokenClassification(tokenizer)
metric = load_metric("seqeval")
# 定义评价指标
def compute_metrics(p):
predictions, labels = p
predictions = np.argmax(predictions, axis=2)
# 移除需要忽略的下标(之前记为-100)
true_predictions = [
[label_list[p] for (p, l) in zip(prediction, label) if l != -100]
for prediction, label in zip(predictions, labels)
]
true_labels = [
[label_list[l] for (p, l) in zip(prediction, label) if l != -100]
for prediction, label in zip(predictions, labels)
]
results = metric.compute(predictions=true_predictions, references=true_labels)
return {
"precision": results["overall_precision"],
"recall": results["overall_recall"],
"f1": results["overall_f1"],
"accuracy": results["overall_accuracy"],
}
# 定义训练参数TrainingArguments和Trainer
args = TrainingArguments(
"ft-conll2003", # 输出路径,存放检查点和其他输出文件
evaluation_strategy="epoch", # 定义每轮结束后进行评价
learning_rate=2e-5, # 定义初始学习率
per_device_train_batch_size=16, # 定义训练批次大小
per_device_eval_batch_size=16, # 定义测试批次大小
num_train_epochs=3, # 定义训练轮数
)
trainer = Trainer(
model,
args,
train_dataset=tokenized_datasets["train"],
eval_dataset=tokenized_datasets["validation"],
data_collator=data_collator,
tokenizer=tokenizer,
compute_metrics=compute_metrics
)
trainer.train()
7.5 深入理解BERT
7.5.1 概述
以BERT、GPT等为代表的预训练技术为自然语言处理领域带来了巨大的变革。模型的性能固然重要,但是对于模型行为给出可信的解释同样很关键。
7.5.2 自注意力可视化分析
BERT 模型依赖 Transformer 结构,其主要由多层自注意力网络层堆叠而成(含残差连接)。而自注意力的本质事实上是对词(或标记)与词之间关系的刻画。
自注意力的分析将有助于理解BERT模型对于关系(relational)特征的学习能力。
7.5.3 探针实验
自注意力的可视化分析有助于从直观上理解模型内部的信息流动。而为了更准确地理解模型的行为,仍然需要定量的实验分析。目前被广为采用的定量分析方法是探针实验。
探针通常是一个非参或者非常轻量的参数模型(如线性分类器),它接受待分析对象作为输入,并对特定行为预测。
,
train_dataset=tokenized_datasets[“train”],
eval_dataset=tokenized_datasets[“validation”],
data_collator=data_collator,
tokenizer=tokenizer,
compute_metrics=compute_metrics
)trainer.train()
### 7.5 深入理解BERT
#### 7.5.1 概述
以BERT、GPT等为代表的预训练技术为自然语言处理领域带来了巨大的变革。模型的性能固然重要,但是对于模型行为给出可信的解释同样很关键。
#### 7.5.2 自注意力可视化分析
BERT 模型依赖 Transformer 结构,其主要由多层自注意力网络层堆叠而成(含残差连接)。而自注意力的本质事实上是对词(或标记)与词之间关系的刻画。
自注意力的分析将有助于理解BERT模型对于关系(relational)特征的学习能力。
#### 7.5.3 探针实验
自注意力的可视化分析有助于从直观上理解模型内部的信息流动。而为了更准确地理解模型的行为,仍然需要定量的实验分析。目前被广为采用的定量分析方法是探针实验。
探针通常是一个非参或者非常轻量的参数模型(如线性分类器),它接受待分析对象作为输入,并对特定行为预测。