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

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

חיזוי של סדרות נתונים מציב אתגר גדול בפני למידת מכונה בגלל שהמחשב צריך לזכור את התוצאות שחושבו בשלבים הקודמים של תהליך הלמידה דבר שאינו אפשרי ברשת נוירונית רגילה (feed forward network). כדי לטפל בבעייה ניתן להשתמש בסוג מיוחד של למידת מכונה, RNN - Recurrent Neural Networks.

ישנם יחידות שונות שמהם ניתן להרכיב RNN, ובמדריך זה נלמד ליישם RNN המבוסס על יחידות LSTM (Long Short Term Memory).

רשתות LSTM משתמשות ביחידות (במקום בנוירונים), כשכל יחידה מכילה 3 סוגי שערים:

  1. שער שכחה, Forget Gate, שמחליט באיזה חלק מהנתונים שהוא מקבל לא להשתמש.
  2. שער קלט, Input Gate, שמחליט איזה חלק מהמידע שמגיע מהקלט יוזן לתוך הרשת.
  3. שער פלט, Output Gate, שמחליט איזה מידע להעביר לשכבה הבאה בתור.

כל יחידה מכילה משקלות משלה ובכך היא מהווה מכונה קטנה בתוך המכונה הגדולה.

את יחידות ה- LSTM מסדרים בשכבות.

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

 

מטרת המדריך

במדריך זה ננסה לחזות את הטמפרטורות לשנים 2016-2018 על פי נתוני הטמפרטורות שנמדדו בין השנים 2004 עד 2015.

 

מאגר הנתונים

את המידע עבור הטמפרטורות בנמל אשדוד הורדתי מאתר מאגרי המידע הממשלתיים https://data.gov.il והוא כולל מדידות שנערכו מדי יום בין השנים 2004-2019. להורדת קובץ הנתונים לחץ כאן.

 

סביבת העבודה וייבוא הספריות שמשמשות במדריך

סביבת העבודה שבה פותח הקוד היא Google Colab, הספרייה לעיבוד המידע היא sklearn והספרייה ללמידת מכונה היא keras.

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.preprocessing import MinMaxScaler
from keras.models import Sequential, load_model
from keras.layers import LSTM, Dense, Dropout
import os

 

טעינת הנתונים

את הנתונים טענתי באמצעות קוד סטנדרטי המשמש לטעינת קבצים בסביבת Google Colab.

from google.colab import files
uploaded = files.upload()
import io
df = pd.read_csv(io.BytesIO(uploaded['ims_data.csv']),usecols=['date', 'time', 'temp'])

נציץ במסד הנתונים:

df.head()
date time temp
0 01-08-2004 02:00 25.2
1 01-08-2004 05:00 24.2
2 01-08-2004 08:00 27.3
3 01-08-2004 11:00 31.3
4 01-08-2004 14:00 29.7

 

הכנת הנתונים ללמידת מכונה

בסט הנתונים שהורדתי היה פורמט התאריך כמקובל בישראל (dd-mm-YYYY) ועל כן כתבתי את הפונקציה הבאה שממירה את הפורמט לזה המקובל בעולם המחשבים:

def to_international_date(d):
 date = d.split('-')
​
 day   = date[0]
 month = date[1]
 year  = date[2]
​
 return '%s-%s-%s' % (year, month, day)

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

# Convert to the date format
df['date'] = df['date'].apply(to_international_date)

# Convert date and time to datetime
df['datetime'] = pd.to_datetime(df['date'] + ' ' + df['time'])

# Drop columns
df = df.drop(['date', 'time'], axis=1)

# Clean the data
df = df[df['temp'] != '-']

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

# Clean the temp column
df = df[df['temp'] != '-']

# Set the type of temp to float
df['temp'] = df['temp'].astype(float)

נשרטט את עמודת הטמפרטורות כנגד התאריכים:

# Plot and describe the data
plt.plot(df['datetime'], df['temp'])

טמפרטורה לעומת תאריך בסט הנתונים

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

נגדיר את עמודת datetime בתור האינדקס:

# Set the index
df = df.set_index('datetime')

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

# Describe
df.describe()
temp
count 41142.000000
mean 21.582084
std 5.498377
min 3.900000
25% 17.300000
50% 21.900000
75% 26.500000
max 37.400000

נתאר את סט הנתונים גם באמצעות התכונות הבאות:

df.info()
df.shape

DatetimeIndex: 41142 entries, 2004-08-01 02:00:00 to 2018-11-30 23:00:00
Data columns (total 1 columns):
temp    41142 non-null float64
dtypes: float64(1)
memory usage: 642.8 KB


(41142, 1)

מערך של 41142 ערכים מסוג float.

נפריד את המידע לשני סטים. סט אימון וסט חיזוי כשהאימון מתבצע מהנתונים שנצברו בין 1.1.2004 ועד 31.12.15, והחיזוי על נתוני השנים 2016-2018.

## Separate between the train and test datasets
split_date = pd.datetime(2015,12,31)

dataset_train = df[:split_date]
dataset_test  = df[split_date:]

# Plot the datasets
ax = dataset_train.plot()
dataset_test.plot(ax=ax)
plt.legend(['train','test'])

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

מכיוון שלמידת מכונה רגישה לטווח הערכים נהוג לנרמל את הנתונים לטווח שבין 0 ל-1:

# Scale the datasets
scaler = MinMaxScaler(feature_range=(0,1))

dataset_train_scaled = scaler.fit_transform(dataset_train)
dataset_test_scaled  = scaler.transform(dataset_test)

print(dataset_train_scaled[:5]) # The first 5 data points
print(dataset_test_scaled[-5:]) # The last 5 data points
[[0.6358209 ]
 [0.60597015]
 [0.69850746]
 [0.81791045]
 [0.77014925]]
[[0.52835821]
 [0.53432836]
 [0.51940299]
 [0.51044776]
 [0.50746269]]

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

את הפונקציה create_dataset נזין בסדרות של ערכים והיא תפריד מתוכם את ה- features וה-labels כאשר סט של 30 מדידות רצופות ישמש לחיזוי המדידה הבאה. הסט של 30 המדידות יהיה feature והערך הבא בתור יהיה label שאותו אנחנו מנסים לחזות.

# Takes a series of 30 time points as feature and the next point as label
def create_dataset(df):
  x = [] # features from the previous 50 time points
  y = [] # labels from the current time points
  
  for i in range(30, df.shape[0]):
    x.append(df[i-30:i,0])
    y.append(df[i,0])
    
  x = np.array(x)
  y = np.array(y)
  
  return x,y

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

x_train, y_train = create_dataset(dataset_train_scaled)

כמו גם את הסטים שישמשו לחיזוי:

x_test, y_test = create_dataset(dataset_test_scaled)

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

print(x_train.shape)
print(x_test.shape)
(32705, 30)
(8377, 30)

הצורה היא דו-ממדית.

הצורה של הנתונים הדרושה לצורך הזנת תאי LSTM היא של tuple תלת ממדי. לפיכך, נמיר את הנתונים לצורה הדרושה:

# Reshape data for LSTM layer
# Make a 2 dimensional tuple into 3 dimensional
x_train = np.reshape(x_train, (x_train.shape[0], x_train.shape[1], 1))
x_test  = np.reshape(x_test, (x_test.shape[0], x_test.shape[1], 1))

print(x_train.shape)
print(x_test.shape)
(32705, 30, 1)
(8377, 30, 1)

 

פיתוח המודל המשמש ללמידת מכונה

מודל Keras המשמש ללמידת מכונה הוא מסוג sequential, והוא כולל 3 שכבות:

model = Sequential()

model.add(LSTM(units=48, return_sequences=True, input_shape=(x_train.shape[1], 1)))
model.add(Dropout(0.2))

model.add(LSTM(units=48, return_sequences=True, input_shape=(x_train.shape[1], 1)))
model.add(Dropout(0.2))

model.add(LSTM(units=48))
model.add(Dropout(0.2))

model.add(Dense(units=1))
  • 2 שכבות LSTM, שבכל אחת 48 תאים.
  • הנתונים שיוצאים משכבת ה-LSTM עוברים דרך שכבת dropout שמפחיתה באקראי 20% מהתוצאות כדי למנוע למידת יתר של הדוגמאות וכך להקל על המודל להכליל לדוגמאות שהמודל לא ראה בתהליך האימון.
  • השכבה האחרונה היא של רשת נוירונית רגילה Dense שמכילה יחידה אחת בגלל שהבעיה היא בעיית רגרסיה שאנחנו מצפים ממנה לחזות רק את הטמפרטורה.

נסכם את המודל שפתחנו:

model.summary()
Layer (type)         Output Shape   #Param   
==========================================
lstm_1 (LSTM)        (None, 30, 48)   9600      
__________________________________________
dropout_1 (Dropout)  (None, 30, 48)   0         
___________________________________________
lstm_2 (LSTM)        (None, 30, 48)   18624     
___________________________________________
dropout_2 (Dropout)  (None, 30, 48)   0         
___________________________________________
lstm_3 (LSTM)        (None, 48)       18624     
___________________________________________
dropout_3 (Dropout)  (None, 48)       0         
___________________________________________
dense_1 (Dense)      (None, 1)        49        
===========================================
Total params: 46,897
Trainable params: 46,897
Non-trainable params: 0

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

model.compile(loss='mean_squared_error', optimizer='adam')

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

from keras.callbacks import EarlyStopping

es = EarlyStopping(monitor='val_loss',
                   min_delta=0,
                   patience=2,
                   verbose=2,
                   mode='auto')

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

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

 

הערכת המודל

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

# First, predict from the train dataset
predicted_train = model.predict(x_train)

# Convert the scaled data back to the original values
predicted_train_rescaled = scaler.inverse_transform(predicted_train)

# Plot
# Pop the first 30 values from the timeseries
x_axis = dataset_train.index[30:]

# Visualize the predicted values against the actual data
#  but first we need to convert back to the actual data
actual_train_rescaled = scaler.inverse_transform(y_train.reshape(-1, 1))

plt.plot(x_axis, actual_train_rescaled, 'b')
plt.plot(x_axis, predicted_train_rescaled, 'r')
plt.legend(['actual','predicted'])
plt.title("Actual vs predicted in the train dataset")

הערכים החזויים לעומת הערכים בפועל בסט האימון

נראה בסדר, אבל מה שיעור הסטייה בין הערכים החזויים והערכים בפועל?

import math
from sklearn.metrics import mean_squared_error

# calculate root mean squared error
train_score = math.sqrt(mean_squared_error(actual_train_rescaled, predicted_train_rescaled))
print('Train Score: %.2f RMSE' % (train_score))
Train Score: 1.30 RMSE

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

נחזור על התרגיל עבור קבוצת המבחן:

# Predict on the test dataset
predictions = model.predict(x_test)

# Convert back to the original values
predictions = scaler.inverse_transform(predictions)

# Plot
# Pop the first 30 values from the timeseries that
x_axis = dataset_test.index[30:]

# Visualize the predicted values against the actual data
#  but first we need to convert back the actual data
actual_test_rescaled = scaler.inverse_transform(y_test.reshape(-1, 1))

# Visualize the predicted values against the actual data
plt.plot(x_axis, actual_test_rescaled, 'b')
plt.plot(x_axis, predictions, 'r')
plt.legend(['actual','predicted'])
plt.title("Actual vs predicted in the test dataset")

הערכים החזויים לעומת הערכים בפועל בסט המבחן

test_score = math.sqrt(mean_squared_error(actual_test_rescaled, predictions))
print('Test Score: %.2f RMSE' % (test_score))
Test Score: 1.26 RMSE

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

 

סיכום

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

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

 

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

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

 

 

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

 

= 3 + 2