L'apprentissage automatique dans le secteur de l'énergie, ou pas seulement tout le monde peut regarder demain

La prévision précise des événements futurs est une tâche prometteuse et intéressante dans de nombreux domaines: des prévisions météorologiques aux fintech (cotations boursières, taux de change). L'apprentissage automatique peut aujourd'hui réduire considérablement le temps et le travail impliqués dans la prise de décisions de gestion.  Pendant environ six mois,

notre équipe Data Science de NORBIT a expérimenté l'utilisation de divers modèles d'apprentissage automatique pour résoudre des problèmes de classification et de régression, et pour optimiser les processus métier b2b. Mais lorsque la tâche de prédire la série chronologique est apparue, il s'est avéré que le matériel disponible sur ce sujet sur le réseau n'était pas suffisant pour développer une solution rapide.


L'essence de la tâche était de prédire avec une précision maximale (erreur absolue moyenne en pourcentage, ou MAPE <3%) la consommation horaire d'une ville entière pour les trois prochains jours (72 heures) pour les besoins d'une grande entreprise de production d'électricité.

L'erreur maximale tolérée de 3% est un indicateur très élevé de la précision des prévisions. Avant l'utilisation du machine learning, l'erreur était de l'ordre de 3 à 23%, ce qui a directement conduit à des pertes financières, car avec une production d'énergie électrique insuffisante, des capacités supplémentaires devaient être achetées sur le marché de gros de l'électricité (qui est beaucoup plus cher que l'autoproduction), avec une surproduction - vendue sur le marché moins cher que ce qu'ils pouvaient vendre à la ville. En outre, l'effet négatif de la déviation de la charge prévue par rapport à la charge réelle est un changement de la fréquence du courant alternatif dans le réseau.

De nombreux facteurs nuisent à l'obtention d'une précision élevée des prévisions, par exemple, avec une augmentation soudaine de la température, les gens recherchent immédiatement les consoles des climatiseurs, tout en baissant, ils obtiennent des radiateurs des mezzanines; pendant les vacances ou les grands matchs de football comprennent des téléviseurs, etc. Si certains événements sont cycliques et potentiellement prévisibles, d'autres sont totalement absents. Supposons que soudainement, en raison d'un accident, l'atelier sidérurgique cesse de fonctionner et toutes les prévisions se transforment en citrouilles (dans notre ville cible, il n'y avait pas de grandes entreprises de production). Il y avait un exemple assez intéressant de problème pour faire des prévisions dans le secteur de l'énergie, lorsque l'un des pics de variation de la quantité d'électricité produite était causé par un navire échoué sur un lac, et le capitaine a convenu avec les propriétaires de centrales hydroélectriques de réduire les rejets d'eau.Naturellement, le modèle d'apprentissage automatique n'a pas pu prédire cet événement sur la base de données historiques.

La bonne nouvelle pour nous est que le client a fourni de tels «jours spéciaux» dans les termes de référence qui ont permis une erreur de prévision moyenne de 8%.

Tout ce que le client a fourni, ce sont les données historiques de la consommation horaire d'électricité pendant 3 ans et le nom de la ville.


La première tâche pour préparer la construction d'un modèle d'apprentissage automatique est la visualisation et la recherche de facteurs supplémentaires pouvant influencer les prévisions. Le degré d'une telle influence est commodément évalué visuellement à l'aide d'une carte thermique de la corrélation des attributs. Le carré sombre dans ce cas signifie la relation inverse inconditionnelle des quantités (la plus grande valeur, la plus petite l'autre et vice versa), blanc - direct:

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


# df - pandas DataFrame  
sns.heatmap(df.corr());


Pour autant que je sache, dans les modèles utilisés pour prévoir la consommation d'électricité dans la Fédération de Russie, 2 facteurs sont utilisés - l'historique de la consommation et les prévisions de température. 


En hiver et en automne, la consommation d'énergie est plus élevée - température basse et lumière du jour relativement courte. Le facteur «jour de la semaine» affecte également la quantité d'énergie dépensée - le week-end, il diminue fortement. Les pics de consommation ont lieu mercredi ou jeudi.





