През повечето време необработените данни са лош вход, те съдържат ценна информация, но са скрити от проектирането на добри модели за машинно обучение, докато не бъдат обработени във форма, по-удобна за използване, това е мястото, където силата на инженерството на функции влиза в игра.
Инженерингът на функциите е от основно значение за направата на моделите за машинно обучение да правят точни прогнози, но също така отнема много време задача, ние обикновено посвещаваме всяка част от нашето време и големи усилия, за да обработим променливите в нашия набор от данни, преди да можем да ги използваме.

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

Ще използвам три различни набора от данни:

  • Набор от данни за Титаник
  • Набор от данни за цените на жилищата
  • Бостънски набор от данни

Можете също да проверите този GitHub Качих някои полезни кодове

https://github.com/MaissaDataScience

И така, да започваме! Донесете топла напитка, отворете своя бележник Jupyter и нека КОДИРАМЕ

Техники за инженерни функции

· 1.Импутация на липсващи данни
· 2.Категорично кодиране
· 3.Трансформация на променливи
· 4.Дискретизация
· 5.Инженеринг на извънредни стойности
· 6.Мащабиране на функции
· 7.Инженеринг на смесени променливи
· 8.Инженеринг на дата-час

Първо, трябва да импортирате библиотеки Pandas, Numpy

import pandas 
import numpy 

1. Липсващи импутирани данни

Импутирането е актът на заместване на липсващите данни със статистически оценки на липсващите стойности. Целта на всяка техника за импутиране е да се създаде пълен набор от данни, който може да се използва за обучение на модели за машинно обучение.

data[['LotFrontage', 'MasVnrArea', 'GarageYrBlt']].isnull().mean()
LotFrontage    0.177397
MasVnrArea     0.005479
GarageYrBlt    0.055479
dtype: float64
def impute_na(df, variable, mean_median): 
   return df[variable].fillna(mean_median)

Средно/медианно импутиране

Вменяването на средна стойност / медиана се състои от заместване на всички случаи на липсващи стойности (NA) в рамките на променлива със средната стойност (ако променливата има Гаусово разпределение) или медианата (ако променливата има изкривено разпределение). може да се изчисли върху непрекъснати и дискретни числови променливи

median = data['LotFrontage'].median()
data[:,'LotFrontage_median'] = impute_na(data, 'LotFrontage',median)
mean = data['MasVnrArea'].mean() 
data[:,'MasVnrArea_median'] = impute_na(data, 'MasVnrArea', median)

Начисляване на произволна стойност

Вменяването на произволна стойност се състои от замяна на всички появявания на липсващи стойности (NA) в рамките на променлива с произволна стойност. Обикновено използваните произволни стойности са 0, 999, -999 (или други комбинации от 9s) или -1 (ако разпределението е положително) или 1.

data['Age_99'] = impute_na(data, 'age', 99)
data['fare'] = impute_na(X_train, 'fare', 0)

Край на условното разпределение

Определянето на стойността на произволната стойност може да бъде трудоемко и обикновено е ръчна работа. Можем да автоматизираме този процес, като автоматично избираме произволни стойности в края на разпределението на променливите.

  • Ако променливата е нормално разпределена, можем да използваме средната стойност плюс или минус 3 пъти стандартното отклонение
  • Ако променливата е изкривена, можем да използваме правилото за близост на IQR
# Because Age looks approximately Gaussian, I use the # mean and std to calculate the replacement value 
data.age.mean() + 3 *data.age.std()
data['Age_imputed'] = impute_na(data,'age',data.age.mean() + 3 * data.age.std())

#Calculate the IQR
IQR =data['LotFrontage'].quantile(0.75)-data['LotFrontage']
.quantile(0.25)
# calculate the upper boundary 
extreme_value =data['LotFrontage'].quantile(0.75) + 3 * IQRdata.loc[:,'LotFrontage_imputed'] = impute_na(data, 'LotFrontage', extreme_value)

Често приписване на категория | Режим импутация

Вменяването на режима се състои от заместване на всички случаи на липсващи стойности (NA) в рамките на променлива с режима, който с други думи се отнася до най-честата стойност или най-честата категорияна практика , ние използваме тази техника само при категорични

