Машинное обучение: с чего начать или как построить первую модель

МЕНЮ


Искусственный интеллект
Поиск
Регистрация на сайте
Помощь проекту

ТЕМЫ


Новости ИИРазработка ИИВнедрение ИИРабота разума и сознаниеМодель мозгаРобототехника, БПЛАТрансгуманизмОбработка текстаТеория эволюцииДополненная реальностьЖелезоКиберугрозыНаучный мирИТ индустрияРазработка ПОТеория информацииМатематикаЦифровая экономика

Авторизация



RSS


RSS новости


В качестве первой задачи для машинного обучения возьмем что-то понятное и простое, например, прогноз стоимости жилья. Готовый датасет можно найти на сайте kaggle. На первых шагах обучения не стоит брать датасеты с большим количеством переменных, например, «House Prices: Advanced Regression Techniques» состоит из 80 переменных и advanced regression, остановимся на «House Sales in King County, USA» с 21 параметром. Скачиваем данные и анализируем предоставленное описание. В наличии дата, цена, количество спален, ванных комнат, общая и жилая площадь, этажность, оценка вида, вид на море, оценка общего состояния, грейд (оценка строительства и дизайна), площадь над и под уровнем земли, год постройки, год последнего ремонта, код зоны, координаты (долгота и широта), данные о площади домов 15 соседей.
Итак, мы выбрали задачу и готовы приступить к ее решению. Решение будет включать два этапа: анализ данных и построение моделей.

1. Работа с данными.

Сделаем отступление и отдельно отметим важность анализа данных. В настоящий момент все более-менее популярные алгоритмы уже написаны в виде библиотек и непосредственное построение модели сводится к нескольким строкам кода, например, k-ближайших соседей из sklearn в python:
from sklearn .neighbors import KNeighborsClassifier clf_KNN = KNeighborsClassifier()       #Создаем модель clf_KNN.fit(X_train, Y_train)          #Обучаем модель Y_KNN = clf_KNN.predict(X_test)        #Предсказываем значения для выборки 

Всего четыре строчки кода для получения результата. Так в чем же сложность? Сложность заключается в получении того самого X_train – данных, которые подаются на вход модели. Известный принцип «мусор на входе» = «мусор на выходе» (Англ. Garbage in – garbage out (GIGO)) в моделировании работает более чем на 100% и именно от работы с данными во многом будет зависеть качество полученного решения задачи машинного обучения.

А теперь – в бой!

Для анализа данных мы будем использовать pandas, для понимания и оценки «на глаз» используем простые графики из seaborn.

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

Код и Out

import pandas as pd import matplotlib.pyplot as plt import seaborn as sns df = pd.read_csv('…/train.csv') df.head(5)

df.info()

RangeIndex: 21613 entries, 0 to 21612 Data columns (total 21 columns): id 21613 non-null int64 date 21613 non-null object price 21613 non-null float64 bedrooms 21613 non-null int64 bathrooms 21613 non-null float64 sqft_living 21613 non-null int64 sqft_lot 21613 non-null int64 floors 21613 non-null float64 waterfront 21613 non-null int64 view 21613 non-null int64 condition 21613 non-null int64 grade 21613 non-null int64 sqft_above 21613 non-null int64 sqft_basement 21613 non-null int64 yr_built 21613 non-null int64 yr_renovated 21613 non-null int64 zipcode 21613 non-null int64 lat 21613 non-null float64 long 21613 non-null float64 sqft_living15 21613 non-null int64 sqft_lot15 21613 non-null int64 dtypes: float64(5), int64(15), object(1) memory usage: 3.5+ MB

Массив данных состоит из 21613 записей без пропусков в данных и содержит только 1 текстовое поле date.

С каждым признаком поработаем подробнее и начнем с самого простого – откинем id (не несет полезной информации), zipcode (код зоны, где расположен дом) и координаты (lat & long), так как мы только знакомимся c machine learning, а корректное преобразование географических данных слишком специфично для начинающего специалиста.

df=df.drop(['id','zipcode','lat','long'], axis=1)

Теперь посмотрим на дату объявления. Формат даты задан YYYYMMDDT000000, в целом ее тоже можно было бы удалить из датасета, но у нас есть поля год постройки (yr_built) и год последнего ремонта (yr_renovated), которые заданы в в формате года (YYYY), что не очень информативно. Оперируя датой объявления можно преобразовать год в возраст вычитанием (год объявления — год постройки / год ремонта). Отметим по части домов год ремонта стоит 0, и, предположив, что это означает отсутствие ремонта с постройки, заменим нули в году ремонта на год постройки, предварительно убедившись, что в данных отсутствуют некорректные записи, где год ремонта меньше года постройки:

df[(df['yr_renovated']<df['yr_built'])&df['yr_renovated']!=0]

df.loc[df['yr_renovated']==0, ['yr_renovated']]=df['yr_built'] df['yr_built']=df['date'].str[0:4].astype(int)-df['yr_built'] df['yr_renovated']=df['date'].str[0:4].astype(int)-df['yr_renovated'] df=df.drop('date', axis=1) df.head(5)

Следующим параметром проанализируем цену и воспользуемся для этого «Ящиком с усами» (Box plot). Ящик с усами – простой и удобный график, показывающий одномерное распределение вероятностей, или, проще говоря, концентрацию данных. Отрисовывает медиану (линия в центре), верхний и нижний квартили (стороны ящика), края статистически значимой выборки («усы») и выбросы (точки за «усами»). Легко понять по картинке на нормальном распределении (справа). График позволяет быстро оценить где располагается большая часть данных (50% находятся внутри ящика), их симметричность (смещение медианы к одной из сторон ящика и/или длина «усов») и степень разброса – дисперсию (размеры ящика, размеры усов и количество точек-выбросов).
Можно построить распределение только этого признака по всему массиву, но информативнее будет использовать 2 оси – например, цену и количество спален, что в свою очередь также покажет наличие связи между признаками:
sns.boxplot(y='price', data=df)			#только price sns.boxplot(y='price', x='bedrooms', data=df)	#price & bedrooms

Out price & bedrooms:

Из графика сразу видно наличие экстремальных значений price и bedrooms (только представьте дом с 33 спальнями! J). Наличие таких значений (иначе называемых как выбросы) в целевом признаке price часто приводит к переобучению модели, так именно они будут давать большую ошибку, которую алгоритмы стараются минимизировать. Из графика видно, что большая часть (если посчитать – 93,22%) лежит в диапазоне 0-1млн, а свыше 2млн – всего 198 значений (0,92%). От 1% датасета можно избавиться практически безболезненно, поэтому вызвав простой просмотр 217 записей предварительно отсортировав по цене, увидим искомую отметку price в 1 965 000 и удалим все что выше этой цены.
df.sort_values (by='price', ascending=False).head(217)  df=df[df['price']<=1965000]

Подумаем немного над признаком bedrooms. Мы видим 13 домов с bedrooms = 0, а также странную запись о доме с 33 bedrooms. Поступим также как и с price, удалив нули из bedroms (а заодно и bathrooms):

df=df[(df['bedrooms']!=0)&(df['bathrooms']!=0)]

Касательно дома с 33 спальнями – учитывая цену, можно предположить что это опечатка и спален на самом деле 3. Сравним жилую площадь этого дома (1620) со средней жилой площадью домов с 3 спальнями (1798,2), что ж вероятно наша догадка верна, поэтому просто изменим это значение на 3 и еще раз построим предыдущий box plot:

df.loc[df['bedrooms']==33,['bedrooms']]=3  sns.boxplot(y='price', x='bedrooms', data=df)

Чтож, значительно лучше. Аналогично bedrooms посмотрим и на bathrooms. Нулевые значения мы удалили, другие экстремальные значения в поле отсутствуют:
sns.boxplot(y='bathrooms', x='bedrooms', data=df)

В полях sqft_living, floors, waterfront, view, condition, grade, sqft_living15 также все значения более-менее реальны, их трогать не будем:

plt.rcParams['figure.figsize']=2,3 			#размер картинки sns.boxplot(y='sqft_living', data=df) sns.boxplot(y='floors',color='#2ecc71', data=df) sns.boxplot(y='sqft_living15',color='#9b59b6', data=df)  plt.rcParams['figure.figsize']=4,4 sns.boxplot(y='price', x='waterfront', data=df) sns.boxplot(y='price', x='view' , data=df) sns.boxplot(y='price', x='condition' , data=df) sns.boxplot(y='price', x='grade' , data=df)

А вот с sqft_lot и sqft_lot15 нужно что-то придумать и из-за больших значений вполне подойдет логарифмирование:
df['sqft_lot']=np.log(df['sqft_lot']) df['sqft_lot15']=np.log(df['sqft_lot15'])

sqft_lot до и после:

sqft_above и sqft_basement – составные части sqft_living, поэтому также трогать их не будем. На этом с предварительным анализом мы закончим и посмотрим на тепловую карту корреляций:
sns.heatmap(df.corr(),  cmap = 'viridis',annot = True)

Изучив карту корреляций видим, что иногда признаки сильно коррелированы между собой, поэтому удалим часть признаков с высокой корреляцией – sqft_lot15 (оставим sqft_lot), yr_built (оставим yr_renovated), sqft_above (sqft_living). На этом закончим работу с данными и перейдем к созданию модели.
2. Моделирование

В данной части мы построим 2 модели: линейную регрессию и дерево решений.

Все необходимые нам модели содержаться в библиотеке sklearn.

Для начала отделим целевую переменную от остальных данных для обучения, а также разделим выборки на обучающую (70%) и тестовую (30%, на которой мы проверим как работает модель):

Y=df['price'] X=df.drop ('price',axis=1)  from sklearn.model_selection import train_test_split X_train, X_test, Y_train, Y_test  = train_test_split(X, Y, test_size = 0.3, shuffle = True)

Также из sklearn для оценки модели загрузим 3 метрики — mean_absolute_error (средняя абсолютная ошибка), mean_squared_error (Среднеквадратическое отклонение), r2_score (коэффициент детерминации):

from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score

Начнем с линейной регрессии:

from sklearn.linear_model import LinearRegression LR = LinearRegression() 				#Создаем модель LR.fit(X_train, Y_train)				#Обучаем модель Y_LR = LR.predict(X_test)				#Предсказываем значения для выборки     print ('MAE:', round (mean_absolute_error(Y_test, Y_LR),3))		#Метрики print ('?MSE:', round (mean_squared_error(Y_test, Y_LR)**(1/2),3)) print ('R2_score:', round (r2_score(Y_test, Y_LR),3))

MAE: 124477.452

?MSE 175205.645

R2_score: 0.627

Дерево решений:

from sklearn.tree import DecisionTreeRegressor TR = DecisionTreeRegressor()				#Создаем модель TR.fit(X_train, Y_train)				#Обучаем модель Y_TR=TR.predict(X_test)  				#Предсказываем значения для выборк print ('MAE:', round (mean_absolute_error(Y_test, Y_TR),3))		#Метрики print ('?MSE:', round (mean_squared_error(Y_test, Y_TR)**(1/2),3)) print ('R2_score:', round (r2_score(Y_test, Y_TR),3))

MAE: 151734.906

?MSE 220856.721

R2_score: 0.407

Исходя из метрик можно сделать вывод о том, что Линейная регрессия показала лучший результат, поэтому логичнее выбрать ее. Однако, мы не задавались вопросами из чего состоит ошибка модели, не является ли модель переобученной, и пр. Вполне вероятно, что к ухудшению результата DecisionTreeRegressor приводит именно переобучение, так как мы даже не ограничивали глубину дерева в параметрах модели. Можем легко проверить это перебрирая глубину деревьев в коротком цикле:

dep,score=[],[] for i in range(3,16):     TR = DecisionTreeRegressor(max_depth=i)     TR.fit(X_train, Y_train)     Y_TR=TR.predict(X_test)     dep.append(i)     score.append(mean_squared_error(Y_test, Y_TR)**(1/2))	#Массив значений ?MSE 		plt.rcParams['figure.figsize']=6,3 		plt.plot(dep, score)

Очевидно, что лучший показатель при max_depth=7, и, посмотрев, на метрики (MAE: 124861.441, ?MSE 175322.737, R2_score: 0.626) становиться понятно, что модель с таким ограничением аналогична линейной регрессии по качеству. Также мы можем попробовать оценить какие признаки оказались наиболее важны для модели для прогноза стоимости:
Исходя из графика видно, что на стоимость больше всего влияет grade – общая субъективная оценка дома риелторской компанией (что, кстати, говорит о компетентности оценки :-))), на втором месте – площадь дома, а на третьем – год последнего ремонта. Показатели количества спален, ванных комнат, этажей же модель посчитала незначимыми для прогноза. Для лучшего понимания результатов, посчитаем среднюю ошибку в % — по линейной регрессии средняя ошибка 27,5%, то есть модель ошибается чуть больше, чем на четверть при прогнозе стоимости дома, что довольно много.
Можно ли улучшить результаты? Да, несомненно, на текущем этапе мы получили только базовое решение – некую отправную точку для сравнения лучше или хуже будут модели, которые мы можем создать более сложными методами или применяя более сложную обработку данных.

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

Источник: habr.com

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