Briancrouch dejanseo commited on
Commit
dd2d8a4
·
0 Parent(s):

Duplicate from dejanseo/DEJAN-LM

Browse files

Co-authored-by: Dan Petrovic <dejanseo@users.noreply.huggingface.co>

.gitattributes ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
README.md ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ ---
2
+ license: other
3
+ license_name: link-attribution
4
+ license_link: https://dejanmarketing.com/link-attribution/
5
+ ---
app.py ADDED
@@ -0,0 +1,357 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app_interactive.py
2
+ import streamlit as st
3
+ import torch
4
+ import random
5
+ import os
6
+ import pandas as pd
7
+ from transformers import RobertaForMaskedLM, PreTrainedTokenizerFast
8
+ import re
9
+
10
+ # --- Configuration ---
11
+ CHECKPOINT_BASE_DIR = "./checkpoints"
12
+ PRESET_SENTENCE = "The quick brown fox jumps over the lazy dog near the river bank."
13
+ TOP_K = 5
14
+
15
+ # --- Initialize Session State ---
16
+ if 'masked_indices' not in st.session_state:
17
+ st.session_state.masked_indices = set()
18
+ if 'tokens' not in st.session_state:
19
+ st.session_state.tokens = []
20
+ if 'token_ids' not in st.session_state:
21
+ st.session_state.token_ids = []
22
+ if 'input_sentence' not in st.session_state:
23
+ st.session_state.input_sentence = PRESET_SENTENCE
24
+ if 'display_tokens' not in st.session_state:
25
+ st.session_state.display_tokens = []
26
+
27
+ # --- Helper Functions ---
28
+ def sanitize_token_display(token):
29
+ """Clean up token display by removing special characters like Ġ."""
30
+ # Replace the 'Ġ' character with a more readable indicator
31
+ if isinstance(token, str) and token.startswith('Ġ'):
32
+ return token[1:] # Remove the Ġ character
33
+ # Handle other special tokens if needed
34
+ elif token in ['<s>', '</s>', '<pad>']:
35
+ return token
36
+ else:
37
+ return token
38
+
39
+ def find_checkpoints(base_dir):
40
+ """Finds valid checkpoint directories within the base directory."""
41
+ checkpoints = []
42
+ if not os.path.isdir(base_dir):
43
+ return checkpoints
44
+ for item in os.listdir(base_dir):
45
+ path = os.path.join(base_dir, item)
46
+ if os.path.isdir(path) and item.startswith("checkpoint-"):
47
+ if os.path.exists(os.path.join(path, "pytorch_model.bin")) or \
48
+ os.path.exists(os.path.join(path, "model.safetensors")):
49
+ checkpoints.append(item)
50
+ checkpoints.sort(key=lambda x: int(re.search(r'(\d+)', x).group(1)))
51
+ return checkpoints
52
+
53
+ @st.cache_resource
54
+ def load_model_and_tokenizer(checkpoint_name):
55
+ """Loads the model and tokenizer from the specified checkpoint directory name."""
56
+ checkpoint_path = os.path.join(CHECKPOINT_BASE_DIR, checkpoint_name)
57
+ if not os.path.isdir(checkpoint_path):
58
+ st.error(f"Checkpoint directory not found: {checkpoint_path}")
59
+ return None, None
60
+ try:
61
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
62
+ model = RobertaForMaskedLM.from_pretrained(checkpoint_path).to(device)
63
+ tokenizer = PreTrainedTokenizerFast.from_pretrained(checkpoint_path)
64
+ model.eval()
65
+ #st.success(f"Loaded {checkpoint_name} on {device}")
66
+ return model, tokenizer, device
67
+ except Exception as e:
68
+ st.error(f"Error loading {checkpoint_name}: {e}")
69
+ return None, None, None
70
+
71
+ def tokenize_text(text, tokenizer):
72
+ """Tokenize the input text and return tokens and their IDs."""
73
+ encoding = tokenizer(text, return_tensors="pt", add_special_tokens=True)
74
+ input_ids = encoding.input_ids[0].tolist()
75
+
76
+ # Get individual tokens
77
+ tokens = []
78
+ for id in input_ids:
79
+ token = tokenizer.convert_ids_to_tokens(id)
80
+ tokens.append(token)
81
+
82
+ return tokens, input_ids
83
+
84
+ def toggle_token(index):
85
+ """Toggle a token's masked status."""
86
+ if index in st.session_state.masked_indices:
87
+ st.session_state.masked_indices.remove(index)
88
+ else:
89
+ st.session_state.masked_indices.add(index)
90
+
91
+ def update_input_sentence():
92
+ """Update the input sentence and reset masked indices."""
93
+ st.session_state.input_sentence = st.session_state.input_text
94
+ st.session_state.masked_indices = set()
95
+
96
+ def get_predictions(model, tokenizer, device):
97
+ """Get predictions for masked tokens."""
98
+ if not st.session_state.masked_indices:
99
+ return None, None, None, None
100
+
101
+ # Create a copy of the token IDs
102
+ masked_input_ids = st.session_state.token_ids.copy()
103
+
104
+ # Apply masks
105
+ for idx in st.session_state.masked_indices:
106
+ masked_input_ids[idx] = tokenizer.mask_token_id
107
+
108
+ # Convert to tensor
109
+ masked_input_tensor = torch.tensor([masked_input_ids]).to(device)
110
+
111
+ # Get predictions
112
+ with torch.no_grad():
113
+ outputs = model(input_ids=masked_input_tensor)
114
+ logits = outputs.logits
115
+
116
+ results = []
117
+ top1_predictions = {}
118
+ prediction_tokens = {}
119
+ original_token_ranks = {}
120
+
121
+ for masked_index in st.session_state.masked_indices:
122
+ mask_logits = logits[0, masked_index, :]
123
+ probabilities = torch.softmax(mask_logits, dim=-1)
124
+ top_k_probs, top_k_indices = torch.topk(probabilities, TOP_K)
125
+
126
+ # Save top-1 prediction for reconstruction
127
+ top1_id = top_k_indices[0].item()
128
+ top1_predictions[masked_index] = top1_id
129
+
130
+ # Sanitize the token here
131
+ raw_token = tokenizer.convert_ids_to_tokens(top1_id)
132
+ prediction_tokens[masked_index] = sanitize_token_display(raw_token)
133
+
134
+ original_token = st.session_state.tokens[masked_index]
135
+ original_id = st.session_state.token_ids[masked_index]
136
+
137
+ # Check if original token is in top K predictions
138
+ original_token_in_top_k = False
139
+ original_token_rank = -1 # -1 means not in top K
140
+
141
+ for rank, token_id in enumerate(top_k_indices.tolist()):
142
+ predicted_token = tokenizer.convert_ids_to_tokens(token_id)
143
+ if predicted_token.lower() == original_token.lower() or token_id == original_id:
144
+ original_token_in_top_k = True
145
+ original_token_rank = rank
146
+ break
147
+
148
+ original_token_ranks[masked_index] = original_token_rank
149
+
150
+ for rank, (prob, token_id) in enumerate(zip(top_k_probs.tolist(), top_k_indices.tolist())):
151
+ predicted_token = tokenizer.convert_ids_to_tokens(token_id)
152
+ # Sanitize the predicted token for the results table
153
+ clean_predicted_token = sanitize_token_display(predicted_token)
154
+
155
+ # Case insensitive match
156
+ is_match = predicted_token.lower() == original_token.lower()
157
+ results.append({
158
+ "Masked Index": masked_index,
159
+ "Rank": rank + 1,
160
+ "Predicted Token": clean_predicted_token, # Use sanitized token
161
+ "Original Token": sanitize_token_display(original_token), # Sanitize original token
162
+ "Exact Match": is_match,
163
+ "Probability": f"{prob:.4f}"
164
+ })
165
+
166
+ # Reconstruct the sentence using top-1 predictions
167
+ reconstructed_ids = masked_input_ids.copy()
168
+ for idx in st.session_state.masked_indices:
169
+ reconstructed_ids[idx] = top1_predictions[idx]
170
+
171
+ reconstructed_text = tokenizer.decode(reconstructed_ids, skip_special_tokens=True)
172
+
173
+ return results, reconstructed_text, prediction_tokens, original_token_ranks
174
+
175
+ # --- Streamlit App Layout ---
176
+
177
+ st.set_page_config(layout="wide", page_title="Interactive MLM Inference")
178
+
179
+ # Custom CSS to prevent text wrapping in buttons
180
+ st.markdown("""
181
+ <style>
182
+ .stButton button {
183
+ white-space: nowrap;
184
+ overflow: hidden;
185
+ text-overflow: ellipsis;
186
+ min-width: 80px;
187
+ }
188
+ </style>
189
+ """, unsafe_allow_html=True)
190
+
191
+ st.title("🧪 Interactive MLM Inference")
192
+
193
+ # --- Checkpoint Selection ---
194
+ available_checkpoints = find_checkpoints(CHECKPOINT_BASE_DIR)
195
+
196
+ if not available_checkpoints:
197
+ st.error(f"No checkpoints found in '{CHECKPOINT_BASE_DIR}'. Please train a model first.")
198
+ st.stop()
199
+
200
+ selected_checkpoint = st.selectbox(
201
+ "Select Checkpoint:",
202
+ available_checkpoints,
203
+ index=len(available_checkpoints) - 1
204
+ )
205
+
206
+ # --- Load Model ---
207
+ if selected_checkpoint:
208
+ model, tokenizer, device = load_model_and_tokenizer(selected_checkpoint)
209
+ else:
210
+ model, tokenizer, device = None, None, None
211
+
212
+ # --- Interactive Inference Section ---
213
+ st.divider()
214
+ st.subheader("Interactive Token Masking")
215
+
216
+ # 1. Original text area
217
+ st.text_area(
218
+ "Input Sentence:",
219
+ value=st.session_state.input_sentence,
220
+ key="input_text",
221
+ on_change=update_input_sentence,
222
+ height=100
223
+ )
224
+
225
+ if model and tokenizer and device:
226
+ # Tokenize the input text
227
+ st.session_state.tokens, st.session_state.token_ids = tokenize_text(
228
+ st.session_state.input_sentence,
229
+ tokenizer
230
+ )
231
+
232
+ # Create sanitized display tokens
233
+ st.session_state.display_tokens = [sanitize_token_display(token) for token in st.session_state.tokens]
234
+
235
+ # 2. Interactive token display
236
+ st.subheader("Click on tokens to mask/unmask them:")
237
+
238
+ # Group tokens into rows (adjust number as needed)
239
+ tokens_per_row = 12
240
+
241
+ # Calculate how many rows we need
242
+ num_rows = (len(st.session_state.tokens) + tokens_per_row - 1) // tokens_per_row
243
+
244
+ for row in range(num_rows):
245
+ # Create columns for this row
246
+ start_idx = row * tokens_per_row
247
+ end_idx = min(start_idx + tokens_per_row, len(st.session_state.tokens))
248
+ row_tokens = st.session_state.tokens[start_idx:end_idx]
249
+
250
+ # Create equal-width columns
251
+ cols = st.columns(len(row_tokens))
252
+
253
+ for j, col in enumerate(cols):
254
+ idx = start_idx + j
255
+ token = st.session_state.tokens[idx]
256
+
257
+ # Skip special tokens for masking
258
+ is_special = token in [
259
+ tokenizer.cls_token,
260
+ tokenizer.sep_token,
261
+ tokenizer.pad_token
262
+ ]
263
+
264
+ is_masked = idx in st.session_state.masked_indices
265
+
266
+ # Create a button for each token
267
+ button_key = f"token_{idx}"
268
+ button_label = sanitize_token_display(token) if not is_masked else "[MASK]"
269
+
270
+ if col.button(
271
+ button_label,
272
+ key=button_key,
273
+ disabled=is_special,
274
+ help=f"Token ID: {st.session_state.token_ids[idx]}"
275
+ ):
276
+ toggle_token(idx)
277
+ st.rerun()
278
+
279
+ # 3. Prediction area
280
+ if st.session_state.masked_indices:
281
+ results, reconstructed_text, prediction_tokens, original_token_ranks = get_predictions(model, tokenizer, device)
282
+
283
+ st.subheader("Predictions:")
284
+ st.markdown("**Reconstructed sentence with predictions:**")
285
+
286
+ # Create HTML for highlighting predictions
287
+ html = "<div style='padding: 10px; border-radius: 5px; border: 1px solid #ccc;'>"
288
+
289
+ # Use the original tokenization to match masked positions
290
+ for i, token in enumerate(st.session_state.tokens):
291
+ # Skip special tokens
292
+ if token in [tokenizer.cls_token, tokenizer.sep_token, tokenizer.pad_token]:
293
+ continue
294
+
295
+ if i in st.session_state.masked_indices:
296
+ # This was a masked token
297
+ original_token = sanitize_token_display(st.session_state.tokens[i])
298
+ predicted_token = prediction_tokens[i] # This is already sanitized in get_predictions
299
+ original_rank = original_token_ranks[i]
300
+
301
+ # Color based on original token's rank in predictions
302
+ if original_rank == 0: # Rank 0 means it was the top prediction
303
+ # Green for top prediction (rank 1)
304
+ html += f"<span style='background-color: #c3e6cb; padding: 2px 4px; border-radius: 3px; margin: 0 2px;'>{predicted_token}</span>"
305
+ elif original_rank != -1: # In top 5 but not top
306
+ # Blue for in top 5 but not top
307
+ html += f"<span style='background-color: #b8daff; padding: 2px 4px; border-radius: 3px; margin: 0 2px;'>{predicted_token}</span>"
308
+ else: # Not in top 5
309
+ # Red for not in top 5
310
+ html += f"<span style='background-color: #f8d7da; padding: 2px 4px; border-radius: 3px; margin: 0 2px;'>{predicted_token}</span>"
311
+ else:
312
+ # Not a masked token, display normally
313
+ sanitized_token = sanitize_token_display(token)
314
+ html += f"{sanitized_token} "
315
+
316
+ html += "</div>"
317
+
318
+ # Display the highlighted text
319
+ st.markdown(html, unsafe_allow_html=True)
320
+
321
+ # Show detailed predictions
322
+ st.markdown("**Top predictions for each masked token:**")
323
+
324
+ for masked_idx in st.session_state.masked_indices:
325
+ original_token = st.session_state.tokens[masked_idx]
326
+ original_rank = original_token_ranks[masked_idx]
327
+
328
+ # Create a note about whether the original token was in top predictions
329
+ if original_rank == 0:
330
+ rank_note = "✅ Original token was the top prediction"
331
+ elif original_rank != -1:
332
+ rank_note = f"ℹ️ Original token was prediction #{original_rank+1}"
333
+ else:
334
+ rank_note = "❌ Original token not in top 5 predictions"
335
+
336
+ # Sanitize the token display
337
+ clean_original_token = sanitize_token_display(original_token)
338
+ st.markdown(f"**Token {clean_original_token} at position {masked_idx}** - {rank_note}")
339
+
340
+ # The dataframe is already sanitized in the get_predictions function
341
+ df = pd.DataFrame([r for r in results if r['Masked Index'] == masked_idx])
342
+ df = df[["Rank", "Predicted Token", "Probability"]]
343
+
344
+ # Highlight the row with the original token if it's in top 5
345
+ if original_rank != -1:
346
+ # Use pandas styler to highlight the row
347
+ styled_df = df.style.apply(lambda x: ['background-color: #c3e6cb' if i == original_rank else '' for i in range(len(x))], axis=0)
348
+ st.dataframe(styled_df, use_container_width=True)
349
+ else:
350
+ st.dataframe(df, use_container_width=True)
351
+ else:
352
+ st.info("Click on tokens above to mask them and see predictions.")
353
+ else:
354
+ st.warning("Please select a valid checkpoint to enable interactive masking.")
355
+
356
+ st.divider()
357
+ st.caption("Interactive app for RoBERTa Masked Language Modeling.")
config.json ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "RobertaForMaskedLM"
4
+ ],
5
+ "attention_probs_dropout_prob": 0.1,
6
+ "bos_token_id": 2,
7
+ "classifier_dropout": null,
8
+ "eos_token_id": 3,
9
+ "hidden_act": "gelu",
10
+ "hidden_dropout_prob": 0.1,
11
+ "hidden_size": 256,
12
+ "initializer_range": 0.02,
13
+ "intermediate_size": 1024,
14
+ "layer_norm_eps": 1e-12,
15
+ "max_position_embeddings": 514,
16
+ "model_type": "roberta",
17
+ "num_attention_heads": 8,
18
+ "num_hidden_layers": 4,
19
+ "pad_token_id": 0,
20
+ "position_embedding_type": "absolute",
21
+ "torch_dtype": "float32",
22
+ "transformers_version": "4.50.3",
23
+ "type_vocab_size": 2,
24
+ "use_cache": true,
25
+ "vocab_size": 32000
26
+ }
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4f87a907da662f0f7cd1c78bc7e116dc34bf7bc822bd88d0d8be318cb9b6c530
3
+ size 46336400
optimizer.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3540ab6a715c86446c0fcc17747212409c242e583821d6556df5df779c3b4fbc
3
+ size 92717818
rng_state.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:36bbf17e45bd87663cd98ff4d6027892aa4320c31d67540c8ee33c1d805a30c7
3
+ size 14244
scheduler.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8a971a8dd2d90d918014f25aed8de35f62e388573fdd5a7706b6d6fe96f8fb76
3
+ size 1064
special_tokens_map.json ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cls_token": {
3
+ "content": "<s>",
4
+ "lstrip": false,
5
+ "normalized": false,
6
+ "rstrip": false,
7
+ "single_word": false
8
+ },
9
+ "mask_token": {
10
+ "content": "<mask>",
11
+ "lstrip": false,
12
+ "normalized": false,
13
+ "rstrip": false,
14
+ "single_word": false
15
+ },
16
+ "pad_token": {
17
+ "content": "<pad>",
18
+ "lstrip": false,
19
+ "normalized": false,
20
+ "rstrip": false,
21
+ "single_word": false
22
+ },
23
+ "sep_token": {
24
+ "content": "</s>",
25
+ "lstrip": false,
26
+ "normalized": false,
27
+ "rstrip": false,
28
+ "single_word": false
29
+ },
30
+ "unk_token": {
31
+ "content": "<unk>",
32
+ "lstrip": false,
33
+ "normalized": false,
34
+ "rstrip": false,
35
+ "single_word": false
36
+ }
37
+ }
tokenizer.json ADDED
The diff for this file is too large to render. See raw diff
 
