数据预处理教程

在对数据集进行训练之前,需要将其预处理为预期的模型输入格式。无论你的数据是文本、图像还是音频,都需要将其转换并组装成批量的张量(tensor)。🤗 Transformers 提供了一组预处理类,以帮助你准备数据用于模型训练。在本教程中,你将学习以下内容:

  • 对于文本数据,使用 Tokenizer 将文本转换为一个标记序列,创建标记的数值表示,并将其组装成张量。
  • 对于语音和音频数据,使用 Feature extractor 从音频波形中提取顺序特征,并将其转换为张量。
  • 对于图像输入,使用 ImageProcessor 将图像转换为张量。
  • 对于多模态输入,使用 Processor 将 Tokenizer 和 Feature extractor 或 Image processor 结合起来。

AutoProcessor 永远可用,它会自动选择适用于你使用的模型的正确类别,无论你使用的是 tokenizer、image processor、feature extractor 还是 processor。

在开始之前,请先安装 🤗 Datasets,以便您可以加载一些数据集进行实验:

pip install datasets

自然语言处理

预处理文本数据的主要工具是 tokenizer 。Tokenizer 根据一组规则将文本拆分为 tokens。这些 tokens 被转换成数字,然后成为模型的输入。Tokenizer 会添加模型所需的任何额外输入。

如果您计划使用预训练模型,使用相关的预训练 tokenizer 很重要。这样可以确保文本被拆分的方式与预训练语料库中的方式相同,并且在预训练期间使用相同的标记到索引的对应关系(通常称为 vocab)。

通过使用 AutoTokenizer.from_pretrained() 方法加载一个预训练的 tokenizer 来开始工作。这将下载模型预训练时使用的 vocab

>>> from transformers import AutoTokenizer

>>> tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")

然后将文本传递给 tokenizer:

>>> encoded_input = tokenizer("Do not meddle in the affairs of wizards, for they are subtle and quick to anger.")
>>> print(encoded_input)
{'input_ids': [101, 2079, 2025, 19960, 10362, 1999, 1996, 3821, 1997, 16657, 1010, 2005, 2027, 2024, 11259, 1998, 4248, 2000, 4963, 1012, 102],
 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}

Tokenizer 返回一个包含三个重要项的字典:

通过对 input_ids 进行解码,获取输入的文本:

>>> tokenizer.decode(encoded_input["input_ids"])
'[CLS] Do not meddle in the affairs of wizards, for they are subtle and quick to anger. [SEP]'

如您所见,tokenizer 在句子中添加了两个特殊的标记 - CLSSEP(分类器和分隔符)。并非所有模型都需要特殊标记,但如果需要,tokenizer 会自动为您添加它们。

如果有多个句子需要进行预处理,请将它们作为列表传递给 tokenizer:

>>> batch_sentences = [
...     "But what about second breakfast?",
...     "Don't think he knows about second breakfast, Pip.",
...     "What about elevensies?",
... ]
>>> encoded_inputs = tokenizer(batch_sentences)
>>> print(encoded_inputs)
{'input_ids': [[101, 1252, 1184, 1164, 1248, 6462, 136, 102],
               [101, 1790, 112, 189, 1341, 1119, 3520, 1164, 1248, 6462, 117, 21902, 1643, 119, 102],
               [101, 1327, 1164, 5450, 23434, 136, 102]],
 'token_type_ids': [[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]],
 'attention_mask': [[1, 1, 1, 1, 1, 1, 1, 1],
                    [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
                    [1, 1, 1, 1, 1, 1, 1]]}

填充

句子的长度不一定相同,这可能会导致问题,因为张量(模型输入)需要具有统一的形状。填充是一种策略,通过向较短的句子中添加一个特殊的填充标记来确保张量的矩形形状。

padding 参数设置为 True,将批次中较短的序列填充到与最长序列相同的长度:

>>> batch_sentences = [
...     "But what about second breakfast?",
...     "Don't think he knows about second breakfast, Pip.",
...     "What about elevensies?",
... ]
>>> encoded_input = tokenizer(batch_sentences, padding=True)
>>> print(encoded_input)
{'input_ids': [[101, 1252, 1184, 1164, 1248, 6462, 136, 102, 0, 0, 0, 0, 0, 0, 0],
               [101, 1790, 112, 189, 1341, 1119, 3520, 1164, 1248, 6462, 117, 21902, 1643, 119, 102],
               [101, 1327, 1164, 5450, 23434, 136, 102, 0, 0, 0, 0, 0, 0, 0, 0]],
 'token_type_ids': [[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]],
 'attention_mask': [[1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0],
                    [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
                    [1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]]}

第一个和第三个句子现在用 0 填充,因为它们较短。

截断

另一方面,有时候序列可能对模型来说太长无法处理。此时,您需要将序列截断为较短的长度。

truncation 参数设置为 True,将序列截断为模型可接受的最大长度:

>>> batch_sentences = [
...     "But what about second breakfast?",
...     "Don't think he knows about second breakfast, Pip.",
...     "What about elevensies?",
... ]
>>> encoded_input = tokenizer(batch_sentences, padding=True, truncation=True)
>>> print(encoded_input)
{'input_ids': [[101, 1252, 1184, 1164, 1248, 6462, 136, 102, 0, 0, 0, 0, 0, 0, 0],
               [101, 1790, 112, 189, 1341, 1119, 3520, 1164, 1248, 6462, 117, 21902, 1643, 119, 102],
               [101, 1327, 1164, 5450, 23434, 136, 102, 0, 0, 0, 0, 0, 0, 0, 0]],
 'token_type_ids': [[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]],
 'attention_mask': [[1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0],
                    [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
                    [1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]]}

查看 填充和截断 概念指南,了解更多不同的填充和截断参数。

构建张量

最后,您希望 tokenizer 返回实际的张量,以便供模型使用。

return_tensors 参数设置为 pt 表示 PyTorch,或者设置为 tf 表示 TensorFlow:

Pytorch
Hide Pytorch content
>>> batch_sentences = [
...     "But what about second breakfast?",
...     "Don't think he knows about second breakfast, Pip.",
...     "What about elevensies?",
... ]
>>> encoded_input = tokenizer(batch_sentences, padding=True, truncation=True, return_tensors="pt")
>>> print(encoded_input)
{'input_ids': tensor([[101, 1252, 1184, 1164, 1248, 6462, 136, 102, 0, 0, 0, 0, 0, 0, 0],
                      [101, 1790, 112, 189, 1341, 1119, 3520, 1164, 1248, 6462, 117, 21902, 1643, 119, 102],
                      [101, 1327, 1164, 5450, 23434, 136, 102, 0, 0, 0, 0, 0, 0, 0, 0]]),
 'token_type_ids': 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]]),
 'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0],
                           [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
                           [1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]])}
