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

Все нейронные сети состоят из нейронов. А если конкретней из математической модели работы настоящего нейрона. Суть этой модели в том что есть много входов ( X ) по которым поступают какие то данные ( например 1 или 0 ). У каждого сигнала\входа есть вес (сила сигнала W) . Все эти данные обрабатываются внутри нейрона, так называемая функция активации нейрона f(x) .

Из этих кубиков собираются слои : входной, скрытый , выходной. Собственно на входной слой подаються сигналы, далее они передаться на скрытый слой ( их может быть и несколько) . В скрытом слое происходит сама работа всей сети. Ну и по аналоги со скрытого слоя данные поступают на выходной. На этих выходных данных и строиться результат работы. Классификация объектов , распознавание или вероятность чего то. Важно помнить, не одна нейронная сеть не дает 100% точности так же как и мозг человека.
Пишем код.
Мы будем писать Персептрон, простая модель в который сигналы передаются с лева на право (от входного к выходному слою) и все нейроны соединены друг с другом. То есть каждый нейрон входного слоя соединен выходами со всеми нейронами скрытого слоя и т.д.
Первым делом нам надо задать число нейронов в входном,выходном и скрытом слое . Еще нужно задать коэффициент обучения (с какой скоростью будет обучаться сеть от 0 до 1) нам он потребуется позже.
def init_net(): input_nodes = 784 print('Введите число скрытых нейронов: ') hidden_nodes = int(input()) out_nodes = 10 print('Введите скорость обучения(0.5): ') lern_node = float(input()) return input_nodes, hidden_nodes, out_nodes, lern_node
Теперь создадим саму сеть. Мы создадим все связи между слоями, если точнее — силу этих связей. Для этого мы будем использовать матрицы ( двухмерные массивы) которые будут хранить эти связи. Вы же помните что каждый нейрон связен с каждым нейроном следующего слоя ( например если в скрытом слое 100 нейронов, то для каждого нейрона входного слоя будет иметься 1х100 связей и т.д.)
import numpy def creat_net(input_nodes, hidden_nodes, out_nodes,): # сознание массивов. -0.5 вычитаем что бы получить диапазон -0.5 +0.5 для весов input_hidden_w = (numpy.random.rand(hidden_nodes, input_nodes) - 0.5) hidden_out_w = (numpy.random.rand(out_nodes, hidden_nodes) - 0.5) return input_hidden_w, hidden_out_w
Numpy — это библиотека языка Python, добавляющее поддержку больших многомерных массивов и матриц, вместе с большой библиотекой высокоуровневых математических функций для операций с этими массивами.
numpy.random.rand() — создает массив заданной формы и заполняет его случайными значениями от 0 до 1.
Нейроны входного слоя просто передаю информацию поступившую на них( для них не используется функция активации) нейронам следующего слоя по связям. Если очень просто то это как устройство ввода или матрица в фотоаппарате. Далее мы эти данные умножаем на вес связи и они поступают в нейрон следующего слоя. Нейрон используя функцию активации обрабатывает эти данные. Мы будем использовать Сигмоиду. И передает дальше.

Сигмооида — это гладкая монотонная возрастающая нелинейная функция, имеющая форму буквы «S», которая часто применяется для «сглаживания» значений некоторой величины. Формула : f(x) = 1 / (1+e ** -x )
В нейронных сетях применяют не только эту функцию, а например : Гиперболический тангенс , Ступенчатая функция , Гауссова функция.
import scipy.special # библиотека scipy.special содержит сигмоиду expit() def fun_active(x): return scipy.special.expit(x)
Теперь нам нужна функция которая принимает входные данные, все это считает и выдает выходные данные сети. В ней мы будем использовать умножение матриц. Это отличный вариант дял подсчета суммы сигналов для нейронов.
Тут очень важный момент, почему матрицы ? На этой картинке простоя модель

А теперь представим эти данные в виде матрицы :

def query(input_hidden_w, hidden_out_w, inputs_list): # преобразовать список входных значений # в двухмерный массив inputs_sig = numpy.array(inputs_list, ndmin=2).T hidden_inputs = numpy.dot(input_hidden_w, inputs_sig)# умножение матриц hidden_out = fun_active(hidden_inputs) # вычисляем выходной сигнал скрытого слоя # умножение матриц выходи в веса для выходного слоя final_inputs = numpy.dot(hidden_out_w, hidden_out) final_out = fun_active(final_inputs) return final_out
T — означает транспонированная матрица, не буду вдаваться в математику. Вы можете посмотреть что это значит в интернете.
Машинное обучение.
У нас есть каркас. Но наша сеть глупа как только что родившийся ребенок. И теперь нам надо ее обучить распознавать рукописный текст. Первым делам для этого нужны входные данные( много ) а так же проверочные выходные данные с которыми мы будем сверяться в ходе обучения.
Мы возьмем готовую базу MNIST (сокращение от «Modified National Institute of Standards and Technology») — объёмная база данных образцов рукописного написания цифр. База данных является стандартом, предложенным Национальным институтом стандартов и технологий США с целью калибрации и сопоставления методов распознавания изображений с помощью машинного обучения в первую очередь на основе нейронных сетей. Качаем с нашего сайта.
Смысл обучения нейронной сети сводиться к коррекции W весов связей( силы сигнала) между нейронами. Первым делом мы вычисляем ошибку выходных данных : e1 = t1 — o1 где t1 проверочное значение для нейрона выходного слоя, o1 — фактическое значение. Что бы посчитать ошибку для нейронов скрытого слоя мы воспользуемся методом обратного распространения ошибки . Суть проста : Ошибка1 (первого нейрона скрытого слоя) = e1 (выходной) * w1.1 + e2(второй выходной нейрона) * w1.2(вес связи от него)

def treyn(targget_list,input_list, input_hidden_w, hidden_out_w, lern_node): #Прогоняем данные через сеть targgets = numpy.array(targget_list, ndmin=2).T inputs_sig = numpy.array(input_list, ndmin=2).T hidden_inputs = numpy.dot(input_hidden_w, inputs_sig) hidden_out = fun_active(hidden_inputs) final_inputs = numpy.dot(hidden_out_w, hidden_out) final_out = fun_active(final_inputs) #Рассчитываем ошибку выходного слоя out_errors = targgets - final_out #Рассчитываем ошибку скрытого слоя hidden_errors = numpy.dot(hidden_out_w.T, out_errors)
Ошибки нейронов мы посчитали, а на сколько же и как нам менять веса связей ? Не вдаваясь в подробности, будем использовать метод Градиентного спуска. То есть коррекция = lern_nodes * e выходного слой * сигмоида выходного слоя * (1 — сигмоида выходного слоя) * сигмоида скрытого слоя

# Обновление весов связей hidden_out_w += lern_node * numpy.dot((out_errors * final_out*(1 - final_out)), numpy.transpose(hidden_out)) input_hidden_w += lern_node * numpy.dot((hidden_errors * hidden_out*(1-hidden_out)), numpy.transpose(inputs_sig))
Обучающие данные.
Нас с вами интересует два файл :
- mnist_train.csv
- mnist_test.csv
Данные предоставлены в удобной текстовой форме, разделенные запятой. Тренировочный набор содержит около 60.000 промаркированных образцов, тестовый поменьше — около 10.000 .
- Первое значение это маркер от 0-9 , какая цифра изображена.
- Далее следует пиксельный массив 28х28 . Всего 784 значения от 0 до 255.
Прежде чем использовать эти данные для обучения и проверки нейронной сети, нам нужно подготовить их для работы с функцией активации. Что бы данные оставались в оптимальном диапазоне для нее. Для нас будет оптимально значение от 0,01 до 1.00 . Почему не 0 ? Нулевое значение может помешать нам обновлять весовые коэффициенты .
data_file = open('mnist_train.csv','r') trening_list = data_file.readlines() data_file.close() for record in trening_list: получить список значений, используя символы запятой (1,1) в качестве разделителей all_values = record.split(',') #масштабировать и сместить входные значения # numpy.asfarray(a,dtype=float64'>>) Возвращает массив,преобразованный в тип float. inputs = (numpy.asfarray(all_values[1:]) / 255.0 * 0.99) + 0.01 # Создаем массив из 10 элементов и в нужном элемент (считанный из первого знака базы данных) записываем проверочное значение. targets = numpy.zeros(10) + 0.01 targets[int(all_values[0])] =0.99
Наша нейросеть будет классифицировать изображения по 10 признакам , цифра от 0 до 9 . Выходные значения опять же будут в рамках нашей функции активации, фактически сигмоида не может достичь 1 , по этому мы записываем 0.99 .
Немного все сумбурно получилось. Используя эту базу данных мы будем подавать на каждый входной нейрон одно значение пикселя картинки. То есть входной слой будет у нас матрица 28х28 (784 нейрона) . А выходной слой будет содержать 10 нейронов- классификаторов. Самый интерес вопрос : сколько нейронов должно быть в скрытом слое ? Если их будет мало, сеть будет не способна распознать цифры. Если очень много сеть просто запомнит обучающие данные(зазубрит) что скажется на точности распознания. Попробуем остановиться на 100 скрытых нейронах.

