具有批量大小的字符 RNN 分类

Posted

技术标签:

【中文标题】具有批量大小的字符 RNN 分类【英文标题】:Char RNN classification with batch size 【发布时间】:2021-01-13 19:27:44 【问题描述】:

我正在复制 this example 以使用 Pytorch char-rnn 进行分类

for iter in range(1, n_iters + 1):
    category, line, category_tensor, line_tensor = randomTrainingExample()
    output, loss = train(category_tensor, line_tensor)
    current_loss += loss

我看到每个时代只有 1 个示例是随机抽取的。我希望每个时期所有数据集都带有特定的批量大小示例。我可以自己调整代码来执行此操作,但我想知道是否已经存在一些标志。

谢谢

【问题讨论】:

【参考方案1】:

如果您通过从PyTorch Dataset class 继承来构造一个Dataset 类,然后将其输入到PyTorch DataLoader class,那么您可以设置一个参数batch_size 来确定您将在每次训练迭代中得到多少示例循环。

我和你学习了同样的教程。我可以告诉你我是如何使用上面的 PyTorch 类来批量获取数据的。

# load data into a DataFrame using the findFiles function as in the tutorial
files = findFiles('data/names') # load the files as in the tutorial into a dataframe 
df_names = pd.concat([
    pd.read_table(f, names = ["names"], header = None)\
      .assign(lang = f.stem)\
    for f in files]).reset_index(drop = True)
print(df_names.head())

# output: 
#      names      lang
# 0      Abe  Japanese
# 1  Abukara  Japanese
# 2   Adachi  Japanese
# 3     Aida  Japanese
# 4   Aihara  Japanese

# Make train and test data 
from sklearn.model_selection import train_test_split
X_train, X_dev, y_train, y_dev = train_test_split(df_names.names, df_names.lang,
                                                   train_size = 0.8)
df_train = pd.concat([X_train, y_train], axis=1)
df_val = pd.concat([X_dev, y_dev], axis=1)

现在我通过从 PyTorch 数据集类继承,使用上面的数据帧构造一个修改过的数据集类。

import torch
from torch.utils.data import Dataset, DataLoader
class NameDatasetReader(Dataset):
    def __init__(self, df: pd.DataFrame):
        self.df = df      
        
    def __len__(self):
        return len(self.df)

    def __getitem__(self, idx: int):
        row = self.df.loc[idx] # gets a row from the df 
        input_name = list(row.names) # turns name into a list of chars 
        len_name = len(input_name) # length of name (used to pad packed sequence)
        labels = row.label # target 
        return input_name, len_name, labels
train_dat =  NameDatasetReader(df_train) # make dataset from dataframe with training data 

现在,问题是当您想要处理批次和序列时,您需要每个批次中的序列长度相同。这就是为什么我还在上面的 __getitem__() 函数中从数据框中获取提取名称的长度。这将用于修改每批中使用的训练示例的函数。

这称为 collat​​e_batch 函数,在此示例中,它会修改您的每批训练数据,以使给定批次中的序列长度相等。

# Dictionary of all letters (as in the original tutorial,
#  I have just inserted also an entry for the padding token)
all_letters_dict= dict(zip(all_letters, range(1, len(all_letters) +2)))
all_letters_dict['<PAD>'] = 0

# function to turn name into a tensor 
def line_to_tensor(line):
    """turns name into a tensor of one hot encoded vectors"""
    tensor = torch.zeros(len(line),
                         len(all_letters_dict.keys())) # (name_len x vocab_size) - <PAD> is part of vocab
    for li, letter in enumerate(line):
        tensor[li][all_letters_dict[letter]] = 1
    return tensor

def collate_batch_lstm(input_data: Tuple) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
    """
    Combines multiple name samples into a single batch
    :param input_data: The combined input_ids, seq_lens, and labels for the batch
    :return: A tuple of tensors (input_ids, seq_lens, labels)
    """
    
    # loops over batch input and extracts vals 
    names = [i[0] for i in input_data] 
    seq_names_len = [i[1] for i in input_data] 
    labels = [i[2] for i in input_data] 

    max_length = max(seq_names_len) # longest sequence aka. name 

    # Pad all of the input samples to the max length 
    names = [(name + ["<PAD>"] * (max_length - len(name))) for name in names]  
    
    input_ids = [line_to_tensor(name) for name in names] # turn each list of chars into a tensor with one hot vecs
    
    # Make sure each sample is max_length long
    assert (all(len(i) == max_length for i in input_ids))
    return torch.stack(input_ids), torch.tensor(seq_names_len), torch.tensor(labels) 

