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

Модель обучения была выполнена путем разделения моделей по маркам автомобилей вместо обучения одной большой модели как для эффективных процессов оптимизации, так и для более эффективного использования мощности и времени компьютера. При обучении использовалось около 100 000 данных с 12 функциями. Сравниваются модели машинного обучения:

  • Линейная регрессия
  • Хребет
  • Лассо
  • Эластичная сетка
  • K-Ближайшие соседи
  • Случайный лес
  • XGBOOST
  • Машина для повышения градиента

Прежде всего, модели были обучены с параметрами по умолчанию. Затем к этим моделям была применена гиперпараметрическая оптимизация. Значения и оценки параметров в обоих случаях были записаны для последующего просмотра.

Предварительная обработка данных

Данные, полученные с помощью методов веб-парсинга (возможно, это можно будет подробно обсудить в другой статье), загружаются из базы данных. Когда первые библиотеки процессов импортируются, наши данные конвертируются в DataFrame, а затем проверяются на отсутствие данных.

from pymongo import MongoClient
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import re
from sklearn.model_selection import train_test_split, GridSearchCV,ShuffleSplit, cross_val_score
from sklearn import model_selection
from sklearn.neighbors import KNeighborsRegressor
import statsmodels.api as sm
from sklearn.linear_model import LinearRegression, Ridge, Lasso, LassoCV, ElasticNet
from sklearn.metrics import mean_squared_error, mean_absolute_error
import numpy as np
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor
import xgboost as xgb
from xgboost import XGBRegressor
import config
import pickle
from collections import defaultdict
from operator import itemgetter
import random
import lightgbm as lgb
from lightgbm import LGBMRegressor

Поскольку количество недостающих данных очень мало, мы можем их удалить. Некоторые данные, представляющие собой строку типа данных, преобразуются в целые числа, например год («1999», «2018»). Кроме того, данные подготавливаются путем удаления точечных выражений в значениях цены и пробега.

"""Preprocessing of Price"""
df["Fiyat"] = df["Fiyat"].str.replace(".","")
df["Fiyat"] = [re.findall(r"(\d+)", str(x)) for x in df["Fiyat"]]
df["Fiyat"] = df["Fiyat"].str[0]
df["Fiyat"]  = df["Fiyat"].astype(int)
"""Preprocessing of Km and year"""
df["Km"] = df["Km"].str.replace(".","")
df["Km"]  = df["Km"].astype(int)
df["Yıl"]  = df["Yıl"].astype(int)

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

Выше показана неверная информация о пробеге (км). Эти значения пробега неверны, особенно по сравнению с информацией о году (Yıl) и цене (Fiyat). Эти данные можно удалить, поскольку количество обнаруженных выбросов невелико. Однако в случаях, когда количество имеющихся у нас данных невелико, мы можем не захотеть их удалять. В таких случаях среднее значение или медианное значение может быть присвоено столбцам выбросов (в данном случае столбцу пробега). В качестве примера мы рассматривали только Audi, но эти процедуры применялись для каждой марки автомобиля.

Кратко рассмотрим модели машинного обучения, использованные в проекте:

  • Линейная регрессия:

Линейная регрессия используется для определения степени, в которой существует линейная связь между зависимой переменной и одной или несколькими независимыми переменными. Существует два типа линейной регрессии: простая линейная регрессия и множественная линейная регрессия. Поскольку в этом проекте используется более одной независимой переменной, «была использована модель множественной линейной регрессии.

def linear_regression(self):
        self.lr_model = LinearRegression()
        self.lr_model.fit(self.X_train, self.y_train)
        y_pred = self.lr_model.predict(self.X_test)
        lr_rmse = np.sqrt(mean_squared_error(self.y_test,y_pred))
        self.lr_rmse_per  =np.sqrt(np.mean(np.square(((self.y_test - y_pred) / self.y_test))))*100
        self.lr_mae = mean_absolute_error(self.y_test, y_pred)
        self.lr_mape =  np.abs((self.y_test - y_pred) / self.y_test).mean(axis=0) * 100
        return lr_rmse
  • K-ближайшие соседи:

Хотя алгоритм K-ближайших соседей (KNN) обычно используется в задачах классификации, он также дает очень хорошие результаты в задачах регрессии. Принцип работы алгоритма KNN - кластеризация объектов в соответствии с их отношениями близости. Пример классифицируется большинством голосов его соседей; эта выборка отнесена к самому близкому классу среди ее ближайших соседей, что измеряется функцией расстояния.

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

Хотя алгоритм KNN создает эффективную модель машинного обучения, особенно в отношении шума, содержащего данные, к большим данным необходимо подходить осторожно.

Код проекта:

def knn(self):
        knn_model = KNeighborsRegressor().fit(self.X_train, self.y_train)
        y_pred = knn_model.predict(self.X_test)
        self.knn_rmse = np.sqrt(mean_squared_error(self.y_test,y_pred))
        #Model tuning 
   #=============================================================================
        knn_params = {"n_neighbors": [2,5,10,20,30,40],
                      "leaf_size": [15,30,60]}
        n_neighbors_list = knn_params["n_neighbors"]
        leaf_size_list = knn_params["leaf_size"]
        self.knn_result_list = []
        for i in range(len(n_neighbors_list)):
            for j in range(len(leaf_size_list)):
                temp_list = []
                knn_model = KNeighborsRegressor(n_neighbors = n_neighbors_list[i], leaf_size = leaf_size_list[j])
                knn_model.fit(self.X_train, self.y_train)
                y_pred = knn_model.predict(self.X_test)
                knn_rmse = np.sqrt(mean_squared_error(self.y_test, y_pred))
                temp_list.extend((knn_rmse, n_neighbors_list[i], leaf_size_list[j]))
                self.knn_result_list.append(temp_list)
        self.knn_result_list.sort()
        
        self.knn_tuned = KNeighborsRegressor(n_neighbors = self.knn_result_list[0][1], leaf_size = self.knn_result_list[0][2])
        self.knn_tuned.fit(self.X_train, self.y_train)
        y_pred = self.knn_tuned.predict(self.X_test)
        knn_tuned_rmse = np.sqrt(mean_squared_error(self.y_test,y_pred))
        self.knn_tuned_rmse_per  =np.sqrt(np.mean(np.square(((self.y_test - y_pred) / self.y_test))))*100
        self.knn_mae = mean_absolute_error(self.y_test, y_pred)
        self.knn_mape =  np.abs((self.y_test - y_pred) / self.y_test).mean(axis=0) * 100
        return knn_tuned_rmse
  • Случайный лес:

Модель случайного леса, которая используется для задач регрессии и классификации и основана на модели дерева решений.

Одна из самых больших проблем деревьев решений, которые являются одним из традиционных методов, - это переоснащение в образовании. Модель случайного леса была разработана для решения этой проблемы. Эта модель делает случайный выбор как из набора данных, так и из набора атрибутов. Как видно выше, каждое дерево решений делает индивидуальные прогнозы, используя свое собственное подмножество. На последнем этапе мы достигаем окончательного значения прогноза нашей модели случайного леса, беря среднее значение полученных прогнозов.

В нашем проекте гиперпараметры «max_depth», «n_estimators» и «max_features» использовались для применения операций настройки к нашей модели случайного леса.

def random_forest(self):
        
        rf_model = RandomForestRegressor(random_state = 10).fit(self.X_train, self.y_train)
        y_pred = rf_model.predict(self.X_test)
        self.rf_rmse = np.sqrt(mean_squared_error(self.y_test, y_pred))
        #Random Forest Tuning Model
        rf_params = {"max_depth": [None, 2,5,9],
                     "n_estimators": [10,100,500,1000],
                     "max_features": ["auto",3,5]}
        self.rf_result_list = []
        max_depth_list = rf_params["max_depth"]
        n_estimators_list = rf_params["n_estimators"]
        max_features_list = rf_params["max_features"]
        for i in range(len(max_depth_list)):
            for j in range(len(n_estimators_list)):
                for k in range(len(max_features_list)):
                    temp_list = []
                    rf_model = RandomForestRegressor(random_state = 10, max_depth = max_depth_list[i], n_estimators = n_estimators_list[j], max_features = max_features_list[k])
                    rf_model.fit(self.X_train, self.y_train)
                    y_pred = rf_model.predict(self.X_test)
                    knn_rmse = np.sqrt(mean_squared_error(self.y_test, y_pred))
                    temp_list.extend((knn_rmse, max_depth_list[i], n_estimators_list[j], max_features_list[k]))
                    self.rf_result_list.append(temp_list)
        self.rf_result_list.sort()
        self.rf_tuned = RandomForestRegressor(random_state = 10, max_depth = self.rf_result_list[0][1], n_estimators = self.rf_result_list[0][2], max_features = self.rf_result_list[0][3])
        self.rf_tuned.fit(self.X_train, self.y_train)
        y_pred = self.rf_tuned.predict(self.X_test)
        rf_tuned_rmse = np.sqrt(mean_squared_error(self.y_test,y_pred))
        self.rf_tuned_rmse_per  =np.sqrt(np.mean(np.square(((self.y_test - y_pred) / self.y_test))))*100
        self.rf_mae = mean_absolute_error(self.y_test, y_pred)
        self.rf_mape =  np.abs((self.y_test - y_pred) / self.y_test).mean(axis=0) * 100
        return rf_tuned_rmse
  • Машина для повышения градиента:

Повышение - это итеративный метод, в котором предикторы создаются не независимо, а последовательно. Следовательно, вероятность наблюдений в последующих моделях неодинакова, и наиболее вероятны те, которые имеют наибольшую ошибку. Предикторы могут быть выбраны из ряда моделей, таких как деревья решений, регрессоры, классификаторы и т. Д. Поскольку новые предикторы учатся на ошибках, совершенных предыдущими предикторами, требуется меньше времени / итераций, чтобы приблизиться к фактическим прогнозам. Но мы должны тщательно выбирать критерии остановки, иначе это может привести к переобучению обучающих данных. Итак, пример алгоритма повышения градиента.

Скажем, у нас есть среднеквадратичная ошибка (MSE) как потеря, определяемая как:

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

def gbm(self):
        gbm_model = GradientBoostingRegressor().fit(self.X_train, self.y_train)
        y_pred = gbm_model.predict(self.X_test)
        self.gbm_rmse = np.sqrt(mean_squared_error(self.y_test,y_pred))
      
        gbm_params = {"learning_rate" : [0.001, 0.01, 0.1],
                      "max_depth": [3,5,8,50,100],
                      "n_estimators" : [10,100,500,1000], 
                      "subsample" : [1,0.5,0.75]}
        learning_rate_list = gbm_params["learning_rate"]
        max_depth_list = gbm_params["max_depth"]
        n_estimators_list = gbm_params["n_estimators"]
        subsample_list = gbm_params["subsample"]
        self.gbm_result_list = []
        for i in range(len(learning_rate_list)):
            for j in range(len(max_depth_list)):
                for k in range(len(n_estimators_list)):
                    for l in range(len(subsample_list)):
                        temp_list = []
                        gbm_model = GradientBoostingRegressor(learning_rate = learning_rate_list[i],
                                                              max_depth = max_depth_list[j],
                                                              n_estimators = n_estimators_list[k],
                                                              subsample = subsample_list[l])
                        gbm_model.fit(self.X_train, self.y_train)
                        y_pred = gbm_model.predict(self.X_test)
                        gbm_rmse = np.sqrt(mean_squared_error(self.y_test, y_pred))
                        temp_list.extend((gbm_rmse, learning_rate_list[i], max_depth_list[j], n_estimators_list[k], subsample_list[l]))
                        self.gbm_result_list.append(temp_list)
        self.gbm_result_list.sort()
        self.gbm_tuned = GradientBoostingRegressor(learning_rate = self.gbm_result_list[0][1],
                                                   max_depth = self.gbm_result_list[0][2],
                                                   n_estimators = self.gbm_result_list[0][3],
                                                   subsample = self.gbm_result_list[0][4])
        self.gbm_tuned.fit(self.X_train, self.y_train)
        y_pred = self.gbm_tuned.predict(self.X_test)
        gbm_tuned_rmse = np.sqrt(mean_squared_error(self.y_test, y_pred))
        self.gbm_tuned_rmse_per =np.sqrt(np.mean(np.square(((self.y_test - y_pred) / self.y_test))))*100
        self.gbm_mae = mean_absolute_error(self.y_test, y_pred)
        self.gbm_mape =  np.abs((self.y_test - y_pred) / self.y_test).mean(axis=0) * 100
        return gbm_tuned_rmse
  • Экстремальное усиление градиента (XGBOOST):

Хотя XGBoost похож на Gradient Boosting Machine, он более эффективен. XGBoost обеспечивает распараллеливание при создании деревьев решений, что позволяет создавать их намного быстрее. Тот факт, что он может это сделать, - это базовые ученики, в то время как он может переключаться между внутренним и внешним петлителями. Обычно внешний цикл вычисляет атрибуты внутреннего цикла при формировании листьев дерева решений. XGBoost отдает приоритет глубине с max_depth, его сложность значительно увеличивает производительность вычислений. Внеядерные вычисления в XGBoost оптимизируют доступное дисковое пространство и максимизируют его использование при обработке огромных наборов данных, которые не помещаются в память. XGBoost может предотвратить переоснащение, используя регуляризацию как Лассо, так и Ридж. Для более быстрых вычислений XGBoost может использовать несколько ядер ЦП. Это возможно благодаря блочной структуре его системы. Данные сортируются и хранятся в единицах памяти, называемых блоками. В отличие от других алгоритмов, это позволяет повторно использовать макет данных в последующих итерациях вместо повторного вычисления. Одним из самых больших преимуществ XGBoost является то, что он использует точки наблюдения в наборе данных путем взвешивания, чтобы отличить его от правильной точки при разделении на деревья.

В нашем проекте гиперпараметры «max_depth», «n_estimators», «colsample_bytree» и «скорость обучения» использовались для применения операций настройки к нашей модели случайного леса.

def xgboost(self):
        X_train_v = self.X_train.values
        X_test_v = self.X_test.values
        xgb_model = XGBRegressor().fit(X_train_v, self.y_train)
        y_pred = xgb_model.predict(X_test_v)
        self.xgboost_rmse = np.sqrt(mean_squared_error(self.y_test, y_pred))
        #Model Tuning
        xgb_params ={"colsample_bytree": [0.6,0.9,1],
                     "n_estimators" : [100,200,500,1000],
                     "max_depth" : [4,5,6],
                     "learning_rate": [0.1,0.3,0.5]}
        self.xgb_result_list = []
        colsample_bytree_list = xgb_params["colsample_bytree"]
        n_estimators_list = xgb_params["n_estimators"]
        max_depth_list = xgb_params["max_depth"]
        learning_rate_list = xgb_params["learning_rate"]
        for i in range(len(colsample_bytree_list)):
            for j in range(len(n_estimators_list)):
                for k in range(len(max_depth_list)):
                    for l in range(len(learning_rate_list)):
                        temp_list = []
                        xgb_model = XGBRegressor(colsample_bytree = colsample_bytree_list[i],
                                                 n_estimators = n_estimators_list[j],
                                                 max_depth = max_depth_list[k],
                                                 learning_rate = learning_rate_list[l])
                        xgb_model.fit(X_train_v, self.y_train)
                        y_pred = xgb_model.predict(X_test_v)
                        xgb_rmse = np.sqrt(mean_squared_error(self.y_test, y_pred))
                        temp_list.extend((xgb_rmse, colsample_bytree_list[i], n_estimators_list[j], max_depth_list[k], learning_rate_list[l]))
                        self.xgb_result_list.append(temp_list)
        self.xgb_result_list.sort()
        self.xgb_tuned = XGBRegressor(colsample_by_tree = self.xgb_result_list[0][1],
                                      n_estimators  =self.xgb_result_list[0][2],
                                      max_depth = self.xgb_result_list[0][3],
                                      learning_rate = self.xgb_result_list[0][4])
        self.xgb_tuned.fit(X_train_v, self.y_train)
        y_pred = self.xgb_tuned.predict(X_test_v)
        xgb_tuned_rmse = np.sqrt(mean_squared_error(self.y_test,y_pred))
        self.xgb_tuned_rmse_per  =np.sqrt(np.mean(np.square(((self.y_test - y_pred) / self.y_test))))*100
        self.xgb_mae = mean_absolute_error(self.y_test, y_pred)
        self.xgb_mape =  np.abs((self.y_test - y_pred) / self.y_test).mean(axis=0) * 100
        return xgb_tuned_rmse

Среднеквадратичная ошибка представляет собой разницу для каждого наблюдаемого и прогнозируемого значения. Вы можете поменять местами порядок вычитания, потому что следующим шагом будет вычисление квадрата разницы. Это потому, что квадрат отрицательного значения всегда будет положительным значением. Но просто убедитесь, что вы соблюдаете тот же порядок во всем.

После этого разделите сумму всех значений на количество наблюдений. Наконец, мы получаем значение RMSE.

Другим методом сравнения полученных оценок может быть средняя абсолютная ошибка (MAE). Это особенно предпочтительно, когда в данных есть экстремальные наблюдения. Однако, если мы хотим сильнее отразить влияние наблюдений за выбросами на систему, было бы правильнее выбрать значение RMSE. Оба значения были найдены и сопоставлены в проекте.

Заключение

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

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

В результате сравнения моделей (количество лучших моделей):

  • Машина для повышения градиента (GBM): 19
  • Экстремальное усиление градиента (XGBoost): 11
  • Случайный лес: 7
  • Конек: 5
  • Лассо: 2
  • Эластичная сетка: 1

Глядя на сортировку, мы видим, что модель GBM выбирается больше всего. Особенно у моделей XGBoost и GBM есть преимущества перед другими моделями.
Однако с такими результатами нельзя делать вывод о том, что модель GBM является лучшей. Не следует забывать, что каждая модель может быть эффективной для разных типов и размеров данных.

Наконец, как среднеквадратичные значения параметров по умолчанию, так и среднеквадратичные значения настроенных параметров моделей GBM можно увидеть в таблице выше. Если в именах столбцов есть «def» (gbm def max_depth), это параметр по умолчанию, в противном случае («cho») - настроенный параметр. Итак, мы увидели, как модель может развиваться в результате оптимизации гиперпараметров.

Github: https://github.com/ademakdogan

Linkedin: https://www.linkedin.com/in/adem-akdo%C4%9Fan-948334177/

Ссылка