自然语言处理(二十七):迁移学习

自然语言处理笔记总目录


1 迁移学习有关概念

  • 预训练模型
    • 大模型,具有复杂的网络结构,参数比较多,在足够大的语料集上训练出来的模型
  • 微调(Fine-tuning)
    • 微调的对象:预训练模型
    • 微调的部分:改变模型的部分参数或者新增加的输出结构
    • 微调的动作:在较小的语料集上进行训练
    • 微调的目的:使得与训练模型可以完成特定的任务需求
  • 微调脚本
    • 开发人员自己编写
    • 使用规范化的标准的脚本(推荐)
  • 迁移学习的两种方式
    • 直接使用,通过模块化保存,尽量达到开箱即用的效果
    • 微调,预训练模型进行特征抽取 + 微调方式 + 小批量训练数据集 → \rightarrow →可以完成特定任务

2 NLP中的标准数据集

GLUE数据集由纽约大学, 华盛顿大学, Google联合推出,涵盖不同NLP任务类型,截止至2020年1月其中包括11个子任务数据集,成为衡量NLP研究发展的衡量标准

GLUE数据集的下载参考文章:https://zhuanlan.zhihu.com/p/135283598以及https://blog.csdn.net/Light2077/article/details/104561960有详细的介绍以及下载地址

运行脚本download_glue_data.py即可下载glue数据集

GLUE数据集介绍:

  • CoLA 数据集(判断句子语法的正确与否)
    • 任务类型:二分类
    • 评价指标:MCC 马修斯相关系数
  • SST-2 数据集(情感判断正向或是负向)
    • 任务类型:二分类
    • 评价指标:ACC
  • MRPC 数据集(判断句子对是否有相同的含义)
    • 任务类型:句子对的二分类
    • 评估指标:ACC、F1
  • STS-B 数据集(判断句子对的相似程度) [0, 5] 值越高相似度越大
    • 任务类型:多分类、回归
    • 评估指标:Pearson-Spearman Corr
  • QQP 数据集
    • 任务类型:二分类
    • 评估指标:ACC、F1
  • MNLI 数据集 (判断句子是否蕴含或是矛盾) \SNLI 数据集
    • 任务类型:多分类(三分类)
    • 评估指标:ACC
  • QNLI 数据集 \ RTE 数据集 \ WNLI 数据集
    • 任务类型:二分类
    • 评估指标:ACC
  • diagnostics数据集(官方未完善)

3 常见的预训练模型

NLP中流行的预训练模型有很多,比如BERT、GPT、GPT-2、Transformer-XL、XLNet、XLM、RoBERTa、DistilBERT、ALBERT、T5、XLM-RoBERTa等等

  • BERT
    • bert-base-uncased: 编码器具有12个隐层, 输出768维张量, 12个自注意力头, 共110M参数量, 在小写的英文文本上进行训练而得到.
    • bert-base-chinese: 编码器具有12个隐层, 输出768维张量, 12个自注意力头, 共110M参数量, 在简体和繁体中文文本上进行训练而得到.
  • GPT
    • openai-gpt: 编码器具有12个隐层, 输出768维张量, 12个自注意力头, 共110M参数量, 由OpenAI在英文语料上进行训练而得到.
  • GPT-2
  • Transformer-XL
    • transfo-xl-wt103: 编码器具有18个隐层, 输出1024维张量, 16个自注意力头, 共257M参数量, 在wikitext-103英文语料进行训练而得到.
  • XLNet
    • xlnet-base-cased: 编码器具有12个隐层, 输出768维张量, 12个自注意力头, 共110M参数量, 在英文语料上进行训练而得到.
    • xlnet-large-cased: 编码器具有24个隐层, 输出1024维张量, 16个自注意力头, 共240参数量, 在英文语料上进行训练而得到
  • XLM
  • RoBERTa
  • DistilBERT
  • ALBERT
  • T5
    • t5-base: 编码器具有12个隐层, 输出768维张量, 12个自注意力头, 共220M参数量, 在C4语料上进行训练而得到
  • XLM-RoBERTa

