Хабрахабр

Много иероглифов – много нейросетей: как построить эффективную систему распознавания для большого числа классов?

В прошлых статьях уже писали о том, как у нас устроены технологии распознавания текста:

Навигатор по серии постов

Примерно так же до 2018 года было устроено распознавание японских и китайских символов: в первую очередь с использованием растровых и признаковых классификаторов. Но с распознаванием иероглифов есть свои трудности:

  1. Огромное количество классов, которое нужно различать.
  2. Более сложное устройство символа в целом.

image

Но наиболее часто в китайской письменности используются ~10 000 символов. Сказать однозначно, сколько символов насчитывает китайская письменность, так же сложно, как точно посчитать, сколько слов в русском языке. Ими мы и ограничили число классов, используемых при распознавании.

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

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

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

Простой подход: одна свёрточная сеть для распознавания всех иероглифов

В общем-то использование свёрточных сетей для распознавания символов идея совсем не новая. Исторически их первый раз применили именно для этой задачи ещё в далёком 1998 году. Правда тогда это были не печатные иероглифы, а рукописные английские буквы и цифры.

В том числе появились более продвинутые архитектуры и новые подходы к обучению. За 20 лет технологии в области глубокого обучения, конечно, сильно скакнули вперёд.

«Простой» я её называю по сравнению с другими задачами компьютерного зрения как, например, поиск и распознавание лиц. Архитектура, представленная на схеме выше (LeNet), на самом деле и на сегодняшний день очень хорошо подходит для таких простых задач как распознавание печатного текста.

Берём нейронную сеть, выборку из размеченных иероглифов и обучаем её на задачу классификации. Казалось бы – вот решение проще некуда. Все возможные модификации LeNet для задачи классификации 10 000 иероглифов не давали достаточного качества (как минимум сравнимого с уже имеющейся у нас системой распознавания). К сожалению, оказалось, что всё не так просто.

С их помощью удалось достичь требуемой планки качества, но они давали сильную просадку по скорости работы – в 3-5 раз по сравнению с базовым алгоритмом на CPU. Для достижения требуемого качества приходилось рассматривать более глубокие и сложные архитектуры: WideResNet, SqueezeNet и т.д.

Здесь стоит сделать ремарку относительно того, что для нас в первую очередь важна именно скорость работы алгоритма на CPU. Кто-нибудь может задаться вопросом: «В чём смысл измерять скорость работы сети на CPU, если она куда быстрее работает на графическом процессоре (GPU)»? В наибольшем количестве сценариев распознавание производится на стороне клиента, и мы не можем заведомо предполагать, что у него есть GPU. Мы разрабатываем технологии для большой линейки продуктов распознавания в ABBYY.

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

Двухуровневая нейросетевая модель распознавания иероглифов

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

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

Картинка кликабельна

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

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

Построение классификатора первого уровня

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

При этом мы знали, что картинка там должна получаться примерно следующей:

На предпоследнем скрытом слое, идущем до классификации, всего 2 нейрона, благодаря чему статистику их активаций легко отобразить на плоскости. Это простой пример для случая классификации выборки рукописных цифр (MNIST) на 10 классов. Цвет точки соответствует определённому классу. Каждая точка на графике соответствует какому-то примеру из тестовой выборки.

Мы прогнали группу изображений из тестовой выборки и получили для каждого изображения вектор признаков. В нашем случае размерность признакового пространства была больше, чем в примере – 128. Из картинки выше очевидно, почему это стоит сделать. После этого нормализовали их (поделили на длину). Получили разбиение выборки на группы похожих (с точки зрения сети) изображений. Нормализованные вектора мы кластеризовали методом KMeans.

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

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

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

Построение классификаторов второго уровня

Дальше нужно определиться с тем, на какие целевые множества символов будут учиться классификаторы второго уровня. Вроде бы ответ очевиден – это должны быть полученные на предыдущем шаге группы символов. Это будет работать, но не всегда с хорошим качеством.

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

  • Фиксируем некоторую отдельную выборку изображений символов (не участвующую ни в обучении, ни в тестировании);
  • Прогоняем эту выборку через обученный классификатор первого уровня, помечая каждое изображение меткой этого классификатора (метка группы);
  • Для каждого символа рассматриваем все возможные группы, к которым изображения этого символа отнёс классификатор первого уровня;
  • Добавляем этот символ во все группы до тех пор, пока не будет достигнута требуемая степень покрытия T_acc;
  • Итоговые группы символов считаем целевыми множествами второго уровня, на которые будут обучаться классификаторы.

Например, изображения символа «А» были отнесены классификатором первого уровня 980 раз к 5-й группе, 19 раз ко 2-й группе и 1 раз к 6-ой группе. Всего у нас 1000 изображений этого символа.

Можем отнести его к 5-ой и 2-ой группе и получить покрытие 99. Тогда мы можем добавить символ «А» в 5-ю группу и получить покрытие этого символа в 98%. А можем отнести сразу к группам (5, 2, 6) и получить покрытие 100%. 9%.

Чем он выше – тем выше будет итоговое качество классификации, но тем больше будут целевые множества второго уровня и потенциально сложнее устроена классификация на втором уровне. По сути T_acc задаёт некоторый баланс между скоростью и качеством.

Очевидно, что это будет напрямую будет зависеть от качества обученного классификатора первого уровня. Практика показывает, что даже при T_acc=1 увеличение размера множеств в результате описанной выше процедуры пополнения не такое уж и значительное – в среднем где-то в 2 раза.

Вот пример того, как это пополнение работает для одного из множеств из того же разбиения на 500 групп, которое было выше:

image

Результаты встраивания модели

Обученные двухуровневые модели уже наконец-то работали быстрее и лучше используемых ранее классификаторов. На деле её было не так-то просто «подружить» с тем же графом линейного деления (ГЛД). Для этого пришлось отдельно научить модель отличать символы от априорного мусора и ошибок сегментации строки (возвращать в этих ситуациях низкую уверенность).

Итоговый результат встраивания в полный алгоритм распознавания документа ниже (получен на коллекции китайских и японских документов), скорость указана для полного алгоритма:

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

Немного про End-to-End распознавание

На сегодняшний день большая часть публично известных OCR-систем (тот же Tesseract от Google) использует End-to-End архитектуры нейронных сетей для распознавания строк или их фрагментов целиком. Мы же здесь использовали нейронные сети именно как замену модуля распознавания отдельного символа. Это неспроста.

В связи с этим использование End-to-End распознавания для этих языков не сильно улучшает качество, но при этом значительно медленнее (по крайней мере на CPU). Дело в том, что сегментация строки на символы в печатном китайском и японском не является большой проблемой в силу моноширинной печати. Да и вообще, как в контексте End-to-End использовать предложенный двухуровневый подход – непонятно.

Явные примеры – арабский, хинди. Есть же наоборот языки, для которых линейное деление на символы является ключевой проблемой. Но это уже совсем другая история. Для арабского, например, End-to-End решения уже активно у нас исследуются.

Алексей Журавлев, руководитель OCR New Technologies Group

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть