معاينة مختبر آمنة
Text Representation TF
هذي معاينة منقّحة للقراءة فقط؛ ما فيه أي شيء يشتغل داخل الصفحة.
قراءة فقط
معاينة الدفتر
Text Representation TF
> **ملاحظة بيئة التشغيل المدمجة:** هالمعاينة تستخدم عيّنة صغيرة وثابتة وآمنة من ناحية الحقوق عشان تكون النتايج قابلة للتكرار. النتايج بالحجم الكامل تحتاج مجموعة البيانات أو النموذج الموثّق بالدرس داخل بيئة خارجية معتمدة.
# مهمة تصنيف النصوص
في هالوحدة بنبدأ بمهمة بسيطة في **التصنيف** للنصوص اعتمادًا على مجموعة البيانات **[AG_NEWS](https://www.tensorflow.org/datasets/catalog/ag_news_subset)**: بنصنّف عناوين الأخبار ضمن فئة وحدة من 4 فئات، وهي العالم، والرياضة، والأعمال، والعلوم/التقنية.
## مجموعة البيانات
لتحميل مجموعة البيانات، بنستخدم API الخاصة بـ**[TensorFlow Datasets](https://www.tensorflow.org/datasets)**.
import tensorflow as tf
from tensorflow import keras
# In this tutorial, we will be training a lot of models. In order to use GPU memory cautiously,
# we will set tensorflow option to grow GPU memory allocation when required.
physical_devices = tf.config.list_physical_devices("GPU")
if len(physical_devices) > 0:
tf.config.experimental.set_memory_growth(physical_devices[0], True)
# course-edition deterministic TensorFlow news fixture v1
_course_titles = tf.constant([
"Regional leaders agree on a public health plan",
"The home team wins the basketball final",
"Markets rise after a strong business report",
"Researchers release an efficient intelligence model",
"Diplomats begin a new round of peace talks",
"The tennis champion returns after an injury",
"Retail sales improve with consumer confidence",
"A software update improves privacy",
])
_course_descriptions = tf.constant([
"World policy and international cooperation",
"Sports results and player development",
"Business revenue and economic indicators",
"Science and technology research",
"World diplomacy and humanitarian aid",
"Sports training and competition",
"Business investment and employment",
"Science security and technology",
])
_course_labels = tf.constant([0, 1, 2, 3, 0, 1, 2, 3], dtype=tf.int64)
_course_records = {"title": _course_titles, "description": _course_descriptions, "label": _course_labels}
ds_train = tf.data.Dataset.from_tensor_slices(_course_records).take(6)
ds_test = tf.data.Dataset.from_tensor_slices(_course_records).skip(6)
dataset = {"train": ds_train, "test": ds_test}الحين نقدر نوصل إلى جزأي التدريب والاختبار من مجموعة البيانات باستخدام `dataset['train']` و`dataset['test']` بالترتيب:
ds_train = dataset['train']
ds_test = dataset['test']
print(f"Length of train dataset = {len(ds_train)}")
print(f"Length of test dataset = {len(ds_test)}")خلونا نطبع أول 10 عناوين أخبار من مجموعة البيانات:
classes = ['World', 'Sports', 'Business', 'Sci/Tech']
for i,x in zip(range(5),ds_train):
print(f"{x['label']} ({classes[x['label']]}) -> {x['title']} {x['description']}")## تحويل النص إلى متجهات
الحين نحتاج نحوّل النص إلى **أرقام** نقدر نمثّلها على هيئة موترات. وإذا بغينا تمثيلًا على مستوى الكلمات، عندنا خطوتان:
* نستخدم **مجزّئ نص** لتجزئة النص إلى **رموز**.
* نبني **مفردات** من هالرموز.
### تحديد حجم المفردات
في مثال مجموعة البيانات AG News، حجم المفردات كبير ويتعدّى 100k كلمة. عادةً، ما نحتاج الكلمات اللي نادرًا تظهر؛ لأنها ما تجي إلا في جمل قليلة، والنموذج ما بيتعلّم منها. لذلك من المعقول نصغّر حجم المفردات بتمرير معامل إلى مُنشئ vectorizer:
نقدر ننفّذ الخطوتين كلّهن بطبقة **TextVectorization**. خلونا ننشئ كائن vectorizer، ثم نستدعي الطريقة `adapt` عشان تمر على كل النصوص وتبني المفردات:
vocab_size = 50000
vectorizer = keras.layers.TextVectorization(
max_tokens=vocab_size,
output_mode="int",
)
vectorizer.adapt(
ds_train.take(500)
.map(lambda item: item["title"] + " " + item["description"])
.batch(256)
)> **ملاحظة:** نستخدم جزءًا فقط من مجموعة البيانات الكاملة لبناء المفردات؛ عشان يتسارع التنفيذ وما نطوّل عليكم بالانتظار. لكن فيه احتمال إن بعض كلمات مجموعة البيانات الكاملة ما تدخل في المفردات، وبالتالي يتجاهلها التدريب. استخدام الحجم الكامل للمفردات وتمرير البيانات كلها عبر `adapt` المفروض يرفع الدقة النهائية، لكن الزيادة غالبًا بسيطة.
الحين نقدر نوصل إلى المفردات الفعلية:
vocab = vectorizer.get_vocabulary()
vocab_size = len(vocab)
print(vocab[:10])
print(f"Length of vocabulary: {vocab_size}")وباستخدام vectorizer، نقدر بسهولة نحوّل أي نص إلى مجموعة أرقام:
vectorizer('I love to play with my words')## تمثيل النص بنموذج حقيبة الكلمات
بما إن الكلمات تحمل معنى، نقدر أحيانًا نستنتج موضوع النص من الكلمات الموجودة فيه لحالها، حتى لو تجاهلنا ترتيبها في الجملة. مثلًا، في تصنيف الأخبار، كلمتا *weather* و*snow* غالبًا تدلّان على *weather forecast*، بينما *stocks* و*dollar* تميلان إلى الأخبار المالية.
**نموذج حقيبة الكلمات** (Bag-of-Words أو BoW) من أبسط تمثيلات المتجهات التقليدية. نربط كل كلمة بفهرس داخل المتجه، وتكون قيمة العنصر هي عدد مرات ظهور هالكلمة في مستند معيّن.
> **وصف الشكل:** رسم يوضّح كيف يُخزّن تمثيل متجه حقيبة الكلمات في الذاكرة.
> **ملاحظة:** نقدر بعد نفهم BoW على إنه مجموع متجهات one-hot لكل كلمة في النص.
وهذا مثال يوضّح كيف نولّد تمثيل حقيبة الكلمات باستخدام مكتبة Scikit Learn في Python:
from sklearn.feature_extraction.text import CountVectorizer
sc_vectorizer = CountVectorizer()
corpus = [
'I like hot dogs.',
'The dog ran fast.',
'Its hot outside.',
]
sc_vectorizer.fit_transform(corpus)
sc_vectorizer.transform(['My dog likes hot dogs on a hot day.']).toarray()ونقدر بعد نستخدم Keras vectorizer اللي عرّفناه فوق، بحيث نحوّل رقم كل كلمة إلى ترميز one-hot ثم نجمع كل المتجهات:
def to_bow(text):
return tf.reduce_sum(tf.one_hot(vectorizer(text),vocab_size),axis=0)
to_bow('My dog likes hot dogs on a hot day.').numpy()> **ملاحظة:** ممكن تستغربون إن النتيجة تختلف عن المثال السابق. السبب إن طول المتجه في مثال Keras يساوي حجم المفردات المبنية من مجموعة البيانات AG News كاملة، بينما بنينا مفردات مثال Scikit Learn مباشرة من النص النموذجي.
## تدريب مصنّف BoW
بعد ما عرفنا كيف نبني تمثيل نموذج حقيبة الكلمات للنص، خلونا ندرّب مصنّفًا يستخدمه. أولًا، نحتاج نحوّل مجموعة البيانات إلى تمثيل حقيبة الكلمات، ونسوي هالشي بالدالة `map` كالتالي:
batch_size = 128
ds_train_bow = ds_train.map(lambda x: (to_bow(x['title']+x['description']),x['label'])).batch(batch_size)
ds_test_bow = ds_test.map(lambda x: (to_bow(x['title']+x['description']),x['label'])).batch(batch_size)الحين بنعرّف **الشبكة العصبية** البسيطة المستخدمة في **التصنيف**، وفيها طبقة خطية وحدة. حجم الإدخال هو `vocab_size`، وحجم الإخراج يساوي عدد الفئات (4). وبما إن المهمة تصنيف، تكون دالة التنشيط الأخيرة **softmax**:
model = keras.models.Sequential([
keras.layers.Dense(4,activation='softmax',input_shape=(vocab_size,))
])
model.compile(loss='sparse_categorical_crossentropy',optimizer='adam',metrics=['acc'])
model.fit(ds_train_bow,validation_data=ds_test_bow)بما إن عندنا 4 فئات، فأي دقة تتعدّى 80% تعتبر نتيجة زينة.
## تدريب المصنّف كشبكة وحدة
بما إن vectorizer نفسه طبقة من Keras، نقدر نعرّف شبكة تشمله وندرّبها من طرف إلى طرف. بهالطريقة ما نحتاج نحول مجموعة البيانات باستخدام `map`؛ نمرّر مجموعة البيانات الأصلية مباشرة إلى مدخل الشبكة.
> **ملاحظة:** مع هذا، ما زلنا نحتاج نطبّق map على مجموعة البيانات عشان نحوّل الحقول من قواميس، مثل `title` و`description` و`label`، إلى صفوف. لكن عند تحميل البيانات من القرص، نقدر من البداية نبني مجموعة بيانات بالهيكل المطلوب.
def extract_text(x):
return x['title']+' '+x['description']
def tupelize(x):
return (extract_text(x),x['label'])
inp = keras.Input(shape=(1,),dtype=tf.string)
x = vectorizer(inp)
x = keras.ops.sum(keras.ops.one_hot(x, vocab_size), axis=1)
out = keras.layers.Dense(4,activation='softmax')(x)
model = keras.models.Model(inp,out)
model.summary()
model.compile(loss='sparse_categorical_crossentropy',optimizer='adam',metrics=['acc'])
model.fit(ds_train.map(tupelize).batch(batch_size),validation_data=ds_test.map(tupelize).batch(batch_size))## ثنائيات الكلمات وثلاثيات الكلمات وn-grams
من قيود نموذج حقيبة الكلمات إن بعض الكلمات تجي ضمن تعبير مكوّن من أكثر من كلمة. مثلًا، عبارة 'hot dog' معناها يختلف تمامًا عن معنى 'hot' و'dog' كل وحدة في سياق ثاني. وإذا مثّلنا 'hot' و'dog' دائمًا بالمتجهات نفسها، ممكن نربك النموذج.
عشان نعالج هالمشكلة، تُستخدم **تمثيلات n-gram** كثير في **التصنيف** للمستندات؛ لأن تكرار الكلمة، أو زوج الكلمات، أو ثلاث كلمات متتابعة يكون سمة مفيدة لتدريب المصنّف. في تمثيل bigram مثلًا، نضيف كل أزواج الكلمات إلى المفردات بجانب الكلمات الأصلية.
وهذا مثال على توليد تمثيل حقيبة كلمات من نوع bigram باستخدام Scikit Learn:
bigram_vectorizer = CountVectorizer(ngram_range=(1, 2), token_pattern=r'\b\w+\b', min_df=1)
corpus = [
'I like hot dogs.',
'The dog ran fast.',
'Its hot outside.',
]
bigram_vectorizer.fit_transform(corpus)
print("Vocabulary:\n",bigram_vectorizer.vocabulary_)
bigram_vectorizer.transform(['My dog likes hot dogs on a hot day.']).toarray()العيب الأهم في أسلوب n-gram إن حجم المفردات يكبر بسرعة شديدة. عمليًا، نحتاج نجمع تمثيل n-gram مع تقنية لتقليل الأبعاد، مثل *التضمينات* (embeddings)، وبنتكلم عنها في الوحدة الجاية.
ولاستخدام تمثيل n-gram في مجموعة البيانات **AG News**، نمرّر المعامل `ngrams` إلى مُنشئ `TextVectorization`. حجم مفردات bigram **أكبر بكثير**؛ في حالتنا يتعدّى 1.3 مليون رمز! لذلك من المعقول نحط حدًا مناسبًا لعدد رموز bigram بعد.
نقدر نستخدم الكود نفسه اللي فوق لتدريب المصنّف، لكنه بيستهلك الذاكرة بكفاءة سيئة. في الوحدة الجاية بندرّب مصنّف bigram باستخدام التضمينات. وإلى ذاك الوقت، جرّبوا تدريب مصنّف bigram في هالدفتر وشوفوا إذا تقدرون توصلون لدقة أعلى.
## حساب متجهات BoW تلقائيًا
في المثال اللي فوق، حسبنا متجهات BoW يدويًا بجمع ترميزات one-hot للكلمات. لكن الإصدار الأحدث من TensorFlow يخلينا نحسبها تلقائيًا بتمرير المعامل `output_mode='count` إلى مُنشئ vectorizer. وهذا يسهّل تعريف النموذج وتدريبه بشكل واضح:
count_vectorizer = keras.layers.TextVectorization(
max_tokens=vocab_size,
output_mode="count",
pad_to_max_tokens=True,
)
count_vectorizer.adapt(ds_train.take(500).map(extract_text).batch(256))
model = keras.Sequential([
keras.Input(shape=(), dtype=tf.string),
count_vectorizer,
keras.layers.Dense(4, activation="softmax"),
])
model.compile(
loss="sparse_categorical_crossentropy",
optimizer="adam",
metrics=["acc"],
)
model.fit(
ds_train.map(tupelize).batch(batch_size),
validation_data=ds_test.map(tupelize).batch(batch_size),
)## تكرار المصطلح–معكوس تكرار المستند (TF-IDF)
في تمثيل BoW، تأخذ كل كلمة الوزن بالطريقة نفسها مهما كانت الكلمة. لكن واضح إن الكلمات الشائعة، مثل *a* و*in*، أقل فائدة في **التصنيف** من المصطلحات المتخصصة. وفي أغلب مهام معالجة اللغة الطبيعية (NLP) تكون بعض الكلمات أهم من غيرها.
يرمز **TF-IDF** إلى **تكرار المصطلح–معكوس تكرار المستند**. وهو تعديل على نموذج حقيبة الكلمات: بدل قيمة ثنائية 0/1 تدل على وجود الكلمة في المستند، نستخدم قيمة ذات فاصلة عائمة مرتبطة بمعدل ظهور الكلمة في المتن النصي.
وبصياغة أدق، نعرّف وزن الكلمة $i$ في المستند $j$، وهو $w_{ij}$، كالتالي:
$$
w_{ij} = tf_{ij}\times\log({N\over df_i})
$$
بحيث:
* $tf_{ij}$ هو عدد مرات ظهور $i$ في $j$؛ يعني قيمة BoW اللي شفناها قبل
* $N$ هو عدد المستندات في المجموعة
* $df_i$ هو عدد المستندات في المجموعة كلها اللي تحتوي الكلمة $i$
ترتفع قيمة TF-IDF، أي $w_{ij}$، طرديًا مع عدد مرات ظهور الكلمة في المستند، ويوازنها عدد المستندات اللي تحتوي هالكلمة في المتن النصي. بهالطريقة نعالج كون بعض الكلمات أكثر شيوعًا من غيرها. مثلًا، لو ظهرت الكلمة في *كل* مستندات المجموعة، بيكون $df_i=N$ و$w_{ij}=0$، ووقتها نتجاهل هالمصطلح بالكامل.
ونقدر بسهولة نسوي تمثيل TF-IDF للنص باستخدام Scikit Learn:
from sklearn.feature_extraction.text import TfidfVectorizer
vectorizer = TfidfVectorizer(ngram_range=(1,2))
vectorizer.fit_transform(corpus)
vectorizer.transform(['My dog likes hot dogs on a hot day.']).toarray()في Keras، تقدر طبقة `TextVectorization` تحسب تكرارات TF-IDF تلقائيًا إذا مرّرنا المعامل `output_mode='tf-idf'`. خلونا نعيد الكود اللي استخدمناه فوق ونشوف هل TF-IDF يرفع الدقة:
tfidf_vectorizer = keras.layers.TextVectorization(
max_tokens=vocab_size,
output_mode="tf_idf",
pad_to_max_tokens=True,
)
tfidf_vectorizer.adapt(ds_train.take(500).map(extract_text).batch(256))
model = keras.Sequential([
keras.Input(shape=(), dtype=tf.string),
tfidf_vectorizer,
keras.layers.Dense(4, activation="softmax"),
])
model.compile(
loss="sparse_categorical_crossentropy",
optimizer="adam",
metrics=["acc"],
)
model.fit(
ds_train.map(tupelize).batch(batch_size),
validation_data=ds_test.map(tupelize).batch(batch_size),
)## الخلاصة
مع إن تمثيلات TF-IDF تعطي الكلمات أوزانًا حسب تكرارها، إلا إنها ما تمثّل المعنى ولا الترتيب. وكما قال اللغوي المعروف J. R. Firth سنة 1935: «المعنى الكامل للكلمة يعتمد دومًا على السياق، وما يصح نأخذ دراسة للمعنى بجدية إذا فصلناها عن سياقها». قدّام في الدورة بنتعلم كيف نلتقط معلومات السياق من النص باستخدام نمذجة اللغة.
حذفنا المخرجات وعدّادات التشغيل والودجات والمحتوى النشط وقت الاستيراد. شغّل الدفاتر بس في بيئة خارجية تثق فيها.
سجّل تطبيقك
التسجيل اختياري، يفيدك تتذكر وش طبّقت، ولا يمنع إكمال الدورة.