ruv commited on
Commit
afba0a4
·
0 Parent(s):

Banking77 bank: 87.0% with bge-small-en-v1.5 (probe head, 4000 iters, class-balanced)

Browse files
README.md ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: cc-by-4.0
3
+ library_name: ruvector
4
+ tags:
5
+ - text-classification
6
+ - intent-classification
7
+ - ruvector
8
+ - typesafe
9
+ datasets:
10
+ - PolyAI/banking77
11
+ ---
12
+
13
+ # ruvector-typesafe — Banking77 bank
14
+
15
+ A trained example bank for
16
+ [`@ruvector/typesafe`](https://www.npmjs.com/package/@ruvector/typesafe):
17
+ typed decisions over text, locally, with no API bill and no network in the
18
+ decision path. 77 fine-grained banking intents — the standard hard test for intent classification.
19
+
20
+ ```sh
21
+ npm install @ruvector/typesafe
22
+ curl -LO https://huggingface.co/ruvnet/ruvector-typesafe-banking77/resolve/main/bank.json
23
+ curl -LO https://huggingface.co/ruvnet/ruvector-typesafe-banking77/resolve/main/questions.json
24
+
25
+ echo "my card was declined at an atm" \
26
+ | npx typesafe decide --questions questions.json --bank bank.json --embedder onnx \
27
+ --engine-options '{"probeIterations":4000,"probeClassBalanced":true,"head":"probe"}'
28
+ ```
29
+
30
+ **The `--engine-options` are not optional.** A bank stores examples and their
31
+ frozen splits — never hyperparameters — and the head is refit from it on load.
32
+ Omit them and you refit with the library defaults (400 iterations, `head: auto`),
33
+ which is a different and materially worse model than the one measured below.
34
+ These are the exact options this bank was trained under.
35
+
36
+ ## What this artifact is
37
+
38
+ **Labelled examples with frozen split assignments — not weights.** The heads
39
+ (nearest-prototype, or a multinomial probe once a class has enough examples) and
40
+ the temperature calibration are refit from the bank when the engine loads it.
41
+
42
+ Two consequences worth knowing:
43
+
44
+ - The bank is **encoder-independent** — it holds text and content-hashed split
45
+ tags, nothing encoder-derived. For this dataset that is measured, not assumed: both bundled encoders exported byte-identical banks.
46
+ Only the accuracy below is encoder-specific.
47
+ - **The first decision after loading is slow.** `importBankJson` just admits the
48
+ examples; the head is fitted lazily on the first `decide`, and at
49
+ `probeIterations: 4000` over 9,993 examples and
50
+ 77 classes that fit is **minutes**, not milliseconds.
51
+ Every later call is the steady-state latency in the table. Import once, warm
52
+ it with one throwaway decision, and keep the engine alive — do not load a bank
53
+ per request.
54
+
55
+ A reloaded bank reproduces the trained engine's answers exactly — that
56
+ round-trip is asserted by `test/bank-roundtrip.test.mjs`, not assumed.
57
+
58
+ ## Accuracy
59
+
60
+ Held-out `test` split, 3,077 utterances,
61
+ 77 classes.
62
+
63
+ | encoder | accuracy | p50 latency | p95 latency |
64
+ |---|---|---|---|
65
+ | `all-MiniLM-L6-v2` | 76.7% | 15 ms | 16 ms |
66
+ | `bge-small-en-v1.5` | 87.0% | 6 ms | 10 ms |
67
+
68
+ Trained on 9,993 labelled examples
69
+ (`splitsHash: f51a48af2895c08d…`).
70
+
71
+ ## Training
72
+
73
+ ```sh
74
+ node scripts/typesafe-banks/build-bank.mjs --dataset banking77 --encoder all-MiniLM-L6-v2
75
+ ```
76
+
77
+ The probe head is full-batch gradient descent with a **fixed iteration count**,
78
+ and that count is the thing to tune when you add data: the default 400
79
+ iterations fits ~1k examples well and underfits ~10k badly. Raise it through
80
+ `EngineOptions`:
81
+
82
+ ```ts
83
+ createTypesafe({ embedder: …, engine: { probeIterations: 4000 } })
84
+ ```
85
+
86
+ ## Limitations
87
+
88
+ - English only; both bundled encoders are English sentence encoders.
89
+ - The label set is closed. New intents need new examples and a refit.
90
+ - Accuracy is reported on this dataset's own test split — it is not a claim
91
+ about your traffic.
92
+
93
+ ## Credit
94
+
95
+ Casanueva et al., *Efficient Intent Detection with Dual Sentence Encoders* (2020), arXiv:2003.04807. Dataset licence: CC-BY-4.0; this bank redistributes
96
+ the utterance text under that licence. The `@ruvector/typesafe` code is MIT.
bank.json ADDED
The diff for this file is too large to render. See raw diff
 
eval-all-MiniLM-L6-v2.json ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "engineOptions": {
3
+ "probeIterations": 4000,
4
+ "probeClassBalanced": true
5
+ },
6
+ "dataset": "banking77",
7
+ "encoder": "all-MiniLM-L6-v2",
8
+ "questionId": "intent",
9
+ "counts": {
10
+ "train": 9993,
11
+ "test": 3077,
12
+ "labels": 77
13
+ },
14
+ "splitsHash": "f51a48af2895c08ddafcb661530bd07b9ebad6b2d73f04358965a788acc72017",
15
+ "train": {
16
+ "examples": 9993,
17
+ "seconds": 328.446,
18
+ "report": {
19
+ "question": "intent",
20
+ "accepted": 9993,
21
+ "rejected": 0,
22
+ "head": "linear-probe",
23
+ "calibrated": true
24
+ }
25
+ },
26
+ "eval": {
27
+ "inScope": {
28
+ "n": 3077,
29
+ "accuracy": 0.7673058173545662,
30
+ "meanConfidence": 0.7062555000480987,
31
+ "meanAbstain": 1.0337861248008484e-8,
32
+ "p50Ms": 15,
33
+ "p95Ms": 16
34
+ },
35
+ "oos": null
36
+ },
37
+ "bank": {
38
+ "bytes": 2392682,
39
+ "sha256": "661afa0340ecde8ea9efeac69d130f4a33d36383502d3f2cd0244d3240ae4e75"
40
+ },
41
+ "binding": {
42
+ "backend": "native"
43
+ },
44
+ "node": "v22.23.2",
45
+ "finishedAt": "2026-09-22T20:31:18.802Z"
46
+ }
eval-bge-small-en-v1.5.json ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "engineOptions": {
3
+ "probeIterations": 4000,
4
+ "probeClassBalanced": true
5
+ },
6
+ "dataset": "banking77",
7
+ "encoder": "bge-small-en-v1.5",
8
+ "questionId": "intent",
9
+ "counts": {
10
+ "train": 9993,
11
+ "test": 3077,
12
+ "labels": 77
13
+ },
14
+ "splitsHash": "f51a48af2895c08ddafcb661530bd07b9ebad6b2d73f04358965a788acc72017",
15
+ "train": {
16
+ "examples": 9993,
17
+ "seconds": 428.759,
18
+ "report": {
19
+ "question": "intent",
20
+ "accepted": 9993,
21
+ "rejected": 0,
22
+ "head": "linear-probe",
23
+ "calibrated": true
24
+ }
25
+ },
26
+ "eval": {
27
+ "inScope": {
28
+ "n": 3077,
29
+ "accuracy": 0.8696782580435489,
30
+ "meanConfidence": 0.8615509237211584,
31
+ "meanAbstain": 6.934685016282097e-7,
32
+ "p50Ms": 6,
33
+ "p95Ms": 10
34
+ },
35
+ "oos": null
36
+ },
37
+ "bank": {
38
+ "bytes": 2392682,
39
+ "sha256": "661afa0340ecde8ea9efeac69d130f4a33d36383502d3f2cd0244d3240ae4e75"
40
+ },
41
+ "binding": {
42
+ "backend": "native"
43
+ },
44
+ "node": "v22.23.2",
45
+ "finishedAt": "2026-09-22T20:31:44.920Z"
46
+ }
questions.json ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "intent": {
3
+ "type": "choice",
4
+ "instructions": "Which banking intent does this message express",
5
+ "criteria": {
6
+ "card_arrival": "card arrival",
7
+ "card_linking": "card linking",
8
+ "exchange_rate": "exchange rate",
9
+ "card_payment_wrong_exchange_rate": "card payment wrong exchange rate",
10
+ "extra_charge_on_statement": "extra charge on statement",
11
+ "pending_cash_withdrawal": "pending cash withdrawal",
12
+ "fiat_currency_support": "fiat currency support",
13
+ "card_delivery_estimate": "card delivery estimate",
14
+ "automatic_top_up": "automatic top up",
15
+ "card_not_working": "card not working",
16
+ "exchange_via_app": "exchange via app",
17
+ "lost_or_stolen_card": "lost or stolen card",
18
+ "age_limit": "age limit",
19
+ "pin_blocked": "pin blocked",
20
+ "contactless_not_working": "contactless not working",
21
+ "top_up_by_bank_transfer_charge": "top up by bank transfer charge",
22
+ "pending_top_up": "pending top up",
23
+ "cancel_transfer": "cancel transfer",
24
+ "top_up_limits": "top up limits",
25
+ "wrong_amount_of_cash_received": "wrong amount of cash received",
26
+ "card_payment_fee_charged": "card payment fee charged",
27
+ "transfer_not_received_by_recipient": "transfer not received by recipient",
28
+ "supported_cards_and_currencies": "supported cards and currencies",
29
+ "getting_virtual_card": "getting virtual card",
30
+ "card_acceptance": "card acceptance",
31
+ "top_up_reverted": "top up reverted",
32
+ "balance_not_updated_after_cheque_or_cash_deposit": "balance not updated after cheque or cash deposit",
33
+ "card_payment_not_recognised": "card payment not recognised",
34
+ "edit_personal_details": "edit personal details",
35
+ "why_verify_identity": "why verify identity",
36
+ "unable_to_verify_identity": "unable to verify identity",
37
+ "get_physical_card": "get physical card",
38
+ "visa_or_mastercard": "visa or mastercard",
39
+ "topping_up_by_card": "topping up by card",
40
+ "disposable_card_limits": "disposable card limits",
41
+ "compromised_card": "compromised card",
42
+ "atm_support": "atm support",
43
+ "direct_debit_payment_not_recognised": "direct debit payment not recognised",
44
+ "passcode_forgotten": "passcode forgotten",
45
+ "declined_cash_withdrawal": "declined cash withdrawal",
46
+ "pending_card_payment": "pending card payment",
47
+ "lost_or_stolen_phone": "lost or stolen phone",
48
+ "request_refund": "request refund",
49
+ "declined_transfer": "declined transfer",
50
+ "Refund_not_showing_up": "Refund not showing up",
51
+ "declined_card_payment": "declined card payment",
52
+ "pending_transfer": "pending transfer",
53
+ "terminate_account": "terminate account",
54
+ "card_swallowed": "card swallowed",
55
+ "transaction_charged_twice": "transaction charged twice",
56
+ "verify_source_of_funds": "verify source of funds",
57
+ "transfer_timing": "transfer timing",
58
+ "reverted_card_payment?": "reverted card payment?",
59
+ "change_pin": "change pin",
60
+ "beneficiary_not_allowed": "beneficiary not allowed",
61
+ "transfer_fee_charged": "transfer fee charged",
62
+ "receiving_money": "receiving money",
63
+ "failed_transfer": "failed transfer",
64
+ "transfer_into_account": "transfer into account",
65
+ "verify_top_up": "verify top up",
66
+ "getting_spare_card": "getting spare card",
67
+ "top_up_by_cash_or_cheque": "top up by cash or cheque",
68
+ "order_physical_card": "order physical card",
69
+ "virtual_card_not_working": "virtual card not working",
70
+ "wrong_exchange_rate_for_cash_withdrawal": "wrong exchange rate for cash withdrawal",
71
+ "get_disposable_virtual_card": "get disposable virtual card",
72
+ "top_up_failed": "top up failed",
73
+ "balance_not_updated_after_bank_transfer": "balance not updated after bank transfer",
74
+ "cash_withdrawal_not_recognised": "cash withdrawal not recognised",
75
+ "exchange_charge": "exchange charge",
76
+ "top_up_by_card_charge": "top up by card charge",
77
+ "activate_my_card": "activate my card",
78
+ "cash_withdrawal_charge": "cash withdrawal charge",
79
+ "card_about_to_expire": "card about to expire",
80
+ "apple_pay_or_google_pay": "apple pay or google pay",
81
+ "verify_my_identity": "verify my identity",
82
+ "country_support": "country support"
83
+ }
84
+ }
85
+ }