TensorFlow, Keras и глубокое обучение без докторской степени

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

Эта кодовая лаборатория использует набор данных MNIST , набор из 60 000 помеченных цифр, который занимал поколения докторов наук в течение почти двух десятилетий. Вы решите проблему менее чем за 100 строк кода Python/TensorFlow.

Что вы узнаете

  • Что такое нейронная сеть и как ее обучить
  • Как построить базовую однослойную нейронную сеть с помощью tf.keras
  • Как добавить больше слоев
  • Как настроить график скорости обучения
  • Как построить сверточные нейронные сети
  • Как использовать методы регуляризации: отсев, нормализация партии
  • Что такое переобучение

Что вам понадобится

Просто браузер. Этот семинар можно полностью провести с помощью Google Colaboratory.

Обратная связь

Пожалуйста, сообщите нам, если вы видите что-то не так в этой лаборатории или считаете, что ее следует улучшить. Мы обрабатываем отзывы через вопросы GitHub [ ссылка для обратной связи ].

Эта лаборатория использует Google Colaboratory и не требует настройки с вашей стороны. Вы можете запустить его с Chromebook. Пожалуйста, откройте файл ниже и выполните ячейки, чтобы ознакомиться с блокнотами Colab.

Welcome to Colab.ipynb

Дополнительные инструкции ниже:

Выберите серверную часть графического процессора

В меню Colab выберите Runtime > Change runtime type, а затем выберите GPU. Подключение к среде выполнения произойдет автоматически при первом выполнении, или вы можете использовать кнопку «Подключиться» в правом верхнем углу.

Ноутбучное исполнение

Выполняйте ячейки по одной, щелкая ячейку и используя Shift-ENTER. Вы также можете запустить всю записную книжку, выбрав Runtime > Run all .

Оглавление

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

Скрытые ячейки

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

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

keras_01_mnist.ipynb

Работая с блокнотом, сосредоточьтесь на визуализациях. См. пояснения ниже.

Тренировочные данные

У нас есть набор рукописных цифр, которые были помечены, чтобы мы знали, что представляет собой каждое изображение, то есть число от 0 до 9. В блокноте вы увидите отрывок:

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

Как узнать, хорошо ли работает обученная нейронная сеть? Использование обучающего набора данных для тестирования сети было бы мошенничеством. Он уже видел этот набор данных несколько раз во время обучения и, безусловно, очень эффективен на нем. Нам нужен еще один помеченный набор данных, который никогда не видели во время обучения, чтобы оценить «реальную» производительность сети. Это называется « проверочный набор данных ».

Подготовка

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

Справа «точность» — это просто процент правильно распознанных цифр. По ходу тренировки он растет, и это хорошо.

Слева мы видим «потерю» . Чтобы управлять обучением, мы определим функцию «потери», которая представляет, насколько плохо система распознает цифры, и попытаемся минимизировать ее. Здесь вы видите, что потери снижаются как для данных обучения, так и для данных проверки по мере прохождения обучения: это хорошо. Это означает, что нейронная сеть обучается.

Ось X представляет количество «эпох» или итераций по всему набору данных.

Предсказания

Когда модель обучена, мы можем использовать ее для распознавания рукописных цифр. Следующая визуализация показывает, насколько хорошо он работает с несколькими цифрами, отображаемыми из локальных шрифтов (первая строка), а затем с 10 000 цифр проверочного набора данных. Прогнозируемый класс отображается под каждой цифрой, красным цветом, если он неверен.

Как видите, эта исходная модель не очень хороша, но все же правильно распознает некоторые цифры. Его окончательная точность проверки составляет около 90%, что не так уж плохо для упрощенной модели, с которой мы начинаем, но это все же означает, что она пропускает 1000 цифр проверки из 10 000. Это гораздо больше, что можно отобразить, поэтому кажется, что все ответы неверны (красный).

Тензоры

Данные хранятся в матрицах. Изображение в градациях серого размером 28x28 пикселей помещается в двумерную матрицу 28x28. Но для цветного изображения нам нужно больше размеров. На пиксель приходится 3 значения цвета (красный, зеленый, синий), поэтому понадобится трехмерная таблица с размерами [28, 28, 3]. А для хранения партии из 128 цветных изображений нужна четырехмерная таблица с размерностями [128, 28, 28, 3].

Эти многомерные таблицы называются «тензорами» , а список их измерений является их «формой» .

