|
oc_chat
|
Classes | |
| class | TrainConfig |
Functions | |
| list[dict[str, str]] | read_jsonl (Path path) |
| frozenset[str] | held_out_commands (Path bench_dir) |
| list[dict[str, str]] | load_split (Path bench_dir, str name, frozenset[str] held_out) |
| dict[str, int] | label_counts (list[dict[str, str]] rows) |
| list[list[dict[str, str]]] | epoch_batches (list[dict[str, str]] rows, int batch, random.Random rng) |
| list[list[int]] | tokenise (list[str] commands, object tokenizer, int max_length) |
| tuple[str,...] | tokenise_keys (object tokenizer) |
| int | full_vocab_size (object tokenizer) |
| tuple[list[int], dict[int, int]] | prune_vocab (list[str] corpus, object tokenizer, int reserve=DEFAULT_VOCAB_RESERVE, int max_length=DEFAULT_MAX_LENGTH) |
| float | token_coverage (list[str] commands, object tokenizer, set[int]|list[int] kept, int max_length=DEFAULT_MAX_LENGTH) |
| int | resolve_max_length (list[str] commands, object tokenizer, int requested, int fallback=DEFAULT_MAX_LENGTH) |
| str | cache_key (Path corpus_path, str tokenizer_digest, int max_length) |
| list[list[int]]|None | load_cached_ids (Path cache_root, str key) |
| Path | store_cached_ids (Path cache_root, str key, list[list[int]] rows) |
| list[int] | remap_lookup (dict[int, int] remap, int unknown_id, int size) |
| list[dict[str, str]] | validation_subset (list[dict[str, str]] rows, int size, int seed=DEFAULT_SUBSET_SEED) |
| dict[str, float] | probabilities (list[float] logits, float temperature=1.0) |
| float | brier (dict[str, float] probs, str truth) |
| float | mean_negative_log_likelihood (list[list[float]] logits, list[int] labels, float temperature, list[float]|None weights=None) |
| float | fit_temperature (list[list[float]] logits, list[int] labels, list[float]|None weights=None, float low=0.05, float high=20.0) |
| dict[str, float|int] | calibration_report (list[list[float]] logits, list[dict[str, str]] rows, float temperature) |
| list[list[float]] | cost_matrix (float alpha=DEFAULT_ALPHA) |
| float | expected_cost_of (dict[str, float] probs, str pred, list[list[float]] costs) |
| str | min_cost_verdict (dict[str, float] probs, list[list[float]] costs) |
| str | decide (dict[str, float] probs, str decision=DEFAULT_DECISION, list[list[float]]|None costs=None) |
| float | expected_cost (list[list[float]] prob_rows, list[int] labels, list[list[float]] costs) |
| float | stopping_value (dict[str, float|int] record, str metric=DEFAULT_STOP_METRIC) |
| dict[str, float|int|bool] | select_best_epoch (list[tuple[float, float]] pairs, str metric=DEFAULT_STOP_METRIC, int patience=1, float min_delta=EARLY_STOP_MIN_DELTA) |
| str | other_stop_metric (str metric) |
| bool | grants_more_permission (str truth, str pred) |
| dict[str, int] | confident_error_counts (list[dict[str, object]] rows, tuple[float,...] thresholds=CONFIDENT_THRESHOLDS) |
| str | _normalised_program (str token) |
| bool | _is_short_cluster (str token) |
| str|None | strip_recursive_option (str command) |
| tuple[list[dict[str, str]], int] | relabel_recursive_allow (list[dict[str, str]] rows, object classify) |
| object | gate_classifier (Path sandbox_root) |
| dict[str, object] | result_row (dict[str, str] case, list[float] logits, float temperature, float elapsed_ms, str decision=DEFAULT_DECISION, list[list[float]]|None costs=None) |
| None | write_result_file (Path path, str model, str condition, list[dict[str, object]] rows, dict[str, object] provenance) |
| str | file_digest (Path path) |
| str | weights_digest (Path model_dir) |
| tuple[str, bool] | verify_tokenizer_vocab (Path tokenizer_dir, str backbone, Path hf_home) |
| tuple[object, object, object] | _require_torch () |
| str | resolve_device (str requested=DEFAULT_DEVICE) |
| int | peak_device_memory (str device) |
| str | describe_device (str device) |
| dict[str, object] | _to_device (object encoded, str device) |
| object | remap_table (dict[int, int] remap, int unknown_id, int size, str device) |
| object | _remap_input_ids (object input_ids, object table) |
| object | cost_tensor (list[list[float]] costs, str device) |
| object | expected_cost_term (object logits, object targets, object table) |
| int | slice_embeddings (object model, list[int] kept, int pad_row) |
| dict[str, object] | _collate (list[list[int]] rows, int pad_id, tuple[str,...] extra_keys, str device) |
| object|None | _saved_remap_table (Path model_dir, str device) |
| object | _load_tokenizer (Path tokenizer_dir) |
| list[list[float]] | _forward_logits (object model, object tokenizer, list[str] commands, int max_length, int batch, str device="cpu", object|None table=None) |
| float | score_full_validation (Path model_dir, list[dict[str, str]] rows, object tokenizer, int max_length, int batch, str device, object|None table) |
| dict[str, object] | train (TrainConfig config) |
| dict[str, float|int] | calibrate (Path model_dir, Path corpus_dir, int batch=DEFAULT_BATCH, int max_length=DEFAULT_MAX_LENGTH, int threads=DEFAULT_THREADS, str device=DEFAULT_DEVICE) |
| dict[str, object] | evaluate (Path model_dir, Path cases_path, str condition, Path out_path, str model_name, int batch=1, int max_length=DEFAULT_MAX_LENGTH, int threads=DEFAULT_THREADS, str device=DEFAULT_DEVICE, str decision=DEFAULT_DECISION, float alpha=DEFAULT_ALPHA) |
| dict[str, object] | relabel (Path bench_dir, Path out_dir) |
| Path | _bench_dir () |
| argparse.ArgumentParser | _build_parser () |
| int | main (list[str]|None argv=None) |
Variables | |
| tuple | LABELS = ("allow", "confirm", "deny") |
| tuple | PERMISSIVENESS = ("deny", "confirm", "allow") |
| float | DEFAULT_ALPHA = 1.0 |
| float | DEFAULT_COST_WEIGHT = 0.0 |
| tuple | DECISION_CHOICES = ("argmax", "min-cost") |
| str | DEFAULT_DECISION = "argmax" |
| tuple | STOP_METRIC_CHOICES = ("ce", "cost") |
| str | DEFAULT_STOP_METRIC = "ce" |
| tuple | CONFIDENT_THRESHOLDS = (0.9, 0.99) |
| int | DEFAULT_SEED = 7 |
| int | DEFAULT_MAX_LENGTH = 64 |
| int | DEFAULT_BATCH = 32 |
| int | DEFAULT_LEARNING_RATE = 3e-5 |
| int | DEFAULT_MAX_EPOCHS = 4 |
| float | DEFAULT_WARMUP_SHARE = 0.1 |
| int | DEFAULT_THREADS = 8 |
| tuple | DEVICE_CHOICES = ("auto", "cpu", "cuda") |
| str | DEFAULT_DEVICE = "auto" |
| str | VOCAB_PRUNE_FILE = "vocab_prune.json" |
| int | DEFAULT_VOCAB_RESERVE = 2000 |
| DEFAULT_CACHE_ROOT = Path(r"G:\Temp\claude\g--PythonApps-option-c\harness\bench_cache") | |
| int | DEFAULT_SUBSET_ROWS = 1000 |
| int | DEFAULT_SUBSET_SEED = 294 |
| int | FAST_MAX_LENGTH = 48 |
| float | DEFAULT_BUDGET_SECONDS = 3600.0 |
| int | EARLY_STOP_MIN_DELTA = 1e-4 |
| int | BUCKET_BATCHES = 50 |
| DEFAULT_MODEL_ROOT | |
| DEFAULT_TOKENIZER_DIR | |
| DEFAULT_HF_HOME = Path(r"G:\Temp\claude\g--PythonApps-option-c\harness\hf_home") | |
| frozenset | LISTING_PROGRAMS = frozenset({"ls", "dir"}) |
| str | RECURSIVE_OPTION = "R" |
A small DeBERTa-v3 encoder fine-tuned on the gate-labelled corpus, calibrated.
Purpose
-------
The 2026-09-07 classifier note proposed discriminative encoder classifiers as
gates: tens to a few hundred million parameters, CPU milliseconds, a typed
verdict with a probability, trained on our own corpus. The 2026-09-17 session
tested the hosted end of that idea and the zero-shot end of it; this module
tests the proposal itself, which is a small encoder FINE-TUNED on the
gate-labelled commands plan 288 generated. It trains, it calibrates its logits
with one scalar, and it writes result files in the bench schema so the same
scorer reads it beside Jev, DeepSeek and the token floor.
Four commands, in the order a run uses them::
train --backbone <hf id> --out <dir> # fine-tune, early-stopped
calibrate --model <dir> # fit one temperature
evaluate --model <dir> --cases <file> # write a result file
relabel --out <dir> # the policy-change probe
``train``, ``calibrate`` and ``evaluate`` need torch and transformers and run
under a SCRATCH HARNESS venv; ``relabel`` needs the argv gate and runs under
the PROJECT venv. That split is why torch and transformers are imported inside
the functions that use them rather than at module scope: the tests in
``tests/test_290_encoder.py`` and ``tests/test_294_speed.py`` exercise the
loader, the temperature fit, the vocabulary pruning, the tensor cache, the
relabel filter and the result writer in the project venv, where no torch
exists, and an import at module scope would put every one of them out of reach.
Two scratch venvs, and which one to use
---------------------------------------
Plan 290 ran on the CPU venv at ``...\\harness\\.venv``. Plan 294 added a second
one at ``...\\harness_cuda`` carrying ``torch==2.14.*+cu126``, because the host's
GTX 1060 is a Pascal card (compute capability 6.1) and **the CUDA 12.6 wheel of
PyTorch 2.14 is the last prebuilt build that still carries ``sm_61`` kernels**:
PyTorch dropped Pascal from its CUDA 12.8 and 12.9 wheels at release 2.8 and
CUDA 13 cannot build them. Both venvs stay; the CPU one is what keeps every
number plan 290 produced reproducible, and ``--device cpu`` reproduces a CPU run
inside the CUDA venv as well.
Run the trainer from the subproject root with the harness interpreter, with
``HF_HOME`` and ``HF_HUB_OFFLINE=1`` set in the shell::
<harness>\\python.exe bench/argv_policy/baselines/encoder_classifier.py train
--backbone microsoft/deberta-v3-xsmall --out <scratch>/bench_models/xsmall-7
Model directories, their checkpoints and the raw training logs live under
:data:`DEFAULT_MODEL_ROOT` on ``G:`` and are never committed; only the result
JSON and a copy of the training log go into the worktree.
The tokenizer
-------------
``AutoTokenizer.from_pretrained("microsoft/deberta-v3-xsmall")`` cannot be
loaded in the harness venv: the snapshot ships only ``spm.model``, and
converting that to a fast tokenizer needs protobuf and sentencepiece, neither
of which is installed (and installing is not this plan's to do). The
``MoritzLaurer/deberta-v3-base-zeroshot-v2.0`` snapshot in the same cache
ships an already-converted ``tokenizer.json``, and its ``spm.model`` is
BYTE-IDENTICAL to both microsoft backbones' -- SHA-256
``c679fbf93643d19aab7ee10c0b99e460bdbc02fedf34b92b05af343b4af586fd`` for all
three. So the tokenizer is loaded from that directory and
:func:`verify_tokenizer_vocab` re-checks the digest equality at every training
run rather than trusting this paragraph.
Pricing the direction of an error (plan 298)
--------------------------------------------
Cross-entropy prices an ``allow`` where the gate says ``deny`` exactly as it
prices a ``deny`` where the gate says ``allow``. The bench does not: the column
it is read by counts the errors that GRANT MORE PERMISSION than the gate, and
the other kind costs a human one unnecessary confirmation. Plan 298 puts that
asymmetry into the arithmetic as one cost matrix and two levers over it.
``--alpha A`` sets the matrix: zero on the diagonal, ``A`` where the prediction
is more permissive than the truth, ``1`` where it is less. Then:
- ``evaluate --decision min-cost`` reads the verdict minimising expected cost
out of the calibrated probabilities. It retrains nothing and rewrites no
probability -- only the ``pred`` field moves -- so it costs one flag.
- ``train --cost-weight B`` adds ``B`` times the expected cost under that same
matrix to the training loss.
Both default to today's behaviour exactly, not approximately: ``--alpha 1.0``
makes every off-diagonal cell ``1``, under which the min-cost verdict and the
argmax verdict agree on every input including ties, and ``--cost-weight 0.0``
removes the loss term altogether.
Watching the same column the objective prices (plan 300)
--------------------------------------------------------
Plan 298 held the stopping rule fixed on validation cross-entropy so that the
objective was the only variable, and measured what that cost: at
``--cost-weight 3`` the run stopped at epoch 2 and checkpointed an undertrained
model, because an objective that prices the direction moves AWAY from
cross-entropy by construction and a rule watching cross-entropy reads that
movement as a run that has stopped improving. The two pieces are supplied here.
- ``train --stop-metric cost`` makes early stopping and checkpoint selection
watch the subset validation EXPECTED COST under the same ``--alpha`` matrix
the loss term prices with, rather than the subset cross-entropy. Both figures
are computed and logged every epoch whichever one stops the run, so an
after-the-fact reader can see the epoch the other metric would have kept --
and :func:`select_best_epoch` states it in the training record rather than
leaving it to be inferred from the log.
- ``evaluate`` counts the PERMISSION-GRANTING errors a result file makes
confidently, at 0.9 and at 0.99 top probability, into its provenance block.
Plan 298 found that class by hand and it is the class the escalation
threshold cannot reach: an error the model is sure about is not escalated and
no decision rule over a calibrated distribution moves it. Counting it in the
file makes it a column of the table rather than an afternoon's grep.
``--stop-metric ce`` is the default and is plan 290's rule unchanged, so an
unflagged run is still the run every earlier tag on this bench was produced by;
the counts are additive provenance and leave the row shape ``score.py`` reads
untouched.
Responsibilities
----------------
- :func:`held_out_commands` / :func:`load_split` / :func:`label_counts` --
the corpus loader, with the hand-case guard.
- :func:`epoch_batches` -- one epoch of length-bucketed minibatches.
- :func:`resolve_device` -- plan 294 P1's device selection.
- :func:`prune_vocab` / :func:`token_coverage` -- plan 294 P2's vocabulary
pruning, in pure Python.
- :func:`remap_lookup` / :func:`remap_table` -- the pruned id space.
- :func:`cache_key` / :func:`load_cached_ids` / :func:`store_cached_ids` /
:func:`validation_subset` / :func:`resolve_max_length` -- plan 294 P3's
tokenised-id cache and subset validation.
- :func:`fit_temperature` / :func:`mean_negative_log_likelihood` /
:func:`probabilities` / :func:`brier` -- calibration, in pure Python.
- :func:`cost_matrix` / :func:`expected_cost_of` / :func:`min_cost_verdict` /
:func:`decide` / :func:`expected_cost` -- plan 298's direction-weighted cost
matrix, its decision rule and its loss term, in pure Python.
- :func:`cost_tensor` / :func:`expected_cost_term` -- the same loss term on
tensors, which is what the training loop actually calls.
- :func:`stopping_value` / :func:`select_best_epoch` -- plan 300 P1's
direction-aware stopping rule, in pure Python.
- :func:`grants_more_permission` / :func:`confident_error_counts` -- plan 300
P2's confident permission-granting error count, in pure Python.
- :func:`strip_recursive_option` / :func:`relabel_recursive_allow` /
:func:`gate_classifier` -- the policy-change probe's documented relabel.
- :func:`result_row` / :func:`write_result_file` -- the plan-288 result schema.
- :func:`train` / :func:`calibrate` / :func:`evaluate` -- the torch commands.
- :func:`main` -- the CLI.
Diagnostics
-----------
Domain: OC_CHAT.MAIN
Levels:
L2 - command lifecycle (training start and finish, files written)
L3 - per-epoch validation loss, per-case-set agreement, fitted temperature
L4 - module load
Contracts
---------
- Read-only with respect to the committed corpus, the case sets and the gate:
the policy-change probe writes a RELABELLED COPY under the scratch root and
never edits a committed file.
- The relabelled labels come from the unmodified gate applied to a rewritten
command, never from a hand edit of a verdict.
- Bitwise reproduction is not claimed (CPU thread scheduling reorders float
reductions); a run is IDENTIFIABLE instead -- every result file carries the
backbone, the seed, the epoch count, the training wall time, the parameter
count and the SHA-256 of the saved weights.
- Nothing from either case set touches training or calibration.
- Console output is ASCII only; commands are printed through :func:`ascii`.
- No dependency enters any ``pyproject.toml``; torch and transformers are
imported lazily and only exist in the scratch harness venv.
Dependencies
------------
- oc_diagnostics (structured debug logging via _dbg)
- torch (LAZY, harness venv only: tensors, AdamW, the forward and backward pass)
- transformers (LAZY, harness venv only: AutoTokenizer and AutoModelForSequenceClassification)
- oc_chat.tools._argv_policy (LAZY, project venv only: the gate the relabel asks)
|
protected |
``bench/argv_policy`` -- the directory holding the corpus and the cases.
Definition at line 2020 of file encoder_classifier.py.
|
protected |
Construct the CLI argument parser.
Definition at line 2026 of file encoder_classifier.py.
|
protected |
Pad cached ids into the tensors the model's forward pass expects. *extra_keys* mirrors whatever the tokenizer itself emits beside the ids and the mask -- ``token_type_ids`` for this tokenizer -- as zeros, so a cached batch and a freshly tokenised one are the same call.
Definition at line 1333 of file encoder_classifier.py.
|
protected |
Logits for every command, in batches, in evaluation mode. *table* is plan 294 P2's pruned-vocabulary lookup: when the model's embedding table has been sliced, the ids the tokenizer produces are the FULL vocabulary's and have to be carried through the map before they index the sliced table, or every id past the kept count is out of range.
Definition at line 1383 of file encoder_classifier.py.
|
protected |
Whether *token* is a short-option cluster such as ``-la``.
Definition at line 975 of file encoder_classifier.py.
|
protected |
Load the converted DeBERTa-v3 fast tokenizer from a local directory.
Definition at line 1376 of file encoder_classifier.py.
|
protected |
The program name the gate would read out of *token*.
Definition at line 968 of file encoder_classifier.py.
|
protected |
Carry a batch of full-vocabulary ids through a :func:`remap_table`.
Definition at line 1279 of file encoder_classifier.py.
|
protected |
Import torch and transformers, or say which interpreter is needed.
Definition at line 1193 of file encoder_classifier.py.
|
protected |
The lookup table for a model trained with a pruned vocabulary, or ``None``. ``train`` writes :data:`VOCAB_PRUNE_FILE` beside the weights when and only when it pruned, so its presence is what tells ``calibrate`` and ``evaluate`` that the ids a tokenizer hands them are not the ids this model's embedding table is indexed by. A model trained without pruning has no such file and every id goes through untouched.
Definition at line 1355 of file encoder_classifier.py.
|
protected |
Every tensor of a tokenizer's output, moved onto *device*.
Definition at line 1261 of file encoder_classifier.py.
| float encoder_classifier.brier | ( | dict[str, float] | probs, |
| str | truth ) |
The multi-class Brier score of one forecast (``score.brier_row``). Kept here rather than imported because ``score.py`` sits one directory up and this module is run as a script from the harness venv; the arithmetic is two lines and ``tests/test_290_encoder.py`` asserts the two agree.
Definition at line 606 of file encoder_classifier.py.
| str encoder_classifier.cache_key | ( | Path | corpus_path, |
| str | tokenizer_digest, | ||
| int | max_length ) |
The cache identity of one tokenised split. The corpus file's own SHA-256 is in the key, so a regenerated corpus invalidates the cache instead of training the next run on the previous corpus's ids -- which would be a silent wrong answer, not a crash.
Definition at line 525 of file encoder_classifier.py.
| dict[str, float | int] encoder_classifier.calibrate | ( | Path | model_dir, |
| Path | corpus_dir, | ||
| int | batch = DEFAULT_BATCH, | ||
| int | max_length = DEFAULT_MAX_LENGTH, | ||
| int | threads = DEFAULT_THREADS, | ||
| str | device = DEFAULT_DEVICE ) |
Fit one temperature on the validation split and record what it moved. Plan 294 P3 validates on a 1,000-row subset DURING training, for speed; the temperature is still fitted on the whole 3,000-row split here, because a calibration scalar fitted on a third of the rows is a third as well determined and costs one forward pass to get right.
Definition at line 1799 of file encoder_classifier.py.
| dict[str, float | int] encoder_classifier.calibration_report | ( | list[list[float]] | logits, |
| list[dict[str, str]] | rows, | ||
| float | temperature ) |
NLL, Brier and agreement on the validation split, before and after.
Definition at line 684 of file encoder_classifier.py.
| dict[str, int] encoder_classifier.confident_error_counts | ( | list[dict[str, object]] | rows, |
| tuple[float, ...] | thresholds = CONFIDENT_THRESHOLDS ) |
Plan 300 P2: how many permission-granting errors were made CONFIDENTLY. Returns the total, and the count at or above each threshold's top probability. The class matters because it is the one nothing downstream can catch: escalation fires BELOW its threshold, so an error at 0.99 is not escalated; and a min-cost reading moves a verdict only where the distribution leaves enough mass elsewhere to move, which at 0.999 it does not. Plan 298 found five of ``small``'s thirteen at 0.9 or better and three at 0.999 or better by hand; this makes it a number every result file carries. A row with no usable forecast counts towards the total and towards no threshold, which is the same treatment ``score.py`` gives it in the Brier column: a missing probability is not evidence of a confident mistake.
Definition at line 924 of file encoder_classifier.py.
| list[list[float]] encoder_classifier.cost_matrix | ( | float | alpha = DEFAULT_ALPHA | ) |
Plan 298 P1: ``COST[true][pred]``, both axes in :data:`LABELS` order. Zero on the diagonal; *alpha* in every cell where the prediction grants MORE permission than the gate's verdict; ``1.0`` in every cell where it grants less. At *alpha* ``1.0`` every off-diagonal cell is ``1.0``, which is the matrix a plain cross-entropy objective and an argmax decision rule already imply -- which is why that is the default and why it reproduces every number on this bench rather than approximating it. The asymmetry is the whole of this plan. An ``allow`` where the gate says ``deny`` runs something the gate refused; a ``deny`` where the gate says ``allow`` costs a human one unnecessary confirmation. Cross-entropy prices those two mistakes identically. The gate does not, and neither does the column this bench is read by.
Definition at line 712 of file encoder_classifier.py.
| object encoder_classifier.cost_tensor | ( | list[list[float]] | costs, |
| str | device ) |
A :func:`cost_matrix` as a float tensor on *device*, built once per run.
Definition at line 1285 of file encoder_classifier.py.
| str encoder_classifier.decide | ( | dict[str, float] | probs, |
| str | decision = DEFAULT_DECISION, | ||
| list[list[float]] | None | costs = None ) |
The verdict *decision* reads out of one calibrated distribution. The two rules are kept behind one function because ``result_row`` must not grow a branch: a result file differs under the two rules in its ``pred`` field and in nothing else, and that stays true more easily when the choice is made in one place.
Definition at line 772 of file encoder_classifier.py.
| str encoder_classifier.describe_device | ( | str | device | ) |
A one-line description of *device* for a log line and a result file.
Definition at line 1251 of file encoder_classifier.py.
| list[list[dict[str, str]]] encoder_classifier.epoch_batches | ( | list[dict[str, str]] | rows, |
| int | batch, | ||
| random.Random | rng ) |
One epoch of minibatches: a permutation of *rows*, length-bucketed. An epoch is a permutation, so the class balance a batch sequence sees is the corpus's own and no class weighting is needed. Within a chunk of :data:`BUCKET_BATCHES` batches the rows are sorted by command length before being cut into batches, and the batch order is then shuffled: padding follows the chunk rather than the longest command in the epoch, which on this corpus is the difference between 20.6 and 17.1 minutes per epoch, and the shuffle keeps the gradient sequence stochastic.
Definition at line 389 of file encoder_classifier.py.
| dict[str, object] encoder_classifier.evaluate | ( | Path | model_dir, |
| Path | cases_path, | ||
| str | condition, | ||
| Path | out_path, | ||
| str | model_name, | ||
| int | batch = 1, | ||
| int | max_length = DEFAULT_MAX_LENGTH, | ||
| int | threads = DEFAULT_THREADS, | ||
| str | device = DEFAULT_DEVICE, | ||
| str | decision = DEFAULT_DECISION, | ||
| float | alpha = DEFAULT_ALPHA ) |
Predict a case set and write its result file, timing every decision. At ``batch=1`` the clock runs around tokenise-plus-forward for one command, which is the per-decision latency a gate would pay. At ``batch>1`` the clock runs around the whole batch and the per-row figure is the amortised one, which is the throughput reading rather than the latency reading. *decision* and *alpha* are plan 298 P2's rule. They are a property of the READING and not of the model, so both go into the provenance block: two result files from one checkpoint under two rules are otherwise indistinguishable except by their verdicts, and the scorer reports no field that would say which rule produced which. Plan 300 P2 adds :func:`confident_error_counts` to that same block. It goes in the PROVENANCE and not into the rows on purpose: ``score.py`` reads the row shape and a new per-row field would change what every consumer of a result file sees, whereas a provenance key is additive and the scorer never looks at it. The counts are recomputable from the rows by anyone who wants them at another threshold, which is why the file carries the summary rather than a flag per row.
Definition at line 1847 of file encoder_classifier.py.
| float encoder_classifier.expected_cost | ( | list[list[float]] | prob_rows, |
| list[int] | labels, | ||
| list[list[float]] | costs ) |
Plan 298 P3's training term, written out in plain Python. Per row ``sum_pred p[pred] * COST[true][pred]``, averaged over the batch so the term lands on the same scale as the mean cross-entropy it is added to and ``--cost-weight`` therefore means the same thing at any batch size. This function is the DEFINITION and :func:`expected_cost_term` is the fast copy of it; ``tests/test_298_direction.py`` asserts the two agree on a toy batch, which is the only reason the tensor version can be trusted inside a run whose per-step output nobody checks by hand.
Definition at line 792 of file encoder_classifier.py.
| float encoder_classifier.expected_cost_of | ( | dict[str, float] | probs, |
| str | pred, | ||
| list[list[float]] | costs ) |
What predicting *pred* costs in expectation under the forecast *probs*. ``sum_true p(true) * COST[true][pred]`` -- the quantity plan 298 P2's rule minimises, named so a test can assert the arithmetic directly rather than only through the verdict it happens to produce.
Definition at line 740 of file encoder_classifier.py.
| object encoder_classifier.expected_cost_term | ( | object | logits, |
| object | targets, | ||
| object | table ) |
Plan 298 P3's training term on tensors: the batch's mean expected cost. ``softmax(logits)`` is the model's own forecast, so the term is differentiable in the logits and pushes probability mass off the verdicts the matrix prices highly -- not merely off the wrong ones, which is what cross-entropy alone does. ``table[targets]`` selects each row's true-verdict cost row, and that gather is the whole of the indexing. :func:`expected_cost` is the definition this reproduces; the witness in ``tests/test_298_direction.py`` asserts the two agree on a toy batch.
Definition at line 1292 of file encoder_classifier.py.
| str encoder_classifier.file_digest | ( | Path | path | ) |
The SHA-256 of one file, which is how a trained run is identified.
Definition at line 1116 of file encoder_classifier.py.
| float encoder_classifier.fit_temperature | ( | list[list[float]] | logits, |
| list[int] | labels, | ||
| list[float] | None | weights = None, | ||
| float | low = 0.05, | ||
| float | high = 20.0 ) |
Fit one temperature by minimising NLL, by golden-section search. Plan 290 P3 asks for the negative log-likelihood to be minimised over one scalar with the optimisation written out rather than called from a fitting library, and W1 asks for a fit that runs in the project venv, where there is no torch. A golden-section search over log-temperature satisfies both: the objective is unimodal in log T, the search needs no derivative, and eighty iterations close the bracket to about 1e-7.
Definition at line 647 of file encoder_classifier.py.
| int encoder_classifier.full_vocab_size | ( | object | tokenizer | ) |
How many ids the tokenizer can emit, counting any added tokens.
Definition at line 434 of file encoder_classifier.py.
| object encoder_classifier.gate_classifier | ( | Path | sandbox_root | ) |
A ``command -> (verdict, reason)`` callable backed by the real gate. Imported lazily: the argv gate lives in the ``oc_chat`` package, which the scratch harness venv cannot import, and only ``relabel`` needs it.
Definition at line 1044 of file encoder_classifier.py.
| bool encoder_classifier.grants_more_permission | ( | str | truth, |
| str | pred ) |
Whether predicting *pred* where the gate said *truth* grants MORE permission. ``score.py``'s :func:`error_direction` in a boolean, and asserted equal to it in ``tests/test_300_stopping.py`` rather than left as two opinions about which way is dangerous. An unrecognised prediction counts as granting more, for the reason the scorer gives: understating that column is the one direction this bench must never round in.
Definition at line 906 of file encoder_classifier.py.
| frozenset[str] encoder_classifier.held_out_commands | ( | Path | bench_dir | ) |
Every command in the two case sets, which no training row may repeat.
Definition at line 349 of file encoder_classifier.py.
| dict[str, int] encoder_classifier.label_counts | ( | list[dict[str, str]] | rows | ) |
How many rows carry each verdict.
Definition at line 383 of file encoder_classifier.py.
| list[list[int]] | None encoder_classifier.load_cached_ids | ( | Path | cache_root, |
| str | key ) |
The tokenised split cached under *key*, or ``None`` when it is absent.
Definition at line 537 of file encoder_classifier.py.
| list[dict[str, str]] encoder_classifier.load_split | ( | Path | bench_dir, |
| str | name, | ||
| frozenset[str] | held_out ) |
Load one corpus split, refusing any row that is a held-out case. ``gen_corpus.py`` already drops the case sets at generation time, so this guard should never fire -- which is exactly why it is worth asserting here: a hand case that reached training would raise every agreement figure on this bench and nothing downstream would look wrong.
Definition at line 362 of file encoder_classifier.py.
| int encoder_classifier.main | ( | list[str] | None | argv = None | ) |
CLI entry point: train, calibrate, evaluate, or relabel.
Definition at line 2116 of file encoder_classifier.py.
| float encoder_classifier.mean_negative_log_likelihood | ( | list[list[float]] | logits, |
| list[int] | labels, | ||
| float | temperature, | ||
| list[float] | None | weights = None ) |
Mean NLL of *logits* divided by *temperature* against *labels*. *weights* exist for the witness, not for the run: weighting each logit row by the true probability of each class turns this into the EXPECTED NLL, whose minimiser over the scalar is exactly the temperature the logits were scaled by, which is what lets the test assert a known answer to two decimals instead of a sampling interval.
Definition at line 618 of file encoder_classifier.py.
| str encoder_classifier.min_cost_verdict | ( | dict[str, float] | probs, |
| list[list[float]] | costs ) |
Plan 298 P2: the verdict with the lowest expected cost under *probs*. ``argmin_pred sum_true p(true) * COST[true][pred]``. This is the cheap lever: it changes no weights and touches no probability, only which verdict is read out of one, so a model already trained and calibrated can be made more cautious for the price of a second pass over its own output. A tie breaks towards the earlier :data:`LABELS` column, which is precisely how ``max`` breaks an argmax tie over the same dict. That matters more than it looks: it is what makes this rule agree with the argmax rule on EVERY input at *alpha* ``1.0``, rather than on every input that happens not to be tied, so the default really is the old behaviour and not merely close to it.
Definition at line 754 of file encoder_classifier.py.
| str encoder_classifier.other_stop_metric | ( | str | metric | ) |
The stopping metric *metric* is not, which is what the record compares.
Definition at line 895 of file encoder_classifier.py.
| int encoder_classifier.peak_device_memory | ( | str | device | ) |
Peak bytes torch allocated on *device* since the last reset, or ``0``. ``max_memory_allocated`` rather than ``nvidia-smi``: the card also carries the desktop and every browser on this host, so the only figure that means "what this training run needed" is torch's own allocator high-water mark.
Definition at line 1237 of file encoder_classifier.py.
| dict[str, float] encoder_classifier.probabilities | ( | list[float] | logits, |
| float | temperature = 1.0 ) |
The temperature-scaled softmax of one logit row, keyed by verdict.
Definition at line 596 of file encoder_classifier.py.
| tuple[list[int], dict[int, int]] encoder_classifier.prune_vocab | ( | list[str] | corpus, |
| object | tokenizer, | ||
| int | reserve = DEFAULT_VOCAB_RESERVE, | ||
| int | max_length = DEFAULT_MAX_LENGTH ) |
Plan 294 P2: the vocabulary rows worth keeping, and the map onto them. DeBERTa-v3-xsmall carries 70.8M parameters of which the 128,100-row embedding table at width 384 is about 49M -- two thirds of the model devoted to pieces this corpus never emits. The kept set is every id the corpus actually produces, plus every special token, plus a reserve of the most frequent ids that do NOT occur, so an unseen but plausible piece still has a row of its own rather than collapsing onto the unknown token. Returns the kept ids ASCENDING and a map from old id to its position in that list. Ascending order is not cosmetic: it keeps the control symbols at the front in their original relative order, so the padding id survives the remap as itself and a saved config's ``pad_token_id`` stays true.
Definition at line 443 of file encoder_classifier.py.
| list[dict[str, str]] encoder_classifier.read_jsonl | ( | Path | path | ) |
Read a JSON-lines file into a list of rows.
Definition at line 342 of file encoder_classifier.py.
| dict[str, object] encoder_classifier.relabel | ( | Path | bench_dir, |
| Path | out_dir ) |
Write the policy-change probe's relabelled copy of the corpus. The copy goes under *out_dir* -- never over a committed file -- and is a self-consistent bench corpus under the changed policy: both corpus splits and both case sets, each under its canonical name, so ``train --corpus-dir <out_dir>`` finds the held-out guard's case files and ``evaluate --cases <out_dir>/cases.json`` measures against the labels the CHANGED policy implies rather than the committed ones. A report beside them records the filter, the counts and the verdict movement.
Definition at line 1953 of file encoder_classifier.py.
| tuple[list[dict[str, str]], int] encoder_classifier.relabel_recursive_allow | ( | list[dict[str, str]] | rows, |
| object | classify ) |
Relabel every matched row by asking the gate about the stripped command. Returns the new rows and how many verdicts actually moved. A matched row whose verdict does not move is the interesting case and is kept, not dropped: ``ls -laR /etc`` is still a deny after ``-R`` becomes allowed, because the path is still outside the sandbox, and a blanket deny-to-allow rewrite would have taught the model the opposite.
Definition at line 1013 of file encoder_classifier.py.
| list[int] encoder_classifier.remap_lookup | ( | dict[int, int] | remap, |
| int | unknown_id, | ||
| int | size ) |
The old-id to new-id lookup as a plain list, unknown-filled. Every id the pruning dropped points at the unknown token, which is the pruning's whole contract for a piece the corpus never showed. Built here in pure Python rather than inside the tensor constructor so the contract is assertable in the project venv, where there is no torch.
Definition at line 560 of file encoder_classifier.py.
| object encoder_classifier.remap_table | ( | dict[int, int] | remap, |
| int | unknown_id, | ||
| int | size, | ||
| str | device ) |
:func:`remap_lookup` as a tensor on *device*, built once per run. A dict lookup per token would cost more than the forward pass it feeds, so plan 294 P2's map is materialised as a tensor indexed by the old id.
Definition at line 1267 of file encoder_classifier.py.
| str encoder_classifier.resolve_device | ( | str | requested = DEFAULT_DEVICE | ) |
The device a run will actually use, or a refusal saying why it cannot. ``auto`` is the default and reads the machine: CUDA when torch reports a usable device, the CPU otherwise. ``cuda`` is an ASSERTION rather than a preference -- if torch cannot see the card it raises, because the failure mode this guards against is a run that silently takes the CPU path and is read as a GPU measurement eighteen minutes later. ``cpu`` always succeeds and is how the CUDA venv reproduces a plan-290 CPU number.
Definition at line 1207 of file encoder_classifier.py.
| int encoder_classifier.resolve_max_length | ( | list[str] | commands, |
| object | tokenizer, | ||
| int | requested, | ||
| int | fallback = DEFAULT_MAX_LENGTH ) |
*requested* if it never truncates *commands*, else *fallback*. Plan 294 P3 shortens the sequence from 64 to 48 because the corpus's longest command is 44 tokens. That is a measured fact about a committed file rather than a property of the task, so it is re-measured here: a regenerated corpus carrying a longer command silently loses its tail otherwise, and a truncated command is a mislabelled training row.
Definition at line 505 of file encoder_classifier.py.
| dict[str, object] encoder_classifier.result_row | ( | dict[str, str] | case, |
| list[float] | logits, | ||
| float | temperature, | ||
| float | elapsed_ms, | ||
| str | decision = DEFAULT_DECISION, | ||
| list[list[float]] | None | costs = None ) |
One result row in the plan-288 schema, calibrated and timed. *decision* is plan 298 P2's rule, defaulting to the argmax every result file written before that plan carries. Note what it does NOT touch: ``probs`` and ``logits`` are the model's own output either way, so two result files differing only in the rule differ only in ``pred``, and the Brier score and the reliability bands the scorer computes are identical across the pair. That is the property that lets the table read the rule's effect off the agreement and direction columns alone.
Definition at line 1067 of file encoder_classifier.py.
| float encoder_classifier.score_full_validation | ( | Path | model_dir, |
| list[dict[str, str]] | rows, | ||
| object | tokenizer, | ||
| int | max_length, | ||
| int | batch, | ||
| str | device, | ||
| object | None | table ) |
The WHOLE validation split's cross-entropy at the saved best checkpoint. Plan 294 P3 validates each epoch on a 1,000-row subset, which is a different number from plan 290's 3,000-row one and cannot be compared with it. The best checkpoint is therefore reloaded once at the end and scored on the full split, so the run still produces the figure the previous plan's log can be read against.
Definition at line 1415 of file encoder_classifier.py.
| dict[str, float | int | bool] encoder_classifier.select_best_epoch | ( | list[tuple[float, float]] | pairs, |
| str | metric = DEFAULT_STOP_METRIC, | ||
| int | patience = 1, | ||
| float | min_delta = EARLY_STOP_MIN_DELTA ) |
Plan 300 P1: which epoch a run watching *metric* keeps, and when it stops. *pairs* is one ``(cross-entropy, expected cost)`` per epoch in epoch order, in :data:`STOP_METRIC_CHOICES` order, which is how *metric* selects the element to compare. Returns the 1-based best epoch (``0`` when no epoch ever improved), the value it scored, how many epochs the run would have got through, and whether patience ended it. This is the loop's rule written out as a function, and it earns its place twice. It is what W1 asserts on hand-written sequences, where the answer is known and a real run's is not. And it is what the training record calls on at the end with the OTHER metric, so the plan-300 table can say which epoch a run would have checkpointed had it watched the other column -- the one figure that separates "the stopping rule moved the model" from "the run happened to differ". Run over the epochs one run actually produced: a run watching the other metric might have stopped earlier and never seen the later pairs, so the alternate epoch is a reading of THIS run's curve, not a prediction of a run nobody made.
Definition at line 837 of file encoder_classifier.py.
| int encoder_classifier.slice_embeddings | ( | object | model, |
| list[int] | kept, | ||
| int | pad_row ) |
Replace the model's word embeddings with the *kept* rows only. Returns how many rows the table held before. The classification head is a pooler and a linear layer, so nothing is tied to this table and slicing it is the whole of the change; ``config.vocab_size`` moves with it so that a ``save_pretrained`` round trip rebuilds the same shape.
Definition at line 1310 of file encoder_classifier.py.
| float encoder_classifier.stopping_value | ( | dict[str, float | int] | record, |
| str | metric = DEFAULT_STOP_METRIC ) |
Plan 300 P1: the figure a run watching *metric* compares for one epoch. One function rather than a conditional expression in two places, because the training loop and :func:`select_best_epoch` have to agree about which number is being watched or the record's "the other metric would have chosen epoch N" line is measuring something no run would have done.
Definition at line 819 of file encoder_classifier.py.
| Path encoder_classifier.store_cached_ids | ( | Path | cache_root, |
| str | key, | ||
| list[list[int]] | rows ) |
Write a tokenised split into the cache and return where it landed.
Definition at line 549 of file encoder_classifier.py.
| str | None encoder_classifier.strip_recursive_option | ( | str | command | ) |
*command* with the recursive listing option removed, or ``None``. This is the whole of the policy change the probe measures: ``-R`` becomes an allowed option of the Tier-1 listing commands, and nothing else moves. Because the option carries no other meaning to the gate, the verdict of a command under the changed policy is the verdict the UNMODIFIED gate gives the same command with ``-R`` removed -- which is how :func:`relabel_recursive_allow` derives every new label without editing ``_argv_policy.py`` and without hand-writing a verdict. ``None`` when the command is not a listing command carrying an unquoted ``-R``: a quoted ``'-R'`` is data, ``--reverse`` is a different option, and ``python -R`` is a real option of a different program in a different tier.
Definition at line 981 of file encoder_classifier.py.
| float encoder_classifier.token_coverage | ( | list[str] | commands, |
| object | tokenizer, | ||
| set[int] | list[int] | kept, | ||
| int | max_length = DEFAULT_MAX_LENGTH ) |
The share of token OCCURRENCES in *commands* that a kept id covers. Occurrences rather than distinct ids: one rare piece appearing once in one hard case is not the same loss as a common piece appearing in a hundred, and the figure that says whether pruning hurt the case sets is the one weighted the way the case sets actually use the vocabulary.
Definition at line 485 of file encoder_classifier.py.
| list[list[int]] encoder_classifier.tokenise | ( | list[str] | commands, |
| object | tokenizer, | ||
| int | max_length ) |
The token ids of every command, unpadded, truncated at *max_length*.
Definition at line 420 of file encoder_classifier.py.
| tuple[str, ...] encoder_classifier.tokenise_keys | ( | object | tokenizer | ) |
Which tensors this tokenizer emits, asked of it rather than assumed.
Definition at line 428 of file encoder_classifier.py.
| dict[str, object] encoder_classifier.train | ( | TrainConfig | config | ) |
Fine-tune one backbone on the corpus, early-stopped on a watched metric. Writes the best epoch's weights, the tokenizer and a training record into ``config.out_dir``, and returns the record. The stopping rule is plan 290 P2's -- validation cross-entropy stops improving -- plus the budget guard P2's one-hour wall-time limit implies: when another epoch would not fit, the run stops at the end of the current one and the record says so, which is the opposite of quietly overrunning the budget. Plan 298 P3 adds ``config.cost_weight`` times :func:`expected_cost_term` to the per-batch loss. Two deliberate limits on it. It is off at ``0.0``, so an unflagged run is still plan 295's run. And the STOPPING rule was left on plain validation cross-entropy even when the term was on: the recipe was being held constant so that the objective was the only variable, and moving the early-stopping criterion at the same time would have changed two things and measured their sum. Plan 300 P1 supplies the second thing, as its own flag. ``config.stop_metric`` chooses which validation figure early stopping and checkpoint selection watch: ``"ce"`` is the rule above, unchanged and still the default, and ``"cost"`` watches the subset validation expected cost under the same ``config.alpha`` matrix -- the column a direction-weighted objective is trying to move, and the one plan 298 measured a run being checkpointed against. Both figures are computed every epoch whichever one stops the run, the epoch log carries both, and the record says which epoch the other metric would have kept. Nothing else moves: the full-split cross-entropy is still scored on the reloaded best checkpoint, and the temperature is still fitted by NLL afterwards, so the probabilities a reading consumes are calibrated the way every earlier tag's were.
Definition at line 1441 of file encoder_classifier.py.
| list[dict[str, str]] encoder_classifier.validation_subset | ( | list[dict[str, str]] | rows, |
| int | size, | ||
| int | seed = DEFAULT_SUBSET_SEED ) |
A fixed *size*-row sample of *rows*, the same one for every run. The sample is drawn from a pinned seed and then restored to corpus order, so the subset a run validates on depends on the seed alone and not on the order the shuffle happened to produce.
Definition at line 575 of file encoder_classifier.py.
| tuple[str, bool] encoder_classifier.verify_tokenizer_vocab | ( | Path | tokenizer_dir, |
| str | backbone, | ||
| Path | hf_home ) |
Check the tokenizer's vocabulary is the backbone's own, by digest. Returns the tokenizer's ``spm.model`` digest and whether a ``spm.model`` found in the backbone's cache directory matches it. A mismatch means the tokenizer is the wrong one for the weights and training would be nonsense, so the caller refuses rather than warning.
Definition at line 1136 of file encoder_classifier.py.
| str encoder_classifier.weights_digest | ( | Path | model_dir | ) |
The SHA-256 of the saved weights, whichever format they were saved in.
Definition at line 1126 of file encoder_classifier.py.
| None encoder_classifier.write_result_file | ( | Path | path, |
| str | model, | ||
| str | condition, | ||
| list[dict[str, object]] | rows, | ||
| dict[str, object] | provenance ) |
Write one result file the shared scorer can read.
Definition at line 1095 of file encoder_classifier.py.
| int encoder_classifier.BUCKET_BATCHES = 50 |
Definition at line 306 of file encoder_classifier.py.
| tuple encoder_classifier.CONFIDENT_THRESHOLDS = (0.9, 0.99) |
Definition at line 241 of file encoder_classifier.py.
| tuple encoder_classifier.DECISION_CHOICES = ("argmax", "min-cost") |
Definition at line 223 of file encoder_classifier.py.
| float encoder_classifier.DEFAULT_ALPHA = 1.0 |
Definition at line 213 of file encoder_classifier.py.
| int encoder_classifier.DEFAULT_BATCH = 32 |
Definition at line 250 of file encoder_classifier.py.
| float encoder_classifier.DEFAULT_BUDGET_SECONDS = 3600.0 |
Definition at line 299 of file encoder_classifier.py.
| encoder_classifier.DEFAULT_CACHE_ROOT = Path(r"G:\Temp\claude\g--PythonApps-option-c\harness\bench_cache") |
Definition at line 283 of file encoder_classifier.py.
| float encoder_classifier.DEFAULT_COST_WEIGHT = 0.0 |
Definition at line 218 of file encoder_classifier.py.
| str encoder_classifier.DEFAULT_DECISION = "argmax" |
Definition at line 224 of file encoder_classifier.py.
| str encoder_classifier.DEFAULT_DEVICE = "auto" |
Definition at line 265 of file encoder_classifier.py.
| encoder_classifier.DEFAULT_HF_HOME = Path(r"G:\Temp\claude\g--PythonApps-option-c\harness\hf_home") |
Definition at line 327 of file encoder_classifier.py.
| int encoder_classifier.DEFAULT_LEARNING_RATE = 3e-5 |
Definition at line 251 of file encoder_classifier.py.
| int encoder_classifier.DEFAULT_MAX_EPOCHS = 4 |
Definition at line 252 of file encoder_classifier.py.
| int encoder_classifier.DEFAULT_MAX_LENGTH = 64 |
Definition at line 249 of file encoder_classifier.py.
| encoder_classifier.DEFAULT_MODEL_ROOT |
Definition at line 311 of file encoder_classifier.py.
| int encoder_classifier.DEFAULT_SEED = 7 |
Definition at line 245 of file encoder_classifier.py.
| str encoder_classifier.DEFAULT_STOP_METRIC = "ce" |
Definition at line 234 of file encoder_classifier.py.
| int encoder_classifier.DEFAULT_SUBSET_ROWS = 1000 |
Definition at line 287 of file encoder_classifier.py.
| int encoder_classifier.DEFAULT_SUBSET_SEED = 294 |
Definition at line 288 of file encoder_classifier.py.
| int encoder_classifier.DEFAULT_THREADS = 8 |
Definition at line 258 of file encoder_classifier.py.
| encoder_classifier.DEFAULT_TOKENIZER_DIR |
Definition at line 318 of file encoder_classifier.py.
| int encoder_classifier.DEFAULT_VOCAB_RESERVE = 2000 |
Definition at line 278 of file encoder_classifier.py.
| float encoder_classifier.DEFAULT_WARMUP_SHARE = 0.1 |
Definition at line 253 of file encoder_classifier.py.
| tuple encoder_classifier.DEVICE_CHOICES = ("auto", "cpu", "cuda") |
Definition at line 264 of file encoder_classifier.py.
| int encoder_classifier.EARLY_STOP_MIN_DELTA = 1e-4 |
Definition at line 302 of file encoder_classifier.py.
| int encoder_classifier.FAST_MAX_LENGTH = 48 |
Definition at line 294 of file encoder_classifier.py.
| tuple encoder_classifier.LABELS = ("allow", "confirm", "deny") |
Definition at line 197 of file encoder_classifier.py.
| frozenset encoder_classifier.LISTING_PROGRAMS = frozenset({"ls", "dir"}) |
Definition at line 331 of file encoder_classifier.py.
| tuple encoder_classifier.PERMISSIVENESS = ("deny", "confirm", "allow") |
Definition at line 207 of file encoder_classifier.py.
| str encoder_classifier.RECURSIVE_OPTION = "R" |
Definition at line 334 of file encoder_classifier.py.
| tuple encoder_classifier.STOP_METRIC_CHOICES = ("ce", "cost") |
Definition at line 233 of file encoder_classifier.py.
| str encoder_classifier.VOCAB_PRUNE_FILE = "vocab_prune.json" |
Definition at line 270 of file encoder_classifier.py.