Модель ML для прогнозирования будущего энергопотребления с использованием данных временных рядов и стратегии множественного вывода.

Краткое содержание

Я создал модель машинного обучения, которая может делать прогнозы на будущее на основе исторических данных о том, сколько энергии будет потребляться в данном месте в мегаваттах (МВт). Такое раннее прогнозирование будущих потребностей может помочь компаниям соответствующим образом планировать свои действия.

Мне удалось снизить MAPE (средняя абсолютная погрешность в процентах) до 9%, что означает, что моя модель может прогнозировать потребление энергии в любой час любого дня в будущем с точностью до 9% от фактического значения потребления, что может быть очень полезным.

Вы можете просмотреть полный блокнот и код здесь.



В этом посте я обсуждаю:

  • Как я обрабатывал данные временных рядов
  • какие выводы я получил?
  • какие модели пробовал?
  • какая модель лучше всего подходит для этой задачи?
  • как я правильно сохранил выбранную модель для дальнейшего использования.

Набор данных

Исторические данные о почасовом потреблении энергии в мегаваттах (МВт), общедоступные на Kaggle.



Информация о наборе данных:

PJM — это региональная передающая организация в Соединенных Штатах, в которой сети управляют системой электропередачи, обслуживающей все или части нескольких штатов.

Данные о почасовом потреблении энергии взяты с веб-сайта PJM и указаны в мегаваттах (МВт).

У нас есть информация о дате и времени и соответствующие значения энергопотребления.

Импорт библиотек

Импортируйте все библиотеки, необходимые для моделирования и прогнозирования.

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

import pickle
from sklearn.metrics import mean_squared_error, mean_absolute_error
from sklearn.model_selection import TimeSeriesSplit
import xgboost as xgb
#fbprophet 
from prophet import Prophet
from sklearn.linear_model import LinearRegression

Определите показатель оценки.

Используя эту общую метрику, мы можем сравнить несколько моделей, а также выбрать лучший набор гиперпараметров с надлежащей перекрестной проверкой временных рядов.

Я использую MAPE в качестве оценочной метрики для этой задачи прогнозирования.

Что такое МАПЕ?

Средняя абсолютная процентная ошибка (MAPE) является мерой точности предсказания метода прогнозирования в статистике.

Он представляет собой среднее значение абсолютных процентных ошибок каждой записи в наборе данных, чтобы рассчитать, насколько точны прогнозируемые количества по сравнению с фактическими количествами.

Таким образом, если у нас есть 10% MAPE, это означает, что мы можем делать прогнозы в пределах 10% диапазона фактических значений.

def mean_absolute_percentage_error(y_true, y_pred):
    """
    Calculate MAPE for given actual data (y_true) and predicted values (y_pred)
    """
    y_true = np.array(y_true)
    y_pred = np.array(y_pred)
    
    return np.mean(np.abs((y_true - y_pred) / y_true)) * 100

Загрузка/чтение данных

Затем мы получаем данные о потреблении энергии и читаем их, используя pandas в качестве фрейма данных, и анализируем данные даты временного ряда как индекс.

df = pd.read_csv("./PJME_hourly.csv")
df = df.set_index('Datetime')
df.index = pd.to_datetime(df.index)

Визуализация данных

Обзор распределения данных за 10-летний период.

Взять образец данных за 1 неделю, чтобы поближе познакомиться.

Мы можем наблюдать эффект дневного и ночного цикла в потреблении энергии с высокими холмами в течение дня и долинами в ночное время. Здесь также может быть эффект будних и выходных дней.

Создание функции

Прямо сейчас у нас есть только 2 столбца — дата-время в качестве индекса и потребление энергии в мегаваттах (МВт). Существует гораздо больше информации, которую можно извлечь из простого столбца даты и времени и столбца потребления, что поможет более внимательно изучить данные и выявить закономерности.

Итак, я создал 3 типа фич:

  • Функции Фурье для моделирования тренда и сезонности данных.
