462 lines
27 KiB
Plaintext
462 lines
27 KiB
Plaintext
{
|
||
"cells": [
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"# רשתות עצביות חוזרות\n",
|
||
"\n",
|
||
"במודול הקודם, עסקנו בייצוגים סמנטיים עשירים של טקסט. הארכיטקטורה שבה השתמשנו תופסת את המשמעות המצטברת של מילים במשפט, אך היא אינה מתחשבת **בסדר** המילים, מכיוון שפעולת האגרגציה שמתרחשת לאחר ההטבעות מסירה את המידע הזה מהטקסט המקורי. מכיוון שמודלים אלו אינם מסוגלים לייצג את סדר המילים, הם אינם יכולים לפתור משימות מורכבות או עמומות יותר כמו יצירת טקסט או מענה על שאלות.\n",
|
||
"\n",
|
||
"כדי לתפוס את המשמעות של רצף טקסט, נשתמש בארכיטקטורה של רשת עצבית הנקראת **רשת עצבית חוזרת**, או RNN. כאשר משתמשים ב-RNN, אנו מעבירים את המשפט דרך הרשת, טוקן אחד בכל פעם, והרשת מייצרת **מצב** מסוים, אותו אנו מעבירים שוב לרשת יחד עם הטוקן הבא.\n",
|
||
"\n",
|
||
"\n",
|
||
"\n",
|
||
"בהינתן רצף הטוקנים $X_0,\\dots,X_n$, ה-RNN יוצר רצף של בלוקים של רשת עצבית, ומאמן את הרצף הזה מקצה לקצה באמצעות שיטת ה-backpropagation. כל בלוק ברשת מקבל זוג $(X_i,S_i)$ כקלט, ומייצר $S_{i+1}$ כתוצאה. המצב הסופי $S_n$ או הפלט $Y_n$ מועבר למסווג ליניארי כדי לייצר את התוצאה. כל בלוקי הרשת חולקים את אותם משקלים, ומאומנים מקצה לקצה באמצעות מעבר אחד של backpropagation.\n",
|
||
"\n",
|
||
"> התרשים לעיל מציג רשת עצבית חוזרת בצורה \"פרוסה\" (משמאל), ובייצוג חוזר קומפקטי יותר (מימין). חשוב להבין שכל תאי ה-RNN חולקים את אותם **משקלים ניתנים לשיתוף**.\n",
|
||
"\n",
|
||
"מכיוון שוקטורי המצב $S_0,\\dots,S_n$ מועברים דרך הרשת, ה-RNN מסוגל ללמוד תלות רציפה בין מילים. לדוגמה, כאשר המילה *לא* מופיעה במקום כלשהו ברצף, הרשת יכולה ללמוד כיצד לשלול אלמנטים מסוימים בתוך וקטור המצב.\n",
|
||
"\n",
|
||
"בתוך כל תא RNN ישנם שני מטריצות משקל: $W_H$ ו-$W_I$, והטיה $b$. בכל שלב של RNN, בהינתן קלט $X_i$ ומצב קלט $S_i$, מצב הפלט מחושב כ-$S_{i+1} = f(W_H\\times S_i + W_I\\times X_i+b)$, כאשר $f$ היא פונקציית הפעלה (לעיתים $\\tanh$).\n",
|
||
"\n",
|
||
"> עבור בעיות כמו יצירת טקסט (שנעסוק בהן ביחידה הבאה) או תרגום מכונה, אנו גם רוצים לקבל ערך פלט בכל שלב של RNN. במקרה כזה, ישנה גם מטריצה נוספת $W_O$, והפלט מחושב כ-$Y_i=f(W_O\\times S_i+b_O)$.\n",
|
||
"\n",
|
||
"בואו נראה כיצד רשתות עצביות חוזרות יכולות לעזור לנו לסווג את מערך הנתונים של החדשות שלנו.\n",
|
||
"\n",
|
||
"> עבור סביבת הסנדבוקס, יש להריץ את התא הבא כדי לוודא שהספרייה הנדרשת מותקנת, והנתונים נטענים מראש. אם אתם עובדים באופן מקומי, ניתן לדלג על התא הבא.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 1,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"import sys\n",
|
||
"!{sys.executable} -m pip install --quiet tensorflow_datasets==4.4.0\n",
|
||
"!cd ~ && wget -q -O - https://mslearntensorflowlp.blob.core.windows.net/data/tfds-ag-news.tgz | tar xz"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 2,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"import tensorflow as tf\n",
|
||
"from tensorflow import keras\n",
|
||
"import tensorflow_datasets as tfds\n",
|
||
"import numpy as np\n",
|
||
"\n",
|
||
"# We are going to be training pretty large models. In order not to face errors, we need\n",
|
||
"# to set tensorflow option to grow GPU memory allocation when required\n",
|
||
"physical_devices = tf.config.list_physical_devices('GPU') \n",
|
||
"if len(physical_devices)>0:\n",
|
||
" tf.config.experimental.set_memory_growth(physical_devices[0], True)\n",
|
||
"\n",
|
||
"ds_train, ds_test = tfds.load('ag_news_subset').values()"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {
|
||
"nteract": {
|
||
"transient": {
|
||
"deleting": false
|
||
}
|
||
}
|
||
},
|
||
"source": [
|
||
"כאשר מאמנים מודלים גדולים, הקצאת זיכרון GPU עשויה להפוך לבעיה. ייתכן שנצטרך גם להתנסות עם גדלים שונים של מיניבאצ'ים, כך שהנתונים יתאימו לזיכרון ה-GPU שלנו, ובכל זאת האימון יהיה מהיר מספיק. אם אתם מריצים את הקוד הזה על מכונת GPU משלכם, תוכלו להתנסות בהתאמת גודל המיניבאצ' כדי להאיץ את האימון.\n",
|
||
"\n",
|
||
"> **Note**: ידוע כי גרסאות מסוימות של מנהלי התקן NVidia אינן משחררות את הזיכרון לאחר אימון המודל. אנחנו מריצים מספר דוגמאות במחברת זו, וזה עלול לגרום למיצוי הזיכרון בתצורות מסוימות, במיוחד אם אתם מבצעים ניסויים משלכם כחלק מאותה מחברת. אם אתם נתקלים בשגיאות מוזרות כאשר אתם מתחילים לאמן את המודל, ייתכן שתרצו להפעיל מחדש את ליבת המחברת.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 3,
|
||
"metadata": {
|
||
"collapsed": true,
|
||
"jupyter": {
|
||
"outputs_hidden": false,
|
||
"source_hidden": false
|
||
},
|
||
"nteract": {
|
||
"transient": {
|
||
"deleting": false
|
||
}
|
||
}
|
||
},
|
||
"outputs": [],
|
||
"source": [
|
||
"batch_size = 16\n",
|
||
"embed_size = 64"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## מסווג RNN פשוט\n",
|
||
"\n",
|
||
"במקרה של RNN פשוט, כל יחידה חוזרת היא רשת לינארית פשוטה, אשר מקבלת וקטור קלט ווקטור מצב, ומפיקה וקטור מצב חדש. ב-Keras, ניתן לייצג זאת באמצעות השכבה `SimpleRNN`.\n",
|
||
"\n",
|
||
"למרות שניתן להעביר טוקנים מקודדים ב-one-hot ישירות לשכבת ה-RNN, זו אינה גישה מומלצת בשל הממדיות הגבוהה שלהם. לכן, נשתמש בשכבת embedding להקטנת הממדיות של וקטורי המילים, ולאחר מכן בשכבת RNN, ולבסוף במסווג `Dense`.\n",
|
||
"\n",
|
||
"> **הערה**: במקרים שבהם הממדיות אינה כה גבוהה, לדוגמה כאשר משתמשים בטוקניזציה ברמת תווים, ייתכן שיהיה הגיוני להעביר טוקנים מקודדים ב-one-hot ישירות לתא ה-RNN.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 4,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"Model: \"sequential\"\n",
|
||
"_________________________________________________________________\n",
|
||
"Layer (type) Output Shape Param # \n",
|
||
"=================================================================\n",
|
||
"text_vectorization (TextVect (None, None) 0 \n",
|
||
"_________________________________________________________________\n",
|
||
"embedding (Embedding) (None, None, 64) 1280000 \n",
|
||
"_________________________________________________________________\n",
|
||
"simple_rnn (SimpleRNN) (None, 16) 1296 \n",
|
||
"_________________________________________________________________\n",
|
||
"dense (Dense) (None, 4) 68 \n",
|
||
"=================================================================\n",
|
||
"Total params: 1,281,364\n",
|
||
"Trainable params: 1,281,364\n",
|
||
"Non-trainable params: 0\n",
|
||
"_________________________________________________________________\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"vocab_size = 20000\n",
|
||
"\n",
|
||
"vectorizer = keras.layers.experimental.preprocessing.TextVectorization(\n",
|
||
" max_tokens=vocab_size,\n",
|
||
" input_shape=(1,))\n",
|
||
"\n",
|
||
"model = keras.models.Sequential([\n",
|
||
" vectorizer,\n",
|
||
" keras.layers.Embedding(vocab_size, embed_size),\n",
|
||
" keras.layers.SimpleRNN(16),\n",
|
||
" keras.layers.Dense(4,activation='softmax')\n",
|
||
"])\n",
|
||
"\n",
|
||
"model.summary()"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"> **הערה:** כאן אנו משתמשים בשכבת הטמעה לא מאומנת לצורך פשטות, אך כדי לקבל תוצאות טובות יותר ניתן להשתמש בשכבת הטמעה מאומנת מראש באמצעות Word2Vec, כפי שתואר ביחידה הקודמת. זה יכול להיות תרגיל טוב עבורך להתאים את הקוד כך שיעבוד עם הטמעות מאומנות מראש.\n",
|
||
"\n",
|
||
"עכשיו בואו נתחיל לאמן את ה-RNN שלנו. באופן כללי, קשה מאוד לאמן RNNs, מכיוון שברגע שתאי ה-RNN נפרסים לאורך רצף, מספר השכבות המעורבות בתהליך ה-backpropagation הופך להיות גדול מאוד. לכן, עלינו לבחור קצב למידה קטן יותר ולאמן את הרשת על מערך נתונים גדול יותר כדי לקבל תוצאות טובות. זה יכול לקחת זמן רב, ולכן מומלץ להשתמש ב-GPU.\n",
|
||
"\n",
|
||
"כדי לזרז את התהליך, נאמן את מודל ה-RNN רק על כותרות חדשות, ונשמיט את התיאור. אתה יכול לנסות לאמן עם התיאור ולראות אם תצליח לגרום למודל להתאמן.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 5,
|
||
"metadata": {
|
||
"scrolled": true
|
||
},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"Training vectorizer\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"def extract_title(x):\n",
|
||
" return x['title']\n",
|
||
"\n",
|
||
"def tupelize_title(x):\n",
|
||
" return (extract_title(x),x['label'])\n",
|
||
"\n",
|
||
"print('Training vectorizer')\n",
|
||
"vectorizer.adapt(ds_train.take(2000).map(extract_title))"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 6,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"7500/7500 [==============================] - 82s 11ms/step - loss: 0.6629 - acc: 0.7623 - val_loss: 0.5559 - val_acc: 0.7995\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\n"
|
||
]
|
||
},
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"<tensorflow.python.keras.callbacks.History at 0x7f3e0030d350>"
|
||
]
|
||
},
|
||
"execution_count": 6,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'], optimizer='adam')\n",
|
||
"model.fit(ds_train.map(tupelize_title).batch(batch_size),validation_data=ds_test.map(tupelize_title).batch(batch_size))"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {
|
||
"nteract": {
|
||
"transient": {
|
||
"deleting": false
|
||
}
|
||
}
|
||
},
|
||
"source": [
|
||
"> **שימו לב** שהדיוק עשוי להיות נמוך יותר כאן, מכיוון שאנחנו מתאמנים רק על כותרות חדשות.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## חזרה על רצפי משתנים\n",
|
||
"\n",
|
||
"זכרו שהשכבה `TextVectorization` תוסיף באופן אוטומטי סימני ריפוד לרצפים באורך משתנה בתוך קבוצת מיניבאץ'. מסתבר שסימנים אלו משתתפים גם בתהליך האימון, והם יכולים להקשות על התכנסות המודל.\n",
|
||
"\n",
|
||
"ישנם מספר גישות שניתן לנקוט כדי לצמצם את כמות הריפוד. אחת מהן היא לסדר מחדש את מערך הנתונים לפי אורך הרצף ולחלק את כל הרצפים לפי גודל. ניתן לעשות זאת באמצעות הפונקציה `tf.data.experimental.bucket_by_sequence_length` (ראו [תיעוד](https://www.tensorflow.org/api_docs/python/tf/data/experimental/bucket_by_sequence_length)).\n",
|
||
"\n",
|
||
"גישה נוספת היא להשתמש ב**מסכות**. ב-Keras, יש שכבות שתומכות בקלט נוסף שמראה אילו סימנים יש לקחת בחשבון במהלך האימון. כדי לשלב מסכות במודל שלנו, ניתן להוסיף שכבת `Masking` נפרדת ([תיעוד](https://keras.io/api/layers/core_layers/masking/)), או להגדיר את הפרמטר `mask_zero=True` בשכבת ה-`Embedding` שלנו.\n",
|
||
"\n",
|
||
"> **Note**: האימון הזה ייקח בערך 5 דקות להשלמת אפוק אחד על כל מערך הנתונים. אתם מוזמנים להפסיק את האימון בכל רגע אם תתייאשו. מה שניתן לעשות גם הוא להגביל את כמות הנתונים המשמשים לאימון, על ידי הוספת `.take(...)` אחרי מערכי הנתונים `ds_train` ו-`ds_test`.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 7,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"7500/7500 [==============================] - 371s 49ms/step - loss: 0.5401 - acc: 0.8079 - val_loss: 0.3780 - val_acc: 0.8822\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\n"
|
||
]
|
||
},
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"<tensorflow.python.keras.callbacks.History at 0x7f3dec118850>"
|
||
]
|
||
},
|
||
"execution_count": 7,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"def extract_text(x):\n",
|
||
" return x['title']+' '+x['description']\n",
|
||
"\n",
|
||
"def tupelize(x):\n",
|
||
" return (extract_text(x),x['label'])\n",
|
||
"\n",
|
||
"model = keras.models.Sequential([\n",
|
||
" vectorizer,\n",
|
||
" keras.layers.Embedding(vocab_size,embed_size,mask_zero=True),\n",
|
||
" keras.layers.SimpleRNN(16),\n",
|
||
" keras.layers.Dense(4,activation='softmax')\n",
|
||
"])\n",
|
||
"\n",
|
||
"model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'], optimizer='adam')\n",
|
||
"model.fit(ds_train.map(tupelize).batch(batch_size),validation_data=ds_test.map(tupelize).batch(batch_size))"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"עכשיו, כשאנחנו משתמשים במסכות, אנחנו יכולים לאמן את המודל על כל מערך הנתונים של הכותרות והתיאורים.\n",
|
||
"\n",
|
||
"> **הערה**: האם שמתם לב שהשתמשנו בווקטורייזר שאומן על כותרות החדשות, ולא על כל גוף המאמר? ייתכן שזה גורם להתעלמות מחלק מהטוקנים, ולכן עדיף לאמן מחדש את הווקטורייזר. עם זאת, ההשפעה עשויה להיות קטנה מאוד, ולכן נמשיך להשתמש בווקטורייזר המאומן מראש לטובת הפשטות.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## זיכרון ארוך-טווח (LSTM)\n",
|
||
"\n",
|
||
"אחת הבעיות המרכזיות של רשתות עצביות חוזרות (RNNs) היא **דעיכת גרדיאנטים**. רשתות חוזרות יכולות להיות די ארוכות, ולעיתים מתקשות להעביר את הגרדיאנטים חזרה לשכבה הראשונה של הרשת במהלך תהליך ה-backpropagation. כאשר זה קורה, הרשת אינה יכולה ללמוד קשרים בין טוקנים רחוקים. דרך אחת להימנע מבעיה זו היא להכניס **ניהול מצב מפורש** באמצעות **שערים**. שתי הארכיטקטורות הנפוצות ביותר שמכניסות שערים הן **זיכרון ארוך-טווח** (LSTM) ויחידת ממסר משולבת (GRU). כאן נתמקד ב-LSTM.\n",
|
||
"\n",
|
||
"\n",
|
||
"\n",
|
||
"רשת LSTM מאורגנת באופן דומה ל-RNN, אך ישנם שני מצבים שעוברים משכבה לשכבה: המצב בפועל $c$ והווקטור הנסתר $h$. בכל יחידה, הווקטור הנסתר $h_{t-1}$ משולב עם הקלט $x_t$, וביחד הם שולטים במה שקורה למצב $c_t$ ולפלט $h_{t}$ באמצעות **שערים**. לכל שער יש פונקציית הפעלה מסוג סיגמואיד (פלט בטווח $[0,1]$), שניתן לחשוב עליה כמסכה ביטית כאשר היא מוכפלת בווקטור המצב. ל-LSTM יש את השערים הבאים (משמאל לימין בתמונה למעלה):\n",
|
||
"* **שער השכחה** שקובע אילו רכיבים בווקטור $c_{t-1}$ עלינו לשכוח ואילו להעביר הלאה.\n",
|
||
"* **שער הקלט** שקובע כמה מידע מהווקטור הקלט ומהווקטור הנסתר הקודם יש לשלב בווקטור המצב.\n",
|
||
"* **שער הפלט** שלוקח את ווקטור המצב החדש ומחליט אילו מרכיביו ישמשו ליצירת הווקטור הנסתר החדש $h_t$.\n",
|
||
"\n",
|
||
"ניתן לחשוב על רכיבי המצב $c$ כדגלים שניתן להפעיל ולכבות. לדוגמה, כאשר אנו נתקלים בשם *אליס* ברצף, אנו מניחים שמדובר באישה, ומפעילים את הדגל במצב שמציין שיש לנו שם עצם נשי במשפט. כאשר אנו נתקלים בהמשך במילים *וגם טום*, נפעיל את הדגל שמציין שיש לנו שם עצם ברבים. כך, באמצעות מניפולציה של המצב, ניתן לעקוב אחר התכונות הדקדוקיות של המשפט.\n",
|
||
"\n",
|
||
"> **Note**: הנה משאב מצוין להבנת המבנה הפנימי של LSTM: [Understanding LSTM Networks](https://colah.github.io/posts/2015-08-Understanding-LSTMs/) מאת כריסטופר אולה.\n",
|
||
"\n",
|
||
"למרות שהמבנה הפנימי של תא LSTM עשוי להיראות מורכב, Keras מסתירה את היישום הזה בתוך שכבת `LSTM`, כך שהדבר היחיד שעלינו לעשות בדוגמה למעלה הוא להחליף את השכבה החוזרת:\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 8,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"15000/15000 [==============================] - 188s 13ms/step - loss: 0.5692 - acc: 0.7916 - val_loss: 0.3441 - val_acc: 0.8870\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\n"
|
||
]
|
||
},
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"<tensorflow.python.keras.callbacks.History at 0x7f3d6af5c350>"
|
||
]
|
||
},
|
||
"execution_count": 8,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"model = keras.models.Sequential([\n",
|
||
" vectorizer,\n",
|
||
" keras.layers.Embedding(vocab_size, embed_size),\n",
|
||
" keras.layers.LSTM(8),\n",
|
||
" keras.layers.Dense(4,activation='softmax')\n",
|
||
"])\n",
|
||
"\n",
|
||
"model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'], optimizer='adam')\n",
|
||
"model.fit(ds_train.map(tupelize).batch(8),validation_data=ds_test.map(tupelize).batch(8))"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"> **שימו לב** שאימון LSTMs הוא גם די איטי, ואתם עשויים לא לראות עלייה רבה בדיוק בתחילת האימון. ייתכן שתצטרכו להמשיך באימון במשך זמן מה כדי להשיג דיוק טוב.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## RNN דו-כיווני ורב-שכבתי\n",
|
||
"\n",
|
||
"בדוגמאות שלנו עד כה, הרשתות החוזרות פועלות מתחילת הרצף ועד סופו. זה מרגיש טבעי לנו כי זה תואם את הכיוון שבו אנו קוראים או מקשיבים לדיבור. עם זאת, בתרחישים שבהם נדרש גישה אקראית לרצף הקלט, יש יותר היגיון להריץ את החישוב החוזר בשני הכיוונים. RNNs שמאפשרים חישובים בשני הכיוונים נקראים **RNN דו-כיווני**, וניתן ליצור אותם על ידי עטיפת השכבה החוזרת עם שכבת `Bidirectional` מיוחדת.\n",
|
||
"\n",
|
||
"> **Note**: שכבת `Bidirectional` יוצרת שני עותקים של השכבה שבתוכה, ומגדירה את המאפיין `go_backwards` של אחד מהעותקים ל-`True`, כך שהוא פועל בכיוון ההפוך לאורך הרצף.\n",
|
||
"\n",
|
||
"רשתות חוזרות, בין אם חד-כיווניות או דו-כיווניות, לוכדות תבניות בתוך רצף, ושומרות אותן בווקטורי מצב או מחזירות אותן כפלט. כמו ברשתות קונבולוציה, ניתן לבנות שכבה חוזרת נוספת אחרי הראשונה כדי ללכוד תבניות ברמה גבוהה יותר, שנבנות מתבניות ברמה נמוכה יותר שהשכבה הראשונה חילצה. זה מוביל אותנו למושג של **RNN רב-שכבתי**, שמורכב משתי רשתות חוזרות או יותר, כאשר הפלט של השכבה הקודמת מועבר לשכבה הבאה כקלט.\n",
|
||
"\n",
|
||
"\n",
|
||
"\n",
|
||
"*תמונה מתוך [הפוסט הנהדר הזה](https://towardsdatascience.com/from-a-lstm-cell-to-a-multilayer-lstm-network-with-pytorch-2899eb5696f3) מאת Fernando López.*\n",
|
||
"\n",
|
||
"Keras הופכת את בניית הרשתות הללו למשימה פשוטה, כי כל מה שצריך לעשות זה להוסיף עוד שכבות חוזרות למודל. עבור כל השכבות למעט האחרונה, יש להגדיר את הפרמטר `return_sequences=True`, כי אנחנו צריכים שהשכבה תחזיר את כל המצבים הביניים, ולא רק את המצב הסופי של החישוב החוזר.\n",
|
||
"\n",
|
||
"בואו נבנה LSTM דו-שכבתי דו-כיווני עבור בעיית הסיווג שלנו.\n",
|
||
"\n",
|
||
"> **Note** הקוד הזה שוב לוקח זמן רב להשלמה, אבל הוא נותן לנו את הדיוק הגבוה ביותר שראינו עד כה. אז אולי שווה להמתין ולראות את התוצאה.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 9,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"5044/7500 [===================>..........] - ETA: 2:33 - loss: 0.3709 - acc: 0.8706\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r5045/7500 [===================>..........] - ETA: 2:33 - loss: 0.3709 - acc: 0.8706"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"model = keras.models.Sequential([\n",
|
||
" vectorizer,\n",
|
||
" keras.layers.Embedding(vocab_size, 128, mask_zero=True),\n",
|
||
" keras.layers.Bidirectional(keras.layers.LSTM(64,return_sequences=True)),\n",
|
||
" keras.layers.Bidirectional(keras.layers.LSTM(64)), \n",
|
||
" keras.layers.Dense(4,activation='softmax')\n",
|
||
"])\n",
|
||
"\n",
|
||
"model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'], optimizer='adam')\n",
|
||
"model.fit(ds_train.map(tupelize).batch(batch_size),\n",
|
||
" validation_data=ds_test.map(tupelize).batch(batch_size))"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## RNNs למשימות אחרות\n",
|
||
"\n",
|
||
"עד כה, התמקדנו בשימוש ב-RNNs לסיווג רצפי טקסט. אך הם יכולים להתמודד עם משימות רבות נוספות, כמו יצירת טקסט ותרגום מכונה — נבחן את המשימות הללו ביחידה הבאה.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"\n---\n\n**כתב ויתור**: \nמסמך זה תורגם באמצעות שירות תרגום מבוסס בינה מלאכותית [Co-op Translator](https://github.com/Azure/co-op-translator). בעוד שאנו שואפים לדיוק, יש להיות מודעים לכך שתרגומים אוטומטיים עשויים להכיל שגיאות או אי דיוקים. המסמך המקורי בשפתו המקורית צריך להיחשב כמקור סמכותי. עבור מידע קריטי, מומלץ להשתמש בתרגום מקצועי על ידי אדם. איננו נושאים באחריות לאי הבנות או לפרשנויות שגויות הנובעות משימוש בתרגום זה.\n"
|
||
]
|
||
}
|
||
],
|
||
"metadata": {
|
||
"kernel_info": {
|
||
"name": "conda-env-py37_tensorflow-py"
|
||
},
|
||
"kernelspec": {
|
||
"display_name": "py37_tensorflow",
|
||
"language": "python",
|
||
"name": "conda-env-py37_tensorflow-py"
|
||
},
|
||
"language_info": {
|
||
"codemirror_mode": {
|
||
"name": "ipython",
|
||
"version": 3
|
||
},
|
||
"file_extension": ".py",
|
||
"mimetype": "text/x-python",
|
||
"name": "python",
|
||
"nbconvert_exporter": "python",
|
||
"pygments_lexer": "ipython3",
|
||
"version": "3.7.9"
|
||
},
|
||
"nteract": {
|
||
"version": "nteract-front-end@1.0.0"
|
||
},
|
||
"coopTranslator": {
|
||
"original_hash": "81351e61f619b432ff51010a4f993194",
|
||
"translation_date": "2025-08-28T21:48:18+00:00",
|
||
"source_file": "lessons/5-NLP/16-RNN/RNNTF.ipynb",
|
||
"language_code": "he"
|
||
}
|
||
},
|
||
"nbformat": 4,
|
||
"nbformat_minor": 4
|
||
} |