Logo

تقنيات تمثيل النصوص في معالجة اللغة الطبيعية (بما يشمل البيانات العربية)

12 دقيقة قراءة
شرائح الدرس
1 / 15

تقنيات تمثيل النصوص في معالجة اللغة الطبيعية (بما في ذلك البيانات العربية)

تحويل النصوص إلى أرقام: الترميز الأحادي الساخن، وحقيبة الكلمات، وTF-IDF، وتضمينات الكلمات

مقدمة

لا يمكن لنماذج تعلّم الآلة معالجة بيانات النص الخام مباشرة؛ فهي تحتاج إلى مُدخلات رقمية. وهنا يأتي دور تقنيات تمثيل النصوص (Text Representation). سنستعرض فيما يلي طرقًا متعددة لتحويل البيانات النصية إلى فضاءات سمات رقمية (Numeric Feature Spaces).


التحضير


texts = [
    "The quick brown fox jumps over the lazy dog",
    "Natural language processing is a subfield of artificial intelligence",
    "Machine learning enables computers to learn from data without explicit programming",
    "Deep learning is a branch of machine learning focused on neural networks",
    "Word embeddings capture semantic relationships between words in a text corpus"
]
 
test_txt = "Machine learning is awesome"
 
# Build Vocabulary
vocab = sorted(set(word for sentence in texts for word in sentence.split()))

الترميز الأحادي الساخن (One-Hot Encoding)

الترميز الأحادي الساخن هو أسلوب لتمثيل النص رقميًا.

ما هو الترميز الأحادي الساخن؟

  • تُمثَّل كل كلمة فريدة بمتجه (Vector) بحجم يساوي حجم المفردات (Vocabulary).
  • يُضبط موضع واحد فقط في المتجه على القيمة 1، وبقية المواضع على 0.

import numpy as np
word_to_index = {word: idx for idx, word in enumerate(vocab)}
def one_hot_encode(word):
    """Convert a single English word into a one-hot encoded vector."""
    one_hot_vector = np.zeros(len(vocab))
    if word in word_to_index:
        one_hot_vector[word_to_index[word]] = 1  # Set index position to 1
    return one_hot_vector
 
# Example: Encode individual words
example_words = test_txt.split()
one_hot_vectors = {word: one_hot_encode(word) for word in example_words}
 
# Print One-Hot Encodings
for word, vector in one_hot_vectors.items():
    print(f"{word}: {vector}")
Machine: [0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
learning: [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
is: [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
awesome: [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
import pandas as pd
import numpy as np
from sklearn.preprocessing import OneHotEncoder
 
encoder = OneHotEncoder(sparse_output=False,handle_unknown='ignore')
vocab_np = np.array(vocab).reshape(-1, 1)
encoder.fit(vocab_np)
test_txt_words = np.array(test_txt.lower().split()).reshape(-1, 1)
encoded_words = encoder.transform(test_txt_words)
for word, vector in zip(test_txt_words.flatten(), encoded_words):
    print(f"{word}: {vector}")
machine: [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
learning: [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
is: [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
awesome: [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]

الترميز الثنائي (Binary Encoding) - أبسط صور حقيبة الكلمات (Bag of Words)


ما هو الترميز الثنائي؟

الترميز الثنائي طريقة بسيطة لتمثيل النص رقميًا. وفكرته الأساسية هي:

  • بناء مفردات (Vocabulary) من جميع الكلمات الفريدة في مجموعة البيانات.
  • إنشاء متجه (Vector) لكل مستند، بحيث يقابل كل موضع فيه كلمة من المفردات.
  • إن وُجدت الكلمة في المستند نضع 1، وإلا نضع 0.
 
def binary_transform(text):
    """Convert text into a binary vector."""
    output = np.zeros(len(vocab))
    words = set(text.split())  # Tokenize the input
    for i, v in enumerate(vocab):
        output[i] = v in words  # Set 1 if the word exists
    return output
 
print(binary_transform(test_txt))
[0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1.
 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
from sklearn.feature_extraction.text import CountVectorizer
import pandas as pd
 
vec = CountVectorizer(binary=True)
vec.fit(texts)
 
df = pd.DataFrame(vec.transform(texts).toarray(), columns=sorted(vec.vocabulary_.keys()))
print(df)
   artificial  between  branch  brown  capture  computers  corpus  data  deep  \
0           0        0       0      1        0          0       0     0     0   
1           1        0       0      0        0          0       0     0     0   
2           0        0       0      0        0          1       0     1     0   
3           0        0       1      0        0          0       0     0     1   
4           0        1       0      0        1          0       1     0     0   
 
   dog  ...  quick  relationships  semantic  subfield  text  the  to  without  \
0    1  ...      1              0         0         0     0    1   0        0   
1    0  ...      0              0         0         1     0    0   0        0   
2    0  ...      0              0         0         0     0    0   1        1   
3    0  ...      0              0         0         0     0    0   0        0   
4    0  ...      0              1         1         0     1    0   0        0   
 
   word  words  
0     0      0  
1     0      0  
2     0      0  
3     0      0  
4     1      1  
 
[5 rows x 43 columns]
print(sorted(vec.vocabulary_.keys()))
print(vec.transform([test_txt]).toarray())
['artificial', 'between', 'branch', 'brown', 'capture', 'computers', 'corpus', 'data', 'deep', 'dog', 'embeddings', 'enables', 'explicit', 'focused', 'fox', 'from', 'in', 'intelligence', 'is', 'jumps', 'language', 'lazy', 'learn', 'learning', 'machine', 'natural', 'networks', 'neural', 'of', 'on', 'over', 'processing', 'programming', 'quick', 'relationships', 'semantic', 'subfield', 'text', 'the', 'to', 'without', 'word', 'words']
[[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0
  0 0 0 0 0 0 0]]
df = pd.DataFrame(vec.transform([test_txt]).toarray(), columns=sorted(vec.vocabulary_.keys()))
print(df)
   artificial  between  branch  brown  capture  computers  corpus  data  deep  \
0           0        0       0      0        0          0       0     0     0   
 
   dog  ...  quick  relationships  semantic  subfield  text  the  to  without  \
0    0  ...      0              0         0         0     0    0   0        0   
 
   word  words  
0     0      0  
 
[1 rows x 43 columns]

حقيبة الكلمات (Bag of Words)


ما هي حقيبة الكلمات؟

حقيبة الكلمات (BoW) تشبه الترميز الثنائي، لكنها تسجّل عدد مرات ظهور الكلمة في المستند بدلًا من الاكتفاء بتسجيل وجودها من عدمه (1 أو 0).

from sklearn.feature_extraction.text import CountVectorizer
import pandas as pd
 
vec = CountVectorizer()
vec.fit(texts)
 
df = pd.DataFrame(vec.transform(texts).toarray(), columns=sorted(vec.vocabulary_.keys()))
print(df)
   artificial  between  branch  brown  capture  computers  corpus  data  deep  \
0           0        0       0      1        0          0       0     0     0   
1           1        0       0      0        0          0       0     0     0   
2           0        0       0      0        0          1       0     1     0   
3           0        0       1      0        0          0       0     0     1   
4           0        1       0      0        1          0       1     0     0   
 
   dog  ...  quick  relationships  semantic  subfield  text  the  to  without  \
0    1  ...      1              0         0         0     0    2   0        0   
1    0  ...      0              0         0         1     0    0   0        0   
2    0  ...      0              0         0         0     0    0   1        1   
3    0  ...      0              0         0         0     0    0   0        0   
4    0  ...      0              1         1         0     1    0   0        0   
 
   word  words  
0     0      0  
1     0      0  
2     0      0  
3     0      0  
4     1      1  
 
[5 rows x 43 columns]

TF-IDF (تكرار المصطلح - التكرار العكسي للمستند) - حقيبة كلمات موزونة


لماذا نستخدم TF-IDF؟

  • يقلّل من وزن الكلمات الشائعة بينما يزيد من أهمية الكلمات النادرة ذات الدلالة.
  • يفيد في مهام مثل تصنيف النصوص ومحركات البحث.
from sklearn.feature_extraction.text import TfidfVectorizer
 
vec = TfidfVectorizer()
vec.fit(texts)
 
df = pd.DataFrame(vec.transform(texts).toarray(), columns=sorted(vec.vocabulary_.keys()))
print(df)
   artificial   between    branch     brown   capture  computers    corpus  \
0     0.00000  0.000000  0.000000  0.301511  0.000000   0.000000  0.000000   
1     0.37007  0.000000  0.000000  0.000000  0.000000   0.000000  0.000000   
2     0.00000  0.000000  0.000000  0.000000  0.000000   0.311561  0.000000   
3     0.00000  0.000000  0.307781  0.000000  0.000000   0.000000  0.000000   
4     0.00000  0.316228  0.000000  0.000000  0.316228   0.000000  0.316228   
 
       data      deep       dog  ...     quick  relationships  semantic  \
0  0.000000  0.000000  0.301511  ...  0.301511       0.000000  0.000000   
1  0.000000  0.000000  0.000000  ...  0.000000       0.000000  0.000000   
2  0.311561  0.000000  0.000000  ...  0.000000       0.000000  0.000000   
3  0.000000  0.307781  0.000000  ...  0.000000       0.000000  0.000000   
4  0.000000  0.000000  0.000000  ...  0.000000       0.316228  0.316228   
 
   subfield      text       the        to   without      word     words  
0   0.00000  0.000000  0.603023  0.000000  0.000000  0.000000  0.000000  
1   0.37007  0.000000  0.000000  0.000000  0.000000  0.000000  0.000000  
2   0.00000  0.000000  0.000000  0.311561  0.311561  0.000000  0.000000  
3   0.00000  0.000000  0.000000  0.000000  0.000000  0.000000  0.000000  
4   0.00000  0.316228  0.000000  0.000000  0.000000  0.316228  0.316228  
 
[5 rows x 43 columns]
df = pd.DataFrame(vec.transform([test_txt]).toarray(), columns=sorted(vec.vocabulary_.keys()))
print(df)
   artificial  between  branch  brown  capture  computers  corpus  data  deep  \
0         0.0      0.0     0.0    0.0      0.0        0.0     0.0   0.0   0.0   
 
   dog  ...  quick  relationships  semantic  subfield  text  the   to  \
0  0.0  ...    0.0            0.0       0.0       0.0   0.0  0.0  0.0   
 
   without  word  words  
0      0.0   0.0    0.0  
 
[1 rows x 43 columns]

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


تضمينات الشخصية: كيف تكون؟

اقرأ: https://jalammar.github.io/illustrated-word2vec/

"أُهديك الحرباء الصحراوية، التي تكشف قدرتها على الاندماج مع خلفيتها عن كل ما تحتاج معرفته بخصوص جذور علم البيئة وأسس الهوية الشخصية." ~ أبناء الكثبان (Children of Dune)

فهم سمات الشخصية

على مقياس من 0 إلى 100، إلى أي درجة أنت انطوائي أم منفتح؟ (حيث 0 تعني الانطوائية القصوى و100 تعني الانفتاح الأقصى). هل سبق أن خضعت لاختبار شخصية مثل MBTI، أو الأفضل من ذلك، اختبار سمات الشخصية الخمس الكبرى (Big Five)؟

إن لم تكن قد فعلت، فهذه الاختبارات تطرح سلسلة من الأسئلة ثم تمنحك درجة على عدد من المحاور، ويُعدّ محور الانطوائية/الانفتاح أحد هذه المحاور.

مثال على نتيجة اختبار شخصية

يمكن لاختبار سمات الشخصية الخمس الكبرى أن يقدّم رؤى عميقة حول الشخصية، وقد ثبت أنه يتنبأ بالنجاح الأكاديمي والشخصي والمهني. وإليك مثالًا على شكل نتيجته:

Openness: 72/100
Conscientiousness: 85/100
Extraversion: 38/100
Agreeableness: 65/100
Neuroticism: 47/100

تصوير درجات الشخصية

لنفترض أنني حصلت على 38/100 على مقياس الانطوائية/الانفتاح. يمكننا تصوير ذلك على النحو التالي:

ولجعله أكثر فائدة للعمليات الرياضية، لنطبّع المدى ليصبح من -1 إلى 1:

إضافة أبعاد أكثر

إلى أي مدى تشعر بأنك تعرف شخصًا اعتمادًا على معلومة واحدة فقط؟ ليس كثيرًا! فالبشر معقّدون. لنُضف سمة أخرى لتمثيل الشخصية في بُعدين اثنين:

وبدلًا من الاكتفاء بـ نقاط، يمكننا تمثيل الشخصية كمتجهات (Vectors). ولماذا؟ لأن المتجهات تتيح لنا قياس التشابه رياضيًا!

مقارنة شخصيتي شخصين

لنفترض أنني بحاجة لأن يحلّ محلي شخص ذو شخصية مشابهة. في الشكل أدناه نقارن بين شخصين آخرين:

أي الشخصين أشبه بي؟

من الطرق الشائعة لقياس التشابه في فضاء المتجهات هي التشابه جيب التمام (Cosine Similarity):

إذا كانت قيمة تشابه جيب التمام قريبة من 1، فإن المتجهات تشير إلى الاتجاه نفسه، أي أنها أكثر تشابهًا.

  • الشخص رقم 1 أشبه بي لأن متجهه يشير إلى الاتجاه العام نفسه الذي يشير إليه متجهي.
  • الشخص رقم 2 أقل تشابهًا لأن متجهه في ربع مختلف.

التوسّع إلى أبعاد أكثر

بُعدان لا يكفيان لتمثيل الشخصية الإنسانية تمثيلًا كاملًا. وتشير عقود من أبحاث علم النفس إلى أن أفضل وصف للشخصية يكون عبر خمس سمات رئيسية (السمات الخمس الكبرى)، إلى جانب سمات فرعية عديدة.

فما المشكلة إذن؟ لا يمكننا رسم أسهم مرتّبة لخمسة أبعاد! لكن في تعلّم الآلة، غالبًا ما نتعامل مع فضاءات عالية الأبعاد. ولحسن الحظ، يظل التشابه جيب التمام صالحًا لأي عدد من الأبعاد!

أهم الأفكار

في ختام هذا الجزء، ينبغي أن نتذكّر فكرتين أساسيتين:

  1. يمكننا تمثيل الأشخاص (والأشياء) كمتجهات من الأرقام - وهو أمر رائع بالنسبة للآلات!
  2. يمكننا بسهولة حساب مدى تشابه هذه المتجهات مع بعضها باستخدام التشابه جيب التمام.


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


Word2Vec (يُدرَّب بالتنبؤ اعتمادًا على السياق)

  • CBOW (حقيبة الكلمات المستمرة): يتنبأ بالكلمة انطلاقًا من سياقها.
  • Skip-Gram: يتنبأ بكلمات السياق انطلاقًا من كلمة واحدة.

import gensim
 
word2vec_model = gensim.models.KeyedVectors.load_word2vec_format(
    "GoogleNews-vectors-negative300.bin", binary=True
)
 
print("Word2Vec (King):", word2vec_model["king"][:10])  # First 10 values
Word2Vec (King): [ 0.12597656  0.02978516  0.00860596  0.13964844 -0.02563477 -0.03613281
  0.11181641 -0.19824219  0.05126953  0.36328125]
print("Word2Vec (Queen):", word2vec_model["Queen"][:10])  # First 10 values
Word2Vec (Queen): [-0.22070312 -0.17480469 -0.10498047  0.2578125   0.16210938 -0.13085938
 -0.16699219  0.07373047 -0.07226562  0.02404785]

GloVe (يُدرَّب باستخدام إحصاءات التوافق اللفظي - Word Co-Occurrence)

  • بخلاف Word2Vec، يعتمد GloVe على تحليل المصفوفات (Matrix Factorization) لمصفوفة توافق الكلمات مع بعضها.
  • يلتقط العلاقات العامة بين الكلمات (Global Word Relationships).

import numpy as np
 
# Load GloVe embeddings
glove_embeddings = {}
with open("glove.6B.100d.txt", "r", encoding="utf-8") as f:
    for line in f:
        values = line.split()
        word = values[0]
        vector = np.asarray(values[1:], dtype="float32")
        glove_embeddings[word] = vector
 
print("GloVe 'king':", glove_embeddings["king"][:10])  # Show first 10 values
GloVe 'king': [-0.32307 -0.87616  0.21977  0.25268  0.22976  0.7388  -0.37954 -0.35307
 -0.84369 -1.1113 ]