def create_fourier_features(df):
    fourier = CalendarFourier(freq="A", order=8)
    
    df = df.copy()
    
    dp = DeterministicProcess(
        index=df.index,
        constant=True,               # dummy feature for bias (y-intercept)
        order=1,                     # trend (order 1 means linear)
        additional_terms=[fourier],  # annual seasonality (fourier)
        drop=True,                   # drop terms to avoid collinearity
    )

    X = dp.in_sample()  # create features for dates in df.index
    X['PJME_MW'] = df['PJME_MW']
    
    return X
  • Функции временных рядов для моделирования сезонных индикаторов, чтобы помочь с сезонностью.
#time series features 
def create_time_series_features(df):
    """
    Creates time series based feature based on datetime index
    """
    df = df.copy()
    df['date'] = df.index
    df['hour'] = df.index.hour
    df['month'] = df.index.month
    df['year'] = df.index.year
    df['dayofweek'] = df.index.dayofweek
    df['dayofyear'] = df.index.dayofyear
    df['quarter'] = df.index.quarter
    df['dayofmonth'] = df['date'].dt.day
    df['weekofyear'] = df['date'].dt.isocalendar().week
    
    df['date_offset'] = (df.date.dt.month*100 + df.date.dt.day - 320)%1300
    df['season'] = pd.cut(df['date_offset'], [-1, 300, 602, 900, 1301],
                         labels=['Spring', 'Summer', 'Fall', 'Winter'])
    
    return df
  • Запаздывание функций для моделирования циклов.
#Lag features
def create_lag_features(df):
    df = df.copy()
    target_map = df['PJME_MW'].to_dict()
    df['lag1year'] = (df.index - pd.Timedelta('364 days')).map(target_map)
    df['lag2year'] = (df.index - pd.Timedelta('728 days')).map(target_map)
    df['lag3year'] = (df.index - pd.Timedelta('1092 days')).map(target_map)
    return df
df = create_fourier_features(df)
df = create_time_series_features(df)
df = create_lag_features(df)

EDA (исследовательский анализ данных)

Анализ выбросов

Мы хотим определить экстремальные выбросы на нижнем уровне, которые мы также можем видеть в данных между 2012 и 2014 годами, это может быть аномалия из-за отключения электроэнергии или по какой-то другой причине, и есть только один из них, поэтому это нельзя предсказать, но это может повредить. производительность модели.

#replace outlier with the general base line value
df.loc[df.PJME_MW < 19000, 'PJME_MW'] = 19000

Особенности против цели

  • Потребление в МВт в час

Потребление энергии значительно варьируется в зависимости от часа дня: низкое потребление ночью и ранним утром и высокое потребление в середине дня и ближе к вечеру.

  • Потребление в МВт по месяцам

Похоже, что в модели потребления энергии есть некоторая сезонность, с пиками в летний и зимний сезоны по сравнению с осенним и весенним сезонами. Это может быть связано с тем, что домохозяйства используют отопление или кондиционеры.

  • Потребление по дням недели в разное время года

Энергопотребление меняется в зависимости от дня недели, и времена года, кажется, играют важную роль в вариациях с пиками летом и зимой и меньше весной и осенью.

Аддитивное моделирование — определение различных компонентов данных временных рядов.

Тренд

мы можем получить общее представление о тенденции в данных, используя график скользящего среднего для удаления шума низкого уровня.

Данные, кажется, имеют сезонность / циклы, о которых нам придется позаботиться.

Смоделируйте общую тенденцию с помощью линейной регрессии.
Линейная регрессия с временным манекеном дает модель:

target = weight * time + bias

X = df.loc[:, ['trend']] #feature
y = df.loc[:, 'PJME_MW'] #target

#create and train the model to find the trend in data wrt time.
lin_reg = LinearRegression()
lin_reg.fit(X, y)
#predictions over dataset
y_pred = pd.Series(lin_reg.predict(X), index=X.index)

Сезонность

Сезонность свыше:

  • в день в часах в течение недели
  • в день по дням недели
  • в день в течение месяца
  • в квартал в течение года в такие сезоны, как лето, зима, весна и сезон дождей
  • в месяц в течение года

Наблюдения

  • Кажется, существует четкая связь между временем суток и потреблением энергии.
  • Это также зависит от дня недели.
  • Нет четкой корреляции между днем ​​месяца и потреблением энергии.
  • Расход сильно зависит от времени года.
  • Месяцы влияют на него так же, как времена года.

