Русский
Русский
English
Статистика
Реклама

Сверточные сети

Рецепт обучения нейросетей

06.02.2021 02:09:12 | Автор: admin

Перевод статьи A Recipe for Training Neural Networks от имени автора (Andrej Karpathy). С некоторыми дополнительными ссылками.

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

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

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

1) Нейронные сети это дырявая абстракция

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

>>> your_data = # подставьте свой датасет здесь>>> model = SuperCrossValidator(SuperDuper.fit, your_data, ResNet50, SGDOptimizer)# покорите мир здесь

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

>>> r = requests.get('https://api.github.com/user', auth=('user', 'pass'))>>> r.status_code200

Круто! Смелый разработчик взял на себя бремя понимание строк запросов, URL, GET / POST запросов, HTTP соединений и т.д., и во многом скрыл сложность за несколькими строками кода. Это то, с чем мы знакомы и ожидаем. К сожалению, нейронные сети не похожи на это. Они не "готовая" технология, когда вы немного отклонились от обучения классификатора ImageNet. Я пытался указать на это в своей публикации "Да вы должны понимать метод обратного распространения ошибки" ("Yes you should understand backprop"), выбрав метод обратного распространения ошибки и назвав его "дырявой абстракцией", но ситуация, к сожалению, гораздо сложнее. "Обратное распространение ошибки" + "Стохастический градиентный спуск не делает вашу нейронную сеть магически работающей. Пакетная нормализация не заставляет ее магически сходиться быстрее. Рекуррентные нейронные сети не позволяют магически "вставить" текст. И только потому, что вы можете сформулировать вашу проблему в форме "обучение с подкреплением" не означает, что вы должны это делать. Если вы настаиваете на использовании технологии, не зная как она работает, вы, вероятно, потерпите неудачу. Что подводит меня к

2) Обучение нейронных сетей ломается молча

Когда вы неправильно написали или настроили код вы часто получаете определенное исключение. Вы передали целое число там где ожидается строка. Функция ожидает только 3 аргумента. Этот импорт неудачный. Тот ключ не существует. Количество элементов в двух списках не ровен. В довесок, часто возможно написать юнит-тесты для определенного функционала.

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

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

Рецепт

На фоне вышеупомянутых двух фактов, я разработал для себя конкретный процесс, которого я придерживаюсь, применяя нейронную сеть к новой проблеме, и который я попробую описать. Вы увидите, что эти два принципа воспринимаются очень серьезно. В частности, проходит построение от простого к сложному и на каждом шагу мы делаем определенные гипотезы о том, что произойдет, а потом либо проверяем их экспериментом, или исследуем, пока не найдем какую-нибудь проблему. То, что мы пытаемся всеми силами предотвратить - это введение большого количества "непроверенной" сложности сразу, что обязательно приведет к ошибкам или неправильной конфигурации, поиски которых будут длиться вечно. Если бы процесс написания кода нейронной сети был бы подобным обучению нейросети (здесь написания кода нейросети используется как прямая аналогия к обучению нейросети), то вы хотели бы использовать очень малую скорость обучения и угадывать, а затем оценивать полный набор тестов после каждой итерации.

1. Cтаньте едиными c данными

Первый шаг к обучению нейронных сетей - это вообще не касаться кода нейронной сети, а взамен начать с тщательной проверки ваших данных. Этот шаг критический. Я люблю тратить много времени (измеряется в часах), проверяя тысячи примеров, понимая их распределение и ища закономерности. К счастью, ваш мозг хорошо с этим справляется. Однажды я обнаружил, что данные содержат примеры которые повторяются. В другой раз я обнаружил поврежденные изображения / разметку. Я ищу дисбаланс данных и смещения. Обычно я также обращаю внимание на свой собственный процесс классификации данных, который намекает на виды архитектур которые мы со временем изучим. В качестве примера - достаточно локальных особенностей, или нам нужен глобальный контекст? Сколько существует вариаций и какую форму они принимают? Какая вариация ошибочная и может быть предварительно обработана? Имеет ли значение пространственное расположение или мы хотим его усреднить (с помощью операции average pool)? Насколько важны детали и насколько сильно мы можем позволить себе уменьшить размер изображений? Насколько зашумленная разметка?

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

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

2. Настройте сквозной скелет обучения / оценки + получите простой базис (базовую модель)

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

Советы и подсказки на этом этапе:

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

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

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

  • проверяйте потери в начале. Убедитесь, что показатель потери начинается с правильного значения. Например, если вы правильно инициализирует свой конечный слой, то у вас должно получиться -log(1 / n_classes) для функции softmax при инициализации. Те же значения по умолчанию можно получить для регрессии L2, потерь Губера и тому подобное.

  • инициализируйте верно. Правильно инициализируйте веса конечного слоя. Например, если вы регрессируете некоторые значения, которые имеют среднее значение 50, тогда инициализируйте окончательное смещение к 50. Если у вас несбалансированный набор данных с соотношением 1:10, установите смещение на своих логитах так, чтобы ваша сеть давала предсказания 0.1 при инициализации. Правильная их установка ускорит сходимость и устранит кривые потерь в виде "хоккейной клюшки", где в первые несколько итераций ваша сеть в основном лишь изучает смещения.

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

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

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

  • проверяйте уменьшения потерь на тренировочной выборке. Думаю, ваш набор данных будет не очень объемным, так как вы работаете с игрушечной моделью. Попробуйте немного увеличить его объем. Значение потерь на тренировочной выборке снизились как следует?

  • визуализируйте непосредственно перед входом нейросети. Однозначно правильное место для визуализации ваших данных находится непосредственно перед вашим y_hat = model (x) (или sess.run в Tensorflow). То есть - вы должны визуализировать именно то, что попадает в вашу сеть, декодируя этот необработанный тензор данных и меток в виде какой-то визуализации. Это единственный "источник истины". Я не могу сосчитать, сколько раз это меня спасало и проявляло проблемы с предварительной обработкой и аугментацией данных.

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

  • используйте метод обратного распространения ошибки для отслеживания зависимостей. Ваш код для глубокого обучения часто может содержать сложные, векторизованные и трансляционные операции. Достаточно распространенная ошибка, с которой я сталкивался несколько раз, заключается в том, что люди достигают этого неправильно (например, они используют view, а не transpose / permute) и нечаянно смешивают информацию в измерении размера пакета. Удручает тот факт, что ваша сеть, как правило, все равно способна хорошо учиться, потому что она научится игнорировать данные из других примеров. Одним из способов налаживания этой (и других связанных с этим проблем) является установление функции потери как чего-то тривиального, такого как сумма всех выходов примера i, запуск обратного прохода до входного сигнала и обеспечения получения ненулевого градиента только на i-м входе. Ту же стратегию можно использовать, чтобы убедиться, что ваша авторегресивная модель в момент времени t зависит только от 1..t-1. В общем, градиенты дают вам информацию о том, что и от чего зависит в вашей сети, это может быть полезно для отладки.

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

3. Переобучайте

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

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

Несколько советов и подсказок на этом этапе:

  • подбор модели. Чтобы достичь хороших значений потерь обучающей выборки, вы должны выбрать соответствующую архитектуру данных. Когда дело доходит до ее выбора, мой первый совет: Не будьте героем. Я видел много людей, которые стремятся сойти с ума в креативности подбора лего-блоков из набора инструментов нейронных сетей в процессе создания различных экзотических архитектур, которые имеют смысл только для них. На первых этапах проекта всеми силами сопротивляйтесь этому искушению. Я всегда советую людям просто найти наиболее похожую научную работу и скопировать ее простейшую архитектуру, которая обеспечивает хорошие показатели. Например, если вы классифицируете изображения, не будьте героем, а просто скопируйте ResNet-50 для первого запуска. Вы сможете делать что-то более специфическое позже и победить этот пункт.

  • Adam (метод адаптивной оценки моментов) безопасен. На ранних стадиях установления базиса мне нравится использовать Adam со скоростью обучения 3e-4. По моему опыту, Adam гораздо лояльнее к гиперпараметрам, включая плохую скорость обучения. Для сверточных нейросетей хорошо настроенный метод стохастического градиента (SGD) почти всегда немного превосходит Adam, но область оптимальной скорости обучения гораздо более узкая и зависит от задачи. (Примечание. Если вы используете рекуррентные нейросети и связанные с ними модели обработки последовательностей, то чаще используют Adam. Опять же, на начальном этапе своего проекта не будьте героем и соблюдайте самые популярные статьи.)

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

  • не доверяйте коэффициенту уменьшения скорости обучения по умолчанию. Если вы переделываете код с какой-то другой задачи, всегда будьте очень осторожны со снижением скорости обучения. Вы не только хотели бы использовать различные графики снижения скорости обучения для различных проблем, но - что еще хуже - в типовой реализации снижение будет базироваться на текущем номере эпохи, который может широко варьироваться просто в зависимости от размера вашего набора данных. Например, ImageNet замедлится в 10 раз на 30-й эпохе. Если вы не обучаетесь с ImageNet (имеется в виду размер датасета), вы, почти наверняка, этого не хотите. Если вы не будете осторожны, ваш код может тайком сводить вашу скорость обучения к нулю слишком рано, не позволяя вашей модели сходиться. В своей работе я всегда полностью выключаю уровень снижения скорости обучения (использую постоянную скорость обучения) и настраиваю его в самом конце.

4. Регуляризируйте

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

  • получите больше данных. Во-первых, безусловно лучшим способом регуляризирования модели в любом практической среде является добавление большего количества реальных учебных данных. Очень распространенной ошибкой является проведение многих инженерных циклов, пытаясь выжать сок из небольшого набора данных, когда вместо этого можно было собирать больше данных. Насколько мне известно, добавление дополнительных данных является едва ли не единственным гарантированным способом монотонно улучшать производительность хорошо настроенной нейронной сети почти неограниченно долго. Остальные - это ансамбли нейросетей (если вы можете себе позволить), но это ограничивается ~ 5-ю моделями.

  • аугментация данных. Следующим лучшим способом после реальных данных является полу фальшивые данные - попробуйте более агрессивную аугментацию данных.

  • креативная аугментация. Если полу фальшивые данные не помогли, фейковые данные также могут что-то сделать. Люди находят творческие способы расширения наборов данных; Например, рандомизация доменов, использование моделирования, умные гибриды, такие как вставка (потенциально смоделированная) данных у сцены или даже GAN.

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

  • придерживайтесь контролируемого обучения (обучение с учителем). Не переоценивайте предварительное обучение без присмотра (без учителя). В отличие от того, что рассказывается в той заметке в блоге от 2008 года [не могу понять о каком сообщении тут идет речь], насколько мне известно, нет версий, которые показывают хорошие результаты на современных задачах компьютерного зрения (хотя NLP, кажется, вполне хорошо справляется вместе с BERT и компанией сегодня, вполне вероятно благодаря умышленному характеру текста и высшему соотношению сигнал / шум).

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

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

  • уменьшайте размер партии. Через нормализацию внутри нормы партии меньшие размеры партии несколько соответствуют сильной регуляризации. Это связано с тем, что эмпирическое среднее / стандартное распределение для партии является более приблизительной версией полного среднего / стандартное распределение, поэтому изменение масштаба и смещения "раскачивают" вашу партию больше.

  • отсеивайте. Добавьте отсеивания. Используйте dropout2d (пространственное отсеивания) для сверточных сетей. Используйте это умеренно / осторожно, поскольку, кажется, отсеивания нехорошо работает при нормализации партии.

  • уменьшение веса. Увеличьте коэффициент уменьшения веса (эффект забывания).

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

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

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

5. Тюнингуйте

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

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

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

6. Выжмите все соки

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

  • ансамбли. Ансамбли моделей - это почти гарантированный способ получить 2% точности на чем-либо. Если вы не можете позволить себе вычисления во время тестирования, посмотрите на перегонку своего ансамбля в сеть, используя темные знания.

  • оставьте ее тренироваться. Я часто видел людей, которые соблазняются прекратить обучение моделей, когда потеря валидации, кажется, выравнивается. По моему опыту, сети продолжают тренироваться не интуитивно долго. Однажды я случайно покинул тренировку модели во время зимних каникул, и когда вернулся в январе, я увидел результат SOTA (state of the art - "современный уровень").

Вывод

Как только вы дойдете сюда, у вас будут все составляющие успеха: Вы глубоко понимаете технологию, набор данных и проблему, вы создали всю инфраструктуру обучения / оценки и достигли высокой уверенности в ее точности, вы исследовали все более сложные модели, получая улучшения производительности способами, которые вы предугадывали на каждом шагу. Теперь вы готовы прочитать много работ, попробовать большое количество экспериментов и получить свои результаты SOTA. Удачи!

Подробнее..

Конкурс World amp AI Data Challenge начинаем решать задачу распознавания шрифта Брайля

13.08.2020 10:12:29 | Автор: admin

Технологии искусственного интеллекта и анализа данных всё стремительнее входят в нашу жизнь, они могут дать еще один шанс решению действительно важных для людей социальных задач, которые ранее не были реализованы. С этой целью центр цифрового развития АСИ организовал конкурс World AI & Data Challenge, цель которого структурировать процесс поиска социальных задач и их решений. В феврале 2020 года команда центра цифрового развития АСИ позвала меня войти в состав экспертов этого конкурса. В этой заметке я немного расскажу о самом конкурсе, а также о том, как можно начать решать одну из интересных задач этого конкурса распознавание шрифта Брайля. Поучаствовать в решении этой и других задач конкурса вы можете до 31 апреля 2020 г..



О конкурсе


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


На первом этапе представители региональных органов власти и сообществ ставят наиболее актуальные для них задачи и, по возможности, находят соответствующие датасеты. После сбора всех задач подключаются эксперты конкурса, чтобы провести акселерацию задач, а также оценить их на соответствие требованиям конкурса и наличие данных для перехода лучших на следующий этап. Всего поступило 147 задач от 43 регионов России, а также из Сингапура, Казахстана, Узбекистана и Монголии. По итогам голосования осталось 30 задач из 30 регионов, в том числе 8 из них задачи международного уровня.


Сейчас идет второй этап конкурса, где разработчики со всего мира находят решения отобранных задач. На сегодняшний день к проекту подключилось уже более 2 тысяч ребят. Каждый участник берет наиболее близкую ему тему: кто-то хочет спрогнозировать потребность медицинских организаций в лекарствах, кто-то разрабатывает сервис по выявлению факта развития сердечно-сосудистых заболеваний, а некоторые берутся решать более глобальную задачу по снижению бедности в мире. Полный список задач для решений можете посмотреть здесь. Главное успеть загрузить свои проекты на платформу http://git.asi.ru до 31 августа 2020 г..


Главное отличие конкурса от других классических соревнований кроется в третьем этапе (сентябрь февраль 2021 г.), на котором лучшие разработанные решения внедряются в реальную жизнь совместно с постановщиками задач и при поддержке АСИ. Лучшие решения также будут представлены для тиражирования по России и в другие страны. Именно на этом этапе решения вступают в режим опытной эксплуатации и могут быть серьезно переработаны.


Призовой фонд проекта будет пополняться, на данный момент он составляет 1,5 млн рублей. Помимо этого, победителям предусмотрены гранты на вычислительные мощности от 1 млн и курсы по Data Science от лучших мировых онлайн-школ.


Распознавание шрифта Брайля


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


Несмотря на кажущуюся простоту задачи, она осложняется рядом моментов:


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

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


Смотрим на существующие решения


Для начала, немного поизучаем, что уже сделано в направлении распознавание Брайля. Например, в 2017 году был вот такой пост, анонсировавший проект распознавателя на GitHub, но при внимательном изучении оказывается, что сам проект это несколько скриптов на Python (из которых, тем не менее, можно будет взять какой-то код для вдохновения). При внимательном поиске находится ещё пара проектов, которые примерно в таком же состоянии, например вот такой. Явно есть над чем поработать!


Также имеет смысл поискать научные статьи на тему, например к упомянутому выше проекту есть вот такая статья. Ещё пара интересных статей: Smart Braille System Recognizer (2013), или совсем современная статья этого года Optical Braille Recognition Based on Semantic Segmentation Network.


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


Изучаем датасет и ищем дополнительные данные


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


Braille Cyryllic Letters


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


Для решения задачи классификации нам потребуется также датасет, который бы устанавливал соответствие изображения и текста, поэтому попробуем поискать какие-то внешние датасеты:


  • Разумный датасет для распознавание отдельных символов [находится на Kaggle][KaggleData], он содержит фотографии отдельный символов Брайля, к которым применены различные трансформации, вроде поворота или изменения яркости. На таком датасете в принципе можно обучить распознаватель отдельных символов.
  • В упомянутом выше проекте на GitHub находится неплохой датасет символов, полученных фотографированием реальных книг, с последующим применением преобразований для data augmentation.
  • Поскольку алфавит азбуки Брайля известен, теоретически возможно попытаться сгенерировать искусственный датасет на основе произвольных текстов. При этом важно научиться делать это так, чтобы изображения выглядели достаточно реалистично, т.е. предусмотреть различные варианты освещения, поворота, диаметров точек и т.д.

Braille Cyrillic Letters


Общая архитектура решения


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


  1. Предварительная обработка фотографии с целью нормализации яркости/контраста
  2. Выделение границ текста, вырезание области с символами и (в идеале) исправление геометрических перспективных искажений
  3. Разбиение текста на отдельные символы
  4. Распознавание отдельных символов, с помощью нейросети или алгоритмически
  5. Коррекция полученного текста с целью исправления единичных ошибок. Для этого, в простейшем случае, может использоваться обычный spell checker, например, имеющийся к наборе когнитивных сервисов Microsoft.

Давайте кратко рассмотрим эти пункты. Код, который я буду здесь демонстрировать, есть на GitHub в репозитории https://github.com/shwars/braillehack. Если вы будете использовать его на хакатоне делайте fork! Запустить и посмотреть работу кода вы можете с помощью Visual Studio Codespaces


Предобработка изображений


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


im = cv2.imread('../data/Photo_Turlom_C1_2.jpeg')im = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)


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


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


im = cv2.blur(im,(3,3))im = cv2.adaptiveThreshold(im, 255, cv2.ADAPTIVE_THRESH_MEAN_C,                           cv2.THRESH_BINARY_INV, 5, 4)im = cv2.medianBlur(im, 3)_,im = cv2.threshold(im, 0, 255, cv2.THRESH_OTSU)im = cv2.GaussianBlur(im, (3,3), 0)_,im = cv2.threshold(im, 0, 255, cv2.THRESH_OTSU)plt.imshow(im)

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


Получаем координаты точек


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


Для нахождения крайних точек текста для начала выделим из картинки ключевые точки. Для этого используются так называемые feature detectors, и в составе OpenCV реализован детектор ORB, который может быть реализован парой строк кода:


orb = cv2.ORB_create(5000)f,d = orb.detectAndCompute(im,None)

5000 это максимальное число ключевых точек, которые мы хотим искать. Если мы теперь построим эти точки на пустом изображении то увидим, что все наши точки из символов алфавита были обнаружены:


def plot_dots(im,dots):    img = np.zeros_like(im)    for x in dots:        cv2.circle(img,(int(x[0]),int(x[1])),1,(255,0,0))    plt.imshow(img)pts = [x.pt for x in f]plot_dots(cim,pts)  


Два подхода к решению задачи


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


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

Использование feature detector позволило нам свести задачу работы с изображением к множеству точек, с которыми мы можем работать алгоритмически.

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


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


Вырезание области с символами


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


min_x, min_y, max_x, max_y = \   [int(f([z[i] for z in pts]))        for f in (min,max) for i in (0,1)]

Далее приведём построенный нами прямоугольник к прямоугольнику с заданной длиной сторон (например, 500):


off = 5src_pts = np.array([(min_x-off,min_y-off),(min_x-off,max_y+off),                    (max_x+off,min_y-off),(max_x+off,max_y+off)])dim = 500dst_pts = np.array([(0,0),(0,dim),(dim,0),(dim,dim)])h,m = cv2.findHomography(src_pts,dst_pts)trim = cv2.warpPerspective(cim,h,(dim,dim))plt.imshow(trim)


Мы получили практически идеальный прямоугольник с текстом.


Разбиваем текст на символы


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


char_h = 32char_w = 22def slice(img):    dy,dx = img.shape    y = 0    while y+char_h<dy:        x=0        while x+char_w<dx:            # Корректируем сдвиг по x            while np.max(img[y:y+char_h,x])!=0:                x+=1            while np.max(img[y:y+char_h,x+char_w])!=0:                x-=1            # Пропускаем полностью пустые символы            if np.max(img[y:y+char_h,x:x+char_w])>0:                yield img[y:y+char_h,x:x+char_w]            x+=char_w        y+=char_hsliced = list(slice(trim))

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


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



Свёрточная сеть для распознавания символов


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



Финальная коррекция текста


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


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


Создание приложения


К этому моменту мы получили модель (в виде некоторого кода или функции на языке Python), способную по входной фотографии вернуть распознанный текст. Однако в задании сказано, что в результате конкурса ожидается приложение, а не только работающая модель. Поэтому будет правильным потратить немного времени и оформить всё в виде конечного работоспособного продукта (MVP), благо облачные технологии Microsoft Azure позволяют сделать это весьма безболезненно.


В качестве клиенской части приложения можно использовать:


  • Чат-бот в Telegram или Skype, который будет получать на вход фотографию. Реализация чат-ботов максимально просто делается с помощью Microsoft Bot Framework.
  • Мобильное приложение, по возможности кросс-платформенное, для создания которого можно использовать Xamarin или React Native
  • Веб-сайт

Основную процедуру распознавания в виде кода на Python удобнее всего выполнять на сервере, в виде облачного REST-сервиса. По сути речь идёт про создание веб-API, которым будет пользоваться ваше клиентское приложение. Такое API лучше всего оформить в виде Azure Function с HTTP-триггером.


Azure Function это по сути фрагмент кода на каком-то языке программирования (в нашем случае на Python), который срабатывает при наступлении какого-то события (в нашем случае при вызове REST-запроса). Подбробнее о том, как создать такую функцию с использованием Python читайте в этом руководстве.


Очень похожий по архитектуре клиент-серверный проект виртуального музейного экспоната я описал в своей статье. Фрагментами кода оттуда тоже можно вдохновляться!


Выводы


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


Работоспособный пример кода, описанного в данной статье, вы можете найти на http://github.com/shwars/braillehack. Он, в некотором смысле, "обрывается на полуслове" в основном для того, чтобы вы могли оттолкнуться от него и доработать своё решение. Если у вас будут возникать вопросы, или если получится сделать что-то работающее на основе этого кода пишите мне, координаты есть на сайте http://soshnikov.com.


Удачного конкурса! Надеемся, что кому-то из участников удастся сделать действительно хорошее решение, которое будет исключительно полезно, поскольку поможет расширить возможности общения учителей, родственников слепых и слабовидящих детей и упростит проверку домашних работ ребенка из-за отсутствия навыков чтения шрифта Брайля.

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru