Aprendizaje automático en el sector energético, o no solo todos pueden ver el mañana

La predicción precisa de eventos futuros es una tarea prometedora e interesante en muchas áreas: desde el pronóstico del tiempo hasta la tecnología financiera (cotizaciones de bolsa, tipos de cambio). El aprendizaje automático de hoy puede reducir significativamente el tiempo y la mano de obra necesarios para tomar decisiones de gestión.  Durante aproximadamente medio año,

nuestro equipo de ciencia de datos en NORBIT experimentó utilizando varios modelos de aprendizaje automático para resolver problemas de clasificación y regresión, y para optimizar los procesos comerciales b2b. Pero cuando apareció la tarea de predecir la serie temporal, resultó que los materiales disponibles sobre este tema en la red no eran suficientes para desarrollar una solución rápida.


La esencia de la tarea era predecir con la máxima precisión (error absoluto promedio en porcentaje, o MAPE <3%) el consumo por hora de toda la ciudad durante los próximos tres días (72 horas) para las necesidades de una gran empresa generadora de electricidad.

El error máximo permitido del 3% es un indicador muy alto para la precisión de los pronósticos. Antes del uso del aprendizaje automático, el error estaba en el rango de 3-23%, lo que condujo directamente a pérdidas financieras, ya que con una generación insuficiente de energía eléctrica, se tuvieron que comprar capacidades adicionales en el mercado mayorista de electricidad (que es mucho más costoso que el autogenerado), con sobreproducción - vendida en el mercado más barato de lo que podrían vender a la ciudad. Además, el efecto negativo de la desviación de la carga planificada de la real es un cambio en la frecuencia de la corriente alterna en la red.

Muchos factores impiden el logro de una alta precisión en la predicción, por ejemplo, con un aumento repentino de la temperatura, las personas alcanzan inmediatamente las consolas de los aires acondicionados y, cuando caen, obtienen calentadores de los entrepisos; durante las vacaciones o los grandes partidos de fútbol incluyen televisores, etc. Si bien algunos eventos son cíclicos y potencialmente predecibles, otros están completamente ausentes. Supongamos que, de repente, debido a un accidente, la fábrica de acero dejó de funcionar y todos los pronósticos se convirtieron en una calabaza (en nuestra ciudad objetivo no había grandes empresas de producción). Hubo un ejemplo bastante interesante de un problema al hacer pronósticos en el sector energético, cuando uno de los picos en los cambios en la cantidad de electricidad generada fue causado por un barco varado en un lago, y el capitán estuvo de acuerdo con los propietarios de las centrales hidroeléctricas para reducir la descarga de agua.Naturalmente, el modelo de aprendizaje automático no pudo predecir este evento sobre la base de datos históricos.

La buena noticia para nosotros es que el cliente proporcionó en los términos de referencia tales "días especiales" en los que un error de pronóstico promedio del 8% era aceptable.

Todo lo que el cliente proporcionó son los datos históricos del consumo de electricidad por hora durante 3 años y el nombre de la ciudad.


La primera tarea para preparar la construcción de un modelo de aprendizaje automático es la visualización y la búsqueda de factores adicionales que puedan influir en el pronóstico. El grado de dicha influencia se evalúa visualmente de manera conveniente utilizando un mapa de calor de la correlación de atributos. El cuadrado oscuro en este caso significa la relación inversa incondicional de las cantidades (cuanto mayor es un valor, menor es el otro y viceversa), blanco - directo:

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


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


Hasta donde sé, en los modelos que se utilizan para pronosticar el consumo de electricidad en la Federación de Rusia, se utilizan 2 factores: el historial de consumo y el pronóstico de temperatura. 


En invierno y otoño, el consumo de energía es mayor: baja temperatura y horas de luz relativamente cortas. El factor "día de la semana" también afecta la cantidad de energía gastada: los fines de semana cae bruscamente. Los picos de consumo ocurren el miércoles o jueves.





Dentro de un día de lunes a viernes, los valores máximos del consumo de energía ocurren a las 11-12 horas y caen gradualmente con una fuerte caída después de las nueve de la noche. 


Todo es como en los libros de texto:

Horario diario de consumo diario de invierno y verano para centros industriales y culturales. Fuente

Modelado


Profeta


La primera idea de cómo hacer la tarea lo más rápido posible es sacar a Prophet de Facebook. Ya tenía experiencia en usarlo, y lo recordaba como "algo que funciona de manera simple y rápida". Profeta quiere ver las columnas "ds" e "y" en el marco de datos de los pandas, es decir fecha en el formato AAAA-MM-DD HH: MM: SS, y el objetivo es el consumo por hora, respectivamente (ejemplos de trabajo se encuentran en la documentación ).

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())



Las predicciones del "profeta" parecían objetivas, pero el error fue un 7–17% mayor que el permitido, por lo tanto, no realicé más experimentos en este marco.

Sarimaax


El segundo intento de resolver este problema fue usar SARIMAX. El método requiere bastante tiempo para seleccionar coeficientes, pero fue posible reducir el error de pronóstico en comparación con Prophet al 6-11%. 

Desafortunadamente, solo quedó el resultado de las predicciones semanales, pero fueron estos datos predictivos los que utilicé como características adicionales en los modelos de impulso.

Para el pronóstico, debe seleccionar los parámetros SARIMA (p, d, q) (P, D, Q, s):

  • p es el orden autorregresivo (AR), que le permite agregar los valores pasados ​​de la serie temporal (puede expresarse como "mañana, probablemente nevará si ha nevado en los últimos días");
  • d es el orden de integración de los datos de origen (determina el número de puntos de tiempo anteriores que se restarán del valor actual);
  • q es el orden de la media móvil (MA), que le permite establecer el error del modelo en forma de una combinación lineal de los valores de error observados anteriormente;
  • P - p, pero estacional;
  • D - d, pero estacional;
  • Q - q, pero estacional;
  • s es la dimensión de la estacionalidad (mes, trimestre, etc.).

En la expansión estacional del historial semanal, puede ver la tendencia y la estacionalidad, lo que permite que se muestre la descomposición estacional:

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


De acuerdo con los gráficos de autocorrelación y autocorrelación parcial, seleccionó las aproximaciones iniciales de los parámetros para el modelo 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)


Y los valores finales seleccionados son (0, 2, 2) x (1, 2, 0, 52). Como resultado, el gráfico de pronóstico de consumo se veía así:


Impulsar


En este punto, se hizo evidente que sin el uso de factores externos adicionales, no se podría lograr la precisión necesaria. En primer lugar, estos son factores climáticos: temperatura, presión, humedad, fuerza y ​​dirección del viento, precipitación. 

El primer intento fue un intento de comprar un archivo del clima en Yandex. Los chicos tienen una excelente API y soporte técnico, pero específicamente en nuestra ciudad había bastantes lagunas en los datos. Como resultado, compré el archivo en el servicio openweathermap.org por $ 10. Es importante tener en cuenta que, a pesar de que el clima es un factor muy útil, los errores de pronóstico obviamente estropearán el modelo MAPE final. Conocí información de que la solución correcta a este problema sería entrenar para usar no los valores históricos reales del clima, sino los pronósticos del tiempo para tres días, pero, desafortunadamente, no tuve esa oportunidad. 

También agregué signos de la hora del día, día de la semana, días festivos, el número de serie del día en el año, así como una gran cantidad de retrasos y valores promedio para períodos anteriores.

Todos los datos después de escalar (MinMaxScale) y One Hot Encoding (los valores de cada elemento categórico se convierten en columnas separadas con 0 y 1, por lo que el elemento con tres valores se convierte en tres columnas diferentes, y la unidad estará en solo una de ellas). competencia de tres populares modelos de impulso XGBoost, LightGBM y CatBoost. 

XGBoost


Se ha escrito mucho sobre XGBoost. Presentado en la conferencia SIGKDD en 2016, causó sensación y aún mantiene una posición de liderazgo. Material interesante para quienes no lo han escuchado.

Vamos a preparar los datos para el modelo: 

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


Estoy enseñando un modelo usando los mejores hiperparámetros previamente seleccionados:

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 es un marco de Microsoft cuya principal ventaja es la velocidad de aprendizaje en conjuntos de datos realmente grandes. Y también, a diferencia de XGBoost, LightGBM puede trabajar con categorías, usa menos memoria. Aquí entonces hay más.

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 es una biblioteca avanzada de aumento de gradiente en árboles de decisión de código abierto "de Yandex . La diferencia ventajosa del modelo es la conveniencia de trabajar con conjuntos de datos que contienen características categóricas: puede transferir datos que contienen categorías al modelo sin conversión a números, y revela patrones a mano nada necesita ser torcido, y la calidad de la predicción sigue siendo alta.

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


Para no repetir numerosos artículos, daré una tabla comparativa desde aquí .


Para calcular el error MAPE, tomé el último mes conocido (28 días) y, moviendo la ventana con una resolución de una hora, hice un pronóstico para las próximas 72 horas.

Y en mi improvisada competencia, los premios fueron:

3er lugar: mi favorito - XGBoost - con la calidad de pronóstico más baja;
2do lugar: CatBoost como el más conveniente y "todo listo para usar";
1er lugar: LightGBM como el más rápido y preciso.

Para una comparación más precisa de los modelos, utilicé R2 (R-cuadrado o coeficiente de determinación, que muestra cuánto difiere la varianza condicional del modelo de la varianza de los valores reales) y RMSLE (Root Mean Square Squared Error, o el error logarítmico cuadrático medio, que es, de hecho, la distancia entre dos puntos en el plano: valor real y predicho). 

MétricaLightgbmCatboostXGBoostProfetaSarimaax
r20.941370.939840.929090.814350.73778
Msle0,024680,024770,012190.008290.00658

Pero todo esto, por supuesto, en relación con mi tarea. En otros datos en diferentes manos, todo puede ser completamente diferente. 

La próxima vez, compartiré otra historia propia sobre la predicción de la salida de empleados para la tarea de planificar el reclutamiento en nuestra empresa.

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


All Articles