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

Мы покажем, как использовать fast.ai для решения задачи Анализ поражения кожи на пути к обнаружению меланомы 2018 года и автоматического определения семи видов кожных патологий.

Автор: Альдо фон Вангенхайм - [email protected]

Это основано на следующем материале:

  1. TowardsDataScience :: Классификация поражений кожи с помощью сверточных нейронных сетей - руководство и введение в глубокое обучение в медицине от Ариана Мисры.
  2. Чандл, Филипп, 2018 г., Набор данных HAM10000, большая коллекция дерматоскопических изображений из нескольких источников распространенных пигментных поражений кожи, https://doi.org/10.7910/DVN/DBW86T, Harvard Dataverse [версия препринта arXiv: ArXiv: 1803.10417 [cs.CV]]
  3. Инструменты для работы с датасетом HAM10000 - GitHub. Этот репозиторий предоставляет доступ к инструментам, созданным и используемым для сборки набора обучающих данных.

Набор данных HAM10000 (Человек против машины с 10000 обучающих изображений) служил обучающим набором для Задачи ISIC 2018 (Задача 3). Официальные наборы для валидации и тестирования этой задачи доступны без ярлыков, подтверждающих достоверность информации, на веб-сайте задачи https://challenge2018.isic-archive.com/. ISIC-Archive также предоставляет сайт представления« Live Challenge для непрерывной оценки автоматических классификаторов на официальном наборе для валидации и тестирования.

Изображения выглядят так:

Что в этом наборе данных?

Этот набор данных содержит пигментные поражения кожи, полученные с помощью стандартной дерматоскопии. Это темные поражения, при которых ткань вырабатывает меланин, естественный пигмент кожи человека. Не все виды поражений, первоначально исследованные и упорядоченные с помощью дерматоскопии, обязательно являются пигментными поражениями. Это означает, что в реальной ситуации терапевт или медсестра, осматривающие пациента с помощью дерматоскопии (или пациента, выполняющего самообследование) с намерением отправить эти изображения дерматологу для первоначальной сортировки, возможно, столкнутся с другими поражениями, кроме изображенные в этом наборе данных здесь (см. Существует ли ISIC 2019? ниже).

Классы поражений в наборе данных HAM10000:

  1. nv: меланоцитарные невусы - доброкачественные новообразования меланоцитов [6705 изображений]
  2. mel: меланома - злокачественное новообразование, происходящее из меланоцитов [1113 изображений]
  3. bkl: доброкачественный кератоз - общий класс, который включает себорейный кератоз, солнечное лентиго и плоский лишай, подобные кератозу [1099 изображений];
  4. bcc: базальноклеточная карцинома - распространенный вариант эпителиального рака кожи, который редко метастазирует, но при отсутствии лечения разрушительно растет (bcc не обязательно вызывает пигментные поражения) [514 изображений];
  5. akiec: актинические кератозы и интраэпителиальная карцинома - распространенные неинвазивные варианты плоскоклеточного рака, которые можно лечить местно без хирургического вмешательства [327 изображений];
  6. vasc: сосудистые поражения кожи от вишневых ангиом до ангиокератом и пиогенных гранулем [142 изображения];
  7. df: дерматофиброма - доброкачественное поражение кожи, рассматриваемое как доброкачественное разрастание или воспалительная реакция на минимальную травму [115 изображений].

Более подробное описание каждого класса можно найти в ядре Kaggle: Skin Lesion Analyzer + Tensorflow.js Web App - записная книжка Python с использованием данных из Skin Cancer MNIST: HAM10000, а также в статье Филиппа Чандла, приведенной выше.

Каковы диагностические ограничения этого набора данных?

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

Если вы хотите узнать больше о протоколах получения теледерматологических исследований, посмотрите здесь:

  • Альдо фон Вангенхайм и Даниэль Хольтхаузен Нуньес. Создание веб-инфраструктуры для поддержки клинических протоколов и клинического управления: пример в теледерматологии. Телемедицина и электронное здравоохранение. Онлайн в преддверии печати: 30 ноября 2018 г. http://doi.org/10.1089/tmj.2018.0197. Также есть препринт, доступный на ResearchGate.

Как получают дерматоскопические изображения?

Используемый в настоящее время контактный дерматоскоп является результатом международных усилий по стандартизации этого обследования, проведенных в первой половине 1990-х годов под руководством группы исследователей из Мюнхенского университета в Германии. В этом оборудовании используется одиночный объектив с 10-кратным увеличением и внутренняя подсветка светодиодами. Обследование проводится с использованием минерального масла, которое наносится на поверхность поражения до того, как дерматоскоп накладывается на поражение и делается фотография.

Выбор монокулярного объектива с 10-кратным увеличением в качестве стандарта позволил разработать очень маленькие устройства, которые вскоре стали очень популярными. Аналоговые дерматоскопы можно носить в нагрудном кармане, а цифровые можно легко разработать как небольшие USB-устройства или как адаптеры для цифровых фотоаппаратов и смартфонов.

Недостатком этого стандарта является то, что 10-кратного увеличения недостаточно для надежного обнаружения некоторых патологий, таких как базальноклеточная карцинома, которая является наиболее распространенной формой рака кожи. Эта форма новообразования характеризуется сосудистыми изменениями, называемыми арбориформными васкуляризациями, которые нельзя надежно наблюдать с помощью монокулярных линз с 10-кратным увеличением: всегда необходима подтверждающая биопсия, чтобы поставить окончательный диагноз. Для надежного обнаружения требуется большее увеличение и бинокулярная оптика [1] [2].

До 1990-х годов разрабатывались и другие типы дерматоскопов, которые могли обеспечить лучшее качество изображения, но были крупнее и не так просты в эксплуатации, как, например, бинокулярный стереоскопический контактный дерматоскоп 5 0x. Это оборудование лучше подходит для визуального раннего обнаружения таких патологий, как базальноклеточная карцинома [1] [2]. Однако практичность, популяризация и стандартизация 10-кратного контактного монокулярного дерматоскопа остановили эти другие направления исследований.

Есть ли МСОК 2019?

ISIC - Международное сотрудничество в области визуализации кожи исправило отсутствие некоторых дерматологических диагностических категорий в наборе данных HAM10000, опубликовав новый набор данных в Задаче ISIC 2019: анализ повреждений кожи на пути к обнаружению меланомы. Набор данных 2019 года, выпущенный 3 мая 2019 года, теперь содержит девять различных диагностических категорий и 25 331 изображение:

  1. Меланома
  2. Меланоцитарный невус
  3. Базально-клеточная карцинома
  4. Старческий кератоз
  5. Доброкачественный кератоз (солнечное лентиго / себорейный кератоз / красный плоский кератоз)
  6. Дерматофиброма
  7. Сосудистое поражение
  8. Плоскоклеточная карцинома
  9. Никто из других

Давайте приступим к работе над набором данных о вызове 2018 года.

Инициализации

Делайте то, что описано в этом разделе ниже, один раз при каждом запуске этой записной книжки

%reload_ext autoreload
%autoreload 2
%matplotlib inline

Тестирование вашей виртуальной машины в Google Colab…

Только для уверенности посмотрите, какой драйвер CUDA и какой графический процессор Colab предоставил вам. Графический процессор обычно будет либо:

  • K80 с 11 ГБ оперативной памяти или (если вам действительно повезет)
  • Tesla T4 с 14 ГБ ОЗУ

Если серверы Google переполнены, у вас в конечном итоге будет доступ только к части графического процессора. Если ваш графический процессор используется совместно с другим ноутбуком Colab, вы увидите, что для вас будет доступен меньший объем памяти.

Совет: избегайте пиковых периодов на западном побережье США. Я живу в GMT-3, и мы на два часа опережаем восточное побережье США, поэтому я всегда стараюсь выполнять тяжелую обработку в утренние часы.

!/opt/bin/nvidia-smi
!nvcc --version

Когда я начал проводить описанные здесь эксперименты, мне повезло: у меня был целый Т4 с 15079 МБ ОЗУ! Мой вывод выглядел так:

Thu May  2 07:36:26 2019       
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 410.79       Driver Version: 410.79       CUDA Version: 10.0     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  Tesla T4            Off  | 00000000:00:04.0 Off |                    0 |
| N/A   63C    P8    17W /  70W |      0MiB / 15079MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+
                                                                               
+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID   Type   Process name                             Usage      |
|=============================================================================|
|  No running processes found                                                 |
+-----------------------------------------------------------------------------+
nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2018 NVIDIA Corporation
Built on Sat_Aug_25_21:08:01_CDT_2018
Cuda compilation tools, release 10.0, V10.0.130

Libray импорт

Здесь мы импортируем все необходимые пакеты. Мы собираемся работать с библиотекой fastai V1, которая находится поверх Pytorch 1.0. Библиотека fastai предоставляет множество полезных функций, которые позволяют нам быстро и легко создавать нейронные сети и обучать наши модели.

from fastai.vision import *
from fastai.metrics import error_rate
from fastai.callbacks import SaveModelCallback
# Imports for diverse utilities
from shutil import copyfile
import matplotlib.pyplot as plt
import operator
from PIL import Image
from sys import intern   # For the symbol definitions

Функции экспорта и восстановления

# Export network for deployment and create a copy
def exportStageTo(learn, path):
    learn.export()
    # Faça backup diferenciado
    copyfile(path/'export.pkl', path/'export-malaria.pkl')
    
#exportStage1(learn, path)
# Restoration of a deployment model, for example in order to conitnue fine-tuning
def restoreStageFrom(path):
  # Restore a backup
  copyfile(path/'export-malaria.pkl', path/'export.pkl')
  return load_learner(path)
  
#learn = restoreStage1From(path)

Скачать дерматоскопические изображения пигментных поражений

Мы загрузим Kaggle версию этого набора данных, потому что в Google Colab предустановлен Kaggle API, и все это организовано в один файл .zip. Для загрузки с Kaggle вам необходимо:

  • аккаунт на Kaggle
  • для установки ваших учетных данных Kaggle (файл .json) на Colab

Чтобы увидеть, как это сделать, сначала просмотрите это руководство и инструкции Kaggle API, а затем сгенерируйте и загрузите свои учетные данные в Colab:

Когда вы создали и скопировали свои учетные данные Kaggle в Colab

Запустите ячейку ниже. Он создаст папку для ваших учетных данных для Kaggle API и установит ваши учетные данные в Colab:

!mkdir .kaggle
!mv kaggle.json .kaggle
!chmod 600 /content/.kaggle/kaggle.json
!cp /content/.kaggle/kaggle.json ~/.kaggle/kaggle.json
!chmod 600 ~/.kaggle/kaggle.json
!kaggle config set -n path -v{/content}

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

- path is now set to: {/content}

Выполните фактическую загрузку и разархивирование

Создайте папку «data» и загрузите в нее изображения дерматоскопии.

!mkdir data
!kaggle datasets download kmader/skin-cancer-mnist-ham10000 -p data

Это даст следующий результат:

Downloading skin-cancer-mnist-ham10000.zip to data
100% 2.61G/2.62G [00:52<00:00, 42.3MB/s]
100% 2.62G/2.62G [00:52<00:00, 53.4MB/s]

