Все курсы > Вводный курс > Занятие 14
Добро пожаловать на практическую часть вводного курса!
Прежде чем мы перейдем непосредственно к детальному изучению алгоритмов регрессии, несколько слов о том, какое вообще бывает машинное обучение.
Обучение с учителем и без
Про машинное обучение с учителем (Supervised Machine Learning) говорят в том случае, когда в данных, на которых мы обучаем модель, уже заложен некоторый результат. Это может быть конкретное число (например, окружность шеи в задаче регрессии) или принадлежность к определенному классу (тип заемщика в задаче классификации). Другими словами, в этих данных есть зависимая переменная y.
В случае, если такого результата нет, речь идет об обучении без учителя (Unsupervised Machine Learning). Такой тип алгоритмов встречается в задаче кластеризации, когда нужно выделить группу или кластер объектов, но сами эти кластеры заранее не известны (в отличие от классов при классификации).

Теперь давайте поговорим откуда мы будем брать данные.
Модуль datasets библиотеки Scikit-learn
В учебных целях мы будем использовать классические наборы данных, которые уже содержатся в библиотеке Scikit-learn в модуле datasets. Нас будут интересовать три набора данных:
- Данные по 506 районам Бостона. Модель будет предсказывать цену на жилье.
- Набор данных по 569 опухолевым образованиям, которые машина научится определять как злокачественные или доброкачественные.
- Данные по 150 образцам цветов ириса. Мы попросим алгоритм разделить эти цветы на виды (или кластеры), основываясь на их характеристиках.
Регрессия
По традиции вначале откроем ноутбук к этому занятию⧉
Этап 1. Загрузка данных — недвижимость в Бостоне

Вначале загрузим данные.
1 2 3 |
# импортируем данные и поместим их в переменную boston from sklearn.datasets import load_boston boston = load_boston() |
1 2 |
# определим тип данных type(boston) |
1 |
sklearn.utils.Bunch |
Наш набор данных является объектом Bunch, который в свою очередь, представляет собой подкласс питоновского словаря. Это значит, что его структуру мы можем увидеть с помощью метода .keys().
1 |
boston.keys() |
1 |
dict_keys(['data', 'target', 'feature_names', 'DESCR', 'filename']) |
В разделе data содержится информация о признаках (независимые переменные). В target содержится целевая (зависимая) переменная, в feature_names — наименование признаков, а DESCR содержит описание датасета.
Начнем с общего обзора. Для этого прочитаем описание.
1 |
print(boston.DESCR) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
:Number of Instances: 506 :Number of Attributes: 13 numeric/categorical predictive. Median Value (attribute 14) is usually the target. :Attribute Information (in order): - CRIM per capita crime rate by town - ZN proportion of residential land zoned for lots over 25,000 sq.ft. - INDUS proportion of non-retail business acres per town - CHAS Charles River dummy variable (= 1 if tract bounds river; 0 otherwise) - NOX nitric oxides concentration (parts per 10 million) - RM average number of rooms per dwelling - AGE proportion of owner-occupied units built prior to 1940 - DIS weighted distances to five Boston employment centres - RAD index of accessibility to radial highways - TAX full-value property-tax rate per $10,000 - PTRATIO pupil-teacher ratio by town - B 1000(Bk - 0.63)^2 where Bk is the proportion of blacks by town - LSTAT % lower status of the population - MEDV Median value of owner-occupied homes in $1000's :Missing Attribute Values: None |
Каждая единица данных — это район города Бостон. У нас есть информация об уровне преступности (CRIM), качестве воздуха (NOX), транспортной доступности (RAD), налогах (TAX), количестве учителей (PTRATIO), социальном положении населения (LSTAT) и некоторые другие показатели. Целевой переменной является медианная цена недвижимости в каждом из районов (MEDV). Именно ее нам и нужно научиться предсказывать.
Давайте выведем информацию по первым пяти районам. Так как читать сырые данные (в формате массива Numpy) не очень удобно, мы преобразуем их в формат DataFrame из библиотеки Pandas.
1 2 3 4 5 6 |
# для этого передадим функции DataFrame массив признаков boston.data # название столбцов возьмем из boston.feature_names boston_df = pd.DataFrame(boston.data, columns = boston.feature_names) # выведем первые пять районов с помощью функции head() boston_df.head() |

1 2 3 4 5 |
# теперь добавим в таблицу целевую переменную и назовем ее MEDV boston_df['MEDV'] = boston.target # снова воспользуемся функцией head() boston_df.head() |

Важно! По этическим соображениям разработчики библиотеки удалят датасет load_boston из последующих версий. В этом случае сделайте следующее.
Шаг 1. Скачайте файл boston.csv
Шаг 2. Загрузите файл в «Сессионное хранилище» в Google Colab.
Шаг 3. Запустите код ниже.
1 2 3 |
# на выходе вы получите уже сформированный датафрейм boston_df = pd.read_csv('/content/boston.csv') boston_df.head() |
Посмотрим с каким типом переменных нам предстоит работать. От этого будет зависеть, какие статистические графики и инструменты мы будем применять при анализе данных.
1 2 3 |
# посмотрим с каким типом переменных нам предстоит работать # для этого есть метод .info() boston_df.info() |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<class 'pandas.core.frame.DataFrame'> RangeIndex: 506 entries, 0 to 505 Data columns (total 14 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 CRIM 506 non-null float64 1 ZN 506 non-null float64 2 INDUS 506 non-null float64 3 CHAS 506 non-null float64 4 NOX 506 non-null float64 5 RM 506 non-null float64 6 AGE 506 non-null float64 7 DIS 506 non-null float64 8 RAD 506 non-null float64 9 TAX 506 non-null float64 10 PTRATIO 506 non-null float64 11 B 506 non-null float64 12 LSTAT 506 non-null float64 13 MEDV 506 non-null float64 dtypes: float64(14) memory usage: 55.5 KB |
Как мы видим, все переменные, включая целевую, являются количественными (тип float). Помимо этого, мы можем посмотреть основные статистические показатели (summary statistics):
1 2 |
# для этого воспользуемся методом .describe() и округлим значения boston_df.describe().round(2) |

В частности, мы видим количество наблюдений (count), среднее арифметическое (mean), среднее квадратическое отклонение (std), минимальное (min) и максимальное (max) значения, а также первый (25%), второй (50%) и третий (75%) квартиль (второй квартиль это то же самое, что медиана) каждой количественной переменной. Подробнее об этом мы поговорим, когда будем изучать анализ данных на более продвинутом уровне.
Прекрасно, мы уже примерно понимаем, с чем нам предстоит работать.
Этап 2. Предварительная обработка данных
Данные редко поступают в идеальном виде. На этапе предварительной обработки данных (data pre-processing) нам необходимо понять:
- что мы будем делать с пропущенными значениями (missing values) и повторами (duplicates),
- как поступим с категориальными переменными (ведь компьютер не понимает, чем отличается одна категория от другой), и
- как не ухудшить модель из-за того, что диапазон или масштаб одной переменной сильно отличается от масштаба другой.
В работе с этим датасетом пока просто отметим, что пропущенные значения отсутствуют. Это видно из описания и, кроме того, мы можем посчитать пропущенные значения с помощью методов .isnull() и .sum().
1 |
boston_df.isnull().sum() |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
CRIM 0 ZN 0 INDUS 0 CHAS 0 NOX 0 RM 0 AGE 0 DIS 0 RAD 0 TAX 0 PTRATIO 0 B 0 LSTAT 0 MEDV 0 dtype: int64 |
Этап 3. Исследовательский анализ данных
Теперь выполним то, что часто называют исследовательским или разведочным анализом данных (Exploratory Data Analysis, EDA). По большом счету, от нас требуется понять какие взаимосвязи мы можем выявить между переменными, чтобы потом построить модель.
Основной инструмент, который нам доступен на данный момент, это корреляция независимых переменных (признаков, описывающих каждый район) с зависимой, целевой переменной (ценами на жилье). Напомню, что мы говорим о сильной корреляции, когда значение приближается к 1 или −1, и о ее отсутствии, когда значение близко к нулю.
1 2 3 |
# посчитаем коэффициент корреляции для всего датафрейма и округлим значение corr_matrix = boston_df.corr().round(2) corr_matrix |

