التعلم الآلي في قطاع الطاقة ، أو لا يمكن للجميع فقط مشاهدته غدًا

يعد التنبؤ الدقيق بالأحداث المستقبلية مهمة واعدة ومثيرة للاهتمام في العديد من المجالات: من التنبؤ بالطقس إلى التكنولوجيا المالية (أسعار الأسهم ، وأسعار الصرف). يمكن للتعلم الآلي اليوم تقليل الوقت والجهد المبذول في اتخاذ قرارات الإدارة بشكل كبير.  لمدة نصف عام تقريبًا ، جرب

فريق علوم البيانات في NORBIT استخدام نماذج التعلم الآلي المختلفة لحل مشاكل التصنيف والانحدار ، وتحسين عمليات الأعمال b2b. ولكن عندما ظهرت مهمة التنبؤ بالسلسلة الزمنية ، اتضح أن المواد المتاحة حول هذا الموضوع على الشبكة لم تكن كافية لتطوير حل سريع.


كان جوهر المهمة هو التنبؤ بأقصى قدر من الدقة (متوسط ​​الخطأ المطلق في المائة ، أو MAPE <3 ٪) استهلاك الساعة للمدينة بأكملها خلال الأيام الثلاثة المقبلة (72 ساعة) لاحتياجات شركة توليد كهرباء كبيرة واحدة.

الحد الأقصى المسموح به للخطأ بنسبة 3 ٪ هو مؤشر مرتفع للغاية لدقة التنبؤات. قبل استخدام التعلم الآلي ، كان الخطأ في حدود 3-23 ٪ ، مما أدى مباشرة إلى خسائر مالية ، لأنه مع عدم كفاية توليد الطاقة الكهربائية ، كان لا بد من شراء قدرات إضافية في سوق الكهرباء بالجملة (وهو أغلى بكثير من التوليد الذاتي) ، مع الإفراط في الإنتاج - يباع في السوق أرخص مما يمكنهم بيعه للمدينة. أيضًا ، التأثير السلبي لانحراف الحمل المخطط عن الحمل الفعلي هو تغيير في تردد التيار المتناوب في الشبكة.

تتداخل العديد من العوامل مع تحقيق دقة عالية للتنبؤ ، على سبيل المثال ، مع الزيادة المفاجئة في درجة الحرارة ، يصل الناس على الفور إلى وحدات التحكم من مكيفات الهواء ، بينما يخفضون ، يحصلون على سخانات من الميزانين. خلال العطلات أو مباريات كرة القدم الكبيرة تشمل أجهزة التلفاز ، إلخ. في حين أن بعض الأحداث دورية ويمكن توقعها ، فإن بعضها الآخر غائب تمامًا. لنفترض ، فجأة ، بسبب حادث ، توقف متجر الصلب عن العمل وتحولت جميع التوقعات إلى قرع (في مدينتنا المستهدفة لم تكن هناك شركات إنتاج كبيرة). كان هناك مثال مثير للاهتمام إلى حد ما لمشكلة في وضع التوقعات في قطاع الطاقة ، عندما كان أحد الزيادات في التغيرات في كمية الكهرباء المولدة ناتجة عن سفينة عالقة على بحيرة ، واتفق القبطان مع أصحاب محطات الطاقة الكهرومائية للحد من تصريف المياه.بطبيعة الحال ، لم يتمكن نموذج التعلم الآلي من توقع هذا الحدث على أساس البيانات التاريخية.

الخبر السار بالنسبة لنا هو أن العميل قدم مثل هذه "الأيام الخاصة" في الشروط المرجعية التي سمحت بمتوسط ​​خطأ متوقع بنسبة 8٪.

كل ما قدمه العميل هو البيانات التاريخية لاستهلاك الكهرباء بالساعة لمدة 3 سنوات واسم المدينة.


المهمة الأولى لإعداد بناء نموذج التعلم الآلي هي التصور والبحث عن عوامل إضافية يمكن أن تؤثر على التوقعات. يتم تقييم درجة هذا التأثير بصريًا بشكل ملائم باستخدام خريطة حرارية لارتباط السمات. المربع المظلمة في هذه الحالة يعني العلاقة العكسية غير المشروطة للكميات (القيمة الأكبر ، الأصغر الأخرى ، والعكس صحيح) ، الأبيض - المباشر:

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


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


على حد علمي ، في النماذج المستخدمة للتنبؤ باستهلاك الكهرباء في الاتحاد الروسي ، يتم استخدام عاملين - تاريخ الاستهلاك وتوقعات درجة الحرارة. 


في الشتاء والخريف ، يكون استهلاك الطاقة أعلى - درجة حرارة منخفضة وساعات النهار القصيرة نسبيًا. يؤثر عامل "يوم الأسبوع" أيضًا على كمية الطاقة المستهلكة - في عطلات نهاية الأسبوع ينخفض ​​بشكل حاد. تحدث ذروة الاستهلاك يوم الأربعاء أو الخميس.





خلال يوم واحد خلال أيام الأسبوع ، تحدث القيم القصوى لاستهلاك الطاقة في 11-12 ساعة وتنخفض تدريجيًا مع انخفاض حاد بعد التاسعة مساءً. 


كل شيء كما في الكتب المدرسية:

جداول الاستهلاك اليومي في الشتاء والصيف للمراكز الصناعية والثقافية. مصدر

النمذجة


نبي


أول فكرة عن كيفية جعل المهمة في أسرع وقت ممكن هي أخذ النبي من الفيسبوك. كانت لدي خبرة في استخدامه بالفعل ، وتذكرته على أنه "شيء يعمل ببساطة وبسرعة خارج الصندوق". يريد النبي أن يرى العمودين "ds" و "y" في إطار بيانات الباندا ، أي التاريخ بالصيغة YYYY-MM-DD HH: MM: SS ، والهدف هو الاستهلاك في الساعة ، على التوالي (توجد أمثلة على العمل في الوثائق ).

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



بدت تنبؤات "النبي" موضوعية ، لكن الخطأ كان أعلى بنسبة 7-17٪ من المسموح به ، لذلك لم أجرِ المزيد من التجارب في هذا الإطار.

ساريماكس


المحاولة الثانية لحل هذه المشكلة كانت باستخدام SARIMAX. تستهلك هذه الطريقة وقتًا طويلاً في تحديد المعاملات ، ولكن كان من الممكن تقليل الخطأ المتوقع مقارنة بالنبي إلى 6-11٪. 

لسوء الحظ ، بقيت نتيجة التوقعات الأسبوعية فقط ، ولكن هذه البيانات التنبؤية هي التي استخدمتها كميزات إضافية في نماذج التعزيز.

للتنبؤ ، تحتاج إلى تحديد معلمات SARIMA (p ، d ، q) (P ، D ، Q ، s):

  • p هو ترتيب الانحدار الذاتي (AR) ، والذي يسمح لك بإضافة القيم السابقة للسلسلة الزمنية (يمكن التعبير عنها كـ "غدًا ، من المحتمل أن تثلج إذا كانت الثلوج قد تساقطت في الأيام القليلة الماضية") ؛
  • d هو ترتيب تكامل بيانات المصدر (يحدد عدد النقاط الزمنية السابقة التي سيتم طرحها من القيمة الحالية) ؛
  • q هو ترتيب المتوسط ​​المتحرك (MA) ، والذي يسمح لك بتعيين خطأ النموذج في شكل مجموعة خطية من قيم الخطأ التي لوحظت سابقًا ؛
  • P - p ، لكن موسمي ؛
  • د - د ، لكن موسمي ؛
  • س - ف ، ولكن الموسمية.
  • s هو البعد الموسمي (الشهر ، الربع ، إلخ.).

في التوسّع الموسمي للتاريخ الأسبوعي ، يمكنك مشاهدة الاتجاه والموسمية ، مما يسمح بعرض تحركات موسمية:

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


وفقًا للرسومات البيانية للترابط الذاتي والترابط الذاتي الجزئي ، اختار التقريبات الأولية لمعلمات نموذج 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)


والقيم المختارة النهائية هي (0 ، 2 ، 2) × (1 ، 2 ، 0 ، 52). ونتيجة لذلك ، بدا الرسم البياني لتوقعات الاستهلاك كما يلي:


التعزيز


عند هذه النقطة ، أصبح من الواضح أنه بدون استخدام عوامل خارجية إضافية ، لا يمكن تحقيق الدقة اللازمة. بادئ ذي بدء ، هذه عوامل الطقس: درجة الحرارة والضغط والرطوبة وقوة الرياح واتجاهها وهطول الأمطار. 

المحاولة الأولى كانت محاولة لشراء أرشيف الأحوال الجوية في ياندكس. يتمتع الرجال بواجهة برمجة تطبيقات ممتازة ودعم فني ممتاز ، ولكن على وجه التحديد في مدينتنا كان هناك الكثير من الإغفالات في البيانات. ونتيجة لذلك ، اشتريت الأرشيف في خدمة openweathermap.org مقابل 10 دولارات. من المهم ملاحظة أنه على الرغم من حقيقة أن الطقس عامل مفيد للغاية ، فإن الأخطاء في التوقعات ستفسد بوضوح نموذج MAPE النهائي. قابلت معلومات تفيد بأن الحل الصحيح لهذه المشكلة سيكون في التدريب ليس لاستخدام القيم التاريخية الفعلية للطقس ، ولكن توقعات الطقس لمدة ثلاثة أيام ، ولكن للأسف ، لم تتح لي مثل هذه الفرصة. 

أضفت أيضًا علامات على الوقت من اليوم ، ويوم من الأسبوع ، والعطلات ، والرقم التسلسلي لليوم في السنة ، بالإضافة إلى عدد كبير من الفواصل الزمنية ومتوسط ​​القيم للفترات السابقة.

جميع البيانات بعد القياس (MinMaxScale) و One Hot Encoding (تصبح قيم كل عنصر فئوي أعمدة منفصلة مع 0 و 1 ، وبالتالي يصبح العنصر الذي يحتوي على ثلاث قيم ثلاثة أعمدة مختلفة ، وستكون الوحدة في واحد منها فقط) استخدمته في منافسة لثلاثة طرازات تعزيز شعبية XGBoost و LightGBM و CatBoost. 

XGBoost


لقد كتب الكثير عن XGBoost. تم تقديمه في مؤتمر SIGKDD في عام 2016 ، وقام برشقة ولا يزال يحتل مكانة رائدة. مادة مثيرة للاهتمام لأولئك الذين لم يسمعوا عنها.

دعنا نعد البيانات للنموذج: 

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


أقوم بتدريس نموذج باستخدام أفضل المعلمات المختارة مسبقًا:

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 هو إطار عمل من Microsoft تتمثل ميزته الرئيسية في سرعة التعلم على مجموعات البيانات الكبيرة حقًا. وأيضًا ، على عكس XGBoost ، يمكن لـ LightGBM العمل مع الفئات ، ويستخدم ذاكرة أقل. هنا ثم هناك المزيد.

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 هي مكتبة متقدمة لتعزيز التدرج على أشجار القرار مفتوحة المصدر "من Yandex . الفرق المفيد للنموذج هو سهولة العمل مع مجموعات البيانات التي تحتوي على ميزات فئوية - يمكنك نقل البيانات التي تحتوي على فئات إلى النموذج دون تحويل إلى أرقام ، وتكشف النقوش يدويًا لا شيء يحتاج إلى الالتواء ، ولا تزال جودة التنبؤ عالية.

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 مقابل LightGBM مقابل كاتبووست


لكي لا أكرر العديد من المقالات ، سأعطي جدول مقارنة من هنا .


لحساب خطأ MAPE ، أخذت آخر شهر معروف (28 يومًا) ، وقمت بتحريك النافذة بدقة ساعة واحدة ، وقمت بتوقع الساعات 72 التالية.

وفي منافسي المرتجلة كانت الجوائز:

المركز الثالث: المفضلة - XGBoost - بأقل جودة متوقعة ؛
المركز الثاني: CatBoost باعتباره الأكثر ملاءمة و "خارج الصندوق" ؛
المركز الأول: LightGBM هو الأسرع والأكثر دقة.

من أجل مقارنة أكثر دقة للنماذج ، استخدمت R2 (R- مربعة أو معامل التحديد ، تبين مدى اختلاف التباين الشرطي للنموذج عن تباين القيم الحقيقية) و RMSLE (خطأ الجذر التربيعي اللوغاريتمي ، أو خطأ الجذر اللوغاريتمي الجذري ، وهو في الواقع المسافة بين نقطتين على متن الطائرة - القيمة الحقيقية والمتوقعة). 

المقاييسLightgbmكاتبووستXGBoostنبيساريماكس
ص 20.941370.939840.929090.814350.73778
Msle0.024680.024770.012190.008290.00658

لكن كل هذا بالطبع يتعلق بمهمتي. على البيانات الأخرى في أيدي مختلفة ، يمكن أن يكون كل شيء مختلفًا تمامًا. 

في المرة القادمة ، سأشارك قصة أخرى خاصة بنا حول التنبؤ بتدفق الموظفين لمهمة تخطيط التوظيف في شركتنا.

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


All Articles