Поиск по сайту:

Как построить нейронную сеть для распознавания рукописных цифр с помощью TensorFlow


Введение

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

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

В этом руководстве вы реализуете небольшой подраздел распознавания объектов — распознавание цифр. Используя TensorFlow, библиотеку Python с открытым исходным кодом, разработанную лабораторией Google Brain для исследований в области глубокого обучения, вы будете брать нарисованные от руки изображения чисел от 0 до 9, а затем строить и обучать нейронную сеть распознавать и предсказывать правильную метку для цифры. отображается.

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

Предпосылки

Для выполнения этого урока вам понадобится:

  • Местный поставщик для создания виртуальных сред.

Примечание. TensorFlow требует минимальной версии Python 3.5 и поддерживает версии до 3.8. Старые или новые версии Python не поддерживаются. Это руководство предназначено для работы с версиями TensorFlow 1.4.0–1.15.5 с Python 3.6. Более новая или старая версия любого из них, скорее всего, приведет к ошибкам установки или выполнения. Полный список требований к версиям для Windows, macOS и Linux см. на странице «Установка TensorFlow с помощью pip» на сайте TensorFlow.

Шаг 1 — Настройка проекта

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

Мы будем использовать виртуальную среду Python 3 для управления зависимостями нашего проекта. Создайте новый каталог для своего проекта и перейдите в новый каталог:

  1. mkdir tensorflow-demo
  2. cd tensorflow-demo

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

  1. python3 -m venv tensorflow-demo
  2. source tensorflow-demo/bin/activate

Затем установите библиотеки, которые вы будете использовать в этом руководстве. Мы будем использовать определенные версии этих библиотек, создав файл requirements.txt в каталоге проекта, в котором указаны требования и нужная нам версия. Создайте файл requirements.txt:

  1. touch requirements.txt

Откройте файл в текстовом редакторе и добавьте следующие строки, чтобы указать библиотеки Image, NumPy и TensorFlow и их версии:

requirements.txt
image==1.5.20 numpy==1.14.3 tensorflow==1.4.0

Сохраните файл и выйдите из редактора. Затем установите эти библиотеки с помощью следующей команды:

  1. pip install -r requirements.txt

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

Шаг 2 — Импорт набора данных MNIST

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

Давайте создадим программу Python для работы с этим набором данных. Мы будем использовать один файл для всей нашей работы в этом уроке. Создайте новый файл с именем main.py:

  1. touch main.py

Теперь откройте этот файл в выбранном вами текстовом редакторе и добавьте в него эту строку кода, чтобы импортировать библиотеку TensorFlow:

import tensorflow as tf

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

...
from tensorflow.examples.tutorials.mnist import input_data


mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)  # y labels are oh-encoded

При чтении данных мы используем однократное кодирование для представления меток (фактически нарисованных цифр, например, \3) изображений. Однократное кодирование использует вектор двоичные значения для представления числовых или категориальных значений. Поскольку наши метки предназначены для цифр от 0 до 9, вектор содержит десять значений, по одному для каждой возможной цифры. Одно из этих значений установлено на 1, чтобы представить цифру в этом индексе вектор, а остальные устанавливаются равными 0. Например, цифра 3 представляется с помощью вектора [0, 0, 0, 1, 0, 0, 0, 0, 0, 0]. Поскольку значение с индексом 3 хранится как 1, вектор, таким образом, представляет цифру 3.

Для представления самих изображений 28x28 пикселей сглаживаются в одномерный вектор размером 784 пикселя. Каждый из 784 пикселей, составляющих изображение, хранится как значение от 0 до 255. Это определяет оттенки серого пикселя, поскольку наши изображения представлены только в черно-белом цвете. Таким образом, черный пиксель представлен 255, а белый пиксель — 0, с различными оттенками серого где-то посередине.

Мы можем использовать переменную mnist, чтобы узнать размер только что импортированного набора данных. Глядя на num_examples для каждого из трех подмножеств, мы можем определить, что набор данных был разделен на 55 000 изображений для обучения, 5 000 для проверки и 10 000 для тестирования. Добавьте следующие строки в ваш файл:

...
n_train = mnist.train.num_examples  # 55,000
n_validation = mnist.validation.num_examples  # 5000
n_test = mnist.test.num_examples  # 10,000

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

Шаг 3 — Определение архитектуры нейронной сети

Архитектура нейронной сети относится к таким элементам, как количество слоев в сети, количество единиц в каждом слое и то, как единицы связаны между слоями. Поскольку нейронные сети частично вдохновлены работой человеческого мозга, здесь термин «единица» используется для обозначения того, что мы биологически считаем нейроном. Подобно нейронам, передающим сигналы по мозгу, блоки принимают некоторые значения от предыдущих блоков в качестве входных данных, выполняют вычисления, а затем передают новое значение в качестве вывода другим блокам. Эти единицы располагаются слоями для формирования сети, начиная как минимум с одного слоя для ввода значений и одного слоя для вывода значений. Термин скрытый слой используется для всех слоев между входным и выходным слоями, т. е. «скрытых» от реального мира.

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

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

...
n_input = 784  # input layer (28x28 pixels)
n_hidden1 = 512  # 1st hidden layer
n_hidden2 = 256  # 2nd hidden layer
n_hidden3 = 128  # 3rd hidden layer
n_output = 10  # output layer (0-9 digits)

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

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

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

...
learning_rate = 1e-4
n_iterations = 1000
batch_size = 128
dropout = 0.5

Скорость обучения показывает, насколько параметры будут корректироваться на каждом этапе процесса обучения. Эти корректировки являются ключевым компонентом обучения: после каждого прохода по сети мы немного подстраиваем веса, чтобы попытаться уменьшить потери. Более высокие скорости обучения могут сходиться быстрее, но также могут превысить оптимальные значения по мере их обновления. Количество итераций относится к тому, сколько раз мы проходим этап обучения, а размер пакета относится к тому, сколько обучающих примеров мы используем на каждом этапе. Переменная dropout представляет собой порог, при котором мы случайным образом исключаем некоторые единицы. Мы будем использовать dropout в нашем последнем скрытом слое, чтобы дать каждому юниту 50% шанс быть исключенным на каждом этапе обучения. Это помогает предотвратить переоснащение.

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

Шаг 4 — Построение графика TensorFlow

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

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

...
X = tf.placeholder("float", [None, n_input])
Y = tf.placeholder("float", [None, n_output])
keep_prob = tf.placeholder(tf.float32)

Единственный параметр, который необходимо указать при его объявлении, — это размер данных, которые мы будем вводить. Для X мы используем форму [None, 784], где None представляет любое количество, так как мы будем вводить неопределенное количество 784-пиксельных изображений. Форма Y[None, 10], так как мы будем использовать ее для неопределенного количества выходных меток с 10 возможными классами. Тензор keep_prob используется для управления частотой отсева, и мы инициализируем его как заполнитель, а не неизменяемую переменную, потому что мы хотим использовать один и тот же тензор как для обучения (когда отсев установлено значение 0,5) и тестирование (когда для параметра dropout установлено значение 1,0).

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

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

...
weights = {
    'w1': tf.Variable(tf.truncated_normal([n_input, n_hidden1], stddev=0.1)),
    'w2': tf.Variable(tf.truncated_normal([n_hidden1, n_hidden2], stddev=0.1)),
    'w3': tf.Variable(tf.truncated_normal([n_hidden2, n_hidden3], stddev=0.1)),
    'out': tf.Variable(tf.truncated_normal([n_hidden3, n_output], stddev=0.1)),
}

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

...
biases = {
    'b1': tf.Variable(tf.constant(0.1, shape=[n_hidden1])),
    'b2': tf.Variable(tf.constant(0.1, shape=[n_hidden2])),
    'b3': tf.Variable(tf.constant(0.1, shape=[n_hidden3])),
    'out': tf.Variable(tf.constant(0.1, shape=[n_output]))
}

Затем настройте слои сети, определив операции, которые будут манипулировать тензорами. Добавьте эти строки в свой файл:

...
layer_1 = tf.add(tf.matmul(X, weights['w1']), biases['b1'])
layer_2 = tf.add(tf.matmul(layer_1, weights['w2']), biases['b2'])
layer_3 = tf.add(tf.matmul(layer_2, weights['w3']), biases['b3'])
layer_drop = tf.nn.dropout(layer_3, keep_prob)
output_layer = tf.matmul(layer_3, weights['out']) + biases['out']

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

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

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

...
cross_entropy = tf.reduce_mean(
    tf.nn.softmax_cross_entropy_with_logits(
        labels=Y, logits=output_layer
        ))
train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)

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

Шаг 5 — Обучение и тестирование

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

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

...
correct_pred = tf.equal(tf.argmax(output_layer, 1), tf.argmax(Y, 1))
accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))

