转载自:https://huggingface.co/learn/nlp-course/zh-CN/

原中文文档有很多地方翻译的太敷衍了,因此才有此系列文章。

NLP课程(五)- Datasets库

在微调模型时有三个主要步骤:

  1. 从hugs Face Hub加载一个数据集。
  2. 使用Dataset.map()对数据进行预处理。
  3. 加载和计算指标(特征)。

数据集不在 Hub

Data format Loading script Example
CSV & TSV csv load_dataset("csv", data_files="my_file.csv")
Text files text load_dataset("text", data_files="my_file.txt")
JSON & JSON Lines json load_dataset("json", data_files="my_file.jsonl")
Pickled DataFrames pandas load_dataset("pandas", data_files="my_dataframe.pkl")

对于每种数据格式, 我们只需要使用 load_dataset() 函数, 使用 data_files 指定一个或多个文件的路径的参数

1.加载本地数据集

SQuAD_it dataset为例,下载并且解压后可以得到SQuAD_it-train.json和SQuAD_it-test.json

使用load_dataset()函数来加载JSON文件, 我们只需要知道我们是在处理普通的 JSON(类似于嵌套字典)还是 JSON 行(行分隔的 JSON)。像许多问答数据集一样, SQuAD-it 使用嵌套格式,所有文本都存储在 data文件中。这意味着我们可以通过指定参数field来加载数据集,如下所示:

1
2
3
from datasets import load_dataset

squad_it_dataset = load_dataset("json", data_files="SQuAD_it-train.json", field="data")

默认情况下, 加载本地文件会创建一个带有trainDatasetDict 对象。 我们可以通过 squad_it_dataset查看:

1
2
3
4
5
6
7
8
squad_it_dataset

DatasetDict({
train: Dataset({
features: ['title', 'paragraphs'], # 列名
num_rows: 442
})
})

这向我们显示了与训练集相关联的行数和列名。我们可以通过索引到 train 查看示例,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
squad_it_dataset["train"][0] # 训练集的2第一行
{
"title": "Terremoto del Sichuan del 2008",
"paragraphs": [
{
"context": "Il terremoto del Sichuan del 2008 o il terremoto...",
"qas": [
{
"answers": [{"answer_start": 29, "text": "2008"}],
"id": "56cdca7862d2951400fa6826",
"question": "In quale anno si è verificato il terremoto nel Sichuan?",
},
...
],
},
...
],
}

同时加载训练集与测试集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
data_files = {"train": "SQuAD_it-train.json", "test": "SQuAD_it-test.json"}
squad_it_dataset = load_dataset("json", data_files=data_files, field="data")
squad_it_dataset

DatasetDict({
train: Dataset({
features: ['title', 'paragraphs'],
num_rows: 442
})
test: Dataset({
features: ['title', 'paragraphs'],
num_rows: 48
})
})

load_dataset()函数的data_files参数非常灵活并且可以是单个文件路径、文件路径列表或将分割后的名称映射到文件路径的字典。您还可以根据Unix shell使用的规则对与指定模式匹配的文件进行全局定位(例如,您可以通过设置’data_files=“*.JSON”‘将目录中的所有JSON文件作为单个拆分进行全局定位)。有关更多详细信息,请参阅🤗Datasets 文档。

🤗 Datasets实际上支持输入文件的自动解压,所以我们可以跳过使用gzip,直接设置 data_files参数传递压缩文件:

1
2
data_files = {"train": "SQuAD_it-train.json.gz", "test": "SQuAD_it-test.json.gz"}
squad_it_dataset = load_dataset("json", data_files=data_files, field="data")

2.加载远程数据集

如果你在公司担任数据研究员或编码员,那么你要分析的数据集很有可能存储在某个远程服务器上。幸运的是,加载远程文件就像加载本地文件一样简单!我们没有提供本地文件的路径, 而是将load_dataset()data_files参数指向存储远程文件的一个或多个URL。例如, 对于托管在 GitHub 上的 SQuAD-it 数据集, 我们可以将 data_files 指向 SQuAD_it-*.json.gz 的网址,如下所示:

1
2
3
4
5
6
url = "https://github.com/crux82/squad-it/raw/master/"
data_files = {
"train": url + "SQuAD_it-train.json.gz",
"test": url + "SQuAD_it-test.json.gz",
}
squad_it_dataset = load_dataset("json", data_files=data_files, field="data")

切片(数据清洗)

1.切片与切分数据

加州大学欧文分校机器学习存储库药物审查数据集为例,其中包含患者对各种药物的评论,以及正在治疗的病情和患者满意度的 10 星评级。

由于 TSV 只是使用制表符而不是逗号作为分隔符的 CSV 变体,我们可以使用加载csv文件的**load_dataset()**函数并指定分隔符 示例如下:

1
2
3
4
5
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")

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

Dataset.shuffle() 和 **Dataset.select()**见https://huggingface.co/docs/datasets/v2.21.0/process)

1
2
3
4
5
6
7
8
9
10
11
12
13
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 字符代码,如**'**。

Dataset.unique()

为了验证Unnamed: 0 列存储的是患者 ID的猜想,我们可以使用 Dataset.unique() 函数来验证匿名ID 的数量是否与拆分后每部分中的行数匹配:

Dataset.unique()用于去重

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

这似乎证实了我们的假设,所以让我们把 Unnamed: 0 列重命名为患者的id。我们可以使用 **DatasetDict.rename_column()**函数一次性重命名DatasetDict中共有的列:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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.map()标准化所有 condition 标签 .正如我们在第三章中所做的那样,我们可以定义一个简单的函数,可以将该函数应用于drug_dataset 拆分后每部分的所有行:

1
2
3
4
5
6
7
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() 。例如:

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

然后运行 drug_dataset.filter(filter_nones)

lambda 函数:

我们可以在一行中使用lambda 函数.在 Python 中,lambda 函数是您无需明确命名即可使用的微函数(匿名函数)。它们一般采用如下形式:

1
lambda <arguments> : <expression>

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

1
lambda x : x * x

我们需要将要输入给这个函数值括在括号中:

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

类似地,我们可以通过用逗号分隔多个参数来定义 lambda 函数。例如,我们可以按如下方式计算三角形的面积:

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

当您想定义小型、一次性使用的函数时,Lambda 函数非常方便

简单的映射和过滤操作

在🤗 Datasets 中,我们可以使用 lambda 函数来定义简单的映射和过滤操作,所以让我们使用这个技巧来消除我们数据集中的 None 条目:

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

None 条目已删除,我们可以标准化我们的 condition 列:

1
2
3
4
5
drug_dataset = drug_dataset.map(lowercase_condition)
# Check that lowercasing worked
drug_dataset["train"]["condition"][:3]

['left ventricular dysfunction', 'adhd', 'birth control']

2.创建新的数据列

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

  • 按空格分割’review’这一列的句子,计算’review’中包含的词数并将其作为新的列。
  • 删除包含少于 30 个单词的评论
  • 评论中可能存在 HTML 字符代码使用 Python 的html模块取消这些字符的转义

按空格分割’review’这一列的句子,计算’review’中包含的词数并将其作为新的列。

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

Dataset.split()默认按空格进行分割,得到一个字符串列表

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

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

(Dataset.map()默认按行进行操作,且产生的新的列添加到表格列的末尾)

1
2
3
4
5
6
7
8
9
10
11
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}

以使用 **Dataset.sort()**对这个新列进行排序,然后查看极端长度的评论的样子:

1
2
3
4
5
6
7
8
9
10
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]}

正如我们所猜想的那样,一些评论只包含一个词,虽然这对于情感分析来说可能没问题,但如果我们想要预测病情,这些评论可能并不适合。

删除包含少于 30 个单词的评论

