В четвертой части знакомства с автоматизированное машинное обучение будем работать с библиотекой TROT по книге "Radečić Dario. Machine Learning Automation with TPOT: Build, validate, and deploy fully automated machine learning models with Python".
Работаем с тремя наборами данных : рыбный рынок, страхование и транспортные средства.
Species | Weight | Length1 | Length2 | Length3 | Height | Width | |
---|---|---|---|---|---|---|---|
0 | Bream | 242.0 | 23.2 | 25.4 | 30.0 | 11.5200 | 4.0200 |
1 | Bream | 290.0 | 24.0 | 26.3 | 31.2 | 12.4800 | 4.3056 |
2 | Bream | 340.0 | 23.9 | 26.5 | 31.1 | 12.3778 | 4.6961 |
3 | Bream | 363.0 | 26.3 | 29.0 | 33.5 | 12.7300 | 4.4555 |
4 | Bream | 430.0 | 26.5 | 29.0 | 34.0 | 12.4440 | 5.1340 |
Species 0 Weight 0 Length1 0 Length2 0 Length3 0 Height 0 Width 0 dtype: int64
Пропущенных значений нет. Это делает процесс подготовки данных гораздо проще
и короче. Следующим шагом является проверка распределения целевой переменной.
Для этого набора данных мы пытаемся предсказать вес. Нарисуем простою
гистограмму для этой переменной
df['Weight'].hist(bins=20, figsize = (12,8))
plt.title('Target variable (Weight) distribution', size=20)
plt.xlabel('Weight', size=14)
plt.ylabel('Count', size=14)
============ BREAM ============ Count: 35 Pct. total: 22.01% Avg. Weight: 617.83 +/- 209.21 Avg. Length1: 30.31 +/- 3.59 Avg. Length2: 33.11 +/- 3.91 Avg. Length3: 38.35 +/- 4.16 Avg. Height: 15.18 +/- 1.96 Avg. Width: 5.43 +/- 0.72 ============ ROACH ============ Count: 20 Pct. total: 12.58% Avg. Weight: 152.05 +/- 88.83 Avg. Length1: 20.65 +/- 3.46 Avg. Length2: 22.28 +/- 3.65 Avg. Length3: 24.97 +/- 4.03 Avg. Height: 6.69 +/- 1.26 Avg. Width: 3.66 +/- 0.69 ============ WHITEFISH ============ Count: 6 Pct. total: 3.77% Avg. Weight: 531.0 +/- 309.6 Avg. Length1: 28.8 +/- 5.58 Avg. Length2: 31.32 +/- 5.72 Avg. Length3: 34.32 +/- 6.02 Avg. Height: 10.03 +/- 1.83 Avg. Width: 5.47 +/- 1.19 ============ PARKKI ============ Count: 11 Pct. total: 6.92% Avg. Weight: 154.82 +/- 78.76 Avg. Length1: 18.73 +/- 3.28 Avg. Length2: 20.35 +/- 3.56 Avg. Length3: 22.79 +/- 3.96 Avg. Height: 8.96 +/- 1.62 Avg. Width: 3.22 +/- 0.64 ============ PERCH ============ Count: 56 Pct. total: 35.22% Avg. Weight: 382.24 +/- 347.62 Avg. Length1: 25.74 +/- 8.56 Avg. Length2: 27.89 +/- 9.02 Avg. Length3: 29.57 +/- 9.53 Avg. Height: 7.86 +/- 2.88 Avg. Width: 4.75 +/- 1.77 ============ PIKE ============ Count: 17 Pct. total: 10.69% Avg. Weight: 718.71 +/- 494.14 Avg. Length1: 42.48 +/- 9.03 Avg. Length2: 45.48 +/- 9.71 Avg. Length3: 48.72 +/- 10.17 Avg. Height: 7.71 +/- 1.66 Avg. Width: 5.09 +/- 1.14 ============ SMELT ============ Count: 14 Pct. total: 8.81% Avg. Weight: 11.18 +/- 4.13 Avg. Length1: 11.26 +/- 1.22 Avg. Length2: 11.92 +/- 1.43 Avg. Length3: 13.04 +/- 1.43 Avg. Height: 2.21 +/- 0.35 Avg. Width: 1.34 +/- 0.29
Для кодирования используем функцию get_dummies. Параметр набор дамми-переменных drop_first задает тип дамми-кодирования. По умолчанию для этого параметра задано значение False и выполняется дамми-кодирование по методу неполного ранга, в противном случае будет выполнено дамми-кодирование по методу полного ранга. В нашем случае функция pd.get_dummies() автоматически преобразует заданную категориальную переменную методом полного ранга в набор из (n-1) дамми-переменных.
Is_Parkki | Is_Perch | Is_Pike | Is_Roach | Is_Smelt | Is_Whitefish | Weight | Length1 | Length2 | Length3 | Height | Width | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 242.0 | 23.2 | 25.4 | 30.0 | 11.5200 | 4.0200 |
1 | 0 | 0 | 0 | 0 | 0 | 0 | 290.0 | 24.0 | 26.3 | 31.2 | 12.4800 | 4.3056 |
2 | 0 | 0 | 0 | 0 | 0 | 0 | 340.0 | 23.9 | 26.5 | 31.1 | 12.3778 | 4.6961 |
3 | 0 | 0 | 0 | 0 | 0 | 0 | 363.0 | 26.3 | 29.0 | 33.5 | 12.7300 | 4.4555 |
4 | 0 | 0 | 0 | 0 | 0 | 0 | 430.0 | 26.5 | 29.0 | 34.0 | 12.4440 | 5.1340 |
((119,), (40,))
Далее создадим модель с алгоритмом линейной регрессии. Эта модель задаст базовый уровень, который должен превзойти TPOT
82.10709519987505
Базовая модель ошибается в среднем на 82 единицы веса. Неплохо, учитывая у нас вес до 1500. Далее подгоним модель оптимизации конвейера TPOT. Будем
использовать показатель RMSE и ограничим время оптимизации 10 минутами.
rmse_scorer = make_scorer(rmse, greater_is_better=False)
pipeline_optimizer = TPOTRegressor(
scoring=rmse_scorer,
max_time_mins=10,
random_state=42
)
pipeline_optimizer.fit(X_train, y_train)
TPOTRegressor(max_time_mins=10, random_state=42, scoring=make_scorer(<lambda>, greater_is_better=False))После завершения оптимизации вот вывод RMSE, который отображается в консоли:
pipeline_optimizer.score(X_test, y_test)
-78.16527159947128Не беспокойтесь о знаке минус перед числом. Фактическое среднеквадратичное
отклонение равно 73,35 единицы веса. Модель TPOT превзошла базовую. В качестве
последнего шага мы можем экспортировать конвейер в файл Python :
pipeline_optimizer.fitted_pipeline_
Pipeline(steps=[('polynomialfeatures', PolynomialFeatures(include_bias=False)), ('robustscaler', RobustScaler()), ('maxabsscaler', MaxAbsScaler()), ('stackingestimator-1', StackingEstimator(estimator=ExtraTreesRegressor(bootstrap=True, max_features=0.9000000000000001, min_samples_leaf=17, random_state=42))), ('stackingestimator-2', StackingEstimator(estimator=DecisionTreeRegressor(max_depth=10, min_samples_leaf=10, min_samples_split=9, random_state=42))), ('adaboostregressor', AdaBoostRegressor(learning_rate=0.5, loss='square', n_estimators=100, random_state=42))])
pipeline_optimizer.export('fish_pipeline.py')
Далее используем решение для автоматизированного машинного обучения к немного
более сложному набор данных. Это набор данных о стоимости медицинского
страхования (https://www.kaggle.com/mirichoi0218/insurance), чтобы предсказать, сколько страховка будет стоить на основе нескольких переменных-предикторов.
age | sex | bmi | children | smoker | region | charges | |
---|---|---|---|---|---|---|---|
0 | 19 | female | 27.900 | 0 | yes | southwest | 16884.92400 |
1 | 18 | male | 33.770 | 1 | no | southeast | 1725.55230 |
2 | 28 | male | 33.000 | 3 | no | southeast | 4449.46200 |
3 | 33 | male | 22.705 | 0 | no | northwest | 21984.47061 |
4 | 32 | male | 28.880 | 0 | no | northwest | 3866.85520 |
age 0 sex 0 bmi 0 children 0 smoker 0 region 0 charges 0 dtype: int64
Для визуализации нашего анализа используем функцию, которая делает столбчатую диаграмму для выбранного показателя. Она вычисляет медиану из сгруппированного набор данных и визуализирует гистограмму с заголовком, метками, легендой и текстом поверх столбца.
region_northwest | region_southeast | region_southwest | age | is_female | bmi | children | smoker | charges | |
---|---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 1 | 19 | 1 | 27.900 | 0 | 1 | 16884.92400 |
1 | 0 | 1 | 0 | 18 | 0 | 33.770 | 1 | 0 | 1725.55230 |
2 | 0 | 1 | 0 | 28 | 0 | 33.000 | 3 | 0 | 4449.46200 |
3 | 1 | 0 | 0 | 33 | 0 | 22.705 | 0 | 0 | 21984.47061 |
4 | 1 | 0 | 0 | 32 | 0 | 28.880 | 0 | 0 | 3866.85520 |
((1003,), (335,))
Создадим базовую модель с алгоритмом линейной регрессии. Её показатели TPOT
должен превзойти.
from sklearn.linear_model import LinearRegression
from sklearn.metrics import r2_score, mean_squared_error
rmse = lambda y, y_hat: np.sqrt(mean_squared_error(y, y_hat))
lm = LinearRegression()
lm.fit(X_train, y_train)
lm_preds = lm.predict(X_test)
print(f'R2 = {r2_score(y_test, lm_preds):.2f}')
print(f'RMSE = {rmse(y_test, lm_preds):.2f}')
R2 = 0.77 RMSE = 5926.02В среднем простая модель линейной регрессии ошибается на 5 926,02 доллара. Эта
модель фиксирует 77% дисперсии в наборе данных. Следующий фрагмент кода
выводит имя переменной и соответствующий ему коэффициенты:
for i, column in enumerate(df.columns[:-1]):
coef = np.round(lm.coef_[i], 2)
print(f'{column:17}: {coef:8}')
region_northwest : -355.15 region_southeast : -781.37 region_southwest : -860.27 age : 259.62 is_female : -45.62 bmi : 339.81 children : 426.5 smoker : 23630.4Как видите, колонка с наибольшим коэффициентом — курильщик. Что подтверждает
нашу визуализацию, сделанную на этапе исследовательского анализа данных. Далее
будем использовать библиотеку TPOT для создания автоматизированного конвейера
машинного обучения. Следующий фрагмент кода импортирует библиотеку TPOT,
создает ее экземпляр и конвейер оптимизации.
from tpot import TPOTRegressor
pipeline_optimizer = TPOTRegressor(
scoring='r2',
max_time_mins=10,
random_state=42,
verbosity=2
)
pipeline_optimizer.fit(X_train, y_train)
10 минут не хватило, вышло следующее сообщение10.02 minutes have elapsed. TPOT will close down. TPOT closed during evaluation in one generation. WARNING: TPOT may not provide a good pipeline if TPOT is stopped/interrupted in a early generation. TPOT closed prematurely. Will use the current best pipeline. Best pipeline: RandomForestRegressorОценка R2 на тестовом наборе может быть получена с помощью следующего кода:pipeline_optimizer.score(X_test, y_test)0.865150174670897Можете получить значения R2 и RMSE для тестового :
tpot_preds = pipeline_optimizer.predict(X_test)
print(f'R2 = {r2_score(y_test, tpot_preds):.2f}')
print(f'RMSE = {rmse(y_test, tpot_preds):.2f}')
R2 = 0.87 RMSE = 4510.84
В качестве последнего шага мы экспортируем оптимизированный конвейер в файл
Python.
Следующее фрагмент кода делает это:
pipeline_optimizer.export('insurance_pipeline.py')
0 | 1 | 2 | 3 | 4 | |
---|---|---|---|---|---|
name | Maruti Swift Dzire VDI | Skoda Rapid 1.5 TDI Ambition | Honda City 2017-2020 EXi | Hyundai i20 Sportz Diesel | Maruti Swift VXI BSIII |
year | 2014 | 2014 | 2006 | 2010 | 2007 |
selling_price | 450000 | 370000 | 158000 | 225000 | 130000 |
km_driven | 145500 | 120000 | 140000 | 127000 | 120000 |
fuel | Diesel | Diesel | Petrol | Diesel | Petrol |
seller_type | Individual | Individual | Individual | Individual | Individual |
transmission | Manual | Manual | Manual | Manual | Manual |
owner | First Owner | Second Owner | Third Owner | First Owner | First Owner |
mileage | 23.4 kmpl | 21.14 kmpl | 17.7 kmpl | 23.0 kmpl | 16.1 kmpl |
engine | 1248 CC | 1498 CC | 1497 CC | 1396 CC | 1298 CC |
max_power | 74 bhp | 103.52 bhp | 78 bhp | 90 bhp | 88.2 bhp |
torque | 190Nm@ 2000rpm | 250Nm@ 1500-2500rpm | 12.7@ 2,700(kgm@ rpm) | 22.4 kgm at 1750-2750rpm | 11.5@ 4,500(kgm@ rpm) |
seats | 5.0 | 5.0 | 5.0 | 5.0 | 5.0 |
name 0 year 0 selling_price 0 km_driven 0 fuel 0 seller_type 0 transmission 0 owner 0 mileage 221 engine 221 max_power 215 torque 222 seats 221 dtype: int64
Некоторые значения отсутствуют, и мы решим эту проблему с помощью самого простого подход – путем их удаления.
df.dropna(inplace=True)
df.isnull().sum()
name 0 year 0 selling_price 0 km_driven 0 fuel 0 seller_type 0 transmission 0 owner 0 mileage 0 engine 0 max_power 0 torque 0 seats 0 dtype: int64Список шагов, необходимых для того, чтобы сделать этот набор данных подходящим
для машинное обучение:
• Преобразование transmission в целое число – 1, механическая, 0 в противномслучае. Переименуйте столбец в is_manual.
• Переназначить столбец owner на целые числа.
• Извлечь марку автомобиля, пробег, двигатель и максимальную мощность из
соответствующих атрибутов
• Создать фиктивные переменные из имени атрибута, топлива и типа продавца.
• Объединить исходный набор данных с фиктивными переменными и удалить
ненужные атрибуты.
Вот код функции remap_owner():
0 | 1 | 2 | 3 | 4 | |
---|---|---|---|---|---|
year | 2014.0 | 2014.00 | 2006.0 | 2010.0 | 2007.0 |
selling_price | 450000.0 | 370000.00 | 158000.0 | 225000.0 | 130000.0 |
km_driven | 145500.0 | 120000.00 | 140000.0 | 127000.0 | 120000.0 |
is_manual | 1.0 | 1.00 | 1.0 | 1.0 | 1.0 |
owner | 1.0 | 2.00 | 3.0 | 1.0 | 1.0 |
mileage | 23.4 | 21.14 | 17.7 | 23.0 | 16.1 |
engine | 1248.0 | 1498.00 | 1497.0 | 1396.0 | 1298.0 |
max_power | 74.0 | 103.52 | 78.0 | 90.0 | 88.2 |
seats | 5.0 | 5.00 | 5.0 | 5.0 | 5.0 |
brand_Ashok | 0.0 | 0.00 | 0.0 | 0.0 | 0.0 |
brand_Audi | 0.0 | 0.00 | 0.0 | 0.0 | 0.0 |
brand_BMW | 0.0 | 0.00 | 0.0 | 0.0 | 0.0 |
brand_Chevrolet | 0.0 | 0.00 | 0.0 | 0.0 | 0.0 |
brand_Daewoo | 0.0 | 0.00 | 0.0 | 0.0 | 0.0 |
brand_Datsun | 0.0 | 0.00 | 0.0 | 0.0 | 0.0 |
brand_Fiat | 0.0 | 0.00 | 0.0 | 0.0 | 0.0 |
brand_Force | 0.0 | 0.00 | 0.0 | 0.0 | 0.0 |
brand_Ford | 0.0 | 0.00 | 0.0 | 0.0 | 0.0 |
brand_Honda | 0.0 | 0.00 | 1.0 | 0.0 | 0.0 |
brand_Hyundai | 0.0 | 0.00 | 0.0 | 1.0 | 0.0 |
brand_Isuzu | 0.0 | 0.00 | 0.0 | 0.0 | 0.0 |
brand_Jaguar | 0.0 | 0.00 | 0.0 | 0.0 | 0.0 |
brand_Jeep | 0.0 | 0.00 | 0.0 | 0.0 | 0.0 |
brand_Kia | 0.0 | 0.00 | 0.0 | 0.0 | 0.0 |
brand_Land | 0.0 | 0.00 | 0.0 | 0.0 | 0.0 |
brand_Lexus | 0.0 | 0.00 | 0.0 | 0.0 | 0.0 |
brand_MG | 0.0 | 0.00 | 0.0 | 0.0 | 0.0 |
brand_Mahindra | 0.0 | 0.00 | 0.0 | 0.0 | 0.0 |
brand_Maruti | 1.0 | 0.00 | 0.0 | 0.0 | 1.0 |
brand_Mercedes-Benz | 0.0 | 0.00 | 0.0 | 0.0 | 0.0 |
brand_Mitsubishi | 0.0 | 0.00 | 0.0 | 0.0 | 0.0 |
brand_Nissan | 0.0 | 0.00 | 0.0 | 0.0 | 0.0 |
brand_Opel | 0.0 | 0.00 | 0.0 | 0.0 | 0.0 |
brand_Renault | 0.0 | 0.00 | 0.0 | 0.0 | 0.0 |
brand_Skoda | 0.0 | 1.00 | 0.0 | 0.0 | 0.0 |
brand_Tata | 0.0 | 0.00 | 0.0 | 0.0 | 0.0 |
brand_Toyota | 0.0 | 0.00 | 0.0 | 0.0 | 0.0 |
brand_Volkswagen | 0.0 | 0.00 | 0.0 | 0.0 | 0.0 |
brand_Volvo | 0.0 | 0.00 | 0.0 | 0.0 | 0.0 |
fuel_Diesel | 1.0 | 1.00 | 0.0 | 1.0 | 0.0 |
fuel_LPG | 0.0 | 0.00 | 0.0 | 0.0 | 0.0 |
fuel_Petrol | 0.0 | 0.00 | 1.0 | 0.0 | 1.0 |
seller_Individual | 1.0 | 1.00 | 1.0 | 1.0 | 1.0 |
seller_Trustmark Dealer | 0.0 | 0.00 | 0.0 | 0.0 | 0.0 |
((5929,), (1977,))
Для оценки точности модели используем среднюю абсолютную процентную ошибку
(MAPE).
Эта метрика не встроена в scikit-learn. библиотеку, поэтому нам придется реализовать
ее вручную.
def mape(y, y_hat):
y, y_hat = np.array(y), np.array(y_hat)
return np.mean(np.abs((y - y_hat) / y)) * 100
Делаем базовую модель - линейная регрессия с показателями R2 и MAPE
R2 = 0.87
MAPE = 42.87
В среднем базовая модель ошибочна на 43%. Это много. Посмотрим на коэффициент
модели линейной регрессии, чтобы определить, их важность
for i, column in enumerate(df.columns[:-1]):
coef = np.round(lm.coef_[i], 2)
print(f'{column:20}: {coef:12}')
year : 43014.37 selling_price : -0.99 km_driven : -93417.99 is_manual : -29134.49 owner : -3223.42 mileage : 51.82 engine : 6442.18 max_power : -6664.42 seats : -0.0 brand_Ashok : 732079.41 brand_Audi : 2062806.87 brand_BMW : -413173.11 brand_Chevrolet : 143836.25 brand_Daewoo : -456934.44 brand_Datsun : -412771.18 brand_Fiat : -350143.14 brand_Force : -351419.7 brand_Ford : -359997.28 brand_Honda : -350773.5 brand_Hyundai : 382947.89 brand_Isuzu : 1084736.28 brand_Jaguar : 403502.72 brand_Jeep : 61492.6 brand_Kia : 2185633.63 brand_Land : 3094325.97 brand_Lexus : 206214.99 brand_MG : -327026.98 brand_Mahindra : -256695.95 brand_Maruti : 689295.55 brand_Mercedes-Benz : -57724.47 brand_Mitsubishi : -371642.93 brand_Nissan : -0.0 brand_Opel : -369176.64 brand_Renault : -422164.25 brand_Skoda : -455033.96 brand_Tata : -19784.95 brand_Toyota : -432636.85 brand_Volkswagen : 1494271.37 brand_Volvo : 153467.18 fuel_Diesel : 178327.49 fuel_LPG : 58605.79 fuel_Petrol : -49419.66 seller_Individual : -76786.21Чем хороша линейная регрессия, так это понятной интерпретируемостью
коэффициентов.
Чем выше год, тем новее автомобиль, тем более высокая цена. Чем больше километров автомобиль проехал, тем больше снижается цена. Автомобили с автоматической
передачей стоят дороже. Нот точность недостаточна. Ее должен улучшить TROT.
Подгоним модель TPOT и оптимизируем ее для оценки MAPE. Обучим модель в течение 10 минут на каждом доступном ядре ЦП (обозначается n_jobs=-1):
from tpot import TPOTRegressor
from sklearn.metrics import make_scorer
mape_scorer = make_scorer(mape, greater_is_better=False)
pipeline_optimizer = TPOTRegressor(
scoring=mape_scorer,
max_time_mins=10,
random_state=42,
verbosity=2,
n_jobs=-1
)
pipeline_optimizer.fit(X_train, y_train)
На выходе получаем :
10.07 minutes have elapsed. TPOT will close down. TPOT closed during evaluation in one generation. WARNING: TPOT may not provide a good pipeline if TPOT is stopped/interrupted in a early generation. TPOT closed prematurely. Will use the current best pipeline. Best pipeline: GradientBoostingRegressor
Оптимизация через 10 минут была прервана, посмотрим что получается при этом на
тестовой выборке
tpot_preds = pipeline_optimizer.predict(X_test)
print(f'R2 = {r2_score(y_test, tpot_preds):.2f}')
print(f'MAPE = {mape(y_test, tpot_preds):.2f}')
R2 = 0.97 MAPE = 14.78
Как видно, TROT значительно улучшил показатели точности.
Комментариев нет:
Отправить комментарий