Sync card and scripts from the monorepo
Browse files- README.md +3 -1
- data_generator.py +792 -231
- inference.py +58 -21
- train.py +105 -43
README.md
CHANGED
|
@@ -116,7 +116,9 @@ classifier = pipeline(
|
|
| 116 |
model="gr8monk3ys/resume-section-classifier",
|
| 117 |
)
|
| 118 |
|
| 119 |
-
result = classifier(
|
|
|
|
|
|
|
| 120 |
print(result)
|
| 121 |
# [{'label': 'education', 'score': 0.98}]
|
| 122 |
```
|
|
|
|
| 116 |
model="gr8monk3ys/resume-section-classifier",
|
| 117 |
)
|
| 118 |
|
| 119 |
+
result = classifier(
|
| 120 |
+
"Bachelor of Science in Computer Science, Stanford University, 2023. GPA: 3.9/4.0"
|
| 121 |
+
)
|
| 122 |
print(result)
|
| 123 |
# [{'label': 'education', 'score': 0.98}]
|
| 124 |
```
|
data_generator.py
CHANGED
|
@@ -17,51 +17,172 @@ from pathlib import Path
|
|
| 17 |
# ---------------------------------------------------------------------------
|
| 18 |
|
| 19 |
FIRST_NAMES = [
|
| 20 |
-
"James",
|
| 21 |
-
"
|
| 22 |
-
"
|
| 23 |
-
"
|
| 24 |
-
"
|
| 25 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
]
|
| 27 |
|
| 28 |
LAST_NAMES = [
|
| 29 |
-
"Smith",
|
| 30 |
-
"
|
| 31 |
-
"
|
| 32 |
-
"
|
| 33 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
]
|
| 35 |
|
| 36 |
COMPANIES = [
|
| 37 |
-
"Google",
|
| 38 |
-
"
|
| 39 |
-
"
|
| 40 |
-
"
|
| 41 |
-
"
|
| 42 |
-
"
|
| 43 |
-
"
|
| 44 |
-
"
|
| 45 |
-
"
|
| 46 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
]
|
| 48 |
|
| 49 |
UNIVERSITIES = [
|
| 50 |
-
"Massachusetts Institute of Technology",
|
| 51 |
-
"
|
| 52 |
-
"
|
| 53 |
-
"University of
|
| 54 |
-
"
|
| 55 |
-
"
|
| 56 |
-
"University of
|
| 57 |
-
"University of
|
| 58 |
-
"
|
| 59 |
-
"
|
| 60 |
-
"
|
| 61 |
-
"University of
|
| 62 |
-
"
|
| 63 |
-
"
|
| 64 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
]
|
| 66 |
|
| 67 |
DEGREES = [
|
|
@@ -77,78 +198,216 @@ DEGREES = [
|
|
| 77 |
]
|
| 78 |
|
| 79 |
MAJORS = [
|
| 80 |
-
"Computer Science",
|
| 81 |
-
"
|
| 82 |
-
"
|
| 83 |
-
"
|
| 84 |
-
"
|
| 85 |
-
"
|
| 86 |
-
"
|
| 87 |
-
"
|
| 88 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
]
|
| 90 |
|
| 91 |
MINORS = [
|
| 92 |
-
"Mathematics",
|
| 93 |
-
"
|
| 94 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
]
|
| 96 |
|
| 97 |
GPA_VALUES = [
|
| 98 |
-
"3.5",
|
| 99 |
-
"3.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
]
|
| 101 |
|
| 102 |
GRAD_YEARS = list(range(2015, 2027))
|
| 103 |
|
| 104 |
JOB_TITLES = [
|
| 105 |
-
"Software Engineer",
|
| 106 |
-
"
|
| 107 |
-
"
|
| 108 |
-
"
|
| 109 |
-
"
|
| 110 |
-
"
|
| 111 |
-
"
|
| 112 |
-
"
|
| 113 |
-
"
|
| 114 |
-
"Research Scientist",
|
| 115 |
-
"
|
| 116 |
-
"
|
| 117 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
]
|
| 119 |
|
| 120 |
PROGRAMMING_LANGUAGES = [
|
| 121 |
-
"Python",
|
| 122 |
-
"
|
| 123 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 124 |
]
|
| 125 |
|
| 126 |
FRAMEWORKS = [
|
| 127 |
-
"React",
|
| 128 |
-
"
|
| 129 |
-
"
|
| 130 |
-
"
|
| 131 |
-
"
|
| 132 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 133 |
]
|
| 134 |
|
| 135 |
TOOLS = [
|
| 136 |
-
"Git",
|
| 137 |
-
"
|
| 138 |
-
"
|
| 139 |
-
"
|
| 140 |
-
"
|
| 141 |
-
"
|
| 142 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 143 |
]
|
| 144 |
|
| 145 |
SOFT_SKILLS = [
|
| 146 |
-
"Leadership",
|
| 147 |
-
"
|
| 148 |
-
"
|
| 149 |
-
"
|
| 150 |
-
"
|
| 151 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 152 |
]
|
| 153 |
|
| 154 |
CERTIFICATIONS_LIST = [
|
|
@@ -178,47 +437,120 @@ CERTIFICATIONS_LIST = [
|
|
| 178 |
]
|
| 179 |
|
| 180 |
AWARDS_LIST = [
|
| 181 |
-
"Dean's List",
|
| 182 |
-
"
|
| 183 |
-
"
|
| 184 |
-
"
|
| 185 |
-
"
|
| 186 |
-
"
|
| 187 |
-
"
|
| 188 |
-
"
|
| 189 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 190 |
]
|
| 191 |
|
| 192 |
CITIES = [
|
| 193 |
-
"San Francisco, CA",
|
| 194 |
-
"
|
| 195 |
-
"
|
| 196 |
-
"
|
| 197 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 198 |
]
|
| 199 |
|
| 200 |
MONTHS = [
|
| 201 |
-
"January",
|
| 202 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 203 |
]
|
| 204 |
|
| 205 |
MONTHS_SHORT = [
|
| 206 |
-
"Jan",
|
| 207 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 208 |
]
|
| 209 |
|
| 210 |
PROJECT_ADJECTIVES = [
|
| 211 |
-
"Real-time",
|
| 212 |
-
"
|
| 213 |
-
"
|
| 214 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 215 |
]
|
| 216 |
|
| 217 |
PROJECT_NOUNS = [
|
| 218 |
-
"Dashboard",
|
| 219 |
-
"
|
| 220 |
-
"
|
| 221 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 222 |
]
|
| 223 |
|
| 224 |
IMPACT_METRICS = [
|
|
@@ -239,21 +571,42 @@ IMPACT_METRICS = [
|
|
| 239 |
]
|
| 240 |
|
| 241 |
PHONE_AREA_CODES = [
|
| 242 |
-
"415",
|
| 243 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 244 |
]
|
| 245 |
|
| 246 |
LINKEDIN_PREFIXES = [
|
| 247 |
-
"linkedin.com/in/",
|
|
|
|
| 248 |
]
|
| 249 |
|
| 250 |
GITHUB_PREFIXES = [
|
| 251 |
-
"github.com/",
|
|
|
|
| 252 |
]
|
| 253 |
|
| 254 |
DOMAINS = [
|
| 255 |
-
"gmail.com",
|
| 256 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 257 |
]
|
| 258 |
|
| 259 |
# ---------------------------------------------------------------------------
|
|
@@ -261,12 +614,53 @@ DOMAINS = [
|
|
| 261 |
# ---------------------------------------------------------------------------
|
| 262 |
|
| 263 |
SYNONYMS = {
|
| 264 |
-
"developed": [
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 270 |
"responsible for": ["in charge of", "accountable for", "tasked with", "owned"],
|
| 271 |
"utilized": ["leveraged", "employed", "used", "applied", "harnessed"],
|
| 272 |
"achieved": ["accomplished", "attained", "reached", "secured", "delivered"],
|
|
@@ -278,6 +672,7 @@ SYNONYMS = {
|
|
| 278 |
# Helper utilities
|
| 279 |
# ---------------------------------------------------------------------------
|
| 280 |
|
|
|
|
| 281 |
def _pick(pool, k=1):
|
| 282 |
"""Return k unique random items from a pool."""
|
| 283 |
k = min(k, len(pool))
|
|
@@ -342,7 +737,7 @@ def _synonym_replace(text: str) -> str:
|
|
| 342 |
if w[0].isupper():
|
| 343 |
replacement = replacement.capitalize()
|
| 344 |
# Preserve trailing punctuation
|
| 345 |
-
trailing = w[len(lower):]
|
| 346 |
result.append(replacement + trailing)
|
| 347 |
else:
|
| 348 |
result.append(w)
|
|
@@ -365,6 +760,7 @@ def _reorder_bullets(bullets: list) -> list:
|
|
| 365 |
# Section generators – each returns a string of realistic text
|
| 366 |
# ---------------------------------------------------------------------------
|
| 367 |
|
|
|
|
| 368 |
def generate_education() -> str:
|
| 369 |
"""Generate a realistic education section."""
|
| 370 |
templates = []
|
|
@@ -399,34 +795,56 @@ def generate_education() -> str:
|
|
| 399 |
|
| 400 |
# Optional coursework
|
| 401 |
if random.random() < 0.5:
|
| 402 |
-
courses = _pick(
|
| 403 |
-
|
| 404 |
-
|
| 405 |
-
|
| 406 |
-
|
| 407 |
-
|
| 408 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 409 |
lines.append(f"{prefix} {', '.join(courses)}")
|
| 410 |
|
| 411 |
# Optional honors
|
| 412 |
if random.random() < 0.3:
|
| 413 |
-
honor = random.choice(
|
| 414 |
-
|
| 415 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 416 |
lines.append(honor)
|
| 417 |
|
| 418 |
# Optional thesis
|
| 419 |
if "Ph.D." in deg_short or ("M.S." in deg_short and random.random() < 0.4):
|
| 420 |
-
topic = random.choice(
|
| 421 |
-
|
| 422 |
-
|
| 423 |
-
|
| 424 |
-
|
| 425 |
-
|
| 426 |
-
|
| 427 |
-
|
|
|
|
|
|
|
| 428 |
label = "Dissertation" if "Ph.D." in deg_short else "Thesis"
|
| 429 |
-
lines.append(f
|
| 430 |
|
| 431 |
return "\n".join(lines)
|
| 432 |
|
|
@@ -438,7 +856,9 @@ def generate_education() -> str:
|
|
| 438 |
deg_full, deg_short = _pick_one(DEGREES)
|
| 439 |
major = _pick_one(MAJORS)
|
| 440 |
year = _pick_one(GRAD_YEARS)
|
| 441 |
-
gpa_line =
|
|
|
|
|
|
|
| 442 |
entries.append(f"{deg_short} in {major}, {uni}, {year}{gpa_line}")
|
| 443 |
return "\n".join(entries)
|
| 444 |
|
|
@@ -450,18 +870,21 @@ def generate_education() -> str:
|
|
| 450 |
year = _pick_one(GRAD_YEARS)
|
| 451 |
lines = [f"{uni}", f"{deg_full} in {major} | {_pick_one(MONTHS)} {year}"]
|
| 452 |
|
| 453 |
-
activities = random.sample(
|
| 454 |
-
|
| 455 |
-
|
| 456 |
-
|
| 457 |
-
|
| 458 |
-
|
| 459 |
-
|
| 460 |
-
|
| 461 |
-
|
| 462 |
-
|
| 463 |
-
|
| 464 |
-
|
|
|
|
|
|
|
|
|
|
| 465 |
|
| 466 |
b = _bullet()
|
| 467 |
for a in activities:
|
|
@@ -492,11 +915,11 @@ def generate_experience() -> str:
|
|
| 492 |
|
| 493 |
# Generate bullet points
|
| 494 |
bullet_templates = [
|
| 495 |
-
f"Developed and maintained {random.choice(['microservices', 'APIs', 'web applications', 'data pipelines', 'ML models', 'backend systems', 'frontend components'])} using {', '.join(_pick(PROGRAMMING_LANGUAGES, k=random.randint(1,3)))} and {', '.join(_pick(FRAMEWORKS, k=random.randint(1,2)))}",
|
| 496 |
-
f"Collaborated with cross-functional teams of {random.randint(3,15)} engineers to deliver {random.choice(['product features', 'platform improvements', 'system migrations', 'infrastructure upgrades'])} on schedule",
|
| 497 |
f"Designed and implemented {random.choice(['CI/CD pipelines', 'testing frameworks', 'monitoring solutions', 'data models', 'caching strategies', 'authentication systems'])} that {_impact()}",
|
| 498 |
f"Led migration of {random.choice(['legacy monolith', 'on-premise infrastructure', 'batch processing system', 'manual workflows'])} to {random.choice(['cloud-native architecture', 'microservices', 'real-time streaming', 'automated pipelines'])}",
|
| 499 |
-
f"Mentored {random.randint(2,8)} junior engineers through code reviews, pair programming, and technical design sessions",
|
| 500 |
f"Optimized {random.choice(['database queries', 'API response times', 'model inference', 'data processing pipelines', 'search indexing'])} resulting in {_impact()}",
|
| 501 |
f"Wrote comprehensive technical documentation and {random.choice(['RFCs', 'design docs', 'runbooks', 'architecture decision records'])} for {random.choice(['system design', 'API contracts', 'deployment procedures', 'incident response'])}",
|
| 502 |
f"Built {random.choice(['real-time', 'batch', 'streaming', 'event-driven'])} {random.choice(['data pipeline', 'ETL process', 'analytics system', 'feature store'])} processing {random.choice(['1M+', '10M+', '100M+', '1B+'])} records {random.choice(['daily', 'per hour', 'in real-time'])}",
|
|
@@ -506,7 +929,9 @@ def generate_experience() -> str:
|
|
| 506 |
]
|
| 507 |
|
| 508 |
n_bullets = random.randint(2, 5)
|
| 509 |
-
selected = random.sample(
|
|
|
|
|
|
|
| 510 |
selected = _reorder_bullets(selected)
|
| 511 |
b = _bullet()
|
| 512 |
for bullet in selected:
|
|
@@ -535,7 +960,9 @@ def generate_skills() -> str:
|
|
| 535 |
|
| 536 |
if random.random() < 0.9:
|
| 537 |
fws = _pick(FRAMEWORKS, k=random.randint(3, 7))
|
| 538 |
-
label = random.choice(
|
|
|
|
|
|
|
| 539 |
categories.append((label, fws))
|
| 540 |
|
| 541 |
if random.random() < 0.8:
|
|
@@ -556,9 +983,11 @@ def generate_skills() -> str:
|
|
| 556 |
return "\n".join(lines)
|
| 557 |
|
| 558 |
def _t_flat():
|
| 559 |
-
all_skills = (
|
| 560 |
-
|
| 561 |
-
|
|
|
|
|
|
|
| 562 |
random.shuffle(all_skills)
|
| 563 |
joiner = random.choice([", ", " | ", " · ", " • "])
|
| 564 |
return joiner.join(all_skills)
|
|
@@ -568,7 +997,9 @@ def generate_skills() -> str:
|
|
| 568 |
levels = ["Expert", "Advanced", "Proficient", "Intermediate", "Familiar"]
|
| 569 |
used = set()
|
| 570 |
for level in random.sample(levels, k=random.randint(2, 4)):
|
| 571 |
-
pool = [
|
|
|
|
|
|
|
| 572 |
items = _pick(pool, k=random.randint(2, 5))
|
| 573 |
used.update(items)
|
| 574 |
lines.append(f"{level}: {', '.join(items)}")
|
|
@@ -621,28 +1052,45 @@ def generate_projects() -> str:
|
|
| 621 |
def generate_summary() -> str:
|
| 622 |
"""Generate a realistic professional summary / objective section."""
|
| 623 |
years = random.randint(2, 15)
|
| 624 |
-
specialties = _pick(
|
| 625 |
-
|
| 626 |
-
|
| 627 |
-
|
| 628 |
-
|
| 629 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 630 |
|
| 631 |
templates = [
|
| 632 |
# Template 1: Traditional summary
|
| 633 |
-
lambda:
|
| 634 |
-
|
|
|
|
| 635 |
# Template 2: Technical focus
|
| 636 |
-
lambda:
|
| 637 |
-
|
|
|
|
| 638 |
# Template 3: Achievement-oriented
|
| 639 |
-
lambda:
|
| 640 |
-
|
|
|
|
| 641 |
# Template 4: Brief objective
|
| 642 |
-
lambda:
|
| 643 |
-
|
|
|
|
| 644 |
# Template 5: Narrative style
|
| 645 |
-
lambda:
|
|
|
|
|
|
|
| 646 |
]
|
| 647 |
|
| 648 |
return random.choice(templates)()
|
|
@@ -660,9 +1108,11 @@ def generate_certifications() -> str:
|
|
| 660 |
f"{cert} ({year})",
|
| 661 |
f"{cert} — Issued {_pick_one(MONTHS)} {year}",
|
| 662 |
f"{cert}, {year}",
|
| 663 |
-
f"{cert}\n Issued: {_pick_one(MONTHS_SHORT)} {year}"
|
|
|
|
| 664 |
f" | Expires: {_pick_one(MONTHS_SHORT)} {year + random.randint(2, 3)}"
|
| 665 |
-
if random.random() < 0.3
|
|
|
|
| 666 |
),
|
| 667 |
]
|
| 668 |
lines.append(random.choice(styles))
|
|
@@ -679,15 +1129,17 @@ def generate_contact() -> str:
|
|
| 679 |
last = _pick_one(LAST_NAMES)
|
| 680 |
city = _pick_one(CITIES)
|
| 681 |
area_code = _pick_one(PHONE_AREA_CODES)
|
| 682 |
-
email_user = random.choice(
|
| 683 |
-
|
| 684 |
-
|
| 685 |
-
|
| 686 |
-
|
| 687 |
-
|
| 688 |
-
|
|
|
|
|
|
|
| 689 |
email = f"{email_user}@{_pick_one(DOMAINS)}"
|
| 690 |
-
phone = f"({area_code}) {random.randint(100,999)}-{random.randint(1000,9999)}"
|
| 691 |
linkedin_user = f"{first.lower()}-{last.lower()}-{random.randint(100, 999)}"
|
| 692 |
github_user = f"{first.lower()}{last.lower()}"
|
| 693 |
|
|
@@ -704,7 +1156,11 @@ def generate_contact() -> str:
|
|
| 704 |
if random.random() < 0.4:
|
| 705 |
parts.append(f"{_pick_one(GITHUB_PREFIXES)}{github_user}")
|
| 706 |
if random.random() < 0.2:
|
| 707 |
-
parts.append(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 708 |
|
| 709 |
sep = random.choice(["\n", " | ", " · ", "\n"])
|
| 710 |
return sep.join(parts)
|
|
@@ -718,12 +1174,23 @@ def generate_awards() -> str:
|
|
| 718 |
|
| 719 |
for award in awards:
|
| 720 |
year = random.randint(2015, 2025)
|
| 721 |
-
org = random.choice(
|
| 722 |
-
|
| 723 |
-
|
| 724 |
-
|
| 725 |
-
|
| 726 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 727 |
styles = [
|
| 728 |
f"{award}, {org} ({year})",
|
| 729 |
f"{award} — {org}, {year}",
|
|
@@ -743,14 +1210,77 @@ def generate_awards() -> str:
|
|
| 743 |
# ---------------------------------------------------------------------------
|
| 744 |
|
| 745 |
SECTION_HEADERS = {
|
| 746 |
-
"education": [
|
| 747 |
-
|
| 748 |
-
|
| 749 |
-
|
| 750 |
-
|
| 751 |
-
|
| 752 |
-
|
| 753 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 754 |
}
|
| 755 |
|
| 756 |
GENERATORS = {
|
|
@@ -769,7 +1299,10 @@ GENERATORS = {
|
|
| 769 |
# Dataset generation
|
| 770 |
# ---------------------------------------------------------------------------
|
| 771 |
|
| 772 |
-
|
|
|
|
|
|
|
|
|
|
| 773 |
"""
|
| 774 |
Generate a single synthetic example for the given label.
|
| 775 |
|
|
@@ -832,7 +1365,9 @@ def generate_dataset(
|
|
| 832 |
|
| 833 |
# Generate augmented versions
|
| 834 |
for _ in range(augmented_copies):
|
| 835 |
-
aug_text = generate_example(
|
|
|
|
|
|
|
| 836 |
dataset.append({"text": aug_text, "label": label})
|
| 837 |
|
| 838 |
random.shuffle(dataset)
|
|
@@ -865,7 +1400,9 @@ def load_as_hf_dataset(dataset: list[dict]):
|
|
| 865 |
|
| 866 |
# 80/10/10 split, stratified at both steps
|
| 867 |
train_test = ds.train_test_split(test_size=0.2, seed=42, stratify_by_column="label")
|
| 868 |
-
val_test = train_test["test"].train_test_split(
|
|
|
|
|
|
|
| 869 |
|
| 870 |
# Decode ids back to names. Casting ClassLabel -> string does NOT do this
|
| 871 |
# (it stringifies the ids, giving "0"/"1"), and mapping into a column that
|
|
@@ -881,10 +1418,12 @@ def load_as_hf_dataset(dataset: list[dict]):
|
|
| 881 |
"validation": val_test["train"],
|
| 882 |
"test": val_test["test"],
|
| 883 |
}
|
| 884 |
-
return DatasetDict(
|
| 885 |
-
|
| 886 |
-
|
| 887 |
-
|
|
|
|
|
|
|
| 888 |
|
| 889 |
|
| 890 |
def get_label_mapping(dataset: list[dict]) -> tuple[dict, dict]:
|
|
@@ -902,25 +1441,46 @@ def get_label_mapping(dataset: list[dict]) -> tuple[dict, dict]:
|
|
| 902 |
if __name__ == "__main__":
|
| 903 |
import argparse
|
| 904 |
|
| 905 |
-
parser = argparse.ArgumentParser(
|
| 906 |
-
|
| 907 |
-
|
| 908 |
-
parser.add_argument(
|
| 909 |
-
|
| 910 |
-
|
| 911 |
-
|
| 912 |
-
|
| 913 |
-
|
| 914 |
-
parser.add_argument(
|
| 915 |
-
|
| 916 |
-
|
| 917 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 918 |
|
| 919 |
args = parser.parse_args()
|
| 920 |
|
| 921 |
-
print(
|
|
|
|
|
|
|
| 922 |
print(f"Augmented copies per example: {args.augmented_copies}")
|
| 923 |
-
print(
|
|
|
|
|
|
|
| 924 |
|
| 925 |
dataset = generate_dataset(
|
| 926 |
examples_per_category=args.examples_per_category,
|
|
@@ -932,6 +1492,7 @@ if __name__ == "__main__":
|
|
| 932 |
|
| 933 |
if args.print_stats:
|
| 934 |
from collections import Counter
|
|
|
|
| 935 |
counts = Counter(d["label"] for d in dataset)
|
| 936 |
print("\nDataset Statistics:")
|
| 937 |
print(f" Total examples: {len(dataset)}")
|
|
@@ -942,11 +1503,11 @@ if __name__ == "__main__":
|
|
| 942 |
print(f" Average text length: {avg_len:.0f} chars")
|
| 943 |
|
| 944 |
if args.print_samples > 0:
|
| 945 |
-
print(f"\n{'='*60}")
|
| 946 |
print(f"Sample Examples (first {args.print_samples}):")
|
| 947 |
-
print(f"{'='*60}")
|
| 948 |
-
for i, example in enumerate(dataset[:args.print_samples]):
|
| 949 |
-
print(f"\n--- Example {i+1} [{example['label']}] ---")
|
| 950 |
print(example["text"][:300])
|
| 951 |
if len(example["text"]) > 300:
|
| 952 |
print("...")
|
|
|
|
| 17 |
# ---------------------------------------------------------------------------
|
| 18 |
|
| 19 |
FIRST_NAMES = [
|
| 20 |
+
"James",
|
| 21 |
+
"Mary",
|
| 22 |
+
"Robert",
|
| 23 |
+
"Patricia",
|
| 24 |
+
"John",
|
| 25 |
+
"Jennifer",
|
| 26 |
+
"Michael",
|
| 27 |
+
"Linda",
|
| 28 |
+
"David",
|
| 29 |
+
"Elizabeth",
|
| 30 |
+
"William",
|
| 31 |
+
"Barbara",
|
| 32 |
+
"Richard",
|
| 33 |
+
"Susan",
|
| 34 |
+
"Joseph",
|
| 35 |
+
"Jessica",
|
| 36 |
+
"Thomas",
|
| 37 |
+
"Sarah",
|
| 38 |
+
"Charles",
|
| 39 |
+
"Karen",
|
| 40 |
+
"Daniel",
|
| 41 |
+
"Lisa",
|
| 42 |
+
"Matthew",
|
| 43 |
+
"Nancy",
|
| 44 |
+
"Anthony",
|
| 45 |
+
"Betty",
|
| 46 |
+
"Mark",
|
| 47 |
+
"Sandra",
|
| 48 |
+
"Aisha",
|
| 49 |
+
"Wei",
|
| 50 |
+
"Carlos",
|
| 51 |
+
"Priya",
|
| 52 |
+
"Olga",
|
| 53 |
+
"Hiroshi",
|
| 54 |
+
"Fatima",
|
| 55 |
+
"Liam",
|
| 56 |
+
"Sofia",
|
| 57 |
+
"Andrei",
|
| 58 |
+
"Mei",
|
| 59 |
+
"Alejandro",
|
| 60 |
+
"Yuki",
|
| 61 |
+
"Omar",
|
| 62 |
+
"Elena",
|
| 63 |
+
"Raj",
|
| 64 |
]
|
| 65 |
|
| 66 |
LAST_NAMES = [
|
| 67 |
+
"Smith",
|
| 68 |
+
"Johnson",
|
| 69 |
+
"Williams",
|
| 70 |
+
"Brown",
|
| 71 |
+
"Jones",
|
| 72 |
+
"Garcia",
|
| 73 |
+
"Miller",
|
| 74 |
+
"Davis",
|
| 75 |
+
"Rodriguez",
|
| 76 |
+
"Martinez",
|
| 77 |
+
"Hernandez",
|
| 78 |
+
"Lopez",
|
| 79 |
+
"Gonzalez",
|
| 80 |
+
"Wilson",
|
| 81 |
+
"Anderson",
|
| 82 |
+
"Thomas",
|
| 83 |
+
"Taylor",
|
| 84 |
+
"Moore",
|
| 85 |
+
"Jackson",
|
| 86 |
+
"Martin",
|
| 87 |
+
"Lee",
|
| 88 |
+
"Perez",
|
| 89 |
+
"Thompson",
|
| 90 |
+
"White",
|
| 91 |
+
"Harris",
|
| 92 |
+
"Sanchez",
|
| 93 |
+
"Clark",
|
| 94 |
+
"Patel",
|
| 95 |
+
"Chen",
|
| 96 |
+
"Kim",
|
| 97 |
+
"Nakamura",
|
| 98 |
+
"Ivanov",
|
| 99 |
+
"Silva",
|
| 100 |
+
"Okafor",
|
| 101 |
]
|
| 102 |
|
| 103 |
COMPANIES = [
|
| 104 |
+
"Google",
|
| 105 |
+
"Microsoft",
|
| 106 |
+
"Amazon",
|
| 107 |
+
"Apple",
|
| 108 |
+
"Meta",
|
| 109 |
+
"Netflix",
|
| 110 |
+
"Stripe",
|
| 111 |
+
"Airbnb",
|
| 112 |
+
"Uber",
|
| 113 |
+
"Salesforce",
|
| 114 |
+
"Adobe",
|
| 115 |
+
"IBM",
|
| 116 |
+
"Oracle",
|
| 117 |
+
"Intel",
|
| 118 |
+
"Tesla",
|
| 119 |
+
"SpaceX",
|
| 120 |
+
"Palantir",
|
| 121 |
+
"Snowflake",
|
| 122 |
+
"Databricks",
|
| 123 |
+
"Confluent",
|
| 124 |
+
"JPMorgan Chase",
|
| 125 |
+
"Goldman Sachs",
|
| 126 |
+
"Morgan Stanley",
|
| 127 |
+
"Deloitte",
|
| 128 |
+
"McKinsey & Company",
|
| 129 |
+
"Boston Consulting Group",
|
| 130 |
+
"Accenture",
|
| 131 |
+
"Lockheed Martin",
|
| 132 |
+
"Boeing",
|
| 133 |
+
"Raytheon",
|
| 134 |
+
"General Electric",
|
| 135 |
+
"Procter & Gamble",
|
| 136 |
+
"Johnson & Johnson",
|
| 137 |
+
"Pfizer",
|
| 138 |
+
"Moderna",
|
| 139 |
+
"Shopify",
|
| 140 |
+
"Square",
|
| 141 |
+
"Twilio",
|
| 142 |
+
"Cloudflare",
|
| 143 |
+
"HashiCorp",
|
| 144 |
+
"DataRobot",
|
| 145 |
+
"Hugging Face",
|
| 146 |
+
"OpenAI",
|
| 147 |
+
"Anthropic",
|
| 148 |
+
"Cohere",
|
| 149 |
+
"Startup XYZ",
|
| 150 |
+
"TechCorp Inc.",
|
| 151 |
+
"InnovateTech",
|
| 152 |
+
"DataDriven LLC",
|
| 153 |
]
|
| 154 |
|
| 155 |
UNIVERSITIES = [
|
| 156 |
+
"Massachusetts Institute of Technology",
|
| 157 |
+
"Stanford University",
|
| 158 |
+
"Harvard University",
|
| 159 |
+
"University of California, Berkeley",
|
| 160 |
+
"Carnegie Mellon University",
|
| 161 |
+
"Georgia Institute of Technology",
|
| 162 |
+
"University of Michigan",
|
| 163 |
+
"University of Illinois Urbana-Champaign",
|
| 164 |
+
"California Institute of Technology",
|
| 165 |
+
"Princeton University",
|
| 166 |
+
"Columbia University",
|
| 167 |
+
"University of Washington",
|
| 168 |
+
"University of Texas at Austin",
|
| 169 |
+
"Cornell University",
|
| 170 |
+
"University of Pennsylvania",
|
| 171 |
+
"University of Southern California",
|
| 172 |
+
"New York University",
|
| 173 |
+
"University of Wisconsin-Madison",
|
| 174 |
+
"Duke University",
|
| 175 |
+
"Northwestern University",
|
| 176 |
+
"University of California, Los Angeles",
|
| 177 |
+
"Rice University",
|
| 178 |
+
"University of Maryland",
|
| 179 |
+
"Purdue University",
|
| 180 |
+
"Ohio State University",
|
| 181 |
+
"Arizona State University",
|
| 182 |
+
"University of Virginia",
|
| 183 |
+
"University of Florida",
|
| 184 |
+
"Boston University",
|
| 185 |
+
"Northeastern University",
|
| 186 |
]
|
| 187 |
|
| 188 |
DEGREES = [
|
|
|
|
| 198 |
]
|
| 199 |
|
| 200 |
MAJORS = [
|
| 201 |
+
"Computer Science",
|
| 202 |
+
"Software Engineering",
|
| 203 |
+
"Data Science",
|
| 204 |
+
"Electrical Engineering",
|
| 205 |
+
"Mechanical Engineering",
|
| 206 |
+
"Information Technology",
|
| 207 |
+
"Mathematics",
|
| 208 |
+
"Statistics",
|
| 209 |
+
"Business Administration",
|
| 210 |
+
"Economics",
|
| 211 |
+
"Finance",
|
| 212 |
+
"Biomedical Engineering",
|
| 213 |
+
"Chemical Engineering",
|
| 214 |
+
"Civil Engineering",
|
| 215 |
+
"Physics",
|
| 216 |
+
"Biology",
|
| 217 |
+
"Artificial Intelligence",
|
| 218 |
+
"Machine Learning",
|
| 219 |
+
"Human-Computer Interaction",
|
| 220 |
+
"Cybersecurity",
|
| 221 |
+
"Information Systems",
|
| 222 |
+
"Operations Research",
|
| 223 |
]
|
| 224 |
|
| 225 |
MINORS = [
|
| 226 |
+
"Mathematics",
|
| 227 |
+
"Statistics",
|
| 228 |
+
"Psychology",
|
| 229 |
+
"Business",
|
| 230 |
+
"Economics",
|
| 231 |
+
"Philosophy",
|
| 232 |
+
"Linguistics",
|
| 233 |
+
"Physics",
|
| 234 |
+
"Data Science",
|
| 235 |
+
"Communication",
|
| 236 |
+
"Sociology",
|
| 237 |
+
"History",
|
| 238 |
]
|
| 239 |
|
| 240 |
GPA_VALUES = [
|
| 241 |
+
"3.5",
|
| 242 |
+
"3.6",
|
| 243 |
+
"3.7",
|
| 244 |
+
"3.8",
|
| 245 |
+
"3.9",
|
| 246 |
+
"4.0",
|
| 247 |
+
"3.52",
|
| 248 |
+
"3.65",
|
| 249 |
+
"3.78",
|
| 250 |
+
"3.85",
|
| 251 |
+
"3.92",
|
| 252 |
+
"3.45",
|
| 253 |
]
|
| 254 |
|
| 255 |
GRAD_YEARS = list(range(2015, 2027))
|
| 256 |
|
| 257 |
JOB_TITLES = [
|
| 258 |
+
"Software Engineer",
|
| 259 |
+
"Senior Software Engineer",
|
| 260 |
+
"Staff Engineer",
|
| 261 |
+
"Principal Engineer",
|
| 262 |
+
"Engineering Manager",
|
| 263 |
+
"Tech Lead",
|
| 264 |
+
"Data Scientist",
|
| 265 |
+
"Senior Data Scientist",
|
| 266 |
+
"Machine Learning Engineer",
|
| 267 |
+
"ML Research Scientist",
|
| 268 |
+
"Data Engineer",
|
| 269 |
+
"Data Analyst",
|
| 270 |
+
"Product Manager",
|
| 271 |
+
"Senior Product Manager",
|
| 272 |
+
"Program Manager",
|
| 273 |
+
"DevOps Engineer",
|
| 274 |
+
"Site Reliability Engineer",
|
| 275 |
+
"Cloud Architect",
|
| 276 |
+
"Full Stack Developer",
|
| 277 |
+
"Frontend Engineer",
|
| 278 |
+
"Backend Engineer",
|
| 279 |
+
"Mobile Developer",
|
| 280 |
+
"iOS Engineer",
|
| 281 |
+
"Android Developer",
|
| 282 |
+
"QA Engineer",
|
| 283 |
+
"Security Engineer",
|
| 284 |
+
"Solutions Architect",
|
| 285 |
+
"Research Scientist",
|
| 286 |
+
"AI Engineer",
|
| 287 |
+
"NLP Engineer",
|
| 288 |
+
"Quantitative Analyst",
|
| 289 |
+
"Financial Analyst",
|
| 290 |
+
"Business Analyst",
|
| 291 |
+
"UX Designer",
|
| 292 |
+
"UI Engineer",
|
| 293 |
+
"Technical Writer",
|
| 294 |
+
"Intern",
|
| 295 |
+
"Software Engineering Intern",
|
| 296 |
+
"Data Science Intern",
|
| 297 |
]
|
| 298 |
|
| 299 |
PROGRAMMING_LANGUAGES = [
|
| 300 |
+
"Python",
|
| 301 |
+
"Java",
|
| 302 |
+
"JavaScript",
|
| 303 |
+
"TypeScript",
|
| 304 |
+
"C++",
|
| 305 |
+
"C",
|
| 306 |
+
"C#",
|
| 307 |
+
"Go",
|
| 308 |
+
"Rust",
|
| 309 |
+
"Kotlin",
|
| 310 |
+
"Swift",
|
| 311 |
+
"Ruby",
|
| 312 |
+
"PHP",
|
| 313 |
+
"Scala",
|
| 314 |
+
"R",
|
| 315 |
+
"MATLAB",
|
| 316 |
+
"Julia",
|
| 317 |
+
"Haskell",
|
| 318 |
+
"Elixir",
|
| 319 |
+
"Dart",
|
| 320 |
]
|
| 321 |
|
| 322 |
FRAMEWORKS = [
|
| 323 |
+
"React",
|
| 324 |
+
"Angular",
|
| 325 |
+
"Vue.js",
|
| 326 |
+
"Next.js",
|
| 327 |
+
"Django",
|
| 328 |
+
"Flask",
|
| 329 |
+
"FastAPI",
|
| 330 |
+
"Spring Boot",
|
| 331 |
+
"Express.js",
|
| 332 |
+
"Node.js",
|
| 333 |
+
"Rails",
|
| 334 |
+
"TensorFlow",
|
| 335 |
+
"PyTorch",
|
| 336 |
+
"Keras",
|
| 337 |
+
"scikit-learn",
|
| 338 |
+
"Pandas",
|
| 339 |
+
"NumPy",
|
| 340 |
+
"Spark",
|
| 341 |
+
"Hadoop",
|
| 342 |
+
"Kubernetes",
|
| 343 |
+
"Docker",
|
| 344 |
+
"AWS",
|
| 345 |
+
"GCP",
|
| 346 |
+
"Azure",
|
| 347 |
+
"Terraform",
|
| 348 |
+
"Ansible",
|
| 349 |
+
".NET",
|
| 350 |
+
"Laravel",
|
| 351 |
+
"Svelte",
|
| 352 |
+
"Remix",
|
| 353 |
+
"Astro",
|
| 354 |
]
|
| 355 |
|
| 356 |
TOOLS = [
|
| 357 |
+
"Git",
|
| 358 |
+
"GitHub",
|
| 359 |
+
"GitLab",
|
| 360 |
+
"Jira",
|
| 361 |
+
"Confluence",
|
| 362 |
+
"Slack",
|
| 363 |
+
"VS Code",
|
| 364 |
+
"IntelliJ",
|
| 365 |
+
"PyCharm",
|
| 366 |
+
"Vim",
|
| 367 |
+
"Emacs",
|
| 368 |
+
"PostgreSQL",
|
| 369 |
+
"MySQL",
|
| 370 |
+
"MongoDB",
|
| 371 |
+
"Redis",
|
| 372 |
+
"Elasticsearch",
|
| 373 |
+
"Kafka",
|
| 374 |
+
"RabbitMQ",
|
| 375 |
+
"Airflow",
|
| 376 |
+
"dbt",
|
| 377 |
+
"Snowflake",
|
| 378 |
+
"Tableau",
|
| 379 |
+
"Power BI",
|
| 380 |
+
"Grafana",
|
| 381 |
+
"Prometheus",
|
| 382 |
+
"Datadog",
|
| 383 |
+
"Jenkins",
|
| 384 |
+
"CircleCI",
|
| 385 |
+
"GitHub Actions",
|
| 386 |
+
"ArgoCD",
|
| 387 |
+
"Figma",
|
| 388 |
+
"Sketch",
|
| 389 |
+
"Adobe XD",
|
| 390 |
+
"Postman",
|
| 391 |
+
"Swagger",
|
| 392 |
]
|
| 393 |
|
| 394 |
SOFT_SKILLS = [
|
| 395 |
+
"Leadership",
|
| 396 |
+
"Communication",
|
| 397 |
+
"Team Collaboration",
|
| 398 |
+
"Problem Solving",
|
| 399 |
+
"Critical Thinking",
|
| 400 |
+
"Time Management",
|
| 401 |
+
"Project Management",
|
| 402 |
+
"Agile Methodologies",
|
| 403 |
+
"Scrum",
|
| 404 |
+
"Cross-functional Collaboration",
|
| 405 |
+
"Mentoring",
|
| 406 |
+
"Strategic Planning",
|
| 407 |
+
"Stakeholder Management",
|
| 408 |
+
"Technical Writing",
|
| 409 |
+
"Public Speaking",
|
| 410 |
+
"Negotiation",
|
| 411 |
]
|
| 412 |
|
| 413 |
CERTIFICATIONS_LIST = [
|
|
|
|
| 437 |
]
|
| 438 |
|
| 439 |
AWARDS_LIST = [
|
| 440 |
+
"Dean's List",
|
| 441 |
+
"Summa Cum Laude",
|
| 442 |
+
"Magna Cum Laude",
|
| 443 |
+
"Cum Laude",
|
| 444 |
+
"Phi Beta Kappa",
|
| 445 |
+
"Tau Beta Pi",
|
| 446 |
+
"National Merit Scholar",
|
| 447 |
+
"Employee of the Quarter",
|
| 448 |
+
"Spot Bonus Award",
|
| 449 |
+
"President's Club",
|
| 450 |
+
"Best Paper Award",
|
| 451 |
+
"Innovation Award",
|
| 452 |
+
"Hackathon Winner",
|
| 453 |
+
"Outstanding Graduate Student Award",
|
| 454 |
+
"Research Fellowship",
|
| 455 |
+
"Teaching Assistant Excellence Award",
|
| 456 |
+
"Community Service Award",
|
| 457 |
+
"IEEE Best Student Paper",
|
| 458 |
+
"ACM ICPC Regional Finalist",
|
| 459 |
+
"Google Code Jam Qualifier",
|
| 460 |
+
"Facebook Hacker Cup Participant",
|
| 461 |
+
"Patent Holder",
|
| 462 |
+
"Top Performer Award",
|
| 463 |
+
"Rising Star Award",
|
| 464 |
]
|
| 465 |
|
| 466 |
CITIES = [
|
| 467 |
+
"San Francisco, CA",
|
| 468 |
+
"New York, NY",
|
| 469 |
+
"Seattle, WA",
|
| 470 |
+
"Austin, TX",
|
| 471 |
+
"Boston, MA",
|
| 472 |
+
"Chicago, IL",
|
| 473 |
+
"Los Angeles, CA",
|
| 474 |
+
"Denver, CO",
|
| 475 |
+
"Portland, OR",
|
| 476 |
+
"Atlanta, GA",
|
| 477 |
+
"Washington, DC",
|
| 478 |
+
"San Jose, CA",
|
| 479 |
+
"Raleigh, NC",
|
| 480 |
+
"Pittsburgh, PA",
|
| 481 |
+
"Minneapolis, MN",
|
| 482 |
+
"Dallas, TX",
|
| 483 |
+
"Miami, FL",
|
| 484 |
+
"Phoenix, AZ",
|
| 485 |
+
"San Diego, CA",
|
| 486 |
+
"Philadelphia, PA",
|
| 487 |
]
|
| 488 |
|
| 489 |
MONTHS = [
|
| 490 |
+
"January",
|
| 491 |
+
"February",
|
| 492 |
+
"March",
|
| 493 |
+
"April",
|
| 494 |
+
"May",
|
| 495 |
+
"June",
|
| 496 |
+
"July",
|
| 497 |
+
"August",
|
| 498 |
+
"September",
|
| 499 |
+
"October",
|
| 500 |
+
"November",
|
| 501 |
+
"December",
|
| 502 |
]
|
| 503 |
|
| 504 |
MONTHS_SHORT = [
|
| 505 |
+
"Jan",
|
| 506 |
+
"Feb",
|
| 507 |
+
"Mar",
|
| 508 |
+
"Apr",
|
| 509 |
+
"May",
|
| 510 |
+
"Jun",
|
| 511 |
+
"Jul",
|
| 512 |
+
"Aug",
|
| 513 |
+
"Sep",
|
| 514 |
+
"Oct",
|
| 515 |
+
"Nov",
|
| 516 |
+
"Dec",
|
| 517 |
]
|
| 518 |
|
| 519 |
PROJECT_ADJECTIVES = [
|
| 520 |
+
"Real-time",
|
| 521 |
+
"Scalable",
|
| 522 |
+
"Distributed",
|
| 523 |
+
"Cloud-native",
|
| 524 |
+
"AI-powered",
|
| 525 |
+
"Automated",
|
| 526 |
+
"Interactive",
|
| 527 |
+
"Cross-platform",
|
| 528 |
+
"Open-source",
|
| 529 |
+
"End-to-end",
|
| 530 |
+
"High-performance",
|
| 531 |
+
"Serverless",
|
| 532 |
+
"Event-driven",
|
| 533 |
+
"Microservice-based",
|
| 534 |
+
"Full-stack",
|
| 535 |
]
|
| 536 |
|
| 537 |
PROJECT_NOUNS = [
|
| 538 |
+
"Dashboard",
|
| 539 |
+
"Platform",
|
| 540 |
+
"Pipeline",
|
| 541 |
+
"Application",
|
| 542 |
+
"System",
|
| 543 |
+
"API",
|
| 544 |
+
"Framework",
|
| 545 |
+
"Tool",
|
| 546 |
+
"Service",
|
| 547 |
+
"Engine",
|
| 548 |
+
"Chatbot",
|
| 549 |
+
"Recommendation System",
|
| 550 |
+
"Search Engine",
|
| 551 |
+
"Analytics Platform",
|
| 552 |
+
"Monitoring System",
|
| 553 |
+
"Marketplace",
|
| 554 |
]
|
| 555 |
|
| 556 |
IMPACT_METRICS = [
|
|
|
|
| 571 |
]
|
| 572 |
|
| 573 |
PHONE_AREA_CODES = [
|
| 574 |
+
"415",
|
| 575 |
+
"650",
|
| 576 |
+
"408",
|
| 577 |
+
"510",
|
| 578 |
+
"212",
|
| 579 |
+
"646",
|
| 580 |
+
"718",
|
| 581 |
+
"206",
|
| 582 |
+
"512",
|
| 583 |
+
"617",
|
| 584 |
+
"312",
|
| 585 |
+
"213",
|
| 586 |
+
"303",
|
| 587 |
+
"503",
|
| 588 |
+
"404",
|
| 589 |
+
"202",
|
| 590 |
]
|
| 591 |
|
| 592 |
LINKEDIN_PREFIXES = [
|
| 593 |
+
"linkedin.com/in/",
|
| 594 |
+
"www.linkedin.com/in/",
|
| 595 |
]
|
| 596 |
|
| 597 |
GITHUB_PREFIXES = [
|
| 598 |
+
"github.com/",
|
| 599 |
+
"www.github.com/",
|
| 600 |
]
|
| 601 |
|
| 602 |
DOMAINS = [
|
| 603 |
+
"gmail.com",
|
| 604 |
+
"outlook.com",
|
| 605 |
+
"yahoo.com",
|
| 606 |
+
"protonmail.com",
|
| 607 |
+
"icloud.com",
|
| 608 |
+
"hotmail.com",
|
| 609 |
+
"mail.com",
|
| 610 |
]
|
| 611 |
|
| 612 |
# ---------------------------------------------------------------------------
|
|
|
|
| 614 |
# ---------------------------------------------------------------------------
|
| 615 |
|
| 616 |
SYNONYMS = {
|
| 617 |
+
"developed": [
|
| 618 |
+
"built",
|
| 619 |
+
"created",
|
| 620 |
+
"engineered",
|
| 621 |
+
"designed",
|
| 622 |
+
"implemented",
|
| 623 |
+
"constructed",
|
| 624 |
+
"authored",
|
| 625 |
+
],
|
| 626 |
+
"managed": [
|
| 627 |
+
"led",
|
| 628 |
+
"oversaw",
|
| 629 |
+
"directed",
|
| 630 |
+
"supervised",
|
| 631 |
+
"coordinated",
|
| 632 |
+
"administered",
|
| 633 |
+
],
|
| 634 |
+
"improved": [
|
| 635 |
+
"enhanced",
|
| 636 |
+
"optimized",
|
| 637 |
+
"upgraded",
|
| 638 |
+
"refined",
|
| 639 |
+
"boosted",
|
| 640 |
+
"strengthened",
|
| 641 |
+
],
|
| 642 |
+
"implemented": [
|
| 643 |
+
"deployed",
|
| 644 |
+
"executed",
|
| 645 |
+
"delivered",
|
| 646 |
+
"rolled out",
|
| 647 |
+
"launched",
|
| 648 |
+
"shipped",
|
| 649 |
+
],
|
| 650 |
+
"analyzed": [
|
| 651 |
+
"examined",
|
| 652 |
+
"evaluated",
|
| 653 |
+
"assessed",
|
| 654 |
+
"investigated",
|
| 655 |
+
"studied",
|
| 656 |
+
"reviewed",
|
| 657 |
+
],
|
| 658 |
+
"collaborated": [
|
| 659 |
+
"partnered",
|
| 660 |
+
"worked closely with",
|
| 661 |
+
"teamed up with",
|
| 662 |
+
"cooperated with",
|
| 663 |
+
],
|
| 664 |
"responsible for": ["in charge of", "accountable for", "tasked with", "owned"],
|
| 665 |
"utilized": ["leveraged", "employed", "used", "applied", "harnessed"],
|
| 666 |
"achieved": ["accomplished", "attained", "reached", "secured", "delivered"],
|
|
|
|
| 672 |
# Helper utilities
|
| 673 |
# ---------------------------------------------------------------------------
|
| 674 |
|
| 675 |
+
|
| 676 |
def _pick(pool, k=1):
|
| 677 |
"""Return k unique random items from a pool."""
|
| 678 |
k = min(k, len(pool))
|
|
|
|
| 737 |
if w[0].isupper():
|
| 738 |
replacement = replacement.capitalize()
|
| 739 |
# Preserve trailing punctuation
|
| 740 |
+
trailing = w[len(lower) :]
|
| 741 |
result.append(replacement + trailing)
|
| 742 |
else:
|
| 743 |
result.append(w)
|
|
|
|
| 760 |
# Section generators – each returns a string of realistic text
|
| 761 |
# ---------------------------------------------------------------------------
|
| 762 |
|
| 763 |
+
|
| 764 |
def generate_education() -> str:
|
| 765 |
"""Generate a realistic education section."""
|
| 766 |
templates = []
|
|
|
|
| 795 |
|
| 796 |
# Optional coursework
|
| 797 |
if random.random() < 0.5:
|
| 798 |
+
courses = _pick(
|
| 799 |
+
MAJORS
|
| 800 |
+
+ [
|
| 801 |
+
"Algorithms",
|
| 802 |
+
"Data Structures",
|
| 803 |
+
"Operating Systems",
|
| 804 |
+
"Database Systems",
|
| 805 |
+
"Computer Networks",
|
| 806 |
+
"Linear Algebra",
|
| 807 |
+
"Probability and Statistics",
|
| 808 |
+
"Deep Learning",
|
| 809 |
+
"Natural Language Processing",
|
| 810 |
+
"Computer Vision",
|
| 811 |
+
"Distributed Systems",
|
| 812 |
+
],
|
| 813 |
+
k=random.randint(3, 6),
|
| 814 |
+
)
|
| 815 |
+
prefix = random.choice(
|
| 816 |
+
["Relevant Coursework:", "Key Courses:", "Coursework:"]
|
| 817 |
+
)
|
| 818 |
lines.append(f"{prefix} {', '.join(courses)}")
|
| 819 |
|
| 820 |
# Optional honors
|
| 821 |
if random.random() < 0.3:
|
| 822 |
+
honor = random.choice(
|
| 823 |
+
[
|
| 824 |
+
"Summa Cum Laude",
|
| 825 |
+
"Magna Cum Laude",
|
| 826 |
+
"Cum Laude",
|
| 827 |
+
"Dean's List (all semesters)",
|
| 828 |
+
"Honors Program",
|
| 829 |
+
"University Scholar",
|
| 830 |
+
]
|
| 831 |
+
)
|
| 832 |
lines.append(honor)
|
| 833 |
|
| 834 |
# Optional thesis
|
| 835 |
if "Ph.D." in deg_short or ("M.S." in deg_short and random.random() < 0.4):
|
| 836 |
+
topic = random.choice(
|
| 837 |
+
[
|
| 838 |
+
"Transformer-based approaches to document classification",
|
| 839 |
+
"Scalable distributed systems for real-time data processing",
|
| 840 |
+
"Graph neural networks for molecular property prediction",
|
| 841 |
+
"Federated learning in healthcare applications",
|
| 842 |
+
"Efficient attention mechanisms for long-sequence modeling",
|
| 843 |
+
"Reinforcement learning for autonomous navigation",
|
| 844 |
+
]
|
| 845 |
+
)
|
| 846 |
label = "Dissertation" if "Ph.D." in deg_short else "Thesis"
|
| 847 |
+
lines.append(f'{label}: "{topic}"')
|
| 848 |
|
| 849 |
return "\n".join(lines)
|
| 850 |
|
|
|
|
| 856 |
deg_full, deg_short = _pick_one(DEGREES)
|
| 857 |
major = _pick_one(MAJORS)
|
| 858 |
year = _pick_one(GRAD_YEARS)
|
| 859 |
+
gpa_line = (
|
| 860 |
+
f" | GPA: {_pick_one(GPA_VALUES)}" if random.random() < 0.5 else ""
|
| 861 |
+
)
|
| 862 |
entries.append(f"{deg_short} in {major}, {uni}, {year}{gpa_line}")
|
| 863 |
return "\n".join(entries)
|
| 864 |
|
|
|
|
| 870 |
year = _pick_one(GRAD_YEARS)
|
| 871 |
lines = [f"{uni}", f"{deg_full} in {major} | {_pick_one(MONTHS)} {year}"]
|
| 872 |
|
| 873 |
+
activities = random.sample(
|
| 874 |
+
[
|
| 875 |
+
"Teaching Assistant for Introduction to Computer Science",
|
| 876 |
+
"President, Computer Science Student Association",
|
| 877 |
+
"Member, ACM Student Chapter",
|
| 878 |
+
"Undergraduate Research Assistant, ML Lab",
|
| 879 |
+
"Peer Tutor, Mathematics Department",
|
| 880 |
+
"Captain, University Programming Competition Team",
|
| 881 |
+
"Volunteer, Engineering Outreach Program",
|
| 882 |
+
"Member, Honors College",
|
| 883 |
+
"Study Abroad Program, Technical University of Munich",
|
| 884 |
+
"Resident Advisor, Engineering Living-Learning Community",
|
| 885 |
+
],
|
| 886 |
+
k=random.randint(1, 3),
|
| 887 |
+
)
|
| 888 |
|
| 889 |
b = _bullet()
|
| 890 |
for a in activities:
|
|
|
|
| 915 |
|
| 916 |
# Generate bullet points
|
| 917 |
bullet_templates = [
|
| 918 |
+
f"Developed and maintained {random.choice(['microservices', 'APIs', 'web applications', 'data pipelines', 'ML models', 'backend systems', 'frontend components'])} using {', '.join(_pick(PROGRAMMING_LANGUAGES, k=random.randint(1, 3)))} and {', '.join(_pick(FRAMEWORKS, k=random.randint(1, 2)))}",
|
| 919 |
+
f"Collaborated with cross-functional teams of {random.randint(3, 15)} engineers to deliver {random.choice(['product features', 'platform improvements', 'system migrations', 'infrastructure upgrades'])} on schedule",
|
| 920 |
f"Designed and implemented {random.choice(['CI/CD pipelines', 'testing frameworks', 'monitoring solutions', 'data models', 'caching strategies', 'authentication systems'])} that {_impact()}",
|
| 921 |
f"Led migration of {random.choice(['legacy monolith', 'on-premise infrastructure', 'batch processing system', 'manual workflows'])} to {random.choice(['cloud-native architecture', 'microservices', 'real-time streaming', 'automated pipelines'])}",
|
| 922 |
+
f"Mentored {random.randint(2, 8)} junior engineers through code reviews, pair programming, and technical design sessions",
|
| 923 |
f"Optimized {random.choice(['database queries', 'API response times', 'model inference', 'data processing pipelines', 'search indexing'])} resulting in {_impact()}",
|
| 924 |
f"Wrote comprehensive technical documentation and {random.choice(['RFCs', 'design docs', 'runbooks', 'architecture decision records'])} for {random.choice(['system design', 'API contracts', 'deployment procedures', 'incident response'])}",
|
| 925 |
f"Built {random.choice(['real-time', 'batch', 'streaming', 'event-driven'])} {random.choice(['data pipeline', 'ETL process', 'analytics system', 'feature store'])} processing {random.choice(['1M+', '10M+', '100M+', '1B+'])} records {random.choice(['daily', 'per hour', 'in real-time'])}",
|
|
|
|
| 929 |
]
|
| 930 |
|
| 931 |
n_bullets = random.randint(2, 5)
|
| 932 |
+
selected = random.sample(
|
| 933 |
+
bullet_templates, min(n_bullets, len(bullet_templates))
|
| 934 |
+
)
|
| 935 |
selected = _reorder_bullets(selected)
|
| 936 |
b = _bullet()
|
| 937 |
for bullet in selected:
|
|
|
|
| 960 |
|
| 961 |
if random.random() < 0.9:
|
| 962 |
fws = _pick(FRAMEWORKS, k=random.randint(3, 7))
|
| 963 |
+
label = random.choice(
|
| 964 |
+
["Frameworks", "Frameworks & Libraries", "Technologies"]
|
| 965 |
+
)
|
| 966 |
categories.append((label, fws))
|
| 967 |
|
| 968 |
if random.random() < 0.8:
|
|
|
|
| 983 |
return "\n".join(lines)
|
| 984 |
|
| 985 |
def _t_flat():
|
| 986 |
+
all_skills = (
|
| 987 |
+
_pick(PROGRAMMING_LANGUAGES, k=random.randint(3, 6))
|
| 988 |
+
+ _pick(FRAMEWORKS, k=random.randint(3, 6))
|
| 989 |
+
+ _pick(TOOLS, k=random.randint(2, 4))
|
| 990 |
+
)
|
| 991 |
random.shuffle(all_skills)
|
| 992 |
joiner = random.choice([", ", " | ", " · ", " • "])
|
| 993 |
return joiner.join(all_skills)
|
|
|
|
| 997 |
levels = ["Expert", "Advanced", "Proficient", "Intermediate", "Familiar"]
|
| 998 |
used = set()
|
| 999 |
for level in random.sample(levels, k=random.randint(2, 4)):
|
| 1000 |
+
pool = [
|
| 1001 |
+
s for s in PROGRAMMING_LANGUAGES + FRAMEWORKS + TOOLS if s not in used
|
| 1002 |
+
]
|
| 1003 |
items = _pick(pool, k=random.randint(2, 5))
|
| 1004 |
used.update(items)
|
| 1005 |
lines.append(f"{level}: {', '.join(items)}")
|
|
|
|
| 1052 |
def generate_summary() -> str:
|
| 1053 |
"""Generate a realistic professional summary / objective section."""
|
| 1054 |
years = random.randint(2, 15)
|
| 1055 |
+
specialties = _pick(
|
| 1056 |
+
MAJORS
|
| 1057 |
+
+ [
|
| 1058 |
+
"full-stack development",
|
| 1059 |
+
"distributed systems",
|
| 1060 |
+
"machine learning",
|
| 1061 |
+
"data engineering",
|
| 1062 |
+
"cloud architecture",
|
| 1063 |
+
"mobile development",
|
| 1064 |
+
"DevOps",
|
| 1065 |
+
"backend development",
|
| 1066 |
+
"frontend development",
|
| 1067 |
+
"natural language processing",
|
| 1068 |
+
"computer vision",
|
| 1069 |
+
],
|
| 1070 |
+
k=random.randint(1, 3),
|
| 1071 |
+
)
|
| 1072 |
|
| 1073 |
templates = [
|
| 1074 |
# Template 1: Traditional summary
|
| 1075 |
+
lambda: (
|
| 1076 |
+
f"Results-driven {_pick_one(JOB_TITLES).lower()} with {years}+ years of experience in {' and '.join(specialties)}. Proven track record of {random.choice(['delivering high-impact solutions', 'building scalable systems', 'driving technical excellence', 'leading cross-functional teams'])} at companies like {_pick_one(COMPANIES)} and {_pick_one(COMPANIES)}. Passionate about {random.choice(['clean code', 'system design', 'open source', 'mentorship', 'continuous learning', 'innovation'])} and {random.choice(['building products that scale', 'solving complex problems', 'leveraging data-driven insights', 'improving developer experience'])}."
|
| 1077 |
+
),
|
| 1078 |
# Template 2: Technical focus
|
| 1079 |
+
lambda: (
|
| 1080 |
+
f"Experienced {_pick_one(JOB_TITLES).lower()} specializing in {', '.join(specialties)}. Skilled in {', '.join(_pick(PROGRAMMING_LANGUAGES, k=3))} with deep expertise in {', '.join(_pick(FRAMEWORKS, k=2))}. {random.choice(['Strong background in', 'Demonstrated ability in', 'Track record of'])} {random.choice(['building distributed systems at scale', 'developing ML models for production', 'architecting cloud-native applications', 'leading agile engineering teams'])}. Seeking to {random.choice(['contribute to cutting-edge products', 'drive technical innovation', 'solve challenging problems', 'build impactful technology'])} at a {random.choice(['fast-growing startup', 'leading technology company', 'mission-driven organization'])}."
|
| 1081 |
+
),
|
| 1082 |
# Template 3: Achievement-oriented
|
| 1083 |
+
lambda: (
|
| 1084 |
+
f"{_pick_one(JOB_TITLES)} with {years} years of experience building {random.choice(['enterprise-scale', 'consumer-facing', 'B2B', 'data-intensive'])} applications. Key achievements include {_impact()}, {_impact()}, and {_impact()}. Proficient in {', '.join(_pick(PROGRAMMING_LANGUAGES, k=3))} and {', '.join(_pick(FRAMEWORKS, k=2))}."
|
| 1085 |
+
),
|
| 1086 |
# Template 4: Brief objective
|
| 1087 |
+
lambda: (
|
| 1088 |
+
f"Motivated {random.choice(['professional', 'engineer', 'developer', 'technologist'])} seeking a {_pick_one(JOB_TITLES).lower()} role where I can apply my expertise in {' and '.join(specialties)} to {random.choice(['build innovative products', 'solve real-world problems', 'drive business impact', 'push the boundaries of technology'])}."
|
| 1089 |
+
),
|
| 1090 |
# Template 5: Narrative style
|
| 1091 |
+
lambda: (
|
| 1092 |
+
f"I am a {_pick_one(JOB_TITLES).lower()} who thrives at the intersection of {_pick_one(specialties)} and {_pick_one(specialties)}. Over the past {years} years, I have {random.choice(['shipped products used by millions', 'built ML systems processing petabytes of data', 'led engineering teams through rapid growth', 'contributed to open-source projects with thousands of stars'])}. I bring a {random.choice(['data-driven', 'user-centric', 'systems-thinking', 'first-principles'])} approach to every problem I tackle."
|
| 1093 |
+
),
|
| 1094 |
]
|
| 1095 |
|
| 1096 |
return random.choice(templates)()
|
|
|
|
| 1108 |
f"{cert} ({year})",
|
| 1109 |
f"{cert} — Issued {_pick_one(MONTHS)} {year}",
|
| 1110 |
f"{cert}, {year}",
|
| 1111 |
+
f"{cert}\n Issued: {_pick_one(MONTHS_SHORT)} {year}"
|
| 1112 |
+
+ (
|
| 1113 |
f" | Expires: {_pick_one(MONTHS_SHORT)} {year + random.randint(2, 3)}"
|
| 1114 |
+
if random.random() < 0.3
|
| 1115 |
+
else ""
|
| 1116 |
),
|
| 1117 |
]
|
| 1118 |
lines.append(random.choice(styles))
|
|
|
|
| 1129 |
last = _pick_one(LAST_NAMES)
|
| 1130 |
city = _pick_one(CITIES)
|
| 1131 |
area_code = _pick_one(PHONE_AREA_CODES)
|
| 1132 |
+
email_user = random.choice(
|
| 1133 |
+
[
|
| 1134 |
+
f"{first.lower()}.{last.lower()}",
|
| 1135 |
+
f"{first.lower()}{last.lower()}",
|
| 1136 |
+
f"{first[0].lower()}{last.lower()}",
|
| 1137 |
+
f"{first.lower()}_{last.lower()}",
|
| 1138 |
+
f"{first.lower()}{random.randint(1, 99)}",
|
| 1139 |
+
]
|
| 1140 |
+
)
|
| 1141 |
email = f"{email_user}@{_pick_one(DOMAINS)}"
|
| 1142 |
+
phone = f"({area_code}) {random.randint(100, 999)}-{random.randint(1000, 9999)}"
|
| 1143 |
linkedin_user = f"{first.lower()}-{last.lower()}-{random.randint(100, 999)}"
|
| 1144 |
github_user = f"{first.lower()}{last.lower()}"
|
| 1145 |
|
|
|
|
| 1156 |
if random.random() < 0.4:
|
| 1157 |
parts.append(f"{_pick_one(GITHUB_PREFIXES)}{github_user}")
|
| 1158 |
if random.random() < 0.2:
|
| 1159 |
+
parts.append(
|
| 1160 |
+
f"{github_user}.dev"
|
| 1161 |
+
if random.random() < 0.5
|
| 1162 |
+
else f"{first.lower()}{last.lower()}.com"
|
| 1163 |
+
)
|
| 1164 |
|
| 1165 |
sep = random.choice(["\n", " | ", " · ", "\n"])
|
| 1166 |
return sep.join(parts)
|
|
|
|
| 1174 |
|
| 1175 |
for award in awards:
|
| 1176 |
year = random.randint(2015, 2025)
|
| 1177 |
+
org = random.choice(
|
| 1178 |
+
[
|
| 1179 |
+
_pick_one(UNIVERSITIES),
|
| 1180 |
+
_pick_one(COMPANIES),
|
| 1181 |
+
random.choice(
|
| 1182 |
+
[
|
| 1183 |
+
"ACM",
|
| 1184 |
+
"IEEE",
|
| 1185 |
+
"Google",
|
| 1186 |
+
"Facebook",
|
| 1187 |
+
"Microsoft",
|
| 1188 |
+
"National Science Foundation",
|
| 1189 |
+
"Department of Education",
|
| 1190 |
+
]
|
| 1191 |
+
),
|
| 1192 |
+
]
|
| 1193 |
+
)
|
| 1194 |
styles = [
|
| 1195 |
f"{award}, {org} ({year})",
|
| 1196 |
f"{award} — {org}, {year}",
|
|
|
|
| 1210 |
# ---------------------------------------------------------------------------
|
| 1211 |
|
| 1212 |
SECTION_HEADERS = {
|
| 1213 |
+
"education": [
|
| 1214 |
+
"EDUCATION",
|
| 1215 |
+
"Education",
|
| 1216 |
+
"Academic Background",
|
| 1217 |
+
"ACADEMIC BACKGROUND",
|
| 1218 |
+
"Education & Training",
|
| 1219 |
+
],
|
| 1220 |
+
"experience": [
|
| 1221 |
+
"EXPERIENCE",
|
| 1222 |
+
"Experience",
|
| 1223 |
+
"WORK EXPERIENCE",
|
| 1224 |
+
"Work Experience",
|
| 1225 |
+
"PROFESSIONAL EXPERIENCE",
|
| 1226 |
+
"Professional Experience",
|
| 1227 |
+
"Employment History",
|
| 1228 |
+
],
|
| 1229 |
+
"skills": [
|
| 1230 |
+
"SKILLS",
|
| 1231 |
+
"Skills",
|
| 1232 |
+
"TECHNICAL SKILLS",
|
| 1233 |
+
"Technical Skills",
|
| 1234 |
+
"Core Competencies",
|
| 1235 |
+
"CORE COMPETENCIES",
|
| 1236 |
+
"Technologies",
|
| 1237 |
+
],
|
| 1238 |
+
"projects": [
|
| 1239 |
+
"PROJECTS",
|
| 1240 |
+
"Projects",
|
| 1241 |
+
"PERSONAL PROJECTS",
|
| 1242 |
+
"Personal Projects",
|
| 1243 |
+
"SIDE PROJECTS",
|
| 1244 |
+
"Selected Projects",
|
| 1245 |
+
"Portfolio",
|
| 1246 |
+
],
|
| 1247 |
+
"summary": [
|
| 1248 |
+
"SUMMARY",
|
| 1249 |
+
"Summary",
|
| 1250 |
+
"PROFESSIONAL SUMMARY",
|
| 1251 |
+
"Professional Summary",
|
| 1252 |
+
"OBJECTIVE",
|
| 1253 |
+
"Objective",
|
| 1254 |
+
"PROFILE",
|
| 1255 |
+
"Profile",
|
| 1256 |
+
"About Me",
|
| 1257 |
+
"ABOUT",
|
| 1258 |
+
],
|
| 1259 |
+
"certifications": [
|
| 1260 |
+
"CERTIFICATIONS",
|
| 1261 |
+
"Certifications",
|
| 1262 |
+
"CERTIFICATES",
|
| 1263 |
+
"Certificates",
|
| 1264 |
+
"Licenses & Certifications",
|
| 1265 |
+
"PROFESSIONAL CERTIFICATIONS",
|
| 1266 |
+
],
|
| 1267 |
+
"contact": [
|
| 1268 |
+
"CONTACT",
|
| 1269 |
+
"Contact",
|
| 1270 |
+
"CONTACT INFORMATION",
|
| 1271 |
+
"Contact Information",
|
| 1272 |
+
"Personal Information",
|
| 1273 |
+
],
|
| 1274 |
+
"awards": [
|
| 1275 |
+
"AWARDS",
|
| 1276 |
+
"Awards",
|
| 1277 |
+
"HONORS & AWARDS",
|
| 1278 |
+
"Honors & Awards",
|
| 1279 |
+
"ACHIEVEMENTS",
|
| 1280 |
+
"Achievements",
|
| 1281 |
+
"Awards & Honors",
|
| 1282 |
+
"RECOGNITION",
|
| 1283 |
+
],
|
| 1284 |
}
|
| 1285 |
|
| 1286 |
GENERATORS = {
|
|
|
|
| 1299 |
# Dataset generation
|
| 1300 |
# ---------------------------------------------------------------------------
|
| 1301 |
|
| 1302 |
+
|
| 1303 |
+
def generate_example(
|
| 1304 |
+
label: str, include_header: bool = False, augment: bool = False
|
| 1305 |
+
) -> str:
|
| 1306 |
"""
|
| 1307 |
Generate a single synthetic example for the given label.
|
| 1308 |
|
|
|
|
| 1365 |
|
| 1366 |
# Generate augmented versions
|
| 1367 |
for _ in range(augmented_copies):
|
| 1368 |
+
aug_text = generate_example(
|
| 1369 |
+
label, include_header=include_header, augment=True
|
| 1370 |
+
)
|
| 1371 |
dataset.append({"text": aug_text, "label": label})
|
| 1372 |
|
| 1373 |
random.shuffle(dataset)
|
|
|
|
| 1400 |
|
| 1401 |
# 80/10/10 split, stratified at both steps
|
| 1402 |
train_test = ds.train_test_split(test_size=0.2, seed=42, stratify_by_column="label")
|
| 1403 |
+
val_test = train_test["test"].train_test_split(
|
| 1404 |
+
test_size=0.5, seed=42, stratify_by_column="label"
|
| 1405 |
+
)
|
| 1406 |
|
| 1407 |
# Decode ids back to names. Casting ClassLabel -> string does NOT do this
|
| 1408 |
# (it stringifies the ids, giving "0"/"1"), and mapping into a column that
|
|
|
|
| 1418 |
"validation": val_test["train"],
|
| 1419 |
"test": val_test["test"],
|
| 1420 |
}
|
| 1421 |
+
return DatasetDict(
|
| 1422 |
+
{
|
| 1423 |
+
name: split.map(to_label_name, batched=True, features=string_features)
|
| 1424 |
+
for name, split in splits.items()
|
| 1425 |
+
}
|
| 1426 |
+
)
|
| 1427 |
|
| 1428 |
|
| 1429 |
def get_label_mapping(dataset: list[dict]) -> tuple[dict, dict]:
|
|
|
|
| 1441 |
if __name__ == "__main__":
|
| 1442 |
import argparse
|
| 1443 |
|
| 1444 |
+
parser = argparse.ArgumentParser(
|
| 1445 |
+
description="Generate synthetic resume section data"
|
| 1446 |
+
)
|
| 1447 |
+
parser.add_argument(
|
| 1448 |
+
"--examples-per-category",
|
| 1449 |
+
type=int,
|
| 1450 |
+
default=80,
|
| 1451 |
+
help="Number of base examples per category (default: 80)",
|
| 1452 |
+
)
|
| 1453 |
+
parser.add_argument(
|
| 1454 |
+
"--augmented-copies",
|
| 1455 |
+
type=int,
|
| 1456 |
+
default=2,
|
| 1457 |
+
help="Number of augmented copies per example (default: 2)",
|
| 1458 |
+
)
|
| 1459 |
+
parser.add_argument(
|
| 1460 |
+
"--output",
|
| 1461 |
+
type=str,
|
| 1462 |
+
default="data/resume_sections.csv",
|
| 1463 |
+
help="Output CSV path (default: data/resume_sections.csv)",
|
| 1464 |
+
)
|
| 1465 |
+
parser.add_argument(
|
| 1466 |
+
"--seed", type=int, default=42, help="Random seed (default: 42)"
|
| 1467 |
+
)
|
| 1468 |
+
parser.add_argument(
|
| 1469 |
+
"--print-stats", action="store_true", help="Print dataset statistics"
|
| 1470 |
+
)
|
| 1471 |
+
parser.add_argument(
|
| 1472 |
+
"--print-samples", type=int, default=0, help="Print N sample examples"
|
| 1473 |
+
)
|
| 1474 |
|
| 1475 |
args = parser.parse_args()
|
| 1476 |
|
| 1477 |
+
print(
|
| 1478 |
+
f"Generating dataset with {args.examples_per_category} base examples per category..."
|
| 1479 |
+
)
|
| 1480 |
print(f"Augmented copies per example: {args.augmented_copies}")
|
| 1481 |
+
print(
|
| 1482 |
+
f"Total expected examples: {args.examples_per_category * (1 + args.augmented_copies) * 8}"
|
| 1483 |
+
)
|
| 1484 |
|
| 1485 |
dataset = generate_dataset(
|
| 1486 |
examples_per_category=args.examples_per_category,
|
|
|
|
| 1492 |
|
| 1493 |
if args.print_stats:
|
| 1494 |
from collections import Counter
|
| 1495 |
+
|
| 1496 |
counts = Counter(d["label"] for d in dataset)
|
| 1497 |
print("\nDataset Statistics:")
|
| 1498 |
print(f" Total examples: {len(dataset)}")
|
|
|
|
| 1503 |
print(f" Average text length: {avg_len:.0f} chars")
|
| 1504 |
|
| 1505 |
if args.print_samples > 0:
|
| 1506 |
+
print(f"\n{'=' * 60}")
|
| 1507 |
print(f"Sample Examples (first {args.print_samples}):")
|
| 1508 |
+
print(f"{'=' * 60}")
|
| 1509 |
+
for i, example in enumerate(dataset[: args.print_samples]):
|
| 1510 |
+
print(f"\n--- Example {i + 1} [{example['label']}] ---")
|
| 1511 |
print(example["text"][:300])
|
| 1512 |
if len(example["text"]) > 300:
|
| 1513 |
print("...")
|
inference.py
CHANGED
|
@@ -39,9 +39,11 @@ from transformers import AutoModelForSequenceClassification, AutoTokenizer
|
|
| 39 |
# Data classes
|
| 40 |
# ---------------------------------------------------------------------------
|
| 41 |
|
|
|
|
| 42 |
@dataclass
|
| 43 |
class SectionPrediction:
|
| 44 |
"""A single section classification result."""
|
|
|
|
| 45 |
text: str
|
| 46 |
label: str
|
| 47 |
confidence: float
|
|
@@ -59,6 +61,7 @@ class SectionPrediction:
|
|
| 59 |
@dataclass
|
| 60 |
class ResumeAnalysis:
|
| 61 |
"""Complete resume analysis output."""
|
|
|
|
| 62 |
sections: list
|
| 63 |
section_count: int = 0
|
| 64 |
label_distribution: dict = field(default_factory=dict)
|
|
@@ -114,7 +117,9 @@ SECTION_HEADER_PATTERNS = [
|
|
| 114 |
r"ACTIVITIES|LEADERSHIP|RESEARCH)\s*:?\s*$",
|
| 115 |
]
|
| 116 |
|
| 117 |
-
COMPILED_HEADERS = [
|
|
|
|
|
|
|
| 118 |
|
| 119 |
|
| 120 |
def is_section_header(line: str) -> bool:
|
|
@@ -196,6 +201,7 @@ def split_resume_into_sections(text: str, min_section_length: int = 20) -> list:
|
|
| 196 |
# Classifier
|
| 197 |
# ---------------------------------------------------------------------------
|
| 198 |
|
|
|
|
| 199 |
class ResumeSectionClassifier:
|
| 200 |
"""
|
| 201 |
Classifies resume text sections into categories.
|
|
@@ -313,17 +319,21 @@ class ResumeSectionClassifier:
|
|
| 313 |
|
| 314 |
results = []
|
| 315 |
for i, text in enumerate(texts):
|
| 316 |
-
scores = {
|
|
|
|
|
|
|
| 317 |
predicted_id = probs[i].argmax().item()
|
| 318 |
predicted_label = self.id2label[predicted_id]
|
| 319 |
confidence = probs[i][predicted_id].item()
|
| 320 |
|
| 321 |
-
results.append(
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
|
|
|
|
|
|
|
| 327 |
|
| 328 |
return results
|
| 329 |
|
|
@@ -361,6 +371,7 @@ class ResumeSectionClassifier:
|
|
| 361 |
# CLI
|
| 362 |
# ---------------------------------------------------------------------------
|
| 363 |
|
|
|
|
| 364 |
def main():
|
| 365 |
import argparse
|
| 366 |
|
|
@@ -380,18 +391,42 @@ Examples:
|
|
| 380 |
input_group.add_argument("--file", type=str, help="Path to resume text file")
|
| 381 |
input_group.add_argument("--text", type=str, help="Direct text to classify")
|
| 382 |
|
| 383 |
-
parser.add_argument(
|
| 384 |
-
|
| 385 |
-
|
| 386 |
-
|
| 387 |
-
|
| 388 |
-
|
| 389 |
-
parser.add_argument(
|
| 390 |
-
|
| 391 |
-
|
| 392 |
-
|
| 393 |
-
|
| 394 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 395 |
|
| 396 |
args = parser.parse_args()
|
| 397 |
|
|
@@ -430,7 +465,9 @@ Examples:
|
|
| 430 |
bar = "#" * int(score * 40)
|
| 431 |
print(f" {label:20s} {score:.4f} {bar}")
|
| 432 |
else:
|
| 433 |
-
analysis = classifier.classify_resume(
|
|
|
|
|
|
|
| 434 |
if args.format == "json":
|
| 435 |
print(analysis.to_json())
|
| 436 |
else:
|
|
|
|
| 39 |
# Data classes
|
| 40 |
# ---------------------------------------------------------------------------
|
| 41 |
|
| 42 |
+
|
| 43 |
@dataclass
|
| 44 |
class SectionPrediction:
|
| 45 |
"""A single section classification result."""
|
| 46 |
+
|
| 47 |
text: str
|
| 48 |
label: str
|
| 49 |
confidence: float
|
|
|
|
| 61 |
@dataclass
|
| 62 |
class ResumeAnalysis:
|
| 63 |
"""Complete resume analysis output."""
|
| 64 |
+
|
| 65 |
sections: list
|
| 66 |
section_count: int = 0
|
| 67 |
label_distribution: dict = field(default_factory=dict)
|
|
|
|
| 117 |
r"ACTIVITIES|LEADERSHIP|RESEARCH)\s*:?\s*$",
|
| 118 |
]
|
| 119 |
|
| 120 |
+
COMPILED_HEADERS = [
|
| 121 |
+
re.compile(p, re.MULTILINE | re.IGNORECASE) for p in SECTION_HEADER_PATTERNS
|
| 122 |
+
]
|
| 123 |
|
| 124 |
|
| 125 |
def is_section_header(line: str) -> bool:
|
|
|
|
| 201 |
# Classifier
|
| 202 |
# ---------------------------------------------------------------------------
|
| 203 |
|
| 204 |
+
|
| 205 |
class ResumeSectionClassifier:
|
| 206 |
"""
|
| 207 |
Classifies resume text sections into categories.
|
|
|
|
| 319 |
|
| 320 |
results = []
|
| 321 |
for i, text in enumerate(texts):
|
| 322 |
+
scores = {
|
| 323 |
+
self.id2label[j]: probs[i][j].item() for j in range(probs.shape[1])
|
| 324 |
+
}
|
| 325 |
predicted_id = probs[i].argmax().item()
|
| 326 |
predicted_label = self.id2label[predicted_id]
|
| 327 |
confidence = probs[i][predicted_id].item()
|
| 328 |
|
| 329 |
+
results.append(
|
| 330 |
+
SectionPrediction(
|
| 331 |
+
text=text,
|
| 332 |
+
label=predicted_label,
|
| 333 |
+
confidence=confidence,
|
| 334 |
+
all_scores=scores,
|
| 335 |
+
)
|
| 336 |
+
)
|
| 337 |
|
| 338 |
return results
|
| 339 |
|
|
|
|
| 371 |
# CLI
|
| 372 |
# ---------------------------------------------------------------------------
|
| 373 |
|
| 374 |
+
|
| 375 |
def main():
|
| 376 |
import argparse
|
| 377 |
|
|
|
|
| 391 |
input_group.add_argument("--file", type=str, help="Path to resume text file")
|
| 392 |
input_group.add_argument("--text", type=str, help="Direct text to classify")
|
| 393 |
|
| 394 |
+
parser.add_argument(
|
| 395 |
+
"--model",
|
| 396 |
+
type=str,
|
| 397 |
+
default="./model_output/final_model",
|
| 398 |
+
help="Path to fine-tuned model (default: ./model_output/final_model)",
|
| 399 |
+
)
|
| 400 |
+
parser.add_argument(
|
| 401 |
+
"--device",
|
| 402 |
+
type=str,
|
| 403 |
+
default=None,
|
| 404 |
+
help="Device: cpu, cuda, mps (auto-detected if omitted)",
|
| 405 |
+
)
|
| 406 |
+
parser.add_argument(
|
| 407 |
+
"--max-length",
|
| 408 |
+
type=int,
|
| 409 |
+
default=256,
|
| 410 |
+
help="Maximum token sequence length (default: 256)",
|
| 411 |
+
)
|
| 412 |
+
parser.add_argument(
|
| 413 |
+
"--min-section-length",
|
| 414 |
+
type=int,
|
| 415 |
+
default=20,
|
| 416 |
+
help="Minimum section length in characters (default: 20)",
|
| 417 |
+
)
|
| 418 |
+
parser.add_argument(
|
| 419 |
+
"--format",
|
| 420 |
+
type=str,
|
| 421 |
+
choices=["text", "json"],
|
| 422 |
+
default="text",
|
| 423 |
+
help="Output format (default: text)",
|
| 424 |
+
)
|
| 425 |
+
parser.add_argument(
|
| 426 |
+
"--single",
|
| 427 |
+
action="store_true",
|
| 428 |
+
help="Classify as single section (no splitting)",
|
| 429 |
+
)
|
| 430 |
|
| 431 |
args = parser.parse_args()
|
| 432 |
|
|
|
|
| 465 |
bar = "#" * int(score * 40)
|
| 466 |
print(f" {label:20s} {score:.4f} {bar}")
|
| 467 |
else:
|
| 468 |
+
analysis = classifier.classify_resume(
|
| 469 |
+
text, min_section_length=args.min_section_length
|
| 470 |
+
)
|
| 471 |
if args.format == "json":
|
| 472 |
print(analysis.to_json())
|
| 473 |
else:
|
train.py
CHANGED
|
@@ -69,10 +69,18 @@ def build_compute_metrics(id2label: dict):
|
|
| 69 |
predictions = np.argmax(logits, axis=-1)
|
| 70 |
|
| 71 |
acc = accuracy_metric.compute(predictions=predictions, references=labels)
|
| 72 |
-
f1_macro = f1_metric.compute(
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
|
| 77 |
return {
|
| 78 |
"accuracy": acc["accuracy"],
|
|
@@ -88,7 +96,9 @@ def build_compute_metrics(id2label: dict):
|
|
| 88 |
# ---------------------------------------------------------------------------
|
| 89 |
# Tokenization
|
| 90 |
# ---------------------------------------------------------------------------
|
| 91 |
-
def tokenize_dataset(
|
|
|
|
|
|
|
| 92 |
"""Tokenize all splits and encode labels as integers."""
|
| 93 |
|
| 94 |
def preprocess(examples):
|
|
@@ -166,7 +176,9 @@ def train(
|
|
| 166 |
logger.info(f"Model: {model_name}")
|
| 167 |
logger.info(f"Output: {output_dir}")
|
| 168 |
logger.info(f"Epochs: {epochs}, Batch size: {batch_size}, LR: {learning_rate}")
|
| 169 |
-
logger.info(
|
|
|
|
|
|
|
| 170 |
logger.info(f"FP16: {fp16}")
|
| 171 |
|
| 172 |
# ------------------------------------------------------------------
|
|
@@ -209,7 +221,9 @@ def train(
|
|
| 209 |
label2id=label2id,
|
| 210 |
)
|
| 211 |
logger.info(f" Parameters: {sum(p.numel() for p in model.parameters()):,}")
|
| 212 |
-
logger.info(
|
|
|
|
|
|
|
| 213 |
|
| 214 |
# ------------------------------------------------------------------
|
| 215 |
# 4. Training
|
|
@@ -253,7 +267,9 @@ def train(
|
|
| 253 |
|
| 254 |
callbacks = []
|
| 255 |
if early_stopping_patience > 0:
|
| 256 |
-
callbacks.append(
|
|
|
|
|
|
|
| 257 |
|
| 258 |
trainer = Trainer(
|
| 259 |
model=model,
|
|
@@ -281,7 +297,9 @@ def train(
|
|
| 281 |
|
| 282 |
logger.info("\nTest Results:")
|
| 283 |
for key, value in test_results.items():
|
| 284 |
-
logger.info(
|
|
|
|
|
|
|
| 285 |
|
| 286 |
# ------------------------------------------------------------------
|
| 287 |
# Save artifacts
|
|
@@ -365,48 +383,92 @@ if __name__ == "__main__":
|
|
| 365 |
)
|
| 366 |
|
| 367 |
# Model & output
|
| 368 |
-
parser.add_argument(
|
| 369 |
-
|
| 370 |
-
|
| 371 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 372 |
|
| 373 |
# Training hyperparameters
|
| 374 |
-
parser.add_argument(
|
| 375 |
-
|
| 376 |
-
|
| 377 |
-
|
| 378 |
-
|
| 379 |
-
|
| 380 |
-
parser.add_argument(
|
| 381 |
-
|
| 382 |
-
|
| 383 |
-
|
| 384 |
-
|
| 385 |
-
|
| 386 |
-
parser.add_argument(
|
| 387 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 388 |
|
| 389 |
# Data
|
| 390 |
-
parser.add_argument(
|
| 391 |
-
|
| 392 |
-
|
| 393 |
-
|
| 394 |
-
|
| 395 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 396 |
|
| 397 |
# Training config
|
| 398 |
-
parser.add_argument(
|
| 399 |
-
|
| 400 |
-
|
| 401 |
-
|
| 402 |
-
parser.add_argument(
|
| 403 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 404 |
|
| 405 |
# Hub
|
| 406 |
-
parser.add_argument(
|
| 407 |
-
|
| 408 |
-
|
| 409 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 410 |
|
| 411 |
args = parser.parse_args()
|
| 412 |
|
|
|
|
| 69 |
predictions = np.argmax(logits, axis=-1)
|
| 70 |
|
| 71 |
acc = accuracy_metric.compute(predictions=predictions, references=labels)
|
| 72 |
+
f1_macro = f1_metric.compute(
|
| 73 |
+
predictions=predictions, references=labels, average="macro"
|
| 74 |
+
)
|
| 75 |
+
f1_weighted = f1_metric.compute(
|
| 76 |
+
predictions=predictions, references=labels, average="weighted"
|
| 77 |
+
)
|
| 78 |
+
precision = precision_metric.compute(
|
| 79 |
+
predictions=predictions, references=labels, average="weighted"
|
| 80 |
+
)
|
| 81 |
+
recall = recall_metric.compute(
|
| 82 |
+
predictions=predictions, references=labels, average="weighted"
|
| 83 |
+
)
|
| 84 |
|
| 85 |
return {
|
| 86 |
"accuracy": acc["accuracy"],
|
|
|
|
| 96 |
# ---------------------------------------------------------------------------
|
| 97 |
# Tokenization
|
| 98 |
# ---------------------------------------------------------------------------
|
| 99 |
+
def tokenize_dataset(
|
| 100 |
+
dataset_dict: DatasetDict, tokenizer, label2id: dict, max_length: int = MAX_LENGTH
|
| 101 |
+
):
|
| 102 |
"""Tokenize all splits and encode labels as integers."""
|
| 103 |
|
| 104 |
def preprocess(examples):
|
|
|
|
| 176 |
logger.info(f"Model: {model_name}")
|
| 177 |
logger.info(f"Output: {output_dir}")
|
| 178 |
logger.info(f"Epochs: {epochs}, Batch size: {batch_size}, LR: {learning_rate}")
|
| 179 |
+
logger.info(
|
| 180 |
+
f"Device: {'CUDA' if torch.cuda.is_available() else 'MPS' if torch.backends.mps.is_available() else 'CPU'}"
|
| 181 |
+
)
|
| 182 |
logger.info(f"FP16: {fp16}")
|
| 183 |
|
| 184 |
# ------------------------------------------------------------------
|
|
|
|
| 221 |
label2id=label2id,
|
| 222 |
)
|
| 223 |
logger.info(f" Parameters: {sum(p.numel() for p in model.parameters()):,}")
|
| 224 |
+
logger.info(
|
| 225 |
+
f" Trainable: {sum(p.numel() for p in model.parameters() if p.requires_grad):,}"
|
| 226 |
+
)
|
| 227 |
|
| 228 |
# ------------------------------------------------------------------
|
| 229 |
# 4. Training
|
|
|
|
| 267 |
|
| 268 |
callbacks = []
|
| 269 |
if early_stopping_patience > 0:
|
| 270 |
+
callbacks.append(
|
| 271 |
+
EarlyStoppingCallback(early_stopping_patience=early_stopping_patience)
|
| 272 |
+
)
|
| 273 |
|
| 274 |
trainer = Trainer(
|
| 275 |
model=model,
|
|
|
|
| 297 |
|
| 298 |
logger.info("\nTest Results:")
|
| 299 |
for key, value in test_results.items():
|
| 300 |
+
logger.info(
|
| 301 |
+
f" {key}: {value:.4f}" if isinstance(value, float) else f" {key}: {value}"
|
| 302 |
+
)
|
| 303 |
|
| 304 |
# ------------------------------------------------------------------
|
| 305 |
# Save artifacts
|
|
|
|
| 383 |
)
|
| 384 |
|
| 385 |
# Model & output
|
| 386 |
+
parser.add_argument(
|
| 387 |
+
"--model-name",
|
| 388 |
+
type=str,
|
| 389 |
+
default=MODEL_NAME,
|
| 390 |
+
help="Pretrained model name or path",
|
| 391 |
+
)
|
| 392 |
+
parser.add_argument(
|
| 393 |
+
"--output-dir",
|
| 394 |
+
type=str,
|
| 395 |
+
default=DEFAULT_OUTPUT_DIR,
|
| 396 |
+
help="Output directory for model and artifacts",
|
| 397 |
+
)
|
| 398 |
|
| 399 |
# Training hyperparameters
|
| 400 |
+
parser.add_argument(
|
| 401 |
+
"--epochs", type=int, default=4, help="Number of training epochs"
|
| 402 |
+
)
|
| 403 |
+
parser.add_argument(
|
| 404 |
+
"--batch-size", type=int, default=16, help="Training batch size per device"
|
| 405 |
+
)
|
| 406 |
+
parser.add_argument(
|
| 407 |
+
"--learning-rate", type=float, default=2e-5, help="Peak learning rate"
|
| 408 |
+
)
|
| 409 |
+
parser.add_argument(
|
| 410 |
+
"--weight-decay", type=float, default=0.01, help="Weight decay for AdamW"
|
| 411 |
+
)
|
| 412 |
+
parser.add_argument(
|
| 413 |
+
"--warmup-ratio",
|
| 414 |
+
type=float,
|
| 415 |
+
default=0.1,
|
| 416 |
+
help="Fraction of total steps for linear warmup",
|
| 417 |
+
)
|
| 418 |
+
parser.add_argument(
|
| 419 |
+
"--max-length",
|
| 420 |
+
type=int,
|
| 421 |
+
default=MAX_LENGTH,
|
| 422 |
+
help="Maximum token sequence length",
|
| 423 |
+
)
|
| 424 |
+
parser.add_argument(
|
| 425 |
+
"--gradient-accumulation-steps",
|
| 426 |
+
type=int,
|
| 427 |
+
default=1,
|
| 428 |
+
help="Number of gradient accumulation steps",
|
| 429 |
+
)
|
| 430 |
|
| 431 |
# Data
|
| 432 |
+
parser.add_argument(
|
| 433 |
+
"--examples-per-category",
|
| 434 |
+
type=int,
|
| 435 |
+
default=80,
|
| 436 |
+
help="Base synthetic examples per category",
|
| 437 |
+
)
|
| 438 |
+
parser.add_argument(
|
| 439 |
+
"--augmented-copies",
|
| 440 |
+
type=int,
|
| 441 |
+
default=2,
|
| 442 |
+
help="Augmented copies per base example",
|
| 443 |
+
)
|
| 444 |
+
parser.add_argument(
|
| 445 |
+
"--seed", type=int, default=42, help="Random seed for reproducibility"
|
| 446 |
+
)
|
| 447 |
|
| 448 |
# Training config
|
| 449 |
+
parser.add_argument(
|
| 450 |
+
"--fp16", action="store_true", default=None, help="Force FP16 training"
|
| 451 |
+
)
|
| 452 |
+
parser.add_argument("--no-fp16", action="store_true", help="Disable FP16 training")
|
| 453 |
+
parser.add_argument(
|
| 454 |
+
"--early-stopping-patience",
|
| 455 |
+
type=int,
|
| 456 |
+
default=3,
|
| 457 |
+
help="Early stopping patience (0 to disable)",
|
| 458 |
+
)
|
| 459 |
|
| 460 |
# Hub
|
| 461 |
+
parser.add_argument(
|
| 462 |
+
"--push-to-hub",
|
| 463 |
+
action="store_true",
|
| 464 |
+
help="Push trained model to HuggingFace Hub",
|
| 465 |
+
)
|
| 466 |
+
parser.add_argument(
|
| 467 |
+
"--hub-model-id",
|
| 468 |
+
type=str,
|
| 469 |
+
default=HUB_MODEL_ID,
|
| 470 |
+
help="HuggingFace Hub model ID",
|
| 471 |
+
)
|
| 472 |
|
| 473 |
args = parser.parse_args()
|
| 474 |
|