В correct_pred мы используем функцию arg_max, чтобы сравнить, какие изображения предсказываются правильно, просматривая output_layer (прогнозы) и Y (метки), и мы используем функцию equal, чтобы вернуть это как список логических значений. Затем мы можем преобразовать этот список в число с плавающей запятой и вычислить среднее значение, чтобы получить общую оценку точности.

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

...
init = tf.global_variables_initializer()
sess = tf.Session()
sess.run(init)

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

  • Распространять значения по сети.
  • Рассчитать убытки
  • Распространять значения обратно по сети
  • Обновите параметры

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

Добавьте этот код в файл:

...
# train on mini batches
for i in range(n_iterations):
    batch_x, batch_y = mnist.train.next_batch(batch_size)
    sess.run(train_step, feed_dict={
        X: batch_x, Y: batch_y, keep_prob: dropout
        })

    # print loss and accuracy (per minibatch)
    if i % 100 == 0:
        minibatch_loss, minibatch_accuracy = sess.run(
            [cross_entropy, accuracy],
            feed_dict={X: batch_x, Y: batch_y, keep_prob: 1.0}
            )
        print(
            "Iteration",
            str(i),
            "\t| Loss =",
            str(minibatch_loss),
            "\t| Accuracy =",
            str(minibatch_accuracy)
            )

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

После завершения обучения мы можем запустить сеанс на тестовых изображениях. На этот раз мы используем коэффициент отсева keep_prob 1,0, чтобы убедиться, что все модули активны в процессе тестирования.

Добавьте этот код в файл:

...
test_accuracy = sess.run(accuracy, feed_dict={X: mnist.test.images, Y: mnist.test.labels, keep_prob: 1.0})
print("\nAccuracy on test set:", test_accuracy)

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

  1. python main.py

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

Output
Iteration 0 | Loss = 3.67079 | Accuracy = 0.140625 Iteration 100 | Loss = 0.492122 | Accuracy = 0.84375 Iteration 200 | Loss = 0.421595 | Accuracy = 0.882812 Iteration 300 | Loss = 0.307726 | Accuracy = 0.921875 Iteration 400 | Loss = 0.392948 | Accuracy = 0.882812 Iteration 500 | Loss = 0.371461 | Accuracy = 0.90625 Iteration 600 | Loss = 0.378425 | Accuracy = 0.882812 Iteration 700 | Loss = 0.338605 | Accuracy = 0.914062 Iteration 800 | Loss = 0.379697 | Accuracy = 0.875 Iteration 900 | Loss = 0.444303 | Accuracy = 0.90625 Accuracy on test set: 0.9206

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

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

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

  1. curl -O https://raw.githubusercontent.com/do-community/tensorflow-digit-recognition/master/test_img.png

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

import numpy as np
from PIL import Image
...

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

...
img = np.invert(Image.open("test_img.png").convert('L')).ravel()

Функция open библиотеки Image загружает тестовое изображение в виде четырехмерного массива, содержащего три цветовых канала RGB и альфа-прозрачность. Это не то представление, которое мы использовали ранее при чтении набора данных с помощью TensorFlow, поэтому нам нужно будет проделать дополнительную работу, чтобы соответствовать формату.

Во-первых, мы используем функцию convert с параметром L, чтобы уменьшить представление 4D RGBA до одного цветового канала в градациях серого. Мы сохраняем это как массив numpy и инвертируем его с помощью np.invert, потому что текущая матрица представляет черный как 0 и белый как 255, тогда как нам нужно обратное. Наконец, мы вызываем ravel, чтобы сгладить массив.

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

Добавьте в файл следующий код, чтобы протестировать изображение и распечатать полученную этикетку.

...
prediction = sess.run(tf.argmax(output_layer, 1), feed_dict={X: [img]})
print ("Prediction for test image:", np.squeeze(prediction))

Функция np.squeeze вызывается для предсказания, чтобы вернуть одно целое число из массива (т. е. перейти от [2] к 2). Полученный результат показывает, что сеть распознала это изображение как цифру 2.

Output
Prediction for test image: 2

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

Заключение

В этом руководстве вы успешно обучили нейронную сеть классифицировать набор данных MNIST с точностью около 92% и протестировали ее на собственном изображении. Текущие современные исследования решают эту же проблему примерно на 99%, используя более сложные сетевые архитектуры, включающие сверточные уровни. Они используют 2D-структуру изображения для лучшего представления содержимого, в отличие от нашего метода, который сводил все пиксели в один вектор из 784 единиц. Вы можете прочитать больше об этой теме на веб-сайте MNIST.

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