pytorch实现seq2seq时对loss进行mask的方式
如何对loss进行mask
pytorch官方教程中有一个Chatbot教程,就是利用seq2seq和注意力机制实现的,感觉和机器翻译没什么不同啊,如果对话中一句话有下一句,那么就把这一对句子加入模型进行训练。其中在训练阶段,损失函数通常需要进行mask操作,因为一个batch中句子的长度通常是不一样的,一个batch中不足长度的位置需要进行填充(pad)补0,最后生成句子计算loss时需要忽略那些原本是pad的位置的值,即只保留mask中值为1位置的值,忽略值为0位置的值,具体演示如下:
importtorch importtorch.nnasnn importtorch.nn.functionalasF importitertools DEVICE=torch.device("cuda"iftorch.cuda.is_available()else"cpu") PAD_token=0
首先是pad函数和建立mask矩阵,矩阵的维度应该和目标一致。
defzeroPadding(l,fillvalue=PAD_token): #输入:[[1,1,1],[2,2],[3]] #返回:[(1,2,3),(1,2,0),(1,0,0)]返回已经是转置后的[L,B] returnlist(itertools.zip_longest(*l,fillvalue=fillvalue)) defbinaryMatrix(l): #将targets里非pad部分标记为1,pad部分标记为0 m=[] fori,seqinenumerate(l): m.append([]) fortokeninseq: iftoken==PAD_token: m[i].append(0) else: m[i].append(1) returnm
假设现在输入一个batch中有三个句子,我们按照长度从大到小排好序,LSTM或是GRU的输入和输出我们需要利用pack_padded_sequence和pad_packed_sequence进行打包和解包,感觉也是在进行mask操作。
inputs=[[1,2,3],[4,5],[6]]#输入句,一个batch,需要按照长度从大到小排好序 inputs_lengths=[3,2,1] targets=[[1,2],[1,2,3],[1]]#目标句,这里的长度是不确定的,mask是针对targets的 inputs_batch=torch.LongTensor(zeroPadding(inputs)) inputs_lengths=torch.LongTensor(inputs_lengths) targets_batch=torch.LongTensor(zeroPadding(targets)) targets_mask=torch.ByteTensor(binaryMatrix(zeroPadding(targets)))#注意这里是ByteTensor print(inputs_batch) print(targets_batch) print(targets_mask)
打印后结果如下,可见维度统一变成了[L,B],并且mask和target长得一样。另外,seq2seq模型处理时for循环每次读取一行,预测下一行的值(即[B,L]时的一列预测下一列)。
tensor([[1,4,6], [2,5,0], [3,0,0]]) tensor([[1,1,1], [2,2,0], [0,3,0]]) tensor([[1,1,1], [1,1,0], [0,1,0]],dtype=torch.uint8)
现在假设我们将inputs输入模型后,模型读入sos后预测的第一行为outputs1,维度为[B,vocab_size],即每个词在词汇表中的概率,模型输出之前需要softmax。
outputs1=torch.FloatTensor([[0.2,0.1,0.7],[0.3,0.6,0.1],[0.4,0.5,0.1]]) print(outputs1)
tensor([[0.2000,0.1000,0.7000], [0.3000,0.6000,0.1000], [0.4000,0.5000,0.1000]])
先看看两个函数
torch.gather(input,dim,index,out=None)->Tensor
沿着某个轴,按照指定维度采集数据,对于3维数据,相当于进行如下操作:
out[i][j][k]=input[index[i][j][k]][j][k]#ifdim==0 out[i][j][k]=input[i][index[i][j][k]][k]#ifdim==1 out[i][j][k]=input[i][j][index[i][j][k]]#ifdim==2
比如在这里,在第1维,选第二个元素。
#收集每行的第2个元素 temp=torch.gather(outputs1,1,torch.LongTensor([[1],[1],[1]])) print(temp)
tensor([[0.1000], [0.6000], [0.5000]])
torch.masked_select(input,mask,out=None)->Tensor
根据mask(ByteTensor)选取对应位置的值,返回一维张量。
例如在这里我们选取temp大于等于0.5的值。
mask=temp.ge(0.5)#大于等于0.5 print(mask) print(torch.masked_select(temp,temp.ge(0.5)))
tensor([[0], [1], [1]],dtype=torch.uint8) tensor([0.6000,0.5000])
然后我们就可以计算loss了,这里是负对数损失函数,之前模型的输出要进行softmax。
#计算一个batch内的平均负对数似然损失,即只考虑mask为1的元素 defmaskNLLLoss(inp,target,mask): nTotal=mask.sum() #收集目标词的概率,并取负对数 crossEntropy=-torch.log(torch.gather(inp,1,target.view(-1,1))) #只保留mask中值为1的部分,并求均值 loss=crossEntropy.masked_select(mask).mean() loss=loss.to(DEVICE) returnloss,nTotal.item()
这里我们计算第一行的平均损失。
#计算预测的第一行和targets的第一行的loss maskNLLLoss(outputs1,targets_batch[0],targets_mask[0]) (tensor(1.1689,device='cuda:0'),3)
最后进行最后把所有行的loss累加起来变为total_loss.backward()进行反向传播就可以了。
以上这篇pytorch实现seq2seq时对loss进行mask的方式就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持毛票票。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。