《自然语言处理——基于预训练模型的方法》——车万翔、郭江、崔一鸣

自然语言处理——基于预训练模型的方法——第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 适配不同的下游任务

自然语言处理数学建模 自然语言处理常用模型_自然语言处理_02

  • 单句文本分类
    输入由单个文本构成,输出由对应的分类标签构成。
  • 文本蕴含
    输入由两段文本构成,输出由分类标签构成,用于判断两段文本之间的蕴含关系。
  • 相似度计算
    相似度计算任务也由两段文本构成。但与文本蕴含任务不同的是,参与相似度计算的两段文本之间不存在顺序关系。
  • 选择性阅读理解
    要将〈篇章,问题,选项〉作为输入,以正确选项编号作为标签,任务是让机器阅读一篇文章,需要从多个选项中选择出问题对应的正确选项。

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 模型对比

自然语言处理数学建模 自然语言处理常用模型_自然语言处理_03

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 探针实验

自注意力的可视化分析有助于从直观上理解模型内部的信息流动。而为了更准确地理解模型的行为,仍然需要定量的实验分析。目前被广为采用的定量分析方法是探针实验。

探针通常是一个非参或者非常轻量的参数模型(如线性分类器),它接受待分析对象作为输入,并对特定行为预测。