#
Membuat Dataset Sendiri

Terkadang, dataset yang kamu butuhkan untuk membangun aplikasi NLP belum tersedia — jadi kamu perlu membuatnya sendiri. Di bagian ini, kita akan menunjukkan cara membuat korpus dari GitHub issues, yang sering digunakan untuk melacak bug atau permintaan fitur pada repositori GitHub. Dataset ini bisa digunakan untuk berbagai tujuan, seperti:

- Menganalisis berapa lama waktu yang dibutuhkan untuk menyelesaikan issues atau pull request
- Melatih multilabel classifier untuk menandai issue dengan metadata berdasarkan deskripsinya (misalnya: “bug”, “enhancement”, atau “question”)
- Membuat mesin pencari semantik untuk menemukan issue yang cocok dengan kueri pengguna

Di sini kita akan fokus pada pembuatan korpus, dan di bagian selanjutnya kita akan membahas bagaimana membangun aplikasi pencarian semantik. Untuk membuatnya lebih menarik, kita akan menggunakan issue dari proyek open source yang populer: 🤗 Datasets! Mari kita mulai dengan mendapatkan data dan mengeksplorasi informasi di dalamnya.

##
Mendapatkan Data

Kamu bisa menemukan semua issue di 🤗 Datasets dengan membuka tab Issues pada repositorinya. Pada saat penulisan, terdapat 331 issue terbuka dan 668 yang sudah ditutup.

Jika kamu klik salah satu issue, kamu akan melihat informasi seperti judul, deskripsi, dan label yang menjelaskan isi issue tersebut. Contohnya terlihat pada gambar berikut:

Untuk mengunduh semua issue dari repositori, kita akan menggunakan GitHub REST API dan mengakses endpoint `Issues`. Endpoint ini akan mengembalikan daftar objek JSON, masing-masing berisi berbagai informasi seperti judul, deskripsi, status, dan lainnya.

Cara paling mudah untuk mengambil issue adalah dengan library `requests`, yaitu pustaka standar Python untuk membuat HTTP request. Instal dulu dengan:

    !pip install requests

Setelah pustaka diinstal, Anda dapat melakukan permintaan GET ke endpoint `Issues` dengan memanggil fungsi `requests.get()`. Sebagai contoh, Anda bisa menjalankan perintah berikut untuk mengambil isu pertama pada halaman pertama:

    import requests

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

Objek `response` berisi banyak informasi termasuk status kode HTTP:

    response.status_code

    200

Kode status `200` berarti permintaan berhasil (Anda dapat menemukan daftar kode status HTTP yang mungkin di sini). Namun, yang benar-benar ingin kita lihat adalah payload-nya, yang dapat diakses dalam berbagai format seperti byte, string, atau JSON. Karena kita tahu bahwa isu-isu kita berada dalam format JSON, mari kita periksa payload-nya sebagai berikut:

    response.json()

Contoh output:

    [{'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}]

Seperti yang bisa dilihat, kita mendapat banyak informasi, termasuk `title`, `body`, dan `number` dari issue, serta detail user yang membukanya.

✏️ Coba sendiri! Klik beberapa URL di payload JSON untuk melihat informasi apa saja yang terhubung dengan setiap issue GitHub.

Seperti dijelaskan dalam dokumentasi GitHub, permintaan tanpa autentikasi dibatasi hingga 60 permintaan per jam. Meskipun Anda dapat meningkatkan parameter kueri `per_page` untuk mengurangi jumlah permintaan yang dikirim, Anda tetap akan mencapai batas tersebut pada repositori yang memiliki lebih dari beberapa ribu isu. Sebagai gantinya, Anda sebaiknya mengikuti instruksi GitHub untuk membuat personal access token agar dapat meningkatkan batas permintaan menjadi 5.000 per jam. Setelah Anda memiliki token, Anda bisa menyertakannya sebagai bagian dari header permintaan:

    GITHUB_TOKEN = "xxx"  # Ganti dengan token milikmu
    headers = {"Authorization": f"token {GITHUB_TOKEN}"}

⚠️ Jangan pernah membagikan notebook yang berisi `GITHUB_TOKEN` Anda secara langsung. Kami menyarankan Anda menghapus sel terakhir setelah mengeksekusinya untuk menghindari kebocoran informasi secara tidak sengaja. Lebih baik lagi, simpan token tersebut di dalam file .env dan gunakan pustaka `python-dotenv` untuk memuatnya secara otomatis sebagai variabel lingkungan (environment variable).

Berikut fungsi Python untuk mengunduh semua issue dari repositori 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  # Jumlah issue per halaman
        num_pages = math.ceil(num_issues / per_page)
        base_url = "https://api.github.com/repos"

        for page in tqdm(range(num_pages)):
            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 = []  # Kosongkan batch
                print("Batas permintaan GitHub tercapai. Menunggu 1 jam ...")
                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"Berhasil mengunduh semua issue untuk {repo}! Dataset disimpan di {issues_path}/{repo}-issues.jsonl"
        )

Sekarang ketika kita memanggil `fetch_issues()`, fungsi ini akan mengunduh semua issue secara bertahap (batch) untuk menghindari melampaui batas permintaan per jam dari GitHub. Hasilnya akan disimpan dalam file repository_name-issues.jsonl, di mana setiap baris adalah objek JSON yang merepresentasikan satu issue. Mari kita gunakan fungsi ini untuk mengambil semua issue dari proyek 🤗 Datasets:

    # Bergantung pada koneksi internetmu, proses ini bisa memakan waktu beberapa menit...
    fetch_issues()

Setelah issue berhasil diunduh, kita bisa memuatnya secara lokal menggunakan teknik yang telah kita pelajari di bagian 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
    })

