LLM 课程文档

切片和切块的时候到了

Hugging Face's logo
加入 Hugging Face 社区

并获得增强的文档体验

开始入门

切片和切块的时候到了

Ask a Question Open In Colab Open In Studio Lab

大多数时候,您使用的数据不会为训练模型做好完美准备。在本节中,我们将探讨 🤗 Datasets 提供的各种功能来清理您的数据集。

切片和切块我们的数据

与 Pandas 类似,🤗 Datasets 提供了几个函数来操作 DatasetDatasetDict 对象的内容。我们在第 3 章中已经遇到过 Dataset.map() 方法,在本节中,我们将探讨我们可用的其他一些函数。

对于本示例,我们将使用托管在 UC Irvine 机器学习仓库上的药物评论数据集,其中包含患者对各种药物的评论,以及正在治疗的疾病和患者满意度的 10 星评级。

首先我们需要下载并解压数据,这可以使用 wgetunzip 命令来完成

!wget "https://archive.ics.uci.edu/ml/machine-learning-databases/00462/drugsCom_raw.zip"
!unzip drugsCom_raw.zip

由于 TSV 只是 CSV 的变体,它使用制表符而不是逗号作为分隔符,我们可以通过使用 csv 加载脚本并在 load_dataset() 函数中指定 delimiter 参数来加载这些文件,如下所示

from datasets import load_dataset

data_files = {"train": "drugsComTrain_raw.tsv", "test": "drugsComTest_raw.tsv"}
# \t is the tab character in Python
drug_dataset = load_dataset("csv", data_files=data_files, delimiter="\t")

在进行任何类型的数据分析时,一个好的做法是抓取一个小的随机样本,以快速了解您正在处理的数据类型。在 🤗 Datasets 中,我们可以通过链接 Dataset.shuffle()Dataset.select() 函数来创建一个随机样本

drug_sample = drug_dataset["train"].shuffle(seed=42).select(range(1000))
# Peek at the first few examples
drug_sample[:3]
{'Unnamed: 0': [87571, 178045, 80482],
 'drugName': ['Naproxen', 'Duloxetine', 'Mobic'],
 'condition': ['Gout, Acute', 'ibromyalgia', 'Inflammatory Conditions'],
 'review': ['"like the previous person mention, I'm a strong believer of aleve, it works faster for my gout than the prescription meds I take. No more going to the doctor for refills.....Aleve works!"',
  '"I have taken Cymbalta for about a year and a half for fibromyalgia pain. It is great\r\nas a pain reducer and an anti-depressant, however, the side effects outweighed \r\nany benefit I got from it. I had trouble with restlessness, being tired constantly,\r\ndizziness, dry mouth, numbness and tingling in my feet, and horrible sweating. I am\r\nbeing weaned off of it now. Went from 60 mg to 30mg and now to 15 mg. I will be\r\noff completely in about a week. The fibro pain is coming back, but I would rather deal with it than the side effects."',
  '"I have been taking Mobic for over a year with no side effects other than an elevated blood pressure.  I had severe knee and ankle pain which completely went away after taking Mobic.  I attempted to stop the medication however pain returned after a few days."'],
 'rating': [9.0, 3.0, 10.0],
 'date': ['September 2, 2015', 'November 7, 2011', 'June 5, 2013'],
 'usefulCount': [36, 13, 128]}

请注意,我们在 Dataset.shuffle() 中固定了种子以实现可重复性。Dataset.select() 期望一个索引的可迭代对象,因此我们传递了 range(1000) 以从洗牌后的数据集中抓取前 1,000 个示例。从这个样本中,我们已经可以看到数据集中的一些怪异之处

  • Unnamed: 0 列看起来非常像每个患者的匿名 ID。
  • condition 列包含大小写混合的标签。
  • 评论的长度各不相同,并且包含 Python 行分隔符 (\r\n) 以及 HTML 字符代码,如 &\#039;

让我们看看如何使用 🤗 Datasets 来处理这些问题中的每一个。为了测试 Unnamed: 0 列的患者 ID 假设,我们可以使用 Dataset.unique() 函数来验证 ID 的数量是否与每个拆分中的行数匹配

