LLM 课程文档
创建您自己的数据集
并获得增强的文档体验
开始使用
创建您自己的数据集
有时,您构建 NLP 应用程序所需的数据集并不存在,因此您需要自己创建它。在本节中,我们将向您展示如何创建一个 GitHub issues 语料库,该语料库通常用于跟踪 GitHub 仓库中的错误或功能。该语料库可用于各种目的,包括
- 探索关闭开放问题或拉取请求所需的时间
- 训练一个*多标签分类器*,可以根据问题的描述(例如,“bug”、“增强”或“问题”)为问题添加元数据标签
- 创建一个语义搜索引擎来查找与用户查询匹配的问题
这里我们将专注于创建语料库,在下一节中我们将处理语义搜索应用程序。为了保持元数据,我们将使用与流行开源项目 🤗 Datasets 相关的 GitHub 问题!让我们看看如何获取数据并探索这些问题中包含的信息。
获取数据
您可以通过导航到 🤗 Datasets 仓库的 Issues 选项卡 来查找所有问题。如下面截图所示,在撰写本文时,有 331 个开放问题和 668 个已关闭问题。

如果您点击其中一个问题,您会发现它包含标题、描述和一组描述问题的标签。下面的截图显示了一个示例。

要下载仓库的所有问题,我们将使用 GitHub REST API 来轮询 Issues
端点。该端点返回一个 JSON 对象列表,每个对象包含大量字段,其中包括标题和描述以及有关问题状态等的元数据。
下载问题的一种便捷方法是使用 requests
库,它是 Python 中进行 HTTP 请求的标准方式。您可以通过运行以下命令安装该库
!pip install requests
安装库后,您可以通过调用 requests.get()
函数向 Issues
端点发出 GET 请求。例如,您可以运行以下命令来检索第一页上的第一个问题
import requests
url = "https://api.github.com/repos/huggingface/datasets/issues?page=1&per_page=1"
response = requests.get(url)
response
对象包含大量有关请求的有用信息,包括 HTTP 状态码
response.status_code
200
其中 200
状态表示请求成功(您可以在此处找到可能的 HTTP 状态码列表)。不过,我们真正感兴趣的是*有效负载*,它可以以各种格式访问,例如字节、字符串或 JSON。由于我们知道问题采用 JSON 格式,因此我们按如下方式检查有效负载
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}]
哇,好多信息啊!我们可以看到有用的字段,如 title
、body
和 number
描述了问题,以及有关打开该问题的 GitHub 用户的信息。
✏️ 试试看!点击上面 JSON 有效负载中的几个 URL,了解每个 GitHub 问题链接到哪种类型的信息。
正如 GitHub 文档中所述,未经身份验证的请求每小时限制为 60 个。尽管您可以增加 per_page
查询参数以减少您发出的请求数量,但对于任何拥有数千个问题以上的仓库,您仍然会达到速率限制。因此,您应该按照 GitHub 的说明创建一个*个人访问令牌*,以便将速率限制提高到每小时 5,000 个请求。一旦您拥有令牌,您可以将其作为请求头的一部分包含在内
GITHUB_TOKEN = xxx # Copy your GitHub token here
headers = {"Authorization": f"token {GITHUB_TOKEN}"}
⚠️ 请勿分享粘贴了 GITHUB_TOKEN
的笔记本。我们建议您在执行完最后一个单元格后删除它,以避免意外泄露此信息。更好的是,将令牌存储在 *.env* 文件中,并使用 python-dotenv
库将其自动加载为环境变量。
现在我们有了访问令牌,让我们创建一个可以从 GitHub 仓库下载所有问题的函数
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()
时,它将批量下载所有问题,以避免超出 GitHub 每小时的请求限制;结果将存储在 *repository_name-issues.jsonl* 文件中,其中每行是一个表示问题 JSON 对象。让我们使用此函数从 🤗 Datasets 中获取所有问题
# Depending on your internet connection, this can take several minutes to run...
fetch_issues()
问题下载完成后,我们可以使用 第 2 节 中新学到的技能将其本地加载
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
})
太棒了,我们从头创建了第一个数据集!但是,为什么当 🤗 Datasets 仓库的 Issues 选项卡 总共只显示大约 1,000 个问题时,却有数千个问题 🤔?正如 GitHub 文档所述,那是因为我们也下载了所有拉取请求
GitHub 的 REST API v3 将每个拉取请求视为一个问题,但并非每个问题都是拉取请求。因此,“Issues”端点可能会在响应中返回问题和拉取请求。您可以通过
pull_request
键识别拉取请求。请注意,从“Issues”端点返回的拉取请求的id
将是问题 id。
由于问题和拉取请求的内容差异很大,因此让我们进行一些小的预处理,以便能够区分它们。
清理数据
上述 GitHub 文档片段告诉我们,pull_request
列可用于区分问题和拉取请求。让我们随机抽取一些样本来看看有什么区别。正如我们在第 3 节中所做的,我们将链式使用 Dataset.shuffle()
和 Dataset.select()
来创建一个随机样本,然后压缩 html_url
和 pull_request
列,以便我们可以比较各种 URL
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'}
这里我们可以看到,每个拉取请求都与各种 URL 相关联,而普通问题则有一个 None
条目。我们可以使用这个区别来创建一个新的 is_pull_request
列,该列检查 pull_request
字段是否为 None
issues_dataset = issues_dataset.map(
lambda x: {"is_pull_request": False if x["pull_request"] is None else True}
)
✏️ 试试看!计算 🤗 Datasets 中关闭问题所需的平均时间。您可能会发现 Dataset.filter()
函数在过滤掉拉取请求和开放问题方面很有用,并且您可以使用 Dataset.set_format()
函数将数据集转换为 DataFrame
,以便轻松操作 created_at
和 closed_at
时间戳。作为额外加分项,计算关闭拉取请求所需的平均时间。
虽然我们可以通过删除或重命名一些列来进一步清理数据集,但在此阶段将数据集保持尽可能“原始”通常是一个好习惯,这样它就可以很容易地用于多个应用程序。
在我们把数据集推送到 Hugging Face Hub 之前,我们先处理一下缺失的内容:与每个问题和拉取请求相关的评论。接下来,我们将通过——你猜对了——GitHub REST API 来添加它们!
扩充数据集
如下面的截图所示,与问题或拉取请求相关的评论提供了丰富的信息来源,特别是如果我们有兴趣构建一个搜索引擎来回答用户关于库的查询。

