[Python, Алгоритмы, Обработка изображений, Машинное обучение] Распознаем номера автомобилей. Разработка multihead-модели в Catalyst

Автор Сообщение
news_bot ®

Стаж: 6 лет 9 месяцев
Сообщений: 27286

Создавать темы news_bot ® написал(а)
11-Июн-2021 10:31

Фиксация различных нарушений, контроль доступа, розыск и отслеживание автомобилей – лишь часть задач, для которых требуется по фотографии определить номер автомобиля (государственный регистрационный знак или ГРЗ). В этой статье мы рассмотрим создание модели для распознавания с помощью Catalyst – одного из самых популярных высокоуровневых фреймворков для Pytorch. Он позволяет избавиться от большого количества повторяющегося из проекта в проект кода – цикла обучения, расчёта метрик, создания чек-поинтов моделей и другого – и сосредоточиться непосредственно на эксперименте.
Сделать модель для распознавания можно с помощью разных подходов, например, путем поиска и определения отдельных символов, или в виде задачи image-to-text. Мы рассмотрим модель с несколькими выходами (multihead-модель). В качестве датасета возьмём датасет с российскими номерами от проекта Nomeroff Net. Примеры изображений из датасета представлены на рис. 1.
Рис. 1. Примеры изображений из датасета Общий подход к решению задачиНеобходимо разработать модель, которая на входе будет принимать изображение ГРЗ, а на выходе отдавать строку распознанных символов. Модель будет состоять из экстрактора фичей и нескольких классификационных “голов”. В датасете представлены ГРЗ из 8 и 9 символов, поэтому голов будет девять. Каждая голова будет предсказывать один символ из алфавита “1234567890ABEKMHOPCTYX”, плюс специальный символ “-” (дефис) для обозначения отсутствия девятого символа в восьмизначных ГРЗ. Архитектура схематично представлена на рис. 2.
Рис. 2. Архитектура модели В качестве loss-функции возьмём стандартную кросс-энтропию. Будем применять её к каждой голове в отдельности, а затем просуммируем полученные значения для получения общего лосса модели. Оптимизатор – Adam. Используем также OneCycleLRWithWarmup как планировщик leraning rate. Размер батча – 128. Длительность обучения установим в 10 эпох. В качестве предобработки входных изображений будем выполнять нормализацию и преобразование к единому размеру.КодированиеДалее рассмотрим основные моменты кода. Класс датасета (листинг 1) в общем обычный для CV-задач на Pytorch. Обратить внимание стоит лишь на то, как мы возвращаем список кодов символов в качестве таргета. В параметре label_encoder передаётся служебный класс, который умеет преобразовывать символы алфавита в их коды и обратно.
class NpOcrDataset(Dataset):
   def __init__(self, data_path, transform, label_encoder):
       super().__init__()
       self.data_path = data_path
       self.image_fnames = glob.glob(os.path.join(data_path, "img", "*.png"))
       self.transform = transform
       self.label_encoder = label_encoder
   def __len__(self):
       return len(self.image_fnames)
   def __getitem__(self, idx):
       img_fname = self.image_fnames[idx]
       img = cv2.imread(img_fname)
       if self.transform:
           transformed = self.transform(image=img)
           img = transformed["image"]
       img = img.transpose(2, 0, 1)
       label_fname = os.path.join(self.data_path, "ann",
                                  os.path.basename(img_fname).replace(".png", ".json"))
       with open(label_fname, "rt") as label_file:
           label_struct = json.load(label_file)
           label = label_struct["description"]
       label = self.label_encoder.encode(label)
       return img, [c for c in label]
Листинг 1. Класс датасета В классе модели (листинг 2) мы используем библиотеку PyTorch Image Models для создания экстрактора фичей. Каждую из классификационных голов модели мы добавляем в ModuleList, чтобы их параметры были доступны оптимизатору. Логиты с выхода каждой из голов возвращаются списком.
class MultiheadClassifier(nn.Module):
   def __init__(self, backbone_name, backbone_pretrained, input_size, num_heads, num_classes):
       super().__init__()
       self.backbone = timm.create_model(backbone_name, backbone_pretrained, num_classes=0)
       backbone_out_features_num = self.backbone(torch.randn(1, 3, input_size[1], input_size[0])).size(1)
       self.heads = nn.ModuleList([
           nn.Linear(backbone_out_features_num, num_classes) for _ in range(num_heads)
       ])
   def forward(self, x):
       features = self.backbone(x)
       logits = [head(features) for head in self.heads]
       return logits
Листинг 2. Класс модели Центральным звеном, связывающим все компоненты и обеспечивающим обучение модели, является Runner. Он представляет абстракцию над циклом обучения-валидации модели и отдельными его компонентами. В случае обучения multihead-модели нас будет интересовать реализация метода handle_batch и набор колбэков.Метод handle_batch, как следует из названия, отвечает за обработку батча данных. Мы в нём будем только вызывать модель с данными батча, а обработку полученных результатов – расчёт лосса, метрик и т.д. – мы реализуем с помощью колбэков. Код метода представлен в листинге 3.
class MultiheadClassificationRunner(dl.Runner):
   def __init__(self, num_heads, *args, **kwargs):
       super().__init__(*args, **kwargs)
       self.num_heads = num_heads
   def handle_batch(self, batch):
       x, targets = batch
       logits = self.model(x)
       batch_dict = { "features": x }
       for i in range(self.num_heads):
           batch_dict[f"targets{i}"] = targets[i]
       for i in range(self.num_heads):
           batch_dict[f"logits{i}"] = logits[i]
       self.batch = batch_dict
Листинг 3. Реализация runner’а Колбэки мы будем использовать следующие:
  • CriterionCallback – для расчёта лосса. Нам потребуется по отдельному экземпляру для каждой из голов модели.
  • MetricAggregationCallback – для агрегации лоссов отдельных голов в единый лосс модели.
  • OptimizerCallback – чтобы запускать оптимизатор и обновлять веса модели.
  • SchedulerCallback – для запуска LR Scheduler’а.
  • AccuracyCallback – чтобы иметь представление о точности классификации каждой из голов в ходе обучения модели.
  • CheckpointCallback – чтобы сохранять лучшие веса модели.
Код, формирующий список колбэков, представлен в листинге 4.
def get_runner_callbacks(num_heads, num_classes_per_head, class_names, logdir):
   cbs = [
       *[
           dl.CriterionCallback(
               metric_key=f"loss{i}",
               input_key=f"logits{i}",
               target_key=f"targets{i}"
           )
           for i in range(num_heads)
       ],
       dl.MetricAggregationCallback(
           metric_key="loss",
           metrics=[f"loss{i}" for i in range(num_heads)],
           mode="mean"
       ),
       dl.OptimizerCallback(metric_key="loss"),
       dl.SchedulerCallback(),
       *[
           dl.AccuracyCallback(
               input_key=f"logits{i}",
               target_key=f"targets{i}",
               num_classes=num_classes_per_head,
               suffix=f"{i}"
           )
           for i in range(num_heads)
       ],
       dl.CheckpointCallback(
           logdir=os.path.join(logdir, "checkpoints"),
           loader_key="valid",
           metric_key="loss",
           minimize=True,
           save_n_best=1
       )
   ]
   return cbs
Листинг 4. Код получения колбэковОстальные части кода являются тривиальными для Pytorch и Catalyst, поэтому мы не станем приводить их здесь. Полный код к статье доступен на GitHub.Результаты эксперимента
Рис. 3. График лосс-функции модели в процессе обучения. Оранжевая линия – train loss, синяя – valid loss В списке ниже перечислены некоторые ошибки, которые модель допустила на тест-сете:
  • Incorrect prediction: T970XT23- instead of T970XO123
  • Incorrect prediction: X399KT161 instead of X359KT163
  • Incorrect prediction: E166EP133 instead of E166EP123
  • Incorrect prediction: X225YY96- instead of X222BY96-
  • Incorrect prediction: X125KX11- instead of X125KX14-
  • Incorrect prediction: X365PC17- instead of X365PC178
Здесь присутствуют все возможные типы: некорректно распознанные буквы и цифры основной части ГРЗ, некорректно распознанные цифры кода региона, лишняя цифра в коде региона, а также неверно предсказанное отсутствие последней цифры.ЗаключениеВ статье мы рассмотрели способ реализации multihead-модели для распознавания ГРЗ автомобилей с помощью фреймворка Catalyst. Основными компонентами явились собственно модель, а также раннер и набор колбэков для него. Модель успешно обучилась и показала высокую точность на тестовой выборке.Спасибо за внимание! Надеемся, что наш опыт был вам полезен. Больше наших статей по машинному обучению и обработке изображений:
===========
Источник:
habr.com
===========

Похожие новости: Теги для поиска: #_python, #_algoritmy (Алгоритмы), #_obrabotka_izobrazhenij (Обработка изображений), #_mashinnoe_obuchenie (Машинное обучение), #_kompjuternoe_zrenie (компьютерное зрение), #_image_processing, #_raspoznavanie_izobrazhenij (распознавание изображений), #_ocr, #_raspoznavanie_nomerov (распознавание номеров), #_cnn, #_pytorch, #_blog_kompanii_simbirsoft (
Блог компании SimbirSoft
)
, #_python, #_algoritmy (
Алгоритмы
)
, #_obrabotka_izobrazhenij (
Обработка изображений
)
, #_mashinnoe_obuchenie (
Машинное обучение
)
Профиль  ЛС 
Показать сообщения:     

Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы

Текущее время: 22-Ноя 12:19
Часовой пояс: UTC + 5