深度学习-数据加载优化-训练速度提升一倍

Posted yealxxy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深度学习-数据加载优化-训练速度提升一倍相关的知识,希望对你有一定的参考价值。

1,介绍

  • 数据加载
    深度学习的训练,简单的说就是将数据切分成batch,丢入模型中,并计算loss训练。其中比较重要的一环是数据打batch部分(数据加载部分)。

  • 训练时间优化:
    深度学习训练往往需要大量的数据,训练过程也比较慢,常见的提升训练速度的方法包括:数据加载优化、模型计算优化、fp16半精度训练、加大batch、多卡训练等方法。这篇文章主要介绍从数据加载的思路提升训练速度。

  • 结论:
    数据加载优化后,可以提升1倍以上的训练速度。

2,数据加载流程

  • 数据加载一般分为四步:
    • 从文本中读取数据,并处理成想要的格式。(比如分类任务,就需要输入+label的格式)
    • 将读取的输入,转换成模型的输入feature。(nlp中一般是把文本切分成token)
    • 创建:dataset、dataloader、sampler。(数据的采样方式,打batch的方式)
    • 遍历训练数据,并训练
  • 数据优化在第二步、已经第三步中。具体如下
 	examples, label_list = get_data(data_dir=file_dir)

    # 构建 feature
    features = convet_text_feature(examples)

    # 构建 dataset、dataloader、sampler
    train_dataset = ClassifyDataset(features, examples)
    train_sampler = SequentialSampler(train_dataset)
    train_dataloader = DataLoader(
        train_dataset,
        sampler=train_sampler,
        batch_size=4, collate_fn=torch.Tensor)

    # 获取训练数据
     for step, batch in enumerate(train_dataloader):
        print(batch.shape)

3, 简单版本

(完整代码:https://github.com/xxyliuyang/yl_nlp/blob/main/data_load_opt/simple.py)

  • 数据转换成feature步骤:将文本切词并转换成token id;之后,一次性将所有数据padding成最大长度。
def convet_text_feature(examples):
    tokenizer = AutoTokenizer.from_pretrained(tokenize_model)
    features = []
    for example in examples['train']:
        ids = tokenizer.convert_tokens_to_ids(tokenizer.tokenize(example.text_a, add_special_tokens=True))
        features.append(ids)

    # pad
    length = [len(ids) for ids in features]
    max_length = max(length)
    for i, feature in enumerate(features):
        features[i] = feature + [tokenizer.pad_token_id for _ in range(max_length-len(feature))]
    print(max_length)
    return features
  • 数据打batch: 随机的方法每次定时去取固定大小的数据即可

4, 高级版本

(完整代码:https://github.com/xxyliuyang/yl_nlp/blob/main/data_load_opt/advance.py)
可能已经发现问题了:

  • 问题一:转换成feature过程中一次性padding成最长,那么每次送入模型过程中,padding部分的计算就是多余的计算,大大增加的计算量。
  • 优化过程:在转换成feature部分,不进行padding,送入模型之前动态的padding
def convet_text_feature(examples):
    features = []
    for example in examples['train']:
        ids = tokenizer.convert_tokens_to_ids(tokenizer.tokenize(example.text_a, add_special_tokens=True))
        features.append(ids)

    # no pad
    length = [len(ids) for ids in features]
    max_length = max(length)
    print(max_length)
    return features
    
def batch_list_to_batch_tensors(batch): # 动态padding 函数
    batch_tensors = []
    max_len = max([len(x) for x in batch])
    for feature in batch:
        feature = feature + [tokenizer.pad_token_id for _ in range(max_len-len(feature))]
        batch_tensors.append(feature)
    return torch.tensor(batch_tensors, dtype=torch.long)
  • 问题二:如果是随机构建batch,那么输入长度是不确定的,假设一个batch,最小的文本长度是10,最大的文本长度是100,那么10长度的输入也要padding成100,同样无形中增加了过多的padding无效计算。
  • 优化过程:通过sampler优化,将相同长度的数据放到一个batch里面,减少padding的长度。
class BucketSampler(Sampler):
    def __init__(self, data_source, padding_noise=0.1):
        super().__init__(data_source)
        self.lengths = [len(x) for x in data_source.features]
        self.padding_noise = padding_noise

    def _add_noise_to_value(self, value: int):
        noise_value = value * self.padding_noise
        noise = random.uniform(-noise_value, noise_value)
        return value + noise

    def __iter__(self):
        noisy_lengths = [self._add_noise_to_value(l) for l in self.lengths]
        indice_lengths = [(idx, length) for idx, length in enumerate(noisy_lengths)]
        indice_lengths.sort(key=lambda x: x[1])
        return iter([indice_length[0] for indice_length in indice_lengths])

    def __len__(self):
        return len(self.lengths)

总结:

数据加载优化:

  • 先打batch,然后动态的padding(减少整体数据的padding长度)。
  • 将相同长度的数据放到一个batch里面(减少单个batch里面padding的长度)。

以上是关于深度学习-数据加载优化-训练速度提升一倍的主要内容,如果未能解决你的问题,请参考以下文章

深度学习-数据加载优化-训练速度提升一倍

数据并行:提升训练吞吐的高效方法 |深度学习分布式训练专题

如何将深度学习训练速度提升一百倍?PAISoar 来了

优化pytorch DataLoader提升数据加载速度

云原生AIFluid + JindoFS 助力微博海量小文件模型训练速度提升 18 倍

模型压缩75%,推理速度提升20%,Paddle Lite v2.3发布