# Let's find the most frequent category for BsmtQual  data['BsmtQual'].mode()
0    TA dtype: object
data['BsmtQual'].fillna('TA', inplace=True)
0    TA dtype: object
data['BsmtQual'].fillna('TA', inplace=True)

2. Категорично кодиране

Едно горещо кодиране

Едно горещо кодиране се състои от кодиране на всяка категорична променлива с различни булеви променливи (наричани още фиктивни променливи), които приемат стойности 0 или 1, показващи дали дадена категория присъства в наблюдението.

Например, за категориалната променлива „Пол“ с етикети „женски“ и „мъжки“, можем да генерираме булевата променлива „женски“, която приема 1, ако лицето е „женско“ или 0 в противен случай, или можем да генерираме променлива „мъж“, която приема 1, ако лицето е „мъж“ и 0 в противен случай.

#With sklearn
from sklearn.preprocessing import OneHotEncoder
encoder = OneHotEncoder(categories='auto',drop='first',sparse=False,                                              handle_unknown='ignore')
tmp = encoder.transform(data.fillna('Missing'))
#With get dummies
tmp = pd.get_dummies(data['sex'])

Едно горещо кодиране на често срещани категории

OHE на чести или топ категории е еквивалентно на групиране на всички останали категории в нова категория, можем да създадем фиктивни променливи само за най-честите категории

Целочислено кодиране

Целочисленото кодиране се състои от заместване на категориите с цифри от 1 до n (или от 0 до n-1, в зависимост от изпълнението), където n е броят на отделните категории на променливата.

#first let's create a dictionary with the mappings of categories to numbers
ordinal_mapping = {     k: i     for i, k in enumerate(X_train['Neighborhood'].unique(), 0) }
# replace the labels with the integers
data['Neighborhood'] = data['Neighborhood'].map(ordinal_mapping)

Брой или честотно кодиране

При кодирането на броя заместваме категориите с броя на наблюденията, които показват тази категория в набора от данни. По същия начин можем да заменим категорията с честотата - или процента - на наблюденията в набора от данни. Тоест, ако 10 от нашите 100 наблюдения показват син цвят, ние ще заменим синьото с 10, ако правим кодиране с броене, или с 0,1, ако сменим с честотата.

# replace the labels with the counts
count_map = data['Neighborhood'].value_counts().to_dict()
data['Neighborhood'] =data['Neighborhood'].map(count_map)
# replace the labels with the frequencies
frequency_map = (data['Exterior1st'].value_counts() / data) ).to_dict()
data['Exterior1st'] = data['Exterior1st'].map(frequency_map)

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

  • Подредено целочислено кодиране
  • Средно кодиране
  • Кодиране на коефициент на вероятност
  • Кодиране на теглото на доказателствата

Тези методи ни позволяват да улавяме информация, докато предварително обработваме етикетите на категоричните променливи, те създават монотонна връзка между променливата и целта.

ще разгледаме един от тях, който е средното кодиране

Средното кодиране предполага замяна на категорията със средната целева стойност за тази категория. Например, ако имаме променливата град с категории Лондон, Манчестър и Бристол и искаме да прогнозираме процента по подразбиране, ако процентът по подразбиране за Лондон е 30%, заместваме Лондон с 0,3, ако процентът по подразбиране за Манчестър е 20 % заместваме Манчестър с 0,2 и т.н.

data.groupby(['cabin'])['survived'].mean()
ordered_labels =data.groupby(['cabin'])['survived'].mean().to_dict()
data['cabin'] = data['cabin'].map(ordered_labels)

3. Трансформация на променливи

Някои модели на машинно обучение като линейна и логистична регресия предполагат, че променливите са нормално разпределени. Често променливите не са нормално разпределени, но трансформирането на променливите, за да картографира тяхното разпределение към разпределение на Гаус, може и често го прави, да повиши производителността на алгоритъма за машинно обучение.

Ако една променлива не е нормално разпределена, често е възможно да се намери математическа трансформация, за да се нормализира нейното разпределение.

Логаритмична трансформация — np.log(X)

data['GrLivArea_log'] = np.log(data['GrLivArea'])

