T5或GPT等生成模型,如何计算模型输出特定文本的概率?

T5属于生成模型,不同于Bert的掩码预测(完形填空)任务,掩码预测可以直接给出[mask]为某token的概率,而生成模型由于输出的文本长度是不确定的,所以计算概率更加复杂。

生成模型本质上是根据上文一个个的预测token。

如果目标输出为“123”,首先要进行预处理,使得labels为“123<eos>”,decoder_inputs为“<pad>123”,两者长度一致。

然后模型根据“<pad>”给出第一个token的概率分布,即得到了token_1=“1”的概率,然后根据“<pad>1”给出第二个token的概率分布,即得到了token_2=“2”的概率…这些概率记录在output[‘logits’]中,logits的shape为[batch_size,seq_len,vocab_size]

以此类推,最后将所有对应概率相乘,就得到“123<eos>”的概率:
P(“123<eos>”)=P(token_1=“1”)*P(token_2=“2”)*P(token_3=“3”)*P(token_4=“<eos>”)

如果只想计算一句话的概率,不是一个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.

猜你喜欢

转载自blog.csdn.net/qq_51750957/article/details/128592198