Как да изградим трислойна невронна мрежа от нулата

Снимка на Thaï Hamelin на Unsplash

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

Проблемът за решаване

Фермер в Италия имаше проблем с машината си за етикетиране: той смесваше етикетите на три сортове вино. Сега има 178 бутилки и никой не знае кой сорт ги е направил! За да помогнем на този беден човек, ще изградим класификатор, който разпознава виното въз основа на 13 атрибута на виното.

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

Ще обучим нашия алгоритъм, за да се усъвършенстваме по-добре и да прогнозираме (y-hat) коя бутилка принадлежи към кой етикет.

Сега е време да започнем да изграждаме невронната мрежа!

Приближаване

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

Преглед на 3-слоевата невронна мрежа, класификатор на виното

Накратко:

  • Входният слой (x) се състои от 178 неврона.
  • А1, първият слой, се състои от 8 неврона.
  • A2, вторият слой, се състои от 5 неврона.
  • A3, третият и изходният слой, се състои от 3 неврона.

Стъпка 1: обичайната предварителна подготовка

Импортирайте всички необходими библиотеки (NumPy, skicit-learn, панди) и набора от данни и дефинирайте x и y.

# импортиране на всички библиотеки и набор от данни
импортиране на панди като pd
импортиране numpy като np
df = pd.read_csv ('../ вход / W1data.csv')
df.head ()
# Пакет внос
# Matplotlib
внос matplotlib
импортиране matplotlib.pyplot като plt
# SciKitLearn е библиотека за комунални услуги за машинно обучение
import sklearn
# Модулът на набора от данни sklearn помага за генериране на набори от данни
import sklearn.datasets
import sklearn.linear_model
от sklearn.preprocessing import OneHotEncoder
от sklearn.metrics импортиране точност_score

Стъпка 2: Инициализация

Преди да можем да използваме тежестите си, трябва да ги инициализираме. Тъй като все още нямаме стойности, които да използваме за тежестите, ние използваме случайни стойности между 0 и 1.

В Python функцията random.seed генерира „случайни числа“. Въпреки това, случайните числа не са наистина случайни. Генерираните числа са псевдослучайни, което означава, че числата са генерирани от сложна формула, която го прави да изглежда произволен. За да генерира числа, формулата приема предишната стойност, генерирана като своя вход. Ако няма генерирана предишна стойност, тя често отнема времето като първа стойност.

Ето защо засяваме генератора - за да сме сигурни, че винаги получаваме еднакви случайни числа. Ние предоставяме фиксирана стойност, с която може да започне генераторът на числа, която е нула в този случай.

np.random.seed (0)

Стъпка 3: Размножаване напред

Има приблизително две части за обучение на невронна мрежа. Първо, вие се разпространявате напред през NN. Тоест, правите стъпки напред и сравнявате тези резултати с реалните стойности, за да получите разликата между вашите резултати и това, което трябва да бъде. По принцип виждате как се справя NN и откривате грешките.

След като инициализираме тежестите с псевдослучайно число, правим линейна стъпка напред. Ние изчисляваме това, като вземаме нашия вход A0 пъти по-кратният продукт на произволните инициализирани тегла плюс отклонение. Започнахме с отклонение от 0. Това е представено като:

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

Има различни видове функции за активиране (обяснени в дълбочина в тази статия). За този модел избрахме да използваме функцията за активиране на tanh за нашите два скрити слоя - А1 и А2 - което ни дава изходна стойност между -1 и 1.

Тъй като това е мултикласов проблем с класификацията (имаме 3 изходни етикета), ще използваме функцията softmax за изходния слой - A3 - тъй като това ще изчисли вероятностите за класовете, като изплюе стойност между 0 и 1.

tanh функция

Преминавайки z1 чрез функцията за активиране, ние създадохме първия си скрит слой - A1 - който може да се използва като вход за изчисляване на следващата линейна стъпка, z2.

В Python този процес изглежда така:

# Това е функцията за разпространение напред
def forward_prop (модел, a0):
    
    # Заредете параметри от модела
    W1, b1, W2, b2, W3, b3 = модел ['W1'], модел ['b1'], модел ['W2'], модел ['b2'], модел ['W3'], модел [' b3 ']
    
    # Направете първата линейна стъпка
    z1 = a0.dot (W1) + b1
    
    # Поставете го чрез първата функция за активиране
    a1 = np.tanh (z1)
    
    # Втора линейна стъпка
    z2 = a1.dot (W2) + b2
    
    # Прекарайте чрез втора функция за активиране
    a2 = np.tanh (z2)
    
    # Трета линейна стъпка
    z3 = a2.dot (W3) + b3
    
    #За третата функция на линейно активиране използваме функцията softmax
    a3 = softmax (z3)
    
    # Съхранявайте всички резултати в тези стойности
    cache = {'a0': a0, 'z1': z1, 'a1': a1, 'z2': z2, 'a2': a2, 'a3': a3, 'z3': z3}
    връщане кеш

В крайна сметка всички наши стойности се съхраняват в кеша.

Стъпка 4: размножаване назад

След като разпространим напред чрез нашата NN, ние разпространяваме градиента на грешката си назад, за да актуализираме параметрите на теглото си. Ние знаем грешката си и искаме да я намалим доколкото е възможно.

Правим това, като вземаме производната на функцията за грешка по отношение на теглата (W) на нашата NN, използвайки градиентно спускане.

Да визуализираме този процес с аналогия.

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

Но тогава си спомняте, че къщата ви е в долина, най-ниската точка в цялата област. Така че, ако просто вървите по планината стъпка по стъпка, докато не почувствате някакъв склон, на теория трябва да пристигнете в дома си.

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

Функцията за загуба е представена от планината. За да стигнем до ниска загуба, алгоритъмът следва наклона - това е производното - на функцията загуба.

Когато вървим по планината, ние актуализираме координатите на местоположението си. Алгоритъмът актуализира параметрите на невронната мрежа. Приближавайки се до минималната точка, ние се приближаваме до целта си да минимизираме грешката си.

В действителност градиентното спускане изглежда по-така:

Винаги започваме с изчисляването на наклона на функцията на загуба спрямо z, наклона на линейната стъпка, която предприемаме.

Нотацията е следната: dv е производното на функцията загуба по отношение на променлива v.

След това изчисляваме наклона на функцията за загуба по отношение на нашите тегла и отклонения. Тъй като това е 3-слойна NN, ще повторим този процес за z3,2,1 + W3,2,1 и b3,2,1. Разпространение назад от изхода към входния слой.

Ето как изглежда този процес в Python:

# Това е функцията за размножаване назад
def backward_prop (модел, кеш, y):
# Заредете параметри от модела
    W1, b1, W2, b2, W3, b3 = модел ['W1'], модел ['b1'], модел ['W2'], модел ['b2'], модел ['W3'], модел [' b3 ']
    
    # Заредете резултатите за разпространение напред
    a0, a1, a2, a3 = кеш ['a0'], кеш ['a1'], кеш ['a2'], кеш ['a3']
    
    # Вземете брой проби
    m = y.shape [0]
    
    # Изчислете производната загуба по отношение на продукцията
    dz3 = загуба_производно (y = y, y_hat = a3)
# Изчислете производната на загубата по отношение на теглата на втория слой
    dW3 = 1 / m * (a2.T) .dot (dz3) # dW2 = 1 / m * (a1.T) .dot (dz2)
    
    # Изчислете производната на загубата по отношение на отклонения от втория слой
    db3 = 1 / m * np.sum (dz3, ос = 0)
    
    # Изчислете производната на загубата по отношение на първия слой
    dz2 = np.multiply (dz3.dot (W3.T), tanh_derivative (a2))
    
    # Изчислете производната на загубата по отношение на теглата на първия слой
    dW2 = 1 / m * np.dot (a1.T, dz2)
    
    # Изчислете производната на загубата по отношение на отклонението от първия слой
    db2 = 1 / m * np.sum (dz2, ос = 0)
    
    dz1 = np.multiply (dz2.dot (W2.T), tanh_derivative (a1))
    
    dW1 = 1 / m * np.dot (a0.T, dz1)
    
    db1 = 1 / m * np.sum (dz1, ос = 0)
    
    # Съхранявайте градиенти
    grad = {'dW3': dW3, 'db3': db3, 'dW2': dW2, 'db2': db2, 'dW1': dW1, 'db1': db1}
    връщане град

Стъпка 5: фазата на обучение

За да достигнем оптималните тегла и пристрастия, които ще ни дадат желания резултат (трите винени култури), ще трябва да тренираме невронната си мрежа.

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

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

По същия начин трябва да посочите „степен на обучение” за модела. Степента на обучение е умножител за актуализиране на параметрите. Той определя колко бързо могат да се променят. Ако степента на обучение е ниска, обучението ще отнеме повече време. Ако обаче степента на обучение е твърде висока, може да пропуснем минимум. Степента на обучение се изразява като:

  • : = означава, че това е определение, а не уравнение или доказано твърдение.
  • a е скоростта на обучение, наречена алфа
  • dL (w) е производното на общата загуба спрямо нашето тегло w
  • da е производното на алфата

Избрахме степен на обучение 0,07 след някои експерименти.

# Това се връщаме в края
модел = начални_параметри (nn_input_dim = 13, nn_hdim = 5, nn_output_dim = 3)
model = влак (модел, X, y, learning_rate = 0.07, epochs = 4500, print_loss = Вярно)
plt.plot (загуби)

И накрая, е нашата графика. Можете да очертаете своята точност и / или загуба, за да получите хубава графика на точността на вашите прогнози. След 4500 епохи алгоритъмът ни има точност от 99.4382022472%.

Кратко обобщение

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

Изходът от тази функция на активиране се използва като вход за следващия слой, за да следва същата процедура. Този процес се повтаря три пъти, тъй като имаме три слоя. Нашата крайна продукция е y-hat, което е прогнозата кое вино принадлежи към кой сорт. Това е краят на процеса на разпространение напред.

След това изчисляваме разликата между нашата прогноза (y-hat) и очакваната продукция (y) и използваме тази стойност на грешката по време на обратното разпространение.

По време на обратното размножаване ние приемаме грешката си - разликата между нашата прогноза y-hat и y - и математически я избутваме обратно през NN в другата посока. Учим се от грешките си.

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

И след много епохи или повторения, NN се научи да ни дава по-точни прогнози, като адаптира своите параметри към нашия набор от данни.

Преглед на разпространението напред и назад

Този пост беше вдъхновен от предизвикателството за първата седмица от Bletchley Machine Learning Bootcamp, което започна на 7 февруари. В следващите девет седмици съм един от 50 ученици, които ще преминат през основите на машинното обучение. Всяка седмица обсъждаме различна тема и трябва да изпращате предизвикателство, което изисква наистина да разберете материалите.

Ако имате въпроси или предложения или, уведомете ме!

Или ако искате да проверите целия код, можете да го намерите тук на Kaggle.

Препоръчани видеоклипове за по-задълбочено разбиране в невронните мрежи:

  • Серия 3Blue1Brown в невронни мрежи
  • Поредицата на Siraj Raval за задълбочено обучение