【自然语言处理|迁移学习-08】:中文语料完型填空

文章目录

  • 1 中文语料完型填空任务介绍
  • 2 数据集加载及处理
  • 3 定义下游任务模型
  • 4 模型训练
  • 5. 模型测试

1 中文语料完型填空任务介绍

任务介绍:

  • 完成中文语料完型填空
  • 完型填空是一个分类问题,[MASK]单词有21128种可能
  • 数据构建
    【自然语言处理|迁移学习-08】:中文语料完型填空_第1张图片
    实现分析:
  • 使用迁移学习方式完成
  • 使用预训练模型bert模型提取文特征,后面添加全连接层和softmax进行单标签多分类

2 数据集加载及处理

数据介绍:数据文件有三个train.csv,test.csv,validation.csv,数据样式都是一样的。
【自然语言处理|迁移学习-08】:中文语料完型填空_第2张图片

# 迁移学习 中文填空
import torch
import torch.nn as nn
from torch.utils.data import DataLoader
from datasets import load_dataset
from transformers import BertTokenizer, BertModel
from transformers import AdamW
import time


model_path = './bert-base-chinese' # 模型路径
csv_filepath_train = './mydata1/train.csv'  # 中文分类 训练集
csv_filepath_test = './mydata1/test.csv'  # 中文分类 测试集
epochs = 1
my_lr = 5e-4

# 加载字典和分词工具
my_tokenizer = BertTokenizer.from_pretrained(model_path)

# 加载预训练模型
my_model_pretrained = BertModel.from_pretrained(model_path)
# 数据预处理 实现思路分析
# 1 通过加载本地文件 load_dataset('csv', data_files='', split="train")
#  1-1支持index、切片方式检索数据dataset_train[8] dataset_train[0:8]
#  1-2 按照条件过滤数据源对象 dataset_train_tmp.filter(lambda x: len(x['text']) > 32)

# 2 实例化myDataLoader DataLoader(dataset_train, batch_size, collate_fn, shuffle, drop_last )

# 3 自定义数据处理函数collate_fn2(data) 按照批次整理数据x 文本数值化
# 3-1 按照批次整理数据 sents = [i['text'] for i in data]
# 3-2 分词器编码data my_tokenizer.batch_encode_plus(batch_text_or_text_pairs = sents,truncation,
#               padding,max_length,return_tensors,return_length)
# 3-3 把index=16的词替换为mask
# 组织返回数据 input_ids = data['input_ids'] ...
# labels = input_ids[:, 16].reshape(-1).clone()
# input_ids[:, 16] = my_tokenizer.get_vocab()[my_tokenizer.mask_token]
# labels = torch.LongTensor(labels)
# 返回 input_ids, attention_mask, token_type_ids, labels

