「零基礎」AI神經元解析(含實例代碼) [復制鏈接]

2019-10-8 10:35
BeatBeat 閱讀:65 評論:0 贊:0
Tag:  

一、序言

本文通過使用單個神經元實現圖片“手寫數字9”的識別,從而引申出神經網絡的4個關鍵函數。大部分代碼是前面兩個教程內提供的,我做了一些簡單的處理。希望看完本文后你會說“哇!好神奇”以及“原來如此!好簡單”

二、訓練數據獲取

要做深度學習,首先面臨的第一個問題是,上哪里找到大量的訓練數據,好在前輩們已經為我們準備好了一切,就等你來拿了。

MNIST是一個“手寫數字”的數據集,包含了6萬個訓練數據和1萬個測試數據,怎么理解呢?就是它提供了7萬張圖片,圖片的內容是手寫的各種數字圖片,如下圖所示(不要在意圖片的命名),同時它還提供了圖片對應的具體數字稱為“標注”。圖片+標注的數據對就是我們需要的“大數據”了,所謂AI民工就是干這個“標注”工作的。

「零基礎」AI神經元解析(含實例代碼)


不過MINIST提供的方式不是直接給你下載圖片,而是將圖片按像素轉為像下面這樣的一維的數組。數組中0的部分就是圖片中黑色的部分,大于零的部分就是圖片中白色的部分。

「零基礎」AI神經元解析(含實例代碼)


MNIST提供了下載數據集的方法,而在《深度學習入門:基于Python的理論與實現》這本書中附錄了”mnist.py“文件,里面已經寫好了如何下載mnist數據集并加載到程序中,文末附有下載此文件的方法。

使用此文件時,還需要在你的項目目錄中新建一個”datasets”目錄。

調用“init_mnist()”函數就自動下載該數據集并以pkl格式保存在本地,以后再次調用會自動檢查該文件是否存在,存在的話就不會再次下載了。

調用“load_mnist()”函數可以從pkl文件中導入訓練數據和測試數據,每一個數據集包含若干圖片和對應的正確標注

「零基礎」AI神經元解析(含實例代碼)


如上圖,train_img、train_label分別是導入的訓練圖片和訓練標注,test_img、test_label分別是測試用的圖片和測試標注。

(所有代碼在文末都有的下載方式,不要慌,文章主要講講思路)

我們使用print隨便輸出一個數據看看,print(train_img[9])輸出的是一個一維數組,print(train_label[9])輸出的是這個一維數組對應的標注。然后你應該注意到這里面的數字都是0.9882353這樣的小數,實際上mnist給的是0-255這樣的整數,0表示該像素完全黑色,255表示完全白色。每一張圖片由28x28個像素組成,每一個數就表示該像素的顏色值(都是黑白圖,所以顏色只需要一個維度來表示)。本來應該是28x28的二維數組,為了方便使用就直接轉成了1x784的一維數組。我”mnist.py“文件中在導入MNIST數據時將所有數字都除以255,首先所有數據都除以255不影響數據的使用,其次后面一些公式需要像0~1這樣的小數才可以正確執行。(后面再講)

「零基礎」AI神經元解析(含實例代碼)


「零基礎」AI神經元解析(含實例代碼)


為了簡化整個程序,我們只針對數字9做識別,需要對train_label和test_label做一些修改。這樣圖片內如果寫的是9那相應的標注為1,否則為0。

train_label = np.where(train_label==9,1,0) #將標注為9的改為標注1,其他為0

test_label = np.where(test_label==9,1,0)

print(train_label)

「零基礎」AI神經元解析(含實例代碼)


至此,我們的數據都準備好了,測試的時候6萬個數據似乎有點太多了,所以我們可以裁剪一下,只用6000個數據來訓練,1000個數據來測試。

train_img = np.resize(train_img,(6000,train_img.shape[1])) #裁剪數據集

train_label = np.resize(train_label,(6000,1))

test_img = np.resize(test_img,(1000,test_img.shape[1]))

test_label = np.resize(test_label,(1000,1))

裁剪后的數據形狀:

「零基礎」AI神經元解析(含實例代碼)


但是這樣的數據還是沒法使用,我們還需要再將數據做一次轉置才行(具體原因后面再說)。

train_img = train_img.T

train_label = train_label.T

test_img = test_img.T

test_label = test_label.T

轉置后的數據形狀:

「零基礎」AI神經元解析(含實例代碼)


好了,終于準備好數據了,下面開始深度學習吧。

三、神經網絡的4個函數

神經網絡最基本的模型其實就是由4個函數組成的,所以你可以看到很多教程只需要十幾行代碼就可以構建一個深度學習網絡,這是真實可行的。它們分別是:

傳播函數、激活函數、反向傳播函數、損失函數

他們分別實現了幾個核心功能:向前預測、輸出映射、反向優化、損失計算

乍看上去有點摸不著頭腦,我們先來看一下單個神經元的構成示意圖:

「零基礎」AI神經元解析(含實例代碼)


神經元中x是輸入信號、w和b是預測參數、a是預測的基本結果、h()是激活函數、y是最終預測結果以及下一層神經元的輸入。對于本文要實現的目標“手寫數字9識別”,在訓練階段我們從x輸入大量的數據,然后根據y的結果來反向推導w和b的值如何進行優化。在圖片識別階段,使用優化好的w和b值進行一次計算得到y,根據y的值來判斷被識別圖片是否是數字“9”。下面分別是訓練和測試的示意:

「零基礎」AI神經元解析(含實例代碼)


「零基礎」AI神經元解析(含實例代碼)


目前為止,一切都還比較難以理解,但只需要建立下面幾個模糊的概念:

1、圖片是以一維數組的形式傳入神經元,每一張圖片由784個像素組成,即一維數組的長度為784

2、神經元通過一個叫“傳播函數”的東西嘗試預測輸入的圖片是否是數字9

3、傳播函數的原理是通過參數w和b與輸入x做運算得到結果a

4、結果a又需要通過一個叫“激活函數”的東西來調整其輸出,使其變為我們需要的輸出形式y

5、將輸出y與真實的標注進行對比,并根據對比結果使用“反向傳播函數”來優化w和b

6、最終,傳播函數結合最優的參數w和b就能做所謂的“AI識圖”了(從一堆輸入里識別出手寫數字9)

五、傳播函數

傳播函數的數學公式如下,其中w叫權重、b叫偏置。

「零基礎」AI神經元解析(含實例代碼)


權重w的意義在于體現出x的那些像素比較重要,比如優化后的w1較w2值更大,則說明x1較x2更重要,體現在圖片上則x1更多可能是數字的組成部分,x2更多可能是背景。b的意義在于調整神經元向下傳遞信號的難易程度,要講清楚w和b就需要從感知機開始了,這個我們以后再說,這里只需要記住傳播函數中,用權重w乘以x并加上偏置b就能知道輸入x是否為一張“手寫的數字9”圖片。

上面的公式看起來挺簡單的,但實際操作中又有所不同。

1)輸入x有784個元素

上面的傳播函數公式中,輸入x只有兩個元素,對于我們這里使用的圖片,需要的是784個輸入,所以實際公式應為:

a = b + w1x1 + w2x2 + w3x3 + ...... + w784x784

相應的參數w也有784個元素,所以初始化時我們將w生成為一個(784,1)的數組。

2)一次輸入6000張圖片

我們在訓練時需要輸入6000張圖片,最直觀的應該是寫一個for循環,循環6000次即可,但有時可能訓練圖片是10萬、100萬,那寫個for循環效率就太低了。好在python提供了非常強大的數據處理方式即“矩陣乘積”一次性完成所有計算,具體原理就不細講,只需要知道我們將待處理數據train_img由(6000,784)轉置成(784,6000)的形式就是為了做矩陣的乘積,一次將6000張圖片計算完畢。下圖是矩陣乘積的示意。


總的來講,通過矩陣乘積我們可以一次計算出6000個a,并將結果放入名為A的新矩陣中。

在python中實現上述矩陣乘積非常簡潔,如下:

A = np.dot(W.T, X)+b

其中W.T是權重w的轉置,X是輸入(已經轉置過的),b是偏置,A是計算結果。np.dot()的作用就是將兩個矩陣相乘。需要注意的是X是6000個輸入,A是6000個計算結果。因為權重W和X都是轉置后再相乘的,所以最終結果與6000個for循環一致。

