0
点赞
收藏
分享

微信扫一扫

nlp任务之预测中间词-huggingface

目录

1.加载编码器

1.1编码试算 

2.加载数据集 

3.数据集处理 

3.1 map映射:只对数据集中的'sentence'数据进行编码

3.2用filter()过滤 单词太少的句子过滤掉

3.3截断句子 

4.创建数据加载器Dataloader 

5. 下游任务模型 

6.测试预测代码 

7.训练代码

 8.保存与加载模型


 

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)

 

 

举报

相关推荐

0 条评论