迁移学习概念
预训练模型
定义: 简单来说别人训练好的模型。一般预训练模型具备复杂的网络模型结构;一般是在大量的语料下训练完成的。
预训练语言模型的类别:
现在我们接触到的预训练语言模型,基本上都是基于transformer这个模型迭代而来的
因此划分模型类别的时候,以transformer架构来划分:
Encoder-Only: 只有编码器部分的模型,代表:BERT
Decoder-Only: 只要解码器部分的模型,代表:GPT
Encoder-Decoder: 本质就transformer架构,代表:T5
微调
定义:一般是对预训练语言模型,进行垂直领域数据的微调,可以将预训练模型的参数全部微调或者部分微调或者不微调,但是一般我们在做任务的时候,会在预训练模型后加入自定义网络,自定义网络模型的参数需要训练。
迁移学习的两种方式
开箱即用: 当预训练模型的任务和我们要做的任务相似时,可以直接使用预训练模型来解决对应的任务。
微调: 进行垂直领域数据的微调,一般在预训练网络模型后,加入自定义网络,自定义网络模型的参数需要训练,但是预训练模型的参数可以全部微调或者部分微调或者不微调。
NLP中常用预训练模型
当下NLP中流行的预训练模型
- BERT
- GPT
- GPT-2
- Transformer-XL
- XLNet
- RoBERTa
- DistilBERT
- ALBERT
BERT及其变体
- bert-base-uncased: 编码器具有12个隐层, 输出768维张量, 12个自注意力头, 共110M参数量, 在小写的英文文本上进行训练而得到.
- bert-large-uncased: 编码器具有24个隐层, 输出1024维张量, 16个自注意力头, 共340M参数量, 在小写的英文文本上进行训练而得到.
- bert-base-cased: 编码器具有12个隐层, 输出768维张量, 12个自注意力头, 共110M参数量, 在不区分大小写的英文文本上进行训练而得到.
- bert-large-cased: 编码器具有24个隐层, 输出1024维张量, 16个自注意力头, 共340M参数量, 在不区分大小写的英文文本上进行训练而得到.
- bert-base-multilingual-uncased: 编码器具有12个隐层, 输出768维张量, 12个自注意力头, 共110M参数量, 在小写的102种语言文本上进行训练而得到.
- bert-large-multilingual-uncased: 编码器具有24个隐层, 输出1024维张量, 16个自注意力头, 共340M参数量, 在小写的102种语言文本上进行训练而得到.
- bert-base-chinese: 编码器具有12个隐层, 输出768维张量, 12个自注意力头, 共110M参数量, 在简体和繁体中文文本上进行训练而得到.
GPT
- openai-gpt: 解码器具有12个隐层, 输出768维张量, 12个自注意力头, 共110M参数量, 由OpenAI在英文语料上进行训练而得到.
GPT-2
- gpt2: 解码器具有12个隐层, 输出768维张量, 12个自注意力头, 共117M参数量, 在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个自注意力头, 共340参数量, 在英文语料上进行训练而得到.
RoBERTa及其变体
- roberta-base: 编码器具有12个隐层, 输出768维张量, 12个自注意力头, 共125M参数量, 在英文文本上进行训练而得到.
- roberta-large: 编码器具有24个隐层, 输出1024维张量, 16个自注意力头, 共355M参数量, 在英文文本上进行训练而得到.
DistilBERT及其变体
- distilbert-base-uncased: 基于bert-base-uncased的蒸馏(压缩)模型, 编码器具有6个隐层, 输出768维张量, 12个自注意力头, 共66M参数量.
- distilbert-base-multilingual-cased: 基于bert-base-multilingual-uncased的蒸馏(压缩)模型, 编码器具有6个隐层, 输出768维张量, 12个自注意力头, 共66M参数量.
ALBERT
- albert-base-v1: 编码器具有12个隐层, 输出768维张量, 12个自注意力头, 共125M参数量, 在英文文本上进行训练而得到.
- albert-base-v2: 编码器具有12个隐层, 输出768维张量, 12个自注意力头, 共125M参数量, 在英文文本上进行训练而得到, 相比v1使用了更多的数据量, 花费更长的训练时间.
预训练模型说明
- 所有上述预训练模型及其变体都是以transformer为基础,只是在模型结构如神经元连接方式,编码器隐层数,多头注意力的头数等发生改变,这些改变方式的大部分依据都是由在标准数据集上的表现而定,因此,对于我们使用者而言,不需要从理论上深度探究这些预训练模型的结构设计的优劣,只需要在自己处理的目标数据上,尽量遍历所有可用的模型对比得到最优效果即可.
Transformer库使用
了解Transformers库
- Huggingface总部位于纽约,是一家专注于自然语言处理、人工智能和分布式系统的创业公司。他们所提供的聊天机器人技术一直颇受欢迎,但更出名的是他们在NLP开源社区上的贡献。Huggingface一直致力于自然语言处理NLP技术的平民化(democratize),希望每个人都能用上最先进(SOTA, state-of-the-art)的NLP技术,而非困窘于训练资源的匮乏。同时Hugging Face专注于NLP技术,拥有大型的开源社区。尤其是在github上开源的自然语言处理,预训练模型库 Transformers,已被下载超过一百万次,github上超过24000个star。
- Huggingface Transformers 是基于一个开源基于 transformer 模型结构提供的预训练语言库。它支持 Pytorch,Tensorflow2.0,并且支持两个框架的相互转换。Transformers 提供了NLP领域大量state-of-art的 预训练语言模型结构的模型和调用框架。
- 框架支持了最新的各种NLP预训练语言模型,使用者可快速的进行模型调用,并且支持模型further pretraining 和 下游任务fine-tuning。举个例子Transformers 库提供了很多SOTA的预训练模型,比如BERT, GPT-2, RoBERTa, XLM, DistilBert, XLNet, CTRL。
- 社区Transformer的访问地址为:https://huggingface.co/,见下图。
- 备注 :1.点击 Model链接可查看、下载预训练模型。点击Datasets链接可查看、下载数据集。点击Docs链接可以阅读预训练模型的编程文档,十分方便。2. SOTA(state-of-the-art)是指目前对某项任务“最好的”算法或技术。
Transformers库三层应用结构
- 管道(Pipline)方式:高度集成的极简使用方式,只需要几行代码即可实现一个NLP任务。
- 自动模型(AutoMode)方式:可载入并使用BERTology系列模型。
- 具体模型(SpecificModel)方式:在使用时,需要明确指定具体的模型,并按照每个BERTology系列模型中的特定参数进行调用,该方式相对复杂,但具有较高的灵活度。
管道方式完成多种NLP任务
文本分类任务
- 文本分类是指模型可以根据文本中的内容来进行分类。例如根据内容对情绪进行分类,根据内容对商品分类等。文本分类模型一般是通过有监督训练得到的。对文本内容的具体分类,依赖于训练时所使用的样本标签。
# 导入工具包
import torch
from transformers import pipeline
import numpy as np
# 情感分类任务
def dm01_test_classification():
# 1 使用中文预训练模型chinese_sentiment
# 模型下载地址 git clone https://huggingface.co/techthiyanes/chinese_sentiment
# 2 实例化pipeline对象
my_model = pipeline(task='sentiment-analysis', model='./chinese_sentiment')
# my_model = pipeline(task='sentiment-analysis', model='./bert-base-chinese')
# 3 文本送给模型 进行文本分类
output = my_model('我爱北京天安门,天安门上太阳升。')
print('output--->', output)
# 结果输出
output---> [{'label': 'star 5', 'score': 0.6314294338226318}]
- pipeline函数可以自动从官网下载预训练模型,也可以加载本地的预训练模型
transformer库中预训练模型查找和下载:
特征提取任务
- 特征抽取任务只返回文本处理后的特征,属于预训练模型的范畴。特征抽取任务的输出结果需要和其他模型一起工作。
# 特征抽取任务
def dm02_test_feature_extraction():
# 1 下载中文预训练模型 git clone https://huggingface.co/bert-base-chinese
# 2 实例化pipeline对象 返回模型对象
my_model = pipeline(task='feature-extraction', model='./bert-base-chinese')
# 3 给模型送数据 提取语句特征
output = my_model('人生该如何起头')
print('output--->', type(output), np.array(output).shape)
# 输出结果
# output---> <class 'list'> (1, 9, 768)
# 7个字变成9个字原因: [CLS] 人 生 该 如 何 起 头 [SEP]
- 不带任务头输出:特征抽取任务属于不带任务头输出,本bert-base-chinese模型的9个字,每个字的特征维度是768
- 带头任务头输出:其他有指定任务类型的比如文本分类,完型填空属于带头任务输出,会根据具体任务类型不同输出不同的结果
完型填空任务
- 完型填空任务又被叫做“遮蔽语言建模任务”,它属于BERT模型训练过程中的子任务。下面完成一个中文场景的完型填空。
# 完型填空任务
def dm03_test_fill_mask():
# 1 下载预训练模型 全词模型git clone https://huggingface.co/hfl/chinese-bert-wwm
# 2 实例化pipeline对象 返回一个模型
my_model = pipeline(task='fill-mask', model='chinese-bert-wwm')
# 3 给模型送数据 做预测
input = '我想明天去[MASK]家吃饭。'
output = my_model(input)
# 4 输出预测结果
print('output--->', output)
# 输出结果
# output--->
# [{'score': 0.34331339597702026, 'token': 1961, 'token_str': '她', 'sequence': '我 想 明 天 去 她 家 吃 饭.'},
# {'score': 0.2533259987831116, 'token': 872, 'token_str': '你', 'sequence': '我 想 明 天 去 你 家 吃 饭.'},
# {'score': 0.1874391734600067, 'token': 800, 'token_str': '他', 'sequence': '我 想 明 天 去 他 家 吃 饭.'},
# {'score': 0.1273055076599121, 'token': 2769, 'token_str': '我', 'sequence': '我 想 明 天 去 我 家 吃 饭.'},
# {'score': 0.02162978984415531, 'token': 2644, 'token_str': '您', 'sequence': '我 想 明 天 去 您 家 吃 饭.'}]
可以在官网在线查找完型填空结果 :
阅读理解任务
- 阅读理解任务又称为“抽取式问答任务”,即输入一段文本和一个问题,让模型输出结果。
# 阅读理解任务(抽取式问答)
def dm04_test_question_answering():
# 问答语句
context = '我叫张三,我是一个程序员,我的喜好是打篮球。'
questions = ['我是谁?', '我是做什么的?', '我的爱好是什么?']
# 1 下载模型 git clone https://huggingface.co/luhua/chinese_pretrain_mrc_roberta_wwm_ext_large
# 2 实例化化pipeline 返回模型
model = pipeline('question-answering', model='chinese_pretrain_mrc_roberta_wwm_ext_large')
# 3 给模型送数据 的预测结果
print(model(context=context, question=questions))
# 输出结果
'''
[{'score': 1.2071758523357623e-12, 'start': 2, 'end': 4, 'answer': '张三'},
{'score': 2.60890374192968e-06, 'start': 9, 'end': 12, 'answer': '程序员'},
{'score': 4.1686924134864967e-08, 'start': 18, 'end': 21, 'answer': '打篮球'}]
'''
文本摘要任务
- 摘要生成任务的输入一一段文本,输出是一段概况、简单的文字。
# 文本摘要任务
def dm05_test_summarization():
# 1 下载模型 git clone https://huggingface.co/sshleifer/distilbart-cnn-12-6
# 2 实例化pipline 返回模型
my_model = pipeline(task = 'summarization', model="distilbart-cnn-12-6")
# 3 准备文本 送给模型
text = "BERT is a transformers model pretrained on a large corpus of English data " \
"in a self-supervised fashion. This means it was pretrained on the raw texts " \
"only, with no humans labelling them in any way (which is why it can use lots " \
"of publicly available data) with an automatic process to generate inputs and " \
"labels from those texts. More precisely, it was pretrained with two objectives:Masked " \
"language modeling (MLM): taking a sentence, the model randomly masks 15% of the " \
"words in the input then run the entire masked sentence through the model and has " \
"to predict the masked words. This is different from traditional recurrent neural " \
"networks (RNNs) that usually see the words one after the other, or from autoregressive " \
"models like GPT which internally mask the future tokens. It allows the model to learn " \
"a bidirectional representation of the sentence.Next sentence prediction (NSP): the models" \
" concatenates two masked sentences as inputs during pretraining. Sometimes they correspond to " \
"sentences that were next to each other in the original text, sometimes not. The model then " \
"has to predict if the two sentences were following each other or not."
output = my_model(text)
# 4 打印摘要结果
print('output--->', output)
# 输出结果
output---> [{'summary_text': ' BERT is a transformers model pretrained on a large corpus of English data in a self-supervised fashion . It was pretrained with two objectives: Masked language modeling (MLM) and next sentence prediction (NSP) This allows the model to learn a bidirectional representation of the sentence .'}]
NER任务
- 实体词识别(NER)任务是NLP中的基础任务。它用于识别文本中的人名(PER)、地名(LOC)、组织(ORG)以及其他实体(MISC)等。例如:(王 B-PER) (小 I-PER) (明 I-PER) (在 O) (办 B-LOC) (公 I-LOC) (室 I-LOC)。其中O表示一个非实体,B表示一个实体的开始,I表示一个实体块的内部。
- 实体词识别本质上是一个分类任务(又叫序列标注任务),实体词识别是句法分析的基础,而句法分析优势NLP任务的核心。`
# NER任务
def dm06_test_ner():
# 1 下载模型 git clone https://huggingface.co/uer/roberta-base-finetuned-cluener2020-chinese
# 2 实例化pipeline 返回模型
model = pipeline('ner', model='roberta-base-finetuned-cluener2020-chinese')
# 3 给模型送数据 打印NER结果
print(model('我爱北京天安门,天安门上太阳升。'))
'''
[{'entity': 'B-address', 'score': 0.8838121, 'index': 3, 'word': '北', 'start': 2, 'end': 3},
{'entity': 'I-address', 'score': 0.83543754, 'index': 4, 'word': '京', 'start': 3, 'end': 4},
{'entity': 'I-address', 'score': 0.4240591, 'index': 5, 'word': '天', 'start': 4, 'end': 5},
{'entity': 'I-address', 'score': 0.7524443, 'index': 6, 'word': '安', 'start': 5, 'end': 6},
{'entity': 'I-address', 'score': 0.6949866, 'index': 7, 'word': '门', 'start': 6, 'end': 7},
{'entity': 'B-address', 'score': 0.65552264, 'index': 9, 'word': '天', 'start': 8, 'end': 9},
{'entity': 'I-address', 'score': 0.5376768, 'index': 10, 'word': '安', 'start': 9, 'end': 10},
{'entity': 'I-address', 'score': 0.510813, 'index': 11, 'word': '门', 'start': 10, 'end': 11}]
'''
自动模型方式完成多种NLP任务
文本分类任务
- 文本分类是指模型可以根据文本中的内容来进行分类。例如根据内容对情绪进行分类,根据内容对商品分类等。文本分类模型一般是通过有监督训练得到的。对文本内容的具体分类,依赖于训练时所使用的样本标签。
# 导入工具包
import torch
from transformers import AutoConfig, AutoModel, AutoTokenizer
from transformers import AutoModelForSequenceClassification, AutoModelForMaskedLM, AutoModelForQuestionAnswering
# AutoModelForSeq2SeqLM:文本摘要
# AutoModelForTokenClassification:ner
from transformers import AutoModelForSeq2SeqLM, AutoModelForTokenClassification
# 情感分类任务
def dm01_test_classification():
# 1 加载tokenizer
my_tokenizer = AutoTokenizer.from_pretrained('./chinese_sentiment')
# 2 加载模型
my_model = AutoModelForSequenceClassification.from_pretrained('./chinese_sentiment')
# 3 文本转张量
message = '人生该如何起头'
# 3-1 return_tensors='pt' 返回是二维tensor
msg_tensor1 = my_tokenizer.encode(text=message, return_tensors='pt', padding=True, truncation=True, max_length=20)
print('msg_tensor1--->', msg_tensor1)
# 3-2 不用return_tensors='pt'是一维列表
msg_list2 = my_tokenizer.encode(text=message, padding=True, truncation=True, max_length=20)
print('msg_list2--->', msg_list2)
msg_tensor2 = torch.tensor([msg_list2])
print('msg_tensor2--->', msg_tensor2)
# 4 数据送给模型
# 4-1
my_model.eval()
output1 = my_model(msg_tensor2)
print('情感分类模型头输出outpout1--->', output1)
# 4-2
output2 = my_model(msg_tensor2, return_dict=False)
print('情感分类模型头输出outpout2--->', output2)
- AutoTokenizer、AutoModelForSequenceClassification函数可以自动从官网下载预训练模型,也可以加载本地的预训练模型
- AutoModelForSequenceClassification类管理着分类任务,会根据参数的输入选用不同的模型。
- AutoTokenizer的encode()函数使用return_tensors=’pt‘参数和不使用pt参数对文本编码的结果不同
- AutoTokenizer的encode()函数使用padding='max_length'可以按照最大程度进行补齐,俗称打padding
- 调用模型的forward函数输入return_dict=False参数,返回结果也不同
程序运行结果
msg_tensor1---> tensor([[ 101, 782, 4495, 6421, 1963, 862, 6629, 1928, 102]])
msg_list2---> [101, 782, 4495, 6421, 1963, 862, 6629, 1928, 102]
msg_tensor2---> tensor([[ 101, 782, 4495, 6421, 1963, 862, 6629, 1928, 102]])
情感分类模型头输出outpout1---> SequenceClassifierOutput(loss=None, logits=tensor([[-2.7387, -1.7528, 0.2273, 2.0507, 1.4128]],
grad_fn=<AddmmBackward>), hidden_states=None, attentions=None)
情感分类模型头输出outpout2---> (tensor([[-2.7387, -1.7528, 0.2273, 2.0507, 1.4128]],
grad_fn=<AddmmBackward>),)
#注1:101代表[CLS] 102代表[SEP]
特征提取任务
- 特征抽取任务只返回文本处理后的特征,属于预训练模型的范畴。特征抽取任务的输出结果需要和其他模型一起工作。
# 特征提取任务-不带任务输出头的任务
def dm02_test_feature_extraction():
# 1 加载tokenizer
my_tokenizer = AutoTokenizer.from_pretrained(pretrained_model_name_or_path='./bert-base-chinese')
# 2 加载模型
my_model = AutoModel.from_pretrained(pretrained_model_name_or_path = './bert-base-chinese')
# 3 文本转张量
message = ['你是谁', '人生该如何起头']
msgs_tensor = my_tokenizer.encode_plus(text=message, return_tensors='pt', truncation=True, pad_to_max_length=True, max_length=30)
print('msgs_tensor--->', msgs_tensor)
# 4 给模型送数据提取特征
my_model.eval()
output = my_model(**msgs_tensor)
print('不带模型头输出output--->', output)
print('outputs.last_hidden_state.shape--->', output.last_hidden_state.shape) # torch.Size([1, 30, 768])
print('outputs.pooler_output.shape--->', output.pooler_output.shape) # torch.Size([1, 768])
- 不带任务头输出:特征抽取任务属于不带任务头输出,本bert-base-chinese模型的9个字,每个字的特征维度是768
- 带头任务头输出:其他有指定任务类型的比如文本分类,完型填空属于带头任务输出,会根据具体任务类型不同输出不同的结果
程序运行结果
msgs_tensor--->
# 1 input_ids对两个句子text2id以后的结果,
# 101表示段落开头,第一个102代表第一个句子结束,第二个102点第二个句子结束
# 后面的0表示 按照编码要求pad_to_max_length=True和max_length=30补充pad零
{'input_ids': tensor([[ 101, 872, 3221, 6443, 102, 782, 4495, 6421, 1963, 862, 6629, 1928,
102, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0]]),
# 2 token_type_ids表示段落标志0代表第一个句子,1代表第二个句子
'token_type_ids': tensor([[0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0]]),
# 3 attention_mask表示注意力机制的掩码数据,1表示有真实数据,0表示是pad数据需要掩码
'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0]])}
# 1 last_hidden_state表示最后一个隐藏层的数据 [1,30,768]
# 2 pooler_output表示池化,也就是对最后一个隐藏层再进行线性变换以后平均池化的结果。分类时候使用。
不带模型头输出output---> BaseModelOutputWithPoolingAndCrossAttentions(
last_hidden_state=tensor([[[ 0.7001, 0.4651, 0.2427, ..., 0.5753, -0.4330, 0.1878],
[ 0.4017, 0.1123, 0.4482, ..., -0.2614, -0.2649, -0.1497],
[ 1.2000, -0.4859, 1.1970, ..., 0.7543, -0.2405, -0.2627],
...,
[ 0.2074, 0.4022, -0.0448, ..., -0.0849, -0.0766, -0.2134],
[ 0.0879, 0.2482, -0.2356, ..., 0.2967, -0.2357, -0.5138],
[ 0.4944, 0.1340, -0.2387, ..., 0.2375, -0.1011, -0.3314]]],
grad_fn=<NativeLayerNormBackward>),
pooler_output=tensor([[ 0.9996, 1.0000, 0.9995, 0.9412, 0.8629, 0.9592, -0.8144, -0.9654,
0.9892, -0.9997, 1.0000, 0.9998, -0.1187, -0.9373, 0.9999, -1.0000,
...,
-0.9967, 1.0000, 0.8626, -0.9993, -0.9704, -0.9993, -0.9971, 0.8522]],
grad_fn=<TanhBackward>),
hidden_states=None, past_key_values=None, attentions=None, cross_attentions=None)
outputs.last_hidden_state.shape---> torch.Size([1, 30, 768])
outputs.pooler_output.shape---> torch.Size([1, 768])
完型填空任务
- 完型填空任务又被叫做“遮蔽语言建模任务”,它属于BERT模型训练过程中的子任务。下面完成一个中文场景的完型填空。
# 完型填空任务
def dm03_test_fill_mask():
# 1 加载tokenizer
modename = "chinese-bert-wwm"
# modename = "bert-base-chinese"
my_tokenizer = AutoTokenizer.from_pretrained(modename)
# 2 加载模型
my_model = AutoModelForMaskedLM.from_pretrained(modename)
# 3 文本转张量
input = my_tokenizer.encode_plus('我想明天去[MASK]家吃饭.', return_tensors='pt')
print('input--->', input)
# 4 给模型送数据提取特征
my_model.eval()
output = my_model(**input)
print('output--->', output)
print('output.logits--->', output.logits.shape) # [1,12,21128]
# 5 取概率最高
mask_pred_idx = torch.argmax(output.logits[0][6]).item()
print('打印概率最高的字:', my_tokenizer.convert_ids_to_tokens([mask_pred_idx]))
程序运行结果
# 1 input_ids 对句子text2id以后的结果
# 2 token_type_ids 句子分段信息
# 3 attention_mask 句子掩码信息
input---> {'input_ids': tensor([[ 101, 2769, 2682, 3209, 1921, 1343, 103, 2157, 1391, 7649, 119, 102]]), 'token_type_ids': tensor([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]), 'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]])}
# 1 logits表示MASK预测的结果,也是一种分类概率
# 2 output.logits的分类形状 [1, 12, 21128]
# 3 通过 my_tokenizer.convert_ids_to_tokens()函数完成id2text的操作
output---> MaskedLMOutput(loss=None, logits=tensor([[[ -9.9017, -9.6006, -9.8032, ..., -7.9744, -7.7402, -8.2912],
[-14.3878, -15.0353, -14.7893, ..., -10.0437, -10.5279, -9.7544],
[-14.2215, -14.1145, -14.5770, ..., -6.3246, -4.1784, -4.6072],
...,
[-14.6938, -16.8133, -15.1296, ..., -9.2327, -8.1931, -15.2430],
[-10.8649, -11.4887, -11.5731, ..., -6.5378, -0.8715, -5.3870],
[-11.8495, -11.8358, -12.0314, ..., -8.4242, -6.2741, -8.2787]]],
grad_fn=<AddBackward0>), hidden_states=None, attentions=None)
output.logits---> torch.Size([1, 12, 21128])
打印概率最高的字: ['她']
阅读理解任务
- 阅读理解任务又称为“抽取式问答任务”,即输入一段文本和一个问题,让模型输出结果。
# 阅读理解任务(抽取式问答)
def dm04_test_question_answering():
# 1 加载tokenizer
my_tokenizer = AutoTokenizer.from_pretrained('./chinese_pretrain_mrc_roberta_wwm_ext_large')
# 2 加载模型
my_model = AutoModelForQuestionAnswering.from_pretrained('./chinese_pretrain_mrc_roberta_wwm_ext_large')
# 3 文本转张量
# 文字中的标点符号如果是中文的话,会影响到预测结果 也可以去掉标点符号
context = '我叫张三 我是一个程序员 我的喜好是打篮球'
questions = ['我是谁?', '我是做什么的?', '我的爱好是什么?']
# 4 给模型送数据 模型做抽取式问答
my_model.eval()
for question in questions:
input = my_tokenizer.encode_plus(question, context, return_tensors='pt')
print('input--->', input)
output = my_model(**input)
print('output--->', output)
start, end = torch.argmax(output.start_logits), torch.argmax(output.end_logits) +1
answer = my_tokenizer.convert_ids_to_tokens(input['input_ids'][0][start:end] )
print('question:', question, 'answer:', answer)
程序运行结果
# input_ids表示text2id后结果 # token_type_ids表示句子分段信息 # attention_mask表示句子attention掩码信息
input---> {'input_ids': tensor([[ 101, 2769, 3221, 6443, 8043, 102, 2769, 1373, 2476, 676, 2769, 3221,
671, 702, 4923, 2415, 1447, 2769, 4638, 1599, 1962, 3221, 2802, 5074,
4413, 102]]), 'token_type_ids': tensor([[0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1]]), 'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1]])}
# start_logits end_logits分布表示从原文中抽取答案的位置概率
# 比如:start_logits的最大值代表句子答案最可能开始的位置
# 比如:end_logits的最大值代表句子答案可能结束的位置
output---> QuestionAnsweringModelOutput(loss=None, start_logits=tensor([[ -1.9978, -11.4788, -12.6324, -11.8324, -12.4148, -11.9371, -2.7246,
-6.6402, 3.9131, -2.9533, -7.0866, -9.5696, -4.2775, -8.9042,
0.5753, -6.9468, -7.0469, -8.5334, -11.3796, -9.3905, -11.0242,
-11.1047, -5.7124, -2.7293, -7.5896, -12.6013]],
grad_fn=<CopyBackwards>), end_logits=tensor([[ -1.3483, -12.0141, -11.6312, -11.6629, -11.9607, -12.0039, -4.6118,
-7.4034, -2.3499, 4.7159, -7.2880, -9.5317, -6.6742, -6.0915,
-7.0023, -4.9691, 1.4515, -7.8329, -9.0895, -10.3742, -8.7482,
-9.8567, -7.2930, -5.8163, -1.7323, -12.2525]],
grad_fn=<CopyBackwards>), hidden_states=None, attentions=None)
question: 我是谁? answer: ['张', '三']
question: 我是做什么的? answer: ['程', '序', '员']
question: 我的爱好是什么? answer: ['打', '篮', '球']
文本摘要任务
- 摘要生成任务的输入一一段文本,输出是一段概况、简单的文字。
# 文本摘要任务
def dm05_test_summarization():
text = "BERT is a transformers model pretrained on a large corpus of English data " \
"in a self-supervised fashion. This means it was pretrained on the raw texts " \
"only, with no humans labelling them in any way (which is why it can use lots " \
"of publicly available data) with an automatic process to generate inputs and " \
"labels from those texts. More precisely, it was pretrained with two objectives:Masked " \
"language modeling (MLM): taking a sentence, the model randomly masks 15% of the " \
"words in the input then run the entire masked sentence through the model and has " \
"to predict the masked words. This is different from traditional recurrent neural " \
"networks (RNNs) that usually see the words one after the other, or from autoregressive " \
"models like GPT which internally mask the future tokens. It allows the model to learn " \
"a bidirectional representation of the sentence.Next sentence prediction (NSP): the models" \
" concatenates two masked sentences as inputs during pretraining. Sometimes they correspond to " \
"sentences that were next to each other in the original text, sometimes not. The model then " \
"has to predict if the two sentences were following each other or not."
# 1 加载tokenizer
my_tokenizer = AutoTokenizer.from_pretrained(pretrained_model_name_or_path="distilbart-cnn-12-6")
# 2 加载模型
my_model = AutoModelForSeq2SeqLM.from_pretrained(pretrained_model_name_or_path='distilbart-cnn-12-6')
# 3 文本转张量
input = my_tokenizer([text], return_tensors='pt')
# print('input--->', input)
# 4 送给模型做摘要
my_model.eval()
output = my_model.generate(input.input_ids)
print('output--->', output)
# 5 处理摘要结果
# 5-1 decode 的 skip_special_tokens 参数可以去除 token 前面的特殊字符
print([my_tokenizer.decode(g, skip_special_tokens=True, clean_up_tokenization_spaces=False) for g in output])
# 5-2 convert_ids_to_tokens 函数只能将 ids 还原为 token
# print(my_tokenizer.convert_ids_to_tokens(output[0]))
程序运行结果
output---> tensor([[ 2, 0, 11126, 565, 16, 10, 7891, 268, 1421, 11857,
26492, 15, 10, 739, 42168, 9, 2370, 414, 11, 10,
1403, 12, 16101, 25376, 2734, 479, 85, 21, 11857, 26492,
19, 80, 10366, 35, 31755, 196, 2777, 19039, 36, 10537,
448, 43, 8, 220, 3645, 16782, 36, 487, 4186, 43,
20, 3092, 10146, 26511, 1626, 80, 24397, 11305, 25, 16584,
148, 11857, 32155, 479, 7411, 51, 20719, 7, 11305, 14,
58, 220, 7, 349, 97, 11, 5, 1461, 2788, 6,
2128, 45, 479, 2]])
['BERT is a transformers model pretrained on a large corpus of English data in a self-supervised fashion . It was pretrained with two objectives: Masked language modeling (MLM) and next sentence prediction (NSP) The models concatenates two masked sentences as inputs during pretraining . Sometimes they correspond to sentences that were next to each other in the original text, sometimes not .']
NER任务
- 实体词识别(NER)任务是NLP中的基础任务。它用于识别文本中的人名(PER)、地名(LOC)、组织(ORG)以及其他实体(MISC)等。例如:(王 B-PER) (小 I-PER) (明 I-PER) (在 O) (办 B-LOC) (公 I-LOC) (室 I-LOC)。其中O表示一个非实体,B表示一个实体的开始,I表示一个实体块的内部。
- 实体词识别本质上是一个分类任务(又叫序列标注任务),实体词识别是句法分析的基础,而句法分析优势NLP任务的核心。
# NER任务
def dm06_test_ner():
# 1 加载tokenizer 加载模型 加载配置文件
# https://huggingface.co/uer/roberta-base-finetuned-cluener2020-chinese
my_tokenizer = AutoTokenizer.from_pretrained('roberta-base-finetuned-cluener2020-chinese')
my_model = AutoModelForTokenClassification.from_pretrained('roberta-base-finetuned-cluener2020-chinese')
config = AutoConfig.from_pretrained('roberta-base-finetuned-cluener2020-chinese')
# 2 数据张量化
inputs = my_tokenizer.encode_plus('我爱北京天安门,天安门上太阳升', return_tensors='pt')
print('inputs--->', inputs.input_ids.shape, inputs.input_ids) # torch.Size([1, 17])
# 3 送入模型 预测ner概率 每个字预测的标签概率
my_model.eval()
logits = my_model(inputs.input_ids).logits
print('logits--->', logits.shape) # torch.Size([1, 17, 32])
# 4 对预测数据 进行显示
input_tokens = my_tokenizer.convert_ids_to_tokens(inputs.input_ids[0])
print('input_tokens--->', input_tokens)
outputs = []
for token, value in zip(input_tokens, logits[0]):
if token in my_tokenizer.all_special_tokens:
continue
# 获得每个字预测概率最大的标签索引
idx = torch.argmax(value).item()
# 打印索引对应标签
outputs.append((token, config.id2label[idx]))
print(outputs)
程序运行结果
inputs---> torch.Size([1, 17]) tensor([[ 101, 2769, 4263, 1266, 776, 1921, 2128, 7305, 8024, 1921, 2128, 7305,
677, 1922, 7345, 1285, 102]])
logits---> torch.Size([1, 17, 32])
input_tokens---> ['[CLS]', '我', '爱', '北', '京', '天', '安', '门', ',', '天', '安', '门', '上', '太', '阳', '升', '[SEP]']
[('我', 'O'), ('爱', 'O'), ('北', 'B-address'), ('京', 'I-address'), ('天', 'I-address'), ('安', 'I-address'), ('门', 'I-address'), (',', 'O'), ('天', 'B-address'), ('安', 'I-address'), ('门', 'I-address'), ('上', 'O'), ('太', 'O'), ('阳', 'O'), ('升', 'O')]
具体模型方式完成NLP任务
完型填空任务
- 完型填空任务又被叫做“遮蔽语言建模任务”,它属于BERT模型训练过程中的子任务。下面完成一个中文场景的完型填空。
# 具体模型完型填空任务
def dm01_test_bert_fill_mask():
# 1 加载tokenizer
modename = "bert-base-chinese"
my_tokenizer = BertTokenizer.from_pretrained(modename)
# 2 加载模型
my_model = BertForMaskedLM.from_pretrained(modename)
# 3 文本转张量
input = my_tokenizer.encode_plus('我想明天去[MASK]家吃饭', return_tensors='pt')
print('input--->', input)
# 4 给模型送数据提取特征
output = my_model(**input)
print('output--->', output)
print('output.logits--->', output.logits.shape) # [1,11,21128]
# 5 取概率最高
mask_pred_idx = torch.argmax(output.logits[0][6]).item()
print('打印概率最高的字:', my_tokenizer.convert_ids_to_tokens([mask_pred_idx]))
程序运行结果
# input_ids表示text2id后结果 # token_type_ids表示句子分段信息 # attention_mask表示句子attention掩码信息
input---> {'input_ids': tensor([[ 101, 2769, 2682, 3209, 1921, 1343, 103, 2157, 1391, 7649, 102]]), 'token_type_ids': tensor([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]), 'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]])}
output---> MaskedLMOutput(loss=None, logits=tensor([[[ -8.1771, -8.1008, -8.1191, ..., -6.8355, -6.9482, -6.9834],
[ -8.2775, -8.1251, -8.1655, ..., -6.8471, -7.4265, -6.1365],
[-14.1093, -13.1037, -14.6324, ..., -6.0959, -3.7550, -5.7456],
...,
[-16.2103, -16.7243, -15.9876, ..., -5.9727, -8.2757, -7.3852],
[-13.5615, -13.7670, -13.4497, ..., -7.8282, -4.9095, -9.1699],
[-10.3200, -10.1068, -10.4439, ..., -6.6468, -7.0597, -7.5027]]],
grad_fn=<AddBackward0>), hidden_states=None, attentions=None)
output.logits---> torch.Size([1, 11, 21128])
迁移学习实践
通过微调方式进行迁移学习的两种类型
- 类型一: 直接加载预训练模型进行输入文本的特征表示, 后接自定义网络进行微调输出结果
- 类型二: 使用指定任务类型的微调脚本微调预训练模型, 后接带有输出头的预定义网络输出结果
- 说明: 所有类型的实战演示, 都将针对中文文本进行
迁移学习-中文分类
任务介绍
- 直接加载预训练模型进行输入文本的特征表示, 后接自定义网络进行微调输出结果
数据介绍
- 数据文件有三个train.csv,test.csv,validation.csv,数据样式都是一样的。
- 数据下载:https://pan.baidu.com/s/1rnFBdLwFCyGbhAEc2l__hw?pwd=uqe1 提取码: uqe1
label,text
1,选择珠江花园的原因就是方便,有电动扶梯直接到达海边,周围餐馆、食廊、商场、超市、摊位一应俱全。酒店装修一般,但还算整洁。 泳池在大堂的屋顶,因此很小,不过女儿倒是喜欢。 包的早餐是西式的,还算丰富。 服务吗,一般
1,15.4寸笔记本的键盘确实爽,基本跟台式机差不多了,蛮喜欢数字小键盘,输数字特方便,样子也很美观,做工也相当不错
0,房间太小。其他的都一般。。。。。。。。。
0,"1.接电源没有几分钟,电源适配器热的不行. 2.摄像头用不起来. 3.机盖的钢琴漆,手不能摸,一摸一个印. 4.硬盘分区不好办."
1,"今天才知道这书还有第6卷,真有点郁闷:为什么同一套书有两种版本呢?当当网是不是该跟出版社商量商量,单独出个第6卷,让我们的孩子不会有所遗憾。"
- 通过huggingface的datasets工具,加载信息文件信息如下:
加载训练集
dataset_train---> Dataset({
features: ['label', 'text'],
num_rows: 9600
})
{'label': [1, 1, 0], 'text': ['选择珠江花园的原因就是方便,有电动扶梯直接到达海边,周围餐馆、食廊、商场、超市、摊位一应俱全。酒店装修一般,但还算整洁。 泳池在大堂的屋顶,因此很小,不过女儿倒是喜欢。 包的早餐是西式的,还算丰富。 服务吗,一般', '15.4寸笔记本的键盘确实爽,基本跟台式机差不多了,蛮喜欢数字小键盘,输数字特方便,样子也很美观,做工也相当不错', '房间太小。其他的都一般。。。。。。。。。']}
加载测试集
my_dataset_test---> Dataset({
features: ['label', 'text'],
num_rows: 1200
})
{'label': [1, 0, 0], 'text': ['这个宾馆比较陈旧了,特价的房间也很一般。总体来说一般', '怀着十分激动的心情放映,可是看着看着发现,在放映完毕后,出现一集米老鼠的动画片!开始还怀疑是不是赠送的个别现象,可是后来发现每张DVD后面都有!真不知道生产商怎么想的,我想看的是猫和老鼠,不是米老鼠!如果厂家是想赠送的话,那就全套米老鼠和唐老鸭都赠送,只在每张DVD后面添加一集算什么??简直是画蛇添足!!', '还稍微重了点,可能是硬盘大的原故,还要再轻半斤就好了。其他要进一步验证。贴的几种膜气泡较多,用不了多久就要更换了,屏幕膜稍好点,但比没有要强多了。建议配赠几张膜让用用户自己贴。']}
加载验证集
my_dataset_validation---> Dataset({
features: ['label', 'text'],
num_rows: 1200
})
{'label': [1, 1, 0], 'text': ['這間酒店環境和服務態度亦算不錯,但房間空間太小~~不宣容納太大件行李~~且房間格調還可以~~ 中餐廳的廣東點心不太好吃~~要改善之~~~~但算價錢平宜~~可接受~~ 西餐廳格調都很好~~但吃的味道一般且令人等得太耐了~~要改善之~~', '<荐书> 推荐所有喜欢<红楼>的红迷们一定要收藏这本书,要知道当年我听说这本书的时候花很长时间去图书馆找和借都没能如愿,所以这次一看到当当有,马上买了,红迷们也要记得备货哦!', '商品的不足暂时还没发现,京东的订单处理速度实在.......周二就打包完成,周五才发货...']}
- 通过huggingface的datasets工具,加载代码如下:
def dm_file2dataset():
# 实例化数据源对象my_dataset_train
print('\n加载训练集')
my_dataset_train = load_dataset('csv', data_files='./mydata1/train.csv', split='train')
print('dataset_train--->', my_dataset_train)
print(my_dataset_train[0:3])
# 实例化数据源对象my_dataset_test
print('\n加载测试集')
my_dataset_test = load_dataset('csv', data_files='./mydata1/test.csv', split='train')
print('my_dataset_test--->', my_dataset_test)
print(my_dataset_test[0:3])
print('\n加载验证集')
# 实例化数据源对象my_dataset_train
my_dataset_validation = load_dataset('csv', data_files='./mydata1/validation.csv', split="train")
print('my_dataset_validation--->', my_dataset_validation)
print(my_dataset_validation[0:3])
- 导入工具包和辅助工具实例化对象
# 导入工具包
import torch
from datasets import load_dataset
from transformers import BertTokenizer, BertModel
from transformers import AdamW
import time
# 加载字典和分词工具 实例化分词工具
my_tokenizer = BertTokenizer.from_pretrained('bert-base-chinese')
# 加载预训练模型 实例化预训练模型
my_model_pretrained = BertModel.from_pretrained('bert-base-chinese')
数据预处理
对持久化文件中数据进行处理,以满足模型训练要求。
- 数据预处理和相关测试函数
# 数据集处理自定义函数
def collate_fn1(data):
# data传过来的数据是list eg: 批次数8,8个字典
# [{'text':'xxxx','label':0} , {'text':'xxxx','label':1}, ...]
sents = [i['text'] for i in data]
labels = [i['label'] for i in data]
# 编码text2id 对多句话进行编码用batch_encode_plus函数
data = my_tokenizer.batch_encode_plus(batch_text_or_text_pairs=sents,
truncation=True,
padding='max_length',
max_length=500,
return_tensors='pt',
return_length=True)
# input_ids:编码之后的数字
# attention_mask:是补零的位置是0,其他位置是1
input_ids = data['input_ids']
attention_mask = data['attention_mask']
token_type_ids = data['token_type_ids']
labels = torch.LongTensor(labels)
# 返回text2id信息 掩码信息 句子分段信息 标签y
return input_ids, attention_mask, token_type_ids, labels
# 测试数据
def dm01_test_dataset():
# 实例化数据源 通过训练文件
dataset_train = load_dataset('csv', data_files='./mydata1/train.csv', split="train")
print('dataset_train--->', dataset_train)
# 实例化数据迭代器 mydataloader
mydataloader = torch.utils.data.DataLoader(dataset_train,
batch_size=8,
collate_fn=collate_fn1,
shuffle=True,
drop_last=True)
print('mydataloader--->', len(mydataloader))
# 调整数据迭代器对象数据返回格式
for i, (input_ids, attention_mask, token_type_ids, labels) in enumerate(mydataloader):
print(len(mydataloader))
print(input_ids.shape, attention_mask.shape, token_type_ids.shape, labels)
# 打印句子text2id后的信息
print('input_ids', input_ids)
# 打印句子attention掩码信息
print('attention_mask', attention_mask)
# 打印句子分段信息
print('token_type_ids', token_type_ids)
# 打印目标y信息
print('labels', labels)
break
- 程序运行效果
# 显示训练集字段和样本数目
dataset_train---> Dataset({
features: ['label', 'text'],
num_rows: 9600
})
mydataloader---> 1200
# 显示处理后送给模型的数据信息
torch.Size([8, 500]) torch.Size([8, 500]) torch.Size([8, 500]) tensor([1, 1, 0, 0, 1, 0, 0, 0])
# 句子text2id后的信息
input_ids tensor([[ 101, 6848, 2885, ..., 0, 0, 0],
[ 101, 8115, 119, ..., 0, 0, 0],
[ 101, 2791, 7313, ..., 0, 0, 0],
...,
[ 101, 3322, 1690, ..., 0, 0, 0],
[ 101, 1457, 1457, ..., 0, 0, 0],
[ 101, 6821, 3315, ..., 0, 0, 0]])
# 句子注意力机制掩码信息
attention_mask tensor([[1, 1, 1, ..., 0, 0, 0],
[1, 1, 1, ..., 0, 0, 0],
[1, 1, 1, ..., 0, 0, 0],
...,
[1, 1, 1, ..., 0, 0, 0],
[1, 1, 1, ..., 0, 0, 0],
[1, 1, 1, ..., 0, 0, 0]])
# 句子分段信息
token_type_ids tensor([[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0],
...,
[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0]])
# 句子的标签信息
labels tensor([1, 1, 0, 0, 1, 0, 0, 0])
自定义下游任务网络模型
自定义单层的全连接网络作为微调网络。根据实际经验, 自定义的微调网络参数总数应大于0.5倍的训练数据量, 小于10倍的训练数据量, 这样有助于模型在合理的时间范围内收敛
- 自定义下游任务网络模型
# 定义下游任务模型
class MyModel(torch.nn.Module):
def __init__(self):
super().__init__()
# 定义全连接层
self.fc = torch.nn.Linear(768, 2)
def forward(self, input_ids, attention_mask, token_type_ids):
# 预训练模型不训练 只进行特征抽取 [8,500] ---> [8,768]
with torch.no_grad():
out = my_model_pretrained(input_ids=input_ids,
attention_mask=attention_mask,
token_type_ids=token_type_ids)
# 下游任务模型训练 数据经过全连接层 [8,768] --> [8,2]
out = self.fc(out.last_hidden_state[:, 0])
# 数据进行softmax归一化 分类概率值
out = out.softmax(dim=1)
return out
- 模型测试
# 下游任务模型输入和输出测试
def dm02_test_mymodel():
# 实例化数据源 通过训练文件
dataset_train = load_dataset('csv', data_files='./mydata1/train.csv', split="train")
# print('dataset_train--->', dataset_train)
# 实例化数据迭代器 mydataloader
mydataloader = torch.utils.data.DataLoader(dataset_train,
batch_size=8,
collate_fn=collate_fn1,
shuffle=False,
drop_last=True)
# print('mydataloader--->', len(mydataloader))
# 实例化下游任务模型
mymodel = MyModel()
print('mymodel--->', mymodel)
# 调整数据迭代器对象数据返回格式
for i, (input_ids, attention_mask, token_type_ids, labels) in enumerate(mydataloader):
print(len(mydataloader))
# print(input_ids.shape, attention_mask.shape, token_type_ids.shape, labels)
# 数据送给模型
y_out = mymodel(input_ids, attention_mask, token_type_ids)
print('y_out---->', y_out.shape, y_out)
break
- 输出效果
# 模型信息打印
mymodel---> MyModel(
(fc): Linear(in_features=768, out_features=2, bias=True)
)
# 模型运算后分类结果展示
y_out----> torch.Size([8, 2]) tensor([[0.4062, 0.5938],
[0.2788, 0.7212],
[0.3671, 0.6329],
[0.2496, 0.7504],
[0.2995, 0.7005],
[0.2566, 0.7434],
[0.2537, 0.7463],
[0.3832, 0.6168]], grad_fn=<SoftmaxBackward>)
模型训练
# 模型训练
def dm03_train_model():
# 实例化下游任务模型my_model
my_model = MyModel()
# 实例化优化器my_optimizer
my_optimizer = AdamW(my_model.parameters(), lr=5e-4)
# 实例化损失函数my_criterion
my_criterion = torch.nn.CrossEntropyLoss()
# 实例化数据源对象my_dataset_train
my_dataset_train = load_dataset('csv', data_files='./mydata1/train.csv', split="train")
print('dataset_train--->', my_dataset_train)
# 不训练预训练模型 只让预训练模型计算数据特征 不需要计算梯度
for param in my_model_pretrained.parameters():
param.requires_grad_(False)
# 设置训练参数
epochs = 3
# 设置模型为训练模型
my_model.train()
# 外层for循环 控制轮数
for eporch_idx in range(epochs):
# 每次轮次开始计算时间
starttime = (int)(time.time())
# 实例化数据迭代器对象my_dataloader
my_dataloader = torch.utils.data.DataLoader(my_dataset_train,
batch_size=8,
collate_fn=collate_fn1,
shuffle=True,
drop_last=True)
# 内层for循环 控制迭代次数
for i, (input_ids, attention_mask, token_type_ids, labels) in enumerate(my_dataloader, start=1):
# 给模型喂数据 [8,500] --> [8,2]
my_out = my_model(input_ids=input_ids,
attention_mask=attention_mask,
token_type_ids=token_type_ids)
# 计算损失
my_loss = my_criterion(my_out, labels)
# 梯度清零
my_optimizer.zero_grad()
# 反向传播
my_loss.backward()
# 梯度更新
my_optimizer.step()
# 每5次迭代 算一下准确率
if i % 5 == 0:
out = my_out.argmax(dim=1) # [8,2] --> (8,)
accuracy = (out == labels).sum().item() / len(labels)
print('轮次:%d 迭代数:%d 损失:%.6f 准确率%.3f 时间%d' \
%(eporch_idx, i, my_loss.item(), accuracy, (int)(time.time())-starttime))
# 每个轮次保存模型
torch.save(my_model.state_dict(), './my_model_%d.bin' % (eporch_idx + 1))
- 模型训练效果输出
轮次:0 迭代数:5 损失:0.735494 准确率0.250 时间40
轮次:0 迭代数:10 损失:0.614211 准确率0.875 时间81
轮次:0 迭代数:15 损失:0.635408 准确率0.750 时间119
轮次:0 迭代数:20 损失:0.575522 准确率1.000 时间157
轮次:0 迭代数:25 损失:0.661196 准确率0.625 时间196
轮次:0 迭代数:30 损失:0.546462 准确率0.875 时间234
轮次:0 迭代数:35 损失:0.609517 准确率0.875 时间272
轮次:0 迭代数:40 损失:0.529246 准确率1.000 时间310
轮次:0 迭代数:45 损失:0.474820 准确率1.000 时间348
轮次:0 迭代数:50 损失:0.540127 准确率0.875 时间387
轮次:0 迭代数:55 损失:0.575326 准确率0.625 时间426
# 从以上的训练输出效果来看,预训练模型是十分强大的,只需要短短的几次迭代,就可以让准确率上88%
模型评估
# 模型测试
def dm04_evaluate_model():
# 实例化数据源对象my_dataset_test
print('\n加载测试集')
my_dataset_test = load_dataset('csv', data_files='./mydata1/test.csv', split='train')
print('my_dataset_test--->', my_dataset_test)
# print(my_dataset_test[0:3])
# 实例化下游任务模型my_model
path = './my_model_3.bin'
my_model = MyModel()
my_model.load_state_dict(torch.load(path))
print('my_model-->', my_model)
# 设置下游任务模型为评估模式
my_model.eval()
# 设置评估参数
correct = 0
total = 0
# 实例化化dataloader
my_loader_test = torch.utils.data.DataLoader(my_dataset_test,
batch_size=8,
collate_fn=collate_fn1,
shuffle=True,
drop_last=True)
# 给模型送数据 测试预测结果
for i, (input_ids, attention_mask, token_type_ids,
labels) in enumerate(my_loader_test):
# 预训练模型进行特征抽取
with torch.no_grad():
my_out = my_model(input_ids=input_ids,
attention_mask=attention_mask,
token_type_ids=token_type_ids)
# 贪心算法求预测结果
out = my_out.argmax(dim = 1)
# 计算准确率
correct += (out == labels).sum().item()
total += len(labels)
# 每5次迭代打印一次准确率
if i % 5 == 0:
print(correct / total, end=" ")
print(my_tokenizer.decode(input_ids[0], skip_special_tokens=True), end=" ")
print('预测值 真实值:', out[0].item(), labels[0].item())
- 输出效果:
0.875 我 没 有 收 到 这 本 书 , 我 明 明 和 另 外 一 本 一 起 买 的 , 服 务 也 不 好 。 至 少 应 该 让 我 知 道 这 个 情 况 在 ! 预测值 真实值: 0 0
0.8125 也 许 这 不 算 一 个 很 好 的 理 由, 但 是 我 之 所 以 喜 欢 读 书 而 不 是 看 网 上 的 资 料 什 么 的, 就 是 喜 欢 闻 着 书 香. 这 本 书 可 能 是 印 刷 的 油 墨 不 好 还 是 什 么 原 因, 感 觉 臭 臭 的 不 好 闻. 里 面 是 一 些 关 于 中 式 英 语 的 小 趣 闻, 有 些 小 乐 趣, 但 感 觉 对 于 有 浓 重 中 式 思 维 习 惯 说 英 说 的 人 来 说 才 比 较 有 点 用 处. 预测值 真实值: 1 0
0.8409090909090909 1. 有 急 事 出 去 , 要 们 童 叫 出 租 车 , 他 们 就 叫 酒 店 里 的 黑 车 , 价 格 是 普 通 出 租 价 的 两 倍 。 你 提 出 不 要 酒 店 的 黑 车 时 , 他 们 就 告 诉 你 外 面 拦 不 到 出 租 车 , 我 们 自 己 走 出 去 时 , 外 面 出 租 车 随 时 可 以 拦 到 。 住 店 期 间 不 止 一 次 发 生 。 预测值 真实值: 0 0
0.828125 这 本 书 中 的 图 片 很 让 人 触 动 , 比 如 为 劳 拉 祈 祷 的 校 友 , 惠 特 尼 和 马 特 的 笑 容 , 很 动 人 。 预测值 真实值: 1 1
0.8511904761904762 这 是 我 住 过 的 最 差 的 酒 店 , 房 间 气 味 难 闻 , 刚 打 了 灭 蚊 药 水 , 换 了 三 个 房 间 还 是 如 此 , 服 务 员 说 : 住 久 了 就 习 惯 了 , 每 个 宾 馆 都 有 自 己 的 味 道 。 考 ! 我 又 不 是 来 体 验 生 活 的 。 周 围 环 境 复 杂 , 脏 乱 差 。 预测值 真实值: 0 0
尾声
迁移学习的介绍就到这里啦,内容比较简单,友友们可以多多操作一下,多使用使用已有的模型去训练。下一篇将介绍BERT模型和GPT模型,感兴趣的友友们可以关注一下(●'◡'●)。