3)總結一下

W的數據格式是(784,1),轉置后是(1,784)

X的數據格式是(784,6000),6000代表一次計算6000個圖片的結果

A的數據格式是(1,6000),A是6000個圖片的計算結果合集

圖解一下計算過程大概是這樣:

「零基礎」AI神經元解析(含實例代碼)


六、激活函數

前面我們解析了傳播函數,它的實現代碼是這樣:

A = np.dot(W.T, X)+b

一次計算的結果大概是這樣,其中每一個A都是一張圖片與參數w、b的計算結果。

「零基礎」AI神經元解析(含實例代碼)


參數w、b優化數百次后再次計算A的結果大概是下圖這樣(先不要考慮如何優化),可以看到大部分數值都還是負數(而且越負越多),但有部分值已變為正值。其實隨著參數的優化,數字9的圖片計算結果會越來越向正數靠攏,而非數字9的圖片計算結果會越來越向負數靠攏。這里先不講原因,但可以先建立這樣的映像“當a的值大于0時圖片為數字9,a的值小于0時圖片不為數字9”且“a的值越大于0則圖片越有可能是數字9,a的值越小于0則圖片越有可能不是數字9”。

「零基礎」AI神經元解析(含實例代碼)


但是這樣的表述無法與“標簽”進行比較,因為標簽的值是0或1(0代表非數字9、1代表是數字9)。如果A的值是0~1的一個數字,那A與標簽值進行比較,我們就能知道當前被識別圖片是更趨向于數字9抑或更趨向于不是數字9。激活函數sigmoid就是這么個作用,它將A的值“映射”到區間0~1方便與標簽進行比較。sigmoid函數式如下:

「零基礎」AI神經元解析(含實例代碼)


使用python也非常易于實現

「零基礎」AI神經元解析(含實例代碼)


從數學上來解析這個式子,我們可以知道:

1)當x為0時,h(x)輸出為0.5。

2)當x趨向于負數,且負的越多則h(x)趨向于0

3)當x趨向于正數,且正的越多則h(x)趨向于1

從數學的角度來理解,它是將X軸的值映射到了Y軸,如此實現了輸入值在0~1區間上的轉化。有了激活函數,我們就可以方便地與標簽進行對比,以此為基礎實現參數的優化。激活函數sigmoid畫成圖像就是這樣(需要注意,sigmoid只是激活函數的一種,還有其他類型的激活函數,原理一樣但是映射的值不一樣):

「零基礎」AI神經元解析(含實例代碼)


七、反向傳播函數

我們先回顧一下前面的內容:

1)使用傳播函數可以用圖片中的每一個像素與權重w和閾值b做運算,計算結果a表明該圖片是否為手寫數字9(a大于0是手寫數字9,a小于0就不是手寫數字9)

2)使用激活函數sigmoid可以將a的值映射到0~1的區間,計算結果y表明該圖片是否為手寫數字9(y大于0.5是手寫數字9,y小于0.5就不是手寫數字9)

雖然前面我們講的很詳細很復雜了,反向傳播函數依舊很難以理解。大體上我們可以建立這樣的映像:

1)傳播函數通過w、b計算出A,通過激活函數又計算出Y

2)反向傳播函數通過Y計算出dw和db

3)使用dw和db,通過一種叫“梯度下降”的方法得到新的w和b

4)使用更新后的w和b重復前面的運算過程

需要注意的是,“梯度下降”是一種更新w和b的方法,不同模型可能會使用不同的方法來更新w和b,只是我們這里使用這個比較容易理解的方法而已。其次我們不討論“反向傳播”的原理,只是對實現過程做分析(因為原理看不懂啊!)

前面激活函數中我們講過,使用激活函數的目的是將A的值轉為0~1的區間值Y,方便與標簽進行對比。對比的方法很直接,使用Y減去標簽值即可:

dZ = Y - train_label

其中Y是激活函數的輸出,代表著圖片是否接近數字9或更遠離數字9。train_label就是所有訓練圖片的標簽值,是判斷Y的依據。dZ是計算結果。截取某一次訓練的數據大概是下面這樣:

「零基礎」AI神經元解析(含實例代碼)