Такой способ представления корреляции называется корреляционной матрицей. В ней, в частности, мы видим (смотрите на последний столбец или строку), что переменные RM и LSTAT имеют достаточно сильную корреляцию с целевой переменной MEDV, 0,70 и −0,74, соответственно. Также умеренная корреляция наблюдается у переменных PTRATIO, TAX и INDUS.
Взаимосвязь двух переменных также можно визуализировать с помощью диаграммы рассеяния.
1 2 3 4 |
# подготовим данные (поместим столбцы датафрейма в переменные) x1 = boston_df['LSTAT'] x2 = boston_df['RM'] y = boston_df['MEDV'] |
1 2 3 4 5 6 7 8 |
# зададим размер и построим первый график plt.figure(figsize = (10,6)) plt.scatter(x1, y) # добавим подписи plt.xlabel('Процент населения с низким социальным статусом', fontsize = 15) plt.ylabel('Медианная цена недвижимости, тыс. долларов', fontsize = 15) plt.title('Социальный статус населения и цены на жилье', fontsize = 18) |

1 2 3 4 5 6 7 8 |
# зададим размер и построим второй график plt.figure(figsize = (10,6)) plt.scatter(x2, y) # добавим подписи plt.xlabel('Среднее количество комнат', fontsize = 15) plt.ylabel('Медианная цена недвижимости, тыс. долларов', fontsize = 15) plt.title('Среднее количество комнат и цены на жилье', fontsize = 18) |

