预处理
在您可以在数据集上训练模型之前,需要将其预处理成模型期望的输入格式。无论您的数据是文本、图像还是音频,都需要将其转换为张量批次并进行组装。 🤗 Transformers 提供了一组预处理类来帮助您准备模型数据。在本教程中,您将了解到:
- 对于文本,使用 分词器 将文本转换为令牌序列,创建令牌的数值表示,并将它们组装成张量。
- 对于语音和音频,使用 特征提取器 从音频波形中提取顺序特征并将其转换为张量。
- 对于图像输入,使用 图像处理器 将图像转换为张量。
- 对于多模态输入,使用 处理器 来组合分词器和特征提取器或图像处理器。
AutoProcessor
**始终** 有效,并自动为您的模型选择正确的类,无论您使用的是分词器、图像处理器、特征提取器还是处理器。
在开始之前,请安装 🤗 Datasets,以便您可以加载一些数据集进行实验。
pip install datasets
自然语言处理
文本数据预处理的主要工具是 分词器。分词器根据一组规则将文本分成词元。词元被转换为数字,然后转换为张量,成为模型的输入。模型所需的任何其他输入由分词器添加。
如果你打算使用预训练模型,使用相关的预训练分词器非常重要。这确保文本以与预训练语料库相同的方式分割,并在预训练期间使用相同的对应词元到索引(通常称为词汇表)。
使用 AutoTokenizer.from_pretrained() 方法加载预训练分词器。这将下载模型预训练的词汇表
>>> from transformers import AutoTokenizer
>>> tokenizer = AutoTokenizer.from_pretrained("google-bert/bert-base-cased")
然后将你的文本传递给分词器
>>> 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]}
分词器返回一个包含三个重要项目的字典
- input_ids 是与句子中每个词元相对应的索引。
- attention_mask 指示是否应该注意某个词元。
- token_type_ids 识别当存在多个序列时词元属于哪个序列。
通过解码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]'
如你所见,分词器在句子中添加了两个特殊词元 - CLS
和 SEP
(分类器和分隔符)。并非所有模型都需要特殊词元,但如果需要,分词器会自动为你添加它们。
如果你想预处理多个句子,将它们作为列表传递给分词器
>>> 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]]}
查看 填充和截断 概念指南,了解有关不同填充和截断参数的更多信息。
构建张量
最后,你希望分词器返回实际传递给模型的张量。
将return_tensors
参数设置为pt
(用于 PyTorch)或tf
(用于 TensorFlow)
>>> 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]])}
>>> 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)>}
__call__()
中以不同的方式支持分词器参数。text-2-text-generation
管道仅支持(即传递)truncation
。text-generation
管道支持max_length
、truncation
、padding
和add_special_tokens
。在fill-mask
管道中,分词器参数可以在tokenizer_kwargs
参数(字典)中传递。音频
对于音频任务,你需要一个 特征提取器 来准备你的数据集以供模型使用。特征提取器旨在从原始音频数据中提取特征,并将它们转换为张量。
加载 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
是加载的语音信号 - 可能是重新采样的 - 作为一维数组。path
指向音频文件的位置。sampling_rate
指的是每秒测量语音信号中多少个数据点。
对于本教程,你将使用 Wav2Vec2 模型。查看模型卡,你将了解 Wav2Vec2 是在 16kHz 采样语音音频上预训练的。你的音频数据的采样率必须与用于预训练模型的数据集的采样率匹配。如果你的数据采样率不相同,那么你需要重新采样你的数据。
- 使用 🤗 数据集的 cast_column 方法将采样率上采样到 16kHz
>>> dataset = dataset.cast_column("audio", Audio(sampling_rate=16_000))
- 再次调用
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 数据集(有关如何加载数据集的更多详细信息,请参阅 🤗 数据集教程)以查看如何在计算机视觉数据集上使用图像处理器
使用 🤗 数据集的split
参数仅从训练拆分中加载一小部分样本,因为数据集非常大!
>>> from datasets import load_dataset
>>> dataset = load_dataset("food101", split="train[:100]")
接下来,使用 🤗 数据集的 Image
特性查看图像
>>> dataset[0]["image"]
使用 AutoImageProcessor.from_pretrained() 加载图像处理器
>>> from transformers import AutoImageProcessor
>>> image_processor = AutoImageProcessor.from_pretrained("google/vit-base-patch16-224")
首先,让我们添加一些图像增强。您可以使用任何您喜欢的库,但在本教程中,我们将使用 torchvision 的 transforms
模块。如果您有兴趣使用其他数据增强库,请在 Albumentations 或 Kornia 笔记本 中学习如何使用。
- 这里我们使用
Compose
将几个转换链接在一起 -RandomResizedCrop
和ColorJitter
。请注意,对于调整大小,我们可以从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)])
- 该模型接受
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_processor
的 size
属性。如果您在图像增强期间没有调整图像大小,请省略此参数。默认情况下,ImageProcessor
将处理调整大小。
如果您希望将图像规范化作为增强转换的一部分,请使用 image_processor.image_mean
和 image_processor.image_std
值。
- 然后使用 🤗 Datasetsset_transform 实时应用转换
>>> dataset.set_transform(transforms)
- 现在,当您访问图像时,您会注意到图像处理器已添加
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 的 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
多模态
对于涉及多模态输入的任务,您将需要一个 处理器 来为您的模型准备数据集。处理器将两个处理对象(例如标记器和特征提取器)耦合在一起。
加载 LJ Speech 数据集(有关如何加载数据集的更多详细信息,请参阅 🤗 Datasets 教程),以了解如何将处理器用于自动语音识别 (ASR)
>>> from datasets import load_dataset
>>> lj_speech = load_dataset("lj_speech", split="train")
对于 ASR,您主要关注 audio
和 text
,因此您可以删除其他列
>>> lj_speech = lj_speech.map(remove_columns=["file", "id", "normalized_text"])
现在看一下 audio
和 text
列
>>> 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")
- 创建一个函数来处理包含在
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
- 将
prepare_dataset
函数应用于样本
>>> prepare_dataset(lj_speech[0])
处理器现在已添加 input_values
和 labels
,并且采样率也已正确降采样到 16kHz。现在,您可以将处理后的数据集传递给模型!