yeomtong commited on
Commit
0c0db8b
Β·
verified Β·
1 Parent(s): b14c6f6

Upload 3 files

Browse files
Files changed (3) hide show
  1. SRL_preprocessing.py +209 -0
  2. trainer.py +288 -0
  3. utils.py +10 -0
SRL_preprocessing.py ADDED
@@ -0,0 +1,209 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse, json, torch
2
+ from dataclasses import dataclass
3
+ from typing import List, Dict, Optional
4
+ from transformers import AutoTokenizer
5
+ from torch.utils.data import Dataset
6
+
7
+ # ==============================================================
8
+ # 1. Data structure
9
+ # ==============================================================
10
+ @dataclass
11
+ class SRLSample:
12
+ words: List[str]
13
+ predicate_word_idx: int
14
+ labels: List[str]
15
+ predicate_form: Optional[str] = None
16
+
17
+
18
+ # ==============================================================
19
+ # 2. Bracket β†’ BIO conversion (unchanged)
20
+ # ==============================================================
21
+ def _bio_from_brackets(tags):
22
+ bio, stack = [], []
23
+ for t in tags:
24
+ if "(V*" in t:
25
+ bio.append("B-V")
26
+ continue
27
+ opens = []
28
+ i = 0
29
+ while True:
30
+ s = t.find("(", i)
31
+ if s == -1: break
32
+ e = t.find("*", s)
33
+ if e == -1: break
34
+ role = t[s+1:e]
35
+ opens.append(role)
36
+ i = e + 1
37
+ closes = t.count(")")
38
+ if opens:
39
+ bio.append(f"B-{opens[0]}")
40
+ for r in opens: stack.append(r)
41
+ elif stack:
42
+ bio.append(f"I-{stack[-1]}")
43
+ else:
44
+ bio.append("O")
45
+ for _ in range(closes):
46
+ if stack: stack.pop()
47
+ return bio
48
+
49
+
50
+ def _read_sentences(path):
51
+ sent = []
52
+ with open(path, "r", encoding="utf-8", errors="replace") as f:
53
+ for line in f:
54
+ line = line.rstrip("\n")
55
+ if not line:
56
+ if sent: yield sent; sent = []
57
+ continue
58
+ cols = line.split()
59
+ if cols: sent.append(cols)
60
+ if sent: yield sent
61
+
62
+
63
+ # ==============================================================
64
+ # 3. CoNLL β†’ SRLSample objects (in-memory)
65
+ # ==============================================================
66
+ def load_conll_samples(in_path, word_col_idx=3, srl_first_col_idx=11):
67
+ """
68
+ Reads .gold_conll file and returns list[SRLSample],
69
+ one per predicate column.
70
+ """
71
+ samples = []
72
+ for sent in _read_sentences(in_path):
73
+ words = [row[word_col_idx] for row in sent]
74
+ max_cols = max(len(row) for row in sent)
75
+ for srl_col in range(srl_first_col_idx, max_cols):
76
+ tags = [row[srl_col] if srl_col < len(row) else "*" for row in sent]
77
+ try:
78
+ pred_idx = next(i for i, t in enumerate(tags) if "(V*" in t)
79
+ except StopIteration:
80
+ continue
81
+ labels = _bio_from_brackets(tags)
82
+ predicate_form = words[pred_idx]
83
+ samples.append(SRLSample(words, pred_idx, labels, predicate_form))
84
+ print(f"[SRL_preprocessing] Loaded {len(samples)} predicate instances from {in_path}")
85
+ return samples
86
+
87
+
88
+ # ==============================================================
89
+ # 4. Dataset + Collate (same as yours, lightly cleaned)
90
+ # ==============================================================
91
+ class SRLDataset(Dataset):
92
+ def __init__(self, samples: List[SRLSample], tokenizer: AutoTokenizer,
93
+ label2id: Dict[str, int], max_length: int = 256, debug_print=False):
94
+ self.samples = samples
95
+ self.tokenizer = tokenizer
96
+ self.label2id = label2id
97
+ self.id2label = {v: k for k, v in label2id.items()}
98
+ self.max_length = max_length
99
+ self.debug_print = debug_print
100
+
101
+ def __len__(self): return len(self.samples)
102
+
103
+ def _tokenize_sentence(self, words):
104
+ return self.tokenizer(words, is_split_into_words=True,
105
+ add_special_tokens=False, return_attention_mask=False,
106
+ return_token_type_ids=False)
107
+
108
+ def _tokenize_predicate(self, form):
109
+ return self.tokenizer(form, add_special_tokens=False,
110
+ return_attention_mask=False,
111
+ return_token_type_ids=False)
112
+
113
+ def __getitem__(self, idx):
114
+ instance = self.samples[idx]
115
+ words = instance.words
116
+ n_words = len(words)
117
+ pred_form = instance.predicate_form or words[instance.predicate_word_idx]
118
+
119
+ enc_sent = self._tokenize_sentence(words)
120
+ enc_pred = self._tokenize_predicate(pred_form)
121
+ sent_wp_ids = enc_sent["input_ids"]
122
+ pred_wp_ids = enc_pred["input_ids"]
123
+
124
+ input_ids = [self.tokenizer.cls_token_id] + sent_wp_ids + [self.tokenizer.sep_token_id] \
125
+ + pred_wp_ids + [self.tokenizer.sep_token_id]
126
+ ttids = [0] * (1 + len(sent_wp_ids) + 1) + [1] * (len(pred_wp_ids) + 1)
127
+
128
+ tmp = self.tokenizer(words, is_split_into_words=True)
129
+ word_ids = tmp.word_ids()
130
+ first_pos_by_wid = {}
131
+ for pos, wid in enumerate(word_ids):
132
+ if wid is not None and wid not in first_pos_by_wid:
133
+ first_pos_by_wid[wid] = pos
134
+ word_first_wp_fullidx = [first_pos_by_wid[w] for w in range(n_words)]
135
+
136
+ label_ids = [self.label2id[l] for l in instance.labels]
137
+ indicator = [0]*n_words; indicator[instance.predicate_word_idx] = 1
138
+ attention_mask = [1]*len(input_ids)
139
+
140
+ if len(input_ids) > self.max_length:
141
+ max_pos = self.max_length-1
142
+ input_ids = input_ids[:self.max_length]
143
+ ttids = ttids[:self.max_length]
144
+ attention_mask = attention_mask[:self.max_length]
145
+ word_first_wp_fullidx = [min(p, max_pos) for p in word_first_wp_fullidx]
146
+
147
+ return {
148
+ "input_ids": torch.tensor(input_ids, dtype=torch.long),
149
+ "token_type_ids": torch.tensor(ttids, dtype=torch.long),
150
+ "attention_mask": torch.tensor(attention_mask, dtype=torch.long),
151
+ "word_first_wp_fullidx": torch.tensor(word_first_wp_fullidx, dtype=torch.long),
152
+ "labels": torch.tensor(label_ids, dtype=torch.long),
153
+ "indicator": torch.tensor(indicator, dtype=torch.long),
154
+ "sent_len": torch.tensor(len(words), dtype=torch.long),
155
+ "pred_word_idx": torch.tensor(instance.predicate_word_idx, dtype=torch.long)
156
+ }
157
+
158
+
159
+ def srl_collate(batch: List[Dict], pad_token_id: int, pad_label_id: int = -100):
160
+ B = len(batch)
161
+ max_L = max(item["input_ids"].size(0) for item in batch)
162
+ input_ids = torch.full((B, max_L), pad_token_id, dtype=torch.long)
163
+ token_type_ids = torch.zeros((B, max_L), dtype=torch.long)
164
+ attention_mask = torch.zeros((B, max_L), dtype=torch.long)
165
+ max_n = max(int(item["sent_len"]) for item in batch)
166
+ word_first_wp_fullidx = torch.full((B, max_n), -1, dtype=torch.long)
167
+ labels = torch.full((B, max_n), pad_label_id, dtype=torch.long)
168
+ indicator = torch.zeros((B, max_n), dtype=torch.long)
169
+ sent_lens = torch.zeros((B,), dtype=torch.long)
170
+ pred_word_idx = torch.zeros((B,), dtype=torch.long)
171
+ sentence_mask = torch.zeros((B, max_n), dtype=torch.bool)
172
+
173
+ for i, item in enumerate(batch):
174
+ L = item["input_ids"].size(0)
175
+ input_ids[i, :L] = item["input_ids"]
176
+ token_type_ids[i, :L] = item["token_type_ids"]
177
+ attention_mask[i, :L] = item["attention_mask"]
178
+ n = int(item["sent_len"])
179
+ word_first_wp_fullidx[i, :n] = item["word_first_wp_fullidx"]
180
+ labels[i, :n] = item["labels"]
181
+ indicator[i, :n] = item["indicator"]
182
+ sent_lens[i] = n
183
+ pred_word_idx[i] = item["pred_word_idx"]
184
+ sentence_mask[i, :n] = True
185
+
186
+ return {
187
+ "input_ids": input_ids,
188
+ "token_type_ids": token_type_ids,
189
+ "attention_mask": attention_mask,
190
+ "word_first_wp_fullidx": word_first_wp_fullidx,
191
+ "sentence_mask": sentence_mask,
192
+ "labels": labels,
193
+ "indicator": indicator,
194
+ "sent_lens": sent_lens,
195
+ "pred_word_idx": pred_word_idx,
196
+ }
197
+
198
+
199
+ # ==============================================================
200
+ # 5. Helper for trainer
201
+ # ==============================================================
202
+ def data_processing_for_loader_from_conll(conll_path, tokenizer, word_col_idx=3, srl_first_col_idx=11):
203
+ """Reads .gold_conll β†’ SRLSample β†’ SRLDataset, builds label2id/id2label."""
204
+ samples = load_conll_samples(conll_path, word_col_idx, srl_first_col_idx)
205
+ label_set = set(l for s in samples for l in s.labels)
206
+ label2id = {l: i for i, l in enumerate(sorted(label_set))}
207
+ id2label = {i: l for l, i in label2id.items()}
208
+ dataset = SRLDataset(samples, tokenizer, label2id, max_length=256)
209
+ return dataset, label2id, id2label
trainer.py ADDED
@@ -0,0 +1,288 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ from transformers import AutoTokenizer, get_linear_schedule_with_warmup
4
+ from sklearn.metrics import f1_score
5
+ from torch.utils.data import DataLoader
6
+
7
+ from SRL_preprocessing import data_processing_for_loader_from_conll, srl_collate
8
+ from model import PredicateAwareSRL
9
+ from utils import save_pkl
10
+ import re, pathlib, argparse, json, os, sys
11
+
12
+
13
+ try:
14
+ import _jsonnet
15
+ except ImportError:
16
+ _jsonnet = None
17
+
18
+ def load_cfg_from_jsonnet():
19
+ parser = argparse.ArgumentParser()
20
+ parser.add_argument("--config", required=True, help="Path to .jsonnet config")
21
+ parser.add_argument("--out_dir", default=None, help="Override training.out_dir")
22
+ parser.add_argument("--best_model_path", default=None, help="Override best model save path")
23
+ parser.add_argument("--save_history_path", default=None, help="Override history pickle path")
24
+ args, unknown = parser.parse_known_args()
25
+
26
+ if _jsonnet is None:
27
+ raise RuntimeError("Please `pip install jsonnet` to use --config")
28
+
29
+ cfg = json.loads(_jsonnet.evaluate_file(args.config))
30
+
31
+ # Apply CLI overrides
32
+ if args.out_dir:
33
+ cfg.setdefault("training", {})["out_dir"] = args.out_dir
34
+
35
+ # Ensure out_dir exists & derive default file paths if missing
36
+ out_dir = cfg["training"].get("out_dir", "./checkpoints")
37
+ os.makedirs(out_dir, exist_ok=True)
38
+
39
+ # Derive defaults if not provided in config
40
+ cfg["training"].setdefault("best_model_path", os.path.join(out_dir, "best_srl_fr.ckpt"))
41
+ cfg["training"].setdefault("save_history_path", os.path.join(out_dir, "loss_history_fr.pkl"))
42
+
43
+ # Allow explicit overrides
44
+ if args.best_model_path:
45
+ cfg["training"]["best_model_path"] = args.best_model_path
46
+ if args.save_history_path:
47
+ cfg["training"]["save_history_path"] = args.save_history_path
48
+
49
+ return cfg
50
+
51
+ # ==============================================================
52
+ # 1. Training Loop
53
+ # ==============================================================
54
+ def train_one_epoch(
55
+ model,
56
+ dataloader,
57
+ optimizer,
58
+ device="cuda",
59
+ scheduler=None,
60
+ grad_accum_steps=1,
61
+ amp=True,
62
+ max_grad_norm=1.0,
63
+ ):
64
+ model.train()
65
+ total_loss, n_steps = 0.0, 0
66
+
67
+ use_amp = amp and torch.cuda.is_available()
68
+ scaler = torch.cuda.amp.GradScaler(enabled=use_amp)
69
+
70
+ optimizer.zero_grad(set_to_none=True)
71
+
72
+ for step, batch in enumerate(dataloader, 1):
73
+ batch = {k: v.to(device) if torch.is_tensor(v) else v for k, v in batch.items()}
74
+
75
+ with torch.cuda.amp.autocast(enabled=use_amp, dtype=torch.float16):
76
+ _, loss = model(**batch) # model must return (logits, loss)
77
+
78
+ total_loss += float(loss.detach().item())
79
+ n_steps += 1
80
+ loss = loss / grad_accum_steps
81
+
82
+ if use_amp:
83
+ scaler.scale(loss).backward()
84
+ else:
85
+ loss.backward()
86
+
87
+ if step % grad_accum_steps == 0:
88
+ if use_amp:
89
+ scaler.unscale_(optimizer)
90
+ nn.utils.clip_grad_norm_(model.parameters(), max_grad_norm)
91
+
92
+ if use_amp:
93
+ scaler.step(optimizer)
94
+ scaler.update()
95
+ else:
96
+ optimizer.step()
97
+
98
+ optimizer.zero_grad(set_to_none=True)
99
+
100
+ if scheduler is not None:
101
+ scheduler.step()
102
+
103
+ return total_loss / max(1, n_steps)
104
+
105
+
106
+ # ==============================================================
107
+ # 2. Evaluation Loop
108
+ # ==============================================================
109
+ @torch.no_grad()
110
+ def eval_loss_and_token_f1(model, dataloader, id2label=None, device="cuda", average="micro"):
111
+ model.eval()
112
+ total_loss, n_batches = 0.0, 0
113
+ all_preds, all_golds = [], []
114
+
115
+ for batch in dataloader:
116
+ gold = batch["labels"]
117
+ mask = (gold != -100)
118
+ batch = {k: v.to(device) if torch.is_tensor(v) else v for k, v in batch.items()}
119
+
120
+ logits, loss = model(**batch)
121
+ total_loss += float(loss.item()); n_batches += 1
122
+
123
+ preds = logits.argmax(-1).cpu()
124
+ all_preds.extend(preds[mask].tolist())
125
+ all_golds.extend(gold[mask].tolist())
126
+
127
+ f1 = f1_score(all_golds, all_preds, average=average)
128
+ return total_loss / max(1, n_batches), f1
129
+
130
+
131
+ # ==============================================================
132
+ # 3. Flexible Model Loader (English β†’ French transfer)
133
+ # ==============================================================
134
+ def load_model(
135
+ bert_name: str,
136
+ label2id,
137
+ resume_path: str = None,
138
+ replace_encoder_with: str = None,
139
+ **kwargs
140
+ ):
141
+ """
142
+ Creates a PredicateAwareSRL model.
143
+ - If resume_path is given: loads SRL weights (English model)
144
+ - If replace_encoder_with is given: replaces only the BERT encoder
145
+ (e.g., replace 'bert-base-cased' with 'camembert-base')
146
+ """
147
+ print(f"🧩 Loading model backbone: {bert_name}")
148
+ model = PredicateAwareSRL(
149
+ bert_name=bert_name,
150
+ num_labels=len(label2id),
151
+ use_indicator=kwargs.get("use_indicator", True),
152
+ use_distance=kwargs.get("use_distance", True),
153
+ indicator_dim=kwargs.get("indicator_dim", 10),
154
+ lstm_hidden=kwargs.get("lstm_hidden", 768),
155
+ mlp_hidden=kwargs.get("mlp_hidden", 300),
156
+ pos_dim=kwargs.get("pos_dim", 50),
157
+ max_distance=kwargs.get("max_distance", 128),
158
+ dropout=kwargs.get("dropout", 0.1),
159
+ pad_label_id=-100,
160
+ )
161
+
162
+ if resume_path and os.path.exists(resume_path):
163
+ print(f"πŸ” Loading SRL checkpoint from: {resume_path}")
164
+ state = torch.load(resume_path, map_location="cpu")
165
+ state_dict = state.get("model_state", state)
166
+ missing, unexpected = model.load_state_dict(state_dict, strict=False)
167
+ print(f" β†’ missing: {len(missing)}, unexpected: {len(unexpected)}")
168
+
169
+ if replace_encoder_with:
170
+ print(f"🌍 Replacing encoder with: {replace_encoder_with}")
171
+ from transformers import AutoModel
172
+ model.encoder = AutoModel.from_pretrained(replace_encoder_with)
173
+
174
+ return model
175
+
176
+
177
+ # ==============================================================
178
+ # 4. Main
179
+ # ==============================================================
180
+ if __name__ == "__main__":
181
+ # ------------------------------
182
+ # βš™οΈ Configuration
183
+ # ------------------------------
184
+ cfg = load_cfg_from_jsonnet()
185
+
186
+ # read values from cfg as usual:
187
+ conll_train_path = cfg["data"]["conll_train"]
188
+ conll_dev_path = cfg["data"].get("conll_dev")
189
+ word_col_idx = cfg["data"]["word_col_idx"]
190
+ srl_first_col_idx= cfg["data"]["srl_first_col_idx"]
191
+
192
+ bert_name = cfg["model"]["bert_name"]
193
+ resume_from = cfg["model"].get("resume_from")
194
+ replace_encoder_with = cfg["model"].get("replace_encoder_with")
195
+ tok_name = (cfg["model"].get("tokenizer", {}) or {}).get("name", replace_encoder_with or bert_name)
196
+
197
+ out_dir = cfg["training"]["out_dir"]
198
+ num_epochs = cfg["training"]["num_epochs"]
199
+ batch_size = cfg["training"]["batch_size"]
200
+ lr = cfg["training"]["lr"]
201
+ weight_decay = cfg["training"]["weight_decay"]
202
+ grad_accum = cfg["training"]["grad_accum_steps"]
203
+ warmup_ratio = cfg["training"]["warmup_ratio"]
204
+ amp = cfg["training"]["amp"]
205
+ max_grad_norm = cfg["training"]["max_grad_norm"]
206
+
207
+ best_model_path = cfg["training"]["best_model_path"]
208
+ save_history_path = cfg["training"]["save_history_path"]
209
+ device = "cuda" if torch.cuda.is_available() else "cpu"
210
+
211
+ # ------------------------------
212
+ # 🧩 Tokenizer + data loading
213
+ # ------------------------------
214
+ tokenizer = AutoTokenizer.from_pretrained(replace_encoder_with or bert_name)
215
+ print(f"πŸ”€ Using tokenizer: {replace_encoder_with or bert_name}")
216
+
217
+ print(f"πŸ“‚ Loading French CoNLL data: {conll_train_path}")
218
+ train_dataset, label2id, id2label = data_processing_for_loader_from_conll(
219
+ conll_path=conll_train_path,
220
+ tokenizer=tokenizer,
221
+ word_col_idx=3,
222
+ srl_first_col_idx=11,
223
+ )
224
+
225
+ pad_token_id = tokenizer.pad_token_id or tokenizer.eos_token_id
226
+ collate = lambda b: srl_collate(b, pad_token_id=pad_token_id, pad_label_id=-100)
227
+
228
+ train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True, collate_fn=collate)
229
+ dev_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=False, collate_fn=collate) # no dev split yet
230
+
231
+ # ------------------------------
232
+ # 🧠 Model initialization
233
+ # ------------------------------
234
+ model = load_model(
235
+ bert_name=bert_name,
236
+ label2id=label2id,
237
+ resume_path=resume_from,
238
+ replace_encoder_with=replace_encoder_with,
239
+ use_indicator=True,
240
+ use_distance=True,
241
+ indicator_dim=10,
242
+ lstm_hidden=768,
243
+ mlp_hidden=300,
244
+ pos_dim=50,
245
+ max_distance=128,
246
+ dropout=0.1,
247
+ ).to(device)
248
+
249
+ # ------------------------------
250
+ # πŸ”§ Optimizer + Scheduler
251
+ # ------------------------------
252
+ optimizer = torch.optim.AdamW(model.parameters(), lr=lr)
253
+ total_steps = len(train_loader) * num_epochs // max(1, grad_accum_steps)
254
+ warmup_steps = int(warmup_ratio * total_steps)
255
+
256
+ scheduler = get_linear_schedule_with_warmup(
257
+ optimizer,
258
+ num_warmup_steps=warmup_steps,
259
+ num_training_steps=total_steps,
260
+ )
261
+
262
+ # ------------------------------
263
+ # πŸ‹οΈ Training Loop
264
+ # ------------------------------
265
+ history = {"epoch": [], "train_loss": [], "dev_loss": [], "dev_f1": []}
266
+ best_dev, best_path = -1.0, "best_srl_fr.ckpt"
267
+
268
+ for epoch in range(num_epochs):
269
+ tr_loss = train_one_epoch(
270
+ model, train_loader, optimizer, device=device,
271
+ scheduler=scheduler, grad_accum_steps=grad_accum,
272
+ amp=True, max_grad_norm=1.0,
273
+ )
274
+ dev_loss, dev_f1 = eval_loss_and_token_f1(model, dev_loader, id2label, device=device)
275
+
276
+ history["epoch"].append(epoch + 1)
277
+ history["train_loss"].append(tr_loss)
278
+ history["dev_loss"].append(dev_loss)
279
+ history["dev_f1"].append(dev_f1)
280
+
281
+ print(f"Epoch {epoch+1}: train_loss={tr_loss:.4f} dev_loss={dev_loss:.4f} dev_F1={dev_f1:.4f}")
282
+
283
+ if dev_f1 > best_dev:
284
+ best_dev = dev_f1
285
+ torch.save({"model_state": model.state_dict(), "label2id": label2id}, best_path)
286
+ print(f" ↳ new best dev; saved to {best_path}")
287
+
288
+ save_pkl(history, "loss_history_fr.pkl")
utils.py ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import pickle
2
+
3
+ def save_pkl(tgt_list, svg_path):
4
+ with open(svg_path, "wb") as f:
5
+ pickle.dump(tgt_list, f)
6
+
7
+ def load_pkl(path) :
8
+ with open(path, "rb") as f:
9
+ data = pickle.load(f)
10
+ return data