GitHub REST API 提供了一个 Comments
端点,该端点返回与问题编号关联的所有评论。让我们测试该端点,看看它返回了什么
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
内容中选择,返回与问题相关的所有评论
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()
为数据集中的每个问题添加一个新的 comments
列
# 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"])}
)
最后一步是将我们的数据集推送到 Hub。让我们看看如何做到这一点。
将数据集上传到 Hugging Face Hub
现在我们有了增强数据集,是时候将其推送到 Hub,以便与社区共享了!上传数据集非常简单:就像 🤗 Transformers 中的模型和分词器一样,我们可以使用 push_to_hub()
方法来推送数据集。为此,我们需要一个身份验证令牌,可以通过首先使用 notebook_login()
函数登录 Hugging Face Hub 来获取
from huggingface_hub import notebook_login
notebook_login()
这将创建一个小部件,您可以在其中输入用户名和密码,API 令牌将保存在 *~/.huggingface/token* 中。如果您在终端中运行代码,则可以改为通过 CLI 登录
huggingface-cli login
完成此操作后,我们可以通过运行以下命令上传数据集
issues_with_comments_dataset.push_to_hub("github-issues")
从此以后,任何人都可以通过简单地将存储库 ID 作为 path
参数提供给 load_dataset()
来下载数据集
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
})
太棒了,我们已将数据集推送到 Hub,供其他人使用!只剩下最后一件重要的事情要做:添加一个*数据集卡片*,解释语料库是如何创建的,并为社区提供其他有用的信息。
💡 您还可以通过使用 huggingface-cli
和一些 Git 技巧,直接从终端将数据集上传到 Hugging Face Hub。有关如何执行此操作的详细信息,请参阅 🤗 Datasets 指南。
创建数据集卡片
文档完善的数据集更有可能对他人(包括未来的您自己!)有用,因为它们提供了上下文,使用户能够决定数据集是否与其任务相关,并评估使用数据集可能存在的任何潜在偏差或风险。
在 Hugging Face Hub 上,此信息存储在每个数据集存储库的 *README.md* 文件中。在创建此文件之前,您应该采取两个主要步骤
- 使用
datasets-tagging
应用程序 以 YAML 格式创建元数据标签。这些标签用于 Hugging Face Hub 上的各种搜索功能,并确保社区成员可以轻松找到您的数据集。由于我们在这里创建了一个自定义数据集,您需要克隆datasets-tagging
仓库并在本地运行该应用程序。界面如下所示

- 阅读关于创建信息丰富的数据集卡片的 🤗 Datasets 指南,并将其用作模板。
您可以直接在 Hub 上创建 *README.md* 文件,并在 lewtun/github-issues
数据集仓库中找到一个模板数据集卡片。下面显示了填写完整的数据集卡片的截图。

✏️ 试试看! 使用 dataset-tagging
应用程序和 🤗 Datasets 指南 来为您的 GitHub issues 数据集完成 *README.md* 文件。
就是这样!我们在本节中看到,创建一个好的数据集可能相当复杂,但幸运的是,上传并与社区共享它并不复杂。在下一节中,我们将使用我们的新数据集创建一个语义搜索引擎,使用 🤗 Datasets 将问题与最相关的问题和评论进行匹配。
✏️ 试试看! 按照本节中的步骤为您最喜欢的开源库(当然,选择 🤗 Datasets 以外的库!)创建一个 GitHub issues 数据集。作为额外加分项,微调一个多标签分类器来预测 labels
字段中存在的标签。