# 数据集处理自定义函数
def collate_fn2(data):
    # data是列表中嵌套字典的数据结构
    # 获取到每个样本中的句子放到列表中
    sents = [i['text'] for i in data]

    # 文本数值化
    # data返回的是一个字典,其中有三个属性,input_ids,token_type_ids,attention_mask
    data = my_tokenizer.batch_encode_plus(batch_text_or_text_pairs=sents,
                                   truncation=True,
                                   padding='max_length',
                                   max_length=32, # 设置句子长度为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']

    # 把第17个词固定替换为mask
    # input_ids的形状为(batch_size,max_length) -> (8,32)
    labels = input_ids[:, 16].reshape(-1).clone()  # 取出数据8句话 在第16个位置clone出来 做标签
    # print("my_tokenizer.get_vocab()",my_tokenizer.get_vocab()) # 返回的是一个字典:key是字符,value是对应的数值
    print("my_tokenizer.mask_token",my_tokenizer.mask_token) # mask_token --> [MASK]
    input_ids[:, 16] = my_tokenizer.get_vocab()[my_tokenizer.mask_token] # 将第17列个字符替换为MASK
    labels = torch.LongTensor(labels)

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


# 数据源 数据迭代器 测试
def dm01_test_dataset():

    # 生成数据源dataset对象
    dataset_train_tmp = load_dataset('csv', data_files=csv_filepath_train, split="train")
    print('dataset_train_tmp--->', dataset_train_tmp)

    # 按照条件过滤数据源对象
    my_dataset_train = dataset_train_tmp.filter(lambda x: len(x['text']) > 32)
    print('my_dataset_train--->', my_dataset_train)     # 9035
    print('my_dataset_train[0:3]-->', my_dataset_train[0:3])

    # 通过dataloader进行迭代
    mydataloader = DataLoader(my_dataset_train, batch_size=8, collate_fn=collate_fn2, shuffle=True, drop_last=True)
    print('mydataloader--->', mydataloader)

    # 调整数据迭代器对象数据返回格式
    for i, (input_ids, attention_mask, token_type_ids, labels) in enumerate(mydataloader):

        print(input_ids.shape, attention_mask.shape, token_type_ids.shape, labels)

        print('\n第1句mask的信息')
        print(my_tokenizer.decode(input_ids[0]))
        print(my_tokenizer.decode(labels[0]))

        print('\n第2句mask的信息')
        print(my_tokenizer.decode(input_ids[1]))
        print(my_tokenizer.decode(labels[1]))
        break

3 定义下游任务模型

# 定义下游任务模型 MyModel 类 实现思路分析:
# 1 函数init() super()
#  self.decoder = nn.Linear(768, my_tokenizer.vocab_size)
# 2 函数forward(self, input_ids, attention_mask, token_type_ids)
#   预训练模型提取特征with torch.no_grad():  my_model_pretrained() out.last_hidden_state[:,16]
#   数据经过全连接层 self.decoder(out)
class MyModel(nn.Module):
    def __init__(self):
        super(MyModel, self).__init__()
        # 定义全连接层
        # self.decoder = nn.Linear(768, my_tokenizer.vocab_size, bias=False)
        # 设置全连接层偏置为零
        # self.decoder.bias = torch.nn.Parameter(torch.zeros(my_tokenizer.vocab_size))
        self.decoder = nn.Linear(768, 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 = out.last_hidden_state[:, 16]
        out = self.decoder(out)
        # out = self.decoder(out.last_hidden_state[:, 16])
        # 返回
        return out

【自然语言处理|迁移学习-08】:中文语料完型填空_第3张图片
【自然语言处理|迁移学习-08】:中文语料完型填空_第4张图片

4 模型训练

# 模型训练 dm03_train_model() 实现思路分析
# 实例化下游任务模型 my_model  # 实例化损失函数my_criterion
# 实例化数据源对象 dataset_train_tmp.filter(lambda x: len(x['text']) > 32)
# 实例化优化器 my_optimizer AdamW(my_model.parameters(), lr=5e-4)
# 预训练模型不参与训练 不计算梯度 for param in my_model_pretrained.parameters(): .requires_grad_
# 设置训练参数 epochs = 3 my_model.train() starttime(轮次内部)
# 外层for循环 控制轮次 for eporch_idx in range(epochs):
# 内层for循环 控制迭代
# for i, (input_ids, attention_mask, token_type_ids, labels) in enumerate(my_dataloader, start=1):
# 给模型喂数据 # 计算损失 # 梯度清零 # 反向传播 # 梯度更新
# 打印训练信息:每5次迭代 算一下批次准确率
# out = my_out.argmax(dim=1) accuracy = (out == labels).sum().item() / len(labels)
# 每个轮次保存模型 torch.save(my_model.state_dict(), './my_model_%d.bin' % (eporch_idx + 1))
def dm03_train_model():

    # 实例化数据源对象my_dataset_train
    dataset_train_tmp = load_dataset('csv', data_files=csv_filepath_train, 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()

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

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

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


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

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

        starttime = (int)(time.time())

        # 实例化数据迭代器对象my_dataloader
        my_dataloader = DataLoader(my_dataset_train, batch_size=8, collate_fn=collate_fn2, shuffle=True, drop_last=True)

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

            # 条件退出
            if i == 100:
                break

        # 每个轮次保存模型
        torch.save(my_model.state_dict(), './my_model_mask_%d.bin' % (eporch_idx + 1))

5. 模型测试

# 模型测试 dm04_evaluate_model 思路分析
# 实例化数据源对象 my_dataset_test 实例化数据源 my_loader_test
# 实例化下游任务模型 加载模型参数 my_model.load_state_dict(torch.load(path))
# 设置评估模式 my_model.eval()
# 设置评估辅助变量  correct = 0 total = 0
# 给模型喂一轮数据 for i, (input_ids, attention_mask, token_type_ids, labels) in enumerate(my_loader_test):
# 贪心算法计算准确率 out = my_out.argmax(dim = 1) correct += (out == labels).sum().item()
# 统计已训练样本数 total += len(labels)
# 每5次迭代打印一次准确率
# my_tokenizer.decode(input_ids[0]) out[0], labels[0]), print(correct/total)
def dm04_evaluate_model():

    # 实例化数据源对象my_dataset_test
    print('\n加载测试集')
    my_dataset_tmp = load_dataset('csv', data_files=csv_filepath_test, 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_1.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 = 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, start=1):
        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 % 5 == 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)

你可能感兴趣的:(深度学习,自然语言处理(NLP),自然语言处理,迁移学习,人工智能)