for split in drug_dataset.keys():
    assert len(drug_dataset[split]) == len(drug_dataset[split].unique("Unnamed: 0"))

这似乎证实了我们的假设,因此让我们通过将 Unnamed: 0 列重命名为更易于理解的名称来清理数据集。我们可以使用 DatasetDict.rename_column() 函数一次性重命名两个拆分中的列

drug_dataset = drug_dataset.rename_column(
    original_column_name="Unnamed: 0", new_column_name="patient_id"
)
drug_dataset
DatasetDict({
    train: Dataset({
        features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount'],
        num_rows: 161297
    })
    test: Dataset({
        features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount'],
        num_rows: 53766
    })
})

✏️ 试一试! 使用 Dataset.unique() 函数查找训练集和测试集中唯一药物和疾病的数量。

接下来,让我们使用 Dataset.map() 标准化所有 condition 标签。正如我们在第 3 章中使用分词一样,我们可以定义一个简单的函数,该函数可以应用于 drug_dataset 中每个拆分的所有行

def lowercase_condition(example):
    return {"condition": example["condition"].lower()}


drug_dataset.map(lowercase_condition)
AttributeError: 'NoneType' object has no attribute 'lower'

哦不,我们的 map 函数遇到了问题!从错误中我们可以推断出 condition 列中的某些条目是 None,它们不是字符串,因此无法转换为小写。让我们使用 Dataset.filter() 删除这些行,它的工作方式与 Dataset.map() 类似,并且期望一个函数接收数据集的单个示例。而不是编写像这样的显式函数

def filter_nones(x):
    return x["condition"] is not None

然后运行 drug_dataset.filter(filter_nones),我们可以使用lambda 函数在一行中完成此操作。在 Python 中,lambda 函数是您可以定义而无需显式命名的小函数。它们采用通用形式

lambda <arguments> : <expression>

其中 lambda 是 Python 的特殊关键字之一,<arguments> 是定义函数输入的逗号分隔值列表/集合,<expression> 表示您希望执行的操作。例如,我们可以定义一个简单的 lambda 函数,将数字平方,如下所示

lambda x : x * x

要将此函数应用于输入,我们需要将它和输入都放在括号中

(lambda x: x * x)(3)
9

同样,我们可以通过用逗号分隔它们来定义具有多个参数的 lambda 函数。例如,我们可以计算三角形的面积,如下所示

(lambda base, height: 0.5 * base * height)(4, 8)
16.0

当您想要定义小的、一次性使用的函数时,Lambda 函数非常方便(有关它们的更多信息,我们建议阅读 Andre Burgaud 撰写的优秀Real Python 教程)。在 🤗 Datasets 上下文中,我们可以使用 lambda 函数来定义简单的 map 和 filter 操作,因此让我们使用这个技巧来消除数据集中的 None 条目

drug_dataset = drug_dataset.filter(lambda x: x["condition"] is not None)

删除 None 条目后,我们可以标准化我们的 condition

drug_dataset = drug_dataset.map(lowercase_condition)
# Check that lowercasing worked
drug_dataset["train"]["condition"][:3]
['left ventricular dysfunction', 'adhd', 'birth control']

它工作了!现在我们已经清理了标签,让我们看一下清理评论本身。

创建新列

每当您处理客户评论时,一个好的做法是检查每个评论中的单词数。评论可能只是一个词,例如“太棒了!”,或者是一篇数千字的完整文章,并且根据用例,您需要以不同的方式处理这些极端情况。为了计算每个评论中的单词数,我们将使用一个粗略的启发式方法,基于按空格分隔每个文本。

让我们定义一个简单的函数来计算每个评论中的单词数

def compute_review_length(example):
    return {"review_length": len(example["review"].split())}

与我们的 lowercase_condition() 函数不同,compute_review_length() 返回一个字典,其键与数据集中的列名之一不对应。在这种情况下,当 compute_review_length() 传递给 Dataset.map() 时,它将应用于数据集中的所有行以创建一个新的 review_length

