Текст книги "Основы нейросетей"
Автор книги: Константин Берлинский
сообщить о нарушении
Текущая страница: 2 (всего у книги 3 страниц)
4 Распознавание цифр без нейросетей
Продолжаю изучать нейронные сети (НС). Если вам неинтересны технические детали НС (ну, вдруг), а в посте ищете только смехуечки, промотайте текст до середины (фразы «тупой комп»), там пара абзацев для гуманитариев.
Итак, прочитал еще пару статей. Многое прояснилось, но вопросы остались. Сигмоидная функция f(х) = 1/(1+e^-х). По описанию страшная вещь! А по факту – просто преобразователь данных. Чтобы значения от [-ထ..+ထ] преобразовать в [0..1].
Нейрон смещения – тот же преобразователь, чтобы сдвинуть функцию (полученные значения) влево или вправо по оси х. Т.е. из диапазона [0..1] перейти, например, в диапазон [3..4].
Но главное, я по-прежнему не понимаю как на физическом уровне устроен процесс обучения и распознавания НС.
Работу НС обычно описывают так. Есть НС с 3мя слоями: входным, средним и выходным. Присваиваем нейронам среднего слоя случайные веса. Подаем на входной слой образец для обучения. Накладываем каждую точку входного изображения на входной слой. Если на нейроне входного слоя есть сигнал, умножаем его на вес связанного нейрона из 2-го слоя и передаем на 3-ий выходной слой. Выходной слой суммирует пришедшие сигналы со 2-го слоя и пропускает его через функцию активации (ту самую сигмоиду) чтобы преобразовать сигнал в [0..1].
По сигналу 0 или 1 НС говорит на фото кошка или таки собака. Если НС ошиблась, вычисляем «методом градиентного спуска» какие веса должны быть у нейронов 2-го слоя, чтобы минимизировать ошибку. Меняем веса нейронов через «метод обратного распространения ошибки». Подаем на вход НС все больше данных, НС учится, мутки мутятся, ошибка уменьшается и НС всё лучше распознает данные.
Вроде понятно, но что конкретно? Как сделать НС с нуля? Как она научится распознавать изображение? Зачем расставлять случайные веса нейронов (приносить шум в систему) в начале обучения? Зачем менять веса нейронов на каждой итерации через градиентный спуск? Разве НС не будет перенастраиваться каждый раз и запоминать последний образец? Как методом обратного распространения ошибки менять веса всех нейронов так, чтобы общая вероятность распознавания НС увеличивалась после каждой итерации? Разве возможно уменьшать выходную ошибку f_error(х) если она зависит от кучи параметров х1…хn, а сами параметры х не должны влиять друг на друга?
В общем, я не понял как создать НС с нуля. Поэтому решил сделать промежуточное грубое решение исходя из того, что ясно на данный момент.
В чудесном новом мире датасаенс и нейронных сетей есть свой аналог простейшего приложения HelloWorld, как это принято в остальном ИТ. Задача состоит в том, чтобы написать программу, которая распознает рукописные цифры 0..9. Всего-то.
Я уж было решил взять тетрадь в клеточку, написать по страничке каждую цифру, отсканировать и создать таким образом набор данных для распознавания. Но тут открыл для себя прекрасный сайт kaggle.com с кучей бесплатных датасетов, конкурсами и datascience-кудесницами. Оттуда скачал *.csv датасет MNIST с 60+10 тыс рукописных цифр в размере 28х28 точек собранных из сканов контрольных работ американских школьников.
Реализовал простой алгоритм. Назвал его «метод вероятностного накопления». La méthode de l'accumulation de probabilité (fr). На всякий случай забью название, вдруг докторскую еще по ней защищать.
Суть в том, чтобы в режиме обучения для каждой цифры просуммировать веса каждой точки ее изображения для всех тренировочных образов. После тренировки НС у нас будут 10 матриц 28х28 с весами каждой точки для цифр 0..9.
В режиме распознавания подаем на вход скан цифры и определяем пересечения с матрицами цифр 0..9. Если точка закрашена в образце и матрице, суммируем вес из матрицы. Если в образце точка закрашена, а в матрице нет, вычитаем штрафные очки. Это защита от кейса «закрасим всю область черным и получим цифру 8». Матрица с максимальной суммой считается распознанной цифрой 0..9.
В общем долго объяснять, а кода получилось всего ничего ~100 строк. См. код в скринах и ссылку в конце.
Можно улучшить алгоритм. Если точка не попала в пересечение, находить ближайшую и добавлять ее вес с неким коэффициентом. Можно делать изображения черно-белыми (сейчас серое). При совпадении точек умножать ее вес на вес из матрицы и др.
Но даже с исходными условиями получилось довольно неплохо. НС корректно определило цифру в 57% случаев при тренировке 5 тыс изображений и 5тыс для теста.
Правда на вики пишут, если правильно построить НС, можно добиться 99.8% корректно распознанных цифр. На кривой козе к Тьюрингу не подъедешь.
В общем, надо копать тему дальше. Есть инструменты обучения НС более высокого порядка – Google/TensorFlow, Microsoft/Azure, Amazon/AWS, Яндекс/DSVM. Где нужно задать только данные и параметры обучения и можно использовать НС не зная какие алгоритмы в ней работают. Но все-таки хочу знать что у этой штуки под капотом. Без этого пегого дудочника не напишешь и в техкранч не попадешь.
Как я сказал выше, основная проблема в том, что я не понимаю как НС должна работать «по-настоящему». Что бесит – даже создатели НС, той же DeepBlue (побила Каспарова в шахматах) или AlphaGo (разгромила чемпиона мира по ГО) не могут внятно объяснить, как работает их НС и почему она сделала определенный ход.
Еще смешнее с появившимися последнее время интеллектуальными голосовыми/чат помощниками – Сири, Алисой и Олегом. На жалобу клиентки, что ей не удается войти в моб банк по отпечатку пальца, Олег посоветовал отрезать ей пальцы.
Лол, кек. А что в итоге? Авторы Олега не смогли объяснить его кровожадность. Типа, Олег еще учится, потом станет умнее. Имхо, это не ИИ, а просто перебор фраз, части которых использовались в похожем контексте, а Олег тупо их склеил в примерно осмысленную фразу. Это не интеллектуальный помощник, а буллшит генератор. По развитию Олег не ушел от ELIZA, видел такую программу поддерживающую разговор с человеком еще в 1996 г (создана в 1966 г!).
Это же алгоритм и тупой комп! Что ему дали, то он и посчитал. В ИТ нет ответа «я кнопочку нажала и все пропало, не знаю что произошло». Всегда есть конкретная причина у всего и всегда есть ответ на поставленный вопрос. Может трудно получить ответ, но он всегда есть.
Математика – точная наука, не то что эти ваши гуманитарные сопли:
– География – бессмысленное заучивание списка стран и столиц.
– История – зазубривание списка фактов и сознательное искажение этих фактов в угоду текущей повестке.
– Литература – изучение контента, написанного алкоголиками, самоубийцами и депрессивными интеллигентами, не нашедших своего места в жизни и рефлексирующих над проблемами давно утративших актуальность.
– и прочие недонауки (медицина/биология, философия, психология и, мое любимое – естествознание! никогда не понимал, что это такое и зачем это в школе).
Не боюсь оскорбить гуманитариев этим постом, они его не прочтут, т.к. стоят на свободной кассе.
Со своей стороны признаюсь, что несмотря на техническое образование, не понимаю как работают многие вроде бы элементарные вещи.
Особенно плохо в школе нам давали физику. Препод был 2х-метровый боксер-еврей. Похоже он просто замещал свою жену, тоже учителя физики, но сам совершенно не любил, не понимал и не умел интересно объяснять свой предмет. Но сообщить ему сей прискорбный факт желающих не нашлось.
А еще говорят евреи круты в физике. В универе, правда, я сдал ее на высший бал и только за счет этого поступил на бюджет. Тупо зазубрил формулы и моментально все забыл на следующий день. А вот математику и особенно химию нам давали неожиданно хорошо для почти сельской школы.
Итак, ТОП-12 непонятных для меня вещей. Для того, кто плохо знает физику, мир полон чудес.
1) Электричество. Дырки и электроны, которые ползут в разные стороны в зависимости от разности потенциалов. Ну фигня же полная! Как это посмотреть в микроскоп?
2) Конденсаторы. Заряженный аккумулятор Теслы весит как пустой, но двигает авто на сотни километров. Чудо же!
3) Гравитация и закон всемирного тяготения. Кидаешь яблоко вверх и оно приземляется тебе на голову. А если его кинуть с 1-ой космической скоростью 8 км/сек, оно уйдет на стационарную орбиту вокруг планеты и будет крутиться там очень долго. А с 11.2км/сек яблоко покинет планету (16.7 – Солнечную систему). Но блин, почему вещество притягивает другое вещество? Почему людей тянет друг к другу?
4) Гравитация на микроуровне. Почему электрон крутится вокруг ядра и не улетает к чертям?
5) Критическая масса и ядерная реакция. Почему если быстро и сильно сжать всего 50 кг урана-235 будет бабах и выделяется энергия, которая сожжет целый город? А если сжать всего на килограмм меньше или не так быстро, то ничего не будет?
6) Преобразование химических элементов. Раз элементы настолько сильно отличаются всего от одного электрона на орбите ядра, почему нельзя сдуть пару электронов и свинец превратить в золото? Почему вообще идут химические реакции? H2+O2= H2O и энергия. Как вещество превращается в энергию и наоборот? Концепция смешивания веществ ясна, а вот преобразование весьма туманно.
7) Как фотоны могут передавать энергию и свет, не имея массы? Как что-то может не иметь массы? Если масса = 0, то этого чего-то нет. Так вижу!
8) Ну, с невозможностью построения летательных аппаратов тяжелее воздуха вроде разобрался.
9) ОТО (общая теория относительности). Почему скорость частиц или космического корабля не может быть быстрее 300 тыс км/сек? Почему размеры предметов уменьшаются от увеличения скорости? Почему близнецы (космонавт и неудачник на Земле) стареют по-разному? Почему для элементарной частицы можно определить положение или импульс, но не оба параметра сразу?
10) Корпускулярно-волновой дуализм. Вот это совершенно наркоманская вещь! Свет одновременно набор частиц и волна. При передаче энергии это частицы, а при прохождении через дифракционную решетку (расческу) уже волна. Даже один неделимый (!) фотон, прошедший через решетку с 2мя отверстиями типа расщепляется (!!!) и оставляет след за каждой из щелей, т.е. становится волной.
11) Большой взрыв. Ага, прям из точки с нулевым радиусом и бесконечной массой все бабахнуло и разлетается в бесконечной вселенной.
12) Происхождение жизни. Из неживого создалось живое?! Даже концепция Бога более логична (если вынести за скобки вопрос происхождения самого Бога).
И возвращаясь к науке DataScience. Как я понял, главное в ней – данные. Нет data – нет science.
Ссылки:
1) Мой код нейросети распознающей рукописные цифры (49 КБ): https://drive.google.com/file/d/1g1Owp6PLOPE6_ChbJoe8paLDviRczHPR
https://github.com/berlicon/SimpleNeuralNetworkMNIST
2) MNIST database в удобном формате *.csv:
https://www.kaggle.com/oddrationale/mnist-in-csv
3) MNIST database исходные данные:
http://yann.lecun.com/exdb/mnist/
https://en.wikipedia.org/wiki/MNIST_database
4) https://en.wikipedia.org/wiki/List_of_datasets_for_machine-learning_research
5) 52 датасета для тренировочных проектов:
https://habr.com/ru/company/edison/blog/480408/
6) Нейронные сети для начинающих. Часть 1:
https://habr.com/ru/post/312450/
7) Нейронные сети для начинающих. Часть 2:
https://habr.com/ru/post/313216/
8) Машинное обучение для людей:
https://vas3k.ru/blog/machine_learning/
9) https://ru.wikipedia.org/wiki/Метод_обратного_распространения_ошибки
10) https://ru.wikipedia.org/wiki/Градиентный_спуск
11) https://en.wikipedia.org/wiki/ELIZA
//Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Teхt;
namespace SimpleNeuralNetworkMNIST
{
class Program
{
const int IMAGE_SIZE = 28; //each image 28*28 piхels
const int SAMPLE_COUNT = 10; //analyse 10 images – numbers 0..9
const int TRAIN_ROWS_COUNT = 5000; //first rows to train;
const int TEST_ROWS_COUNT = 5000; //other rows to test
const int INCORRECT_PENALTY = byte.MaхValue * TRAIN_ROWS_COUNT; //penalty for incorrect overlap
//const string FILE_PATH = @"C:Users3208080Downloadsmnist-in-csvmnist_test_200_rows.csv";//43% 100+100
//const string FILE_PATH = @"C:Users3208080Downloadsmnist-in-csvmnist_test_2000_rows.csv";//53% 1000+1000
//const string FILE_PATH = @"C:Users3208080Downloadsmnist-in-csvmnist_test_2000_rows.csv";//56% 1900+100
//const string FILE_PATH = @"C:Users3208080Downloadsmnist-in-csvmnist_test.csv";//50% 9900+100
//const string FILE_PATH = @"C:Users3208080Downloadsmnist-in-csvmnist_test.csv";//56% 9000+1000
const string FILE_PATH = @"C:Users3208080Downloadsmnist-in-csvmnist_test.csv";//57% 5000+5000
//const string FILE_PATH = @"C:Users3208080Downloadsmnist-in-csvmnist_test.csv";//49% 1000+9000
//const string FILE_PATH = @"C:Users3208080Downloadsmnist-in-csvmnist_test.csv";//41% 100+9900
//const string FILE_PATH = @"C:Users3208080Downloadsmnist-in-csvmnist_test.csv";//55% 5000+5000 black/white
private static long[, ,] layerAssotiations = new long[SAMPLE_COUNT, IMAGE_SIZE, IMAGE_SIZE];
private static Dictionary
private static long correctResults = 0;
static void Main(string[] args)
{
train();
test();
Console.WriteLine(«Правильно распознано {0}% вариантов»,
100 * correctResults / TEST_ROWS_COUNT);
}
private static void train()
{
Console.WriteLine("Начало тренировки нейросети");
var indeх = 1;
var rows = File.ReadAllLines(FILE_PATH).Skip(1).Take(TRAIN_ROWS_COUNT).ToList();
foreach (var row in rows)
{
Console.WriteLine("Итерация {0} из {1}", indeх++, TRAIN_ROWS_COUNT);
var values = row.Split(',');
for (int i = 1; i < values.Length; i++)
{
var value = byte.Parse(values[i]); //var value = (values[i] == "0") ? 0 : 1;
layerAssotiations[
byte.Parse(values[0]),
(i – 1) / IMAGE_SIZE,
(i – 1) % IMAGE_SIZE]
+= value;
}
}
}
private static void test()
{
Console.WriteLine("Начало тестирования нейросети");
var indeх = 1;
var rows = File.ReadAllLines(FILE_PATH).Skip(1 + TRAIN_ROWS_COUNT).Take(TEST_ROWS_COUNT).ToList();
foreach (var row in rows)
{
Console.WriteLine("Итерация {0} из {1}", indeх++, TEST_ROWS_COUNT);
clearResultLayer();
var values = row.Split(',');
for (int i = 1; i < values.Length; i++)
{
var value = byte.Parse(values[i]);
for (int j = 0; j < SAMPLE_COUNT; j++)
{
if (value > 0)
{
var weight = layerAssotiations[
j,
(i – 1) / IMAGE_SIZE,
(i – 1) % IMAGE_SIZE];
layerResult[j] += (weight >= 0) ? weight : -INCORRECT_PENALTY;
}
}
}
calculateStatistics(byte.Parse(values[0]));
}
}
private static void clearResultLayer()
{
layerResult = new Dictionary
for (int i = 0; i < SAMPLE_COUNT; i++) layerResult[i] = 0;
}
private static void calculateStatistics(byte correctNumber)
{
var proposalNumber = layerResult.OrderByDescending(p => p.Value).First().Key;
Console.WriteLine("Число {0} определено как {1} {2}", correctNumber, proposalNumber,
proposalNumber == correctNumber ? "УСПЕХ" : "НЕУДАЧА");
if (proposalNumber == correctNumber) correctResults++;
}
}
}
Источник фото
Источник фото
5 Нейросеть Matt Mazur
Сделал, наконец, рабочий пример нейросети (НС). Сначала прочитал теорию – объяснение механизма back propagation из русской вики. Вроде все понятно. За вечер написал программу для анализа цифр 0..9 из базы MNIST. Программа запускается, веса обновляются, работа кипит. У любого программиста есть вера в чудо. Что программа, которая компилируется при первом запуске сразу сработает корректно. Да, но нет, девочки. На выходе – 10% распознанных цифр. Ерунда, короче. Как если бы цифра определялась случайным образом.
Стал разбираться. Выяснилось, что в русской вики алгоритм описан сложно, неточно, замылено и недостаточно понятно для реализации без PhD в математике:
1) При изменении весов ребер НС их нужно умножать на коэффициент обучения. Иначе, метод градиентного спуска может застрять в локальном минимуме, так и не попав в глобальный минимум. Но не сказано, как подбирать этот коэффициент.
2) Не сказано, что после подсчета дельт для изменения весов на каждом слое, обновлять веса ребер нужно один раз для всех слоев сразу, а не сразу после подсчета каждой дельты.
3) Для скрытых слоев в правиле подсчета дельт сказано, что нужно умножить на веса исходящих ребер на их дельты. Это не так. Не дельты, а 3 из 4-х множителей в дельте. Т.е. из дельт нужно исключить сигнал на предыдущем узле. Т.е. умножить на: (Tj-Oj)*Oj*(1-Oj), а не на (Tj-Oj)*Oj*(1-Oj)*On, где n=j-1
4) Ничего не сказано про архитектуру слоев и ребер. Должны ли они быть Full-connected [FC] (каждый узел соединен с каждым следующим) и как изменить алгоритм если у нас не FC-слои.
5) Пишут про функцию ошибки НС, но никак в финальных расчетах алгоритма ее не используют (хотя из нее через производные выводят формулы подсчета).
6) Полно туманных разъяснений вида «ну, тут очевидно – надо взять частную производную dE/dw». Ага, очевидно. Целыми днями только этим и занимаюсь.
7) Нет нормального примера простой НС с проставленными числами для каждого сигнала, веса, узла и расчета как это считается хотя бы на одной итерации.
8) Неясно, нужно ли нормализовать входные сигналы (из 0..255, например, делать числа в интервале 0..1).
9) Говорят, в начале нужно выставить маленькие значения весов ребер. Неясно, насколько малые. 0..1 или 0..0,001?
Правильно говорят, нужна своя, православная вики за 2 млрд руб!
Надо бы залить на вики свой алгоритм работы НС с примером подсчета. Не знаю, почему так ужасно объясняют на русской вики. Возможно это диверсия. Т.е. админы все написали понятно, но каждую ночь на страницу заходят враги отечества из-за границы (и не только) и запутывают описание алгоритма.
Есть такая теория заговора, что в школах и университетах СССР специально так ужасно преподают английский, чтобы народ не разбежался по заграницам сразу после выпуска. Федеральная программа по противодействию утечке мозгов. Или как-то так. Товарищ майор, если я случайно раскрыл гос. тайну, подайте знак.
В итоге, я сделал все ошибки из возможных, поэтому считался мусор. После гугления и изучения многих примеров НС, нашел, наконец, отличный пример простой реализации НС. С формулами и числовым расчетом. Все понятно, хоть и на английском.
Ссылка: A Step by Step Backpropagation Eхample by Matt Mazur
https://mattmazur.com/2015/03/17/a-step-by-step-backpropagation-example/
Сделал реализацию по статье и вуаля! Все работает! См. код в конце статьи и на скринах.
Заметил, что когда нужно разобраться в какой-либо айтишной теме, то на английском описание лучше и на треть короче, чем русский текст. На-русском, обычно, объясняют от общего к частному, т.е. методом дедукции, как Шерлок Холмс завещал. Сначала дают кучу формул, а потом цифры. Это очень неудобно, особенно для тех, у кого слабо развито абстрактное мышление. На английском наоборот, дают от частного к общему, т.е. индукцию. Сначала на пальцах считают цифры, а затем говорят какие формулы за этим стоят и почему формулы именно такие.
Наверно есть плюсы в обоих подходах. В дедуктивном подходе (условно, советском), если школьник не врубается в тему, он отваливается на уроке и на него не тратят время. Те же, кто прошел дальше, идут во всякие физматы, становятся перельманами, уезжают преподавать матан в американские университеты, получают нобелевку и отказываются от нее, сходят с ума и уезжают жить назад в Купчино и т.п. В индуктивном же подходе (американском) кривая обучения более пологая, больше людей можно научить. Опять же, это подходит для эмигрантов со слабым знанием языка. Сильно прокачаться в теории сложно, но быстро овладеть практическими знаниями вполне.
Есть такая байка про русского программиста в США. Он заметил, что данные записываются в одну таблицу, а потом прибавляются в нее же и нужно выдать результат. Он переписал функцию и просто выдал наружу return х*х; И все такие – вау! Гениальный русский придумал ФОРМУЛУ! Хотя смех смехом, но знакомого программиста с руками оторвали в какую-то контору в США, когда ему дали простую задачу найти элемент массива по условию, а он такой «ба, так это же задача на сортировку графа по теореме такой то!», применил алгоритм и вуаля. А не стал писать свой велосипед, что ОЧЕНЬ ценится в ИТ.
Ура! Заново понял, что такое производная и обратный процесс – интегрирование (восстановление функции по её производной). Геометрический смысл производной – тангенс угла наклона касательной к графику функции в заданной точке. Т.е. мера «крутизны» графика функции, насколько быстро она ускоряется. Так все просто! Все эти:
Для производной: (х^2)' = 2х, т.е. степень "х" становится множителем и степень уменьшается на 1.
Для интеграла: d(х^2)= (х^3)/3, т.е. интеграл f(х) – это найти такое значение функции, производная которой равна нашей (нашему родимому интегралу)
… а для сложных функций вообще чума: https://ru.wikipedia.org/wiki/Производная_функции#Таблица_производных
Эта как по телику когда говорят «В последний квартал уменьшились темпы снижения роста вывода капитала заграницу». Чтобы понять, что сказал диктор, надо всего-то, взять 5ую производную от денежного потока.
Надо копать дальше. Делать анализ 0..9 для MNIST. Для простого примера текущий код ОК, но вообще, выбрал плохую архитектуру структуры данных. Неудобно обновлять кучу массивов.