À l'intérieur d'une journée en semaine, les valeurs maximales de consommation d'énergie se produisent à 11-12 heures et diminuent progressivement avec une forte baisse après neuf heures du soir. 


Tout est comme dans les manuels:

Horaires journaliers de consommation d'hiver et d'été pour les centres industriels et culturels. La source

La modélisation


Prophète


La première idée pour rendre la tâche aussi rapide que possible est de prendre Prophet de Facebook. J'avais déjà de l'expérience à l'utiliser, et je m'en suis souvenu comme «une chose qui fonctionne simplement et rapidement hors de la boîte». Le prophète veut voir les colonnes «ds» et «y» dans le cadre de données des pandas, c.-à-d. date au format AAAA-MM-JJ HH: MM: SS, et l'objectif est la consommation par heure, respectivement (des exemples de travaux sont dans la documentation ).

df = pd.read_pickle('full.pkl')
pjme = df.loc[:'2018-06-01 23:00'].rename(columns={'hour_value': 'y'}) 
pjme['ds'] = pjme.index
split_date = '2018-03-01 23:00'
pjme_train = pjme.loc[pjme.index <= split_date]
pjme_test = pjme.loc[pjme.index > split_date]


playoffs = pd.DataFrame({
  'holiday': 'playoff',
  'ds': df[(df.rest_day == 1)|(df.is_weekend == 1)].index,
  'lower_window': 0,
  'upper_window': 1,
})


model = Prophet(holidays=playoffs)
model.fit(pjme_train.reset_index())
forecast = model.predict(df=pjme_test.reset_index())



Les prédictions du «prophète» semblaient objectives, mais l'erreur était de 7 à 17% plus élevée que ce qui était permis, par conséquent, je n'ai pas mené d'autres expériences sur ce cadre.

Sarimaax


La deuxième tentative pour résoudre ce problème a été d'utiliser SARIMAX. La méthode prend beaucoup de temps pour sélectionner les coefficients, mais il a été possible de réduire l'erreur de prévision par rapport à Prophet à 6-11%. 

Malheureusement, seul le résultat des prédictions hebdomadaires est resté, mais ce sont ces données prédictives que j'ai utilisées comme fonctionnalités supplémentaires dans les modèles de boosting.

Pour la prévision, vous devez sélectionner les paramètres SARIMA (p, d, q) (P, D, Q, s):

  • p est l'ordre autorégressif (AR), qui vous permet d'ajouter les valeurs passées de la série chronologique (peut être exprimée comme "demain, il neige probablement s'il a neigé au cours des derniers jours");
  • d est l'ordre d'intégration des données sources (il détermine le nombre de points temporels précédents à soustraire de la valeur courante);
  • q est l'ordre de la moyenne mobile (MA), ce qui vous permet de régler l'erreur du modèle sous la forme d'une combinaison linéaire des valeurs d'erreur précédemment observées;
  • P - p, mais saisonnier;
  • D - d, mais saisonnier;
  • Q - q, mais saisonnier;
  • s est la dimension de la saisonnalité (mois, trimestre, etc.).

Sur l'expansion saisonnière de l'historique hebdomadaire, vous pouvez voir la tendance et la saisonnalité, ce qui permet d'afficher saisonnier_décompose:

import statsmodels.api as sm
sm.tsa.seasonal_decompose(df_week).plot()


Selon les graphiques d'autocorrélation et d'autocorrélation partielle, il a sélectionné les approximations initiales des paramètres du modèle SARIMAX: p = 0, P = 1, q = 2, Q = 2.

sm.graphics.tsa.plot_acf(df_week.diff_week[52:].values.squeeze(), lags=60, ax=ax)
sm.graphics.tsa.plot_pacf(df_week.diff_week[52:].values.squeeze(), lags=60, ax=ax)


Et les valeurs finales sélectionnées sont (0, 2, 2) x (1, 2, 0, 52). Par conséquent, le graphique des prévisions de consommation ressemblait à ceci:


Booster


À ce stade, il est devenu évident que sans l'utilisation de facteurs externes supplémentaires, la précision nécessaire ne pourrait pas être obtenue. Tout d'abord, ce sont des facteurs météorologiques: température, pression, humidité, force et direction du vent, précipitations. 

