Создание функций, которые могут помочь алгоритмам машинного обучения работать
Этот шаг делает ваши данные готовыми для окончательного обучения. Подход к проектированию функций основан на хорошем понимании данных, как упоминалось в моей предыдущей статье об 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 функции.
Отсюда вывод: нельзя слепо смотреть на результаты какой-либо функции. Мы должны каждый момент спрашивать себя, имеет ли это смысл. Поэтому говорят, что достижение высокой точности с помощью машинного обучения - это не просто наука, это искусство. Разработка функций часто является итеративным процессом, и можно дополнительно изменить набор данных для достижения более высокой точности при реализации модели.
Я продолжил работу с этим набором данных и тренировался с разными алгоритмами, о которых я расскажу в своем следующем блоге.
Всегда приветствуются предложения по дальнейшему улучшению проектирования функций!