Реципрочна трансформация — 1 / X

data['GrLivArea_reciprocal'] = 1 / (data['GrLivArea'])

Трансформация на корен квадратен — X**(1/2)

data['GrLivArea_sqr'] = data['GrLivArea']**(1/2)

Експоненциален

data['GrLivArea_exp'] = data['GrLivArea']**(1/1.5)

Трансформация на Бокс-Кокс

Трансформацията на Бокс-Кокс се определя като:

T(Y)=(Y exp(λ)−1)/λ, ако λ!=0, или log(Y) в противен случай.

където Y е променливата на реакцията и λ е параметърът на трансформацията. λ варира от -5 до 5. Накратко, за всеки λ (трансформацията тества няколко λs) се изчислява коефициентът на корелация на вероятностната графика (Q-Q графика по-долу, корелацията между подредени стойности и теоретични квантили) (това уравнение за оптимизация всъщност варира с внедряването). Тогава стойността на λ, съответстваща на максималната корелация на диаграмата, е оптималният избор за λ

import scipy.stats as stats
data['GrLivArea_boxcox'], param = stats.boxcox(data['GrLivArea'])

Трансформация на Йео-Джонсън

Yeo-Johnson е същото като Box-Cox за положителните стойности на променливата, но има различни уравнения за отрицателните стойности на променливата, както е описано. Отново функцията търси в куп λ и избира този, който връща най-добрия отговарят на нормално разпределение.

data['Gr_yeojohnson'], param = stats.yeojohnson(data['GrLivArea'])

4.Дискретизация

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

Има няколко подхода за трансформиране на непрекъснати променливи в дискретни. Методите за дискретизация попадат в 2 категории: надзиравани и ненаблюдавани. Неконтролираните методи не използват никаква информация, освен разпределението на променливите, за създаване на съседни контейнери, в които ще бъдат поставени стойностите. Контролираните методи обикновено използват целева информация, за да създадат контейнери или интервали.

Дискретизация с еднаква ширина

Дискретизацията с еднаква ширина разделя обхвата на възможните стойности на N контейнера с еднаква ширина. Ширината се определя от диапазона от стойности в променливата и броя на контейнерите, които искаме да използваме, за да разделим променливата:

ширина = (макс. стойност — мин. стойност) / N и N е броят на контейнерите или интервалите.

# let's capture the range of the variable age
age_range = X_train['age'].max() - X_train['age'].min()
# let's divide the range into 10 equal width bins
age_range / 10
# now let's capture the lower and upper boundaries
min_value = int(np.floor( X_train['age'].min()))
max_value = int(np.ceil( X_train['age'].max()))
# let's round the bin width
inter_value = int(np.round(age_range / 10))
# let's capture the interval limits, so we can pass them to the pandas cut
# function to generate the bins
intervals = [i for i in range(min_value, max_value+inter_value, inter_value)]
# let's make labels to label the different bins
labels = ['Bin_' + str(i) for i in range(1, len(intervals))]
# create binned age / discretise age
# create one column with labels
data['Age_disc_labels'] = pd.cut(x=data['age'],bins=intervals, labels=labels, include_lowest=True)
# and one with bin boundaries
data['Age_disc'] = pd.cut(x=data['age'],bins=intervals include_lowest=True)

Дискретизация с k-означава групиране

Този метод на дискретизация се състои в прилагане на групиране на k-средни стойности към непрекъснатата променлива.

Накратко, алгоритъмът работи по следния начин:

  • 1) Инициализация: произволно създаване на K центрове
  • 2) Всяка точка от данни е свързана с най-близкия център
  • 3) Всяка централна позиция се преизчислява като център на свързаните с нея точки

Стъпки 2 и 3 се повтарят до достигане на конвергенция. Алгоритъмът минимизира двойните квадратни отклонения на точки в рамките на един и същи клъстер.

from sklearn.preprocessing import KBinsDiscretizer
disc = KBinsDiscretizer(n_bins=5, encode='ordinal', strategy='kmeans')
disc.fit(data[['age', 'fare']])
data_t = disc.transform(data[['age', 'fare']])
data_t = pd.DataFrame(data_t, columns = ['age', 'fare'])

