将 MinIO 集成到 Hugging Face 数据集

Integrating MinIO with Hugging Face Datasets

Hugging Face 的 DatasetDict 类是 Datasets 库的一部分,旨在使处理为 Hugging Face Hub 上的任何模型设计的数据集变得高效。顾名思义,DatasetDict 类是一个数据集字典。理解从这个类创建的对象的最佳方法是查看一个简短的代码示例。以下代码将从 Hugging Face Hub 加载 emotions 数据集到 DatasetDict 对象中。我在之前关于 微调特征提取 的两篇文章中介绍了这个数据集。

from datasets import load_dataset
emotions = load_dataset('emotion')
print(type(emotions))
print(type(emotions['train']))
print(emotions)

输出

<class 'datasets.dataset_dict.DatasetDict'> 
<class 'datasets.arrow_dataset.Dataset'> 
DatasetDict({ 
  train: Dataset({ 
    features: ['text', 'label'], 
    num_rows: 16000 }) 
  validation: Dataset({ 
    features: ['text', 'label'], 
    num_rows: 2000 }) 
  test: Dataset({ 
    features: ['text', 'label'], 
    num_rows: 2000 }) })

将单个字符串参数传递给 load_dataset 函数意味着您想要从 Hugging Face Hub 加载数据集。它知道如何连接到服务器。不需要任何连接信息。

通过查看输出,我们可以立即发现该对象正在保存我们的训练集、验证集和测试集。还有一些元数据,例如每个集合的特征和行数。在学习新库时,我喜欢使用的一个技巧是打印新对象的类型。我在输出的前两行中执行了此操作。顶级对象是 DatasetDict,每个拆分都是一个 Dataset 对象。

虽然看起来很不错,但有人可能会质疑——“这是过度设计吗?”毕竟,Pandas DataFrame 和 Numpy 数组已经成为处理 ML 数据的事实标准。另一个可能会出现在你脑海中的问题是,“如何将我的数据(不能存在于 Hugging Face 的 Hub 中)加载到 DatasetDict 对象中呢?”

在这篇文章中,我将解决这些问题。我将从查看 DatasetDict 对象的特性和优势开始。然后,我将展示几种将 MinIO 中的数据加载到 DatasetDict 对象中的方法。具体来说,我想要为所有希望将数据发送到 Transformer(用于生成式 AI 的大型语言模型)的人启用以下数据流,无论其目的是训练还是推理。该流程从 MinIO 开始。然后,数据将加载到 DatasetDict 对象中,从那里它可以被 Hugging Face 的 Hub 中的任何 Transformer 使用——假设您拥有适合手头任务的正确数据和模型。

数据集字典的特性和优势

首先要理解的是,Pandas DataFrame 和 Numpy 数组早于 Transformer(用于生成式 AI 的大型语言模型)。因此,虽然它们在标准数据操作方面非常有用,但 DatasetDict 对象在某些方面做得更好,因为 DatasetDict 是专门为大型语言模型而设计的。第一个就是对文本进行分词。请考虑以下代码,摘录自我的 微调特征提取 文章。

from transformers import AutoTokenizer
from transformers.tokenization_utils_base import BatchEncoding

model_check_point = 'distilbert-base-uncased'
tokenizer = AutoTokenizer.from_pretrained(model_check_point)

def tokenize(batch) -> BatchEncoding:
  out = tokenizer(batch['text'], padding=True, truncation=True, return_tensors='pt')
  return out

emotions_encoded = emotions.map(tokenize, batched=True, batch_size=None)
emotions_encoded['train'][0].keys()

输出

dict_keys(['text', 'label', 'input_ids', 'attention_mask'])

使用短短几行代码,我就可以获取 Hugging Face Hub 中每个 Transformer 捆绑的 Tokenizer,并使用 DatasetDict 的 map 函数将它应用于训练集、验证集和测试集。

另一个优势是,DatasetDict 对象与框架无关。仔细观察 tokenizer 函数,你会看到 return_tensor 参数设置为“pt”,这表示返回的张量应该是 Pytorch 张量。

最后一个优势(我将在本文后面演示)是延迟加载。在训练大型语言模型时,你会拥有一个无法放入内存的训练集。DatasetDict 对象具有允许将大型训练集缓存在本地,以便只加载当前训练批次所需的样本的功能。

详细调查将会发现更多宝贵的工具,这些工具可以节省 Transformer 的编码时间。但是,在本节中,我重点介绍了三个最显著的优势:映射、框架无关性和延迟加载。

接下来的部分将介绍组织将无法存在于云端中的自定义数据加载到 DatasetDict 对象中的三种方法。

使用 MinIO SDK

如果你已经有一个使用 MinIO SDK 构建的可重用函数模块,那么很有可能你有一个 get_object 函数可以将对象下载到文件。如果你没有一个带有可重用函数的实用程序模块来访问 MinIO 中的 ML 数据,请下载本文的代码示例。其中有一个 utils.py 模块,其中包含几个用于获取和发送 MinIO 数据的实用程序。

假设你有一个 MinIO 存储桶,其中包含三个用于训练、验证和测试的产品评论信息的 对象,如以下屏幕截图所示。

以下代码展示了如何使用 get_object 函数下载这些对象并将它们加载到 DatasetDict 对象中。

from utils import get_object
data_files = {}
data_files['train'] = 'reviews-train.csv'
data_files['validation'] = 'reviews-validation.csv'
data_files['test'] = 'reviews-test.csv'

for split in data_files.keys():
  object_info = get_object(bucket_name, data_files[split], data_files[split])

reviews = load_dataset('csv', data_files=data_files)
print(type(reviews))
print(type(reviews['train']))
print(reviews)

