当前位置:   article > 正文

自然语言处理——基于预训练模型的方法——第7章 预训练语言模型_自然语言处理 基于预训练模型的方法

自然语言处理 基于预训练模型的方法

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

自然语言处理——基于预训练模型的方法——第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)
      
      • 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
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
  • 下一个句子预测(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()
    
    • 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
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
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()
    
    • 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
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
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()
    
    • 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
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
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()
    
    • 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
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87

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

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

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



  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号