yuvstk commited on
Commit
ceb3a09
·
verified ·
1 Parent(s): 2da4585

BERT fine-tuned on BLiMP + CoLA for grammar error detection

Browse files
README.md ADDED
@@ -0,0 +1,188 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ language:
4
+ - en
5
+ base_model: google-bert/bert-base-uncased
6
+ pipeline_tag: text-classification
7
+ tags:
8
+ - grammatical-error-detection
9
+ - linguistic-acceptability
10
+ - bert
11
+ - blimp
12
+ - cola
13
+ datasets:
14
+ - nyu-mll/blimp
15
+ - nyu-mll/glue
16
+ metrics:
17
+ - accuracy
18
+ - matthews_correlation
19
+ - f1
20
+ widget:
21
+ - text: "Katherine can't help himself."
22
+ example_title: "Reflexive agreement error"
23
+ - text: "The professor talked us."
24
+ example_title: "Verb argument error"
25
+ - text: "She has been working here since 2019."
26
+ example_title: "Correct sentence"
27
+ - text: "They drank the pub."
28
+ example_title: "Selectional restriction error"
29
+ ---
30
+
31
+ # BERT for Grammatical Error Detection (BLiMP + CoLA)
32
+
33
+ `bert-base-uncased` fine-tuned for **binary grammatical error detection**: given
34
+ one English sentence, decide whether it contains a grammatical error.
35
+
36
+ | label | meaning |
37
+ |-------|---------|
38
+ | `0` | grammatical |
39
+ | `1` | ungrammatical |
40
+
41
+ Note the orientation: **1 means "has an error."** This is the inverse of CoLA's
42
+ native convention (where 1 = acceptable), and the training labels were flipped
43
+ accordingly.
44
+
45
+ ## Usage
46
+
47
+ ```python
48
+ from transformers import AutoModelForSequenceClassification, AutoTokenizer
49
+ import torch
50
+
51
+ model_id = "YOUR-USERNAME/bert-grammar-error-detection"
52
+ tokenizer = AutoTokenizer.from_pretrained(model_id)
53
+ model = AutoModelForSequenceClassification.from_pretrained(model_id)
54
+
55
+ sentences = ["Katherine can't help himself.", "She went home early."]
56
+ inputs = tokenizer(sentences, padding=True, truncation=True,
57
+ max_length=64, return_tensors="pt")
58
+
59
+ with torch.no_grad():
60
+ probs = torch.softmax(model(**inputs).logits, dim=-1)
61
+
62
+ for sentence, prob in zip(sentences, probs):
63
+ label = int(prob.argmax())
64
+ print(f"{'UNGRAMMATICAL' if label else 'GRAMMATICAL'} "
65
+ f"({prob[label]:.1%}) — {sentence}")
66
+ ```
67
+
68
+ Or with a pipeline (`LABEL_1` = ungrammatical):
69
+
70
+ ```python
71
+ from transformers import pipeline
72
+ clf = pipeline("text-classification", model=model_id)
73
+ clf("The professor talked us.")
74
+ ```
75
+
76
+ ## Training data
77
+
78
+ Two sources merged into a single 4000 + 9594 sentence corpus:
79
+
80
+ | source | rows (train) | what it contributes |
81
+ |---|---|---|
82
+ | [BLiMP](https://huggingface.co/datasets/nyu-mll/blimp) — `anaphor_gender_agreement` + `anaphor_number_agreement` | 3200 | synthetic minimal pairs; reflexive pronoun agreement; exactly 50/50 balanced |
83
+ | [CoLA](https://huggingface.co/datasets/nyu-mll/glue) (GLUE) | 7695 | real linguistics-literature sentences; many error types; ~70/30 imbalanced |
84
+
85
+ Splitting differs per source, because the sources need different treatment:
86
+
87
+ - **BLiMP is split by `pair_id`**, never by row. The two sentences of a minimal
88
+ pair differ by exactly one word, so a row-level split would put a
89
+ near-duplicate of a test sentence into training.
90
+ - **CoLA is split by stratified rows.** GLUE's `test` split is unlabelled
91
+ (all `-1`), so GLUE `validation` is used as the test set and the validation
92
+ set is carved out of GLUE `train`.
93
+
94
+ Total: 10,895 train / 1,256 validation / 1,443 test.
95
+
96
+ ## Results
97
+
98
+ Evaluated separately per source, because the two halves differ enormously in
99
+ difficulty — a single pooled number would mostly reflect the mixture ratio.
100
+
101
+ | test set | n | accuracy | precision | recall | F1 | MCC |
102
+ |---|---|---|---|---|---|---|
103
+ | **BLiMP** | 400 | **1.000** | 1.000 | 1.000 | 1.000 | 1.000 |
104
+ | **CoLA** | 1043 | **0.837** | 0.833 | 0.590 | 0.691 | **0.601** |
105
+ | pooled | 1443 | 0.882 | 0.911 | 0.747 | 0.821 | 0.743 |
106
+
107
+ CoLA MCC of 0.601 is in the normal published range for BERT-base (~0.55–0.60).
108
+
109
+ **Merging helped.** The same model trained on CoLA alone reached MCC 0.576;
110
+ adding BLiMP raised it to 0.601 while BLiMP itself stayed at 1.000 — positive
111
+ transfer, not interference.
112
+
113
+ ### Baselines, for scale
114
+
115
+ | method | accuracy | MCC |
116
+ |---|---|---|
117
+ | majority class (CoLA) | 0.691 | 0.000 |
118
+ | bag-of-words logistic regression (CoLA) | 0.718 | 0.092 |
119
+ | pronoun-only rule (BLiMP) | 0.688 | — |
120
+ | **zero-shot `bert-base-uncased`, no fine-tuning** (BLiMP) | **0.973** | — |
121
+
122
+ That last row is worth dwelling on: masking the pronoun and asking the *raw*
123
+ pretrained model which word it prefers already solves BLiMP at 97.3%.
124
+ Fine-tuning on BLiMP mostly attaches an output head to knowledge the model
125
+ already had. CoLA is where fine-tuning does real work.
126
+
127
+ ## Training procedure
128
+
129
+ | hyperparameter | value |
130
+ |---|---|
131
+ | base model | `bert-base-uncased` (109.5M parameters) |
132
+ | epochs | 4 |
133
+ | learning rate | 2e-5 |
134
+ | warmup ratio | 0.06 |
135
+ | batch size | 32 |
136
+ | max sequence length | 64 |
137
+ | weight decay | 0.01 |
138
+ | optimizer | AdamW |
139
+ | seed | 42 |
140
+ | best checkpoint by | validation **MCC** (not accuracy — the data is imbalanced) |
141
+
142
+ Per-epoch validation MCC: 0.727 → 0.735 → 0.771 → **0.775**.
143
+
144
+ ## Limitations
145
+
146
+ Measured on 40 hand-written test sentences (33/40 correct, 82.5%), the failure
147
+ modes are systematic rather than random:
148
+
149
+ 1. **Blind to omissions.** *"Although it was raining, we decided go for a
150
+ walk."* is judged correct. Both training sets create errors by
151
+ **substituting** a word, never deleting one, so the model never learned to
152
+ notice something missing.
153
+ 2. **Over-flags correct sentences.** *"He is an honest man."* and *"She arrived
154
+ at the airport."* are both flagged as errors. Recall on CoLA is 0.590 while
155
+ precision is 0.833 — it misses more errors than it invents, but its false
156
+ alarms land on perfectly ordinary sentences.
157
+ 3. **Untrained phenomena fail.** Determiner–noun agreement (*"Raymond is
158
+ selling this sketch."*) is flagged as an error. On BLiMP's
159
+ `determiner_noun_agreement_1` — a phenomenon never seen in training — the
160
+ model scores 0.675, far below its 1.000 on trained phenomena.
161
+ 4. **Confidence is not reliability.** Several wrong predictions are made at
162
+ 100% confidence. Do not treat the softmax score as a calibrated probability.
163
+ 5. **English only**, and short sentences only — training data averaged well
164
+ under 20 words.
165
+
166
+ This is a coursework model built to study what fine-tuning contributes, not a
167
+ production grammar checker.
168
+
169
+ ## Intended use
170
+
171
+ Educational and research use: demonstrating grammatical acceptability
172
+ classification, and comparing fine-tuned versus zero-shot versus from-scratch
173
+ transformers. Not suitable for grading student writing, automated proofreading,
174
+ or any decision affecting a person.
175
+
176
+ ## Citation
177
+
178
+ ```bibtex
179
+ @misc{bert-grammar-error-detection,
180
+ title = {BERT for Grammatical Error Detection (BLiMP + CoLA)},
181
+ author = {YOUR NAME},
182
+ year = {2026},
183
+ url = {https://huggingface.co/YOUR-USERNAME/bert-grammar-error-detection}
184
+ }
185
+ ```
186
+
187
+ Datasets: BLiMP (Warstadt et al., TACL 2020) and CoLA (Warstadt et al., TACL
188
+ 2019).
config.json ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "BertForSequenceClassification"
4
+ ],
5
+ "attention_probs_dropout_prob": 0.1,
6
+ "classifier_dropout": null,
7
+ "dtype": "float32",
8
+ "gradient_checkpointing": false,
9
+ "hidden_act": "gelu",
10
+ "hidden_dropout_prob": 0.1,
11
+ "hidden_size": 768,
12
+ "initializer_range": 0.02,
13
+ "intermediate_size": 3072,
14
+ "layer_norm_eps": 1e-12,
15
+ "max_position_embeddings": 512,
16
+ "model_type": "bert",
17
+ "num_attention_heads": 12,
18
+ "num_hidden_layers": 12,
19
+ "pad_token_id": 0,
20
+ "position_embedding_type": "absolute",
21
+ "transformers_version": "4.57.1",
22
+ "type_vocab_size": 2,
23
+ "use_cache": true,
24
+ "vocab_size": 30522
25
+ }
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b9d1790417a080510b11e2e79c59935be66ec4ff4c40467872b7c2fad8c0508d
3
+ size 437958648
special_tokens_map.json ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ {
2
+ "cls_token": "[CLS]",
3
+ "mask_token": "[MASK]",
4
+ "pad_token": "[PAD]",
5
+ "sep_token": "[SEP]",
6
+ "unk_token": "[UNK]"
7
+ }
tokenizer.json ADDED
The diff for this file is too large to render. See raw diff
 
tokenizer_config.json ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "added_tokens_decoder": {
3
+ "0": {
4
+ "content": "[PAD]",
5
+ "lstrip": false,
6
+ "normalized": false,
7
+ "rstrip": false,
8
+ "single_word": false,
9
+ "special": true
10
+ },
11
+ "100": {
12
+ "content": "[UNK]",
13
+ "lstrip": false,
14
+ "normalized": false,
15
+ "rstrip": false,
16
+ "single_word": false,
17
+ "special": true
18
+ },
19
+ "101": {
20
+ "content": "[CLS]",
21
+ "lstrip": false,
22
+ "normalized": false,
23
+ "rstrip": false,
24
+ "single_word": false,
25
+ "special": true
26
+ },
27
+ "102": {
28
+ "content": "[SEP]",
29
+ "lstrip": false,
30
+ "normalized": false,
31
+ "rstrip": false,
32
+ "single_word": false,
33
+ "special": true
34
+ },
35
+ "103": {
36
+ "content": "[MASK]",
37
+ "lstrip": false,
38
+ "normalized": false,
39
+ "rstrip": false,
40
+ "single_word": false,
41
+ "special": true
42
+ }
43
+ },
44
+ "clean_up_tokenization_spaces": false,
45
+ "cls_token": "[CLS]",
46
+ "do_lower_case": true,
47
+ "extra_special_tokens": {},
48
+ "mask_token": "[MASK]",
49
+ "model_max_length": 512,
50
+ "pad_token": "[PAD]",
51
+ "sep_token": "[SEP]",
52
+ "strip_accents": null,
53
+ "tokenize_chinese_chars": true,
54
+ "tokenizer_class": "BertTokenizer",
55
+ "unk_token": "[UNK]"
56
+ }
train_meta.json ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "run_name": "merged-bert-base",
3
+ "dataset": "merged (blimp + cola)",
4
+ "model_name": "bert-base-uncased",
5
+ "from_scratch": false,
6
+ "config_size": "base",
7
+ "n_params": 109483778,
8
+ "epochs": 4,
9
+ "learning_rate": 2e-05,
10
+ "warmup_ratio": 0.06,
11
+ "batch_size": 32,
12
+ "class_weights": false,
13
+ "seed": 42,
14
+ "train_rows": 10895,
15
+ "train_rows_by_source": {
16
+ "cola": 7695,
17
+ "blimp": 3200
18
+ }
19
+ }
training_args.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4118c1cbdc5de40842c4ebfee1932c47b01a662a02556f1415d8f96bcd2ac3a2
3
+ size 5841
vocab.txt ADDED
The diff for this file is too large to render. See raw diff