| import math |
| import torch |
| import torch.nn as nn |
| from fastapi import FastAPI |
| from pydantic import BaseModel |
| from transformers import AutoTokenizer, AutoModelForSeq2SeqLM, pipeline |
|
|
| |
| |
| |
| DEVICE = torch.device('cpu') |
|
|
| FATHA = '\u064E' |
| DAMMA = '\u064F' |
| KASRA = '\u0650' |
| SUKUN = '\u0652' |
| SHADDA = '\u0651' |
| FATHATAN = '\u064B' |
| DAMMATAN = '\u064C' |
| KASRATAN = '\u064D' |
|
|
| ALL_DIACRITICS = set([FATHA, DAMMA, KASRA, SUKUN, SHADDA, FATHATAN, DAMMATAN, KASRATAN]) |
| DIACRITIC_CLASSES = [ |
| '', FATHA, DAMMA, KASRA, SUKUN, SHADDA, |
| SHADDA + FATHA, SHADDA + DAMMA, SHADDA + KASRA, |
| FATHATAN, DAMMATAN, KASRATAN, |
| SHADDA + FATHATAN, SHADDA + DAMMATAN, SHADDA + KASRATAN, |
| ] |
| IDX_TO_DIAC = {i: d for i, d in enumerate(DIACRITIC_CLASSES)} |
| NUM_CLASSES = 15 |
|
|
| def remove_diacritics(text): |
| return ''.join(c for c in text if c not in ALL_DIACRITICS) |
|
|
| |
| |
| |
| class MultiHeadSelfAttention(nn.Module): |
| def __init__(self, hidden_dim, num_heads=8, dropout=0.1): |
| super().__init__() |
| assert hidden_dim % num_heads == 0 |
| self.num_heads = num_heads |
| self.head_dim = hidden_dim // num_heads |
| self.q_proj = nn.Linear(hidden_dim, hidden_dim) |
| self.k_proj = nn.Linear(hidden_dim, hidden_dim) |
| self.v_proj = nn.Linear(hidden_dim, hidden_dim) |
| self.out_proj = nn.Linear(hidden_dim, hidden_dim) |
| self.dropout = nn.Dropout(dropout) |
| self.scale = math.sqrt(self.head_dim) |
|
|
| def forward(self, x, mask=None): |
| B, T, C = x.shape |
| Q = self.q_proj(x).view(B, T, self.num_heads, self.head_dim).transpose(1, 2) |
| K = self.k_proj(x).view(B, T, self.num_heads, self.head_dim).transpose(1, 2) |
| V = self.v_proj(x).view(B, T, self.num_heads, self.head_dim).transpose(1, 2) |
| attn = torch.matmul(Q, K.transpose(-2, -1)) / self.scale |
| if mask is not None: |
| attn_mask = mask.unsqueeze(1).unsqueeze(2) |
| attn = attn.masked_fill(attn_mask == 0, float('-inf')) |
| attn = torch.softmax(attn, dim=-1) |
| attn = self.dropout(attn) |
| out = torch.matmul(attn, V) |
| out = out.transpose(1, 2).contiguous().view(B, T, C) |
| return self.out_proj(out) |
|
|
| class AttentionBlock(nn.Module): |
| def __init__(self, hidden_dim, num_heads=8, dropout=0.1): |
| super().__init__() |
| self.norm1 = nn.LayerNorm(hidden_dim) |
| self.attn = MultiHeadSelfAttention(hidden_dim, num_heads, dropout) |
| self.norm2 = nn.LayerNorm(hidden_dim) |
| self.ff = nn.Sequential( |
| nn.Linear(hidden_dim, hidden_dim * 2), |
| nn.GELU(), |
| nn.Dropout(dropout), |
| nn.Linear(hidden_dim * 2, hidden_dim), |
| nn.Dropout(dropout) |
| ) |
|
|
| def forward(self, x, mask=None): |
| x = x + self.attn(self.norm1(x), mask) |
| x = x + self.ff(self.norm2(x)) |
| return x |
|
|
| class BiLSTMAttentionDiacritizer(nn.Module): |
| def __init__(self, vocab_size, embed_dim=256, hidden_dim=256, |
| num_lstm_layers=3, num_attn_layers=2, num_heads=8, |
| num_classes=15, dropout=0.3): |
| super().__init__() |
| self.embedding = nn.Embedding(vocab_size, embed_dim, padding_idx=0) |
| self.embed_dropout = nn.Dropout(dropout) |
| self.lstm = nn.LSTM( |
| embed_dim, hidden_dim, num_lstm_layers, |
| batch_first=True, bidirectional=True, |
| dropout=dropout if num_lstm_layers > 1 else 0 |
| ) |
| lstm_out_dim = hidden_dim * 2 |
| self.attn_layers = nn.ModuleList([ |
| AttentionBlock(lstm_out_dim, num_heads, dropout) |
| for _ in range(num_attn_layers) |
| ]) |
| self.final_norm = nn.LayerNorm(lstm_out_dim) |
| self.dropout = nn.Dropout(dropout) |
| self.classifier = nn.Sequential( |
| nn.Linear(lstm_out_dim, hidden_dim), |
| nn.GELU(), |
| nn.Dropout(dropout), |
| nn.Linear(hidden_dim, num_classes) |
| ) |
|
|
| def forward(self, input_ids, attention_mask=None): |
| x = self.embed_dropout(self.embedding(input_ids)) |
| lstm_out, _ = self.lstm(x) |
| for attn_layer in self.attn_layers: |
| lstm_out = attn_layer(lstm_out, attention_mask) |
| lstm_out = self.dropout(self.final_norm(lstm_out)) |
| return self.classifier(lstm_out) |
|
|
| |
| |
| |
| print("جاري تحميل نموذج التشكيل...") |
| |
| CHECKPOINT_PATH = 'Tashkeel_model.pt' |
| checkpoint = torch.load(CHECKPOINT_PATH, map_location=DEVICE) |
| char_to_idx = checkpoint['char_to_idx'] |
|
|
| tashkeel_model = BiLSTMAttentionDiacritizer( |
| vocab_size=len(char_to_idx), |
| embed_dim=256, |
| hidden_dim=256, |
| num_lstm_layers=3, |
| num_attn_layers=2, |
| num_heads=8, |
| num_classes=NUM_CLASSES, |
| dropout=0.3 |
| ).to(DEVICE) |
|
|
| tashkeel_model.load_state_dict(checkpoint['model_state_dict']) |
| tashkeel_model.eval() |
|
|
| print("جاري تحميل نموذج AraBART...") |
| |
| ARABART_MODEL_NAME = "CAMeL-Lab/arabart-qalb15-gec-ged-13" |
| arabart_tokenizer = AutoTokenizer.from_pretrained(ARABART_MODEL_NAME) |
| arabart_model = AutoModelForSeq2SeqLM.from_pretrained(ARABART_MODEL_NAME) |
| gec_pipeline = pipeline("text2text-generation", model=arabart_model, tokenizer=arabart_tokenizer) |
|
|
| print("✅ تمت تهيئة جميع النماذج بنجاح!") |
|
|
| |
| |
| |
| def diacritize_text(text, model, char_to_idx, max_len=200): |
| clean = remove_diacritics(text) |
| char_ids = [char_to_idx.get(c, 1) for c in clean] |
| all_preds = [0] * len(char_ids) |
| counts = [0] * len(char_ids) |
| stride = max_len - 40 |
| |
| for start in range(0, max(len(char_ids), 1), stride): |
| chunk = char_ids[start:start+max_len] |
| actual = len(chunk) |
| padded = chunk + [0]*(max_len - actual) |
| mask = [1]*actual + [0]*(max_len - actual) |
| |
| input_t = torch.LongTensor([padded]).to(DEVICE) |
| mask_t = torch.LongTensor([mask]).to(DEVICE) |
| |
| with torch.no_grad(): |
| logits = model(input_t, mask_t) |
| preds = logits[0, :actual].argmax(dim=-1).cpu().tolist() |
| |
| for i, p in enumerate(preds): |
| pos = start + i |
| if pos < len(all_preds): |
| if counts[pos] == 0 or p != 0: |
| all_preds[pos] = p |
| counts[pos] += 1 |
| |
| if start + max_len >= len(char_ids): break |
| |
| result = [] |
| for char, pidx in zip(clean, all_preds): |
| result.append(char) |
| d = IDX_TO_DIAC.get(pidx, '') |
| if d: result.append(d) |
| return ''.join(result) |
|
|
| |
| |
| |
| from fastapi.middleware.cors import CORSMiddleware |
|
|
| app = FastAPI() |
|
|
| |
| app.add_middleware( |
| CORSMiddleware, |
| allow_origins=["*"], |
| allow_credentials=True, |
| allow_methods=["*"], |
| allow_headers=["*"], |
| ) |
|
|
| class TextRequest(BaseModel): |
| text: str |
|
|
| @app.get("/") |
| def read_root(): |
| return { |
| "status": "online", |
| "message": "مرحباً بك في واجهة برمجة تطبيقات نظام نحو (NAHW) للمعالجة الذكية للنصوص", |
| "endpoints": ["/tashkeel", "/spell-check", "/grammar-check"] |
| } |
|
|
| @app.post("/tashkeel") |
| def process_tashkeel(request: TextRequest): |
| result = diacritize_text(request.text, tashkeel_model, char_to_idx) |
| return {"result_text": result} |
|
|
| @app.post("/spell-check") |
| def spell_check(request: TextRequest): |
| result = gec_pipeline(request.text) |
| return {"result_text": result[0]['generated_text']} |
|
|
| @app.post("/grammar-check") |
| def grammar_check(request: TextRequest): |
| result = gec_pipeline(request.text) |
| return {"result_text": result[0]['generated_text']} |