Библиотека сайта rus-linux.net
Система распознавания символов (OCR)
Оригинал: Optical Character Recognition (OCR)
Автор: Marina Samuel
Дата публикации: July 12, 2016
Перевод: Н.Ромоданов
Дата перевода: февраль 2017 г.
Это третья часть статьи "Система распознавания символов (OCR)".
Перейти к
началу.
Сервер (server.py
)
Несмотря на то, что наш небольшой сервер просто перенаправляет информацию, нам нужно еще рассмотреть вопрос о том, как принимать и обрабатывать запросы HTTP. Во-первых, мы должны решить, какой использовать тип запроса HTTP. В последнем приведенном примере клиент использует запрос типа POST, но почему мы приняли такое решение? Т.к. на сервер отправляются данные, то имеет смысл пользоваться запросами PUT или POST. Нам нужно посылать тело объекта json, причем без параметров URL. Хотя в теории, запрос GET мог бы работать также хорошо, но семантически его использование смысла не имеет. Выбор между запросами PUT или POST – это дебаты, постоянно длящиеся среди программистов; в лаборатории KNPLabs эти вопросы решают
Еще нам нужно решить, посылать ли "обучающие" запросы и запросы на "предсказание" на разные веб страницы (например, на http://localhost/train
и http://localhost/predict
) или же на одну и ту же страницу, на которой данные затем будут обрабатываться поотдельности. В данном случае мы можем выбрать второй вариант, так как разница между тем, что делается с данными в каждом из вариантов, незначительна. На практике в случаях, если сервер должен для каждого типа запроса делать какую-нибудь более конкретную обработку, было бы лучше пользоваться отдельными страницами. Такое решение, в свою очередь, влияет на то, как сервер будет выдавать коды ошибок. Например, ошибка 400 "Bad Request" (плохой запрос) отправляется в случае, когда в полезной нагрузке запроса отсутствуют "обучающие данные" и "данные для предсказания". Если бы вместо этого мы пользовались отдельными страницами, то такая ситуация не вызывала бы проблем. Обработка выполняется в фоновом режиме при помощи системы распознавания образов, в работе которой может по какой-либо причине возникнуть проблема и, если внутри сервера эту ситуацию не обработать правильно, то сервер отправит сообщение 500 "Внутренняя ошибка сервера". Опять же, если страницы были бы отдельными, то было бы больше возможностей для более подробного анализа запросов и можно было бы отправлять более конкретные сообщения об ошибках. Например, определить, что внутренняя ошибка сервера была фактически вызвана плохим запросом.
И, наконец, мы должны решить, когда и где для инициализировать работу системы распознавания символов. Лучшем способом было бы инициализировать ее в server.py
, но до того, как будет запущен сервер. Это обусловлено тем, что, когда нейронная сеть запускается, ее нужно сначала обучить на некотором количестве уже существующих данных, и на это может потребоваться несколько минут. Если сервер будет запущен до того момента, когда эта процедура будет завершена, то в ответ на все запросы на "обучение" или "предсказание" будет генерироваться исключение, т. к. на момент получения запросов объект OCR (система распознавания символов) еще не будет инициализирован. При другом допустимом варианте реализации можно создать некоторую не очень точную первоначальную нейронную сеть, которая будет использоваться в течение нескольких первых запросов до тех пор, пока для этой новой нейронной сети в фоновом режиме будет асинхронно происходить обучение. Этот альтернативный подход действительно позволяет сразу же обращаться к нейронной сети, но такая реализация более сложная и экономила бы время лишь при запуске сервера в тех ситуациях, когда происходит перезагрузка серверов. Подобный вариант реализации мог бы быть более предпочтительным в случае организации сервиса распознавания символов, которому необходима высокая степень доступности.
Ниже приведена большая часть нашего серверного кода, реализованного в одной короткой функции, с помощью которой происходит обработка запросов POST.
def do_POST(s): response_code = 200 response = "" var_len = int(s.headers.get('Content-Length')) content = s.rfile.read(var_len); payload = json.loads(content); if payload.get('train'): nn.train(payload['trainArray']) nn.save() elif payload.get('predict'): try: response = { "type":"test", "result":nn.predict(str(payload['image'])) } except: response_code = 500 else: response_code = 400 s.send_response(response_code) s.send_header("Content-type", "application/json") s.send_header("Access-Control-Allow-Origin", "*") s.end_headers() if response: s.wfile.write(json.dumps(response)) return
Создание искусственной сети прямого распространения (neural_network_design.py
)
При создании искусственной нейронной сети прямого распространения есть ряд важных факторов, которые нужно учитывать. Во-первых, это то, что необходимо использовать функцию активации. Мы ранее уже упоминали о функциях активации в качестве средства принятия решения о выходных данных некоторого узла сети. Решение, принимаемое функцией активации, поможет нам решить, какой из узлов сети необходимо использовать. В нашем случае, мы будем создавать нейронную сеть, которая для каждой распознаваемой цифры (0-9) выдает значение в диапазоне от 0 до 1. Значение, близкое к 1, будет означать, что нейронная сеть предсказывает эти нарисованные цифры, а значение, близкое 0, означает, что она будет предсказывать, что это не та цифра. Таким образом, нам нужна функция активации, выходные данные которых которой будут либо ближе к 0, либо ближе к 1. Нам также необходима такая функция, которая позволяла "двигаться обратно против хода вычислений". Функции, обычно используемые в таких случаях, являются сигмоидальными и они удовлетворяют обоим этим требованиям. На сайте StatSoft предлагается
Вторым фактором, который следует рассмотреть, это хотим ли мы пользоваться смещениями (biases). Мы прежде уже пару раз упоминали о смещениях, но, на самом деле, не рассказывали о том, что они из себя представляют и почему мы ими пользуемся. Давайте попробуем в них разобраться и возвратимся к рис.15.1, на котором показано, как вычисляются выходные данные некоторого узла. Предположим, что у нас есть один входной узел и один выходной узел, тогда наша формула выходных данных будет иметь вид y=f(wx), где y является выходными данными, f() является функцией активации, w является весом связи между узлами, а x является входной переменной узла. Смещение является, по существу узлом, выходное значение которого всегда равно 1. Формула расчета выходных значений данных изменится на y=f(wx+b), где b будет весом связи между узлом смещения и следующим узлом. Если рассматривать w и b как константы, а x - как переменную, то при добавлении смещенияк нашей линейной функции входных данных f(.) будет добавляться константа.
Использование смещений позволяет менять входные значения и более гибко рассчитывать для конкретного узла выходные значения. На практике часто особенно удобно использовать смещениями в нейронных сетях с небольшим числом входов и выходов. Смещения, благодаря большей гибкости при расчете выходных значений, позволяют добиться большей точности. Без использования смещений, мы с помощью нейронной сети получали бы правильные предсказания с меньшей вероятностью или нам бы потребовалось больше скрытых узлов.
К числу других факторов, которые необходимо рассматривать, относятся количество скрытых слоев и количество скрытых узлов в слое. Для больших нейронных сетей со многими входами и выходами эти числа выбираются путем перебора различных значений и тестирования производительности сети. В таких случаях производительность измеряется в процессе обучения нейронной сети определенного размера и получения процентной оценки правильных предсказаний. В большинстве случаев для достижения хорошей производительности оказывается достаточным одного слоя, поэтому мы здесь экспериментируем только с количеством скрытых узлов.
# Try various number of hidden nodes and see what performs best for i in xrange(5, 50, 5): nn = OCRNeuralNetwork(i, data_matrix, data_labels, train_indices, False) performance = str(test(data_matrix, data_labels, test_indices, nn)) print "{i} Hidden Nodes: {val}".format(i=i, val=performance)
Здесь мы инициализируем нейронную сеть с числом скрытых узлов от 5 до 50 с шагом 5. Затем мы вызываем функцию test()
.
def test(data_matrix, data_labels, test_indices, nn): avg_sum = 0 for j in xrange(100): correct_guess_count = 0 for i in test_indices: test = data_matrix[i] prediction = nn.predict(test) if data_labels[i] == prediction: correct_guess_count += 1 avg_sum += (correct_guess_count / float(len(test_indices))) return avg_sum / 100
Во внутреннем цикле вычисляется количество правильных классификаций, которое затем в конце цикла делится на количество попыток классификаций. В результате получается коэффициент или процент точности нейронной сети. Поскольку каждый раз выполняется обучение нейронной сети, веса могут несколько отличаться, поэтому мы повторяем этот процесс во внешнем цикле 100 раз и можем вычислить среднюю точность для конкретной конфигурации нейронной сети. В нашем случае результат запуска neural_network_design.py
выглядит следующим образом:
PERFORMANCE ----------- 5 Hidden Nodes: 0.7792 10 Hidden Nodes: 0.8704 15 Hidden Nodes: 0.8808 20 Hidden Nodes: 0.8864 25 Hidden Nodes: 0.8808 30 Hidden Nodes: 0.888 35 Hidden Nodes: 0.8904 40 Hidden Nodes: 0.8896 45 Hidden Nodes: 0.8928
По выданным результатам можно сделать вывод, что наиболее оптимальным будет 15 скрытых узлов. Добавление 5 узлов в диапазоне от 10 до 15 узлов дает нам повышение точности приблизительно на 1%, а дальнейшее повышение точности еще на 1% потребовало от нас добавить еще 20 узлов. В результате увеличения количества скрытых узла происходит увеличение вычислительной нагрузки. Из-за этого сети с большим количеством скрытых узлов будут дольше обучаться и дольше делать прогнозы. Именно поэтому мы решили использовать то количество скрытых узлов, переход к которому дает последнее существенное увеличению точности. Конечно, возможна ситуация, когда при разработке искусственной нейронной сети вычислительные накладные расходы не представляют собой проблему, а главный приоритет – получить настолько точно работающую нейронную сеть, насколько это возможно. В подобном случае лучше выбрать 45 скрытых узлов вместо 15.
Перейти к следующей части статьи.
Перейти к началу статьи.