@ericrisco/rsc 0.1.38 → 0.1.40

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.
Files changed (59) hide show
  1. package/README.md +8 -2
  2. package/manifest.json +181 -3
  3. package/package.json +1 -1
  4. package/scripts/lib/domains.js +1 -0
  5. package/scripts/lib/recommend.js +6 -0
  6. package/skills/automation-strategy/SKILL.md +2 -2
  7. package/skills/automation-strategy/references/platform-selection-matrix.md +2 -2
  8. package/skills/deep-learning/SKILL.md +263 -0
  9. package/skills/deep-learning/evals/README.md +11 -0
  10. package/skills/deep-learning/evals/cases.yaml +48 -0
  11. package/skills/deep-learning/references/distributed.md +105 -0
  12. package/skills/deep-learning/references/training-recipes.md +135 -0
  13. package/skills/finetuning/SKILL.md +244 -0
  14. package/skills/finetuning/evals/README.md +13 -0
  15. package/skills/finetuning/evals/cases.yaml +50 -0
  16. package/skills/finetuning/references/hyperparameters-and-eval.md +74 -0
  17. package/skills/finetuning/references/methods.md +189 -0
  18. package/skills/harness/references/providers.yaml +38 -2
  19. package/skills/huggingface/SKILL.md +1 -1
  20. package/skills/machine-learning/SKILL.md +260 -0
  21. package/skills/machine-learning/evals/README.md +15 -0
  22. package/skills/machine-learning/evals/cases.yaml +54 -0
  23. package/skills/machine-learning/references/gbdt-and-tuning.md +83 -0
  24. package/skills/machine-learning/references/metrics-and-imbalance.md +82 -0
  25. package/skills/machine-learning/references/pipelines-and-cv.md +135 -0
  26. package/skills/n8n/SKILL.md +1 -1
  27. package/skills/nlp/SKILL.md +225 -0
  28. package/skills/nlp/evals/README.md +10 -0
  29. package/skills/nlp/evals/cases.yaml +45 -0
  30. package/skills/nlp/references/evaluation.md +107 -0
  31. package/skills/nlp/references/tokenization.md +95 -0
  32. package/skills/open-weights/SKILL.md +239 -0
  33. package/skills/open-weights/evals/README.md +14 -0
  34. package/skills/open-weights/evals/cases.yaml +42 -0
  35. package/skills/open-weights/references/licenses.md +123 -0
  36. package/skills/open-weights/references/model-families.md +83 -0
  37. package/skills/open-weights/references/sizing-and-quant.md +89 -0
  38. package/skills/roast-me/tools/adapters/__pycache__/__init__.cpython-312.pyc +0 -0
  39. package/skills/roast-me/tools/adapters/__pycache__/base.cpython-312.pyc +0 -0
  40. package/skills/roast-me/tools/adapters/__pycache__/claude.cpython-312.pyc +0 -0
  41. package/skills/roast-me/tools/adapters/__pycache__/codex.cpython-312.pyc +0 -0
  42. package/skills/roast-me/tools/adapters/__pycache__/gemini.cpython-312.pyc +0 -0
  43. package/skills/roast-me/tools/adapters/__pycache__/registry.cpython-312.pyc +0 -0
  44. package/skills/training-data/SKILL.md +226 -0
  45. package/skills/training-data/evals/README.md +13 -0
  46. package/skills/training-data/evals/cases.yaml +41 -0
  47. package/skills/training-data/references/formats.md +118 -0
  48. package/skills/training-data/references/synthesis-dedup-quality.md +134 -0
  49. package/skills/unsloth/SKILL.md +254 -0
  50. package/skills/unsloth/evals/README.md +10 -0
  51. package/skills/unsloth/evals/cases.yaml +42 -0
  52. package/skills/unsloth/references/export.md +75 -0
  53. package/skills/unsloth/references/grpo.md +91 -0
  54. package/skills/unsloth/references/masking-and-templates.md +91 -0
  55. package/skills/vllm/SKILL.md +235 -0
  56. package/skills/vllm/evals/README.md +9 -0
  57. package/skills/vllm/evals/cases.yaml +47 -0
  58. package/skills/vllm/references/flags-and-endpoints.md +105 -0
  59. 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
+ ```
@@ -99,7 +99,7 @@ n8n_create_workflow { "name": "...", "nodes": [ … ], "connections": { …
99
99
 
100
100
  ```bash
101
101
  curl -sS -X POST "$N8N_API_URL/api/v1/workflows/$ID/execute" -H "X-N8N-API-KEY: $N8N_API_KEY"
102
- curl -sS -X POST "$N8N_HOST/webhook-test/my-path" -H "Content-Type: application/json" -d '{"foo":"bar"}'
102
+ curl -sS -X POST "$N8N_API_URL/webhook-test/my-path" -H "Content-Type: application/json" -d '{"foo":"bar"}'
103
103
  ```
104
104
  ```
105
105
  n8n_test_workflow { "id": "$ID" }
@@ -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"