tokenizer_config.json ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "add_prefix_space": true,
3
+ "added_tokens_decoder": {
4
+ "0": {
5
+ "content": "<pad>",
6
+ "lstrip": false,
7
+ "normalized": false,
8
+ "rstrip": false,
9
+ "single_word": false,
10
+ "special": true
11
+ },
12
+ "1": {
13
+ "content": "<unk>",
14
+ "lstrip": false,
15
+ "normalized": false,
16
+ "rstrip": false,
17
+ "single_word": false,
18
+ "special": true
19
+ },
20
+ "2": {
21
+ "content": "<s>",
22
+ "lstrip": false,
23
+ "normalized": false,
24
+ "rstrip": false,
25
+ "single_word": false,
26
+ "special": true
27
+ },
28
+ "3": {
29
+ "content": "</s>",
30
+ "lstrip": false,
31
+ "normalized": false,
32
+ "rstrip": false,
33
+ "single_word": false,
34
+ "special": true
35
+ },
36
+ "4": {
37
+ "content": "<mask>",
38
+ "lstrip": false,
39
+ "normalized": false,
40
+ "rstrip": false,
41
+ "single_word": false,
42
+ "special": true
43
+ }
44
+ },
45
+ "clean_up_tokenization_spaces": false,
46
+ "cls_token": "<s>",
47
+ "extra_special_tokens": {},
48
+ "mask_token": "<mask>",
49
+ "model_max_length": 512,
50
+ "pad_token": "<pad>",
51
+ "sep_token": "</s>",
52
+ "tokenizer_class": "PreTrainedTokenizer",
53
+ "unk_token": "<unk>"
54
+ }
train.py ADDED
@@ -0,0 +1,246 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # train_fixed_clean_keys_v2.py
2
+
3
+ import os
4
+ import math
5
+ import random
6
+ import torch
7
+ import pandas as pd
8
+ import numpy as np
9
+ import streamlit as st
10
+ import plotly.graph_objects as go
11
+ from transformers import (
12
+ RobertaConfig, RobertaForMaskedLM, Trainer, TrainingArguments,
13
+ PreTrainedTokenizerFast, DataCollatorForLanguageModeling, TrainerCallback
14
+ )
15
+ # Import Value from datasets alongside others
16
+ from datasets import load_dataset, Features, Sequence, Value
17
+
18
+ # --- Streamlit setup ---
19
+ st.set_page_config(layout="wide")
20
+
21
+ # --- Constants ---
22
+ TOKENIZER_DIR = "tokenizer" # Ensure this matches the one used in preprocessing
23
+ DATA_PATH = "training_data.jsonl" # Ensure this is the output from sentence_aware_processor.py
24
+ OUTPUT_DIR = "./checkpoints"
25
+ VOCAB_SIZE = 32000
26
+ MAX_LEN = 512
27
+ BATCH_SIZE = 64
28
+ EPOCHS = 50
29
+ GRAD_ACC = 8
30
+ LEARNING_RATE = 1e-3
31
+ MLM_PROB = 0.15
32
+ SEED = 42
33
+
34
+ # --- Seed ---
35
+ def set_seed(seed):
36
+ random.seed(seed)
37
+ np.random.seed(seed)
38
+ torch.manual_seed(seed)
39
+ if torch.cuda.is_available():
40
+ torch.cuda.manual_seed_all(seed)
41
+
42
+ set_seed(SEED)
43
+
44
+ # --- Tokenizer ---
45
+ if not os.path.exists(os.path.join(TOKENIZER_DIR, "tokenizer.json")):
46
+ st.error(f"Tokenizer not found in {TOKENIZER_DIR}")
47
+ st.stop()
48
+ try:
49
+ tokenizer = PreTrainedTokenizerFast.from_pretrained(TOKENIZER_DIR)
50
+ tokenizer.model_max_length = MAX_LEN
51
+ except Exception as e:
52
+ st.error(f"Error loading tokenizer from {TOKENIZER_DIR}: {e}")
53
+ st.stop()
54
+
55
+
56
+ # --- Dataset ---
57
+ # !!! MODIFIED: Updated Features definition to match the JSONL structure !!!
58
+ features = Features({
59
+ 'id': Value(dtype='int64'), # Added id field
60
+ 'input_ids': Sequence(Value(dtype='int32')),
61
+ 'source': Value(dtype='string') # Added source field
62
+ })
63
+ # (Error handling remains)
64
+ try:
65
+ # Load the dataset using the updated features
66
+ dataset = load_dataset("json", data_files=DATA_PATH, features=features, split="train")
67
+ #st.success(f"Loaded dataset from {DATA_PATH} with columns: {dataset.column_names}")
68
+ except Exception as e:
69
+ st.error(f"Failed to load dataset from {DATA_PATH}: {e}")
70
+ st.info(f"Ensure '{DATA_PATH}' exists and matches the features: {features}")
71
+ st.stop()
72
+
73
+ # --- Add Attention Mask ---
74
+ # This function remains the same, as it only needs 'input_ids'
75
+ if 'attention_mask' not in dataset.column_names:
76
+ def add_attention_mask(example):
77
+ # The length is derived from the 'input_ids' field
78
+ example["attention_mask"] = [1] * len(example["input_ids"])
79
+ return example
80
+ dataset = dataset.map(add_attention_mask, num_proc=max(1, os.cpu_count() // 2))
81
+ #st.info("Added 'attention_mask' column.")
82
+
83
+ # --- Collator ---
84
+ # DataCollatorForLanguageModeling will automatically ignore extra columns like 'id' and 'source'
85
+ collator = DataCollatorForLanguageModeling(
86
+ tokenizer=tokenizer,
87
+ mlm=True,
88
+ mlm_probability=MLM_PROB
89
+ )
90
+
91
+ # --- Model ---
92
+ # Model definition remains the same
93
+ config = RobertaConfig(
94
+ vocab_size=VOCAB_SIZE,
95
+ hidden_size=256,
96
+ num_hidden_layers=4,
97
+ num_attention_heads=8,
98
+ intermediate_size=1024,
99
+ max_position_embeddings=MAX_LEN + 2,
100
+ pad_token_id=tokenizer.pad_token_id,
101
+ bos_token_id=tokenizer.cls_token_id,
102
+ eos_token_id=tokenizer.sep_token_id,
103
+ )
104
+ model = RobertaForMaskedLM(config=config)
105
+
106
+ # --- UI State ---
107
+ # UI setup remains the same
108
+ log = {"step": [], "loss": [], "grad_norm": [], "perplexity": []}
109
+ progress = st.empty()
110
+ col1, col2 = st.columns(2)
111
+ with col1:
112
+ chart1_placeholder = st.empty()
113
+ chart2_placeholder = st.empty()
114
+ with col2:
115
+ chart3_placeholder = st.empty()
116
+ chart4_placeholder = st.empty()
117
+
118
+ # --- Plotting Functions (Unchanged) ---
119
+ def get_safe_range(values, pad_percent=0.1):
120
+ values = pd.Series(values).dropna()
121
+ if values.empty: return (0, 1)
122
+ if len(values) == 1: return (values.iloc[0] * 0.9, values.iloc[0] * 1.1)
123
+ numeric_values = pd.to_numeric(values, errors='coerce').dropna()
124
+ if numeric_values.empty: return (0, 1)
125
+ low, high = np.percentile(numeric_values, [2, 95])
126
+ pad = abs(high - low) * pad_percent
127
+ return max(0, low - pad), high + pad
128
+
129
+ def forecast_plot(df):
130
+ if len(df) < 10: return go.Figure(layout_title_text="Loss Forecast (Need more data)")
131
+ x = pd.to_numeric(df["step"], errors='coerce').dropna().values
132
+ y = pd.to_numeric(df["loss"], errors='coerce').dropna().values
133
+ if len(x) < 2 or len(y) < 2 or len(x) != len(y):
134
+ return go.Figure(layout_title_text="Loss Forecast (Data error)")
135
+
136
+ forecast_x = np.linspace(x[0], x[-1] * 1.5, 300)
137
+ fig = go.Figure()
138
+ fig.add_trace(go.Scatter(x=x, y=y, mode='lines', name="Actual Loss"))
139
+
140
+ for percent, color in [(1, 'orange'), (10, 'green'), (50, 'red')]:
141
+ n = max(5, int(len(x) * percent / 100))
142
+ if len(x) >= n and n >= 2:
143
+ sub_x, sub_y = x[-n:], y[-n:]
144
+ try:
145
+ valid_indices = ~np.isnan(sub_x) & ~np.isnan(sub_y)
146
+ if np.sum(valid_indices) >= 2:
147
+ m, b = np.polyfit(sub_x[valid_indices], sub_y[valid_indices], 1)
148
+ y_fit = m * forecast_x + b
149
+ fig.add_trace(go.Scatter(x=forecast_x, y=y_fit, name=f"{percent}% Trend", line=dict(dash='dot', color=color)))
150
+ except (np.linalg.LinAlgError, ValueError) as e:
151
+ print(f"Warning: Could not fit trend for {percent}%: {e}")
152
+
153
+ fig.update_layout(title="Loss Forecast", xaxis_title="Step", yaxis_title="Loss", legend_title_text='Trend % (Recent)')
154
+ return fig
155
+
156
+ # --- Streamlit Callback (Unchanged) ---
157
+ class StreamlitCallback(TrainerCallback):
158
+ def on_log(self, args, state, control, logs=None, **kwargs):
159
+ if state.is_world_process_zero:
160
+ if logs is not None and "loss" in logs:
161
+ step = state.global_step
162
+ loss = float(logs["loss"]) if isinstance(logs["loss"], (int, float)) else None
163
+ grad = float(logs.get("grad_norm")) if isinstance(logs.get("grad_norm"), (int, float)) else None
164
+
165
+ if loss is not None:
166
+ ppl = math.exp(min(loss, 700))
167
+ log["step"].append(step)
168
+ log["loss"].append(loss)
169
+ log["grad_norm"].append(grad)
170
+ log["perplexity"].append(ppl)
171
+
172
+ df = pd.DataFrame(log).dropna(subset=['step', 'loss'])
173
+ if not df.empty:
174
+ try:
175
+ r1 = get_safe_range(df["loss"])
176
+ r2 = get_safe_range(df["grad_norm"])
177
+ r3 = get_safe_range(df["perplexity"])
178
+
179
+ fig1 = go.Figure().add_trace(go.Scatter(x=df["step"], y=df["loss"], mode='lines'))
180
+ grad_norm_data = df["grad_norm"].dropna()
181
+ if not grad_norm_data.empty:
182
+ fig2 = go.Figure().add_trace(go.Scatter(x=df.loc[grad_norm_data.index, "step"], y=grad_norm_data, mode='lines'))
183
+ else:
184
+ fig2 = go.Figure()
185
+ fig3 = go.Figure().add_trace(go.Scatter(x=df["step"], y=df["perplexity"], mode='lines'))
186
+
187
+ fig1.update_layout(title="Loss", yaxis_range=r1, xaxis_title="Step", yaxis_title="Loss")
188
+ fig2.update_layout(title="Gradient Norm", yaxis_range=r2, xaxis_title="Step", yaxis_title="Grad Norm")
189
+ fig3.update_layout(title="Perplexity", yaxis_range=r3, xaxis_title="Step", yaxis_title="Perplexity")
190
+
191
+ fig4 = forecast_plot(df)
192
+
193
+ chart1_placeholder.plotly_chart(fig1, use_container_width=True, key=f"loss_chart_{step}")
194
+ chart2_placeholder.plotly_chart(fig2, use_container_width=True, key=f"grad_norm_chart_{step}")
195
+ chart3_placeholder.plotly_chart(fig3, use_container_width=True, key=f"perplexity_chart_{step}")
196
+ chart4_placeholder.plotly_chart(fig4, use_container_width=True, key=f"forecast_chart_{step}")
197
+ except Exception as e:
198
+ print(f"Error updating Streamlit charts at step {step}: {e}")
199
+
200
+
201
+ # --- Training args ---
202
+ # Training args remain the same
203
+ args = TrainingArguments(
204
+ output_dir=OUTPUT_DIR,
205
+ per_device_train_batch_size=BATCH_SIZE,
206
+ gradient_accumulation_steps=GRAD_ACC,
207
+ num_train_epochs=EPOCHS,
208
+ learning_rate=LEARNING_RATE,
209
+ lr_scheduler_type='linear',
210
+ warmup_ratio=0.1,
211
+ weight_decay=0.01,
212
+ max_grad_norm=1.0,
213
+ save_strategy="steps",
214
+ save_steps=1000,
215
+ save_total_limit=10,
216
+ logging_strategy="steps",
217
+ logging_steps=10,
218
+ dataloader_num_workers=4,
219
+ bf16=torch.cuda.is_bf16_supported(),
220
+ fp16=not torch.cuda.is_bf16_supported() and torch.cuda.is_available(),
221
+ seed=SEED,
222
+ report_to=["none"],
223
+ # !! Remember to handle checkpoints appropriately for a fresh run !!
224
+ resume_from_checkpoint=False, # Explicitly set to False for clean run
225
+ )
226
+
227
+ # --- Trainer ---
228
+ # Trainer setup remains the same
229
+ trainer = Trainer(
230
+ model=model,
231
+ args=args,
232
+ train_dataset=dataset,
233
+ data_collator=collator,
234
+ callbacks=[StreamlitCallback()]
235
+ )
236
+
237
+ # --- Train ---
238
+ # Train call remains the same
239
+ try:
240
+ # Start training (explicitly not resuming here due to args setting)
241
+ trainer.train() # No need to pass resume_from_checkpoint if set in args
242
+ progress.success("✅ Training complete.")
243
+ st.success("Training finished!")
244
+ except Exception as e:
245
+ st.error(f"Training failed: {e}")
246
+ progress.error("❌ Training stopped due to error.")
train_tokenizer.py ADDED
@@ -0,0 +1,172 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # improved_train_tokenizer_v2.py
2
+
3
+ import os
4
+ import sys
5
+ from tokenizers import Tokenizer, models, pre_tokenizers, decoders, trainers, processors, normalizers
6
+ from transformers import PreTrainedTokenizerFast
7
+
8
+ # --- Configuration ---
9
+ TRAIN_FILES = ["improved_sentences.txt"] # Use the preprocessed file
10
+ VOCAB_SIZE = 32000
11
+ SPECIAL_TOKENS = ["<pad>", "<unk>", "<s>", "</s>", "<mask>"]
12
+ OUTPUT_DIR = "./improved_tokenizer_v2"
13
+
14
+ # --- Input File Check ---
15
+ if not TRAIN_FILES or not os.path.exists(TRAIN_FILES[0]):
16
+ print(f"Error: Training file '{TRAIN_FILES[0]}' not found.")
17
+ sys.exit(1)
18
+
19
+ print(f"Starting tokenizer training...")
20
+ print(f"Training file(s): {TRAIN_FILES}")
21
+ print(f"Target vocab size: {VOCAB_SIZE}")
22
+ print(f"Output directory: {OUTPUT_DIR}")
23
+
24
+ # --- Initialize Tokenizer ---
25
+ # We'll use ByteLevel BPE with proper whitespace handling
26
+ tokenizer = Tokenizer(models.BPE(unk_token="<unk>"))
27
+
28
+ # --- Set Normalizer ---
29
+ # This helps standardize the text before tokenization
30
+ tokenizer.normalizer = normalizers.Sequence([
31
+ normalizers.NFC(), # Unicode normalization
32
+ normalizers.Replace(r"\s+", " ") # Replace multiple spaces with a single space
33
+ ])
34
+
35
+ # --- Set Pre-tokenizer ---
36
+ # This is critical for handling whitespace correctly
37
+ tokenizer.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=True) # Back to True for proper space handling
38
+ print(f"Using pre-tokenizer: ByteLevel(add_prefix_space=True)")
39
+
40
+ # --- Set Decoder ---
41
+ tokenizer.decoder = decoders.ByteLevel()
42
+ print(f"Using decoder: {tokenizer.decoder.__class__.__name__}")
43
+
44
+ # --- Define Trainer ---
45
+ trainer = trainers.BpeTrainer(
46
+ vocab_size=VOCAB_SIZE,
47
+ special_tokens=SPECIAL_TOKENS,
48
+ show_progress=True,
49
+ initial_alphabet=pre_tokenizers.ByteLevel.alphabet(),
50
+ )
51
+
52
+ # --- Train Tokenizer ---
53
+ print("\nTraining the tokenizer model (this might take a while)...")
54
+ try:
55
+ tokenizer.train(files=TRAIN_FILES, trainer=trainer)
56
+ print("Training completed successfully.")
57
+ except Exception as e:
58
+ print(f"\nError during tokenizer training: {e}")
59
+ sys.exit(1)
60
+
61
+ # --- Add Post-processor ---
62
+ tokenizer.post_processor = processors.TemplateProcessing(
63
+ single="<s> $A </s>",
64
+ pair="<s> $A </s> $B </s>",
65
+ special_tokens=[
66
+ ("<s>", tokenizer.token_to_id("<s>")),
67
+ ("</s>", tokenizer.token_to_id("</s>")),
68
+ ],
69
+ )
70
+
71
+ # --- Save Core Tokenizer ---
72
+ os.makedirs(OUTPUT_DIR, exist_ok=True)
73
+ tokenizer_path = os.path.join(OUTPUT_DIR, "tokenizer.json")
74
+ try:
75
+ tokenizer.save(tokenizer_path)
76
+ print(f"\nCore tokenizer saved to: {tokenizer_path}")
77
+ except Exception as e:
78
+ print(f"Error saving core tokenizer: {e}")
79
+ sys.exit(1)
80
+
81
+ # --- Create and Save HF Wrapper ---
82
+ print("\nWrapping tokenizer with PreTrainedTokenizerFast...")
83
+ try:
84
+ hf_tokenizer = PreTrainedTokenizerFast(
85
+ tokenizer_file=tokenizer_path,
86
+ unk_token="<unk>",
87
+ pad_token="<pad>",
88
+ cls_token="<s>",
89
+ sep_token="</s>",
90
+ mask_token="<mask>",
91
+ add_prefix_space=True # Match the pre-tokenizer setting
92
+ )
93
+ hf_tokenizer.save_pretrained(OUTPUT_DIR)
94
+ print(f"Hugging Face compatible tokenizer files saved to: {OUTPUT_DIR}")
95
+ except Exception as e:
96
+ print(f"Error saving Hugging Face tokenizer: {e}")
97
+ sys.exit(1)
98
+
99
+ # --- Verification Step ---
100
+ print("\n--- Verification ---")
101
+ try:
102
+ print(f"Loading tokenizer for verification from: {OUTPUT_DIR}")
103
+ loaded_hf_tokenizer = PreTrainedTokenizerFast.from_pretrained(OUTPUT_DIR)
104
+
105
+ # Test multiple cases, especially those starting with periods or spaces
106
+ test_cases = [
107
+ "Simple sentence.",
108
+ " Sentence starting with space.",
109
+ "Sentence. Another sentence.",
110
+ ". Sentence starting with period.",
111
+ "Word.Word",
112
+ "The quick brown fox jumps over the lazy dog."
113
+ ]
114
+
115
+ print("\n=== Testing with new tokenizer ===")
116
+ for i, text in enumerate(test_cases):
117
+ print(f"\nTest {i+1}: '{text}'")
118
+ tokens = loaded_hf_tokenizer.tokenize(text)
119
+ print(f"Tokens: {tokens}")
120
+
121
+ encoded = loaded_hf_tokenizer.encode(text, add_special_tokens=True)
122
+ decoded = loaded_hf_tokenizer.decode(encoded, skip_special_tokens=True)
123
+ print(f"Encoded: {encoded}")
124
+ print(f"Decoded: '{decoded}'")
125
+
126
+ # Check if tokenization properly preserves content
127
+ if text.strip() == decoded.strip():
128
+ print("✓ Encoding/decoding preserved text content")
129
+ else:
130
+ print(f"⚠ Warning: Text content changed during encoding/decoding")
131
+ print(f" Original: '{text}'")
132
+ print(f" Decoded: '{decoded}'")
133
+
134
+ # Check first token distributions
135
+ print("\n=== First Position Token Analysis ===")
136
+ print("Analyzing first token after <s> for potential bias...")
137
+
138
+ # Simplified analysis of first token (just for demonstration)
139
+ from collections import Counter
140
+ first_token_counter = Counter()
141
+
142
+ with open(TRAIN_FILES[0], 'r', encoding='utf-8') as f:
143
+ for i, line in enumerate(f):
144
+ if i >= 100: # Just check first 100 lines
145
+ break
146
+ line = line.strip()
147
+ if not line:
148
+ continue
149
+
150
+ encoded = loaded_hf_tokenizer.encode(line, add_special_tokens=True)
151
+ if len(encoded) > 1: # Make sure there's at least one token after <s>
152
+ first_token_id = encoded[1]
153
+ first_token_counter[first_token_id] += 1
154
+
155
+ total = sum(first_token_counter.values())
156
+ if total > 0:
157
+ print(f"\nTop 5 tokens at first position (after <s>) from {total} samples:")
158
+ for token_id, count in first_token_counter.most_common(5):
159
+ token_text = loaded_hf_tokenizer.decode([token_id])
160
+ percentage = (count / total) * 100
161
+ print(f"Token: '{token_text}' (ID: {token_id}) | Count: {count} | {percentage:.2f}%")
162
+
163
+ # Specifically check period token
164
+ period_id = loaded_hf_tokenizer.encode('.', add_special_tokens=False)[0]
165
+ period_count = first_token_counter.get(period_id, 0)
166
+ period_percentage = (period_count / total) * 100 if total > 0 else 0
167
+ print(f"\nPeriod token ('.', ID: {period_id}) at first position: {period_count} times ({period_percentage:.2f}%)")
168
+
169
+ except Exception as e:
170
+ print(f"Error during verification: {e}")
171
+
172
+ print("\n--- Tokenizer training script finished ---")
trainer_state.json ADDED
The diff for this file is too large to render. See raw diff
 
training_args.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b4255e8645ef79d5d89578e0550408329539962f036c0ac03649b791aa1cf604
3
+ size 5304