понедельник, 12 декабря 2022 г.

Машинное обучение с библиотеками PyCaret и TROT : регрессия, часть четвертая

В четвертой части знакомства с автоматизированное машинное обучение будем работать с библиотекой TROT по книге "Radečić Dario. Machine Learning Automation with TPOT: Build, validate, and deploy fully automated machine learning models with Python".

Работаем с тремя наборами данных : рыбный рынок, страхование и транспортные средства.

Загружаем необходимые библиотеки

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from matplotlib import rcParams
rcParams['axes.spines.top'] = False
rcParams['axes.spines.right'] = False

Первый набор данных - данные рыбного рынка (https://www.kaggle. com/aungpyaeap/fish-market) для исследования и регрессионного моделирования. Цель состоит в том, чтобы предсказать вес рыбы. 

Загружаем данные

df = pd.read_csv('data/Fish.csv')
df.head()

SpeciesWeightLength1Length2Length3HeightWidth
0Bream242.023.225.430.011.52004.0200
1Bream290.024.026.331.212.48004.3056
2Bream340.023.926.531.112.37784.6961
3Bream363.026.329.033.512.73004.4555
4Bream430.026.529.034.012.44405.1340

Переходим к исследовательскому анализу данных. Первое, что интересует, недостающие значения. Вот как их проверить:

df.isnull().sum()

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)




Большая часть рыбы легкая, но есть пара тяжелых. Исследуем виды дальше, чтобы лучше понять. Для этого выведем количество экземпляров определенного вида. (количество и процент от общего числа), а также  среднее и стандартное отклонение по каждому признаку. Создадим функцию и затем эта функция вызывается для каждого уникального вида:

def describe_species(species):
    subset = df[df['Species'] == species]
    print(f'============ {species.upper()} ============')
    print(f'Count: {len(subset)}')
    print(f'Pct. total: {(len(subset) / len(df) * 100):.2f}%')
    for column in df.columns[1:]:
        avg = np.round(subset[column].mean(), 2)
        sd = np.round(subset[column].std(), 2)
        print(f'Avg. {column:>7}: {avg:6} +/- {sd:6}')

for species in df['Species'].unique():
    describe_species(species)
    print()

============ 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

Проверим корреляцию между атрибутами. Корреляцию можно рассчитать только для числовых атрибутов. Визуализируем матрица корреляции с библиотекой seaborn.

sns.heatmap(df.corr(),vmin=0.6,vmax=1.0,annot=True,fmt='.2f',
mask=~np.tri(df.corr().shape[1], k=-1, dtype=bool),
linewidth=2,cbar=True,cmap='Blues')


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

Для кодирования используем функцию get_dummies. Параметр набор дамми-переменных drop_first задает тип дамми-кодирования. По умолчанию для этого параметра задано значение False и выполняется дамми-кодирование по методу неполного ранга, в противном случае будет выполнено дамми-кодирование по методу полного ранга. В нашем случае функция pd.get_dummies() автоматически преобразует заданную категориальную переменную методом полного ранга в набор из (n-1) дамми-переменных.

species_dummies = pd.get_dummies(df['Species'], drop_first=True, prefix='Is')

df = pd.concat([species_dummies, df], axis=1)
df.drop('Species', axis=1, inplace=True)

df.head()

Is_ParkkiIs_PerchIs_PikeIs_RoachIs_SmeltIs_WhitefishWeightLength1Length2Length3HeightWidth
0000000242.023.225.430.011.52004.0200
1000000290.024.026.331.212.48004.3056
2000000340.023.926.531.112.37784.6961
3000000363.026.329.033.512.73004.4555
4000000430.026.529.034.012.44405.1340



Удаляем столбец Species, потому что он больше не нужен. Необходимо выбрать  оценочный   показатель. TPOT поставляется с парой показателей оценки регрессии. По умолчанию neg_mean_squared_error.  Для оценки качества регрессии мы используем показатель RMSE (корень из среднеквадратичной ошибки)

Определим его с помощью лямбда-функций:

from tpot import TPOTRegressor
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, make_scorer

rmse = lambda y, y_hat: np.sqrt(mean_squared_error(y, y_hat))

Разделим входной набор на обучающую и тестовую части. Будем хранить 75% данных для обучение и 25% на тест :

X = df.drop('Weight', axis=1)
y = df['Weight']

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.25, random_state=42
)

y_train.shape, y_test.shape

((119,), (40,))

Далее создадим модель с алгоритмом линейной регрессии. Эта модель задаст базовый уровень, который должен превзойти TPOT

from sklearn.linear_model import LinearRegression

lm = LinearRegression()
lm.fit(X_train, y_train)

lm_preds = lm.predict(X_test)
rmse(y_test, lm_preds)

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), чтобы предсказать, сколько страховка будет стоить на основе нескольких переменных-предикторов.


df = pd.read_csv('data/insurance.csv')
df.head()

agesexbmichildrensmokerregioncharges
019female27.9000yessouthwest16884.92400
118male33.7701nosoutheast1725.55230
228male33.0003nosoutheast4449.46200
333male22.7050nonorthwest21984.47061
432male28.8800nonorthwest3866.85520

Проверяем количество пропущенных значений

df.isnull().sum()

age         0
sex         0
bmi         0
children    0
smoker      0
region      0
charges     0
dtype: int64


С помощью этого набора данных будем предсказывать столбец расходов. Выведем гистограмму этого показателя : 

df['charges'].hist(bins=20, figsize = (12,8))
plt.title('Target variable (charges) distribution', size=20)
plt.xlabel('Charge', size=14)
plt.ylabel('Count', size=14)



Как видим, распределение сильно скошенное, так что будет интересно увидеть, как модель справится с этим.  Рассмотрим и другие переменные, цель состоит в том, чтобы увидеть средние затраты на страхование для каждой категориальной переменной сегмента. Будем использовать медиану как среднее значение, поскольку оно менее подвержено выбросам.

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


def make_bar_chart(x,y,title, ylabel, xlabel,y_offset=0.12, x_offset=700):
    ax = df.groupby(x).median()[[y]].plot(kind='bar',
    figsize=(10, 6), fontsize=13)
    ax.set_title(title, size=20, pad=30)
    ax.set_ylabel(ylabel, fontsize=14)
    ax.set_xlabel(xlabel, fontsize=14)
    #Удаляем легенду с диаграммы
    ax.get_legend().remove()
    #Цикл по элементам формы - добавляем значения    
    for i in ax.patches:
        ax.text(i.get_x() + x_offset, i.get_height() + y_offset,
        f'${str(round(i.get_height(), 2))}', fontsize=15)
    return ax

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

make_bar_chart(
    x='smoker',
    y='charges',
    title='Median insurance charges for smokers and non-smokers',
    ylabel='Insurance charge ($)',
    xlabel='Do they smoke?',
    y_offset=700,
    x_offset=0.12
    )





Как видите, курильщики платят страховой взнос в несколько раз выше, чем некурящие. Сделаем похожую визуализацию для сравнения медианных затрат на страхование. между полами:

make_bar_chart(
    x='sex',
    y='charges',
    title='Median insurance charges between genders',
    ylabel='Insurance charge ($)',
    xlabel='Gender',
    y_offset=200,
    x_offset=0.15
    )



Тут не большая разница. Сравниваем медианные затраты на страхование по количеству детей.

make_bar_chart(
    x='children',
    y='charges',
    title='Median insurance charges between genders',
    ylabel='Insurance charge ($)',
    xlabel='Gender',
    y_offset=200,
    x_offset=0.15
    )



Расходы на страхование, кажется, растут до пятого ребенка. А может там не так много семей с пятью детьми. Вот код для визуализации медианных затрат на страхование по регионам:

make_bar_chart(
    x='region',
    y='charges',
    title='Median insurance charges between genders',
    ylabel='Insurance charge ($)',
    xlabel='Gender',
    y_offset=200,
    x_offset=0.15
    )



Значения не сильно отличаются. Для того, чтобы этот набор данных можно было использовать для машинного обучения, необходимо сделать некоторые преобразования.  Во-первых, необходимо преобразовать строковые значения в целые числа для столбцов пола и курильщик. Затем нам нужно создать фиктивные переменные для столбца региона. Этот шаг необходим, потому что TPOT не может понимать необработанные текстовые данные

df['sex'] = [1 if x == 'female' else 0 for x in df['sex']]
df.rename(columns={'sex': 'is_female'}, inplace=True)
df['smoker'] = [1 if x == 'yes' else 0 for x in df['smoker']]
region_dummies = pd.get_dummies(df['region'], drop_first=True, prefix='region')
df = pd.concat([region_dummies, df], axis=1)
df.drop('region', axis=1, inplace=True)
df.head()



region_northwestregion_southeastregion_southwestageis_femalebmichildrensmokercharges
000119127.9000116884.92400
101018033.770101725.55230
201028033.000304449.46200
310033022.7050021984.47061
410032028.880003866.85520



Теперь набор данных готов для прогнозного моделирования. Прежде чем мы это сделаем, давайте проверим корреляции переменных с целевой переменной. 

sns.heatmap(df.corr(),annot=True,fmt='.2f',
mask=~np.tri(df.corr().shape[1], k=-1, dtype=bool),
linewidth=2,cbar=True,cmap='Blues')




Делим данные на обучающую и тестовую части

X = df.drop('charges', axis=1)
y = df['charges']
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.25, random_state=42)

y_train.shape, y_test.shape

((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')


Следующий пример -  применение автоматизированного регрессионного моделирования к набор данных транспортного средства (https://www.kaggle. com/nehalbirla/vehicle-dataset-from-cardekho).


Загружаем набор

df = pd.read_csv('data/Car.csv')
df.head().T

01234
nameMaruti Swift Dzire VDISkoda Rapid 1.5 TDI AmbitionHonda City 2017-2020 EXiHyundai i20 Sportz DieselMaruti Swift VXI BSIII
year20142014200620102007
selling_price450000370000158000225000130000
km_driven145500120000140000127000120000
fuelDieselDieselPetrolDieselPetrol
seller_typeIndividualIndividualIndividualIndividualIndividual
transmissionManualManualManualManualManual
ownerFirst OwnerSecond OwnerThird OwnerFirst OwnerFirst Owner
mileage23.4 kmpl21.14 kmpl17.7 kmpl23.0 kmpl16.1 kmpl
engine1248 CC1498 CC1497 CC1396 CC1298 CC
max_power74 bhp103.52 bhp78 bhp90 bhp88.2 bhp
torque190Nm@ 2000rpm250Nm@ 1500-2500rpm12.7@ 2,700(kgm@ rpm)22.4 kgm at 1750-2750rpm11.5@ 4,500(kgm@ rpm)
seats5.05.05.05.05.0

Проверка пропущенных значений

df.isnull().sum()

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():

def remap_owner(owner):
    if owner == 'First Owner': return 1
    elif owner == 'Second Owner': return 2
    elif owner == 'Third Owner': return 3
    elif owner == 'Fourth & Above Owner': return 4
    else: return 0


Код для выполнения всех упомянутых преобразований:

df['transmission'] = [1 if x == 'Manual' else 0 for x in df['transmission']]
df.rename(columns={'transmission': 'is_manual'},inplace=True)
df['owner'] = df['owner'].apply(remap_owner)
df['name'] = df['name'].apply(lambda x: x.split()[0])
df['mileage'] = df['mileage'].apply(lambda x: x.split()[0]).astype(float)
df['engine'] = df['engine'].apply(lambda x: x.split()[0]).astype(int)
df['max_power'] = df['max_power'].apply(lambda x:x.split()[0]).astype(float)
brand_dummies = pd.get_dummies(df['name'], drop_first=True, prefix='brand')
fuel_dummies = pd.get_dummies(df['fuel'], drop_first=True, prefix='fuel')
seller_dummies = pd.get_dummies(df['seller_type'], drop_first=True, prefix='seller')
df.drop(['name', 'fuel', 'seller_type', 'torque'],axis=1, inplace=True)
df = pd.concat([df, brand_dummies, fuel_dummies, seller_dummies], axis=1)

После применения преобразований набор данных выглядит так:

df.head().T

01234
year2014.02014.002006.02010.02007.0
selling_price450000.0370000.00158000.0225000.0130000.0
km_driven145500.0120000.00140000.0127000.0120000.0
is_manual1.01.001.01.01.0
owner1.02.003.01.01.0
mileage23.421.1417.723.016.1
engine1248.01498.001497.01396.01298.0
max_power74.0103.5278.090.088.2
seats5.05.005.05.05.0
brand_Ashok0.00.000.00.00.0
brand_Audi0.00.000.00.00.0
brand_BMW0.00.000.00.00.0
brand_Chevrolet0.00.000.00.00.0
brand_Daewoo0.00.000.00.00.0
brand_Datsun0.00.000.00.00.0
brand_Fiat0.00.000.00.00.0
brand_Force0.00.000.00.00.0
brand_Ford0.00.000.00.00.0
brand_Honda0.00.001.00.00.0
brand_Hyundai0.00.000.01.00.0
brand_Isuzu0.00.000.00.00.0
brand_Jaguar0.00.000.00.00.0
brand_Jeep0.00.000.00.00.0
brand_Kia0.00.000.00.00.0
brand_Land0.00.000.00.00.0
brand_Lexus0.00.000.00.00.0
brand_MG0.00.000.00.00.0
brand_Mahindra0.00.000.00.00.0
brand_Maruti1.00.000.00.01.0
brand_Mercedes-Benz0.00.000.00.00.0
brand_Mitsubishi0.00.000.00.00.0
brand_Nissan0.00.000.00.00.0
brand_Opel0.00.000.00.00.0
brand_Renault0.00.000.00.00.0
brand_Skoda0.01.000.00.00.0
brand_Tata0.00.000.00.00.0
brand_Toyota0.00.000.00.00.0
brand_Volkswagen0.00.000.00.00.0
brand_Volvo0.00.000.00.00.0
fuel_Diesel1.01.000.01.00.0
fuel_LPG0.00.000.00.00.0
fuel_Petrol0.00.001.00.01.0
seller_Individual1.01.001.01.01.0
seller_Trustmark Dealer0.00.000.00.00.0

Данные в этом формате можно передавать алгоритму машинного обучения. Делим на обучающую и тестовую части

X = df.drop('selling_price', axis=1)
y = df['selling_price']
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.25, random_state=42)
y_train.shape, y_test.shape

((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

from sklearn.linear_model import LinearRegression
from sklearn.metrics import r2_score

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'MAPE = {mape(y_test, lm_preds):.2f}')


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 значительно улучшил показатели точности.


















Комментариев нет:

Отправить комментарий