迁移学习(含代码示例)

感谢阅读

  • fasttext
    • 安装
    • 文本分类种类
    • 文本分类代码版过程详解
      • 获取数据+训练测试
      • ValueError:XXX cannot be opened for training!
      • 模型测试
      • 模型调优
        • 增加训练轮数
        • 调整学习率
        • 增加n-gram特征
        • 修改损失计算方式
        • 自动超参数调优
        • 模型保存与重加载
  • 词向量
    • 简介
    • 词向量模型压缩文件
      • 下载
      • 解压文件
    • 代码实现
      • 加载bin文件
      • 获取词向量
      • 邻近词进行效果检验
  • 迁移学习
    • 迁移学习三大概念
      • 预训练模型(Pretrained model)
      • 微调(Fine-tuning)
      • 微调脚本(Fine-tuning script)
    • 迁移的两种方式
    • GLUE数据集合介绍
    • 当下NLP中流行的预训练模型
  • Transformers库
    • 简介
    • 备注
    • 三层结构
    • 管道方式完成多种NLP任务
      • 下载并安装库
      • 文本分类任务
      • 特征提取任务
      • 完型填空任务
      • 阅读理解任务
      • 文本摘要任务
      • NER任务
    • 自动模型方式完成多种NLP任务
      • 代码前说明
      • 代码实现
        • 分类
        • NER
  • 迁移学习实践
    • 通过微调方式进行迁移学习的两种类型
    • 直接加载预训练模型进行输入文本的特征表示, 后接自定义网络进行微调输出结果
      • 导包
      • 数据集下载以及介绍
      • 数据预处理
      • 自定义下游任务网络模型
      • 模型训练
      • 模型评估
    • 说明
    • 中文填空
      • 数据预处理
      • 自定义下游任务网络模型
      • 训练模型
      • 模型评估

fasttext

安装

先安装fasttext

pip install fasttext

文本分类种类

二分类:
文本被分类两个类别中, 往往这两个类别是对立面, 比如: 判断一句评论是好评还是差评.
单标签多分类:
文本被分入到多个类别中, 且每条文本只能属于某一个类别(即被打上某一个标签), 比如: 输入一个人名, 判断它是来自哪个国家的人名.
多标签多分类:
文本被分人到多个类别中, 但每条文本可以属于多个类别(即被打上多个标签), 比如: 输入一段描述, 判断可能是和哪些兴趣爱好有关, 一段描述中可能即讨论了美食, 又太讨论了游戏爱好.

文本分类代码版过程详解

获取数据+训练测试

fasttext_data文件中
下载完数据文件并上传到服务器后,可以进入相应目录进行查看数据操作
迁移学习(含代码示例)_第1张图片

# 导入fasttext
import fasttext
# help(fasttext)
# 使用fasttext的train_supervised方法进行文本分类模型的训练
model = fasttext.train_supervised(input="../data/fasttext_data/cooking.train")

运行结果
迁移学习(含代码示例)_第2张图片

ValueError:XXX cannot be opened for training!

1.检查一下路径名是否正确还有就是不能有中文
2.路径开头使用…/ 而不要使用./(部分版本会出这个问题)

模型测试

model.test("data/cooking/cooking.valid")

在这里插入图片描述

模型调优

# 这些因素对我们最终的分类目标没有益处, 反是增加了模型提取分类规律的难度,
# 因此我们选择将它们去除或转化

# 处理前的部分数据
__label__fish Arctic char available in North-America
__label__pasta __label__salt __label__boiling When cooking pasta in salted water how much of the salt is absorbed?
__label__coffee Emergency Coffee via Chocolate Covered Coffee Beans?
__label__cake Non-beet alternatives to standard red food dye
__label__cheese __label__lentils Could cheese "halt" the tenderness of cooking lentils?
__label__asian-cuisine __label__chili-peppers __label__kimchi __label__korean-cuisine What kind of peppers are used in Gochugaru ()?
__label__consistency Pavlova Roll failure
__label__eggs __label__bread What qualities should I be looking for when making the best French Toast?
__label__meat __label__flour __label__stews __label__braising Coating meat in flour before browning, bad idea?
__label__food-safety Raw roast beef on the edge of safe?
__label__pork __label__food-identification How do I determine the cut of a pork steak prior to purchasing it?

增加训练轮数

def add_train():
    model = fasttext.train_supervised(input="../data/fasttext_data/cooking.train", epoch=25)
    result = model.test("../data/fasttext_data/cooking.valid")
    return result