输出结果如下。注意行数和列名与从 Hub 下载的数据集相同。

<class 'datasets.dataset_dict.DatasetDict'> 
<class 'datasets.arrow_dataset.Dataset'> 
DatasetDict({ 
  train: Dataset({ 
    features: ['text', 'label'], 
    num_rows: 16000 }) 
  validation: Dataset({ 
    features: ['text', 'label'], 
    num_rows: 2000 }) 
  test: Dataset({ 
    features: ['text', 'label'], 
    num_rows: 2000 
  }) 
})

load_dataset() 函数的参数与从 Hub 下载时所见大不相同。在这里,我们必须首先指定文件格式。在我们的演示中它是 CSV,但也支持 JSON 和 Parquet。您还需要发送一个字典,该字典标识拆分(训练、验证或测试),并且对于每个拆分,必须指定一个文件指针。

使用 S3 接口

如果您不想使用 MinIO SDK,而是希望将标准化到 S3 接口上,这没问题。下面的代码将使用标准的 s3fs 库,该库可以与任何 S3 对象存储进行交互。

from s3fs import S3FileSystem

data_files = {}
data_files['train'] = 'reviews-train.csv'
data_files['validation'] = 'reviews-validation.csv'
data_files['test'] = 'reviews-test.csv'

url = 'http://localhost:9000'
bucket_name = 'review-data'

# 加载凭据和连接信息。
with open('credentials.json') as f:
  credentials = json.load(f)

s3 = S3FileSystem(key=credentials['accessKey'], secret=credentials['secretKey'],              

                  endpoint_url=url, use_ssl=False)

for split in data_files.keys():
  object_name = f'{bucket_name}/{data_files[split]}'
  s3.download(object_name, data_files[split])

reviews = load_dataset('csv', data_files=data_files)
print(type(reviews))
print(type(reviews['train']))
print(reviews)

输出与之前使用 MinIO SDK 的技术相同,因此为了简洁起见省略了。此外,load_dataset() 函数的使用也是相同的。

对大型数据集使用 Python 生成器

如果您正在使用 LLM,并且正在对自定义文档语料库上的现有 LLM 进行微调或从头开始训练,那么您将处理一个无法放入内存的训练集。例如,考虑以下 MinIO 存储桶,其中每个对象都是尼科洛·马基雅维利《君主论》中的一段话。

假设您想创建一个能够回答深刻哲学问题的 LLM。我们需要一种能够绕过内存限制的技术来使用这些数据训练 LLM。from_generator() 方法是使用 Python 生成器创建数据集最节省内存的方式。(您可以在这里学习 Python 生成器。)数据集是逐渐生成到磁盘上的,然后进行内存映射。下面的代码演示了如何实现。在下面的代码中,get_object_list() 和 get_text() 函数是 util.py 模块的一部分,可以在本文的代码示例中找到。

from datasets.arrow_dataset import Dataset
from utils import get_object_list, get_text

bucket_name = 'philosophy-corpus'

def document_generator(bucket_name: str) -> Dict:
  object_list = []

  if len(object_list) == 0:
    bucket_name = 'philosophy-corpus'
    object_list = get_object_list(bucket_name)

  for index in range(0, len(object_list)):
    yield {'text': get_text(bucket_name, object_list[index])}


ds = Dataset.from_generator(document_generator,
                            cache_dir='./.cache/huggingface/datasets',
                            gen_kwargs={'bucket_name': bucket_name})

print(type(ds))
print(ds[6])

输出

 

{'text': '*** START OF THE PROJECT GUTENBERG EBOOK THE PRINCE ***'}

from_generator() 函数接受一个 Python 生成器函数,以及一个用于缓存数据的目录,该目录最终将被内存映射。 gen_kwargs 参数是一个字典,用于传递给生成器的值 - 在这种情况下,它是我们的 MinIO 存储桶名称。

请注意输出结果表明该技术将填充一个 Dataset 对象,而不是 DatasetDict 对象。如果您需要一个 DatasetDict 对象,则可以执行以下操作。

from datasets.dataset_dict import DatasetDict

dd = DatasetDict({'train': ds})
dd

输出

DatasetDict({ 

  train: Dataset({ 

    features: ['text'], 

    num_rows: 429 

  }) 

})

缺失了什么?

对于可以放入内存的数据集,直接从 MinIO 流式传输到 DatasetDict 对象,而无需将数据复制到临时文件,会更高效。在上面的示例中,当使用 MinIO SDK 和 S3 库时,数据会保存到临时文件,然后 DatasetDict 对象会被发送到相应的文件的路径。

事实证明,Hugging Face 的团队已经将这个功能列入了他们的待办事项。你可以在我的 讨论 中了解更多信息,这是我与他们关于这个请求的讨论。

总结

在这篇文章中,我重点介绍了 Hugging Face 的 Datasets 库的功能和优势,该库包含 DatasetDict 类,该类需要与 Hugging Face Hub 中的 transformers 交互。这些优势包括映射(用于分词)、对所有主要框架的支持以及对无法放入内存的大型数据集的延迟加载。

从那里,我展示了三种将企业数据(或无法驻留在云中的数据)从 MinIO 获取到 DatasetDict 对象的方法。目前,可以使用三种技术:MinIO SDK、S3 接口和大型数据集的延迟加载。

最后,我讨论了希望在 Dataset 库中实现的一个未来功能,它将允许 DatasetDict 对象通过流加载 - 无需中间文件。

如果你有任何问题,请通过 hello@min.io 与我们联系,或者加入我们 通用 Slack 频道 的讨论。