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

🚲 🚲 🚲 🚲 🚲 🚲 🚲 🚲 🚲 🚲 🚲 🚲 🚲 🚲 🚲 🚲 🚲 🚲 🚲 🚲 🚲 🚲 🚲 🚲 🚲 🚲

  • Часть I: Извлечение и хранение данных в AWS
  • Часть II. Веб-приложение с Flask, использующее простую модель на Python для прогнозирования доступности станций
  • Часть III. Улучшение модели с помощью дополнительных функций и улучшенного алгоритма.
  • Часть IV: Настройка чат-бота в Slack или Messenger? (пока не уверен в этой части)

🚲 🚲 🚲 🚲 🚲 🚲 🚲 🚲 🚲 🚲 🚲 🚲 🚲 🚲 🚲 🚲 🚲 🚲 🚲 🚲 🚲 🚲 🚲 🚲 🚲 🚲

Эта вторая часть касается фактической модели машинного обучения, которая прогнозирует количество мест, доступных для данной станции Vélib, через один или два часа. Мы создадим очень простую модель, которая работает, и постепенно усовершенствуем ее в следующей части!

Затем мы создадим веб-приложение с помощью Flask для использования прогнозной модели и развернем его на Heroku.

  1. Создайте чистый фрейм данных с предыдущим обновлением

В первой части мы создали историю обновлений станции и сохранили ее в базе данных PSQL. Это необработанные данные, где дата-время соответствует моменту записи строки в базу данных, а response_api — словарь, который возвращает API Vélib.

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

Кроме того, мы хотим узнать количество велосипедов на станциях за 1 час до этого. Действительно, значение, которое мы хотим предсказать, — это количество доступных велосипедов через час, учитывая количество велосипедов на станциях и другие переменные.

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

class AddPreviousVariables:
    def __init__(self, stations_df):
        self.stations_df = stations_df

    def find_previous_update(self, df_row):
        stations_df = self.stations_df
        number_station = df_row.number
        date_time = df_row.last_update
        previous_date_time = date_time - timedelta(hours=1)
        dt_high = previous_date_time + timedelta(minutes=10)
        dt_low = previous_date_time - timedelta(minutes=10)
        previous_update_array = 
stations_df[(stations_df.number == number_station) \
& (stations_df.last_update < dt_high) \
& (stations_df.last_update > dt_low)]
        if (len(previous_update_array) != 0):
            previous_update=previous_update_array.iloc[0]
            last_update_previous=previous_update.last_update
            available_bikes_previous=previous_update.available_bikes
        else:
            last_update_previous = np.nan
            available_bikes_previous = np.nan

        previous_update = pd.Series(
            {'last_update_previous': last_update_previous,
             'available_bikes_previous': available_bikes_previous})
        return previous_update

Обратите внимание, что это очень неэффективно и отнимает много времени, но это лучшее решение, которое я нашел в краткосрочной перспективе. Например, на обработку менее 140 000 строк ушло 40 минут. Это немного раздражает, учитывая, что в конечном итоге нам придется обработать более 15 миллионов. Я постараюсь улучшить эту часть в следующем посте или найти альтернативу — например, Оконные функции PSQL.

Теперь мы можем напрямую получать информацию в виде фрейма данных — своего рода таблицы со столбцами потенциально разных типов (текст, число, дата). Чтобы ограничить количество обрабатываемых данных, я добавил фильтр по районам.

postal_code_list = ['75001', '75002', '75003', '75004', '75005', '75006', '75010', '75011', '75012']

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

2. Создание модели и проверка прогнозов

Мы устанавливаем столбец, который мы пытаемся предсказать — доступные велосипеды — и столбцы, которые будут использоваться для заполнения модели. Затем мы загружаем данные, используя удобный пакет db.py, созданный Yhat.

#train_model.py - extract

target_column = 'available_bikes'
columns_model_list = [‘number’, ‘weekday’, ‘hour’, ‘minute’,   ‘latitude’, ‘longitude’, ‘available_bikes_previous’, ‘weekday_previous’, ‘hour_previous’, ‘minute_previous’, ‘temperature’, ‘humidity’, ‘wind’, ‘precipitation’]

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

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

Мы добавляем эти новые столбцы, вызывая функцию rich_stations, которая также отфильтровывает строки с данными о погоде или предыдущими переменными.

#station_enricher.py - extract
def enrich_stations(df):
    stations_df = df.copy()
    stations_df = add_date_variables(stations_df)

    # Load and add weather data
    path_weather_data = 'files/input/paris_temperature.csv'
    weather_data = load_weather_data(path_weather_data)
    stations_df = add_weather_data(stations_df, weather_data)
    # Filter out rows without weather data
    stations_df = FilterWeatherData(stations_df) 

    stations_df = add_previous_date_variables(stations_df)
    stations_df = FilterPreviousVariables(stations_df)
    stations_df = cast_df(stations_df)
    return stations_df_enriched

Мы делим station_df_enriched на два: набор поездов (80% данных) и тестовый набор. Набор поездов будет использоваться для обучения модели, и мы оценим производительность модели на тестовом наборе, сравнив прогнозируемое значение с фактическим значением. Это даст нам представление о том, насколько хорошо его можно обобщить на новые, невидимые данные.

#data_loader.py - extract
# Get features and target, divided by train & test
logger.info("Split target and features")
features, target = SplitFeaturesTarget(df_enriched, target_column)
logger.info("Train/test split")
features_train, features_test, target_train, target_test = \
    train_test_split(features, target, test_size=0.2, random_state=42)

Теперь мы готовы приступить к обучению! Для этой цели я буду использовать ванильный Random Forest Regressor, используя библиотеку scikit-learn. Напомним, что мы только пытаемся построить предсказатель, который работает, поэтому в данный момент мы не будем идти дальше.

Обучение Random Forest также требует очень много времени — более 77 минут для обучения 107 000 строк! Мы попробуем другие алгоритмы и гиперпараметры, чтобы ускорить итерации.

Мы оцениваем модель по разным метрикам. Within_two — это процент времени, в течение которого ошибка — разница между нашим прогнозом и реальным количеством велосипедов — меньше двух. Это 67%, неплохо, но недостаточно для надежного прогноза.

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

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

Однако так не должно быть, и я ожидаю, что это поведение исчезнет, ​​когда мы будем использовать больше данных. Если нет, то придется копать глубже ⛏ — в следующем посте! Следующие переменные ближе всего к истине. Номер станции, долгота, а также час дня должны быть действительно важными параметрами для прогнозирования количества велосипедов на станции.

PS: MAPE — это пиздец, потому что иногда знаменатель действительно близок к нулю — 1e-6, если быть точным. Игнорируй это !

3. Создание веб-приложения с помощью Flask

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

Войдите в Колба. Это микрофреймворк для Python, что означает очень простой инструмент для создания веб-сайтов с помощью Python.

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

# Load model
model = load_pickle("files/app_model/model.pkl")

# Load list of stations
list_stations = pd.read_csv('files/input/list_stations.csv', encoding='utf-8')

@app.route('/prediction', methods=['POST'])
def ask_prediction():
    number_station = request.form['number_station']
    time_prediction = request.form['time_prediction']
    prediction = predict_available_bikes(model, number_station,   time_prediction)
    return jsonify({'prediction': prediction})

@app.route('/')
def index():
    return render_template('prediction.html', list_stations=list_stations.values.tolist(), number_station=4006)

Я не буду вдаваться в подробности здесь, потому что это не цель этого поста, но когда вы загружаете страницу в первый раз, вы генерируете файл «prediction.html» со ​​списком станций. Затем, когда вы нажимаете кнопку «Предсказать», вы запускаете функцию «ask_prediction» с заданными «number_station» и «time_prediction», и она возвращает количество доступных велосипедов.

За кулисами Javascript делает несколько вещей:

  • Загрузка карты Google и отображение всех станций
  • Запуск POST-запроса при нажатии на кнопку «Предсказать» и возврат результата
  • Обработка ошибок пользователя (хотя я свел пользовательский ввод к минимуму, чтобы избежать проблем)

Более того, каждый раз, когда вы запрашиваете прогноз, мы вызываем Vélib’ API, чтобы найти количество велосипедов, доступных на станции прямо сейчас. Мы также вызываем wunderground api для определения текущей погоды.

Наконец, есть немного HTML и CSS для того, чтобы сделать что-то визуально приличное, но самый важный сторонний инструмент, который я использую, — это bootstrap. Это самая популярная среда HTML, CSS и JS для разработки адаптивных мобильных проектов в Интернете. Это позволяет нам очень легко организовывать различные элементы без проблем с CSS. Я настоятельно рекомендую вам использовать его, если вы хотите создать веб-сайт, особенно из-за его потрясающей системы сетки.

4. Разверните приложение с помощью Heroku

Эта последняя часть всегда занимает больше времени, чем ожидалось, поскольку всегда возникают новые проблемы. Вместо того, чтобы пытаться объяснить, как это сделать с моим ломаным английским, я перенаправлю вас к этому замечательному руководству ниже, которое идеально подходит для нашей ситуации: как получить приложение Flask на Heroku, облачной платформе как сервисе.



Действительно, мы должны перенести каждый фрагмент кода и файлы, которые у нас есть локально на нашем компьютере, в облако ⛅️, чтобы создать веб-сайт, доступный для всех. Обратите внимание, что HTTP по умолчанию использует порт 80, поэтому мы используем его. Наконец, когда придет время развертывания, вам также необходимо отправить файлы конфигурации в Heroku. По моему мнению, самое простое решение — создать еще одну папку на вашем компьютере со всеми файлами, которые вам нужны для вашего приложения, и отправить все на Git и Heroku.

Заключение

Вот и все ! Вы можете нажать на ссылку ниже, чтобы поиграть с приложением. Поскольку я буду продолжать улучшать модель и макет веб-сайта, он может не выглядеть так, как на картинке в начале статьи, когда вы нажмете !



ps: если вы не видите красные станции в приложении, продолжайте обновлять веб-страницу или используйте другой браузер…

Вы можете найти код проекта на github, если хотите внести свой вклад или просто посмотреть на него. Если вам понравилась эта статья/руководство, пожалуйста, не стесняйтесь и нажмите маленькую кнопку с сердечком 🙂

Следующий пост будет о том, как вывести это маленькое приложение на новый уровень, улучшив модель (на несколько порядков?!), а также дизайн и UX. Спасибо за чтение людей!