Применение модели последовательности для добычи открытых данных Waze о дорожных происшествиях с использованием Python и Keras.

В этом руководстве я покажу вам, как использовать модель глубокого обучения RNN, чтобы найти закономерности в отчете об открытых данных об инцидентах Waze Traffic, и спрогнозировать, возникнут ли в ближайшее время серьезные пробки. Вмешательства могут быть приняты эффективно.

Конкуренция

1 декабря 2018 года я участвовал в соревновании по программированию на хакатоне в UNT Inspire Park во Фриско, штат Техас. Хакатон называется «HackNTX».

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

Хост предоставил конкурентам различные виды наборов открытых данных с некоторыми предопределенными демонстрационными задачами. Вы можете выбрать свою новую задачу и решить ее в соревновании.

Данные Waze были одним из предоставленных наборов данных.

В Китае я никогда не использовал приложение Waze для навигации. Для меня это в новинку.

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

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

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

Данные Waze, предоставленные организатором HackNTX, касаются дорожных происшествий с 1 по 29 ноября в районе DFW.

Необработанные данные были в формате TSV, а общий размер - около 300 МБ.

Каждая строка представляет собой отдельный отчет об инциденте, содержащий координаты и отметку времени.

На этапе исследовательского анализа данных я сделал несколько визуализаций на основе данных.

Я впервые услышал о QGIS, и это очень мощное программное обеспечение. Спасибо, Джесси, за то, что научил меня его использовать!

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

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

Рисунок выше был сделан с помощью пакета Python Geopandas. Он показывает три различных типа дорожных событий, а именно пробку (красным цветом), аварии (желтым цветом) и «остановившуюся машину на обочине» (синим цветом). Обратите внимание, что они были извлечены только из первых 3000 строк в наборе данных.

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

Я извлек из данных все уникальные типы событий и получил следующий список:

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

Я объединил эти два типа событий в набор A. В то время как другие события можно рассматривать как набор B.

Для каждого события в наборе A я проследил 30 минут назад и суммировал все события, о которых сообщалось на одной и той же дороге, в последовательность. Всего их 987 штук. Однако некоторые из них - пустые списки, то есть до серьезного затора ничего не сообщалось, и все произошло внезапно. Для таких пробок никто не может их предсказать, поэтому я решил их убрать и сохранил 861 непустую последовательность.

Точно так же я произвольно извлек 861 непустую последовательность, проследив события из набора B. Обратите внимание, что эти последовательности не приводили к серьезным пробкам.

Теперь, когда у нас есть последовательности в качестве входных данных, мы помечаем последовательности, сгенерированные из набора A, как 1, а остальные - как 0.

Наш исследовательский вопрос: можем ли мы использовать модель для классификации этих данных о последовательностях?

Результат оказался удачным. Мы выиграли первый приз!

Наша команда, получившая прозвище «часы-пельмени», сформирована Чуниинг Ван, приглашенным аспирантом из Уханьского университета и мной, представляющим лабораторию UNT IIA. Каждый из нас получил по 100 долларов. Посмотри, как мы были счастливы!

На сайте HackNTX вскоре появился отчет о конкурсе Hackathon. Вот ссылка".

А через несколько дней новость передала и ЕНТ.

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

В последних частях этой статьи я попытаюсь показать вам, как реализовать модель RNN для классификации последовательностей событий Waze с использованием Python и Keras.

Среда

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

Я использовал Google Colab. Это довольно круто, и люди из Google так великодушны, предоставляя пользователям бесплатные GPU и TPU.

Пожалуйста, сначала загрузите и установите Google Chrome, а затем нажмите эту ссылку, чтобы установить плагин под названием Colaboratory.

И вы увидите его желтый значок на панели расширений Chrome.

Я уже загрузил весь код и данные этого руководства на это репозиторий github.

Перейдите по ссылке выше и нажмите demo.ipynb.

Теперь вы можете щелкнуть значок Colaboratory, и Chrome автоматически откроет для вас Google Colab и загрузит в него этот файл ipynb.

Нажмите кнопку «КОПИРОВАТЬ НА ДИСК», которую я обвел красным цветом на приведенном выше снимке экрана, и Google создаст вам копию файла Jupyter Notebook на вашем Google Диске.

Перейдите в меню «Runtime» и нажмите «Change runtime type».

Дважды проверьте, что параметры установлены, как показано ниже:

Сохраните его, и тогда все готово.

Чтобы запустить фрагмент кода, просто нажмите кнопку запуска слева.

Давайте сделаем это шаг за шагом, и я дам вам необходимые пояснения.

Код

Нам нужно загрузить пакет Pandas для обработки табличных данных.

import pandas as pd

Нам понадобится другой пакет для загрузки данных, сохраненных на этапе предварительной обработки. Это называется рассолом.

import pickle

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

Давайте клонируем репозиторий github для этого руководства и перенесем данные в рабочую область Google Colab.

!git clone https://github.com/wshuyi/demo_traffic_jam_prediction.git

Данные не такие уж большие и будут загружены в короткие сроки.

Cloning into 'demo_traffic_jam_prediction'...
remote: Enumerating objects: 6, done.[K
remote: Counting objects: 100% (6/6), done.[K
remote: Compressing objects: 100% (4/4), done.[K
remote: Total 6 (delta 0), reused 3 (delta 0), pack-reused 0[K
Unpacking objects: 100% (6/6), done.

Сообщим Jupyter Notebook путь к папке с данными.

from pathlib import Path
data_dir = Path('demo_traffic_jam_prediction')

Мы откроем файл данных и загрузим две разные переменные данных с помощью pickle.

with open(data_dir / 'data.pickle', 'rb') as f:
    [event_dict, df] = pickle.load(f)

Сначала заглянем в словарь событий под названием event_dict

event_dict

Вот все типы событий.

{1: 'road closed due to construction',
 2: 'traffic jam',
 3: 'stopped car on the shoulder',
 4: 'road closed',
 5: 'other',
 6: 'object on roadway',
 7: 'major event',
 8: 'pothole',
 9: 'traffic heavier than normal',
 10: 'road construction',
 11: 'fog',
 12: 'accident',
 13: 'slowdown',
 14: 'stopped car',
 15: 'small traffic jam',
 16: 'stopped traffic',
 17: 'heavy traffic',
 18: 'minor accident',
 19: 'medium traffic jam',
 20: 'malfunctioning traffic light',
 21: 'missing sign on the shoulder',
 22: 'animal on the shoulder',
 23: 'animal struck',
 24: 'large traffic jam',
 25: 'hazard on the shoulder',
 26: 'hazard on road',
 27: 'ice on roadway',
 28: 'weather hazard',
 29: 'flooding',
 30: 'road closed due to hazard',
 31: 'hail',
 32: 'huge traffic jam'}

Затем давайте посмотрим, что в фрейме данных называется df.

Это довольно длинный стол. Давайте просто отобразим первые 10 строк.

df.head(10)

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

Затем мы попросим Панд показать нам последние 10 строк.

df.tail(10)

Теперь, когда мы правильно загрузили данные, мы увидим, какая строка содержит самую длинную последовательность.

Мы будем использовать функцию idxmax() из Pandas, и она поможет нам получить индекс наибольшего значения.

max_len_event_id = df.events.apply(len).idxmax()
max_len_event_id

Вот результат:

105

Давайте углубимся в последовательность, которая есть в этой строке:

max_len_event = df.iloc[max_len_event_id]
max_len_event.events

В результате получился довольно длинный список.

['stopped car on the shoulder',
 'heavy traffic',
 'heavy traffic',
 'heavy traffic',
 'slowdown',
 'stopped traffic',
 'heavy traffic',
 'heavy traffic',
 'heavy traffic',
 'heavy traffic',
 'traffic heavier than normal',
 'stopped car on the shoulder',
 'traffic jam',
 'heavy traffic',
 'stopped traffic',
 'stopped traffic',
 'stopped traffic',
 'heavy traffic',
 'traffic jam',
 'stopped car on the shoulder',
 'stopped traffic',
 'stopped traffic',
 'stopped traffic',
 'heavy traffic',
 'traffic heavier than normal',
 'traffic heavier than normal',
 'traffic heavier than normal',
 'traffic heavier than normal',
 'heavy traffic',
 'stopped traffic',
 'traffic heavier than normal',
 'pothole',
 'stopped car on the shoulder',
 'traffic jam',
 'slowdown',
 'stopped traffic',
 'heavy traffic',
 'traffic heavier than normal',
 'traffic jam',
 'traffic jam',
 'stopped car on the shoulder',
 'major event',
 'traffic jam',
 'traffic jam',
 'stopped traffic',
 'heavy traffic',
 'traffic heavier than normal',
 'stopped car on the shoulder',
 'slowdown',
 'heavy traffic',
 'heavy traffic',
 'stopped car on the shoulder',
 'traffic jam',
 'slowdown',
 'slowdown',
 'heavy traffic',
 'stopped car on the shoulder',
 'heavy traffic',
 'minor accident',
 'stopped car on the shoulder',
 'heavy traffic',
 'stopped car on the shoulder',
 'heavy traffic',
 'stopped traffic',
 'heavy traffic',
 'traffic heavier than normal',
 'heavy traffic',
 'stopped car on the shoulder',
 'traffic heavier than normal',
 'stopped traffic',
 'heavy traffic',
 'heavy traffic',
 'heavy traffic',
 'stopped car on the shoulder',
 'slowdown',
 'stopped traffic',
 'heavy traffic',
 'stopped car on the shoulder',
 'traffic heavier than normal',
 'heavy traffic',
 'minor accident',
 'major event',
 'stopped car on the shoulder',
 'stopped car on the shoulder']

Если вы внимательно изучите последовательность, вы заметите, что на этой дороге есть намеки на серьезную пробку. Однако вам нужно позволить машине «почувствовать» и автоматически классифицировать последовательности.

Какова самая длинная последовательность?

maxlen = len(max_len_event.events)
maxlen

Вот ответ:

84

Ух ты! Это длинный список событий!

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

Чтобы сделать это эффективно, нам нужно отменить ранее загруженный словарь. То есть, чтобы попытаться преобразовать формат «индекс: тип события» в «тип события: индекс».

reversed_dict = {}
for k, v in event_dict.items():
  reversed_dict[v] = k

Давайте исследуем перевернутый словарь.

reversed_dict

Вот результат.

{'accident': 12,
 'animal on the shoulder': 22,
 'animal struck': 23,
 'flooding': 29,
 'fog': 11,
 'hail': 31,
 'hazard on road': 26,
 'hazard on the shoulder': 25,
 'heavy traffic': 17,
 'huge traffic jam': 32,
 'ice on roadway': 27,
 'large traffic jam': 24,
 'major event': 7,
 'malfunctioning traffic light': 20,
 'medium traffic jam': 19,
 'minor accident': 18,
 'missing sign on the shoulder': 21,
 'object on roadway': 6,
 'other': 5,
 'pothole': 8,
 'road closed': 4,
 'road closed due to construction': 1,
 'road closed due to hazard': 30,
 'road construction': 10,
 'slowdown': 13,
 'small traffic jam': 15,
 'stopped car': 14,
 'stopped car on the shoulder': 3,
 'stopped traffic': 16,
 'traffic heavier than normal': 9,
 'traffic jam': 2,
 'weather hazard': 28}

Мы сделали это.

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

def map_event_list_to_idxs(event_list):
  list_idxs = []
  for event in (event_list):
    idx = reversed_dict[event]
    list_idxs.append(idx)
  return list_idxs

Давайте попробуем функцию из нашего самого длинного списка.

map_event_list_to_idxs(max_len_event.events)

Результат:

[3,
 17,
 17,
 17,
 13,
 16,
 17,
 17,
 17,
 17,
 9,
 3,
 2,
 17,
 16,
 16,
 16,
 17,
 2,
 3,
 16,
 16,
 16,
 17,
 9,
 9,
 9,
 9,
 17,
 16,
 9,
 8,
 3,
 2,
 13,
 16,
 17,
 9,
 2,
 2,
 3,
 7,
 2,
 2,
 16,
 17,
 9,
 3,
 13,
 17,
 17,
 3,
 2,
 13,
 13,
 17,
 3,
 17,
 18,
 3,
 17,
 3,
 17,
 16,
 17,
 9,
 17,
 3,
 9,
 16,
 17,
 17,
 17,
 3,
 13,
 16,
 17,
 3,
 9,
 17,
 18,
 7,
 3,
 3]

Теперь мы загружаем numpy и некоторые служебные функции из Keras.

import numpy as np
from keras.utils import to_categorical
from keras.preprocessing.sequence import pad_sequences

Нам нужно выяснить, сколько разных типов событий у нас есть.

len(event_dict)

Вот результат:

32

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

df.events.apply(map_event_list_to_idxs)

Результат:

0      [9, 17, 18, 14, 13, 17, 3, 13, 16, 3, 17, 17, ...
1                                             [2, 10, 3]
2                                                    [2]
3                                                    [2]
4                               [2, 2, 2, 2, 2, 2, 2, 9]
5                                             [3, 2, 17]
6                                             [3, 2, 17]
7                        [2, 15, 2, 17, 2, 2, 13, 17, 2]
8                                  [17, 2, 2, 16, 17, 2]
9                                  [17, 2, 2, 16, 17, 2]
10     [17, 16, 17, 2, 17, 3, 17, 17, 16, 17, 16, 18,...
11                                                  [17]
12                                                  [17]
13                                              [24, 24]
14                                    [24, 2, 24, 24, 2]
15                                    [24, 2, 24, 24, 2]
16     [2, 10, 2, 2, 2, 18, 16, 16, 7, 2, 16, 2, 2, 9...
17     [2, 10, 2, 2, 2, 18, 16, 16, 7, 2, 16, 2, 2, 9...
18                               [24, 24, 24, 16, 2, 16]
19                               [24, 24, 24, 16, 2, 16]
20                                                [2, 2]
21                                            [2, 16, 2]
22                                            [2, 16, 2]
23                                                [2, 2]
24                                                [2, 2]
25                                              [24, 24]
26                                                [2, 2]
27                                         [2, 2, 2, 17]
28                                            [2, 19, 2]
29                                                  [24]
                             ...
831                     [9, 9, 9, 2, 9, 9, 17, 2, 9, 17]
832                                            [3, 3, 3]
833                                 [2, 9, 2, 17, 17, 2]
834       [3, 3, 17, 3, 13, 3, 3, 23, 9, 3, 3, 25, 3, 3]
835      [3, 17, 9, 14, 9, 17, 14, 9, 2, 9, 3, 2, 2, 17]
836                                                  [2]
837         [17, 2, 16, 3, 9, 17, 17, 17, 13, 17, 9, 17]
838    [13, 17, 17, 3, 3, 16, 17, 16, 17, 16, 3, 9, 1...
839                                                  [2]
840                                                  [3]
841                                                  [2]
842    [17, 17, 17, 3, 17, 23, 16, 17, 17, 3, 2, 13, ...
843                                               [3, 3]
844                                                  [2]
845                     [2, 17, 2, 2, 2, 2, 2, 17, 2, 2]
846                                   [7, 17, 3, 18, 17]
847                                            [3, 3, 3]
848    [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, ...
849                                               [2, 2]
850          [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 13, 3, 2]
851                                            [2, 2, 2]
852                                          [16, 2, 16]
853                [3, 16, 5, 3, 17, 3, 16, 9, 3, 2, 17]
854                                                 [16]
855    [3, 3, 3, 3, 3, 3, 3, 3, 2, 13, 3, 6, 3, 6, 3,...
856                    [17, 17, 17, 2, 3, 2, 2, 2, 2, 2]
857                                               [2, 2]
858                                  [2, 2, 9, 17, 2, 2]
859                            [17, 3, 2, 2, 2, 2, 2, 2]
860    [17, 3, 3, 17, 3, 17, 2, 3, 18, 14, 3, 3, 16, ...
Name: events, Length: 1722, dtype: object

Нам, людям, трудно распознать значение каждого представляемого числа. Однако для компьютера это намного проще.

Мы называем список результатов sequences и отображаем первые пять строк.

sequences = df.events.apply(map_event_list_to_idxs).tolist()
sequences[:5]

Вот результат:

[[9,
  17,
  18,
  14,
  13,
  17,
  3,
  13,
  16,
  3,
  17,
  17,
  16,
  3,
  16,
  17,
  9,
  17,
  2,
  17,
  2,
  7,
  16,
  17,
  17,
  17,
  17,
  13,
  5,
  17,
  9,
  9,
  16,
  16,
  3],
 [2, 10, 3],
 [2],
 [2],
 [2, 2, 2, 2, 2, 2, 2, 9]]

Обратите внимание, что первая строка намного длиннее следующих.

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

data = pad_sequences(sequences, maxlen=maxlen)
data

Вот дополненные последовательности:

array([[ 0,  0,  0, ..., 16, 16,  3],
       [ 0,  0,  0, ...,  2, 10,  3],
       [ 0,  0,  0, ...,  0,  0,  2],
       ...,
       [ 0,  0,  0, ..., 17,  2,  2],
       [ 0,  0,  0, ...,  2,  2,  2],
       [ 0,  0,  0, ...,  3,  3,  2]], dtype=int32)

Теперь все последовательности имеют одинаковую длину.

Нам нужно будет получить столбец метки и сохранить его в переменной с именем labels.

labels = np.array(df.label)

Поскольку нам нужно использовать несколько случайных функций, чтобы ваш результат был идентичен моему, мы укажем случайное начальное значение как 12.

np.random.seed(12)

Когда вы закончите первый запуск кода, не стесняйтесь его изменять.

Мы перемешиваем последовательности вместе с соответствующими метками.

indices = np.arange(data.shape[0])
np.random.shuffle(indices)
data = data[indices]
labels = labels[indices]

Обучающий набор будет содержать 80% данных, а остальные 20% войдут в набор для проверки.

training_samples = int(len(indices) * .8)
validation_samples = len(indices) - training_samples

Следующие коды разделяют данные на наборы для обучения и проверки, а также метки.

X_train = data[:training_samples]
y_train = labels[:training_samples]
X_valid = data[training_samples: training_samples + validation_samples]
y_valid = labels[training_samples: training_samples + validation_samples]

Покажем содержание обучающих данных:

X_train

Вот результат.

array([[ 0,  0,  0, ..., 15, 15,  3],
       [ 0,  0,  0, ...,  0,  2,  2],
       [ 0,  0,  0, ...,  0,  0, 16],
       ...,
       [ 0,  0,  0, ...,  2, 15, 16],
       [ 0,  0,  0, ...,  2,  2,  2],
       [ 0,  0,  0, ...,  0,  0,  2]], dtype=int32)

Обратите внимание, что, поскольку мы заполнили последовательности 0 в качестве значения заполнения, теперь у нас есть 33 типа событий вместо 32.

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

num_events = len(event_dict) + 1

Если мы просто поместим числа в модель классификации, она будет рассматривать каждое число как непрерывное значение. Однако это не так. Итак, мы позволим числам пройти через слой встраивания и преобразовать каждое число (представляющее определенный тип события) в вектор. Каждый вектор будет содержать 20 скаляров.

embedding_dim = 20

Исходная матрица вложения будет сгенерирована случайным образом.

embedding_matrix = np.random.rand(num_events, embedding_dim)

Наконец, теперь мы можем построить модель.

Мы используем последовательную модель в Keras и накладываем разные слои один за другим, как мы играем с лего.

Первый слой - это слой встраивания, затем следует слой LSTM, последний слой является плотным, функция активации которого сигмоидальная, чтобы выполнить двоичную классификацию.

from keras.models import Sequential
from keras.layers import Embedding, Flatten, Dense, LSTM

units = 32

model = Sequential()
model.add(Embedding(num_events, embedding_dim))
model.add(LSTM(units))
model.add(Dense(1, activation='sigmoid'))

Если вы не знакомы с Керасом, я рекомендую вам прочитать «Глубокое обучение с помощью Python» Франсуа Шоле, создателя Кераса.

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

model.layers[0].set_weights([embedding_matrix])
model.layers[0].trainable = False

Затем мы обучаем модель и сохраняем ее в файл h5.

model.compile(optimizer='rmsprop',
              loss='binary_crossentropy',
              metrics=['acc'])
history = model.fit(X_train, y_train,
                    epochs=50,
                    batch_size=32,
                    validation_data=(X_valid, y_valid))
model.save("mymodel_embedding_untrainable.h5")

При сильной поддержке ТПУ обучение проходит довольно быстро.

После обучения модели давайте визуализируем кривые точности и потерь с помощью matplotlib.

import matplotlib.pyplot as plt

acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']

epochs = range(1, len(acc) + 1)

plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()

plt.figure()

plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()

plt.show()

Это кривая точности.

Как видите, неплохо. Если мы используем фиктивную модель для предсказания всего как метки 0 (или все как 1), точность останется на уровне 0,50. Таким образом, наша модель, по-видимому, уловила какой-то паттерн и превзошла фиктивный.

Однако это очень нестабильно.

Тогда давайте посмотрим на кривую потерь.

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

Важнее выяснить причину.

Обратите внимание, что мы использовали случайно инициализированную матрицу внедрения, которая оставалась статичной во время фазы обучения. Это может привести к неприятностям.

Итак, следующим шагом мы можем провести эксперимент, чтобы позволить обучить и настроить слой Embedding.

from keras.models import Sequential
from keras.layers import Embedding, Flatten, Dense, LSTM

units = 32

model = Sequential()
model.add(Embedding(num_events, embedding_dim))
model.add(LSTM(units))
model.add(Dense(1, activation='sigmoid'))

Единственное отличие в коде - параметр trainable был установлен на True.

model.layers[0].set_weights([embedding_matrix])
model.layers[0].trainable = True

Скомпилируем модель и прогоним еще раз.

model.compile(optimizer='rmsprop',
              loss='binary_crossentropy',
              metrics=['acc'])
history = model.fit(X_train, y_train,
                    epochs=50,
                    batch_size=32,
                    validation_data=(X_valid, y_valid))
model.save("mymodel_embedding_trainable.h5")

А еще мы рисуем кривые точности и потерь.

import matplotlib.pyplot as plt

acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']

epochs = range(1, len(acc) + 1)

plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()

plt.figure()

plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()

plt.show()

Кривая точности показана ниже.

Как видите, стало лучше. Колебание кривой точности валидации снизилось, а точность валидации превысила 0,75.

Эта модель в некоторой степени более ценна.

Однако не стоит так быстро делать выводы. Если вы посмотрите на кривую потерь, вы не сможете быть столь оптимистичны.

Начиная с полпути, тенденции потерь на разных сетах пошли в разном направлении.

Это намек на переоснащение.

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

Вы можете либо добавить больше данных для обучения, либо снизить сложность.

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

Однако, чтобы снизить сложность модели, это можно легко сделать с помощью Dropout.

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

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

Мы добавим два параметра, связанных с отсевами. Для этого мы используем dropout=0.2, recurrent_dropout=0.2 при определении уровня LSTM.

from keras.models import Sequential
from keras.layers import Embedding, Flatten, Dense, LSTM

units = 32

model = Sequential()
model.add(Embedding(num_events, embedding_dim))
model.add(LSTM(units, dropout=0.2, recurrent_dropout=0.2))
model.add(Dense(1, activation='sigmoid'))

Мы оставим параметр trainable слоя внедрения равным True.

model.layers[0].set_weights([embedding_matrix])
model.layers[0].trainable = True

Давайте снова проведем обучение.

model.compile(optimizer='rmsprop',
              loss='binary_crossentropy',
              metrics=['acc'])
history = model.fit(X_train, y_train,
                    epochs=50,
                    batch_size=32,
                    validation_data=(X_valid, y_valid))
model.save("mymodel_embedding_trainable_with_dropout.h5")

Нет никаких модификаций части визуализации.

import matplotlib.pyplot as plt

acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']

epochs = range(1, len(acc) + 1)

plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()

plt.figure()

plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()

plt.show()

На кривой точности вы не увидите ничего интересного.

Однако, если вы посмотрите на кривую потерь, вы увидите значительное улучшение.

Кривая потери валидации более плавная и намного ближе к тенденции потери тренировки.

Была решена проблема чрезмерной подгонки, и теперь модель стала более стабильной и поддающейся обобщению для невидимых данных.

Затем администрация трафика может использовать модель для прогнозирования возникновения серьезных пробок с помощью отчета Waze с открытыми данными об инцидентах. Ожидаемая точность модели составляет около 75%.

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

Резюме

Надеюсь, вы сможете получить следующие сведения из этого урока.

  • Модель последовательности, такая как RNN и LSTM, может использоваться не только с текстами, но и с другими последовательными данными.
  • Вы можете использовать слой встраивания в таких задачах, даже если у них нет предварительно обученных моделей встраивания слов, таких как word2vec, glove или fasttext. Убедитесь, что вы установили веса встраиваемого слоя обучаемого.
  • Вы можете попытаться победить перетягивание несколькими разными методами. Отказ от учебы - один из них. В нашем случае это эффективно.

Надеюсь, теперь вы справитесь с собственной задачей классификации с последовательными данными.

Удачного глубокого обучения!

Другие мои учебники по глубокому обучению: