72_text_generation\vae-seq2seq

本文详细介绍了PyTorch中NLLLoss的功能和使用方法,指出它常用于多分类任务,且输入需要先通过log_softmax转化为概率分布。NLLLoss计算涉及对数似然损失,结合weight、size_average等参数能处理类别不平衡问题。同时,文章对比了NLLLoss与CrossEntropyLoss的关系,强调后者已包含softmax和负对数似然损失。此外,还展示了代码示例来帮助理解。
摘要由CSDN通过智能技术生成

NLLLoss

class torch.nn.NLLLoss(weight=None, size_average=None, ignore_index=-100, reduce=None, reduction=‘elementwise_mean’)

功能: 不好用言语描述其功能!请看计算公式:loss(input, class) = -input[class]。举个例,三分类任务,input=[-1.233, 2.657, 0.534], 真实标签为2(class=2),则loss为-0.534。就是对应类别上的输出,取一个负号!感觉被NLLLoss的名字欺骗了。 实际应用: 常用于多分类任务,但是input在输入NLLLoss()之前,需要对input进行log_softmax函数激活,即将input转换成概率分布的形式,并且取对数。其实这些步骤在CrossEntropyLoss中就有,如果不想让网络的最后一层是log_softmax层的话,就可以采用CrossEntropyLoss完全代替此函数。

参数: weight(Tensor)- 为每个类别的loss设置权值,常用于类别不均衡问题。weight必须是float类型的tensor,其长度要于类别C一致,即每一个类别都要设置有weight。 size_average(bool)- 当reduce=True时有效。为True时,返回的loss为除以权重之和的平均值;为False时,返回的各样本的loss之和。

reduce(bool)- 返回值是否为标量,默认为True。

ignore_index(int)- 忽略某一类别,不计算其loss,其loss会为0,并且,在采用size_average时,不会计算那一类的loss,除的时候的分母也不会统计那一类的样本。

实例: /Code/3_optimizer/3_1_lossFunction/4_NLLLoss.py

特别注意: 当带上权值,reduce = True, size_average = True, 其计算公式为:
在这里插入图片描述
例如当input为[[0.6, 0.2, 0.2], [0.4, 1.2, 0.4]],target= [0, 1], weight = [0.6, 0.2, 0.2] l1 = - 0.60.6 = - 0.36 l2 = - 1.20.2 = - 0.24 loss = -0.36/(0.6+0.2) + -0.24/(0.6+0.2) = -0.75

import torch
input=torch.randn(3,3)
soft_input = torch.nn.Softmax(dim=0)
soft_input(input)
Out[20]: 
tensor([[0.7284, 0.7364, 0.3343],
        [0.1565, 0.0365, 0.0408],
        [0.1150, 0.2270, 0.6250]])

#对softmax结果取log
torch.log(soft_input(input))
Out[21]: 
tensor([[-0.3168, -0.3059, -1.0958],
        [-1.8546, -3.3093, -3.1995],
        [-2.1625, -1.4827, -0.4701]])

假设标签是[0,1,2],第一行取第0个元素,第二行取第1个,第三行取第2个,去掉负号,即[0.3168,3.3093,0.4701],求平均值,就可以得到损失值。
nn.NLLLoss
官方文档中介绍称: nn.NLLLoss输入是一个对数概率向量和一个目标标签,它与nn.CrossEntropyLoss的关系可以描述为:softmax(x)+log(x)+nn.NLLLoss====>nn.CrossEntropyLoss

def prepare_train_input(insts, bos_id, eos_id, pad_id):
    # Add eos token id and bos token id.
    src = [[bos_id] + inst + [eos_id] for inst in insts]
    trg = [inst[:-1] for inst in insts]
    label = [inst[1:] for inst in insts]

    # Pad sequence using eos id.
    src, src_length = Pad(pad_val=pad_id, ret_length=True, dtype="int64")(
        [ids for ids in src])
    trg, trg_length = Pad(pad_val=pad_id, ret_length=True, dtype="int64")(
        [ids for ids in trg])
    label, _ = Pad(pad_val=pad_id, ret_length=True, dtype="int64")(
        [ids for ids in label])

    label = np.array(label)
    label = label.reshape((label.shape[0], label.shape[1], 1))
    return src, src_length, trg, trg_length, label

src = [[bos_id] + inst + [eos_id] for inst in insts]
trg = [inst[:-1] for inst in insts]
label = [inst[1:] for inst in insts]
输入都是一个batch,一个batch内的没个样本都做处理,已经添加开始符号和结束符号了,解码部分真实输入就是去掉结束符号,保留开始符号,解码部分真实的标签就是输入去掉起始符号。刚好错开一位。
记住编码阶段有输入输出,解码阶段有输入输出,但是训练是一起训练的,同时输入输出的。

class CrossEntropyWithKL(nn.Layer):
    """
    backward_loss = kl_loss * kl_weight + cross_entropy_loss
    """

    def __init__(self, base_kl_weight, anneal_r):
        super(CrossEntropyWithKL, self).__init__()
        self.kl_weight = base_kl_weight
        self.anneal_r = anneal_r
        self.loss = 0.0
        self.kl_loss = 0.0
        self.rec_loss = 0.0

    def update_kl_weight(self):
        self.kl_weight = min(1.0, self.kl_weight + self.anneal_r)

    def forward(self, kl_loss, dec_output, trg_mask, label):
        self.update_kl_weight()
        self.kl_loss = kl_loss

        rec_loss = F.cross_entropy(
            input=dec_output, label=label, reduction='none', soft_label=False)

        rec_loss = paddle.squeeze(rec_loss, axis=[2])
        rec_loss = rec_loss * trg_mask
        rec_loss = paddle.mean(rec_loss, axis=[0])
        rec_loss = paddle.sum(rec_loss)
        self.rec_loss = rec_loss

        self.loss = self.kl_loss * self.kl_weight + self.rec_loss
        return self.loss

这个就是两个loss输出的loss和分布的loss

 # Encoder
        _, enc_final_state = self.encoder(src, src_length)

虽然做了填充,但是实际的长度是要知道的,填充部分不能参与运算

        outputs, _ = nn.dynamic_decode(
            decoder,
            inits=dec_initial_states,
            max_step_num=self.max_out_len,
            latent_z=latent_z)
        return outputs

动态LSTM编码,在函数里面直接把循环做了,一次性得到结果。


def infer(args):
    print(args)
    device = paddle.set_device(args.device)
    _, _, _, vocab, bos_id, eos_id, _ = create_data_loader(args)

    net = VAESeq2SeqInferModel(args.embed_dim, args.hidden_size,
                               args.latent_size, len(vocab) + 2)

    model = paddle.Model(net)
    model.prepare()
    model.load(args.init_from_ckpt)

    infer_output = paddle.ones((args.batch_size, 1), dtype='int64') * bos_id

    space_token = ' '
    line_token = '\n'
    with io.open(args.infer_output_file, 'w', encoding='utf-8') as out_file:
        predict_lines = model.predict_batch(infer_output)[0]
        for line in predict_lines:
            end_id = -1
            if eos_id in line:
                end_id = np.where(line == eos_id)[0][0]
            new_line = [vocab.to_tokens(e[0]) for e in line[:end_id]]
            out_file.write(space_token.join(new_line))
            out_file.write(line_token)

应用预测生产文本阶段,只用了解码部分,动态编码,在在内部解码循环一次性全部生成相应的输出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值
>