「零基礎」AI神經元解析(含實例代碼)


通過上面的實例演示,我們會發現“若圖片真的是數字9,則Y減去train_label的值會變為負數(因為train_label值為1),若圖片不是數字9,則Y減去train_label的值是不變的(因為train_label的值為0)”。

下面依據dZ的值我們來計算出dw和db。

dw = np.dot(train_img, dZ.T)/m

db = np.sum(dZ)/m

計算方法就很簡單,但是具體含義呢?為什么dw可以用來更新參數w,db可以用來更新參數b?

我們知道train_img是訓練用的圖片數據,前面已經將其轉置為(784,6000)這樣的數據形式,dZ由Y和train_label相減得來,所以dZ的數據形式為(1,6000),轉置后為(6000,1)。所以train_img與dZ.T的乘積結果dw為(784,1)這樣的數據形式,dw剛好契合了權重w的數據形式(784,1)和圖片像素的數量784。

dw計算結果示意如下圖,我們會發現部分dw的值向正數越來越大,部分dw值向負數越來越小。

「零基礎」AI神經元解析(含實例代碼)


對于dZ與train_img的乘積dw,我們可以用一張圖來描述這個過程:

「零基礎」AI神經元解析(含實例代碼)


可以看到,dw的值是其實是6000個圖片中,某一個像素值與dZ的平均乘積。以dw1為例,它是6000張圖片中,所有圖片的第一個像素值,與dZ相乘,再取平均值。我們再回顧一下前面的計算,dZ=Y-train_label。dZ中每一個元素就代表著某一個圖片是更傾向于是數字9(負數)或更傾向于不是數字9(正數)。所以,對于dZ與train_img的乘積dw,我們可以這么理解:

【若dw的某一個元素值為負,且負的越多說明圖片中該像素點在“圖片為數字9的判斷中,權重更大”。若dw的值為正,且正的越多說明圖片中該像素點在“圖片為數字9的判斷中,權重更小”。】

那么相應的,我們是不是可以用dw作為權重w的更新參考?

db的含義我還沒有完全理解,這里只是簡單說明下,db是dZ的和取平均。可以想到,若訓練圖片中有更多的數字9,則db的值偏向于負值,若訓練圖片中有更多的非9,則db的值偏向于正值。

更新參數

前面我們通過傳播函數和激活函數得到圖片為數字9的概率,又通過反向傳播函數計算出了dw和db。有了dw和db,我們就可以更新權重w和偏置b。

w = w - learning_rate*dw

b = b - learning_rate*db

這里的learning_rate叫做學習率,其實就是用來調整dw的幅度,使得w的值可以合理的增加或減少。這里需要注意的是,對于比較重要的像素,其dw是負值,所以這里是用w減去dw(增加該像素的權重)。learning_rate太大或太小都不行,一般就看學習效果如何來手動調整,那如何看學習的效果呢,這就要講到最后一個函數“損失函數”了。

八、損失函數

我們使用的損失函數公式如下:

「零基礎」AI神經元解析(含實例代碼)


實現代碼如下:

cost = -np.sum(train_label*np.log(Y)+(1-train_label)*np.log(1-Y)) / m

其中Y是使用激活函數處理后的數值,即訓練圖片是否為數字9在0~1上的概率分布。cost值的意義在于計算出最新Y與標注train_label間的誤差。實質上這個值在本文中僅作為參考,看當前運算是否延正常路徑進行中(cost值會越來越小)。

九、匯總回顧

前面我們詳細敘述了神經元的4個函數,這里我們就依據這四個函數實現一個單神經元“手寫數字9”識別的AI。完整代碼在文末有下載方式。

#下載數據集,會下載6W個訓練數據核1W個測試數據

init_mnist()

#加載數據集 數據集包括一張圖片和一個正確標注

(train_img,train_label),(test_img,test_label) = load_mnist(normalize=True, flatten=True, one_hot_label=False)

#將label不是9的數據全部轉為0,將9轉為1

train_label = np.where(train_label==9,1,0)

test_label = np.where(test_label==9,1,0)

#修改數組的大小

train_img = np.resize(train_img,(6000,train_img.shape[1]))

train_label = np.resize(train_label,(6000,1))

test_img = np.resize(test_img,(1000,test_img.shape[1]))