Bagus! Kita telah berhasil membuat dataset pertama kita dari nol! Tapi, kenapa jumlahnya ribuan issue padahal di tab Issues repositori 🤗 Datasets hanya terlihat sekitar 1.000 saja 🤔? Seperti yang dijelaskan dalam dokumentasi GitHub:

> API REST GitHub v3 menganggap setiap pull request sebagai sebuah issue, tetapi tidak semua issue adalah pull request. Oleh karena itu, endpoint “Issues” dapat mengembalikan baik issue maupun pull request. Kamu dapat membedakan pull request dengan adanya kunci `pull_request`. Perlu diketahui bahwa `id` dari pull request yang dikembalikan dari endpoint “Issues” adalah ID issue.

Karena konten antara issue dan pull request berbeda, mari kita lakukan sedikit pembersihan agar bisa membedakannya.

##
Membersihkan Data

Berdasarkan dokumentasi di atas, kolom `pull_request` dapat digunakan untuk membedakan antara issue dan pull request. Mari kita lihat beberapa contoh acak untuk membandingkan. Sama seperti di bagian 3, kita akan menggunakan `Dataset.shuffle()` dan `Dataset.select()`:

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

    # Cetak URL dan entri pull request-nya
    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://github.com/huggingface/datasets/issues/2773
    >> Pull request: None

    >> URL: https://github.com/huggingface/datasets/pull/783
    >> Pull request: {...}

Dari sini kita bisa lihat bahwa setiap pull request memiliki berbagai URL terkait, sementara issue biasa memiliki nilai `None`. Berdasarkan hal ini, kita bisa menambahkan kolom baru `is_pull_request` untuk membedakan keduanya:

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

✏️ Coba sendiri! Hitung rata-rata waktu yang dibutuhkan untuk menutup issue pada proyek 🤗 Datasets. Kamu bisa menggunakan `Dataset.filter()` untuk menyaring pull request dan issue yang masih terbuka, lalu gunakan `Dataset.set_format()` untuk mengubah dataset menjadi `DataFrame` agar lebih mudah memproses kolom `created_at` dan `closed_at`. Untuk tantangan tambahan, hitung juga rata-rata waktu penutupan pull request.

Meskipun kita bisa lanjut membersihkan dataset dengan menghapus atau mengganti nama kolom, umumnya lebih baik menyimpan dataset dalam bentuk mentah agar bisa digunakan dalam berbagai aplikasi.

Sebelum kita unggah dataset ini ke Hugging Face Hub, ada satu hal lagi yang perlu kita tambahkan: komentar pada setiap issue atau pull request. Mari kita tambahkan menggunakan — seperti yang bisa ditebak — GitHub REST API!

##
Menambahkan Data Komentar

Seperti terlihat pada gambar berikut, komentar pada issue atau pull request memberikan informasi yang sangat kaya, terutama jika kita ingin membuat mesin pencari untuk menjawab pertanyaan pengguna tentang pustaka ini.

GitHub REST API menyediakan endpoint `Comments` yang mengembalikan semua komentar terkait dengan nomor issue tertentu. Mari kita uji endpoint ini:

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

Kita dapat melihat bahwa komentar disimpan dalam field `body`, jadi mari kita tulis fungsi sederhana yang mengembalikan semua komentar yang terkait dengan sebuah issue dengan mengambil isi `body` dari setiap elemen dalam `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()]

    # Uji apakah fungsi kita bekerja sebagaimana mestinya
    get_comments(2792)

    ["@albertvillanova tes saya gagal di sini:\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\nSaat saya mencoba memuat dataset di mesin lokal, semuanya berjalan baik. Ada saran bagaimana cara menghindari error ini?"]

Ini terlihat bagus, jadi mari kita gunakan `Dataset.map()` untuk menambahkan kolom `comments` baru ke setiap issue dalam dataset kita:

    # Tergantung pada koneksi internet Anda, ini bisa memakan waktu beberapa menit...
    issues_with_comments_dataset = issues_dataset.map(
        lambda x: {"comments": get_comments(x["number"])}
    )

Langkah terakhir adalah mengunggah dataset kita ke Hub. Mari kita lihat bagaimana caranya.

##
Mengunggah dataset ke Hugging Face Hub

Sekarang kita memiliki dataset yang sudah ditambah dengan komentar, saatnya untuk mengunggahnya ke Hub agar bisa dibagikan kepada komunitas! Mengunggah dataset sangat mudah: sama seperti model dan tokenizer dari 🤗 Transformers, kita bisa menggunakan metode `push_to_hub()` untuk mengunggah dataset. Untuk melakukannya, kita memerlukan token autentikasi, yang bisa didapatkan dengan login ke Hugging Face Hub menggunakan fungsi `notebook_login()`:

    from huggingface_hub import notebook_login

    notebook_login()

Ini akan menampilkan widget tempat Anda bisa memasukkan nama pengguna dan kata sandi, dan token API akan disimpan di ~/.huggingface/token. Jika Anda menjalankan kode di terminal, Anda bisa login lewat CLI:

    huggingface-cli login

Setelah itu, kita bisa mengunggah dataset kita dengan menjalankan:

    issues_with_comments_dataset.push_to_hub("github-issues")

Mulai dari sini, siapa pun bisa mengunduh dataset ini cukup dengan memberikan ID repositori sebagai argumen `path` ke `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
    })

Keren, kita sudah mengunggah dataset ke Hub dan sekarang bisa digunakan oleh orang lain! Masih ada satu hal penting yang perlu dilakukan: menambahkan dataset card yang menjelaskan bagaimana korpus dibuat dan menyediakan informasi berguna lainnya untuk komunitas.

💡 Anda juga bisa mengunggah dataset ke Hugging Face Hub langsung dari terminal dengan menggunakan `huggingface-cli` dan sedikit perintah Git. Lihat panduan 🤗 Datasets untuk detailnya.

##
Membuat dataset card

Dataset yang terdokumentasi dengan baik lebih mungkin bermanfaat bagi orang lain (termasuk diri Anda di masa depan!), karena mereka menyediakan konteks yang memungkinkan pengguna memutuskan apakah dataset tersebut relevan untuk tugas mereka dan mengevaluasi potensi bias atau risiko dalam penggunaannya.

Di Hugging Face Hub, informasi ini disimpan dalam file README.md di setiap repositori dataset. Ada dua langkah utama yang perlu Anda lakukan sebelum membuat file ini:

1. Gunakan aplikasi `datasets-tagging` untuk membuat tag metadata dalam format YAML. Tag ini digunakan untuk berbagai fitur pencarian di Hugging Face Hub dan memastikan dataset Anda dapat dengan mudah ditemukan oleh komunitas. Karena kita membuat dataset kustom di sini, Anda perlu mengkloning repositori `datasets-tagging` dan menjalankan aplikasi secara lokal. Berikut tampilan antarmukanya:

2. Baca panduan 🤗 Datasets tentang cara membuat dataset card yang informatif dan gunakan sebagai template.

Anda bisa membuat file README.md langsung di Hub, dan Anda bisa menemukan contoh template dataset card di repositori dataset `lewtun/github-issues`. Berikut adalah tangkapan layar dari dataset card yang telah diisi:

✏️ Coba sendiri! Gunakan aplikasi `dataset-tagging` dan panduan 🤗 Datasets untuk menyelesaikan file README.md untuk dataset GitHub issues Anda.

Itu saja! Kita telah melihat dalam bagian ini bahwa membuat dataset yang bagus bisa jadi cukup rumit, tapi untungnya proses mengunggah dan membagikannya ke komunitas sangatlah mudah. Di bagian selanjutnya kita akan menggunakan dataset baru ini untuk membuat mesin pencari semantik dengan 🤗 Datasets yang dapat mencocokkan pertanyaan ke issue dan komentar yang paling relevan.

✏️ Coba sendiri! Ikuti langkah-langkah di bagian ini untuk membuat dataset GitHub issues dari pustaka open source favorit Anda (pilih yang selain 🤗 Datasets, tentu saja!). Untuk poin tambahan, latih model klasifikasi multilabel untuk memprediksi tag yang ada di field `labels`.

< > Update on GitHub