CATT & CATT-Whisper (code, dataset, models, papers, spaces)
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +4 -0
- Abjad AI at NADI 2025. CATT-Whisper. Multimodal Diacritic Restoration Using Text and Speech Representations.pdf +3 -0
- CATT. Character-based Arabic Tashkeel Transformer.pdf +3 -0
- NADI 2025. The First Multidialectal Arabic Speech Processing Shared Task.pdf +3 -0
- Thaka at KSAA-2026 Task 2. Regularized Fine-Tuning for Arabic Speech Diacritization.pdf +3 -0
- code/Arabic-Romanizer (ArabicTransliterator +8).zip +3 -0
- code/ArabicTransliterator.zip +3 -0
- code/catt-whisper.zip +3 -0
- code/catt.zip +3 -0
- code/mishkal.zip +3 -0
- code/saara.zip +3 -0
- dataset/dataset.zip +3 -0
- models/CATT-Whisper/catt_whisper_base_model_v1_epoch_26_with_spec_augment.pt +3 -0
- models/CATT-Whisper/source.txt +1 -0
- models/CATT/best_ed_epoch_175.pt +3 -0
- models/CATT/best_ed_mlm_epoch_175.pt +3 -0
- models/CATT/best_ed_mlm_ns_epoch_178.pt +3 -0
- models/CATT/best_eo_epoch_192.pt +3 -0
- models/CATT/best_eo_mlm_epoch_192.pt +3 -0
- models/CATT/best_eo_mlm_ns_epoch_193.pt +3 -0
- models/CATT/char_bert_model_pretrained.pt +3 -0
- models/CATT/ed_epoch_5.pt +3 -0
- models/CATT/ed_mlm_epoch_5.pt +3 -0
- models/CATT/ed_model_onnx.zip +3 -0
- models/CATT/eo_epoch_5.pt +3 -0
- models/CATT/eo_mlm_epoch_5.pt +3 -0
- models/CATT/eo_model_onnx.zip +3 -0
- models/CATT/source.txt +1 -0
- models/catt-diacritizer/.gitattributes +35 -0
- models/catt-diacritizer/README.md +43 -0
- models/catt-diacritizer/bw2ar.py +126 -0
- models/catt-diacritizer/catt_ed_decoder.int8.onnx +3 -0
- models/catt-diacritizer/catt_ed_decoder.onnx +3 -0
- models/catt-diacritizer/catt_ed_encoder.int8.onnx +3 -0
- models/catt-diacritizer/catt_ed_encoder.onnx +3 -0
- models/catt-diacritizer/catt_eo.int8.onnx +3 -0
- models/catt-diacritizer/catt_eo.onnx +3 -0
- models/catt-diacritizer/catt_models_onnx.py +273 -0
- models/catt-diacritizer/source.txt +1 -0
- models/catt-diacritizer/tashkeel_tokenizer_onnx.py +158 -0
- models/catt-diacritizer/utils.py +139 -0
- spaces/arabic-auto-tashkeel/.gitattributes +35 -0
- spaces/arabic-auto-tashkeel/README.md +13 -0
- spaces/arabic-auto-tashkeel/app.py +104 -0
- spaces/arabic-auto-tashkeel/bw2ar.py +126 -0
- spaces/arabic-auto-tashkeel/ed.py +63 -0
- spaces/arabic-auto-tashkeel/ed_pl.py +164 -0
- spaces/arabic-auto-tashkeel/eo.py +50 -0
- spaces/arabic-auto-tashkeel/eo_pl.py +151 -0
- spaces/arabic-auto-tashkeel/models/best_ed_mlm_ns_epoch_178.pt +3 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,7 @@ saved_model/**/* 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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 36 |
+
Abjad[[:space:]]AI[[:space:]]at[[:space:]]NADI[[:space:]]2025.[[:space:]]CATT-Whisper.[[:space:]]Multimodal[[:space:]]Diacritic[[:space:]]Restoration[[:space:]]Using[[:space:]]Text[[:space:]]and[[:space:]]Speech[[:space:]]Representations.pdf filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
CATT.[[:space:]]Character-based[[:space:]]Arabic[[:space:]]Tashkeel[[:space:]]Transformer.pdf filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
NADI[[:space:]]2025.[[:space:]]The[[:space:]]First[[:space:]]Multidialectal[[:space:]]Arabic[[:space:]]Speech[[:space:]]Processing[[:space:]]Shared[[:space:]]Task.pdf filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
Thaka[[:space:]]at[[:space:]]KSAA-2026[[:space:]]Task[[:space:]]2.[[:space:]]Regularized[[:space:]]Fine-Tuning[[:space:]]for[[:space:]]Arabic[[:space:]]Speech[[:space:]]Diacritization.pdf filter=lfs diff=lfs merge=lfs -text
|
Abjad AI at NADI 2025. CATT-Whisper. Multimodal Diacritic Restoration Using Text and Speech Representations.pdf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:16235cec951d5a337c988d5fa66b809946d070ea69244f16242d93f49057cc48
|
| 3 |
+
size 296424
|
CATT. Character-based Arabic Tashkeel Transformer.pdf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:fbbef8cd0bfa0f7a721af845316e82da4bcc735635b079127933d14455e14610
|
| 3 |
+
size 400230
|
NADI 2025. The First Multidialectal Arabic Speech Processing Shared Task.pdf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:633ffcd7c2dcdb1b3c9e7c16e0ec9c0850285f313c882e076e286e684b1ba45a
|
| 3 |
+
size 1215064
|
Thaka at KSAA-2026 Task 2. Regularized Fine-Tuning for Arabic Speech Diacritization.pdf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:b063a23f75d4104f1a34087ab00b81a4641fe2195edf3936e5624dc854917ad5
|
| 3 |
+
size 159709
|
code/Arabic-Romanizer (ArabicTransliterator +8).zip
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:98fed58e6d6f1fad9f245a5f915061bcd11f69a85f193da2dde63aa3ad83e853
|
| 3 |
+
size 22718916
|
code/ArabicTransliterator.zip
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:81ef1cbdc5c266d6e501010b39f9998ef3d3a03dec095a5c722fe2d0c9b679ec
|
| 3 |
+
size 46528355
|
code/catt-whisper.zip
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:220f178a205ca4bfff5f4cbf752979dc273e38a6b29f724c045d84945d5a9e9e
|
| 3 |
+
size 1271147
|
code/catt.zip
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:93f4f236ba71de710cf2316558f8bf0b0d3e5853c84a13659f8505c13bc90771
|
| 3 |
+
size 8552916
|
code/mishkal.zip
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d9243591c7a437ee16dccc343d51e61e3d1b6b2bd9383a42863d95c9ec108495
|
| 3 |
+
size 117913533
|
code/saara.zip
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:c8fcb5eef9e6ad428cb1129481175b1af32009a072e8f353d2eb79d10460a059
|
| 3 |
+
size 46530545
|
dataset/dataset.zip
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:fa332ca9d3118ed8bc974debc681ba3f9d5ebca3cb824073b24bef0314e5fb00
|
| 3 |
+
size 209268505
|
models/CATT-Whisper/catt_whisper_base_model_v1_epoch_26_with_spec_augment.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:f7ca2e8107eaab302b2c48a91f4d4f3ec8c151f96ee5ffd27750778a51f5ecac
|
| 3 |
+
size 159223099
|
models/CATT-Whisper/source.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
https://github.com/abjadai/catt-whisper
|
models/CATT/best_ed_epoch_175.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:5125495c8fd6c5480c8443a2d73b766be00c1c85988d6b67ec4e5dc7c17bdd5a
|
| 3 |
+
size 88403216
|
models/CATT/best_ed_mlm_epoch_175.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a250c29acdb8cbdd9e729287a6631710986d8243d487b2fa5a25b996cbaa84be
|
| 3 |
+
size 88403608
|
models/CATT/best_ed_mlm_ns_epoch_178.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:c6297ec54f9af518a59d4b9e5831d3ad4da304435b8c7fb969f6e3a16816ac23
|
| 3 |
+
size 88403510
|
models/CATT/best_eo_epoch_192.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:dbe3b98ca741382fbdb7acdf5a2314b7a93bc295b2e3364c6343b9a7c8985081
|
| 3 |
+
size 75761995
|
models/CATT/best_eo_mlm_epoch_192.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:4f21dfe38acf867b1531be3e20ab76c6b2e438eaae6e1ff108607be986518d11
|
| 3 |
+
size 75762311
|
models/CATT/best_eo_mlm_ns_epoch_193.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:4ad3e811b1bf7ecda252dc9284d2bf3087c1795add47be5ff34cb13688413322
|
| 3 |
+
size 75762232
|
models/CATT/char_bert_model_pretrained.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:440b0dcad32278a607d34d110a00a0ff20d9ad7d67f7dea8b5e323645976c9c4
|
| 3 |
+
size 75807234
|
models/CATT/ed_epoch_5.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:47b811b4db7675876a83356af04c1f61b2d60aab4ce57ebfbe848aa95f4b7a05
|
| 3 |
+
size 88402530
|
models/CATT/ed_mlm_epoch_5.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:8f5534cf55306e31ad7cbdd89f931a1ae1b4724f04d6d68391b4e699f52fc1df
|
| 3 |
+
size 88402922
|
models/CATT/ed_model_onnx.zip
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:75d2a6360725c7e0bbb238b5a950f621fb8e30b21ad7c65e91e0f36759bcb3ce
|
| 3 |
+
size 85926550
|
models/CATT/eo_epoch_5.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:0997479729f9cbbfa0b10070e0099fcc88ff540e9dcd113b2e0c27bd59f0ca8b
|
| 3 |
+
size 75761442
|
models/CATT/eo_mlm_epoch_5.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:f2bd85659b985949849c7377ddd941f3965bec60d0b8eb60f5a012f10f46bec3
|
| 3 |
+
size 75761758
|
models/CATT/eo_model_onnx.zip
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:5343bfdfda70c981b587811b79348b8dde91f65c0a551ca98f232ea5cb62ceac
|
| 3 |
+
size 72109998
|
models/CATT/source.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
https://github.com/abjadai/catt
|
models/catt-diacritizer/.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
|
models/catt-diacritizer/README.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: apache-2.0
|
| 3 |
+
language: ar
|
| 4 |
+
library_name: onnx
|
| 5 |
+
tags:
|
| 6 |
+
- arabic
|
| 7 |
+
- diacritization
|
| 8 |
+
- tashkeel
|
| 9 |
+
- onnx
|
| 10 |
+
- transformer
|
| 11 |
+
---
|
| 12 |
+
|
| 13 |
+
# CATT-EO — Arabic diacritizer (stitched single ONNX)
|
| 14 |
+
|
| 15 |
+
Single-file ONNX of the **encoder-only [CATT](https://github.com/abjadai/catt)** model
|
| 16 |
+
(Abjad AI, Apache-2.0). CATT-EO is non-autoregressive — a transformer encoder followed by
|
| 17 |
+
a linear classifier head — so [TigreGotico](https://huggingface.co/TigreGotico)
|
| 18 |
+
**stitched the two stages into one ONNX** with `onnx.compose`. The stitched graph is
|
| 19 |
+
**byte-identical** (max|Δ| = 0.0) to the original two-stage encoder→head pipeline.
|
| 20 |
+
|
| 21 |
+
| file | precision | size |
|
| 22 |
+
|------|-----------|-----:|
|
| 23 |
+
| `catt_eo.onnx` | fp32 | 78 MB |
|
| 24 |
+
| `catt_eo.int8.onnx` | dynamic int8 | 21.6 MB |
|
| 25 |
+
|
| 26 |
+
**I/O.** Inputs `src` (int64, `[batch, seq]` Buckwalter ids) and `src_mask`
|
| 27 |
+
(bool, `[batch, 1, seq, seq]`). Output `[batch, seq, 18]` tag logits. Tokenizer:
|
| 28 |
+
`tashkeel_tokenizer_onnx.py` (+ `bw2ar.py`, `utils.py`) — Buckwalter encoding and the
|
| 29 |
+
18-tag tashkeel scheme. See `catt_models_onnx.py` for an end-to-end example.
|
| 30 |
+
|
| 31 |
+
**Benchmark.** ~4.27 % DER on the broad
|
| 32 |
+
[`TigreGotico/arabic_diacritized_text`](https://huggingface.co/datasets/TigreGotico/arabic_diacritized_text)
|
| 33 |
+
test (much lower on CATT's own narrow benchmark — distribution-dependent). The int8 file
|
| 34 |
+
is dynamic-quantized; expect small divergence from fp32.
|
| 35 |
+
|
| 36 |
+
Part of the
|
| 37 |
+
[Arabic Diacritizers / Tashkeel collection](https://huggingface.co/collections/TigreGotico/arabic-diacritizers-tashkeel-6a247318559bcc49e128aa5f).
|
| 38 |
+
Original CATT © Abjad AI (Apache-2.0); this is a stitched re-export with attribution.
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
## Encoder-decoder variant (CATT-ED)
|
| 42 |
+
|
| 43 |
+
`catt_ed_encoder.onnx` + `catt_ed_decoder.onnx` (+ `.int8`) are the **autoregressive** encoder-decoder model — the decoder runs once per output position (causal mask), so it is two ONNX (not stitchable) and much slower than CATT-EO. In `text2tashkeel` it is the `catt-ed` / `catt-ed-int8` model.
|
models/catt-diacritizer/bw2ar.py
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
functions to convert Arabic words/text into buckwalter encoding and vice versa
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
import sys
|
| 6 |
+
import re
|
| 7 |
+
import utils
|
| 8 |
+
|
| 9 |
+
buck2uni = {
|
| 10 |
+
"'": u"\u0621", # hamza-on-the-line
|
| 11 |
+
"|": u"\u0622", # madda
|
| 12 |
+
">": u"\u0623", # hamza-on-'alif
|
| 13 |
+
"&": u"\u0624", # hamza-on-waaw
|
| 14 |
+
"<": u"\u0625", # hamza-under-'alif
|
| 15 |
+
"}": u"\u0626", # hamza-on-yaa'
|
| 16 |
+
"A": u"\u0627", # bare 'alif
|
| 17 |
+
"b": u"\u0628", # baa'
|
| 18 |
+
"p": u"\u0629", # taa' marbuuTa
|
| 19 |
+
"t": u"\u062A", # taa'
|
| 20 |
+
"v": u"\u062B", # thaa'
|
| 21 |
+
"j": u"\u062C", # jiim
|
| 22 |
+
"H": u"\u062D", # Haa'
|
| 23 |
+
"x": u"\u062E", # khaa'
|
| 24 |
+
"d": u"\u062F", # daal
|
| 25 |
+
"*": u"\u0630", # dhaal
|
| 26 |
+
"r": u"\u0631", # raa'
|
| 27 |
+
"z": u"\u0632", # zaay
|
| 28 |
+
"s": u"\u0633", # siin
|
| 29 |
+
"$": u"\u0634", # shiin
|
| 30 |
+
"S": u"\u0635", # Saad
|
| 31 |
+
"D": u"\u0636", # Daad
|
| 32 |
+
"T": u"\u0637", # Taa'
|
| 33 |
+
"Z": u"\u0638", # Zaa' (DHaa')
|
| 34 |
+
"E": u"\u0639", # cayn
|
| 35 |
+
"g": u"\u063A", # ghayn
|
| 36 |
+
"_": u"\u0640", # taTwiil
|
| 37 |
+
"f": u"\u0641", # faa'
|
| 38 |
+
"q": u"\u0642", # qaaf
|
| 39 |
+
"k": u"\u0643", # kaaf
|
| 40 |
+
"l": u"\u0644", # laam
|
| 41 |
+
"m": u"\u0645", # miim
|
| 42 |
+
"n": u"\u0646", # nuun
|
| 43 |
+
"h": u"\u0647", # haa'
|
| 44 |
+
"w": u"\u0648", # waaw
|
| 45 |
+
"Y": u"\u0649", # 'alif maqSuura
|
| 46 |
+
"y": u"\u064A", # yaa'
|
| 47 |
+
"F": u"\u064B", # fatHatayn
|
| 48 |
+
"N": u"\u064C", # Dammatayn
|
| 49 |
+
"K": u"\u064D", # kasratayn
|
| 50 |
+
"a": u"\u064E", # fatHa
|
| 51 |
+
"u": u"\u064F", # Damma
|
| 52 |
+
"i": u"\u0650", # kasra
|
| 53 |
+
"~": u"\u0651", # shaddah
|
| 54 |
+
"o": u"\u0652", # sukuun
|
| 55 |
+
"`": u"\u0670", # dagger 'alif
|
| 56 |
+
"{": u"\u0671", # waSla
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
# For a reverse transliteration (Unicode -> Buckwalter), a dictionary
|
| 60 |
+
# which is the reverse of the above buck2uni is essential.
|
| 61 |
+
uni2buck = {}
|
| 62 |
+
|
| 63 |
+
# Iterate through all the items in the buck2uni dict.
|
| 64 |
+
for (key, value) in buck2uni.items():
|
| 65 |
+
# The value from buck2uni becomes a key in uni2buck, and vice
|
| 66 |
+
# versa for the keys.
|
| 67 |
+
uni2buck[value] = key
|
| 68 |
+
|
| 69 |
+
# add special characters
|
| 70 |
+
uni2buck[u"\ufefb"] = "lA"
|
| 71 |
+
uni2buck[u"\ufef7"] = "l>"
|
| 72 |
+
uni2buck[u"\ufef5"] = "l|"
|
| 73 |
+
uni2buck[u"\ufef9"] = "l<"
|
| 74 |
+
|
| 75 |
+
# clean the arabic text from unwanted characters that may cause problem while building the language model
|
| 76 |
+
def clean_text(text):
|
| 77 |
+
text = re.sub(u"[\ufeff]", "", text, flags=re.UNICODE) # strip Unicode Character 'ZERO WIDTH NO-BREAK SPACE' (U+FEFF). For more info, check http://www.fileformat.info/info/unicode/char/feff/index.htm
|
| 78 |
+
text = utils.remove_non_arabic(text)
|
| 79 |
+
text = utils.strip_tashkeel(text)
|
| 80 |
+
text = utils.strip_tatweel(text)
|
| 81 |
+
return text
|
| 82 |
+
|
| 83 |
+
# convert a single word into buckwalter and vice versa
|
| 84 |
+
def transliterate_word(input_word, direction='bw2ar'):
|
| 85 |
+
output_word = ''
|
| 86 |
+
# Loop over each character in the string, bw_word.
|
| 87 |
+
for char in input_word:
|
| 88 |
+
# Look up current char in the dictionary to get its
|
| 89 |
+
# respective value. If there is no match, e.g., chars like
|
| 90 |
+
# spaces, then just stick with the current char without any
|
| 91 |
+
# conversion.
|
| 92 |
+
# if type(char) == bytes:
|
| 93 |
+
# char = char.decode('ascii')
|
| 94 |
+
if direction == 'bw2ar':
|
| 95 |
+
#print('in bw2ar')
|
| 96 |
+
output_word += buck2uni.get(char, char)
|
| 97 |
+
elif direction == 'ar2bw':
|
| 98 |
+
#print('in ar2bw')
|
| 99 |
+
output_word += uni2buck.get(char, char)
|
| 100 |
+
else:
|
| 101 |
+
sys.stderr.write('Error: invalid direction!')
|
| 102 |
+
sys.exit()
|
| 103 |
+
return output_word
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
# convert a text into buckwalter and vice versa
|
| 107 |
+
def transliterate_text(input_text, direction='bw2ar'):
|
| 108 |
+
output_text = ''
|
| 109 |
+
for input_word in input_text.split(' '):
|
| 110 |
+
output_text += transliterate_word(input_word, direction) + ' '
|
| 111 |
+
|
| 112 |
+
return output_text[:-1] # remove the last space ONLY
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
if __name__ == '__main__':
|
| 116 |
+
if len(sys.argv) < 2:
|
| 117 |
+
sys.stderr.write('Usage: INPUT TEXT | python {} DIRECTION(bw2ar|ar2bw)'.format(sys.argv[1]))
|
| 118 |
+
exit(1)
|
| 119 |
+
for line in sys.stdin:
|
| 120 |
+
line = line if sys.argv[1] == 'bw2ar' else clean_text(line)
|
| 121 |
+
output_text = transliterate_text(line, direction=str(sys.argv[1]))
|
| 122 |
+
if output_text.strip() != '':
|
| 123 |
+
sys.stdout.write('{}\n'.format(output_text.strip()))
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
|
models/catt-diacritizer/catt_ed_decoder.int8.onnx
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:b5819a139e8b27ed2bd8d0209b6a75206e86f1a3d3dc1b359f75b4fcbcfa2e4e
|
| 3 |
+
size 15158542
|
models/catt-diacritizer/catt_ed_decoder.onnx
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:14b5580ecb33f8687cdccb14edb120afa2a2cfbf7caf4444fa4a2a86548c375c
|
| 3 |
+
size 52991044
|
models/catt-diacritizer/catt_ed_encoder.int8.onnx
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d5dff46db736a1b1c17babad46db499322cd0c30da65121fd45446b03e806db8
|
| 3 |
+
size 11845630
|
models/catt-diacritizer/catt_ed_encoder.onnx
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:3ec637243feb55f3b83b35264a5daa9c23e1261097ce42ecc85163fba5e1d962
|
| 3 |
+
size 40235317
|
models/catt-diacritizer/catt_eo.int8.onnx
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:59ba20d02a4b507a6b816a9b46b1230ba73d341c6731aa48da5de99388a39fca
|
| 3 |
+
size 21564456
|
models/catt-diacritizer/catt_eo.onnx
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:0f4040ae643c2a5aa40221bb845e146d8826690a95501c9250f72dbc5a12479c
|
| 3 |
+
size 78308696
|
models/catt-diacritizer/catt_models_onnx.py
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import math
|
| 3 |
+
import numpy as np
|
| 4 |
+
from tqdm import tqdm
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
import onnxruntime as ort
|
| 7 |
+
from abc import ABC, abstractmethod
|
| 8 |
+
from utils import remove_non_arabic
|
| 9 |
+
from tashkeel_tokenizer_onnx import TashkeelTokenizer
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class BaseONNXTashkeel(ABC):
|
| 13 |
+
"""
|
| 14 |
+
Base class for ONNX-based Arabic text diacritization models
|
| 15 |
+
|
| 16 |
+
This abstract base class provides common functionality for Arabic text
|
| 17 |
+
diacritization using pre-trained ONNX models.
|
| 18 |
+
"""
|
| 19 |
+
|
| 20 |
+
def __init__(self, encoder_path=None, decoder_path=None, tokenizer=None,
|
| 21 |
+
auto_preprocess=True, model_type="ed_model"):
|
| 22 |
+
"""
|
| 23 |
+
Initialize ONNX Tashkeel model
|
| 24 |
+
|
| 25 |
+
Args:
|
| 26 |
+
encoder_path (str, optional): Path to encoder ONNX file
|
| 27 |
+
decoder_path (str, optional): Path to decoder ONNX file
|
| 28 |
+
tokenizer (TashkeelTokenizer, optional): Tokenizer instance
|
| 29 |
+
auto_preprocess (bool): Automatically preprocess text (remove non-Arabic)
|
| 30 |
+
model_type (str): Type of model ("ed_model" or "eo_model")
|
| 31 |
+
"""
|
| 32 |
+
self.tokenizer = tokenizer or TashkeelTokenizer()
|
| 33 |
+
self.auto_preprocess = auto_preprocess
|
| 34 |
+
self.src_pad_idx = self.tokenizer.letters_map['<PAD>']
|
| 35 |
+
|
| 36 |
+
# Set model-specific paths
|
| 37 |
+
encoder_path, decoder_path = self._get_model_paths(
|
| 38 |
+
encoder_path, decoder_path, model_type
|
| 39 |
+
)
|
| 40 |
+
|
| 41 |
+
# Load ONNX sessions
|
| 42 |
+
self._load_onnx_sessions(encoder_path, decoder_path)
|
| 43 |
+
|
| 44 |
+
def _get_model_paths(self, encoder_path, decoder_path, model_type):
|
| 45 |
+
"""Get default model paths if not provided"""
|
| 46 |
+
if encoder_path is None or decoder_path is None:
|
| 47 |
+
package_dir = Path(__file__).parent
|
| 48 |
+
model_dir = package_dir / "onnx_models" / model_type
|
| 49 |
+
encoder_path = encoder_path or str(model_dir / "encoder.onnx")
|
| 50 |
+
decoder_path = decoder_path or str(model_dir / "decoder.onnx")
|
| 51 |
+
return encoder_path, decoder_path
|
| 52 |
+
|
| 53 |
+
def _load_onnx_sessions(self, encoder_path, decoder_path):
|
| 54 |
+
"""Load ONNX runtime sessions"""
|
| 55 |
+
if not os.path.exists(encoder_path):
|
| 56 |
+
raise FileNotFoundError(f"Encoder ONNX file not found: {encoder_path}")
|
| 57 |
+
if not os.path.exists(decoder_path):
|
| 58 |
+
raise FileNotFoundError(f"Decoder ONNX file not found: {decoder_path}")
|
| 59 |
+
|
| 60 |
+
_opt = ort.SessionOptions()
|
| 61 |
+
import os as _os
|
| 62 |
+
_t = _os.environ.get("TT_ORT_THREADS")
|
| 63 |
+
if _t:
|
| 64 |
+
_opt.intra_op_num_threads = int(_t); _opt.inter_op_num_threads = int(_t)
|
| 65 |
+
self.encoder_session = ort.InferenceSession(encoder_path, sess_options=_opt)
|
| 66 |
+
self.decoder_session = ort.InferenceSession(decoder_path, sess_options=_opt)
|
| 67 |
+
print("✅ Loaded model successfully!")
|
| 68 |
+
|
| 69 |
+
def _preprocess_text(self, text):
|
| 70 |
+
"""Preprocess text if auto_preprocess is enabled"""
|
| 71 |
+
return remove_non_arabic(text) if self.auto_preprocess else text
|
| 72 |
+
|
| 73 |
+
def _make_pad_mask(self, q, k, q_pad_idx, k_pad_idx):
|
| 74 |
+
"""Create padding mask (numpy version)"""
|
| 75 |
+
len_q, len_k = q.shape[1], k.shape[1]
|
| 76 |
+
k_mask = (k != k_pad_idx).reshape(-1, 1, 1, len_k)
|
| 77 |
+
k_mask = np.repeat(k_mask, len_q, axis=2)
|
| 78 |
+
q_mask = (q != q_pad_idx).reshape(-1, 1, len_q, 1)
|
| 79 |
+
q_mask = np.repeat(q_mask, len_k, axis=3)
|
| 80 |
+
return k_mask & q_mask
|
| 81 |
+
|
| 82 |
+
def _make_no_peak_mask(self, q, k):
|
| 83 |
+
"""Create no-peak mask (numpy version)"""
|
| 84 |
+
len_q, len_k = q.shape[1], k.shape[1]
|
| 85 |
+
return np.tril(np.ones((len_q, len_k))).astype(bool)
|
| 86 |
+
|
| 87 |
+
def _run_encoder(self, src_np):
|
| 88 |
+
"""Run encoder using ONNX"""
|
| 89 |
+
src_mask_np = self._make_pad_mask(
|
| 90 |
+
src_np, src_np, self.src_pad_idx, self.src_pad_idx
|
| 91 |
+
).astype(bool)
|
| 92 |
+
|
| 93 |
+
encoder_inputs = {'src': src_np, 'src_mask': src_mask_np}
|
| 94 |
+
return self.encoder_session.run(None, encoder_inputs)[0]
|
| 95 |
+
|
| 96 |
+
def _pad_seq(self, sequences, batch_first=True, padding_value=0.0, prepadding=True):
|
| 97 |
+
"""Pad sequences to same length"""
|
| 98 |
+
lens = [seq.shape[0] for seq in sequences]
|
| 99 |
+
max_len = max(lens)
|
| 100 |
+
|
| 101 |
+
# Create padded array
|
| 102 |
+
if len(sequences[0].shape) == 1:
|
| 103 |
+
padded_sequences = np.full((len(sequences), max_len), padding_value, dtype=sequences[0].dtype)
|
| 104 |
+
else:
|
| 105 |
+
shape = (len(sequences), max_len) + sequences[0].shape[1:]
|
| 106 |
+
padded_sequences = np.full(shape, padding_value, dtype=sequences[0].dtype)
|
| 107 |
+
|
| 108 |
+
# Fill with actual sequence data
|
| 109 |
+
for i, seq in enumerate(sequences):
|
| 110 |
+
padded_sequences[i, :lens[i]] = seq
|
| 111 |
+
|
| 112 |
+
if prepadding:
|
| 113 |
+
for i in range(len(lens)):
|
| 114 |
+
padded_sequences[i] = np.roll(padded_sequences[i], max_len - lens[i], axis=0)
|
| 115 |
+
|
| 116 |
+
if not batch_first:
|
| 117 |
+
padded_sequences = padded_sequences.transpose(1, 0, *range(2, padded_sequences.ndim))
|
| 118 |
+
|
| 119 |
+
return padded_sequences
|
| 120 |
+
|
| 121 |
+
def _get_batches(self, X, batch_size=16):
|
| 122 |
+
"""Generate batches from list"""
|
| 123 |
+
num_batches = math.ceil(len(X) / batch_size)
|
| 124 |
+
for i in range(num_batches):
|
| 125 |
+
yield X[i*batch_size : (i+1)*batch_size]
|
| 126 |
+
|
| 127 |
+
def _prepare_batch_input(self, texts_mini):
|
| 128 |
+
"""Prepare input batch for processing"""
|
| 129 |
+
input_ids_list = []
|
| 130 |
+
for text in texts_mini:
|
| 131 |
+
input_ids, _ = self.tokenizer.encode(text, test_match=False)
|
| 132 |
+
input_ids_list.append(input_ids)
|
| 133 |
+
|
| 134 |
+
return self._pad_seq(
|
| 135 |
+
input_ids_list,
|
| 136 |
+
batch_first=True,
|
| 137 |
+
padding_value=self.tokenizer.letters_map['<PAD>'],
|
| 138 |
+
prepadding=False
|
| 139 |
+
)
|
| 140 |
+
|
| 141 |
+
def _apply_space_mask(self, predictions, input_ids):
|
| 142 |
+
"""Apply space masking to predictions"""
|
| 143 |
+
space_mask = (input_ids == self.tokenizer.letters_map[' '])
|
| 144 |
+
predictions[space_mask] = self.tokenizer.tashkeel_map[self.tokenizer.no_tashkeel_tag]
|
| 145 |
+
return predictions
|
| 146 |
+
|
| 147 |
+
@abstractmethod
|
| 148 |
+
def _process_batch(self, batch_input_ids):
|
| 149 |
+
"""Process a batch of input IDs - to be implemented by subclasses"""
|
| 150 |
+
pass
|
| 151 |
+
|
| 152 |
+
def do_tashkeel_batch(self, texts, batch_size=16, verbose=True):
|
| 153 |
+
"""
|
| 154 |
+
Add tashkeel to multiple Arabic texts
|
| 155 |
+
|
| 156 |
+
Args:
|
| 157 |
+
texts (list): List of input Arabic texts
|
| 158 |
+
batch_size (int): Batch size for processing
|
| 159 |
+
verbose (bool): Show progress bar
|
| 160 |
+
|
| 161 |
+
Returns:
|
| 162 |
+
list: List of texts with tashkeel
|
| 163 |
+
"""
|
| 164 |
+
processed_texts = [self._preprocess_text(text) for text in texts]
|
| 165 |
+
text_with_tashkeel = []
|
| 166 |
+
data_iter = self._get_batches(processed_texts, batch_size)
|
| 167 |
+
|
| 168 |
+
if verbose:
|
| 169 |
+
num_batches = math.ceil(len(processed_texts) / batch_size)
|
| 170 |
+
data_iter = tqdm(data_iter, total=num_batches)
|
| 171 |
+
|
| 172 |
+
for texts_mini in data_iter:
|
| 173 |
+
batch_input_ids = self._prepare_batch_input(texts_mini)
|
| 174 |
+
predictions = self._process_batch(batch_input_ids)
|
| 175 |
+
text_with_tashkeel_mini = self.tokenizer.decode(batch_input_ids, predictions)
|
| 176 |
+
text_with_tashkeel.extend(text_with_tashkeel_mini)
|
| 177 |
+
|
| 178 |
+
return text_with_tashkeel
|
| 179 |
+
|
| 180 |
+
def do_tashkeel(self, text, verbose=True):
|
| 181 |
+
"""
|
| 182 |
+
Add tashkeel to Arabic text
|
| 183 |
+
|
| 184 |
+
Args:
|
| 185 |
+
text (str): Input Arabic text
|
| 186 |
+
|
| 187 |
+
Returns:
|
| 188 |
+
str: Text with tashkeel
|
| 189 |
+
"""
|
| 190 |
+
return self.do_tashkeel_batch([text], verbose=False)[0]
|
| 191 |
+
|
| 192 |
+
|
| 193 |
+
class CATTEncoderDecoder(BaseONNXTashkeel):
|
| 194 |
+
"""
|
| 195 |
+
ONNX-based Arabic text diacritization model using encoder-decoder architecture
|
| 196 |
+
"""
|
| 197 |
+
|
| 198 |
+
def __init__(self, encoder_path=None, decoder_path=None, tokenizer=None,
|
| 199 |
+
auto_preprocess=True):
|
| 200 |
+
"""Initialize encoder-decoder model"""
|
| 201 |
+
super().__init__(encoder_path, decoder_path, tokenizer, auto_preprocess, "ed_model")
|
| 202 |
+
self.trg_pad_idx = self.tokenizer.tashkeel_map['<PAD>']
|
| 203 |
+
|
| 204 |
+
def _run_decoder(self, trg_np, enc_src_np, src_np):
|
| 205 |
+
"""Run decoder using ONNX"""
|
| 206 |
+
src_trg_mask_np = self._make_pad_mask(
|
| 207 |
+
trg_np, src_np, self.trg_pad_idx, self.src_pad_idx
|
| 208 |
+
).astype(bool)
|
| 209 |
+
|
| 210 |
+
trg_mask_np = self._make_pad_mask(
|
| 211 |
+
trg_np, trg_np, self.trg_pad_idx, self.trg_pad_idx
|
| 212 |
+
).astype(bool)
|
| 213 |
+
|
| 214 |
+
no_peak_mask_np = self._make_no_peak_mask(trg_np, trg_np)
|
| 215 |
+
trg_mask_np = trg_mask_np & no_peak_mask_np
|
| 216 |
+
|
| 217 |
+
decoder_inputs = {
|
| 218 |
+
'trg': trg_np,
|
| 219 |
+
'enc_src': enc_src_np,
|
| 220 |
+
'trg_mask': trg_mask_np,
|
| 221 |
+
'src_trg_mask': src_trg_mask_np
|
| 222 |
+
}
|
| 223 |
+
|
| 224 |
+
return self.decoder_session.run(None, decoder_inputs)[0]
|
| 225 |
+
|
| 226 |
+
def _process_batch(self, batch_input_ids):
|
| 227 |
+
"""Process batch using encoder-decoder with autoregressive decoding"""
|
| 228 |
+
src = batch_input_ids
|
| 229 |
+
enc_src = self._run_encoder(src)
|
| 230 |
+
|
| 231 |
+
# Initialize target with BOS token
|
| 232 |
+
target_ids = np.array([[self.tokenizer.tashkeel_map['<BOS>']]] * len(batch_input_ids), dtype=np.int64)
|
| 233 |
+
|
| 234 |
+
# Autoregressive decoding loop
|
| 235 |
+
for i in range(src.shape[1] - 1):
|
| 236 |
+
preds = self._run_decoder(target_ids, enc_src, src)
|
| 237 |
+
next_tokens = np.argmax(preds[:, -1, :], axis=1)
|
| 238 |
+
target_ids = np.concatenate([target_ids, np.expand_dims(next_tokens, 1)], axis=1)
|
| 239 |
+
|
| 240 |
+
# Apply space handling logic
|
| 241 |
+
target_ids = self._apply_space_mask(target_ids, src[:, :target_ids.shape[1]])
|
| 242 |
+
|
| 243 |
+
return target_ids
|
| 244 |
+
|
| 245 |
+
|
| 246 |
+
class CATTEncoderOnly(BaseONNXTashkeel):
|
| 247 |
+
"""
|
| 248 |
+
ONNX-based Arabic text diacritization model using encoder-only architecture
|
| 249 |
+
"""
|
| 250 |
+
|
| 251 |
+
def __init__(self, encoder_path=None, decoder_path=None, tokenizer=None,
|
| 252 |
+
auto_preprocess=True):
|
| 253 |
+
"""Initialize encoder-only model"""
|
| 254 |
+
super().__init__(encoder_path, decoder_path, tokenizer, auto_preprocess, "eo_model")
|
| 255 |
+
|
| 256 |
+
def _run_decoder(self, enc_src_np):
|
| 257 |
+
"""Run decoder using ONNX"""
|
| 258 |
+
decoder_inputs = {'enc_src': enc_src_np}
|
| 259 |
+
return self.decoder_session.run(None, decoder_inputs)[0]
|
| 260 |
+
|
| 261 |
+
def _process_batch(self, batch_input_ids):
|
| 262 |
+
"""Process batch using encoder-only architecture"""
|
| 263 |
+
# Remove BOS and EOS tokens
|
| 264 |
+
batch_input_ids = batch_input_ids[:, 1:-1]
|
| 265 |
+
|
| 266 |
+
enc_src = self._run_encoder(batch_input_ids)
|
| 267 |
+
preds = self._run_decoder(enc_src)
|
| 268 |
+
predictions = np.argmax(preds, axis=-1)
|
| 269 |
+
|
| 270 |
+
# Apply space masking
|
| 271 |
+
predictions = self._apply_space_mask(predictions, batch_input_ids)
|
| 272 |
+
|
| 273 |
+
return predictions
|
models/catt-diacritizer/source.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
https://huggingface.co/TigreGotico/catt-diacritizer
|
models/catt-diacritizer/tashkeel_tokenizer_onnx.py
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import re
|
| 3 |
+
import numpy as np
|
| 4 |
+
import bw2ar
|
| 5 |
+
from utils import HARAKAT_PAT
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class TashkeelTokenizer:
|
| 9 |
+
|
| 10 |
+
def __init__(self):
|
| 11 |
+
self.letters = [' ', '$', '&', "'", '*', '<', '>', 'A', 'D', 'E', 'H', 'S', 'T', 'Y', 'Z',
|
| 12 |
+
'b', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'm', 'n', 'p', 'q', 'r', 's', 't',
|
| 13 |
+
'v', 'w', 'x', 'y', 'z', '|', '}'
|
| 14 |
+
]
|
| 15 |
+
self.letters = ['<PAD>', '<BOS>', '<EOS>'] + self.letters + ['<MASK>']
|
| 16 |
+
|
| 17 |
+
self.no_tashkeel_tag = '<NT>'
|
| 18 |
+
self.tashkeel_list = ['<NT>', '<SD>', '<SDD>', '<SF>', '<SFF>', '<SK>',
|
| 19 |
+
'<SKK>', 'F', 'K', 'N', 'a', 'i', 'o', 'u', '~']
|
| 20 |
+
|
| 21 |
+
self.tashkeel_list = ['<PAD>', '<BOS>', '<EOS>'] + self.tashkeel_list
|
| 22 |
+
|
| 23 |
+
self.tashkeel_map = {c:i for i,c in enumerate(self.tashkeel_list)}
|
| 24 |
+
self.letters_map = {c:i for i,c in enumerate(self.letters)}
|
| 25 |
+
self.inverse_tags = {
|
| 26 |
+
'~a': '<SF>', # shaddah and fatHa
|
| 27 |
+
'~u': '<SD>', # shaddah and Damma
|
| 28 |
+
'~i': '<SK>', # shaddah and kasra
|
| 29 |
+
'~F': '<SFF>', # shaddah and fatHatayn
|
| 30 |
+
'~N': '<SDD>', # shaddah and Dammatayn
|
| 31 |
+
'~K': '<SKK>' # shaddah and kasratayn
|
| 32 |
+
}
|
| 33 |
+
self.tags = {v:k for k,v in self.inverse_tags.items()}
|
| 34 |
+
self.shaddah_last = ['a~', 'u~', 'i~', 'F~', 'N~', 'K~']
|
| 35 |
+
self.shaddah_first = ['~a', '~u', '~i', '~F', '~N', '~K']
|
| 36 |
+
self.tahkeel_chars = ['F','N','K','a', 'u', 'i', '~', 'o']
|
| 37 |
+
|
| 38 |
+
def clean_text(self, text):
|
| 39 |
+
text = re.sub(u'[%s]' % u'\u0640', '', text) # strip tatweel
|
| 40 |
+
text = text.replace('ٱ', 'ا')
|
| 41 |
+
return ' '.join(re.sub(u"[^\u0621-\u063A\u0640-\u0652\u0670\u0671\ufefb\ufef7\ufef5\ufef9 ]", " ", text, flags=re.UNICODE).split())
|
| 42 |
+
|
| 43 |
+
def check_match(self, text_with_tashkeel, letter_n_tashkeel_pairs):
|
| 44 |
+
text_with_tashkeel = text_with_tashkeel.strip()
|
| 45 |
+
# test if the reconstructed text with tashkeel is the same as the original one
|
| 46 |
+
syn_text = self.combine_tashkeel_with_text(letter_n_tashkeel_pairs)
|
| 47 |
+
return syn_text == text_with_tashkeel or syn_text == self.unify_shaddah_position(text_with_tashkeel)
|
| 48 |
+
|
| 49 |
+
def unify_shaddah_position(self, text_with_tashkeel):
|
| 50 |
+
# unify the order of shaddah and the harakah to make shaddah always at the beginning
|
| 51 |
+
for i in range(len(self.shaddah_first)):
|
| 52 |
+
text_with_tashkeel = text_with_tashkeel.replace(self.shaddah_last[i], self.shaddah_first[i])
|
| 53 |
+
return text_with_tashkeel
|
| 54 |
+
|
| 55 |
+
def split_tashkeel_from_text(self, text_with_tashkeel, test_match=True):
|
| 56 |
+
text_with_tashkeel = self.clean_text(text_with_tashkeel)
|
| 57 |
+
text_with_tashkeel = bw2ar.transliterate_text(text_with_tashkeel, 'ar2bw')
|
| 58 |
+
text_with_tashkeel = text_with_tashkeel.replace('`', '') # remove dagger 'alif
|
| 59 |
+
|
| 60 |
+
# unify the order of shaddah and the harakah to make shaddah always at the beginning
|
| 61 |
+
text_with_tashkeel = self.unify_shaddah_position(text_with_tashkeel)
|
| 62 |
+
|
| 63 |
+
# remove duplicated harakat
|
| 64 |
+
for i in range(len(self.tahkeel_chars)):
|
| 65 |
+
text_with_tashkeel = text_with_tashkeel.replace(self.tahkeel_chars[i]*2, self.tahkeel_chars[i])
|
| 66 |
+
|
| 67 |
+
letter_n_tashkeel_pairs = []
|
| 68 |
+
for i in range(len(text_with_tashkeel)): # go over the whole text
|
| 69 |
+
# check if the first character is a normal letter and the second character is a tashkeel
|
| 70 |
+
if i < (len(text_with_tashkeel) - 1) and not text_with_tashkeel[i] in self.tashkeel_list and text_with_tashkeel[i+1] in self.tashkeel_list:
|
| 71 |
+
# IMPORTANT: check if tashkeel is Shaddah, then there might be another Tashkeel char associated with it. If so,
|
| 72 |
+
# replace both Shaddah and the Tashkeel chars with the appropriate tag
|
| 73 |
+
if text_with_tashkeel[i+1] == '~':
|
| 74 |
+
# IMPORTANT: the following if statement depends on the concept of short circuit!!
|
| 75 |
+
# The first condition checks if there are still more chars before it access position i+2
|
| 76 |
+
# "text_with_tashkeel[i+2]" since it causes "index out of range" exception. Notice that
|
| 77 |
+
# Shaddah here is put in the first position before the Harakah.
|
| 78 |
+
if i+2 < len(text_with_tashkeel) and f'~{text_with_tashkeel[i+2]}' in self.inverse_tags:
|
| 79 |
+
letter_n_tashkeel_pairs.append((text_with_tashkeel[i], self.inverse_tags[f'~{text_with_tashkeel[i+2]}']))
|
| 80 |
+
else:
|
| 81 |
+
# if it is only Shaddah, just add it to the list
|
| 82 |
+
letter_n_tashkeel_pairs.append((text_with_tashkeel[i], '~'))
|
| 83 |
+
else:
|
| 84 |
+
letter_n_tashkeel_pairs.append((text_with_tashkeel[i], text_with_tashkeel[i+1]))
|
| 85 |
+
# if the character at position i is a normal letter and has no Tashkeel, then add
|
| 86 |
+
# it with the tag "self.no_tashkeel_tag"
|
| 87 |
+
# IMPORTANT: this elif block ensures also that there is no two or more consecutive tashkeel other than shaddah
|
| 88 |
+
elif not text_with_tashkeel[i] in self.tashkeel_list:
|
| 89 |
+
letter_n_tashkeel_pairs.append((text_with_tashkeel[i], self.no_tashkeel_tag))
|
| 90 |
+
|
| 91 |
+
if test_match:
|
| 92 |
+
# test if the split is done correctly by ensuring that we can retrieve back the original text
|
| 93 |
+
assert self.check_match(text_with_tashkeel, letter_n_tashkeel_pairs)
|
| 94 |
+
return [('<BOS>', '<BOS>')] + letter_n_tashkeel_pairs + [('<EOS>', '<EOS>')]
|
| 95 |
+
|
| 96 |
+
def combine_tashkeel_with_text(self, letter_n_tashkeel_pairs):
|
| 97 |
+
combined_with_tashkeel = []
|
| 98 |
+
for letter, tashkeel in letter_n_tashkeel_pairs:
|
| 99 |
+
combined_with_tashkeel.append(letter)
|
| 100 |
+
if tashkeel in self.tags:
|
| 101 |
+
combined_with_tashkeel.append(self.tags[tashkeel])
|
| 102 |
+
elif tashkeel != self.no_tashkeel_tag:
|
| 103 |
+
combined_with_tashkeel.append(tashkeel)
|
| 104 |
+
text = ''.join(combined_with_tashkeel)
|
| 105 |
+
return text
|
| 106 |
+
|
| 107 |
+
def encode(self, text_with_tashkeel, test_match=True):
|
| 108 |
+
letter_n_tashkeel_pairs = self.split_tashkeel_from_text(text_with_tashkeel, test_match)
|
| 109 |
+
text, tashkeel = zip(*letter_n_tashkeel_pairs)
|
| 110 |
+
input_ids = [self.letters_map[c] for c in text]
|
| 111 |
+
target_ids = [self.tashkeel_map[c] for c in tashkeel]
|
| 112 |
+
return np.array(input_ids, dtype=np.int64), np.array(target_ids, dtype=np.int64)
|
| 113 |
+
|
| 114 |
+
def filter_tashkeel(self, tashkeel):
|
| 115 |
+
tmp = []
|
| 116 |
+
for i, t in enumerate(tashkeel):
|
| 117 |
+
if i != 0 and t == '<BOS>':
|
| 118 |
+
t = self.no_tashkeel_tag
|
| 119 |
+
elif i != (len(tashkeel) - 1) and t == '<EOS>':
|
| 120 |
+
t = self.no_tashkeel_tag
|
| 121 |
+
tmp.append(t)
|
| 122 |
+
tashkeel = tmp
|
| 123 |
+
return tashkeel
|
| 124 |
+
|
| 125 |
+
def decode(self, input_ids, target_ids):
|
| 126 |
+
ar_texts = []
|
| 127 |
+
for j in range(len(input_ids)):
|
| 128 |
+
letters = [self.letters[i] for i in input_ids[j]]
|
| 129 |
+
tashkeel = [self.tashkeel_list[i] for i in target_ids[j]]
|
| 130 |
+
|
| 131 |
+
letters = list(filter(lambda x: x != '<BOS>' and x != '<EOS>' and x != '<PAD>', letters))
|
| 132 |
+
tashkeel = self.filter_tashkeel(tashkeel)
|
| 133 |
+
tashkeel = list(filter(lambda x: x != '<BOS>' and x != '<EOS>' and x != '<PAD>', tashkeel))
|
| 134 |
+
|
| 135 |
+
# VERY IMPORTANT NOTE: zip takes min(len(letters), len(tashkeel)) and discard the reset of letters / tashkeels
|
| 136 |
+
letter_n_tashkeel_pairs = list(zip(letters, tashkeel))
|
| 137 |
+
bw_text = self.combine_tashkeel_with_text(letter_n_tashkeel_pairs)
|
| 138 |
+
ar_text = bw2ar.transliterate_text(bw_text, 'bw2ar')
|
| 139 |
+
ar_texts.append(ar_text)
|
| 140 |
+
return ar_texts
|
| 141 |
+
|
| 142 |
+
def get_tashkeel_with_case_ending(self, text, case_ending=True):
|
| 143 |
+
text_split = self.split_tashkeel_from_text(text, test_match=False)
|
| 144 |
+
text_spaces_indecies = [i for i, el in enumerate(text_split) if el == (' ', '<NT>')]
|
| 145 |
+
new_text_split = []
|
| 146 |
+
for i, el in enumerate(text_split):
|
| 147 |
+
if not case_ending and (i+1) in text_spaces_indecies:
|
| 148 |
+
el = (el[0], '<NT>') # no case ending
|
| 149 |
+
new_text_split.append(el)
|
| 150 |
+
letters, tashkeel = zip(*new_text_split)
|
| 151 |
+
return letters, tashkeel
|
| 152 |
+
|
| 153 |
+
def remove_tashkeel(self, text):
|
| 154 |
+
text = HARAKAT_PAT.sub('', text)
|
| 155 |
+
text = re.sub(u"[\u064E]", "", text, flags=re.UNICODE) # fattha
|
| 156 |
+
text = re.sub(u"[\u0671]", "", text, flags=re.UNICODE) # waSla
|
| 157 |
+
return text
|
| 158 |
+
|
models/catt-diacritizer/utils.py
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
note: this code is used in bw2ar.py file
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
#!/usr/bin/python
|
| 6 |
+
# -*- coding=utf-8 -*-
|
| 7 |
+
#---
|
| 8 |
+
# $Id: arabic.py,v 1.6 2003/04/22 17:18:22 elzubeir Exp $
|
| 9 |
+
#
|
| 10 |
+
# ------------
|
| 11 |
+
# Description:
|
| 12 |
+
# ------------
|
| 13 |
+
#
|
| 14 |
+
# Arabic codes
|
| 15 |
+
#
|
| 16 |
+
# (C) Copyright 2003, Arabeyes, Mohammed Elzubeir
|
| 17 |
+
# (C) Copyright 2019, Faris Abdullah Alasmary
|
| 18 |
+
# -----------------
|
| 19 |
+
# Revision Details: (Updated by Revision Control System)
|
| 20 |
+
# -----------------
|
| 21 |
+
# $Date: 2003/04/22 17:18:22 $
|
| 22 |
+
# $Author: elzubeir $
|
| 23 |
+
# $Revision: 1.6 $
|
| 24 |
+
# $Source: /home/arabeyes/cvs/projects/duali/pyduali/pyduali/arabic.py,v $
|
| 25 |
+
#
|
| 26 |
+
# This program is written under the BSD License.
|
| 27 |
+
#---
|
| 28 |
+
""" Constants for arabic """
|
| 29 |
+
import re
|
| 30 |
+
COMMA = u'\u060C'
|
| 31 |
+
SEMICOLON = u'\u061B'
|
| 32 |
+
QUESTION = u'\u061F'
|
| 33 |
+
HAMZA = u'\u0621'
|
| 34 |
+
ALEF_MADDA = u'\u0622'
|
| 35 |
+
ALEF_HAMZA_ABOVE = u'\u0623'
|
| 36 |
+
WAW_HAMZA = u'\u0624'
|
| 37 |
+
ALEF_HAMZA_BELOW = u'\u0625'
|
| 38 |
+
YEH_HAMZA = u'\u0626'
|
| 39 |
+
ALEF = u'\u0627'
|
| 40 |
+
BEH = u'\u0628'
|
| 41 |
+
TEH_MARBUTA = u'\u0629'
|
| 42 |
+
TEH = u'\u062a'
|
| 43 |
+
THEH = u'\u062b'
|
| 44 |
+
JEEM = u'\u062c'
|
| 45 |
+
HAH = u'\u062d'
|
| 46 |
+
KHAH = u'\u062e'
|
| 47 |
+
DAL = u'\u062f'
|
| 48 |
+
THAL = u'\u0630'
|
| 49 |
+
REH = u'\u0631'
|
| 50 |
+
ZAIN = u'\u0632'
|
| 51 |
+
SEEN = u'\u0633'
|
| 52 |
+
SHEEN = u'\u0634'
|
| 53 |
+
SAD = u'\u0635'
|
| 54 |
+
DAD = u'\u0636'
|
| 55 |
+
TAH = u'\u0637'
|
| 56 |
+
ZAH = u'\u0638'
|
| 57 |
+
AIN = u'\u0639'
|
| 58 |
+
GHAIN = u'\u063a'
|
| 59 |
+
TATWEEL = u'\u0640'
|
| 60 |
+
FEH = u'\u0641'
|
| 61 |
+
QAF = u'\u0642'
|
| 62 |
+
KAF = u'\u0643'
|
| 63 |
+
LAM = u'\u0644'
|
| 64 |
+
MEEM = u'\u0645'
|
| 65 |
+
NOON = u'\u0646'
|
| 66 |
+
HEH = u'\u0647'
|
| 67 |
+
WAW = u'\u0648'
|
| 68 |
+
ALEF_MAKSURA = u'\u0649'
|
| 69 |
+
YEH = u'\u064a'
|
| 70 |
+
MADDA_ABOVE = u'\u0653'
|
| 71 |
+
HAMZA_ABOVE = u'\u0654'
|
| 72 |
+
HAMZA_BELOW = u'\u0655'
|
| 73 |
+
ZERO = u'\u0660'
|
| 74 |
+
ONE = u'\u0661'
|
| 75 |
+
TWO = u'\u0662'
|
| 76 |
+
THREE = u'\u0663'
|
| 77 |
+
FOUR = u'\u0664'
|
| 78 |
+
FIVE = u'\u0665'
|
| 79 |
+
SIX = u'\u0666'
|
| 80 |
+
SEVEN = u'\u0667'
|
| 81 |
+
EIGHT = u'\u0668'
|
| 82 |
+
NINE = u'\u0669'
|
| 83 |
+
PERCENT = u'\u066a'
|
| 84 |
+
DECIMAL = u'\u066b'
|
| 85 |
+
THOUSANDS = u'\u066c'
|
| 86 |
+
STAR = u'\u066d'
|
| 87 |
+
MINI_ALEF = u'\u0670'
|
| 88 |
+
ALEF_WASLA = u'\u0671'
|
| 89 |
+
FULL_STOP = u'\u06d4'
|
| 90 |
+
BYTE_ORDER_MARK = u'\ufeff'
|
| 91 |
+
|
| 92 |
+
# Diacritics
|
| 93 |
+
FATHATAN = u'\u064b'
|
| 94 |
+
DAMMATAN = u'\u064c'
|
| 95 |
+
KASRATAN = u'\u064d'
|
| 96 |
+
FATHA = u'\u064e'
|
| 97 |
+
DAMMA = u'\u064f'
|
| 98 |
+
KASRA = u'\u0650'
|
| 99 |
+
SHADDA = u'\u0651'
|
| 100 |
+
SUKUN = u'\u0652'
|
| 101 |
+
|
| 102 |
+
#Ligatures
|
| 103 |
+
LAM_ALEF = u'\ufefb'
|
| 104 |
+
LAM_ALEF_HAMZA_ABOVE = u'\ufef7'
|
| 105 |
+
LAM_ALEF_HAMZA_BELOW = u'\ufef9'
|
| 106 |
+
LAM_ALEF_MADDA_ABOVE = u'\ufef5'
|
| 107 |
+
SIMPLE_LAM_ALEF = u'\u0644\u0627'
|
| 108 |
+
SIMPLE_LAM_ALEF_HAMZA_ABOVE = u'\u0644\u0623'
|
| 109 |
+
SIMPLE_LAM_ALEF_HAMZA_BELOW = u'\u0644\u0625'
|
| 110 |
+
SIMPLE_LAM_ALEF_MADDA_ABOVE = u'\u0644\u0622'
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
HARAKAT_PAT = re.compile(u"["+u"".join([FATHATAN, DAMMATAN, KASRATAN,
|
| 114 |
+
FATHA, DAMMA, KASRA, SUKUN,
|
| 115 |
+
SHADDA])+u"]")
|
| 116 |
+
HAMZAT_PAT = re.compile(u"["+u"".join([WAW_HAMZA, YEH_HAMZA])+u"]")
|
| 117 |
+
ALEFAT_PAT = re.compile(u"["+u"".join([ALEF_MADDA, ALEF_HAMZA_ABOVE,
|
| 118 |
+
ALEF_HAMZA_BELOW, HAMZA_ABOVE,
|
| 119 |
+
HAMZA_BELOW])+u"]")
|
| 120 |
+
LAMALEFAT_PAT = re.compile(u"["+u"".join([LAM_ALEF,
|
| 121 |
+
LAM_ALEF_HAMZA_ABOVE,
|
| 122 |
+
LAM_ALEF_HAMZA_BELOW,
|
| 123 |
+
LAM_ALEF_MADDA_ABOVE])+u"]")
|
| 124 |
+
|
| 125 |
+
def strip_tashkeel(text):
|
| 126 |
+
text = HARAKAT_PAT.sub('', text)
|
| 127 |
+
text = re.sub(u"[\u064E]", "", text, flags=re.UNICODE) # fattha
|
| 128 |
+
text = re.sub(u"[\u0671]", "", text, flags=re.UNICODE) # waSla
|
| 129 |
+
return text
|
| 130 |
+
|
| 131 |
+
def strip_tatweel(text):
|
| 132 |
+
return re.sub(u'[%s]' % TATWEEL, '', text)
|
| 133 |
+
|
| 134 |
+
# remove removing Tashkeel + removing Tatweel + non Arabic chars
|
| 135 |
+
def remove_non_arabic(text):
|
| 136 |
+
text = strip_tashkeel(text)
|
| 137 |
+
text = strip_tatweel(text)
|
| 138 |
+
return ' '.join(re.sub(u"[^\u0621-\u063A\u0641-\u064A ]", " ", text, flags=re.UNICODE).split())
|
| 139 |
+
|
spaces/arabic-auto-tashkeel/.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
|
spaces/arabic-auto-tashkeel/README.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Arabic Auto Tashkeel
|
| 3 |
+
emoji: 🔥
|
| 4 |
+
colorFrom: indigo
|
| 5 |
+
colorTo: yellow
|
| 6 |
+
sdk: gradio
|
| 7 |
+
sdk_version: 4.27.0
|
| 8 |
+
app_file: app.py
|
| 9 |
+
pinned: false
|
| 10 |
+
license: apache-2.0
|
| 11 |
+
---
|
| 12 |
+
|
| 13 |
+
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
spaces/arabic-auto-tashkeel/app.py
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import spaces
|
| 2 |
+
import gradio as gr
|
| 3 |
+
from shakkala import Shakkala
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
import torch
|
| 6 |
+
from eo_pl import TashkeelModel as TashkeelModelEO
|
| 7 |
+
from ed_pl import TashkeelModel as TashkeelModelED
|
| 8 |
+
from tashkeel_tokenizer import TashkeelTokenizer
|
| 9 |
+
from utils import remove_non_arabic
|
| 10 |
+
|
| 11 |
+
# Initialize the Shakkala model
|
| 12 |
+
sh = Shakkala(version=3)
|
| 13 |
+
model, graph = sh.get_model()
|
| 14 |
+
|
| 15 |
+
def infer_shakkala(input_text):
|
| 16 |
+
input_int = sh.prepare_input(input_text)
|
| 17 |
+
logits = model.predict(input_int)[0]
|
| 18 |
+
predicted_harakat = sh.logits_to_text(logits)
|
| 19 |
+
final_output = sh.get_final_text(input_text, predicted_harakat)
|
| 20 |
+
print(final_output)
|
| 21 |
+
return final_output
|
| 22 |
+
|
| 23 |
+
# Initialize the CaTT model and tokenizer
|
| 24 |
+
tokenizer = TashkeelTokenizer()
|
| 25 |
+
eo_ckpt_path = Path(__file__).parent / 'models/best_eo_mlm_ns_epoch_193.pt'
|
| 26 |
+
ed_ckpt_path = Path(__file__).parent / 'models/best_ed_mlm_ns_epoch_178.pt'
|
| 27 |
+
|
| 28 |
+
device = 'cuda' if torch.cuda.is_available() else 'cpu'
|
| 29 |
+
max_seq_len = 1024
|
| 30 |
+
print('Creating Model...')
|
| 31 |
+
eo_model = TashkeelModelEO(tokenizer, max_seq_len=max_seq_len, n_layers=6, learnable_pos_emb=False)
|
| 32 |
+
ed_model = TashkeelModelED(tokenizer, max_seq_len=max_seq_len, n_layers=3, learnable_pos_emb=False)
|
| 33 |
+
|
| 34 |
+
eo_model.load_state_dict(torch.load(eo_ckpt_path, map_location=device))
|
| 35 |
+
eo_model.eval().to(device)
|
| 36 |
+
ed_model.load_state_dict(torch.load(ed_ckpt_path, map_location=device))
|
| 37 |
+
ed_model.eval().to(device)
|
| 38 |
+
|
| 39 |
+
@spaces.GPU()
|
| 40 |
+
def infer_catt(input_text, choose_model):
|
| 41 |
+
input_text = remove_non_arabic(input_text)
|
| 42 |
+
batch_size = 16
|
| 43 |
+
verbose = True
|
| 44 |
+
if choose_model == 'Encoder-Only':
|
| 45 |
+
output_text = eo_model.do_tashkeel_batch([input_text], batch_size, verbose)
|
| 46 |
+
else:
|
| 47 |
+
output_text = ed_model.do_tashkeel_batch([input_text], batch_size, verbose)
|
| 48 |
+
|
| 49 |
+
return output_text[0]
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
examples = ["السلام عليكم ورحمة الله وبركاته", "العلم نور", "الحمد لله"]
|
| 54 |
+
|
| 55 |
+
with gr.Blocks(title="Arabic Tashkeel") as demo:
|
| 56 |
+
gr.HTML("<center><h1>Arabic Tashkeel</h1></center>")
|
| 57 |
+
gr.HTML(
|
| 58 |
+
"<center><p>Compare different methods for adding tashkeel to Arabic text.</p></center>"
|
| 59 |
+
)
|
| 60 |
+
with gr.Tab(label="CATT"):
|
| 61 |
+
gr.HTML("<center><h2>CATT: Character-based Arabic Tashkeel Transformer</h2></center>")
|
| 62 |
+
gr.HTML("<center><a href='https://github.com/abjadai/catt'>GitHub</a> - <a href='https://arxiv.org/abs/2407.03236'>Arxiv Paper</a></center>")
|
| 63 |
+
|
| 64 |
+
with gr.Row():
|
| 65 |
+
with gr.Column():
|
| 66 |
+
text_input1 = gr.Textbox(label="Input Text", rtl=True, text_align="right")
|
| 67 |
+
choose_model = gr.Radio(
|
| 68 |
+
label="Choose Model",
|
| 69 |
+
value="Encoder-Decoder",
|
| 70 |
+
choices=["Encoder-Only", "Encoder-Decoder"],
|
| 71 |
+
)
|
| 72 |
+
with gr.Row():
|
| 73 |
+
clear_button1 = gr.Button(value="Clear", variant="secondary")
|
| 74 |
+
submit_button1 = gr.Button(value="Add Tashkeel", variant="primary")
|
| 75 |
+
|
| 76 |
+
with gr.Column():
|
| 77 |
+
text_output1 = gr.Textbox(label="Output Text", rtl=True, text_align="right")
|
| 78 |
+
|
| 79 |
+
gr.Examples(examples, text_input1, cache_examples=False)
|
| 80 |
+
|
| 81 |
+
submit_button1.click(infer_catt, inputs=[text_input1, choose_model], outputs=text_output1)
|
| 82 |
+
clear_button1.click(lambda: ("", ""), outputs=[text_input1, text_output1])
|
| 83 |
+
|
| 84 |
+
with gr.Tab(label="Shakkala"):
|
| 85 |
+
gr.HTML("<center><h2>Shakkala: Arabic Diacritization</h2></center>")
|
| 86 |
+
gr.HTML("<center><a href='https://github.com/Barqawiz/Shakkala'>GitHub</a> - <a href='https://pypi.org/project/shakkala/'>PyPi Package</a></center>")
|
| 87 |
+
with gr.Row():
|
| 88 |
+
with gr.Column():
|
| 89 |
+
text_input2 = gr.Textbox(label="Input Text", rtl=True, text_align="right")
|
| 90 |
+
with gr.Row():
|
| 91 |
+
clear_button2 = gr.Button(value="Clear", variant="secondary")
|
| 92 |
+
submit_button2 = gr.Button(value="Apply Tashkeel", variant="primary")
|
| 93 |
+
with gr.Column():
|
| 94 |
+
text_output2 = gr.Textbox(
|
| 95 |
+
lines=1, label="Output Text", rtl=True, text_align="right"
|
| 96 |
+
)
|
| 97 |
+
|
| 98 |
+
submit_button2.click(infer_shakkala, inputs=text_input2, outputs=text_output2)
|
| 99 |
+
clear_button2.click(lambda: ("", ""), outputs=[text_input2, text_output2])
|
| 100 |
+
|
| 101 |
+
gr.Examples(examples, text_input2, cache_examples=False)
|
| 102 |
+
|
| 103 |
+
if __name__ == '__main__':
|
| 104 |
+
demo.queue().launch()
|
spaces/arabic-auto-tashkeel/bw2ar.py
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
functions to convert Arabic words/text into buckwalter encoding and vice versa
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
import sys
|
| 6 |
+
import re
|
| 7 |
+
import utils
|
| 8 |
+
|
| 9 |
+
buck2uni = {
|
| 10 |
+
"'": u"\u0621", # hamza-on-the-line
|
| 11 |
+
"|": u"\u0622", # madda
|
| 12 |
+
">": u"\u0623", # hamza-on-'alif
|
| 13 |
+
"&": u"\u0624", # hamza-on-waaw
|
| 14 |
+
"<": u"\u0625", # hamza-under-'alif
|
| 15 |
+
"}": u"\u0626", # hamza-on-yaa'
|
| 16 |
+
"A": u"\u0627", # bare 'alif
|
| 17 |
+
"b": u"\u0628", # baa'
|
| 18 |
+
"p": u"\u0629", # taa' marbuuTa
|
| 19 |
+
"t": u"\u062A", # taa'
|
| 20 |
+
"v": u"\u062B", # thaa'
|
| 21 |
+
"j": u"\u062C", # jiim
|
| 22 |
+
"H": u"\u062D", # Haa'
|
| 23 |
+
"x": u"\u062E", # khaa'
|
| 24 |
+
"d": u"\u062F", # daal
|
| 25 |
+
"*": u"\u0630", # dhaal
|
| 26 |
+
"r": u"\u0631", # raa'
|
| 27 |
+
"z": u"\u0632", # zaay
|
| 28 |
+
"s": u"\u0633", # siin
|
| 29 |
+
"$": u"\u0634", # shiin
|
| 30 |
+
"S": u"\u0635", # Saad
|
| 31 |
+
"D": u"\u0636", # Daad
|
| 32 |
+
"T": u"\u0637", # Taa'
|
| 33 |
+
"Z": u"\u0638", # Zaa' (DHaa')
|
| 34 |
+
"E": u"\u0639", # cayn
|
| 35 |
+
"g": u"\u063A", # ghayn
|
| 36 |
+
"_": u"\u0640", # taTwiil
|
| 37 |
+
"f": u"\u0641", # faa'
|
| 38 |
+
"q": u"\u0642", # qaaf
|
| 39 |
+
"k": u"\u0643", # kaaf
|
| 40 |
+
"l": u"\u0644", # laam
|
| 41 |
+
"m": u"\u0645", # miim
|
| 42 |
+
"n": u"\u0646", # nuun
|
| 43 |
+
"h": u"\u0647", # haa'
|
| 44 |
+
"w": u"\u0648", # waaw
|
| 45 |
+
"Y": u"\u0649", # 'alif maqSuura
|
| 46 |
+
"y": u"\u064A", # yaa'
|
| 47 |
+
"F": u"\u064B", # fatHatayn
|
| 48 |
+
"N": u"\u064C", # Dammatayn
|
| 49 |
+
"K": u"\u064D", # kasratayn
|
| 50 |
+
"a": u"\u064E", # fatHa
|
| 51 |
+
"u": u"\u064F", # Damma
|
| 52 |
+
"i": u"\u0650", # kasra
|
| 53 |
+
"~": u"\u0651", # shaddah
|
| 54 |
+
"o": u"\u0652", # sukuun
|
| 55 |
+
"`": u"\u0670", # dagger 'alif
|
| 56 |
+
"{": u"\u0671", # waSla
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
# For a reverse transliteration (Unicode -> Buckwalter), a dictionary
|
| 60 |
+
# which is the reverse of the above buck2uni is essential.
|
| 61 |
+
uni2buck = {}
|
| 62 |
+
|
| 63 |
+
# Iterate through all the items in the buck2uni dict.
|
| 64 |
+
for (key, value) in buck2uni.items():
|
| 65 |
+
# The value from buck2uni becomes a key in uni2buck, and vice
|
| 66 |
+
# versa for the keys.
|
| 67 |
+
uni2buck[value] = key
|
| 68 |
+
|
| 69 |
+
# add special characters
|
| 70 |
+
uni2buck[u"\ufefb"] = "lA"
|
| 71 |
+
uni2buck[u"\ufef7"] = "l>"
|
| 72 |
+
uni2buck[u"\ufef5"] = "l|"
|
| 73 |
+
uni2buck[u"\ufef9"] = "l<"
|
| 74 |
+
|
| 75 |
+
# clean the arabic text from unwanted characters that may cause problem while building the language model
|
| 76 |
+
def clean_text(text):
|
| 77 |
+
text = re.sub(u"[\ufeff]", "", text, flags=re.UNICODE) # strip Unicode Character 'ZERO WIDTH NO-BREAK SPACE' (U+FEFF). For more info, check http://www.fileformat.info/info/unicode/char/feff/index.htm
|
| 78 |
+
text = utils.remove_non_arabic(text)
|
| 79 |
+
text = utils.strip_tashkeel(text)
|
| 80 |
+
text = utils.strip_tatweel(text)
|
| 81 |
+
return text
|
| 82 |
+
|
| 83 |
+
# convert a single word into buckwalter and vice versa
|
| 84 |
+
def transliterate_word(input_word, direction='bw2ar'):
|
| 85 |
+
output_word = ''
|
| 86 |
+
# Loop over each character in the string, bw_word.
|
| 87 |
+
for char in input_word:
|
| 88 |
+
# Look up current char in the dictionary to get its
|
| 89 |
+
# respective value. If there is no match, e.g., chars like
|
| 90 |
+
# spaces, then just stick with the current char without any
|
| 91 |
+
# conversion.
|
| 92 |
+
# if type(char) == bytes:
|
| 93 |
+
# char = char.decode('ascii')
|
| 94 |
+
if direction == 'bw2ar':
|
| 95 |
+
#print('in bw2ar')
|
| 96 |
+
output_word += buck2uni.get(char, char)
|
| 97 |
+
elif direction == 'ar2bw':
|
| 98 |
+
#print('in ar2bw')
|
| 99 |
+
output_word += uni2buck.get(char, char)
|
| 100 |
+
else:
|
| 101 |
+
sys.stderr.write('Error: invalid direction!')
|
| 102 |
+
sys.exit()
|
| 103 |
+
return output_word
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
# convert a text into buckwalter and vice versa
|
| 107 |
+
def transliterate_text(input_text, direction='bw2ar'):
|
| 108 |
+
output_text = ''
|
| 109 |
+
for input_word in input_text.split(' '):
|
| 110 |
+
output_text += transliterate_word(input_word, direction) + ' '
|
| 111 |
+
|
| 112 |
+
return output_text[:-1] # remove the last space ONLY
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
if __name__ == '__main__':
|
| 116 |
+
if len(sys.argv) < 2:
|
| 117 |
+
sys.stderr.write('Usage: INPUT TEXT | python {} DIRECTION(bw2ar|ar2bw)'.format(sys.argv[1]))
|
| 118 |
+
exit(1)
|
| 119 |
+
for line in sys.stdin:
|
| 120 |
+
line = line if sys.argv[1] == 'bw2ar' else clean_text(line)
|
| 121 |
+
output_text = transliterate_text(line, direction=str(sys.argv[1]))
|
| 122 |
+
if output_text.strip() != '':
|
| 123 |
+
sys.stdout.write('{}\n'.format(output_text.strip()))
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
|
spaces/arabic-auto-tashkeel/ed.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import torch.nn as nn
|
| 3 |
+
from transformer import *
|
| 4 |
+
|
| 5 |
+
class Transformer(nn.Module):
|
| 6 |
+
|
| 7 |
+
def __init__(self, src_pad_idx, trg_pad_idx, enc_voc_size, dec_voc_size, d_model, n_head, max_len,
|
| 8 |
+
ffn_hidden, n_layers, drop_prob, learnable_pos_emb=True):
|
| 9 |
+
super().__init__()
|
| 10 |
+
self.src_pad_idx = src_pad_idx
|
| 11 |
+
self.trg_pad_idx = trg_pad_idx
|
| 12 |
+
self.encoder = Encoder(d_model=d_model,
|
| 13 |
+
n_head=n_head,
|
| 14 |
+
max_len=max_len,
|
| 15 |
+
ffn_hidden=ffn_hidden,
|
| 16 |
+
enc_voc_size=enc_voc_size,
|
| 17 |
+
drop_prob=drop_prob,
|
| 18 |
+
n_layers=n_layers,
|
| 19 |
+
padding_idx=src_pad_idx,
|
| 20 |
+
learnable_pos_emb=learnable_pos_emb)
|
| 21 |
+
|
| 22 |
+
self.decoder = Decoder(d_model=d_model,
|
| 23 |
+
n_head=n_head,
|
| 24 |
+
max_len=max_len,
|
| 25 |
+
ffn_hidden=ffn_hidden,
|
| 26 |
+
dec_voc_size=dec_voc_size,
|
| 27 |
+
drop_prob=drop_prob,
|
| 28 |
+
n_layers=n_layers,
|
| 29 |
+
padding_idx=trg_pad_idx,
|
| 30 |
+
learnable_pos_emb=learnable_pos_emb)
|
| 31 |
+
|
| 32 |
+
def get_device(self):
|
| 33 |
+
return next(self.parameters()).device
|
| 34 |
+
|
| 35 |
+
def forward(self, src, trg):
|
| 36 |
+
device = self.get_device()
|
| 37 |
+
src_mask = self.make_pad_mask(src, src, self.src_pad_idx, self.src_pad_idx).to(device)
|
| 38 |
+
src_trg_mask = self.make_pad_mask(trg, src, self.trg_pad_idx, self.src_pad_idx).to(device)
|
| 39 |
+
trg_mask = self.make_pad_mask(trg, trg, self.trg_pad_idx, self.trg_pad_idx).to(device) * \
|
| 40 |
+
self.make_no_peak_mask(trg, trg).to(device)
|
| 41 |
+
enc_src = self.encoder(src, src_mask)
|
| 42 |
+
output = self.decoder(trg, enc_src, trg_mask, src_trg_mask)
|
| 43 |
+
return output
|
| 44 |
+
|
| 45 |
+
def make_pad_mask(self, q, k, q_pad_idx, k_pad_idx):
|
| 46 |
+
len_q, len_k = q.size(1), k.size(1)
|
| 47 |
+
# batch_size x 1 x 1 x len_k
|
| 48 |
+
k = k.ne(k_pad_idx).unsqueeze(1).unsqueeze(2)
|
| 49 |
+
# batch_size x 1 x len_q x len_k
|
| 50 |
+
k = k.repeat(1, 1, len_q, 1)
|
| 51 |
+
# batch_size x 1 x len_q x 1
|
| 52 |
+
q = q.ne(q_pad_idx).unsqueeze(1).unsqueeze(3)
|
| 53 |
+
# batch_size x 1 x len_q x len_k
|
| 54 |
+
q = q.repeat(1, 1, 1, len_k)
|
| 55 |
+
mask = k & q
|
| 56 |
+
return mask
|
| 57 |
+
|
| 58 |
+
def make_no_peak_mask(self, q, k):
|
| 59 |
+
len_q, len_k = q.size(1), k.size(1)
|
| 60 |
+
# len_q x len_k
|
| 61 |
+
mask = torch.tril(torch.ones(len_q, len_k)).type(torch.BoolTensor)
|
| 62 |
+
return mask
|
| 63 |
+
|
spaces/arabic-auto-tashkeel/ed_pl.py
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import torch
|
| 3 |
+
import torch.nn as nn
|
| 4 |
+
import pytorch_lightning as pl
|
| 5 |
+
from torch.nn import functional as F
|
| 6 |
+
from torch.utils.data import DataLoader
|
| 7 |
+
from ed import Transformer
|
| 8 |
+
from tqdm import tqdm
|
| 9 |
+
import math
|
| 10 |
+
import torch
|
| 11 |
+
import torch.nn as nn
|
| 12 |
+
|
| 13 |
+
from torch.nn.utils.rnn import pad_sequence
|
| 14 |
+
# sequences is a list of tensors of shape TxH where T is the seqlen and H is the feats dim
|
| 15 |
+
def pad_seq(sequences, batch_first=True, padding_value=0.0, prepadding=True):
|
| 16 |
+
lens = [i.shape[0]for i in sequences]
|
| 17 |
+
padded_sequences = pad_sequence(sequences, batch_first=True, padding_value=padding_value) # NxTxH
|
| 18 |
+
if prepadding:
|
| 19 |
+
for i in range(len(lens)):
|
| 20 |
+
padded_sequences[i] = padded_sequences[i].roll(-lens[i])
|
| 21 |
+
if not batch_first:
|
| 22 |
+
padded_sequences = padded_sequences.transpose(0, 1) # TxNxH
|
| 23 |
+
return padded_sequences
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def get_batches(X, batch_size=16):
|
| 28 |
+
num_batches = math.ceil(len(X) / batch_size)
|
| 29 |
+
for i in range(num_batches):
|
| 30 |
+
x = X[i*batch_size : (i+1)*batch_size]
|
| 31 |
+
yield x
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
class TashkeelModel(pl.LightningModule):
|
| 35 |
+
def __init__(self, tokenizer, max_seq_len, d_model=512, n_layers=3, n_heads=16, drop_prob=0.1, learnable_pos_emb=True):
|
| 36 |
+
|
| 37 |
+
super(TashkeelModel, self).__init__()
|
| 38 |
+
|
| 39 |
+
ffn_hidden = 4 * d_model
|
| 40 |
+
src_pad_idx = tokenizer.letters_map['<PAD>']
|
| 41 |
+
trg_pad_idx = tokenizer.tashkeel_map['<PAD>']
|
| 42 |
+
enc_voc_size = len(tokenizer.letters_map) # 37 + 3
|
| 43 |
+
dec_voc_size = len(tokenizer.tashkeel_map) # 15 + 3
|
| 44 |
+
self.transformer = Transformer(src_pad_idx=src_pad_idx,
|
| 45 |
+
trg_pad_idx=trg_pad_idx,
|
| 46 |
+
d_model=d_model,
|
| 47 |
+
enc_voc_size=enc_voc_size,
|
| 48 |
+
dec_voc_size=dec_voc_size,
|
| 49 |
+
max_len=max_seq_len,
|
| 50 |
+
ffn_hidden=ffn_hidden,
|
| 51 |
+
n_head=n_heads,
|
| 52 |
+
n_layers=n_layers,
|
| 53 |
+
drop_prob=drop_prob,
|
| 54 |
+
learnable_pos_emb=learnable_pos_emb
|
| 55 |
+
)
|
| 56 |
+
|
| 57 |
+
self.criterion = nn.CrossEntropyLoss(ignore_index=tokenizer.tashkeel_map['<PAD>'])
|
| 58 |
+
self.tokenizer = tokenizer
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def forward(self, x, y=None):
|
| 62 |
+
y_pred = self.transformer(x, y)
|
| 63 |
+
return y_pred
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def training_step(self, batch, batch_idx):
|
| 67 |
+
input_ids, target_ids = batch
|
| 68 |
+
input_ids = input_ids[:, :-1]
|
| 69 |
+
y_in = target_ids[:, :-1]
|
| 70 |
+
y_out = target_ids[:, 1:]
|
| 71 |
+
y_pred = self(input_ids, y_in)
|
| 72 |
+
loss = self.criterion(y_pred.transpose(1, 2), y_out)
|
| 73 |
+
|
| 74 |
+
self.log('train_loss', loss, prog_bar=True)
|
| 75 |
+
sch = self.lr_schedulers()
|
| 76 |
+
sch.step()
|
| 77 |
+
self.log('lr', sch.get_last_lr()[0], prog_bar=True)
|
| 78 |
+
return loss
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
def validation_step(self, batch, batch_idx):
|
| 82 |
+
input_ids, target_ids = batch
|
| 83 |
+
input_ids = input_ids[:, :-1]
|
| 84 |
+
y_in = target_ids[:, :-1]
|
| 85 |
+
y_out = target_ids[:, 1:]
|
| 86 |
+
y_pred = self(input_ids, y_in)
|
| 87 |
+
loss = self.criterion(y_pred.transpose(1, 2), y_out)
|
| 88 |
+
|
| 89 |
+
pred_text_with_tashkeels = self.tokenizer.decode(input_ids, y_pred.argmax(2).squeeze())
|
| 90 |
+
true_text_with_tashkeels = self.tokenizer.decode(input_ids, y_out)
|
| 91 |
+
total_val_der_distance = 0
|
| 92 |
+
total_val_der_ref_length = 0
|
| 93 |
+
for i in range(len(true_text_with_tashkeels)):
|
| 94 |
+
pred_text_with_tashkeel = pred_text_with_tashkeels[i]
|
| 95 |
+
true_text_with_tashkeel = true_text_with_tashkeels[i]
|
| 96 |
+
val_der = self.tokenizer.compute_der(true_text_with_tashkeel, pred_text_with_tashkeel)
|
| 97 |
+
total_val_der_distance += val_der['distance']
|
| 98 |
+
total_val_der_ref_length += val_der['ref_length']
|
| 99 |
+
|
| 100 |
+
total_der_error = total_val_der_distance / total_val_der_ref_length
|
| 101 |
+
self.log('val_loss', loss)
|
| 102 |
+
self.log('val_der', torch.FloatTensor([total_der_error]))
|
| 103 |
+
self.log('val_der_distance', torch.FloatTensor([total_val_der_distance]))
|
| 104 |
+
self.log('val_der_ref_length', torch.FloatTensor([total_val_der_ref_length]))
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
def test_step(self, batch, batch_idx):
|
| 108 |
+
input_ids, target_ids = batch
|
| 109 |
+
y_pred = self(input_ids, None)
|
| 110 |
+
loss = self.criterion(y_pred.transpose(1, 2), target_ids)
|
| 111 |
+
self.log('test_loss', loss)
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
def configure_optimizers(self):
|
| 115 |
+
optimizer = torch.optim.AdamW(self.parameters(), lr=3e-4)
|
| 116 |
+
#max_iters = 10000
|
| 117 |
+
#lr_scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=max_iters, eta_min=3e-6)
|
| 118 |
+
gamma = 1 / 1.000001
|
| 119 |
+
#gamma = 1 / 1.0001
|
| 120 |
+
lr_scheduler = torch.optim.lr_scheduler.ExponentialLR(optimizer, gamma)
|
| 121 |
+
opts = {"optimizer": optimizer, "lr_scheduler": lr_scheduler}
|
| 122 |
+
return opts
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
@torch.no_grad()
|
| 126 |
+
def do_tashkeel_batch(self, texts, batch_size=16, verbose=True):
|
| 127 |
+
self.eval()
|
| 128 |
+
device = next(self.parameters()).device
|
| 129 |
+
text_with_tashkeel = []
|
| 130 |
+
data_iter = get_batches(texts, batch_size)
|
| 131 |
+
if verbose:
|
| 132 |
+
num_batches = math.ceil(len(texts) / batch_size)
|
| 133 |
+
data_iter = tqdm(data_iter, total=num_batches)
|
| 134 |
+
for texts_mini in data_iter:
|
| 135 |
+
input_ids_list = []
|
| 136 |
+
for text in texts_mini:
|
| 137 |
+
input_ids, _ = self.tokenizer.encode(text, test_match=False)
|
| 138 |
+
input_ids_list.append(input_ids)
|
| 139 |
+
batch_input_ids = pad_seq(input_ids_list, batch_first=True, padding_value=self.tokenizer.letters_map['<PAD>'], prepadding=False)
|
| 140 |
+
target_ids = torch.LongTensor([[self.tokenizer.tashkeel_map['<BOS>']]] * len(texts_mini)).to(device)
|
| 141 |
+
src = batch_input_ids.to(device)
|
| 142 |
+
|
| 143 |
+
src_mask = self.transformer.make_pad_mask(src, src, self.transformer.src_pad_idx, self.transformer.src_pad_idx).to(device)
|
| 144 |
+
enc_src = self.transformer.encoder(src, src_mask)
|
| 145 |
+
|
| 146 |
+
for i in range(src.shape[1] - 1):
|
| 147 |
+
trg = target_ids
|
| 148 |
+
src_trg_mask = self.transformer.make_pad_mask(trg, src, self.transformer.trg_pad_idx, self.transformer.src_pad_idx).to(device)
|
| 149 |
+
trg_mask = self.transformer.make_pad_mask(trg, trg, self.transformer.trg_pad_idx, self.transformer.trg_pad_idx).to(device) * \
|
| 150 |
+
self.transformer.make_no_peak_mask(trg, trg).to(device)
|
| 151 |
+
|
| 152 |
+
preds = self.transformer.decoder(trg, enc_src, trg_mask, src_trg_mask)
|
| 153 |
+
# IMPORTANT NOTE: the following code snippet is to FORCE the prediction of the input space char to output no_tashkeel tag '<NT>'
|
| 154 |
+
target_ids = torch.cat([target_ids, preds[:, -1].argmax(1).unsqueeze(1)], axis=1)
|
| 155 |
+
target_ids[self.tokenizer.letters_map[' '] == src[:, :target_ids.shape[1]]] = self.tokenizer.tashkeel_map[self.tokenizer.no_tashkeel_tag]
|
| 156 |
+
# target_ids = torch.cat([target_ids, preds[:, -1].argmax(1).unsqueeze(1)], axis=1)
|
| 157 |
+
text_with_tashkeel_mini = self.tokenizer.decode(src, target_ids)
|
| 158 |
+
text_with_tashkeel += text_with_tashkeel_mini
|
| 159 |
+
return text_with_tashkeel
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
@torch.no_grad()
|
| 163 |
+
def do_tashkeel(self, text):
|
| 164 |
+
return self.do_tashkeel_batch([text])[0]
|
spaces/arabic-auto-tashkeel/eo.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch.nn as nn
|
| 2 |
+
from transformer import *
|
| 3 |
+
|
| 4 |
+
class Transformer(nn.Module):
|
| 5 |
+
def __init__(self, src_pad_idx, trg_pad_idx, enc_voc_size, dec_voc_size, d_model, n_head, max_len,
|
| 6 |
+
ffn_hidden, n_layers, drop_prob, learnable_pos_emb=True):
|
| 7 |
+
super().__init__()
|
| 8 |
+
self.src_pad_idx = src_pad_idx
|
| 9 |
+
self.trg_pad_idx = trg_pad_idx
|
| 10 |
+
self.encoder = Encoder(d_model=d_model,
|
| 11 |
+
n_head=n_head,
|
| 12 |
+
max_len=max_len,
|
| 13 |
+
ffn_hidden=ffn_hidden,
|
| 14 |
+
enc_voc_size=enc_voc_size,
|
| 15 |
+
drop_prob=drop_prob,
|
| 16 |
+
n_layers=n_layers,
|
| 17 |
+
padding_idx=src_pad_idx,
|
| 18 |
+
learnable_pos_emb=learnable_pos_emb)
|
| 19 |
+
|
| 20 |
+
self.decoder = nn.Linear(d_model, dec_voc_size)
|
| 21 |
+
|
| 22 |
+
def get_device(self):
|
| 23 |
+
return next(self.parameters()).device
|
| 24 |
+
|
| 25 |
+
def forward(self, src):
|
| 26 |
+
device = self.get_device()
|
| 27 |
+
src_mask = self.make_pad_mask(src, src, self.src_pad_idx, self.src_pad_idx).to(device)
|
| 28 |
+
enc_src = self.encoder(src, src_mask)
|
| 29 |
+
output = self.decoder(enc_src)
|
| 30 |
+
return output
|
| 31 |
+
|
| 32 |
+
def make_pad_mask(self, q, k, q_pad_idx, k_pad_idx):
|
| 33 |
+
len_q, len_k = q.size(1), k.size(1)
|
| 34 |
+
# batch_size x 1 x 1 x len_k
|
| 35 |
+
k = k.ne(k_pad_idx).unsqueeze(1).unsqueeze(2)
|
| 36 |
+
# batch_size x 1 x len_q x len_k
|
| 37 |
+
k = k.repeat(1, 1, len_q, 1)
|
| 38 |
+
# batch_size x 1 x len_q x 1
|
| 39 |
+
q = q.ne(q_pad_idx).unsqueeze(1).unsqueeze(3)
|
| 40 |
+
# batch_size x 1 x len_q x len_k
|
| 41 |
+
q = q.repeat(1, 1, 1, len_k)
|
| 42 |
+
mask = k & q
|
| 43 |
+
return mask
|
| 44 |
+
|
| 45 |
+
def make_no_peak_mask(self, q, k):
|
| 46 |
+
len_q, len_k = q.size(1), k.size(1)
|
| 47 |
+
# len_q x len_k
|
| 48 |
+
mask = torch.tril(torch.ones(len_q, len_k)).type(torch.BoolTensor)
|
| 49 |
+
return mask
|
| 50 |
+
|
spaces/arabic-auto-tashkeel/eo_pl.py
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import torch
|
| 3 |
+
import torch.nn as nn
|
| 4 |
+
import pytorch_lightning as pl
|
| 5 |
+
from torch.nn import functional as F
|
| 6 |
+
from torch.utils.data import DataLoader
|
| 7 |
+
from eo import Transformer
|
| 8 |
+
from tqdm import tqdm
|
| 9 |
+
import math
|
| 10 |
+
import torch
|
| 11 |
+
import torch.nn as nn
|
| 12 |
+
|
| 13 |
+
from torch.nn.utils.rnn import pad_sequence
|
| 14 |
+
# sequences is a list of tensors of shape TxH where T is the seqlen and H is the feats dim
|
| 15 |
+
def pad_seq(sequences, batch_first=True, padding_value=0.0, prepadding=True):
|
| 16 |
+
lens = [i.shape[0]for i in sequences]
|
| 17 |
+
padded_sequences = pad_sequence(sequences, batch_first=True, padding_value=padding_value) # NxTxH
|
| 18 |
+
if prepadding:
|
| 19 |
+
for i in range(len(lens)):
|
| 20 |
+
padded_sequences[i] = padded_sequences[i].roll(-lens[i])
|
| 21 |
+
if not batch_first:
|
| 22 |
+
padded_sequences = padded_sequences.transpose(0, 1) # TxNxH
|
| 23 |
+
return padded_sequences
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def get_batches(X, batch_size=16):
|
| 28 |
+
num_batches = math.ceil(len(X) / batch_size)
|
| 29 |
+
for i in range(num_batches):
|
| 30 |
+
x = X[i*batch_size : (i+1)*batch_size]
|
| 31 |
+
yield x
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
class TashkeelModel(pl.LightningModule):
|
| 35 |
+
def __init__(self, tokenizer, max_seq_len, d_model=512, n_layers=3, n_heads=16, drop_prob=0.1, learnable_pos_emb=True):
|
| 36 |
+
|
| 37 |
+
super(TashkeelModel, self).__init__()
|
| 38 |
+
|
| 39 |
+
ffn_hidden = 4 * d_model
|
| 40 |
+
src_pad_idx = tokenizer.letters_map['<PAD>']
|
| 41 |
+
trg_pad_idx = tokenizer.tashkeel_map['<PAD>']
|
| 42 |
+
enc_voc_size = len(tokenizer.letters_map) # 37 + 3
|
| 43 |
+
dec_voc_size = len(tokenizer.tashkeel_map) # 15 + 3
|
| 44 |
+
self.transformer = Transformer(src_pad_idx=src_pad_idx,
|
| 45 |
+
trg_pad_idx=trg_pad_idx,
|
| 46 |
+
d_model=d_model,
|
| 47 |
+
enc_voc_size=enc_voc_size,
|
| 48 |
+
dec_voc_size=dec_voc_size,
|
| 49 |
+
max_len=max_seq_len,
|
| 50 |
+
ffn_hidden=ffn_hidden,
|
| 51 |
+
n_head=n_heads,
|
| 52 |
+
n_layers=n_layers,
|
| 53 |
+
drop_prob=drop_prob,
|
| 54 |
+
learnable_pos_emb=learnable_pos_emb
|
| 55 |
+
)
|
| 56 |
+
|
| 57 |
+
self.criterion = nn.CrossEntropyLoss(ignore_index=tokenizer.tashkeel_map['<PAD>'])
|
| 58 |
+
self.tokenizer = tokenizer
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def forward(self, x):
|
| 62 |
+
y_pred = self.transformer(x)
|
| 63 |
+
return y_pred
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def training_step(self, batch, batch_idx):
|
| 67 |
+
input_ids, target_ids = batch
|
| 68 |
+
input_ids = input_ids[:, 1:-1]
|
| 69 |
+
y_out = target_ids[:, 1:-1]
|
| 70 |
+
y_pred = self(input_ids)
|
| 71 |
+
loss = self.criterion(y_pred.transpose(1, 2), y_out)
|
| 72 |
+
|
| 73 |
+
self.log('train_loss', loss, prog_bar=True)
|
| 74 |
+
# sch = self.lr_schedulers()
|
| 75 |
+
# sch.step()
|
| 76 |
+
# self.log('lr', sch.get_last_lr()[0], prog_bar=True)
|
| 77 |
+
return loss
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
def validation_step(self, batch, batch_idx):
|
| 81 |
+
input_ids, target_ids = batch
|
| 82 |
+
input_ids = input_ids[:, 1:-1]
|
| 83 |
+
y_out = target_ids[:, 1:-1]
|
| 84 |
+
y_pred = self(input_ids)
|
| 85 |
+
loss = self.criterion(y_pred.transpose(1, 2), y_out)
|
| 86 |
+
|
| 87 |
+
pred_text_with_tashkeels = self.tokenizer.decode(input_ids, y_pred.argmax(2).squeeze())
|
| 88 |
+
true_text_with_tashkeels = self.tokenizer.decode(input_ids, y_out)
|
| 89 |
+
total_val_der_distance = 0
|
| 90 |
+
total_val_der_ref_length = 0
|
| 91 |
+
for i in range(len(true_text_with_tashkeels)):
|
| 92 |
+
pred_text_with_tashkeel = pred_text_with_tashkeels[i]
|
| 93 |
+
true_text_with_tashkeel = true_text_with_tashkeels[i]
|
| 94 |
+
val_der = self.tokenizer.compute_der(true_text_with_tashkeel, pred_text_with_tashkeel)
|
| 95 |
+
total_val_der_distance += val_der['distance']
|
| 96 |
+
total_val_der_ref_length += val_der['ref_length']
|
| 97 |
+
|
| 98 |
+
total_der_error = total_val_der_distance / total_val_der_ref_length
|
| 99 |
+
self.log('val_loss', loss)
|
| 100 |
+
self.log('val_der', torch.FloatTensor([total_der_error]))
|
| 101 |
+
self.log('val_der_distance', torch.FloatTensor([total_val_der_distance]))
|
| 102 |
+
self.log('val_der_ref_length', torch.FloatTensor([total_val_der_ref_length]))
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
def test_step(self, batch, batch_idx):
|
| 106 |
+
input_ids, target_ids = batch
|
| 107 |
+
y_pred = self(input_ids, None)
|
| 108 |
+
loss = self.criterion(y_pred.transpose(1, 2), target_ids)
|
| 109 |
+
self.log('test_loss', loss)
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
def configure_optimizers(self):
|
| 113 |
+
optimizer = torch.optim.AdamW(self.parameters(), lr=3e-5)
|
| 114 |
+
#max_iters = 10000
|
| 115 |
+
#lr_scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=max_iters, eta_min=3e-6)
|
| 116 |
+
gamma = 1 / 1.000001
|
| 117 |
+
#gamma = 1 / 1.0001
|
| 118 |
+
#lr_scheduler = torch.optim.lr_scheduler.ExponentialLR(optimizer, gamma)
|
| 119 |
+
opts = {"optimizer": optimizer} #, "lr_scheduler": lr_scheduler}
|
| 120 |
+
return opts
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
@torch.no_grad()
|
| 124 |
+
def do_tashkeel_batch(self, texts, batch_size=16, verbose=True):
|
| 125 |
+
self.eval()
|
| 126 |
+
device = next(self.parameters()).device
|
| 127 |
+
text_with_tashkeel = []
|
| 128 |
+
data_iter = get_batches(texts, batch_size)
|
| 129 |
+
if verbose:
|
| 130 |
+
num_batches = math.ceil(len(texts) / batch_size)
|
| 131 |
+
data_iter = tqdm(data_iter, total=num_batches)
|
| 132 |
+
for texts_mini in data_iter:
|
| 133 |
+
input_ids_list = []
|
| 134 |
+
for text in texts_mini:
|
| 135 |
+
input_ids, _ = self.tokenizer.encode(text, test_match=False)
|
| 136 |
+
input_ids_list.append(input_ids)
|
| 137 |
+
batch_input_ids = pad_seq(input_ids_list, batch_first=True, padding_value=self.tokenizer.letters_map['<PAD>'], prepadding=False)
|
| 138 |
+
batch_input_ids = batch_input_ids[:, 1:-1].to(device)
|
| 139 |
+
y_pred = self(batch_input_ids)
|
| 140 |
+
y_pred = y_pred.argmax(-1)
|
| 141 |
+
# IMPORTANT NOTE: the following code snippet is to FORCE the prediction of the input space char to output no_tashkeel tag '<NT>'
|
| 142 |
+
y_pred[self.tokenizer.letters_map[' '] == batch_input_ids] = self.tokenizer.tashkeel_map[self.tokenizer.no_tashkeel_tag]
|
| 143 |
+
text_with_tashkeel_mini = self.tokenizer.decode(batch_input_ids, y_pred)
|
| 144 |
+
text_with_tashkeel += text_with_tashkeel_mini
|
| 145 |
+
|
| 146 |
+
return text_with_tashkeel
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
@torch.no_grad()
|
| 150 |
+
def do_tashkeel(self, text):
|
| 151 |
+
return self.do_tashkeel_batch([text])[0]
|
spaces/arabic-auto-tashkeel/models/best_ed_mlm_ns_epoch_178.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:c6297ec54f9af518a59d4b9e5831d3ad4da304435b8c7fb969f6e3a16816ac23
|
| 3 |
+
size 88403510
|