חיזוי מחירי בתים באמצעות למידת מכונה ומודל מרובה משתנים

מחבר:
בתאריך:

אחרי שבמדריך הקודם פיתחנו מודל רגרסיה לינארית באמצעות Keras על משתנה אחד בלבד (חיזוי מחירי דירות בהינתן השטח) במדריך הנוכחי נמשיך באותו נושא ונלמד את המכונה לחזות את מחירי הדירות על סמך מספר משתנים (שטח דירה, מספר חדרי השינה ומספר חדרי שירותים). חיזוי על סמך מספר משתנים מכונה ניתוח רב משתנים (multivariate analysis).

המשתנים במדריך שישמשו לאנליזה מתחלקים לשניים, מספריים וקטגוריים. המטרה שלנו היא לפתח מודל שלתוכו נזין את הנתונים של הדירה. שטח, מספר חדרי השינה, סוג הדירה (מגורים, פרטית או רב-משפחתית) ונקבל הערכה של שווי הנכס.

לצורך פיתוח המודל נשתמש ב-Keras שהיא ספרייה של למידת מכונה שפותחה על ידי המדענים של גוגל.

לחץ כאן כדי להוריד קובץ ה-csv של מסד הנתונים

לחץ כאן כדי להוריד את קובץ קוד המלא שנפתח במדריך

* המדריך הנוכחי מבוסס על מדריך קודם מודל רגרסיה לינארית באמצעות Keras על משתנה אחד בלבד שבו הסברתי כיצד לבנות מודל של למידת מכונה. אם לא קראתם את המדריך הקודם ואין לכם מספיק ידע בתחום, רצוי שתתחילו מקריאת המדריך הקודם שמניח את היסודות לנוכחי.

 

ייבוא הספריות

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
  • ספריית Numpy של Python מאפשרת לעבוד עם מערכים רב-ממדיים, ומספקת פונקציות לביצוע פעולות של אלגברה לינארית הנדרשות לפתרון הבעיות המתמטיות בתהליך הלמידה.
  • Pandas משמשת לסידור ולסינון מידע בדומה לגיליון אקסל.
  • Matplotlib משמשת להצגת מידע באמצעות גרפים ותרשימים.

 

ייבוא מסד הנתונים

מסד הנתונים המשמש במדריך מסכם את מחירם של בתים שנמכרו בסקרמנטו. תוכלו להוריד אותו מכאן.

df = pd.read_csv('./sacramentorealestatetransactions.csv',usecols=['beds','baths','sq__ft','type','price'])

 

סקירת הנתונים וניקוי דוגמאות חריגות

קודם כל, נסקור את מסד הנתונים כדי לנסות לחוש אותו ולנקות ממנו דוגמאות חריגות.

df.shape
(985, 5)

התוצאה היא חמישה טורים ו-985 שורות.

התוצאה היא שני טורים ו-985 שורות.

df.info()
RangeIndex: 985 entries, 0 to 984
Data columns (total 5 columns):
beds      985 non-null int64
baths     985 non-null int64
sq__ft    985 non-null int64
type      985 non-null object
price     985 non-null int64
dtypes: int64(4), object(1)
memory usage: 38.6+ KB

כל הרשומות מלאות. לא חסר מידע.

df.describe()
beds baths sq__ft price
count 985.000000 985.000000 985.000000 985.000000
mean 2.911675 1.776650 1314.916751 234144.263959
std 1.307932 0.895371 853.048243 138365.839085
min 0.000000 0.000000 0.000000 1551.000000
25% 2.000000 1.000000 952.000000 145000.000000
50% 3.000000 2.000000 1304.000000 213750.000000
75% 4.000000 2.000000 1718.000000 300000.000000
max 8.000000 5.000000 5822.000000 884790.000000

לפי ערכי המינימום, אפשר לראות שחלק מהעמודות כוללות ערכי 0. לדוגמה, מספר חדרים אפסי או שטח אפסי. זה עלול לפגוע בתוצאות האנליזה. ננקה את הנתונים.

 

ניקוי המידע

ננקה את מסד הנתונים שלנו מכל הדוגמאות שבהם השטח נמוך מ-10 וגם מדוגמאות שבהם מספר חדרי השינה או השירותים אפסי.

filtered_data = df[(df.sq__ft > 10) & (df.beds > 0) & (df.baths > 0)]

מעניין מה מידת הקורלציה בין שטח הבית לבין יתר המשתנים.

filtered_data.corr()['sq__ft'].sort_values()
price     0.693708
beds      0.695710
baths     0.724631
sq__ft    1.000000
Name: sq__ft, dtype: float64

.שטח הבית נמצא בקורלציה חיובית וחזקה למדי עם המחיר וגם עם מספר החדרים

האם ניתן לאתר דוגמאות חריגות נוספות?

def plot_ft_vs_price():
  plt.plot(filtered_data['sq__ft'], filtered_data['price'], 'o')
  plt.ylabel('Price')
  plt.xlabel('Square foot')
  plt.title('Price vs square foot')
  
plot_ft_vs_price()

הקשר בין השטח ובין המחיר

אפשר לראות דוגמה חריגה כאשר השטח גדול מ-5000. נסיר גם את הדוגמה הזו.

filtered_data = filtered_data[(filtered_data.sq__ft < 5000)]

 

קידוד המשתנים הקטגוריים

שימוש במשתנה קטגורי (שמי) מציב בעיה מפני שמחשבים צריכים מספרים כדי לעבוד איתם. גישה אחת לפתרון הבעיה היא למפות כל אחת מהקטגוריות למספר. הבעיה עם הגישה הזו שמודלים של למידת מכונה מניחים שיש למספרים ערך כמותי. לדוגמה, אם אנחנו נקודד את השכונות למספרים. שכונה א תקבל את הערך 1, שכונה ב את הערך 2 ושכונה ג את הערך 3. המודל עלול להסיק ששכונה א + ב שוות לשכונה ג. כדי למנוע את הבעיה משתמשים בקידוד one hot encoding שהופכת את המשתנה השמי למערך של 0 ו-1 שבו כל המשתנים מקבלים 0 לבד מאחד שמקבל 1. כל קטגוריה מקבלת 1 במקום שונה במערך.

בוא נראה איך עושים את זה, ובסוף הדוגמה תוכלו לראות בעצמכם איך נראים משתנים קטגוריים אחרי קידוד בשיטת one hot encoding.

נעזר ב-numpy כדי למצות את שמות הקטגוריות של סוגי הדירות.

type_names = np.unique(np.array(filtered_data.type))
type_names
array(['Condo', 'Multi-Family', 'Residential'], dtype=object)

3 שמות ל-3 סוגים.

נשתמש ב-pandas כדי לקודד בשיטת one-hot encoding, המתאימה לקידוד נתונים קטגוריים.

t_dummies = pd.get_dummies(filtered_data.type, dummy_na=False)

נציץ במספר דוגמאות אקראיות כדי לראות את הקידוד בפעולה:

t_dummies.tail(3)
Condo Multi-Family Residential
979 0 0 1
297 0 0 1
344 1 0 0
  • דוגמאות 979 ו-297 שייכות לקטגוריה Residential
  • דוגמה 344 שייכת לקטגוריה Condo.

נסיר את העמודה type כי קודדנו אותה באמצעות one-hot encoding ואנחנו לא מעוניינים שעמודה שמית תפריע למודל שלנו שיודע לעבוד עם נתונים מספריים בלבד.

type = filtered_data.pop('type')

נאחד את כל העמודות למסד נתונים אחד שנקרא לו df_united.

df_united = pd.concat([filtered_data, t_dummies], axis=1)

נציץ בזנבו של מסד הנתונים המאוחד כדי לוודא שהאיחוד בין העמודות נעשה כהלכה. אם האינדקסים אינם מתואמים בין העמודות השונות נראה ערכים חסרים בזנב.

df_united.tail(3)
beds baths sq__ft price Condo Multi-Family Residential
982 3 2 1216 235000 0 0 1
983 4 2 1685 235301 0 0 1
984 3 2 1362 235738 0 0 1

אין נקודות מידע חסרות. נראה שתהליך האיחוד עבר בשלום.

 

נורמליזציה

עדיין יש לנו בעיה אחת משמעותית והיא שהנתונים שלנו אינם על אותה הסקלה כי השטח נמדד ביחידות של עשרות ואף מאות בעוד החדרים נמדדים ביחידות בודדות. כדי לפתור את הבעיה ננרמל את הנתונים כך שכל אחד מהפיצ'רים יהיה בדיוק באותה הסקלה עם ממוצע 0 וסטיית תקן 1.

אנחנו מנרמלים רק את העמודות שיהוו את הקלט (עמודות ה-x) אבל לא את הפלט שבמקרה שלנו הוא עמודת המחיר. נסיר את עמודת המחיר ממסד הנתונים.


price = filtered_data.pop('price')

את כל יתר העמודות, מספריות וקטגוריות, ננרמל באמצעות הפונקציה הבאה:

def normalize(x):
  return (x.values-x.mean()) / x.std()

כל אחד מהפיצ'רים, כל אחת מהעמודות תעבור את אותו תהליך של נורמליזציה.

for col in df_united:
  df_united[col] = normalize(df_united[col])

נבחן את מה שעשינו.

df_united.describe()
beds baths sq__ft Condo Multi-Family Residential
980 0.885774 1.563404 1.072031 -0.233255 -0.127397 0.269203
981 -0.287083 0.062758 -0.168270 -0.233255 -0.127397 0.269203
982 -0.287083 0.062758 -0.571407 -0.233255 -0.127397 0.269203
983 0.885774 0.062758 0.153003 -0.233255 -0.127397 0.269203
984 -0.287083 0.062758 -0.345898 -0.233255 -0.127397 0.269203

אכן. בכל העמודות סטיית התקן היא 1, והממוצע שואף לאפס.

 

הפרדת הנתונים לסט אימון ומבחן

הנתונים הבלתי תלויים הם מספר חדרי השינה, מספר חדרי אמבטיה, השטח וסוג הדירה. נגדיר אותם כ-x.

x = df_united.values

מכיוון שאנו מנסים לצפות את המחיר אז הוא יהווה את ה-y - המשתנה התלוי שלנו.

y = price.values

נפריד את סט הנתונים למסד נתוני אימון ומבחן.

# Split the data to test and train groups
from sklearn.model_selection import train_test_split

x_train, x_test, y_train, y_test = train_test_split(x, y, 
                                   test_size=0.20, random_state=42)

 

אימון המודל

את המודל נפתח באמצעות Keras.

נייבא את החבילות:

# Imports
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Activation
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.callbacks import EarlyStopping

נבנה את המודל:

model = Sequential()

# The model receives 6 inputs and outputs a single value, price
model.add(Dense(32, activation='relu', input_shape=(6,)))
model.add(Dense(32, activation='relu'))
model.add(Dense(1))
  • אנחנו מזינים את המודל ב-6 קלטים (שטח, מספר חדרים, מספר שירותים ושלוש סוגי דירות) ומצפים לקבל פלט יחיד (מחיר), וזה מה שמכתיב את הארכיטקטורה של המודל.
  • השכבות הם צפופות dense, וכוללות 32 נוירונים בשתי השכבות הראשונות
  • בין שכבת הקלט ושכבת הפלט ישנה שכבה חבויה אחת.
  • פונקצית האקטיבציה היא relu שהיא הבחירה ברירת המחדל בעולם למידת המכונה.

נתאר את המודל:

model.summary()
Model: "sequential"
___________________________________
Layer (type)    Output Shape  Param   
===================================
dense (Dense)   (None, 32)    224
___________________________________
dense_1 (Dense) (None, 32)    1056
___________________________________
dense_2 (Dense) (None, 1)     33
===================================
Total params: 1,313
Trainable params: 1,313
Non-trainable params: 0

נקמפל את המודל:

# Compile the model
# Adam optimizer for the learning rate
# In preliminary trials I found the learning rate of 10 to be the best for 
# the task at hand
model.compile(Adam(lr=10), 'mean_squared_error')

האופטימיזציה נעשית באמצעות ADAM שהוא ברירת המחדל, וקצב הלמידה הוא 10 כי מצאתי בניסוי מקדים שזהו קצב הלמידה שנותן את התוצאות הטובות ביותר.

הפונקציה הבאה תפסיק את תהליך הלמידה כאשר המודל מגיע לרווייה.

# The EarlyStopping callback stops the learning process if the loss doesn't improve
es = EarlyStopping(monitor='val_loss',
                   min_delta=0,
                   patience=10,
                   verbose=1,
                   mode='auto', 
                   restore_best_weights=True)

את הסבלנות קבעתי על 10 epochs שבמהלכם המודל ימשיך לבדוק האם הגיע לאופטימום . בכל מקרה, האלגוריתם יבחר את ה-epoch האופטימלי וממנו הוא ייקח את המשקלים.

נריץ את המודל:

model.fit(x_train,
          y_train,
          batch_size=32,
          epochs=10000,
          validation_data=(x_test,y_test),
          callbacks=[es])

תמונת האילוסטרציה הבאה מדגימה כיצד נראה תהליך האימון. במציאות, המודל שלי רץ במשך 1056 epochs.

למידת מכונה עם Keras רצה על המחשב שלי

 

הערכת המודל

יש לנו מודל. קוראים לו model, אבל מה הוא אומר לנו על הנתונים? עד כמה הוא מצליח לתאר בצורה טובה את סט הנתונים? האם נצליח להפיק ממנו תחזיות מספקות לגבי סט נתוני המבחן?

קודם כל, כדי לקבל תחושה נחשב את המחיר הצפוי עבור דירה אחת מסוימת בסט נתוני המבחן. זו רשומה שמספרה הסידורי 42.

# Pick a single sample and predict
y_pred0 = model.predict(np.array([x_test[42]]))

נשווה בין התוצאה החזויה לתוצאה בפועל.

print('Predicted value: {0:.0f}'.format(y_pred0.reshape(1)[0]))
print('Actual value: {0:.0f}'.format(y[42]))
Predicted value: 224060
Actual value: 156896

יכול להיות שהתוצאות בטווח השגיאה של המודל, אבל מהו טווח השגיאה של המודל? כדי למצוא את טווח השגיאה נעשה את התחזית על כל הדוגמאות.

y_pred = model.predict(x_test)

נחשב את טווח השגיאה:

from sklearn.metrics import mean_absolute_error, mean_squared_error

print('MAE: %.2f' % mean_absolute_error(y_test, y_pred))
print('RMSE: %.2f' % np.sqrt(mean_squared_error(y_test, y_pred)))
MAE: 76116
RMSE: 112223

ככל שהערך של MAE נמוך יותר, כך המודל מדויק יותר.

מדד RMSE הוא היותר שימושי לנו מפני שהוא מבטא את טווח השגיאה באותם היחידות של הטור שאותו אנו מנסים לחזות. לפיכך, טווח השגיאה של המודל הוא +/-112,223 דולר.

 

סיכום

במדריך הזה למדנו לפתח מודל של רגרסיה שתלוי במספר משתנים בלתי תלויים. למדנו לקודד משתנים קטגוריים ולנרמל את הנתונים הבלתי תלויים למנוע את השפעת שיעור הגודל של עמודות הנתונים השונות. כמו כן, פיתחנו מודל עמוק שכולל שכבה חבויה אחת.

לכל המדריכים בנושא של למידת מכונה

 

אהבתם? לא אהבתם? דרגו!

0 הצבעות, ממוצע 0 מתוך 5 כוכבים

 

 

הוסף תגובה חדשה

 

= 5 + 8