Дискретизация с дървета на решенията

Дискретизацията с дървета на решенията се състои от използване на дърво на решенията за идентифициране на оптималните контейнери. Когато дървото на решенията вземе решение, то присвоява наблюдение на един от n крайни листа. Следователно всяко дърво на решенията ще генерира отделен изход, чиито стойности са прогнозите на всеки от неговите n листа.

5. Outlier Engineering

Извънредната стойност е точка от данни, която е значително различна от останалите данни.

Статистически данни като средна стойност и дисперсия са много податливи на отклонения. Освен това някои модели на машинно обучение са чувствителни към отклонения, което може да намали ефективността им. По този начин, в зависимост от това кой алгоритъм искаме да обучим, ние често премахваме извънредните стойности от нашите променливи.

Проверете отклоненията

#Visualise Outlier with Boxplot
plt.subplot(1, 3, 3)
sns.boxplot(y=df[variable])
plt.title('Boxplot')
plt.show()

Подстригване

Изрязването, известно още като отрязване, включва премахване на извънредните стойности от набора от данни. Трябва само да вземем решение за показател, за да определим отклоненията.

# Let's calculate the boundaries outside which sit the outliers
# for skewed distributions distance passed as an argument, gives us the option to estimate 1.5 times or 3 times the IQR to calculate
the boundaries.
def find_skewed_boundaries(df, variable, distance):
  IQR = df[variable].quantile(0.75) - df[variable].quantile(0.25)
  lower_boundary = df[variable].quantile(0.25) - (IQR * distance)
  upper_boundary = df[variable].quantile(0.75) + (IQR * distance)return upper_boundary, lower_boundary
# find limits for RM
RM_upper_limit, RM_lower_limit = find_skewed_boundaries(boston, 'RM', 1.5)
# let's flag the outliers in the data set
outliers_RM = np.where(boston['RM'] > RM_upper_limit, True, np.where(boston['RM'] < RM_lower_limit, True, False))
# let's trimm the dataset
boston_trimmed = boston.loc[~(outliers_RM + outliers_LSTAT + outliers_CRIM), ]

Цензуриране или ограничаване

Цензуриране или ограничаване означава ограничаване на максимума и/или минимума на разпространение на произволна стойност. С други думи, стойности, по-големи или по-малки от произволно определените, се цензурират. Може да се направи и на двете опашки или само на една от опашките, в зависимост от променливата и потребителя.

Ограничаване на IQR правило за близост

def find_skewed_boundaries(df, variable, distance):
IQR = df[variable].quantile(0.75) - df[variable].quantile(0.25)      lower_boundary = df[variable].quantile(0.25) - (IQR * distance)
upper_boundary = df[variable].quantile(0.75) + (IQR * distance)
return upper_boundary, lower_boundary
# find limits for RM
RM_upper_limit, RM_lower_limit = find_skewed_boundaries(boston, 'RM', 1.5)
# let's flag the outliers in the data set
outliers_RM = np.where(boston['RM'] > RM_upper_limit, True, np.where(boston['RM'] < RM_lower_limit, True, False))
# Now let's replace the outliers by the maximum and minimum limit
boston['RM']= np.where(boston['RM'] > RM_upper_limit, RM_upper_limit, np.where(boston['RM'] < RM_lower_limit, RM_lower_limit, boston['RM']))

Ограничаване на апроксимацията на Гаус

# calculate the boundaries outside which sit the outliers
# for a Gaussian distribution
def find_normal_boundaries(df, variable, distance):
upper_boundary = df[variable].mean() + distance * df[variable].std()
lower_boundary = df[variable].mean() - distance * df[variable].std()
return upper_boundary, lower_boundary

# find limits for RM
RM_upper_limit, RM_lower_limit = find_skewed_boundaries(boston, 'RM', 1.5)
RM_upper_limit, RM_lower_limit

# Now let's replace the outliers by the maximum and minimum limitboston['RM']= np.where(boston['RM'] > RM_upper_limit, RM_upper_limit,np.where(boston['RM'] < RM_lower_limit, RM_lower_limit, boston['RM']))

Ограничаване на квантилите

# the boundaries are the quantiles
def find_boundaries(df, variable):
 lower_boundary = df[variable].quantile(0.05)
 upper_boundary = df[variable].quantile(0.95)
return upper_boundary, lower_boundary
# find limits for RM
RM_upper_limit, RM_lower_limit = find_boundaries(boston, 'RM')
# Now let's replace the outliers by the maximum and minimum limit
boston['RM']= np.where(boston['RM'] > RM_upper_limit, RM_upper_limit, np.where(boston['RM'] < RM_lower_limit, RM_lower_limit, boston['RM']))

6. Мащабиране на функции

Повечето от данните съдържат характеристики, силно вариращи по величини, единици и обхват повечето от алгоритмите за машинно обучение за регресия използват евклидово разстояние между две точки от данни в своите изчисления, това е проблем и коефициентът на регресия е пряко повлиян от мащаба на променливата Мащабиране на характеристиките се отнася до методите или техниките, използвани за нормализиране на диапазона от независими променливи в нашите данни, или с други думи, методите за задаване на диапазона на стойностите на характеристиките в рамките на подобен мащаб

Стандартизация

Стандартизацията включва центриране на променливата на нула и стандартизиране на дисперсията до 1. Процедурата включва изваждане на средната стойност на всяко наблюдение и след това разделяне на стандартното отклонение:

z = (x — x_средно) / std

from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
scaler.fit(X_train)
X_train_scaled = scaler.transform(X_train)
X_test_scaled = scaler.transform(X_test)

Средна нормализация

Нормализирането на средната стойност включва центриране на променливата на нула и повторно мащабиране до диапазона на стойностите, нейните минимални и максимални стойности са в диапазона от -1 до 1. Процедурата включва изваждане на средната стойност на всяко наблюдение и след това разделяне на разликата между минималната и максимална стойност:

x_scaled = (x — x_mean) / (x_max — x_min)

from sklearn.preprocessing import StandardScaler ,  RobustScaler
scaler_mean = StandardScaler(with_mean=True, with_std=False)
# set up the robustscaler so that it does NOT remove the median
# but normalises by max()-min(), important for this to set up the
# quantile range to 0 and 100, which represent the min and max values
scaler_minmax = RobustScaler(with_centering=False,with_scaling=True, quantile_range=(0, 100))
scaler_mean.fit(X_train)
scaler_minmax.fit(X_train)
X_train_scaled=scaler_minmax.transform(scaler_mean.transform(X_train))
X_test_scaled=scaler_minmax.transform(scaler_mean.transform(X_test))

Минималното и максималното мащабиране свива стойностите между 0 и 1, но средната стойност не е центрирана на нула и стандартното отклонение варира в различните променливи. Той изважда минималната стойност от всички наблюдения и след това я разделя на диапазона на стойността:

X_мащабирано = (X — X.min )/ (X.max — X.min)

from sklearn.preprocessing import StandardScaler
scaler_mean = StandardScaler(with_mean=True, with_std=False)
# set up the robustscaler so that it does NOT remove the median
# but normalises by max()-min(), important for this to set up the
# quantile range to 0 and 100, which represent the min and max values
scaler_minmax = RobustScaler(with_centering=False,with_scaling=True, quantile_range=(0, 100))
scaler_mean.fit(X_train)
scaler_minmax.fit(X_train)
X_train_scaled=scaler_minmax.transform(scaler_mean.transform(X_train))
X_test_scaled=scaler_minmax.transform(scaler_mean.transform(X_test))

Мащабиране до квантили и медиана — RobustScaling

При тази процедура медианата се премахва от наблюденията и след това те се мащабират до междуквантилния диапазон (IQR). IQR е диапазонът между 1-ви квартил (25-ти квантил) и 3-ти квартил (75-ти квантил).

X_scaled = X — X_median / ( X.quantile(0,75) — X.quantile(0,25) )

from sklearn.preprocessing import RobustScaler
scaler = RobustScaler()
scaler.fit(X_train)
X_train_scaled = scaler.transform(X_train)
X_test_scaled = scaler.transform(X_test)

7. Инженеринг на смесени променливи