input_nodes, hidden_nodes, out_nodes, lern_node = init_net() input_hidden_w, hidden_out_w = creat_net(input_nodes, hidden_nodes, out_nodes) data_file = open('mnist_train.csv','r') trening_list = data_file.readlines() data_file.close() for record in trening_list: all_values = record.split(',') #масштабировать и сместить входные значения # numpy.asfarray(a,dtype=float64'>>) Возвращает массив,преобразованный в тип float. inputs = (numpy.asfarray(all_values[1:]) / 255.0 * 0.99) + 0.01 # Создаем массив из 10 элементов и в нужном элемент (считанный из первого знака базы данных) записываем проверочное значение. targets = numpy.zeros(10) + 0.01 targets[int(all_values[0])] = 0.99 hidden_out_w, input_hidden_w = treyn(targets, inputs, input_hidden_w, hidden_out_w, lern_node) data_file = open('mnist_test.csv','r') test_list = data_file.readlines() data_file.close() test = [] for record in test_list: all_values = record.split(',') inputs = (numpy.asfarray(all_values[1:]) / 255.0 * 0.99) + 0.01 out_session = query(input_hidden_w, hidden_out_w, inputs) if int(all_values[0]) == numpy.argmax(out_session): #Если ответ верный записывает 1 в список test.append(1) else: test.append(0) test = numpy.asarray(test) # Сумму делим на размер массива и вычисляем эффективность сети print('Эффективность сети % =', (test.sum()/test.size)*100)
Все, дальше вы можете поиграться с числом нейронов скрытого слоя, с коэффициентом обучения, попробовать несколько раз обучать сеть на тех же данных, добавить еще один скрытый слой и т.д. В общем успехов.
Код полностью.
import numpy import scipy.special # pip install scipy def init_net(): input_nodes = 784 print('Введите число скрытых нейронов: ') hidden_nodes = int(input()) out_nodes = 10 print('Введите скорость обучения(0.5): ') lern_node = float(input()) return input_nodes, hidden_nodes, out_nodes, lern_node def creat_net(input_nodes, hidden_nodes, out_nodes,): # сознание массивов. -0.5 вычитаем что бы получить диапазон -0.5 +0.5 для весов input_hidden_w = (numpy.random.rand(hidden_nodes, input_nodes) - 0.5) hidden_out_w = (numpy.random.rand(out_nodes, hidden_nodes) - 0.5) return input_hidden_w, hidden_out_w def fun_active(x): return scipy.special.expit(x) def query(input_hidden_w, hidden_out_w, inputs_list): # преобразовать список входных значений # в двухмерный массив inputs_sig = numpy.array(inputs_list, ndmin=2).T hidden_inputs = numpy.dot(input_hidden_w, inputs_sig) # умножение матриц hidden_out = fun_active(hidden_inputs) # вычисляем выходной сигнал скрытого слоя # умножение матриц выходи в веса для выходного слоя final_inputs = numpy.dot(hidden_out_w, hidden_out) final_out = fun_active(final_inputs) return final_out def treyn(targget_list,input_list, input_hidden_w, hidden_out_w, lern_node): #Прогоняем данные через сеть targgets = numpy.array(targget_list, ndmin=2).T inputs_sig = numpy.array(input_list, ndmin=2).T hidden_inputs = numpy.dot(input_hidden_w, inputs_sig) hidden_out = fun_active(hidden_inputs) final_inputs = numpy.dot(hidden_out_w, hidden_out) final_out = fun_active(final_inputs) #Рассчитываем ошибку выходного слоя out_errors = targgets - final_out #Рассчитываем ошибку скрытого слоя hidden_errors = numpy.dot(hidden_out_w.T, out_errors) # Обновление весов связей hidden_out_w += lern_node * numpy.dot((out_errors * final_out * (1 - final_out)), numpy.transpose(hidden_out)) input_hidden_w += lern_node * numpy.dot((hidden_errors * hidden_out * (1 - hidden_out)),numpy.transpose(inputs_sig)) return hidden_out_w, input_hidden_w def test_set(hidden_out_w, input_hidden_w): data_file = open('mnist_train.csv', 'r') trening_list = data_file.readlines() data_file.close() for record in trening_list: all_values = record.split(',') # масштабировать и сместить входные значения # numpy.asfarray(a,dtype=float64'>>) Возвращает массив,преобразованный в тип float. inputs = (numpy.asfarray(all_values[1:]) / 255.0 * 0.99) + 0.01 # Создаем массив из 10 элементов и в нужном элемент (считанный из первого знака базы данных) записываем проверочное значение. targets = numpy.zeros(10) + 0.01 targets[int(all_values[0])] = 0.99 hidden_out_w, input_hidden_w = treyn(targets, inputs, input_hidden_w, hidden_out_w, lern_node) data_file = open('mnist_test.csv', 'r') test_list = data_file.readlines() data_file.close() test = [] for record in test_list: all_values = record.split(',') inputs = (numpy.asfarray(all_values[1:]) / 255.0 * 0.99) + 0.01 out_session = query(input_hidden_w, hidden_out_w, inputs) if int(all_values[0]) == numpy.argmax(out_session): test.append(1) else: test.append(0) print(len(test)) test = numpy.asarray(test) print('Эфективность сети % =', (test.sum() / test.size) * 100) return hidden_out_w, input_hidden_w input_nodes, hidden_nodes, out_nodes, lern_node = init_net() input_hidden_w, hidden_out_w = creat_net(input_nodes, hidden_nodes, out_nodes) for i in range (5): print('Test #', i+1) hidden_out_w, input_hidden_w = test_set(hidden_out_w, input_hidden_w)
Автор безграмотный долбозвон. Веб обезъянка.
Голос народа )) Я могу только предложить вам написать вашу версию статьи на страницах сайта , не сомненно она будет лучше )))
В начале пункта «Обучающие данные ошибка: указаны два одинаковых файла. Также неплохо было бы добавить пример заполнения таблицы для особо одарённых вроде меня
Спасибо , исправил опечатку. Про какую таблицу вы говорите ? Если честно не очень понял. Можно написать в обратной связи , постараюсь добавить )))
Вот в функции идет присвоение переменной data_file файла mnist_train.csv (назвал его таблицей ошибочно, потому что только мельком ознакомился с расширением .csv)
def test_set(hidden_out_w, input_hidden_w):
data_file = open(‘mnist_train.csv’, ‘r’)
trening_list = data_file.readlines()
data_file.close()
Так же по аналогии с этой строкой
data_file = open(‘mnist_test.csv’, ‘r’)
Я в принципе этот момент не особо понял. Мы просто берём какой-то файл, ничего в него не записываем, так как он уже заполнен, и открываем его? Объясните, пожалуйста.
Тут мы открываем файл . Читаем его построчно. По сути формат csv это текстовые файл где данные разделенные между собой запятой ( «,»). В этом файле каждая строка это картинка, а точнее число (яркости пикселя) разделенные запятой :
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,18,18,18,126,136,175,26,166,255,247,127,0,0,0,0,0,0,0,0,0,0,0,0,30,36,94,154,170,253,253,253,253,253,225,172,253,242,19
Первое число в строке индекс ( какое число изображено на картинке) . Собственно потом мы в цикле из каждой строки делаем массив с числами ( саму картинку)…
data_file = open(‘mnist_train.csv’, ‘r’) Тут мы открываем файл для чтения. data_file это переменная метода.
trening_list = data_file.readlines() Здесь мы построчно записываем прочитанные строки в список.
data_file.close() Закрываем файл.
Надеюсь я смог объяснить ваш вопрос.
Здравствуйте, спасибо за статью! А как применить этот код к собственным изображениям?
Для ответа на это вопрос можно написать еще целую статью. Я бы вам рекомендовал пользоваться уже готовой библиотекой Keres. На сайте есть цикл статей, она популярная и думаю в инете найдутся ответы на все вопросы.
Здравствуйте статья хорошая но можете подсказать где найти файлы:mnist_train.csv
mnist_test.csv
Скачать с нашего сайта.. Ссылка есть в статье : https://линуксблог.рф/down/mnist_test.zip
Спасибо!
Спасибо за статью! Но могли бы ли вы подсказать из каких соображений берётся данная формула для метода градиентного спуска?
Формула бралась из книги по нейросетям. Я не очень силен в вышей математики. Если интересно можете почитать например тут http://mechanoid.kiev.ua/neural-net-backprop2.html
Спасибо!
Привет! я только начинаю разбираться в нейросетях. До этого на питоне не писала,поэтому не помню всех значений ошибок, как исправить эту?:no module named numpy
не прописалось: до этого долго на питоне не писала(около 1-1,5 лет)
У вас не стати библиотека для работы с массивами Numpy скорей всего.
Очень интересно, но для меня ничего не понятно. Я далек по программирования. Но ищу способы создания нейронки для распознавания рукописного текста. С ее помощью хочу оцифровать архивные документы в которых можно находить информацию о наших предках вглубь на 300 лет. Может кто хочет поучаствовать в этом проекте? mbg3@mail.ru
а на какой версии питона этот код писался?
Это python 3. Точнее не помню уже, возможно 3.5….
Название статьи неверное. Нейронка распознает только цифры.