4 加载和使用预训练模型

上述这些预训练模型都是以transformer为基础,只是在模型结构如神经元连接方式,编码器隐层数,多头注意力的头数等发生改变,这些改变方式的大部分依据都是由在标准数据集上的表现而定,因此,对于我们使用者而言,不需要从理论上深度探究这些预训练模型的结构设计的优劣,只需要在自己处理的目标数据上,尽量遍历所有可用的模型对比得到最优效果即可

假设我们现在的任务是处理中文文本任务,需要加载的模型是BERT的中文模型: bert-base-chinese

示例如下:

import torch
from transformers import BertModel, BertTokenizer

# 加载预训练模型,指定模型名称,实例化分词器
model_name = 'bert-base-chinese'
tokenizer = BertTokenizer.from_pretrained(model_name)

# 输入文本
input_text = "人生该如何起头"
# 对输入文本进行字符映射
input_encode = tokenizer.encode(input_text)
# [101, 782, 4495, 6421, 1963, 862, 6629, 1928, 102],其中101、102 为SOS、EOS
# tokenizer映射后的结果, 101和102是起止符, 
# 中间的每个数字对应"人生该如何起头"的每个字.

# 将映射后的编码转换为张量
input_tensor = torch.tensor([input_encode])

# 加载不带头的模型
model = BertModel.from_pretrained(model_name)

with torch.no_grad():
    model_out = model(input_tensor)
print('不带头模型输出结果为:\t', model_out[0])
print("不带头的模型输出结果的尺寸:", model_out[0].shape)

Out:

不带头模型输出结果为:	 tensor([[[ 0.5421,  0.4526, -0.0179,  ...,  1.0447, -0.1140,  0.0068],
         [-0.1343,  0.2785,  0.1602,  ..., -0.0345, -0.1646, -0.2186],
         [ 0.9960, -0.5121, -0.6229,  ...,  1.4173,  0.5533, -0.2681],
         ...,
         [ 0.0115,  0.2150, -0.0163,  ...,  0.6445,  0.2452, -0.3749],
         [ 0.8649,  0.4337, -0.1867,  ...,  0.7397, -0.2636,  0.2144],
         [-0.6207,  0.1668,  0.1561,  ...,  1.1218, -0.0985, -0.0937]]])
不带头的模型输出结果的尺寸: torch.Size([1, 9, 768])

加载预训练模型的时候我们可以选择带头或者不带头的模型,这里的头是指模型的任务输出层,选择加载不带头的模型,相当于使用模型对输入文本进行特征表示;选择加载带头的模型时,有三种类型的’头’可供选择,modelWithLMHead(语言模型头)、modelForSequenceClassification(分类模型头)、modelForQuestionAnswering(问答模型头)

不同类型的’头’,可以使预训练模型输出指定的张量维度。如使用’分类模型头’,则输出尺寸为(1,2)的张量,用于进行分类任务判定结果

加载带头的预训练模型:

from transformers import AutoModelWithLMHead, BertForSequenceClassification, BertForQuestionAnswering

# 加载带有语言模型头的预训练模型
lm_model = AutoModelWithLMHead.from_pretrained(model_name)
# 加载带有类模型头的预训练模型
classification_model = BertForSequenceClassification.from_pretrained(model_name)
# 加载带有问答模型头的预训练模型
qa_model = BertForQuestionAnswering.from_pretrained(model_name)

获取各种模型的输出结果:

with torch.no_grad():
    lm_out = lm_model(input_tensor)
print("带语言模型头的模型输出结果:", lm_out[0])
print("带语言模型头的模型输出结果的尺寸:", lm_out[0].shape)

print("=================================================")