现在,我可以通过将上面的数据集对象、上面的 collat​​e_batch_lstm() 函数和给定的 batch_size 插入 DataLoader 类来构造一个数据加载器。

train_dat_loader = DataLoader(train_dat, batch_size = 4, collate_fn = collate_batch_lstm)

您现在可以迭代 train_dat_loader,它会在每次迭代中返回一个包含 4 个名称的训练批次。

考虑来自 train_dat_loader 的给定批次:

seq_tensor, seq_lengths, labels = iter(train_dat_loader).next()
print(seq_tensor.shape, seq_lengths.shape, labels.shape)
print(seq_tensor)
print(seq_lengths)
print(labels)
# output: 
# torch.Size([4, 11, 59]) torch.Size([4]) torch.Size([4])
# tensor([[[0., 0., 0.,  ..., 0., 0., 0.],
#          [0., 0., 0.,  ..., 0., 0., 0.],
#          [0., 0., 0.,  ..., 0., 0., 0.],
#          ...,
#          [0., 0., 0.,  ..., 0., 0., 0.],
#          [0., 0., 0.,  ..., 0., 0., 0.],
#          [0., 0., 0.,  ..., 0., 0., 0.]],

#         [[0., 0., 0.,  ..., 0., 0., 0.],
#          [0., 0., 0.,  ..., 0., 0., 0.],
#          [0., 0., 0.,  ..., 0., 0., 0.],
#          ...,
#          [1., 0., 0.,  ..., 0., 0., 0.],
#          [1., 0., 0.,  ..., 0., 0., 0.],
#          [1., 0., 0.,  ..., 0., 0., 0.]],

#         [[0., 0., 0.,  ..., 0., 0., 0.],
#          [0., 0., 0.,  ..., 0., 0., 0.],
#          [0., 0., 0.,  ..., 0., 0., 0.],
#          ...,
#          [1., 0., 0.,  ..., 0., 0., 0.],
#          [1., 0., 0.,  ..., 0., 0., 0.],
#          [1., 0., 0.,  ..., 0., 0., 0.]],

#         [[0., 0., 0.,  ..., 0., 0., 0.],
#          [0., 0., 0.,  ..., 0., 0., 0.],
#          [0., 0., 0.,  ..., 0., 0., 0.],
#          ...,
#          [1., 0., 0.,  ..., 0., 0., 0.],
#          [1., 0., 0.,  ..., 0., 0., 0.],
#          [1., 0., 0.,  ..., 0., 0., 0.]]])
# tensor([11,  3,  8,  7])
# tensor([14,  1, 14,  2])

它给了我们一个大小为 (4 x 11 x 59) 的张量。 4 因为我们已经指定我们想要批量大小为 4。 11 是给定批次中最长名称的长度(所有其他名称都用零填充,以使它们的长度相等)。 59 是我们词汇表中的字符数。

接下来的事情是将其合并到您的训练例程中并使用packing routine 以避免对您已填充数据的零进行冗余计算:)

【讨论】:

感谢您的解释! ? 我集成了这段代码,我注意到与较小的批次大小相比,完成一个时期的估计时间比较高的批次大小要短。我认为情况正好相反,实际上我只是为了加快进程而询问批量大小。这种情况是否也发生在你身上? 这就像我的 GPU 无法并行化数据。我的处理通常需要 2 x bs 的一半时间。在这种情况下不是。我计算出在同一时间段内,bs = 1 计算的示例比 bs = 2 多。我预计至少相同的数字

以上是关于具有批量大小的字符 RNN 分类的主要内容,如果未能解决你的问题,请参考以下文章

PyTorch-14 使用字符级RNN分类名字(姓名)

RNN经典案例:RNN模型构建人名分类器

RNN经典案例:RNN模型构建人名分类器

RNN经典案例实战使用RNNLSTMGRU模型构建姓名分类器

RNN经典案例实战使用RNNLSTMGRU模型构建姓名分类器

使用循环网络的电影评论分类