让我们使用 Dataset.filter() 功能来删除包含少于 30 个单词的评论。与我们对 condition 列的处理相似,我们可以通过选取评论的长度高于此阈值来过滤掉非常短的评论:

1
2
3
4
drug_dataset = drug_dataset.filter(lambda x: x["review_length"] > 30)
print(drug_dataset.num_rows)

{'train': 138514, 'test': 46108}

评论中可能存在 HTML 字符代码使用 Python 的html模块取消这些字符的转义

如下所示:

1
2
3
4
5
import html

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

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

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

3.加速map方法

Dataset.map()函数时指定 batched=True。

Dataset.map() 方法有一个 batched 参数,如果设置为 True , map 函数将会分批执行所需要进行的操作(批量大小是可配置的,但默认为 1,000)。可以通过使用列表推导同时处理多个元素来加快速度。

当您在使用 **Dataset.map()**函数时指定 batched=True该函数会接收一个包含数据集字段的字典,每个值都是一个列表,而不仅仅是单个值。Dataset.map() 的返回值应该是相同的:一个包含我们想要更新或添加到数据集中的字段的字典,字典的键是要添加的字段,字典的值是结果的列表。

例如,这是使用 batched=True对所有 HTML 字符进行转义的方法。

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

如果您运行此代码,您会看到此命令的执行速度比前一个命令快得多。这是因为列表推导式通常比在同一代码中用 for 循环执行相同的代码更快,并且我们还通过同时访问多个元素而不是一个一个来处理来提高处理的速度。

快速分词器:

例如,要使用快速标记器标记所有药物评论(AutoTokenizer 的默认设置是use_fast=True,也就是快速标记,可以设置use_fast=True来对比速度。他们能够实现这样的加速,因为在底层的标记化代码是在 Rust 中执行的,Rust 是一种可以轻松并行化执行的语言),我们可以使用这样的函数:

1
2
3
4
5
6
7
from transformers import AutoTokenizer

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


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

正如你在第三章所看到的,我们原本就可以将一个或多个示例传递给分词器,因此在batched=True是一个非必须的选项。在笔记本中,您可以在您要测量的代码行之前添加 %time来测试改行运行所消耗的时间:

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

多线程处理

Dataset.map() 也有一些自己的并行化能力。由于它们不受 Rust 的支持,因此慢速分词器的速度赶不上快速分词器,但它们仍然会更快一些(尤其是当您使用没有快速版本的分词器时)。要启用多处理,请在**Dataset.map()**时使用 num_proc 参数并指定要在调用中使用的进程数 :

1
2
3
4
5
6
7
8
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)

PS:除了 num_proc=8,我们的测试表明,使用batched=True而不带有num_proc参数的选项处理起来更快。通常,我们不建议将 Python 多线程处理用于具有batched=True功能的快速标记器 .

从单个示例的一列中提取多个特征

一个例子通常可以为我们的模型提供一组特征。在某些情况下,这些特征会储存在数据集的几个列,但在其他情况下(例如此处的例子和用于问答的数据),可以从单个示例的一列中提取多个特征

让我们来看看它是如何工作的!在这里,我们将标记化我们的示例并将最大截断长度设置128,但我们将要求标记器返回全部文本块,而不仅仅是第一个。这可以用 return_overflowing_tokens=True

1
2
3
4
5
6
7
def tokenize_and_split(examples):
return tokenizer(
examples["review"],
truncation=True,
max_length=128,
return_overflowing_tokens=True,
)

在使用Dataset.map() 正式在整个数据集上开始处理之前让我们先在一个例子上测试一下:

1
2
3
4
result = tokenize_and_split(drug_dataset["train"][0])
[len(inp) for inp in result["input_ids"]]

[128, 49]

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

1
2
3
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。1,000行的”review”给出了 1,463 行的新特征,导致和原本的1000行数据不匹配。

问题出在我们试图混合两个不同大小的不同数据集: drug_dataset 列将有一定数量的元素(我们错误中的 1,000),但是我们正在构建tokenized_dataset 将有更多的元素(错误消息中的 1,463)。这不适用于 Dataset ,因此我们需要从旧数据集中删除列或使它们的大小与新数据集中的大小相同。我们可以用 remove_columns 参数:

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

现在这个过程没有错误。我们可以通过比较长度来检查新数据集的元素是否比原始数据集多得多:

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

长度不匹配的问题

可以通过使旧列与新列的大小相同来处理长度不匹配的问题。

为此,我们可以使用 overflow_to_sample_mapping 字段,当我们设置return_overflowing_tokens=True .它为我们提供了特征到它所产生的样本的映射。使用这个,我们可以将原始数据集中的每个键关联到一个合适大小的值列表中,通过遍历所有的数据来生成新特性:

1
2
3
4
5
6
7
8
9
10
11
12
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()**来进行批处理,这样无需我们删除旧列:

1
2
3
4
5
6
7
8
9
10
11
12
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
})
})

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

4.Datasets和DataFrames的相互转换

Dataset.set_format()

为了实现各种第三方库之间的转换,🤗 Datasets 提供了一个 Dataset.set_format() 功能。此功能可以通过仅更改输出格式的,轻松切换到另一种格式,而不会影响底层数据格式,即 Apache Arrow。格式化会在数据本身上进行。为了演示,让我们将数据集转换为 Pandas:

1
drug_dataset.set_format("pandas")

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

1
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] 的所有元素:

1
train_df = drug_dataset["train"][:]

🚨 在底层,Dataset.set_format() 改变了数据集的 __getitem__() dunder 方法的返回格式。 这意味着当我们想从 "pandas" 格式的 Dataset 中创建像 train_df 这样的新对象时,我们需要对整个数据集进行切片以获得 pandas.DataFrame

无论输出格式如何,您都可以自己验证 drug_dataset["train"] 的类型依然还是 Dataset

从这里我们可以使用我们想要的所有 Pandas 功能。例如,我们可以通过花式链接来计算 condition类之间的分布 :

1
2
3
4
5
6
7
8
9
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 如下:

1
2
3
4
5
6
7
8
from datasets import Dataset

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

Dataset.reset_format()

重置格式

1
drug_dataset.reset_format()

5.创建验证集

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

Dataset.train_test_split()

Datasets提供了一个基于scikit-learn的经典方法Dataset.train_test_split() .让我们用它把我们的训练集分成 trainvalidation (为了可以复现,我们将设置seed的值为一个常量):

1
2
3
4
5
6
drug_dataset_clean = drug_dataset["train"].train_test_split(train_size=0.8, seed=42) #  将原训练集的0.8作为新训练集,0.2作为新测试集
# 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
})
})

6.保存数据集

Datasets 提供了三个主要功能来以不同的格式保存您的数据集:

数据格式 对应的方法
Arrow Dataset.save_to_disk()
CSV Dataset.to_csv()
JSON Dataset.to_json()

Arrow 格式

例如,让我们以 Arrow 格式保存我们清洗过的数据集:

1
drug_dataset_clean.save_to_disk("drug-reviews")

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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

在那里我们可以看到每个部分.arrow表,以及一些元数据数据集信息.json和状态文件保存在一起.您可以将 Arrow 格式视为一个精美的列和行的表格,它针对构建处理和传输大型数据集的高性能应用程序进行了优化。

load_from_disk()

保存数据集后,我们可以使用 load_from_disk() 功能从磁盘读取数据如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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 格式

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

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

这将保存每个拆分都是JSON的标准格式,其中数据集中的每一行都存储为一行 JSON。这是第一个示例:

1
2
!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}

然后我们可以使用第二节学过的技术加载 JSON 文件如下:

1
2
3
4
5
6
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用于大模型

Datasets 旨在克服加载极大数据集的存储限制。它通过将数据集作为内存映射文件来处理,并通过在语料库中流化条目来摆脱硬盘限制, 从而使你避免内存管理问题。

1.Pile

The Pile 是由EleutherAI创建的一个英语文本语料库, 用于训练大规模语言模型。它包含各种各样的数据集, 涵盖科学文章, GitHub 代码库以及过滤的Web文本。训练语料库在14 GB chunks, 并且你也可以下载几个单独的组件。 让我们先来看看 PubMed Abstracts 数据集, 它是PubMed上的1500万篇生物医学出版物的摘要的语料库。 数据集采用JSON行格式 并使用zstandard库进行压缩, 所以我们首先需要先安装zstandard库:

1
!pip install zstandard

接下来, 我们可以使用第二节中所学的加载远程数据集的方法加载数据集:

注意该数据集链接已经不能用了

1
2
3
4
5
6
7
8
9
10
11
from datasets import load_dataset

# This takes a few minutes to run, so go grab a tea or coffee while you wait :)
data_files = "https://the-eye.eu/public/AI/pile_preliminary_components/PUBMED_title_abstracts_2019_baseline.jsonl.zst"
pubmed_dataset = load_dataset("json", data_files=data_files, split="train")
pubmed_dataset

Dataset({
features: ['meta', 'text'],
num_rows: 15518009
})

我们可以看到我们的数据集中有 15,518,009 行和 2 列 — 这是非常多的!

✎ 默认情况下, 🤗 Datasets 会自动解压加载数据集所需的文件。 如果你想保留硬盘空间, 你可以传递 DownloadConfig(delete_extracted=True)download_configload_dataset()参数. 有关更多详细信息, 请参阅文档](https://huggingface.co/docs/datasets/package_reference/builder_classes#datasets.DownloadConfig)。

让我们看看数据集的第一个元素的内容:

1
2
3
pubmed_dataset[0]
{'meta': {'pmid': 11409574, 'language': 'eng'},
'text': 'Epidemiology of hypoxaemia in children with acute lower respiratory infection.\nTo determine the prevalence of hypoxaemia in children aged under 5 years suffering acute lower respiratory infections (ALRI), the risk factors for hypoxaemia in children under 5 years of age with ALRI, and the association of hypoxaemia with an increased risk of dying in children of the same age ...'}

可以看到, 这看起来像是医学文章的摘要。 现在让我们看看我们使用了RAM的多少存储空间来加载数据集!

2.内存映射

在 Python 中测量内存使用情况的一个简单的方法是使用psutil库,它可以使用 pip安装, 如下所示:

1
!pip install psutil

它提供了一个 Process 类,这个类允许我们检查当前进程的内存使用情况, 如下所示:

1
2
3
4
5
6
import psutil

# Process.memory_info is expressed in bytes, so convert to megabytes
print(f"RAM used: {psutil.Process().memory_info().rss / (1024 * 1024):.2f} MB")

RAM used: 5678.33 MB

这里的rss属性是指常驻集的大小, 它是进程在RAM中占用的内存比例。 这个测量结果也包括了 Python 编译器和我们加载的库所使用的内存, 所以实际上用于加载数据集的内存会更小一些。为了比较, 让我们使用 dataset_size 属性看看数据集在磁盘上有多大。由于结果像之前一样用字节表示, 我们需要手动将其转换为GB:

1
2
3
4
5
6
print(f"Number of files in dataset : {pubmed_dataset.dataset_size}")
size_gb = pubmed_dataset.dataset_size / (1024**3) # kb,mb,gb,故为1024的三次方
print(f"Dataset size (cache file) : {size_gb:.2f} GB")

Number of files in dataset : 20979437051
Dataset size (cache file) : 19.54 GB

非常棒 — 尽管它将近20GB, 但我们能够占用很少的RAM空间加载和访问数据集!

✏️ 试试看!subsets中选择一个大于你的笔记本或者台式机的RAM大小的子集, 用 🤗 Datasets加载这个数据集, 并且测量RAM的使用量。 请注意, 要获得准确的测量结果, 你需要在另一个进程中执行这个操作。你可以在 the Pile paper的表一中找到每个子集解压后的大小。

如果你熟悉 Pandas, 这个结果可能会让人感到很意外。因为 Wes Kinney 的著名的经验法则 是你需要的RAM应该是数据集的大小的5倍到10倍。 那么 🤗 Datasets 是如何解决这个内存管理问题的呢? 🤗 Datasets 将每一个数据集看作一个内存映射文件, 它提供了RAM和文件系统存储之间的映射, 该映射允许库访问和操作数据集的元素, 而且无需将其完全加载到内存中。

内存映射文件也一个在多个进程之间共享, 这使得像 Dataset.map()之类的方法可以并行化, 并且无需移动或者赋值数据集。在底层, 这些功能都是由Apache Arrow内存格式和pyarrow库提供的支持, 使得数据加载和处理速度快如闪电。 (更多有关Apache Arrow的详细信息以及与Pandas的比较, 请查看Dejan Simic’s blog post.) 为了更清晰地看到这个过程, 让我们通过迭代PubMed Abstracts数据集中的所有元素来运行一个速度测试小程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import timeit

code_snippet = """batch_size = 1000

for idx in range(0, len(pubmed_dataset), batch_size):
_ = pubmed_dataset[idx:idx + batch_size]
"""

time = timeit.timeit(stmt=code_snippet, number=1, globals=globals())
print(
f"Iterated over {len(pubmed_dataset)} examples (about {size_gb:.1f} GB) in "
f"{time:.1f}s, i.e. {size_gb/time:.3f} GB/s"
)
'Iterated over 15518009 examples (about 19.5 GB) in 64.2s, i.e. 0.304 GB/s'

这里我们使用了 Python的 timeit 模块来测量执行 code_snippet所耗的时间。 你通常能以十分之几GB/s到几GB/s的速度迭代数据集。通过上述的方法就已经能够解决大多数大数据集加载的限制, 但是有时候你不得不使用一个很大的数据集, 它甚至都不能存储在笔记本电脑的硬盘上。例如, 如果我们尝试下载整个 Pile, 我们需要825GB的可用磁盘空间! 为了处理这种情况, 🤗 Datasets 提供了一个流式功能, 这个功能允许我们动态下载和访问元素, 并且不需要下载整个数据集。让我们来看看这个功能是如何工作的。

3.流数据集

要使用数据集流, 你只需要将 streaming=True 参数传递给 load_dataset() 函数。接下来, 让我们再次加载 PubMed Abstracts 数据集, 但是采用流模式:

1
2
3
pubmed_dataset_streamed = load_dataset(
"json", data_files=data_files, split="train", streaming=True
)

与我们在本章其他地方遇到的熟悉的 Dataset 不同, streaming=True 返回的对象是一个 IterableDataset。 顾名思义, 要访问 IterableDataset , 我们需要迭代它。我们可以按照如下方式访问流式数据集的第一个元素:

1
2
3
4
next(iter(pubmed_dataset_streamed))

{'meta': {'pmid': 11409574, 'language': 'eng'},
'text': 'Epidemiology of hypoxaemia in children with acute lower respiratory infection.\nTo determine the prevalence of hypoxaemia in children aged under 5 years suffering acute lower respiratory infections (ALRI), the risk factors for hypoxaemia in children under 5 years of age with ALRI, and the association of hypoxaemia with an increased risk of dying in children of the same age ...'}

IterableDataset.map()

如果您需要在训练期间标记流式数据集中的元素可以使用 IterableDataset.map()进行动态处理。该过程与我们在第三章中标记数据集的过程完全相同, 唯一的区别是输出是逐个返回的:

1
2
3
4
5
6
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("distilbert-base-uncased")
tokenized_dataset = pubmed_dataset_streamed.map(lambda x: tokenizer(x["text"]))
next(iter(tokenized_dataset))
{'input_ids': [101, 4958, 5178, 4328, 6779, ...], 'attention_mask': [1, 1, 1, 1, 1, ...]}

IterableDataset.shuffle()

💡 你可以传递 batched=True 来通过流式加速标记化, 如同我们在上一节看到的那样。它将逐批处理示例; 默认的批量大小为 1,000, 可以使用 batch_size 参数指定批量大小。

你还可以使用 IterableDataset.shuffle() 打乱流式数据集, 但与 Dataset.shuffle() 不同的是这只会打乱预定义 buffer_size 中的元素:

1
2
3
4
5
shuffled_dataset = pubmed_dataset_streamed.shuffle(buffer_size=10_000, seed=42)
next(iter(shuffled_dataset))

{'meta': {'pmid': 11410799, 'language': 'eng'},
'text': 'Randomized study of dose or schedule modification of granulocyte colony-stimulating factor in platinum-based chemotherapy for elderly patients with lung cancer ...'}

IterableDataset.take() 和 IterableDataset.skip()

在这个示例中, 我们从缓冲区的前 10,000 个示例中随机选择了一个示例。一旦访问了一个示例, 它在缓冲区中的位置就会被语料库中的下一个示例填充 (即, 上述案例中的第 10,001个示例)。你还可以使用 IterableDataset.take()IterableDataset.skip() 函数从流式数据集中选择元素, 它的作用类似于 Dataset.select()。例如, 要选择 PubMed Abstracts 数据集的前5个示例, 我们可以执行以下操作:

1
2
3
4
5
6
7
8
9
10
11
12
dataset_head = pubmed_dataset_streamed.take(5)
list(dataset_head)
[{'meta': {'pmid': 11409574, 'language': 'eng'},
'text': 'Epidemiology of hypoxaemia in children with acute lower respiratory infection ...'},
{'meta': {'pmid': 11409575, 'language': 'eng'},
'text': 'Clinical signs of hypoxaemia in children with acute lower respiratory infection: indicators of oxygen therapy ...'},
{'meta': {'pmid': 11409576, 'language': 'eng'},
'text': "Hypoxaemia in children with severe pneumonia in Papua New Guinea ..."},
{'meta': {'pmid': 11409577, 'language': 'eng'},
'text': 'Oxygen concentrators and cylinders ...'},
{'meta': {'pmid': 11409578, 'language': 'eng'},
'text': 'Oxygen supply in rural africa: a personal experience ...'}]

同样, 你可以使用 IterableDataset.skip() 函数将打乱的数据集拆分为训练集和验证集, 如下所示:

1
2
3
4
# Skip the first 1,000 examples and include the rest in the training set
train_dataset = shuffled_dataset.skip(1000)
# Take the first 1,000 examples for the validation set
validation_dataset = shuffled_dataset.take(1000)

interleave_datasets()

让我们用一个常见的任务来进行我们对数据集流的最后探索: 将多个数据集组合在一起创建一个心得语料库。 🤗 Datasets 提供了一个 interleave_datasets() 函数, 它将一个 IterableDataset 对象列表组合为单个的 IterableDataset, 其中新数据集的元素是通过在列表中的对象交替获得的(轮换各选一个)。当你试图组合大型数据集时, 这个函数特别有用, 让我们通过下面这个例子来试着组合 Pile的自由法律数据集,它是来自美国法院的51 GB的法律意见数据集:

1
2
3
4
5
6
7
8
9
10
11
law_dataset_streamed = load_dataset(
"json",
data_files="https://the-eye.eu/public/AI/pile_preliminary_components/FreeLaw_Opinions.jsonl.zst",
split="train",
streaming=True,
)
next(iter(law_dataset_streamed))
{'meta': {'case_ID': '110921.json',
'case_jurisdiction': 'scotus.tar.gz',
'date_created': '2010-04-28T17:12:49Z'},
'text': '\n461 U.S. 238 (1983)\nOLIM ET AL.\nv.\nWAKINEKONA\nNo. 81-1581.\nSupreme Court of United States.\nArgued January 19, 1983.\nDecided April 26, 1983.\nCERTIORARI TO THE UNITED STATES COURT OF APPEALS FOR THE NINTH CIRCUIT\n*239 Michael A. Lilly, First Deputy Attorney General of Hawaii, argued the cause for petitioners. With him on the brief was James H. Dannenberg, Deputy Attorney General...'}

这个数据集足够大, 可以对大多数笔记本电脑的RAM有足够的压力, 但是我们已经能够毫不费力地加载和访问它! 现在我们使用 interleave_datasets() 函数加载来自 FreeLaw 和 PubMed Abstracts 的数据集:

1
2
3
4
5
6
7
8
9
10
11
from itertools import islice
from datasets import interleave_datasets

combined_dataset = interleave_datasets([pubmed_dataset_streamed, law_dataset_streamed])
list(islice(combined_dataset, 2))
[{'meta': {'pmid': 11409574, 'language': 'eng'},
'text': 'Epidemiology of hypoxaemia in children with acute lower respiratory infection ...'},
{'meta': {'case_ID': '110921.json',
'case_jurisdiction': 'scotus.tar.gz',
'date_created': '2010-04-28T17:12:49Z'},
'text': '\n461 U.S. 238 (1983)\nOLIM ET AL.\nv.\nWAKINEKONA\nNo. 81-1581.\nSupreme Court of United States.\nArgued January 19, 1983.\nDecided April 26, 1983.\nCERTIORARI TO THE UNITED STATES COURT OF APPEALS FOR THE NINTH CIRCUIT\n*239 Michael A. Lilly, First Deputy Attorney General of Hawaii, argued the cause for petitioners. With him on the brief was James H. Dannenberg, Deputy Attorney General...'}]

这里我们使用了来自Python的 itertools 模块的 islice() 函数从合并的数据集中选择前两个示例, 并且我们可以看到它们实际上就是两个源数据集中的前两个示例拼在一起形成的:

传输整个Pile

最后, 如果你想流式传输整个825GB的 Pile, 你可以按照如下方式获取所有准备好的文件:

1
2
3
4
5
6
7
8
9
10
base_url = "https://the-eye.eu/public/AI/pile/"
data_files = {
"train": [base_url + "train/" + f"{idx:02d}.jsonl.zst" for idx in range(30)],
"validation": base_url + "val.jsonl.zst",
"test": base_url + "test.jsonl.zst",
}
pile_dataset = load_dataset("json", data_files=data_files, streaming=True)
next(iter(pile_dataset["train"]))
{'meta': {'pile_set_name': 'Pile-CC'},
'text': 'It is done, and submitted. You can play “Survival of the Tastiest” on Android, and on the web...'}

✏️ 试试看! 使用像mc4 或者 oscar这样的大型 Common Crawl 语料库来创建一个流式多语言数据集, 该数据集代表你选择的国家/地区语言的口语比例。例如, 瑞士的四种民族语言分别是德语、法语、意大利语和罗曼什语, 因此你可以尝试根据根据口语比例对Oscar子集进行采用来创建瑞士语料库。

创建自己的数据集

在本节中,我们将向您展示如何创建一个GitHub issues的语料库,GitHub issues通常用于跟踪 GitHub 存储库中的错误或功能。该语料库可用于各种目的,包括:

  • 探索关闭未解决的issue或拉取请求需要多长时间
  • 训练一个多标签分类器可以根据issue的描述(例如,“错误”、“增强”或“issue”)用元数据标记issue
  • 创建语义搜索引擎以查找与用户查询匹配的issue

GitHub issue可以理解为讨论区中的问题以及解答

1.获取数据集

您可以浏览 🤗 Datasets 中的所有issueIssues tab.如以下屏幕截图所示

一个issue,它包含一个标题、一个描述和一组表征该issue的标签。下面的屏幕截图显示了一个示例.

**方法1:**要下载所有存储库的issue,我们将使用GitHub REST API投票Issues endpoint.此节点返回一个 JSON 对象列表,每个对象包含大量字段,其中包括标题和描述以及有关issue状态的元数据等。

**方法2:**下载issue的一种便捷方式是通过 requests 库,这是用 Python 中发出 HTTP 请求的标准方式。您可以通过运行以下的代码来安装库:

1
!pip install requests

安装库后,您通过调用 requests.get() 功能来获取Issues节点。例如,您可以运行以下命令来获取第一页上的第一个Issues:

1
2
3
4
import requests

url = "https://api.github.com/repos/huggingface/datasets/issues?page=1&per_page=1"
response = requests.get(url)

response 对象包含很多关于请求的有用信息,包括 HTTP 状态码:

1
2
3
response.status_code

200

其中一个状态码 200 表示请求成功(您可以在这里找到可能的 HTTP 状态代码列表)。然而,我们真正感兴趣的是有效的信息,由于我们知道我们的issues是 JSON 格式,让我们按如下方式查看所有的信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
response.json()
[{'url': 'https://api.github.com/repos/huggingface/datasets/issues/2792',
'repository_url': 'https://api.github.com/repos/huggingface/datasets',
'labels_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792/labels{/name}',
'comments_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792/comments',
'events_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792/events',
'html_url': 'https://github.com/huggingface/datasets/pull/2792',
'id': 968650274,
'node_id': 'MDExOlB1bGxSZXF1ZXN0NzEwNzUyMjc0',
'number': 2792,
'title': 'Update GooAQ',
'user': {'login': 'bhavitvyamalik',
'id': 19718818,
'node_id': 'MDQ6VXNlcjE5NzE4ODE4',
'avatar_url': 'https://avatars.githubusercontent.com/u/19718818?v=4',
'gravatar_id': '',
'url': 'https://api.github.com/users/bhavitvyamalik',
'html_url': 'https://github.com/bhavitvyamalik',
'followers_url': 'https://api.github.com/users/bhavitvyamalik/followers',
'following_url': 'https://api.github.com/users/bhavitvyamalik/following{/other_user}',
'gists_url': 'https://api.github.com/users/bhavitvyamalik/gists{/gist_id}',
'starred_url': 'https://api.github.com/users/bhavitvyamalik/starred{/owner}{/repo}',
'subscriptions_url': 'https://api.github.com/users/bhavitvyamalik/subscriptions',
'organizations_url': 'https://api.github.com/users/bhavitvyamalik/orgs',
'repos_url': 'https://api.github.com/users/bhavitvyamalik/repos',
'events_url': 'https://api.github.com/users/bhavitvyamalik/events{/privacy}',
'received_events_url': 'https://api.github.com/users/bhavitvyamalik/received_events',
'type': 'User',
'site_admin': False},
'labels': [],
'state': 'open',
'locked': False,
'assignee': None,
'assignees': [],
'milestone': None,
'comments': 1,
'created_at': '2021-08-12T11:40:18Z',
'updated_at': '2021-08-12T12:31:17Z',
'closed_at': None,
'author_association': 'CONTRIBUTOR',
'active_lock_reason': None,
'pull_request': {'url': 'https://api.github.com/repos/huggingface/datasets/pulls/2792',
'html_url': 'https://github.com/huggingface/datasets/pull/2792',
'diff_url': 'https://github.com/huggingface/datasets/pull/2792.diff',
'patch_url': 'https://github.com/huggingface/datasets/pull/2792.patch'},
'body': '[GooAQ](https://github.com/allenai/gooaq) dataset was recently updated after splits were added for the same. This PR contains new updated GooAQ with train/val/test splits and updated README as well.',
'performed_via_github_app': None}]

哇,这是很多信息!我们可以看到有用的字段,例如 标题 , 内容参与的成员issue的描述信息,以及打开issue的GitHub 用户的信息。

如 GitHub文档 中所述,未经身份验证的请求限制为每小时 60 个请求。虽然你可以增加 per_page 查询参数以减少您发出的请求数量,您仍然会遭到任何超过几千个issue的存储库的速率限制。因此,您应该关注 GitHub 的创建个人身份令牌,创建一个个人访问令牌这样您就可以将速率限制提高到每小时 5,000 个请求。获得令牌后,您可以将其包含在请求标头中:

1
2
3
GITHUB_TOKEN = xxx  # Copy your GitHub token here

headers = {"Authorization": f"token {GITHUB_TOKEN}"}

⚠️ 不要与陌生人共享存在GITHUB令牌的笔记本。我们建议您在使用完后将GITHUB令牌删除,以避免意外泄漏此信息。一个更好的做法是,将令牌存储在.env文件中,并使用 python-dotenv library 为您自动将其作为环境变量加载。0

下载所有issue

现在我们有了访问令牌,让我们创建一个可以从 GitHub 存储库下载所有issue的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import time
import math
from pathlib import Path
import pandas as pd
from tqdm.notebook import tqdm


def fetch_issues(
owner="huggingface",
repo="datasets",
num_issues=10_000,
rate_limit=5_000,
issues_path=Path("."),
):
if not issues_path.is_dir():
issues_path.mkdir(exist_ok=True)

batch = []
all_issues = []
per_page = 100 # Number of issues to return per page
num_pages = math.ceil(num_issues / per_page)
base_url = "https://api.github.com/repos"

for page in tqdm(range(num_pages)):
# Query with state=all to get both open and closed issues
query = f"issues?page={page}&per_page={per_page}&state=all"
issues = requests.get(f"{base_url}/{owner}/{repo}/{query}", headers=headers)
batch.extend(issues.json())

if len(batch) > rate_limit and len(all_issues) < num_issues:
all_issues.extend(batch)
batch = [] # Flush batch for next time period
print(f"Reached GitHub rate limit. Sleeping for one hour ...")
time.sleep(60 * 60 + 1)

all_issues.extend(batch)
df = pd.DataFrame.from_records(all_issues)
df.to_json(f"{issues_path}/{repo}-issues.jsonl", orient="records", lines=True)
print(
f"Downloaded all the issues for {repo}! Dataset stored at {issues_path}/{repo}-issues.jsonl"
)

现在我们可以调用 fetch_issues() 批量下载所有issue,避免超过GitHub每小时的请求数限制;结果将存储在repository_name-issues.jsonl文件,其中每一行都是一个 JSON 对象,代表一个issue。让我们使用这个函数从 🤗 Datasets中抓取所有issue:

1
2
# Depending on your internet connection, this can take several minutes to run...
fetch_issues()

下载issue后,我们可以使用我们 section 2新学会的方法在本地加载它们:

1
2
3
4
5
6
issues_dataset = load_dataset("json", data_files="datasets-issues.jsonl", split="train")
issues_dataset
Dataset({
features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'timeline_url', 'performed_via_github_app'],
num_rows: 3019
})