drug_dataset = drug_dataset.map(compute_review_length)
# Inspect the first training example
drug_dataset["train"][0]
{'patient_id': 206461,
 'drugName': 'Valsartan',
 'condition': 'left ventricular dysfunction',
 'review': '"It has no side effect, I take it in combination of Bystolic 5 Mg and Fish Oil"',
 'rating': 9.0,
 'date': 'May 20, 2012',
 'usefulCount': 27,
 'review_length': 17}

正如预期的那样,我们看到一个 review_length 列已添加到我们的训练集中。我们可以使用 Dataset.sort() 对这个新列进行排序,以查看极端值是什么样的

drug_dataset["train"].sort("review_length")[:3]
{'patient_id': [103488, 23627, 20558],
 'drugName': ['Loestrin 21 1 / 20', 'Chlorzoxazone', 'Nucynta'],
 'condition': ['birth control', 'muscle spasm', 'pain'],
 'review': ['"Excellent."', '"useless"', '"ok"'],
 'rating': [10.0, 1.0, 6.0],
 'date': ['November 4, 2008', 'March 24, 2017', 'August 20, 2016'],
 'usefulCount': [5, 2, 10],
 'review_length': [1, 1, 1]}

正如我们怀疑的那样,有些评论只包含一个词,虽然这对于情感分析来说可能还可以,但如果我们想预测疾病,则没有信息量。

🙋 向数据集添加新列的另一种方法是使用 Dataset.add_column() 函数。这允许您将列作为 Python 列表或 NumPy 数组提供,并且在 Dataset.map() 不太适合您的分析的情况下非常方便。

让我们使用 Dataset.filter() 函数删除包含少于 30 个单词的评论。与我们对 condition 列所做的类似,我们可以通过要求评论的长度高于此阈值来过滤掉非常短的评论

drug_dataset = drug_dataset.filter(lambda x: x["review_length"] > 30)
print(drug_dataset.num_rows)
{'train': 138514, 'test': 46108}

如您所见,这已从我们原始的训练集和测试集中删除了大约 15% 的评论。

✏️ 试一试! 使用 Dataset.sort() 函数检查单词数最多的评论。查看文档,了解您需要使用哪个参数按长度降序对评论进行排序。

我们需要处理的最后一件事是评论中 HTML 字符代码的存在。我们可以使用 Python 的 html 模块来转义这些字符,如下所示

import html

text = "I&#039;m a transformer called BERT"
html.unescape(text)
"I'm a transformer called BERT"

我们将使用 Dataset.map() 来转义语料库中的所有 HTML 字符

drug_dataset = drug_dataset.map(lambda x: {"review": html.unescape(x["review"])})

如您所见,Dataset.map() 方法对于处理数据非常有用 - 而且我们甚至还没有触及它可以做的所有事情的表面!

map() 方法的超能力

Dataset.map() 方法接受一个 batched 参数,如果设置为 True,则会导致它一次将一批示例发送到 map 函数(批处理大小是可配置的,但默认为 1,000)。例如,之前转义所有 HTML 的 map 函数运行起来有点慢(您可以从进度条中读取花费的时间)。我们可以通过使用列表推导同时处理多个元素来加快速度。

当您指定 batched=True 时,该函数会收到一个字典,其中包含数据集的字段,但每个值现在都是值列表,而不仅仅是单个值。Dataset.map() 的返回值应该相同:一个字典,其中包含我们想要更新或添加到数据集的字段,以及一个值列表。例如,这是另一种转义所有 HTML 字符的方法,但使用 batched=True

new_drug_dataset = drug_dataset.map(
    lambda x: {"review": [html.unescape(o) for o in x["review"]]}, batched=True
)

如果您在笔记本中运行此代码,您会看到此命令的执行速度比上一个命令快得多。这并不是因为我们的评论已经被 HTML 转义了 - 如果您重新执行上一节中的指令(不带 batched=True),它将花费与之前相同的时间。这是因为列表推导式通常比在 for 循环中执行相同的代码更快,而且我们还可以通过同时访问大量元素而不是逐个访问来获得一些性能提升。

