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

1、序言python

  關於「深度學習」大部分文章講的都雲裏霧裏,直到看到「牀長」的系列教程以及《深度學習入門:基於Python的理論與實現》,這裏主要是對這兩個教程進行我的化的總結,目標是讓「0基礎」的童鞋也能看懂神祕的神經網絡。數組

  若是你是AI新手,能夠先大概看看《深度學習入門:基於Python的理論與實現》,這本書主要從數學的角度來描述神經網絡的各類概念並輔以具體的實現代碼。看個大概知道一些概念就好了。而後強烈推薦「牀長」的人工智能系列教程(https://www.captainbed.net/),不過我如今也就看到第一章而已。最後推薦你們關注公衆號「零基礎愛學習」,之後還會繼續推送零基礎入門的文章。網絡

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

2、訓練數據獲取學習

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

  MNIST是一個「手寫數字」的數據集,包含了6萬個訓練數據和1萬個測試數據,怎麼理解呢?就是它提供了7萬張圖片,圖片的內容是手寫的各類數字圖片,以下圖所示(不要在乎圖片的命名),同時它還提供了圖片對應的具體數字稱爲「標註」。圖片+標註的數據對就是咱們須要的「大數據」了,所謂AI民工就是幹這個「標註」工做的。大數據

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

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

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

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

  調用「load_mnist()」函數能夠從pkl文件中導入訓練數據和測試數據,每個數據集包含若干圖片和對應的正確標註

  如上圖,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這樣的小數才能夠正確執行。(後面再講)

 

  爲了簡化整個程序,咱們只針對數字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)

   至此,咱們的數據都準備好了,測試的時候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))

  裁剪後的數據形狀:

   可是這樣的數據仍是無法使用,咱們還須要再將數據作一次轉置才行(具體緣由後面再說)。

  train_img = train_img.T
  train_label = train_label.T
  test_img = test_img.T
  test_label = test_label.T

  轉置後的數據形狀:

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

 3、神經網絡的4個函數

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

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

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

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

 

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

 

 

 

 

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

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

  二、神經元經過一個叫「傳播函數」的東西嘗試預測輸入的圖片是不是數字9

  三、傳播函數的原理是經過參數w和b與輸入x作運算獲得結果a

  四、結果a又須要經過一個叫「激活函數」的東西來調整其輸出,使其變爲咱們須要的輸出形式y

  五、將輸出y與真實的標註進行對比,並根據對比結果使用「反向傳播函數」來優化w和b

  六、最終,傳播函數結合最優的參數w和b就能作所謂的「AI識圖」了(從一堆輸入裏識別出手寫數字9)

5、傳播函數

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

   權重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個圖片的計算結果合集

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

 

6、激活函數

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

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

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

   參數w、b優化數百次後再次計算A的結果大概是下圖這樣(先不要考慮如何優化),能夠看到大部分數值都仍是負數(並且越負越多),但有部分值已變爲正值。其實隨着參數的優化,數字9的圖片計算結果會愈來愈向正數靠攏,而非數字9的圖片計算結果會愈來愈向負數靠攏。這裏先不講緣由,但能夠先創建這樣的映像「當a的值大於0時圖片爲數字9,a的值小於0時圖片不爲數字9」且「a的值越大於0則圖片越有多是數字9,a的值越小於0則圖片越有可能不是數字9」。

   可是這樣的表述沒法與「標籤」進行比較,由於標籤的值是0或1(0表明非數字九、1表明是數字9)。若是A的值是0~1的一個數字,那A與標籤值進行比較,咱們就能知道當前被識別圖片是更趨向於數字9抑或更趨向於不是數字9。激活函數sigmoid就是這麼個做用,它將A的值「映射」到區間0~1方便與標籤進行比較。sigmoid函數式以下:

    使用python也很是易於實現

  

 

 

   從數學上來解析這個式子,咱們能夠知道:

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

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

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

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

7、反向傳播函數

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

  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是計算結果。截取某一次訓練的數據大概是下面這樣:

 

 

   經過上面的實例演示,咱們會發現「若圖片真的是數字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值向負數愈來愈小。

  對於dZ與train_img的乘積dw,咱們能夠用一張圖來描述這個過程:

   能夠看到,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太大或過小都不行,通常就看學習效果如何來手動調整,那如何看學習的效果呢,這就要講到最後一個函數「損失函數」了。

8、損失函數

  咱們使用的損失函數公式以下:

 

  實現代碼以下:

  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值會愈來愈小)。

 9、彙總回顧

  前面咱們詳細敘述了神經元的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))

10、總結

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

  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。重複的次數其實沒有具體的限制,只須要平衡好訓練的次數和時間成本便可

  若是須要完整的代碼,請關注公衆號「零基礎愛學習」回覆「AI4」就能獲取代碼文件了。之後還會繼續推送零基礎入門的文章。

相關文章
相關標籤/搜索