ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

PaddleNLP 数据准备与处理全指南:从 load_dataset 到 DataLoader 的完整工作流

PaddleNLP 数据准备与处理全指南:从 load_dataset 到 DataLoader 的完整工作流 PaddleNLP 数据准备与处理全指南从 load_dataset 到 DataLoader 的完整工作流【免费下载链接】PaddleNLPEasy-to-use and powerful LLM and SLM library with awesome model zoo.项目地址: https://gitcode.com/gh_mirrors/pa/PaddleNLP数据集加载与数据预处理是 NLP 任务中最关键的环节之一。PaddleNLP 为这一阶段提供了从内置数据集加载、自定义数据集构造、基于预训练模型/非预训练模型的特征转换到 batchify 与 DataLoader 构建的一整套开箱即用的 API并以低耦合、高内聚的模块化设计降低了学习成本。本文以 docs/en/data_prepare/overview.rst 为主线结合 load_dataset 加载文档、数据预处理文档、自定义数据集文档 以及paddlenlp/datasets/dataset.py等源码系统讲解 PaddleNLP 数据准备阶段的完整技术方案。读完本文你将掌握如何一键加载 20 内置数据集并自由切换MapDataset/IterDataset如何基于本地文件、paddle.io.Dataset或第三方数据集自定义数据如何编写trans_func完成 tokenization 与特征转换并最终组装出可训练的DataLoader。一、数据准备的整体设计四大核心能力在进入具体 API 之前先看 PaddleNLP 对数据与数据处理这一阶段的设计理念。官方文档将其概括为三个特性Powerful APIs强大的 API为大多数常见 NLP 任务的数据处理流程提供统一、开箱即用的接口避免重复造轮子Flexible Encapsulation灵活的封装模块之间保持低耦合、高内聚用户可以基于继承与自定义机制按需扩展出满足特定业务场景的数据处理方案Built-in Datasets内置数据集内置数据集覆盖大部分 NLP 任务并提供简洁易用的加载协议与贡献指南对新手与社区贡献者都非常友好。这三个特性分别对应着三条主线load_dataset等加载 API、DatasetBuilder继承机制、以及数量庞大的内置数据集。其中数据集加载与处理的核心 API有三个API定位load_dataset快速加载数据集的统一入口通过传入数据集读取脚本名数据集名及其他参数调用对应DatasetBuilder子类的方法生成数据集DatasetBuilder所有内置数据集的基类负责数据集文件的下载与Dataset对象的生成绝大多数方法已被封装贡献者只需重写_get_data与_read等方法即可向社区提交新数据集MapDataset/IterDatasetPaddleNLP 内置的数据集类型分别扩展自paddle.io.Dataset与paddle.io.IterableDataset提供map、filter等 NLP 专属的数据处理方法可轻松构造自定义数据集这三个 API 的具体实现与调用方式将在后续章节逐一展开。二、理解 PaddleNLP 的数据处理工作流PaddleNLP 的通用数据处理流程遵循一条清晰的流水线任何 NLP 任务的数据准备都可以套用这个框架加载数据集内置或自定义数据集返回原始数据 raw data定义trans_func包括 tokenization、token 转 ID 等并通过数据集的map方法将其应用于每条样本把原始数据转换为特征 features基于处理后的数据定义batchify 方法与BatchSampler使用BatchSampler与batchify_fn定义DataLoader。下图是 BERT 文本分类任务的数据处理流程图原始数据 →Dataset.map(trans_func)→ 特征数据 → Batchify → 批次数据从图中可以直观看到整条链路原始样本textlabel首先由trans_func通过 Bert Tokenizer 编码为input_ids、token_type_ids同时将标签包装为列表形式随后batchify_fn使用data.Pad将序列统一补齐到一致长度、使用data.Stack堆叠标签最终产出模型可直接消费的批次数据。这条流程在 data_preprocess.rst 中有完整的分步示例下面我们逐一深入每个环节。三、加载内置数据集load_dataset 详解3.1 一行代码加载数据集PaddleNLP 提供了 20 个内置 NLP 数据集覆盖阅读理解、文本分类、序列标注、机器翻译等任务。以msra_ner中文命名实体识别数据集为例from paddlenlp.datasets import load_dataset train_ds, test_ds load_dataset(msra_ner, splits(train, test))load_dataset会在paddlenlp.datasets包默认路径为 paddlenlp/datasets/msra_ner.py中定位msra_ner对应的数据读取脚本并调用脚本中DatasetBuilder类的相关方法生成数据集。从源码看load_dataset 的核心逻辑正是根据传入的数据集名找到注册的DatasetBuilder类将name、splits、data_files、lazy等参数透传给 builder 的_get_data/_read流程最终返回数据集对象。3.2 MapDataset 与 IterDatasetlazy 参数切换生成的数据集既可以是MapDataset也可以是IterDataset二者分别是paddle.io.Dataset与paddle.io.IterableDataset的扩展。通过load_dataset的lazy参数即可切换lazyFalse返回MapDatasetlazyTrue返回IterDataset。默认值为None此时返回DatasetBuilder预定义的数据集类型大多数内置数据集为MapDataset。from paddlenlp.datasets import load_dataset train_ds load_dataset(msra_ner, splitstrain) print(type(train_ds)) # class paddlenlp.datasets.dataset.MapDataset # 默认 train_ds load_dataset(msra_ner, splitstrain, lazyTrue) print(type(train_ds)) # class paddlenlp.datasets.dataset.IterDataset两种数据集类型的差异在源码中体现得很清楚MapDataset 继承自paddle.io.Dataset支持__getitem__随机索引访问适合数据量适中、可整体载入内存的场景IterDataset 继承自paddle.io.IterableDataset只能顺序迭代适合超大规模、无法一次性载入内存的数据。它们都提供了map、filter等扩展方法见 dataset.py 与 dataset.py语义与paddle.io的数据集方法保持一致但增加了 NLP 场景的灵活性。3.3 选择子数据集name 参数部分数据集是多个子数据集的集合每个子集是一个独立的数据集。例如GLUE数据集就包含 COLA、SST2、MRPC、QQP 等 10 个子集。load_dataset通过name参数指定子集。例如从 XTREME 基准中加载 SQuAD 数据集from paddlenlp.datasets import load_dataset squad_train load_dataset(xtreme, namesquad, splitstrain)数据加载脚本会把name参数自动拼接到数据集文件路径中例如 SQuAD 子集的文件通常存放在xtreme/squad目录下。splits参数则用于指定要取的数据集划分例如同时取训练集与验证集train_ds, dev_ds load_dataset(glue, namecola, splits(train, dev))3.4 按内置数据集格式读取本地数据data_files 参数有时我们希望使用与内置数据集同格式的本地数据来替换部分内置数据例如在 SQuAD 竞赛训练中做数据增强。load_dataset的data_files参数即可实现这一功能。以SQuAD为例from paddlenlp.datasets import load_dataset train_ds, dev_ds load_dataset(squad, data_files(my_train_file.json, my_dev_file.json)) test_ds load_dataset(squad, data_filesmy_test_file.json)需要注意两点对某些数据集不同划分可能需要不同的读取方式。此时必须在splits参数中提供相应的划分信息且splits需要与data_files一一对应此场景下splits不再表示选择内置数据集划分而是指定本地数据的读取格式。以COLA数据集为例train_ds, test_ds load_dataset( glue, cola, splits[train, test], data_files[my_train_file.csv, my_test_file.csv], )重要提示该数据集没有默认的加载选项必须至少指定splits与data_files中的一项。四、自定义数据集三种构造方式除了使用内置数据集PaddleNLP 还支持通过load_dataset、MapDataset、IterDataset轻松定义自己的数据集。4.1 从本地文件创建数据集从本地文件创建数据集时官方推荐基于本地数据格式编写一个 reader 函数并把它传给load_datasetfrom paddlenlp.datasets import load_dataset def read(data_path): with open(data_path, r, encodingutf-8) as f: # 跳过表头 next(f) for line in f: words, labels line.strip(\n).split(\t) words words.split(\002) labels labels.split(\002) yield {tokens: words, labels: labels} # data_path 是 read() 的参数 map_ds load_dataset(read, data_pathtrain.txt, lazyFalse) iter_ds load_dataset(read, data_pathtrain.txt, lazyTrue)这里有几个最佳实践尽量把数据读取代码写成生成器generator这样能同时良好地构造MapDataset与IterDataset建议将每条样本格式化为字典便于数据流监控与后续的map处理大多数场景下MapDataset即可满足需求只有数据集过大、无法一次性载入内存时才考虑IterDataset自定义 reader 函数的参数可以直接以关键字参数的形式传给load_dataset对自定义数据集lazy参数是必填的只有 PaddleNLP 内置数据集才支持自动的 label 转 id详见 如何编写 DatasetBuilder。像上面这种自定义数据集需要在自定义特征转换函数中手动实现 label 转 id 的逻辑。4.2 从 paddle.io.Dataset / IterableDataset 包装如果你习惯用paddle.io.Dataset/paddle.io.IterableDataset创建数据集可以直接用MapDataset/IterDataset包装转换为 PaddleNLP 数据集对象从而获得map、filter等数据处理能力。下面是一个简单示例IterDataset的用法类似from paddle.io import Dataset from paddlenlp.datasets import MapDataset class MyDataset(Dataset): def __init__(self, path): def load_data_from_source(path): ... return data self.data load_data_from_source(path) def __getitem__(self, idx): return self.data[idx] def __len__(self): return len(self.data) ds MyDataset(data_path) # paddle.io.Dataset new_ds MapDataset(ds) # paddlenlp.datasets.MapDataset4.3 从其他 Python 对象创建理论上任何包含__getitem__和__len__方法的 Python 对象都可以用来创建MapDataset包括List、Tuple、DataFrame等from paddlenlp.datasets import MapDataset data_source_1 [1, 2, 3, 4, 5] data_source_2 (a, b, c, d) list_ds MapDataset(data_source_1) tuple_ds MapDataset(data_source_2) print(list_ds[0]) # 1 print(tuple_ds[0]) # a同理包含__iter__方法的 Python 对象如List、Generator可以创建IterDatasetfrom paddlenlp.datasets import IterDataset data_source_1 [a, b, c, d] data_source_2 (i for i in range(5)) list_ds IterDataset(data_source_1) gen_ds IterDataset(data_source_2) print([data for data in list_ds]) # [a, b, c, d] print([data for data in gen_ds]) # [0, 1, 2, 3, 4]注意向IterDataset传入生成器对象时第二次迭代会自动重置生成器若想复用生成的数据需要先将其转换为 list。此外IterDataset生成的数据集只能被迭代一次。这种通用性意味着我们也可以用同样的方法从第三方数据集创建 PaddleNLP 数据集例如 HuggingFacedatasetsfrom paddlenlp.datasets import MapDataset from datasets import load_dataset hf_train_ds load_dataset(msra_ner, splittrain) train_ds MapDataset(hf_train_ds) print(type(train_ds)) # class paddlenlp.datasets.dataset.MapDataset print(train_ds[2]) # {id: 2, ner_tags: [0, 0, 0, 5, 0, 0, 5, 0, ...], tokens: [因, 有, 关, 日, 寇, ...]}五、数据预处理从原始数据到特征数据集里通常是原始数据需要经过特定处理与采样形成批次再通过paddle.io.DataLoader喂给模型训练或推理。PaddleNLP 对流水线中的每一步都提供了对应支持。根据是否使用预训练模型数据预处理分为两条路径。5.1 基于预训练模型的预处理Tokenizer map使用预训练模型做 NLP 任务时加载对应的 Tokenizer 是必不可少的。PaddleNLP 的PreTrainedTokenizer通过内置的__call__方法实现了基础的数据处理能力所有预训练模型的 Tokenizer 都继承自PreTrainedTokenizer。以BertTokenizer为例from paddlenlp.transformers import BertTokenizer tokenizer BertTokenizer.from_pretrained(bert-base-chinese) # 单句转换单条数据 print(tokenizer(text天气不错)) # {input_ids: [101, 1921, 3698, 679, 7231, 102], token_type_ids: [0, 0, 0, 0, 0, 0]} # 句对转换单条数据 print(tokenizer(text天气, text_pair不错)) # {input_ids: [101, 1921, 3698, 102, 679, 7231, 102], token_type_ids: [0, 0, 0, 0, 1, 1, 1]} # 单句转换多条数据 print(tokenizer(text[天气, 不错])) # [{input_ids: [101, 1921, 3698, 102], token_type_ids: [0, 0, 0, 0]}, # {input_ids: [101, 679, 7231, 102], token_type_ids: [0, 0, 0, 0]}]接下来通过MapDataset.map方法将转换函数统一作用于数据集。以LCQMC中文语义匹配数据集句子对二分类任务为例展示完整的数据处理管线from paddlenlp.transformers import BertTokenizer from paddlenlp.datasets import load_dataset tokenizer BertTokenizer.from_pretrained(bert-base-chinese) train_ds load_dataset(lcqmc, splitstrain) print(train_ds[0]) # {query: 喜欢打篮球的男生喜欢什么样的女生, title: 爱打篮球的男生喜欢什么样的女生, label: 1}LCQMC 是句子对匹配任务即判断两个句子是否语义相似需要处理键为query和title的文本数据。我们基于PreTrainedTokenizer实现一个数据处理函数并传给数据集的map方法def convert_example(example, tokenizer): tokenized_example tokenizer( textexample[query], text_pairexample[title]) # 为训练添加 label tokenized_example[label] [example[label]] return tokenized_example from functools import partial trans_func partial( convert_example, tokenizertokenizer) train_ds.map(trans_func) print(train_ds[0]) # {input_ids: [101, 1599, 3614, 2802, ..., 102], # token_type_ids: [0, 0, ..., 1, 1, 1], # label: [1]}可以看到数据集中的文本数据已被处理成模型可接受的特征。map方法有一个重要参数batched默认False。当设为True时trans_func接收一批样本进行批量处理def convert_examples(examples, tokenizer): queries [example[query] for example in examples] titles [example[title] for example in examples] tokenized_examples tokenizer(textqueries, text_pairtitles, return_dictFalse) # 为训练添加 label for idx in range(len(tokenized_examples)): tokenized_examples[idx][label] [examples[idx][label]] return tokenized_examples from functools import partial trans_func partial(convert_examples, tokenizertokenizer) train_ds.map(trans_func, batchedTrue) print(train_ds[0]) # 与上例结果一致两种实现的结果相同但在机器阅读理解、对话等任务中单条原始数据可能生成多条特征通常就需要将batched参数设为True参考run_squad.py示例。map方法还有一个num_workers参数当它大于 0 时开启多进程数据处理可提升处理速度。但需注意如果数据处理函数使用了与数据索引相关的信息多进程可能导致结果出错。5.2 基于非预训练模型的预处理JiebaTokenizer Vocab使用非预训练模型时可以借助 PaddleNLP 内置的JiebaTokenizer与Vocab完成数据处理整体流程与预训练模型路径类似。以中文情感分析数据集ChnSentiCorp为例from paddlenlp.data import JiebaTokenizer, Vocab from paddlenlp.datasets import load_dataset train_ds load_dataset(chnsenticorp, splitstrain) print(train_ds[0]) # {text: 选择珠江花园的原因就是方便有电动扶梯直接到达海边..., label: 1} # 从本地词表文件构建 Vocab vocab Vocab.load_vocabulary(./senta_word_dict.txt, unk_token[UNK], pad_token[PAD]) # 用 Vocab 初始化 JiebaTokenizer tokenizer JiebaTokenizer(vocab)补充说明除了从本地词表文件初始化Vocab还提供多种初始化方式包括从dict和数据集创建除了内置的JiebaTokenizer用户也可以实现自定义分词方式或使用第三方分词库再通过Vocab.to_indices将 token 转为索引。convert_example方法将 token 转为 id随后沿用与预训练模型路径相似的处理流程def convert_example(example, tokenizer): input_ids tokenizer.encode(example[text]) valid_length [len(input_ids)] label [example[label]] return input_ids, valid_length, label from functools import partial trans_fn partial(convert_example, tokenizertokenizer) train_ds.map(trans_fn) print(train_ds[0]) # ([417329, 128448, 140437, 173188, 118001, 213058, ..., 1250380], # [67], # [1])注意这里单条数据是元组而非字典因此后续的batchify_fn需要相应调整见下一节。六、Batchify 与 DataLoader组装训练批次PaddleNLP 提供了多种内置的 collate 函数与paddle.io.BatchSampler配合即可简化批次的构建。6.1 基于字典数据Dict Pad Stack继续沿用 LCQMC 的例子。上一节处理后的每条样本是一个包含input_ids、token_type_ids、label三个键的字典。其中input_ids和token_type_ids在送入模型前需要pad补齐而label需要stack堆叠后传给损失函数。因此使用 PaddleNLP 内置的Dict、Stack、Pad函数来组织批次数据from paddlenlp.data import Dict, Stack, Pad # 用 Dict 将 Pad/Stack 函数与字典键对应起来 train_batchify_fn lambda samples, fnDict({ input_ids: Pad(axis0, pad_valtokenizer.pad_token_id), token_type_ids: Pad(axis0, pad_valtokenizer.pad_token_type_id), label: Stack(dtypeint64) }): fn(samples)接着用paddle.io.BatchSampler和batchify_fn构建paddle.io.DataLoaderfrom paddle.io import DataLoader, BatchSampler train_batch_sampler BatchSampler(train_ds, batch_size2, shuffleTrue) train_data_loader DataLoader( datasettrain_ds, batch_samplertrain_batch_sampler, collate_fntrain_batchify_fn, )至此完整的数据准备管线就构建完成了。更丰富的 batchify 方法可参考 paddlenlp.data.collate 文档。注意事项进行单机多卡训练时请用DistributedBatchSampler替换BatchSampler对于更复杂的 batching 需求如 batch 内排序、按 token 数切分 batch可使用 PaddleNLP 内置的SamplerHelper参考机器翻译 transformer 的reader.py示例。6.2 基于元组数据Tuple Pad Stack在非预训练模型路径ChnSentiCorp 示例中单条数据不是字典而是元组input_ids、valid_length、label因此batchify_fn需要改用Tuple函数按索引对齐from paddlenlp.data import Tuple, Stack, Pad # 用 Tuple 函数将 Pad、Stack 等函数与数据中的元素按位置对应 train_batchify_fn lambda samples, fnTuple(( Pad(axis0, pad_valvocab.token_to_idx.get([PAD], 0)), # input_ids Stack(dtypeint64), # seq len Stack(dtypeint64) # label )): fn(samples)对比可见Dict函数按键将单条数据中的键值对映射到对应的Pad/Stack等函数适用于每条数据是字典的情形Tuple则通过索引对齐单条数据中的不同成分。因此需要特别注意convert_example与batchify_fn之间的对应关系。此后的流程与预训练模型路径保持一致。七、向社区贡献数据集DatasetBuilder 核心协议PaddleNLP 鼓励社区贡献数据集而贡献方式正是定义一个DatasetBuilder子类。一个合格的DatasetBuilder需要遵循特定的协议与规范。以LCQMC为例其核心成员变量如下from paddle.dataset.common import md5file from paddle.utils.download import get_path_from_url from paddlenlp.utils.env import DATA_HOME class LCQMC(DatasetBuilder): LCQMC: A Large-scale Chinese Question Matching Corpus lazy False URL https://bj.bcebos.com/paddlehub-dataset/lcqmc.tar.gz MD5 62a7ba36f786a82ae59bbde0b0a9af0c META_INFO collections.namedtuple(META_INFO, (file, md5)) SPLITS { train: META_INFO(os.path.join(lcqmc, train.tsv), 2193c022439b038ac12c0ae918b211a1), dev: META_INFO(os.path.join(lcqmc, dev.tsv), c5dcba253cb4105d914964fd8b3c0e94), test: META_INFO(os.path.join(lcqmc, test.tsv), 8f4b71e15e67696cc9e112a459ec42bd), }贡献的数据集需继承paddlenlp.datasets.DatasetBuilder类类名采用驼峰命名并在 docstring 中简要描述数据集来源等信息。需要定义的成员变量包括lazy默认数据集类型False对应MapDatasetTrue对应IterDatasetURL数据集压缩包的下载地址必须是有效、稳定的链接若数据集不是压缩包格式可以省略MD5数据集压缩包的 MD5 校验值用于文件校验非压缩包格式可省略META_INFO数据集划分信息的格式定义SPLITS数据集的划分信息包含解压后的文件位置、文件名、MD5 值等对非压缩包数据集通常在此提供下载地址也可包含文件读取配置等参数。此外部分数据集还需要其他成员变量如VOCAB_INFO可参考iwslt15.py。成员变量的格式可能各不相同贡献者可根据实际需求调整。协议要点如果贡献的数据集没有子集DatasetBuilder必须包含SPLITS成员变量且必须是以划分名称为键的字典如果数据集包含子集则必须包含BUILDER_CONFIGS成员变量以子集的name为键、值为包含splits划分信息的字典格式可参考glue.py。随后需要实现两个必须的方法_get_data方法——定位并校验数据集文件def _get_data(self, mode, **kwargs): Check and download Dataset default_root os.path.join(DATA_HOME, self.__class__.__name__) filename, data_hash self.SPLITS[mode] fullname os.path.join(default_root, filename) if not os.path.exists(fullname) or (data_hash and not md5file(fullname) data_hash): get_path_from_url(self.URL, default_root, self.MD5) return fullname_get_data根据输入的mode和划分信息定位具体的数据集文件先对本地文件做 MD5 校验校验失败则调用paddle.utils.download.get_path_from_url下载并校验数据集文件最后返回数据集文件的本地路径。_read方法——从给定文件路径读取数据def _read(self, filename): Reads data. with open(filename, r, encodingutf-8) as f: head None for line in f: data line.strip().split(\t) if not head: head data else: query, title, label data yield {query: query, title: title, label: label}_read方法必须是生成器这样DatasetBuilder才能同时构造MapDataset和IterDataset。当不同划分需要不同的读取方式时该方法还需支持split参数并分别处理。可选方法get_labels返回数据集全部标签的列表用于将类别标签转换为 id该列表会作为实例变量传给生成的数据集。若想让DatasetBuilder在数据集生成时自动完成 label 转 id需要将样本中的标签键命名为label或labels并正确实现get_labelsget_vocab当数据集提供词表文件时需配合VOCAB_INFO变量实现返回包含数据集词表信息的Dictionary对象用于训练时初始化paddlenlp.data.Vocab。总结_read与_get_data是必需的get_labels与get_vocab视数据集内容可选若不想做 md5 校验也可省略相关成员变量与校验代码。完整规范详见 如何编写 DatasetBuilder。八、结语一条可复用的数据准备方法论回顾全文PaddleNLP 的数据准备体系可以归纳为一套可复用的方法论用load_dataset/自定义 reader 拿到原始数据 → 用maptrans_funcTokenizer 或 Vocab 路径把原始数据转成特征 → 用Dict/Tuple组合Pad/Stack定义batchify_fn→ 用BatchSamplerbatchify_fn构建DataLoader。无论任务是基于 BERT 的文本分类、LCQMC 语义匹配还是基于词表的中文情感分析这条链路都能直接套用需要扩展时MapDataset/IterDataset的map、filter方法以及DatasetBuilder的继承机制提供了充分的定制空间。各环节更完整的 API 说明可继续查阅 paddlenlp.datasets.dataset 文档、paddlenlp.data.collate 文档 及 数据预处理文档。【免费下载链接】PaddleNLPEasy-to-use and powerful LLM and SLM library with awesome model zoo.项目地址: https://gitcode.com/gh_mirrors/pa/PaddleNLP创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表