الوحدة 3 — حقيبة الكلمات، TF-IDF، وحدودها
قبل أن تُحرَق دورات GPU على نموذج محوّلات، يجب أن يُبنى نموذج مرجعي بسيط، قابل للتفسير، سريع التدريب، ورخيص التشغيل. مهمّته ليست الفوز، بل رسم حدّ أدنى معقول يجب أن يتفوّق عليه أيّ نموذج أثقل بفارق واضح. إن لم يفعل، فالثمن الإضافي غير مبرَّر.
هذا النموذج المرجعي في التصنيف النصّي يكاد يكون هو نفسه منذ ثلاثين سنة: تمثيل النصّ بحقيبة كلمات موزونة بـTF-IDF، ثمّ انحدار لوجستي فوقها. على مراجعاتنا العربية، سيبلغ هذا الخطّ خطأً مقبولًا في دقائق قليلة، ويقدّم علاوة على ذلك تفسيرًا شفّافًا لكلّ تنبّؤ.
من نصّ إلى متّجه: حقيبة الكلمات
فكرة حقيبة الكلمات فقيرة عمدًا: نصّ يُمثَّل بعدد ظهور كلّ كلمة فيه، دون اعتبار للترتيب. الجملة المنتج جيّد لكنّ التوصيل بطيء والجملة التوصيل بطيء لكنّ المنتج جيّد تُنتجان المتّجه نفسه.
بمفردات من كلمة، يُمثَّل كلّ نصّ بمتّجه من مركّبة. جسم من نصًّا يُعطي مصفوفة ، غالبًا . لكنّ معظم قِيَمها أصفار: نصّ يحتوي 40 كلمة فقط، فيمتلك على الأكثر 40 مركّبة غير صفريّة من أصل 30 000.
الحلّ التقني هو المصفوفة المتناثرة (sparse matrix). لا يُخزَّن سوى المواقع غير الصفرية.
from sklearn.feature_extraction.text import CountVectorizer
corpus = [
"المنتج رائع جدا سأشتريه مرة أخرى",
"التوصيل بطيء والمنتج لم يعجبني",
"جودة عالية وسعر مناسب",
"أسوأ تجربة شراء على الإطلاق",
]
vec = CountVectorizer()
X = vec.fit_transform(corpus) # مصفوفة متناثرة (4, V)
print(X.shape, type(X)) # scipy.sparse
print(vec.vocabulary_) # القاموس كلمة → معرف
لماذا لا يكفي العدّ الخام؟ TF-IDF
عدّ خام يعطي كلمة ال وزنًا هائلًا لأنّها تظهر في كلّ نصّ تقريبًا، بينما كلمة نادرة ذات دلالة كـاسترداد تحصل على نقطة يتيمة. النموذج يتعلّم أنّ ا لوجه المشترك للنصوص أهمّ من خصوصياتها، وهذا مقلوب.
TF-IDF يُصحّح هذا الاختلال. لكلّ زوج (نصّ، كلمة) نحسب حاصل ضرب:
حيث تواتر الكلمة في النصّ ، و إجمالي عدد النصوص، و عدد النصوص التي تحوي . الجزء الثاني، التواتر المستندي المعكوس، صغير للكلمات المنتشرة، كبير للكلمات النادرة.
النتيجة: كلمة ال تحصل على وزن قريب من الصفر، وكلمة استرداد تحصل على وزن كبير كلّما ظهرت.
from sklearn.feature_extraction.text import TfidfVectorizer
vec = TfidfVectorizer(
max_features=30000,
ngram_range=(1, 2), # كلمات فردية + ثنائيات
min_df=3, # نتجاهل ما ظهر في أقل من 3 نصوص
max_df=0.95, # ونتجاهل ما ظهر في أكثر من 95% منها
)
X = vec.fit_transform(corpus)
المعطى ngram_range=(1, 2) يضيف الأزواج المتتالية من الكلمات. هذا يستعيد شيئًا من الترتيب المفقود: ليس جيد تصبح جُذاذة مستقلّة عن جيد وحدها. الفرق كبير على مسار المشاعر، حيث تنفي ليس ولم ولا ما بعدها.
بناء الحدّ الأدنى الجاد
يكفي الآن انحدار لوجستي فوق هذه المتّجهات:
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
X_tr, X_te, y_tr, y_te = train_test_split(
corpus_kamil, ynajmat, # النصّ المطبّع، وعدد النجوم مصنّفا (سلبي/إيجابي)
test_size=0.2, stratify=ynajmat, random_state=42,
)
vec = TfidfVectorizer(ngram_range=(1, 2), min_df=3, max_df=0.95)
Xt = vec.fit_transform(X_tr)
Xe = vec.transform(X_te)
model = LogisticRegression(max_iter=1000, class_weight="balanced", C=1.0)
model.fit(Xt, y_tr)
print(classification_report(y_te, model.predict(Xe), digits=3))
على 10 000 مراجعة عربية، هذا الخطّ يبلغ عمليًّا F1 موزونًا يتراوح بين 0,82 و0,86 حسب جودة التنظيف. يتدرّب في أقلّ من دقيقة، ويحتلّ في الذاكرة بضع ميغابايتات، ويمكن نشره على أيّ خادم دون GPU.
ما يقدّمه TF-IDF مجّانًا: التفسير
الميزة التي لا تعرضها الحلول الأثقل هي الشفافية. لكلّ كلمة معامل موجب أو سالب، يمكن استخلاصه بسطر واحد.
import numpy as np
asma = np.array(vec.get_feature_names_out())
awzan = model.coef_[0] # في التصنيف الثنائي
# أكثر عشر كلمات دلالة على المشاعر الإيجابية
top_ijaby = asma[np.argsort(awzan)[-10:]]
top_salby = asma[np.argsort(awzan)[:10]]
print("إيجابي:", top_ijaby)
print("سلبي:", top_salby)
على مراجعاتنا، يظهر عادة في القمّة الإيجابية ممتاز، رائع، أنصح، يستحق، سعر مناسب. وفي القمّة السلبية سيئ، مضيعة، مخيّب، توصيل بطيء، لا أنصح. هذه القائمة هي مراجعة سريعة لنموذجك: لو ظهر فيها اسم علامة تجارية بوزن مرتفع، فذلك تسرّب واضح للتدريب.
متى يكفي TF-IDF فعلًا
TF-IDF ليس نموذجًا يجب دائمًا الانتقال منه. في عدد من الحالات يبقى الخيار الصحيح:
- جسم صغير (أقلّ من بضعة آلاف نصّ): النماذج الكبيرة تحفظ عليه بسرعة، ولا تُعمّم أفضل.
- مفردات صناعية جدًّا: نصوص طبّية أو قانونية مليئة بمصطلحات تخصّصية. كلمة نادرة ولكن دلالية بامتياز، وTF-IDF يعطيها وزنها الطبيعي.
- قيود صارمة على الكلفة أو الزمن: تصنيف في الوقت الحقيقي على معالج مركزيّ من دون تسريع، أو بيئات لا تسمح بنشر نموذج ضخم.
- حاجة إلى تفسير رسمي: قطاعات منظّمة (مالية، تأمين) تشترط بيانًا يمكن فحصه لكلّ قرار آلي.
في هذه الح الات، صرف أسبوع على تحسين تنظيف نصّ TF-IDF يعطي عائدًا أكبر من صرف أسبوع على تعديل معماريّة محوّلات.
حدود التمثيل: ما يفوت التوافيق
مقابل ما يقدّمه، يفوت TF-IDF أشياء جوهرية:
- لا معنى للترتيب: نضيف الأزواج لتخفيف ذلك، لكنّه سطحي.
الفيلم ممتاز لولا ملله البادئوالفيلم ممل لولا نهايته الممتازةقد يعطيان متّجهين متشابهين. - لا تعميم بين المرادفات:
رائعوممتازكلمتان مختلفتان تمامًا، لا يعرف النموذج أنّهما قريبتان. ما لم يظهرا معًا كثيرًا في تدريب، يتعلّم النموذج معاملين مستقلّين. - حساسية للإملاء: خطأ إملائي على كلمة مفتاحية يقصيها.
ممتاااازكلمة جديدة إن لم نُطبَّع التطويل. - الكلمات المجهولة يُقصيها التحويل، فيصبح الاختبار على نصوص مختلفة قليلًا عن التدريب أقلّ ثباتًا.
هذه القيود الأربعة هي بالضبط ما ستأتي التضمينات لحلّه في الوحدتين 4 و5.
حسبت الـIDF على الجسم كلّه شامل التدريب والاختبار؟ سرّبت بذلك معلومة عن الاختبار إلى التدريب. اضبط الـTfidfVectorizer على التدريب فقط بـfit_transform(X_tr)، وطبّق transform(X_te) على الاختبار. الفرق قد يكون ضئيلًا هنا لكنّه يصبح كارثيًّا حين تنشر النموذج ويلقى نصوصًا جديدة.
قبل الاحتفاء بنموذج AraBERT يبلغ F1 قدره 0,89، تذكّر أنّ TF-IDF بلغ 0,86. الفارق ثلاث نقاط، لا خمس عشرة. اسأل نفسك: هل تكلفة GPU وتعقيد النشر تستحقّ هذه النقاط الثلاث لحالة استخدامك؟ في مسار الإنتاج الحقيقي، أحيانًا نعم، وأحيانًا لا.
في الخلاصة
- حقيبة الكلمات + TF-IDF تحوّل نصًّا إلى متّجه متناثر، وتزن كلّ كلمة بتواترها في النصّ مقسومًا بانتشارها في الجسم.
- الانحدار اللوجستي فوق TF-IDF يبني نموذجًا مرجعيًّا يتدرّب في دقيقة، ويقدّم أوزانًا قابلة للقراءة كأنّها قاموس مشاعر.
- إضافة الثنائيات (n-grammes) تسترجع جزءًا من الترتيب المفقود، وهي ضرورية على مسار المشاعر بسبب أدوات النفي.
- الحدود الجوهرية (لا ترتيب، لا مرادفات، حساسية للإملاء، غياب المجهول) تبرّر الانتقال إلى التضمينات، لكن لا تُلغي كون TF-IDF أفضل خيار في حالات ملموسة.
الوحدة التالية: Word2Vec وGloVe، أوّل من قدّم كلمة كمتّجه ذي معنى هندسي حقيقي.