La première tentative a été une tentative d'acheter une archive de la météo à Yandex. Les gars ont une excellente API et un support technique, mais spécifiquement dans notre ville, il y avait beaucoup de lacunes dans les données. En conséquence, j'ai acheté l'archive dans le service openweathermap.org pour 10 $. Il est important de noter que, malgré le fait que la météo est un facteur très utile, les erreurs de prévision vont évidemment gâcher le modèle MAPE final. J'ai rencontré des informations selon lesquelles la bonne solution à ce problème serait de s'entraîner à utiliser non pas les valeurs historiques réelles de la météo, mais les prévisions météorologiques sur trois jours, mais, malheureusement, je n'ai pas eu une telle opportunité. 

J'ai également ajouté des signes de l'heure, du jour de la semaine, des jours fériés, du numéro de série du jour de l'année, ainsi qu'un grand nombre de décalages horaires et de valeurs moyennes pour les périodes précédentes.

Toutes les données après mise à l'échelle (MinMaxScale) et One Hot Encoding (les valeurs de chaque élément catégoriel deviennent des colonnes séparées avec 0 et 1, donc l'élément avec trois valeurs devient trois colonnes différentes, et l'unité sera dans une seule d'entre elles) que j'ai utilisée dans une petite concurrence de trois modèles de boost populaires XGBoost, LightGBM et CatBoost. 

XGBoost


Beaucoup de bien a été écrit sur XGBoost. Présenté à la conférence SIGKDD en 2016, il a fait sensation et occupe toujours une position de leader. Matériel intéressant pour ceux qui n'en ont pas entendu parler.

Préparons les données du modèle: 

from sklearn.preprocessing import MinMaxScaler, StandardScaler
from sklearn.model_selection import train_test_split


def gen_data(
    stop_day = '2018-06-01 23:00', 
    drop_columns = [],
    test_size=0.15,
    is_dummies=True,
    is_target_scale=False):

    df = pd.read_pickle('full.pkl')
    df = df.loc[:stop_day]
    scaler = MinMaxScaler()
    target_scaler = StandardScaler()
    y = df.hour_total
    
    if is_target_scale:        
        y = target_scaler.fit_transform(y.values.reshape(-1, 1)).reshape(1,-1)[0]
        
    X = df.drop(['hour_value', *drop_columns], axis=1)
    num_columns = X.select_dtypes(include=['float64']).columns
    X[num_columns] = scaler.fit_transform(X[num_columns])
    
    if is_dummies:
        X = pd.get_dummies(X)

    train_count_hours = len(X) - 72
    valid_count_hours = len(X) - int(len(X) * 0.2)
    X_test  = X[train_count_hours:]
    y_test  = y[train_count_hours:]
    X = X[:train_count_hours]
    y = y[:train_count_hours]

    #           ,       
    X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=test_size, random_state=42)
    train_dates = X_train.index
    val_dates = X_test.index
    return X, y, X_train, X_val, y_train, y_val, X_test, y_test, target_scaler


J'enseigne un modèle en utilisant les meilleurs hyperparamètres précédemment sélectionnés:

X, y, X_train, X_val, y_train, y_val, X_test, y_test, _ = gen_data(
    stop_day = stop_day, 
    drop_columns = drop_columns,
    test_size=0.1,
    is_dump=True,
    is_target_scale=False)


params_last = {
    'base_score': 0.5, 
    'booster': 'gbtree', 
    'colsample_bylevel': 1, 
    'colsample_bynode': 1, 
    'colsample_bytree': 0.4, 
    'gamma': 0,
    'max_depth': 2, 
    'min_child_weight': 5, 
    'reg_alpha': 0, 
    'reg_lambda': 1, 
    'seed': 38,
    'subsample': 0.7, 
    'verbosity': 1,
    'learning_rate':0.01
}


reg = xgb.XGBRegressor(**params_last, n_estimators=2000)
print(X_train.columns)
reg.fit(X_train, y_train,
        eval_set=[(X_val, y_val)],
        early_stopping_rounds=20,
        verbose=False)


