Создание функций, которые могут помочь алгоритмам машинного обучения работать

Этот шаг делает ваши данные готовыми для окончательного обучения. Подход к проектированию функций основан на хорошем понимании данных, как упоминалось в моей предыдущей статье об EDA. Я продолжу с набора данных Housing на Kaggle (использованного для объяснения EDA в предыдущей статье), чтобы проиллюстрировать, как я его спроектировал.

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

Удаление выбросов

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

Преобразование категориальных переменных в числовые

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

Обратите внимание, что для некоторых категориальных переменных, таких как Alley, GarageQual, BsmtQual, некоторые значения отсутствуют, а другие заполнены такими значениями, как «Po», «Gd», «TA» и т. Д. Файл описания данных помогает расшифровать значения этих переменных. В этом случае недостающие данные просто означают, что в доме, вероятно, нет переулка, гаража или подвала. Поэтому я использовал приведенный ниже код для вменения отсутствующих значений, а также преобразования существующих значений в числовые:

combined.Alley = combined.Alley.map({np.nan:0, 'Grvl':1, 'Pave':2})
combined.BsmtCond = combined.BsmtCond.map({np.nan:0, 'Po':1, 'Fa':2, 'TA':3, 'Gd':4, 'Ex':5})
*combined is the name of the merged dataset of train and test

Это возможно только потому, что присвоенные значения подобны ранговым переменным, например «Fa» лучше, чем «Po», а «TA» лучше, чем «Fa».

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

Ввод пропущенных значений как категориальных, так и числовых признаков

Но перед этим важно вменять недостающие значения всех категориальных переменных. Например, у MiscFeature и MasVnrType отсутствуют значения, что, вероятно, указывает на их отсутствие. Есть простой способ проверить это. Если MiscVal и MasVnrArea равны нулю, что соответствует отсутствующим значениям, предположение является правильным и может быть вычислено следующим образом:

combined.MiscFeature.fillna('NA', inplace=True)
combined.MasVnrType.fillna('None', inplace=True)

Для других функций, таких как KitchenQual или SaleType, безопаснее приписать им режим значений.

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

GarageYrBlt также имеет пропущенные значения, которым я присвоил ноль, поскольку невозможно угадать отсутствующий год!

Работа с переменными времени

В наборе данных 5 временных переменных. Преобразование их в возрастные переменные кажется более значимым, поскольку они предлагают больше информации о долговечности функции. Это аналогично тому, что утверждение «Mr. X умер в возрасте 66 лет »содержит для нас больше информации, чем утверждение« Mr. X умер в 2019 году ». Отсюда вводятся три возрастных признака:

combined['Age'] = combined.YrSold - combined.YearBuilt
combined['AgeRemod'] = combined.YrSold - combined.YearRemodAdd
combined['AgeGarage'] = combined.YrSold - combined.GarageYrBlt 

Столбец «AgeGarage», кажется, имеет несколько значений, около 2000! Это связано с тем, что отсутствующим значениям GarageYrBlt был имплантирован ноль, как упоминалось в предыдущем разделе. Все эти аномальные значения заменяются максимально возможным значением ниже определенного порога в соответствии с приведенным ниже кодом:

max_AgeGarage = np.max(combined.AgeGarage[combined.AgeGarage < 1000])
combined['AgeGarage'] = combined['AgeGarage'].map(lambda x: max_AgeGarage if x > 1000 else x)

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

Преобразование категориальных столбцов в фиктивные столбцы

Категориальные особенности теперь можно преобразовать в фиктивные столбцы с помощью примера кода:

Foundation_dummies=pd.get_dummies(combined['Foundation'], prefix='Foundation_', drop_first=True)
combined=pd.concat([combined, Foundation_dummies], axis=1)
combined.drop('Foundation', axis=1, inplace=True)

Аргумент префикса помогает позже идентифицировать столбцы. Нам нужно оставить только k-1 столбцов из k столбцов, поэтому drop_first имеет значение True. Подробнее об аргументах читайте здесь.

Набор данных dummies объединяется с исходным набором данных, а фактический столбец удаляется.

Набор данных почти готов, но еще не готов.

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

Удаление неважных функций

Вопрос в том, как решить, какую функцию отбросить? К счастью, у XGBoost есть ответ.

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

X = combined[:-1459]
y = targets
y = y.drop(index_drop).reset_index(drop=True)
xgb = XGBRegressor()
xgb.fit(X, y)

Затем значения признаков рассчитываются и сортируются:

imp = pd.DataFrame(xgb.feature_importances_ ,columns = ['Importance'],index = X.columns)
imp = imp.sort_values(['Importance'], ascending = False)
print(imp)

