目录
3.1 map映射:只对数据集中的'sentence'数据进行编码
1.加载编码器
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained(r'../data/model/distilroberta-base/')
print(tokenizer)
1.1编码试算
tokenizer.batch_encode_plus([
'hide new secretions from the parental units',
'this moive is great'
])
2.加载数据集
from datasets import load_from_disk #从本地加载已经下载好的数据集
dataset_dict = load_from_disk('../data/datasets/glue_sst2/')
dataset_dict
3.数据集处理
3.1 map映射:只对数据集中的'sentence'数据进行编码
#预测中间词任务:只需要'sentence' ,不需要'label'和'idx'
#用map()函数,映射:只对数据集中的'sentence'数据进行编码
def f_1(data, tokenizer):
return tokenizer.batch_encode_plus(data['sentence'])
dataset_dict = dataset_dict.map(f_1,
batched=True,
batch_size=16,
drop_last_batch=True,
remove_columns=['sentence', 'label', 'idx'],
fn_kwargs={'tokenizer': tokenizer},
num_proc=8) #8个进程, 查看任务管理器>性能>逻辑处理器
dataset_dict
3.2用filter()过滤 单词太少的句子过滤掉
#处理句子,让每一个句子的都至少有9个单词,单词太少的句子过滤掉
#用filter()过滤
def f_2(data):
return [len(i) >= 9 for i in data['input_ids']]
dataset_dict = dataset_dict.filter(f_2, batched=True,
batch_size=1000,
num_proc=8)
dataset_dict
tokenizer.vocab['<mask>']
tokenizer.get_vocab()['<mask>']
#两句输出都是:
#50264
3.3截断句子
#截断句子, 同时将数据集的句子整理成预训练模型需要的格式
def f_3(data):
b = len(data['input_ids'])
data['labels'] = data['attention_mask'].copy() #复制,不影响原数据
for i in range(b):
#将句子长度就裁剪到9
data['input_ids'][i] = data['input_ids'][i][:9]
data['attention_mask'][i] = [1] * 9
data['labels'][i] = [-100] * 9
#使用的distilroberta-base是基于Bert模型的,每个句子de 'input_ids'最后一个单词需要设置成2
data['input_ids'][i][-1] = 2
#每一个句子的第四个词需要被预测,赋值给‘labels’,成为标签真实值
data['labels'][i][4] = data['input_ids'][i][4]
#每一个句子的第四个词为mask
# data['input_ids'][i][4] = tokenizer.vocab['<mask>']
data['input_ids'][i][4] = 50264
return data
#map()函数是对传入数据的下一层级的数据进行操作
#dataset_dict是一个字典, map函数会直接对dataset_dict['train']下的数据操作
dataset_dict = dataset_dict.map(f_3, batched=True, batch_size=1000, num_proc=12)
dataset_dict
dataset_dict['train'][0]
dataset_dict['train']['input_ids']
4.创建数据加载器Dataloader
import torch
from transformers.data.data_collator import default_data_collator #将从一条一条数据传输变成一批批数据传输
loader = torch.utils.data.DataLoader(
dataset=dataset_dict['train'],
batch_size=8,
shuffle=True,
collate_fn=default_data_collator,
drop_last=True #最后一批数据不满足一批的数据量batch_size,就删掉
)
for data in loader:
break #遍历赋值,不输出
len(loader), data
#'labels'中的-100就是占个位置,没有其他含义, 后面用交叉熵计算损失时, -100的计算结果接近0,损失没有用
5. 下游任务模型
from transformers import AutoModelForCausalLM, RobertaModel
class Model(torch.nn.Module):
def __init__(self):
super().__init__()
#相当于encoder
self.pretrained_model = RobertaModel.from_pretrained(r'../data/model/distilroberta-base/')
#decoder:就是一层全连接层(线性层)
#bert-base模型输出是768
decoder = torch.nn.Linear(768, tokenizer.vocab_size)
#全连接线性层有bias, 初始化为0 , size=tokenizer.vocab_size
decoder.bias = torch.nn.Parameter(torch.zeros(tokenizer.vocab_size))
#全连接
self.fc = torch.nn.Sequential(
#全连接索引0层
torch.nn.Linear(768, 768),
#全连接索引1层
torch.nn.GELU(), #激活函数,把线性的变成非线性的
#一般 线性层+激活函数+BN层标准化
#在NLP中,一般为: 线性层+激活函数+LN层标准化
#全连接索引2层
torch.nn.LayerNorm(768, eps=1e-5),
#全连接索引3层
decoder) #输出层:一层全连接,不用加激活函数
#加载预训练模型的参数
pretrained_parameters_model = AutoModelForCausalLM.from_pretrained(r'../data/model/distilroberta-base/')
self.fc[0].load_state_dict(pretrained_parameters_model.lm_head.dense.state_dict())
self.fc[2].load_state_dict(pretrained_parameters_model.lm_head.layer_norm.state_dict())
self.fc[3].load_state_dict(pretrained_parameters_model.lm_head.decoder.state_dict())
self.criterion = torch.nn.CrossEntropyLoss()
#forward拼写错误将无法接收传入的参数!!!
def forward(self, input_ids, attention_mask, labels=None):
#labels算损失时再传入
logits = self.pretrained_model(input_ids, attention_mask)
logits = logits.last_hidden_state #最后一层的hidden_state
logits = self.fc(logits)
#计算损失
loss = None
if labels is not None: #若传入了labels
#distilroberta-base这个模型本身做的任务是:根据上一个词预测下一个词
#在这个模型里会有一个偏移量,
#我么需要对labels和logits都做一个shift偏移
#logits的shape是(batch_Size, 一句话的长度, vocab_size)
shifted_logits = logits[:, :-1].reshape(-1, tokenizer.vocab_size) #三维tensor数组变成二维
#labels是二维tensor数组, shape(batch_size, 一句话的长度)
shifted_labels = labels[:,1:].reshape(-1) #二维变成一维
#计算损失
loss = self.criterion(shifted_logits, shifted_labels)
return {'loss': loss, 'logits': logits} #logits是预测值
model = Model()
#参数量
#for i in model.parameters() 获取一层的参数 i, 是一个tensor()
# i.numel()是用于返回一个数组或矩阵中元素的数量。函数名称“numel”是“number of elements”的缩写
print(sum(i.numel() for i in model.parameters()))
#未训练之前,先看一下加载的预训练模型的效果
out = model(**data)
out['loss'], out['logits'].shape
#out['logits']的shape(batch_size, 每个句子的长度, vocab_size)
#8:就是一批中8个句子,每个句子有9个单词, 每个单词有50265个类别
6.测试预测代码
def test(model):
model.eval() #调回评估模式,适用于预测
#创建测试数据的加载器
loader_test = torch.utils.data.DataLoader(
dataset=dataset_dict['test'],
batch_size=8,
collate_fn = default_data_collator, #默认的批量取数据
shuffle=True,
drop_last=True)
correct = 0
total = 0
for i, data in enumerate(loader_test):
#克隆data['labels']中索引为4的元素,取出来
label = data['labels'][:, 4].clone()
#从数据中抹掉label, 防止模型作弊
data['labels'] = None
#计算
with torch.no_grad(): #不求导,不进行梯度下降
#out:下游任务模型的输出结果,是一个字典,包含loss和logits
out = model(**data) #**data直接把data这个字典解包成关键字参数
#计算出logits最大概率
##out['logits']的shape(batch_size, 每个句子的长度, vocab_size)
#argmax()之后,shape变成(8, 9)
out = out['logits'].argmax(dim=2)[:, 4] #取出第四个才是我们的预测结果
correct += (label == out).sum().item()
total += 8 #batch_size=8, 每一批数据处理完,处理的数据总量+8
#每隔10次输出信息
if i % 10 == 0:
print(i)
print(label)
print(out)
if i == 50:
break
print('accuracy:', correct / total)
for i in range(8): #输出8句话的预测与真实值的对比
print(tokenizer.decode(data['input_ids'][i]))
print(tokenizer.decode(label[i]), tokenizer.decode(out[i]))
print()
test(model)
7.训练代码
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
device
from transformers import AdamW
from transformers.optimization import get_scheduler
#训练代码
def train():
optimizer = AdamW(model.parameters(), lr=2e-5)
#学习率下降计划
scheduler = get_scheduler(name='linear',
num_warmup_steps=0, #从一开始就预热,没有缓冲区
num_training_steps=len(loader),
optimizer=optimizer)
model.to(device) #将模型设置到设备上
model.train() #调到训练模式
for i,data in enumerate(loader): #一个loader的数据传完算一个epoch,NLP中很少写epoch,因为文本样本数太大
#接收数据
input_ids, attention_mask, labels = data['input_ids'], data['attention_mask'], data['labels']
#将数据都传送到设备上
input_ids, attention_mask, labels = input_ids.to(device), attention_mask.to(device), labels.to(device)
#将出入设备的数据再次传入到设备上的模型中
#out是一个字典,包括‘loss’和‘logits’预测的每个类别的概率
out = model(input_ids=input_ids, attention_mask=attention_mask, labels=labels)
#从返回结果中获取损失
loss = out['loss']
#反向传播
loss.backward()
#为了稳定训练, 进行梯度裁剪
torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) #让公式中的c=1.0
#梯度更新
optimizer.step()
scheduler.step()
#梯度清零
optimizer.zero_grad()
model.zero_grad()
if i % 50 == 0 :
#训练时,data['labels']不需要抹除掉设置成空
#只需要把每句话第四个单词取出来,赋值给label做为真实值即可
label = data['labels'][:, 4].to(device)
#获取预测的第4个单词字符
out = out['logits'].argmax(dim=2)[:, 4]
#计算预测准确的数量
correct = (label == out).sum().item()
#一批次计算处理的句子总量
total = 8
#计算准确率
accuracy = correct / total
lr = optimizer.state_dict()['param_groups'][0]['lr']
print(i, loss.item(), accuracy, lr)
train() #相当于训练了一个epoch
#8G显存一次跑两亿参数就差不多,再多放不下跑不了
#我的是6G,一次跑1.3亿左右就行
8.保存与加载模型
#保存模型
torch.save(model, '../data/model/预测中间词.model')
#加载模型 需要加载到cpu,不然会报错
model_2 = torch.load('../data//model/预测中间词.model', map_location='cpu')
test(model_2)