From be25b6c0539a57f80cd658e6c48c4711cd9a708d Mon Sep 17 00:00:00 2001 From: hbyang Date: Wed, 16 Sep 2026 13:18:07 +0900 Subject: [PATCH] feat: add KLUE NER sample viewer for scorecard capture MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 성적서 "1. 데이터 준비" 는 KLUE NER 을 원본 배포 형식인 인라인 태그 (`<영동고속도로:LC>`) 로 싣는데, HuggingFace datasets 판은 tokens/ner_tags 배열이라 모양이 다르다. BIO 를 엔티티 단위로 묶어 원본 형식으로 되돌린다. 전 차수 성적서 8p 첫 예시와 문자 단위로 일치함을 확인했다. 열람 전용이며 학습 경로에는 관여하지 않는다. Co-Authored-By: Claude Opus 5 (1M context) --- scripts/show_klue_ner_samples.py | 104 +++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 scripts/show_klue_ner_samples.py diff --git a/scripts/show_klue_ner_samples.py b/scripts/show_klue_ner_samples.py new file mode 100644 index 0000000..a408651 --- /dev/null +++ b/scripts/show_klue_ner_samples.py @@ -0,0 +1,104 @@ +"""성능지표 #3 학습/평가 데이터 예시를 성적서 형식으로 출력한다. + +전 차수 성적서(GERI `GERIR.CE.2511-02.009`) 8페이지 "1. 데이터 준비" 는 KLUE NER 을 +원본 배포 형식인 인라인 태그로 싣는다. + + "id": 0, + "sentence": "특히 <영동고속도로:LC> <강릉:LC> 방향 <문막휴게소:LC>에서 ..." + +HuggingFace `datasets` 의 klue/ner 는 음절 토큰(`tokens`)과 BIO 태그(`ner_tags`) +배열로 들어오므로 모양이 다르다. 여기서 BIO 를 엔티티 단위로 묶어 원본 형식으로 +되돌린다. 성적서에 그대로 캡처해 붙이기 위한 열람 전용 스크립트이며 학습에는 +관여하지 않는다. + +사용: + python scripts/show_klue_ner_samples.py # 학습셋 앞뒤 3건씩 + python scripts/show_klue_ner_samples.py --split validation + python scripts/show_klue_ner_samples.py --head 5 --tail 5 + python scripts/show_klue_ner_samples.py --all --out train_samples.txt +""" +from __future__ import annotations + +import argparse +import sys + + +def to_inline(tokens: list[str], tags: list[str]) -> str: + """음절 토큰 + BIO 태그 → `<표면형:태그>` 인라인 문장. + + KLUE NER 의 tokens 는 공백을 포함한 음절 단위라 그대로 이어 붙이면 원문이 된다. + """ + out: list[str] = [] + buf: list[str] = [] + label: str | None = None + + def flush() -> None: + if buf: + out.append("<%s:%s>" % ("".join(buf), label)) + buf.clear() + + for token, tag in zip(tokens, tags): + if tag.startswith("B-"): + flush() + label = tag[2:] + buf.append(token) + elif tag.startswith("I-") and label == tag[2:]: + buf.append(token) + else: + flush() + label = None + out.append(token) + flush() + return "".join(out) + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--split", default="train", choices=("train", "validation")) + parser.add_argument("--head", type=int, default=3, help="앞에서 N건") + parser.add_argument("--tail", type=int, default=3, help="뒤에서 N건") + parser.add_argument("--all", action="store_true", help="전건 출력") + parser.add_argument("--out", help="파일로 저장 (미지정 시 표준출력)") + args = parser.parse_args() + + from datasets import load_dataset + + ds = load_dataset("klue", "ner", split=args.split) + names = ds.features["ner_tags"].feature.names + total = len(ds) + + if args.all: + indices = list(range(total)) + else: + indices = list(range(min(args.head, total))) + tail_start = max(len(indices), total - args.tail) + indices += list(range(tail_start, total)) + + lines: list[str] = [] + kind = "학습" if args.split == "train" else "평가" + lines.append("<%s 데이터 예시(총 %d 건)>" % (kind, total)) + lines.append("") + + previous = -1 + for i in indices: + if previous >= 0 and i != previous + 1: + lines.append("...") + lines.append("") + row = ds[i] + sentence = to_inline(row["tokens"], [names[t] for t in row["ner_tags"]]) + lines.append('"id": %d,' % i) + lines.append('"sentence": "%s",' % sentence) + previous = i + + text = "\n".join(lines) + if args.out: + with open(args.out, "w", encoding="utf-8") as f: + f.write(text + "\n") + print("%s 에 %d건 기록 (전체 %d건)" % (args.out, len(indices), total)) + else: + print(text) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())