def main():
    # print( model_test_by_ft() )
    add_train()
    return 0

迁移学习(含代码示例)_第3张图片

调整学习率

model = fasttext.train_supervised(input="../data/fasttext_data/cooking.train", lr=1.0, epoch=25)

增加n-gram特征

model = fasttext.train_supervised(input="../data/fasttext_data/cooking.train", lr=1.0, epoch=25, wordNgrams=2)

N-Gram特征的创建:设定滑动窗口长度(字节片段长度N),对恶意代码内容按字节特征大小进行滑动,每一个字节片段称为gram;统计gram出现的频度,设定阈值进行归一化

修改损失计算方式

model = fasttext.train_supervised(input="../data/fasttext_data/cooking.train", lr=1.0, epoch=25, wordNgrams=2, loss='hs')

自动超参数调优

# autotuneValidationFile参数需要指定验证数据集所在路径, 它将在验证集上使用随机搜索方法寻找可能最优的超参数.
# 使用autotuneDuration参数可以控制随机搜索的时间, 默认是300s, 根据不同的需求, 我们可以延长或缩短时间.
# 验证集路径'cooking.valid', 随机搜索600秒
model = fasttext.train_supervised(input='../data/fasttext_data/cooking.train', autotuneValidationFile='data/cooking/cooking.pre.valid', autotuneDuration=600)

模型保存与重加载

def save_model(model, path):
    model.save_model(path)


def read_model_by_path(path):
    model = fasttext.load_model(path)
    return model


def main():
    # print( model_test_by_ft() )
    # add_train()
    model = fasttext.train_supervised\
        (input='../data/fasttext_data/cooking.train',
         autotuneValidationFile='../data/fasttext_data/cooking.valid',
         autotuneDuration=600)
    save_model(model, "../data/model/model_cooking.bin")
    read_model_by_path(path="../data/model/model_cooking.bin")
    return 0

在这里插入图片描述

词向量

简介

用向量表示文本中的词汇(或字符)是现代机器学习中最流行的做法, 这些向量能够很好的捕捉语言之间的关系, 从而提升基于词向量的各种NLP任务的效果.

词向量模型压缩文件

下载

wget https://dl.fbaipublicfiles.com/fasttext/vectors-crawl/cc.zh.300.bin.gz

解压文件

gunzip cc.zh.300.bin.gz

代码实现

加载bin文件

model = fasttext.load_model("./data/cc.zh.300.bin")

获取词向量

model.get_word_vector("音乐")

邻近词进行效果检验

model.get_nearest_neighbors("音乐")

迁移学习

迁移学习三大概念

预训练模型(Pretrained model)

一般情况下预训练模型都是大型模型,具备复杂的网络结构,众多的参数量,以及在足够大的数据集下进行训练而产生的模型. 在NLP领域,预训练模型往往是语言模型,因为语言模型的训练是无监督的,可以获得大规模语料,同时语言模型又是许多典型NLP任务的基础

微调(Fine-tuning)

根据给定的预训练模型,改变它的部分参数或者为其新增部分输出结构后,通过在小部分数据集上训练,来使整个模型更好的适应特定任务

微调脚本(Fine-tuning script)

实现微调过程的代码文件。这些脚本文件中,应包括对预训练模型的调用,对微调参数的选定以及对微调结构的更改等,同时,因为微调是一个训练过程,它同样需要一些超参数的设定,以及损失函数和优化器的选取等, 因此微调脚本往往也包含了整个迁移学习的过程.

迁移的两种方式

1.直接使用预训练模型,进行相同任务的处理,不需要调整参数或模型结构,这些模型开箱即用。但是这种情况一般只适用于普适任务
2.更加主流的迁移学习方式是发挥预训练模型特征抽象的能力,然后再通过微调的方式,通过训练更新小部分参数以此来适应不同的任务。

GLUE数据集合介绍

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

当下NLP中流行的预训练模型

BERT
GPT
GPT-2
Transformer-XL
XLNet
XLM
RoBERTa
DistilBERT
ALBERT
T5
XLM-RoBERTa

Transformers库

简介

Huggingface总部位于纽约,是一家专注于自然语言处理、人工智能和分布式系统的创业公司。他们所提供的聊天机器人技术一直颇受欢迎,但更出名的是他们在NLP开源社区上的贡献。Huggingface一直致力于自然语言处理NLP技术的平民化(democratize),希望每个人都能用上最先进(SOTA, state-of-the-art)的NLP技术,而非困窘于训练资源的匮乏。同时Hugging Face专注于NLP技术,拥有大型的开源社区。尤其是在github上开源的自然语言处理,预训练模型库 Transformers,已被下载超过一百万次,github上超过24000个star

备注

  • https://gluebenchmark.com/tasks 官方网站下载
  • https://github.com/CLUEbenchmark/CLUE
  • https://www.cluebenchmarks.com/ **中文语言理解测评基准(CLUE)

三层结构

管道(Pipline)方式:高度集成的极简使用方式,只需要几行代码即可实现一个NLP任务。
自动模型(AutoMode)方式:可载入并使用BERTology系列模型。
具体模型(SpecificModel)方式:在使用时,需要明确指定具体的模型,并按照每个BERTology系列模型中的特定参数进行调用,该方式相对复杂,但具有较高的灵活度。

管道方式完成多种NLP任务

下载并安装库

# 注意在执行clone之前,要查看当前是在那个目录下,比如$HOME/nlpdev/目录下
# 克隆huggingface的transfomers文件
git clone https://github.com/huggingface/transformers.git

# 进行transformers文件夹
cd transformers

# 切换transformers到指定版本
git checkout v4.19.0

# 安装transformers包
pip install .

顺便安装一下数据集库

pip install datasets

文本分类任务

def dm01_test_classification():
    # 1 使用中文预训练模型chinese_sentiment
    # 模型下载地址 git clone https://huggingface.co/techthiyanes/chinese_sentiment
    # 2 实例化pipeline对象
    # my_model = pipeline(task='sentiment-analysis', model='../data/chinese_sentiment.bin',
    #                     encoding='gbk')
    my_model = pipeline(task='sentiment-analysis', model='./bert-base-chinese')
    # 3 文本送给模型 进行文本分类
    output = my_model('我爱北京天安门,天安门上太阳升。')
    print('output--->', output)


def main():
    dm01_test_classification()
    return 0


if __name__ == '__main__':
    main()

特征提取任务

# 特征抽取任务
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--->  (1, 9, 768)
# 7个字变成9个字原因: [CLS] 人 生 该 如 何 起 头 [SEP]

完型填空任务

# 完型填空任务
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)

在这里插入图片描述

阅读理解任务

# 阅读理解任务(抽取式问答)
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))

文本摘要任务

# 文本摘要任务
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)

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('我爱北京天安门,天安门上太阳升。'))

自动模型方式完成多种NLP任务

由于和前面大同小异
以分类、NER为列子

代码前说明

AutoTokenizer、AutoModelForSequenceClassification函数可以自动从官网下载预训练模型,也可以加载本地的预训练模型
AutoModelForSequenceClassification类管理着分类任务,会根据参数的输入选用不同的模型。
AutoTokenizer的encode()函数使用return_tensors=’pt‘参数和不使用pt参数对文本编码的结果不同
AutoTokenizer的encode()函数使用padding='max_length'可以按照最大程度进行补齐,俗称打padding
调用模型的forward函数输入return_dict=False参数,返回结果也不同

代码实现

分类

# 导入工具包
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)

NER

# 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)

迁移学习实践

通过微调方式进行迁移学习的两种类型

类型一: 直接加载预训练模型进行输入文本的特征表示, 后接自定义网络进行微调输出结果
类型二: 使用指定任务类型的微调脚本微调预训练模型, 后接带有输出头的预定义网络输出结果
说明: 所有类型的实战演示, 都将针对中文文本进行

直接加载预训练模型进行输入文本的特征表示, 后接自定义网络进行微调输出结果

导包

# 导入工具包
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')

数据集下载以及介绍

数据文件有三个train.csv,test.csv,validation.csv,数据样式都是一样的
zhujiang_garden下载
代码查看一下

def check_dataset_by_csv():

    # 实例化数据源对象my_dataset_train
    print('\n加载训练集')
    my_dataset_train = load_dataset('csv', data_files='../data/'
                                                      'zhujiang_garden/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='../data/'
                                                      'zhujiang_garden/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='../data/'
                                                      'zhujiang_garden/validation.csv', split="train")
    print('my_dataset_validation--->', my_dataset_validation)
    print(my_dataset_validation[0:3])


def main():
    check_dataset_by_csv()
    return 0


if __name__ == '__main__':
    main()

数据预处理

# 数据集处理自定义函数
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

自定义下游任务网络模型

# 定义下游任务模型
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 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='../data/'
                                                     'zhujiang_garden/test.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))

模型评估

# 模型测试
def evaluate_model():

    # 实例化数据源对象my_dataset_test
    print('\n加载测试集')
    my_dataset_test = load_dataset('csv', data_files='../data/'
                                                     'zhujiang_garden/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())

说明

大多数模型可以进行多种任务,基本上除了个别地方不一致,大多数是一致的,下面的代码只搞不一致的

中文填空

数据预处理

def collate_fn2(data):
    sents = [i['text'] for i in data]

    # 文本数值化
    data = my_tokenizer.batch_encode_plus(batch_text_or_text_pairs=sents,
                                   truncation=True,
                                   padding='max_length',
                                   max_length=32,
                                   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']

    # 把第16个词固定替换为mask
    labels = input_ids[:, 16].reshape(-1).clone()  # 取出数据8句话 在第16个位置clone出来 做标签
    input_ids[:, 16] = my_tokenizer.get_vocab()[my_tokenizer.mask_token]
    labels = torch.LongTensor(labels)

    # tmpa = input_ids[:, 16]
    # print('tmpa--->', tmpa, tmpa.shape)       # torch.Size([8]
    # print('labels-->', labels.shape, labels)  # torch.Size([8]

    return input_ids, attention_mask, token_type_ids, labels

自定义下游任务网络模型

# 定义下游任务模型
class MyModel_add(torch.nn.Module):
    def __init__(self):
        super().__init__()
        # 定义全连接层
        self.decoder = torch.nn.Linear(768, my_tokenizer.vocab_size, bias=False)
        # 设置全连接层偏置为零
        self.decoder.bias = torch.nn.Parameter(torch.zeros(my_tokenizer.vocab_size))

    def forward(self, input_ids, attention_mask, token_type_ids):
        # 预训练模型不进行训练
        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, 21128]
        out = self.decoder(out.last_hidden_state[:, 16])

        # 返回
        return out

训练模型

def train_model_add():

    # 实例化数据源对象my_dataset_train
    dataset_train_tmp = load_dataset('csv', data_files='../data/'
                                                     'zhujiang_garden/train.csv', split="train")
    my_dataset_train = dataset_train_tmp.filter(lambda x: len(x['text']) > 32)
    print('my_dataset_train--->', my_dataset_train)

    # 实例化下游任务模型my_model
    my_model = MyModel_add()

    # 实例化优化器my_optimizer
    my_optimizer = AdamW(my_model.parameters(), lr=5e-4)

    # 实例化损失函数my_criterion
    my_criterion = torch.nn.CrossEntropyLoss()

    # 不训练预训练模型 只让预训练模型计算数据特征 不需要计算梯度
    for param in my_model_pretrained.parameters():
        param.requires_grad_(False)

    # 设置训练参数
    epochs = 3

    # 设置模型为训练模型
    my_model.train()

    # 外层for循环 控制轮数
    for eporch_idx in range(epochs):

        # 实例化数据迭代器对象my_dataloader
        my_dataloader = torch.utils.data.DataLoader(my_dataset_train,
                                                    batch_size=8,
                                                    collate_fn=collate_fn2,
                                                    shuffle=True,
                                                    drop_last=True)
        starttime = (int)(time.time())
        # 内层for循环 控制迭代次数
        for i, (input_ids, attention_mask, token_type_ids, labels) in enumerate(my_dataloader, start=1):
            # 给模型喂数据 [8,32] --> [8,21128]
            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 % 20 == 0:
                out = my_out.argmax(dim=1) # [8,21128] --> (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_mask_%d.bin' % (eporch_idx + 1))

模型评估

# 模型测试:填空
def evaluate_model_add():

    # 实例化数据源对象my_dataset_test
    print('\n加载测试集')
    my_dataset_tmp = load_dataset('csv', data_files='../data/'
                                                     'zhujiang_garden/test.csv', split='train')
    my_dataset_test = my_dataset_tmp.filter(lambda x: len(x['text']) > 32)
    print('my_dataset_test--->', my_dataset_test)
    # print(my_dataset_test[0:3])

    # 实例化下游任务模型my_model
    path = './my_model_mask_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_fn2,
                                              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)

        if i % 25 == 0:
            print(i+1, my_tokenizer.decode(input_ids[0]))
            print('预测值:', my_tokenizer.decode(out[0]), '\t真实值:', my_tokenizer.decode(labels[0]))
            print(correct / total)

你可能感兴趣的:(机器学习,深度学习,transformer,python)