Logo

LSTM على مجموعة بيانات 20 Newsgroups باستخدام Keras

8 دقائق قراءة
شرائح الدرس
1 / 13

LSTM على مجموعة بيانات 20 Newsgroups باستخدام Keras

بناء ومقارنة LSTM قياسية مقابل LSTM ثنائية الاتجاه لتصنيف النصوص

يوضّح هذا الدفتر (Notebook) كيفية بناء نموذج LSTM قياسي ونموذج LSTM ثنائي الاتجاه (Bidirectional) ومقارنتهما باستخدام مجموعة بيانات 20 Newsgroups. نستخدم 10 فئات وننفّذ تصنيفًا متعدد الفئات (Multi-Class Classification).

مجموعة بيانات 20 Newsgroups هي مسألة تصنيف معروفة جيدًا. الهدف هو تصنيف أي مجموعة إخبارية (Newsgroup) جاءت منها منشورة معيّنة. المجموعات العشرون المحتملة هي:

comp.graphics comp.os.ms-windows.misc comp.sys.ibm.pc.hardware comp.sys.mac.hardware comp.windows.x rec.autos rec.motorcycles rec.sport.baseball rec.sport.hockey sci.crypt sci.electronics sci.med sci.space misc.forsale talk.politics.misc talk.politics.guns talk.politics.mideast talk.religion.misc alt.atheism soc.religion.christian

هنا نختار 10 فئات للعمل عليها.

from sklearn.datasets import fetch_20newsgroups
from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.preprocessing.sequence import pad_sequences
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Embedding, LSTM, Dense, Bidirectional
import numpy as np
from sklearn.preprocessing import LabelBinarizer
# Load dataset
categories = [
    'sci.space', 'comp.graphics', 'rec.sport.baseball', 'talk.politics.misc',
    'sci.electronics', 'soc.religion.christian', 'rec.autos',
    'talk.politics.mideast', 'comp.sys.mac.hardware', 'misc.forsale'
]
newsgroups = fetch_20newsgroups(subset='train', categories=categories)
texts = newsgroups.data
labels = newsgroups.target
 
len(texts)
5750
# ✅ Display a sample
print("Sample text:")
print(texts[1][:1000])
print("\nLabel:", labels[1])
print("Actual Category:", newsgroups.target_names[labels[1]])
Sample text:
From: [email protected] (Wolfgang Pest)
Subject: Speedstar 24 - how to program the TrueColor mode ?
Distribution: world
Organization: Kontron Elektronik GmbH Eching, Germany
Lines: 17
 
Hello,
I purchased my new 486 with a NoName graphics card installed which is obviously 
Speedstar 24 compatible. Its name is "VGA 4000 TrueColor".
It is accompanied with some drivers and the utilities VMODE, XMODE and
at least one more MODE, as well as some drivers for Lotus, Windows, etc.
Only one of the drivers is told to provide the TrueColor mode, namely
the Windows 3.1 driver.
Nowhere else, except in the ad, is any pointer to the TrueColor mode.
Some articles in this group about the Speedstar 24 and some other facts
made me believe that my card is compatible to that one.
 
Does anybody out there know how this mode can be adjusted? How can I write
a driver which allows me to have 16.7 millions of colors with a resolution
of 640 x 480 with 45 Hz interlaced ?
 
Greetings,
    Wolfgang
 
 
Label: 0
Actual Category: comp.graphics
for i, name in enumerate(newsgroups.target_names):
    print(f"Label {i}: {name}")
Label 0: comp.graphics
Label 1: comp.sys.mac.hardware
Label 2: misc.forsale
Label 3: rec.autos
Label 4: rec.sport.baseball
Label 5: sci.electronics
Label 6: sci.space
Label 7: soc.religion.christian
Label 8: talk.politics.mideast
Label 9: talk.politics.misc

🔹 شرح Tokenizer

يُستخدَم الـTokenizer لتحويل مجموعة نصوص (Corpus) إلى متجهات (Vectorize) عن طريق:

  1. إنشاء فهرس للكلمات (Word Index) (ربط الكلمات بأعداد صحيحة)
  2. تحويل كل مستند إلى قائمة من فهارس الكلمات

Tokenizer: يحوّل الكلمات إلى أعداد صحيحة (مثلًا "space" ← 345). يقصر الـTokenizer استخدامه على أعلى vocab_size كلمة فقط (مثلًا 10,000 كلمة). يُتجاهَل أي كلمة خارج أعلى 10,000 كلمة الأكثر تكرارًا أثناء تحويل النص إلى تسلسل (Sequence).

fit_on_texts: يبني قاموس الكلمات.

texts_to_sequences: يحوّل كل نص إلى قائمة من الأرقام.

pad_sequences: يبطّن (Pads) جميع التسلسلات لتصبح بنفس الطول (200 كلمة).

