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

Этот пост был вдохновлен Udacity Lab Challenge для их программы стипендий PyTorch Nanodegree. Почти 10 000 честолюбивых студентов получают задание создать с нуля лучший классификатор изображений, который будет идентифицировать виды цветов. Лучшие студенты получат стипендию в своей Программе глубокого обучения наностепеней.

К счастью, я был одним из первых студентов их недавно созданной Программы наностепеней по науке о данных, и это был тот же проект в одном из их разделов. Он использует PyTorch для реализации классификатора изображений. Существует бесплатный курс Введение в глубокое обучение с PyTorch, который является точно тем же курсом для стипендиальной программы PyTorch Nanodegree, но в нем отсутствует Lab Challenge.

Поскольку это был один и тот же проект, я уже завершил его и отправил в Udacity для записи. Я не получил никакой обратной связи о моем счете. Все, что я получил, это то, что мой классификатор изображений прошел, и я уже завершил программу. Однако на момент написания этого я еще не закончил оставшиеся курсы. Моя цель в этом посте — помочь моим однокурсникам завершить проект, если они найдут здесь что-то полезное.

Загрузка данных

torchvision использовался для загрузки данных, которые были разделены на три части: обучение, проверка и тест. Для каждого набора были сделаны следующие преобразования:

Необходимо обеспечить изменение размера входных данных до 224x224 пикселей в соответствии с требованиями предварительно обученных сетей. Для всех трех наборов средние значения и стандартные отклонения изображений должны быть нормализованы в соответствии с ожиданиями сети. Для средних значений они равны [0.485, 0.456, 0.406], а для стандартных отклонений — [0.229, 0.224, 0.225], и все они рассчитываются на основе изображений ImageNet.

Рекомендуется сначала выполнить Resize(), а затем RandomCrop() для обучающих наборов, а Resize() затем выполнить CenterCrop() для проверочных и тестовых наборов. Это помогает обрезать основную область до квадрата 224x224, чтобы сохранить соотношение сторон. Если бы мы не выполнили изменение размера и не обрезали центр, мы могли бы получить неправильную обрезку. Например, скажем, у нас есть изображение размером 1024x1024, если бы мы обрезали квадрат 224x224 в центре, мы бы вырезали действительно увеличенную часть цветка, поэтому нормализованное изменение размера важно.

Построение и обучение классификатора

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

Для создания нового классификатора была определена новая необученная сеть с прямой связью, использующая активацию и выпадение ReLU. Архитектура сети состоит из 2 скрытых слоев и 102 выходных объектов. Классификатор был создан с использованием модуля OrderedDict(), показанного ниже:

classifier = nn.Sequential(OrderedDict([
                          ('fc1', nn.Linear(input_size, hidden_sizes[0])),
                          ('relu1', nn.ReLU()),
                          ('dropout1', nn.Dropout(p=0.5)),
                          ('fc2', nn.Linear(hidden_sizes[0], hidden_sizes[1])),
                          ('relu2', nn.ReLU()),
                          ('dropout2', nn.Dropout(p=0.5)),
                          ('fc3', nn.Linear(hidden_sizes[1], output_size)),
                          ('output', nn.LogSoftmax(dim=1))]))

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

  • критерий — NLLLoss()
  • скорость обучения — 0,001
  • оптимизатор — optim.Adam()

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

Для этого проекта Udacity предоставил 50 часов поддержки графического процессора, поэтому не нужно было вручную настраивать удаленный сервер с поддержкой графического процессора. К сожалению, для студентов, участвующих в программе PyTorch Scholarship Nanodegree, такая поддержка недоступна, поэтому они также должны:

  1. Тренируйтесь, используя их процессор (не рекомендуется)
  2. Настройте свое устройство для поддержки графического процессора (если поддерживается)
  3. Настройте удаленный сервер с поддержкой GPU

Мне еще предстоит узнать, как настроить удаленный сервер, и я, вероятно, обновлю этот пост, как только найду возможные решения. Мои сокурсники опубликовали несколько решений на форуме Slack программы Nanodegree.

После создания функции прохождения проверки теперь можно определить отслеживание потерь и точности набора проверки. Обучение длилось 10 эпох, а результаты печатались каждые 40 шагов. Результаты прохождения обучения и валидации показаны ниже.

Epoch: 1/10...  Training Loss: 4.2235.. Validation Loss: 2.456.. Validation Accuracy: 0.411
Epoch: 1/10...  Training Loss: 2.3041.. Validation Loss: 1.399.. Validation Accuracy: 0.636
Epoch: 2/10...  Training Loss: 0.6656.. Validation Loss: 0.975.. Validation Accuracy: 0.734
Epoch: 2/10...  Training Loss: 1.4481.. Validation Loss: 0.811.. Validation Accuracy: 0.783
Epoch: 2/10...  Training Loss: 1.4606.. Validation Loss: 0.781.. Validation Accuracy: 0.788
Epoch: 3/10...  Training Loss: 1.0886.. Validation Loss: 0.680.. Validation Accuracy: 0.828
Epoch: 3/10...  Training Loss: 1.2539.. Validation Loss: 0.649.. Validation Accuracy: 0.840
Epoch: 4/10...  Training Loss: 0.3003.. Validation Loss: 0.662.. Validation Accuracy: 0.825
Epoch: 4/10...  Training Loss: 1.1409.. Validation Loss: 0.612.. Validation Accuracy: 0.839
Epoch: 4/10...  Training Loss: 1.1565.. Validation Loss: 0.605.. Validation Accuracy: 0.837
Epoch: 5/10...  Training Loss: 0.7102.. Validation Loss: 0.544.. Validation Accuracy: 0.857
Epoch: 5/10...  Training Loss: 1.1316.. Validation Loss: 0.580.. Validation Accuracy: 0.845
Epoch: 6/10...  Training Loss: 0.1032.. Validation Loss: 0.559.. Validation Accuracy: 0.869
Epoch: 6/10...  Training Loss: 0.9763.. Validation Loss: 0.534.. Validation Accuracy: 0.851
Epoch: 6/10...  Training Loss: 1.0441.. Validation Loss: 0.588.. Validation Accuracy: 0.856
Epoch: 7/10...  Training Loss: 0.5328.. Validation Loss: 0.485.. Validation Accuracy: 0.877
Epoch: 7/10...  Training Loss: 1.0184.. Validation Loss: 0.523.. Validation Accuracy: 0.867
Epoch: 7/10...  Training Loss: 1.0777.. Validation Loss: 0.553.. Validation Accuracy: 0.855
Epoch: 8/10...  Training Loss: 1.0494.. Validation Loss: 0.505.. Validation Accuracy: 0.873
Epoch: 8/10...  Training Loss: 0.9969.. Validation Loss: 0.511.. Validation Accuracy: 0.862
Epoch: 9/10...  Training Loss: 0.3838.. Validation Loss: 0.508.. Validation Accuracy: 0.887
Epoch: 9/10...  Training Loss: 0.8927.. Validation Loss: 0.463.. Validation Accuracy: 0.899
Epoch: 9/10...  Training Loss: 1.0563.. Validation Loss: 0.514.. Validation Accuracy: 0.883
Epoch: 10/10...  Training Loss: 0.8287.. Validation Loss: 0.480.. Validation Accuracy: 0.885
Epoch: 10/10...  Training Loss: 1.0279.. Validation Loss: 0.509.. Validation Accuracy: 0.878

Запуск этой обученной сети на тестовых данных дал точность 87%.

Сохранение и загрузка контрольной точки

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

Рекомендуется разделять структуру параметров модели и веса. Причина для этого многообразна. Давайте начнем с ситуации, когда вы обучили модель, но теперь хотите использовать обученные веса, но использовать другой оптимизатор, но с вашим текущим подходом происходит то, что вы также сохраняете свой оптимизатор вместе с вашей моделью, что выглядит довольно хорошо в в этом случае, но некоторые другие оптимизаторы, такие как оптимизаторы Адама, работают немного по-другому, они создают оболочку вокруг всех узлов в вычислительном графе для обратного распространения, поэтому, если вы хотите начать обучение модели с другими гиперпараметрами вашего Адама он не будет использовать существующий Adam , но добавит дополнительную оболочку Adam Optimizer, которая увеличит размер вашей модели. Это одна из запущенных причин. Как правило, хорошей практикой является сохранение веса отдельно, потому что размер вашей контрольной точки будет намного меньше.

Моя предыдущая контрольная точка была изменена, и окончательный словарь показан ниже. Файл контрольной точки имел размер 1,4 ГБ.

checkpoint = {'epoch': epochs + 1,
              'learning_rate': learning_rate,
              'arch': 'vgg16',
              'class_to_idx': model.class_to_idx,
              'classifier': model.classifier,
              'state_dict': model.state_dict(),
              'optimizer': optimizer.state_dict()}

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

Вывод для классификации

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

Во-первых, изображение было загружено с помощью PIL.. Как было предложено, функция process_image была написана для предварительной обработки изображения, чтобы его можно было использовать в качестве входных данных для модели. Функция возвращает транспонированный массив, поскольку PyTorch ожидает, что цветовой канал будет первым измерением, но это третье измерение в изображении PIL и массиве Numpy. Сводка шагов показана на блок-схеме ниже.

Это работает с функцией, которая преобразует тензор PyTorch и возвращает исходное изображение (за исключением обрезанных частей). После того, как изображения были получены в правильном формате, пришло время написать функцию для прогнозирования модели. Первым шагом является вычисление вероятностей классов, а затем поиск K самых больших значений с использованием x.topk(k).

Последняя функция для прогнозирования класса или классов изображения с использованием обученной модели глубокого обучения показана ниже. Важно использовать методы unsqueeze() и squeeze(), чтобы в PyTorch передавались правильные размеры.

def predict(image_path, model, topk=5):
    image = Image.open(image_path)
    image = process_image(image)
    
    model.eval()
    with torch.no_grad():
        tensor = torch.from_numpy(image).float().to(device).unsqueeze_(0)
        outputs = model(tensor)
        ps = torch.exp(outputs)

    probs = ps.topk(topk)[0].cpu().numpy().squeeze()
    inv_classes = ps.topk(topk)[1].cpu().numpy().squeeze()
    
    mapping = {v: k for k, v in model.class_to_idx.items()}
    classes = []
    for i in inv_classes:
        classes.append(mapping[i])
    
    return probs, classes

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

Как улучшить модель

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

  1. Используйте ResNet-152 или DenseNet-201 для повышения производительности.
  2. Тренируйте больше эпох, если позволяют ресурсы
  3. Попробуйте другие сетевые архитектуры (например, добавьте больше скрытых слоев)

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