LLM 课程文档

创建你自己的数据集

Hugging Face's logo
加入 Hugging Face 社区

并获得增强的文档体验

开始入门

创建你自己的数据集

Ask a Question Open In Colab Open In Studio Lab

有时,构建 NLP 应用程序所需的数据集并不存在,因此你需要自己创建它。在本节中,我们将向你展示如何创建一个 GitHub 议题 的语料库,GitHub 议题通常用于跟踪 GitHub 仓库中的错误或功能。此语料库可用于各种目的,包括

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

在这里,我们将专注于创建语料库,在下一节中,我们将解决语义搜索应用程序。为了保持元数据,我们将使用与一个流行的开源项目相关的 GitHub 议题:🤗 Datasets!让我们看看如何获取数据并探索这些议题中包含的信息。

获取数据

你可以通过导航到仓库的 Issues 标签页,在 🤗 Datasets 中找到所有议题。如下面的屏幕截图所示,在撰写本文时,有 331 个未解决议题和 668 个已关闭议题。

The GitHub issues associated with 🤗 Datasets.

如果你单击其中一个议题,你会发现它包含标题、描述和一组描述议题特征的标签。下面屏幕截图显示了一个示例。

A typical GitHub issue in the 🤗 Datasets repository.

要下载仓库的所有议题,我们将使用 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}]

哇,信息量真大!我们可以看到有用的字段,如 titlebodynumber,它们描述了议题,以及有关打开议题的 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_urlpull_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 相关联,而普通议题的 pull_request 条目为 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_atclosed_at 时间戳。为了获得加分,请计算关闭拉取请求的平均时间。

虽然我们可以继续通过删除或重命名某些列来进一步清理数据集,但在现阶段保持数据集尽可能“原始”通常是一个好习惯,以便它可以轻松地用于多个应用程序。

在我们将数据集推送到 Hugging Face Hub 之前,让我们处理一件数据集中缺少的东西:与每个议题和拉取请求相关的评论。接下来我们将添加它们,你猜对了,使用 GitHub REST API!

扩充数据集

如下面的屏幕截图所示,与议题或拉取请求相关的评论提供了丰富的信息来源,特别是如果我们有兴趣构建一个搜索引擎来回答用户关于库的查询。

Comments associated with an issue about 🤗 Datasets.

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")

从这里开始,任何人都可以通过简单地为 load_dataset() 提供仓库 ID 作为 path 参数来下载数据集

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 文件中。在创建此文件之前,你应该执行两个主要步骤

  1. 使用 datasets-tagging 应用程序 以 YAML 格式创建元数据标签。这些标签用于 Hugging Face Hub 上的各种搜索功能,并确保你的数据集可以很容易地被社区成员找到。由于我们在此处创建了自定义数据集,因此你需要克隆 datasets-tagging 仓库并在本地运行该应用程序。以下是该界面的外观
The 'datasets-tagging' interface.
  1. 阅读有关创建信息丰富的数据集卡片的 🤗 Datasets 指南,并将其用作模板。

你可以直接在 Hub 上创建 README.md 文件,并且可以在 lewtun/github-issues 数据集仓库中找到模板数据集卡片。下面显示了填写完整的数据集卡片的屏幕截图。

A dataset card.

✏️ 试试看! 使用 dataset-tagging 应用程序和 🤗 Datasets 指南 来完成你的 GitHub 议题数据集的 README.md 文件。

就这样!我们在本节中看到,创建一个好的数据集可能非常复杂,但幸运的是,上传它并与社区分享它并不复杂。在下一节中,我们将使用我们的新数据集创建一个使用 🤗 Datasets 的语义搜索引擎,该引擎可以将问题与最相关的议题和评论相匹配。

✏️ 试试看! 按照我们在本节中采取的步骤,为你最喜欢的开源库创建一个 GitHub 议题数据集(当然,选择 🤗 Datasets 以外的其他内容!)。为了获得加分,微调一个多标签分类器来预测 labels 字段中存在的标签。

< > 在 GitHub 上更新