# 使用带有分类模型头的预训练模型获得结果
with torch.no_grad():
    classification_out = classification_model(input_tensor)
print("带分类模型头的模型输出结果:", classification_out[0])
print("带分类模型头的模型输出结果的尺寸:", classification_out[0].shape)

print("=================================================")

# 使用带有问答模型头的模型进行输出时, 需要使输入的形式为句子对
# 第一条句子是对客观事物的陈述
# 第二条句子是针对第一条句子提出的问题
# 问答模型最终将得到两个张量, 
# 每个张量中最大值对应索引的分别代表答案的在文本中的起始位置和终止位置.
input_text1 = "我家的小狗是黑色的"
input_text2 = "我家的小狗是什么颜色的呢?"
# 映射两个句子
indexed_tokens = tokenizer.encode(input_text1, input_text2)
# 用0,1来区分第一条和第二条句子
segments_ids = [0]*11 + [1]*14
# 转成张量形式
segments_tensors = torch.tensor([segments_ids])
tokens_tensor = torch.tensor([indexed_tokens])

with torch.no_grad():
    qa_out = qa_model(tokens_tensor, token_type_ids=segments_tensors)
print("带问答模型头的模型输出结果:", (qa_out[0], qa_out[1]))
print('segments_ids长度为:', len(segments_ids))
print("带问答模型头的模型输出结果的尺寸:", (qa_out[0].shape, qa_out[1].shape))

Out:

带语言模型头的模型输出结果: tensor([[[ -7.9706,  -7.9119,  -7.9317,  ...,  -7.2174,  -7.0263,  -7.3746],
         [ -8.2097,  -8.1810,  -8.0645,  ...,  -7.2349,  -6.9283,  -6.9856],
         [-13.7458, -13.5978, -12.6076,  ...,  -7.6817,  -9.5642, -11.9928],
         ...,
         [ -9.0928,  -8.6858,  -8.4648,  ...,  -8.2368,  -7.5684, -10.2419],
         [ -8.9458,  -8.5784,  -8.6325,  ...,  -7.0547,  -5.3288,  -7.8077],
         [ -8.4154,  -8.5217,  -8.5379,  ...,  -6.7102,  -5.9782,  -7.6909]]])
带语言模型头的模型输出结果的尺寸: torch.Size([1, 9, 21128])
=================================================
带分类模型头的模型输出结果: tensor([[-0.0769,  0.5593]])
带分类模型头的模型输出结果的尺寸: torch.Size([1, 2])
=================================================
带问答模型头的模型输出结果: (tensor([[-0.2749,  0.0092,  0.2135,  0.5658,  0.3289,  0.8302, -0.1749,  0.2905,
         -0.4372,  0.1618, -0.1352,  0.5258,  0.1739,  0.3888,  0.3152,  0.7269,
          0.0626,  0.4722,  0.1609,  0.0509,  0.0181,  0.4251,  0.6331,  0.2457,
         -0.1352]]), tensor([[-0.3716,  0.3101, -0.9654,  0.4308, -0.5302, -0.7496,  0.0664, -0.7638,
         -0.5381, -0.2097, -0.3282,  0.2683, -0.8065,  0.5906, -0.4676, -0.6432,
          0.4774,  0.2185, -0.4065, -0.4240, -0.4468,  0.5315, -0.0225, -0.2061,
         -0.3282]]))
# 输出为两个形状1x25的张量, 他们是两条句子合并长度的概率分布,
# 第一个张量中最大值所在的索引代表答案出现的起始索引, 
# 第二个张量中最大值所在的索引代表答案出现的终止索引.
segments_ids长度为: 25
# 输出结果的最后一维大小为两个句子长度 + 3(一个101,两个102,第一个句长9,第二个句长13)
带问答模型头的模型输出结果的尺寸: (torch.Size([1, 25]), torch.Size([1, 25]))
上一篇:【无标题】


下一篇:使用NProgress 实现进度条功能