T5属于生成模型,不同于Bert的掩码预测(完形填空)任务,掩码预测可以直接给出[mask]为某token的概率,而生成模型由于输出的文本长度是不确定的,所以计算概率更加复杂。
生成模型本质上是根据上文一个个的预测token。
如果目标输出为“123”,首先要进行预处理,使得labels为“123
然后模型根据“
以此类推,最后将所有对应概率相乘,就得到“123
P(“123
如果只想计算一句话的概率,不是一个batch,那么使用loss就可以很快计算,因为loss是交叉熵,本质就是每一步的概率取对数求平均,代码如下
from transformers import AutoTokenizer, AutoConfig,T5ForConditionalGeneration,T5Tokenizer,T5Config,Text2TextGenerationPipeline
from datasets import load_dataset
import torch
import numpy as np
import pandas as pd
from torch import nn
import os
import random
import copy
#计算T5模型生成特定text的概率
def cal_prob(target_text,input_text,model,tokenizer):
#将input_text转换为t5模型的输入格式
encodings = tokenizer(input_text, return_tensors="pt")
encodings = {k: v.to(device) for k, v in encodings.items()}
#将target_text转换为t5模型的输出格式
labels = tokenizer.encode(target_text, return_tensors="pt", max_length=64, padding=True).to(device)
#由labels生成decoder_input_ids,需要在前面补0使得长度与labels相同
decoder_input_ids = torch.cat([torch.zeros_like(labels[:, :1]), labels[:, :-1]], dim=-1).to(device)
#计算生成text的概率
outputs = model(**encodings, labels=labels,decoder_input_ids=decoder_input_ids)
loss = outputs[0]
text_prob=torch.exp(-loss)**(len(target_text))
return text_prob
if __name__ == "__main__":
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model_name = "ClueAI/PromptCLUE-base"
tokenizer = AutoTokenizer.from_pretrained(model_name)
cfg=AutoConfig.from_pretrained(model_name)
model = T5ForConditionalGeneration.from_pretrained(model_name,config=cfg)
model.to(device)
input_text = "这是关于哪方面的评论: 看起来很好吃 选项:价格,物流,外观/颜色/看起来,服务,售后/客服,其他 答案:"
target_text = "外观/颜色/看起来"
#计算生成target_text的概率
text_prob=cal_prob(target_text,input_text,model,tokenizer)
如果要计算一个batch的概率,则不能用loss,因为loss是一个标量,除非模型返回的是每个样本的loss才可以计算。所以改用output[‘logits’]计算
# 计算T5模型生成特定text的概率,一个batch
def cal_prob_batch(target_text: list, input_text: list, model, tokenizer):
# 将input_text转换为t5模型的输入格式
encodings = tokenizer(input_text, return_tensors="pt")
encodings = {k: v.to(device) for k, v in encodings.items()}
# 将target_text转换为t5模型的输出格式
labels = tokenizer(target_text, return_tensors="pt", max_length=64, padding=True)['input_ids'].to(device)
# 由labels生成decoder_input_ids,需要在前面补0使得长度与labels相同
decoder_input_ids = torch.cat([torch.zeros_like(labels[:, :1]), labels[:, :-1]], dim=-1).to(device)
# 计算生成text的概率
outputs = model(**encodings, labels=labels, decoder_input_ids=decoder_input_ids)
# 使用logits计算生成labels的概率,logits的shape为[batch_size,seq_len,vocab_size]
logits = outputs["logits"].detach()
# 对logits进行softmax,得到每个词的概率
logits_softmax = torch.softmax(logits, dim=-1)
# 计算生成labels的概率,假设labels的长度为n,那么生成labels[0]的概率为logits[0,0,labels[0]]*logits[0,1,labels[1]]*...*logits[0,n-1,labels[n-1]]
# x坐标为0到seq_len-1,y坐标为labels[n],从logits中选择对应的概率
labels_token_prob_list = [logits_softmax[i, range(labels.shape[-1]), labels[i, :]] for i in
range(labels.shape[0])]
#labels_token_prob_list大小与labels相同,[batch_size,max_seq_len]
labels_token_prob_list = torch.stack(labels_token_prob_list)
#将labels中为0的位置的概率设置为1
labels_token_prob_list[labels==0]=1
# 计算生成每个label的概率,labels_token_prob_list中所有token的概率相乘
labels_prob_list = torch.prod(labels_token_prob_list, dim=-1)
return labels_prob_list
if __name__ == "__main__":
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model_name = "ClueAI/PromptCLUE-base"
tokenizer = AutoTokenizer.from_pretrained(model_name)
cfg = AutoConfig.from_pretrained(model_name)
model = T5ForConditionalGeneration.from_pretrained(model_name, config=cfg)
model.to(device)
input_text = ["这是关于哪方面的评论: 看起来很好吃 选项:价格,物流,外观/颜色/看起来,服务,售后/客服,其他 答案:"] * 6
target_text = ["外观/颜色/看起来", "其他","价格","物流","服务","售后/客服"]
# 计算生成target_text的概率,输入list,返回大小为len(list)的tensor
text_prob = cal_prob_batch(target_text, input_text, model, tokenizer)
print(text_prob)
#输出结果tensor([0.5207, 0.1666, 0.0379, 0.0028, 0.0287, 0.0008], device='cuda:0')
当然,由于生成模型的输出总共有 m a x _ l e n g t h v o a c b _ s i z e max\_length^{voacb\_size} max_lengthvoacb_size种可能,所以即使在问题里指定了6个选项 [“外观/颜色/看起来”, “其他”,“价格”,“物流”,“服务”,“售后/客服”],最后这6个选项的概率之和也不为1,还有可能生成“无答案”或者其他乱七八糟的答案,当然概率很低。
可以再对输出的6个选项的概率做一次归一化,使其和为1.