Результат примерно такой:

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

RFECV точно сообщает нам, сколько и какие функции следует сохранить. У него есть аргумент оценки, для которого я использовал RMSE (среднеквадратичную ошибку), поскольку упражнение Kaggle также будет оценивать на тех же условиях. Следующая функция определяет rmse, а также делает его счетчиком:

def rmse(y_true, y_pred):
    return np.sqrt(np.mean((y_true-y_pred)**2))
rmse = make_scorer(rmse, greater_is_better=False)

Поскольку цель состоит в том, чтобы уменьшить среднеквадратичное значение, аргумент «better_is_better» устанавливается в значение «Ложь». Наконец, RFECV подходит для наборов данных X и Y и дает нам оптимальное количество функций, равное 67!

rfecv = RFECV(estimator=xgb, step=1, cv=3, n_jobs=-1, scoring=rmse)
rfecv = rfecv.fit(X, y)
print("Optimal number of features : %d" % rfecv.n_features_)

График функций и оценок перекрестной проверки дает нам график ниже:

plt.figure()
plt.xlabel("Number of features selected")
plt.ylabel("Cross validation score")
plt.xticks(np.arange(0,200,10))
plt.plot(range(1, len(rfecv.grid_scores_) + 1), rfecv.grid_scores_)
plt.show()

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

features_kept = X.columns.values[rfecv.support_] 
X = X[features_kept]

Особенности X:

LotFrontage
LotArea
Alley
OverallQual
OverallCond
MasVnrArea
ExterQual
ExterCond
BsmtQual
BsmtCond
BsmtExposure
BsmtFinType1
BsmtFinSF1
BsmtFinSF2
BsmtUnfSF
TotalBsmtSF
HeatingQC
CentralAir
1stFlrSF
2ndFlrSF
GrLivArea
BsmtFullBath
FullBath
HalfBath
KitchenAbvGr
KitchenQual
Functional
FireplaceQu
GarageType
GarageFinish
GarageCars
GarageArea
GarageQual
GarageCond
PavedDrive
WoodDeckSF
OpenPorchSF
EnclosedPorch
ScreenPorch
PoolArea 
Fence
Age
AgeRemod
AgeGarage
MSZoning__FV
MSZoning__RL
MSZoning__RM
LotConfig__CulDSac
Street__Pave
Foundation__CBlock
Neighborhood__BrkSide
Neighborhood__ClearCr
Neighborhood__Crawfor
Neighborhood__OldTown
Neighborhood__Sawyer
Neighborhood__StoneBr
Condition1__Norm
Exterior1st__BrkComm
Exterior1st__BrkFace
Exterior1st__HdBoard
Exterior2nd__BrkFace
Exterior2nd__Wd Shng
HouseStyle__SLvl
SaleType__New
SaleCondition__Family
SaleCondition__Normal
MSSubClass__class2

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

Следовательно, PoolArea можно отбросить.

Во-вторых, есть некоторые переменные, коррелированные друг с другом. Следовательно, имеет смысл оставить только один из них. При запуске команды (см. Последнюю статью на EDA):

print(corr['SalePrice'].sort_values(ascending=False))
SalePrice        1.000000
OverallQual      0.790982
GrLivArea        0.708624
GarageCars       0.640409
GarageArea       0.623431
TotalBsmtSF      0.613581
1stFlrSF         0.605852
FullBath         0.560664
TotRmsAbvGrd     0.533723
YearBuilt        0.522897
YearRemodAdd     0.507101
GarageYrBlt      0.486362
MasVnrArea       0.477493
Fireplaces       0.466929
BsmtFinSF1       0.386420
LotFrontage      0.351799
WoodDeckSF       0.324413
2ndFlrSF         0.319334
OpenPorchSF      0.315856
HalfBath         0.284108
LotArea          0.263843
BsmtFullBath     0.227122
BsmtUnfSF        0.214479
BedroomAbvGr     0.168213
ScreenPorch      0.111447
PoolArea         0.092404
MoSold           0.046432
3SsnPorch        0.044584
BsmtFinSF2      -0.011378
BsmtHalfBath    -0.016844
MiscVal         -0.021190
Id              -0.021917
LowQualFinSF    -0.025606
YrSold          -0.028923
OverallCond     -0.077856
MSSubClass      -0.084284
EnclosedPorch   -0.128578
KitchenAbvGr    -0.135907

Следовательно, GarageArea, 1stFlrSF, вероятно, можно будет исключить, и у нас останется 64 функции.

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

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

Всегда приветствуются предложения по дальнейшему улучшению проектирования функций!