Смесени променливи са тези, чиито стойности съдържат както числа, така и етикети. Смесена променлива може да съдържа числа или етикети в различни наблюдения или числа и етикети във всяко наблюдение.

Например, променливата регистрация на превозното средство е пример за букви и цифри, комбинирани във всяко наблюдение (напр. NK11DGX)

Наблюденията на променливата съдържат или числа, или низове

data['open_il_24m'].unique()
array(['C', 'A', 'B', '0.0', '1.0', '2.0', '4.0', '3.0', '6.0', '5.0',        '9.0', '7.0', '8.0', '13.0', '10.0', '19.0', '11.0', '12.0',        '14.0', '15.0'], dtype=object)
# extract numerical part
data['open_il_24m_numerical'] =   pd.to_numeric(data["open_il_24m"],errors='coerce',downcast='integer' )
# extract categorical part
data['open_il_24m_categorical']=np.where(data['open_il_24m_numerical'].isnull(),data['open_il_24m'],np.nan)

Наблюденията на променливата съдържат числа и низове

data['cabin'].unique()

array(['B5', 'C22', 'E12', 'D7', 'A36', 'C101', nan, 'C62', 'B35', 'A23','B58', 'D15', 'C6', 'D35', 'C148', 'C97', 'B49', 'C99', 'C52', 'T','A31', 'C7', 'C103', 'D22', 'E33', 'A21', 'B10', 'B4', 'E40',        'B38', 'E24', 'B51', 'B96', 'C46', 'E31', 'E8', 'B61', 'B77', 'A9',        'C89', 'A14', 'E58', 'E49', 'E52', 'E45', 'B22', 'B26', 'C85',        'E17', 'B71', 'B20', 'A34', 'C86', 'A16', 'A20', 'A18', 'C54',        'C45', 'D20', 'A29', 'C95', 'E25', 'C111', 'C23', 'E36', 'D34',])
# let's extract the numerical and categorical part for cabin
data['cabin_num'] = data['cabin'].str.extract('(\d+)') 
data['cabin_cat'] = data['cabin'].str[0] # captures the first letter

8. Дата-час Инженеринг

Инженерни дати

Променливите за дата са специален тип категориална променлива. По своята същност променливите за дата ще съдържат множество различни етикети, всеки от които съответства на определена дата и понякога час. Променливите за дата, когато са предварително обработени правилно, могат значително да обогатят набор от данни. Например, от променлива за дата можем да извлечем: ден, месец, година, седмица, семестър...

import datetime
# let's parse the dates, currently cast as strings, into datetime format
data['issue_dt'] = pd.to_datetime(data.issue_d)
# Extracting week of year from date, varies from 1 to 52
data['issue_dt_week'] = data['issue_dt'].dt.week
#Extracting month from date - 1 to 12
data['issue_dt_month'] = data['issue_dt'].dt.month

Инженерно време

Инженерното време е да извлечем различни начини за представяне на времето от клеймо за време, можем да извлечем например час, минута, секунда, изминало време...

import datetime
df['hour'] = df['date'].dt.hour
df['min'] = df['date'].dt.minute
df['sec'] = df['date'].dt.second

Референции и допълнителни ресурси за четене:

https://www.udemy.com/course/feature-engineering-for-machine-learning/

https://towardsdatascience.com/smarter-ways-to-encode-categorical-data-for-machine-learning-part-1-of-3-6dca2f71b159

https://towardsdatascience.com/6-different-ways-to-compensate-for-missing-values-data-imputation-with-examples-6022d9ca0779

https://medium.com/@Cambridge_Spark/tutorial-introduction-to-missing-data-imputation-4912b51c34eb

http://www.mtome.com/Publications/CiML/CiML-v3-book.pdf

https://blog.zopa.com/2017/07/20/tips-honing-logistic-regression-models/

https://papers.ssrn.com/sol3/papers.cfm?abstract_id=1431352

https://towardsdatascience.com/ways-to-detect-and-remove-the-outliers-404d16608dba

https://sebastianraschka.com/Articles/2014_about_feature_scaling.html

https://scikitlearn.org/stable/auto_examples/preprocessing/plot_all_scalin.html#sphx-glr-auto-examples-preprocessing-plot-all-scaling-py