太好了,我们已经从头开始创建了我们的第一个数据集!但是为什么会有几千个issue,而🤗 Datasets存储库中的Issues 选项卡总共却只显示了大约 1,000 个issue🤔?如 GitHub 文档中所述,那是因为我们也下载了所有的拉取请求:

Git Hub的REST API v3认为每个pull请求都是一个issue,但并不是每个issue都是一个pull请求。因此,“Issues”节点可能在响应中同时返回issue和拉取请求。你可以通过pull_request 的 key来辨别pull请求。请注意,从“Issues”节点返回的pull请求的id将是一个issue id。

由于issue和pull request的内容有很大的不同,我们先做一些小的预处理,让我们能够区分它们。

2.清洗数据集

pull_request 列可用于区分issue和拉取请求。让我们随机挑选一些样本,看看有什么不同。我们将使用在第三节, 学习的方法,使用 Dataset.shuffle()Dataset.select() 抽取一个随机样本,然后将 html_urlpull_request 列使用zip函数打包,以便我们可以比较各种 URL:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
sample = issues_dataset.shuffle(seed=666).select(range(3))

# Print out the URL and pull request entries
for url, pr in zip(sample["html_url"], sample["pull_request"]):
print(f">> URL: {url}")
print(f">> Pull request: {pr}\n")
>> URL: https://github.com/huggingface/datasets/pull/850
>> Pull request: {'url': 'https://api.github.com/repos/huggingface/datasets/pulls/850', 'html_url': 'https://github.com/huggingface/datasets/pull/850', 'diff_url': 'https://github.com/huggingface/datasets/pull/850.diff', 'patch_url': 'https://github.com/huggingface/datasets/pull/850.patch'}

