Введение

Классификация изображений известна как азбука глубокого обучения, до такой степени, что классификация цифр MNIST превратилась в практическое упражнение Hello World. Это наиболее широко известный и широко обсуждаемый аспект глубокого обучения, предлагающий лучшие варианты обслуживания, поскольку на Kaggle представлены сотни наборов данных и сотни руководств по широкому спектру приложений. Ежегодный турнир ImageNet L arge Scale Visual Recognition Challenge известен тем, что стал ареной, на которой уже ставшие отраслевыми стандартами VGGNet, ResNet, Inception прославились как современные.

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

Недавно я принял участие в хакатоне AI for SEA, организованном Grab, целью которого было создание эффективного, но в то же время быстродействующего в вычислительном отношении классификатора для набора данных автомобилей Stanford-196 на тему автономного вождения. Хотя различение различных классов транспортных средств может быть несложным, становится значительно труднее различать внутриклассовые предметы различных моделей или марок. В этом руководстве мы попытаемся воспроизвести нашу работу по улучшению базовой производительности быстрого классификатора MobileNetV2 по различению марки и модели различных автомобилей из набора данных Stanford Cars-196.

Реализация

Мы уже рассматривали классификацию изображений в нашем примере классификации малярии, и мы будем повторно использовать большую часть кода; читателю рекомендуется вернуться к нему для ознакомления с кодом. Для нашей модели использовалась предварительно отсортированная версия набора данных Stanford Cars-196, доступная на Kaggle. Весь наш код был разделен между двумя блокнотами, работающими в среде Google Colaboratory t. Давайте определимся с архитектурой нашей модели.

Наша модель состоит из базовой модели MobileNetV2, в которой верхние уровни связаны с двумя плотно связанными слоями (размером 1024 и 196 соответственно), разделенными слоем исключения 50% для предотвращения переобучения. В сеть были предварительно загружены веса ImageNet, и обучение проводилось с использованием оптимизатора ADAM со скоростью обучения 0,0002.

Сначала мы обучили архитектуру MobileNetV2, обучили с помощью весов ImageNet, на наборе данных за 50 эпох в качестве базовой линии и обнаружили низкую точность проверки ниже 40%. Было высказано предположение, что такая низкая точность может быть вызвана целым рядом факторов, в том числе:

  • Неоптимизированный вес: высокая вариативность предметов по сравнению с классом транспортных средств ImageNet, а также небольшой относительный размер класса по сравнению с другими классами в целом (например, ImageNet содержит 374 тыс. изображений транспортных средств по сравнению с 2799 тыс. изображений животных).
  • Ограниченный набор данных: размер набора данных Stanford-196 был небольшим, составляя примерно сотню изображений на набор данных.
  • Сходство предмета: общий профиль различных транспортных средств одного класса был похож, что затрудняло различение. Это сложно даже для людей, и, следовательно, совсем не удивительно.
  • Фоновый шум: на всех изображениях присутствует большой фон. Некоторые из этих фонов содержат вспомогательные транспортные средства, что может привести к тому, что наш классификатор усвоит вводящие в заблуждение функции.

Определив наши проблемы, давайте обратимся к каждому из этих пунктов.

Удаление фона

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

Было решено, что детектор посевов на основе YOLO будет более масштабируемым в реальных условиях сбора данных. YOLO сам по себе является обученным классификатором нейронной сети на основе InceptionV3 и предназначен для обнаружения основного объекта изображения, создания рамки для объекта и последующего вырезания фона. Такой детектор был реализован в Notebook 1, а выходные данные собирались и сохранялись на Google Диске в качестве входных данных для Notebook 2. Точный метод, вызываемый во время основного потока, - это crop_path ():

detector.crop_path(
 ‘../content/car_data/test/’, 
 ‘../content/cropped_car_test/’,
 dirs,
 params={
 ‘detect_largest_box’: True,
 ‘smallest_detected_area’: 0.5,
 }
)

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

class Detector:
 def __init__(self, model):
   execution_path = os.getcwd()
   detector = ObjectDetection()
   if model == ‘yolo’:
     detector.setModelTypeAsYOLOv3()
   elif model == ‘yolo-tiny’:
     detector.setModelTypeAsTinyYOLOv3()
   else:
     raise ValueError(‘Model ‘ + model + ‘not fould. you should download the model and put it into “modules” directory.’)
   detector.setModelPath(os.path.join(execution_path , ‘../content/’ + model + ‘.h5’))
   detector.loadModel()
   custom_objects = detector.CustomObjects(car=True)
   self.detector = detector
   self.custom_objects = custom_objects
   self.execution_path = execution_path
 def get_box(self, detections, size, params = {‘detect_largest_box’: False, ‘smallest_detected_area’: None}):
   detect_largest_box = params[‘detect_largest_box’]
   smallest_detected_area = params[‘smallest_detected_area’]
 
   area = 0
   points = None
   if detect_largest_box == True:
     for detection in detections:
        current_area = (detection[‘box_points’][2] —    detection[‘box_points’][0]) * (detection[‘box_points’][3] —   detection[‘box_points’][1])
          if current_area > area:
             area = current_area
             points = detection[‘box_points’]
          else:
             points = detections[0][‘box_points’]
         img_area = size[0] * size[1] if smallest_detected_area !=   None else None
 
         if (img_area != None) and (area / img_area <  smallest_detected_area):
            return None
 else:
 return points
 def crop_image(self, source_image, input_type, **kwargs):
   image = None
   input_image = None
   if input_type == ‘file’:
     image = load_img(source_image)
     input_image = source_image
   elif input_type == ‘array’:
     image = source_image
     input_image = img_to_array(source_image)
   detections = self.detector.detectCustomObjectsFromImage(
      custom_objects=self.custom_objects,
      input_type=input_type,
      input_image=input_image, 
      minimum_percentage_probability=10,
      extract_detected_objects=False,
    )
  box = self.get_box(detections, image.size, **kwargs)
  cropped_img = image.crop(box)
  return cropped_img

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

После инициализации метод crop_path () в конечном итоге вызывает метод crop_image (), где мы загружаем каждое изображение каталога и вызываем метод обнаружения детектора YOLO, чтобы сгенерировать набор граничных полей, обнаруженных классом автомобиля, через detectCustomObjectsFromImage (), которые затем передаются методу get_box ().

Наконец, метод get_box () выполняет итерацию по каждому обнаруженному граничному блоку и выбирает максимально возможную запись. Это основано на предположении, что основной объект автомобиля на изображении должен иметь максимально возможный ограничивающий прямоугольник. Давайте посмотрим на небольшой пример:

Сегментация и извлечение признаков

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

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

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

for dir in dirnames:
 files = os.listdir(dir)
 #Each subfolder has a differring number of files in it, so we iterate with an int tracker
 i=0
 for f in files:
 
   filename = os.path.join(dir,f)
   print(“Generating quarter crops for file “+str(filename))
   
   im = cv2.imread(filename)
   h,w = im.shape[:2]
   midh = h/2
   midw = w/2
   crop1 = im[0:int(h),0:int(w/2)]
   crop2 = im[0:int(h),int(w/2):int(w)]
   crop3 = im[0:int(h/2),0:int(w)]
   crop4 = im[int(h/2):int(h),0:int(w)]

 
   filecrop1 = str(i)+’crop1.jpg’
   filecrop2=str(i)+’crop2.jpg’
   filecrop3 = str(i)+’crop3.jpg’
   filecrop4 = str(i)+’crop4.jpg’
 
 
   #Create the new filenames 
   filesave1=os.path.join(dir,filecrop1)
   filesave2=os.path.join(dir,filecrop2)
   filesave3=os.path.join(dir,filecrop3)
   filesave4=os.path.join(dir,filecrop4)
 
 
   #Save images
   save_img(filesave1, crop1)
   save_img(filesave2, crop2)
   save_img(filesave3, crop3)
   save_img(filesave4, crop4)
 
 
   i=i+1

Использование пакета Python CV2 упрощает работу с изображениями. Определив среднюю точку по высоте и ширине, мы можем легко обрезать каждое изображение соответствующим образом и сохранить их вместе с исходным изображением, увеличив размер набора данных на 400%.

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

Своевременная предварительная обработка

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

  • Изменение масштаба
  • Преобразования на основе сдвига
  • Преобразования на основе масштабирования
  • Переводы
  • Листать

В то время как методы на основе Keras фокусируются на преобразованиях на основе переводов, библиотека Tensorflow предоставляет более продвинутые возможности для изменения цветового пространства:

  • Рандомизация оттенка
  • Рандомизация насыщенности
  • Рандомизация яркости
  • Рандомизация контраста

Это было сделано для того, чтобы классификатор был представлен в различных цветах для каждого класса транспортных средств. Точно так же в нашем наборе данных следует учитывать различные условия освещения, чтобы имитировать изображения, сделанные в условиях высокой контрастности или низкой видимости. Обратите внимание, что вызов tf.enable_eager_execution () должен вызываться в начале Notebook, чтобы избежать утечек памяти, иначе вы быстро обнаружите, что объем оперативной памяти вашего сеанса достигает 12 ГБ в течение 2 эпох.

После определения наших функций предварительной обработки, обучение весов базовой модели было разрешено за пределами 75 уровней базовой сети со скоростью обучения 0,0002 с оптимизатором ADAM, с тонкой настройкой, разрешенной на 30 уровнях и далее с использованием оптимизатора RMSProp при обучении. Скорость 2E-5. Модель обучалась на 50 эпох, с возможностью точной настройки на 10 эпох.

На рисунке ниже показаны улучшения в точности и потери за первые 50 эпох.

Как можно заметить, теперь мы можем достичь точности проверки, близкой к 90%, то есть улучшения, близкого к 40%, без каких-либо значительных изменений в нашей сетевой архитектуре. Такой классификатор затем можно легко перенести на мобильное устройство через Tensorflow Lite для использования в классификации дорожных транспортных средств.

У нашего классификатора хорошие показатели, но прыгать от радости еще рано. Некоторые из проблем включают:

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

Надеемся, вам понравилась эта статья. Пожалуйста, посетите GradientCrescent для получения дополнительной информации - скоро мы обсудим стратегии противодействия дипфейкам.