Text Classification
Transformers
Safetensors
English
Korean
Japanese
qev_decision_merged
feature-extraction
qev
dlp
typed-decision
japanese
bf16
custom_code
Instructions to use querypieai/QEV2.1-12B with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use querypieai/QEV2.1-12B with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-classification", model="querypieai/QEV2.1-12B", trust_remote_code=True)# pip install -U transformers accelerate # Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("querypieai/QEV2.1-12B", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
File size: 12,204 Bytes
6438a35 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 | #!/usr/bin/env python3
"""Build an isolated QEV2 DLP multitask dataset.
The source DLP corpus has document-level findings. This converter keeps that
ground truth and adds a small, deterministic bundle of type questions to every
record: is_dlp plus up to three type decisions. Japanese vulnerability rows
are converted from their existing explicit annotations and are never used to
overwrite the official corpus.
"""
from __future__ import annotations
import argparse
import hashlib
import json
import random
from collections import Counter
from pathlib import Path
TYPE_MAP = {
"PERSON_NAME": "is_person_name",
"GOVERNMENT_ID": "is_government_id",
"CREDIT_CARD_NUMBER": "is_credit_card",
"FINANCIAL_ACCOUNT_NUMBER": "is_financial_account",
"PASSWORD": "is_password",
"MEDICAL_RECORD_NUMBER": "is_medical_record",
"EMAIL_ADDRESS": "is_email",
"PHONE_NUMBER": "is_phone",
"API_KEY": "is_api_key",
"AUTH_TOKEN": "is_auth_token",
"DEVICE_ID": "is_device_id",
"MAC_ADDRESS": "is_mac_address",
"DATE_OF_BIRTH": "is_birth_date",
"IP_ADDRESS": "is_ip_address",
"DRIVERS_LICENSE_NUMBER": "is_drivers_license",
"STREET_ADDRESS": "is_address",
"BLOOD_TYPE": "is_blood_type",
"PASSPORT": "is_passport",
"AGE": "is_age",
"VEHICLE_IDENTIFICATION_NUMBER": "is_vehicle_id",
"IBAN_CODE": "is_iban",
}
TYPE_ORDER = list(dict.fromkeys(TYPE_MAP.values()))
JP_KEY_MAP = {
"phone": "is_phone",
"date_of_birth": "is_birth_date",
"drivers_license": "is_drivers_license",
"passport": "is_passport",
"government_id": "is_government_id",
"medical": "is_medical_record",
"biometric": "is_biometric",
"email": "is_email",
"address": "is_address",
"credit_card": "is_credit_card",
"password": "is_password",
"api_key": "is_api_key",
"auth_token": "is_auth_token",
}
LABEL_TEXT = {
"en": {
"is_dlp": ("Does this source text contain one or more actual privacy-sensitive values?", "No actual privacy-sensitive value is present.", "At least one actual privacy-sensitive value is present."),
"is_email": "an actual email address",
"is_phone": "an actual telephone number",
"is_birth_date": "an actual date of birth",
"is_drivers_license": "an actual driver's license number",
"is_passport": "an actual passport or travel document number",
"is_government_id": "an actual government-issued identifier",
"is_medical_record": "an actual medical record or patient identifier",
"is_person_name": "an actual person's name",
"is_address": "an actual street address",
},
"jp": {
"is_dlp": ("このテキストに実際のプライバシー・機密情報の値が1つ以上含まれていますか?", "実際の値は含まれていない。", "実際の値が含まれている。"),
"is_email": "実際のメールアドレス",
"is_phone": "実際の電話番号",
"is_birth_date": "実際の生年月日(西暦・和暦・全角数字を含む)",
"is_drivers_license": "実際の運転免許証番号・免許番号",
"is_passport": "実際のパスポート番号・旅券番号",
"is_government_id": "実際の国民ID・個人番号など政府発行ID",
"is_medical_record": "実際の患者ID・診療録番号など医療識別子",
"is_person_name": "実際の個人名",
"is_address": "実際の住所",
},
"ko": {
"is_dlp": ("이 텍스트에 실제 개인정보·민감정보 값이 하나 이상 포함되어 있습니까?", "실제 값이 포함되어 있지 않습니다.", "실제 값이 하나 이상 포함되어 있습니다."),
"is_email": "실제 이메일 주소",
"is_phone": "실제 전화번호",
"is_birth_date": "실제 생년월일",
"is_drivers_license": "실제 운전면허번호",
"is_passport": "실제 여권번호",
"is_government_id": "실제 정부 발행 식별번호",
"is_medical_record": "실제 환자·진료 기록 식별자",
"is_person_name": "실제 사람 이름",
"is_address": "실제 주소",
},
}
def lang_of(row: dict, default="en") -> str:
value = row.get("language") or row.get("_meta", {}).get("language") or default
value = {"ja": "jp", "japanese": "jp", "en-us": "en", "ko-kr": "ko"}.get(str(value).lower(), str(value).lower())
return value if value in {"en", "jp", "ko"} else default
def question(qid: str, lang: str, value: bool, *, is_dlp=False) -> dict:
if is_dlp:
instruction, no_text, yes_text = LABEL_TEXT[lang]["is_dlp"]
else:
phrase = LABEL_TEXT[lang].get(qid, qid)
if lang == "jp":
instruction = f"このテキストに{phrase}が含まれていますか?全角・半角、区切り記号、表形式、改行を含む表記を判定します。"
elif lang == "ko":
instruction = f"이 텍스트에 {phrase}가 포함되어 있습니까? 전각·반각, 구분기호, 표 형식, 줄바꿈 표기를 포함해 판정합니다."
else:
instruction = f"Does this source text contain {phrase}? Include full-width/half-width, separators, tables, and line-wrapped forms."
no_text = "実際の値は含まれていない。" if lang == "jp" else ("실제 값이 포함되어 있지 않습니다." if lang == "ko" else "No actual value is present.")
yes_text = "実際の値が含まれている。" if lang == "jp" else ("실제 값이 포함되어 있습니다." if lang == "ko" else "An actual value is present.")
return {
"type": "noul",
"instructions": instruction,
"criteria": {"false": no_text, "true": yes_text},
"label": "true" if value else "false",
}
def stable_choice(seed: str, candidates: list[str], n: int) -> list[str]:
rng = random.Random(int(hashlib.sha256(seed.encode()).hexdigest()[:16], 16))
shuffled = list(candidates)
rng.shuffle(shuffled)
return shuffled[:n]
def official_record(row: dict, split: str, ordinal: int) -> dict:
lang = lang_of(row)
positive = {TYPE_MAP[x.get("info_type")] for x in row.get("findings", []) if x.get("info_type") in TYPE_MAP}
# Three type slots per record keeps the demo-compatible four-question limit.
chosen = list(sorted(positive))[:3]
if len(chosen) < 3:
pool = [x for x in TYPE_ORDER if x not in positive]
chosen += stable_choice(row.get("id", str(ordinal)), pool, 3 - len(chosen))
qs = {"is_dlp": question("is_dlp", lang, bool(row.get("findings")), is_dlp=True)}
for qid in chosen:
qs[qid] = question(qid, lang, qid in positive)
return {
"id": f"mt-{split}-{row.get('id', ordinal)}",
"state": row.get("text", ""),
"questions": qs,
"_meta": {"origin": "official-dlp-findings", "source_id": row.get("id"), "source_split": split,
"language": lang, "info_types": sorted(x for x in positive), "binary_label": int(bool(row.get("findings")))},
}
def ja_record(row: dict, split: str, ordinal: int) -> list[dict]:
lang = "jp"
state = row.get("state", "")
src_q = row.get("questions", {})
labels = {JP_KEY_MAP[k]: (q.get("label") == "true") for k, q in src_q.items() if k in JP_KEY_MAP}
pos = {k for k, v in labels.items() if v}
# retain explicitly annotated types; fill with negatives to create bundles.
keys = list(labels)
bundles = [keys[i:i + 3] for i in range(0, len(keys), 3)] or [[]]
out = []
for bi, bundle in enumerate(bundles):
qs = {"is_dlp": question("is_dlp", lang, bool(row.get("_meta", {}).get("info_types") or row.get("questions", {}).get("is_dlp", {}).get("label") == "true"), is_dlp=True)}
for qid in bundle:
qs[qid] = question(qid, lang, labels[qid])
out.append({"id": f"mt-ja-{split}-{row.get('id', ordinal)}-b{bi}", "state": state, "questions": qs,
"_meta": {"origin": "ja-vulnerability-annotations", "source_id": row.get("id"), "source_split": split,
"language": "jp", "info_types": sorted(pos), "binary_label": int(bool(pos))}})
return out
def general_record(row: dict) -> dict:
# Keep replay records unchanged except for a provenance marker.
row = dict(row)
meta = dict(row.get("_meta", {})); meta["origin"] = "general-replay"; row["_meta"] = meta
# Make the multitask contract explicit for replay too. The replay corpus
# is the non-DLP general set, so this additional target is always false.
if isinstance(row.get("questions"), dict) and "is_dlp" not in row["questions"]:
lang = lang_of(row, "en")
row["questions"] = {"is_dlp": question("is_dlp", lang, False, is_dlp=True), **row["questions"]}
return row
def read_jsonl(path: Path):
with path.open(encoding="utf-8") as f:
for line in f:
if line.strip():
yield json.loads(line)
def write_jsonl(path: Path, rows):
path.parent.mkdir(parents=True, exist_ok=True)
with path.open("w", encoding="utf-8") as f:
for row in rows:
f.write(json.dumps(row, ensure_ascii=False, separators=(",", ":")) + "\n")
def build(args):
out = Path(args.out); out.mkdir(parents=True, exist_ok=True)
source_root, dlp_root, ja_root = map(Path, (args.source_root, args.dlp_root, args.ja_root))
split_map = {"train": "train", "validation": "development", "test": "historical"}
stats = Counter(); generated = {}
for source_split, target_split in split_map.items():
rows = []
for i, row in enumerate(read_jsonl(source_root / f"{source_split}.jsonl")):
rows.append(official_record(row, source_split, i)); stats[f"official_{target_split}"] += 1
if target_split == "train":
# Existing official QEV2 replay is already a carefully selected 30% general mix.
for row in read_jsonl(dlp_root / "train-r0-replay30.jsonl"):
if row.get("_meta", {}).get("origin") == "general-replay" or row.get("_meta", {}).get("group_id"):
rows.append(general_record(row)); stats["replay"] += 1
ja_path = ja_root / "train-r4-focused.jsonl"
elif target_split == "development":
ja_path = ja_root / "development-r3.jsonl"
else:
ja_path = ja_root / "historical-test-multitask.jsonl"
# JA source is explicitly annotated; include only Japanese records to avoid duplicating EN/KO.
if ja_path.exists():
for i, row in enumerate(read_jsonl(ja_path)):
raw_lang = row.get("language") or row.get("_meta", {}).get("language")
if str(raw_lang).lower() in {"jp", "ja", "japanese"} and row.get("_meta", {}).get("origin") != "official-dlp-findings":
converted = ja_record(row, target_split, i); rows.extend(converted); stats[f"ja_{target_split}"] += len(converted)
write_jsonl(out / f"{target_split}-multitask.jsonl", rows)
generated[target_split] = len(rows)
manifest = {"schema": "qev2-train-multitask-v1", "question_limit": 4, "type_map": TYPE_MAP,
"logical_split": {"train": "train-multitask.jsonl", "development": "development-multitask.jsonl", "historical": "historical-multitask.jsonl"},
"official_source": str(source_root), "replay_source": str(dlp_root / "train-r0-replay30.jsonl"),
"ja_source": str(ja_root), "counts": generated, "stats": dict(stats),
"label_rule": "is_dlp=true iff source findings/info_types is non-empty; type labels come only from findings or explicit JA annotations"}
(out / "manifest.json").write_text(json.dumps(manifest, ensure_ascii=False, indent=2), encoding="utf-8")
print(json.dumps(manifest, ensure_ascii=False, indent=2))
if __name__ == "__main__":
ap = argparse.ArgumentParser()
ap.add_argument("--out", required=True)
ap.add_argument("--source-root", required=True)
ap.add_argument("--dlp-root", required=True)
ap.add_argument("--ja-root", required=True)
build(ap.parse_args())
|