ResNet50 Transfer Learning с оптимизированными параметрами поиска по сетке для обнаружения Covid-19 и пневмонии

В предыдущих сообщениях мы создали несколько сетей CNN для решения бизнес-задач, начиная от дизайна одежды, обнаружения рака и обнаружения животных и т. Д. В этом сообщении мы вернемся к теме CNN. Но вместо создания модели с нуля мы воспользуемся трансфертным обучением и построим сеть на основе известной архитектуры CNN: ResNet50.

«Если я и видел дальше, то стоя на плечах гигантов». Исаак Ньютон в 1675 году

Как обычно, разделите на следующие части:

  1. Контекст и проблема
  2. Обзор ResNet
  3. Просмотр данных
  4. Модель поезда
  5. Вывод

Начнем путешествие 🏃‍♀️🏃‍♂️!

1. Контекст и проблема

Искусственный интеллект и машинное обучение произвели революцию в отрасли здравоохранения и медицины. Согласно исследованию, опубликованному на сайте Nature, искусственный интеллект более точен, чем врачи, в диагностике рака груди с помощью маммографии. Недавно исследователи из Google Health и Имперского колледжа Лондона обучили модель на рентгеновских снимках почти 29 000 женщин. Алгоритм превзошел шесть рентгенологов в чтении маммограмм 👏👏.

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

2. Обзор ResNet

ResNet50 - это 50-слойная остаточная сеть, представленная He et al. в своей статье 2015 года Глубокое остаточное обучение для распознавания изображений. Он основан на остаточном обучении, той же архитектуре, которую используют его собратья, такие как ResNet101 и ResNet152.

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

В отличие от CNN, которая изучает особенности входных изображений, остаточное обучение изучает «остаточное» (разве не так очевидно?). Что ж, если нет, то остаток можно понимать как вычитание признаков из входных изображений. ResNet делает это, вводя так называемое «соединение быстрого доступа к идентификации», которое пропускает один или несколько промежуточных уровней. Другими словами, выходные данные более раннего уровня передаются на более глубокий уровень без какого-либо преобразования, как показано на рисунке 1.

При быстром подключении желаемое сопоставление - F (X) + X. Таким образом, остаточному блоку нужно сосредоточиться только на остаточном обучении F (X), потому что он может, по крайней мере, изучить X, заставив F (X ) до нуля. Интуиция за соединением пропуска заключается в том, что выучить остаток легче, чем любое другое прямое отображение. Это побуждает более глубокие слои узнавать что-то отличное от того, что уже закодировано во входных данных. Между тем, он позволяет обучать очень глубокие сети, не беспокоясь о проблеме деградации.

Архитектура ResNet содержит 2 вида остаточных блоков: Блок идентификации и блок Conv. На рис.2 показан идентификационный блок. Основная ветвь имеет сверточный слой, за которым следует пакетная нормализация и уровень активации, который обычно использует ReLu. После этого еще один блок сверточного слоя, пакетной нормализации и активационного слоя. Затем мы добавляем третий сверточный слой и пакетную нормализацию. Выход из нормы партии добавляется вместе с исходным входом перед активацией. Блоки идентичности работают хорошо, когда форма входа X такая же, как у выхода из основной ветви.

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

Всего архитектура ResNet50 состоит из 4 блоков Conv и 12 блоков Identity, а также 1 нормального сверточного слоя и 1 плотного слоя.

3. Обзор данных

Нам дается 133 изображения для каждого класса: Здоровые; COVID-19; Бактериальная пневмония; Вирусная пневмония. Изображения сохраняются на локальном диске, поэтому мы будем использовать метод flow_from_directory () из Keras для чтения изображений. Конкретно,

image_generator = ImageDataGenerator(rescale = 1./255, validation_split= 0.2)
train_gen = image_generator.flow_from_directory(batch_size = 8,directory= train_data_dir,shuffle = True,target_size = (256, 256), class_mode = ‘categorical’,subset= ‘training’)
validation_gen = image_generator.flow_from_directory(batch_size = 8,directory= train_data_dir,shuffle = True,target_size = (256, 256),class_mode = ‘categorical’, subset= ‘validation’)

Давайте коснемся изображений каждого класса. На рис.4 показаны изображения в градациях серого. Такие люди, как я, не могут ничего сказать о своей разнице 😭😭. Но не беспокойтесь 😎😎! Это работа модели.

4. Модель поезда

Как упоминалось ранее, мы сделаем переносное обучение из предварительно обученной модели ResNet50. Он обучен на наборе данных ImageNet, который содержит 11 миллионов изображений и 11000 категорий, что огромно по сравнению с набором данных, который мы получили здесь.

4.1 Модель нагрузки

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

basemodel = ResNet50(weights = ‘imagenet’, include_top = False, input_tensor = Input(shape = (256, 256, 3)))

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

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

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

for layer in basemodel.layers[: -10]:
    layer.trainable = False

4.2 Создание и обучение модели

Я думаю, что этот раздел - самое интересное, потому что вы увидите, как «стоять на плечах гигантов».

Внимательно посмотрите на архитектуру ниже. Мы принимаем выходные данные модели ResNet50 в качестве входных данных для ее следующих слоев. Мы сложили 3 плотных слоя с выпадением перед добавлением последнего плотного слоя для вывода.

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

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

headmodel = basemodel.output
headmodel = AveragePooling2D(pool_size = (4,4))(headmodel)
headmodel = Flatten(name= ‘flatten’)(headmodel)
headmodel = Dense(256, activation = “relu”)(headmodel)
headmodel = Dropout(0.4)(headmodel)
headmodel = Dense(128, activation = “relu”)(headmodel)
headmodel = Dropout(0.4)(headmodel)
headmodel = Dense(64, activation = “relu”)(headmodel)
headmodel = Dropout(0.4)(headmodel)
headmodel = Dense(4, activation = ‘softmax’)(headmodel)
model = Model(inputs = basemodel.input, outputs = headmodel)
model.compile(loss = ‘categorical_crossentropy’,optimizer = optimizers.RMSprop(lr = 1e-4, decay = 1e-6),metrics = [f1, ‘accuracy’])

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

earlystopping = EarlyStopping(monitor = ‘val_loss’, mode = ‘min’, patience = 10)
checkpointer = ModelCheckpoint(filepath = ‘weights.hdfs’, verbose = 1, save_best_only=True)

Здорово. Количество эпох (20) и размер пакета (8) оптимизированы для получения наилучших результатов. Итак, чтобы обучить модель,

history = model.fit_generator(
train_generator, steps_per_epoch= train_generator.n//8, 
epochs = 20, validation_data= val_generator, 
validation_steps= val_generator.n//8, callbacks=[checkpointer, earlystopping])

4.3 Оценить модель

Рис.5 показывает, что точность модели постепенно увеличивается примерно до 0,75, а потери уменьшаются примерно до 0,65. Другое дело, что тренировочный процесс стабильный, без резких пиков и спадов.

На рис.6 показана точность модели во время проверки. Модель достигла точности проверки примерно от 0,65 до 0,7, что указывает на незначительное переоснащение.

Наконец, давайте посмотрим на производительность модели в тестовом наборе данных.

evaluate=model.evaluate_generator(test_generator,steps=test_generator.n//4, verbose = 1)

На рис.7 показаны прогнозируемые и фактические классы для 15 изображений. Модель очень хорошо предсказала Covid-19. Отчет о классификации, показанный на рисунке 8, показывает, что для Covid-19 модель получила 100% отзыв ✨✨. Это означает, что из 10 изображений Covid-19 удалось все правильно спрогнозировать. Для пневмонии отзыв не идеален, примерно от 50% до 60% предсказаны правильно. Безусловно, модели есть что улучшать.

5. Вывод

Мы продемонстрировали, как использовать трансферное обучение для создания настраиваемой модели для прогнозирования Covid-19 / пневмонии. Имея всего около 500 изображений, нам удалось добиться достойного результата, особенно при обнаружении Covid-19.

Что интересно сделать дальше, если вы хотите запачкать руки, так это переоборудовать модель только на нормальных изображениях и изображениях Covid-19. Создание модели обнаружения Covid-19 было бы замечательной вещью, которую мы можем внести в борьбу с ним.

Здорово! Вот и все путешествия. Надеюсь, вам понравилось. Если вам нужен код, посетите мои репозитории Github 💕💕.