Keras Tuner - לבחירת ההיפר-פרמטרים למודל למידת מכונה
בכל פעם שאני צריך לבנות מודל של למידת מכונה אני צריך לקבל החלטות בנוגע להיפר-פרמטרים של הרשת מה שיוצר לי בעיה כי מספר האפשרויות הוא עצום. במדריך זה אנסה לפתור את הבעיה בשתי גישות. גישה אחת של ניחוש מושכל המבוסס על ידע אנושי. גישה שנייה היא סריקת מגוון עצום של פרמטרים וארכיטקטורות באמצעות ספריית Keras Tuner. אם תרצו אדם כנגד מכונה במשימה האולטימטיבית למצוא את מודל המכונה הטוב ביותר.
גישה של ניחוש מושכל מבוססת על הידע הקיים שלי בתחום. מתוכו פיתחתי הנחיות בנוגע לבחירת ההיפר-פרמטרים לבניית מודלים של למידת מכונה.
ההנחיות כוללות:
- עדיף מודל פשוט על פני מסובך ובתנאי שהוא מצליח לעשות את העבודה. זה אומר פחות שכבות ופחות יחידות בכל שכבה.
- המבנה של השכבות החבויות צריך ליצור מעין משפך עם מספר גדול יותר של יחידות בשכבה הראשונה ומספר הולך וקטן בכל אחת מהשכבות הבאות.
- מספר היחידות בכל שכבה צריך להיות זוגי.
- פונקצית האקטיבציה העדיפה היא relu.
- Adam הוא אלגוריתם האופטימיזציה ברירת המחדל.
הגישה המנוגדת מניחה שאני לא יודע מספיק, ולכן כדאי לחפש בתוך המרחב העצום של ארכיטקטורות ופרמטרים את המודל שיתן את התוצאות הטובות ביותר. אפשר להשתמש בגישה של הרצת מודלים בתוך לולאות של פייתון. במדריך זה אני משתמש בספרייה מבוססת אלגוריתם, Keras Tuner, שעליה ממליצים היוצרים של TensorFlow.
להורדת מחברת עם הקוד המלא של המדריך בפורמט Jupyter notebook
הספריות
import numpy as np
import pandas as pd
TensorFlow היא ספרייה שמפתחת Google ללמידת מכונה:
import tensorflow as tf
הגירסה חשובה:
tf.__version__
2.4.1
רק עם גרסה 2.4 הצלחתי להתקין את Keras Tuner.
import kerastuner as kt
אנחנו זקוקים לספריות נוספות אותם נייבא במהלך המדריך.
מסד הנתונים
מסד הנתונים במדריך הוא churn dataset המכיל מידע על 7,043 לקוחות שחלק קטן מהם נטש את חברת Telco הדמיונית. המטרה שלנו היא לפתח מודל שיחזה איזה לקוחות יעזבו את החברה.
את מסד הנתונים פתחו ב-IBM והם היו מספיק נחמדים כדי להעלות אותו ל-GitHub.
https://github.com/IBM/telco-customer-churn-on-icp4d
את מסד הנתונים הורדתי לסביבת colab שמאפשרת להריץ קוד פייתון על מחברת בסגנון Jupyter notebook, בתוך השרתים של גוגל בחינם.
# Load the dataset
df = pd.read_csv('Telco_customer_churn.csv')
נקבל תחושה על מסד הנתונים:
# Explore
df.head()
כמה עמודות ושורות?
df.shape
(7043, 21)
המידע בטבלה כולל: מין, סוג השירות (טלפוניה, אינטרנט), מספר החודשים שהלקוח בחברה, שיטת התשלום והאם נטש את שירותי החברה. churn בעגת אנשי המכירות.
במחברת המצורפת אפשר לראות ש-18 מתוך 21 עמודות הם קטגוריות שמיות והיתר מספריות. מכאן שנצטרך להמיר את הנתונים הקטגוריים למספריים כדי שהמודל יוכל לעבוד איתם.
מה בנוגע לעמודה שהמודל צריך לחזות?
# What are the labels for the 'Churn' column?
df.Churn.unique()
array(['No', 'Yes'], dtype=object)
- עמודה קטגורית עם שני ערכים: Yes - נטש - או No.
כיצד הנתונים מתפלגים?
# How many samples do we have of each type?
df.Churn.value_counts()
No 5174 Yes 1869 Name: Churn, dtype: int64
- רוב הלקוחות לא עזבו ועל כן סט הנתונים אינו מאוזן. בבעיה זו נטפל בהמשך.
הכנת הנתונים ללמידת מכונה
במחברת המצורפת אני מפרט את התהליך שעבר מסד הנתונים כדי להכין אותו ללמידה.
בקצרה:
# since all the values are unique we can dismiss the column as mere noise
del df['customerID']
# 'TotalCharges' needs to be a float. How come it is an object?
# We need to replace the spaces with zeros prior to converting the type to float
df.TotalCharges.replace(' ', np.NaN, inplace=True)
df.TotalCharges.fillna(0, inplace=True)
df.TotalCharges = df.TotalCharges.astype(float)
# next we're going to one-hot encode the dataset and split it into train and split datasets
# first copy the original dataframe so we'll have a fallback to return to. just in case
df_clean = df
# which columns are of type object and thus need to be one-hot encoded
df_clean.columns.to_series().groupby(df.dtypes).groups
categorical_columns = ['gender', 'Partner', 'Dependents', 'PhoneService', 'MultipleLines', 'InternetService', 'OnlineSecurity', 'OnlineBackup', 'DeviceProtection', 'TechSupport', 'StreamingTV', 'StreamingMovies', 'Contract', 'PaperlessBilling', 'PaymentMethod', 'Churn']
for cname in categorical_columns:
dummies = pd.get_dummies(df_clean[cname], prefix=cname)
df_clean = pd.concat([df_clean, dummies], axis=1)
df_clean.drop([cname], axis=1, inplace=True)
- ניקיתי את הנתונים.
- העתקתי את הנתונים לתוך מסד נתונים ששמו df_clean שאיתו נעבוד בהמשך.
- הפכתי את הקטגוריות למספרים בשיטת one-hot encoding.
בגלל קידוד one-hot encoding מסד הנתונים מכיל עכשיו שתי עמודות Churn: Churn_Yes ו- Churn_No הם יהיו המשתנים התלויים שלנו. אותם ננסה לחזות.
נפריד את הנתונים התלויים מהבלתי תלויים תוך התחשבות בחוסר האיזון של העמודה
# 1. separate into dependent and independent dataframes
y_cols = df_clean.columns[-2:]
X_cols = df_clean.columns[:-2]
# make the actual separation
y = df_clean[y_cols]
X = df_clean[X_cols]
# 2. split into train and test datasets
# stratify to keep the ratio of y
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y,
train_size=.7,
random_state=42,
stratify=y)
- סט הנתונים הבלתי תלויים מוכל במשתנה X. ממנו ננסה לחזות את y.
- הפונקציה train_test_split שימשה להפרדה לסט ניסוי ומבחן. הפרמטר stratify שומר על היחס בין הקטגוריות ב-y.
נוודא שהיחס אכן נשמר:
# explore to see if the ratio is kept
# original dataset
sum(y.Churn_Yes)/len(y.Churn_Yes) # 0.2653698707936959
# the ratio following the split
sum(y_test.Churn_Yes)/len(y_test.Churn_Yes) # 0.26549929010885
המודל הראשוני
את המודל אני בונה על סמך הידע שלי בתחום של פיתוח מודלים של למידה עמוקה באמצעות TensorFlow.
נייבא את התלויות של TensorFlow:
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Activation
from tensorflow.keras.optimizers import Adam
נבנה את המודל:
# build the model
model = Sequential()
# the first layer receives 45 input features and outputs 64 to the next layer
# the activation function 'relu' is the standard in the literature
model.add(Dense(64, input_shape=(45,), activation='relu'))
# the hidden layer has less units because that's a common practice
# to architect the hidden layers as a funnel
# it is also common that the number of neurons in a hidden has a power of 2
model.add(Dense(16, activation='relu'))
# the third layer outputs 2 classes as the number of categories
# because it is categorical we use the softmax activation function
model.add(Dense(2, activation='softmax'))
- המודל מקבל 45 קלטים inputs כמספר העמודות הבלתי תלויות.
- שכבת הקלט, כמו יתר השכבות, היא מסוג Dense, והיא כוללת 64 יחידות עיבוד.
- השכבה השנייה היא חבויה hidden layer והיא כוללת פחות יחידות עיבוד 16.
- שכבת הפלט מכילה פונקצית אקטיבציה מסוג softmax כי אנחנו מנסים לחזות מידע קטגורי.
- בשתי השכבות האחרות פונקציית האקטיבציה היא מסוג relu כי ידוע שהיא נותנת תוצאות טובות במיוחד.
נציג את מבנה המודל:
model.summary()
Model: "sequential" ______________________________________ Layer (type) Output Shape Param # ====================================== dense (Dense) (None, 64) 2944 ______________________________________ dense_1 (Dense) (None, 16) 1040 ______________________________________ dense_2 (Dense) (None, 2) 34 ====================================== Total params: 4,018 Trainable params: 4,018 Non-trainable params: 0
נקמפל:
# The categorical_crossentropy loss function is the one we
# use when working with categorical labels
# the adam optimizer and accuracy as a metrics are standard
model.compile(optimizer='adam',
loss='categorical_crossentropy',
metrics=['accuracy'])
- מטרת תהליך הלמידה היא לשפר את המודל. עבור בעיות סיווג מרבים להשתמש בפונקציית loss מסוג categorical_crossentropy המודדת את התקדמות המודל באמצעות מדידת רמת הדיוק accuracy.
- על האופטימיזציה אחראי אלגוריתם מסוג Adam הידוע ביעילותו הרבה.
נאמן את המודל במשך 5 מחזורים כי בניסויים מקדימים ראיתי שהפונקציה מתכנסת לפיתרון אחרי 5 סיבובים:
# Train the model
model.fit(X_train, y_train,
validation_data=(X_test,y_test),
batch_size=32,
epochs=5)
המודל סיים לרוץ.
מה מידת הדיוק?
# find the accuracy
loss, acc = model.evaluate(X_test, y_test)
67/67 [====] - 0s 981us/step - loss: 0.6821 - accuracy: 0.7937
- מידת הדיוק של המודל היא 79.37%
בשלב זה אני נוהג להציג מדדים נוספים שמבוססים על confusion matrix. לדוגמה, מדריך לאבחנה בין קבוצות באמצעות למידת מכונה.
מכיוון שזה לא העיקר של המדריך אסתפק במדד accuracy שנותן תוצאה די גבוהה הקרובה ל-80%.
אבל כשמשתמשים במדד דיוק (accuracy) חשוב להשוות לדיוק null (null accuracy) שהוא שיעור ההצלחה במקרה שהמודל חוזה תמיד את הקטגוריה הנפוצה ביותר.
כשמשתמשים במדד דיוק (accuracy) חשוב להשוות לדיוק null (null accuracy) שהוא שיעור ההצלחה במקרה שהמודל חוזה תמיד את הקטגוריה הנפוצה ביותר.
נחשב את ה-null accuracy:
# the most frequent class is Churn_No
# calculate the ratio by summing the number
# of samples and dividing by the overall length
# 1 - sum(y_test.Churn_Yes)/len(y_test.Churn_Yes)
sum(y_test.Churn_No)/len(y_test.Churn_No)
0.73450070989115
- הקטגוריה הנפוצה יותר היא לקוחות שנשארו בחברה ולכן השיעור שלהם בקבוצת המבחן הוא ה-null accuracy.
- ערך null accuracy 73% אומר לנו שמודל פשוט שכל מה שהוא יודע לעשות הוא לחזות עבור 100% מהדוגמאות שהם לא יעזבו את החברה היה צודק ב-73% מהמקרים. בכלל לא רחוק מ-79.3% מה שגורם לי למחשבות שניות בנוגע ליעילות המודל.
אפשר להמשיך ולחקור היכן המודל הרבה לשגות ומה ניתן לעשות כדי לשפר אותו, אבל נניח לזה כרגע כי אנחנו מעוניינים לגלות האם הספרייה מבוססת האלגוריתם תצליח יותר.
מציאת מודל למידת המכונה הטוב ביותר באמצעות Keras Tuner
Keras Tuner היא ספרייה מבוססת אלגוריתם שפתחו בגוגל במטרה למצוא את ההיפר-פרמטרים הטובים ביותר שיש להשתמש בהם במודל TensorFlow. האלגוריתם סורק את מרחב האפשרויות שמעבירים אליו. דוגמת: מספר שכבות, מספר יחידות בשכבה, קצב למידה עד שהוא מוצא את שילוב ההיפר-פרמטרים שמגיע לתוצאות הטובות ביותר.
נתקין את הספרייה:
!pip install -U keras-tuner
הפונקציה הבאה תשמש לבניית המודלים:
# build the model
def build_model(hp):
model = Sequential()
# the model layers
meturn model
- בתוך הפונקציה Keras Tuner בונה את המודלים.
- הפונקציה מקבלת משתנה hp היפר-פרמטרים ומחזירה מודלים של TensorFlow.
נוסיף את שכבת הקלט:
# build the model
def build_model(hp):
model = Sequential()
# input layers with variable number of units
model.add(Dense(units=hp.Int('units',
min_value=8,
max_value=640,
step=8),
input_shape=(45,),
activation='relu'))
- מספר יחידות הקלט חייב להיות 45. כמספר העמודות הבלתי תלויות במסד הנתונים.
- בתוך הפונקציה hp.Int() אנחנו מגדירים את הטווח של מספר הנוירונים בשכבה. המספר יכול להיות בין 8 - 640 בקפיצות של 8.
נוסיף את השכבות החבויות:
# build the model
def build_model(hp):
model = Sequential()
# input layers with variable number of units
model.add(Dense(units=hp.Int('units',
min_value=8,
max_value=640,
step=8),
input_shape=(45,),
activation='relu'))
# variable number of hidden layers
# with a variable number of units in each
for i in range(hp.Int('num_layers', 1, 4)):
model.add(Dense(units=hp.Int('units_' + str(i),
min_value=8,
max_value=640,
step=8),
activation='relu'))
- הגדרנו מספר משתנה של שכבות ויחידות בכל שכבה מהם האלגוריתם צריך לבחור .
- את מספר השכבות נגדיר בתוך לולאה פייתונית.
- את מספר היחידות בכל שכבה אנו מגדירים שוב באמצעות הפונקציה hp.Int()
נגדיר את שכבת הפלט:
# build the model
def build_model(hp):
model = Sequential()
# input layers with variable number of units
model.add(Dense(units=hp.Int('units',
min_value=8,
max_value=640,
step=8),
input_shape=(45,),
activation='relu'))
# variable number of hidden layers
# with a variable number of units in each
for i in range(hp.Int('num_layers', 1, 4)):
model.add(Dense(units=hp.Int('units_' + str(i),
min_value=8,
max_value=640,
step=8),
activation='relu'))
# output layer
model.add(Dense(2, activation='softmax'))
- שכבת הפלט היא ללא שינוי וכוללת 2 יחידות כמספר הקטגוריות ופונקצית אקטיבציה softmax. .
את המודל צריך להחזיר מקומפל וזו ההזדמנות להגדיר טווח של קצבי למידה לבחור מתוכם:
# build the model
def build_model(hp):
model = Sequential()
# input layers with variable number of units
model.add(Dense(units=hp.Int('units',
min_value=8,
max_value=640,
step=8),
input_shape=(45,),
activation='relu'))
# variable number of hidden layers
# with a variable number of units in each
for i in range(hp.Int('num_layers', 1, 4)):
model.add(Dense(units=hp.Int('units_' + str(i),
min_value=8,
max_value=640,
step=8),
activation='relu'))
# output layer
model.add(Dense(2, activation='softmax'))
# a range of learning rates to choose from
hp_learning_rate = hp.Choice('learning_rate', values = [1e-2, 1e-3, 1e-4])
model.compile(optimizer = keras.optimizers.Adam(learning_rate = hp_learning_rate),
loss='categorical_crossentropy',
metrics = ['accuracy'])
return model
- העברנו לפונקציה hp.Choice() טווח של ערכים אפשריים לבחור מתוכם את קצב הלמידה.
ניצור תיקייה שבתוכה ה-tuner ישמור את המודלים השונים שהוא בודק בתהליך.
!rm -rf /content/logs
הפונקציה RandomSearch() היא זו שמייצרת את המודלים ובוחרת את המוצלחים:
tuner = RandomSearch(
build_model,
objective='val_accuracy',
max_trials=5,
executions_per_trial=3,
directory='/content/logs',
project_name='optimize_churn')
הפרמטרים כוללים:
- את שם הפונקציה שבונה את המודלים ללא פרמטרים. רק השם.
- הפרמטר objective מגדיר את מטרת המודל. במקרה זה המטרה היא להגיע ל-accuracy הגבוה ביותר עבור קבוצת המבחן.
- מספר הניסיונות max_trials הוא 5 הכוללים 3 ניסיונות לבניית מודלים בכל ניסיון executions_per_trial.
- באמצעות הפרמטרים directory ו-project_name נגדיר את התיקייה שבתוכה ישמרו תוצאות הניסוי.
את מקום הפונקציה fit() שבה אנו משתמשים בדרך כלל להרצת מודלים של TensorFlow תתפוס הפונקציה tuner.search() שמקבלת את אותם הפרמטרים:
#model.fit(X_train, y_train, validation_data=(X_test,y_test), batch_size=32, epochs=10)
tuner.search(x = X_train,
y = y_train,
epochs = 5,
batch_size = 32,
validation_data = (X_test, y_test))
התוצאה:
Trial 5 Complete [00h 00m 12s] val_accuracy: 0.7830888231595358 Best val_accuracy So Far: 0.7830888231595358 Total elapsed time: 00h 01m 32s INFO:tensorflow:Oracle triggered exit
מהם המודלים הטובים ביותר שמצא האלגוריתם?
tuner.results_summary()
Results summary Results in /content/logs/optimize_churn Showing 10 best trials Objective(name='val_accuracy', direction='max') Trial summary Hyperparameters: units: 488 num_layers: 3 units_0: 168 learning_rate: 0.0001 units_1: 256 units_2: 40 Score: 0.7830888231595358 Trial summary Hyperparameters: units: 608 num_layers: 3 units_0: 624 learning_rate: 0.01 units_1: 576 units_2: 152 Score: 0.7805647651354471 Trial summary Hyperparameters: units: 528 num_layers: 1 units_0: 440 learning_rate: 0.001 units_1: 432 units_2: 528 Score: 0.7794604897499084 Trial summary Hyperparameters: units: 448 num_layers: 2 units_0: 152 learning_rate: 0.01 units_1: 8 Score: 0.7345007061958313 Trial summary Hyperparameters: units: 568 num_layers: 3 units_0: 488 learning_rate: 0.01 units_1: 592 units_2: 8 Score: 0.7345007061958313
- המודל הטוב ביותר הגיע ל-accuracy של 0.78
- המודל החמישי בטיבו הגיע לציון 0.73
נבחן את המודל הנבחר.
נמצה את ההיפר-פרמטרים עבור המודל הנבחר:
# Get the optimal hyperparameters
best_hps = tuner.get_best_hyperparameters(num_trials = 1)[0]
מההיפר-פרמטרים נבנה את המודל הנבחר:
# Build the model with the optimal hyperparameters and train it on the data
model = tuner.hypermodel.build(best_hps)
- המודל הוא כרגיל כשמשתמשים ב- TensorFlow.
מהי ארכיטקטורת המודל?
print(model.summary())
Model: "sequential" _________________________________________ Layer (type) Output Shape Param # ========================================= dense (Dense) (None, 488) 22448 _________________________________________ dense_1 (Dense) (None, 168) 82152 _________________________________________ dense_2 (Dense) (None, 256) 43264 _________________________________________ dense_3 (Dense) (None, 40) 10280 _________________________________________ dense_4 (Dense) (None, 2) 82 ========================================= Total params: 158,226 Trainable params: 158,226 Non-trainable params: 0 _________________________
- 3 שכבות חבויות.
- 488 יחידות בשכבת הקלט
- מספר היחידות מצטמצם בין השכבות החבויות מ-168 ל-40.
נריץ את המודל:
model.fit(X_train, y_train,
batch_size = 32,
epochs = 5,
validation_data = (X_test, y_test))
נעריך את ביצועי המודל:
loss, acc = model.evaluate(X_test, y_test)
67/67 [====] - 0s 1ms/step - loss: 1.4820 - accuracy: 0.7795
- המודל אותו בחר האלגוריתם הגיע לרמת דיוק של 78%.
סיכום ודיון
במדריך השתמשתי באלגוריתם Keras Tuner כדי לבחור את המודל הטוב ביותר, והשוותי את התוצאות למודל שאני פתחתי על סמך הידע שרכשתי בתחום. להפתעתי, Keras Tuner לא התעלה על התוצאות שהמודל שלי השיג על פי מדד דיוק (78% לעומת 80%). יתרה מכך, המודל שהצעתי הוא קטן ורזה משמעותית מבחינת מספר השכבות ומספר היחידות בכל שכבה. הבדל שמהווה יתרון משמעותי בכל הנוגע לסיכוי להתאמת יתר over fitting ואם המודל היה גדול יותר החסכון הוא גם בחשמל ובשעות מחשב.
מצד שני, ההבדל אינו אדיר ושתי התוצאות עולות רק במעט על ה-null accuracy. מה שמחזיר את הכדור למגרש שלי כי נראה שהייתי צריך להשקיע יותר בהנדסת הנתונים שאני מזין למודל. אפשרות חלופית, היא הגדלת רגישות המודל על ידי שינוי רמות הסף שמעליהם נחשיב את התוצאה לחיובית.
בכל מקרה, טוב שיהיה כלי כמו ה- Keras Tuner בארגז הכלים אבל גם לא צריך לזנוח את דרך הלמידה שעמדה לאבותינו ולנו.
אולי גם זה יעניין אותך?
- סיווג לקבוצות באמצעות למידת מכונה
- מודל לחיזוי השרדות נוסעים על סיפון הטיטניק באמצעות למידת מכונה אוטומטית ו-AutoKeras
- שמירת וטעינת מודל TensorFlow 2
לכל המדריכים בנושא של למידת מכונה
אהבתם? לא אהבתם? דרגו!
0 הצבעות, ממוצע 0 מתוך 5 כוכבים
המדריכים באתר עוסקים בנושאי תכנות ופיתוח אישי. הקוד שמוצג משמש להדגמה ולצרכי לימוד. התוכן והקוד המוצגים באתר נבדקו בקפידה ונמצאו תקינים. אבל ייתכן ששימוש במערכות שונות, דוגמת דפדפן או מערכת הפעלה שונה ולאור השינויים הטכנולוגיים התכופים בעולם שבו אנו חיים יגרום לתוצאות שונות מהמצופה. בכל מקרה, אין בעל האתר נושא באחריות לכל שיבוש או שימוש לא אחראי בתכנים הלימודיים באתר.
למרות האמור לעיל, ומתוך רצון טוב, אם נתקלת בקשיים ביישום הקוד באתר מפאת מה שנראה לך כשגיאה או כחוסר עקביות נא להשאיר תגובה עם פירוט הבעיה באזור התגובות בתחתית המדריכים. זה יכול לעזור למשתמשים אחרים שנתקלו באותה בעיה ואם אני רואה שהבעיה עקרונית אני עשוי לערוך התאמה במדריך או להסיר אותו כדי להימנע מהטעיית הציבור.
שימו לב! הסקריפטים במדריכים מיועדים למטרות לימוד בלבד. כשאתם עובדים על הפרויקטים שלכם אתם צריכים להשתמש בספריות וסביבות פיתוח מוכחות, מהירות ובטוחות.
המשתמש באתר צריך להיות מודע לכך שאם וכאשר הוא מפתח קוד בשביל פרויקט הוא חייב לשים לב ולהשתמש בסביבת הפיתוח המתאימה ביותר, הבטוחה ביותר, היעילה ביותר וכמובן שהוא צריך לבדוק את הקוד בהיבטים של יעילות ואבטחה. מי אמר שלהיות מפתח זו עבודה קלה ?
השימוש שלך באתר מהווה ראייה להסכמתך עם הכללים והתקנות שנוסחו בהסכם תנאי השימוש.