В этом видео мы продолжим анализировать задачу Bike Sharing. Напомню, что в задаче требуется по историческим данным о погодных условиях и аренде велосипедов спрогнозировать количество занятых велосипедов на определенный час определенного дня. Давайте снова импортируем нужные библиотеки и загрузим данные. Напомню, как они выглядят: это некоторая матрица, некоторая таблица, состоящая из описания объектов, и в последнем столбце мы видим значения целевой переменной. Итак, давайте снова сгенерируем признаки «месяц» и «час». И также разобъем данные на обучение и тест. Разбивать данные будем по времени, обучаться будем на более ранних данных, тестироваться будем на более поздних данных. Таким образом, в тест мы отправляем 1000 объектов за наиболее позднее время. Итак, посмотрим на размер получившихся наборов данных. Практически 10 000 данных идет в обучение, и ровно 1000 объектов идет в тест. Теперь давайте снова разделим данные на признаки и целевую метку. В качестве целевой метки используем столбец count, а из признаков вырезаем datetime, как идентификатор объектов, вырезаем столбец count, а также вырезаем два столбца — casual и registered, потому что эти столбцы в сумме дают значение целевой переменной. Делаем это для обучения и для теста. Теперь давайте поговорим про данные. В предыдущем уроке мы с вами рассматривали признаки, которые имеют численный тип. В данном уроке мы с вами хотим обработать все признаки, но при этом мы понимаем, что если признаки являются данными разного типа, то, наверное, и методы, которыми мы будем их обрабатывать, должны отличаться. Поэтому давайте сделаем следующее. Давайте разделим наши данные по типам. Тип данных мы понимаем из описания — оно нам доступно. И давайте получим следующее. Давайте получим некоторый логический индекс, который будет показывать, в каком столбце находятся данные данного типа. То есть мы хотим получить индекс, который будет принимать значение true в позиции нужных столбцов. Для начала давайте сделаем это для бинарных данных. Таких признаков всего два — это holiday и workingday. Признаки принимают значения 0 или 1, соответственно, если данные являются выходным, то первый признак принимает значение 1, в противоположном случае — 0. И если день является рабочим, то второй признак принимает значение 1, в противоположном случае — 0. Вот давайте получим логический индекс для бинарных данных. И сразу посмотрим, как это выглядит. Вот мы получаем массив, у которого true соответствует тем позициям в DataFrame, в котором мы видим эти столбцы. Аналогичные расчеты давайте сделаем для категориальных признаков. Таких признаков всего три: это сезон, погода по ощущениям и месяц. Да, получили правильный индекс и закончим процедуру на численных данных. Все индексы у нас есть. Теперь давайте строить Pipeline, давайте строить цепочку преобразований, в результате которой мы получим dataset, состоящий из обработанных данных. Сначала давайте создадим модель, которую мы хотим применять — это снова будет регрессия SGD. Однако давайте сразу же укажем некоторые параметры, которые мы подобрали в прошлый раз. Понятно, что с учетом новых данных эти параметры могут перестать быть оптимальными, но тем не менее это неплохое приближение. Создаем модель, и теперь давайте посмотрим на цепочку. В данном случае она выглядит чуть более сложной, но давайте разберемся, что же там такое. Фактически наше преобразование снова состоит из двух больших шагов: первый шаг — это обработка данных, второй шаг — это непосредственно построение модели. Давайте для начала посмотрим на построение модели, оно не отличается — здесь мы задаем имя шага и передаем нашу модель, наш регрессор. Теперь давайте разберемся с преобразованием данных. Для того чтобы по-разному обрабатывать разные части dataset, разные столбцы из набора данных, нам нужно сначала данные разбить, получить три набора данных. Дальше каждый из них обработать по отдельности и дальше собрать их вместе. Причем собирать их каждый раз нужно в одинаковом порядке — это важно, потому что фактически мы можем использовать это преобразование для кросс-валидации, для подбора параметров, поэтому нам действительно нужно получать каждый раз набор данных в заданном порядке. Вот давайте посмотрим, как такого добиться. Для того чтобы сначала разделить данные на несколько частей, а потом собрать их вместе, нам нужна функциональность FeatureUnion — это некоторый трансформер, который ровно это и делает. Вот давайте его создадим и будем работать в рамках этого трансформера, внутри него мы будем делать три преобразования, то есть фактически данные разбиваются на три части. Первое преобразование — работа с бинарными данными, второе преобразование — работа с численными данными, и последнее преобразование — работа с категориальными данными. Это и есть наш transformer list — список преобразований. Теперь смотрим на первое. Бинарные данные устраивают нас в таком виде, в котором они уже есть, мы не хотим никак их изменять. Поэтому что мы здесь делаем? Мы просто используем FunctionTransformer, которому мы передаем наш логический индекс — логический индекс, соответствующий бинарным признакам, и говорим, что нам достаточно просто эти данные выбрать, отделить их от всего dataset и, не изменяя, положить их отдельно. Вот ровно это здесь и происходит. Теперь следующий тип данных — эти числовые признаки. Здесь нам придется применить целую цепочку преобразований, потому что фактически их два. Сначала эти данные нужно отделить от остальных, потом нужно их отмасштабировать с помощью scaler. В данном случае нам придется объявить внутри Pipeline, в отличие от предыдущего случая, но это также несложно. Объявляем Pipeline и говорим, что он состоит из двух шагов: первый шаг — это трансформер, то есть фактически мы передаем сюда логический индекс для числовых данных и отделяем их от всех остальных. И дальше к данным, которые мы отделили, мы применяем scaling — делаем масштабирование. На этом преобразование числовых признаков заканчивается. Последний тип признаков, с которыми мы работаем — это категориальные данные. Здесь также будет цепочка преобразований, поэтому создаем Pipeline. Шаги следующие: первый шаг — это отделение этих данных, то есть фактически мы передаем сюда логический индекс для категориальных признаков и отделяем нужную часть набора данных. И дальше применяем методику hot encoding — помните, что если у нас есть категориальный признак, внутри которого доступно несколько значений, то после кодирования hot encoding мы получаем n признаков, каждый из которых является бинарным. Фактически он, каждый из новых признаков, соответствует отдельному значению старого категориального признака — принимает значение 1 на тех объектах, где достигается это значение, и 0 на всех остальных. Вот ровно такое преобразование мы здесь и применяем. На этом наша цепочка преобразований заканчивается, и последнее, что нужно сделать — это собрать данные вместе и применить второй шаг, обучить модель. Вот давайте такую цепочку создадим, и попробуем для начала просто ее обучить, и оценить качество. Делаем это с помощью метода fit, обучение закончено. И теперь оцениваем качество. Вот получаем, что мы ошибаемся на 120 велосипедах — приблизительно то же самое, что и раньше. То есть фактически преобразование данных не помогло нам принципиально улучшить модель. Но давайте посмотрим: может быть, подбор параметров способен на это повлиять? Давайте посмотрим, как в случае такой сложной цепочки обращаться к параметрам. Делаем это с помощью комбинации методов get_params и keys. И видим, что да, но не очень удобно, потому что нужно использовать расширенные имена, нужно указывать имя шага, потом имена всех промежуточных шагов и так вплоть до параметра. Но, в общем-то, это тоже можно сделать. Вот давайте мы с вами не будем перебирать слишком много параметров, а для того чтобы ускорить процесс, переберем только два: это коэффициенты alpha и eta0. Создадим такой словарь. И теперь давайте снова воспользуемся поиском по сетке, будем делать полный перебор. Метрика, которую мы оцениваем — это средняя ошибка. И делаем кросс-валидацию на четыре фолда. Итак, создаем сетку, и теперь давайте ее обучать. В этот раз все должно быть довольно быстро. Да, видим, что процесс занял меньше секунды. И теперь давайте посмотрим на лучшую оценку и на лучшую комбинацию параметров. Ну вот видим, что ошибка вновь довольно большая и мы получили правильные веса. Теперь давайте воспользуемся этой лучшей моделью и оценим ее качество на отложенном dataset. Итак, строим наше предсказание, теперь считаем метрику. Видим, что мы ошибаемся в среднем на 125 велосипедов. Это не очень хороший результат. Давайте выведем метки и выведем наши результаты. Метки и результаты. Да, видим, что, действительно, ошибаемся мы очень сильно. Но давайте теперь построим следующий график. Отобразим наши объекты в координатах исходных значений целевой метки и наших прогнозов. Вот мы получили график. Во-первых, мы видим, что область точек достаточно далека от диагональной области, которая получилась бы в случае хорошей модели. А с другой стороны, мы понимаем, что наш график очень сильно похож на тот график, который мы получили на предыдущем уроке. Это означает, что все наши преобразования — генерация новых признаков, вновь подбор параметров — не привели к улучшению модели, мы не смогли ее улучшить. Как же нам быть в такой ситуации? Но давайте для начала проанализируем, почему это так. Мы с вами строим линейную модель — это говорит о том, что мы предполагаем некоторую линейную зависимость между признаками и целевой переменной. На самом деле это не совсем так. Ведь мы понимаем, что не всегда количество занятых велосипедов линейно зависит, например, от времени. Понятно, что на каком-то отрезке это будет так, допустим, рост времени будет приводить к росту занятых велосипедов, но на другом отрезке это может быть наоборот. И те же самые рассуждения более-менее справедливы, более-менее применимы к другим признакам, например таким как температура, или номер месяца, или давление. Соответственно, что мы может сделать? С одной стороны, чтобы помочь нашей линейной модели, мы можем сгенерировать другие признаки — такие признаки, на которых мы такую зависимость будем предполагать. Другой вариант — мы можем воспользоваться другой моделью, более сложной, которая умеет учитывать нелинейные зависимости между признаками и целевой функцией. Процесс генерации новых признаков является творческим, поэтому, конечно же, вы можете самостоятельно изучить набор данных и придумать новые признаки. Попробуйте, это довольно интересно. А мы с вами исследуем другой подход. Давайте обучим другую, нелинейную модель. Вы такие модели еще не проходили, но это будет некоторым анонсом к следующей неделе. Возможно, это только подогреет ваш интерес. Модель, которую я предлагаю строить, называется «случайный лес». Она находится в модуле ensemble. Вот давайте создадим случайный лес, зададим туда некоторые параметры, не будем пока акцентировать на них внимание. И сделаем следующее: построим точно такую же цепочку преобразований с единственным изменением — подменим наш регрессор с линейной модели на случайный лес, на random forest. Итак, получили цепочку, теперь давайте модель обучим — делаем это с помощью метода fit. И даже не будем подбирать никакие параметры, просто обучим модель и посмотрим на ее ошибку. Видим, что случайный лес обучается несколько дольше, чем линейная модель. Но чуть позже вы узнаете, почему это происходит. Итак, наша модель обучена, оцениваем ее качество. Так, видим, что ошибка сильно уменьшилась. Теперь мы ошибаемся в среднем на 80 велосипедов — ну, кажется, что это сильно лучше, чем было, но непонятно — это улучшение принципиальное или нет? 80 и 120 — большая ли это разница? Давайте для начала выведем наши предсказания и правильные значения целевой функции. М-да, кажется, что стало получше, но все еще непонятно. Давайте попробуем снова построить аналогичный график — отобразим объекты в координатах «значение целевой метки» и «наше предсказание» и посмотрим, приблизились ли эти объекты к диагональной области. А заодно давайте сравним график для «случайного леса» и для линейной модели. Итак, наш график готов. Теперь я думаю, что разница очевидна. Видим, что в данном случае наши объекты очень близко подошли, достаточно близко подошли к диагональной области. Получается, что с помощью этой модели у нас получилось установить зависимость гораздо лучше. А мы на этом заканчиваем. На этом видео мы научились обрабатывать признаки разных типов, а также научились строить сложные вложенные цепочки преобразований. Мы построили несколько моделей на данных Bike Sharing — это линейная модель и модель random forest, а также сравнили их качества. Я надеюсь, что модель random forest вас заинтересовала, потому что уже на следующей неделе вы перейдете к изучению новых семейств алгоритмов, среди которых random forest обязательно будет.