В двух словах

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

ведьма.png

Для моделей, построенных в виде последовательности слоев, Keras предлагает Sequential API. Например, классификатор изображений, использующий три плотных слоя, можно записать в Keras следующим образом:

model = tf.keras.Sequential([
    tf.keras.layers.Flatten(input_shape=[28, 28, 1]),
    tf.keras.layers.Dense(200, activation="relu"),
    tf.keras.layers.Dense(60, activation="relu"),
    tf.keras.layers.Dense(10, activation='softmax') # classifying into 10 classes
])

# this configures the training of the model. Keras calls it "compiling" the model.
model.compile(
  optimizer='adam',
  loss= 'categorical_crossentropy',
  metrics=['accuracy']) # % of correct answers

# train the model
model.fit(dataset, ... )

Один плотный слой

Рукописные цифры в наборе данных MNIST представляют собой изображения в градациях серого размером 28x28 пикселей. Самый простой подход к их классификации — использовать 28x28=784 пикселя в качестве входных данных для однослойной нейронной сети.

Снимок экрана 26 июля 2016 г., 12.32.24.png

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

На рисунке выше представлена ​​однослойная нейронная сеть с 10 выходными нейронами, поскольку мы хотим классифицировать цифры по 10 классам (от 0 до 9).

С умножением матриц

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

матмул.gif

Используя первый столбец весов в матрице весов W, мы вычисляем взвешенную сумму всех пикселей первого изображения. Эта сумма соответствует первому нейрону. Используя второй столбец весов, делаем то же самое для второго нейрона и так до 10-го нейрона. Затем мы можем повторить операцию для оставшихся 99 изображений. Если мы назовем X матрицей, содержащей наши 100 изображений, все взвешенные суммы для наших 10 нейронов, вычисленные на 100 изображениях, будут просто XW, умножением матриц.

Теперь каждый нейрон должен добавить свое смещение (константу). Поскольку у нас есть 10 нейронов, у нас есть 10 констант смещения. Назовем этот вектор из 10 значений b. Его необходимо добавить в каждую строку ранее вычисленной матрицы. Используя немного магии под названием «трансляция», мы напишем это с помощью простого знака «плюс».

Наконец, мы применяем функцию активации, например «softmax» (поясняется ниже), и получаем формулу, описывающую однослойную нейронную сеть, примененную к 100 изображениям:

Скриншот 2016-07-26 в 16.02.36.png

В Керасе

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

tf.keras.layers.Dense(10, activation='softmax')

Углубляться

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

Единственным отличием, кроме количества нейронов, будет выбор функции активации.

Функции активации: relu, softmax и sigmoid

Обычно вы используете функцию активации "relu" для всех слоев, кроме последнего. Последний слой в классификаторе будет использовать активацию «softmax».

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

Самая популярная функция активации называется «RELU» для Rectified Linear Unit. Это очень простая функция, как вы можете видеть на графике выше.

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

Активация Softmax для классификации

Последний слой нашей нейронной сети имеет 10 нейронов, потому что мы хотим классифицировать рукописные цифры по 10 классам (0,..9). Он должен вывести 10 чисел от 0 до 1, представляющих вероятность того, что эта цифра будет 0, 1, 2 и так далее. Для этого на последнем слое мы будем использовать функцию активации под названием «softmax» .

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

Выход последнего слоя перед активацией иногда называют «логитами» . Если этот вектор равен L = [L0, L1, L2, L3, L4, L5, L6, L7, L8, L9], то:

Перекрестная энтропийная потеря

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

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

Градиентный спуск

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

Перекрестная энтропия является функцией весов, смещений, пикселей тренировочного изображения и его известного класса.

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

градиентный спуск2.png

Мини-пакетирование и импульс

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

Этот метод, иногда называемый «стохастическим градиентным спуском», имеет еще одно, более прагматичное преимущество: работа с партиями также означает работу с большими матрицами, которые обычно легче оптимизировать на GPU и TPU.

Однако схождение все еще может быть немного хаотичным и может даже остановиться, если вектор градиента равен нулю. Значит ли это, что мы нашли минимум? Не всегда. Компонент градиента может быть равен нулю на минимуме или максимуме. С вектором градиента с миллионами элементов, если все они нули, вероятность того, что каждый ноль соответствует точке минимума и ни один из них — точке максимума, довольно мала. В многомерном пространстве седловые точки довольно распространены, и мы не хотим останавливаться на них.

Иллюстрация: седловая точка. Градиент равен 0, но он не является минимальным во всех направлениях. (Атрибуция изображения Wikimedia: By Nicoguaro — собственная работа, CC BY 3.0 )

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

Глоссарий

пакет или мини-пакет : обучение всегда выполняется на пакетах обучающих данных и меток. Это помогает алгоритму сходиться. «Пакетное» измерение обычно является первым измерением тензоров данных. Например, тензор формы [100, 192, 192, 3] содержит 100 изображений размером 192x192 пикселей с тремя значениями на пиксель (RGB).

кросс-энтропийная потеря : специальная функция потерь, часто используемая в классификаторах.

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

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

labels : другое название «классов» или правильных ответов в задаче контролируемой классификации.

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

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

потеря : функция ошибки, сравнивающая выходные данные нейронной сети с правильными ответами

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

одногорячее кодирование : класс 3 из 5 кодируется как вектор из 5 элементов, все нули, кроме 3-го, который равен 1.

relu : выпрямленная линейная единица. Популярная функция активации для нейронов.

sigmoid : еще одна функция активации, которая раньше была популярна и до сих пор полезна в особых случаях.

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

тензор : «тензор» похож на матрицу, но с произвольным количеством измерений. Одномерный тензор — это вектор. Двумерный тензор представляет собой матрицу. И тогда у вас могут быть тензоры с 3, 4, 5 или более измерениями.

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

keras_01_mnist.ipynb

Пройдемся по всем ячейкам в этой тетради.

Ячейка «Параметры»

Здесь определяется размер пакета, количество эпох обучения и расположение файлов данных. Файлы данных размещаются в корзине Google Cloud Storage (GCS), поэтому их адрес начинается с gs://

Ячейка «Импорт»

Сюда импортированы все необходимые библиотеки Python, включая TensorFlow, а также matplotlib для визуализаций.

Ячейка « Утилиты визуализации [RUN ME] »

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

Ячейка « tf.data.Dataset: анализ файлов и подготовка наборов данных для обучения и проверки »

Эта ячейка использовала API tf.data.Dataset для загрузки набора данных MNIST из файлов данных. На эту ячейку не нужно тратить слишком много времени. Если вы заинтересованы в API tf.data.Dataset, вот учебник, который объясняет его: Конвейеры данных со скоростью TPU . На данный момент основы таковы:

Изображения и метки (правильные ответы) из набора данных MNIST хранятся в виде записей фиксированной длины в 4 файлах. Файлы могут быть загружены с помощью специальной функции фиксированной записи:

imagedataset = tf.data.FixedLengthRecordDataset(image_filename, 28*28, header_bytes=16)

Теперь у нас есть набор данных байтов изображения. Их нужно расшифровать в образы. Мы определяем функцию для этого. Изображение не сжато, поэтому функции не нужно ничего декодировать ( decode_raw практически ничего не делает). Затем изображение преобразуется в значения с плавающей запятой от 0 до 1. Мы могли бы изменить его здесь как 2D-изображение, но на самом деле мы сохраняем его как плоский массив пикселей размером 28 * 28, потому что это то, что ожидает наш первоначальный плотный слой.

def read_image(tf_bytestring):
    image = tf.decode_raw(tf_bytestring, tf.uint8)
    image = tf.cast(image, tf.float32)/256.0
    image = tf.reshape(image, [28*28])
    return image

Мы применяем эту функцию к набору данных с помощью .map и получаем набор изображений:

imagedataset = imagedataset.map(read_image, num_parallel_calls=16)

Мы делаем то же самое чтение и декодирование для меток и .zip изображения и метки вместе:

dataset = tf.data.Dataset.zip((imagedataset, labelsdataset))

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

dataset = dataset.cache()
dataset = dataset.shuffle(5000, reshuffle_each_iteration=True)
dataset = dataset.repeat()
dataset = dataset.batch(batch_size)
dataset = dataset.prefetch(tf.data.experimental.AUTOTUNE)

API tf.data.Dataset имеет все необходимые служебные функции для подготовки наборов данных:

.cache кэширует набор данных в оперативной памяти. Это крошечный набор данных, поэтому он будет работать. .shuffle его с буфером из 5000 элементов. Важно, чтобы обучающие данные были хорошо перемешаны. .repeat зацикливает набор данных. Мы будем тренироваться на нем несколько раз (несколько эпох). .batch несколько изображений и ярлыков в мини-приложение. Наконец, .prefetch может использовать ЦП для подготовки следующего пакета, в то время как текущий пакет обучается на графическом процессоре.

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

