NLP 课程文档

是时候切片切块了

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 加载脚本并将 delimiter 参数指定为 load_dataset() 函数中的如下所示来加载这些文件

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循环中执行相同的代码更快,而且我们还通过一次访问大量元素而不是一个一个地访问来提高性能。

第 6 章中,我们将使用batched=TrueDataset.map()对于解锁“快速”分词器的速度至关重要,这些分词器可以快速分词大量文本。例如,要使用快速分词器分词所有药物评论,我们可以使用这样的函数

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.8s 4分钟41秒
batched=False 59.2s 5分钟3秒

这意味着使用带有batched=True选项的快速分词器比不使用批处理的慢速分词器快 30 倍——这真是太棒了!这就是快速分词器在使用AutoTokenizer时成为默认分词器(以及为什么它们被称为“快速”)的主要原因。它们能够实现如此大的加速,因为幕后分词代码是用 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.8s 4分钟41秒
batched=False 59.2s 5分钟3秒
batched=True, num_proc=8 6.52s 41.3s
batched=False, num_proc=8 9.49s 45.2s

对于慢速分词器来说,这些结果要合理得多,但快速分词器的性能也得到了显著提升。但是请注意,情况并非总是如此——对于除 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

让我们为整个训练集创建一个pandas.DataFrame,方法是选择drug_dataset["train"]的所有元素

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 如何帮助你在不占用笔记本电脑的情况下处理海量数据集!