# Tokenization
vocab_size = 10000
maxlen = 200
tokenizer = Tokenizer(num_words=vocab_size)
 
# Learns the word -> number mapping
tokenizer.fit_on_texts(texts)
# Converts each article into a sequence of word indices
sequences = tokenizer.texts_to_sequences(texts)
 
padded_sequences = pad_sequences(sequences, maxlen=maxlen, padding='post')
 
# Encode labels to one-hot
encoder = LabelBinarizer()
one_hot_labels = encoder.fit_transform(labels)
# ✅ Display a sample
print("Sample text:")
print(padded_sequences[500])
print("\nLabel:", labels[500])
Sample text:
[  13 4108 1631   14 1822 8471   28  209   10  411 1317   31  548  487
    3  372 2498   30  343   85   74   81 1631   14  388 1317  411  146
   56   36    6   20    4 2581  209   24  119   13   38  249 2974 3020
 1317   27   12   47  310   66    2  504   11  169    3   12    1  231
  232    8   22    4  467  503  146   73    3   55   94 9770 1108   24
   80   31    9   40 1620 1317  184    4 4010 4108 1631   14    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    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    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]
 
Label: 5

🔹 شرح الطبقات

طبقة Embedding

  • تحوّل كل فهرس كلمة إلى متجه كثيف (Dense Vector).
  • input_dim: حجم المفردات (Vocabulary Size).
  • output_dim: أبعاد متجهات التضمين (Embedding Vectors).
  • input_length: الطول الثابت لتسلسلات الإدخال.

طبقة LSTM

  • units: عدد الوحدات المخفية (Hidden Units) أو خلايا الذاكرة.
  • return_sequences: القيمة True تُعيد التسلسل الكامل؛ استخدمها عند رصّ عدة طبقات LSTM فوق بعضها.
  • dropout: معدل الإسقاط (Dropout) لاتصالات الإدخال.
  • recurrent_dropout: معدل الإسقاط لاتصالات حالة الذاكرة بين الخطوات الزمنية
 
model_lstm = Sequential()
model_lstm.add(Embedding(input_dim=vocab_size, output_dim=128, input_length=maxlen))
model_lstm.add(LSTM(units=64, return_sequences=True))
model_lstm.add(LSTM(units=32, dropout=0.2, recurrent_dropout=0.2))
model_lstm.add(Dense(10, activation='softmax'))
model_lstm.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
model_lstm.fit(padded_sequences, one_hot_labels, epochs=10, batch_size=32, validation_split=0.2)
Epoch 1/10
/usr/local/lib/python3.11/dist-packages/keras/src/layers/core/embedding.py:90: UserWarning: Argument `input_length` is deprecated. Just remove it.
  warnings.warn(
144/144 ━━━━━━━━━━━━━━━━━━━━ 64s 355ms/step - accuracy: 0.1436 - loss: 2.2707 - val_accuracy: 0.2157 - val_loss: 2.1740
Epoch 2/10
144/144 ━━━━━━━━━━━━━━━━━━━━ 52s 361ms/step - accuracy: 0.2254 - loss: 2.1460 - val_accuracy: 0.2365 - val_loss: 2.1035
Epoch 3/10
144/144 ━━━━━━━━━━━━━━━━━━━━ 80s 343ms/step - accuracy: 0.2947 - loss: 1.9751 - val_accuracy: 0.2365 - val_loss: 2.0300
Epoch 4/10
144/144 ━━━━━━━━━━━━━━━━━━━━ 51s 353ms/step - accuracy: 0.3294 - loss: 1.8010 - val_accuracy: 0.3339 - val_loss: 1.7684
Epoch 5/10
144/144 ━━━━━━━━━━━━━━━━━━━━ 82s 357ms/step - accuracy: 0.3438 - loss: 1.7629 - val_accuracy: 0.2504 - val_loss: 2.0128
Epoch 6/10
144/144 ━━━━━━━━━━━━━━━━━━━━ 81s 352ms/step - accuracy: 0.3253 - loss: 1.7569 - val_accuracy: 0.3365 - val_loss: 1.8139
Epoch 7/10
144/144 ━━━━━━━━━━━━━━━━━━━━ 83s 363ms/step - accuracy: 0.4165 - loss: 1.5375 - val_accuracy: 0.3530 - val_loss: 1.7347
Epoch 8/10
144/144 ━━━━━━━━━━━━━━━━━━━━ 81s 355ms/step - accuracy: 0.4726 - loss: 1.3878 - val_accuracy: 0.4209 - val_loss: 1.6340
Epoch 9/10
144/144 ━━━━━━━━━━━━━━━━━━━━ 82s 352ms/step - accuracy: 0.5262 - loss: 1.2354 - val_accuracy: 0.4609 - val_loss: 1.5254
Epoch 10/10
144/144 ━━━━━━━━━━━━━━━━━━━━ 51s 350ms/step - accuracy: 0.5796 - loss: 1.0998 - val_accuracy: 0.5191 - val_loss: 1.4216
<keras.src.callbacks.history.History at 0x7fa0e2224190>

طبقة Bidirectional

  • تعالج تسلسل الإدخال في كلا الاتجاهين، الأمامي والخلفي.
  • غالبًا ما تحسّن الأداء في مهام معالجة اللغة الطبيعية (NLP) حيث يكون السياق من الجهتين مفيدًا.
 
model_bi = Sequential()
model_bi.add(Embedding(input_dim=vocab_size, output_dim=128, input_length=maxlen))
model_bi.add(Bidirectional(LSTM(units=64, dropout=0.2, recurrent_dropout=0.2)))
model_bi.add(Dense(10, activation='softmax'))
model_bi.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
model_bi.fit(padded_sequences, one_hot_labels, epochs=10, batch_size=32, validation_split=0.2)
Epoch 1/10
144/144 ━━━━━━━━━━━━━━━━━━━━ 92s 581ms/step - accuracy: 0.1717 - loss: 2.2183 - val_accuracy: 0.3843 - val_loss: 1.8057
Epoch 2/10
144/144 ━━━━━━━━━━━━━━━━━━━━ 81s 566ms/step - accuracy: 0.4948 - loss: 1.5136 - val_accuracy: 0.6157 - val_loss: 1.1395
Epoch 3/10
144/144 ━━━━━━━━━━━━━━━━━━━━ 82s 568ms/step - accuracy: 0.7449 - loss: 0.8354 - val_accuracy: 0.7070 - val_loss: 0.8469
Epoch 4/10
144/144 ━━━━━━━━━━━━━━━━━━━━ 82s 565ms/step - accuracy: 0.8816 - loss: 0.4428 - val_accuracy: 0.7530 - val_loss: 0.7618
Epoch 5/10
144/144 ━━━━━━━━━━━━━━━━━━━━ 82s 565ms/step - accuracy: 0.9387 - loss: 0.2518 - val_accuracy: 0.7783 - val_loss: 0.7177
Epoch 6/10
144/144 ━━━━━━━━━━━━━━━━━━━━ 82s 564ms/step - accuracy: 0.9585 - loss: 0.1622 - val_accuracy: 0.8026 - val_loss: 0.6888
Epoch 7/10
144/144 ━━━━━━━━━━━━━━━━━━━━ 82s 563ms/step - accuracy: 0.9787 - loss: 0.0978 - val_accuracy: 0.8096 - val_loss: 0.6960
Epoch 8/10
144/144 ━━━━━━━━━━━━━━━━━━━━ 82s 569ms/step - accuracy: 0.9895 - loss: 0.0547 - val_accuracy: 0.8113 - val_loss: 0.7218
Epoch 9/10
144/144 ━━━━━━━━━━━━━━━━━━━━ 80s 553ms/step - accuracy: 0.9871 - loss: 0.0556 - val_accuracy: 0.8061 - val_loss: 0.7460
Epoch 10/10
144/144 ━━━━━━━━━━━━━━━━━━━━ 86s 579ms/step - accuracy: 0.9971 - loss: 0.0288 - val_accuracy: 0.8070 - val_loss: 0.7524
<keras.src.callbacks.history.History at 0x7fa0f4273cd0>
sample_text = ["NASA is planning a mission to Jupiter"]
seq = tokenizer.texts_to_sequences(sample_text)
pad = pad_sequences(seq, maxlen=maxlen, padding='post')
 
prediction = model_bi.predict(pad)
predicted_class_index = np.argmax(prediction)
print("Predicted class index:", predicted_class_index)
print("Actual Category:", newsgroups.target_names[predicted_class_index])
1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 74ms/step
Predicted class index: 6
Actual Category: sci.space

🔚 ملخص المقارنة

  • LSTM القياسي: يعالج التسلسلات من الماضي إلى المستقبل.
  • LSTM ثنائي الاتجاه (Bidirectional): يعالج التسلسلات من كلا الاتجاهين.
  • يمكن للنماذج ثنائية الاتجاه تعلّم سياق أغنى، وغالبًا ما تُعطي نتائج أفضل في مهام معالجة اللغة الطبيعية.

في النموذج السابق، حاول رصّ طبقة LSTM أخرى وإضافة طبقة Dropout بعد طبقة LSTM

تمرين: درّب نموذج GRU

  • استبدل LSTM الخاص بك بـGRU.
  • نزّل مجموعة بيانات 20 newsgroups الكاملة؛ توجد بالفعل مجموعتا "تدريب" و"اختبار" محدَّدتان، واستخدم مجموعة الاختبار لتقييم نموذجك
  • جرّب أطوال تسلسل (Sequence Lengths) مختلفة، وأبعادًا مختلفة للحالة المخفية (Hidden State) في LSTM. هل يمكنك تحسين النموذج؟