Анализ 50 000 шрифтов с помощью глубоких нейронных сетей

Автор оригинальной публикации: Эрик Бернхардсон (Erik Bernhardsson)

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

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

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

В результате я получил тензор размерности 56443 * 62 * 64 * 64. Упражнение для читателя: откуда взялось число 62? Я сохранил эти данные в виде маленького (13 ГБ) HDF5-файла, который вы можете загрузить по следующей ссылке: fonts.hdf5.

Если вычислить среднее всех шрифтов, вот что мы получим:

Теперь вы уже точно догадались, что означает число 62.

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

Как среднее, так и медиана дают в результате стандартные достаточно разборчивые символы! Однако отдельные шрифты могут быть очень разнообразными:

Такое разнообразие вполне ожидаемо, ведь я собирал экземпляры со всех уголков Интернета. Многие шрифты даже не имеют строчных версий символов. В некоторых шрифтах отсутствует часть символов, вместо которых выводятся прямоугольники. Обратите внимание на смешное изображение Могучего Рейнджера (Power Ranger), используемое в качестве строчной буквы «c»!

Обучение нейронной сети

Теперь обучим нейронную сеть, которая будет генерировать символы! В частности, я хотел создать «вектор шрифта», то есть вектор в скрытом пространстве (latent space), который «определяет» некоторый шрифт. Таким образом мы можем представить все шрифты в пространстве, где сходные шрифты имеют сходные векторы.

Я создал простую нейронную сеть с помощью Lasagne/Theano (код находится здесь). Для обучения потребовалось несколько недель, вероятно, из-за большого количества данных и параметров.

Характеристики модели:

  • 4 скрытых полносвязных слоя размером 1024.

  • Выходной слой размером 4096 (64 * 64) обладает сигмоидной нелинейностью, соответственно, на выходе мы получаем значения от 0 (белый) до 1 (черный).

  • В качестве функция потерь используется L1. Этот подход более эффективен, поскольку, применяя L2, мы получили бы очень «серые» изображения.

  • Достаточно сильная L2-регуляризация всех параметров.

  • ReLU-нейроны с утечкой (leaky ReLU) (alpha=0,01).

  • Первый слой является 102-мерным: 40-мерный вектор шрифта, объединенный с 62-мерным вектором в прямом унитарном коде (one-hot vector), задающим символ.

  • Скорость обучения (learning rate) равна 1,0. Это очень большое значение, но оно обеспечило хорошие результаты. Скорость обучения следует уменьшить в 3 раза, если не наблюдается улучшение на 10%-й тестовой выборке ни в одной эпохе.

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

  • Дропаут (dropout) не дал положительного результата. Я добавил немного умеренного гауссовского шума (sigma=0,03) в вектор шрифта, и это обеспечило небольшое улучшение.

  • Я создал дополнительные обучающие данные (data augmentation) с помощью случайного размытия при значении sigma из интервала [0, 1]. Мое предположение заключалось в том, что это поможет в тех случаях, когда символы имеют тонкие линии.

Весь код доступен в следующем репозитории на GitHub: erikbern/deep-fonts.

В итоге я получил 40-мерное векторное представление всех 50 000 шрифтов. В данном случае мы имеем дело с многомерным нормальным распределением (multivariate normal distribution). Ниже показаны распределения для каждого из 40 измерений:

Экспериментируем с моделью

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

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

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

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

Некоторые символы, например, строчная буква «g», имеют различные варианты написания, между которыми мы также можем интерполировать:

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

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

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

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

Заключение

Описанный подход позволяет сделать множество других интересных вещей. Кроме того, есть пространство для усовершенствования модели. В частности, если бы у меня было больше времени, я бы обязательно исследовал генеративные соревновательные сети (generative adversarial network), демонстрирующие хорошие результаты в задачах генерации изображений. Кроме того, достаточно легко можно реализовать нормализацию пакетов (batch normalization) и параметрические ReLU с утечкой (parametric leaky ReLU). И, наконец, сама архитектура сети, вероятно, выиграет от применения развертки (deconvolution) вместо полносвязных слоев.

Если вас заинтересовал проект, вы можете загрузить данные и поэкспериментировать!

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

Ваш e-mail не будет опубликован.

закрыть

Поделиться

Отправить на почту
закрыть

Вход

закрыть

Регистрация

+ =