[PyTorch] 批量训练数据的一个流程

加载数据和RNN的批量处理(decoder)

使用DataLoader加载数据

trainloader = torch.utils.data.DataLoader(dataset=Dataset(FILEPATH,config.trainDataNum),\
batch_size=config.batchSize,shuffle=False,collate_fn=collate_fun)

Dataset类必须是继承自torch.utils.data.Dataset,而且要重载两个函数__getitem____len__,一个用来加载单个数据,一个用来获取数据集总的数目。

  • torch.utils.data.Dataset 这个包导入的时候不仅需要import torch,还得单独的导入一次import torch.utils.data
  • collate_fn是将一个batch的数据进行再次处理,比如我的这个就是对一个batch内的数据再次进行排序,并做了padding
class Dataset(torch.utils.data.Dataset):

    def __init__(self, filepath=None,dataLen=None,voc=None):
        self.file = filepath
        self.dataLen = dataLen
        self.voc = voc
        
    def __getitem__(self, index):
        A,B,path,hop = linecache.getline(self.file, index+1).split('\t')
        return self.voc[A],self.voc[B],[self.voc[i] for i in path.split(' ')],int(hop.strip('\n'))

    def __len__(self):
        return self.dataLen

def collate_fun(data):
    A,B,path,hop=zip(*data)
    pathLen = [len(p) for p in path]
    idx = sorted(enumerate(pathLen),key=lambda x:x[1],reverse=True)
    idx=[i[0] for i in idx]
    pathLen=[pathLen[i] for i in idx]
    A = [A[i] for i in idx]
    B = [B[i] for i in idx]
    path = [path[i] for i in  idx]
    hop = [hop[i] for i in idx]
    pathPad = torch.zeros(len(path),max(pathLen)).long()
    for i,s in enumerate(path):
        end = pathLen[i]
        pathPad[i,:end] = torch.LongTensor(s)
    return torch.LongTensor(A),torch.LongTensor(B),pathPad.t(),torch.LongTensor(pathLen),torch.LongTensor(hop)

"""
['really', 'fake', 'DistinctFrom', '1\n']
['really', 'genuine', 'DistinctFrom fake Antonym', '2\n']
['really', 'flog', 'DistinctFrom fake DerivedFrom', '2\n']
['really', 'sports', 'DistinctFrom fake HasContext', '2\n']
['really', 'nautical', 'DistinctFrom fake HasContext', '2\n']
['really', 'imitation', 'DistinctFrom fake IsA', '2\n']
['really', 'false', 'DistinctFrom fake RelatedTo', '2\n']
['really', 'fraudulent', 'DistinctFrom fake RelatedTo', '2\n']
['really', 'advantage', 'DistinctFrom fake RelatedTo', '2\n']
['really', 'deceive', 'DistinctFrom fake RelatedTo', '2\n']
A: ('really', 'really', 'really', 'really', 'really', 'really', 'really', 'really', 'really', 'really')  
B: ('fake', 'genuine', 'flog', 'sports', 'nautical', 'imitation', 'false', 'fraudulent', 'advantage', 'deceive')  
path: (['DistinctFrom'], ['DistinctFrom', 'fake', 'Antonym'], ['DistinctFrom', 'fake', 'DerivedFrom'], ['DistinctFrom', 'fake', 'HasContext'], ['DistinctFrom', 'fake', 'HasContext'], ['DistinctFrom', 'fake', 'IsA'], ['DistinctFrom', 'fake', 'RelatedTo'], ['DistinctFrom', 'fake', 'RelatedTo'], ['DistinctFrom', 'fake', 'RelatedTo'], ['DistinctFrom', 'fake', 'RelatedTo'])  
hop: (1, 2, 2, 2, 2, 2, 2, 2, 2, 2)
"""
  • 读取数据for index,data in enumerate(trainloader),里面的data就是collate_fn对应的函数所返回的形式。
for index,data in enumerate(trainloader):
    A,B,path,lens,hop=map(lambda x:x.to(device),data)
    optimizer_f.zero_grad()
    optimizer_p2v.zero_grad()
    embeddingPath=p2v(path,lens)
            outputs=f(embedding(A.to(device)),embedding(B.to(device)),embeddingPath)
            loss=criterion(outputs.squeeze(1), torch.log(torch.add(hop.to(device),1).to(dtype=torch.float)))
  • 使用的时候,因为是批量的数据,所以要有一个pack进行压包操作,做完了还要有一个pad解包操作。解包操作pad的第一个第一个返回值ouput就是gru每一步的输出维度是maxLen * batchSize * hiddenSize,第二个返回值就是这个batch里面每个句子的单词数目是一个一维tensor,和传入到Pack的第二个参数感觉一毛一样,如tensor[3,3,3,3,3,2,1]
  • pack传入的句子长度必须是降序的,所以collate_fn里面的函数还要做一下排序。
  • return torch.mean(ouput,0) 直接求均值,不用算来算去的。