>> URL: https://github.com/huggingface/datasets/issues/2773
>> Pull request: None

>> URL: https://github.com/huggingface/datasets/pull/783
>> Pull request: {'url': 'https://api.github.com/repos/huggingface/datasets/pulls/783', 'html_url': 'https://github.com/huggingface/datasets/pull/783', 'diff_url': 'https://github.com/huggingface/datasets/pull/783.diff', 'patch_url': 'https://github.com/huggingface/datasets/pull/783.patch'}

这里我们可以看到,每个pull请求都与各种url相关联,而普通issue只有一个None条目。我们可以使用这一点不同来创建一个新的is_pull_request列通过检查pull_request字段是否为None来区分它们:

1
2
3
issues_dataset = issues_dataset.map(
lambda x: {"is_pull_request": False if x["pull_request"] is None else True}
)

尽管我们可以通过删除或重命名某些列来进一步清理数据集,但在此阶段尽可能保持数据集“原始”状态通常是一个很好的做法,以便它可以在多个应用程序中轻松使用。在我们将数据集推送到 Hugging Face Hub 之前,让我们再添加一些缺少的数据:与每个issue和拉取请求相关的评论。我们接下来将添加它们——你猜对了——我们将依然使用GitHub REST API!

3.扩充数据集

拉取评论