TensorFlow
Hide TensorFlow content
>>> batch_sentences = [
...     "But what about second breakfast?",
...     "Don't think he knows about second breakfast, Pip.",
...     "What about elevensies?",
... ]
>>> encoded_input = tokenizer(batch_sentences, padding=True, truncation=True, return_tensors="tf")
>>> print(encoded_input)
{'input_ids': <tf.Tensor: shape=(2, 9), dtype=int32, numpy=
array([[101, 1252, 1184, 1164, 1248, 6462, 136, 102, 0, 0, 0, 0, 0, 0, 0],
       [101, 1790, 112, 189, 1341, 1119, 3520, 1164, 1248, 6462, 117, 21902, 1643, 119, 102],
       [101, 1327, 1164, 5450, 23434, 136, 102, 0, 0, 0, 0, 0, 0, 0, 0]],
      dtype=int32)>,
 'token_type_ids': <tf.Tensor: shape=(2, 9), dtype=int32, numpy=
array([[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]], dtype=int32)>,
 'attention_mask': <tf.Tensor: shape=(2, 9), dtype=int32, numpy=
array([[1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0],
       [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
       [1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=int32)>}

音频

对于音频任务,您需要一个 特征提取器 来将数据集准备为模型所需的格式。特征提取器旨在从原始音频数据中提取特征,并将其转换为张量。

加载 MInDS-14 数据集(有关如何加载数据集的详细信息,请查看🤗 数据集教程 ),以了解如何在音频数据集上使用特征提取器:

>>> from datasets import load_dataset, Audio

>>> dataset = load_dataset("PolyAI/minds14", name="en-US", split="train")

访问audio列的第一个元素以查看输入。调用audio列会自动加载和重新采样音频文件:

>>> dataset[0]["audio"]
{'array': array([ 0.        ,  0.00024414, -0.00024414, ..., -0.00024414,
         0.        ,  0.        ], dtype=float32),
 'path': '/root/.cache/huggingface/datasets/downloads/extracted/f14948e0e84be638dd7943ac36518a4cf3324e8b7aa331c5ab11541518e9368c/en-US~JOINT_ACCOUNT/602ba55abb1e6d0fbce92065.wav',
 'sampling_rate': 8000}

该返回结果包括三个项目:

  • array 是加载的语音信号,可能经过重新采样,以1D数组表示。
  • path 是音频文件的位置。
  • sampling_rate 表示每秒测量的语音信号数据点数。

在本教程中,您将使用 Wav2Vec2 模型。查看模型卡片,您将了解到Wav2Vec2在采样率为16kHz的语音音频数据上进行了预训练。因此,您的音频数据的采样率必须与用于预训练模型的数据集的采样率相匹配。如果您的数据的采样率与之不同,则需要对数据进行重新采样。

  1. 使用🤗 Datasets的 cast_column 方法将采样率升采样到16kHz:
>>> dataset = dataset.cast_column("audio", Audio(sampling_rate=16_000))
  1. 再次调用 audio 列以重新采样音频文件:
>>> dataset[0]["audio"]
{'array': array([ 2.3443763e-05,  2.1729663e-04,  2.2145823e-04, ...,
         3.8356509e-05, -7.3497440e-06, -2.1754686e-05], dtype=float32),
 'path': '/root/.cache/huggingface/datasets/downloads/extracted/f14948e0e84be638dd7943ac36518a4cf3324e8b7aa331c5ab11541518e9368c/en-US~JOINT_ACCOUNT/602ba55abb1e6d0fbce92065.wav',
 'sampling_rate': 16000}

接下来,加载一个特征提取器来对输入进行归一化和填充。当填充文本数据时,默认为较短的序列添加 0。对于音频数据,即为特征提取器向 array 添加了 0(表示静音)。

使用 AutoFeatureExtractor.from_pretrained() 加载特征提取器:

>>> from transformers import AutoFeatureExtractor

>>> feature_extractor = AutoFeatureExtractor.from_pretrained("facebook/wav2vec2-base")

将音频 array 传递给特征提取器。我们建议在特征提取器中添加 sampling_rate 参数,以更好地调试可能发生的静音错误:

>>> audio_input = [dataset[0]["audio"]["array"]]
>>> feature_extractor(audio_input, sampling_rate=16000)
{'input_values': [array([ 3.8106556e-04,  2.7506407e-03,  2.8015103e-03, ...,
        5.6335266e-04,  4.6588284e-06, -1.7142107e-04], dtype=float32)]}

与分词器类似,您可以应用填充或截断来处理批量中的可变长度序列。查看这两个音频样本的序列长度:

>>> dataset[0]["audio"]["array"].shape
(173398,)

>>> dataset[1]["audio"]["array"].shape
(106496,)

创建一个函数来预处理数据集,以使音频样本具有相同的长度。指定最大样本长度,特征提取器将填充或截断序列以匹配该长度:

>>> def preprocess_function(examples):
...     audio_arrays = [x["array"] for x in examples["audio"]]
...     inputs = feature_extractor(
...         audio_arrays,
...         sampling_rate=16000,
...         padding=True,
...         max_length=100000,
...         truncation=True,
...     )
...     return inputs

preprocess_function 应用于数据集中的前几个示例:

>>> processed_dataset = preprocess_function(dataset[:5])

样本长度现在相同,并且与指定的最大长度匹配。现在您可以将处理后的数据集传递给模型了:

>>> processed_dataset["input_values"][0].shape
(100000,)

>>> processed_dataset["input_values"][1].shape
(100000,)

计算机视觉

对于计算机视觉任务,您需要一个 图像处理器 来将数据集准备为模型所需的格式。图像预处理包括几个步骤,将图像转换为模型所期望的输入,包括但不限于调整大小、归一化、颜色通道校正以及将图像转换为张量。

图像预处理通常紧随图像增强之后。图像预处理和图像增强都可以转换图像数据,但它们的目的不同:

  • 图像增强以某种方式改变图像,有助于防止过拟合并增加模型的稳定性。您可以在数据增强中做一些创造性的操作-调整亮度和颜色,裁剪、旋转、调整大小、缩放等。但是,请注意不要通过增强改变图像的含义。
  • 图像预处理确保图像与模型的预期输入格式匹配。当微调计算机视觉模型时,必须对图像进行与模型初始训练时相同的预处理。

您可以使用任何库来进行图像增强。对于图像预处理,请使用与模型关联的ImageProcessor

加载 food101 数据集(有关如何加载数据集的详细信息,请查看🤗 数据集教程 ),以了解如何在计算机视觉数据集上使用图像处理器:

使用🤗 Datasets split 参数仅加载训练集中的一小部分样本,因为数据集非常大!

>>> from datasets import load_dataset

>>> dataset = load_dataset("food101", split="train[:100]")

接下来,查看带有🤗 Datasets Image 特征的图像:

>>> dataset[0]["image"]

使用AutoImageProcessor.from_pretrained()加载图像处理器:

>>> from transformers import AutoImageProcessor

>>> image_processor = AutoImageProcessor.from_pretrained("google/vit-base-patch16-224")

首先,让我们添加一些图像增强。你可以使用任何你喜欢的库,但在本教程中,我们将使用torchvision的 transforms 模块。如果你有兴趣使用其他数据增强库,请查看 AlbumentationsKornia notebooks 中的说明。

  1. 这里我们使用 Compose 来链式地组合几个变换 - RandomResizedCropColorJitter 。注意,对于调整大小,我们可以从 image_processor 中获取图像大小要求。对于某些模型,期望精确的高度和宽度,对于其他模型只定义了 “shortest_edge”。
>>> from torchvision.transforms import RandomResizedCrop, ColorJitter, Compose

>>> size = (
...     image_processor.size["shortest_edge"]
...     if "shortest_edge" in image_processor.size
...     else (image_processor.size["height"], image_processor.size["width"])
... )

>>> _transforms = Compose([RandomResizedCrop(size), ColorJitter(brightness=0.5, hue=0.5)])
  1. 模型将 pixel_values 作为输入。ImageProcessor 可以处理图像的归一化,并生成适当的张量。创建一个将图像增强和图像预处理结合在一起,并为一批图像生成 pixel_values 的函数:
>>> def transforms(examples):
...     images = [_transforms(img.convert("RGB")) for img in examples["image"]]
...     examples["pixel_values"] = image_processor(images, do_resize=False, return_tensors="pt")["pixel_values"]
...     return examples

在上面的例子中,我们设置 do_resize=False,因为我们已经在图像增强过程中调整了图像的大小,并且使用了来自适当的 image_processorsize 属性。如果你在图像增强过程中不调整图像的大小,请忽略此参数。默认情况下,ImageProcessor 将处理调整大小的操作。

如果您希望在增强变换过程中对图像进行归一化处理,请使用 image_processor.image_meanimage_processor.image_std 值。

  1. 然后使用🤗 Datasets set_transform 来实时应用变换:
>>> dataset.set_transform(transforms)
  1. 现在当你访问图像时,你将会注意到图像处理器已经添加了 pixel_values。现在你可以将处理后的数据集传递给模型!
>>> dataset[0].keys()

下面是应用变换后图像的效果。图像已经被随机裁剪,并且其颜色属性也不同。

>>> import numpy as np
>>> import matplotlib.pyplot as plt

>>> img = dataset[0]["pixel_values"]
>>> plt.imshow(img.permute(1, 2, 0))

对于目标检测、语义分割、实例分割和全景分割等任务,ImageProcessor 提供了后处理方法。这些方法将模型的原始输出转换为有意义的预测,如边界框或分割图。

填充

在某些情况下,例如在微调 DETR 时,模型在训练时应用了尺度增强。这可能导致批中的图像大小不同。你可以使用 DetrImageProcessor.pad 以及定义一个自定义的 collate_fn 将图像批处理在一起。

>>> def collate_fn(batch):
...     pixel_values = [item["pixel_values"] for item in batch]
...     encoding = image_processor.pad(pixel_values, return_tensors="pt")
...     labels = [item["labels"] for item in batch]
...     batch = {}
...     batch["pixel_values"] = encoding["pixel_values"]
...     batch["pixel_mask"] = encoding["pixel_mask"]
...     batch["labels"] = labels
...     return batch

多模态

对于涉及多模态输入的任务,你需要一个 processor 来为模型准备数据集。一个 processor 将 tokenizer 和特征提取器这两个处理对象耦合在一起。

加载 LJ Speech 数据集(详见 🤗 Datasets 教程 了解如何加载数据集的更多详情),以了解如何使用处理器进行自动语音识别(ASR):

>>> from datasets import load_dataset

>>> lj_speech = load_dataset("lj_speech", split="train")

对于 ASR,你主要关注 audiotext,所以可以删除其他列:

>>> lj_speech = lj_speech.map(remove_columns=["file", "id", "normalized_text"])

现在查看 audiotext 列:

>>> lj_speech[0]["audio"]
{'array': array([-7.3242188e-04, -7.6293945e-04, -6.4086914e-04, ...,
         7.3242188e-04,  2.1362305e-04,  6.1035156e-05], dtype=float32),
 'path': '/root/.cache/huggingface/datasets/downloads/extracted/917ece08c95cf0c4115e45294e3cd0dee724a1165b7fc11798369308a465bd26/LJSpeech-1.1/wavs/LJ001-0001.wav',
 'sampling_rate': 22050}

>>> lj_speech[0]["text"]
'Printing, in the only sense with which we are at present concerned, differs from most if not from all the arts and crafts represented in the Exhibition'

记住,你应该始终 重新采样 音频数据集的采样率,以与用于预训练模型的数据集的采样率匹配!

>>> lj_speech = lj_speech.cast_column("audio", Audio(sampling_rate=16_000))

AutoProcessor.from_pretrained() 加载一个处理器:

>>> from transformers import AutoProcessor

>>> processor = AutoProcessor.from_pretrained("facebook/wav2vec2-base-960h")
  1. 创建一个函数,将 array 中包含的音频数据处理为 input_values,将 text 进行分词处理为 labels。这些是传递给模型的输入:
>>> def prepare_dataset(example):
...     audio = example["audio"]

...     example.update(processor(audio=audio["array"], text=example["text"], sampling_rate=16000))

...     return example
  1. prepare_dataset 函数应用于一个示例:
>>> prepare_dataset(lj_speech[0])

处理器现在已经添加了 input_valueslabels,采样率也已经正确地降采样为16kHz。现在你可以将处理后的数据集传递给模型!