С Керасом и Матплотлибом

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

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

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

Весь код написан на Python в записных книжках Jupyter и в одном модуле. Вы можете найти код на Github.

Как это работает

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

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

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

Два предостережения, прежде чем я начну:

  1. Пока этот код работает только для моделей Keras с одной функцией и одной меткой.
  2. Это собака медлителен, и Керас будет ныть по этому поводу. Используйте это для обучения, а не для производства. Дополнительные мысли о производительности и о том, как сделать ее управляемой, см. Ниже.

Построение графика модели

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

  1. Фиксированная диаграмма разброса подходящих данных. (Серые точки заполняют большую часть сюжета.)
  2. График выхода последней модели кандидата. (Сплошная синяя линия.)
  3. Визуализация потери. (Закрашенная синяя область вокруг сплошной линии.)

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

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

Две другие интересные серии данных, особенно для непрофессионалов, знакомых с машинным обучением, можно увидеть на меньших графиках справа от изображения выше:

  1. График потерь в процессе обучения для всего набора данных (или репрезентативной выборки).
  2. График потерь для каждой партии в процессе обучения.

В рамках обратного вызова вычисление данных, необходимых для графиков, выполняется просто:

y_pred = model.predict(X)
loss = np.square(y - y_pred)

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

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

if loss_smoothing > 1:
    loss = np.convolve(loss,
             np.ones((loss_smoothing,))/loss_smoothing, mode='same')

Отрисовка графиков - это в основном простое использование Matplotlib, за исключением того, что каждый компонент создается только один раз, например.

y_pred_line = ax_main.plot(X, y_pred, '-',
                 color='steelblue', lw=4,
                 label='model', zorder=15)[0]

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

y_pred_line.set_ydata(y_pred)

Несколько замечаний:

  1. Используйте zorder для управления порядком слоев (что поверх чего) в сложном графике.
  2. Диаграмма рассеяния стоит дорого и не меняется. Итак, он рисуется один раз во время настройки и больше никогда.
  3. Предполагается, что потеря представляет собой среднеквадратичную ошибку. В идеале это можно было бы выбирать и переопределять.
  4. Любые штрафы за регуляризацию игнорируются. Было бы интересно отобразить штраф за регуляризацию и визуализировать, когда он станет значительной частью потерь.

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

При использовании % matplotlib inline обратному вызову достаточно вызвать plt.show (), чтобы сбросить изменения на экран. Включите приведенное ниже утверждение, чтобы iPython каждый раз очищал выходной граф. В противном случае каждый вызов plt.show () будет создавать новый график под предыдущими.

clear_output(wait=True)

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

fig.canvas.draw()
fig.canvas.flush_events()

Регистрация обратного звонка

Keras предоставляет параметр лямбда для обратных вызовов самостоятельно.

LambdaCallback

keras.callbacks.LambdaCallback(on_epoch_begin=None, on_epoch_end=None, on_batch_begin=None, on_batch_end=None, on_train_begin=None, on_train_end=None)

LambdaCallback () на самом деле просто оборачивает ваш обратный вызов в некоторый код, который сообщает Keras, когда его вызывать.

redraw_callback = LambdaCallback(on_batch_end=cb_redraw)

Обернутый обратный вызов передается на шаг подгонки.

model.fit(X_train, y_train, epochs=epochs, batch_size=batch_size,
          callbacks=[redraw_callback])

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

def get_redraw(X_in, y_in, model, batch_size, epochs, **kwargs):
    # [argument processing]
    # [setup steps]
    # [initialization of any variables retained between calls]
    def redraw(batch, logs):
        # [code that will be run after each batch]
    # return the closure around the actual callback function
    return redraw

Затем в коде, который передает вызов обратно в model.fit ():

cb_redraw = get_redraw( [arguments] )
redraw_callback = LambdaCallback(on_batch_end=cb_redraw)
model.fit(X_train, y_train, epochs=epochs, batch_size=batch_size,
          callbacks=[redraw_callback])

Представление

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

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

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

Обратный вызов принимает три аргумента, которые управляют размером данных:

  • graph_sparsity может уменьшить объем данных, используемых при пересчете. Значение n говорит об использовании только 1 / n точек данных.
  • scatter_sparsity делает то же самое для диаграммы разброса. Диаграмма разброса не будет узким местом для производительности, поскольку она рисуется только один раз, но может вызвать переполнение.
  • loss_smoothing управляет внешним видом линий потерь, но также уменьшает количество точек, которые необходимо нарисовать. Это может иметь большой эффект, поскольку убыток отображается несколько раз выше и ниже линии модели.

Другой важный рычаг производительности - частота, который определяет, как часто модель оценивается и перерисовывается. Предлагаю 3 варианта частоты:

  • Скалярное значение n указывает, что график следует обновлять каждые n пакетов. Поскольку процесс обучения вначале имеет тенденцию быть быстрым, а позже - медленным, это действительно работает только для небольших наборов данных.
  • Массив истинных / ложных значений, по одному на пакет, указывающий, следует ли обновлять. Обратите внимание, что это общее количество запущенных пакетов. Он не сбрасывается до нуля в конце каждой эпохи.
  • Частотный обратный вызов, который решает для каждого пакета, следует ли обновлять, например в зависимости от того, достаточно ли изменилась модель, чтобы ее можно было перерисовать.

Получение результатов

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

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