GitHub REST API 提供了一个 评论节点 返回与issue编号相关的所有评论。让我们测试节点以查看它返回的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
issue_number = 2792
url = f"https://api.github.com/repos/huggingface/datasets/issues/{issue_number}/comments"
response = requests.get(url, headers=headers)
response.json()
[{'url': 'https://api.github.com/repos/huggingface/datasets/issues/comments/897594128',
'html_url': 'https://github.com/huggingface/datasets/pull/2792#issuecomment-897594128',
'issue_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792',
'id': 897594128,
'node_id': 'IC_kwDODunzps41gDMQ',
'user': {'login': 'bhavitvyamalik',
'id': 19718818,
'node_id': 'MDQ6VXNlcjE5NzE4ODE4',
'avatar_url': 'https://avatars.githubusercontent.com/u/19718818?v=4',
'gravatar_id': '',
'url': 'https://api.github.com/users/bhavitvyamalik',
'html_url': 'https://github.com/bhavitvyamalik',
'followers_url': 'https://api.github.com/users/bhavitvyamalik/followers',
'following_url': 'https://api.github.com/users/bhavitvyamalik/following{/other_user}',
'gists_url': 'https://api.github.com/users/bhavitvyamalik/gists{/gist_id}',
'starred_url': 'https://api.github.com/users/bhavitvyamalik/starred{/owner}{/repo}',
'subscriptions_url': 'https://api.github.com/users/bhavitvyamalik/subscriptions',
'organizations_url': 'https://api.github.com/users/bhavitvyamalik/orgs',
'repos_url': 'https://api.github.com/users/bhavitvyamalik/repos',
'events_url': 'https://api.github.com/users/bhavitvyamalik/events{/privacy}',
'received_events_url': 'https://api.github.com/users/bhavitvyamalik/received_events',
'type': 'User',
'site_admin': False},
'created_at': '2021-08-12T12:21:52Z',
'updated_at': '2021-08-12T12:31:17Z',
'author_association': 'CONTRIBUTOR',
'body': "@albertvillanova my tests are failing here:\r\n```\r\ndataset_name = 'gooaq'\r\n\r\n def test_load_dataset(self, dataset_name):\r\n configs = self.dataset_tester.load_all_configs(dataset_name, is_local=True)[:1]\r\n> self.dataset_tester.check_load_dataset(dataset_name, configs, is_local=True, use_local_dummy_data=True)\r\n\r\ntests/test_dataset_common.py:234: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\ntests/test_dataset_common.py:187: in check_load_dataset\r\n self.parent.assertTrue(len(dataset[split]) > 0)\r\nE AssertionError: False is not true\r\n```\r\nWhen I try loading dataset on local machine it works fine. Any suggestions on how can I avoid this error?",
'performed_via_github_app': None}]

我们可以看到注释存储在body字段中,所以让我们编写一个简单的函数,通过在response.json()中为每个元素挑选body内容来返回与某个issue相关的所有评论:

1
2
3
4
5
6
7
8
9
def get_comments(issue_number):
url = f"https://api.github.com/repos/huggingface/datasets/issues/{issue_number}/comments"
response = requests.get(url, headers=headers)
return [r["body"] for r in response.json()]


# Test our function works as expected
get_comments(2792)
["@albertvillanova my tests are failing here:\r\n```\r\ndataset_name = 'gooaq'\r\n\r\n def test_load_dataset(self, dataset_name):\r\n configs = self.dataset_tester.load_all_configs(dataset_name, is_local=True)[:1]\r\n> self.dataset_tester.check_load_dataset(dataset_name, configs, is_local=True, use_local_dummy_data=True)\r\n\r\ntests/test_dataset_common.py:234: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\ntests/test_dataset_common.py:187: in check_load_dataset\r\n self.parent.assertTrue(len(dataset[split]) > 0)\r\nE AssertionError: False is not true\r\n```\r\nWhen I try loading dataset on local machine it works fine. Any suggestions on how can I avoid this error?"]

这看起来不错,所以让我们使用 Dataset.map() 方法在我们数据集中每个issue的添加一个comments列:

1
2
3
4
# Depending on your internet connection, this can take a few minutes...
issues_with_comments_dataset = issues_dataset.map(
lambda x: {"comments": get_comments(x["number"])}
)

4.上传数据集

现在我们有了增强的数据集,是时候将它推送到 Hub 以便我们可以与社区共享它了! 上传数据集非常简单:就像 🤗 Transformers 中的模型和分词器一样,我们可以使用 push_to_hub() 方法来推送数据集。 为此,我们需要一个身份验证令牌,它可以通过首先使用 notebook_login() 函数登录到 Hugging Face Hub 来获得:

1
2
3
from huggingface_hub import notebook_login

notebook_login()

这将创建一个小部件,您可以在其中输入您的用户名和密码, API 令牌将保存在~/.huggingface/令牌.如果您在终端中运行代码,则可以改为通过 CLI 登录:

1
huggingface-cli login

完成此操作后,我们可以通过运行下面的代码上传我们的数据集:

1
issues_with_comments_dataset.push_to_hub("github-issues")

之后,任何人都可以通过便捷地提供带有存储库 ID 作为 path 参数的 load_dataset() 来下载数据集:

1
2
3
4
5
6
remote_dataset = load_dataset("lewtun/github-issues", split="train")
remote_dataset
Dataset({
features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'performed_via_github_app', 'is_pull_request'],
num_rows: 2855
})

您还可以使用一些 Git 魔法直接从终端将数据集上传到 Hugging Face Hub。有关如何执行此操作的详细信息,请参阅 🤗 Datasets guide 指南。

5.创建数据集卡片

创建自己的数据集 (huggingface.co)

使用 FAISS 进行语义搜索

1.使用embedding进行语义搜索

