Kodax

Word Embeddings — التعلم العميق

تضمين الكلمات (Word Embeddings)

الشبكات تحتاج أرقاماً. كيف نحوّل الكلمات؟

المحاولة الأولى: One-Hot

مفردات من 10,000 كلمة ← كل كلمة متجه طوله 10,000 فيه 1 واحد. المشاكل:

- ضخم ومليء بالأصفار.

- كل الكلمات متساوية البعد عن بعضها: "ممتاز" بعيدة عن "رائع" بقدر بعدها عن "سيارة".

التضمين: متجهات كثيفة تحمل المعنى

كل كلمة ← متجه قصير (50–300 رقماً) يُتعلم من البيانات. الكلمات المستخدمة في سياقات متشابهة تحصل على متجهات متقاربة.

import numpy as np

# متجهات مصطنعة للتوضيح (البعدان: "إيجابية" و "تتعلق بالطعام")
vec = {
    "رائع":  np.array([0.9, 0.1]),
    "ممتاز": np.array([0.85, 0.05]),
    "سيء":   np.array([-0.8, 0.1]),
    "لذيذ":  np.array([0.7, 0.9]),
}

def cosine(a, b):
    return float(a @ b / (np.linalg.norm(a) * np.linalg.norm(b)))

for w in ["ممتاز", "سيء", "لذيذ"]:
    print(f"تشابه 'رائع' و '{w}': {cosine(vec['رائع'], vec[w]):.2f}")

تشابه جيب التمام (cosine) هو المقياس المعتاد للتقارب بين المتجهات — وهو أساس البحث الدلالي وأنظمة RAG اليوم.

طبقة Embedding

جدول بحث قابل للتدريب: صف لكل كلمة في المفردات.

Embedding(input_dim=حجم المفردات, output_dim=طول المتجه)
المدخل: أرقام الكلمات (عينات × طول الجملة) ← المخرج: (عينات × طول الجملة × طول المتجه)

يُدرب مع بقية الشبكة: الأوزان التي تساعد على المهمة (تصنيف المشاعر مثلاً) تجعل الكلمات المهمة لها متمايزة.

تجهيز النص في Keras 3

TextVectorization تقوم بكل شيء: تقسيم الكلمات، بناء المفردات (adapt)، التحويل لأرقام، والحشو لطول ثابت. (Tokenizer القديمة و input_length في Embedding حُذفتا في Keras 3.)

- الرقم 0 محجوز للحشو، و 1 للكلمات غير المعروفة.

- mask_zero=True في Embedding تجعل الطبقات اللاحقة تتجاهل الحشو.

التجهيز يدوياً بـ Python (للفهم)

texts = ["هذا الفيلم رائع جدا", "فيلم سيء", "قصة رائعة وممثلون رائعون"]
vocab = {"<pad>": 0, "<unk>": 1}
for t in texts:
    for word in t.split():
        vocab.setdefault(word, len(vocab))

def encode(text, length=6):
    ids = [vocab.get(w, 1) for w in text.split()][:length]
    return ids + [0] * (length - len(ids))

print(encode("هذا فيلم رائع"), encode("فيلم ممل جدا"))

لاحظ أن "ممل" غير معروفة ← 1. وأن "رائع" و "رائعة" و "رائعون" كلمات مختلفة تماماً هنا؛ العربية الغنية بالتصريف تستفيد من تقسيم أدق (subword tokenization) كما في النماذج الحديثة.

التضمينات المدربة مسبقاً

Word2Vec و GloVe و FastText (يدعم العربية جيداً) دُربت على مليارات الكلمات؛ يمكنك تحميلها كأوزان ابتدائية لطبقة Embedding. لكن اليوم الأقوى تضمينات سياقية من Transformers: كلمة "عين" تحصل على متجه مختلف في "عين الماء" و "عين الإنسان".

تمرين

في المثال الكامل أضف 6 جمل جديدة (3 إيجابية و 3 سلبية) وأعد التدريب، ثم اختبر جملة فيها كلمة لم تظهر في التدريب. ماذا يحدث لها في المتجهات؟

import numpy as np
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers

texts = np.array([
    "هذا الفيلم رائع جدا",
    "فيلم سيء ومضيعة للوقت",
    "أحببت القصة كثيرا",
    "لن أشاهده مرة أخرى",
    "تمثيل رائع وقصة جميلة",
    "ممل جدا ولا أنصح به",
])
labels = np.array([1, 0, 1, 0, 1, 0])   # 1 = إيجابي، 0 = سلبي

max_tokens, max_length, embedding_dim = 1000, 10, 16

# ===== TextVectorization (Keras 3) بدل Tokenizer المحذوف =====
vectorizer = layers.TextVectorization(max_tokens=max_tokens, output_sequence_length=max_length)
vectorizer.adapt(texts)                       # بناء المفردات من النصوص
print("أول المفردات:", vectorizer.get_vocabulary()[:8])   # '' للحشو و [UNK] للمجهول
print("ترميز أول جملتين:\n", vectorizer(texts[:2]).numpy())

# ===== نموذج يأخذ النص الخام مباشرة =====
model = keras.Sequential([
    keras.Input(shape=(), dtype="string"),
    vectorizer,
    layers.Embedding(max_tokens, embedding_dim, mask_zero=True),   # لا input_length في Keras 3
    layers.GlobalAveragePooling1D(),
    layers.Dense(16, activation="relu"),
    layers.Dense(1, activation="sigmoid"),
])
model.compile(optimizer="adam", loss="binary_crossentropy", metrics=["accuracy"])
model.fit(texts, labels, epochs=40, verbose=0)

for sentence in ["قصة رائعة", "فيلم ممل"]:
    p = float(model.predict(np.array([sentence]), verbose=0)[0, 0])
    print(f"{sentence}: احتمال إيجابي {p:.2f}")

# متجه كلمة متعلَّم من طبقة Embedding
vocab = vectorizer.get_vocabulary()
weights = model.layers[1].get_weights()[0]
print("متجه 'رائع' (أول 4 قيم):", weights[vocab.index("رائع")][:4].round(3))
اختبر فهمك