Dataset.map()batched=True 结合使用对于解锁我们在第 6 章中将遇到的“快速”分词器的速度至关重要,它可以快速分词大量文本列表。例如,要使用快速分词器对所有药物评论进行分词,我们可以使用如下函数

from transformers import AutoTokenizer

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


def tokenize_function(examples):
    return tokenizer(examples["review"], truncation=True)

正如您在第 3 章中看到的那样,我们可以将一个或多个示例传递给分词器,因此我们可以使用此函数,无论是否使用 batched=True。让我们借此机会比较不同选项的性能。在笔记本中,您可以通过在要测量的代码行之前添加 %time 来计时单行指令

%time tokenized_dataset = drug_dataset.map(tokenize_function, batched=True)

您还可以通过在单元格的开头放置 %%time 来计时整个单元格。在我们执行此操作的硬件上,它显示此指令为 10.8 秒(它是写在“Wall time”之后的数字)。

✏️ 试一试! 在有和没有 batched=True 的情况下执行相同的指令,然后使用慢速分词器(在 AutoTokenizer.from_pretrained() 方法中添加 use_fast=False)进行尝试,以便您可以看到在您的硬件上获得哪些数字。

以下是我们使用和不使用批处理、使用快速和慢速分词器获得的结果

选项 快速分词器 慢速分词器
batched=True 10.8 秒 4 分 41 秒
batched=False 59.2 秒 5 分 3 秒

这意味着使用带有 batched=True 选项的快速分词器比不带批处理的慢速分词器快 30 倍 - 这真是太棒了!这就是为什么在使用 AutoTokenizer 时快速分词器是默认设置(以及为什么它们被称为“快速”分词器)的主要原因。它们之所以能够实现如此大的加速,是因为在幕后,分词代码是在 Rust 中执行的,Rust 是一种可以轻松并行化代码执行的语言。

并行化也是快速分词器通过批处理实现近 6 倍加速的原因:您无法并行化单个分词操作,但是当您想要同时对大量文本进行分词时,您可以将执行分散到多个进程中,每个进程负责自己的文本。

Dataset.map() 本身也具有一些并行化功能。由于它们没有 Rust 的支持,因此它们不会让慢速分词器赶上快速分词器,但它们仍然可能有所帮助(特别是如果您使用的分词器没有快速版本)。要启用多处理,请使用 num_proc 参数并在您调用 Dataset.map() 时指定要使用的进程数

slow_tokenizer = AutoTokenizer.from_pretrained("bert-base-cased", use_fast=False)


def slow_tokenize_function(examples):
    return slow_tokenizer(examples["review"], truncation=True)


tokenized_dataset = drug_dataset.map(slow_tokenize_function, batched=True, num_proc=8)

您可以稍微尝试一下计时,以确定要使用的最佳进程数;在我们的例子中,8 似乎产生了最佳速度增益。以下是我们使用和不使用多处理获得的数据

选项 快速分词器 慢速分词器
batched=True 10.8 秒 4 分 41 秒
batched=False 59.2 秒 5 分 3 秒
batched=True, num_proc=8 6.52 秒 41.3 秒
batched=False, num_proc=8 9.49 秒 45.2 秒

对于慢速分词器来说,这些结果更加合理,但快速分词器的性能也得到了显着提高。但是请注意,情况并非总是如此 - 对于 8 以外的 num_proc 值,我们的测试表明,在没有该选项的情况下使用 batched=True 会更快。一般来说,我们不建议将 Python 多处理用于带有 batched=True 的快速分词器。

使用 num_proc 加速您的处理通常是一个好主意,只要您使用的函数本身没有进行某种多处理即可。

所有这些功能都浓缩在一个方法中已经非常惊人了,但还有更多!使用 Dataset.map()batched=True,您可以更改数据集中的元素数量。这在许多情况下非常有用,在这些情况下,您想要从一个示例创建多个训练特征,并且我们将需要在第 7 章中进行的几个 NLP 任务的预处理中执行此操作。

💡 在机器学习中,示例通常定义为我们输入到模型中的特征集。在某些上下文中,这些特征将是 Dataset 中的列集,但在其他上下文中(例如此处和对于问答),可以从单个示例中提取多个特征并属于单个列。