基于 Transformer 的语言模型会将文本中的每个标记转换为嵌入向量.事实证明,可以“汇集”各个嵌入向量来创建整个句子、段落或文档(在某些情况下)的向量表示。然后,通过计算每个嵌入之间的点积相似度(或其他一些相似度度量)并返回相似度最大的文档,这些嵌入可用于在语料库中找到相似的文档。在本节中,我们将使用嵌入来开发语义搜索引擎。与基于将查询中的关键字的传统方法相比,这些搜索引擎具有多种优势。

2.加载和准备数据集

我们需要做的第一件事是下载我们的 GitHub issues 数据集,所以让我们像往常那样使用 load_dataset()函数:

1
2
3
4
5
6
7
8
9
10
from datasets import load_dataset

issues_dataset = load_dataset("lewtun/github-issues", split="train")

issues_dataset

Dataset({
features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'performed_via_github_app', 'is_pull_request'],
num_rows: 2855
})

这里我们在load_dataset()中使用了默认的训练集分割,所以它返回一个数据集而不是数据集字典。

过滤掉pull请求

第一项任务是过滤掉pull请求,因为这些请求很少用于回答用户提出的问题,而且会给我们的搜索引擎带来噪声。现在应该很熟悉了,我们可以使用dataset.filter()函数来排除数据集中的这些行。同时,让我们也过滤掉没有注释的行,因为这些行不会是用户提问的答案:

1
2
3
4
5
6
7
8
9
issues_dataset = issues_dataset.filter(
lambda x: (x["is_pull_request"] == False and len(x["comments"]) > 0)
)
issues_dataset

Dataset({
features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'performed_via_github_app', 'is_pull_request'],
num_rows: 771
})

删除其余部分

我们可以看到我们的数据集中有很多列,其中大部分我们不需要构建我们的搜索引擎。从搜索的角度来看,信息量最大的列是 title , body , 和 comments ,而 html_url 为我们提供了一个回到源问题的链接。让我们使用 Dataset.remove_columns() 删除其余部分的功能:

1
2
3
4
5
6
7
8
9
columns = issues_dataset.column_names
columns_to_keep = ["title", "body", "html_url", "comments"]
columns_to_remove = set(columns_to_keep).symmetric_difference(columns)
issues_dataset = issues_dataset.remove_columns(columns_to_remove)
issues_dataset
Dataset({
features: ['html_url', 'title', 'comments', 'body'],
num_rows: 771
})

用问题的标题和正文来扩充每条评论

为了创建我们的嵌入,我们将用问题的标题和正文来扩充每条评论,因为这些字段通常包含有用的上下文信息。因为我们的 comments 列当前是每个问题的评论列表,我们需要“重新组合”列,以便每一条评论都包含一个 (html_url, title, body, comment) 元组。在 Pandas 中,我们可以使用 DataFrame.explode() 函数, 它为类似列表的列中的每个元素创建一个新行,同时复制所有其他列值。为了看到它的实际效果,让我们首先切换到 Pandas的DataFrame 格式:

1
2
issues_dataset.set_format("pandas")
df = issues_dataset[:]

如果我们检查这里的第一行 DataFrame 我们可以看到有四个评论与这个问题相关:

1
2
3
4
5
df["comments"][0].tolist()
['the bug code locate in :\r\n if data_args.task_name is not None:\r\n # Downloading and loading a dataset from the hub.\r\n datasets = load_dataset("glue", data_args.task_name, cache_dir=model_args.cache_dir)',
'Hi @jinec,\r\n\r\nFrom time to time we get this kind of `ConnectionError` coming from the github.com website: https://raw.githubusercontent.com\r\n\r\nNormally, it should work if you wait a little and then retry.\r\n\r\nCould you please confirm if the problem persists?',
'cannot connect,even by Web browser,please check that there is some problems。',
'I can access https://raw.githubusercontent.com/huggingface/datasets/1.7.0/datasets/glue/glue.py without problem...']

我们希望这些评论中的每一条都得到一行。让我们检查是否是这种情况:

1
2
comments_df = df.explode("comments", ignore_index=True)
comments_df.head(4)
html_url title comments body
0 https://github.com/huggingface/datasets/issues/2787 ConnectionError: Couldn’t reach https://raw.githubusercontent.com the bug code locate in :\r\n if data_args.task_name is not None… Hello,\r\nI am trying to run run_glue.py and it gives me this error…
1 https://github.com/huggingface/datasets/issues/2787 ConnectionError: Couldn’t reach https://raw.githubusercontent.com Hi @jinec,\r\n\r\nFrom time to time we get this kind of ConnectionError coming from the github.com website: https://raw.githubusercontent.com Hello,\r\nI am trying to run run_glue.py and it gives me this error…
2 https://github.com/huggingface/datasets/issues/2787 ConnectionError: Couldn’t reach https://raw.githubusercontent.com cannot connect,even by Web browser,please check that there is some problems。 Hello,\r\nI am trying to run run_glue.py and it gives me this error…
3 https://github.com/huggingface/datasets/issues/2787 ConnectionError: Couldn’t reach https://raw.githubusercontent.com I can access https://raw.githubusercontent.com/huggingface/datasets/1.7.0/datasets/glue/glue.py without problem… Hello,\r\nI am trying to run run_glue.py and it gives me this error…

太好了,我们可以看到评论成功被扩充, comments 是包含个人评论的列!现在我们已经完成了 Pandas要完成的部分功能,我们可以快速切换回 Dataset 通过加载 DataFrame 在内存中:

1
2
3
4
5
6
7
8
from datasets import Dataset

comments_dataset = Dataset.from_pandas(comments_df)
comments_dataset
Dataset({
features: ['html_url', 'title', 'comments', 'body'],
num_rows: 2842
})

太好了,我们获取到了几千条的评论!

✏️ Try it out! 看看能不能不用pandas就可以完成列的扩充; 这有点棘手; 你可能会发现 🤗 Datasets 文档的 “Batch mapping” 对这个任务很有用。

创建一个新的comments_length列来存放每条评论的字数。

现在我们每行有一个评论,让我们创建一个新的 comments_length 列来存放每条评论的字数:

1
2
3
comments_dataset = comments_dataset.map(
lambda x: {"comment_length": len(x["comments"].split())}
)

过滤掉简短的评论

我们可以使用这个新列来过滤掉简短的评论,其中通常包括“cc @lewtun”或“谢谢!”之类与我们的搜索引擎无关的内容。虽然无法为过滤器选择的精确数字,但大约大于15 个单词似乎是一个不错的选择:

1
2
3
4
5
6
comments_dataset = comments_dataset.filter(lambda x: x["comment_length"] > 15)
comments_dataset
Dataset({
features: ['html_url', 'title', 'comments', 'body', 'comment_length'],
num_rows: 2098
})

将问题标题、描述和评论连接到一个新的 text 列

稍微清理了我们的数据集后,让我们将问题标题、描述和评论连接到一个新的 text 列。像往常一样,我们可以编写一个简单的函数,并将其传递给 **Dataset.map()**来做到这些 :

1
2
3
4
5
6
7
8
9
10
11
def concatenate_text(examples):
return {
"text": examples["title"]
+ " \n "
+ examples["body"]
+ " \n "
+ examples["comments"]
}


comments_dataset = comments_dataset.map(concatenate_text)

我们终于准备好创建一些嵌入了!让我们来看看。

3.创建文本嵌入

我们在第二章学过,我们可以通过使用 AutoModel 类来完成词嵌入。我们需要做的就是选择一个合适的检查点来加载模型。幸运的是,有一个名为 sentence-transformers 专门用于创建词嵌入。如库中文档所述的,我们这次要实现的是非对称语义搜索,因为我们有一个简短的查询,我们希望在比如问题评论等更长的文档中找到其答案。通过查看模型概述表我们可以发现 multi-qa-mpnet-base-dot-v1 检查点在语义搜索方面具有最佳性能,因此我们将在我们的应用程序中使用它。我们还将使用相同的检查点加载标记器:

1
2
3
4
5
from transformers import AutoTokenizer, AutoModel

model_ckpt = "sentence-transformers/multi-qa-mpnet-base-dot-v1"
tokenizer = AutoTokenizer.from_pretrained(model_ckpt)
model = AutoModel.from_pretrained(model_ckpt)

为了加快嵌入过程,将模型和输入放在 GPU 设备上,所以现在让我们这样做:

1
2
3
4
import torch

device = torch.device("cuda")
model.to(device)

正如我们之前提到的,我们希望将 GitHub 问题语料库中的每个条目表示为单个向量,因此我们需要以某种方式“池化”或平均化我们的标记嵌入。一种流行的方法是在我们模型的输出上执行CLS 池化,我们只获取[CLS]令牌的最后一个隐藏状态。以下函数为我们提供了这样的方法:

1
2
def cls_pooling(model_output):
return model_output.last_hidden_state[:, 0]

接下来,我们将创建一个辅助函数,该函数将标记文档列表,将tensor放在 GPU 上,然后提供给模型,最后对输出使用CLS 池化:

1
2
3
4
5
6
7
def get_embeddings(text_list):
encoded_input = tokenizer(
text_list, padding=True, truncation=True, return_tensors="pt"
)
encoded_input = {k: v.to(device) for k, v in encoded_input.items()}
model_output = model(**encoded_input)
return cls_pooling(model_output)

我们可以通过在我们的语料库中输入第一个文本条目并检查输出维度来测试该函数是否有效:

1
2
3
embedding = get_embeddings(comments_dataset["text"][0])
embedding.shape
torch.Size([1, 768])

太好了,我们已经将语料库中的第一个条目转换为 768 维向量!我们可以用 Dataset.map() 应用我们的 get_embeddings() 函数到我们语料库中的每一行,所以让我们创建一个新的 embeddings 列如下:

1
2
3
embeddings_dataset = comments_dataset.map(
lambda x: {"embeddings": get_embeddings(x["text"]).detach().cpu().numpy()[0]}
)

请注意,我们已经将嵌入转换为 NumPy 数组——这是因为当我们尝试使用 FAISS 索引它们时,🤗 Datasets需要这种格式,我们接下来会这样做。

4.使用 FAISS 进行高效的相似性搜索

指定我们要索引的数据集的哪一列

现在我们有了一个词嵌入数据集,我们需要一些方法来搜索它们。为此,我们将在 🤗 Datasets中使用一种特殊的数据结构,称为 FAISS指数.FAISS (short for Facebook AI Similarity Search) (Facebook AI Similarity Search 的缩写)是一个提供高效算法来快速搜索和聚类嵌入向量的库。FAISS 背后的基本思想是创建一个特殊的数据结构,称为指数。这允许人们找到哪些嵌入词与输入的词嵌入相似。在 🤗 Datasets中创建一个 FAISS 索引很简单——我们使用 Dataset.add_faiss_index() 函数并指定我们要索引的数据集的哪一列:

1
embeddings_dataset.add_faiss_index(column="embeddings")

问题embedding

现在,我们可以使用**Dataset.get_nearest_examples()**函数进行最近邻居查找。让我们通过首先嵌入一个问题来测试这一点,如下所示:

1
2
3
4
question = "How can I load a dataset offline?"
question_embedding = get_embeddings([question]).cpu().detach().numpy()
question_embedding.shape
torch.Size([1, 768])

找到最相似的嵌入

就像文档一样,我们现在有一个 768 维向量表示查询,我们可以将其与整个语料库进行比较以找到最相似的嵌入:

1
2
3
scores, samples = embeddings_dataset.get_nearest_examples(
"embeddings", question_embedding, k=5
)

Dataset.get_nearest_examples() 函数返回一个分数元组,对查询和文档之间的相似度进行排序,以及一组最佳匹配的结果(这里是 5 个)。

收集到pandas.DataFrame

让我们把这些收集到一个 pandas.DataFrame 以便我们可以轻松地对它们进行排序:

1
2
3
4
5
import pandas as pd

samples_df = pd.DataFrame.from_dict(samples)
samples_df["scores"] = scores
samples_df.sort_values("scores", ascending=False, inplace=True)

查看查询与评论的匹配程度

现在我们可以遍历前几行来查看我们的查询与评论的匹配程度:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
for _, row in samples_df.iterrows():
print(f"COMMENT: {row.comments}")
print(f"SCORE: {row.scores}")
print(f"TITLE: {row.title}")
print(f"URL: {row.html_url}")
print("=" * 50)
print()
"""
COMMENT: Requiring online connection is a deal breaker in some cases unfortunately so it'd be great if offline mode is added similar to how `transformers` loads models offline fine.

@mandubian's second bullet point suggests that there's a workaround allowing you to use your offline (custom?) dataset with `datasets`. Could you please elaborate on how that should look like?
SCORE: 25.505046844482422
TITLE: Discussion using datasets in offline mode
URL: https://github.com/huggingface/datasets/issues/824
==================================================

COMMENT: The local dataset builders (csv, text , json and pandas) are now part of the `datasets` package since #1726 :)
You can now use them offline
\`\`\`python
datasets = load_dataset("text", data_files=data_files)
\`\`\`

We'll do a new release soon
SCORE: 24.555509567260742
TITLE: Discussion using datasets in offline mode
URL: https://github.com/huggingface/datasets/issues/824
==================================================

COMMENT: I opened a PR that allows to reload modules that have already been loaded once even if there's no internet.

Let me know if you know other ways that can make the offline mode experience better. I'd be happy to add them :)

I already note the "freeze" modules option, to prevent local modules updates. It would be a cool feature.

----------

> @mandubian's second bullet point suggests that there's a workaround allowing you to use your offline (custom?) dataset with `datasets`. Could you please elaborate on how that should look like?

Indeed `load_dataset` allows to load remote dataset script (squad, glue, etc.) but also you own local ones.
For example if you have a dataset script at `./my_dataset/my_dataset.py` then you can do
\`\`\`python
load_dataset("./my_dataset")
\`\`\`
and the dataset script will generate your dataset once and for all.

----------

About I'm looking into having `csv`, `json`, `text`, `pandas` dataset builders already included in the `datasets` package, so that they are available offline by default, as opposed to the other datasets that require the script to be downloaded.
cf #1724
SCORE: 24.14896583557129
TITLE: Discussion using datasets in offline mode
URL: https://github.com/huggingface/datasets/issues/824
==================================================

COMMENT: > here is my way to load a dataset offline, but it **requires** an online machine
>
> 1. (online machine)
>

import datasets

data = datasets.load_dataset(…)

data.save_to_disk(/YOUR/DATASET/DIR)

1
2
3
4
5

2. copy the dir from online to the offline machine

3. (offline machine)

import datasets

data = datasets.load_from_disk(/SAVED/DATA/DIR)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31



HTH.


SCORE: 22.893993377685547
TITLE: Discussion using datasets in offline mode
URL: https://github.com/huggingface/datasets/issues/824
==================================================

COMMENT: here is my way to load a dataset offline, but it **requires** an online machine
1. (online machine)
\`\`\`
import datasets
data = datasets.load_dataset(...)
data.save_to_disk(/YOUR/DATASET/DIR)
\`\`\`
2. copy the dir from online to the offline machine
3. (offline machine)
\`\`\`
import datasets
data = datasets.load_from_disk(/SAVED/DATA/DIR)
\`\`\`

HTH.
SCORE: 22.406635284423828
TITLE: Discussion using datasets in offline mode
URL: https://github.com/huggingface/datasets/issues/824
==================================================
"""

我们的第二个搜索结果似乎与查询相符。