線性二分類的神經網絡實現
提出問題
回憶歷史,公元前206年,楚漢相爭,當時劉邦項羽麾下的城池地理位置如下:
0.紅色圓點,項羽的城池
1.綠色叉子,劉邦的城池
其中,在邊界處有一些紅色和綠色重合的城池,表示雙方激烈爭奪的拉鋸戰。
樣本序號 | 1 | 2 | 3 | … | 119 |
---|---|---|---|---|---|
經度相對值 | 0 | .025 | 4.109 | … | 7.767 |
緯度相對值 | 3 | .408 | 8.012 | … | 1.872 |
1=漢, 0=楚 | 1 | 1 | 0 | … | 1 |
問題:
經緯度相對值爲(5,1)時,屬於楚還是漢?
經緯度相對值爲(6,9)時,屬於楚還是漢?
經緯度相對值爲(5,5)時,屬於楚還是漢?
你可能會覺得這個太簡單了,這不是有圖嗎?定位座標值後一下子就找到對應的區域了。但是我們要求你用機器學習的方法來解決這個看似簡單的問題,以便將來的預測行爲是快速準確的,而不是拿個尺子在圖上去比劃。再說了,我們用這個例子,主要是想讓大家對問題和解決方法都有一個視覺上的清晰認識,而這類可以可視化的問題,在實際生產環境中並不多見,絕大多數都是屬於樂山大佛——一頭霧水。
問題分析
從圖示來看,在兩個顏色區間之間似乎存在一條直線,即線性可分的。我們如何通過神經網絡精確地找到這條分界線呢?
從視覺上判斷是線性可分的,所以我們使用單層神經網絡即可
輸入特徵是經度和緯度,所以我們在輸入層設置兩個輸入X1=經度,X2=維度
最後輸出的是兩個分類,分別是楚漢地盤,可以看成非0即1的二分類問題,所以我們只用一個輸出單元就可以了
定義神經網絡結構
根據前一節學習的二分類原理,我們只需要一個二入一出的神經元就可以搞定。這個網絡只有輸入層和輸出層,由於輸入層不算在內,所以是一層網絡。
這次我們第一次使用了分類函數,所以有個A的輸出,而不是以往的Z的輸出。
輸入層
輸入經度(x1)和緯度(x2)兩個特徵:
權重矩陣W1/B1
輸入層是2個特徵,則W的尺寸就是1x2:
B的尺寸是1x1,行數永遠和W一樣,列數永遠是1。
輸出層
損失函數
二分類交叉熵函損失數 Cross Entropy
分類的方式是,可以指定當A > 0.5時是正例,A <= 0.5時就是反例。或者根據實際情況指定別的閾值比如0.3,0.8等等。
此時反向傳播矩陣運算的公式推導結果是:
所以W的梯度:
加粗樣式B的梯度:
樣本數據
下載後拷貝到您要運行的Python文件所在的文件夾。
樣本特徵值
表示第m個樣本值,表示第m個樣本的第n個特徵值。樣本數據集中一共有200個數據,每個數據有兩個特徵:經度和緯度。所以定義矩陣如下:
樣本標籤值
一般來說,在標記樣本時,我們會用1,2,3這樣的標記,來指明是哪一類。在本例的二分類情況下,我們只需要把正例標記爲1,負例標記爲0。這個需要檢查原始樣本數據的格式,在自己的code中做相應的轉化。如果你認爲劉邦是“好人”,你就把漢標記爲正例。對於一般的疾病分類來說,我們習慣於把陽性(有疾病嫌疑)標記爲正例。
代碼實現
我們先無恥地從第5章的代碼庫ch05中,把一些已經寫好的函數copy過來,形成一個BaseClassification.py文件,其中會包括神經網絡訓練的基本過程函數,加載數據,數據的歸一化函數,結果顯示函數等等。
加載數據
基本的加載數據和對樣本數據的歸一化工作,都可以用前一章的代碼來完成,統一集成在BaseClassification.py中了,下面代碼中的from BaseClassification import *就是完成了代碼引入的工作。但是對於標籤數據,需要一個特殊處理。
import numpy as np
import matplotlib.pyplot as plt
from pathlib import Path
import math
from BaseClassification import *
x_data_name = "X2.dat"
y_data_name = "Y2.dat"
def ToBool(YData):
num_example = YData.shape[1]
Y = np.zeros((1, num_example))
for i in range(num_example):
if YData[0,i] == 1: # 第一類的標籤設爲0
Y[0,i] = 0
elif YData[0,i] == 2: # 第二類的標籤設爲1
Y[0,i] = 1
# end if
# end for
return Y
遍歷標籤數據YData中所有記錄,設置類別爲1的標籤爲負例0,設置類別爲2的標籤爲正例1。下載的數據中,被標記爲1和2,表示第1類和第2類,需要轉換成0/1。
上述函數在本例中並沒有用,因爲樣本本身就是0/1標記的。
前向計算
前向計算需要增加分類函數調用:
def Sigmoid(x):
s=1/(1+np.exp(-x))
return s
前向計算
def ForwardCalculationBatch(W, B, batch_X):
Z = np.dot(W, batch_X) + B
A = Sigmoid(Z)
return A
計算損失函數值
損失函數不再是均方差了,而是交叉熵函數對於二分類的形式。
def CheckLoss(W, B, X, Y):
m = X.shape[1]
A = ForwardCalculationBatch(W,B,X)
p1 = 1 - Y
p2 = np.log(1-A)
p3 = np.log(A)
p4 = np.multiply(p1 ,p2)
p5 = np.multiply(Y, p3)
LOSS = np.sum(-(p4 + p5)) #binary classification
loss = LOSS / m
return loss
推理函數
def Inference(W,B,X_norm,xt):
xt_normalized = NormalizePredicateData(xt, X_norm)
A = ForwardCalculationBatch(W,B,xt_normalized)
return A, xt_normalized
主程序
if __name__ == '__main__':
# SGD, MiniBatch, FullBatch
method = "SGD"
# read data
XData,YData = ReadData(x_data_name, y_data_name)
X, X_norm = NormalizeData(XData)
Y = ToBool(YData)
W, B = train(method, X, Y, ForwardCalculationBatch, CheckLoss)
print("W=",W)
print("B=",B)
xt = np.array([5,1,6,9,5,5]).reshape(2,3,order='F')
result, xt_norm = Inference(W,B,X_norm,xt)
print(result)
print(np.around(result))
運行結果
epoch=99, iteration=199, loss=0.093750
W= [[-18.18569771 6.49279869]]
B= [[7.77920305]]
result=
[[0.33483134 0.93729121 0.87242717]]
[[0. 1. 1.]]
打印出來的W,B的值對我們來說是幾個很神祕的數字,下一節再解釋。result值是返回結果,
經緯度相對值爲(5,1)時,概率爲0.33,屬於楚
經緯度相對值爲(6,9)時,概率爲0.93,屬於漢
經緯度相對值爲(5,5)時,概率爲0.87,屬於漢
損失函數值記錄
PS:
Sigmoid的輸出值域是(0,1)。從前面講過的二分類原理看,Sigmoid是假設所有正類的標籤值都是,負類的標籤值都是0。而Tanh要求的是-1和1,所以如果要用tanh做分類函數的話需要將標籤歸一化到[-1, 1]之間。
https://github.com/microsoft/ai-edu/blob/master/B-教學案例與實踐/B6-神經網絡基本原理簡明教程/06.2-線性二分類實現.md