Skip to content

Commit 32d3e2d

Browse files
Datasets
1 parent 8757b77 commit 32d3e2d

5 files changed

Lines changed: 243 additions & 9 deletions

File tree

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,13 +221,14 @@ Currently automated sources:
221221
|---|---|---|
222222
| `bcb` | Hugging Face `google/code_x_glue_cc_clone_detection_big_clone_bench` | Stored directly as `pair_jsonl`. |
223223
| `poj104` | Hugging Face `google/code_x_glue_cc_clone_detection_poj104` | Official task is retrieval; downloader builds deterministic binary pairs for this repository. |
224+
| `poolc` | Hugging Face `PoolC/5-fold-clone-detection-600k-5fold` | Stored as `pair_jsonl`; the Hugging Face `val` split is deterministically divided into validation and test rows. |
224225

225226
For POJ-104, use `--poj_pairs_per_label all` to materialize exhaustive
226227
positive pairs. The default samples 1000 positive pairs per label per split and
227228
the same number of negatives, which is much smaller and suitable for smoke
228229
tests or constrained runs.
229230

230-
GCJ, Karnalim, PoolC, CodeNet, CLCDSA, SemanticCloneBench, and GPTCloneBench
231+
GCJ, Karnalim, CodeNet, CLCDSA, SemanticCloneBench, and GPTCloneBench
231232
still require manual source acquisition or conversion because no stable public
232233
direct-download endpoint is registered in this repository.
233234

@@ -247,7 +248,7 @@ Currently supported local conversions:
247248
| `gcj` | `train.txt`, `valid.txt`, `test.txt`, plus `googlejam4_src/` files | Adds `data.jsonl`, converts `-1/1` labels to `0/1`, backs up raw splits as `raw_*.txt`. |
248249
| `karnalim` | `training.json`, `validation.json`, `test.json` | Adds `data.jsonl`, `train.txt`, `valid.txt`, `test.txt`. |
249250

250-
PoolC, CodeNet, CLCDSA, SemanticCloneBench, and GPTCloneBench still need their
251+
CodeNet, CLCDSA, SemanticCloneBench, and GPTCloneBench still need their
251252
actual source files or official pair files before they can be normalized.
252253

253254
## Run Artifacts

