Наши партнеры

UnixForum





Библиотека сайта rus-linux.net

Система распознавания символов (OCR)

Оригинал: Optical Character Recognition (OCR)
Автор: Marina Samuel
Дата публикации: July 12, 2016
Перевод: Н.Ромоданов
Дата перевода: февраль 2017 г.

Это четвертая часть статьи "Система распознавания символов (OCR)".
Перейти к
началу.

Базовые возможности распознавания символов

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

Обучение с помощью алгоритма обратного распространения (ocr.py)

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

Сначала мы инициализируем веса небольшими случайными значениями (от -1 до 1). В нашем случае, мы инициализируем их значениями между -0.06 и 0.06 и храним их в виде матриц theta1, theta2, input_layer_bias и hidden_layer_bias. Поскольку каждый узел в слое имеет связь с любым другим узлом в следующем слое, мы можем построить матрицу, которая имеет m строк и n столбцов, где n - число узлов в одном слое, а m - это число узлов в соседнем слое. Эта матрица будет представлять все весовые коэффициенты связи между этими двумя слоями. Здесь theta1 имеет 400 столбцов для наших 20x20 пикселей входов и num_hidden_nodes строк. Точно так же, theta2 представляет связь между скрытым слоем и выходным слоем.В этой матрице num_hidden_nodes столбцов и NUM_DIGITS (10) строк. Остальные два вектора (по одной строке), input_layer_bias и hidden_layer_bias, представляют собой смещения.

    def _rand_initialize_weights(self, size_in, size_out):
        return [((x * 0.12) - 0.06) for x in np.random.rand(size_out, size_in)]
            self.theta1 = self._rand_initialize_weights(400, num_hidden_nodes)
            self.theta2 = self._rand_initialize_weights(num_hidden_nodes, 10)
            self.input_layer_bias = self._rand_initialize_weights(1, 
                                                                  num_hidden_nodes)
            self.hidden_layer_bias = self._rand_initialize_weights(1, 10)

Второй шаг представляет собой алгоритм прямого распространение, которое, по существу, является послойным вычислением значений для выходных узлов начиная от входных узлов, причем в том виде, как это было описано в самом начале главы. Здесь y0 представляет собой массив размером 400 с входами, которые мы хотим использовать для обучения нейронной сети. Умножение theta1 на y0 дает нам две матрицы размерами (num_hidden_nodes x 400) * (400 x 1) и результирующий вектор с выходными данными для скрытого слоя, размер которого равен num_hidden_nodes. Затем мы добавляем вектор смещения и применяем к этому вектору с выходными значениями сигмовидную функцию активации.В результате чего мы получаем вектор y1. Вектор y1 является вектором, в котором хранятся выходные значения нашего скрытого слоя. Чтобы вычислить вектор y2 для выходных узлов, тот же процесс повторяется снова. Теперь вектор y2 будет нашим вектором выходного слоя со значениями, представляющими собой индексы, отражающие вероятность того, что нарисовано конкретное число. Например, если кто-то рисует цифру 8, то в случае, если нейронная сеть сделает правильный прогноз, значение y2 для 8-го индекса будет самым большим. Но для цифры 6 этот показатель может большим, чем для нарисованной цифры 1, т. к. она очень похожа на цифру 8 и, скорее всего, для нее будут использоваться те же самые пиксели, что и для цифры 8. С каждой дополнительной нарисованной цифрой нейронная сеть обучается и значение y2 становится более точным.

    # The sigmoid activation function. Operates on scalars.
    def _sigmoid_scalar(self, z):
        return 1 / (1 + math.e ** -z)
            y1 = np.dot(np.mat(self.theta1), np.mat(data['y0']).T)
            sum1 =  y1 + np.mat(self.input_layer_bias) # Add the bias
            y1 = self.sigmoid(sum1)

            y2 = np.dot(np.array(self.theta2), y1)
            y2 = np.add(y2, self.hidden_layer_bias) # Add the bias
            y2 = self.sigmoid(y2)

Третий шаг представляет собой алгоритм обратного распространения, который выполняет вычисление ошибок в выходных узлах, а затем на каждом промежуточном слое в обратном направлении по направлению ко входным узлам. Здесь мы начнем с создания вектора с предполагаемыми выходными данными, actual_vals, с 1 в качестве индекса для цифры, которая соответствует нарисованной цифре, и нулям в противном случае. Вектор ошибок в выходных узлах, output_errors, вычисляется при помощи вычитания из actual_vals вектора y2, в котором хранятся фактически полученные результаты. После этого мы для каждого скрытого слоя вычисляем два компонента. Во-первых, у нас есть транспонированная матрица весов следующего слоя, умноженная на значения выходных ошибок этого слоя. Затем у нас есть производная от функции активации, которая была применена к предыдущему слою. После этого мы выполняем поэлементное умножение этих двух компонентов и получаем вектор ошибок для скрытого слоя. Здесь он называется hidden_errors.

            actual_vals = [0] * 10 
            actual_vals[data['label']] = 1
            output_errors = np.mat(actual_vals).T - np.mat(y2)
            hidden_errors = np.multiply(np.dot(np.mat(self.theta2).T, output_errors), 
                                        self.sigmoid_prime(sum1))

На основе ошибок, вычисленных ранее, происходит обновление весов, используемых при настройке весов нейронной сети. Веса обновляются на каждом уровне при помощи умножения матриц. Матрица ошибок на каждом слое умножается на матрицу с выходными данными предыдущего слоя. Затем это произведение умножается на скаляр, который называется скоростью обучения и добавляется к матрице весов. Скорость обучения представляет собой значение от 0 и до 1, которое влияет на скорость и точность обучения в нейронной сети. При больших значениях скорости обучения будет создана нейронная сеть, которая обучается быстро, но менее точно, в то время как для меньших значений будет сгенерирована нейронная сеть, которая обучается медленнее, но будет более точной. В нашем случае скорость обучения имеет относительно небольшое значение, равное 0,1. Оно для нас подходит, поскольку от нас не требуется немедленной готовности нейронной сети и пользователю не нужно немедленно продолжать ее обучение и делать запросы на предсказание. Смещения обновляются с помощью простого умножения скорости обучения на вектор ошибок слоя.

            self.theta1 += self.LEARNING_RATE * np.dot(np.mat(hidden_errors), 
                                                       np.mat(data['y0']))
            self.theta2 += self.LEARNING_RATE * np.dot(np.mat(output_errors), 
                                                       np.mat(y1).T)
            self.hidden_layer_bias += self.LEARNING_RATE * output_errors
            self.input_layer_bias += self.LEARNING_RATE * hidden_errors

Проверка обученной сети (ocr.py)

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

    def predict(self, test):
        y1 = np.dot(np.mat(self.theta1), np.mat(test).T)
        y1 =  y1 + np.mat(self.input_layer_bias) # Add the bias
        y1 = self.sigmoid(y1)

        y2 = np.dot(np.array(self.theta2), y1)
        y2 = np.add(y2, self.hidden_layer_bias) # Add the bias
        y2 = self.sigmoid(y2)

        results = y2.T.tolist()[0]
        return results.index(max(results))

Другие способы принятия решений (ocr.py)

В интернете доступно много ресурсов, лучше описывающих все детали реализации алгоритма обратного распространения. Одним из таких ресурсов является учебный курс университета Вилламет (University of Willamette). В нем шаг за шагом объясняется алгоритм обратного распространения, а затем объясняется, как его можно преобразовать в матричный вид. Хотя объем вычислений с использованием матриц будет точно таким же, как и при использовании циклов, преимущество в том, что благодаря меньшему количеству циклов код становится проще и его легче читать. Видно, что при использовании матричной алгебры весь процесс обучения можно записать с помощью менее чем 25 строк.

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

    def save(self):
        if not self._use_file:
            return

        json_neural_network = {
            "theta1":[np_mat.tolist()[0] for np_mat in self.theta1],
            "theta2":[np_mat.tolist()[0] for np_mat in self.theta2],
            "b1":self.input_layer_bias[0].tolist()[0],
            "b2":self.hidden_layer_bias[0].tolist()[0]
        };
        with open(OCRNeuralNetwork.NN_FILE_PATH,'w') as nnFile:
            json.dump(json_neural_network, nnFile)

    def _load(self):
        if not self._use_file:
            return

        with open(OCRNeuralNetwork.NN_FILE_PATH) as nnFile:
            nn = json.load(nnFile)
        self.theta1 = [np.array(li) for li in nn['theta1']]
        self.theta2 = [np.array(li) for li in nn['theta2']]
        self.input_layer_bias = [np.array(nn['b1'][0])]
        self.hidden_layer_bias = [np.array(nn['b2'][0])]

Заключение

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

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

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

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

Перейти к началу статьи.