HuggingfaceNLP笔记5.4Creating your own dataset

有时候,构建NLP应用所需的数据集并不存在,因此你需要自己创建。本节将向你展示如何创建一个GitHub问题的语料库,这些问题通常用于GitHub存储库中跟踪bug或功能。这个语料库可以用于多种目的,例如:

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

我们将重点放在创建语料库上,下一节将探讨语义搜索应用。为了保持“元”风格,我们将使用一个流行的开源项目 🤗 Datasets 的GitHub问题。让我们看看如何获取数据并探索这些问题中的信息。

获取数据

你可以通过访问🤗 Datasets 仓库的“问题”标签页来找到所有问题。如下面截图所示,撰写本文时,共有331个开放问题和668个已关闭问题。

在这里插入图片描述

点击其中一个问题,你会看到它包含标题、描述和一组标签,用于描述问题。下面的截图是一个例子。

在这里插入图片描述

要下载整个仓库的所有问题,我们将使用GitHub REST API来查询“问题”端点。这个端点返回一个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状态代码列表: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"  # 将你的GitHub令牌复制到这里
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  # 每页问题数量
    num_pages = math.ceil(num_issues / per_page)
    base_url = "https://api.github.com/repos"

    for page in tqdm(range(num_pages)):
        # 使用state=all获取开放和关闭的问题
        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 = []  # 为下个时间周期清空批处理
            print(f"达到GitHub速率限制。将休息一小时...")
            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"已下载{repo}的所有问题!数据集存储在{issues_path}/{repo}-issues.jsonl"
    )

现在,当你调用fetch_issues()时,它会分批下载问题,以避免超出GitHub每小时请求次数的限制;结果将存储在repository_name-issues.jsonl文件中,其中每一行都是表示问题的JSON对象。让我们使用这个函数从🤗 Datasets获取所有问题:

# 根据您的网络连接,这可能需要几分钟的时间运行...
`fetch_issues()`

一旦问题下载完成,我们可以使用我们在第5章第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仓库的问题页面显示的总共有大约1000个问题呢?根据GitHub的文档,这是因为我们下载了所有的拉取请求:

GitHub的REST API v3将每个拉取请求视为一个问题,但并非所有问题都是拉取请求。因此,“问题”端点的响应中可能会返回问题和拉取请求。你可以通过pull_request键来识别拉取请求。请注意,从“问题”端点返回的拉取请求的id将是一个问题ID。

由于问题和拉取请求的内容大不相同,让我们做一些轻微的预处理,以便区分它们。

数据清理

GitHub文档中的这一段告诉我们,pull_request列可以用来区分问题和拉取请求。让我们随机查看一个样本,看看它们之间的区别。就像我们在第5章第3节中所做的那样,我们将使用Dataset.shuffle()Dataset.select()方法创建一个随机样本,然后将html_urlpull_request列进行配对,以便比较各个URL:

sample = issues_dataset.shuffle(seed=666).select(range(3))

# 打印URL和拉取请求条目
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_atclosed_at时间戳。额外加分:计算关闭拉取请求的平均时间。

虽然我们可以通过删除或重命名某些列来进一步清理数据集,但在这个阶段,保持数据集尽可能原始通常是一个好习惯,以便于在多个应用中使用。

在将数据集推送到Hugging Face Hub之前,我们还需要处理缺失的内容:每个问题和拉取请求关联的评论。接下来,我们将使用——你猜对了——GitHub REST API 来添加这些评论!

扩展数据集

如下面的屏幕截图所示,与问题或拉取请求关联的评论提供了丰富的信息,特别是当我们构建一个回答用户关于库的查询的搜索引擎时。
在这里插入图片描述

GitHub REST API 提供了一个名为Comments的端点(https://docs.github.com/en/rest/reference/issues#list-issue-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()]


# 测试我们的函数是否按预期工作
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列:

# 根据您的网络连接,这可能需要几分钟...
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()path参数设置为仓库ID来下载数据集:

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仓库并在本地运行应用。这是界面的样子:
    在这里插入图片描述

  2. 阅读🤗 Datasets指南关于创建有信息的数据集卡片,将其作为模板。

你可以在Hub上直接创建README.md文件,lewtun/github-issues数据集仓库中有一个模板数据集卡片。下面是填写后的数据集卡片截图:
在这里插入图片描述

📝 动手试试! 使用dataset-tagging应用和🤗 Datasets指南,为你的GitHub问题数据集完成README.md文件。

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

📝 动手试试! 按照本节的步骤,为你最喜欢的开源库创建一个GitHub问题数据集(当然,不要选择🤗 Datasets)。额外加分:微调一个多标签分类器,预测labels字段中的标签。

  • 20
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

HITzwx

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值