LLM 課程文件

建立您自己的資料集

Hugging Face's logo
加入 Hugging Face 社群

並獲得增強的文件體驗

開始使用

建立您自己的資料集

Ask a Question Open In Colab Open In Studio Lab

有時,您構建 NLP 應用程式所需的資料集並不存在,因此您需要自己建立它。在本節中,我們將向您展示如何建立一個 GitHub issues 語料庫,該語料庫通常用於跟蹤 GitHub 倉庫中的錯誤或功能。該語料庫可用於各種目的,包括

  • 探索關閉開放問題或拉取請求所需的時間
  • 訓練一個*多標籤分類器*,可以根據問題的描述(例如,“bug”、“增強”或“問題”)為問題新增元資料標籤
  • 建立一個語義搜尋引擎來查詢與使用者查詢匹配的問題

這裡我們將專注於建立語料庫,在下一節中我們將處理語義搜尋應用程式。為了保持元資料,我們將使用與流行開源專案 🤗 Datasets 相關的 GitHub 問題!讓我們看看如何獲取資料並探索這些問題中包含的資訊。

獲取資料

您可以透過導航到 🤗 Datasets 倉庫的 Issues 選項卡 來查詢所有問題。如下面截圖所示,在撰寫本文時,有 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 相關聯,而普通問題則有一個 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")

從此以後,任何人都可以透過簡單地將儲存庫 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* 檔案中。在建立此檔案之前,您應該採取兩個主要步驟

  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 issues 資料集完成 *README.md* 檔案。

就是這樣!我們在本節中看到,建立一個好的資料集可能相當複雜,但幸運的是,上傳並與社群共享它並不複雜。在下一節中,我們將使用我們的新資料集建立一個語義搜尋引擎,使用 🤗 Datasets 將問題與最相關的問題和評論進行匹配。

✏️ 試試看! 按照本節中的步驟為您最喜歡的開源庫(當然,選擇 🤗 Datasets 以外的庫!)建立一個 GitHub issues 資料集。作為額外加分項,微調一個多標籤分類器來預測 labels 欄位中存在的標籤。

< > 在 GitHub 上更新

© . This site is unofficial and not affiliated with Hugging Face, Inc.