作者 | Victor Zhou
有個(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ò)程被稱為前饋(feedforward)。
編碼一個(gè)神經(jīng)元
讓我們來(lái)實(shí)現(xiàn)一個(gè)神經(jīng)元!用Python的NumPy庫(kù)來(lái)完成其中的數(shù)學(xué)計(jì)算:
importnumpyasnp defsigmoid(x): #我們的激活函數(shù):f(x)=1/(1+e^(-x)) return1/(1+np.exp(-x)) classNeuron: def__init__(self,weights,bias): self.weights=weights self.bias=bias deffeedforward(self,inputs): #加權(quán)輸入,加入偏置,然后使用激活函數(shù) total=np.dot(self.weights,inputs)+self.bias returnsigmoid(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è)圖:
importnumpyasnp #...codefromprevioussectionhere classOurNeuralNetwork: ''' Aneuralnetworkwith: -2inputs -ahiddenlayerwith2neurons(h1,h2) -anoutputlayerwith1neuron(o1) Eachneuronhasthesameweightsandbias: -w=[0,1] -b=0 ''' def__init__(self): weights=np.array([0,1]) bias=0 #這里是來(lái)自前一節(jié)的神經(jīng)元類 self.h1=Neuron(weights,bias) self.h2=Neuron(weights,bias) self.o1=Neuron(weights,bias) deffeedforward(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])) returnout_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ò)的輸出。
被稱為方差(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損失的代碼:
importnumpyasnp defmse_loss(y_true,y_pred): #y_trueandy_predarenumpyarraysofthesamelength. 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ù),被稱為學(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 |
importnumpyasnp defsigmoid(x): #Sigmoidactivationfunction:f(x)=1/(1+e^(-x)) return1/(1+np.exp(-x)) defderiv_sigmoid(x): #Derivativeofsigmoid:f'(x)=f(x)*(1-f(x)) fx=sigmoid(x) returnfx*(1-fx) defmse_loss(y_true,y_pred): # y_true和y_pred是相同長(zhǎng)度的numpy數(shù)組。 return((y_true-y_pred)**2).mean() classOurNeuralNetwork: ''' Aneuralnetworkwith: -2inputs -ahiddenlayerwith2neurons(h1,h2) -anoutputlayerwith1neuron(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() deffeedforward(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) returno1 deftrain(self,data,all_y_trues): ''' -dataisa(nx2)numpyarray,n=#ofsamplesinthedataset. -all_y_truesisanumpyarraywithnelements. Elementsinall_y_truescorrespondtothoseindata. ''' learn_rate=0.1 epochs=1000#遍歷整個(gè)數(shù)據(jù)集的次數(shù) forepochinrange(epochs): forx,y_trueinzip(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_w1represents"partialL/partialw1" d_L_d_ypred=-2*(y_true-y_pred) #Neurono1 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) #Neuronh1 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) #Neuronh2 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)重和偏差 #Neuronh1 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 #Neuronh2 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 #Neurono1 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ì)算總損失 ifepoch%10==0: y_preds=np.apply_along_axis(self.feedforward,1,data) loss=mse_loss(all_y_trues,y_preds) print("Epoch%dloss:%.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;
其他類型的激活函數(shù);
其他類型的優(yōu)化器;
學(xué)習(xí)卷積神經(jīng)網(wǎng)絡(luò),這給計(jì)算機(jī)視覺(jué)領(lǐng)域帶來(lái)了革命;
學(xué)習(xí)遞歸神經(jīng)網(wǎng)絡(luò),常用于自然語(yǔ)言處理;
審核編輯:湯梓紅
-
神經(jīng)網(wǎng)絡(luò)
+關(guān)注
關(guān)注
42文章
4726瀏覽量
100315 -
網(wǎng)絡(luò)
+關(guān)注
關(guān)注
14文章
7444瀏覽量
88449 -
函數(shù)
+關(guān)注
關(guān)注
3文章
4262瀏覽量
62233 -
神經(jīng)元
+關(guān)注
關(guān)注
1文章
363瀏覽量
18423 -
python
+關(guān)注
關(guān)注
54文章
4759瀏覽量
84294
原文標(biāo)題:從 0 到 1 實(shí)現(xiàn)神經(jīng)網(wǎng)絡(luò)(Python)
文章出處:【微信號(hào):vision263com,微信公眾號(hào):新機(jī)器視覺(jué)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論