任务介绍:
数据介绍:数据文件有三个train.csv,test.csv,validation.csv,数据样式都是一样的。
# 迁移学习 中文填空
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
# 定义下游任务模型 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
# 模型训练 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))
# 模型测试 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)