y_pred = reg.predict(X_test)


Lightgbm


LightGBM est un framework de Microsoft dont le principal avantage est la rapidité d'apprentissage sur de très grands ensembles de données. Et aussi, contrairement à XGBoost, LightGBM peut fonctionner avec des catégories, utilise moins de mémoire. Ici, il y a plus.

import lightgbm as lgb
from lightgbm import LGBMRegressor
from sklearn.metrics import mean_squared_error


stop_day = '2018-06-01 23:00'
start_day_for_iterate = datetime.strptime(stop_day, '%Y-%m-%d %H:%M')
test_size = 0.2

X, y, X_train, X_val, y_train, y_val, X_test, y_test, _ = gen_data(
    stop_day = stop_day, 
    drop_columns = drop_columns,
    test_size=test_size,
    is_dump=True,
    drop_weekend=False,
    is_target_scale=False)


model = LGBMRegressor(boosting_type='gbdt'
                num_leaves=83
                max_depth=-1
                learning_rate=0.008
                n_estimators=15000
                max_bin=255
                subsample_for_bin=50000
                min_split_gain=0
                min_child_weight=3,
                min_child_samples=10
                subsample=0.3
                subsample_freq=1
                colsample_bytree=0.5
                reg_alpha=0.1
                reg_lambda=0
                seed=38,
                silent=False
                nthread=-1)


history = model.fit(X_train, y_train, 
            eval_metric='rmse',
            eval_set=[(X_val, y_val)],
            early_stopping_rounds=40,
            verbose = 0)


y_pred = model.predict(X_test, num_iteration=model.best_iteration_)


Catboost


CatBoost est une bibliothèque avancée de boosting de gradient sur les arbres de décision open source "de Yandex . La différence avantageuse du modèle est la commodité de travailler avec des ensembles de données contenant des attributs catégoriels - vous pouvez transférer des données contenant des catégories vers le modèle sans conversion en nombres, et il révèle les modèles par lui-même, à la main rien n'a besoin d'être tordu et la qualité de la prédiction reste élevée.

cat_features=np.where(X.dtypes == 'category')[0]
eval_dataset = Pool(X_test, y_test)

model = CatBoostRegressor(learning_rate=0.5,
                          eval_metric='RMSE'
                          leaf_estimation_iterations=3,
                          depth=3)

model.fit(X_train, y_train,
          eval_set=(X_val, y_val),
          cat_features=cat_features,
          plot=True,
          verbose=True)

y_pred = model.predict(X_test)


XGBoost vs. LightGBM vs. Catboost


Afin de ne pas répéter de nombreux articles, je vais vous donner un tableau comparatif d'ici .


Pour calculer l'erreur par MAPE, j'ai pris le dernier mois connu (28 jours) et, en déplaçant la fenêtre avec une résolution d'une heure, j'ai fait une prévision pour les 72 heures suivantes.

Et dans ma compétition impromptue, les prix étaient:

3ème place: mon préféré - XGBoost - avec la qualité de prévision la plus basse;
2e place: CatBoost comme le plus pratique et «tout prêt à l'emploi»;
1ère place: LightGBM comme le plus rapide et le plus précis.

Pour une comparaison plus précise des modèles, j'ai utilisé R2 (R-carré ou coefficient de détermination, montrant à quel point la variance conditionnelle du modèle diffère de la variance des valeurs réelles) et RMSLE (Root Mean Squared Logarithmic Error, ou l'erreur racine moyenne logarithmique carrée, qui est, en fait, la distance entre deux points de l'avion - valeur réelle et prédite). 

MétriqueLightgbmCatboostXGBoostProphèteSarimaax
R20,941370,939840,929090,814350,73778
Msle0,024680,024770,012190,008290,00658

Mais tout cela, bien sûr, par rapport à ma tâche. Sur d'autres données entre différentes mains, tout peut être complètement différent. 

La prochaine fois, je partagerai une autre histoire qui nous est propre sur la prévision des sorties d’employés pour planifier le recrutement dans notre entreprise.

Source: https://habr.com/ru/post/undefined/


All Articles