Всяка сложна система може да бъде абстрахирана по прост начин или поне разчленена до нейните основни абстрактни компоненти. Сложността възниква от натрупването на няколко прости слоя. Целта на тази публикация е да обясни как работят невронните мрежи с най-простата абстракция. Ще се опитаме да намалим механизма за машинно обучение в NN до основните му абстрактни компоненти. За разлика от други публикации, които обясняват невронните мрежи, ние ще се опитаме да използваме възможно най-малкото количество математически уравнения и програмен код и ще се съсредоточим само върху абстрактните концепции на високо ниво.

Една контролирана невронна мрежа, в най-високо и най-просто представяне, може да бъде представена като черна кутия с 2 метода за обучение и прогнозиране, както следва:

Процесът на обучение взема входовете и желаните изходи и съответно актуализира своето вътрешно състояние, така че изчисленият изход се доближава възможно най-близо до желания изход. Процесът на прогнозиране приема входни данни и генерира, използвайки вътрешното състояние, най-вероятния изход според миналия си „обучителен опит“. Ето защо понякога машинното обучение се нарича напасване на модел.

За да постигнем това, ние ще разделим учебния процес на няколко градивни елемента:

Прост числов пример

Най-лесният пример за започване с невронна мрежа и контролирано обучение е просто да започнете с вход и изход и линейна връзка между тях. Целта на контролираната невронна мрежа е да се опита да търси във всички възможни линейни функции коя от тях отговаря най-добре на данните. Вземете например следния набор от данни:

+--------+-----------------+
| Input  |  Desired output |
+--------+-----------------+
|     0  |               0 |
|     1  |               2 |
|     2  |               4 |
|     3  |               6 |
|     4  |               8 |
+--------+-----------------+

За този пример може да ви изглежда много очевидно, че изходът = 2 x вход, но не е така за повечето от реалните набори от данни (където връзката между входа и изхода е силно неравна -линейно и не толкова очевидно).

Стъпка 1- Инициализация на модела

Първата стъпка от обучението е да започнем отнякъде: първоначалната хипотеза. Подобно на генетичните алгоритми и еволюционната теория, невронните мрежи могат да започнат отвсякъде. Следователно произволната инициализация на модела е обичайна практика. Рационалната основа е, че откъдето и да започнем, ако сме достатъчно постоянни и чрез итеративен процес на учене, можем да достигнем до псевдоидеалния модел.

За да дадем аналогия, вземете например човек, който никога през живота си не е играл футбол. Първият път, когато се опитва да стреля по топката, той може просто да я стреля произволно. По същия начин, за нашия числен казус, нека разгледаме следната произволна инициализация: (Модел 1): y=3.x. Числото 3 тук се генерира на случаен принцип. Друга произволна инициализация може да бъде: (Модел 2): y=5.x или (Модел 3): y=0,5.x.
Ние ще проучи по-късно как чрез процеса на обучение всички тези модели могат да се сближат до идеалното решение (y=2.x) (което се опитваме да намерим).

В този пример ние проучваме кой модел на общата форма y=W.x може да пасне най-добре на текущия набор от данни. Където W се нарича тегла на мрежата и може да се инициализира произволно. Тези видове модели се наричат ​​просто линейни слоеве.

Стъпка 2- Разпространяване напред

Следващата естествена стъпка, която трябва да направите след инициализиране на модела на случаен принцип, е да проверите неговата производителност. Започваме от входните данни, които имаме, прекарваме ги през мрежовия слой и директно изчисляваме действителния изход на модела.

+--------+------------------------------------+
| Input  |  Actual output of model 1 (y= 3.x) |
+--------+------------------------------------+
|     0  |                                  0 |
|     1  |                                  3 |
|     2  |                                  6 |
|     3  |                                  9 |
|     4  |                                 12 |
+--------+------------------------------------+

Тази стъпка се нарича предаване напред, тъй като изчислителният поток върви в естествената напред посока от входа -› през невронната мрежа -› към изхода.

Стъпка 3- Функция за загуба

На този етап, от една страна, имаме действителния резултат от нашата произволно инициализирана невронна мрежа. От друга страна, имаме желания резултат, който бихме искали мрежата да научи. Нека ги поставим и двете в една и съща таблица.

+--------+-----------------+-----------------+
| Input  |  Actual output  |  Desired output |
+--------+-----------------+-----------------+
|     0  |              0  |               0 |
|     1  |              3  |               2 |
|     2  |              6  |               4 |
|     3  |              9  |               6 |
|     4  |              12 |               8 |
+--------+-----------------+-----------------+

Ако сравним това с нашия футболист, който стреля за първи път, действителният изход ще бъде крайната позиция на топката, желаният резултат би бил топката да влезе във вратата. В началото нашият играч просто стреля на случаен принцип. Да кажем, че топката минава през повечето време от дясната страна на вратата. Това, което той може да научи от това, е, че трябва да стреля малко повече наляво следващия път, когато тренира.

За да можем да обобщим за всеки проблем, ние дефинираме това, което наричаме: функция на загуба. По принцип това е метрика на ефективността за това колко добре NN успява да постигне целта си за генериране на изходи, възможно най-близки до желаните стойности.

Най-интуитивната функция за загуба е просто загуба = (Желана продукция — действителна продукция). Въпреки това, тази функция на загубите връща положителни стойности, когато мрежата се превишава (предсказание ‹ желан изход), и отрицателни стойности, когато мрежата превишава (предсказание › желан изход). Ако искаме функцията за загуба да отразява абсолютна грешка върху производителността, независимо дали е превишаване или по-ниско ниво, можем да я дефинираме като:
загуба = Абсолютна стойност на (желана — действителна) .

Ако се върнем към нашия пример с футболист, ако нашият начинаещ човек стреля по топката на 10 метра отдясно или 10 метра отляво на вратата, считаме и в двата случая, че той е пропуснал целта с 10 метра, независимо от посоката (надясно или наляво). В този случай ще добавим нова колона към таблицата -› абсолютната грешка.

Няколко ситуации обаче могат да доведат до една и съща обща сума на грешките: например много малки грешки или няколко големи грешки могат да сумират точно до една и съща обща сума на грешките. Тъй като бихме искали предвиждането да работи при всяка ситуация, по-предпочитано е да имаме разпределение на много малки грешки, вместо няколко големи.
За да се насърчи NN за да се сближим с такава ситуация, можем да дефинираме функцията на загуба като сума от квадрати на абсолютните грешки (което е най-известната функция на загуба в NN). По този начин малките грешки се броят много по-малко от големите! (квадратът на 2 е 4, но квадратът на 10 е 100! Така че грешка от 10 се наказва 25 пъти повече от грешка от 2 — не само 5 пъти!). Таблицата ни става следната:

+--------+----------+-----------+------------------+---------------+
| Input  |  actual  |  Desired  |  Absolute Error  |  Square Error |
+--------+----------+-----------+------------------+---------------+
| 0      |       0  |        0  |               0  |             0 |
| 1      |       3  |        2  |               1  |             1 |
| 2      |       6  |        4  |               2  |             4 |
| 3      |       9  |        6  |               3  |             9 |
| 4      |      12  |        8  |               4  |            16 |
| Total: |       -  |        -  |              10  |            30 |
+--------+----------+-----------+------------------+---------------+

Забележете как, ако вземем предвид само първия вход 0, можем да кажем, че мрежата е предсказала правилно резултата!
Това обаче е само късметът на начинаещия в нашата аналогия с футболист, който може да успее да отбележи от първия удар като добре. Това, за което се грижим, е да минимизираме общата грешка за целия набор от данни (общата сума на квадратите на грешките!).

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

Най-просто казано, целта на машинното обучение се превръща в минимизиране на функцията на загубата (за достигане възможно най-близко до 0).
Можем просто да трансформираме нашия проблем с машинното обучение сега в процес на оптимизация, който има за цел да минимизира тази функция на загуба.

Стъпка 4- Диференциация

