File size: 8,967 Bytes
905ca6e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
---
license: apache-2.0
language: en
library_name: pytorch
base_model: allenai/scibert_scivocab_cased
tags: [information-extraction, relation-extraction, named-entity-recognition, scientific-text, lab-protocols]
---

# Model card: ProtocolLedger span-relation extractor

| | |
|---|---|
| Served model | `scibert-uniform-negatives-s21-7dc7a1893e` |
| Architecture | SpERT-style span and directed-relation classifier over a shared encoder (independent implementation of Eberts and Ulges, arXiv:1909.07755) |
| Encoder | `allenai/scibert_scivocab_cased` at revision `ddf0be025f8e432a1870e34811997ba6725bf04a` (Apache-2.0), fine-tuned |
| Training data | WNUT-2020 Wet Lab Protocols, cleaned **train** split (314 protocols); see the [dataset card](https://github.com/professor3333/ProtocolLedger/blob/main/docs/dataset_card.md) |
| Selection | dev end-to-end relation micro-F1; configuration chosen on a 3-seed mean, then the **median** seed served |
| Status | Evaluated once on the locked test split (2026-10-11) after targets were frozen |

## Intended use

The model extracts actions, materials, quantities, equipment and locations, conditions,
and the directed relations between them, from English wet-lab protocol text. Its output
is a set of **suggested** preparation-sheet entries, each linked to its source
characters, which a reviewer confirms or corrects in the ProtocolLedger interface before
export.

## Not for

- Judging whether a protocol is scientifically complete, correct or safe.
- Executing or automating protocols from unreviewed output.
- Using unreviewed results as final records.
- Filling in implicit arguments, or treating "not explicitly stated" as "not required".
  The system never does this; downstream users should not either.
- Entity or relation types outside the 18-entity and 14-relation schema.
- Non-English text, or PDF / OCR input.
- Other domains or protocol sources without local validation.

## Provenance

- **Encoder.** SciBERT cased, pinned by Hugging Face commit. Case preservation is forced
  and checked at load time, because the cased checkpoint ships no tokenizer config and
  would otherwise lowercase `mM` to `mm`.
- **Encoder view.** Characters the vocabulary cannot represent (for example the micro
  sign in `µL`, which SciBERT maps to `[UNK]`) are substituted only in the text handed to
  the encoder. Offsets always refer to the original text.
- **Heads.**
  - A span is the max-pool of its words plus a width embedding.
  - Entity logits come from the span concatenated with the CLS vector.
  - Relation logits use both spans, the max-pooled words between them, both entity types
    and a signed distance bucket.
  - Relations are multilabel (sigmoid).
- **Training.**
  - AdamW: encoder learning rate 2e-5, heads 1e-3, 10% warmup with linear decay.
  - Batch size 16, at most 10 epochs, patience 3, mixed precision on one Tesla T4 (Kaggle).
  - About 100 sampled negative spans and pairs per step, sampled uniformly.
  - Seed 21; best epoch 9.
- **Reproducibility.** Code commit, split-manifest hash, encoder revision, checkpoint
  hash and the selection-report hash are all recorded in the bundle's `bundle.json`. The
  bundle was rebuilt on CPU and reproduces the GPU dev scores exactly (entity F1 0.8003,
  relation F1 0.6389).

## Training data and splits

The released WNUT-2020 splits were the starting point, with these changes:
- **Deduplication.** Exact and near-duplicate copies and revisions were removed by
  document, always keeping the copy in the most protected split.
- **Correction ledger.** 291 whitespace-trimming corrections were applied identically to
  every split.
- **Locked test.** `test_data_2020` is never used for any decision.

Cleaned sizes: train 314, dev 112, test 111 protocols.

## Metrics (dev, 112 protocols)

| Metric | Selected configuration (3 seeds) | Served seed | CRF + logistic regression |
|---|---:|---:|---:|
| Strict entity micro-F1 | 0.800 | 0.800 | 0.756 |
| **End-to-end relation micro-F1** (primary) | **0.640 ± 0.004** | 0.639 [0.614, 0.665] | 0.582 |
| Relation F1 given gold entities | 0.826 | 0.830 | 0.812 |
| Quantity attachment accuracy | 0.758 | 0.757 | 0.708 |

**Proposed targets** (not yet frozen):
- Entity F1 ≥ 0.75: met.
- At least 5 points above the traditional baseline: met (+5.8).
- **End-to-end relation F1 ≥ 0.65: not met** (0.640).

**Confidence** (served seed, joint score P(relation) × P(head) × P(tail)):
- **Precision.** 0.920 at 30% coverage. 95% precision is reachable only at 18.7%
  coverage, so the target of 95% precision at ≥ 30% coverage is not met.
- **Calibration.** Isotonic calibration, cross-fitted by document on dev, reduces the
  expected calibration error from 0.183 to 0.009.

**Error profile** (served seed):
- **Correct:** 62.7% of gold relations.
- **Endpoint entity wrong:** 27.7%.
- **Missed:** 4.8%.
- **Genuine attachment errors:** 2.9%.
- **Wrong label:** 1.8%.

**Slices:**

| Slice | Served seed | CRF + logistic regression |
|---|---:|---:|
| Reagent recall, names unseen in train | 0.658 | 0.570 |
| Reagent recall, names seen in train | 0.903 | 0.873 |
| Relation recall, documents without a template sibling in train | 0.613 | 0.524 |
| Relation recall, documents with a template sibling in train | 0.740 | 0.719 |

**Reference point.** Duplicate copies of the same protocol in the corpus agree with each
other at entity F1 0.863 and relation F1 0.711.

## Locked test results (evaluated once)

| Metric (cleaned `test_data_2020`, 111 protocols) | SciBERT (served) | CRF + logistic regression |
|---|---:|---:|
| Strict entity micro-F1 | 0.766 [0.748, 0.784] | 0.703 |
| **End-to-end relation micro-F1** | **0.580** [0.552, 0.606] | 0.504 |
| Relation F1 given gold entities | 0.787 | 0.768 |
| Quantity attachment accuracy | 0.699 | 0.629 |
| Reagent recall, names unseen in train | 0.762 | 0.631 |
| Precision at 30% coverage (joint score) | 0.905 | — |
| Coverage at 95% precision | 13.4% | — |
| ECE raw / with the dev-fitted calibrator | 0.172 / 0.021 | — |

Frozen targets on test:
- **Met:** entity F1 ≥ 0.75, at least 5 points above the traditional baseline (+7.6),
  and robustness (−0.01).
- **Not met:** end-to-end relation F1 ≥ 0.65, and 95% precision at ≥ 30% coverage.

Test is harder than dev for every system. Full report:
[reports/test/locked-test.md](https://github.com/professor3333/ProtocolLedger/blob/main/reports/test/locked-test.md).

## Inference performance

Measured on an Apple M1 (8 GB) with 4 inference threads: 200 sequential requests of dev
protocols up to 500 words, each runtime in a fresh process
([report](https://github.com/professor3333/ProtocolLedger/blob/main/reports/performance/scibert-uniform-negatives-s21-7dc7a1893e-runtimes.md)).

| Runtime | Relation F1 | p50 | p95 | Peak RSS |
|---|---:|---:|---:|---:|
| **PyTorch FP32 (served)** | 0.6389 | 0.48 s | **2.07 s** | 938 MiB |
| ONNX Runtime FP32 | 0.6389 | 2.29 s | 6.94 s | 1,058 MiB |
| ONNX Runtime INT8 (dynamic) | 0.6361 | 0.93 s | 2.90 s | 918 MiB |

On this ARM machine PyTorch FP32 is the fastest runtime and loses no accuracy, so it is
served. INT8 stays within the 1-point accuracy gate but is slower here. Re-measure on the
deployment host (likely x86) with `scripts/optimize_bundle.py` before changing runtimes.

## Known failure modes

- **Entity errors cascade into relations.** Most relation errors come from a wrong or
  missed endpoint entity.
- **Open-vocabulary types are weakest.** Modifier, Method, Device and Generic-Measure
  score lowest.
- **Unseen reagent names** are found less often than seen ones.
- **Rare relations** are unreliable: Commands (37 dev instances), Of-Type (26) and
  Product.
- **Implicit references** such as "it", and cross-step coreference, are not resolved.
- **Negation, alternatives and conditional instructions** are not modelled.
- **Text glued together by table flattening** (for example `100 µlof`) cannot be
  segmented.
- **Template overlap.** Protocols that share a vendor template with training data score
  higher than the rest.

## Release process

- **Selection.** Thresholds and the configuration are chosen on dev only, never on test.
- **Bundle.** Weights are stored as safetensors (no pickle). The encoder config and
  tokenizer are included, and every file is hash-verified on load, so serving needs no
  network access.
- **Release.** A release pins the image digest together with the bundle hash.
  `scripts/release.py` smoke-tests each activation and restores the previous image and
  bundle together if it fails.

## Loading

This repository is a ProtocolLedger bundle (`bundle.json` lists every file with its SHA-256). Load it with the project's verified loader:

```python
from pathlib import Path
from protocolledger.serve.bundle import load_bundle

bundle = load_bundle(Path('scibert-uniform-negatives-s21-7dc7a1893e'))
entities, relations = bundle.extractor.extract('Add 5 mL of water to the tube.')
```