Разархивируйте весь zip-файл в / content / data, а затем незаметно (-q) разархивируйте файлы изображений (вы не хотите подробно разархивировать более 10 КБ изображений!). Мы будем использовать параметр - override (-o), чтобы разрешить тихое переопределение файлов, которые могли быть созданы в результате некоторой прерванной предыдущей попытки, которую вы сделали.

# Unzip the whole zipfile into /content/data
!unzip -o data/skin-cancer-mnist-ham10000.zip -d data
# Quietly unzip the image files
!unzip -o -q data/HAM10000_images_part_1.zip -d data
!unzip -o -q data/HAM10000_images_part_2.zip -d data
# Tell me how many files I unzipped///
!echo files in /content/data: `ls data | wc -l`

Если у вас есть 10 023 файла, вы все сделали правильно!

Archive:  data/skin-cancer-mnist-ham10000.zip
  inflating: data/hmnist_28_28_RGB.csv  
  inflating: data/HAM10000_metadata.csv  
  inflating: data/HAM10000_images_part_1.zip  
  inflating: data/hmnist_28_28_L.csv  
  inflating: data/hmnist_8_8_L.csv   
  inflating: data/HAM10000_images_part_2.zip  
  inflating: data/hmnist_8_8_RGB.csv  
files in /content/data: 10023

Подготовьте свои данные

Теперь подготовим наши данные к обработке в fast.ai.

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

Здесь все изображения хранятся в одной папке, а метаданные хранятся в электронной таблице, которую мы будем читать с помощью метода fast.ai ImageDataBunch.from_csv () из API блока данных fast.ai.

Что мы здесь делаем по-другому?

Набор данных HAM10000 не предоставляет изображения, отсортированные по папкам в соответствии с их классами. Вместо этого все изображения находятся в одной папке, и предоставляется электронная таблица с несколькими метаданными для каждого из изображений. В этом руководстве мы будем читать класс каждого изображения из этой электронной таблицы .csv вместо того, чтобы организовывать файлы изображений в папки, где имя папки является классом, к которому принадлежат изображения. fast.ai также предоставляет готовые методы для интерпретации электронных таблиц и извлечения данных классификации изображений. В этой публикации мы узнаем, как использовать эти методы.

Для этого мы будем использовать API блока данных от fast.ai. В следующем сообщении есть очень хорошее объяснение, как это работает:

Создайте свои пакеты данных для обучения и проверки

В исходном учебнике bove, в котором используется Keras, есть процедура создания папок для обучения, проверки и тестирования на основе данных. С fast.ai в этом нет необходимости: если у вас есть только папка «train», вы можете разделить ее при создании DataBunch, просто передав несколько параметров ...

С fast.ai мы также можем легко работать с разрешениями, которые отличаются от исходных разрешений ImageNet, используемых для предварительного обучения сетей, которые мы будем использовать. В указанном выше руководстве автор уменьшил разрешение изображения набора данных до 224x224, чтобы использовать модель Keras MobileNet. Мы будем использовать разрешение 448x448:

bs = 64        # Batch size, 64 for medium images on a T4 GPU...
size = 448      # Image size, 448x448 is double than the orignal 
                # ImageNet
path = Path("./data")   # The path to the 'train' folder you created...
# Limit your augmentations: it's medical data! You do not want to phantasize data...
# Warping, for example, will let your images badly distorted, so don't do it!
# This dataset is big, so don't rotate the images either. Lets stick to flipping...
tfms = get_transforms(flip_vert=True, max_lighting=0.1, max_rotate=None, max_warp=None, max_zoom=1.0)
# Create the DataBunch!
# Remember that you'll have images that are bigger than 128x128 and images that are smaller,   
# so squish them all in order to occupy exactly 128x128 pixels...
data = ImageDataBunch.from_csv('data', csv_labels='HAM10000_metadata.csv', suffix='.jpg', fn_col=1, label_col=2, 
                               ds_tfms=tfms, valid_pct = 0.2,size=size, bs=bs)
print('Transforms = ', len(tfms))
# Save the DataBunch in case the training goes south... so you won't have to regenerate it..
# Remember: this DataBunch is tied to the batch size you selected. 
data.save('imageDataBunch-bs-'+str(bs)+'-size-'+str(size)+'.pkl')
# Show the statistics of the Bunch...
print(data.classes)
data

Это даст следующий результат:

Transforms =  2
['akiec', 'bcc', 'bkl', 'df', 'mel', 'nv', 'vasc']
ImageDataBunch;
Train: LabelList (8012 items)
x: ImageList
Image (3, 448, 448),Image (3, 448, 448),Image (3, 448, 448),Image (3, 448, 448),Image (3, 448, 448)
y: CategoryList
bkl,bkl,bkl,bkl,bkl
Path: data;
Valid: LabelList (2003 items)
x: ImageList
Image (3, 448, 448),Image (3, 448, 448),Image (3, 448, 448),Image (3, 448, 448),Image (3, 448, 448)
y: CategoryList
bkl,nv,nv,nv,nv
Path: data;
Test: None

Посмотрите на свой DataBunch, чтобы увидеть, приемлемы ли дополнения…

data.show_batch(rows=5, figsize=(15,15))

Первый обучающий эксперимент: ResNet34

Если вы не знаете, какую модель использовать, лучше начать с остаточной сети с 34 слоями. Мощный, но не слишком маленький и не слишком большой…

В указанном выше руководстве автор использовал MobileNet, реализованный в Keras, и исходное разрешение изображения сети 224x224. В fast.ai ResNet легко адаптируется для работы с разрешением 448x448 нашего DataBunch.

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

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

  • точность: точность проверки.
  • error_rate: частота ошибок проверки.

Если вам нужна дополнительная информация, загляните на https://docs.fast.ai/metrics.html.

learn = cnn_learner(data, models.resnet34, metrics=[accuracy, error_rate, dice(iou=True), fbeta])
learn.model