Очевидно можем да използваме всяка техника за оптимизация, която променя вътрешните тегла на невронните мрежи, за да минимизираме общата функция на загуба, която дефинирахме по-рано. Тези техники могат да включват генетични алгоритми или алчно търсене или дори просто грубо търсене:
В нашия прост числен пример, само с един параметър на тегло за оптимизиране на W, можем да търсим от - 1000,0 до +1000,0 стъпка 0,001, която W има най-малката сума от квадрати на грешките в набора от данни.

Това може да работи, ако моделът има само много малко параметри и ние не се интересуваме много от точността. Въпреки това, ако обучаваме NN върху масив от 600x400 входове (както при обработката на изображения), можем много лесно да достигнем до модели с милиони тегла за оптимизиране и грубата сила не може дори да си представим, тъй като това е чиста загуба на изчислителни ресурси!

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

За да видим ефекта от производната, можем да си зададем следния въпрос: колко ще се промени общата грешка, ако променим вътрешното тегло на невронната мрежа с определена малка стойност δW. За по-голяма простота ще разгледаме δW=0,0001. в действителност трябва да е още по-малък!.

Нека преизчислим сумата от квадратите на грешките, когато теглото W се промени много леко:

+--------+----------+-------+-----------+------------+---------+
| Input  |  Output  |  W=3  |  sq.e(3)  |  W=3.0001  |   sq.e  |
+--------+----------+-------+-----------+------------+---------+
| 0      |       0  |    0  |        0  |         0  |       0 |
| 1      |       2  |    3  |        1  |    3.0001  |  1.0002 |
| 2      |       4  |    6  |        4  |    6.0002  |  4.0008 |
| 3      |       6  |    9  |        9  |    9.0003  |  9.0018 |
| 4      |       8  |   12  |       16  |   12.0004  | 16.0032 |
| Total: |        - |     - |       30  |         -  |  30.006 |
+--------+----------+-------+-----------+------------+---------+

Сега, както можем да видим от тази таблица, ако увеличим W от 3 на 3,0001, сумата на квадратите на грешката ще се увеличи от 30 на 30,006. Тъй като знаем, че най-добрата функция, която отговаря на този модел, е y=2.x, увеличаването на теглата от 3 на 3,0001 очевидно трябва да създаде малко повече грешка (защото отиваме по-далеч от интуитивната правилна тегло 2. Имаме: 3,0001 › 3 › 2, следователно грешката е по-висока). Но това, което наистина ни интересува, е скоростта, с която грешката се променя спрямо на промените в теглото. По принцип тук тази скорост е увеличението от 0,006 на общата грешка за всяко 0,0001 нарастващо тегло -> това е скорост от 0,006/0,0001 = 60x! Работи и в двете посоки, така че основно, ако намалим теглата с 0,0001, трябва да можем да намалим и общата грешка с 0,006! Ето доказателството, ако изпълним отново изчислението при W=2,9999, получаваме грешка от 29,994. Успяхме да намалим общата грешка с 0,006, научихме нещо! подобрихме модела!

Можехме да познаем тази скорост, като изчислим директно производната на функцията на загубата. Предимството на използването на математическата производна е, че е много по-бързо и по-точно за изчисляване (по-малко проблеми с точността на плаваща запетая).

Ето как изглежда нашата функция за загуба:

  • Ако w=2, имаме загуба от 0, тъй като действителният изход на невронната мрежа ще пасне идеално на набора за обучение.
  • Ако w‹2, имаме положителна функция на загуба, но производната е отрицателна, което означава, че увеличаването на теглото ще намали функцията на загуба.
  • При w=2 загубата е 0 и производната е 0, достигнахме перфектен модел, нищо не е необходимо.
  • Ако w>2, загубата отново става положителна, но производната също е положителна, което означава, че всяко повече увеличаване на теглото ще увеличи загубите още повече!!

Ако инициализираме произволно мрежата, ние поставяме произволна точка на тази крива (да кажем w=3). Процесът на обучение всъщност казва следното:

- Нека проверим производната.
- Ако е положителна, което означава, че грешката се увеличава, ако увеличим теглата, тогава трябва да намалим тежестта.
- Ако е отрицателна, което означава, че грешката намалява, ако увеличим теглата тежести, тогава трябва да увеличим тежестта.
- Ако е 0, не правим нищо, достигаме нашата стабилна точка.

Казано по-просто, ние проектираме процес, който действа като гравитация. Без значение къде произволно инициализираме топката на тази крива на функцията на грешката, има един вид силово поле, което връща топката обратно към най-ниското енергийно ниво на ниво 0.

Стъпка 5- Обратно разпространение

В този пример използвахме само един слой вътре в невронната мрежа между входовете и изходите. В много случаи са необходими повече слоеве, за да се достигнат повече вариации във функционалността на невронната мрежа.
Разбира се, винаги можем да създадем една сложна функция, която представлява композицията върху целите слоеве на мрежата. Например, ако слой 1 прави: 3.x за генериране на скрит изход z, а слой 2 прави: z² за генериране на крайния изход, съставената мрежа ще прави (3.x)² = 9.x². В повечето случаи обаче композирането на функциите е много трудно. Освен това за всяка композиция трябва да се изчисли специалната производна на композицията (която изобщо не е мащабируема и е много податлива на грешки).

За да разрешим проблема, за наше щастие, производната е разложима, следователно може да се разпространи обратно.
Имаме началната точка на грешките, която е функцията на загубата, и знаем как да изчислим нейната производна и ако знаем как да изчислим производната на всяка функция от композицията, можем да разпространим обратно грешката от края към началото.
Нека разгледаме простия линеен пример: където умножаваме входа 3 пъти, за да получим скрито слой, след което умножаваме скрития (среден слой) 2 пъти, за да получим резултата.

вход -› 3.x -› 2.x -› изход.

Промяна от 0,001 делта на входа ще бъде преобразувана в промяна от 0,003 делта след първия слой, след това в промяна от 0,006 делта на изхода.
което е случаят, ако съставим двете функции в една:

вход -› 6.x -› изход.

По подобен начин грешка на изхода от 0,006 може да бъде предадена обратно до грешка от 0,003 в средния скрит етап, след това до 0,001 на входа.
Ако създадем библиотека от диференцируеми функции или слоеве, където за всяка функция знаем как да разпространяваме напред (чрез директно прилагане на функцията) и как да разпространяваме обратно (чрез изчисляване на производната на функцията), можем да съставим всяка сложна невронна мрежа. Трябва само да запазим стек от извикванията на функции по време на преминаването напред и техните параметри, за да знаем обратния път за обратно разпространение на грешките, използвайки производните на тези функции. Това може да се направи чрез премахване на стека чрез извикванията на функцията. Тази техника се нарича автоматично диференциране и изисква само всяка функция да бъде снабдена с изпълнението на нейната производна. В бъдеща публикация в блога ще обясним как да ускорим автоматичното диференциране чрез прилагане на основни математически операции върху вектори/матрици/и тензори.

В невронната мрежа всеки слой може да препраща своите резултати към много други слоеве, в този случай, за да направим обратно разпространение, ние сумираме делтите, идващи от всички целеви слоеве. Така нашият линеен изчислителен стек може да се превърне в „сложна изчислителна графика“.

Тази фигура показва процеса на обратно разпространение на грешки след тези схеми:
Вход -› Препращане на повиквания -› Функция на загуба -› производна -› обратно разпространение на грешки. На всеки етап получаваме делтите на теглата на този етап.

Стъпка 6- Актуализация на теглото

Както представихме по-рано, производната е просто скоростта, при която грешката се променя спрямо промените в теглото. В числения пример, представен по-рано, тази скорост е 60x. Което означава, че 1 единица промяна в теглата води до 60 общи единици промяна във функцията на загубата.
И тъй като знаем, че грешката на загубата в момента е 30 единици, чрез екстраполиране на скоростта, за да намалим грешката до 0, трябва да намалим теглата с 0,5 единици.
Въпреки това, за проблеми от реалния живот не трябва да актуализираме теглата с толкова големи стъпки. Тъй като има много нелинейност, всяка голяма промяна в теглата ще доведе до хаотично поведение. Не трябва да забравяме, че производната е локална само в точката, където изчисляваме производната.

Така като общо правило за актуализации на теглото е „делта правилото“:

Ново тегло = старо тегло — Производна * скорост на обучение

Скоростта на обучение се въвежда като константа (обикновено много малка), за да принуди теглото да се актуализира много гладко и бавно (за да се избегнат големи стъпки и хаотично поведение). (За да запомните: Учете бавно и стабилно!)

За да потвърдите това уравнение:

  • Ако процентът на производната е положителен, това означава, че увеличаването на теглото ще увеличи грешката, следователно новото тегло трябва да бъде по-малко.
  • Ако процентът на производната е отрицателен, това означава, че увеличаването на теглото ще намали грешката, следователно трябва да увеличим теглата.
  • Ако производната е 0, това означава, че сме в стабилен минимум. Следователно не е необходима актуализация на теглата -› достигнахме стабилно състояние.

Сега съществуват няколко метода за актуализиране на теглото. Тези методи често се наричат ​​оптимизатори. Делта правилото е най-простото и интуитивно, но има няколко недостатъка. „Тази отлична публикация в блога“ представя различните налични методи за актуализиране на теглата.

В числовия пример, който представихме тук, имаме само 5 набора за обучение за вход/изход. В действителност може да имаме милиони записи. Преди това говорихме за минимизиране на функцията на разходите за грешка (функцията на загуба) за целия набор от данни. Това се нарича пакетно обучение и може да е много бавно за големи данни. Това, което можем да направим вместо това, е да актуализираме теглата на всяко BatchSize=Nот обучението, при условие че наборът от данни се разбърква на случаен принцип. Това се нарича мини-партидно градиентно спускане. И ако N=1, наричаме този случай пълно онлайн обучение или стохастичен градиентен спад, тъй като актуализираме теглата след всеки наблюдаван входен изход!
Всеки оптимизатор може да работи с тези 3 режими (пълна онлайн/мини-партида/пълна партида).

Стъпка 7- Повторете до конвергенция

Тъй като актуализираме теглата с малка делта стъпка наведнъж, ще са необходими няколко итерации, за да се научим.
Това е много подобно на генетичните алгоритми, при които след всяко поколение прилагаме малък процент на мутация и най-силният оцелява.
В невронната мрежа, след всяка итерация, силата на градиентно спускане актуализира теглата към все по-малка функция на глобалната загуба.
Приликата е, че делта правилото действа като оператор на мутация, а функцията на загуба действа като фитнес функция за минимизиране.
Разликата е, че в генетичните алгоритми мутацията е сляпа. Някои мутации са лоши, други са добри, но добрите имат по-голям шанс да оцелеят. Актуализацията на теглото в NN обаче е по-интелигентна, тъй като се ръководи от намаляващата сила на градиента над грешката.

Колко итерации са необходими за сближаване?

  • Това зависи от това колко силен темп на обучение прилагаме. Високата скорост на учене означава по-бързо учене, но с по-голям шанс за „нестабилност“ и по-голям шанс за неоптимални резултати.
  • Зависи и от мета-параметрите на мрежата (колко слоя, колко сложни са нелинейните функции). Колкото повече променливи има, толкова повече време отнема да се сближи, но толкова по-висока точност може да достигне.
  • Зависи от използвания метод за оптимизация, някои правила за актуализиране на теглото са доказано по-бързи от други.

  • Зависи от произволната инициализация на мрежата. Може би с малко късмет ще инициализирате мрежата с **W=1.99** и сте само на 0.01 стъпка от оптималното решение.
  • Зависи от качеството на комплекта за обучение. Ако входът и изходът нямат корелация помежду си, невронната мрежа няма да направи магия и не може да научи произволна корелация.

Обща картина

За да обобщим, Ето как изглежда процесът на обучение на невронни мрежи (Пълна картина):

Пример и код

Пример и супер проста реализация на невронна мрежа е предоставена в тази публикация в блога.

В случай, че все още имате въпроси, моля, не се колебайте да коментирате или да се свържете с мен на: [email protected]

Винаги ще се радвам да ви отговоря, да подобря тази статия или да си сътруднича, ако имате някаква идея. Ако ви е харесало да четете, последвайте ни във: Facebook, Twitter, LinkedIn