Подготовка данных для моделирования

Чтобы подготовить общий набор данных для моделирования, я выполнил следующие шаги:

  • Созданы / определены переменные X (функции) и y (цель).
  • Выполненный поезд — тестовое разделение со всеми данными за 2017 год и после него в качестве тестового набора.

  • Определено, как создавать наборы обучения и оценки для перекрестной проверки для данных временных рядов из данных обучения.

  • Созданы списки для хранения оценок модели для сравнения и выбора модели.

Моделирование

Простая модель линейной регрессии

Я создал простую модель линейной регрессии и применил ее к тренировочному набору. При обучении на нескольких функциях, таких как тренд, сезонные индикаторы и запаздывания, для прогнозирования энергопотребления она может делать прогнозы только с 9,8% означает абсолютную процентную ошибку (MAPE), что означает, что в среднем мы можем прогнозировать потребление в пределах 10% от фактического значения.

X_train = train[features_linear_reg]
y_train = train[target]

lin_reg = LinearRegression()
lin_reg.fit(X_train, y_train)
y_train_pred = pd.Series(lin_reg.predict(X_train), index=X_train.index)

Модель XGBoost (экстремальное усиление градиента)

Я использовал перекрестную проверку, чтобы определить и выбрать количество оценщиков дерева для использования в модели.

При использовании регрессорной модели XGBoost для прогнозирования потребления с использованием нескольких функций, таких как тенденции, функции сезонности и функции задержки, наша модель XGBoost научилась делать прогноз с MAPE 9,1%, что означает, что она работает лучше, чем простая модель линейной регрессии при тех же параметрах.

#train on all training data
X_train_all = train[features_xgb_reg]
y_train_all = train[target]

xgb_reg = xgb.XGBRegressor(base_score=0.5,
                      booster='gbtree',
                      n_estimators=350,
                      objective='reg:squarederror',
                      max_depth=3,
                      learning_rate=0.01)
xgb_reg.fit(X_train_all, y_train_all,
       eval_set=[(X_train_all, y_train_all)],
       verbose=100)
#making Predictions
X_test = test[features_xgb_reg]
y_test = test[target]
test['predictions'] = xgb_reg.predict(X_test)

Модель пророка — fbprophet

Я также использовал модель пророка Facebook, чтобы попытаться составить прогноз, но она не смогла собрать всю доступную информацию и закономерности и смогла получить оценку только 14,51%. над MAPE на тестовом наборе.

#format the data to be used in prophet model
prophet_train_df = train.reset_index().rename(
    columns={'Datetime' : 'ds', 'PJME_MW' : 'y'})

prophet_model = Prophet()
prophet_model.fit(prophet_train_df)
#format the data to make predictions with prophet model
prophet_test_df = test.reset_index().rename(
    columns={'Datetime' : 'ds', 'PJME_MW' : 'y'})
prophet_test_forecast = prophet_model.predict(prophet_test_df)

Модель Пророка является аддитивной моделью, поэтому она также предоставила мне некоторую изученную информацию о компонентах.

Я добавил информацию Holiday в модель пророка, которая может быть полезна для прогнозирования потребления энергии.

но это не улучшило производительность модели, а лишь немного ухудшило ее с 14,57% MAPE.

Объединение моделей линейной регрессии и XGBoost

Я создал модель линейной регрессии, чтобы сначала изучить основные свойства тренда и сезонности данных, а затем использовать XGBoost для прогнозирования остаточной части данных.

Это похоже на бустинг, когда одна модель подгоняется к остаточной ошибке другой модели.

На тестовом наборе он смог получить оценку MAPE 10%.

#Prepare Datasets
# training data
X_train = train[features_lin_reg]
y_train = train[target]

#define and train linear regression model
lin_reg = LinearRegression()
lin_reg.fit(X_train, y_train)
#Make predictions on train set and prepare data for XGBoost Model
y_train_residuals = y_train - pd.Series(lin_reg.predict(X_train), index=X_train.index)
#train XGBoost model on all training data with decided number of estimators 
X_train = train[features_xgb_reg]
y_train = y_train_residuals
xgb_reg = xgb.XGBRegressor(base_score=0.5,
                      booster='gbtree',
                      n_estimators=200,
                      objective='reg:squarederror',
                      max_depth=3,
                      learning_rate=0.01)
xgb_reg.fit(X_train, y_train,
       eval_set=[(X_train, y_train)],
       verbose=100)
#Making Predictions on test set
y_pred_lin_reg = lin_reg.predict(test[features_lin_reg])
y_pred_xgb_reg = xgb_reg.predict(test[features_xgb_reg])

y_pred_combined = y_pred_lin_reg + y_pred_xgb_reg
y_pred_combined = pd.Series(y_pred_combined, index = test[features_xgb_reg].index)

Выбор модели

                                              Training Scores    Testing Scores
Model Names  
Linear Regression Model                       8.371212           9.873013
XGBoost Model                                 7.482306           9.092429
Prophet Model                                 8.230357           14.509800
Prophet Model with Holidays                   8.187294           14.570953
Linear Regression and XGBoost Combined Model  7.723010           9.779092

Наблюдения:

Из четырех моделей простые модели XGBoost и линейной регрессии, по-видимому, хорошо подходят для нашей задачи прогнозирования энергопотребления.

В целом, нашей лучшей моделью является модель XGBoost, поэтому я продолжу обучение этой модели на полном наборе данных, а затем сделаю будущие прогнозы о потреблении энергии в мегаваттах (МВт).

Обучить модель с полным набором данных

Чтобы наилучшим образом использовать доступный набор данных для прогнозирования будущего, мы должны обучить выбранную модель полным набором данных.

#train on all training data
X_all = df[features_xgb_reg]
y_all = df[target]

xgb_reg = xgb.XGBRegressor(base_score=0.5,
                      booster='gbtree',
                      n_estimators=350,
                      objective='reg:squarederror',
                      max_depth=3,
                      learning_rate=0.01)
xgb_reg.fit(X_all, y_all,
       eval_set=[(X_all, y_all)],
       verbose=100)

Важность изученной функции

Мы можем проверить, каковы основные функции, изученные моделью XGBoost для прогнозирования.

Наблюдения:

Запаздывающие признаки, по-видимому, играют важную роль в прогнозировании, а затем в нашей модели учитываются сезонные индикаторы и особенности.

Прогнозы на будущее

Теперь, когда полная модель готова, я приступил к прогнозированию энергопотребления — будущему прогнозу, который может помочь нам принимать решения на основе данных.

#Create future dataframe
future = pd.date_range('2018-08-03', '2019-08-01', freq='1h')
future_df = pd.DataFrame(index=future)
future_df['isFuture']=True
df['isFuture']=False
future_df = create_time_series_features(future_df)
future_df = pd.get_dummies(future_df)
future_df.weekofyear = future_df.weekofyear.astype(int)
df_final = pd.concat([df, future_df])
df_final = create_lag_features(df_final)

#Making predictions
future_df_with_features['predictions'] = xgb_reg.predict(future_df_with_features[features_xgb_reg])

Сохранить/экспортировать модель

Наконец, я сохранил полную модель XGBoost с помощью функции save_model(), потому что вспомогательные атрибуты объекта Python Booster
(такие как feature_names) не будут сохранены при использовании двоичного формата. Чтобы сохранить эти атрибуты, я использовал JSON/UBJ.

xgb_reg.save_model('final_model.json')

Эту сохраненную модель можно использовать в будущем для прогнозирования потребления энергии в мегаваттах (МВт) на следующий год, принятия решений на основе данных и планирования наперед.

Полученные знания и будущая работа

С помощью этого проекта я многое узнал о данных временных рядов и сделал прогнозы, а также о том, как прошлые значения могут помочь определить, что мы можем ожидать в будущем.

Что касается этого проекта, я хотел бы в ближайшем будущем создать интерактивную панель инструментов для более легкого доступа.

Ссылки



Большое спасибо за ваше время и не забудьте оценить пост, если вам понравилась моя работа и усилия. 😊✨