Ячейка "Модель Кераса"

Все наши модели будут состоять из прямых последовательностей слоев, поэтому для их создания мы можем использовать стиль tf.keras.Sequential . Изначально здесь это один плотный слой. В нем 10 нейронов, потому что мы классифицируем рукописные цифры по 10 классам. Он использует активацию «softmax», потому что это последний слой в классификаторе.

Модель Keras также должна знать форму своих входных данных. Для его определения можно использовать tf.keras.layers.Input . Здесь входные векторы представляют собой плоские векторы значений пикселей длиной 28*28.

model = tf.keras.Sequential(
  [
    tf.keras.layers.Input(shape=(28*28,)),
    tf.keras.layers.Dense(10, activation='softmax')
  ])

model.compile(optimizer='sgd',
              loss='categorical_crossentropy',
              metrics=['accuracy'])

# print model layers
model.summary()

# utility callback that displays training curves
plot_training = PlotTraining(sample_rate=10, zoom=1)

Настройка модели выполняется в Keras с помощью функции model.compile . Здесь мы используем базовый оптимизатор 'sgd' (стохастический градиентный спуск). Для модели классификации требуется функция кросс-энтропийной потери, которая в Keras называется 'categorical_crossentropy' . Наконец, мы просим модель вычислить показатель 'accuracy' , который представляет собой процент правильно классифицированных изображений.

Keras предлагает очень удобную утилиту model.summary() , которая выводит детали созданной вами модели. Ваш любезный инструктор добавил утилиту PlotTraining (определяется в ячейке «утилиты визуализации»), которая будет отображать различные кривые обучения во время обучения.

Ячейка «Обучить и проверить модель»

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

model.fit(training_dataset, steps_per_epoch=steps_per_epoch, epochs=EPOCHS,
          validation_data=validation_dataset, validation_steps=1,
          callbacks=[plot_training])

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

Ячейка «Визуализировать прогнозы»

Как только модель обучена, мы можем получить от нее прогнозы, вызвав model.predict() :

probabilities = model.predict(font_digits, steps=1)
predicted_labels = np.argmax(probabilities, axis=1)

Здесь мы подготовили набор печатных цифр, отрендеренных из местных шрифтов, в качестве теста. Помните, что нейронная сеть возвращает вектор из 10 вероятностей из своего окончательного «softmax». Чтобы получить метку, мы должны выяснить, какая вероятность наибольшая. np.argmax из библиотеки numpy делает это.

Чтобы понять, зачем нужен параметр axis=1 , вспомните, что мы обработали пакет из 128 изображений, поэтому модель возвращает 128 векторов вероятностей. Форма выходного тензора [128, 10]. Мы вычисляем argmax для 10 вероятностей, возвращаемых для каждого изображения, таким образом, axis=1 (первая ось равна 0).

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

Годип.png

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

Снимок экрана 27 июля 2016 г., 15.36.55.png

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

Например, ваша модель может выглядеть так (не забудьте запятые, tf.keras.Sequential принимает список слоев, разделенных запятыми):

model = tf.keras.Sequential(
  [
      tf.keras.layers.Input(shape=(28*28,)),
      tf.keras.layers.Dense(200, activation='sigmoid'),
      tf.keras.layers.Dense(60, activation='sigmoid'),
      tf.keras.layers.Dense(10, activation='softmax')
  ])

Посмотрите на "резюме" вашей модели. Теперь у него как минимум в 10 раз больше параметров. Должно быть в 10 раз лучше! Но почему-то это не...

Поражение, похоже, тоже зашкаливает. Что-то не так.

Вы только что познакомились с нейронными сетями, какими люди их проектировали в 80-х и 90-х годах. Неудивительно, что они отказались от этой идеи, начав так называемую «зиму ИИ». Действительно, по мере добавления слоев нейронным сетям становится все труднее сойтись.

Оказывается, глубокие нейронные сети со многими слоями (20, 50, даже 100 сегодня) могут работать очень хорошо, если прибегнуть к паре математических грязных приемов, чтобы заставить их сходиться. Открытие этих простых приемов — одна из причин возрождения глубокого обучения в 2010-х годах.

Активация RELU

