AlphaGo在人機(jī)大戰(zhàn)中戰(zhàn)勝李世乭讓深度學(xué)習(xí)這個(gè)名詞在大眾口中廣為傳播赠涮,深度學(xué)習(xí)的強(qiáng)大子寓,也讓其增加了一層神秘色彩,似乎這是名校博士才能理解的算法笋除。雖然相關(guān)的論文自己也無法看懂斜友,然而,在網(wǎng)絡(luò)查閱了無數(shù)資料之后垃它,才發(fā)現(xiàn)如果不去糾結(jié)理論的證明鲜屏,只關(guān)心結(jié)論的話烹看,神經(jīng)網(wǎng)絡(luò)算法也并非高不可攀。
在查閱資料的過程中墙歪,有幸看到iamtrask的一篇文章A Neural Network in 11 lines of Python (Part 1),文章拋棄推理贝奇,直接從結(jié)論出發(fā)虹菲,用簡(jiǎn)單的Python實(shí)現(xiàn)了一個(gè)簡(jiǎn)易的神經(jīng)網(wǎng)絡(luò)算法,對(duì)于像我這樣數(shù)學(xué)基礎(chǔ)一般的初學(xué)者掉瞳,具有很大的啟發(fā)作用毕源。本文主要參考這篇文章,并加入自己的理解陕习,試圖以易于理解的方式闡述其思路霎褐,如果有表述不當(dāng)?shù)牡胤剑梢詤⒖荚母昧停蛘邊⒖计?a target="_blank" rel="nofollow">中文翻譯冻璃。
什么是神經(jīng)網(wǎng)絡(luò)算法
在解釋什么是神經(jīng)網(wǎng)絡(luò)之前,我們先明確幾個(gè)機(jī)器學(xué)習(xí)中的術(shù)語:特征损合,樣本省艳,監(jiān)督學(xué)習(xí)
- 樣本:可以用于學(xué)習(xí)的個(gè)體,我們可以獲得這些個(gè)體的其某些信息嫁审,并且我們已知這些個(gè)體的類別
- 特征:同一屬性在不同個(gè)體所體現(xiàn)出來的特點(diǎn)跋炕,在神經(jīng)網(wǎng)絡(luò)中,特征應(yīng)該是易于提取的
- 監(jiān)督學(xué)習(xí):根據(jù)樣本中個(gè)體的特征以及類別律适,然后創(chuàng)建我們的判定規(guī)則辐烂,并依靠這些調(diào)查所得信息,調(diào)整我們的規(guī)則捂贿,使得規(guī)則的預(yù)測(cè)結(jié)果盡可能接近我們的樣本纠修,這樣,我們利用這個(gè)規(guī)則和新個(gè)體的特征信息厂僧,來判定其所屬分類
舉個(gè)例子分瘾,我們想要構(gòu)建一套性別識(shí)別算法,而我們能夠獲得的特征信息只有三種:這個(gè)人不是長(zhǎng)頭發(fā)吁系;衣服顏色是紅色德召;身高是否超過了178cm。那以一個(gè)正常成年人的思維汽纤,我們應(yīng)該采用什么策略來判斷這個(gè)人的性別呢上岗?
我想大部分人都會(huì)采用和我一樣的策略:依據(jù)我們平時(shí)的經(jīng)驗(yàn),一個(gè)人如果留長(zhǎng)頭發(fā)蕴坪,很大可能這個(gè)人是女的肴掷,而一個(gè)人穿紅色衣服敬锐,很難判斷其性別,一個(gè)人身高超過175cm呆瞻,在中國(guó)人里面可能都屬于偏高的台夺,而依據(jù)我們的經(jīng)驗(yàn),男性的平均身高要大于女性痴脾,所以單純通過身高這個(gè)信息颤介,我們推斷這個(gè)人是男性的可能性較大。
以上推測(cè)過程中赞赖,我們對(duì)每一個(gè)條件的推斷都基于一個(gè)詞滚朵,經(jīng)驗(yàn),這里的經(jīng)驗(yàn)就是我們長(zhǎng)期以來觀察身邊的人的特征所留下的記憶前域。而我們的推測(cè)結(jié)果主要用了一個(gè)詞匯辕近,可能性,其代表基于某條已知信息推測(cè)出某個(gè)未知結(jié)論的概率匿垄。
科學(xué)家將我們的思維方法歸納為信息在神經(jīng)元之間的傳導(dǎo)過程移宅,這些神經(jīng)元每一個(gè)都處理及簡(jiǎn)單的信息,但是通過無數(shù)個(gè)神經(jīng)元之間錯(cuò)綜復(fù)雜的連接傳導(dǎo)椿疗,使得我們的大腦能夠處理及其復(fù)雜的信息吞杭。神經(jīng)網(wǎng)絡(luò)算法就是對(duì)我們大腦思維方式的抽象,比如在上面的例子中变丧,我們將每一個(gè)特征芽狗,輸入到一個(gè)神經(jīng)元,這些接受輸入的神經(jīng)元構(gòu)成了第一層神經(jīng)網(wǎng)絡(luò)痒蓬,也叫做輸入層童擎,我們的目標(biāo)是判斷一個(gè)人的性別,是男是女分屬于兩個(gè)不同類別攻晒,我們將其抽象為一個(gè)神經(jīng)元顾复,這個(gè)神經(jīng)元構(gòu)成神經(jīng)網(wǎng)絡(luò)的輸出層,每一個(gè)特征(輸入層神經(jīng)元)鲁捏,都有到分類(輸出層神經(jīng)元)的連接芯砸,也就是從特征轉(zhuǎn)化為分類的概率(權(quán)重),通過綜合各個(gè)連接的權(quán)重给梅,傳送到輸出神經(jīng)元假丧。
以上便是神經(jīng)網(wǎng)絡(luò)算法最簡(jiǎn)單的模型,神經(jīng)網(wǎng)絡(luò)算法的學(xué)習(xí)過程中动羽,首先隨機(jī)初始化各層神經(jīng)元之間的連接權(quán)重(上文中的可能性)包帚,然后輸入樣本進(jìn)行預(yù)測(cè),根據(jù)預(yù)測(cè)的誤差运吓,調(diào)整神經(jīng)網(wǎng)絡(luò)中的權(quán)重渴邦,完成這個(gè)過程之后疯趟,我們的神經(jīng)網(wǎng)絡(luò)就訓(xùn)練好了。所以谋梭,神經(jīng)網(wǎng)絡(luò)訓(xùn)練的結(jié)果信峻,就是得到各個(gè)連接的權(quán)重(經(jīng)驗(yàn)),這樣瓮床,一旦有新的數(shù)據(jù)輸入神經(jīng)網(wǎng)絡(luò)的時(shí)候盹舞,就能推測(cè)出其分類了。
神經(jīng)網(wǎng)絡(luò)算法實(shí)例
問題定義
還是用上面的預(yù)測(cè)性別的例子纤垂,現(xiàn)在將其數(shù)學(xué)化矾策,假設(shè)在一群人中磷账,我們只能獲得每個(gè)人的三個(gè)特征:
- 特征1:長(zhǎng)發(fā)(1)還是短發(fā)(0)
- 特征2:衣服顏色是紅色(1)還是不是紅色(0)
- 特征3:身高大于178cm(1)還是不超過178(0)
假設(shè)我們只知道其中四個(gè)人的性別(男:0峭沦,女:1),我們需要依據(jù)這四個(gè)人的三個(gè)特征以及性別訓(xùn)練一個(gè)神經(jīng)網(wǎng)絡(luò)逃糟,用于預(yù)測(cè)一個(gè)人的性別吼鱼。樣本信息如下:
頭發(fā) | 衣服 | 身高 | 性別 |
---|---|---|---|
0 | 0 | 1 | 0 |
1 | 1 | 1 | 1 |
1 | 0 | 1 | 1 |
0 | 1 | 1 | 0 |
下面我們先實(shí)現(xiàn)最簡(jiǎn)單的單層神經(jīng)網(wǎng)絡(luò)。我們用X表示輸入的特征向量绰咽,由于每個(gè)樣本有三個(gè)特征菇肃,一共有四個(gè)樣本,所以我們定義一個(gè)4X3的矩陣取募,每一行代表一個(gè)樣本琐谤,如下代碼所示。其中玩敏,NumPy是Python語言的一個(gè)擴(kuò)充程序庫(kù)斗忌。支持高級(jí)大量的維度數(shù)組與矩陣運(yùn)算,此外也針對(duì)數(shù)組運(yùn)算提供大量的數(shù)學(xué)函數(shù)庫(kù)旺聚。
#import numpy
import numpy as np
# input dataset
X = np.array([ [0,0,1],
[0,1,1],
[1,0,1],
[1,1,1] ])
而四個(gè)樣本對(duì)應(yīng)輸出(分類結(jié)果)我們用一個(gè)1X4的矩陣表示织阳。“.T” 為轉(zhuǎn)置函數(shù)砰粹,轉(zhuǎn)置后變成了4X1的矩陣唧躲。同我們的輸入一致,每一行是一個(gè)訓(xùn)練實(shí)例碱璃,而每一列(僅有一列)對(duì)應(yīng)一個(gè)輸出節(jié)點(diǎn)弄痹。因此,這個(gè)網(wǎng)絡(luò)還有三個(gè)輸入和一個(gè)輸出嵌器。代碼如下所示:
# output dataset
y = np.array([[0,0,1,1]]).T
訓(xùn)練開始之前界酒,我們先要初始化神經(jīng)網(wǎng)絡(luò)的權(quán)重,由于輸入層有三個(gè)神經(jīng)元嘴秸,而輸出結(jié)果只有一個(gè)神經(jīng)元毁欣,所以權(quán)重矩陣為3X1庇谆。由于一般初始化權(quán)重是隨機(jī)選擇的,因此要為隨機(jī)數(shù)設(shè)定產(chǎn)生的種子凭疮,如下第一行代碼所示饭耳。這樣可以使每次訓(xùn)練開始時(shí),得到的訓(xùn)練隨機(jī)數(shù)都是一致的执解。這樣便于觀察策略變動(dòng)是如何影響網(wǎng)絡(luò)訓(xùn)練的寞肖,消除初始權(quán)重的影響。
對(duì)于第二行代碼衰腌,這里由于我們要將隨機(jī)初始化的權(quán)重矩陣均值設(shè)定為 0 (至于權(quán)重矩陣的初始化新蟆,大家有興趣的話,請(qǐng)查看相關(guān)資料)右蕊。因此使用第二行代碼來計(jì)算syn0(第一層網(wǎng)絡(luò)間的權(quán)重矩陣)琼稻,如下所示:
# seed random numbers to make calculation
# deterministic (just a good practice)
np.random.seed(1)
# initialize weights randomly with mean 0
syn0 = 2*np.random.random((3,1)) - 1
為了將輸出的權(quán)重歸一化,定義一個(gè)sigmoid函數(shù)饶囚,其定義為:
sigmoid函數(shù)可以用以下Python代碼實(shí)現(xiàn)帕翻,其中,deriv參數(shù)表示是否計(jì)算的是其導(dǎo)數(shù)值:
# sigmoid function
def nonlin(x,deriv=False):
if(deriv==True):
return x*(1-x)
return 1/(1+np.exp(-x))
其函數(shù)圖像如下圖所示:
sigmoid函數(shù)的特點(diǎn)是萝风,其導(dǎo)數(shù)可以用其自身表示出來嘀掸,在計(jì)算的時(shí)候,我們只需要計(jì)算出其函數(shù)值规惰,就可以計(jì)算出其導(dǎo)數(shù)值睬塌,從而可以減少浮點(diǎn)運(yùn)算次數(shù),提高效率歇万,其導(dǎo)數(shù)如下:
接下來揩晴,我們開始訓(xùn)練神經(jīng)網(wǎng)絡(luò):
for iter in range(10000):
# forward propagation
l0 = X
l1 = nonlin(np.dot(l0,syn0))
# how much did we miss?
l1_error = y - l1
# multiply how much we missed by the
# slope of the sigmoid at the values in l1
l1_delta = l1_error * nonlin(l1,True)
# update weights
syn0 += np.dot(l0.T,l1_delta)
我們的訓(xùn)練過程迭代10000次,以得到一個(gè)較優(yōu)的結(jié)果堕花,每一次迭代的過程可以描述為:
- 計(jì)算輸入層的加權(quán)和文狱,即用輸入矩陣L0乘以權(quán)重矩陣syn0,并通過sigmid函數(shù)進(jìn)行歸一化缘挽。得到輸出結(jié)果l1瞄崇;
- 計(jì)算輸出結(jié)果L1與真實(shí)結(jié)果y之間的誤差L1_error;
- 計(jì)算權(quán)重矩陣的修正L1_delta壕曼,即用誤差乘以sigmoid在L處的導(dǎo)數(shù)苏研;
- 用L1_delta更新權(quán)重矩陣syn0
此處著重解釋 下第三步,這里利用的是梯度下降法腮郊,算法的原理我們暫不深究摹蘑,只需要明白其目的是為了使迭代后的誤差逐漸減小即可。
一次訓(xùn)練過程的參數(shù)更新如下圖所示:
由于我們的輸入X中一共有四個(gè)樣本轧飞,我們進(jìn)行“批量的訓(xùn)練”衅鹿,所以其過程類似于下圖所示:
在上述代碼中撒踪,我們通過10000次迭代,我們得到的輸出結(jié)果如下:
Output syn0 After Training:
[[ 9.67299303]
[-0.2078435 ]
[-4.62963669]]
可以看出大渤,syn0的第一個(gè)元素制妄,也就是第一個(gè)輸入特征(長(zhǎng)發(fā))的權(quán)重最大,而第二個(gè)和第三個(gè)特征都很小泵三,所以神經(jīng)網(wǎng)絡(luò)學(xué)習(xí)的結(jié)果是加重第一個(gè)特征的權(quán)重耕捞,而其他兩個(gè)特征對(duì)于是女性這個(gè)推測(cè)的貢獻(xiàn)較小,所以減小其權(quán)重烫幕。為了驗(yàn)證訓(xùn)練結(jié)果俺抽,我們加入兩組新數(shù)據(jù),(短頭發(fā)较曼,紅衣服磷斧,矮個(gè)子),(長(zhǎng)頭發(fā)诗芜,不是紅衣服瞳抓,矮個(gè)子)埃疫,并用神經(jīng)網(wǎng)絡(luò)來進(jìn)行分類:
X_new = np.array([[0,1,0],
[1,0,0]])
y_new = np.dot(X_new,syn0)
計(jì)算結(jié)果如下:
Predicte With syn0:
[[-0.2078435 ]
[ 9.67299303]]
二層神經(jīng)網(wǎng)絡(luò)
上面的例子中伏恐,我們只用了一層神經(jīng)網(wǎng)絡(luò),這只能解決線性問題栓霜,而現(xiàn)實(shí)中翠桦,一個(gè)孤立的特征并不是對(duì)應(yīng)一個(gè)分類,還是用上面的例子說明:上面的問題中胳蛮,我們假定了長(zhǎng)頭發(fā)是女性的概率一定大于男性销凑,高個(gè)子是男性的概率一定大于女性,這種假設(shè)中仅炊,特征和分類是一種確定的關(guān)系斗幼,而特征之間沒有依賴關(guān)系。而現(xiàn)在抚垄,我修改這種假設(shè)蜕窿,在穿紅衣服的人群中,長(zhǎng)頭發(fā)更可能是女性呆馁,而在穿其他顏色的衣服中桐经,短頭發(fā)更有可能是女性,此時(shí)浙滤,我們上面的神經(jīng)網(wǎng)絡(luò)模型就失效了阴挣,因?yàn)槲覀儫o法直接建頭發(fā)這個(gè)輸入特征到性別這個(gè)輸入的直接聯(lián)系。
為了解決上面的問題纺腊,我們需要在加入一層神經(jīng)網(wǎng)絡(luò)畔咧,將輸入層的特征進(jìn)行組合茎芭,然后在傳導(dǎo)到輸出層,這就是二層神經(jīng)網(wǎng)絡(luò)的模型誓沸,其示意圖如下:
中間加入的這一層佳作隱含層骗爆,由于這一層的加入,我們多了一層傳導(dǎo)蔽介,所以初始化的時(shí)候需要再加入一個(gè)權(quán)重矩陣:
syn1 = 2*np.random.random((4,1)) - 1
兩層神經(jīng)網(wǎng)絡(luò)的學(xué)習(xí)更新過程如下:
for j in range(60000):
# Feed forward through layers 0, 1, and 2
l0 = X
l1 = nonlin(np.dot(l0,syn0))
l2 = nonlin(np.dot(l1,syn1))
# how much did we miss the target value?
l2_error = y - l2
if (j% 10000) == 0:
print("Error:" + str(np.mean(np.abs(l2_error))))
# in what direction is the target value?
# were we really sure? if so, don't change too much.
l2_delta = l2_error*nonlin(l2,deriv=True)
# how much did each l1 value contribute to the l2 error (according to the weights)?
l1_error = l2_delta.dot(syn1.T)
# in what direction is the target l1?
# were we really sure? if so, don't change too much.
l1_delta = l1_error * nonlin(l1,deriv=True)
syn1 += l1.T.dot(l2_delta)
syn0 += l0.T.dot(l1_delta)
結(jié)合前面的單層神經(jīng)網(wǎng)絡(luò)的實(shí)現(xiàn)摘投,就很容易理解上面的代碼了,代碼中虹蓄,L0的輸出沒有直接作為最終輸出層犀呼,而是傳導(dǎo)給了L2層,L2層以相同的方式傳導(dǎo)到輸出層薇组。而更新權(quán)重的時(shí)候外臂,采用的是相反的過程,先依據(jù)L2輸出的誤差律胀,更新syn1宋光,再用L2的誤差乘以syn1,作為L(zhǎng)1層的誤差炭菌,最后用同樣的方法更新第一層權(quán)重矩陣syn0
結(jié)語
這篇文章以最簡(jiǎn)單的方式構(gòu)建了一個(gè)基本的神經(jīng)網(wǎng)絡(luò)罪佳,雖然離實(shí)用還相去甚遠(yuǎn),但是已經(jīng)初現(xiàn)神經(jīng)網(wǎng)絡(luò)的雛形框架黑低,如果需要構(gòu)建一個(gè)實(shí)用級(jí)別的神經(jīng)網(wǎng)絡(luò)赘艳,還需要加入一些其他的功能,原作者建議我們從以下這些概念開始入手克握,優(yōu)化我們的神經(jīng)網(wǎng)絡(luò)):
- Alpha
- Bias Units
- Mini-Batches
- Delta Trimming
- Parameterized Layer Sizes
- Regularization
- Dropout
- Momentum
- Batch Normalization
- GPU Compatability
- 其他腦洞