易懂的神經網絡Python實戰:單個神經元+隨機梯度下降學習邏輯與規則
目錄
實踐:用程序表示一個手工設置權重weight和偏置bias的神經元
AI韜鴿的筆記專欄中《零神經網絡實戰》系列持續更新介紹神經元怎麼工作,最後使用python從0到1不調用神經網絡框架(不使用tensorflow等框架)來實現神經網絡。從0基礎角度進行神經網絡實戰。
下一篇:Ai醬:零基礎神經網絡實戰(2):理解並實現反向傳播及驗證神經網絡是否正確
點贊:https://blog.csdn.net/varyshare/phoenix/article/digg?ArticleId=89556131
目錄爲:
邏輯與、破除神經元認知障礙、手工調神經元、讓計算機自己學習參數、隨機梯度下降實踐
作者:知乎 @Ai醬
邏輯與(AND)
我們先不管什麼是神經元,咱們先介紹下邏輯與(AND)是什麼。在計算機中,存儲的是0和1。計算機有很多對這種01操作的運算,其中有一種運算叫做邏輯與,在編程中運算符通常表示爲&
。兩個數進行邏輯與運算的規則就是只要有一個數是0,那麼整個運算就是0。還是不太懂?我們舉個例子。
1&0 == 0
0&1 == 0
0&0 == 0
1&1 == 1
然後我們要解決的問題是,怎麼讓神經元根據這四組數據自己學習到這種規則。接下來我們整理下這四個數據。然後我們將x1&x2==y
的四種情況可以表示爲如下所示。我們需要做的是讓一個神經元根據這四個數據學習到邏輯與的規則。
x1 x2 y
1 0 0
0 1 0
0 0 0
1 1 1
也就是說,將(x1,x2)視作一個座標,將它描點在二維平面是這樣的。
我們讓神經元能夠學習到將(0,1)、(0,0)、(1,0)這些點分類爲0,將(1,1)這個點分類爲1。更直觀的講就是神經元得是像圖中的這條直線一樣,將四個點劃分成兩類。在直線左下是分類爲0,直線右上分裂爲1.
破除神經元的認知障礙
在人工智能範疇的的神經元它本質是一條直線,這條直線將數據劃分爲兩類。與生物意義上的神經元可謂是千差萬別。你可以理解爲它是受到生物意義上的神經元啓發,然後將它用來形象化數學公式。
既然神經元它在數學意義上就是一條直線那麼,怎麼表示呢?
學計算機一個很重要的思維就是:任何一個系統都是由輸入、輸出、和處理組成。
我們在瞭解神經元也是一樣,在本文中的需求是知道一個座標(x1,x2)
,輸出這個座標的分類y。
我們按照計算機的輸入輸出思維整理下思路:
輸入:x1,x2
輸出:y
處理:自己學習到從x1,x2
到y
的一種映射方法
我們知道輸入輸出,並且我們知道它是直線,那麼我們就可以描述這個問題了。
我們讓神經元能夠學習到將(0,1)、(0,0)、(1,0)這些點分類爲0,將(1,1)這個點分類爲1。更直觀的講就是神經元得是像圖中的這條直線一樣,將四個點劃分成兩類。在直線左下是分類爲0,直線右上分裂爲1.
我們繼續看這個圖,在直線的右上方是x1&x2==1
的情況,即分類爲1的情況。在直線左下是x1&x2==0
的情況。這個時候就可以用我們高中的知識來解決這個問題了。在高中我們知道一條直線可以表示爲。由於啊,在神經元裏面大家喜歡把前面的係數叫做權重(weight),把常數項叫做偏置(bias)。因此一般大家把前面係數命名爲,把常數項命名爲。這些都是約定俗成,所以大家記憶下就好。在本文中,我用下面這個公式表示圖中的那條綠色的直線。
根據高中知識,一條直線將平面劃分成兩半,它可以用如下方式來描述所劃分的兩個半平面。
- 左下:,當和至少有一個是0 的時候。
- 右上:,當和都是1 的時候。
其實,就是神經元,一個公式而已。是不是覺得神經元的神祕感頓時消失?大家平常看到的那種圖只不過是受生物啓發而畫的,用圖來描述這個公式而已。我們畫個圖來表示神經元,這種畫圖方法是受到生物啓發。而求解圖中的參數則從來都是數學家早就知道的方法(最小二乘法)。
然後,前面提到過,神經元的形象化是受到生物啓發。所以我們先介紹下怎麼啓發的。
生物意義上的神經元,有突觸(輸入),有軸突(輸出),有細胞核(處理)。並且神經元傳輸的時候信號只有兩個正離子和負離子。這和計算機1和0很類似。
總結下,神經元有突觸(輸入),有軸突(輸出),有細胞核(處理)
。開工畫圖:
咱們把上面這個神經元的數學表達式寫一下對比下,.以後啊,大家見到神經元就用這種方式理解就可以。大家不要對神經網絡過於迷信化和模糊化,它不是玄學。神經元不是玄學,不是藝術(我只是那種感性經驗思維),它是數學和生物啓發結合的產物。
好現在我們知道神經元的數學表達和怎麼畫它的形象化的圖了。那麼不如我們用編程也表達下它吧?
實踐:用程序表示一個手工設置權重weight和偏置bias的神經元
去掉註釋就10行左右的代碼,好現在自己動手實踐下吧?計算機思維:動手寫代碼進行實踐是進步的唯一方法
# -*- coding: utf-8 -*-
"""
讓神經元學習到邏輯與這個規則
@author: 李韜_varyshare
"""
class Neuron(object):
def __init__(self):
"""
手工設置神經元的權重w_i(輸入x_i前面的係數),和偏置常數項b=-1.1
"""
# 初始化權重數組,假定weights[i]和公式中的wi一一對應
self.weights = [1.0,1.0]
# 偏置也初始化爲0
self.bias = -1.1
def f(self,x0,x1):
"""
返回值是 weights[0]*x0+weights[1]*x1 + bias > 0? 1:0;
計算這個用於判斷(x0,x1)的分類。大於0則是點(x0,x1)在右上輸出1,小於0則點在左下輸出0;
"""
if self.weights[0]*x0+self.weights[1]*x1 + self.bias > 0:
return 1
else:
return 0
# 哈哈我們手工設置了一個神經元能完美實現邏輯與
n = Neuron()
# 0&1==0
print('0&1=',n.f(0,1))
# 1&0==0
print('1&0=',n.f(1,0))
# 0&0==0
print('0&0=',n.f(0,0))
# 1&1 == 1
print('1&1=',n.f(1,1))
"""
輸出:
0&1= 0
1&0= 0
0&0= 0
1&1= 1
"""
那麼怎麼讓計算機自己確定神經元的參數?
現在問題來了,現在我們有三個參數,我們怎麼設置這三個參數呢?一個很直觀的想法就是自己手動調。
別笑,還真可以。因爲我們現在要解決的問題太簡單了。一個很直觀的解就是隻要直線的斜率爲-1,即.然後我們讓b=-1.1就可以完美實現邏輯與分類功能。也就是說這就是一個符合條件的神經元。
好了,可以洗洗睡散了。
但是,通常我們要解決的問題比這個複雜的多,神經網絡是由神經元連接而成。而一般神經網絡都是有幾百個神經元。假設有200個神經元,我們現在一個神經元有3個參數,那麼我們手動需要調200*3=600
個參數。想想都覺得頭皮發麻,而且200個神經元這還算少的,AlphaGo那種級別得上千個。恐怖如斯
。
梯度下降
堆一堆公式沒意思,我接地氣的講講吧。分三點:1.直觀理解 2. 梯度下降怎麼做(附帶推薦編程實踐) 3. 梯度下降有啥用
1。直觀理解梯度下降
其實它沒啥就是讓計算機不斷猜最小值的那個點自變量x在哪,猜大了讓它小一點,猜小了讓它大一點。猜個幾萬次就猜中了。怎麼猜的根據梯度猜的。梯度是啥,導數。怎麼根據導數猜的?高中老師教過當前這個點導數大於0證明它周圍單調增,最小值點肯定比當前猜的這個點小,那我下次就猜小一點(因爲我想找最小值點在哪)。導數小於0同理。沒看懂?沒關係看下面的。
想象下:
按照上面那個圖的特點,假設這個圖放大1萬倍,大到你不能一眼看到最小值。那麼要你找最小值對應的自變量x,你怎麼找??記住我們目的是爲了找自變量x,記住我們目的是爲了找x
你將可能會在電腦屏幕看到原先那個圖的局部,按照它們單調性來分主要有這三種情況
當你遇到情況1:單調下降,導數爲負(梯度爲負),要想找到函數的最小值所對應的自變量的值(曲線最低點對應x的值)怎麼走?當然是水平向右滑啦,也就是讓x增大,此時隨着x增大,函數值是減小(下降)的(梯度下降含義懂了吧哈哈就這個意思)
當你遇到情況2:單調上升,導數爲正(梯度爲正),要想找到函數的自變量的值(曲線最低點對應x的值)怎麼走?當然是水平向左滑啦,也就是讓x減小,此時隨着x減小,函數值減小的(也就是梯度下降)。
綜上所述:
- 梯度下降:兩個意思,1.根據梯度(導數)的符號來判斷最小值點x在哪; 2. 讓函數值下降(變小)。
- 梯度就是導數(對於多維就是偏導數)
- 梯度下降作用是找到函數的最小值所對應的自變量的值(曲線最低點對應x的值)。記住我們目的是爲了找x.
2。梯度下降怎麼做的?
- 梯度下降它是一個函數f(x)找它的最小值所在的那個點 的一種方法。
- 怎麼找呢?它原理就是猜,按照一定規則的猜。怎麼猜的?
- 高中知識告訴我們 函數單調遞增。增大 函數值也會變大。我們想找最小值那麼最小值那個點 肯定比在當前這個點 小。那麼我們讓 小一點。
- 然後計算 。
- 同理,只不過得讓 大一點。
- 直到f'(x)==0,因爲鍋底很平坦導數是0.此時就找到了最小值所在的那個點 。買大買小,按照一定規則猜。梯度下降就是按照導數來猜最小值所在的那個點x在哪。
- 參考知乎專欄《適合初學者的機器學習神經網絡理論到實踐》裏面這幾個文章。推薦這個專欄。
4。梯度下降有啥用?
機器學習和神經網絡,還有各種數學建模問題。最終不是有個目標函數|損失函數|代價函數|誤差函數,不管叫啥函數反正他們都一樣。反正就是求他最小值點x。不是求最小值點?加個負號不就好了。
注意是最小值點而不是最小值。
爲啥?因爲機器學習和神經網絡等等他們損失函數|代價函數|誤差函數最小值肯定是0啊。
我們想要的是當誤差最小時,最優自變量|權重|參數是啥。不同問題叫法不一樣,但是一樣的是他們都是想找自變量而不是函數值。
當然,數學家不會這麼傻乎乎的自己手動調。那數學家怎麼解決的呢?
現在我們的神經元用公式可以表示爲,現在未知參數是。那麼我們怎麼衡量這些未知參數到底等於多少纔是最優的呢?
這就得想個辦法去量化它對吧。在物理界有個東西叫做誤差。我們神經元不就是一個函數麼,我們要做的是讓這個函數儘可能的準確模擬邏輯與(&)這個功能。假設邏輯與(&)可以表示成一個函數。那麼用物理界的術語,我可以稱呼爲真實值,爲測量值。真實值和測量值之間的誤差可以兩者相減並平方來量化。
其實,本文中的非常簡單。就四個點而已。
x1 x2 g(x1,x2)
1 0 0
0 1 0
0 0 0
1 1 1
這樣我們就得到了神經元的誤差,不同的參數是對應不同的誤差值。這樣我們得到了一個誤差函數,它的自變量是。所以問題就轉變爲了求誤差函數最小時,的取值就是最優的參數。
注意了:誤差函數=代價函數=目標函數=損失函數,這三個詞可以隨意替換的。所以你們在其他地方看到這三個詞就都替換成誤差函數就可以。別被概念搞蒙了。一定記住誤差函數(error)=代價函數(cost function)=目標函數(objective function)=損失函數(loss function)
好是時候表達一波誤差函數了,注意誤差函數是隨。不同的參數是對應不同的誤差。本文中誤差函數可以這麼寫。然後,現在好像看到右邊沒有慌得狠。那麼我們快點將代入寫成看得見的形式。展開後可以寫成這個樣子:.
看一看,你覺得它長啥樣?如果是隻看的話,它是個過原點的二次函數。如果綜合看,它像口鍋。如果看的話,這個維度太高。我等人類想象不出來。
那怎麼求的最小值點呢?注意: 最小值點含有兩個意思。一是:我們要求的是自變量的值,二是函數在這個自變量值取最小值。
再次強調:我們要求的是的值。因爲神經元就是隻有這三個未知量,我們不關心其他的。而且在本文中的的最小值很明顯,它就是0。它是個完全平方嘛肯定>=0。
那怎麼求這個誤差函數的最小值點呢?
數學家想到了一個辦法叫做梯度下降
。大家不知道這個方法沒關係,現在你需要知道的是梯度下降
可以讓計算機自動求一個函數最小值時它的自變量值就可以了。注意梯度下降只關心自變量值。不關心函數值。這兒你迷糊也沒關係,看到後面回頭看這句話就懂了。反正梯度下降它做的事就是找自變量。
接下來咱們一起來學習下啥是梯度下降?啥是梯度?爲何下降?梯度下降爲了能求得最優參數?
是時候回答前面的“梯度下降”三問了。
- 啥是梯度?梯度就是導數,大家見到“梯度”就把它替換成導數就可以。在多維情況下梯度也是導數只不過是個向量,這個向量每個元素是一個偏導數。
- 爲何下降?因爲在數學裏面(因爲神經網絡優化本來就是數學問題),在數學裏面優化一般只求最小值點。那麼有些問題要求最大值點怎麼辦?答:“在前面加負號”。簡單粗暴就解決了。好繼續回答爲何下降,因爲是求最小值點,那麼這個函數肯定就像一口鍋,我們要找的就是鍋底的那個點在哪,然後我們當然要下降才能找到最小值點啦。
- 爲何梯度下降能求最優參數?接下來的幾段,聽我娓娓道來。因爲只有知道梯度下降怎麼做的,才知道爲何它能求最優參數。所以接下來我要介紹的是,
梯度下降
如何實現利用負梯度
進行下降
的。
再次總結下,我們需要讓計算機求的參數是. 我們先講怎麼怎使用梯度下降
求最優的的吧。會求其它的幾個參數都是一樣的求法。
那梯度下降怎麼做到求最優的呢?(最優指的是誤差函數此時取最小)
它呀,採取的一個策略是猜。沒錯,就是猜。簡單粗暴。不過它是理性的猜。說的好聽點叫做理性的去估計。我們前面提到了誤差函數關於是一個二次函數,假設長下面這樣。
然後計算機它啊,當然不知道長這樣啦。它只能看到局部。首先一開始它隨便猜,好就猜吧。我們前面提到了計算機它只知道局部。爲何呢?你想,我現在取值是,我可以增大一點點或減小一點點,這樣我當然只知道這周圍的誤差函數值啦。
現在停下來,花3秒鐘,你現在按照你的直覺,,你覺得最優的到底比0.5大還是比0.5小?
我想我們答案應該是一樣的,肯定比0.5小。因爲往右邊走,即讓比0.5大的話,它的誤差函數是增長的。也就是在周圍時候,越大,誤差越大。
其實梯度下降就是讓計算機這麼猜,因爲計算機判斷速度快嘛。猜個上萬次就能很容易猜到最優值。
回顧下我們怎麼猜的?
我們是直覺上覺得,這個增大誤差函數是增加的。那麼這個怎麼用理性思考呢?這個時候就得用到高中老師叫我們的判斷函數單調性的方法了。求一階導數啊,背口訣:“一階導數>0,函數單調遞增,函數值隨自變量的增大而增大”。
所以,我們只需要讓計算機判斷當前猜的這個點導數是大於0還是小於0. 該點一階導數大於0的話,這個局部是單調遞增的,增大的值,誤差函數值會增大。因此就得減小的值;同理,如果一階導數小於0 ,那麼這個局部是單調遞減的,我們增大的值可以降低誤差函數值。
總結規律:
導數>0, 減小的值。導數<0,增大的值。可以發現,的變化與導數符號相反。因此下次要猜的的值可以這麼表示:下次要猜的的值=本次猜的的值-導數。有時候導數太大也可能會猜跳的太猛,我們在導數前面乘個小數防止猜的太猛。這個小數大家通常稱呼它爲學習率
。(從這裏可以看出學習率肯定小於1的)因此可以這麼表示:下次要猜的的值=本次猜的的值-學習率導數。*學習率一般設置0.001,0.01,0.03等慢慢增大,如果計算機猜了很久沒猜到證明得讓它放開步子猜了。就增大它。
現在我們知道梯度下降幹嘛了吧,它就是理性的猜最優值。那什麼時候結束猜的過程呢?我們看下圖,發現它最優值的那個地方導數也是0.那麼我們只需要判斷誤差函數對的導數是否接近0就可以。比如它絕對值小於0.01我們就認爲已經到了最優點了。
誤差函數是這個:. 對求導,得到導函數.
現在總結下梯度下降(採用的是很常用的隨機梯度下降)的步驟:
初始化。(隨便猜一個數),比如讓;// 注意了,同樣數據反覆輸入進去訓練的次數大家一般叫他epoch,// 大家都這麼叫記住就可以,在英語裏面epoch=times=次數循環多次(同樣的數據重複訓練){遍歷樣本(逐個樣本更新參數){ 將樣本,和標籤,代入損失函數對的導函數,求得導數值; 下次要猜的的值=本次猜的的值-學習率*導數值;}}然後我們就可以得到最優的了。
對於剩下的他們是一樣的,都是利用讓計算機猜。大家參考怎麼求的吧。
注意了,因爲我們是二分類問題,雖然我們想讓它直接輸出0和1,但是很抱歉。它只能輸出接近0和接近1的小數。爲了方便編程,我們用-1代替0。也就是說只要神經元輸出負數我們認爲它就是輸出0. 因爲直接判斷它是否接近0和是否接近1編程會更麻煩
注意了,因爲我們是二分類問題,雖然我們想讓它直接輸出0和1,但是很抱歉。它只能輸出接近0和接近1的小數。爲了方便編程,我們用-1代替0。也就是說只要神經元輸出負數我們認爲它就是輸出0. 因爲直接判斷它是否接近0和是否接近1編程會更麻煩
注意了,大家以後也會發現很多數據集二分類問題都會用1和-1而不用0.這是爲了編程方便。我們只需要判斷正負就可以知道是哪個分類。而不是需要判斷是接近0還是接近1
注意我們只有下面這四個樣本數據:
x1 x2 g(x1,x2)
1 0 -1
0 1 -1
0 0 -1
1 1 1
然後我們用僞代碼表示下梯度下降求的值:(10行左右解決隨機梯度下降是不是很激動)
# 先隨便猜w1,w2,b是多少
w1 = 0.5
w2= 0.8
b = 0.2
#我們設置一個學習率防止猜得太猛,比如跨度太大會從4猜到3,下一步就是從3猜到4
learning_rate = 0.01
do{
for (x1,x2) in 樣本集:
# 我們先對各參數求下導數
L關於w1的導數=2*(w1*x1+w2*x2+b-g(x1,x2))*x1
L關於w2的導數=2*(w1*x1+w2*x2+b-g(x1,x2))*x2
L關於b的導數=2*(w1*x1+w2*x2+b-g(x1,x2))
# 接下來是猜各參數的環節
# 下次猜的= 本次猜的 - 學習率*導數
w1 = w1 - learning_rate*L關於w1的導數
w2 = w2 - learning_rate*L關於w2的導數
b = b - learning_rate*L關於b的導數
}while(足夠多次數epoch);
# 這樣我們就得到了最優的參數了
輸出(w1,w2,b)
實踐:動手實現隨機梯度下降(根據上面的那個僞代碼)
# -*- coding: utf-8 -*-
"""
讓神經元學習到邏輯與這個規則
@author: 李韜_varyshare
"""
# 先隨便猜w1,w2,b是多少
w1 = 0.666
w2 = 0.333
b = 0.233
def train():
# 用於訓練的數據(四行)一行樣本數據格式爲 [x1,x2,g(x1,x2)]
data = [
[1, 0, -1],
[0, 1, -1],
[0, 0, -1],
[1, 1, 1]
]
global w1,w2,b # 告訴計算機我修改的是全局變量(每個函數都能修改這個變量)
epoch = 20 # 同樣的數據反覆訓練20次
for _ in range(epoch):
# 逐個樣本更新權重
for i in data:
# 這裏的i = [x1,x2,g(x1,x2)],它是data中的一行
# 求各自導函數在(x1,x2,g(x1,x2))處的導函數值
d_w1 = 2*(w1*i[0]+w2*i[1]+b-i[2])*i[0]
d_w2 = 2*(w1*i[0]+w2*i[1]+b-i[2])*i[1]
d_b = 2*(w1*i[0]+w2*i[1]+b-i[2])
# 接下來就是愉快的理性猜環節了
# 設置學習率,防止蹦的步子太大
learning_rate = 0.01
# 下次猜的數 = 本次猜的數 - 學習率*導數值
w1_next = w1 - learning_rate*d_w1
w2_next = w2 - learning_rate*d_w2
b_next = b - learning_rate*d_b
# 更新各參數
w1 = w1_next
w2 = w2_next
b = b_next
pass
pass
def f(x1,x2):
"""
這是一個神經元(本質就是一個表達式)
經過訓練,我們期望它的返回值是x1&x2
返回值是 w1*x1+w2*x2 + b > 0? 1:0;
計算這個用於判斷(x0,x1)的分類。
大於0則是點(x0,x1)在右上輸出1,小於0則點在左下輸出0;
"""
global w1,w2,b # 告訴計算機我修改的是全局變量(每個函數都能修改這個變量)
if w1*x1+w2*x2 + b > 0:
return 1
else:
return 0
# 我們首先執行下訓練,讓神經元自己根據四條數據學習邏輯與的規則
train()
# 打印出模型計算出來的三個比較優的參數
print(w1,w2,b)
"""
輸出:0.4514297388906616 0.2369025056182418 -0.611635769357402
"""
# 好我們測試下,看神經元有沒有自己學習到邏輯與的規則
print("0&1",f(0,1))
print("1&0",f(1,0))
print("0&0",f(0,0))
print("1&1",f(1,1))
"""
輸出:
0&1= 0
1&0= 0
0&0= 0
1&1= 1
"""
到這裏我們已經完成了神經元自己學習邏輯與規則的實踐了
github代碼下載地址:https://github.com/varyshare/newbie_neural_network_practice/blob/master/neuron_gradient_descent.py
打開你的編輯器,參照我的思路實現吧
如果文章對你有幫助,不如點個贊,生活充滿儀式感。你的贊是我持續更新的動力。
[1] 知乎專欄《適合初學者的神經網絡機器學習入門理論到實踐》
[2] 知乎人工智能、機器人專注初學者教程答主 Ai醬
猜你喜歡:
初學者機器學習神經網絡資料分享,與學習探討羣: