1. Обзор
В этой лабораторной работе вы научитесь создавать, обучать и настраивать собственные сверточные нейронные сети с нуля, используя Keras и TensorFlow 2. Теперь это можно сделать за считанные минуты, благодаря возможностям TPU. Вы также изучите различные подходы, от очень простого трансферного обучения до современных сверточных архитектур, таких как Squeezenet. Эта лабораторная работа включает теоретические объяснения нейронных сетей и является хорошей отправной точкой для разработчиков, изучающих глубокое обучение.
Чтение научных работ по глубокому обучению может быть сложным и запутанным. Давайте рассмотрим на практике современные архитектуры сверточных нейронных сетей.

Что вы узнаете
- Чтобы использовать Keras и тензорные процессоры (TPU) для более быстрого создания собственных моделей.
- Для эффективного использования API tf.data.Dataset и формата TFRecord для загрузки обучающих данных.
- Чтобы схитрить 😈, можно использовать трансферное обучение вместо создания собственных моделей.
- Для использования последовательного и функционального стилей моделирования Keras.
- Чтобы создать собственный классификатор Keras с слоем softmax и функцией потерь на основе кросс-энтропии.
- Для тонкой настройки вашей модели необходимо правильно подобрать сверточные слои.
- Для изучения современных идей архитектуры сверточных сетей, таких как модули, глобальное усредняющее пулинг и т. д.
- Цель — создать простую современную сверточную сеть, используя архитектуру Squeezenet.
Обратная связь
Если вы заметите какие-либо ошибки в этом примере кода, пожалуйста, сообщите нам. Обратная связь может быть предоставлена через систему отслеживания проблем GitHub [ ссылка для обратной связи ].
2. Быстрый старт в Google Colaboratory
Эта лабораторная работа использует Google Collaboratory и не требует от вас никакой настройки. Вы можете запустить её с Chromebook. Пожалуйста, откройте файл ниже и выполните ячейки, чтобы ознакомиться с блокнотами Colab.
Выберите бэкэнд TPU.

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

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

Во всех блокнотах есть оглавление. Открыть его можно с помощью черной стрелки слева.
Скрытые клетки

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

Colab может получить доступ к вашим частным хранилищам Google Cloud Storage при условии аутентификации с помощью авторизованной учетной записи. Приведенный выше фрагмент кода запустит процесс аутентификации.
3. [ИНФО] Что такое тензорные процессоры (TPU)?
В двух словах

Код для обучения модели на TPU в Keras (с возможностью переключения на GPU или CPU, если TPU недоступен):
try: # detect TPUs
tpu = tf.distribute.cluster_resolver.TPUClusterResolver.connect()
strategy = tf.distribute.TPUStrategy(tpu)
except ValueError: # detect GPUs
strategy = tf.distribute.MirroredStrategy() # for CPU/GPU or multi-GPU machines
# use TPUStrategy scope to define model
with strategy.scope():
model = tf.keras.Sequential( ... )
model.compile( ... )
# train model normally on a tf.data.Dataset
model.fit(training_dataset, epochs=EPOCHS, steps_per_epoch=...)
Сегодня мы будем использовать TPU для создания и оптимизации классификатора цветов с интерактивной скоростью (минут на один тренировочный запуск).

Почему именно ТПУ?
Современные графические процессоры построены на основе программируемых «ядер» — очень гибкой архитектуры, позволяющей им справляться с различными задачами, такими как 3D-рендеринг, глубокое обучение, физическое моделирование и т. д. TPU, с другой стороны, сочетают классический векторный процессор со специализированным блоком умножения матриц и превосходно справляются с любыми задачами, где преобладают умножения больших матриц, например, с нейронными сетями.

Иллюстрация: слой плотной нейронной сети представлен в виде матричного умножения, при этом одновременно обрабатывается пакет из восьми изображений. Пожалуйста, выполните одно умножение строки на столбец, чтобы убедиться, что действительно выполняется взвешенная сумма всех значений пикселей изображения. Сверточные слои также можно представить в виде матричного умножения, хотя это немного сложнее ( объяснение здесь, в разделе 1 ).
Аппаратное обеспечение
MXU и VPU
Ядро TPU v2 состоит из блока умножения матриц (MXU), который выполняет умножение матриц, и блока векторной обработки (VPU) для всех остальных задач, таких как активации, softmax и т. д. VPU обрабатывает вычисления с числами float32 и int32. MXU, в свою очередь, работает в формате чисел с плавающей запятой смешанной точности 16-32 бит.

Число с плавающей запятой смешанной точности и bfloat16
Модуль MXU выполняет умножение матриц, используя входные данные типа bfloat16 и выходные данные типа float32. Промежуточные вычисления выполняются с точностью до float32.

Обучение нейронных сетей обычно устойчиво к шуму, вносимому пониженной точностью чисел с плавающей запятой. В некоторых случаях шум даже помогает оптимизатору сходиться. 16-битная точность чисел с плавающей запятой традиционно используется для ускорения вычислений, но форматы float16 и float32 имеют очень разные диапазоны. Снижение точности с float32 до float16 обычно приводит к переполнению и недополнению. Решения существуют, но обычно требуется дополнительная работа, чтобы заставить работать float16.
Именно поэтому Google ввела формат bfloat16 в TPU. bfloat16 — это усеченный float32 с точно таким же количеством битов экспоненты и диапазоном значений, как и float32. Это, в сочетании с тем фактом, что TPU выполняют умножение матриц в смешанной точности с входными данными bfloat16, но выходными данными float32, означает, что, как правило, для получения преимуществ от повышения производительности за счет пониженной точности не требуется никаких изменений в коде.
Систолический массив
MXU реализует матричные умножения в аппаратном обеспечении, используя так называемую архитектуру «систолического массива», в которой элементы данных проходят через массив аппаратных вычислительных блоков. (В медицине «систолический» относится к сокращениям сердца и кровотоку, в данном случае — к потоку данных.)
Основным элементом матричного умножения является скалярное произведение прямой одной матрицы и столбца другой матрицы (см. иллюстрацию в начале этого раздела). Для матричного умножения Y=X*W одним из элементов результата будет:
Y[2,0] = X[2,0]*W[0,0] + X[2,1]*W[1,0] + X[2,2]*W[2,0] + ... + X[2,n]*W[n,0]
На графическом процессоре (GPU) скалярное произведение запрограммировано в «ядро» GPU, а затем выполняется параллельно на максимально возможном количестве «ядер», чтобы попытаться вычислить каждое значение результирующей матрицы одновременно. Если результирующая матрица имеет размер 128x128, потребуется 128x128=16K «ядер», что обычно невозможно. Самые большие графические процессоры имеют около 4000 ядер. TPU, с другой стороны, использует минимальное количество аппаратных средств для вычислительных блоков в MXU: только умножители-аккумуляторы bfloat16 x bfloat16 => float32 , ничего больше. Они настолько малы, что TPU может реализовать 16K таких умножителей в MXU размером 128x128 и обработать это матричное умножение за один раз.

Иллюстрация: систолический массив MXU. Вычислительные элементы представляют собой умножители-аккумуляторы. Значения одной матрицы загружаются в массив (красные точки). Значения другой матрицы проходят через массив (серые точки). Вертикальные линии передают значения вверх. Горизонтальные линии передают частичные суммы. Пользователю предлагается проверить, что по мере прохождения данных через массив результат умножения матриц выходит с правой стороны.
Кроме того, пока в MXU вычисляются скалярные произведения, промежуточные суммы просто передаются между соседними вычислительными блоками. Их не нужно хранить и извлекать из памяти или даже из регистрового файла. В результате архитектура систолической матрицы TPU обладает значительным преимуществом в плотности и энергопотреблении, а также существенным преимуществом в скорости по сравнению с GPU при вычислении матричных умножений.
Облачный TPU
Когда вы запрашиваете « Cloud TPU v2» на платформе Google Cloud Platform, вы получаете виртуальную машину (ВМ) с платой TPU, подключенной через PCI. Плата TPU имеет четыре двухъядерных чипа TPU. Каждое ядро TPU включает в себя VPU (Vector Processing Unit) и 128x128 MXU (MatriX multiply Unit). Затем этот «Cloud TPU» обычно подключается по сети к виртуальной машине, которая его запросила. Таким образом, полная картина выглядит следующим образом:

Иллюстрация: ваша виртуальная машина с сетевым ускорителем "Cloud TPU". Сам "Cloud TPU" представляет собой виртуальную машину с платой TPU, подключенной через PCI, на которой размещены четыре двухъядерных чипа TPU.
TPU-капсулы
В центрах обработки данных Google процессоры TPU подключены к высокопроизводительному вычислительному соединению (HPC), благодаря чему они могут выглядеть как один очень большой ускоритель. Google называет их «подами», и они могут включать до 512 ядер TPU v2 или 2048 ядер TPU v3.

Иллюстрация: модуль TPU v3. Платы и стойки TPU соединены через высокопроизводительный межсоединительный интерфейс.
В процессе обучения градиенты обмениваются между ядрами TPU с использованием алгоритма all-reduce ( подробное объяснение all-reduce здесь ). Обучаемая модель может использовать преимущества аппаратного обеспечения, обучаясь на больших пакетах данных.

Иллюстрация: синхронизация градиентов во время обучения с использованием алгоритма all-reduce на высокопроизводительной вычислительной сети Google TPU с 2D-тороидальной сеткой.
Программное обеспечение
Обучение в больших группах
Идеальный размер пакета данных для TPU составляет 128 элементов данных на ядро TPU, но оборудование может демонстрировать хорошую загрузку уже при 8 элементах данных на ядро TPU. Следует помнить, что один облачный TPU имеет 8 ядер.
В этой практической работе мы будем использовать API Keras. В Keras указанный вами размер пакета данных является глобальным размером пакета для всего TPU. Ваши пакеты будут автоматически разделены на 8 частей и запущены на 8 ядрах TPU.

Дополнительные советы по повышению производительности см. в Руководстве по производительности TPU . При очень больших объемах партий в некоторых моделях может потребоваться особый подход; подробности см. в LARSOptimizer .
Под капотом: XLA
Программы TensorFlow определяют вычислительные графы. TPU не выполняет код Python напрямую, он выполняет вычислительный граф, определенный вашей программой TensorFlow. Под капотом компилятор XLA (ускоренный компилятор линейной алгебры) преобразует граф вычислительных узлов TensorFlow в машинный код TPU. Этот компилятор также выполняет множество сложных оптимизаций вашего кода и структуры памяти. Компиляция происходит автоматически по мере отправки задач на TPU. Вам не нужно явно включать XLA в цепочку сборки.

Иллюстрация: для работы на TPU вычислительный граф, определенный вашей программой Tensorflow, сначала преобразуется в представление XLA (ускоренный компилятор линейной алгебры), а затем компилируется XLA в машинный код TPU.
Использование TPU в Keras
Начиная с TensorFlow 2.1, поддержка TPU осуществляется через API Keras. Keras работает как с TPU, так и с TPU-подами. Вот пример, работающий на TPU, GPU(ах) и CPU:
try: # detect TPUs
tpu = tf.distribute.cluster_resolver.TPUClusterResolver.connect()
strategy = tf.distribute.TPUStrategy(tpu)
except ValueError: # detect GPUs
strategy = tf.distribute.MirroredStrategy() # for CPU/GPU or multi-GPU machines
# use TPUStrategy scope to define model
with strategy.scope():
model = tf.keras.Sequential( ... )
model.compile( ... )
# train model normally on a tf.data.Dataset
model.fit(training_dataset, epochs=EPOCHS, steps_per_epoch=...)
В этом фрагменте кода:
-
TPUClusterResolver().connect()находит TPU в сети. Она работает без параметров в большинстве систем Google Cloud (задания AI Platform, Colaboratory, Kubeflow, виртуальные машины глубокого обучения, созданные с помощью утилиты 'ctpu up'). Эти системы знают, где находится их TPU, благодаря переменной среды TPU_NAME. Если вы создаете TPU вручную, либо установите переменную среды TPU_NAME на виртуальной машине, с которой вы его используете, либо вызовитеTPUClusterResolverс явными параметрами:TPUClusterResolver(tp_uname, zone, project) -
TPUStrategy— это часть, реализующая распределение и алгоритм синхронизации градиентов "all-reduce". - Стратегия применяется через область видимости. Модель должна быть определена внутри области видимости стратегии (scope()).
- Функция
tpu_model.fitожидает в качестве входных данных для обучения на TPU объект tf.data.Dataset.
Типичные задачи портирования TPU
- Хотя существует множество способов загрузки данных в модель TensorFlow, для TPU требуется использование API
tf.data.Dataset. - TPU работают очень быстро, и при их использовании узким местом часто становится процесс приема данных. В руководстве по производительности TPU есть инструменты для выявления узких мест в передаче данных и другие советы по повышению производительности.
- Числа типа int8 или int16 обрабатываются как int32. В TPU отсутствует аппаратное обеспечение для работы с целочисленными значениями менее 32 бит.
- Некоторые операции TensorFlow не поддерживаются. Список можно найти здесь . Хорошая новость в том, что это ограничение распространяется только на код обучения, то есть на прямой и обратный проходы через вашу модель. Вы по-прежнему можете использовать все операции TensorFlow в своем конвейере ввода данных, поскольку они будут выполняться на ЦП.
-
tf.py_funcне поддерживается на TPU.
4. Загрузка данных






Мы будем работать с набором данных изображений цветов. Цель — научиться классифицировать их по 5 типам цветов. Загрузка данных осуществляется с помощью API tf.data.Dataset . Для начала давайте познакомимся с этим API.
Практический опыт
Пожалуйста, откройте следующую записную книжку, выполните действия в ячейках (Shift-ENTER) и следуйте инструкциям везде, где видите пометку "ТРЕБУЕТСЯ РАБОТА".
Fun with tf.data.Dataset (playground).ipynb
Дополнительная информация
О наборе данных «цветы»
Набор данных организован в 5 папок. Каждая папка содержит цветы одного вида. Папки называются: подсолнухи, ромашки, одуванчики, тюльпаны и розы. Данные размещены в общедоступном хранилище Google Cloud Storage. Отрывок:
gs://flowers-public/sunflowers/5139971615_434ff8ed8b_n.jpg
gs://flowers-public/daisy/8094774544_35465c1c64.jpg
gs://flowers-public/sunflowers/9309473873_9d62b9082e.jpg
gs://flowers-public/dandelion/19551343954_83bb52f310_m.jpg
gs://flowers-public/dandelion/14199664556_188b37e51e.jpg
gs://flowers-public/tulips/4290566894_c7f061583d_m.jpg
gs://flowers-public/roses/3065719996_c16ecd5551.jpg
gs://flowers-public/dandelion/8168031302_6e36f39d87.jpg
gs://flowers-public/sunflowers/9564240106_0577e919da_n.jpg
gs://flowers-public/daisy/14167543177_cd36b54ac6_n.jpg
Почему именно tf.data.Dataset?
Keras и TensorFlow принимают наборы данных (Dataset) во всех своих функциях обучения и оценки. После загрузки данных в набор данных API предоставляет все распространенные функции, полезные для обучения нейронных сетей:
dataset = ... # load something (see below)
dataset = dataset.shuffle(1000) # shuffle the dataset with a buffer of 1000
dataset = dataset.cache() # cache the dataset in RAM or on disk
dataset = dataset.repeat() # repeat the dataset indefinitely
dataset = dataset.batch(128) # batch data elements together in batches of 128
AUTOTUNE = tf.data.AUTOTUNE
dataset = dataset.prefetch(AUTOTUNE) # prefetch next batch(es) while training
В этой статье вы найдете советы по повышению производительности и рекомендации по работе с наборами данных. Справочная документация находится здесь .
Основы tf.data.Dataset
Данные обычно поступают в нескольких файлах, в данном случае — в виде изображений. Вы можете создать набор данных с именами файлов, вызвав следующую команду:
filenames_dataset = tf.data.Dataset.list_files('gs://flowers-public/*/*.jpg')
# The parameter is a "glob" pattern that supports the * and ? wildcards.
Затем вы назначаете функцию каждому имени файла, которая, как правило, загружает и декодирует файл в фактические данные в памяти:
def decode_jpeg(filename):
bits = tf.io.read_file(filename)
image = tf.io.decode_jpeg(bits)
return image
image_dataset = filenames_dataset.map(decode_jpeg)
# this is now a dataset of decoded images (uint8 RGB format)
Для итерации по набору данных:
for data in my_dataset:
print(data)
Наборы данных кортежей
В контролируемом обучении обучающий набор данных обычно состоит из пар обучающих данных и правильных ответов. Для этого функция декодирования может возвращать кортежи. В результате у вас будет набор данных кортежей, и эти кортежи будут возвращаться при итерации по нему. Возвращаемые значения представляют собой тензоры TensorFlow, готовые к использованию вашей моделью. Вы можете вызвать метод .numpy() для просмотра исходных значений:
def decode_jpeg_and_label(filename):
bits = tf.read_file(filename)
image = tf.io.decode_jpeg(bits)
label = ... # extract flower name from folder name
return image, label
image_dataset = filenames_dataset.map(decode_jpeg_and_label)
# this is now a dataset of (image, label) pairs
for image, label in dataset:
print(image.numpy().shape, label.numpy())
Вывод: загрузка изображений по одному происходит медленно!
В процессе работы с этим набором данных вы увидите, что можете загружать примерно 1-2 изображения в секунду. Это слишком медленно! Аппаратные ускорители, которые мы будем использовать для обучения, способны поддерживать скорость во много раз выше. Перейдите к следующему разделу, чтобы узнать, как мы этого добьемся.
Решение
Вот блокнот с решениями. Вы можете использовать его, если у вас возникнут трудности.
Fun with tf.data.Dataset (solution).ipynb
Что мы рассмотрели
- 🤔 tf.data.Dataset.list_files
- 🤔 tf.data.Dataset.map
- 🤔 Наборы данных кортежей
- 😀 Итерация по наборам данных
Пожалуйста, уделите немного времени, чтобы мысленно пройтись по этому контрольному списку.
5. Быстрая загрузка данных
Аппаратные ускорители Tensor Processing Unit (TPU), которые мы будем использовать в этой лабораторной работе, очень быстрые. Проблема часто заключается в том, чтобы подавать им данные достаточно быстро, чтобы они были загружены. Google Cloud Storage (GCS) способен поддерживать очень высокую пропускную способность, но, как и во всех облачных системах хранения, установление соединения требует некоторого обмена данными по сети. Поэтому хранение наших данных в виде тысяч отдельных файлов не является оптимальным вариантом. Мы будем объединять их в меньшее количество файлов и использовать возможности tf.data.Dataset для параллельного чтения из нескольких файлов.
Прочтение до конца
Код, который загружает файлы изображений, изменяет их размер до стандартного и затем сохраняет их в 16 файлах TFRecord, находится в следующем блокноте. Пожалуйста, быстро ознакомьтесь с ним. Запускать его не нужно, поскольку данные в правильном формате TFRecord будут предоставлены для остальной части практического занятия.
Flower pictures to TFRecords.ipynb
Идеальная организация данных для оптимальной пропускной способности GCS.
Формат файла TFRecord
В TensorFlow предпочтительным форматом файлов для хранения данных является формат TFRecord, основанный на протоколе protobuf . Подойдут и другие форматы сериализации, но загрузить набор данных из файлов TFRecord можно напрямую, написав:
filenames = tf.io.gfile.glob(FILENAME_PATTERN)
dataset = tf.data.TFRecordDataset(filenames)
dataset = dataset.map(...) # do the TFRecord decoding here - see below
Для оптимальной производительности рекомендуется использовать следующий более сложный код для одновременного чтения из нескольких файлов TFRecord. Этот код будет читать данные из N файлов параллельно, игнорируя порядок данных в пользу скорости чтения.
AUTOTUNE = tf.data.AUTOTUNE
ignore_order = tf.data.Options()
ignore_order.experimental_deterministic = False
filenames = tf.io.gfile.glob(FILENAME_PATTERN)
dataset = tf.data.TFRecordDataset(filenames, num_parallel_reads=AUTOTUNE)
dataset = dataset.with_options(ignore_order)
dataset = dataset.map(...) # do the TFRecord decoding here - see below
шпаргалка по TFRecord
В TFRecords можно хранить три типа данных: байтовые строки (список байтов), 64-битные целые числа и 32-битные числа с плавающей запятой . Они всегда хранятся в виде списков, при этом один элемент данных будет представлять собой список размером 1. Для сохранения данных в TFRecords можно использовать следующие вспомогательные функции.
запись байтовых строк
# warning, the input is a list of byte strings, which are themselves lists of bytes
def _bytestring_feature(list_of_bytestrings):
return tf.train.Feature(bytes_list=tf.train.BytesList(value=list_of_bytestrings))
запись целых чисел
def _int_feature(list_of_ints): # int64
return tf.train.Feature(int64_list=tf.train.Int64List(value=list_of_ints))
письменные плавающие
def _float_feature(list_of_floats): # float32
return tf.train.Feature(float_list=tf.train.FloatList(value=list_of_floats))
Создание TFRecord с использованием приведенных выше вспомогательных функций.
# input data in my_img_bytes, my_class, my_height, my_width, my_floats
with tf.python_io.TFRecordWriter(filename) as out_file:
feature = {
"image": _bytestring_feature([my_img_bytes]), # one image in the list
"class": _int_feature([my_class]), # one class in the list
"size": _int_feature([my_height, my_width]), # fixed length (2) list of ints
"float_data": _float_feature(my_floats) # variable length list of floats
}
tf_record = tf.train.Example(features=tf.train.Features(feature=feature))
out_file.write(tf_record.SerializeToString())
Для чтения данных из TFRecords необходимо сначала объявить структуру хранимых записей. В объявлении вы можете получить доступ к любому именованному полю как к списку фиксированной длины или списку переменной длины:
чтение с сайта TFRecords
def read_tfrecord(data):
features = {
# tf.string = byte string (not text string)
"image": tf.io.FixedLenFeature([], tf.string), # shape [] means scalar, here, a single byte string
"class": tf.io.FixedLenFeature([], tf.int64), # shape [] means scalar, i.e. a single item
"size": tf.io.FixedLenFeature([2], tf.int64), # two integers
"float_data": tf.io.VarLenFeature(tf.float32) # a variable number of floats
}
# decode the TFRecord
tf_record = tf.io.parse_single_example(data, features)
# FixedLenFeature fields are now ready to use
sz = tf_record['size']
# Typical code for decoding compressed images
image = tf.io.decode_jpeg(tf_record['image'], channels=3)
# VarLenFeature fields require additional sparse.to_dense decoding
float_data = tf.sparse.to_dense(tf_record['float_data'])
return image, sz, float_data
# decoding a tf.data.TFRecordDataset
dataset = dataset.map(read_tfrecord)
# now a dataset of triplets (image, sz, float_data)
Полезные фрагменты кода:
чтение отдельных элементов данных
tf.io.FixedLenFeature([], tf.string) # for one byte string
tf.io.FixedLenFeature([], tf.int64) # for one int
tf.io.FixedLenFeature([], tf.float32) # for one float
чтение списков элементов фиксированного размера
tf.io.FixedLenFeature([N], tf.string) # list of N byte strings
tf.io.FixedLenFeature([N], tf.int64) # list of N ints
tf.io.FixedLenFeature([N], tf.float32) # list of N floats
чтение переменного количества элементов данных
tf.io.VarLenFeature(tf.string) # list of byte strings
tf.io.VarLenFeature(tf.int64) # list of ints
tf.io.VarLenFeature(tf.float32) # list of floats
Объект VarLenFeature возвращает разреженный вектор, и после декодирования TFRecord требуется дополнительный шаг:
dense_data = tf.sparse.to_dense(tf_record['my_var_len_feature'])
В TFRecords также можно использовать необязательные поля. Если при чтении поля указать значение по умолчанию, то вместо ошибки, если поле отсутствует, будет возвращено именно это значение.
tf.io.FixedLenFeature([], tf.int64, default_value=0) # this field is optional
Что мы рассмотрели
- 🤔 Разделение файлов данных для быстрого доступа из GCS
- 😓 Как писать TFRecords. (Вы уже забыли синтаксис? Ничего страшного, добавьте эту страницу в закладки как шпаргалку.)
- 🤔 Загрузка набора данных из TFRecords с помощью TFRecordDataset
Пожалуйста, уделите немного времени, чтобы мысленно пройтись по этому контрольному списку.
6. [ИНФО] Нейронная сеть-классификатор 101
В двух словах
Если все выделенные жирным шрифтом термины в следующем абзаце вам уже знакомы, можете перейти к следующему упражнению. Если же вы только начинаете изучать глубокое обучение, добро пожаловать, и читайте дальше.
Для моделей, построенных в виде последовательности слоев, Keras предлагает API Sequential. Например, классификатор изображений, использующий три полносвязных слоя, можно записать в Keras следующим образом:
model = tf.keras.Sequential([
tf.keras.layers.Flatten(input_shape=[192, 192, 3]),
tf.keras.layers.Dense(500, activation="relu"),
tf.keras.layers.Dense(50, activation="relu"),
tf.keras.layers.Dense(5, activation='softmax') # classifying into 5 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, ... )

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

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

Наиболее популярной функцией активации является RELU (Rectified Linear Unit — выпрямленный линейный блок). Это очень простая функция, как видно на графике выше.
Активация Softmax
Представленная выше нейронная сеть заканчивается слоем из 5 нейронов, поскольку мы классифицируем цветы по 5 категориям (роза, тюльпан, одуванчик, маргаритка, подсолнух). Нейроны в промежуточных слоях активируются с помощью классической функции активации RELU. Однако в последнем слое мы хотим вычислить числа от 0 до 1, представляющие вероятность того, что этот цветок является розой, тюльпаном и так далее. Для этого мы будем использовать функцию активации под названием "softmax".
Применение функции softmax к вектору осуществляется путем экспоненциального вычисления каждого элемента и последующей нормализации вектора, обычно с использованием L1-нормы (суммы абсолютных значений), так что сумма значений равна 1 и может быть интерпретирована как вероятность.


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

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

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

Иллюстрация: седловая точка. Градиент равен 0, но точка не является минимумом во всех направлениях. (Источник изображения: Wikimedia: Nicoguaro - собственная работа, CC BY 3.0 )
Решение состоит в том, чтобы добавить алгоритму оптимизации некоторый импульс, чтобы он мог проходить седловые точки, не останавливаясь.
Глоссарий
Пакетная обработка или мини-пакетная обработка : обучение всегда выполняется на пакетах обучающих данных и меток. Это помогает алгоритму сойтись. Размерность «пакета» обычно представляет собой первую размерность тензоров данных. Например, тензор формы [100, 192, 192, 3] содержит 100 изображений размером 192x192 пикселя с тремя значениями на пиксель (RGB).
Функция потерь кросс-энтропии : специальная функция потерь, часто используемая в классификаторах.
Плотный слой : слой нейронов, в котором каждый нейрон соединен со всеми нейронами предыдущего слоя.
Признаки : входные данные нейронной сети иногда называют «признаками». Искусство определения того, какие части набора данных (или комбинации частей) следует подавать в нейронную сеть для получения хороших прогнозов, называется «инженерией признаков».
метки : другое название для «классов» или правильных ответов в задаче классификации с учителем.
Скорость обучения : доля градиента, на которую обновляются веса и смещения на каждой итерации цикла обучения.
Логиты : выходные сигналы слоя нейронов до применения функции активации называются «логитами». Термин происходит от «логистической функции», также известной как «сигмоидная функция», которая раньше была наиболее популярной функцией активации. Выражение «выходные сигналы нейронов до применения логистической функции» было сокращено до «логиты».
Функция потерь : функция ошибки, сравнивающая выходные данные нейронной сети с правильными ответами.
Нейрон : вычисляет взвешенную сумму своих входных сигналов, добавляет смещение и пропускает результат через функцию активации.
one-hot кодирование : класс 3 из 5 кодируется как вектор из 5 элементов, все из которых равны нулю, кроме третьего, который равен 1.
relu : выпрямленная линейная единица. Популярная функция активации для нейронов.
Сигмоидная функция : еще одна функция активации, которая когда-то была популярна и до сих пор полезна в особых случаях.
softmax : специальная функция активации, которая действует на вектор, увеличивая разницу между наибольшей компонентой и всеми остальными, а также нормализует вектор так, чтобы его сумма равнялась 1, что позволяет интерпретировать его как вектор вероятностей. Используется в качестве последнего шага в классификаторах.
Тензор : «Тензор» — это как матрица, но с произвольным числом измерений. Одномерный тензор — это вектор. Двумерный тензор — это матрица. И, наконец, могут существовать тензоры с 3, 4, 5 или более измерениями.
7. Перенос знаний
Для задачи классификации изображений, вероятно, будет недостаточно полносвязных слоев. Нам необходимо изучить сверточные слои и множество способов их расположения.
Но можно и пойти по короткому пути! В продаже имеются полностью обученные сверточные нейронные сети, которые можно скачать. Можно удалить их последний слой, классификационную часть softmax, и заменить его своим собственным. Все обученные веса и смещения остаются неизменными, переобучается только добавленный слой softmax. Этот метод называется трансферным обучением, и, что удивительно, он работает, если набор данных, на котором предварительно обучена нейронная сеть, достаточно «близок» к вашему.
Практический опыт
Пожалуйста, откройте следующую записную книжку, выполните действия в ячейках (Shift-ENTER) и следуйте инструкциям везде, где видите пометку "ТРЕБУЕТСЯ РАБОТА".
Keras Flowers transfer learning (playground).ipynb
Дополнительная информация
При использовании трансферного обучения вы получаете преимущества как от передовых архитектур сверточных нейронных сетей, разработанных ведущими исследователями, так и от предварительного обучения на огромном наборе данных изображений. В нашем случае мы будем использовать трансферное обучение на сети, обученной на ImageNet, базе данных изображений, содержащей множество растений и пейзажей, что достаточно близко к цветам.

Иллюстрация: использование сложной, уже обученной сверточной нейронной сети в качестве «черного ящика», переобучение только классификационной части. Это трансферное обучение. Мы увидим, как работают такие сложные конфигурации сверточных слоев, позже. А пока это уже задача кого-то другого.
Перенос обучения в Keras
В Keras можно создать экземпляр предварительно обученной модели из коллекции tf.keras.applications.* . Например, MobileNet V2 — это очень хорошая сверточная архитектура, которая остается разумной по размеру. Выбрав include_top=False , вы получите предварительно обученную модель без финального слоя softmax, чтобы добавить свой собственный:
pretrained_model = tf.keras.applications.MobileNetV2(input_shape=[*IMAGE_SIZE, 3], include_top=False)
pretrained_model.trainable = False
model = tf.keras.Sequential([
pretrained_model,
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(5, activation='softmax')
])
Обратите внимание также на параметр pretrained_model.trainable = False . Он фиксирует веса и смещения предварительно обученной модели, так что вы обучаете только слой softmax. Обычно это включает относительно небольшое количество весов и может быть выполнено быстро и без необходимости использования очень большого набора данных. Однако, если у вас много данных, трансферное обучение может работать еще лучше с pretrained_model.trainable = True . В этом случае предварительно обученные веса обеспечивают отличные начальные значения и могут быть скорректированы в процессе обучения для лучшего соответствия вашей задаче.
Наконец, обратите внимание на слой Flatten() вставленный перед плотным слоем softmax. Плотные слои работают с плоскими векторами данных, но мы не знаем, возвращает ли их предварительно обученная модель. Вот почему нам нужно использовать Flatten(). В следующей главе, когда мы углубимся в сверточные архитектуры, мы объясним формат данных, возвращаемых сверточными слоями.
При таком подходе вы должны получить точность, близкую к 75%.
Решение
Вот блокнот с решениями. Вы можете использовать его, если у вас возникнут трудности.
Keras Flowers transfer learning (solution).ipynb
Что мы рассмотрели
- 🤔 Как написать классификатор в Keras
- 🤓 сконфигурирован с последним слоем softmax и функцией потерь кросс-энтропии.
- 😈 Перенос знаний
- 🤔 Обучение вашей первой модели
- 🧐 Following its loss and accuracy during training
Please take a moment to go through this checklist in your head.
8. [INFO] Convolutional neural networks
В двух словах
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 with convolutional neural networks please read on.

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([
# input: images of size 192x192x3 pixels (the three stands for RGB channels)
tf.keras.layers.Conv2D(kernel_size=3, filters=24, padding='same', activation='relu', input_shape=[192, 192, 3]),
tf.keras.layers.Conv2D(kernel_size=3, filters=24, padding='same', activation='relu'),
tf.keras.layers.MaxPooling2D(pool_size=2),
tf.keras.layers.Conv2D(kernel_size=3, filters=12, padding='same', activation='relu'),
tf.keras.layers.MaxPooling2D(pool_size=2),
tf.keras.layers.Conv2D(kernel_size=3, filters=6, padding='same', activation='relu'),
tf.keras.layers.Flatten(),
# classifying into 5 categories
tf.keras.layers.Dense(5, activation='softmax')
])
model.compile(
optimizer='adam',
loss= 'categorical_crossentropy',
metrics=['accuracy'])

Convolutional neural nets 101
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, using 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.

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.
C onvolutional classifier
Finally, we attach a classification head by flattening the last data cube and feeding it through a dense, softmax-activated layer. A typical convolutional classifier can look like this:

Illustration: an image classifier using convolutional and softmax layers. It uses 3x3 and 1x1 filters. The maxpool layers take the max of groups of 2x2 data points. The classification head is implemented with a dense layer with softmax activation.
In Keras
The convolutional stack illustrated above can be written in Keras like this:
model = tf.keras.Sequential([
# input: images of size 192x192x3 pixels (the three stands for RGB channels)
tf.keras.layers.Conv2D(kernel_size=3, filters=32, padding='same', activation='relu', input_shape=[192, 192, 3]),
tf.keras.layers.Conv2D(kernel_size=1, filters=32, padding='same', activation='relu'),
tf.keras.layers.MaxPooling2D(pool_size=2),
tf.keras.layers.Conv2D(kernel_size=3, filters=32, padding='same', activation='relu'),
tf.keras.layers.Conv2D(kernel_size=1, filters=32, padding='same', activation='relu'),
tf.keras.layers.MaxPooling2D(pool_size=2),
tf.keras.layers.Conv2D(kernel_size=3, filters=32, padding='same', activation='relu'),
tf.keras.layers.Conv2D(kernel_size=1, filters=32, padding='same', activation='relu'),
tf.keras.layers.MaxPooling2D(pool_size=2),
tf.keras.layers.Conv2D(kernel_size=3, filters=32, padding='same', activation='relu'),
tf.keras.layers.Conv2D(kernel_size=1, filters=32, padding='same', activation='relu'),
tf.keras.layers.MaxPooling2D(pool_size=2),
tf.keras.layers.Conv2D(kernel_size=3, filters=16, padding='same', activation='relu'),
tf.keras.layers.Conv2D(kernel_size=1, filters=8, padding='same', activation='relu'),
tf.keras.layers.Flatten(),
# classifying into 5 categories
tf.keras.layers.Dense(5, activation='softmax')
])
model.compile(
optimizer='adam',
loss= 'categorical_crossentropy',
metrics=['accuracy'])
9. Your custom convnet
Практический опыт
Let us build and train a convolutional neural network from scratch. Using a TPU will allow us to iterate very fast. Please open the following notebook, execute the cells (Shift-ENTER) and follow the instructions wherever you see a "WORK REQUIRED" label.
Keras_Flowers_TPU (playground).ipynb
The goal is to beat the 75% accuracy of the transfer learning model. That model had an advantage, having been pre-trained on a dataset of millions of images while we only have 3670 images here. Can you at least match it?
Дополнительная информация
How many layers, how big?
Selecting layer sizes is more of an art than a science. You have to find the right balance between having too few and too many parameters (weights and biases). With too few weights, the neural network cannot represent the complexity of flower shapes. With too many, it can be prone to "overfitting", ie specializing in the training images and not being able to generalize. With a lot of parameters, the model will also be slow to train. In Keras, the model.summary() function displays the structure and parameter count of your model:
Layer (type) Output Shape Param #
=================================================================
conv2d (Conv2D) (None, 192, 192, 16) 448
_________________________________________________________________
conv2d_1 (Conv2D) (None, 192, 192, 30) 4350
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 96, 96, 30) 0
_________________________________________________________________
conv2d_2 (Conv2D) (None, 96, 96, 60) 16260
_________________________________________________________________
...
_________________________________________________________________
global_average_pooling2d (Gl (None, 130) 0
_________________________________________________________________
dense (Dense) (None, 90) 11790
_________________________________________________________________
dense_1 (Dense) (None, 5) 455
=================================================================
Total params: 300,033
Trainable params: 300,033
Non-trainable params: 0
_________________________________________________________________
A couple of tips:
- Having multiple layers is what makes "deep" neural networks effective. For this simple flower recognition problem, 5 to 10 layers make sense.
- Use small filters. Typically 3x3 filters are good everywhere.
- 1x1 filters can be used too and are cheap. They do not really "filter" anything but compute linear combinations of channels. Alternate them with real filters. (More about "1x1 convolutions" in the next section.)
- For a classification problem like this, downsample frequently with max-pooling layers (or convolutions with stride >1). You do not care where the flower is, only that it is a rose or a dandelion so losing x and y information is not important and filtering smaller areas is cheaper.
- The number of filters usually becomes similar to the number of classes at the end of the network (why? see "global average pooling" trick below). If you classify into hundreds of classes, increase the filter count progressively in consecutive layers. For the flower dataset with 5 classes, filtering with only 5 filters would not be enough. You can use the same filter count in most layers, for example 32 and decrease it towards the end.
- The final dense layer(s) is/are expensive. It/they can have more weights than all the convolutional layers combined. For example, even with a very reasonable output from the last data cube of 24x24x10 data points, a 100 neuron dense layer would cost 24x24x10x100=576,000 weights !!! Try to be thoughtful, or try global average pooling (see below).
Global average pooling
Instead of using an expensive dense layer at the end of a convolutional neural network, you can split the incoming data "cube" into as many parts as you 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, the syntax is tf.keras.layers.GlobalAveragePooling2D().

Решение
Here is the solution notebook. You can use it if you are stuck.
Keras_Flowers_TPU (solution).ipynb
Что мы рассмотрели
- 🤔 Played with convolutional layers
- 🤓 Experimented with max pooling, strides, global average pooling, ...
- 😀 iterated on a real-world model fast, on TPU
Please take a moment to go through this checklist in your head.
10. [INFO] Modern convolutional architectures
В двух словах

Illustration: a convolutional "module". What is best at this point ? A max-pool layer followed by a 1x1 convolutional layer or a different combination of layers ? Try them all, concatenate the results and let the network decide. On the right: the " inception " convolutional architecture using such modules.
In Keras, to create models where the data flow can branch in and out, you have to use the "functional" model style. Here is an example:
l = tf.keras.layers # syntax shortcut
y = l.Conv2D(filters=32, kernel_size=3, padding='same',
activation='relu', input_shape=[192, 192, 3])(x) # x=input image
# module start: branch out
y1 = l.Conv2D(filters=32, kernel_size=1, padding='same', activation='relu')(y)
y3 = l.Conv2D(filters=32, kernel_size=3, padding='same', activation='relu')(y)
y = l.concatenate([y1, y3]) # output now has 64 channels
# module end: concatenation
# many more layers ...
# Create the model by specifying the input and output tensors.
# Keras layers track their connections automatically so that's all that's needed.
z = l.Dense(5, activation='softmax')(y)
model = tf.keras.Model(x, z)

Other cheap tricks
Small 3x3 filters

In this illustration, you see the result of two consecutive 3x3 filters. Try to trace back which data points contributed to the result: these two consecutive 3x3 filters compute some combination of a 5x5 region. It is not exactly the same combination that a 5x5 filter would compute but it is worth trying because two consecutive 3x3 filters are cheaper than a single 5x5 filter.
1x1 convolutions ?

In mathematical terms, a "1x1" convolution is a multiplication by a constant, not a very useful concept. In convolutional neural networks however, remember that the filter is applied to a data cube, not just a 2D image. Therefore, a "1x1" filter computes a weighted sum of a 1x1 column of data (see illustration) and as you slide it across the data, you will obtain a linear combination of the channels of the input. This is actually useful. If you think of the channels as the results of individual filtering operations, for example a filter for "pointy ears", another one for "whiskers" and a third one for "slit eyes" then a "1x1" convolutional layer will be computing multiple possible linear combinations of these features, which might be useful when looking for a "cat". On top of that, 1x1 layers use fewer weights.
11. Squeezenet
A simple way of putting these ideas together has been showcased in the "Squeezenet" paper . The authors suggest a very simple convolutional module design, using only 1x1 and 3x3 convolutional layers.

Illustration: squeezenet architecture based on "fire modules". They alternate a 1x1 layer that "squeezes" the incoming data in the vertical dimension followed by two parallel 1x1 and 3x3 convolutional layers that "expand" the depth of the data again.
Практический опыт
Continue in your previous notebook and build a squeezenet-inspired convolutional neural network. You will have to change the model code to the Keras "functional style".
Keras_Flowers_TPU (playground).ipynb
Дополнительная информация
It will be useful for this exercise to define a helper function for a squeezenet module:
def fire(x, squeeze, expand):
y = l.Conv2D(filters=squeeze, kernel_size=1, padding='same', activation='relu')(x)
y1 = l.Conv2D(filters=expand//2, kernel_size=1, padding='same', activation='relu')(y)
y3 = l.Conv2D(filters=expand//2, kernel_size=3, padding='same', activation='relu')(y)
return tf.keras.layers.concatenate([y1, y3])
# this is to make it behave similarly to other Keras layers
def fire_module(squeeze, expand):
return lambda x: fire(x, squeeze, expand)
# usage:
x = l.Input(shape=[192, 192, 3])
y = fire_module(squeeze=24, expand=48)(x) # typically, squeeze is less than expand
y = fire_module(squeeze=32, expand=64)(y)
...
model = tf.keras.Model(x, y)
The objective this time is to hit 80% accuracy.
Что стоит попробовать
Start with a single convolutional layer, then follow with " fire_modules ", alternating with MaxPooling2D(pool_size=2) layers. You can experiment with 2 to 4 max pooling layers in the network and also with 1, 2 or 3 consecutive fire modules between the max pooling layers.
In fire modules, the "squeeze" parameter should typically be smaller than the "expand" parameter. These parameters are actually numbers of filters. They can range from 8 to 196, typically. You can experiment with architectures where the number of filters gradually increases through the network, or straightforward architectures where all fire modules have the same number of filters.
Вот пример:
x = tf.keras.layers.Input(shape=[*IMAGE_SIZE, 3]) # input is 192x192 pixels RGB
y = tf.keras.layers.Conv2D(kernel_size=3, filters=32, padding='same', activation='relu')(x)
y = fire_module(24, 48)(y)
y = tf.keras.layers.MaxPooling2D(pool_size=2)(y)
y = fire_module(24, 48)(y)
y = tf.keras.layers.MaxPooling2D(pool_size=2)(y)
y = fire_module(24, 48)(y)
y = tf.keras.layers.GlobalAveragePooling2D()(y)
y = tf.keras.layers.Dense(5, activation='softmax')(y)
model = tf.keras.Model(x, y)
At this point, you might notice that your experiments are not going so well and that the 80% accuracy objective seems remote. Time for a couple more cheap tricks.
Batch Normalization
Batch norm will help with the convergence problems you are experiencing. There will be detailed explanations about this technique in the next workshop, for now, please use it as a black box "magic" helper by adding this line after every convolutional layer in your network, including the layers inside of your fire_module function:
y = tf.keras.layers.BatchNormalization(momentum=0.9)(y)
# please adapt the input and output "y"s to whatever is appropriate in your context
The momentum parameter has to be decreased from its default value of 0.99 to 0.9 because our dataset is small. Never mind this detail for now.
Data augmentation
You will get a couple more percentage points by augmenting the data with easy transformations like left-right flips of saturation changes:


This is very easy to do in Tensorflow with the tf.data.Dataset API. Define a new transformation function for your data:
def data_augment(image, label):
image = tf.image.random_flip_left_right(image)
image = tf.image.random_saturation(image, lower=0, upper=2)
return image, label
Then use it in you final data transformation (cell "training and validation datasets", function "get_batched_dataset"):
dataset = dataset.repeat() # existing line
# insert this
if augment_data:
dataset = dataset.map(data_augment, num_parallel_calls=AUTO)
dataset = dataset.shuffle(2048) # existing line
Do not forget to make the data augmentation optional and to add the necessary code to make sure only the training dataset is augmented . It makes no sense to augment the validation dataset.
80% accuracy in 35 epochs should now be within reach.
Решение
Here is the solution notebook. You can use it if you are stuck.
Keras_Flowers_TPU_squeezenet.ipynb
Что мы рассмотрели
- 🤔 Keras "functional style" models
- 🤓 Squeezenet architecture
- 🤓 Data augmentation with tf.data.datset
Please take a moment to go through this checklist in your head.
12. Xception fine-tuned
Separable convolutions
A different way of implementing convolutional layers had been gaining popularity recently: depth-separable convolutions. I know, it's a mouthful, but the concept is quite simple. They are implemented in Tensorflow and Keras as tf.keras.layers.SeparableConv2D .
A separable convolution also runs a filter on the image but it uses a distinct set of weights for each channel of the input image. It follows with a "1x1 convolution", a series of dot products resulting in a weighted sum of the filtered channels. With new weights each time, as many weighted recombinations of the channels are computed as necessary.

Illustration: separable convolutions. Phase 1: convolutions with a separate filter for each channel. Phase 2: linear recombinations of channels. Repeated with a new set of weights until the desired number of output channels is reached. Phase 1 can be repeated too, with new weights each time but in practice it is rarely so.
Separable convolutions are used in most recent convolutional networks architectures: MobileNetV2, Xception, EfficientNet. By the way, MobileNetV2 is what you used for transfer learning previously.
They are cheaper than regular convolutions and have been found to be just as effective in practice. Here is the weight count for the example illustrated above:
Convolutional layer: 4 x 4 x 3 x 5 = 240
Separable convolutional layer: 4 x 4 x 3 + 3 x 5 = 48 + 15 = 63
It is left as an exercise for the reader to compute than number of multiplications required to apply each style of convolutional layer scales in a similar way. Separable convolutions are smaller and much more computationally effective.
Практический опыт
Restart from the "transfer learning" playground notebook but this time select Xception as the pre-trained model. Xception uses separable convolutions only. Leave all weights trainable. We will be fine-tuning the pre-trained weights on our data instead of using the pre-trained layers as such.
Keras Flowers transfer learning (playground).ipynb
Goal: accuracy > 95% (No, seriously, it is possible!)
This being the final exercise, it requires a bit more code and data science work.
Additional info on fine-tuning
Xception is available in the standard pre-trained models in tf.keras.application.* Do not forget to leave all weights trainable this time.
pretrained_model = tf.keras.applications.Xception(input_shape=[*IMAGE_SIZE, 3],
include_top=False)
pretrained_model.trainable = True
To get good results when fine-tuning a model, you will need to pay attention to the learning rate and use a learning rate schedule with a ramp-up period. Like this:

Starting with a standard learning rate would disrupt the pre-trained weights of the model. Starting progressively preserves them until the model has latched on your data is able to modify them in a sensible way. After the ramp, you can continue with a constant or an exponentially decaying learning rate.
In Keras, the learning rate is specified through a callback in which you can compute the appropriate learning rate for each epoch. Keras will pass the correct learning rate to the optimizer for each epoch.
def lr_fn(epoch):
lr = ...
return lr
lr_callback = tf.keras.callbacks.LearningRateScheduler(lr_fn, verbose=True)
model.fit(..., callbacks=[lr_callback])
Решение
Here is the solution notebook. You can use it if you are stuck.
07_Keras_Flowers_TPU_xception_fine_tuned_best.ipynb
Что мы рассмотрели
- 🤔 Depth-separable convolution
- 🤓 Learning rate schedules
- 😈 Fine-tuning a pre-trained model.
Please take a moment to go through this checklist in your head.
13. Congratulations!
You have built your first modern convolutional neural network and trained it to 90% + accuracy, iterating on successive training runs in only minutes thanks to TPUs.
TPUs in practice
TPUs and GPUs are available on Google Cloud's Vertex AI :
- On Deep Learning VMs
- In Vertex AI Notebooks
- In Vertex AI Training Jobs jobs
Finally, we love feedback. Please tell us if you see something amiss in this lab or if you think it should be improved. Feedback can be provided through GitHub issues [ feedback link ].

|
|

