תחי ישראל - אין לנו ארץ אחרת

תחי ישראל -אין לנו ארץ אחרת

הפיכת מודל מכונה לחיזוי מחירי דירות לאפליקצית אינטרנט עם FastApi

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

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

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

הקמת אפליקצית אינטרנט כממשק למודל למידת מכונה

FastAPI הוא פריימוורק של פייתון להקמת אפליקציות ברשת האינטרנט ונחשב בעיני רבים לאידיאלי כשרוצים להקים REST API.

במדריך קודם, הדגמתי כיצד ניתן לחשוף מודל למידת מכונה לשימוש הציבור באמצעות JavaScript. לפתרון הזה היו שני בעיות עיקריות: המודל היה חשוף לציבור כי הוא רץ על הדפדפן בצד המשתמש וגם קשה להעביר אותו דרך אותם שלבים של עיבוד מקדים preprocessing כמו שעושה פייתון. לכן, במדריך הנוכחי, נפרוס את המודל על שרת פייתון באמצעות FastApi.

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

להורדת הקוד אותו נפתח במדריך

 

בניית אפליקציה מבוססת FastApi

לצורך הפשטות, נרכז את כל הקוד בתוך קובץ אחד, main.py. מתוכו נריץ את האפליקציה.

נייבא את הספריות הדרושות לנו ליישום מודל למידת המכונה ועיבוד הנתונים:

import tensorflow as tf
from tensorflow.keras.models import load_model
import numpy as np
import pandas as pd
  • numpy ו-pandas ישמשו לעיבוד הנתונים. 
  • TensorFlow היא הספרייה באמצעותה כתבתי את מודל המכונה.

נייבא את הספריות בשביל האפליקציה האינטרנטית:

from enum import Enum
import uvicorn
from fastapi import FastAPI
  • uvicorn הוא השרת עליו נקים את האפליקציה באמצעות FastApi.
  • ב-enum נשתמש כדי להוסיף שדה המציע מספר אפשרויות של סוגי בתים.

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

model = load_model('predict_house_prices.h5')

נוסיף שלוש פונקציות עזר בשביל עיבוד הנתונים.

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

def normalize(x, mean, std):
 return (x-mean)/std
  • הפונקציה מקבלת ערך מספרי, ואת הממוצע וסטיית התקן ומחזירה את הערך המנורמל.

כדי להסיק את הערכים הסטטיסטיים של כל אחד מהמשתנים:

def get_col_stats(x):
 return {"mean": x.mean(), "std": x.std()}
  • הפונקציה מקבלת את כל הערכים בטור, לדוגמה, כל המחירים, ומחזירה את הממוצע וסטיית התקן.

כדי לקבל את הערכים הסטטיסטיים של הטורים המספריים:

def get_stats():
 cols = ['beds','baths','sq__ft', 'price']
 df = pd.read_csv('./sacramentorealestatetransactions.csv',usecols=cols)
 filtered_data = df[(df.sq__ft > 10) & (df.beds > 0) & (df.baths > 0) & (df.sq__ft < 5000)]
 
 stats = {}
 for col in cols:
   stats[col] = get_col_stats(filtered_data[col])
 
 return stats
  1. המשתנה cols כולל את שמם של הטורים המספריים.
  2. הפונקציה read_csv קוראת את מסד הנתונים לתוך DataFrame של Pandas.
  3. הפונקציה מסננת ערכים קיצוניים.
  4. הפונקציה מריצה לולאה בתוכה היא אוספת לתוך משתנה stats את הערכים הסטטיסטיים של כל עמודה.
  5. הפונקציה מחזירה את המשתנה stats.

נאתחל את האפליקציה האינטרנטית:

app = FastAPI()

יש כמה סוגי דירות אותם נגדיר בתוך הקלאס Resedentials:

class Resedentials(str, Enum):
   Condo = "Condo"
   Multi_Family = "Multi-Family"
   Residential = "Residential"

הפונקציה predict_house_price מקבלת את הערכים שמזין המשתמש ומחזירה את תחזית המחיר:

@app.get('/predict-house-price/')
async def predict_house_price(beds: int, baths: int, sq__ft: int, type: Resedentials):
 pass
  • כל סוגי המשתנים שהפונקציה מצפה לקבל הם מספרים מלבד type השייך לסוג Resedentials.

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

stats = get_stats()
 
normalized_record = {}
for col in ['beds', 'baths', 'sq__ft']:
  normalized_record[col] = normalize(rec[col], stats[col]['mean'], stats[col]['std'])
  • את הנורמליזציה עושה הפונקציה normalize המקבלת כל אחד מהערכים שמזין המשתמש כמו גם הממוצע וסטיית התקן. הערכים המנורמלים שמחזירה הפונקציה נשמרים לתוך המילון normalized_record ביחד עם שמות המשתנים.

המשתנה הקטגורי type מקודד בשיטת one-hot encoding. נתחשב בכך כשאנו מוסיפים אותו למילון normalized_record:

normalized_record['Multi-Family'] = 0
normalized_record['Residential'] = 0
 
# if type == 'Condo':
   # no need to change
 if type == 'Multi-Family':
   normalized_record['Multi-Family'] = 1
 elif type == 'Residential':
   normalized_record['Residential'] = 1

נהפוך את המילון לרשימה דו ממדית אותה אנחנו יכולים להזין ל-TensorFlow בשביל לקבל תחזית:

# flatten dictionary to list
col_order = ['beds',  'baths',  'sq__ft', 'Multi-Family', 'Residential']
 
norm_rec = []
for col in col_order:
    norm_rec.append(normalized_record[col])
 
norm_rec = np.array(norm_rec)
norm_rec = norm_rec.reshape(1, -1)

תחזית המודל היא לשיעור סטיות התקן של המחיר מהממוצע:

prediction = model.predict(norm_rec)

נמצה את הערך המספרי:

predicted_normalized_price = prediction.reshape(1)[0]

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

predicted_price = stats['price']['mean'] + predicted_normalized_price * stats['price']['std']

נחזיר תחזית בדולרים:

return 'Predicted price: {0:.0f}$'.format(predicted_price)

ועכשיו הכל ביחד:

@app.get('/predict-house-price/')
async def predict_house_price(beds: int, baths: int, sq__ft: int, type: Resedentials):
 rec = {'beds':beds, 'baths':baths, 'sq__ft':sq__ft}
 
 stats = get_stats()
 
 normalized_record = {}
 for col in ['beds', 'baths', 'sq__ft']:
   normalized_record[col] = normalize(rec[col], stats[col]['mean'], stats[col]['std'])
 
 normalized_record['Multi-Family'] = 0
 normalized_record['Residential'] = 0
 
 # if type == 'Condo':
   # no need to change
  if type == 'Multi-Family':
   normalized_record['Multi-Family'] = 1
 elif type == 'Residential':
   normalized_record['Residential'] = 1
 
 # flatten dictionary to list
 col_order = ['beds',  'baths',  'sq__ft', 'Multi-Family', 'Residential']
 
 norm_rec = []
 for col in col_order:
   norm_rec.append(normalized_record[col])
 
 norm_rec = np.array(norm_rec)
 norm_rec = norm_rec.reshape(1, -1)
 
 prediction = model.predict(norm_rec)
 predicted_normalized_price = prediction.reshape(1)[0]
 predicted_price = stats['price']['mean'] + predicted_normalized_price * stats['price']['std']
  return 'Predicted price: {0:.0f}$'.format(predicted_price)

השורה הבאה תאפשר לנו להריץ את האפליקציה בקלות משורת הפקודות:

if __name__ == '__main__':
 uvicorn.run("main:app", host="127.0.0.1", port=8000, reload=True, access_log=False)

נריץ את האפליקציה על ידי הקלדת הפקודה הבאה בשורת הפקודות:

$ python3 main.py

ננסה האפליקציה מהדפדפן שם היא רצה בכתובת //127.0.0.1:8000/docs:

להורדת הקוד אותו פיתחנו במדריך

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

 

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

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

 

 

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

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

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

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

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

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

 

 

ענה על השאלה הפשוטה הבאה כתנאי להוספת תגובה:

איך אומרים בעברית אינטרנט?