docs/REPRODUCIBILITY.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,14 @@ Automatically retrieve public datasets with stable sources first:
1818
python scripts/download_datasets.py --dataset all --output_root datasets --skip_existing
1919
```
2020

21-
This currently downloads CodeXGLUE BigCloneBench as `datasets/bcb` and
22-
CodeXGLUE POJ-104 as `datasets/poj104`. POJ-104 is officially a retrieval task;
23-
the downloader stores the snippets and creates deterministic binary pairs for
24-
this repository's pair-classification trainers. The source and conversion
25-
settings are written to `dataset_source.json`.
21+
This currently downloads CodeXGLUE BigCloneBench as `datasets/bcb`, CodeXGLUE
22+
POJ-104 as `datasets/poj104`, and PoolC as `datasets/poolc`. POJ-104 is
23+
officially a retrieval task; the downloader stores the snippets and creates
24+
deterministic binary pairs for this repository's pair-classification trainers.
25+
PoolC is downloaded from Hugging Face pair rows; the official `train` split is
26+
kept intact and the official `val` split is deterministically divided into
27+
validation and test rows. The source and conversion settings are written to
28+
`dataset_source.json`.
2629

2730
Run with `--strict_data` when preparing final results. This fails fast on
2831
malformed rows, missing snippet IDs, and labels outside `{0, 1}`.

scripts/normalize_local_datasets.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525

2626
SUPPORTED_DATASETS = ("gcj", "karnalim")
2727
MANUAL_DATASETS = {
28-
"poolc": "Only data.txt is present locally; download/extract the PoolC files first.",
2928
"codenet": "Use scripts/prepare_pair_dataset.py on problem-directory source files.",
3029
"clcdsa": "Use scripts/prepare_pair_dataset.py on problem-directory source files.",
3130
"semanticclonebench": "Convert the official released pairs to pair_jsonl first.",

small_code_models/dataset_download.py

Lines changed: 163 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@
3232
"Code-Code/Clone-detection-POJ-104"
3333
),
3434
},
35+
"poolc": {
36+
"display_name": "PoolC",
37+
"source": "PoolC/5-fold-clone-detection-600k-5fold",
38+
"homepage": "https://huggingface.co/datasets/PoolC/5-fold-clone-detection-600k-5fold",
39+
},
3540
}
3641

3742
DATASET_ALIASES = {
@@ -40,12 +45,12 @@
4045
"poj": "poj104",
4146
"poj-104": "poj104",
4247
"codexglue_poj104": "poj104",
48+
"pool-c": "poolc",
4349
}
4450

4551
MANUAL_DATASETS: dict[str, str] = {
4652
"gcj": "No stable public direct-download endpoint is registered here.",
4753
"karnalim": "No stable public direct-download endpoint is registered here.",
48-
"poolc": "No stable public direct-download endpoint is registered here.",
4954
"codenet": "Use the official Project CodeNet release, then prepare subsets locally.",
5055
"clcdsa": "Use the official release, then prepare problem directories locally.",
5156
"semanticclonebench": "Convert the official released pairs to pair_jsonl locally.",
@@ -419,6 +424,156 @@ def download_poj104(
419424
return report
420425

421426

427+
def download_poolc(
428+
output_dir: str | Path,
429+
*,
430+
overwrite: bool = False,
431+
hf_cache_dir: str | Path | None = None,
432+
include_diagnostics: bool = False,
433+
) -> dict[str, Any]:
434+
"""Download PoolC pair rows and normalize to pair_jsonl layout.
435+
436+
The Hugging Face release exposes ``train`` and ``val`` splits. This
437+
repository requires train/validation/test files, so the converter keeps the
438+
official training split intact and deterministically alternates rows from
439+
``val`` into validation and test.
440+
"""
441+
destination = Path(output_dir)
442+
_check_can_write(destination, overwrite)
443+
load_dataset = _require_hf_datasets()
444+
try:
445+
from huggingface_hub import hf_hub_url, list_repo_files
446+
except ImportError as exc: # pragma: no cover - installed with datasets
447+
raise RuntimeError(
448+
"PoolC download requires huggingface_hub, which is installed with "
449+
"the Hugging Face datasets package."
450+
) from exc
451+
452+
spec = AUTO_DATASETS["poolc"]
453+
repo_files = list_repo_files(spec["source"], repo_type="dataset")
454+
source_files = {
455+
"train": sorted(
456+
file_name
457+
for file_name in repo_files
458+
if file_name.startswith("data/train-") and file_name.endswith(".parquet")
459+
),
460+
"val": sorted(
461+
file_name
462+
for file_name in repo_files
463+
if file_name.startswith("data/val-") and file_name.endswith(".parquet")
464+
),
465+
}
466+
if not source_files["train"] or not source_files["val"]:
467+
raise ValueError(
468+
"PoolC source is missing expected data/train-*.parquet or "
469+
"data/val-*.parquet files."
470+
)
471+
data_files = {
472+
split_name: [
473+
hf_hub_url(spec["source"], filename=file_name, repo_type="dataset")
474+
for file_name in file_names
475+
]
476+
for split_name, file_names in source_files.items()
477+
}
478+
split_rows = {"train": 0, "validation": 0, "test": 0}
479+
source_split_rows = {"train": 0, "val": 0}
480+
label_counts = {
481+
"train": {"0": 0, "1": 0},
482+
"validation": {"0": 0, "1": 0},
483+
"test": {"0": 0, "1": 0},
484+
}
485+
snippet_hashes: dict[str, str] = {}
486+
conflict_count = 0
487+
488+
def write_pair(
489+
row: Mapping[str, Any],
490+
split_name: str,
491+
split_handle: Any,
492+
data_handle: Any,
493+
) -> None:
494+
nonlocal conflict_count
495+
left_code = str(row["code1"])
496+
right_code = str(row["code2"])
497+
label = _label_to_int(row["similar"])
498+
left_id = f"poolc:{_text_sha256(left_code)}"
499+
right_id = f"poolc:{_text_sha256(right_code)}"
500+
501+
for snippet_id, code in ((left_id, left_code), (right_id, right_code)):
502+
digest = _text_sha256(code)
503+
previous_digest = snippet_hashes.get(snippet_id)
504+
if previous_digest is None:
505+
snippet_hashes[snippet_id] = digest
506+
data_handle.write(json.dumps({"idx": snippet_id, "func": code}, sort_keys=True))
507+
data_handle.write("\n")
508+
elif previous_digest != digest:
509+
conflict_count += 1
510+
511+
split_handle.write(f"{left_id}\t{right_id}\t{label}\n")
512+
split_rows[split_name] += 1
513+
label_counts[split_name][str(label)] += 1
514+
515+
load_kwargs = {"cache_dir": None if hf_cache_dir is None else str(hf_cache_dir)}
516+
with (destination / "data.jsonl").open("w", encoding="utf-8") as data_handle:
517+
train_dataset = load_dataset(
518+
"parquet",
519+
data_files={"train": data_files["train"]},
520+
split="train",
521+
streaming=True,
522+
**load_kwargs,
523+
)
524+
with (destination / "train.txt").open("w", encoding="utf-8") as train_handle:
525+
for row in train_dataset:
526+
write_pair(row, "train", train_handle, data_handle)
527+
source_split_rows["train"] += 1
528+
529+
val_dataset = load_dataset(
530+
"parquet",
531+
data_files={"val": data_files["val"]},
532+
split="val",
533+
streaming=True,
534+
**load_kwargs,
535+
)
536+
with (destination / "valid.txt").open(
537+
"w",
538+
encoding="utf-8",
539+
) as validation_handle, (destination / "test.txt").open(
540+
"w",
541+
encoding="utf-8",
542+
) as test_handle:
543+
for row_index, row in enumerate(val_dataset):
544+
split_name = "validation" if row_index % 2 == 0 else "test"
545+
split_handle = validation_handle if split_name == "validation" else test_handle
546+
write_pair(row, split_name, split_handle, data_handle)
547+
source_split_rows["val"] += 1
548+
549+
if conflict_count:
550+
raise ValueError(
551+
f"Encountered {conflict_count} conflicting PoolC snippet ids while "
552+
"normalizing the dataset."
553+
)
554+
555+
report = {
556+
"dataset_key": "poolc",
557+
"display_name": spec["display_name"],
558+
"source": spec["source"],
559+
"homepage": spec["homepage"],
560+
"source_files": source_files,
561+
"layout": "pair_jsonl",
562+
"source_format": "hf_code_pair_rows",
563+
"output_dir": str(destination),
564+
"snippets": len(snippet_hashes),
565+
"split_rows": split_rows,
566+
"source_split_rows": source_split_rows,
567+
"label_counts": label_counts,
568+
"validation_test_source_split": "val",
569+
"validation_test_strategy": "alternating_even_odd_rows",
570+
}
571+
if include_diagnostics:
572+
report["diagnostics"] = inspect_dataset_directory(destination)
573+
_write_json(destination / "dataset_source.json", report)
574+
return report
575+
576+
422577
def download_dataset(
423578
dataset_key: str,
424579
output_root: str | Path,
@@ -453,4 +608,11 @@ def download_dataset(
453608
seed=seed,
454609
include_diagnostics=include_diagnostics,
455610
)
611+
if key == "poolc":
612+
return download_poolc(
613+
output_dir,
614+
overwrite=overwrite,
615+
hf_cache_dir=hf_cache_dir,
616+
include_diagnostics=include_diagnostics,
617+
)
456618
raise KeyError(f"Dataset {dataset_key!r} is not registered for automatic download.")

tests/test_dataset_download.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
"""Unit tests for dataset download normalization helpers."""
22

3+
import json
34
import random
5+
import sys
6+
import types
47
from pathlib import Path
58

69
import pytest
@@ -18,6 +21,7 @@
1821
def test_normalize_dataset_key_accepts_aliases() -> None:
1922
assert normalize_dataset_key("codexglue_bcb") == "bcb"
2023
assert normalize_dataset_key("poj-104") == "poj104"
24+
assert normalize_dataset_key("pool-c") == "poolc"
2125

2226

2327
def test_parse_pairs_per_label() -> None:
@@ -107,3 +111,68 @@ def fake_load_dataset(source: str, *, split: str, cache_dir: str | None = None)
107111
assert (output_dir / "valid.txt").read_text() == "1\t3\t0\n"
108112
assert (output_dir / "test.txt").read_text() == "2\t3\t0\n"
109113
assert (output_dir / "dataset_source.json").exists()
114+
115+
116+
def test_download_poolc_writes_normalized_files(
117+
tmp_path: Path,
118+
monkeypatch: pytest.MonkeyPatch,
119+
) -> None:
120+
rows = {
121+
"train": [
122+
{"code1": "print(1)", "code2": "print(1)", "similar": 1},
123+
{"code1": "print(2)", "code2": "print(3)", "similar": 0},
124+
],
125+
"val": [
126+
{"code1": "a = 1", "code2": "a=1", "similar": 1},
127+
{"code1": "b = 1", "code2": "c = 1", "similar": 0},
128+
{"code1": "d = 1", "code2": "d=1", "similar": 1},
129+
],
130+
}
131+
132+
def fake_load_dataset(
133+
source: str,
134+
*,
135+
data_files: dict[str, list[str]],
136+
split: str,
137+
cache_dir: str | None = None,
138+
streaming: bool = False,
139+
) -> list:
140+
assert source == "parquet"
141+
assert split in data_files
142+
assert cache_dir is None
143+
assert streaming is True
144+
return rows[split]
145+
146+
monkeypatch.setattr(dataset_download, "_require_hf_datasets", lambda: fake_load_dataset)
147+
fake_hub = types.ModuleType("huggingface_hub")
148+
fake_hub.hf_hub_url = lambda repo_id, filename, repo_type=None: (
149+
f"https://example.test/{filename}"
150+
)
151+
fake_hub.list_repo_files = lambda repo_id, repo_type=None: [
152+
"data/train-00000-of-00001.parquet",
153+
"data/val-00000-of-00001.parquet",
154+
]
155+
monkeypatch.setitem(sys.modules, "huggingface_hub", fake_hub)
156+
157+
report = download_dataset("poolc", tmp_path)
158+
159+
output_dir = tmp_path / "poolc"
160+
snippets = [
161+
json.loads(line)
162+
for line in (output_dir / "data.jsonl").read_text().splitlines()
163+
]
164+
train_rows = (output_dir / "train.txt").read_text().splitlines()
165+
validation_rows = (output_dir / "valid.txt").read_text().splitlines()
166+
test_rows = (output_dir / "test.txt").read_text().splitlines()
167+
168+
assert report["snippets"] == len(snippets)
169+
assert len(train_rows) == 2
170+
assert len(validation_rows) == 2
171+
assert len(test_rows) == 1
172+
assert train_rows[0].endswith("\t1")
173+
assert train_rows[1].endswith("\t0")
174+
assert validation_rows[0].endswith("\t1")
175+
assert validation_rows[1].endswith("\t1")
176+
assert test_rows[0].endswith("\t0")
177+
assert report["validation_test_source_split"] == "val"
178+
assert report["validation_test_strategy"] == "alternating_even_odd_rows"

0 commit comments

Comments
 (0)