LLM Course documentation

Crearea propriului tău dataset

Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

Crearea propriului tău dataset

Ask a Question Open In Colab Open In Studio Lab

Uneori, datasetul necesar pentru a construi o aplicație NLP nu există, astfel încât veți trebui să-l creați singuri. În această secțiune vom arăta cum să creați un corpus de GitHub issues, care sunt utilizate în mod obișnuit pentru a urmări erorile sau feature-urile din repositoriile GitHub. Acest corpus poate fi folosit pentru diverse scopuri, inclusiv:

  • Explorarea timpului necesar pentru închiderea unor issues deschise sau pull requesturi
  • Antrenarea unui multilabel classifier care poate eticheta issue-urile cu metadate pe baza descrierii issue-urilor (de exemplu, “bug”, “enhancement” sau “question”)
  • Crearea unui motor de căutare semantică pentru a găsi care issues se potrivesc query-ului utilizatorului

În această secțiune ne vom focusa pe crearea corpusului, și în următoarea vom aborda aplicația motorului de căutare semantic. Pentru a păstra lucrurile meta, vom folosi issue-urile GitHub asociate cu un proiect open source popular: 🤗 Datasets! Să vedem cum să obținem datele și să explorăm informațiile conținute în aceste issue-uri.

Obținerea datelor

Puteți găsi toate issue-urile din 🤗 Datasets navigând către tabul Issues al repositorului. Așa cum arată următorul screenshot, la momentul scrierii acestui text existau 331 de issues deschise și 668 închise.

Issue-urile GitHub asociate cu 🤗 Datasets.

Dacă ați da clic pe una dintre aceste issue-uri veți găsi că aceasta conține un titlu, o descriere și un set de labeluri care caracterizează issue-ul. Un exemplu este prezentat în screenshotul următor.

Un issue tipic în GitHub din repositoriul 🤗 Datasets.

Pentru a descărca toate issue-urile din repositoriu, vom folosi GitHub REST API pentru a enumera Issues endpoint. Aceast endpoint returnează o listă de obiecte JSON, cu fiecare obiect conținând un număr mare de câmpuri care includ titlul și descrierea precum și metadata despre starea issue-ului și așa mai departe.

Un mod convenabil de descărcare a issue-urilor este prin utilizarea librăriei requests, care este modalitatea standard pentru a face cereri HTTP în Python. Puteți instala libraria rulând comanda:

!pip install requests

Odată cu instalarea librariei, puteți face cereri GET la Issues endpoint prin invocarea funcției requests.get(). De exemplu, puteți rula următorul cod pentru a obține primul issue din prima pagină:

import requests

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

Obiectul response conține o cantitate mare de informații utile despre requestul efectuat, inclusiv HTTP status code:

response.status_code
200

unde statusul 200 înseamnă că cererea a fost reușită (puteți găsi o listă completă de status coduri aici). De ceea ce suntem însă interesați este payload, care poate fi accesat în diverse formaturi precum bytes, string sau JSON. Deoarece știm că issue-urile noastre sunt în format JSON, să inspectăm payload-ul astfel:

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}]

Uau, aceasta e o cantitate mare de informație! Putem vedea câmpuri utile cum ar fi title, body și number care descriu problema, precum și informații despre utilizatorul GitHub care a deschis issue-ul.

✏️ Încercați! Faceți clic pe câteva dintre URL-urile din payload-ul JSON de mai sus pentru a vă familiariza cu tipul de informații către care se face referire pentru fiecare GitHub issue.

După cum este descris în documentația GitHub, solicitările neautentificate sunt limitate la 60 de solicitări pe oră. Deși puteți crește per_page query parameter pentru a reduce numărul de solicitări pe care le faceți, oricum veți atinge limita pentru orice repository care are mai mult de câteva mii de issues. Prin urmare, ar trebui să urmați instrucțiunile GitHub pentru crearea unui personal access token astfel încât să puteți crește limita la 5.000 de solicitări pe oră. Odată ce aveți tokenul, îl puteți include ca parte a request header:

GITHUB_TOKEN = xxx  # Copy your GitHub token here
headers = {"Authorization": f"token {GITHUB_TOKEN}"}

