През повечето време необработените данни са лош вход, те съдържат ценна информация, но са скрити от проектирането на добри модели за машинно обучение, докато не бъдат обработени във форма, по-удобна за използване, това е мястото, където силата на инженерството на функции влиза в игра.
Инженерингът на функциите е от основно значение за направата на моделите за машинно обучение да правят точни прогнози, но също така отнема много време задача, ние обикновено посвещаваме всяка част от нашето време и големи усилия, за да обработим променливите в нашия набор от данни, преди да можем да ги използваме.
В тази статия ще разгледам някои от най-полезните техники, които ще помогнат особено на всеки начинаещ в машинното обучение да подобри своя модел, като избере „най-добрите“ техники.
Ще използвам три различни набора от данни:
- Набор от данни за Титаник
- Набор от данни за цените на жилищата
- Бостънски набор от данни
Можете също да проверите този 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://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