让我们看看它是如何工作的!在这里,我们将对我们的示例进行分词,并将它们截断为最大长度 128,但我们将要求分词器返回文本的所有块,而不仅仅是第一个块。这可以使用 return_overflowing_tokens=True 完成

def tokenize_and_split(examples):
    return tokenizer(
        examples["review"],
        truncation=True,
        max_length=128,
        return_overflowing_tokens=True,
    )

让我们在对整个数据集使用 Dataset.map() 之前,在一个示例上测试一下

result = tokenize_and_split(drug_dataset["train"][0])
[len(inp) for inp in result["input_ids"]]
[128, 49]

因此,我们训练集中的第一个示例变成了两个特征,因为它被分词为超过我们指定的最大标记数:第一个长度为 128,第二个长度为 49。现在让我们对数据集的所有元素执行此操作!

tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True)
ArrowInvalid: Column 1 named condition expected length 1463 but got length 1000

哦不!那行不通!为什么不行?查看错误消息会给我们一个线索:其中一列的长度不匹配,一列的长度为 1,463,另一列的长度为 1,000。如果您查看过 Dataset.map() 文档,您可能会记得它是传递给我们正在映射的函数的样本数量;这里这些 1,000 个示例给出了 1,463 个新特征,从而导致形状错误。

问题是我们试图混合两个不同大小的不同数据集:drug_dataset 列将具有一定数量的示例(错误中的 1,000 个),但我们正在构建的 tokenized_dataset 将具有更多(错误消息中的 1,463 个;它超过 1,000 个,因为我们通过使用 return_overflowing_tokens=True 将长评论分词为多个示例)。这对于 Dataset 不起作用,因此我们需要删除旧数据集中的列,或者使它们与新数据集中的列大小相同。我们可以使用 remove_columns 参数来完成前者

tokenized_dataset = drug_dataset.map(
    tokenize_and_split, batched=True, remove_columns=drug_dataset["train"].column_names
)

现在它可以正常工作而不会出错。我们可以通过比较长度来检查我们的新数据集是否比原始数据集具有更多元素

len(tokenized_dataset["train"]), len(drug_dataset["train"])
(206772, 138514)

我们提到过,我们还可以通过使旧列与新列的大小相同来解决长度不匹配问题。为此,我们将需要分词器在设置 return_overflowing_tokens=True 时返回的 overflow_to_sample_mapping 字段。它为我们提供了从新特征索引到其原始样本索引的映射。使用此映射,我们可以将原始数据集中存在的每个键与正确大小的值列表关联起来,方法是将每个示例的值重复多次,就像它生成新特征一样

def tokenize_and_split(examples):
    result = tokenizer(
        examples["review"],
        truncation=True,
        max_length=128,
        return_overflowing_tokens=True,
    )
    # Extract mapping between new and old indices
    sample_map = result.pop("overflow_to_sample_mapping")
    for key, values in examples.items():
        result[key] = [values[i] for i in sample_map]
    return result

我们可以看到它与 Dataset.map() 一起工作,而我们无需删除旧列

tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True)
tokenized_dataset
DatasetDict({
    train: Dataset({
        features: ['attention_mask', 'condition', 'date', 'drugName', 'input_ids', 'patient_id', 'rating', 'review', 'review_length', 'token_type_ids', 'usefulCount'],
        num_rows: 206772
    })
    test: Dataset({
        features: ['attention_mask', 'condition', 'date', 'drugName', 'input_ids', 'patient_id', 'rating', 'review', 'review_length', 'token_type_ids', 'usefulCount'],
        num_rows: 68876
    })
})

我们获得了与之前相同数量的训练特征,但在这里我们保留了所有旧字段。如果您需要在应用模型后进行一些后处理,您可能需要使用此方法。

您现在已经了解了如何使用 🤗 Datasets 以各种方式预处理数据集。虽然 🤗 Datasets 的处理功能将涵盖您的大部分模型训练需求,但在某些时候您可能需要切换到 Pandas 以访问更强大的功能,例如 DataFrame.groupby() 或用于可视化的高级 API。幸运的是,🤗 Datasets 旨在与 Pandas、NumPy、PyTorch、TensorFlow 和 JAX 等库互操作。让我们看看这是如何工作的。