⚠️ Nu oferiți nimănui un notebook cu GITHUB_TOKEN în el . Vă recomandăm să ștergeți ultima celulă odată ce ați executat-o pentru a evita scurgerea accidentală a acestor informații. Chiar mai bine, stocați tokenul într-un fișier .env și utilizați biblioteca python-dotenv pentru a îl încărca automat ca variabilă de mediu.

Acum că avem tokenul de acces, hai să creăm o funcție care să poată descărca toate issue-urile dintr-un repositoriu 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"
    )

Acum când apelăm fetch_issues() va descărca toate problemele în batch-uri pentru a evita depășirea limitei GitHub pe numărul de solicitări pe oră; rezultatul va fi stocat într-un fișier _repository_name-issues.jsonl, unde fiecare linie este un obiect JSON care reprezintă un issue. Mai jos folosim această funcție pentru a obține toate issue-urile de la 🤗 Datasets:

# În dependență de conexiunea ta la internet, acest lucru poate dura câteva minute...
fetch_issues()

Odată ce issue-urile sunt descărcate, le putem încărca local utilizând abilitățile noastre dobândite în secțiunea 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
})

Great, am creat primul nostru dataset de la zero! Dar de ce sunt mai mult de câteva mii de issue-uri atunci când tabul de issue-uri al repositoriului 🤗 Datasets afișează doar aproximativ 1.000 de issue-uri în total 🤔? Conform descris în documentația GitHub, acest lucru s-a întâmplat pentru că am descărcat și toate pull requesturile:

GitHub’s REST API v3 considers every pull request an issue, but not every issue is a pull request. For this reason, “Issues” endpoints may return both issues and pull requests in the response. You can identify pull requests by the pull_request key. Be aware that the id of a pull request returned from “Issues” endpoints will be an issue id.

Deoarece conținutul issue-urilor și pull requesturilor este destul de diferit, hai să preprocesăm puțin datele pentru a ne permite să le diferențiem între ele.

Curățarea datelor

Fragmentul de mai sus din documentația GitHub ne spune că coloana pull_request poate fi utilizată pentru a diferenția între issues și pull requests. Să analizăm un sampple aleatoriu pentru a vedea care este diferența. Așa cum am făcut în secțiunea 3, vom înlănțui Dataset.shuffle() și Dataset.select() pentru a crea un sample aleatoriu și apoi vom împerechea coloanele html_url și pull_request pentru a putea compara diversele URL-uri:

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'}

Aici putem vedea că fiecare pull request este asociat cu diverse URL-uri, în timp ce issue-urile obișnuite au o intrare None. Putem utiliza această distincție pentru a crea o nouă coloană is_pull_request care verifică dacă câmpul pull_request este None sau nu:

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

✏️ Încercați! Calculați timpul mediu necesar pentru închiderea issue-urilor în Datasets. Vă poate fi utilă funcția Dataset.filter() pentru a filtra pull requesturile și issue-urile deschise, și puteți utiliza funcția Dataset.set_format() pentru a converti datasetul într-un DataFrame astfel încât să puteți manipula cu ușurință timestampurile created_at și closed_at. Pentru puncte bonus, calculați timpul mediu necesar pentru închiderea pull requesturilor.

Deși am putea continua să curățăm datasetul prin eliminarea sau redenumirea unor coloane, este, în general, o practică bună să păstrăm datasetul cât mai “raw” posibil la acest stadiu, astfel încât să poată fi utilizat ușor în multiple aplicații.

Înainte de a încărca datasetul în Hugging Face Hub, trebuie să rezolvăm chestie care lipsește din el: comentariile asociate fiecărui issue și pull request. Le vom adăuga în continuare cu— ați ghicit — GitHub REST API!

Îmbunătățirea datasetului

După cum se vede în următorul screenshot, comentariile asociate unui issue sau pull request oferă o sursă bogată de informații, în special dacă suntem interesați să construim un motor de căutare pentru a răspunde la întrebările utilizatorilor despre bibliotecă.

Comentariile asociate unei probleme despre 🤗 Datasets.

GitHub REST API oferă un endpoint Comments care returnează toate comentariile asociate numărului problemei. Să testăm endpointul pentru a vedea ce returnează:

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}]

Putem vedea că comentariul este stocat în câmpul body, așa că putem scrie o funcție simplă care returnează toate comentariile asociate unei probleme prin extragerea conținutului body pentru fiecare element în response.json():

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ăm dacă funcția lucrează cum ne dorim
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?"]

Arată bine. Acum hai să folosim Dataset.map() pentru a adăuga noi coloane comments fiecărui issue în datasetul nostru:

# 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"])}
)

Ultimul pas este să facem push datasetului nostru pe Hub. Să vedem cum putem face asta.

Încărcarea datasetului pe Hugging Face Hub

Acum că avem datasetul nostru augmentat, este timpul să îi facem push pe Hub pentru a-l oferi comunității! Încărcarea unui dataset este foarte simplu: la fel ca modelele și tokenizerrii din 🤗 Transformers, putem utiliza o metodă push_to_hub() pentru a face push unui dataset. Pentru a face asta, avem nevoie de un token de autentificare, care poate fi obținut prin autentificarea pe Hugging Face Hub cu funcția notebook_login():

from huggingface_hub import notebook_login

notebook_login()

Acest lucru va crea un widget unde poți să scrii usernameul și parola ta, iar un API token va fi salvat în ~/.huggingface/token. Dacă rulezi codeul într-un terminal, te poți loga cu ajutor CLI: This will create a widget where you can enter your username and password, and an API token will be saved in ~/.huggingface/token. If you’re running the code in a terminal, you can log in via the CLI instead:

huggingface-cli login

O dată ce ai făcut asta, putem încărca datasetul rulând:

issues_with_comments_dataset.push_to_hub("github-issues")

De acum, orice poate să descarce datasetul, utilizând load_dataset() cu ID-ul repositoriului ca path argument:

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

Cool, am încărcat datasetul nostru pe Hub și acum este disponibil pentru alții să îl utilizeze! Mai este doar un lucru important de făcut: adăugarea unui dataset card care explică cum a fost creat corpusul și oferă alte informații utile pentru comunitate.

💡 De asemenea, puteți încărca un dataset pe Hugging Face Hub direct din terminal utilizând huggingface-cli și puțină magie Git. Consultați ghidul 🤗 Datasets pentru detalii despre cum puteți face asta.

Crearea unei dataset card

Datasetiroșe bine documentate sunt mai probabil să fie utile altora (inclusiv ție din viitor!), deoarece furnizează contextul pentru a permite utilizatorilor să decidă dacă datasetul este relevant pentru taskul lor și să evalueze eventualele biasuri sau riscurile asociate cu utilizarea datasetului.

Pe Hugging Face Hub, această informație este stocată în fișierul README.md al fiecărui dataset repository. Sunt doi pași principali pe care trebuie să îi efectuați înainte de a crea acest fișier:

  1. Utilizați aplicația datasets-tagging pentru a crea etichete de metadate în format YAML. Aceste taguri sunt utilizate pentru o varietate de funcționalități de căutare pe Hugging Face Hub și asigură că datasetul poate fi găsit ușor de membrii comunității. Deoarece am creat un dataset custom aici, veți fi nevoiți să clonați repositoriul datasets-tagging și să rulați aplicația local. Iată cum arată interfața:
Interfața 'datasets-tagging'.
  1. Citiți ghidul 🤗 Datasets despre crearea de dataset cards informative și utilizați-l ca șablon.

Puteți crea fișierul README.md direct pe Hub și puteți găsi un template pentru dataset card în repositoriul lewtun/github-issues. Un screenshot a dataset card completată este afișată mai jos.

Dataset card.

✏️ Încercați! Utilizați aplicația dataset-tagging și ghidul 🤗 Datasets pentru a completa fișierul README.md pentru datasetul de probleme GitHub.

Astfel, am văzut în această secțiune că crearea unui dataset bun poate fi destul de complicată, dar, spre norocul nsotru, încărcarea și oferirea acestuia comunității nu sunt. În secțiunea următoare, vom utiliza datasetul nou pentru a crea un motor de căutare semantic cu 🤗 Datasets care poate să asocieze întrebări cu cele mai relevante issues și comentarii.

✏️ Încercați! Treceți prin pașii pe care i-am făcut în această secțiune pentru a crea un dataset de issues GitHub pentru o biblioteca open source care îți place(alegeți altceva înafară de 🤗 Datasets, desigur!). Pentru puncte bonus, faceți fine-tune unui multilabel classifier pentru a prezice tagurile prezente în câmpul labels.

< > Update on GitHub