Так получилось, что в университете тема нейросетей успешно прошла мимо моей специальности, несмотря на огромный интерес с моей стороны. Попытки самообразования несколько раз разбивались невежественным челом о несокрушимые стены цитадели науки в облике непонятных «с наскока» терминов и путанных объяснений сухим языком вузовских учебников.
В данной статье (цикле статей?) я попытаюсь осветить тему нейросетей с точки зрения человека непосвященного, простым языком, на простых примерах, раскладывая все по полочкам, а не «массив нейронов образует перцептрон, работающий по известной, зарекомендовавшей себя схеме».
Заинтересовавшихся прошу под кат.
Цели
Для чего же нужны нейросети?
Нейросеть – это обучаемая система. Она действует не только в соответствии с заданным алгоритмом и формулами, но и на основании прошлого опыта. Этакий ребенок, который с каждым разом складывает пазл, делая все меньше ошибок.
И, как принято писать у модных авторов – нейросеть состоит из нейронов.
Тут нужно сделать остановку и разобраться.
Договоримся, что нейрон – это просто некая воображаемая чёрная коробка, у которой кучка входных отверстий и одно выходное.
Причем как входящая, так и исходящая информация может быть аналоговой (чаще всего так и будет).
Как выходной сигнал формируется из кучи входных – определяет внутренний алгоритм нейрона.
Для примера напишем небольшую программу, которая будет распознавать простые изображения, скажем, буквы русского языка на растровых изображениях.
Условимся, что в исходном состоянии наша система будет иметь «пустую» память, т.е. этакий новорожденный
Для того чтобы заставить его корректно работать, нам нужно будет потратить время на обучение.
Уворачиваясь от летящих в меня помидоров, скажу, что писать будем на Delphi (на момент написания статьи была под рукой). Если возникнет необходимость – помогу перевести пример на другие языки.
Также прошу легкомысленно отнестись к качеству кода – программа писалась за час, просто чтобы разобраться с темой, для серьезных задач такой код вряд ли применим.
Итак, исходя из поставленной задачи — сколько вариантов выхода может быть? Правильно, столько, сколько букв мы будем уметь определять. В алфавите и пока только 33, на том и остановимся.
Далее, определимся со входными данными.Чтобы слишком не заморачиватсья – будем подавать на вход битовый массив 30х30 в виде растрового изображения:
В итоге – нужно создать 33 нейрона, у каждого из которых будет 30х30=900 входов.
Создадим класс для нашего нейрона:
type
Neuron = class
name: string; // Тут название нейрона – буква, с которой он ассоциируется
input: array[0..29,0..29] of integer; // Тут входной массив 30х30
output:integer; // Сюда он будет говорить, что решил
memory:array[0..29,0..29] of integer; // Тут он будет хранить опыт о предыдущем опыте
end;
Создадим массив нейронов, по количеству букв:
For i:=0 to 32 do begin
neuro_web[i]:=Neuron.Create;
neuro_web[i].output:=0; // Пусть пока молчит
neuro_web[i].name:=chr(Ord('A')+i); // Буквы от А до Я
end;
Теперь вопрос – где мы будем хранить «память» нейросети, когда программа не работает?
Чтобы не углубляться в INI или, не дай бог, базы данных, я решил хранить их в тех же растровых изображениях 30х30.
Вот например, память нейрона «К» после прогона программы по разным шрифтам:
Как видно, самые насыщенные области соответствуют наиболее часто встречаемым пикселям.
Будем загружать «память» в каждый нейрон при его создании:
p:=TBitmap.Create;
p.LoadFromFile(ExtractFilePath(Application.ExeName)+'res'+ neuro_web[i].name+'.bmp')
В начале работы необученной программы, память каждого нейрона будет белым пятном 30х30.
Распознавать нейрон будет так:
— Берем 1й пиксель
— Сравниваем его с 1м пикселем в памяти (там лежит значение 0..255)
— Сравниваем разницу с неким порогом
— Если разница меньше порога – считаем, что в данной точке буква похожа на лежащую в памяти, добавляем +1 к весу нейрона.
И так по всем пикселям.
Вес нейрона – это некоторое число (в теории до 900), которое определяется степенью сходства обработанной информации с хранимой в памяти.
В конце распознавания у нас будет набор нейронов, каждый из которых считает, что он прав на сколько-то процентов. Эти проценты – и есть вес нейрона. Чем больше вес, тем вероятнее, что именно этот нейрон прав.
Теперь будем скармливать программе произвольное изображение и пробегать каждым нейроном по нему:
for x:=0 to 29 do
for y:=0 to 29 do begin
n:=neuro_web[i].memory[x,y];
m:=neuro_web[i].input[x,y];
if ((abs(m-n)<120)) then // Порог разницы цвета
if m<250 then neuro_web[i].weight:=neuro_web[i].weight+1; // Кроме того, не будем учитывать белые пиксели, чтобы не получать лишних баллов в весах
if m<>0 then begin
if m<250 then n:=round((n+(n+m)/2)/2);
neuro_web[i].memory[x,y]:=n; end
else if n<>0 then
if m<250 then n:=round((n+(n+m)/2)/2);
neuro_web[i].memory[x,y]:=n;
end;
Как только закончится цикл для последнего нейрона – будем выбирать из всех тот, у которого вес больше:
if neuro_web[i].weight>max then begin
max:=neuro_web[i].weight;
max_n:=i;
end;
Именно по вот этому значению max_n, программа и скажет нам, что, по её мнению, мы ей подсунули.
По началу это будет не всегда верно, поэтому нужно сделать алгоритм обучения.
s:=InputBox('Enter the letter', ‘программа считает, что это буква ’+neuro_web[max_n].name, neuro_web[max_n].name);
for i:=0 to 32 do begin //Пробегаем по нейронам
if neuro_web[i].name=s then begin //В нужном нейроне обновляем память
for x:=0 to 29 do begin
for y:=0 to 29 do begin
p.Canvas.Pixels[x,y]:=RGB(neuro_web[i].memory[x,y],neuro_web[i].memory[x,y], neuro_web[i].memory[x,y]); //Записываем новое значение пикселя памяти
end;
end;
p.SaveToFile(ExtractFilePath(Application.ExeName)+'res'+ neuro_web[i].name+'.bmp');
Само обновление памяти будем делать так:
n:=round(n+(n+m)/2);
Т.е. если данная точка в памяти нейрона отсутствует, но учитель говорит, что она есть в этой букве – мы её запоминаем, но не полностью, а только наполовину. С дальнейшим обучением, степень влияния данного урока будет увеличиваться.
Вот несколько итераций для буквы Г:
На этом наша программа готова.
Обучение
Начнем обучение.
Открываем изображения букв и терпеливо указываем программе на её ошибки:
Через некоторое время программа начнет стабильно определять даже не знакомые ей ранее буквы:
Заключение
Программа представляет собой один сплошной недостаток – наша нейросеть очень глупа, она не защищена от ошибок пользователя при обучении и алгоритмы распознавания просты как палка.
Зато она дает базовые знания о функционировании нейросетей.
Если данная статья заинтересует уважаемых хабравчан, то я продолжу цикл, постепенно усложняя систему, вводя дополнительные связи и веса, рассмотрю какую-нибудь из популярных архитектур нейросетей и т.д.
Поиздеваться над нашим свежерожденный интеллектом вы можете, скачав программу вместе с исходниками тут.
За сим откланяюсь, спасибо за чтение.
Автор: Paul_Smith
Спасибо! Коротко и ясно.