class Path2Vec(nn.Module):  #we use a LSTM network generate a vector which is corresponding to a path  [node 1,relation 1, node 2] to 
    def __init__(self,embedding,hiddenLen):
        super(Path2Vec,self).__init__()
        self.hiddenLen=hiddenLen
        self.embedding = embedding
        self.gru = nn.GRU(hiddenLen,hiddenLen)
    def forward(self,path,lens):
        packed = torch.nn.utils.rnn.pack_padded_sequence(embedding(path),lens)
        output,_ = self.gru(packed)  
        output,lens=torch.nn.utils.rnn.pad_packed_sequence(output)
        return torch.div(output.sum(0),lens.to(device=device,dtype=torch.float).unsqueeze(1))
pack所做的操作,pad是pack的逆操作,要求一个batch里面的数据按照句子长短进行降序!

decoder端

  • 可以看到decoder是按照最大句子长度一个一个输入然后组合成最后结果的,并且每次都要输入encoder的所有输出,因为内部要做attention
  • 每一步的输出取出softmax后最大的那个下标,当做下一次decoder的输入。
  • 因为top里面是一个batch数据,所有要 [[topi[i][0] for i in range(batch_size)]]
encoder_outputs, encoder_hidden = encoder(input_variable, lengths)
...
for t in range(max_target_len):
    decoder_output, decoder_hidden = decoder(
        decoder_input, decoder_hidden, encoder_outputs
    )
    # No teacher forcing: next input is decoder's own current output
    _, topi = decoder_output.topk(1)
    decoder_input = torch.LongTensor([[topi[i][0] for i in range(batch_size)]]).to(device)
    # Calculate and accumulate loss
    mask_loss, nTotal = maskNLLLoss(decoder_output, target_variable[t], mask[t])
    loss += mask_loss

loss端

inpdecoder端每个时间步用模型跑出来的,target是这个时间步一个batch的真实数据,mask是这个时间步这个batchByteTensor类型的掩码。

  • torch.gather(inp, 1, target.view(-1, 1)),其中第三个参数表示index,他的类型必须是LongTensor类型的。这个函数的意思是对inp在第1维上按照target的次序组合数据,相当于Python代码,[inp[i] for i in target]
def maskNLLLoss(inp, target, mask):
    nTotal = mask.sum()
    crossEntropy = -torch.log(torch.gather(inp, 1, target.view(-1, 1)))
    loss = crossEntropy.masked_select(mask).mean()
    loss = loss.to(device)
    return loss, nTotal.item()
mask的制作

上面我的代码中其实是没有mask矩阵的,因为只相当于一个encoder,并没有decoder,所以在训练的时候也不用mask矩阵使得decoder产生真实的数据以后再和target对比,使用mask矩阵的目的是将padding的部分mask掉。

  • itertools.zip_longest(*l, fillvalue='-')'AB','CD','E'变成'ACE','BD-'
  • crossEntropy.masked_select(mask).mean()仅仅计算mask矩阵为1的部分进行求均值。
paddingIdx =0
sentBatch=[[1,2,3],[9,3],[2,5,8,9]]  #一个batch中的句子,数字表示单词在词汇表中的下标,0表示填充
sentBatch.sort(key=lambda x:len(x),reverse=True)  #为了能使用pack
sentBatchPad=list(itertools.zip_longest(*sentBatch,fillvalue=paddingIdx))#翻转并填充
a=torch.tensor(sentBatchPad)
b=a.ne(torch.tensor(paddingIdx)).byte()#torch.tensor(paddingIdx)是一个标量
print(b)

官网里面chatbot教程里面制作掩码矩阵的方法,感觉我的方法好像更加简单一点。

def zeroPadding(l, fillvalue=PAD_token):
    return list(itertools.zip_longest(*l, fillvalue=fillvalue))

def binaryMatrix(l, value=PAD_token):
    m = []
    for i, seq in enumerate(l):
        m.append([])
        for token in seq:
            if token == PAD_token:
                m[i].append(0)
            else:
                m[i].append(1)
    return m
mask = binaryMatrix(padList)
mask = torch.ByteTensor(mask)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容