有個(gè)事情可能會(huì)讓初學(xué)者驚訝:神經(jīng)網(wǎng)絡(luò)模型并不復(fù)雜!『神經(jīng)網(wǎng)絡(luò)』這個(gè)詞讓人覺(jué)得很高大上,但實(shí)際上神經(jīng)網(wǎng)絡(luò)算法要比人們想象的簡(jiǎn)單。
這篇文章完全是為新手準(zhǔn)備的。我們會(huì)通過(guò)用Python從頭實(shí)現(xiàn)一個(gè)神經(jīng)網(wǎng)絡(luò)來(lái)理解神經(jīng)網(wǎng)絡(luò)的原理。本文的脈絡(luò)是:
介紹了神經(jīng)網(wǎng)絡(luò)的基本結(jié)構(gòu)——神經(jīng)元;
在神經(jīng)元中使用S型激活函數(shù);
神經(jīng)網(wǎng)絡(luò)就是連接在一起的神經(jīng)元;
構(gòu)建了一個(gè)數(shù)據(jù)集,輸入(或特征)是體重和身高,輸出(或標(biāo)簽)是性別;
學(xué)習(xí)了損失函數(shù)和均方差損失;
訓(xùn)練網(wǎng)絡(luò)就是最小化其損失;
用反向傳播方法計(jì)算偏導(dǎo);
用隨機(jī)梯度下降法訓(xùn)練網(wǎng)絡(luò)。 ?
磚塊:神經(jīng)元
首先讓我們看看神經(jīng)網(wǎng)絡(luò)的基本單位,神經(jīng)元。神經(jīng)元接受輸入,對(duì)其做一些數(shù)據(jù)操作,然后產(chǎn)生輸出。例如,這是一個(gè)2-輸入神經(jīng)元:
這里發(fā)生了三個(gè)事情。首先,每個(gè)輸入都跟一個(gè)權(quán)重相乘(紅色):
然后,加權(quán)后的輸入求和,加上一個(gè)偏差b(綠色):
最后,這個(gè)結(jié)果傳遞給一個(gè)激活函數(shù)f:
激活函數(shù)的用途是將一個(gè)無(wú)邊界的輸入,轉(zhuǎn)變成一個(gè)可預(yù)測(cè)的形式。常用的激活函數(shù)就就是S型函數(shù):
S型函數(shù)的值域是(0, 1)。簡(jiǎn)單來(lái)說(shuō),就是把(?∞, +∞)壓縮到(0, 1) ,很大的負(fù)數(shù)約等于0,很大的正數(shù)約等于1。
一個(gè)簡(jiǎn)單的例子
假設(shè)我們有一個(gè)神經(jīng)元,激活函數(shù)就是S型函數(shù),其參數(shù)如下:
就是以向量的形式表示?,F(xiàn)在,我們給這個(gè)神經(jīng)元一個(gè)輸入。我們用點(diǎn)積來(lái)表示:
當(dāng)輸入是[2, 3]時(shí),這個(gè)神經(jīng)元的輸出是0.999。給定輸入,得到輸出的過(guò)程被稱(chēng)為前饋(feedforward)。
編碼一個(gè)神經(jīng)元
讓我們來(lái)實(shí)現(xiàn)一個(gè)神經(jīng)元!用Python的NumPy庫(kù)來(lái)完成其中的數(shù)學(xué)計(jì)算:
?
?
import?numpy?as?np def?sigmoid(x): ??#?我們的激活函數(shù):?f(x)?=?1?/?(1?+?e^(-x)) ??return?1?/?(1?+?np.exp(-x)) class?Neuron: ??def?__init__(self,?weights,?bias): ????self.weights?=?weights ????self.bias?=?bias ??def?feedforward(self,?inputs): ????#?加權(quán)輸入,加入偏置,然后使用激活函數(shù) ????total?=?np.dot(self.weights,?inputs)?+?self.bias ????return?sigmoid(total) weights?=?np.array([0,?1])?#?w1?=?0,?w2?=?1 bias?=?4???????????????????#?b?=?4 n?=?Neuron(weights,?bias) x?=?np.array([2,?3])???????#?x1?=?2,?x2?=?3 print(n.feedforward(x))????#?0.9990889488055994
?
?
還記得這個(gè)數(shù)字嗎?就是我們前面算出來(lái)的例子中的0.999。
把神經(jīng)元組裝成網(wǎng)絡(luò)
所謂的神經(jīng)網(wǎng)絡(luò)就是一堆神經(jīng)元。這就是一個(gè)簡(jiǎn)單的神經(jīng)網(wǎng)絡(luò):
這個(gè)網(wǎng)絡(luò)有兩個(gè)輸入,一個(gè)有兩個(gè)神經(jīng)元(?和?)的隱藏層,以及一個(gè)有一個(gè)神經(jīng)元(?) )的輸出層。要注意,?的輸入就是?和??的輸出,這樣就組成了一個(gè)網(wǎng)絡(luò)。
隱藏層就是輸入層和輸出層之間的層,隱藏層可以是多層的。
例子:前饋
我們繼續(xù)用前面圖中的網(wǎng)絡(luò),假設(shè)每個(gè)神經(jīng)元的權(quán)重都是??,截距項(xiàng)也相同??,激活函數(shù)也都是S型函數(shù)。分別用?表示相應(yīng)的神經(jīng)元的輸出。
當(dāng)輸入??時(shí),會(huì)得到什么結(jié)果?
這個(gè)神經(jīng)網(wǎng)絡(luò)對(duì)輸入的輸出是0.7216,很簡(jiǎn)單。
一個(gè)神經(jīng)網(wǎng)絡(luò)的層數(shù)以及每一層中的神經(jīng)元數(shù)量都是任意的?;具壿嫸家粯樱狠斎朐谏窠?jīng)網(wǎng)絡(luò)中向前傳輸,最終得到輸出。接下來(lái),我們會(huì)繼續(xù)使用前面的這個(gè)網(wǎng)絡(luò)。
編碼神經(jīng)網(wǎng)絡(luò):前饋
接下來(lái)我們實(shí)現(xiàn)這個(gè)神經(jīng)網(wǎng)絡(luò)的前饋機(jī)制,還是這個(gè)圖:
?
?
import?numpy?as?np #?...?code?from?previous?section?here class?OurNeuralNetwork: ??''' ??A?neural?network?with: ????-?2?inputs ????-?a?hidden?layer?with?2?neurons?(h1,?h2) ????-?an?output?layer?with?1?neuron?(o1) ??Each?neuron?has?the?same?weights?and?bias: ????-?w?=?[0,?1] ????-?b?=?0 ??''' ??def?__init__(self): ????weights?=?np.array([0,?1]) ????bias?=?0 ????#?這里是來(lái)自前一節(jié)的神經(jīng)元類(lèi) ????self.h1?=?Neuron(weights,?bias) ????self.h2?=?Neuron(weights,?bias) ????self.o1?=?Neuron(weights,?bias) ??def?feedforward(self,?x): ????out_h1?=?self.h1.feedforward(x) ????out_h2?=?self.h2.feedforward(x) ????#?o1的輸入是h1和h2的輸出 ????out_o1?=?self.o1.feedforward(np.array([out_h1,?out_h2])) ????return?out_o1 network?=?OurNeuralNetwork() x?=?np.array([2,?3]) print(network.feedforward(x))?#?0.7216325609518421
?
?
結(jié)果正確,看上去沒(méi)問(wèn)題。
訓(xùn)練神經(jīng)網(wǎng)絡(luò) 第一部分
現(xiàn)在有這樣的數(shù)據(jù):
?
姓名 | 體重(磅) | 身高 (英寸) | 性別 |
---|---|---|---|
Alice | 133 | 65 | F |
Bob | 160 | 72 | M |
Charlie | 152 | 70 | M |
Diana | 120 | 60 | F |
?
接下來(lái)我們用這個(gè)數(shù)據(jù)來(lái)訓(xùn)練神經(jīng)網(wǎng)絡(luò)的權(quán)重和截距項(xiàng),從而可以根據(jù)身高體重預(yù)測(cè)性別:
我們用0和1分別表示男性(M)和女性(F),并對(duì)數(shù)值做了轉(zhuǎn)化:
?
姓名 | 體重 (減 135) | 身高 (減 66) | 性別 |
---|---|---|---|
Alice | -2 | -1 | 1 |
Bob | 25 | 6 | 0 |
Charlie | 17 | 4 | 0 |
Diana | -15 | -6 | 1 |
?
我這里是隨意選取了135和66來(lái)標(biāo)準(zhǔn)化數(shù)據(jù),通常會(huì)使用平均值。
損失
在訓(xùn)練網(wǎng)絡(luò)之前,我們需要量化當(dāng)前的網(wǎng)絡(luò)是『好』還是『壞』,從而可以尋找更好的網(wǎng)絡(luò)。這就是定義損失的目的。
我們?cè)谶@里用平均方差(MSE)損失:?,讓我們仔細(xì)看看:
是樣品數(shù),這里等于4(Alice、Bob、Charlie和Diana)。
表示要預(yù)測(cè)的變量,這里是性別。
是變量的真實(shí)值(『正確答案』)。例如,Alice的??就是1(男性)。
變量的預(yù)測(cè)值。這就是我們網(wǎng)絡(luò)的輸出。
被稱(chēng)為方差(squared error)。我們的損失函數(shù)就是所有方差的平均值。預(yù)測(cè)效果越好,損失就越少。
更好的預(yù)測(cè) = 更少的損失!
訓(xùn)練網(wǎng)絡(luò) = 最小化它的損失。
損失計(jì)算例子
假設(shè)我們的網(wǎng)絡(luò)總是輸出0,換言之就是認(rèn)為所有人都是男性。損失如何?
?
Name | y_true | y_pred | (y_true - y_pred)^2 |
---|---|---|---|
Alice | 1 | 0 | 1 |
Bob | 0 | 0 | 0 |
Charlie | 0 | 0 | 0 |
Diana | 1 | 0 | 1 |
?
代碼:MSE損失
下面是計(jì)算MSE損失的代碼:
?
?
import?numpy?as?np def?mse_loss(y_true,?y_pred): ??#?y_true?and?y_pred?are?numpy?arrays?of?the?same?length. ??return?((y_true?-?y_pred)?**?2).mean() y_true?=?np.array([1,?0,?0,?1]) y_pred?=?np.array([0,?0,?0,?0]) print(mse_loss(y_true,?y_pred))?#?0.5
?
?
如果你不理解這段代碼,可以看看NumPy的快速入門(mén)中關(guān)于數(shù)組的操作。
好的,繼續(xù)。
訓(xùn)練神經(jīng)網(wǎng)絡(luò) 第二部分
現(xiàn)在我們有了一個(gè)明確的目標(biāo):最小化神經(jīng)網(wǎng)絡(luò)的損失。通過(guò)調(diào)整網(wǎng)絡(luò)的權(quán)重和截距項(xiàng),我們可以改變其預(yù)測(cè)結(jié)果,但如何才能逐步地減少損失?
這一段內(nèi)容涉及到多元微積分,如果不熟悉微積分的話,可以跳過(guò)這些數(shù)學(xué)內(nèi)容。
為了簡(jiǎn)化問(wèn)題,假設(shè)我們的數(shù)據(jù)集中只有Alice:
假設(shè)我們的網(wǎng)絡(luò)總是輸出0,換言之就是認(rèn)為所有人都是男性。損失如何?
?
姓名 | 體重 (減 135) | 身高 (減 66) | Gender |
---|---|---|---|
Alice | -2 | -1 | 1 |
?
那均方差損失就只是Alice的方差:
也可以把損失看成是權(quán)重和截距項(xiàng)的函數(shù)。讓我們給網(wǎng)絡(luò)標(biāo)上權(quán)重和截距項(xiàng):
這樣我們就可以把網(wǎng)絡(luò)的損失表示為:
假設(shè)我們要優(yōu)化??,當(dāng)我們改變??時(shí),損失??會(huì)怎么變化?可以用??來(lái)回答這個(gè)問(wèn)題,怎么計(jì)算?
接下來(lái)的數(shù)據(jù)稍微有點(diǎn)復(fù)雜,別擔(dān)心,準(zhǔn)備好紙和筆。
首先,讓我們用來(lái)改寫(xiě)這個(gè)偏導(dǎo)數(shù):
因?yàn)槲覀円呀?jīng)知道??,所以我們可以計(jì)算
現(xiàn)在讓我們來(lái)搞定??。分別是其所表示的神經(jīng)元的輸出,我們有:
由于??只會(huì)影響??(不會(huì)影響??),所以:
對(duì)??,我們也可以這么做:
在這里,?是身高,?是體重。這是我們第二次看到??(S型函數(shù)的導(dǎo)數(shù))了。求解:
稍后我們會(huì)用到這個(gè)??。
我們已經(jīng)把?分解成了幾個(gè)我們能計(jì)算的部分:
這種計(jì)算偏導(dǎo)的方法叫『反向傳播算法』(backpropagation)。
好多數(shù)學(xué)符號(hào),如果你還沒(méi)搞明白的話,我們來(lái)看一個(gè)實(shí)際例子。
例子:計(jì)算偏導(dǎo)數(shù)
我們還是看數(shù)據(jù)集中只有Alice的情況:
?
Name | ? | ? | ? |
---|---|---|---|
Alice | 1 | 0 | 1 |
姓名 | 身高 (minus 135) | 體重 (minus 66) | Gender |
---|---|---|---|
Alice | -2 | -1 | 1 |
?
把所有的權(quán)重和截距項(xiàng)都分別初始化為1和0。在網(wǎng)絡(luò)中做前饋計(jì)算:
網(wǎng)絡(luò)的輸出是??,對(duì)于Male(0)或者Female(1)都沒(méi)有太強(qiáng)的傾向性。算一下
提示:前面已經(jīng)得到了S型激活函數(shù)的導(dǎo)數(shù)??。
搞定!這個(gè)結(jié)果的意思就是增加也會(huì)隨之輕微上升。
訓(xùn)練:隨機(jī)梯度下降
現(xiàn)在訓(xùn)練神經(jīng)網(wǎng)絡(luò)已經(jīng)萬(wàn)事俱備了!我們會(huì)使用名為隨機(jī)梯度下降法的優(yōu)化算法來(lái)優(yōu)化網(wǎng)絡(luò)的權(quán)重和截距項(xiàng),實(shí)現(xiàn)損失的最小化。核心就是這個(gè)更新等式:
是一個(gè)常數(shù),被稱(chēng)為學(xué)習(xí)率,用于調(diào)整訓(xùn)練的速度。我們要做的就是用??減去
如果??是正數(shù),??變小,?會(huì)下降。
如果???是負(fù)數(shù),??會(huì)變大,??會(huì)上升。
如果我們對(duì)網(wǎng)絡(luò)中的每個(gè)權(quán)重和截距項(xiàng)都這樣進(jìn)行優(yōu)化,損失就會(huì)不斷下降,網(wǎng)絡(luò)性能會(huì)不斷上升。
我們的訓(xùn)練過(guò)程是這樣的:
從我們的數(shù)據(jù)集中選擇一個(gè)樣本,用隨機(jī)梯度下降法進(jìn)行優(yōu)化——每次我們都只針對(duì)一個(gè)樣本進(jìn)行優(yōu)化;
計(jì)算每個(gè)權(quán)重或截距項(xiàng)對(duì)損失的偏導(dǎo)(例如?、?等);
用更新等式更新每個(gè)權(quán)重和截距項(xiàng);
重復(fù)第一步;
代碼:一個(gè)完整的神經(jīng)網(wǎng)絡(luò)
我們終于可以實(shí)現(xiàn)一個(gè)完整的神經(jīng)網(wǎng)絡(luò)了:
?
姓名 | 身高 (減 135) | 體重 (減 66) | Gender |
---|---|---|---|
Alice | -2 | -1 | 1 |
Bob | 25 | 6 | 0 |
Charlie | 17 | 4 | 0 |
Diana | -15 | -6 | 1 |
?
?
?
import?numpy?as?np def?sigmoid(x): ??#?Sigmoid?activation?function:?f(x)?=?1?/?(1?+?e^(-x)) ??return?1?/?(1?+?np.exp(-x)) def?deriv_sigmoid(x): ??#?Derivative?of?sigmoid:?f'(x)?=?f(x)?*?(1?-?f(x)) ??fx?=?sigmoid(x) ??return?fx?*?(1?-?fx) def?mse_loss(y_true,?y_pred): ??# y_true和y_pred是相同長(zhǎng)度的numpy數(shù)組。 ??return?((y_true?-?y_pred)?**?2).mean() class?OurNeuralNetwork: ??''' ??A?neural?network?with: ????-?2?inputs ????-?a?hidden?layer?with?2?neurons?(h1,?h2) ????-?an?output?layer?with?1?neuron?(o1) ??***?免責(zé)聲明?***: ????下面的代碼是為了簡(jiǎn)單和演示,而不是最佳的。 ????真正的神經(jīng)網(wǎng)絡(luò)代碼與此完全不同。不要使用此代碼。 ????相反,讀/運(yùn)行它來(lái)理解這個(gè)特定的網(wǎng)絡(luò)是如何工作的。 ??''' ??def?__init__(self): ????#?權(quán)重,Weights ????self.w1?=?np.random.normal() ????self.w2?=?np.random.normal() ????self.w3?=?np.random.normal() ????self.w4?=?np.random.normal() ????self.w5?=?np.random.normal() ????self.w6?=?np.random.normal() ????#?截距項(xiàng),Biases ????self.b1?=?np.random.normal() ????self.b2?=?np.random.normal() ????self.b3?=?np.random.normal() ??def?feedforward(self,?x): ????# X是一個(gè)有2個(gè)元素的數(shù)字?jǐn)?shù)組。 ????h1?=?sigmoid(self.w1?*?x[0]?+?self.w2?*?x[1]?+?self.b1) ????h2?=?sigmoid(self.w3?*?x[0]?+?self.w4?*?x[1]?+?self.b2) ????o1?=?sigmoid(self.w5?*?h1?+?self.w6?*?h2?+?self.b3) ????return?o1 ??def?train(self,?data,?all_y_trues): ????''' ????-?data?is?a?(n?x?2)?numpy?array,?n?=?#?of?samples?in?the?dataset. ????-?all_y_trues?is?a?numpy?array?with?n?elements. ??????Elements?in?all_y_trues?correspond?to?those?in?data. ????''' ????learn_rate?=?0.1 ????epochs?=?1000?#?遍歷整個(gè)數(shù)據(jù)集的次數(shù) ????for?epoch?in?range(epochs): ??????for?x,?y_true?in?zip(data,?all_y_trues): ????????#?---?做一個(gè)前饋(稍后我們將需要這些值) ????????sum_h1?=?self.w1?*?x[0]?+?self.w2?*?x[1]?+?self.b1 ????????h1?=?sigmoid(sum_h1) ????????sum_h2?=?self.w3?*?x[0]?+?self.w4?*?x[1]?+?self.b2 ????????h2?=?sigmoid(sum_h2) ????????sum_o1?=?self.w5?*?h1?+?self.w6?*?h2?+?self.b3 ????????o1?=?sigmoid(sum_o1) ????????y_pred?=?o1 ????????#?---?計(jì)算偏導(dǎo)數(shù)。 ????????#?---?Naming:?d_L_d_w1?represents?"partial?L?/?partial?w1" ????????d_L_d_ypred?=?-2?*?(y_true?-?y_pred) ????????#?Neuron?o1 ????????d_ypred_d_w5?=?h1?*?deriv_sigmoid(sum_o1) ????????d_ypred_d_w6?=?h2?*?deriv_sigmoid(sum_o1) ????????d_ypred_d_b3?=?deriv_sigmoid(sum_o1) ????????d_ypred_d_h1?=?self.w5?*?deriv_sigmoid(sum_o1) ????????d_ypred_d_h2?=?self.w6?*?deriv_sigmoid(sum_o1) ????????#?Neuron?h1 ????????d_h1_d_w1?=?x[0]?*?deriv_sigmoid(sum_h1) ????????d_h1_d_w2?=?x[1]?*?deriv_sigmoid(sum_h1) ????????d_h1_d_b1?=?deriv_sigmoid(sum_h1) ????????#?Neuron?h2 ????????d_h2_d_w3?=?x[0]?*?deriv_sigmoid(sum_h2) ????????d_h2_d_w4?=?x[1]?*?deriv_sigmoid(sum_h2) ????????d_h2_d_b2?=?deriv_sigmoid(sum_h2) ????????#?---?更新權(quán)重和偏差 ????????#?Neuron?h1 ????????self.w1?-=?learn_rate?*?d_L_d_ypred?*?d_ypred_d_h1?*?d_h1_d_w1 ????????self.w2?-=?learn_rate?*?d_L_d_ypred?*?d_ypred_d_h1?*?d_h1_d_w2 ????????self.b1?-=?learn_rate?*?d_L_d_ypred?*?d_ypred_d_h1?*?d_h1_d_b1 ????????#?Neuron?h2 ????????self.w3?-=?learn_rate?*?d_L_d_ypred?*?d_ypred_d_h2?*?d_h2_d_w3 ????????self.w4?-=?learn_rate?*?d_L_d_ypred?*?d_ypred_d_h2?*?d_h2_d_w4 ????????self.b2?-=?learn_rate?*?d_L_d_ypred?*?d_ypred_d_h2?*?d_h2_d_b2 ????????#?Neuron?o1 ????????self.w5?-=?learn_rate?*?d_L_d_ypred?*?d_ypred_d_w5 ????????self.w6?-=?learn_rate?*?d_L_d_ypred?*?d_ypred_d_w6 ????????self.b3?-=?learn_rate?*?d_L_d_ypred?*?d_ypred_d_b3 ??????#?---?在每次epoch結(jié)束時(shí)計(jì)算總損失? ??????if?epoch?%?10?==?0: ????????y_preds?=?np.apply_along_axis(self.feedforward,?1,?data) ????????loss?=?mse_loss(all_y_trues,?y_preds) ????????print("Epoch?%d?loss:?%.3f"?%?(epoch,?loss)) #?定義數(shù)據(jù)集 data?=?np.array([ ??[-2,?-1],??#?Alice ??[25,?6],???#?Bob ??[17,?4],???#?Charlie ??[-15,?-6],?#?Diana ]) all_y_trues?=?np.array([ ??1,?#?Alice ??0,?#?Bob ??0,?#?Charlie ??1,?#?Diana ]) #?訓(xùn)練我們的神經(jīng)網(wǎng)絡(luò)! network?=?OurNeuralNetwork() network.train(data,?all_y_trues)
?
?
隨著網(wǎng)絡(luò)的學(xué)習(xí),損失在穩(wěn)步下降。
現(xiàn)在我們可以用這個(gè)網(wǎng)絡(luò)來(lái)預(yù)測(cè)性別了:
?
?
#?做一些預(yù)測(cè) emily?=?np.array([-7,?-3])?#?128?磅,?63?英寸 frank?=?np.array([20,?2])??#?155?磅,?68?英寸 print("Emily:?%.3f"?%?network.feedforward(emily))?#?0.951?-?F print("Frank:?%.3f"?%?network.feedforward(frank))?#?0.039?-?M
?
?
接下來(lái)?
搞定了一個(gè)簡(jiǎn)單的神經(jīng)網(wǎng)絡(luò),快速回顧一下:
介紹了神經(jīng)網(wǎng)絡(luò)的基本結(jié)構(gòu)——神經(jīng)元;
在神經(jīng)元中使用S型激活函數(shù);
神經(jīng)網(wǎng)絡(luò)就是連接在一起的神經(jīng)元;
構(gòu)建了一個(gè)數(shù)據(jù)集,輸入(或特征)是體重和身高,輸出(或標(biāo)簽)是性別;
學(xué)習(xí)了損失函數(shù)和均方差損失;
訓(xùn)練網(wǎng)絡(luò)就是最小化其損失;
用反向傳播方法計(jì)算偏導(dǎo);
用隨機(jī)梯度下降法訓(xùn)練網(wǎng)絡(luò);
接下來(lái)你還可以:
用機(jī)器學(xué)習(xí)庫(kù)實(shí)現(xiàn)更大更好的神經(jīng)網(wǎng)絡(luò),例如TensorFlow、Keras和PyTorch;
其他類(lèi)型的激活函數(shù);
其他類(lèi)型的優(yōu)化器;
學(xué)習(xí)卷積神經(jīng)網(wǎng)絡(luò),這給計(jì)算機(jī)視覺(jué)領(lǐng)域帶來(lái)了革命; 學(xué)習(xí)遞歸神經(jīng)網(wǎng)絡(luò),常用于自然語(yǔ)言處理;
作者:Victor Zhou?
編輯:黃飛
評(píng)論
查看更多