从 Dataset 到 DataFrame 以及返回

为了实现各种第三方库之间的转换,🤗 Datasets 提供了 Dataset.set_format() 函数。此函数仅更改数据集的输出格式,因此您可以轻松切换到另一种格式,而不会影响底层的数据格式,即 Apache Arrow。格式化是就地完成的。为了演示,让我们将我们的数据集转换为 Pandas

drug_dataset.set_format("pandas")

现在,当我们访问数据集的元素时,我们得到的是 pandas.DataFrame 而不是字典

drug_dataset["train"][:3]
patient_id drugName condition review rating date usefulCount review_length
0 95260 Guanfacine adhd "My son is halfway through his fourth week of Intuniv..." 8.0 April 27, 2010 192 141
1 92703 Lybrel birth control "I used to take another oral contraceptive, which had 21 pill cycle, and was very happy- very light periods, max 5 days, no other side effects..." 5.0 December 14, 2009 17 134
2 138000 Ortho Evra birth control "This is my first time using any form of birth control..." 8.0 November 3, 2015 10 89

让我们通过选择 drug_dataset["train"] 的所有元素来为整个训练集创建一个 pandas.DataFrame

train_df = drug_dataset["train"][:]

🚨 在底层,Dataset.set_format() 更改了数据集的 __getitem__() dunder 方法的返回格式。这意味着,当我们想要从 "pandas" 格式的 Dataset 创建一个新对象(如 train_df)时,我们需要对整个数据集进行切片以获得 pandas.DataFrame。您可以自己验证 drug_dataset["train"] 的类型是 Dataset,而与输出格式无关。

从这里我们可以使用我们想要的所有 Pandas 功能。例如,我们可以进行花哨的链接来计算 condition 条目中的类别分布

frequencies = (
    train_df["condition"]
    .value_counts()
    .to_frame()
    .reset_index()
    .rename(columns={"index": "condition", "condition": "frequency"})
)
frequencies.head()
condition frequency
0 birth control 27655
1 depression 8023
2 acne 5209
3 anxiety 4991
4 pain 4744

一旦我们完成了 Pandas 分析,我们始终可以使用 Dataset.from_pandas() 函数创建一个新的 Dataset 对象,如下所示

from datasets import Dataset

freq_dataset = Dataset.from_pandas(frequencies)
freq_dataset
Dataset({
    features: ['condition', 'frequency'],
    num_rows: 819
})

✏️ 试一试! 计算每种药物的平均评分,并将结果存储在一个新的 Dataset 中。

这结束了我们对 🤗 Datasets 中可用的各种预处理技术的介绍。为了完善本节,让我们创建一个验证集,以准备用于训练分类器的数据集。在此之前,我们将 drug_dataset 的输出格式从 "pandas" 重置为 "arrow"

drug_dataset.reset_format()

创建验证集

虽然我们有一个可以用于评估的测试集,但一个好的做法是保持测试集不变,并在开发过程中创建一个单独的验证集。一旦您对模型在验证集上的性能感到满意,您就可以对测试集进行最终的健全性检查。此过程有助于降低您过度拟合测试集并部署在真实世界数据上失败的模型的风险。

🤗 Datasets 提供了一个 Dataset.train_test_split() 函数,该函数基于 scikit-learn 中的著名功能。让我们使用它将我们的训练集拆分为 trainvalidation 拆分(我们设置了 seed 参数以实现可重复性)

drug_dataset_clean = drug_dataset["train"].train_test_split(train_size=0.8, seed=42)
# Rename the default "test" split to "validation"
drug_dataset_clean["validation"] = drug_dataset_clean.pop("test")
# Add the "test" set to our `DatasetDict`
drug_dataset_clean["test"] = drug_dataset["test"]
drug_dataset_clean
DatasetDict({
    train: Dataset({
        features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length', 'review_clean'],
        num_rows: 110811
    })
    validation: Dataset({
        features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length', 'review_clean'],
        num_rows: 27703
    })
    test: Dataset({
        features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length', 'review_clean'],
        num_rows: 46108
    })
})

太棒了,我们现在已经准备好了一个数据集,可以用于训练一些模型!在第 5 节中,我们将向您展示如何将数据集上传到 Hugging Face Hub,但现在让我们通过查看一些您可以将数据集保存在本地计算机上的方法来结束我们的分析。

保存数据集

虽然 🤗 Datasets 将缓存每个下载的数据集和对其执行的操作,但在某些时候您可能想要将数据集保存到磁盘(例如,以防缓存被删除)。如下表所示,🤗 Datasets 提供了三个主要函数,用于以不同的格式保存您的数据集

数据格式 函数
Arrow Dataset.save_to_disk()
CSV Dataset.to_csv()
JSON Dataset.to_json()

例如,让我们以 Arrow 格式保存我们清理后的数据集

drug_dataset_clean.save_to_disk("drug-reviews")

这将创建一个具有以下结构的目录

drug-reviews/
├── dataset_dict.json
├── test
│   ├── dataset.arrow
│   ├── dataset_info.json
│   └── state.json
├── train
│   ├── dataset.arrow
│   ├── dataset_info.json
│   ├── indices.arrow
│   └── state.json
└── validation
    ├── dataset.arrow
    ├── dataset_info.json
    ├── indices.arrow
    └── state.json

在这里我们可以看到每个拆分都与其自己的 dataset.arrow 表相关联,以及 dataset_info.jsonstate.json 中的一些元数据。您可以将 Arrow 格式视为列和行的精美表格,该表格经过优化,可用于构建处理和传输大型数据集的高性能应用程序。

数据集保存后,我们可以使用 load_from_disk() 函数加载它,如下所示

from datasets import load_from_disk

drug_dataset_reloaded = load_from_disk("drug-reviews")
drug_dataset_reloaded
DatasetDict({
    train: Dataset({
        features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length'],
        num_rows: 110811
    })
    validation: Dataset({
        features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length'],
        num_rows: 27703
    })
    test: Dataset({
        features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length'],
        num_rows: 46108
    })
})

对于 CSV 和 JSON 格式,我们必须将每个拆分存储为单独的文件。一种方法是迭代 DatasetDict 对象中的键和值

for split, dataset in drug_dataset_clean.items():
    dataset.to_json(f"drug-reviews-{split}.jsonl")

这会将每个拆分保存在 JSON Lines 格式中,其中数据集中的每一行都存储为 JSON 的单行。这是第一个示例的样子

!head -n 1 drug-reviews-train.jsonl
{"patient_id":141780,"drugName":"Escitalopram","condition":"depression","review":"\"I seemed to experience the regular side effects of LEXAPRO, insomnia, low sex drive, sleepiness during the day. I am taking it at night because my doctor said if it made me tired to take it at night. I assumed it would and started out taking it at night. Strange dreams, some pleasant. I was diagnosed with fibromyalgia. Seems to be helping with the pain. Have had anxiety and depression in my family, and have tried quite a few other medications that haven't worked. Only have been on it for two weeks but feel more positive in my mind, want to accomplish more in my life. Hopefully the side effects will dwindle away, worth it to stick with it from hearing others responses. Great medication.\"","rating":9.0,"date":"May 29, 2011","usefulCount":10,"review_length":125}

然后我们可以使用第 2 节中的技术加载 JSON 文件,如下所示

data_files = {
    "train": "drug-reviews-train.jsonl",
    "validation": "drug-reviews-validation.jsonl",
    "test": "drug-reviews-test.jsonl",
}
drug_dataset_reloaded = load_dataset("json", data_files=data_files)

这就是我们使用 🤗 Datasets 进行数据整理的全部内容!现在我们有了一个清理后的数据集,用于训练模型,以下是一些您可以尝试的想法

  1. 使用第 3 章中的技术训练一个分类器,该分类器可以根据药物评论预测患者病情。
  2. 使用第 1 章中的 summarization 管道生成评论摘要。

接下来,我们将了解 🤗 Datasets 如何使您能够处理巨大的数据集而不会使您的笔记本电脑崩溃!

< > 在 GitHub 上更新