hadikhamoud commited on
Commit
1328c9a
·
verified ·
1 Parent(s): 4ac1aeb

Upload folder using huggingface_hub

Browse files
1_Pooling/config.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "word_embedding_dimension": 768,
3
+ "pooling_mode_cls_token": false,
4
+ "pooling_mode_mean_tokens": true,
5
+ "pooling_mode_max_tokens": false,
6
+ "pooling_mode_mean_sqrt_len_tokens": false,
7
+ "pooling_mode_weightedmean_tokens": false,
8
+ "pooling_mode_lasttoken": false,
9
+ "include_prompt": true
10
+ }
README.md ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ library_name: sentence-transformers
3
+ pipeline_tag: sentence-similarity
4
+ tags:
5
+ - sentence-transformers
6
+ - sentence-similarity
7
+ - feature-extraction
8
+ - dense
9
+ - arabic
10
+ ---
11
+
12
+ # NeoAraBERT-STS
13
+
14
+ Sentence-transformers model for Arabic semantic textual similarity.
15
+
16
+ ## Usage
17
+
18
+ ```bash
19
+ pip install -U sentence-transformers torch
20
+ ```
21
+
22
+ ```python
23
+ import torch
24
+ from sentence_transformers import SentenceTransformer
25
+
26
+ model_name = "U4RASD/NeoAraBERT-STS"
27
+
28
+ finetuned_model = SentenceTransformer(
29
+ model_name,
30
+ model_kwargs={"trust_remote_code": True, "torch_dtype": torch.float32},
31
+ tokenizer_kwargs={"trust_remote_code": True},
32
+ config_kwargs={"trust_remote_code": True},
33
+ )
34
+ finetuned_model.max_seq_length = 512
35
+
36
+ sentences = [
37
+ "التقارير بدأت تصل في وقت متأخر من هذا العام ويتم مراجعتها",
38
+ "يتم مراجعة التقارير في أواخر هذا العام.",
39
+ "لم يكن هناك تقارير هذا العام على الإطلاق.",
40
+ ]
41
+
42
+ embeddings = finetuned_model.encode(sentences)
43
+ similarities = finetuned_model.similarity(embeddings, embeddings)
44
+
45
+ print(embeddings.shape)
46
+ print(similarities)
47
+ ```
48
+
49
+ ## Model Type
50
+
51
+ - **Model type:** Sentence Transformer
52
+ - **Task:** Sentence similarity / semantic textual similarity
53
+ - **Language:** Arabic
54
+ - **Embedding size:** 768
55
+ - **Max sequence length:** 512
56
+ - **Similarity function:** Cosine similarity
acrps_light_stemming.py ADDED
@@ -0,0 +1,420 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Fast Arabic Stemmer
3
+
4
+ Optimized stemmer for Arabic text with the following optimizations:
5
+ 1. MLE word-level caching - avoids redundant disambiguation
6
+ 2. O(1) set lookups instead of O(n) list lookups
7
+ 3. String operations instead of byte encoding
8
+ 4. Reduced redundant dediac_ar() calls
9
+ 5. Fast Arabic-focused tokenizer regex (1000x faster than full Unicode)
10
+
11
+ Usage:
12
+ from stemmer import stem, create_stemmer
13
+
14
+ # Simple usage (uses module-level stemmer instance)
15
+ result = stem("وَالْكِتَابُ الْجَمِيلُ")
16
+
17
+ # With diacritics preservation
18
+ result = stem("وَالْكِتَابُ الْجَمِيلُ", apply_diacritics=True)
19
+
20
+ # Or create your own instance
21
+ stemmer = create_stemmer()
22
+ result = stemmer.stem("النص العربي")
23
+ """
24
+
25
+ import re
26
+ from collections import deque
27
+ from types import MethodType
28
+
29
+ from camel_tools.disambig.mle import MLEDisambiguator
30
+ from camel_tools.utils.dediac import dediac_ar
31
+ from .constants import list_al_t, list_al, list_t
32
+
33
+ __all__ = ["stem", "create_stemmer", "Stemmer"]
34
+
35
+
36
+ _SET_AL_T = frozenset(list_al_t)
37
+ _SET_AL = frozenset(list_al)
38
+ _SET_T = frozenset(list_t)
39
+
40
+ _ALEF_LAM = "ال"
41
+ _TAA_MARBOUTA_ATTACHED = "ة"
42
+ _TAA_MARBOUTA_DETACHED = "\ufe93"
43
+ _HAA_ATTACHED = "ه"
44
+
45
+ _PATTERN_LAM_PLUS = "ل[+]"
46
+ _PATTERN_ALEF_LAM_PLUS = "ال[+]"
47
+ _REPLACEMENT_LAM_LAM_PLUS = "لل[+]"
48
+
49
+ _DIACRITIC_MARKS = frozenset(
50
+ {
51
+ "\u064b",
52
+ "\u064c",
53
+ "\u064d",
54
+ "\u064e",
55
+ "\u064f",
56
+ "\u0650",
57
+ "\u0651",
58
+ "\u0652",
59
+ "\u0670",
60
+ }
61
+ )
62
+
63
+
64
+ _ARABIC_WORD_CHARS = (
65
+ r"\u0621-\u063A" # Arabic letters (hamza to ghain)
66
+ r"\u0641-\u064A" # Arabic letters (fa to ya)
67
+ r"\u064B-\u0652" # Arabic diacritics
68
+ r"\u0653-\u0655" # Combining marks
69
+ r"\u0670" # Superscript alef
70
+ r"\u0671-\u06D3" # Extended Arabic letters
71
+ r"\u06D5-\u06FF" # More letters and marks
72
+ r"\u0750-\u077F" # Arabic Supplement
73
+ r"\u08A0-\u08FF" # Arabic Extended-A
74
+ r"\uFB50-\uFDFF" # Arabic Presentation Forms-A
75
+ r"\uFE70-\uFEFF" # Arabic Presentation Forms-B
76
+ )
77
+ _LATIN_NUM = r"a-zA-Z0-9"
78
+ _ARABIC_INDIC_DIGITS = r"\u0660-\u0669"
79
+
80
+ _WORD_PATTERN = f"[{_ARABIC_WORD_CHARS}{_LATIN_NUM}{_ARABIC_INDIC_DIGITS}]+"
81
+ _PUNCT_PATTERN = f"[^{_ARABIC_WORD_CHARS}{_LATIN_NUM}{_ARABIC_INDIC_DIGITS}\\s]"
82
+ _WHITESPACE_PATTERN = r"\s+"
83
+
84
+ _TOKENIZE_RE = re.compile(f"{_WORD_PATTERN}|{_PUNCT_PATTERN}|{_WHITESPACE_PATTERN}")
85
+ _NORM_TATWEEL_RE = re.compile(r"\u0640")
86
+ _NORM_ZERO_WIDTH_RE = re.compile(r"[\u200B-\u200D\u200E\u200F\uFEFF]")
87
+
88
+
89
+ def _normalize_for_stem(text: str) -> str:
90
+ text = _NORM_TATWEEL_RE.sub("", text)
91
+ text = _NORM_ZERO_WIDTH_RE.sub("", text)
92
+ return text
93
+
94
+
95
+ def _tokenize(text):
96
+ return _TOKENIZE_RE.findall(text)
97
+
98
+
99
+ def _merge_tokens(tokens, original_word):
100
+ parts = []
101
+ for tok in tokens:
102
+ if tok == "[+]":
103
+ parts.append("_")
104
+ elif tok.endswith("[+]"):
105
+ parts.append(tok[:-3])
106
+ elif tok.startswith("[+]"):
107
+ parts.append(tok[3:])
108
+ elif tok.endswith("+"):
109
+ parts.append(tok[:-1])
110
+ elif tok.startswith("+"):
111
+ parts.append(tok[1:])
112
+ else:
113
+ parts.append(tok)
114
+ return "".join(parts)
115
+
116
+
117
+ def _has_diacritics(word):
118
+ for char in word:
119
+ if char in _DIACRITIC_MARKS:
120
+ return True
121
+ return False
122
+
123
+
124
+ def _apply_diacritics_to_segments(segments, diacritized_word):
125
+ result = []
126
+
127
+ leading_diacritics = []
128
+ i = 0
129
+ while i < len(diacritized_word) and diacritized_word[i] in _DIACRITIC_MARKS:
130
+ leading_diacritics.append(diacritized_word[i])
131
+ i += 1
132
+
133
+ diacritic_index = len(leading_diacritics)
134
+
135
+ for segment_idx, segment in enumerate(segments):
136
+ if segment == "[+]":
137
+ result.append(segment)
138
+ else:
139
+ diacritized_segment = []
140
+ if segment_idx == 0 and leading_diacritics:
141
+ diacritized_segment.extend(leading_diacritics)
142
+
143
+ i = 0
144
+ while i < len(segment):
145
+ char = segment[i]
146
+ if segment[i : i + 3] == "[+]":
147
+ diacritized_segment.append("[+]")
148
+ i += 3
149
+ continue
150
+
151
+ if diacritic_index < len(diacritized_word):
152
+ while (
153
+ diacritic_index < len(diacritized_word)
154
+ and diacritized_word[diacritic_index] in _DIACRITIC_MARKS
155
+ ):
156
+ diacritic_index += 1
157
+
158
+ if (
159
+ diacritic_index < len(diacritized_word)
160
+ and diacritized_word[diacritic_index] == char
161
+ ):
162
+ diacritized_segment.append(char)
163
+ diacritic_index += 1
164
+ while (
165
+ diacritic_index < len(diacritized_word)
166
+ and diacritized_word[diacritic_index] in _DIACRITIC_MARKS
167
+ ):
168
+ diacritized_segment.append(
169
+ diacritized_word[diacritic_index]
170
+ )
171
+ diacritic_index += 1
172
+ else:
173
+ diacritized_segment.append(char)
174
+ else:
175
+ diacritized_segment.append(char)
176
+ i += 1
177
+
178
+ result.append("".join(diacritized_segment))
179
+ return result
180
+
181
+
182
+ def _merge_alef_and_alef_lam(input_list):
183
+ modified_list = []
184
+ i = 0
185
+ while i < len(input_list):
186
+ if i < len(input_list) - 1:
187
+ if (
188
+ input_list[i] == _PATTERN_LAM_PLUS
189
+ and input_list[i + 1] == _PATTERN_ALEF_LAM_PLUS
190
+ ):
191
+ modified_list.append(_REPLACEMENT_LAM_LAM_PLUS)
192
+ i += 2
193
+ continue
194
+ modified_list.append(input_list[i])
195
+ i += 1
196
+ return modified_list
197
+
198
+
199
+ def _process_NOAN_word(word):
200
+ starts_with_al = word.startswith(_ALEF_LAM)
201
+ ends_with_ta = word.endswith(_TAA_MARBOUTA_ATTACHED) or word.endswith(
202
+ _TAA_MARBOUTA_DETACHED
203
+ )
204
+
205
+ if starts_with_al and ends_with_ta:
206
+ if word in _SET_AL_T:
207
+ stripped_word = word[2:-1]
208
+ first_part = word[0:2] + "[+]"
209
+ last_part = "[+]" + word[-1]
210
+ return [first_part, stripped_word, last_part]
211
+
212
+ if starts_with_al:
213
+ if word in _SET_AL:
214
+ stripped_word = word[2:]
215
+ first_part = word[0:2] + "[+]"
216
+ return [first_part, stripped_word]
217
+
218
+ if ends_with_ta:
219
+ if word in _SET_T:
220
+ stripped_word = word[:-1]
221
+ last_part = "[+]" + word[-1]
222
+ return [stripped_word, last_part]
223
+
224
+ return [word]
225
+
226
+
227
+ def _split_token_on_t(list_toks):
228
+ new_list = []
229
+ for token in list_toks:
230
+ last_char = token[-1] if token else ""
231
+ if last_char in (_TAA_MARBOUTA_ATTACHED, _TAA_MARBOUTA_DETACHED, _HAA_ATTACHED):
232
+ if token == _HAA_ATTACHED:
233
+ new_list.append("[+]" + _TAA_MARBOUTA_ATTACHED)
234
+ else:
235
+ new_list.append(token[:-1])
236
+ new_list.append("[+]" + token[-1])
237
+ else:
238
+ new_list.append(token)
239
+ return new_list
240
+
241
+
242
+ def _replace_separator(toks):
243
+ for i, tok in enumerate(toks):
244
+ if tok.startswith("+"):
245
+ toks[i] = "[+]" + tok[1:]
246
+ if tok.endswith("+"):
247
+ toks[i] = tok[:-1] + "[+]"
248
+ return toks
249
+
250
+
251
+ def _morph_tokenize(
252
+ words, disambiguator, scheme="d3tok", split=True, apply_diacritics=True
253
+ ):
254
+ disambig_words = disambiguator.disambiguate(words)
255
+ result = deque()
256
+
257
+ for original, disambig_word in zip(words, disambig_words):
258
+ scored_analyses = disambig_word.analyses
259
+ original_word = original
260
+ dediac_word = dediac_ar(original_word)
261
+ word_has_diacritics = _has_diacritics(original_word)
262
+
263
+ if not scored_analyses:
264
+ result.append(original_word)
265
+ continue
266
+
267
+ analysis = scored_analyses[0].analysis
268
+ tok_raw = analysis.get(scheme, None)
269
+ tok = dediac_ar(tok_raw) if tok_raw is not None else None
270
+
271
+ ends_with_ta = dediac_word.endswith(
272
+ _TAA_MARBOUTA_ATTACHED
273
+ ) or dediac_word.endswith(_TAA_MARBOUTA_DETACHED)
274
+
275
+ if ends_with_ta:
276
+ if tok is not None:
277
+ toks = tok.split("_")
278
+ toks = _split_token_on_t(toks)
279
+ toks = _replace_separator(toks)
280
+ toks = _merge_alef_and_alef_lam(toks)
281
+ merged_toks = _merge_tokens(toks, dediac_word)
282
+
283
+ if merged_toks == dediac_word and len(toks) > 1:
284
+ if apply_diacritics and word_has_diacritics:
285
+ toks = _apply_diacritics_to_segments(toks, original)
286
+ result.extend(toks)
287
+ continue
288
+ else:
289
+ result.append(original_word)
290
+ continue
291
+
292
+ if tok is None or "NOAN" in tok:
293
+ noan_toks = _process_NOAN_word(dediac_word)
294
+ if apply_diacritics and word_has_diacritics:
295
+ noan_toks = _apply_diacritics_to_segments(noan_toks, original)
296
+ result.extend(noan_toks)
297
+
298
+ elif split:
299
+ toks = tok.split("_")
300
+ toks = _replace_separator(toks)
301
+ toks = _merge_alef_and_alef_lam(toks)
302
+ merged_toks = _merge_tokens(toks, dediac_word)
303
+
304
+ if merged_toks == dediac_word and len(toks) > 1:
305
+ if apply_diacritics and word_has_diacritics:
306
+ toks = _apply_diacritics_to_segments(toks, original)
307
+ result.extend(toks)
308
+ else:
309
+ result.append(original_word)
310
+
311
+ else:
312
+ if tok == dediac_word:
313
+ result.append(original_word)
314
+ else:
315
+ result.append(original_word)
316
+
317
+ return list(result)
318
+
319
+
320
+ def _create_cached_score_fn(mle_instance):
321
+ cache = {}
322
+ original_method = mle_instance._scored_analyses
323
+
324
+ def cached_score_fn(self, word_dd):
325
+ if word_dd in cache:
326
+ return cache[word_dd]
327
+ result = original_method(word_dd)
328
+ cache[word_dd] = result
329
+ return result
330
+
331
+ return cache, MethodType(cached_score_fn, mle_instance)
332
+
333
+
334
+ # ============================================================================
335
+
336
+
337
+ class Stemmer:
338
+ """
339
+ Fast Arabic stemmer with MLE caching.
340
+
341
+ Example:
342
+ stemmer = Stemmer()
343
+ result = stemmer.stem("النص العربي")
344
+ result = stemmer.stem("وَالْكِتَابُ", apply_diacritics=True)
345
+ """
346
+
347
+ def __init__(self):
348
+ """Initialize the stemmer with MLE disambiguator and caching."""
349
+ self._mle = MLEDisambiguator.pretrained("calima-msa-r13")
350
+ self._cache, cached_method = _create_cached_score_fn(self._mle)
351
+ self._mle._score_fn = cached_method
352
+
353
+ def stem(self, text: str, apply_diacritics: bool = False) -> str:
354
+ """
355
+ Stem Arabic text.
356
+
357
+ Args:
358
+ text: Arabic text to stem.
359
+ apply_diacritics: If True, preserve diacritics from input in output.
360
+ If False (default), output will be without diacritics.
361
+
362
+ Returns:
363
+ Stemmed text with morphological segmentation markers [+].
364
+ """
365
+ text = _normalize_for_stem(text)
366
+ tokens = _tokenize(text)
367
+ stemmed_tokens = _morph_tokenize(
368
+ tokens, self._mle, apply_diacritics=apply_diacritics
369
+ )
370
+ return "".join(stemmed_tokens)
371
+
372
+ def clear_cache(self):
373
+ """Clear the disambiguation cache."""
374
+ self._cache.clear()
375
+
376
+ @property
377
+ def cache_size(self) -> int:
378
+ """Return the number of cached word disambiguations."""
379
+ return len(self._cache)
380
+
381
+
382
+ def create_stemmer() -> Stemmer:
383
+ """
384
+ Create a new Stemmer instance.
385
+
386
+ Returns:
387
+ A new Stemmer instance with its own cache.
388
+ """
389
+ return Stemmer()
390
+
391
+
392
+ _default_stemmer = None
393
+
394
+
395
+ def stem(text: str, apply_diacritics: bool = False) -> str:
396
+ """
397
+ Stem Arabic text using a shared stemmer instance.
398
+
399
+ This is a convenience function that uses a module-level stemmer.
400
+ For better control over caching, create your own Stemmer instance.
401
+
402
+ Args:
403
+ text: Arabic text to stem.
404
+ apply_diacritics: If True, preserve diacritics from input in output.
405
+ If False (default), output will be without diacritics.
406
+
407
+ Returns:
408
+ Stemmed text with morphological segmentation markers [+].
409
+
410
+ Example:
411
+ >>> stem("والكتاب الجميل")
412
+ 'و[+]ال[+]كتاب ال[+]جميل'
413
+
414
+ >>> stem("وَالْكِتَابُ", apply_diacritics=True)
415
+ 'وَ[+]الْ[+]كِتَابُ'
416
+ """
417
+ global _default_stemmer
418
+ if _default_stemmer is None:
419
+ _default_stemmer = Stemmer()
420
+ return _default_stemmer.stem(text, apply_diacritics=apply_diacritics)
config.json ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "NeoBERT"
4
+ ],
5
+ "auto_map": {
6
+ "AutoConfig": "model.NeoBERTConfig",
7
+ "AutoModel": "model.NeoBERT",
8
+ "AutoModelForMaskedLM": "model.NeoBERTLMHead",
9
+ "AutoModelForSequenceClassification": "model.NeoBERTForSequenceClassification"
10
+ },
11
+ "classifier_init_range": 0.02,
12
+ "decoder_init_range": 0.02,
13
+ "dim_head": 64,
14
+ "dtype": "float32",
15
+ "embedding_init_range": 0.02,
16
+ "hidden_size": 768,
17
+ "intermediate_size": 3072,
18
+ "kwargs": {
19
+ "architectures": [
20
+ "NeoBERTLMHead"
21
+ ],
22
+ "attn_implementation": null,
23
+ "auto_map": {
24
+ "AutoConfig": "model.NeoBERTConfig",
25
+ "AutoModel": "model.NeoBERT",
26
+ "AutoModelForMaskedLM": "model.NeoBERTLMHead",
27
+ "AutoModelForSequenceClassification": "model.NeoBERTForSequenceClassification"
28
+ },
29
+ "classifier_init_range": 0.02,
30
+ "dim_head": 64,
31
+ "kwargs": {
32
+ "classifier_init_range": 0.02,
33
+ "trust_remote_code": true
34
+ },
35
+ "model_type": "neobert",
36
+ "torch_dtype": "float32",
37
+ "transformers_version": "4.48.2",
38
+ "trust_remote_code": true
39
+ },
40
+ "max_length": 1024,
41
+ "model_type": "neobert",
42
+ "norm_eps": 1e-05,
43
+ "num_attention_heads": 12,
44
+ "num_hidden_layers": 28,
45
+ "pad_token_id": 0,
46
+ "transformers_version": "4.57.0",
47
+ "trust_remote_code": true,
48
+ "vocab_size": 65000
49
+ }
config_sentence_transformers.json ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "model_type": "SentenceTransformer",
3
+ "__version__": {
4
+ "sentence_transformers": "5.2.3",
5
+ "transformers": "4.57.0",
6
+ "pytorch": "2.11.0+cu128"
7
+ },
8
+ "prompts": {
9
+ "query": "",
10
+ "document": ""
11
+ },
12
+ "default_prompt_name": null,
13
+ "similarity_fn_name": "cosine"
14
+ }
constants.py ADDED
The diff for this file is too large to render. See raw diff
 
model.py ADDED
@@ -0,0 +1,434 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # From https://github.com/facebookresearch/llama/blob/main/llama/model.py
2
+
3
+ import torch
4
+ from torch import nn
5
+
6
+ from torch.nn import BCEWithLogitsLoss, CrossEntropyLoss, MSELoss
7
+ from torch.nn.functional import scaled_dot_product_attention
8
+
9
+ from typing import Optional, Tuple
10
+ import numpy as np
11
+
12
+ from xformers.ops import SwiGLU
13
+
14
+ try:
15
+ from flash_attn.flash_attn_interface import flash_attn_varlen_func
16
+
17
+ FLASH_ATTN_AVAILABLE = True
18
+ except ImportError:
19
+ FLASH_ATTN_AVAILABLE = False
20
+
21
+ from transformers import (
22
+ PreTrainedModel,
23
+ PretrainedConfig,
24
+ DataCollatorForLanguageModeling,
25
+ )
26
+ from transformers.modeling_outputs import (
27
+ BaseModelOutput,
28
+ MaskedLMOutput,
29
+ SequenceClassifierOutput,
30
+ )
31
+
32
+ from .rotary import precompute_freqs_cis, apply_rotary_emb
33
+
34
+
35
+ class DataCollatorWithPacking(DataCollatorForLanguageModeling):
36
+ def __init__(self, pack_sequences=False, **kwargs):
37
+ super().__init__(**kwargs)
38
+ self.pack_sequences = pack_sequences
39
+
40
+ def __call__(self, batch):
41
+ if self.pack_sequences:
42
+ # Add position_ids if not present
43
+ if "position_ids" not in batch[0]:
44
+ for item in batch:
45
+ item["position_ids"] = list(range(len(item["input_ids"])))
46
+
47
+ # Pack the sequences into a single list
48
+ input_ids_list = [item["input_ids"] for item in batch]
49
+ position_ids_list = [item["position_ids"] for item in batch]
50
+ seqlens = np.array([0] + [len(ids) for ids in input_ids_list])
51
+
52
+ packed_batch = {
53
+ "position_ids": np.concatenate(position_ids_list, axis=0),
54
+ "input_ids": np.concatenate(input_ids_list, axis=0),
55
+ "cu_seqlens": np.cumsum(seqlens),
56
+ "max_seqlen": max(seqlens),
57
+ }
58
+
59
+ batch = super().__call__([packed_batch])
60
+ batch["cu_seqlens"] = batch["cu_seqlens"].to(torch.int32).squeeze()
61
+ else:
62
+ batch = super().__call__(batch)
63
+ batch["attention_mask"] = batch["attention_mask"].to(torch.bool)
64
+
65
+ return batch
66
+
67
+
68
+ class NeoBERTConfig(PretrainedConfig):
69
+ model_type = "neobert"
70
+
71
+ # All config parameters must have a default value.
72
+ def __init__(
73
+ self,
74
+ hidden_size: int = 768,
75
+ num_hidden_layers: int = 28,
76
+ num_attention_heads: int = 12,
77
+ intermediate_size: int = 3072,
78
+ embedding_init_range: float = 0.02,
79
+ decoder_init_range: float = 0.02,
80
+ norm_eps: float = 1e-05,
81
+ vocab_size: int = 65000,
82
+ pad_token_id: int = 0,
83
+ max_length: int = 1024,
84
+ **kwargs,
85
+ ):
86
+ super().__init__(**kwargs)
87
+
88
+ self.hidden_size = hidden_size
89
+ self.num_hidden_layers = num_hidden_layers
90
+ self.num_attention_heads = num_attention_heads
91
+ if hidden_size % num_attention_heads != 0:
92
+ raise ValueError("Hidden size must be divisible by the number of heads.")
93
+ self.dim_head = hidden_size // num_attention_heads
94
+ self.intermediate_size = intermediate_size
95
+ self.embedding_init_range = embedding_init_range
96
+ self.decoder_init_range = decoder_init_range
97
+ self.norm_eps = norm_eps
98
+ self.vocab_size = vocab_size
99
+ self.pad_token_id = pad_token_id
100
+ self.max_length = max_length
101
+ self.kwargs = kwargs
102
+
103
+
104
+ class EncoderBlock(nn.Module):
105
+ """Transformer encoder block."""
106
+
107
+ def __init__(self, config: NeoBERTConfig):
108
+ super().__init__()
109
+
110
+ self.config = config
111
+
112
+ # Attention
113
+ self.qkv = nn.Linear(in_features=config.hidden_size, out_features=config.hidden_size * 3, bias=False)
114
+ self.wo = nn.Linear(in_features=config.hidden_size, out_features=config.hidden_size, bias=False)
115
+
116
+ # Feedforward network
117
+ multiple_of = 8
118
+ intermediate_size = int(2 * config.intermediate_size / 3)
119
+ intermediate_size = multiple_of * ((intermediate_size + multiple_of - 1) // multiple_of)
120
+ self.ffn = SwiGLU(config.hidden_size, intermediate_size, config.hidden_size, bias=False)
121
+
122
+ # Layer norms
123
+ self.attention_norm = nn.RMSNorm(config.hidden_size, config.norm_eps)
124
+ self.ffn_norm = nn.RMSNorm(config.hidden_size, config.norm_eps)
125
+
126
+ def forward(
127
+ self,
128
+ x: torch.Tensor,
129
+ attention_mask: torch.Tensor,
130
+ freqs_cis: torch.Tensor,
131
+ output_attentions: bool,
132
+ max_seqlen: int = None,
133
+ cu_seqlens: torch.Tensor = None,
134
+ ):
135
+ # Attention
136
+ attn_output, attn_weights = self._att_block(
137
+ self.attention_norm(x), attention_mask, freqs_cis, output_attentions, max_seqlen, cu_seqlens
138
+ )
139
+
140
+ # Residual
141
+ x = x + attn_output
142
+
143
+ # Feed-forward
144
+ x = x + self.ffn(self.ffn_norm(x))
145
+
146
+ return x, attn_weights
147
+
148
+ def _att_block(
149
+ self,
150
+ x: torch.Tensor,
151
+ attention_mask: torch.Tensor,
152
+ freqs_cis: torch.Tensor,
153
+ output_attentions: bool,
154
+ max_seqlen: int = None,
155
+ cu_seqlens: torch.Tensor = None,
156
+ ):
157
+ batch_size, seq_len, _ = x.shape
158
+
159
+ xq, xk, xv = self.qkv(x).view(batch_size, seq_len, self.config.num_attention_heads, self.config.dim_head * 3).chunk(3, axis=-1)
160
+
161
+ xq, xk = apply_rotary_emb(xq, xk, freqs_cis)
162
+
163
+ # Attn block
164
+ attn_weights = None
165
+
166
+ # Flash attention if the tensors are packed
167
+ if cu_seqlens is not None:
168
+ attn = flash_attn_varlen_func(
169
+ q=xq.squeeze(0),
170
+ k=xk.squeeze(0),
171
+ v=xv.squeeze(0),
172
+ cu_seqlens_q=cu_seqlens,
173
+ cu_seqlens_k=cu_seqlens,
174
+ max_seqlen_q=max_seqlen,
175
+ max_seqlen_k=max_seqlen,
176
+ dropout_p=0.0,
177
+ causal=False,
178
+ )
179
+ # Eager attention if attention weights are needed in the output
180
+ elif output_attentions:
181
+ attn_weights = xq.permute(0, 2, 1, 3) @ xk.permute(0, 2, 3, 1) / (xq.size(-1) ** 0.5)
182
+ if attention_mask is not None:
183
+ attn_weights = attn_weights * attention_mask
184
+ attn_weights = attn_weights.softmax(-1)
185
+ attn = attn_weights @ xv.permute(0, 2, 1, 3)
186
+ attn = attn.transpose(1, 2)
187
+ # Fall back to SDPA otherwise
188
+ else:
189
+ attn = scaled_dot_product_attention(
190
+ query=xq.transpose(1, 2),
191
+ key=xk.transpose(1, 2),
192
+ value=xv.transpose(1, 2),
193
+ attn_mask=attention_mask.bool(),
194
+ dropout_p=0,
195
+ ).transpose(1, 2)
196
+
197
+ return self.wo(attn.reshape(batch_size, seq_len, self.config.num_attention_heads * self.config.dim_head)), attn_weights
198
+
199
+
200
+ class NeoBERTPreTrainedModel(PreTrainedModel):
201
+ config_class = NeoBERTConfig
202
+ base_model_prefix = "model"
203
+ _supports_cache_class = True
204
+
205
+ def _init_weights(self, module):
206
+ if isinstance(module, nn.Linear):
207
+ module.weight.data.uniform_(-self.config.decoder_init_range, self.config.decoder_init_range)
208
+ elif isinstance(module, nn.Embedding):
209
+ module.weight.data.uniform_(-self.config.embedding_init_range, self.config.embedding_init_range)
210
+
211
+
212
+ class NeoBERT(NeoBERTPreTrainedModel):
213
+ config_class = NeoBERTConfig
214
+
215
+ def __init__(self, config: NeoBERTConfig):
216
+ super().__init__(config)
217
+
218
+ self.config = config
219
+
220
+ self.encoder = nn.Embedding(config.vocab_size, config.hidden_size, padding_idx=config.pad_token_id)
221
+
222
+ # Ensures freqs_cis is moved to the same devices as the model. Non-persistent buffers are not saved in the state_dict.
223
+ freqs_cis = precompute_freqs_cis(config.hidden_size // config.num_attention_heads, config.max_length)
224
+ self.register_buffer("freqs_cis", freqs_cis, persistent=False)
225
+
226
+ self.transformer_encoder = nn.ModuleList()
227
+ for _ in range(config.num_hidden_layers):
228
+ self.transformer_encoder.append(EncoderBlock(config))
229
+
230
+ self.layer_norm = nn.RMSNorm(config.hidden_size, config.norm_eps)
231
+
232
+ # Initialize weights and apply final processing
233
+ self.post_init()
234
+
235
+ def forward(
236
+ self,
237
+ input_ids: Optional[torch.Tensor] = None,
238
+ position_ids: torch.Tensor = None,
239
+ max_seqlen: int = None,
240
+ cu_seqlens: torch.Tensor = None,
241
+ attention_mask: torch.Tensor = None,
242
+ inputs_embeds: Optional[torch.Tensor] = None,
243
+ output_hidden_states: bool = False,
244
+ output_attentions: bool = False,
245
+ **kwargs,
246
+ ):
247
+ # Initialize
248
+ hidden_states, attentions = [], []
249
+
250
+ if (input_ids is None) ^ (inputs_embeds is not None):
251
+ raise ValueError("You must specify exactly one of input_ids or inputs_embeds")
252
+
253
+ # Expand and repeat: (Batch, Length) -> (Batch, Heads, Length, Length)
254
+ if attention_mask is not None:
255
+ attention_mask = attention_mask.unsqueeze(1).unsqueeze(1).repeat(1, self.config.num_attention_heads, attention_mask.size(-1), 1)
256
+
257
+ # Checks to be done if inputs are packed sequences
258
+ if cu_seqlens is not None:
259
+ assert (
260
+ FLASH_ATTN_AVAILABLE
261
+ ), "Flash-attention is not available. Please ''pip install flash_attn'', or provide un-packed sequences."
262
+ assert not output_attentions, "Output attentions is not supported when sequences are packed."
263
+ assert max_seqlen is not None, "Missing max_seqlen. It must be provided when cu_seqlens are not None."
264
+ assert (input_ids if input_ids is not None else inputs_embeds).shape[
265
+ 0
266
+ ] == 1, "Cumulative sequence lengths are provided but inputs are not packed."
267
+ assert (
268
+ input_ids if input_ids is not None else inputs_embeds
269
+ ).is_cuda, "Packing uses an implementation of flash-attention and is only supported on GPU."
270
+
271
+ # RoPE
272
+ freqs_cis = (
273
+ self.freqs_cis[position_ids]
274
+ if position_ids is not None
275
+ else self.freqs_cis[: (input_ids if input_ids is not None else inputs_embeds).shape[1]].unsqueeze(0)
276
+ )
277
+
278
+ # Embedding
279
+ x = self.encoder(input_ids) if input_ids is not None else inputs_embeds
280
+
281
+ # Transformer encoder
282
+ for layer in self.transformer_encoder:
283
+ x, attn = layer(x, attention_mask, freqs_cis, output_attentions, max_seqlen, cu_seqlens)
284
+ if output_hidden_states:
285
+ hidden_states.append(x)
286
+ if output_attentions:
287
+ attentions.append(attn)
288
+
289
+ # Final normalization layer
290
+ x = self.layer_norm(x)
291
+
292
+ # Return the output of the last hidden layer
293
+ return BaseModelOutput(
294
+ last_hidden_state=x,
295
+ hidden_states=hidden_states if output_hidden_states else None,
296
+ attentions=attentions if output_attentions else None,
297
+ )
298
+
299
+
300
+ class NeoBERTLMHead(NeoBERTPreTrainedModel):
301
+ config_class = NeoBERTConfig
302
+
303
+ def __init__(self, config: NeoBERTConfig):
304
+ super().__init__(config)
305
+
306
+ self.config = config
307
+
308
+ self.model = NeoBERT(config)
309
+ self.decoder = nn.Linear(config.hidden_size, config.vocab_size)
310
+
311
+ self.post_init()
312
+
313
+ def forward(
314
+ self,
315
+ input_ids: torch.Tensor,
316
+ position_ids: torch.Tensor = None,
317
+ max_seqlen: int = None,
318
+ cu_seqlens: torch.Tensor = None,
319
+ attention_mask: torch.Tensor = None,
320
+ output_hidden_states: bool = False,
321
+ output_attentions: bool = False,
322
+ **kwargs,
323
+ ):
324
+
325
+ output = self.model.forward(
326
+ input_ids=input_ids,
327
+ position_ids=position_ids,
328
+ max_seqlen=max_seqlen,
329
+ cu_seqlens=cu_seqlens,
330
+ attention_mask=attention_mask,
331
+ output_hidden_states=output_hidden_states,
332
+ output_attentions=output_attentions,
333
+ )
334
+ logits = self.decoder(output.last_hidden_state)
335
+
336
+ return MaskedLMOutput(
337
+ hidden_states=output.hidden_states if output_hidden_states else None,
338
+ attentions=output.attentions if output_attentions else None,
339
+ logits=logits,
340
+ )
341
+
342
+
343
+ class NeoBERTForSequenceClassification(NeoBERTPreTrainedModel):
344
+ config_class = NeoBERTConfig
345
+
346
+ def __init__(self, config: NeoBERTConfig):
347
+ super().__init__(config)
348
+
349
+ self.config = config
350
+
351
+ self.num_labels = getattr(config, "num_labels", 2)
352
+ self.classifier_dropout = getattr(config, "classifier_dropout", 0.1)
353
+ self.classifier_init_range = getattr(config, "classifier_init_range", 0.02)
354
+
355
+ self.model = NeoBERT(config)
356
+
357
+ self.dense = nn.Linear(self.config.hidden_size, self.config.hidden_size)
358
+ self.dropout = nn.Dropout(self.classifier_dropout)
359
+ self.classifier = nn.Linear(self.config.hidden_size, self.num_labels)
360
+
361
+ self.post_init()
362
+
363
+ def _init_weights(self, module):
364
+ if isinstance(module, nn.Linear):
365
+ module.weight.data.normal_(mean=0.0, std=self.classifier_init_range)
366
+ if module.bias is not None:
367
+ module.bias.data.zero_()
368
+
369
+ def forward(
370
+ self,
371
+ input_ids: Optional[torch.Tensor] = None,
372
+ position_ids: torch.Tensor = None,
373
+ max_seqlen: int = None,
374
+ cu_seqlens: torch.Tensor = None,
375
+ attention_mask: torch.Tensor = None,
376
+ output_hidden_states: bool = False,
377
+ output_attentions: bool = False,
378
+ labels: Optional[torch.Tensor] = None,
379
+ return_dict: Optional[bool] = None,
380
+ ):
381
+ return_dict = return_dict if return_dict is not None else self.config.use_return_dict
382
+
383
+ output = self.model.forward(
384
+ input_ids=input_ids,
385
+ position_ids=position_ids,
386
+ max_seqlen=max_seqlen,
387
+ cu_seqlens=cu_seqlens,
388
+ attention_mask=attention_mask,
389
+ output_hidden_states=output_hidden_states,
390
+ output_attentions=output_attentions,
391
+ )
392
+ hidden_states = output.last_hidden_state
393
+
394
+ x = hidden_states[:, 0, :]
395
+ x = self.dropout(x)
396
+ x = self.dense(x)
397
+ x = torch.tanh(x)
398
+ x = self.dropout(x)
399
+
400
+ logits = self.classifier(x)
401
+
402
+ loss = None
403
+ if labels is not None:
404
+ if self.config.problem_type is None:
405
+ if self.num_labels == 1:
406
+ self.config.problem_type = "regression"
407
+ elif self.num_labels > 1 and (labels.dtype == torch.long or labels.dtype == torch.int):
408
+ self.config.problem_type = "single_label_classification"
409
+ else:
410
+ self.config.problem_type = "multi_label_classification"
411
+
412
+ if self.config.problem_type == "regression":
413
+ loss_fct = MSELoss()
414
+ if self.num_labels == 1:
415
+ loss = loss_fct(logits.squeeze(), labels.squeeze())
416
+ else:
417
+ loss = loss_fct(logits, labels)
418
+ elif self.config.problem_type == "single_label_classification":
419
+ loss_fct = CrossEntropyLoss()
420
+ loss = loss_fct(logits.view(-1, self.num_labels), labels.view(-1))
421
+ elif self.config.problem_type == "multi_label_classification":
422
+ loss_fct = BCEWithLogitsLoss()
423
+ loss = loss_fct(logits, labels)
424
+
425
+ if not return_dict:
426
+ result = (logits,)
427
+ return ((loss,) + result) if loss is not None else result
428
+
429
+ return SequenceClassifierOutput(
430
+ loss=loss,
431
+ logits=logits,
432
+ hidden_states=output.hidden_states if output_hidden_states else None,
433
+ attentions=output.attentions if output_attentions else None,
434
+ )
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:130f043d267f9b9afbbf7da2f162af784f3c1288ec82bb8254e7e7283fbf02dc
3
+ size 992597168
modules.json ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "idx": 0,
4
+ "name": "0",
5
+ "path": "",
6
+ "type": "sentence_transformers.models.Transformer"
7
+ },
8
+ {
9
+ "idx": 1,
10
+ "name": "1",
11
+ "path": "1_Pooling",
12
+ "type": "sentence_transformers.models.Pooling"
13
+ }
14
+ ]
rotary.py ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # From https://github.com/facebookresearch/llama/blob/main/llama/model.py
2
+
3
+ import torch
4
+ from typing import Tuple
5
+
6
+
7
+ def precompute_freqs_cis(dim: int, end: int, theta: float = 10000.0):
8
+ """
9
+ Precompute the frequency tensor for complex exponentials (cis) with given dimensions.
10
+
11
+ This function calculates a frequency tensor with complex exponentials using the given dimension 'dim'
12
+ and the end index 'end'. The 'theta' parameter scales the frequencies.
13
+ The returned tensor contains complex values in complex64 data type.
14
+
15
+ Args:
16
+ dim (int): Dimension of the frequency tensor.
17
+ end (int): End index for precomputing frequencies.
18
+ theta (float, optional): Scaling factor for frequency computation. Defaults to 10000.0.
19
+
20
+ Returns:
21
+ torch.Tensor: Precomputed frequency tensor with complex exponentials.
22
+ """
23
+
24
+ freqs = 1.0 / (theta ** (torch.arange(0, dim, 2)[: (dim // 2)].float() / dim))
25
+ t = torch.arange(end, device=freqs.device)
26
+ freqs = torch.outer(t, freqs).float()
27
+ return torch.polar(torch.ones_like(freqs), freqs)
28
+
29
+
30
+ def reshape_for_broadcast(freqs_cis: torch.Tensor, x: torch.Tensor):
31
+ assert freqs_cis.shape[1:] == (x.shape[1], x.shape[-1])
32
+ return freqs_cis.contiguous().unsqueeze(2)
33
+
34
+
35
+ def apply_rotary_emb(
36
+ xq: torch.Tensor,
37
+ xk: torch.Tensor,
38
+ freqs_cis: torch.Tensor,
39
+ ) -> Tuple[torch.Tensor, torch.Tensor]:
40
+ """
41
+ Apply rotary embeddings to input tensors using the given frequency tensor.
42
+
43
+ This function applies rotary embeddings to the given query 'xq' and key 'xk' tensors using the provided
44
+ frequency tensor 'freqs_cis'. The input tensors are reshaped as complex numbers, and the frequency tensor
45
+ is reshaped for broadcasting compatibility. The resulting tensors contain rotary embeddings and are
46
+ returned as real tensors.
47
+
48
+ Args:
49
+ xq (torch.Tensor): Query tensor to apply rotary embeddings.
50
+ xk (torch.Tensor): Key tensor to apply rotary embeddings.
51
+ freqs_cis (torch.Tensor): Precomputed frequency tensor for complex exponentials.
52
+
53
+ Returns:
54
+ Tuple[torch.Tensor, torch.Tensor]: Tuple of modified query tensor and key tensor with rotary embeddings.
55
+ """
56
+ xq_ = torch.view_as_complex(xq.float().reshape(*xq.shape[:-1], -1, 2))
57
+ xk_ = torch.view_as_complex(xk.float().reshape(*xk.shape[:-1], -1, 2))
58
+ freqs_cis = reshape_for_broadcast(freqs_cis, xq_)
59
+ xq_out = torch.view_as_real(xq_ * freqs_cis).flatten(3)
60
+ xk_out = torch.view_as_real(xk_ * freqs_cis).flatten(3)
61
+ return xq_out.type_as(xq), xk_out.type_as(xk)
sentence_bert_config.json ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ {
2
+ "max_seq_length": 512,
3
+ "do_lower_case": false
4
+ }
special_tokens_map.json ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "additional_special_tokens": [
3
+ "[+]"
4
+ ],
5
+ "cls_token": {
6
+ "content": "[CLS]",
7
+ "lstrip": false,
8
+ "normalized": false,
9
+ "rstrip": false,
10
+ "single_word": false
11
+ },
12
+ "mask_token": {
13
+ "content": "[MASK]",
14
+ "lstrip": false,
15
+ "normalized": false,
16
+ "rstrip": false,
17
+ "single_word": false
18
+ },
19
+ "pad_token": {
20
+ "content": "[PAD]",
21
+ "lstrip": false,
22
+ "normalized": false,
23
+ "rstrip": false,
24
+ "single_word": false
25
+ },
26
+ "sep_token": {
27
+ "content": "[SEP]",
28
+ "lstrip": false,
29
+ "normalized": false,
30
+ "rstrip": false,
31
+ "single_word": false
32
+ },
33
+ "unk_token": {
34
+ "content": "[UNK]",
35
+ "lstrip": false,
36
+ "normalized": false,
37
+ "rstrip": false,
38
+ "single_word": false
39
+ }
40
+ }
tokenizer.json ADDED
The diff for this file is too large to render. See raw diff
 
tokenizer.py ADDED
@@ -0,0 +1,147 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List, Tuple
2
+ from transformers import PreTrainedTokenizerFast
3
+ import re
4
+ from .acrps_light_stemming import stem
5
+ from .constants import ARABIC_DIACRITICS
6
+
7
+ _TATWEEL_RE = re.compile(r"\u0640")
8
+ _ALIF_RE = re.compile(r"[آأإٱ]")
9
+ _ALIF_MAK_RE = re.compile(r"ى")
10
+ _TEH_MARB_RE = re.compile(r"ة")
11
+ _ZERO_WIDTH_RE = re.compile(r"[\u200B-\u200D\u200E\u200F\uFEFF]")
12
+
13
+ def separate_diacritics(text):
14
+ tokens = re.split(r'(\s+|\[\+\])', text)
15
+ processed_tokens = []
16
+
17
+ for token in tokens:
18
+ if not token:
19
+ continue
20
+ if token.isspace() or token == '[+]':
21
+ processed_tokens.append(token)
22
+ continue
23
+
24
+ if not any(c in ARABIC_DIACRITICS for c in token):
25
+ processed_tokens.append(token)
26
+ continue
27
+
28
+ base_chars = []
29
+ diac_groups = []
30
+
31
+ for char in token:
32
+ if char in ARABIC_DIACRITICS:
33
+ if not diac_groups:
34
+ base_chars.append(" ")
35
+ diac_groups.append([])
36
+ diac_groups[-1].append(char)
37
+ else:
38
+ base_chars.append(char)
39
+ diac_groups.append([])
40
+
41
+ base_word = "".join(base_chars)
42
+ diac_string = []
43
+ for group in diac_groups:
44
+ if group:
45
+ diac_string.append("".join(group))
46
+ else:
47
+ diac_string.append("◌")
48
+
49
+ processed_tokens.append(base_word + " " + "".join(diac_string))
50
+ return "".join(processed_tokens)
51
+
52
+ def normalize_arabic(text):
53
+ text = _TATWEEL_RE.sub("", text)
54
+ text = _ZERO_WIDTH_RE.sub("", text)
55
+ text = _ALIF_RE.sub("ا", text)
56
+ text = _ALIF_MAK_RE.sub("ي", text)
57
+ text = _TEH_MARB_RE.sub("ه", text)
58
+ return text
59
+
60
+ class ArabicMorphTokenizer(PreTrainedTokenizerFast):
61
+ slow_tokenizer_class = None
62
+
63
+ def __init__(self, tokenizer_file=None, apply_stemming=True, **kwargs):
64
+ super().__init__(tokenizer_file=tokenizer_file, **kwargs)
65
+ self.apply_stemming = True if apply_stemming is None else bool(apply_stemming)
66
+
67
+ def _preprocess_one(self, s, do_stem):
68
+ if isinstance(s, (list, tuple)):
69
+ return [self._preprocess_one(x, do_stem) for x in s]
70
+ if do_stem:
71
+ s = stem(s, apply_diacritics=True)
72
+ s = normalize_arabic(s)
73
+ s = separate_diacritics(s)
74
+ return s
75
+
76
+ def _preprocess_pair(self, text, text_pair, do_stem):
77
+ def maybe(s):
78
+ return self._preprocess_one(s, do_stem) if isinstance(s, str) else s
79
+ if isinstance(text, (list, tuple)):
80
+ text = [maybe(x) for x in text]
81
+ else:
82
+ text = maybe(text)
83
+ if isinstance(text_pair, (list, tuple)):
84
+ text_pair = [maybe(x) for x in text_pair]
85
+ else:
86
+ text_pair = maybe(text_pair)
87
+ return text, text_pair
88
+
89
+ def _pop_flag(self, kwargs):
90
+ v = kwargs.pop("apply_stemming", None)
91
+ return self.apply_stemming if v is None else bool(v)
92
+
93
+ def __call__(self, text=None, text_pair=None, *args, **kwargs):
94
+ flag = self._pop_flag(kwargs)
95
+ if not getattr(self, "_processing", False):
96
+ self._processing = True
97
+ try:
98
+ text, text_pair = self._preprocess_pair(text, text_pair, flag)
99
+ return super().__call__(text=text, text_pair=text_pair, *args, **kwargs)
100
+ finally:
101
+ self._processing = False
102
+ return super().__call__(text=text, text_pair=text_pair, *args, **kwargs)
103
+
104
+ def encode(self, text, text_pair=None, *args, **kwargs):
105
+ flag = self._pop_flag(kwargs)
106
+ if not getattr(self, "_processing", False):
107
+ self._processing = True
108
+ try:
109
+ text, text_pair = self._preprocess_pair(text, text_pair, flag)
110
+ return super().encode(text, text_pair, *args, **kwargs)
111
+ finally:
112
+ self._processing = False
113
+ return super().encode(text, text_pair, *args, **kwargs)
114
+
115
+ def encode_plus(self, text=None, text_pair=None, *args, **kwargs):
116
+ flag = self._pop_flag(kwargs)
117
+ if not getattr(self, "_processing", False):
118
+ self._processing = True
119
+ try:
120
+ text, text_pair = self._preprocess_pair(text, text_pair, flag)
121
+ return super().encode_plus(text=text, text_pair=text_pair, *args, **kwargs)
122
+ finally:
123
+ self._processing = False
124
+ return super().encode_plus(text=text, text_pair=text_pair, *args, **kwargs)
125
+
126
+ def batch_encode_plus(self, batch_text_or_text_pairs=None, *args, **kwargs):
127
+ flag = self._pop_flag(kwargs)
128
+ if not getattr(self, "_processing", False):
129
+ self._processing = True
130
+ try:
131
+ data = batch_text_or_text_pairs
132
+ if isinstance(data, (list, tuple)):
133
+ new_data = []
134
+ for item in data:
135
+ if isinstance(item, (list, tuple)) and len(item) == 2:
136
+ new_data.append(self._preprocess_pair(item[0], item[1], flag))
137
+ else:
138
+ new_data.append(self._preprocess_one(item, flag))
139
+ batch_text_or_text_pairs = new_data
140
+ return super().batch_encode_plus(batch_text_or_text_pairs=batch_text_or_text_pairs, *args, **kwargs)
141
+ finally:
142
+ self._processing = False
143
+ return super().batch_encode_plus(batch_text_or_text_pairs=batch_text_or_text_pairs, *args, **kwargs)
144
+
145
+ def preprocess(self, text, apply_stemming=True):
146
+ flag = self.apply_stemming if apply_stemming is None else bool(apply_stemming)
147
+ return self._preprocess_one(text, flag)
tokenizer_config.json ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ "1": {
12
+ "content": "[UNK]",
13
+ "lstrip": false,
14
+ "normalized": false,
15
+ "rstrip": false,
16
+ "single_word": false,
17
+ "special": true
18
+ },
19
+ "2": {
20
+ "content": "[CLS]",
21
+ "lstrip": false,
22
+ "normalized": false,
23
+ "rstrip": false,
24
+ "single_word": false,
25
+ "special": true
26
+ },
27
+ "3": {
28
+ "content": "[SEP]",
29
+ "lstrip": false,
30
+ "normalized": false,
31
+ "rstrip": false,
32
+ "single_word": false,
33
+ "special": true
34
+ },
35
+ "4": {
36
+ "content": "[MASK]",
37
+ "lstrip": false,
38
+ "normalized": false,
39
+ "rstrip": false,
40
+ "single_word": false,
41
+ "special": true
42
+ },
43
+ "5": {
44
+ "content": "[+]",
45
+ "lstrip": false,
46
+ "normalized": false,
47
+ "rstrip": false,
48
+ "single_word": false,
49
+ "special": true
50
+ }
51
+ },
52
+ "additional_special_tokens": [
53
+ "[+]"
54
+ ],
55
+ "auto_map": {
56
+ "AutoTokenizer": [
57
+ "tokenizer.ArabicMorphTokenizer",
58
+ null
59
+ ]
60
+ },
61
+ "clean_up_tokenization_spaces": false,
62
+ "cls_token": "[CLS]",
63
+ "do_lower_case": false,
64
+ "extra_special_tokens": {},
65
+ "mask_token": "[MASK]",
66
+ "model_max_length": 1000000000000000019884624838656,
67
+ "pad_token": "[PAD]",
68
+ "sep_token": "[SEP]",
69
+ "strip_accents": null,
70
+ "tokenize_chinese_chars": true,
71
+ "tokenizer_class": "ArabicMorphTokenizer",
72
+ "trust_remote_code": true,
73
+ "unk_token": "[UNK]"
74
+ }