@ericrisco/rsc 0.1.39 → 0.1.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -2
- package/manifest.json +181 -3
- package/package.json +1 -1
- package/scripts/lib/domains.js +1 -0
- package/scripts/lib/recommend.js +6 -0
- package/skills/deep-learning/SKILL.md +263 -0
- package/skills/deep-learning/evals/README.md +11 -0
- package/skills/deep-learning/evals/cases.yaml +48 -0
- package/skills/deep-learning/references/distributed.md +105 -0
- package/skills/deep-learning/references/training-recipes.md +135 -0
- package/skills/finetuning/SKILL.md +244 -0
- package/skills/finetuning/evals/README.md +13 -0
- package/skills/finetuning/evals/cases.yaml +50 -0
- package/skills/finetuning/references/hyperparameters-and-eval.md +74 -0
- package/skills/finetuning/references/methods.md +189 -0
- package/skills/harness/references/providers.yaml +35 -0
- package/skills/huggingface/SKILL.md +1 -1
- package/skills/machine-learning/SKILL.md +260 -0
- package/skills/machine-learning/evals/README.md +15 -0
- package/skills/machine-learning/evals/cases.yaml +54 -0
- package/skills/machine-learning/references/gbdt-and-tuning.md +83 -0
- package/skills/machine-learning/references/metrics-and-imbalance.md +82 -0
- package/skills/machine-learning/references/pipelines-and-cv.md +135 -0
- package/skills/nlp/SKILL.md +225 -0
- package/skills/nlp/evals/README.md +10 -0
- package/skills/nlp/evals/cases.yaml +45 -0
- package/skills/nlp/references/evaluation.md +107 -0
- package/skills/nlp/references/tokenization.md +95 -0
- package/skills/open-weights/SKILL.md +239 -0
- package/skills/open-weights/evals/README.md +14 -0
- package/skills/open-weights/evals/cases.yaml +42 -0
- package/skills/open-weights/references/licenses.md +123 -0
- package/skills/open-weights/references/model-families.md +83 -0
- package/skills/open-weights/references/sizing-and-quant.md +89 -0
- package/skills/roast-me/tools/adapters/__pycache__/__init__.cpython-312.pyc +0 -0
- package/skills/roast-me/tools/adapters/__pycache__/base.cpython-312.pyc +0 -0
- package/skills/roast-me/tools/adapters/__pycache__/claude.cpython-312.pyc +0 -0
- package/skills/roast-me/tools/adapters/__pycache__/codex.cpython-312.pyc +0 -0
- package/skills/roast-me/tools/adapters/__pycache__/gemini.cpython-312.pyc +0 -0
- package/skills/roast-me/tools/adapters/__pycache__/registry.cpython-312.pyc +0 -0
- package/skills/training-data/SKILL.md +226 -0
- package/skills/training-data/evals/README.md +13 -0
- package/skills/training-data/evals/cases.yaml +41 -0
- package/skills/training-data/references/formats.md +118 -0
- package/skills/training-data/references/synthesis-dedup-quality.md +134 -0
- package/skills/unsloth/SKILL.md +254 -0
- package/skills/unsloth/evals/README.md +10 -0
- package/skills/unsloth/evals/cases.yaml +42 -0
- package/skills/unsloth/references/export.md +75 -0
- package/skills/unsloth/references/grpo.md +91 -0
- package/skills/unsloth/references/masking-and-templates.md +91 -0
- package/skills/vllm/SKILL.md +235 -0
- package/skills/vllm/evals/README.md +9 -0
- package/skills/vllm/evals/cases.yaml +47 -0
- package/skills/vllm/references/flags-and-endpoints.md +105 -0
- package/skills/vllm/references/memory-and-throughput.md +78 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
skill: machine-learning
|
|
2
|
+
|
|
3
|
+
should_trigger:
|
|
4
|
+
- prompt: "Train a model on this customer table to predict who will churn, and tell me how well it actually generalizes."
|
|
5
|
+
why: Core ask — supervised tabular classification with an honest generalization estimate; the headline use case.
|
|
6
|
+
- prompt: "My gradient-boosting model scores 0.99 ROC-AUC in cross-validation but tanks in production. Is this leakage?"
|
|
7
|
+
why: Leakage debugging (#1 sin) — only this skill explains fit-on-train-only, target-derived features, and the sacred test set.
|
|
8
|
+
- prompt: "My positive class is 2% of the rows — which metric should I optimize, and is accuracy lying to me?"
|
|
9
|
+
why: The accuracy trap on imbalance → PR-AUC/precision/recall/F1; the metric-selection reasoning is this skill's.
|
|
10
|
+
- prompt: "Set up cross-validation for this dataset so no test information leaks into training or preprocessing."
|
|
11
|
+
why: Pipeline + ColumnTransformer + splitter choice so transforms fit per fold — the leakage-safe CV pattern lives here.
|
|
12
|
+
- prompt: "XGBoost or LightGBM for my tabular regression problem, and should I even bother with a neural net?"
|
|
13
|
+
why: GBDT choice + the trees-beat-deep-nets-on-tabular finding; classic-model territory, not deep-learning.
|
|
14
|
+
- prompt: "Add a proper baseline so I can tell whether my random forest is actually doing anything."
|
|
15
|
+
why: DummyClassifier/DummyRegressor floor — a load-bearing rigor step this skill insists on.
|
|
16
|
+
- prompt: "Entrena un modelo de clasificación tabular con scikit-learn y dame una métrica honesta, no solo accuracy."
|
|
17
|
+
why: Spanish trigger — same tabular-classification-with-honest-metric intent, no English keyword present.
|
|
18
|
+
|
|
19
|
+
should_not_trigger:
|
|
20
|
+
- prompt: "Train a convolutional neural network in PyTorch to classify these product images."
|
|
21
|
+
route_to: deep-learning
|
|
22
|
+
why: Neural nets on images — PyTorch training, explicitly the deep-learning boundary, not tabular sklearn.
|
|
23
|
+
- prompt: "This CSV is full of nulls, duplicate rows, and 'N/A' strings — fix it before I can model anything."
|
|
24
|
+
route_to: data-cleaning
|
|
25
|
+
why: Producing the validated table is upstream; this skill starts once the table is clean.
|
|
26
|
+
- prompt: "Classify these free-text support tickets into topics straight from the ticket wording."
|
|
27
|
+
route_to: nlp
|
|
28
|
+
why: Text/token modeling is nlp's domain; route free-text problems there (a TF-IDF baseline can still live in an ML pipeline, but the framing is NLP).
|
|
29
|
+
- prompt: "Forecast next quarter's revenue from this monthly time-series."
|
|
30
|
+
route_to: forecasting
|
|
31
|
+
why: Projecting a dated series forward is a forecasting product, not a tabular predict-a-column task.
|
|
32
|
+
- prompt: "Build a KPI dashboard and executive summary from this already-clean sales table."
|
|
33
|
+
route_to: business-intelligence
|
|
34
|
+
why: Explaining KPIs / dashboards consumes data; it is not model training.
|
|
35
|
+
- prompt: "Assemble a JSONL dataset of chat messages and preference pairs to fine-tune an LLM."
|
|
36
|
+
route_to: training-data
|
|
37
|
+
why: Building an LLM training corpus, not a tabular ML model.
|
|
38
|
+
|
|
39
|
+
capability:
|
|
40
|
+
- scenario: >
|
|
41
|
+
Given a clean, validated table of ~50k rows for a churn/fraud problem where only ~3% of rows are
|
|
42
|
+
positive, produce a trustworthy modeling workflow: hold out a test set, build a leak-proof pipeline,
|
|
43
|
+
cross-validate, pick a sensible metric and model, beat a baseline, and report an honest final number.
|
|
44
|
+
must_include:
|
|
45
|
+
- Splits off a held-out test set FIRST with train_test_split(..., stratify=y, random_state=...), and leaves it untouched until the single final evaluation
|
|
46
|
+
- Puts all learned preprocessing (impute/scale/encode) INSIDE a Pipeline + ColumnTransformer so it is fit on the train fold only
|
|
47
|
+
- Uses StratifiedKFold (not plain KFold) for the imbalanced classes, with an integer random_state for reproducible folds
|
|
48
|
+
- Establishes a DummyClassifier baseline (e.g. strategy="most_frequent") that the real model must beat
|
|
49
|
+
- Defaults to a GBDT (HistGradientBoostingClassifier / XGBoost / LightGBM) and notes that trees still beat deep nets on tabular data (Grinsztajn 2022, arXiv:2207.08815)
|
|
50
|
+
- Reports PR-AUC (average_precision) / precision / recall / F1 / confusion_matrix and explicitly does NOT use accuracy, because the classes are imbalanced
|
|
51
|
+
- Names data/target leakage as the #1 risk and audits for target-derived or not-available-at-prediction-time features
|
|
52
|
+
- Cross-validates the whole pipeline via cross_validate/GridSearchCV (scoring the pipeline, never globally-preprocessed data)
|
|
53
|
+
- Treats the 0.5 decision threshold as a tunable choice (e.g. from the PR curve or TunedThresholdClassifierCV), not a law
|
|
54
|
+
- Hedges the scikit-learn version (current major ~1.9, verify at scikit-learn.org) instead of asserting a frozen pin as fact
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# GBDTs: parameters, early stopping, categoricals, importance (depth)
|
|
2
|
+
|
|
3
|
+
Three interchangeable sklearn-API GBDTs. Versions verified 2026-07 — XGBoost 3.x, LightGBM 4.x, and
|
|
4
|
+
sklearn's `HistGradientBoosting*` (~1.9). Verify current versions before pinning; APIs shift between majors.
|
|
5
|
+
|
|
6
|
+
## What the knobs mean (they translate across libraries)
|
|
7
|
+
|
|
8
|
+
| Concept | HistGradientBoosting | XGBoost | LightGBM |
|
|
9
|
+
| --- | --- | --- | --- |
|
|
10
|
+
| Step size | `learning_rate` | `learning_rate` (`eta`) | `learning_rate` |
|
|
11
|
+
| Number of trees | `max_iter` | `n_estimators` | `n_estimators` |
|
|
12
|
+
| Tree size | `max_leaf_nodes` / `max_depth` | `max_depth` | `num_leaves` (leaf-wise!) / `max_depth` |
|
|
13
|
+
| L2 penalty | `l2_regularization` | `reg_lambda` / `reg_alpha` | `reg_lambda` / `reg_alpha` |
|
|
14
|
+
| Min data per leaf | `min_samples_leaf` | `min_child_weight` | `min_child_samples` |
|
|
15
|
+
| Row/col subsample | (n/a) | `subsample` / `colsample_bytree` | `bagging_fraction` / `feature_fraction` |
|
|
16
|
+
|
|
17
|
+
Tuning order that pays off: get `learning_rate` low-ish with enough trees (use early stopping to pick the
|
|
18
|
+
count), then control complexity (`num_leaves`/`max_depth`, `min_*_leaf`), then regularization and subsampling.
|
|
19
|
+
**Lower `learning_rate` + more trees + early stopping** beats a high learning rate almost always. LightGBM
|
|
20
|
+
grows **leaf-wise**, so cap `num_leaves` (and/or `max_depth`) or it overfits fast.
|
|
21
|
+
|
|
22
|
+
## Early stopping (stop adding trees when validation stops improving)
|
|
23
|
+
|
|
24
|
+
Needs a validation signal. Keep it out of the sacred test set — use a train-derived eval split.
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
# XGBoost 3.x: early_stopping_rounds is a CONSTRUCTOR param; pass eval_set to fit()
|
|
28
|
+
from xgboost import XGBClassifier
|
|
29
|
+
xgb = XGBClassifier(n_estimators=2000, learning_rate=0.05,
|
|
30
|
+
early_stopping_rounds=50, eval_metric="aucpr")
|
|
31
|
+
xgb.fit(X_tr, y_tr, eval_set=[(X_val, y_val)], verbose=False)
|
|
32
|
+
print(xgb.best_iteration)
|
|
33
|
+
|
|
34
|
+
# LightGBM 4.x: callbacks
|
|
35
|
+
from lightgbm import LGBMClassifier, early_stopping, log_evaluation
|
|
36
|
+
lgbm = LGBMClassifier(n_estimators=2000, learning_rate=0.05, num_leaves=31)
|
|
37
|
+
lgbm.fit(X_tr, y_tr, eval_set=[(X_val, y_val)], eval_metric="average_precision",
|
|
38
|
+
callbacks=[early_stopping(50), log_evaluation(0)])
|
|
39
|
+
|
|
40
|
+
# HistGradientBoosting: built-in, no eval_set needed (carves its own validation slice)
|
|
41
|
+
from sklearn.ensemble import HistGradientBoostingClassifier
|
|
42
|
+
hgb = HistGradientBoostingClassifier(max_iter=2000, learning_rate=0.05,
|
|
43
|
+
early_stopping=True, validation_fraction=0.1,
|
|
44
|
+
n_iter_no_change=50, random_state=0)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Native categorical handling (skip one-hot)
|
|
48
|
+
|
|
49
|
+
- **HistGradientBoosting**: `categorical_features="from_dtype"` — columns with pandas `category` dtype are
|
|
50
|
+
handled natively. No `OneHotEncoder`, no explosion of columns.
|
|
51
|
+
- **XGBoost**: `enable_categorical=True` + columns as pandas `category` dtype; `tree_method` `hist`/`approx`.
|
|
52
|
+
- **LightGBM**: pass `categorical_feature=` (names/indices) or use `category` dtype; native by default.
|
|
53
|
+
|
|
54
|
+
One-hot only when cardinality is low and a *linear* model needs it. For trees, native categorical or, for
|
|
55
|
+
very high cardinality, `TargetEncoder` (cross-fit — see pipelines-and-cv.md) beats a thousand dummy columns.
|
|
56
|
+
|
|
57
|
+
## Monotonic constraints (domain priors)
|
|
58
|
+
|
|
59
|
+
Force the model's response to move only one way with a feature (price ↑ ⇒ risk never ↓). Guards against
|
|
60
|
+
noise inverting a known relationship and helps trust/compliance.
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
# HistGradientBoosting: dict of feature -> {-1, 0, +1}
|
|
64
|
+
HistGradientBoostingClassifier(monotonic_cst={"income": +1, "num_late_payments": -1})
|
|
65
|
+
# XGBoost: monotone_constraints="(1,0,-1,...)" LightGBM: monotone_constraints=[1,0,-1,...]
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Feature importance — don't trust the default blindly
|
|
69
|
+
|
|
70
|
+
- **Impurity / gain importance** (`feature_importances_`): fast but biased toward high-cardinality and
|
|
71
|
+
continuous features; computed on train.
|
|
72
|
+
- **Permutation importance** (`sklearn.inspection.permutation_importance`): model-agnostic, measures the
|
|
73
|
+
metric drop when a feature is shuffled — run it **on held-out data**, and beware correlated features
|
|
74
|
+
sharing credit.
|
|
75
|
+
- **SHAP** (separate `shap` library): per-prediction attributions; the most faithful local explanations,
|
|
76
|
+
the heaviest to compute. Use for "why this row" and to sanity-check for leakage — a feature dominating
|
|
77
|
+
SHAP that shouldn't be predictive is a leak flag.
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
from sklearn.inspection import permutation_importance
|
|
81
|
+
r = permutation_importance(model, X_test, y_test, scoring="average_precision",
|
|
82
|
+
n_repeats=10, random_state=0)
|
|
83
|
+
```
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Metrics, thresholds, calibration, and imbalance (depth)
|
|
2
|
+
|
|
3
|
+
Picking the metric and handling imbalance is where "0.99 accuracy" turns into an honest number. API current
|
|
4
|
+
as of scikit-learn ~1.9 (verify at scikit-learn.org).
|
|
5
|
+
|
|
6
|
+
## Choosing the classification metric
|
|
7
|
+
|
|
8
|
+
Everything in `sklearn.metrics`. Score **probabilities** (`predict_proba`) for AUC/ranking metrics; score
|
|
9
|
+
**labels** for precision/recall/F1 (which depend on the decision threshold).
|
|
10
|
+
|
|
11
|
+
| You care about… | Metric | Notes |
|
|
12
|
+
| --- | --- | --- |
|
|
13
|
+
| Ranking, threshold-free, roughly balanced | `roc_auc_score` | Insensitive to the negative flood — flatters rare-positive problems. |
|
|
14
|
+
| Ranking with **rare positives** (fraud/disease) | `average_precision_score` (PR-AUC) | The honest ranking metric under imbalance. |
|
|
15
|
+
| Not crying wolf (false positives costly) | `precision_score` | TP / (TP+FP). |
|
|
16
|
+
| Not missing cases (false negatives costly) | `recall_score` | TP / (TP+FN). |
|
|
17
|
+
| Both, single number | `f1_score` | Harmonic mean; `average="macro"` treats classes equally, `"weighted"` by support. |
|
|
18
|
+
| Per-class fairness, multiclass | `balanced_accuracy_score`, `f1_macro` | Macro-averaging stops the majority class dominating. |
|
|
19
|
+
| The full picture | `confusion_matrix`, `classification_report` | Always look before trusting one scalar. |
|
|
20
|
+
| Probability quality | `brier_score_loss`, `log_loss` | For calibrated-probability use cases. |
|
|
21
|
+
|
|
22
|
+
Multiclass averaging: `micro` (global TP/FP counts — dominated by frequent classes), `macro` (unweighted
|
|
23
|
+
class mean — each class equal), `weighted` (by class support). State which and why.
|
|
24
|
+
|
|
25
|
+
## Regression metrics
|
|
26
|
+
|
|
27
|
+
`r2_score` (proportion of variance explained; can be negative), `mean_absolute_error` (robust, same units),
|
|
28
|
+
`root_mean_squared_error` (penalizes large errors; note this is the current function —
|
|
29
|
+
`mean_squared_error(squared=False)` was removed), `mean_absolute_percentage_error` (scale-free but explodes
|
|
30
|
+
near zero). Scoring strings negate losses: `"neg_root_mean_squared_error"`, `"neg_mean_absolute_error"`.
|
|
31
|
+
|
|
32
|
+
## The threshold is a decision, not 0.5
|
|
33
|
+
|
|
34
|
+
`predict()` thresholds probability at 0.5, which is rarely the business-optimal cut under imbalance or
|
|
35
|
+
asymmetric costs. Tune it on validation, either manually from the PR curve or with the built-in estimator.
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
from sklearn.metrics import precision_recall_curve
|
|
39
|
+
prec, rec, thr = precision_recall_curve(y_val, proba_val)
|
|
40
|
+
# e.g. smallest threshold that reaches precision >= 0.90:
|
|
41
|
+
import numpy as np
|
|
42
|
+
ok = np.where(prec[:-1] >= 0.90)[0]
|
|
43
|
+
chosen = thr[ok[0]] if len(ok) else 1.0
|
|
44
|
+
|
|
45
|
+
# Or let sklearn tune + wrap it (added 1.5):
|
|
46
|
+
from sklearn.model_selection import TunedThresholdClassifierCV, FixedThresholdClassifier
|
|
47
|
+
tuned = TunedThresholdClassifierCV(clf, scoring="f1", cv=5).fit(X_tr, y_tr)
|
|
48
|
+
print(tuned.best_threshold_)
|
|
49
|
+
fixed = FixedThresholdClassifier(clf, threshold=0.30).fit(X_tr, y_tr) # pin a known-good cut
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Probability calibration
|
|
53
|
+
|
|
54
|
+
Tree ensembles and SVMs produce distorted probabilities. If you use the probability itself (expected-value
|
|
55
|
+
decisions, thresholds you trust), calibrate — on a held-out fold, never on train.
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
from sklearn.calibration import CalibratedClassifierCV
|
|
59
|
+
cal = CalibratedClassifierCV(clf, method="isotonic", cv=5) # or method="sigmoid" (Platt)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Imbalance — in order of preference
|
|
63
|
+
|
|
64
|
+
1. **Do nothing to the data; fix the metric + threshold.** Often enough. Use PR-AUC and a tuned threshold.
|
|
65
|
+
2. **`class_weight="balanced"`** (LogisticRegression, RandomForest, SVC, HistGradientBoosting…) or
|
|
66
|
+
`scale_pos_weight` (XGBoost) — reweight the loss, no synthetic rows, no leakage risk.
|
|
67
|
+
3. **Resampling (SMOTE / undersampling) — only if 1–2 fall short, and only inside CV on the train fold.**
|
|
68
|
+
Resampling before the split, or touching the test fold, leaks and evaluates on synthetic data. Use the
|
|
69
|
+
**imbalanced-learn `Pipeline`** (not sklearn's) so the resampler runs per fold, train-only:
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
from imblearn.pipeline import Pipeline as ImbPipeline
|
|
73
|
+
from imblearn.over_sampling import SMOTE
|
|
74
|
+
from sklearn.ensemble import HistGradientBoostingClassifier
|
|
75
|
+
|
|
76
|
+
pipe = ImbPipeline([("pre", pre), ("smote", SMOTE(random_state=0)),
|
|
77
|
+
("clf", HistGradientBoostingClassifier(random_state=0))])
|
|
78
|
+
# In cross_validate/GridSearchCV, SMOTE fits on each TRAIN fold only; test folds stay real.
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Never resample the test set, and never report accuracy on the resampled (artificially balanced) data as if
|
|
82
|
+
it were the real distribution.
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Pipelines, custom transforms, and cross-validation (depth)
|
|
2
|
+
|
|
3
|
+
Everything here keeps the SKILL.md rule: transforms that *learn* live inside the pipeline, so CV/tuning
|
|
4
|
+
re-fit them on each train fold only. API current as of scikit-learn ~1.9 (verify at scikit-learn.org).
|
|
5
|
+
|
|
6
|
+
## make_column_transformer — the terse ColumnTransformer
|
|
7
|
+
|
|
8
|
+
`make_column_transformer` auto-names steps; `make_column_selector` picks columns by dtype/regex.
|
|
9
|
+
|
|
10
|
+
```python
|
|
11
|
+
from sklearn.compose import make_column_transformer, make_column_selector as mcs
|
|
12
|
+
from sklearn.pipeline import make_pipeline
|
|
13
|
+
from sklearn.impute import SimpleImputer
|
|
14
|
+
from sklearn.preprocessing import StandardScaler, OneHotEncoder
|
|
15
|
+
|
|
16
|
+
pre = make_column_transformer(
|
|
17
|
+
(make_pipeline(SimpleImputer(strategy="median"), StandardScaler()), mcs(dtype_include="number")),
|
|
18
|
+
(make_pipeline(SimpleImputer(strategy="most_frequent"),
|
|
19
|
+
OneHotEncoder(handle_unknown="ignore")), mcs(dtype_include=["object", "category"])),
|
|
20
|
+
remainder="drop",
|
|
21
|
+
verbose_feature_names_out=False, # cleaner get_feature_names_out()
|
|
22
|
+
)
|
|
23
|
+
pre.set_output(transform="pandas") # keep a DataFrame with real column names flowing through
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Stateless custom feature: FunctionTransformer
|
|
27
|
+
|
|
28
|
+
For a transform with **no learned state** (log, ratio, date parts), `FunctionTransformer` is leak-safe by
|
|
29
|
+
construction — there is nothing fit on data.
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
import numpy as np
|
|
33
|
+
from sklearn.preprocessing import FunctionTransformer
|
|
34
|
+
|
|
35
|
+
log1p = FunctionTransformer(np.log1p, feature_names_out="one-to-one")
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Stateful custom transformer: fit on train only
|
|
39
|
+
|
|
40
|
+
If the transform learns anything (a mean, a fitted mapping), write a class so CV re-fits it per fold.
|
|
41
|
+
`fit` must store state from **train** only; `transform` applies it. Never compute the statistic in `__init__`
|
|
42
|
+
or over the whole dataset.
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from sklearn.base import BaseEstimator, TransformerMixin
|
|
46
|
+
|
|
47
|
+
class GroupRareCategories(BaseEstimator, TransformerMixin):
|
|
48
|
+
"""Fold categories seen < min_count times in TRAIN into '__other__'."""
|
|
49
|
+
def __init__(self, min_count=10):
|
|
50
|
+
self.min_count = min_count
|
|
51
|
+
def fit(self, X, y=None):
|
|
52
|
+
counts = X.iloc[:, 0].value_counts()
|
|
53
|
+
self.keep_ = set(counts[counts >= self.min_count].index) # learned on train fold
|
|
54
|
+
return self
|
|
55
|
+
def transform(self, X):
|
|
56
|
+
col = X.iloc[:, 0]
|
|
57
|
+
return col.where(col.isin(self.keep_), "__other__").to_frame()
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## High-cardinality categoricals: TargetEncoder cross-fitting
|
|
61
|
+
|
|
62
|
+
`sklearn.preprocessing.TargetEncoder` encodes each category by the target mean — the classic leakage trap
|
|
63
|
+
if done naively. Its `fit_transform(X, y)` uses an **internal cross-fitting** scheme (each row encoded from
|
|
64
|
+
*other* folds' targets), which is why `fit(X, y).transform(X)` deliberately differs and must not be used on
|
|
65
|
+
train. Put it in the pipeline and let CV call `fit_transform`; never hand-roll a group-mean encode.
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from sklearn.preprocessing import TargetEncoder
|
|
69
|
+
enc = TargetEncoder(smooth="auto", cv=5) # cv controls the internal cross-fit
|
|
70
|
+
# inside a ColumnTransformer branch for the high-cardinality columns
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Hyperparameter tuning — over the whole pipeline
|
|
74
|
+
|
|
75
|
+
Address a step's parameter with `stepname__param` (double underscore). Tuning searches must wrap the
|
|
76
|
+
*pipeline*, so preprocessing is re-fit each fold — tuning on globally-preprocessed data leaks.
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
from sklearn.pipeline import Pipeline
|
|
80
|
+
from sklearn.ensemble import HistGradientBoostingClassifier
|
|
81
|
+
from sklearn.model_selection import RandomizedSearchCV, StratifiedKFold
|
|
82
|
+
|
|
83
|
+
pipe = Pipeline([("pre", pre), ("clf", HistGradientBoostingClassifier(random_state=0))])
|
|
84
|
+
param_dist = {
|
|
85
|
+
"clf__learning_rate": [0.01, 0.05, 0.1, 0.2],
|
|
86
|
+
"clf__max_leaf_nodes": [15, 31, 63],
|
|
87
|
+
"clf__l2_regularization": [0.0, 1.0, 10.0],
|
|
88
|
+
}
|
|
89
|
+
search = RandomizedSearchCV(
|
|
90
|
+
pipe, param_dist, n_iter=30,
|
|
91
|
+
cv=StratifiedKFold(5, shuffle=True, random_state=0),
|
|
92
|
+
scoring="average_precision", random_state=0, n_jobs=-1, refit=True,
|
|
93
|
+
)
|
|
94
|
+
search.fit(X_tr, y_tr) # X_tr only — the test set stays sealed
|
|
95
|
+
print(search.best_params_, search.best_score_)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
- `GridSearchCV` — exhaustive; use for a small, discrete grid.
|
|
99
|
+
- `RandomizedSearchCV` — sample `n_iter` combos from distributions; the default for anything nontrivial.
|
|
100
|
+
- `HalvingRandomSearchCV` / `HalvingGridSearchCV` (`sklearn.model_selection`) — successive halving: cheap
|
|
101
|
+
runs on many candidates, more budget to survivors. Fastest when fits are expensive.
|
|
102
|
+
|
|
103
|
+
## Nested CV — unbiased estimate of the tuning process
|
|
104
|
+
|
|
105
|
+
If you report `search.best_score_` as your performance, it is optimistic (you selected on it). To estimate
|
|
106
|
+
how the *whole tuning procedure* generalizes, nest an inner CV (tuning) inside an outer CV (scoring).
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
from sklearn.model_selection import cross_val_score
|
|
110
|
+
outer = StratifiedKFold(5, shuffle=True, random_state=1)
|
|
111
|
+
nested = cross_val_score(search, X_tr, y_tr, cv=outer, scoring="average_precision")
|
|
112
|
+
print(nested.mean(), nested.std()) # honest estimate of "tune-then-fit" on unseen data
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Splitters beyond StratifiedKFold
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
from sklearn.model_selection import TimeSeriesSplit, GroupKFold, StratifiedGroupKFold
|
|
119
|
+
|
|
120
|
+
# time order: train on past, test on future (expanding window); never shuffle the future into train
|
|
121
|
+
for tr, te in TimeSeriesSplit(n_splits=5).split(X):
|
|
122
|
+
...
|
|
123
|
+
|
|
124
|
+
# clustered rows (same user/store): keep a group wholly in train OR test
|
|
125
|
+
for tr, te in GroupKFold(n_splits=5).split(X, y, groups=user_id):
|
|
126
|
+
...
|
|
127
|
+
|
|
128
|
+
# clustered AND imbalanced: preserve class ratio while keeping groups intact
|
|
129
|
+
for tr, te in StratifiedGroupKFold(n_splits=5).split(X, y, groups=user_id):
|
|
130
|
+
...
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Pass `groups=` through tuning too: `search.fit(X, y, groups=user_id)` with a group-aware `cv`. A random
|
|
134
|
+
`KFold` on time-series or grouped data is leakage — CV will look great and production will not.
|
|
135
|
+
```
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: nlp
|
|
3
|
+
description: "Use when choosing how to tokenize text or which transformer type fits an NLP task, when a tokenizer over-fragments non-English text or inflates token cost, when picking a language metric, or when classification / NER / summarization output looks wrong and you can't tell whether the tokenizer, the architecture, or the metric is at fault. Covers BPE / WordPiece / SentencePiece-Unigram / byte-level BPE, special tokens, encoder (BERT) vs decoder (GPT) vs encoder-decoder (T5) choice, classification, NER, seq2seq, sentence embeddings, and metrics (F1, entity-F1, BLEU / ROUGE / chrF, perplexity, LLM-as-judge). Triggers: 'BPE or WordPiece', 'BERT vs GPT vs T5 for this task', 'tokenizer splits Catalan into too many tokens', 'is BLEU good for summaries', 'entity F1 for NER', 'quin model faig servir per classificar text'. NOT retrieval / vector search (that is embeddings-search), NOT the RAG loop (that is rag), NOT prompt wording (that is prompt-engineering), NOT training the net (that is finetuning / deep-learning)."
|
|
4
|
+
tags: [nlp, tokenization, bpe, wordpiece, ner, text-classification, bleu, rouge, perplexity, transformers]
|
|
5
|
+
recommends: [deep-learning, embeddings-search, rag, training-data]
|
|
6
|
+
origin: risco
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# nlp — tokenize the text, pick the model type, pick the metric
|
|
10
|
+
|
|
11
|
+
You own the **language-modeling discipline**: how raw text becomes tokens, which transformer
|
|
12
|
+
architecture fits a task, and which metric actually tells you whether it worked. When the
|
|
13
|
+
question is "which tokenizer," "BERT or GPT or T5 for this," "why does my Catalan text cost 3×
|
|
14
|
+
the tokens," or "is this BLEU score meaningful," this is the skill. You stop at retrieval, the
|
|
15
|
+
RAG loop, prompt wording, and the training step itself — those route out (below).
|
|
16
|
+
|
|
17
|
+
## Route out first (loud — do not duplicate these)
|
|
18
|
+
|
|
19
|
+
- **Retrieval embeddings + vector search** (which embedding model, chunking, recall@k, rerank)
|
|
20
|
+
→ [`../embeddings-search/SKILL.md`](../embeddings-search/SKILL.md). Sentence embeddings *live
|
|
21
|
+
here* as a task; using them to *retrieve* is theirs.
|
|
22
|
+
- **The retrieve → prompt → generate → answer loop** and groundedness → [`../rag/SKILL.md`](../rag/SKILL.md).
|
|
23
|
+
- **Prompt wording / few-shot / system prompts** → [`../prompt-engineering/SKILL.md`](../prompt-engineering/SKILL.md).
|
|
24
|
+
- **Training the network** (LoRA/SFT, trainer loop, PyTorch) → [`../finetuning/SKILL.md`](../finetuning/SKILL.md)
|
|
25
|
+
+ [`../deep-learning/SKILL.md`](../deep-learning/SKILL.md).
|
|
26
|
+
- **The training corpus itself** (JSONL messages, label sets) → [`../training-data/SKILL.md`](../training-data/SKILL.md).
|
|
27
|
+
|
|
28
|
+
## Decision: model type per task (get this right before anything else)
|
|
29
|
+
|
|
30
|
+
Pick the architecture from the task's *shape*, not from what is trendy. A decoder LLM can
|
|
31
|
+
technically classify, but a fine-tuned encoder is smaller, faster, cheaper, and usually more
|
|
32
|
+
accurate on a fixed-label task.
|
|
33
|
+
|
|
34
|
+
| Task shape | Architecture | Why | Example families* |
|
|
35
|
+
|---|---|---|---|
|
|
36
|
+
| Understand / label a whole input (classification, NER, extractive QA, similarity) | **Encoder** (bidirectional) | Attends to the full sentence both directions; cheap to fine-tune and to serve | BERT, RoBERTa, DistilBERT, ModernBERT |
|
|
37
|
+
| Free-form generation, chat, few-shot | **Decoder** (autoregressive) | Attends only to prior tokens; predicts the next token | GPT-style, Llama, Gemma, Qwen |
|
|
38
|
+
| Transform input → new text (summarize, translate, generative QA) | **Encoder-decoder / seq2seq** | Encoder reads all of the source, decoder writes conditioned on it | T5 / FLAN-T5, BART, mT5 |
|
|
39
|
+
|
|
40
|
+
\* Architecture families are stable; specific checkpoints and their licenses are not — check the
|
|
41
|
+
HF model card before you commit (licenses change; see `open-weights`). ModernBERT (2024) is a
|
|
42
|
+
current long-context encoder; verify the latest at author time.
|
|
43
|
+
|
|
44
|
+
The two most common own-goals: reaching for a 7B decoder to do sentiment on 5 classes (an
|
|
45
|
+
encoder does it for a fraction of the cost), and forcing an encoder to *generate* (it cannot —
|
|
46
|
+
it has no decoder).
|
|
47
|
+
|
|
48
|
+
## 1. Tokenization
|
|
49
|
+
|
|
50
|
+
Every downstream number depends on this step, and its failures are **silent**. The single
|
|
51
|
+
load-bearing rule:
|
|
52
|
+
|
|
53
|
+
> **Load the tokenizer that shipped with the checkpoint, and use the same one at train and
|
|
54
|
+
> inference.** `AutoTokenizer.from_pretrained(same_checkpoint)`. A train/inference tokenizer
|
|
55
|
+
> mismatch — different vocab, different special tokens, different casing/normalization — maps
|
|
56
|
+
> text to token ids the model never saw and corrupts everything downstream with no error.
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from transformers import AutoTokenizer # transformers current major ~v5 (verify at author time)
|
|
60
|
+
|
|
61
|
+
tok = AutoTokenizer.from_pretrained("bert-base-cased")
|
|
62
|
+
enc = tok("Tokenizers matter.", return_offsets_mapping=True)
|
|
63
|
+
tok.convert_ids_to_tokens(enc["input_ids"])
|
|
64
|
+
# ['[CLS]', 'Token', '##izers', 'matter', '.', '[SEP]'] — note WordPiece '##' continuation + added specials
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**The four algorithms** (full mechanics in `references/tokenization.md`):
|
|
68
|
+
|
|
69
|
+
| Algorithm | Builds vocab by… | Applies by… | Used by |
|
|
70
|
+
|---|---|---|---|
|
|
71
|
+
| **BPE** | merging the most frequent adjacent pair, repeatedly | split to chars, replay learned merges | GPT-2 (byte-level), many |
|
|
72
|
+
| **WordPiece** | merging pairs that maximize a likelihood score | longest-match subword from the front (`##` continuations) | BERT family |
|
|
73
|
+
| **Unigram** (SentencePiece) | start large, *remove* tokens that least hurt corpus likelihood | most-probable segmentation | T5, ALBERT, mT5 |
|
|
74
|
+
| **Byte-level BPE** | BPE over the 256 raw **bytes**, not Unicode chars | same as BPE on bytes | GPT-2, RoBERTa |
|
|
75
|
+
|
|
76
|
+
- **Byte-level BPE has no `[UNK]`.** Base vocab is exactly 256 (all byte values), so every
|
|
77
|
+
emoji, accent, and script maps to *some* byte sequence — nothing falls out as unknown
|
|
78
|
+
(verified: HF NLP course ch.6). WordPiece/word-level tokenizers *do* have `[UNK]` and lose OOV
|
|
79
|
+
content.
|
|
80
|
+
- **SentencePiece is reversible** — it treats space as a normal symbol (the `▁` meta-symbol), so
|
|
81
|
+
`decode(encode(x)) == x` without language-specific detokenization rules. That is why it
|
|
82
|
+
dominates multilingual models.
|
|
83
|
+
|
|
84
|
+
**Special tokens are not decoration.** `[CLS]`/`<s>` carries the pooled sentence
|
|
85
|
+
representation for classification; `[SEP]`/`</s>` marks segment/end; `[PAD]` fills a batch (and
|
|
86
|
+
must be masked out via `attention_mask`); `[MASK]` is the MLM target; `[UNK]` is the fallback.
|
|
87
|
+
Names differ by model (`[CLS]` in BERT vs `<s>` in RoBERTa) — another reason to never hand-roll
|
|
88
|
+
the tokenizer.
|
|
89
|
+
|
|
90
|
+
**Why it matters — three concrete costs:**
|
|
91
|
+
|
|
92
|
+
- **$ cost & context.** Token count *is* the bill and the context budget. Fewer tokens per
|
|
93
|
+
sentence = cheaper calls and more room in the window.
|
|
94
|
+
- **OOV / information loss.** A tokenizer that emits `[UNK]` throws away content it can't
|
|
95
|
+
represent; byte-level/SentencePiece degrade gracefully instead.
|
|
96
|
+
- **Fairness.** Vocab trained mostly on English fragments other scripts far harder — the same
|
|
97
|
+
meaning costs more tokens, more money, and more latency (section 5).
|
|
98
|
+
|
|
99
|
+
## 2. Tasks
|
|
100
|
+
|
|
101
|
+
### Text classification (encoder + classification head)
|
|
102
|
+
```python
|
|
103
|
+
from transformers import pipeline
|
|
104
|
+
clf = pipeline("text-classification", model="distilbert-base-uncased-finetuned-sst-2-english")
|
|
105
|
+
clf("The service was slow but the food was incredible.")
|
|
106
|
+
# [{'label': 'POSITIVE', 'score': 0.99...}]
|
|
107
|
+
```
|
|
108
|
+
Metric: accuracy on balanced data; **macro-F1** the moment classes are imbalanced (accuracy
|
|
109
|
+
lies when 95% of rows are one class).
|
|
110
|
+
|
|
111
|
+
### Token classification / NER (encoder, per-token labels)
|
|
112
|
+
Labels are **B-/I-/O** spans aligned to subword tokens: the first subword of a word gets the
|
|
113
|
+
label, continuation subwords and special tokens get `-100` (ignored by the loss). Evaluate with
|
|
114
|
+
**seqeval** at the **entity** level, never per-token accuracy (per-token accuracy is inflated by
|
|
115
|
+
the flood of `O` tokens).
|
|
116
|
+
```python
|
|
117
|
+
from transformers import pipeline
|
|
118
|
+
ner = pipeline("token-classification", aggregation_strategy="simple")
|
|
119
|
+
ner("Ada Lovelace worked in London.")
|
|
120
|
+
# groups subwords back into entities: PER 'Ada Lovelace', LOC 'London'
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Seq2seq — summarization / translation (encoder-decoder)
|
|
124
|
+
```python
|
|
125
|
+
summ = pipeline("summarization", model="facebook/bart-large-cnn")
|
|
126
|
+
summ(long_article, max_length=130, min_length=30)
|
|
127
|
+
```
|
|
128
|
+
Metric: **ROUGE** for summarization, **BLEU/chrF** for translation — with the heavy caveat in
|
|
129
|
+
section 4.
|
|
130
|
+
|
|
131
|
+
### Sentence embeddings (SBERT — the *task*, not retrieval)
|
|
132
|
+
```python
|
|
133
|
+
from sentence_transformers import SentenceTransformer # sentence-transformers ~v5 (verify)
|
|
134
|
+
model = SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2")
|
|
135
|
+
emb = model.encode(["The weather is lovely today.", "It's so sunny outside!"])
|
|
136
|
+
model.similarity(emb, emb) # semantic textual similarity / clustering / paraphrase mining
|
|
137
|
+
```
|
|
138
|
+
Producing/judging embeddings **for retrieval** (model choice, chunking, recall@k, rerankers) is
|
|
139
|
+
[`embeddings-search`](../embeddings-search/SKILL.md), not here.
|
|
140
|
+
|
|
141
|
+
## 3. Evaluation — pick the metric that matches the task
|
|
142
|
+
|
|
143
|
+
| Task | Primary metric | Catches | Trap |
|
|
144
|
+
|---|---|---|---|
|
|
145
|
+
| Classification | accuracy + **macro-F1** | wrong labels | accuracy hides minority-class failure |
|
|
146
|
+
| NER / token | **entity-level F1** (seqeval) | missed/partial spans | per-token accuracy is inflated by `O` |
|
|
147
|
+
| Translation | **BLEU / chrF** | n-gram overlap w/ reference | weak on meaning; chrF better for morphology |
|
|
148
|
+
| Summarization | **ROUGE** (1/2/L) | recall of reference n-grams | rewards copying; blind to faithfulness |
|
|
149
|
+
| Generation (LM) | **perplexity** | how well the model predicts held-out text | tokenizer-dependent — not comparable across tokenizers |
|
|
150
|
+
| Open-ended / chat | **LLM-as-judge** + human | quality overlap metrics miss | judge bias (position, verbosity, self-preference) |
|
|
151
|
+
|
|
152
|
+
**The caveat that governs this whole section:** BLEU, ROUGE, and chrF are n-gram/character
|
|
153
|
+
overlap metrics and **correlate weakly with human judgment on open-ended and creative
|
|
154
|
+
generation** — they reward matching the reference's exact phrasing, so a correct paraphrase
|
|
155
|
+
scores low and a fluent-but-wrong copy scores high (well documented; e.g. the summarization and
|
|
156
|
+
MT-evaluation literature). Use them for **regression tracking on a fixed reference set**, never
|
|
157
|
+
as the final verdict on quality. For open-ended output, use an **LLM-as-judge rubric plus a
|
|
158
|
+
human spot-check** — and know the judge has its own biases (position, verbosity, self-preference),
|
|
159
|
+
so pin the rubric and randomize order.
|
|
160
|
+
|
|
161
|
+
**Perplexity** = exp(mean token NLL): lower means the model predicts held-out text better. It is
|
|
162
|
+
**tokenizer-dependent**, so two models with different tokenizers have non-comparable perplexities
|
|
163
|
+
— only compare within the same tokenizer/vocab. Runnable snippets for seqeval, sacrebleu, ROUGE,
|
|
164
|
+
perplexity, and an LLM-judge harness are in `references/evaluation.md`.
|
|
165
|
+
|
|
166
|
+
## 4. Multilingual pitfalls
|
|
167
|
+
|
|
168
|
+
The English-centric trap: a tokenizer whose vocab was learned mostly on English **over-fragments
|
|
169
|
+
other scripts**. The same sentence in Ukrainian, Arabic, Hindi, or even accented Catalan can take
|
|
170
|
+
**2–15× more tokens** than its English equivalent (Petrov et al., *Language Model Tokenizers
|
|
171
|
+
Introduce Unfairness Between Languages*, NeurIPS 2023). That "fertility" (tokens per word)
|
|
172
|
+
inflation is a triple tax:
|
|
173
|
+
|
|
174
|
+
- **Money** — more tokens per identical meaning = a proportionally larger bill for the same work.
|
|
175
|
+
- **Context** — over-fragmented text eats the window faster, so fewer few-shot examples fit and
|
|
176
|
+
long documents truncate sooner.
|
|
177
|
+
- **Quality** — sequences fragmented into byte-shards are harder to model, degrading accuracy for
|
|
178
|
+
exactly the users the tool already serves worst.
|
|
179
|
+
|
|
180
|
+
Mitigations: prefer a **multilingual tokenizer/model** (mT5, XLM-R, a SentencePiece-based model)
|
|
181
|
+
whose vocab actually covers your languages; **measure fertility** on your own corpus (tokens per
|
|
182
|
+
word, per language) before you commit; and don't benchmark cost or latency only on English.
|
|
183
|
+
|
|
184
|
+
## Guardrails / gotchas
|
|
185
|
+
|
|
186
|
+
- **Tokenizer must match the checkpoint, at train and inference.** Mismatch corrupts silently,
|
|
187
|
+
no error. The most expensive bug in this skill.
|
|
188
|
+
- **Encoders can't generate; don't classify with a giant decoder by default.** Match architecture
|
|
189
|
+
to task shape.
|
|
190
|
+
- **BLEU/ROUGE/chrF ≠ quality on open-ended text.** Overlap metrics; weak human correlation. Track
|
|
191
|
+
regressions with them; judge quality with an LLM-judge + human.
|
|
192
|
+
- **Entity-F1 (seqeval), not token accuracy, for NER.** `O` tokens inflate accuracy toward 1.0.
|
|
193
|
+
- **macro-F1, not accuracy, on imbalanced classes.**
|
|
194
|
+
- **Perplexity is tokenizer-relative** — never compare it across different tokenizers.
|
|
195
|
+
- **Don't benchmark tokenization/cost only in English** — fertility varies 2–15× across scripts.
|
|
196
|
+
- **Never assert a model's license from memory** — check the current model card; license classes
|
|
197
|
+
shift (Llama = Meta Community license, not OSI-open; Gemma = custom terms; etc.).
|
|
198
|
+
|
|
199
|
+
## Related skills
|
|
200
|
+
|
|
201
|
+
- [`embeddings-search`](../embeddings-search/SKILL.md) — retrieval embeddings, chunking, recall@k,
|
|
202
|
+
reranking. NLP owns *making/judging* sentence embeddings as a task; using them to search is theirs.
|
|
203
|
+
- [`rag`](../rag/SKILL.md) — the full retrieve→generate answer loop and groundedness.
|
|
204
|
+
- [`prompt-engineering`](../prompt-engineering/SKILL.md) — the wording of the prompt.
|
|
205
|
+
- [`finetuning`](../finetuning/SKILL.md) + [`deep-learning`](../deep-learning/SKILL.md) — actually
|
|
206
|
+
training/adapting the network (this skill picks the type and metric; those move the weights).
|
|
207
|
+
- [`training-data`](../training-data/SKILL.md) — building the labeled corpus you train on.
|
|
208
|
+
|
|
209
|
+
## Checklist
|
|
210
|
+
|
|
211
|
+
- [ ] Architecture picked from task shape (encoder / decoder / enc-dec), not habit.
|
|
212
|
+
- [ ] Tokenizer loaded from the *same* checkpoint, used identically at train and inference.
|
|
213
|
+
- [ ] Tokenizer choice justified vs OOV, cost, and — if multilingual — measured fertility.
|
|
214
|
+
- [ ] Special tokens and `attention_mask` handled (padding masked, `-100` on ignored labels).
|
|
215
|
+
- [ ] Metric matches the task: macro-F1 (imbalanced), entity-F1/seqeval (NER), ROUGE/BLEU/chrF
|
|
216
|
+
only as a regression signal, LLM-judge + human for open-ended.
|
|
217
|
+
- [ ] Perplexity compared only within one tokenizer.
|
|
218
|
+
- [ ] Retrieval / RAG / prompt / training concerns routed to the sibling skill, not re-solved here.
|
|
219
|
+
|
|
220
|
+
## References
|
|
221
|
+
|
|
222
|
+
- `references/tokenization.md` — BPE/WordPiece/Unigram/byte-level training mechanics, special
|
|
223
|
+
tokens per family, offset mapping, and a fertility-measuring snippet.
|
|
224
|
+
- `references/evaluation.md` — runnable seqeval, sacrebleu (BLEU/chrF), ROUGE, perplexity, and an
|
|
225
|
+
LLM-as-judge rubric, with when each lies.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Evals — nlp
|
|
2
|
+
|
|
3
|
+
Routing and capability checks for a human or LLM grader, not an automated suite. Read each
|
|
4
|
+
`should_trigger` prompt and confirm an agent would reach for `nlp` (tokenizer choice, model-type
|
|
5
|
+
decision, task modeling, or metric selection); read each `should_not_trigger` prompt and confirm
|
|
6
|
+
it routes to the named sibling (`embeddings-search`, `rag`, `prompt-engineering`, `finetuning`,
|
|
7
|
+
`training-data`) for the stated reason. Grade the `capability` scenario by checking the produced
|
|
8
|
+
answer against every `must_include` bullet — the current API/method, the load-bearing guardrail
|
|
9
|
+
(tokenizer mismatch, fertility tax), and the honesty hedge (BLEU/ROUGE weak on open-ended gen,
|
|
10
|
+
licenses must be checked). A pass covers all routing cases plus full rubric coverage.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
skill: nlp
|
|
2
|
+
|
|
3
|
+
should_trigger:
|
|
4
|
+
- prompt: "Should I use BPE or WordPiece for my tokenizer, and what's the difference?"
|
|
5
|
+
why: "Core tokenization-algorithm choice this skill owns; no retrieval or training context."
|
|
6
|
+
- prompt: "BERT vs GPT vs T5 — which one do I pick for classifying support tickets into 6 categories?"
|
|
7
|
+
why: "Model-type-per-task decision (encoder vs decoder vs enc-dec) is the skill's opening decision gate."
|
|
8
|
+
- prompt: "My tokenizer splits Catalan text into way more tokens than the English equivalent, so it costs more — why?"
|
|
9
|
+
why: "Multilingual over-fragmentation / fertility tax is section 4; the cost symptom points squarely here."
|
|
10
|
+
- prompt: "Is BLEU or ROUGE a meaningful metric for judging my summaries, or is it misleading?"
|
|
11
|
+
why: "Metric selection plus the weak-correlation caveat for open-ended generation, owned in section 3."
|
|
12
|
+
- prompt: "How do I evaluate a NER model properly — entity-level F1 vs per-token accuracy?"
|
|
13
|
+
why: "Token-classification evaluation with seqeval entity-F1 is explicitly the skill's task + metric."
|
|
14
|
+
- prompt: "quin model faig servir per classificar text: un encoder tipus BERT o un LLM generatiu?"
|
|
15
|
+
why: "Catalan trigger on the encoder-vs-decoder model-type decision, the skill's primary gate."
|
|
16
|
+
- prompt: "My fine-tuned classifier gives garbage at inference but was fine in training — could the tokenizer be the problem?"
|
|
17
|
+
why: "Non-obvious: the classic train/inference tokenizer mismatch that corrupts silently, the skill's load-bearing guardrail."
|
|
18
|
+
|
|
19
|
+
should_not_trigger:
|
|
20
|
+
- prompt: "Which embedding model and chunk size should I use to make semantic search return relevant results?"
|
|
21
|
+
route_to: embeddings-search
|
|
22
|
+
why: "Retrieval embedding choice, chunking and recall@k are the retrieval technique layer, not task modeling."
|
|
23
|
+
- prompt: "Build the full RAG pipeline: retrieve context, prompt the model, and generate a grounded answer."
|
|
24
|
+
route_to: rag
|
|
25
|
+
why: "End-to-end retrieve→generate answer loop and groundedness live in rag, past where nlp stops."
|
|
26
|
+
- prompt: "Rewrite my system prompt with few-shot examples so the model stops refusing valid requests."
|
|
27
|
+
route_to: prompt-engineering
|
|
28
|
+
why: "Prompt wording / few-shot design, not tokenization, architecture, or metrics."
|
|
29
|
+
- prompt: "Fine-tune Llama with LoRA on my instruction dataset using trl and peft."
|
|
30
|
+
route_to: finetuning
|
|
31
|
+
why: "Actually training/adapting the network is the finetuning method skill, not picking type/metric."
|
|
32
|
+
- prompt: "Convert my customer chats into a JSONL messages dataset with system/user/assistant turns for training."
|
|
33
|
+
route_to: training-data
|
|
34
|
+
why: "Building the training corpus format is training-data, not NLP task modeling or metrics."
|
|
35
|
+
|
|
36
|
+
capability:
|
|
37
|
+
- scenario: "A team has 40k multilingual support tickets (English + Catalan + Arabic) to auto-route into 8 categories and also to extract product names as entities. They're unsure whether to use a big generative LLM, why Arabic tickets seem to cost far more tokens, and how to know if the models are any good."
|
|
38
|
+
must_include:
|
|
39
|
+
- "Picks an encoder (BERT-family / ModernBERT, multilingual e.g. XLM-R) with a classification head for the 8-way routing rather than a large generative decoder, justifying cost/latency/accuracy, and hedges that specific checkpoints and licenses must be checked on the model card"
|
|
40
|
+
- "Loads the tokenizer from the same checkpoint and uses it identically at train and inference, calling out that a train/inference tokenizer mismatch corrupts results silently"
|
|
41
|
+
- "Explains the Arabic token blow-up as tokenizer over-fragmentation / fertility (2–15x more tokens on non-English scripts), recommends a multilingual SentencePiece-based tokenizer and measuring fertility per language before committing"
|
|
42
|
+
- "Uses a token-classification (NER) setup with B-/I-/O labels aligned to subwords (-100 on continuations/specials) for product-name extraction"
|
|
43
|
+
- "Evaluates routing with macro-F1 (not raw accuracy, because classes are imbalanced) and NER with entity-level F1 via seqeval (not per-token accuracy)"
|
|
44
|
+
- "Notes that if any free-text summarization/quality judgment is added, BLEU/ROUGE/chrF correlate weakly with human judgment on open-ended output and an LLM-as-judge plus human spot-check is needed, with judge-bias mitigation"
|
|
45
|
+
- "Routes retrieval/vector search to embeddings-search, the RAG answer loop to rag, prompt wording to prompt-engineering, and the actual training to finetuning/deep-learning"
|