Этап 4. Отбор и выделение признаков
После того, как мы собрали достаточно данных о взаимосвязи переменных, мы можем начать отбор наиболее значимых признаков (feature selection) и создание или выделение новых (feature extraction). В текущим исследовании, в частности, мы возьмем переменные RM, LSTAT, PTRATIO, TAX и INDUS, поскольку они имеют наиболее высокую корреляцию с целевой переменной MEDV. Создавать новые признаки мы пока не будем.
Поместим наши признаки в переменную X, а цены на жилье в переменную y.
1 2 |
X = boston_df[['RM', 'LSTAT', 'PTRATIO', 'TAX', 'INDUS']] y = boston_df['MEDV'] |
Как мы видим, большая часть времени уходит именно на подготовительные этапы работы с данными, а не на обучение модели и составление прогноза. Это нормально. При работе с реальными данными их подготовка будет занимать еще больше времени.
Этап 5. Обучение и оценка качества модели
Теперь, когда мы загрузили, обработали и исследовали данные, а также отобрали наиболее значимые признаки, мы готовы к обучению модели. Вначале разобьем данные на обучающую и тестовую выборки.
1 2 3 4 5 6 7 8 |
# импортируем необходимый модуль from sklearn.model_selection import train_test_split # размер тестовой выборки составит 30% # также зададим точку отсчета для воспроизводимости X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.3, random_state = 42) |
Посмотрим на размерность новых наборов данных:
1 2 3 4 5 |
# размерность обучающей print(X_train.shape, y_train.shape) # и тестовой выборки print(X_test.shape, y_test.shape) |
1 2 |
(354, 5) (354,) (152, 5) (152,) |
Теперь обучим модель и сделаем прогноз. Мы это уже делали на прошлом занятии.
1 2 3 4 5 6 7 8 |
# из набора линейных моделей библиотеки sklearn импортируем линейную регрессию from sklearn.linear_model import LinearRegression # создадим объект этого класса и запишем в переменную model model = LinearRegression() # обучим нашу модель model.fit(X_train, y_train) |
1 2 3 4 5 |
# на основе нескольких независимых переменных (Х) предскажем цену на жилье (y) y_pred = model.predict(X_test) # выведем первые пять значений с помощью диапазона индексов print(y_pred[:5]) |
1 |
[26.62981059 31.10008241 16.95701338 25.59771173 18.09307064] |
Осталось оценить качество модели. Посчитаем среднеквадратическую ошибку.
1 2 3 4 5 6 |
# импортируем модуль метрик from sklearn import metrics # выведем корень среднеквадратической ошибки # сравним тестовые и прогнозные значения цен на жилье print('Root Mean Squared Error (RMSE):', np.sqrt(metrics.mean_squared_error(y_test, y_pred))) |
1 |
Root Mean Squared Error (RMSE): 5.107447670220913 |
Также рассчитаем новый для нас критерий качества — коэффициент детерминации (R2 или R-квадрат). R2 показывает, какая доля изменчивости целевой переменной объясняется с помощью нашей модели.
1 |
print('R2:', np.round(metrics.r2_score(y_test, y_pred), 2)) |
1 |
R2: 0.65 |
В данном случае мы видим, что 65% изменчивости цены объясняется независимыми переменными, которые мы выбрали для нашей модели.
Конечно, обе эти метрики можно улучшить, и именно такими улучшениями мы и будем заниматься на последующих курсах.
Подведем итог
Сегодня мы выполнили первое полноценное исследование данных и построили модель. Мы (1) начали с того, что подгрузили данные из библиотеки Scikit-learn, (2) обработали и (3) исследовали данные, а также (4) отобрали наиболее значимые признаки. После этого мы (5) построили модель регрессии и оценили ее качество.
Вопросы для закрепления
Чем обучение с учителем отличается от обучения без учителя?
Посмотреть правильный ответ
Ответ: при обучении с учителем мы задаем нашей модели цель (y), к которой она должна стремиться в процессе оптимизации, при обучении без учителя такой цели нет.
Чем регрессия отличается от классификации?
Посмотреть правильный ответ
Ответ: регрессия предсказывает число, классификация относит объекты к определенному классу
Что показывает коэффициент детерминации?
Посмотреть правильный ответ
Ответ: коэффициент детерминации (R2) показывает долю изменчивости целевой переменной (y), которую мы можем объяснить с помощью нашей модели. Если R2 равен единице (100%), то наша модель полностью объясняет любые изменения в целевой переменной. Если R2 равен нулю (0%), наша модель никак не объясняет эти изменения.
Дополнительные упражнения⧉ вы найдете в конце ноутбука.
Теперь мы готовы к изучению алгоритма классификации.
Ответы на вопросы
Вопрос. Скажите, а почему вы всегда используете число 42, например, в random_state = 42 и в np.random.seed(42)?
Ответ. Никакой технической необходимости указывать число 42 нет, оно может быть любым.
Это число очень часто используется в проектах по Data Science и взято из романа Дугласа Адамса «Автостопом по галактике» (The Hitchhiker’s Guide to the Galaxy). По сюжету, один из героев книги ищет ответ на Главный вопрос жизни, вселенной и всего такого. Через семь с половиной миллонов лет непрерывных вычислений специально созданный для этих целей компьютер «Думатель» (Deep Thought) выдал ответ. Этим ответом и было число 42.
Если в Гугле ввести Answer to the Ultimate Question of Life, the Universe, and Everything, то в результате поиска появится калькулятор с числом 42.
Вопрос. Выполнив упражнение после [лекции], получил, как мне кажется, немного странный результат: RMSE уменьшилось, а $ R^2 $ увеличился. Не могли бы вы подсказать, нормально ли это? Заранее спасибо за ответ.
Ответ. Спасибо за вопрос. Да, это нормально. Уменьшение RMSE показывает, что наша модель ($ \hat{y}_{(i)} $) стала меньше отклоняться от фактических значений ($y_{(i)}$).
Одновременно, после добавления новых признаков/зависимых переменных модель смогла лучше объяснить изменчивость целевой переменной (цены на жилье). Это отразилось на увеличении коэффициента детерминации $ R^2 $.
Продемонстрирую на формулах. Вот как мы рассчитываем RMSE.
$$ RMSE = \sqrt{ \frac {1}{n} \sum_{i=1} (y_{(i)}-\hat{y}_{(i)}) ^2 } $$
Мы считаем квадрат отклонения фактических значений от прогнозных, усредняем и извлекаем квадратный корень. Теперь давайте посмотрим на формулу $ R^2 $.
$$ R^2 = 1-\frac {SS_{residuals}}{SS_{total}} = 1-\frac{\sum{(y_{(i)}-\hat{y}_{(i)})^2}}{\sum{y_{(i)}-\bar{y}}} $$
В данном случае мы делим сумму квадратов отклонений от прогнозных значений $ SS_{residuals} $ на сумму квадратов отклонений от среднего $ SS_{total} $ и вычитаем получившийся результат из единицы. В Википедии⧉ есть хорошая иллюстрация этой метрики.

Обратите внимание, что RMSE и числитель в формуле $ R^2 $ — это почти одно и то же.
$$ SS_{residuals} = RMSE^2 \times n $$
$$ R^2 = 1-\frac {RMSE^2 \times n}{SS_{total}} $$
Если мы уменьшаем RMSE, то соответственно уменьшаем числитель дроби и увеличиваем $R^2$. Другими словами, логично, что если мы смогли уменьшить RMSE (то есть наша модель лучше описывает данные), то она их и лучше объясняет.
Может показаться, что RMSE и $R^2$ дублируют друг друга с тем отличием, что RMSE измеряет ошибку в абсолютных значениях, а коэффициент детерминации объясняет изменчивость в процентах. Однако здесь есть один нюанс. Добавление новых признаков увеличивает, но никогда не уменьшает коэффициент детерминации. Для этого есть две причины:
- При любом количестве признаков знаменатель (то есть $SS_{total}$) остается неизменным, потому что зависит только от целевой переменной.
- Одновременно, при обучении модели (то есть подборе коэффициентов), алгоритм, если новый признак ухудшает $ R^2 $, может придать этому коэффициенту значение ноль и не включать его в финальную модель. Например, в модели $ \hat{y} = \beta_0 + \beta_1 x_1 + \beta_2 x_2 + \beta_3 x_3 $ придать коэффициенту $\beta_3$ значение ноль и, таким образом, исключить его. Значит числитель дроби может только уменьшаться, а $R^2$ только увеличиваться. Зачастую такое увеличение $R^2$ вызвано случаем и не улучшает качество модели.
Как следствие, возникает желание увеличивать количество признаков в погоне за большим коэффициентом детерминации. Для того чтобы этому противостоять, используется скорректированный $R^2$ (adjusted $R^2$).
$$ R^2_{adj} = 1-(1-R^2) \times \left(\frac{n-1}{n-k-1} \right) $$
где k — это количество признаков, а n — количество наблюдений.
При увеличении количества признаков знаменатель увеличивается, уменьшает дробь и, как следствие, понижает весь коэффициент детерминации. И только если новый признак действительно вносит существенный вклад в качество модели, новый скорректированный коэффициент детерминации вырастет.
Именно поэтому полезно измерять как RMSE, чтобы понимать, насколько ваша модель в абсолютных значениях отклоняется от факта, так и скорректированный $R^2$, чтобы понимать, с учетом количества признаков, насколько хорошо модель объясняет изменчивость целевой переменной.
Рассчитаем $ R^2_{adj} $ для модели из лекции.
1 2 3 |
# возьмем n и k для тестовых данных n, k = X_test.shape[0], X_test.shape[1] n, k |
1 |
(152, 5) |
1 2 |
# подставим их в формулу 1 - (1 - model.score(X_test, y_test)) * ((n - 1) / (n - k - 1)) |
1 |
0.6379243352311836 |
Как вы видите, мы были «наказаны» (penalized) за наличие пяти признаков в модели. При построении модели из упражнения, думаю, коэффициент детерминации будет скорректирован еще сильнее.