test_label = np.resize(test_label,(1000,1))

#需要將數據進行轉置

train_img = train_img.T

train_label = train_label.T

test_img = test_img.T

test_label = test_label.T

#sigmoid函數

def sigmoid(x):

s = 1/(1+np.exp(-x))

return s

#初始化權重數組w和偏置b(默認為0)

def initialize_with_zeros(dim):

w = np.zeros((dim,1))#全零的數組

b = 0

return w,b

#通過Y和標注計算成本

def costCAL(img, label, Y):

m = img.shape[1]

cost = -np.sum(train_label*np.log(Y)+(1-train_label)*np.log(1-Y)) / m

return cost

#向前傳播得到Y

def propagate(w, b, img):

m = img.shape[1]

#向前傳播

A = np.dot(w.T, img)+b

#使用激活函數將A的值映射到0~1的區間

Y = sigmoid(A)

return Y

#反向傳播得到dw、db

def back_propagate(Y, img, label):

m = img.shape[1]

dZ = Y - label

dw = np.dot(img,dZ.T)/m

db = np.sum(dZ)/m

return dw,db

#通過梯度下降法更新w和b

def optimize(img,label,w,b,num_iterations,learning_rate,print_cost):

#梯度下降法,循環num_iterations次找到最優w和b

for i in range(num_iterations):

#向前傳播一次

Y = propagate(w, b, img)

#計算成本

cost = costCAL(img, label, Y)

#反向傳播得到dw、db

dw,db = back_propagate(Y, img, label)

#更新w和b

w = w - learning_rate*dw

b = b - learning_rate*db

#每100次輸出一下成本

if i%100 ==0:

if print_cost:

print('優化%i次后成本是:%f' %(i,cost))

return w, b

#預測函數

def predict(w, b, img):

m = img.shape[1]

Y_prediction = np.zeros((1,m))

#向前傳播得到Y

Y = propagate(w, b, img)

for i in range(Y.shape[1]):

#若Y的值大于等于0.5就認為該圖片為數字9,否則不是數字9

if Y[0,i] >= 0.5:

Y_prediction[0,i] = 1

return Y_prediction

#按訓練圖片的數量生成w和b,

w,b = initialize_with_zeros(train_img.shape[0])

#通過梯度下降法更新w和b

w, b = optimize(train_img,train_label,w,b,2000,0.005,True)

Y_prediction_train = predict(w, b, train_img)

Y_prediction_test = predict(w, b, test_img)

print('對訓練圖片的預測準確率為:{}%'.format(100-np.mean(np.abs(Y_prediction_train - train_label))*100))

print('對測試圖片的預測準確率為:{}%'.format(100-np.mean(np.abs(Y_prediction_test - test_label))*100))

十、總結

本文大概對神經網絡中的四個核心函數做了一些解析,還缺少一些數學上的推導留到以后再總結吧。匯總一下前面的信息:

1)圖片以矩陣的形式導入程序,一次可以輸入數千、數萬張圖片批量處理

2)通過矩陣的乘積來計算輸入圖片、權重w、偏置b的值,一次得到6000個圖片的傳播結果A(傳播函數)

3)通過激活函數將A的值映射到0~1的區間得到Y,Y體現的是導入的圖片是數字9的概率(激活函數)

4)通過Y與導入的圖片數據做運算可以得到dw和db,dw體現的是某像素在判斷圖片是否為數字9的作用大不大(反向傳播)

5)通過將dw加到w上來更新w,可以讓更重要的像素得到更大的權重(梯度下降)

6)通過Y與導入的圖片標注做運算可以得到cost,cost體現的是使用當前的w和b做預測與真實結果之間的誤差(損失函數)

7)前面的步驟重復2000次,就得到了最終的w、b。重復的次數其實沒有具體的限制,只需要平衡好訓練的次數和時間成本即可


我來說兩句
您需要登錄后才可以評論 登錄 | 立即注冊
facelist
所有評論(0)
領先的中文移動開發者社區
18620764416
7*24全天服務
意見反饋:[email protected]

掃一掃關注我們

Powered by Discuz! X3.2© 2001-2019 Comsenz Inc.( 粵ICP備15117877號 )

海南特区七星彩