На этом шаге мы рассмотрим особенности конструирования сети.
Далее мы вновь реализуем модель с той же общей структурой, что и в первом примере: сверточная нейронная сеть будет организована как стек чередующихся слоев Conv2D (с функцией активации relu) и MaxPooling2D.
Однако, так как мы имеем дело с большими изображениями и решаем более сложную задачу, мы сделаем сеть больше, добавив еще одну пару слоев Conv2D и MaxPooling2D. Это увеличит емкость модели и обеспечит дополнительное снижение размеров карт признаков, чтобы они не оказались слишком большими, когда достигнут слоя Flatten. Учитывая, что мы начнем с входов, имеющих размер 180 × 180 (выбор был сделан совершенно произвольно), в конце, точно перед слоем Flatten, получится карта признаков размером 7 × 7.
Так как перед нами стоит задача бинарной классификации, сеть должна заканчиваться единственным признаком (слой Dense размером 1 и функцией активации sigmoid). Этот признак будет представлять вероятность принадлежности рассматриваемого изображения одному из двух классов.
И еще одно небольшое отличие: модель будет начинаться со слоя Rescaling, преобразующего входные данные (значения которых изначально находятся в диапазоне [0, 255]) в диапазон [0, 1].
import pathlib from tensorflow import keras from tensorflow.keras import layers new_base_dir = pathlib.Path("cats_vs_dogs_small") # Модель принимает изображения в формате RGB размерами 180 × 180 inputs = keras.Input(shape=(180, 180, 3)) # Привести входные данные к диапазону [0, 1] делением на 255 x = layers.Rescaling(1./255)(inputs) x = layers.Conv2D(filters=32, kernel_size=3, activation="relu")(x) x = layers.MaxPooling2D(pool_size=2)(x) x = layers.Conv2D(filters=64, kernel_size=3, activation="relu")(x) x = layers.MaxPooling2D(pool_size=2)(x) x = layers.Conv2D(filters=128, kernel_size=3, activation="relu")(x) x = layers.MaxPooling2D(pool_size=2)(x) x = layers.Conv2D(filters=256, kernel_size=3, activation="relu")(x) x = layers.MaxPooling2D(pool_size=2)(x) x = layers.Conv2D(filters=256, kernel_size=3, activation="relu")(x) x = layers.Flatten()(x) outputs = layers.Dense(1, activation="sigmoid")(x) model = keras.Model(inputs=inputs, outputs=outputs)
Посмотрим, как изменяются размеры карт признаков с каждым последующим слоем:
print(model.summary())
Model: "functional_2"
Layer (type) Output Shape Param #
input_layer_2 (InputLayer) (None, 180, 180, 3) 0
rescaling_2 (Rescaling) (None, 180, 180, 3) 0
conv2d_10 (Conv2D) (None, 178, 178, 32) 896
max_pooling2d_8 (MaxPooling2D) (None, 89, 89, 32) 0
conv2d_11 (Conv2D) (None, 87, 87, 64) 18,496
max_pooling2d_9 (MaxPooling2D) (None, 43, 43, 64) 0
conv2d_12 (Conv2D) (None, 41, 41, 128) 73,856
max_pooling2d_10 (MaxPooling2D) (None, 20, 20, 128) 0
conv2d_13 (Conv2D) (None, 18, 18, 256) 295,168
max_pooling2d_11 (MaxPooling2D) (None, 9, 9, 256) 0
conv2d_14 (Conv2D) (None, 7, 7, 256) 590,080
flatten_2 (Flatten) (None, 12544) 0
dense_2 (Dense) (None, 1) 12,545
Total params: 991,041 (3.78 MB)
Trainable params: 991,041 (3.78 MB)
Non-trainable params: 0 (0.00 B)
На этапе компиляции, как обычно, используем оптимизатор RMSprop. Так как модель заканчивается единственным сигмоидным выходом, используем функцию потерь binary_crossentropy (для напоминания: в таблице 1 164 шага приводится шпаргалка по использованию разных функций потерь в разных ситуациях) .
model.compile(loss="binary_crossentropy", optimizer="rmsprop", metrics=["accuracy"])
На следующем шаге мы рассмотрим предварительную обработку данных.