NLP 课程文档

是时候切片和切块了

Hugging Face's logo
加入 Hugging Face 社区

并获得增强型文档体验

开始

是时候切片和切块了

Ask a Question Open In Colab Open In Studio Lab

大多数情况下,您使用的数据不会完美地准备好用于训练模型。在本节中,我们将探索 🤗 Datasets 提供的各种功能来清理您的数据集。

切片和切块我们的数据

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

对于此示例,我们将使用药物评论数据集,该数据集托管在加州大学欧文分校机器学习库上,其中包含患者对各种药物的评论,以及正在治疗的疾病和患者满意度的 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) 以从洗牌后的数据集中获取前 1000 个示例。从这个样本中,我们已经可以看到我们数据集中的一些怪癖

  • 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'

哦不,我们在映射函数中遇到了问题!从错误中我们可以推断出 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 函数定义简单的映射和过滤操作,因此让我们使用此技巧来消除数据集中 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,它会将一批示例一次性发送到映射函数(批大小是可配置的,但默认为 1,000)。例如,之前取消转义所有 HTML 的映射函数运行需要一点时间(你可以从进度条中读取花费的时间)。我们可以通过同时处理多个元素使用列表推导来加快速度。

当你指定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循环中执行相同的代码更快,而且我们还可以通过同时访问大量元素而不是逐个访问来获得一些性能提升。

使用batched=TrueDataset.map()对于释放我们在第 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=Truenum_proc=8 6.52 秒 41.3 秒
batched=Falsenum_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]
病人ID 药物名称 病情 评论 评分 日期 有用计数 评论长度
0 95260 Guanfacine 多动症 "我的儿子正在服用 Intuniv 的第四周..." 8.0 2010年4月27日 192 141
1 92703 Lybrel 避孕 "我以前服用另一种口服避孕药,它的周期为 21 天,并且非常满意 - 月经量很少,最多 5 天,没有其他副作用..." 5.0 2009年12月14日 17 134
2 138000 Ortho Evra 避孕 "这是我第一次使用任何形式的避孕措施..." 8.0 2015年11月3日 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()
病情 频率
0 避孕 27655
1 抑郁症 8023
2 痤疮 5209
3 焦虑 4991
4 疼痛 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 如何使您能够处理海量数据集而不会让您的笔记本电脑崩溃!