00227 NLP Course - Time to slice and dice


前言

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

src link: https://huggingface.co/learn/nlp-course/chapter5/3

Operating System: Ubuntu 22.04.4 LTS

参考文档

  1. NLP Course - Time to slice and dice

切割和细分我们的数据

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

在此示例中,我们将使用托管在 UC Irvine Machine Learning Repository 上的药物审查数据集,其中包含患者对各种药物的评论,以及正在治疗的病情和患者满意度的 10 星评级。

首先,我们需要下载并解压数据,这可以通过wget和unzip命令来完成:

!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字符代码,如'。

让我们看看如何使用🤗 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()来标准化所有的状况标签。就像在第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函数遇到了问题!从错误中我们可以推断出,状况列中的一些条目是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条目后,我们可以标准化我们的状况列:

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

成功了!现在我们已经清理了标签,让我们来看看如何清理评论本身。

创建新列

无论何时处理客户评论,一个好的实践是检查每条评论中的单词数量。一条评论可能只有一个词,如“Great!”,也可能是一篇完整的文章,包含成千上万的单词,根据用例的不同,您需要以不同的方式处理这些极端情况。为了计算每条评论中的单词数量,我们将使用一个基于按空格分割每个文本的粗略启发式方法。

让我们定义一个简单的函数,用于计算每条评论中的单词数量:

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个单词的评论。与我们对状况列所做的类似,我们可以通过要求评论长度超过这个阈值来过滤掉非常短的评论:

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函数(批量大小是可配置的,但默认为1000)。例如,前面转义所有HTML的map函数运行了相当长的时间(您可以从进度条中读取所花费的时间)。我们可以通过使用列表推导同时处理多个元素来加速这个过程。

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

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

如果您在笔记本中运行这段代码,您会发现这个命令的执行速度比前一个命令快得多。这并不是因为我们的评论已经被HTML转义了——如果您重新执行上一节的指令(没有batched=True),它将花费与前一次相同的时间。这是因为列表推导通常比在for循环中执行相同的代码要快,而且我们通过同时访问许多元素而不是一个接一个地访问,也获得了一些性能提升。

使用batched=True的Dataset.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),这样您就可以看到您在硬件上得到的数字。

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

Options Fast tokenizer Slow tokenizer
batched=True 10.8s 4min41s
batched=False 59.2s 5min3s

这意味着使用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似乎产生了最好的速度增益。以下是我们使用和不使用多处理得到的数字:

Options Fast tokenizer Slow tokenizer
batched=True 10.8s 4min41s
batched=False 59.2s 5min3s
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任务预处理的一部分来执行这个操作。

💡 在机器学习中,一个示例通常定义为我们将要提供给模型的一组特征。在某些上下文中,这些特征将是数据集中的列集合,但在其他情况下(比如这里和对于问答任务),可以从单个示例中提取多个特征,并且属于单个列。

让我们来看看它是如何工作的!这里我们将对示例进行分词,并将它们截断到最大长度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分词成了不止一个示例)。这对于一个数据集来说是不行的,所以我们需要从旧数据集中移除列,或者使它们与新数据集中的大小相同。我们可以使用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
    })
})

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

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

从数据集到数据帧,再返回数据集

为了启用与各种第三方库之间的转换,🤗数据集提供了一个Dataset.set_format()函数。这个函数只改变数据集的输出格式,所以您可以轻松地切换到另一种格式,而不会影响底层的数据格式,即Apache Arrow。格式化是在原地进行。为了演示,让我们将数据集转换为Pandas:

drug_dataset.set_format("pandas")

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

drug_dataset["train"][:3]

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

train_df = drug_dataset["train"][:]

🚨 在底层,Dataset.set_format()改变了数据集的__getitem__()双下划线方法的返回格式。这意味着当我们要从“pandas”格式的数据集中创建一个新对象,如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()

当我们完成了Pandas分析后,我们总是可以通过使用以下方式使用Dataset.from_pandas()函数来创建一个新的Dataset对象:

from datasets import Dataset

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

✏️ 尝试一下!计算每个药物的平价评分,并将结果存储在新的数据集中。

这总结了我们在🤗 Datasets中可用的各种预处理技术的介绍。为了结束这一部分,让我们创建一个验证集来准备数据集,以便在数据集上训练一个分类器。在这样做之前,我们将把drug_dataset的输出格式从“pandas”重置为“arrow”:

drug_dataset.reset_format()

创建验证集

虽然我们有一个测试集可以用于评估,但在开发过程中,最好是将测试集保持不动,并创建一个单独的验证集。一旦您对模型在验证集上的表现感到满意,您可以在测试集上进行最后的正确性检查。这个过程有助于降低您过度拟合测试集的风险,并部署一个在实际数据上失败的模型。

🤗 Datasets 提供了一个 Dataset.train_test_split() 函数,该函数基于 scikit-learn 中著名的功能。让我们使用它将我们的训练集分割为训练和验证集(我们设置 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 提供了三个主要函数,用于以不同的格式保存您的数据集:

Data format Function
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.json 和 state.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章中的摘要管道生成评论的摘要。

接下来,我们将看看 🤗 Datasets 如何让你在不让你的笔记本电脑崩溃的情况下处理大型数据集!

结语

第二百二十七篇博文写完,开心!!!!

今天,也是充满希望的一天。


文章作者: LuYF-Lemon-love
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 LuYF-Lemon-love !
  目录