תיאוריה
Artificial Neural Networks (ANN)
רשתות ניורונים מלאכותיות (Artificial Neural Networks (ANN)) הינם מודלים מתמטיים אשר בנויים בהשראת רשתות נוירונים ביולוגיות. בדומה לרשות הביולוגיות, הם בנויות כאוסף של יחידות אשר מבצעות כל אחת פעולה מתימטית פשוטה. יחידות אלו מכונות לרוב נוירונים מלאכותיים. כאשר משלבים יחד מספר נויורונים (על ידי מיפוי המוצאים של נויורונים מסויימות לכניסות של נוירונים איחרים) ניתן למדל פונקציות מורכבות.
לרוב, נבחרת הפעולה שאותה מבצע נוירון תהיה הפעלה של פונקציה סקלארית לא לינארית על קומבינציה לינרארית של בתוספת קבוע (bias) של הכניסות של הנוירון.
הפונקציה הלא לינארית , מוכנה לרוב פונקציית הפעלה. בחירות נפוצות של הפונקציית ההפעלה הינן:
- הפונקציה הלוגיסטית, המכונה לרוב גם סיגמואיד (sigmoid):
- טנגנס היפרבולי:
- פונקציה “מיישרת”, המכונה ReLU (Rectified Linear Unit):
בקורס זה, אלא אם צויין אחרת אנו נניח כי הנויירונים ברשת בנויים בצורה זו (קומבינציה לינארית בתוספת קבוע ואחריה פונקציית אקטיבציה).
מושגים:
- יחידות נסתרות (hidden units): הנוירונים אשר אינם מחוברים למוצא הרשת (אינם נמצאים בסוף הרשת).
- רשת עמוקה (deep network): רשת אשר מכילה מסלולים מהכניסה למוצא אשר עוברים דרך יותר מיחידה נסתרת אחת.
- ארכיטקטורה: הצורה שבה הנוירונים מחוברים בתוך הרשת.
אנו מבדילים בין שני סוגי ארכיטקטורות:
- רשת הזנה קדמית (Feed-forward networks): אריטקטורות אשר אינם מכילות מסלולים מעגליים (המידע זורם בכיוון אחד מהכניסה למוצא).
- רשתות נשנות (recurrent networks): ארכיטקטורות אשר מכילות מסלולים מעגליים. בקורס זה לא נעסוק ברשתות מסוג זה.
השימוש ברשתות אלו הינו כמודל לייצוג הפונקציה שאותה אנו מנסים ללמוד (פונקציית החיזוי או הפילוג). בדומה למודלים שאשר איתם עבדנו עד כה אנו ננסה ללמוד את הפרמטרים של הרשת על סמך המדגם שבידינו, כאשר הלימוד של הפרמטרים נעשה לרוב על ידי שימוש באלגוריתם הגרדיאנט. את הגרדיאנט מחשבים בעזרת שיטה המוכנה back propagation, עליו נדבר בהרחבה בתרגול זה.
Multilayer Perceptron (MLP)
אחת הארכיטקטורות הפשוטות ביותר הנמצאות בשימוש הינה ארכיטקטורת הMultilayer Perceptron (MLP). במודל זה הנוירונים מסודרים בשתייים או יותר שכבות (layers) (שכבת הכניסה אינה נספרת), כאשר כל נוירון מוזן מכל הנוריונים שבשכבה שלפניו. רשת בעלת תכונה זו מכונה רשת בעלת fully connected leyers. הנוירונים בMLP הינם פונקציית אקטיבציה הפועלת על קומבינציה לינארית (פלוס קבוע, bias) של הכניסות.
הHyperparameters של MLP הינם:
- מספר השכבות
- מספר הנוירונים בכל שכבה
- פונקציית האקטיבציה
והפרמטרים הנלמדים הינם המשקולות ואברי הbias בקומבינציה הלניארית.
הערה חשובה!!!!
בבואנו להשתמש באלגוריתם הגרדיאנט לשם לימוד הפרמטרים של הרשת אנו נהיה מעוניינים בחישוב הנגזרת של פונקציית ההפסד/סיכון על פי הפרמטרים של הרשת (ולא על פי התצפיות ). זאת אומרת שאנו נהיה מעוניינים לבנות את גרף הנגזרות מהמוצא ועד לכל אחד מהפרמטרים של המודל.
הערה נוספת לגבי שמות
- במתמטיקה השם loss function (פונקציית ההפסד) נמצא בשימוש נרחב לתאור פונקציה כל שהיא שאותה אנו רוצים למזער. בקורס זה השתמשנו במושג זה ספציפית על מנצ לתאר את ה”קנס” אותו אנו משלמים על חיזוי יחיד . כמו כן השתמשנו במושג risk (סיכון) לתאור התוחלת (או התוחלת האמפירית) של ההפסד. בהקשר של deep learning השימוש במושג loss נעשה לרוב בגרסא הרחבה שלו, והוא מתאר פונקציה כל שהיא שאותה נרצה למזער. לרוב זה יהיה הסיכון עצמו.
- במתמטיקה השם sigmoid מתייחס לאוסף רחב של פונקצייות בעלות צורת S. בהקשר של deep learning, שם זה מתייחס לרוב לפונקציה הלוגיסטית. לצורך העניין, ההגדרה מוויקיפדיה הינה: “A sigmoid function is a bounded, differentiable, real function that is defined for all real input values and has a non-negative derivative at each point”
Back-Propagation
ייצוג פונקציה גרף - דוגמא
לשם המחשה, נסתכל על הפונקציה הבאה:
ניתן לייצג פונקציה זו כגרף באופן הבא:
כאשר:
נחשב לדוגמא את מוצא הרשת בעבור הכניסות הבאות:
תהליך זה מוכנה הforwad pass:
חישוב הנגזרות בעזרת הגרף
שיטת הBack propagation הינה שיטה לחישוב הנגזרת של פונקציה המיוצגת כגרף על ידי שימוש בכלל השרשרת של נגזרות. נסתכל על הדוגמא ממקודם, בעזרת כלל השרשרת נוכל לרשום את הנגזרת באופן הבא:
ובאופן דומה:
או באופן גרפי:
נוכל כמובן גם לרשום זאת כגרף אחד, כאשר אנו מאחדים את כל החישובים הזהים ובכך מייעלים את החישוב:
נשים לב כי לגרף זה צורה דומה לגרף של הפונקצייה המקורית, אם כי החישוב מתבצע בו בכיוון הפוך. באופן כללי ניתן להראות כי ניתן להפוך כל גרף של פונקציה על מנת לקבל את הגרף של הנגזרת שלו באופן הבא:
- הופכים את כיוון הזרימה בגרף. זאת אומרת שכל המוצאים של יחידת חישוב הופכים לכניסות ולהיפך.
- מוסיפים כפל בנגזרת לכל יציאה מייחידת חישוב כלשהי. נגזרת הינה של משתנה הכניסה על פי המשתנה באותו מוצא.
- את יחידות החישוב המקוריות מחליפים בפעולת סכימה על כל הכניסות לאותה יחידה (אם ישנה רק כניסה אחת אז אותה כניסה פשוט מוברת לכל המוצאים).
נחשב כעת את הנגזרות ונציב אותם לגרף:
נשיב לב בי בגרף המתקבל מופיעים הערכים של המשתנים . לכן על מנת לחשב את הנגזרות יהיה עלינו לרוב תחילה לחשב את הערכים המקבלים בגרף הפונקציה המקורית. החישוב של הפונקציה המקורית נקרא הforward pass והחישוב של הנגזרות מתוך הגרך ההפוך נקרא הbackward pass.
תרגילים
תרגיל 9.1 - Back propagation
נתונה הפונקציה הבאה:
שרטטו את הפונקציה כגרף המורכב מיחידות המבצעות פעולות פשוטות (חיבור וקטורים, מכפלה וקטורית וכפל בסקלר). שרטטו את הגרף של הפונקציה עצמה ואת הגרף של הנגזרת . חשבו את הforward והbackward pass בעבור ערכי הכניסה הבאים:
פתרון
גרף הפונקציה:
גרף הנגזרת:
Forward pass:
Backward pass:
תרגיל 9.2 - Back propagation in MLP
נתונה רשת מסוג MLP בעלת שתי כניסות, , שכבה נסתרת אחת המכילה 2 נוירונים ושתי יציאות . פונקציית האקטיבציה ברשת זו הינה הפונקציה הלוגיסטית (). בנוסף נתון כי כל נוירונים בכל שכבה חולקים את אותו רכיב bias.
נרצה ללמד רשת זו בעזרת אלגוריתם הגרדיאנט ופונקציית הפסד מסוג : , כאשר .
בעבור ערך התצפית והתוויות , השתמש בשיטת הback propagation על מנת לחשב את הגרדיאנט המקבל בעבור פרמטרי הרשת הבאים:
פתרון
גרף הפונקציה המלא, הכולל גם את פונקציית ההפסד נראה כך:
נזכיר כי אנו מעוניינים לחשב את הגרדיאנט בין המוצא, המסומן כ לכל אחת מעשרת פרמטרי הרשת.
נבנה את הגרף לחישוב הנגזרת על פי העקרונות ממקודם. נשתמש בעובדה כי ניתן לבטא את הנגזרת של הפונקציה הלוגיסטית ניתנת לביטוי על ידי מוצא הפונקציה: . אחרי מעט עבודה נקבל את הגרף הבא:
נחשב ראשית את הforward pass:
וכעת את הbackward pass:
תרגיל 9.3 - משפט הקירוב האוניברסלי
א) הראו כיצד ניתן לייצג את הפונקציה הבאה בעזרת רשת feedforward המכילה מנוירונים בעלי פונקציית הפעלה מסוג ReLU, . שרטטו את הרשת ורשמו את הערכים של פרמטרי הרשת.
ב) האם ניתן לייצג במדוייק את הפונקציה בעזרת רשת feedforward המכילה מנוירונים בעלי פונקציית הפעלה מסוג ReLU בלבד? הסבירו ו/או הדגימו.
פתרון
א)
בעזרת נויירונים בעלי פונקציית אקטיבציה מסוג ReLU הפועלים על קומבינציה לינארית של הכניסות, נוכל לבנות פונקציות רציפות ולינאריות למקוטעין, בעלות מספר סופי של קטעים, כמו זו בשבשאלה זו.
נבנה פונקציה זו בעזרת MLP בעל שיכבה נסתרת אחת אשר דואגת לייצג את המקטעים השונים ושיכבת מוצא אשר דואגת לשיפוע בכל מקטע. נבנה את השכבה הנסתרת כך שאנו מתאימים נוירון לכל נקודה בה משתנה השיפוע של פונקציית המטרה. נקבע את קבוע הbias בכל נוירון כך שהשינוי בשיפוע של ה ReLU (ב$) יהיה ממוקם על נקודה בה משתנה השיפוע של הפונקציה המקורית:
כעת נדאג לשיפועים. נסתכל על מקטעים משמאל לימין.
- המקטע השמאלי ביותר הינו בעל שיפוע 0 ולכן הוא כבר מסודר, שכן כל הפונקציות אקטיבציה מתאפסות באיזור זה.
- המקטע מושפע רק מן הנוירון הראשון. השיפוע במקטע זה הינו 1 ולכן ניתן משקל של 1 לנירון זה.
- המקטע מושפע משני הנוירונים הראשונים. הנוירון הראשון כבר תורם שיפוע של 1 במקטע זה ולכן עלינו להוסיף לו עוד שיפוע של על מנת לקבל את השיפוע של הנדרש. ולכן ניתן משקל של לנירון השני.
- באופן דומה ניתן לנוירון השלישי משקל של .
סה”כ קיבלנו כי
ב)
מכיוון ש:
- נוירון בעל פונקציית הפעלה מסוג ReLU מייצג פונקציה רציפה ולינארית למקוטעין.
- כל הרכבה או סכימה של פונקציות רציפות ולינאריות למקוטעין יצרו תמיד פונקציה חדשה שגם היא רציפה ולינארית למקוטעין.
בעזרת נוירונים מסוג ReLU נוכל רק לייצג פונקציות רציפות ולנאריות למקוטעין. מכיוון ש אינה לינארית אנו נוכל רק לקרב אותה, אך לא לייצג אותה במדוייק.
בעיה מעשית
סביבות פיתוח - Deep Learning Frameworks
עם הצמיחה בפופולריות של התחום של deep learning הופיעו מספר רב של סביבות פיתוח (frameworks). סביבות אלו מגיעות לרוב כספיריה (או toolbox) בעבור שפת תכנות קיימת. ספריות אלו מפשטות מאד את תהליך הפיתוח של מערכות המבוססות על רשתות נוירונים. הם לרוב מציעות יכולות כגון:
- מימוש של מגוון פונקציות נפוצות כגון פונקציות אקטיבציה וכלים לבניית רשתות.
- ביצוע back propagation באופן אוטומטי.
- הרצת אלגוריתמי גרדינאט מתוחכמים.
- הרצת הרשתות והאופטימיזציה על GPU לשם האצה.
מצורפת רשימה חלקית של סביבות שכאלה:
- TensorFlow: סיפריית Python אשר פותחה ומתוחזקת על ידי Google. כיום סביבת הפיתוח הפופולרית ביותר.
- PyTorch: ספרית Python נוספת אשר מבוססת על ספריה ישנה יותר הנקראת Torch אשר נכתבה לשפה בשם LUA. הספריה מפותחת ומתוחזקת על ידי Facebook. ספריה זו מעט יותר צעירה וצוברת פופולריות בקצב מהיר. כיום פופולרית כמעט כמו TensorFlow.
- Caffe & Caffe2: ספריה מעט ישנה יותר בעלת תמיכה ב C, C++, Pyhton ו Matlab. הספריה פותחה במקור בBerkley וכיום מתוחזקת על ידי פייסבוק. הפופולריות של ספריה זו הולך ופוחת בשנים האחרונות.
- Keras: ספרית Python אשר נבנתה על גבי ספריות אחרות כגון TensorFlow ומציעה ממשק יותר “ידידותי”. ספריה זו יוצרת אבסטרקציה גבוהה יותר של תהליך התכנון והאימון של הרשת אך בעקבות כך פחות מאפשרת שליטה על תהליך זה.
- fast.ai: סביבת פיתוח חדשה יחסית אשר עוטפת את PyTorch ומציעה יכולות נוספות והגדרות ברירת מחדל טובות יותר על מנת לייעל את תהליך התכנון והאימון של הרשתות. סביבה זו קיבלה תגובות חיוביות רבות בקהילת הdeep learning ומתחילה לאט לאט לצבור פופולריות.
- Matlab: לאחרונה, פרסמה MathWorks (החברה שכותבת את Matlab) Toolbox לעבודה עם רשתות ניורונים.
בתרגול הבית הרטוב בקורס זה תתנסו בבניה ואימון של רשת בעזרת PyTorch. הבחירה לעבוד עם PyTorch הינה בעיקר בגלל הדמיון שלו לNum-Py אשר מאפשר התחלת עבודה מהירה ואינטואיטיבית.
PyTorch
בתרגול זה נחזור לבעיית הסיווג של תאים סרטניים. אנו נשתמש בPyTorch על מנת לשחזר את הפתרון מהתרגול הקודם של מציאת מודל linear logistic regression אופטימאלי. לאחר מכן נרחיב את התפרון למודל של MLP.
הדרך הנפוצה לבניה והרצה של מודלים ורשתות בPyTorch היא על ידי הגדרת class יעודי לרשת שאיתו נרצה לעבוד. הדבר נעשה באופן הבא:
## Defining the network
class Net(torch.nn.Module):
def __init__(self):
## The following line should appear in the begining of the __init__ function.
## It is realted to inheretance issues and we will not elaborate on this here.
super(Net, self).__init__()
## Defining some objects which will be used in the forward function.
self.linear = torch.nn.Linear(2, 1) ## A linear model with input of 2 and output of 1.
self.sigmoid = torch.nn.Sigmoid() ## A sigmoid function
def forward(self, x):
## The function which defines the forward pass.
x = self.linear(x)
x = self.sigmoid(x)
return x
הרשת הנל מתארת מודל של linear logistic regresion, המוצא הינו קומבינציה לינארית של 2 משתני כניסה, אשר מוזנים לsigmoid כפונקציית אקטיבציה. במימוש של פונקציה זו אנו עושים שימוש באובייקטים הבאים מהחבילה של PyTorch:
- torch.nn.Model: אובייקט שממנו יש לרשת כאשר יוצרים רשת חדש בPyTorch. בקורס זה לא נרחיב על הנושא ונתייחס לצורה זו כאל תבנית שבה יש להשתמש.
- torch.nn.Linear: אובייקט אשר מבצע טרנספורמציה לינארית (לייתר דיוק אפינית) על וקטור כניסה מסיים.
- torch.nn.Sigmoid אובייקט הממש את פונקציית sigmoid.
הקוד הבא עושה שימוש ברשת אשר הוגדרה לעיל, ומאמן אותה על מדגם נתון:
def basic_gradient_decent(net, alpha, tol, max_iter, x_train, y_train):
## Set the loss function
loss_func = torch.nn.BCELoss()
## Initizalie the optimizer
opt = torch.optim.SGD(net.parameters(), lr=alpha)
last_objective = None
objective = None
while (last_objective is None) or (torch.abs(objective - last_objective) < tol) or (i_iter == max_iter):
last_objective = objective
opt.zero_grad()
prob = net(x_train) ## Forward pass
objective = loss_func(prob, y_train.float()) ## Loss calculation
objective.backward() ## Backward pass
opt.step() ## Perform the update step
## Optimization parameters
## =======================
tol = 1e-7
max_iter = 100000
alpha = 1e-2 ## Learning rate
net = Net()
basic_gradient_decent(net, alpha, tol, max_iter, x_train, y_train)
-
האובייקט של torch.nn.BCELoss BCELoss מגדיר loss באופן הבא:
והוא שקול לפתרון המתקבל בשיטת MLE.
-
האובייקט של torch.optim.SGD מגדיר את אלגוריתם האופטמיזציה ומקבל באיתחול את רשימת הפרמטרים שעליהם מבוצעת האופטימיזציה.
תוצאה
הסיכון המשוערך על סט הבחן הינו: 0.097. הגרף של פונקציית המטרה של האופטימיציזה כפונקציה של מספר האיטרציה הינו:
MLP
ניתן כעת בקלות להרחיב מודל זה לMLP, פשוט על ידי הוספה של שכבות נוספות. נוסיף שכבה נוספת של 2 נוירונים ונארה כיצד הדבר משפיע על התוצאות.
class NetMLP(nn.Module):
def __init__(self):
## The following line should appear in the begining of the __init__ function.
## It is realted to inheretance issues and we will not elaborate on this here.
super(NetMLP, self).__init__()
self.linear1 = nn.Linear(2, 2) ## input: 2 feature, output: 2 features
self.linear2 = nn.Linear(2, 1) ## input: n_units_in_hidden, output: 1
self.sigmoid = nn.Sigmoid() ## A sigmoid function
torch.random.manual_seed(0)
torch.nn.init.normal_(self.linear1.weight)
torch.nn.init.normal_(self.linear2.weight)
def forward(self, x):
x = self.linear1(x)
x = self.sigmoid(x)
x = self.linear2(x)
x = self.sigmoid(x)
return x
תוצאה
הסיכון המשוערך על סט הבחן הינו: 0.07. הגרף של פונקציית המטרה של האופטימיציזה כפונקציה של מספר האיטרציה הינו:
כלל החלטה נראה כך: