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

Соответствующие хорошо продуманные функции часто являются решающим фактором производительности вашего окончательного алгоритма. По этой причине специалисты по обработке данных часто тратят 70% -80% своего времени на этап предварительного моделирования, который в основном состоит из проектирования функций.

Давайте углубимся и попробуем поработать на реальном примере…

Данные, которые мы будем использовать, взяты из выигранного мной конкурса.
Мы попытаемся построить модель, которая прогнозирует количество мест, которое Mobiticket может рассчитывать на продавать за каждую поездку. В этом наборе данных 14 маршрутов. Все маршруты заканчиваются в Найроби и берут начало в городах к северо-западу от Найроби в сторону озера Виктория.

Описание переменных:

  • ride_id: уникальный идентификатор транспортного средства на определенном маршруте в определенный день и время.
  • seat_number: место, назначенное билету
  • payment_method: метод, используемый клиентом для покупки билета в Mobiticket (наличными или Mpesa)
  • payment_receipt: уникальный идентификационный номер для билета, приобретенного в Mobiticket
  • travel_date: дата отправления рейса. (ММ / ДД / ГГГГ)
  • travel_time: время отправления по расписанию. Поездки обычно отправляются вовремя. (чч: мм)
  • travel_from: город, из которого началась поездка
  • travel_to: пункт назначения. Все поездки в Найроби.
  • car_type: тип транспортного средства (шаттл или автобус)
  • max_capacity: количество мест в транспортном средстве

Первым делом мы импортируем библиотеки, которые собираемся использовать.

import pandas as pd #to use dataframes
import numpy as np #for linear algebra operations
import matplotlib.pyplot as plt #for visualization
import seaborn as sns
from sklearn.model_selection import train_test_split,KFold
import xgboost as xgb #the model we're going to use
from sklearn.metrics import mean_absolute_error #our metric

Теперь нам нужно загрузить наш поезд и тестовые данные.

train=pd.read_csv(“./Data/train.zip”)
test=pd.read_csv(“./Data/test.csv”)

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

Target=train.groupby([“ride_id”]).seat_number.count().rename(“number_of_ticket”).reset_index()
train=train.drop_duplicates(“ride_id”).drop([‘payment_method’, ‘payment_receipt’, ‘seat_number’],axis=1)
train=train.merge(Target,how=”left”,on=”ride_id”)
train.drop([“travel_to”],axis=1,inplace=True)
test.drop([“travel_to”],axis=1,inplace=True)

Теперь мы должны обработать столбцы данных и добавить временные характеристики, которые очень важны при работе с проблемами временных рядов.

def add_20(x):
   date=x.split(“-”)
   date[-1]=”20"+date[-1]
   return “-”.join(date)
train[“date”]=(train[“travel_date”].apply(add_20)+” “+train[“travel_time”]).astype(str)
test[“date”]=(test[“travel_date”]+” “+test[“travel_time”]).astype(str)

Мы создадим функцию time_features, которая будет извлекать все временные данные из заданных данных, таких как час, день, месяц, выходные или нет ...

def time_features(x): 
    x[“date”]=pd.to_datetime(x[“date”],format=’%d-%m-%Y %H:%M’)
    x[“dayofweek”]=x[“date”].dt.dayofweek
    x[“dayofyear”]=x[“date”].dt.dayofyear
    x[“dayofmonth”]=x[“date”].dt.day        x[“year_woy”]=x[“date”].dt.year.astype(str)+x[“date”].dt.weekofyear.astype(str)
    x[“hour”]=x[“date”].dt.hour
    x[“minute”]=x[“date”].dt.minute
    x[“is_weekend”]=x[“dayofweek”].apply( lambda x : 1 if x in [5,6] else 0 )
    x[“year”]=x[“date”].dt.year
    x[“quarter”]=x[“date”].dt.quarter
    x[“month”]=x[“date”].dt.month
    return x 
train=time_features(train)
test=time_features(test)

Визуализация

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

categorical_feautres=[“travel_from”,”car_type”,”dayofweek”,”dayofmonth”,”hour”,”minute”,”year”]
def plot_categorical_feature(feautre):
     fig, axes = plt.subplots(nrows=1, ncols=2)
     train.groupby(feautre).number_of_ticket.mean()\
     .plot(kind=”bar”,ax=axes[0],figsize=(20,5),title=”mean of    number_of_ticket per {}”.format(feautre))
     train.groupby(feautre).number_of_ticket.count().\
     plot(kind=”bar”,ax=axes[1],figsize=(20,5),title=”ride count per {}”.format(feautre)) 
     plt.show()
for feautre in categorical_feautres : 
     plot_categorical_feature(feautre)

Целевое кодирование

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

def target_encoding(data,feautre,
                    target=”number_of_ticket”,
                    agg_functions={“mean”,”std”}):
    agg=data.groupby(feautre)[target].agg(agg_functions)
    agg.columns=[column+”_per_{}_{}”.format(feautre,target) for column in agg.columns.tolist()]
    return agg

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

Эта функция будет использоваться как наш первый подход к целевой кодировке:

def add_target_encoding_features(train,test):
     for feautre in categorical_feautres : 
          agg=target_encoding(train,feautre)
          train=train.merge(agg,how=”left”,on=feautre)
          test=test.merge(agg,how=”left”,on=feautre)
     return train,test
train_TE,test_TE=add_target_encoding_features(train_TE,test_TE)

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

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

train_VTE,val_VTE=train_test_split(train,random_state=1994,test_size=0.1)
test_VTE=test.copy()
def add_target_encoding_features_validation(train,val,test):
    for feautre in categorical_feautres : 
        agg=target_encoding(train,feautre)
        train=train.merge(agg,how="left",on=feautre)
        val=val.merge(agg,how="left",on=feautre)
        test=test.merge(agg,how="left",on=feautre)
        
    return train,val,test
train_VTE,val_VTE,test_VTE=add_target_encoding_features_validation(train_VTE,val_VTE,test_VTE)

Этот последний метод выполняется с использованием kfold вместо простого разделения.

def add_target_encoding_features_Kfold(train,test,split=10):
    kf = KFold(n_splits=split,random_state=2222,shuffle=False)
    train_final=[]
    for train_index, test_index in kf.split(train):
        train_fold, val_fold = train.loc[train_index], train.loc[test_index]
        for  feautres in categorical_feautres:
            agg=target_encoding(train_fold,feautres)
            val_fold=val_fold.merge(agg,how="left",on=feautres)
        train_final.append(val_fold)
    
    for  feautres in categorical_feautres : 
        agg=target_encoding(train,feautres)
        test=test.merge(agg,how="left",on=feautres)
        
        
    return pd.concat(train_final),test
train_KTE=train.copy()
test_KTE=test.copy()
train_KTE,test_KTE=add_target_encoding_features_Kfold(train=train_KTE,test=test_KTE)

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

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

def train_model(X_train,Y_train,X_val,
Y_val,X_test,parmaters,features_name): 
    d_train = xgb.DMatrix(X_train, Y_train,feature_names=features_name)
    d_valid = xgb.DMatrix(X_val, Y_val,feature_names=features_name)
    d_test = xgb.DMatrix(X_test,feature_names=features_name)
    list_track = [(d_train, 'train'), (d_valid, 'valid')]
    model = xgb.train(parmaters, d_train, 2000,  list_track, maximize=False, verbose_eval=50, early_stopping_rounds=50)
    train_pred =model.predict(d_train)              
    valid_pred =model.predict(d_valid)   
    test_pred = model.predict(d_test)
    return train_pred ,valid_pred,test_pred

Поскольку мы хотим быть более уверенными в производительности нашего алгоритма. Мы создаем пять различных моделей, используя наш алгоритм обучения, и тестируем его на пяти различных наборах тестов. Этот метод реализован в sklearn в функции Kfold из sklearn.model_selection.

def train_kfold(X_train,Y_train,X_test,
                parmaters,features_name,split=5,):
    final_train_pred=np.zeros_like(Y_train)
    final_test_pred=np.zeros(len(X_test))
    
    kf = KFold(n_splits=split,random_state=2222)
    i=1
    for train_index, val_index in kf.split(X_train):
        print("fold:"+str(i))
        train_fold_features, val_fold_features = X_train.loc[train_index], X_train.loc[val_index]
        train_fold_target, val_fold_target = Y_train.loc[train_index], Y_train.loc[val_index] 
        train_pred ,valid_pred,test_pred=train_model(
              X_train=train_fold_features,                                                  
              Y_train= train_fold_target,
              X_val= val_fold_features,
              Y_val= val_fold_target,
              X_test= X_test,
              parmaters=parmaters,         
              features_name=features_name )
        
        final_train_pred[val_index]=valid_pred
        final_test_pred=final_test_pred+test_pred/split
        i=i+1
    return final_train_pred,final_train_pred

Теперь нам нужно удалить бесполезные функции.

columns_to_remove=['ride_id', 'travel_date', 'travel_time', 'travel_from', 'car_type',
       'max_capacity', 'number_of_ticket', 'date', 'dayofweek', 'dayofyear',
       'dayofmonth', 'year_woy', 'hour', 'minute', 'year',
       'quarter', 'month']
features_name=train_KTE.drop(columns_to_remove,axis=1).columns

Обучение

train_TE_train,train_TE_val=train_test_split(train_TE,random_state=1994,test_size=0.1)
X_train=train_TE_train[features_name]
Y_train=train_TE_train["number_of_ticket"]
X_val=train_TE_val[features_name]
Y_val=train_TE_val["number_of_ticket"]
X_test=test_TE[features_name]
params = {'eta': 0.004, 'colsample_bytree': 0.7, 
               'max_depth': 9,'subsample': 0.9, 
               'lambda': 4, 'nthread': 8, 
               'booster' : 'gbtree', 'silent': 1,
               'eval_metric': 'rmse', 'objective': 'reg:linear',
               "gamma":0.1 ,"alpha":0.01} 
train_pred ,valid_pred,test_pred=train_model(X_train,
                                             Y_train,X_val,
                                             Y_val,X_test,
                                             params,features_name)

Ссылка на Github