Просто передайте переменную data, содержащую экземпляр DataBunch, в функцию cnn_learner (), и fast.ai автоматически адаптирует входной уровень новую сеть с более высоким разрешением изображения. Модель будет выглядеть так:

Sequential(
  (0): Sequential(
    (0): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)
    (1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (2): ReLU(inplace)
    (3): MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False)
    (4): Sequential(
      (0): BasicBlock(
        (conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (relu): ReLU(inplace)
        (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      )
      (1): BasicBlock(
        (conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (relu): ReLU(inplace)
        (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      )
      (2): BasicBlock(
        (conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (relu): ReLU(inplace)
        (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      )
    )
    (5): Sequential(
      (0): BasicBlock(
        (conv1): Conv2d(64, 128, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
        (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (relu): ReLU(inplace)
        (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (downsample): Sequential(
          (0): Conv2d(64, 128, kernel_size=(1, 1), stride=(2, 2), bias=False)
          (1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        )
...
...
... and so on...

Стратегия обучения

Мы будем использовать метод fit1cycle, разработанный Лесли Н. Смит - подробности см. Ниже:

Если вы хотите узнать больше о новом обучающем API в библиотеке fast.ai, посмотрите этот блокнот, подготовленный Сильвеном Гуггером.

Мы также будем сохранять сеть каждую эпоху, если производительность улучшится: https://docs.fast.ai/callbacks.html#SaveModelCallback

learn.fit_one_cycle(10, callbacks=[SaveModelCallback(learn, every='epoch', monitor='accuracy', name='derma-1')])
# Salve a rede (necessita regerar o databunch caso a gente continue)
learn.save('derma-stage-1')
# Faça o deploy desta rede para podermos usar offline depois para fazer testes
exportStageTo(learn, path)

Результаты для ResNet34

Посмотрим, какие результаты у нас есть.

Сначала мы увидим, какие категории модель больше всего путала друг с другом. Мы постараемся понять, было ли то, что предсказывала модель, разумным или нет. В этом случае ошибки выглядят разумными (ни одна из них не кажется явно наивной). Это показатель того, что наш классификатор работает правильно.

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

Давайте сгенерируем ClassificationInterpretation и посмотрим на некоторые результаты, матрицу неточностей и кривые потерь.

interp = ClassificationInterpretation.from_learner(learn)
losses,idxs = interp.top_losses()
len(data.valid_ds)==len(losses)==len(idxs)

Сначала посмотрите на свои худшие результаты, не используя тепловую карту:

interp.plot_top_losses(9, figsize=(20,11), heatmap=False)

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

interp.plot_top_losses(9, figsize=(20,11), heatmap=True)

Покажите матрицу путаницы

Здесь у нас есть семь классов, и имеет смысл взглянуть на матрицу путаницы. Кроме того, он делает красивые снимки…

interp.plot_confusion_matrix(figsize=(5,5), dpi=100)

Здесь мы видим следующее:

  • невусы - наиболее частое явление. Можно подумать об уменьшении количества невусов в обучающей выборке, чтобы не искажать результаты;
  • существует несколько ошибочно классифицированных доброкачественных кератозов (bkl). Вероятно, это связано с тем, что bkl в этом наборе данных является общим классом, который включает себорейный кератоз, солнечное лентиго и плоский лишай, подобные кератозу, которые являются дерматозами, которые, даже если они связаны, выглядят действительно очень разные;
  • есть также несколько ошибочно классифицированных меланом (mel). Это было сюрпризом. Я ожидал, что здесь сеть будет работать лучше.

Если вас смущают матрицы путаницы, посмотрите здесь:

Покажите свою кривую обучения:

Постройте график своих потерь, чтобы увидеть кривую обучения:

learn.recorder.plot_losses()

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

Точная настройка ResNet34

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

Метод learn.lr_find () поможет вам найти оптимальную скорость обучения. В нем используется методика, разработанная в статье Cyclical Learning Rates for Training Neural Networks (http://arxiv.org/abs/1506.01186), опубликованной в 2015 году, где мы просто продолжаем увеличивать скорость обучения с очень низкого уровня. небольшое значение, пока убыток не начнет уменьшаться.

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

Давай сделаем это:

# Unfreeze the network
learn.unfreeze()
# Find optimum learning rates
learn.lr_find()
# Include suggestion=True in order to obtain a suggestion on where to look...
learn.recorder.plot(suggestion=True)

Давайте настроим ResNet34. Для уверенности возьмем 30 эпох. Средство поиска скорости обучения определило 1e-5 как «безопасную» скорость обучения. Поэтому мы определим диапазон скоростей обучения, используя эмпирическое правило: заканчиваться на «безопасном» уровне 1e-5 и начинаться со скорости на один порядок. выше: max_lr = slice (1e-4,1e-5).

# Unfreeze the network
learn.unfreeze()
learn.fit_one_cycle(30, max_lr=slice(1e-4,1e-5), 
                    callbacks=[SaveModelCallback(learn, every='epoch', monitor='accuracy', name='derma')])
# Agora, salve como estágio 2...
learn.save('derma-stage-2')
# Deploy definitivo
exportStageTo(learn, path)

Таким образом, мы достигли точности 93% при первом запуске. Это очень хорошо! Точность, достигнутая в руководстве и ядре выше, составляет соответственно 85% и 86%.

Теперь посмотрим на нашу статистику:

interp = ClassificationInterpretation.from_learner(learn)
losses,idxs = interp.top_losses()
# Test to see if there's not anything missing (must return True)
len(data.valid_ds)==len(losses)==len(idxs)

Если это возвращает True, постройте матрицу путаницы для настроенной сети:

interp.plot_confusion_matrix(figsize=(5,5), dpi=100)

В этой матрице прогнозы меланомы выглядят намного лучше! Давайте посмотрим на кривые обучения:

learn.recorder.plot_losses()

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

В записной книжке, которую мы сделали доступной на Colab, мы обучили сеть еще 30 эпох, только для уверенности. На самом деле становится хуже, вероятно, из-за переобучения. Так что остановка здесь - хороший выбор для ResNet34.

Стань больше: ResNet50

С ResNet34 достигается точность 92,9%. Посмотрим, сможем ли мы работать лучше с более крупной сетью. Мы снова создадим DataBunch, на этот раз с меньшим размером пакета, чтобы не перегружать память GPU ...

bs = 28         # Batch size, 28 for medium images on a T4 GPU and ResNet50...
size = 448      # Image size, 448x448 is double than the orignal 
                # ImageNet size of the pre-trained ResNet we'll be using, 
                # should be easy to train...
path = Path("./data")   # The path to the 'train' folder you created...
# Limit your augmentations: it's medical data! You do not want to phantasize data...
# Warping, for example, will let your images badly distorted, so don't do it!
# This dataset is big, so don't rotate the images either. Lets stick to flipping...
tfms = get_transforms(flip_vert=True, max_lighting=0.1, max_rotate=None, max_warp=None, max_zoom=1.0)
# Create the DataBunch!
# Remember that you'll have images that are bigger than 128x128 and images that are smaller,   
# so squish them all in order to occupy exactly 128x128 pixels...
data = ImageDataBunch.from_csv('data', csv_labels='HAM10000_metadata.csv', suffix='.jpg', fn_col=1, label_col=2, 
                               ds_tfms=tfms, valid_pct = 0.2,size=size, bs=bs)
print('Transforms = ', len(tfms))
# Save the DataBunch in case the training goes south... so you won't have to regenerate it..
# Remember: this DataBunch is tied to the batch size you selected. 
data.save('imageDataBunch-bs-'+str(bs)+'-size-'+str(size)+'.pkl')
# Show the statistics of the Bunch...
print(data.classes)
data

Теперь результат должен выглядеть так:

Transforms =  2
['akiec', 'bcc', 'bkl', 'df', 'mel', 'nv', 'vasc']
ImageDataBunch;
Train: LabelList (8012 items)
x: ImageList
Image (3, 448, 448),Image (3, 448, 448),Image (3, 448, 448),Image (3, 448, 448),Image (3, 448, 448)
y: CategoryList
bkl,bkl,bkl,bkl,bkl
Path: data;
Valid: LabelList (2003 items)
x: ImageList
Image (3, 448, 448),Image (3, 448, 448),Image (3, 448, 448),Image (3, 448, 448),Image (3, 448, 448)
y: CategoryList
nv,nv,nv,mel,bkl
Path: data;
Test: None

Теперь создайте ResNet50:

learn50 = cnn_learner(data, models.resnet50, metrics=[accuracy, error_rate])
learn50.model

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

Sequential(
  (0): Sequential(
    (0): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)
    (1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (2): ReLU(inplace)
    (3): MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False)
    (4): Sequential(
      (0): Bottleneck(
        (conv1): Conv2d(64, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)
        (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (conv3): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
        (bn3): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (relu): ReLU(inplace)
        (downsample): Sequential(
          (0): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
          (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        )
      )
      (1): Bottleneck(
        (conv1): Conv2d(256, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)
        (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (conv3): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
        (bn3): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (relu): ReLU(inplace)
      )
      (2): Bottleneck(
        (conv1): Conv2d(256, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)
        (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (conv3): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
        (bn3): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (relu): ReLU(inplace)
      )
    )
...
...
...
(1): Sequential(
    (0): AdaptiveConcatPool2d(
      (ap): AdaptiveAvgPool2d(output_size=1)
      (mp): AdaptiveMaxPool2d(output_size=1)
    )
    (1): Flatten()
    (2): BatchNorm1d(4096, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (3): Dropout(p=0.25)
    (4): Linear(in_features=4096, out_features=512, bias=True)
    (5): ReLU(inplace)
    (6): BatchNorm1d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (7): Dropout(p=0.5)
    (8): Linear(in_features=512, out_features=7, bias=True)
  )
)

Положите, чтобы узнать:

learn50.fit_one_cycle(15, callbacks=[SaveModelCallback(learn50, every='epoch', monitor='accuracy', name='derma50-1')])
# Save weights
learn50.save('derma50-stage-1')
# Deploy the whole network (with the databunch)
exportStageTo(learn50, path)

Посмотрите результаты:

interp = ClassificationInterpretation.from_learner(learn50)
losses,idxs = interp.top_losses()
interp.plot_confusion_matrix(figsize=(5,5), dpi=100)

Посмотрите на кривые обучения:

learn50.recorder.plot_losses()

На данный момент это не впечатлило: сеть колебалась больше, чем ResNet34 во время этой первой фазы обучения передачи, и результаты, даже если они численно лучше (точность 85% x 87%), на самом деле выглядят хуже при визуальном анализе матрицы путаницы. Давайте проведем тонкую настройку ResNet50 и посмотрим, дает ли это лучшие результаты.

Сделаем это в двух экспериментах.

ResNet50 Эксперимент №1: Точная настройка со слепым принятием предложения скорости обучения

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

# Unfreeze the network
learn50.unfreeze()
# Find optimum learning rates
learn50.lr_find()
# Include suggestion=True in order to obtain a suggestion on where to look...
learn50.recorder.plot(suggestion=True)

Это выведет:

LR Finder is complete, type {learner_name}.recorder.plot() to see the graph.
Min numerical gradient: 9.12E-07
Min loss divided by 10: 1.10E-07

Нижнюю границу, 9.12E-07, мы округлим до 1.0E-06. Теперь вы можете выполнить точную настройку, используя наше практическое правило. Мы определим диапазон скоростей обучения, используя эмпирическое правило: заканчиваться с «безопасной» скоростью 1e-06 и начинать со скорости, которая на порядок выше :

# Unfreeze the network
learn50.unfreeze()
learn50.fit_one_cycle(35, max_lr=slice(1e-5,1e-6), 
                    callbacks=[SaveModelCallback(learn50, every='epoch', monitor='accuracy', name='derma50')])
# Save the weights of stage 2 each "better" epoch:
learn50.save('derma50-stage-2')
# Do not overwrite the stage 1 .pkl with  stage 2 
# We will need it for the ResNet50 Experiment #2
# exportStageTo(learn50, path)

Итак, после 35 эпох и большой обработки мы достигли точности 90%. Это выглядит бесперспективным ... Давайте посмотрим на матрицу путаницы и кривые обучения:

interp = ClassificationInterpretation.from_learner(learn50)
losses,idxs = interp.top_losses()
interp.plot_confusion_matrix(figsize=(5,5), dpi=100)

learn50.recorder.plot_losses()

То, что мы видим здесь, сильно отличается от ResNet34. С ResNet50 сеть изучает обучающий набор во время фазы тонкой настройки, но он все время колеблется: некоторые пакеты улучшают его работу, другие пакеты заставляют его возвращаться и работать хуже. Обычно это означает, что данные плохого качества и содержат слишком много шума. Однако в этом случае мы уже видели, что с ResNet34 можно достичь точности 93%. Итак, плохие данные здесь не так. Другая возможность состоит в том, что в сети слишком много параметров, и она не является обобщающей, а адаптируется к отдельным экземплярам обучающей выборки, таким образом изучая отдельные примеры и деобобщая. Это ухудшает его производительность для других частей набора данных, и поэтому сеть действует как маятник, перемещаясь вперед и назад в пространстве ошибок. Потери при проверке, которые намного превышают потери при обучении в течение всего процесса точной настройки, подтверждают эту интерпретацию кривой обучения.

Но мы упрямы ... Давайте проведем еще один эксперимент.

ResNet50 Эксперимент № 2: Точная настройка с ручной настройкой скорости обучения

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

Если мы посмотрим на график скорости обучения, мы увидим, что плато уже формируется примерно на 1.0E-4. Затем скорость обучения падает в две впадины, одну на 1.0E-5, а другую на 1.0E-6. Что, если мы возьмем более стабильную зону плоского плато от 1.0E-4 до 1.0E-5 и сделаем это нашим диапазоном скорости обучения?

Во-первых, восстановите сеть до состояния, которое было у вас, когда вы закончили начальное обучение передачи:

# Will always load a path/'export.pkl' deployment file
learn50 = restoreStageFrom(path)

Снова настройте его, теперь с помощью max_lr = slice (1e-4,1e-5):

# Unfreeze the network
learn50.unfreeze()
learn50.fit_one_cycle(35, max_lr=slice(1e-4,1e-5), 
                    callbacks=[SaveModelCallback(learn50, every='epoch', monitor='accuracy', name='derma50')])
learn50.save('derma50-stage-2')
exportStageTo(learn50, path)

Посмотрим на графику результатов:

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

Давайте пойдем в другом направлении и поэкспериментируем с сетью, которая меньше ResNet34, а не больше: ResNet18.

ResNet18

Почему бы тогда не попробовать сеть гораздо меньшего размера и посмотреть, как она себя ведет? Давайте попробуем ResNet18 с дерматоскопическими данными!

Для этого мы изменим размер нашего пакета и повторно сгенерируем DataBunch. ResNet18 намного меньше, и у нас будет больше памяти, поэтому имеет смысл использовать больший размер пакета:

bs = 48         # Batch size, 64 for medium images on a T4 GPU and ResNet18...
size = 448      # Image size, 448x448 is double than the orignal 
                # ImageNet size of the pre-trained ResNet we'll be using, 
                # should be easy to train...
path = Path("./data")   # The path to the 'train' folder you created...
# Limit your augmentations: it's medical data! You do not want to phantasize data...
# Warping, for example, will let your images badly distorted, so don't do it!
# This dataset is big, so don't rotate the images either. Lets stick to flipping...
tfms = get_transforms(flip_vert=True, max_lighting=0.1, max_rotate=None, max_warp=None, max_zoom=1.0)
# Create the DataBunch!
# Remember that you'll have images that are bigger than 128x128 and images that are smaller,   
# so squish them all in order to occupy exactly 128x128 pixels...
data = ImageDataBunch.from_csv('data', csv_labels='HAM10000_metadata.csv', suffix='.jpg', fn_col=1, label_col=2, 
                               ds_tfms=tfms, valid_pct = 0.2,size=size, bs=bs)
print('Transforms = ', len(tfms))
# Save the DataBunch in case the training goes south... so you won't have to regenerate it..
# Remember: this DataBunch is tied to the batch size you selected. 
data.save('imageDataBunch-bs-'+str(bs)+'-size-'+str(size)+'.pkl')
# Show the statistics of the Bunch...
print(data.classes)
data

Создайте сеть:

learn = cnn_learner(data, models.resnet18, metrics=[accuracy, error_rate])
learn.model

Модель будет выглядеть так (обратите внимание, что эта меньшая модель ResNet имеет только 1024 выходных функции вместо 4096 в последнем блоке):

Sequential(
  (0): Sequential(
    (0): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)
    (1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (2): ReLU(inplace)
    (3): MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False)
    (4): Sequential(
      (0): BasicBlock(
        (conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (relu): ReLU(inplace)
        (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      )
...
...
...
  (1): Sequential(
    (0): AdaptiveConcatPool2d(
      (ap): AdaptiveAvgPool2d(output_size=1)
      (mp): AdaptiveMaxPool2d(output_size=1)
    )
    (1): Flatten()
    (2): BatchNorm1d(1024, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (3): Dropout(p=0.25)
    (4): Linear(in_features=1024, out_features=512, bias=True)
    (5): ReLU(inplace)
    (6): BatchNorm1d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (7): Dropout(p=0.5)
    (8): Linear(in_features=512, out_features=7, bias=True)
  )
)

Перенесем-выучим 15 эпох:

learn.fit_one_cycle(15, callbacks=[SaveModelCallback(learn, every='epoch', monitor='accuracy', name='derma-1')])
learn.save('derma-stage-1')
exportStageTo(learn, path)

interp = ClassificationInterpretation.from_learner(learn)
losses,idxs = interp.top_losses()
interp.plot_confusion_matrix(figsize=(5,5), dpi=100)

learn.recorder.plot_losses()

Численно это на 0,5% лучше, чем у ResNet34. Посмотрим, как он себя ведет после тонкой настройки:

# Unfreeze the network
learn.unfreeze()
# Find optimum learning rates
learn.lr_find()
# Include suggestion=True in order to obtain a suggestion on where to look...
learn.recorder.plot(suggestion=True)

Опять же, у нас есть кривая, которая сначала выходит на плато, а затем переходит в две дыры. Только здесь ямы глубже плато. Предположим, что в этом случае дыры значительны, и примем предложение средства поиска скорости обучения и сделаем max_lr = slice (1e-5,1e-6):

LR Finder is complete, type {learner_name}.recorder.plot() to see the graph.
Min numerical gradient: 1.32E-06
Min loss divided by 10: 1.58E-07

# Unfreeze the network
learn.unfreeze()
learn.fit_one_cycle(35, max_lr=slice(1e-5,1e-6), 
                    callbacks=[SaveModelCallback(learn, every='epoch', monitor='accuracy', name='derma18')])
learn.save('derma18-stage-2')
exportStageTo(learn, path)

Точность 88%! Это намного хуже, чем у ResNet34. Построим кривые обучения:

learn.recorder.plot_losses()

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

Я повторил эксперимент № 2 по тонкой настройке, который мы проводили ранее с ResNet50, установил диапазон скорости обучения на max_lr = slice (1e-4,1e-5) и снова обучил его. В этом случае ResNet18 достиг точности проверки 0,904643. Графики результатов выглядят так:

Это лучше, чем раньше, но все же хуже, чем у ResNet34 и ResNet50. ResNet18 не кажется хорошим выбором для этой проблемы. Код у нас в записной книжке.

Что мне делать, если тренировка была прервана на полпути?

Что делать, если тренировка прервалась? Это может произойти из-за того, что вы достигли 12 часов непрерывной «бесплатной» работы на ноутбуке Google Colab или из-за того, что ваш компьютер по какой-то причине остановился. Я живу в Бразилии, и перебои с электричеством - обычное дело ...

Метод fit_one_cycle работает с различными адаптивными скоростями обучения, следуя кривой, в которой скорость сначала увеличивается, а затем уменьшается. Если вы прервете тренировку в эпоху № 10, скажем, из 20 эпох, а затем начнете снова для более 9 эпох, у вас не будет такого же результата, как при непрерывной тренировке в течение 20 эпох. Вы должны иметь возможность записывать, где вы остановились, а затем возобновлять тренировочный цикл с этой точки и с правильными гиперпараметрами для этой части цикла.

Первое, что вам нужно сделать, это каждую эпоху сохранять вашу сеть:

learn.fit_one_cycle(20, max_lr=slice(1e-5,1e-6), 
       callbacks=[SaveModelCallback(learn, every='epoch',  
                  monitor='accuracy', name='saved_net')])

Таким образом, ваша сеть будет сохраняться каждую эпоху с указанным вами именем, за которым следует _ # epoch. Итак, в эпоху № 3 будет записан файл saved_net_3.pth. Этот файл вы можете загрузить после того, как:

  • повторно создал DataBunch и
  • повторно создал сеть с его помощью.

После перезагрузки файла .pth вы можете перезапустить тренировку, только вы скажете fit_one_cycle учитывать 20 эпох, но начинать тренировку с эпохи №4.

Чтобы узнать, как это делается, посмотрите здесь:

Как вы это делаете?

Метод fit_one_cycle в fast.ai был разработан, чтобы вы могли определить, с какой части цикла следует возобновить прерванное обучение. Код для возобновления обучения будет выглядеть так:

# Create a new net if training was interrupted and you had to 
# restart your Colab session
learn = cnn_learner(data, models.<your_model_here>, 
                    metrics=[accuracy, error_rate])
# If you're resuming, only indicating the epoch from which to 
# resume, indicated by start_epoch=<epoch#> will load the last 
# saved .pth, it is not necessary to explicitly reload the last 
# epoch, you only should NOT change the name given in 
# name=<callback_save_file>:
# when resuming fast.ai will try to reload 
# <callback_save_file>_<previous_epoch>.pth
# Unfreeze the network
learn50.unfreeze()
# Use start_epoch=<some_epoch> to resume training...
learn.fit_one_cycle(20, max_lr=slice(1e-5,1e-6), 
                    start_epoch=<next_epoch#>,
                    callbacks=[SaveModelCallback(learn, 
                    every='epoch', monitor='accuracy', 
                    name=<callback_save_file>)])

fast.ai сообщит вам «Загружено ‹callback_save_file› _‹ previous_epoch # ›» и продолжит обучение.

Здесь вы можете посмотреть все параметры, поддерживаемые методом fit_one_cycle:

Чего мы добились?

Используя разрешение изображения 448x448 и fast.ai, мы получили точность проверки примерно 93% с двумя из трех сетевых моделей, которые мы использовали в нашем эксперименте, ResNet34 и ResNet50. Это намного лучше, чем 85% приведенного выше руководства, в котором использовались MobileNet, разрешение изображения 224x224 и Keras. (В настоящее время) ядро, получившее наибольшее количество голосов на Kaggle (использует TensorFlow.js), получило точность 86%.

В итоговом тесте ISIC 2018 по задаче № 3 участник с наивысшим рейтингом Джордан Яп из MetaOptima Technology Inc. достиг точности 95,8% и сбалансированной мультиклассовая точность, критерий оценки конкурентов, выбранный ISIC, составляет 88,5%. Джордан Яп использовал метод, основанный на:

  • дополнительные, внешние данные [33 644 изображения];
  • изображения с низким разрешением, эквивалентным исходному ImageNet-разрешению;
  • ансамбль из 19 алгоритмов классификации, один из которых не был нейронной сетью (анализ гистограмм);
  • классификатор XGBoost, который был обучен на основе результатов этого ансамбля.

Документ Алексея Ноздрин-Плотницки, Джордана Япа и Уильяма Йолланда, представленный на ISIC Skin Image Analysis Workshop and Challenge @ MICCAI 2018 вместе с их результатами, находится здесь.

Наши результаты нельзя напрямую сопоставить с исходной задачей ISIC 2018, потому что ISIC предоставил тестовый набор из 1512 изображений, вручную извлеченных из набора данных HAM1000, и всем конкурентам пришлось использовать этот тестовый набор и представить свои результаты в ISIC. Мы проверили наше обучение с набором 2003 изображений, случайно извлеченных из набора данных HAM1000, и обучили наши сети с оставшимися 8012 изображениями.

Вы можете посмотреть результаты ISIC 2018 здесь:

Однако интересно отметить, что точность 92,9%, которую мы достигли с помощью fast.ai и только одной сети, ResNet34, и общего времени обучения 190 минут на графическом процессоре NVIDIA T4 (4,2 x 10 + 4,3 x 35 минут) всего на 2,9% хуже, чем точность, полученная конкурентом с самым высоким рейтингом в испытании ISIC 2018, полученная при значительно более сложном подходе с использованием алгоритм машинного обучения поверх 18 различных нейронных сетей.

Скорее всего, это связано с тем, что мы использовали вдвое большее разрешение изображения, что позволяет получить гораздо больше деталей, но HYPO (оптимизация гиперпараметров) в fast.ai, вероятно, также сыграли свою роль.

Что мы узнали?

ResNet34 - хороший выбор для начала: ResNet действительно хорошо управляется в fast.ai с различными простыми в использовании HYPO (гиперпараметрическая оптимизация). Если вы не знаете, какую сеть использовать, возьмите ResNet34, который будет достаточно маленьким, чтобы тренироваться относительно быстро, даже если вы делаете это с помощью своего графического процессора дома, и достаточно большим, чтобы представлять большой набор проблем. Как поезда ResNet34 подскажут, следует ли вам увеличивать или уменьшать размер вашей сети.

Слепое принятие предложения о скорости обучения - не всегда лучший вариант: lr_find () для ResNet50 дает длительное плато, а метод предлагает очень низкое значение скорости обучения в небольшом долина в левом конце рисунка. Когда мы обучили сеть с этим значением, она колебалась и не дала хорошего результата (только точность 90%). Когда мы использовали визуальный анализ графика и взяли в качестве нижней границы диапазона скорости обучения значение на порядок выше, которое лежало в начале более плоской части плато, ResNet50 учился намного лучше и достиг того же уровня. 93% точность ResNet34. Итак, используйте режим advice = True, но серьезно обработайте его, прежде чем принять его, фактически глядя на изображение. Это второе практическое правило диапазона скорости обучения: посмотрите на весь график и найдите реальное плато - ваша идеальная нижняя граница диапазона скорости обучения будет лежать в начале этого плато.

Метод learn.lr_find () поможет вам найти оптимальную скорость обучения. В нем используется методика, разработанная в статье Cyclical Learning Rates for Training Neural Networks (http://arxiv.org/abs/1506.01186), опубликованной в 2015 году, где мы просто продолжаем увеличивать скорость обучения с очень низкого уровня. маленькое значение, пока убыток не начнет уменьшаться. Если вы хотите узнать больше о том, как найти лучшую скорость обучения, посмотрите здесь:

Больше не всегда лучше: в конце концов, ResNet50 работал почти так же, как ResNet34, но на обучение потребовалось гораздо больше времени, и результаты были немного хуже. Плохой выбор начинать тренировочное исследование космоса с большой сетевой модели.

Разрешение изображения играет роль: использовав разрешение, вдвое превышающее разрешение, которое использовал участник с самым высоким рейтингом ISIC 2018 Challenge, мы получили сопоставимые результаты с единственным относительно простым ResNet34, в то время как этот конкурент применил метод машинного обучения для топ 18 различных сетей, включая огромную ResNet152.

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

Благодарности

Эта работа явилась результатом совместных усилий команды увлеченных исследователей, помимо меня:

Мы также хотим поблагодарить Юргена Кройша ‹ [email protected] › и Луиса Фернандо Копке ‹ [email protected] › за стереодермоскопический материал.

использованная литература

[1] Кройш, Дж. Микроскопия падающего света: отражение в микроскопии живой кожи. Int J Dermatol. 1992 Сен; 31 (9): 618–20.

[2] Копке, Л.Ф. Дерматоскопия в раннем выявлении, контроле и хирургическом планировании базальноклеточного рака. Хирургическая косметика Dermatol 2011; 3 (2): 103–8.