Мы обучим модель глубокого обучения с архитектурой, известной как U-Net, на наборе данных электронной микроскопии. Наша модель научится преобразовывать полутоновое ЭМ изображение нервных клеток (слева) в точную карту границ, разделяющую стены между ними (справа) на уровне пикселей, как показано выше. U-Net остается передовым оборудованием для выполнения семантической сегментации, и та же модель с незначительной настройкой гиперпараметров и с экспериментальной головкой может использоваться практически для любой задачи сегментации изображений.
Примечание. В этом руководстве предполагается, что читатель будет иметь представление о том, как работают сверточные нейронные сети, а моя реализация U-Net в основном будет использовать функциональный API Keras, поэтому, если вы новичок в глубоком обучении, я Предлагаю вам получить хорошее представление, прочитав и реализовав мою статью о том, как работают сверточные нейронные сети, нажав здесь.
- Модель, которую мы будем строить в этом посте, была скомпилирована на видеокарте Nvidia GTX 1060, на обучение потребуется несколько часов, если вы скомпилируете ее на процессоре, чтобы добиться хорошей точности, я бы предложил запустить ее на Версия Keras для графического процессора, если у вас есть графический процессор.
Модель глубокого обучения, которую я буду строить в этом посте, основана на этой статье U-Net: сверточные сети для сегментации биомедицинских изображений, которая по-прежнему остается передовой в сегментации изображений для задач, отличных от медицинских изображений. В этой статье представлена новая архитектура для выполнения семантической сегментации, которая значительно лучше предыдущей, большинство подходов использовали сверточные нейронные сети со скользящим окном, и это значительный отход от нее во всех смыслах.
Прежде чем мы перейдем к теории, лежащей в основе нашей нейронной сети, я сначала расскажу вам, какие задачи визуального распознавания мы можем наблюдать в области компьютерного зрения в области машинного обучения.
Итак, самый простой из них - это классификация изображений (а), когда мы пытаемся получить информацию о том, что находится на изображении, но здесь проблема в том, что мы не знаем, где находится определенный класс объектов и сколько его экземпляров присутствует в изображение и так далее. И, следовательно, позже появилась локализация / обнаружение объекта (b), которая не только сообщает нам, что изображено на картинке, но и где она расположена, что очень помогает. Но тогда даже этот подход дает нам только граничные рамки, прямоугольники, отмеченные над объектом, находящимся на изображении. Более глубокий уровень локализации этого объекта - семантическая сегментация, которая является основной темой данной статьи. Семантическую сегментацию можно описать в соответствии с классификацией пикселей для изображений, здесь мы помечаем каждый пиксель его соответствующим классом, как показано ниже:
Приведенное выше изображение является одним из реальных примеров, когда семантическая сегментация применяется как часть создания беспилотных автомобилей, чтобы лучше понять окружающую среду.
Но модель, которую мы будем строить сегодня, - это сегментирование биомедицинских изображений, и документ, который я реализую для этого, был опубликован в 2015 году и стал исключительным в победе в конкурсе ISBI 2015. Эту архитектуру можно применять там, где используются обучающие данные. очень меньше. В частности, в медицинских секторах доступных обучающих выборок очень мало, особенно потому, что опыт в этой области очень ограничен, и очень трудно получить действительно хорошо маркированные и высококачественные данные, но U-Net по-прежнему остается на современном уровне в решении таких проблем. задания.
Теория, лежащая в основе U-Net
Ключевые моменты:
- Сеть можно разделить на два пути: один - сокращающийся, а другой - расширяющийся.
- Контурный путь выполняет понижающую дискретизацию для извлечения признаков, построенную так же, как сверточную нейронную сеть, но за ней следует расширяющийся путь, который выполняет повышающую дискретизацию для точной локализации признаков в слоях с более высоким разрешением.
- Еще один важный аспект, делающий сеть такой особенной, - это использование карт характеристик сверточного слоя, которые обучены на пути понижающей дискретизации, и их конкатенация с соответствующими слоями дек-свертки пути повышающей дискретизации.
Сетевая архитектура:
Изображение выше описывает архитектуру U-Net, взятую из основного документа. Итак, как упоминалось ранее, наша сеть будет иметь 2 пути: путь понижающей дискретизации и путь повышающей дискретизации.
Путь понижающей дискретизации:
Левая часть сети - это часть с понижающей дискретизацией, это путь, по которому мы пропускаем изображение через несколько сверточных слоев и добавляем максимальный пул между ними для понижения дискретизации и уменьшения размера изображения, одновременно увеличивая количество слоев. путем удвоения количества фильтров сверточных слоев в каждом блоке свертки.
- Путь имеет 4 блока свертки (по 2 свертки), за которыми следуют слои максимального объединения размером 2x2 с шагом 2 для понижающей дискретизации.
- За 5-м блоком свертки не следует максимальное объединение, а он связан с путем повышающей дискретизации.
- Первый блок свертки содержит 64 фильтра на каждом сверточном слое.
- Количество фильтров удваивается с каждым последовательным блоком свертки.
- Разрешение уменьшается с увеличением глубины (количества слоев),
- Заполнение не используется (допустимое заполнение »).
- Сверточные фильтры имеют размер 3x3 с функцией активации ReLU.
Путь увеличения выборки:
Здесь, в пути повышающей дискретизации, мы заменяем слои пула операторами повышающей дискретизации, которые увеличивают разрешение вывода.
- Путь повышающей дискретизации остается симметричным пути понижающей дискретизации, превращая сеть в U-образную нейронную сеть, отсюда и название «U-Net».
- Есть 4 блока свертки с 2 слоями свертки в каждом, за которыми следует транспонированная свертка / слой свертки с повышением частоты. Когда транспонированная свертка увеличивает разрешение изображения с помощью изученного фильтра.
- Количество фильтров для каждого последовательного блока свертки равно половине фильтров из предыдущего блока свертки.
- Разрешение увеличивается с уменьшением глубины (количества слоев).
- Здесь также не используется заполнение (допустимое заполнение »).
- Сверточные фильтры имеют размер 3x3 с функцией активации ReLU.
- Соответствующие карты характеристик из пути понижающей дискретизации объединяются с соответствующими слоями повышающей дискретизации для достижения точной локализации.
- Последний слой свертки имеет фильтр размером 1x1 для сопоставления каждого из 64 векторных компонентов с желаемым количеством классов (в данном случае это ячейка и фон).
Примечание. Сверточное ядро, которое изучается во время пути понижающей дискретизации, используется для преобразования изображения из небольшого домена в большой домен во время пути повышающей дискретизации (отсюда и взаимосвязь между путями ).
Внедрение U-Net
Набор данных, который мы будем использовать в этом руководстве, будет набором данных задачи отслеживания ячеек ISBI 2015 года. Он содержит 30 изображений электроскопа с соответствующими аннотированными изображениями (метками). Вы можете найти набор данных и код, описанные в этом руководстве, на github. Итак, давайте построим модель в Керасе.
# unet_model.py
from keras.models import Model
from keras.backend import int_shape
from keras.layers import BatchNormalization, Conv2D, Conv2DTranspose, MaxPooling2D, Dropout, UpSampling2D, Input, concatenate
Давайте посмотрим, что мы импортируем и почему:
'Модель' из Функционального API Keras, используемого для построения сложных моделей глубокого обучения, направленных ациклических графов и т. д., а 'int_shape' возвращает форму тензора или переменная как кортеж из записей типа int или None.
‘BatchNormalization’: нормализует вывод активаций из слоев.
‘Conv2D’: используется для создания сверточного слоя.
‘Conv2DTranspose’: для выполнения транспонированной свертки.
‘MaxPooling2D’: выполняет операцию максимального объединения пространственных данных.
‘Dropout’: используется для удаления единиц (скрытых и видимых) в нейронной сети.
‘Input’: используется для создания экземпляра тензора Кераса.
‘concatenate’: возвращает тензор, который представляет собой конкатенацию входных данных вдоль переданной оси.
def upsample_conv(filters, kernel_size, strides, padding): return Conv2DTranspose(filters, kernel_size, strides=strides, padding=padding)
def upsample_simple(filters, kernel_size, strides, padding): return UpSampling2D(strides)
Вышеупомянутые две функции выполняют два разных типа передискретизации.
Функция upsampling_conv выполняет операцию транспонированной свертки, что означает повышающую дискретизацию изображения на основе изученного фильтра. И для этого мы используем Conv2DTranspose. Переданные для этого параметры говорят сами за себя. Если не уверены, нажмите здесь.
Функция «upsample_simple» выполняет простую прямую операцию повышения дискретизации изображения с ядром указанного размера. Для этого мы используем «Upsampling2D».
def conv2d_block(
inputs,
use_batch_norm=True,
dropout=0.3,
filters=16,
kernel_size=(3,3),
activation='relu',
kernel_initializer='he_normal',
padding='same'):
c = Conv2D(filters, kernel_size, activation=activation,
kernel_initializer=kernel_initializer, padding=padding) (inputs)
if use_batch_norm:
c = BatchNormalization()(c)
if dropout > 0.0:
c = Dropout(dropout)(c)
c = Conv2D(filters, kernel_size, activation=activation,
kernel_initializer=kernel_initializer, padding=padding) (c)
if use_batch_norm:
c = BatchNormalization()(c)
return c
Вышеупомянутая функция «conv2d_block» будет обрабатывать операции свертки в сети. Мы используем классическую функцию «Conv2D» от Keras для выполнения операций свертки. Аргументы, которые могут быть переданы, - это размер ввода, выбор использования пакетной нормализации внутри слоев, частота отсева, количество фильтров, размер ядра, функция активации для использования, инициализатор ядра 'he_normal' (для установите начальные веса сети полностью случайными) и, наконец, заполнение (в нашем случае 'same', т.е. выходы слоя будут иметь те же пространственные размеры, что и его входные данные).
def unet_model( input_shape, num_classes=1, use_batch_norm=True, upsample_mode='deconv', # 'de-convolution' or 'simple upsampling' use_dropout_on_upsampling=False, dropout=0.3, dropout_change_per_layer=0.0, filters=16, num_layers=4, output_activation='sigmoid'): # 'sigmoid' or 'softmax' if upsample_mode=='deconv': upsample=upsample_conv else: upsample=upsample_simple
# Build U-Net model inputs = Input(input_shape) x = inputs
down_layers = [] for l in range(num_layers): x = conv2d_block(inputs=x, filters=filters, use_batch_norm=use_batch_norm, dropout=dropout) down_layers.append(x) x = MaxPooling2D((2, 2)) (x) dropout += dropout_change_per_layer filters = filters*2 # double the number of filters with each layer
x = conv2d_block(inputs=x, filters=filters, use_batch_norm=use_batch_norm, dropout=dropout)
if not use_dropout_on_upsampling: dropout = 0.0 dropout_change_per_layer = 0.0
for conv in reversed(down_layers): filters //= 2 # decrease the number of filters with each layer dropout -= dropout_change_per_layer x = upsample(filters, (2, 2), strides=(2, 2), padding='same') (x) x = concatenate([x, conv]) x = conv2d_block(inputs=x, filters=filters, use_batch_norm=use_batch_norm, dropout=dropout) outputs = Conv2D(num_classes, (1, 1), activation=output_activation) (x) model = Model(inputs=[inputs], outputs=[outputs]) return model
Вышеупомянутая функция unet_model завершает всю модель u-net. Щелкните здесь, чтобы увидеть графическую структуру указанной модели.
# train.py
import numpy as np import matplotlib.pyplot as plt import glob import os import sys from PIL import Image
masks = glob.glob("./dataset/isbi2015/train/label/*.png") orgs = glob.glob("./dataset/isbi2015/train/image/*.png")
Вышеупомянутая функция unet_model завершает всю модель u-net. Щелкните здесь, чтобы увидеть графическую структуру указанной модели.
# train.py
import numpy as np import matplotlib.pyplot as plt import glob import os import sys from PIL import Image
masks = glob.glob("./dataset/isbi2015/train/label/*.png") orgs = glob.glob("./dataset/isbi2015/train/image/*.png")
Мы импортируем набор данных в приведенном выше коде с помощью glob. Не забудьте загрузить или клонировать мой репозиторий github, чтобы найти набор данных.
imgs_list = [] masks_list = [] for image, mask in zip(orgs, masks): imgs_list.append(np.array(Image.open(image).resize((512,512)))) im = Image.open(mask).resize((512,512)) masks_list.append(np.array(im))
imgs_np = np.asarray(imgs_list) masks_np = np.asarray(masks_list)
Здесь мы инициализировали два списка, преобразовав необработанные изображения и изображения с аннотациями (метками) в разрешение 512x512 и добавив их в «imgs_list» и «masks_list» соответственно.
from sklearn.model_selection import train_test_split
x_train, x_val, y_train, y_val = train_test_split(x, y, test_size=0.5, random_state=0)
Здесь мы разделяем наш импортированный набор данных на обучающий набор и набор проверки, используя функцию «train_test_split» из sklearn. Мы выбрали 15 изображений для обучающей выборки и еще 15 изображений в качестве тестовой.
from utils import get_augmented
train_gen = get_augmented( x_train, y_train, batch_size=2, data_gen_args = dict( rotation_range=15., width_shift_range=0.05, height_shift_range=0.05, shear_range=50, zoom_range=0.2, horizontal_flip=True, vertical_flip=True, fill_mode='constant' ))
Вышеупомянутая функция используется для выполнения увеличения данных в нашем наборе данных. Он использует файл utils.py, включенный в мой github, для импорта функции get_augmented, которая использует ImageDataGenerator из keras.preprocessing.image внутри. Имена параметров, переданных в вышеупомянутой функции, описывают типы выполняемых дополнений.
sample_batch = next(train_gen) xx, yy = sample_batch
print(xx.shape, yy.shape)
from keras_unet.utils import plot_imgs plot_imgs(org_imgs=xx, mask_imgs=yy, nm_img_to_plot=2, figsize=6)
Вы можете построить и изучить расширенные изображения, запустив приведенный выше фрагмент кода.
from unet_model import unet_model
input_shape = x_train[0].shape
model = unet_model( input_shape, num_classes=1, filters=64, dropout=0.2, num_layers=4, output_activation='sigmoid' ) print(model.summary())
Инициализация сети и печать резюме реализованной модели.
from keras.callbacks import ModelCheckpoint
model_filename = 'segm_model_v0.h5' callback_checkpoint = ModelCheckpoint( model_filename, verbose=1, monitor='val_loss', save_best_only=True, )
from keras.optimizers import Adam, SGD from metrics import iou, iou_thresholded
model.compile( optimizer=SGD(lr=0.01, momentum=0.99), loss='binary_crossentropy', metrics=[iou, iou_thresholded] )
Здесь мы компилируем вышеуказанную модель, используя стохастический градиентный спуск в качестве нашего оптимизатора со скоростью обучения 0,01. И binary_crossentropy »в качестве нашей функции потерь.
history = model.fit_generator(
train_gen,
steps_per_epoch=100,
epochs=10,
validation_data=(x_val, y_val),
callbacks=[callback_checkpoint]
)
Приведенный выше код обучит модель, а на рисунке ниже показан график потерь и точности обучения:
После завершения обучения веса нашей обученной сети будут сохранены в том же каталоге, что и файл с расширением «.h5».
# test.py
from PIL import Image import numpy as np import glob masks = glob.glob("./dataset/isbi2015/train/label/*.png") orgs = glob.glob("./dataset/isbi2015/train/image/*.png")
imgs_list = [] masks_list = []
for image, mask in zip(orgs, masks): imgs_list.append(np.array(Image.open(image).resize((512,512)))) im = Image.open(mask).resize((512,512)) masks_list.append(np.array(im))
imgs_np = np.asarray(imgs_list) masks_np = np.asarray(masks_list)
x = np.asarray(imgs_np, dtype=np.float32)/255 y = np.asarray(masks_np, dtype=np.float32)/255
y = y.reshape(y.shape[0], y.shape[1], y.shape[2], 1) x = x.reshape(x.shape[0], x.shape[1], x.shape[2], 1)
from sklearn.model_selection import train_test_split
x_train, x_val, y_train, y_val = train_test_split(x, y, test_size=0.1, random_state=0)
from unet_model import unet_model
input_shape = x_train[0].shape
model = unet_model( input_shape, num_classes=1, filters=64, dropout=0.2, num_layers=4, output_activation='sigmoid' )
model_filename = 'segm_model_v0.h5' model.load_weights(model_filename) y_pred = model.predict(x_val)
from utils import plot_imgs
plot_imgs(org_imgs=x_val, mask_imgs=y_val, pred_imgs=y_pred, nm_img_to_plot=3)
Вышеупомянутый скрипт в основном импортирует данные, создает модель, и вместо ее обучения мы прогнозируем метки, загружая наши сохраненные веса. Ниже приведены результаты:
На этом заканчивается мое руководство по семантической сегментации, и то, что мы здесь увидели, является лишь верхушкой айсберга, учитывая широкий спектр приложений семантической сегментации, от медицинских изображений до беспилотных автомобилей. Спасибо.