relu.png

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

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

Лучший оптимизатор

В очень многомерных пространствах, таких как здесь, — у нас порядка 10 000 весов и смещений — часто встречаются «седловые точки». Это точки, которые не являются локальными минимумами, но где градиент, тем не менее, равен нулю, и оптимизатор градиентного спуска остается там. TensorFlow имеет полный набор доступных оптимизаторов, в том числе те, которые работают с некоторой инерцией и безопасно преодолевают седловые точки.

Случайные инициализации

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

Вам нечего делать, так как Керас и так поступает правильно.

НаН ???

Формула кросс-энтропии включает логарифм, а log(0) — это не число (NaN, числовой сбой, если хотите). Может ли вход кросс-энтропии быть 0? Ввод исходит от softmax, который по существу является экспоненциальным, а экспоненциальное никогда не равно нулю. Так что мы в безопасности!

Действительно? В прекрасном мире математики мы были бы в безопасности, но в компьютерном мире exp(-150), представленное в формате float32, равно нулю, и кросс-энтропия дает сбой.

К счастью, вам здесь тоже нечего делать, так как Keras позаботится об этом и вычислит softmax, за которым следует кросс-энтропия, особенно тщательно, чтобы обеспечить числовую стабильность и избежать ужасных NaN.

Успех?

Теперь вы должны получить точность 97%. Цель этого семинара — существенно превысить 99%, так что давайте продолжим.

Если вы застряли, вот решение на данный момент:

keras_02_mnist_dense.ipynb

Может, попробуем тренироваться быстрее? Скорость обучения по умолчанию в оптимизаторе Adam составляет 0,001. Попробуем увеличить.

Кажется, ускорение не сильно помогает, и что это за шум?

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

помедленнее.png

Хорошее решение — начать быстро и снижать скорость обучения по экспоненте. В Keras это можно сделать с помощью обратного вызова tf.keras.callbacks.LearningRateScheduler .

Полезный код для копирования-вставки:

# lr decay function
def lr_decay(epoch):
  return 0.01 * math.pow(0.6, epoch)

# lr schedule callback
lr_decay_callback = tf.keras.callbacks.LearningRateScheduler(lr_decay, verbose=True)

# important to see what you are doing
plot_learning_rate(lr_decay, EPOCHS)

Не забудьте использовать созданный вами lr_decay_callback . Добавьте его в список обратных вызовов в model.fit :

model.fit(...,  callbacks=[plot_training, lr_decay_callback])

Влияние этого небольшого изменения впечатляет. Вы видите, что большая часть шума исчезла, и точность теста теперь стабильно превышает 98%.

Теперь модель хорошо сходится. Попробуем пойти еще глубже.

Это помогает?

Не совсем, точность по-прежнему держится на уровне 98%, и посмотрите на потери при валидации. Это идет вверх! Алгоритм обучения работает только с обучающими данными и соответствующим образом оптимизирует потери при обучении. Он никогда не видит данных проверки, поэтому неудивительно, что через некоторое время его работа перестает влиять на потерю проверки, которая перестает падать, а иногда даже возвращается обратно.

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

выпадающий.png

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

Did it work?

Noise reappears (unsurprisingly given how dropout works). The validation loss does not seem to be creeping up anymore, but it is higher overall than without dropout. And the validation accuracy went down a bit. This is a fairly disappointing result.

It looks like dropout was not the correct solution, or maybe "overfitting" is a more complex concept and some of its causes are not amenable to a "dropout" fix?

What is "overfitting"? Overfitting happens when a neural network learns "badly", in a way that works for the training examples but not so well on real-world data. There are regularisation techniques like dropout that can force it to learn in a better way but overfitting also has deeper roots.

overfitting.png

Basic overfitting happens when a neural network has too many degrees of freedom for the problem at hand. Imagine we have so many neurons that the network can store all of our training images in them and then recognise them by pattern matching. It would fail on real-world data completely. A neural network must be somewhat constrained so that it is forced to generalise what it learns during training.

If you have very little training data, even a small network can learn it by heart and you will see "overfitting". Generally speaking, you always need lots of data to train neural networks.

Finally, if you have done everything by the book, experimented with different sizes of network to make sure its degrees of freedom are constrained, applied dropout, and trained on lots of data you might still be stuck at a performance level that nothing seems to be able to improve. This means that your neural network, in its present shape, is not capable of extracting more information from your data, as in our case here.

Remember how we are using our images, flattened into a single vector? That was a really bad idea. Handwritten digits are made of shapes and we discarded the shape information when we flattened the pixels. However, there is a type of neural network that can take advantage of shape information: convolutional networks. Let us try them.

If you are stuck, here is the solution at this point:

keras_03_mnist_dense_lrdecay_dropout.ipynb

In a nutshell

If all the terms in bold in the next paragraph are already known to you, you can move to the next exercise. If your are just starting out with convolutional neural networks, please read on.

convolutional.gif

Illustration: filtering an image with two successive filters made of 4x4x3=48 learnable weights each.

This is how a simple convolutional neural network looks in Keras:

model = tf.keras.Sequential([
    tf.keras.layers.Reshape(input_shape=(28*28,), target_shape=(28, 28, 1)),
    tf.keras.layers.Conv2D(kernel_size=3, filters=12, activation='relu'),
    tf.keras.layers.Conv2D(kernel_size=6, filters=24, strides=2, activation='relu'),
    tf.keras.layers.Conv2D(kernel_size=6, filters=32, strides=2, activation='relu'),
    tf.keras.layers.Flatten(),
    tf.keras.layers.Dense(10, activation='softmax')
])

In a layer of a convolutional network, one "neuron" does a weighted sum of the pixels just above it, across a small region of the image only. It adds a bias and feeds the sum through an activation function, just as a neuron in a regular dense layer would. This operation is then repeated across the entire image using the same weights. Remember that in dense layers, each neuron had its own weights. Here, a single "patch" of weights slides across the image in both directions (a "convolution"). The output has as many values as there are pixels in the image (some padding is necessary at the edges though). It is a filtering operation. In the illustration above, it uses a filter of 4x4x3=48 weights.

However, 48 weights will not be enough. To add more degrees of freedom, we repeat the same operation with a new set of weights. This produces a new set of filter outputs. Let's call it a "channel" of outputs by analogy with the R,G,B channels in the input image.

Screen Shot 2016-07-29 at 16.02.37.png

The two (or more) sets of weights can be summed up as one tensor by adding a new dimension. This gives us the generic shape of the weights tensor for a convolutional layer. Since the number of input and output channels are parameters, we can start stacking and chaining convolutional layers.

Illustration: a convolutional neural network transforms "cubes" of data into other "cubes" of data.

Strided convolutions, max pooling

By performing the convolutions with a stride of 2 or 3, we can also shrink the resulting data cube in its horizontal dimensions. There are two common ways of doing this:

  • Strided convolution: a sliding filter as above but with a stride >1
  • Max pooling: a sliding window applying the MAX operation (typically on 2x2 patches, repeated every 2 pixels)

Illustration: sliding the computing window by 3 pixels results in fewer output values. Strided convolutions or max pooling (max on a 2x2 window sliding by a stride of 2) are a way of shrinking the data cube in the horizontal dimensions.

The final layer

After the last convolutional layer, the data is in the form of a "cube". There are two ways of feeding it through the final dense layer.

The first one is to flatten the cube of data into a vector and then feed it to the softmax layer. Sometimes, you can even add a dense layer before the softmax layer. This tends to be expensive in terms of the number of weights. A dense layer at the end of a convolutional network can contain more than half the weights of the whole neural network.

Instead of using an expensive dense layer, we can also split the incoming data "cube" into as many parts as we have classes, average their values and feed these through a softmax activation function. This way of building the classification head costs 0 weights. In Keras, there is a layer for this: tf.keras.layers.GlobalAveragePooling2D() .

Jump to the next section to build a convolutional network for the problem at hand.

Let us build a convolutional network for handwritten digit recognition. We will use three convolutional layers at the top, our traditional softmax readout layer at the bottom and connect them with one fully-connected layer:

Notice that the second and third convolutional layers have a stride of two which explains why they bring the number of output values down from 28x28 to 14x14 and then 7x7.

Let's write the Keras code.

Special attention is needed before the first convolutional layer. Indeed, it expects a 3D 'cube' of data but our dataset has so far been set up for dense layers and all the pixels of the images are flattened into a vector. We need to reshape them back into 28x28x1 images (1 channel for grayscale images):

tf.keras.layers.Reshape(input_shape=(28*28,), target_shape=(28, 28, 1))

You can use this line instead of the tf.keras.layers.Input layer you had up to now.

In Keras, the syntax for a 'relu'-activated convolutional layer is:

tf.keras.layers.Conv2D(kernel_size=3, filters=12, padding='same', activation='relu')

For a strided convolution, you would write:

tf.keras.layers.Conv2D(kernel_size=6, filters=24, padding='same', activation='relu', strides=2)

To flatten a cube of data into a vector so that it can be consumed by a dense layer:

tf.keras.layers.Flatten()

And for dense layer, the syntax has not changed:

tf.keras.layers.Dense(200, activation='relu')

Did your model break the 99% accuracy barrier? Pretty close... but look at the validation loss curve. Does this ring a bell?

Also look at the predictions. For the first time, you should see that most of the 10,000 test digits are now correctly recognized. Only about 4½ rows of misdetections remain (about 110 digits out of 10,000)

If you are stuck, here is the solution at this point:

keras_04_mnist_convolutional.ipynb

The previous training exhibits clear signs of overfitting (and still falls short of 99% accuracy). Should we try dropout again?

How did it go this time?

It looks like dropout has worked this time. The validation loss is not creeping up anymore and the final accuracy should be way above 99%. Congratulations!

The first time we tried to apply dropout, we thought we had an overfitting problem, when in fact the problem was in the architecture of the neural network. We could not go further without convolutional layers and there is nothing dropout could do about that.

This time, it does look like overfitting was the cause of the problem and dropout actually helped. Remember, there are many things that can cause a disconnect between the training and validation loss curves, with the validation loss creeping up. Overfitting (too many degrees of freedom, used badly by the network) is only one of them. If your dataset is too small or the architecture of your neural network is not adequate, you might see a similar behavior on the loss curves, but dropout will not help.

Finally, let's try to add batch normalization.

That's the theory, in practice, just remember a couple of rules:

Let's play by the book for now and add a batch norm layer on each neural network layer but the last. Do not add it to the last "softmax" layer. It would not be useful there.

# Modify each layer: remove the activation from the layer itself.
# Set use_bias=False since batch norm will play the role of biases.
tf.keras.layers.Conv2D(..., use_bias=False),
# Batch norm goes between the layer and its activation.
# The scale factor can be turned off for Relu activation.
tf.keras.layers.BatchNormalization(scale=False, center=True),
# Finish with the activation.
tf.keras.layers.Activation('relu'),

How is the accuracy now?

With a little bit of tweaking (BATCH_SIZE=64, learning rate decay parameter 0.666, dropout rate on dense layer 0.3) and a bit of luck, you can get to 99.5%. The learning rate and dropout adjustments were done following the "best practices" for using batch norm:

  • Batch norm helps neural networks converge and usually allows you to train faster.
  • Batch norm is a regularizer. You can usually decrease the amount of dropout you use, or even not use dropout at all.

The solution notebook has a 99.5% training run:

keras_05_mnist_batch_norm.ipynb

You will find a cloud-ready version of the code in the mlengine folder on GitHub , along with instructions for running it on Google Cloud AI Platform . Before you can run this part, you will have to create a Google Cloud account and enable billing. The resources necessary to complete the lab should be less than a couple of dollars (assuming 1h of training time on one GPU). To prepare your account:

  1. Create a Google Cloud Platform project ( http://cloud.google.com/console ).
  2. Enable billing.
  3. Install the GCP command line tools ( GCP SDK here ).
  4. Create a Google Cloud Storage bucket (put in the region us-central1 ). It will be used to stage the training code and store your trained model.
  5. Enable the necessary APIs and request the necessary quotas (run the training command once and you should get error messages telling you what to enable).

You have built your first neural network and trained it all the way to 99% accuracy. The techniques learned along the way are not specific to the MNIST dataset, actually they are widely used when working with neural networks. As a parting gift, here is the "cliff's notes" card for the lab, in cartoon version. You can use it to remember what you have learned:

cliffs notes tensorflow lab.png

Next steps

  • After fully-connected and convolutional networks, you should have a look at recurrent neural networks .
  • To run your training or inference in the cloud on a distributed infrastructure, Google Cloud provides AI Platform .
  • Finally, we love feedback. Please tell us if you see something amiss in this lab or if you think it should be improved. We handle feedback through GitHub issues [ feedback link ].

HR.png

Martin Görner ID small.jpg

The author: Martin Görner

Twitter: @martin_gorner

All cartoon images in this lab copyright: alexpokusay / 123RF stock photos