深度学习基础之-3.3线性二分类的神经网络实现

线性二分类的神经网络实现

提出问题
回忆历史,公元前206年,楚汉相争,当时刘邦项羽麾下的城池地理位置如下:
深度学习基础之-3.3线性二分类的神经网络实现_第1张图片
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的二分类问题,所以我们只用一个输出单元就可以了
定义神经网络结构
根据前一节学习的二分类原理,我们只需要一个二入一出的神经元就可以搞定。这个网络只有输入层和输出层,由于输入层不算在内,所以是一层网络。
深度学习基础之-3.3线性二分类的神经网络实现_第2张图片

这次我们第一次使用了分类函数,所以有个A的输出,而不是以往的Z的输出。

输入层

输入经度(x1)和纬度(x2)两个特征:

X = ( x 1 , 1   x 2 , 1 ) X=\begin{pmatrix} x_{1,1} \ x_{2,1} \end{pmatrix} X=(x1,1 x2,1)

权重矩阵W1/B1

输入层是2个特征,则W的尺寸就是1x2:

W = ( w 1 , 1 w 1 , 2 ) W=\begin{pmatrix} w_{1,1} & w_{1,2} \end{pmatrix} W=(w1,1w1,2)

B的尺寸是1x1,行数永远和W一样,列数永远是1。

B = ( b 1 , 1 ) B=\begin{pmatrix} b_{1,1} \end{pmatrix} B=(b1,1)

输出层

Z = W ⋅ X + B Z=W \cdot X+B Z=WX+B A = S i g m o i d ( Z ) A = Sigmoid(Z) A=Sigmoid(Z)

损失函数

二分类交叉熵函损失数 Cross Entropy

J = − [ Y l n A + ( 1 − Y ) l n ( 1 − A ) ] J = -[YlnA+(1-Y)ln(1-A)] J=[YlnA+(1Y)ln(1A)]

分类的方式是,可以指定当A > 0.5时是正例,A <= 0.5时就是反例。或者根据实际情况指定别的阈值比如0.3,0.8等等。

此时反向传播矩阵运算的公式推导结果是:

(交叉熵函数求导) ∂ J ∂ A = − [ Y A − 1 − Y 1 − A ] = A − Y A ( 1 − A ) \frac{\partial{J}}{\partial{A}}=-[\frac{Y}{A}-\frac{1-Y}{1-A}]=\frac{A-Y}{A(1-A)} \tag{交叉熵函数求导} AJ=[AY1A1Y]=A(1A)AY() (Sigmoid激活函数求导) ∂ A ∂ Z = A ( 1 − A ) \frac{\partial{A}}{\partial{Z}}=A(1-A) \tag{Sigmoid激活函数求导} ZA=A(1A)(Sigmoid) (矩阵运算求导) ∂ Z ∂ W = X T , ∂ Z ∂ B = 1 \frac{\partial{Z}}{\partial{W}}=X^T , \frac{\partial{Z}}{\partial{B}}=1 \tag{矩阵运算求导} WZ=XT,BZ=1()

所以W的梯度:

∂ J ∂ W = ∂ J ∂ A ∂ A ∂ Z ∂ Z ∂ W \frac{\partial{J}}{\partial{W}} = \frac{\partial{J}}{\partial{A}} \frac{\partial{A}}{\partial{Z}} \frac{\partial{Z}}{\partial{W}} WJ=AJZAWZ = A − Y A ( 1 − A ) ⋅ A ( 1 − A ) ⋅ X T =\frac{A-Y}{A(1-A)} \cdot A(1-A) \cdot X^T =A(1A)AYA(1A)XT = ( A − Y ) X T =(A-Y)X^T =(AY)XT

加粗样式B的梯度:

∂ J ∂ B = ∂ J ∂ A ∂ A ∂ Z ∂ Z ∂ B \frac{\partial{J}}{\partial{B}}=\frac{\partial{J}}{\partial{A}}\frac{\partial{A}}{\partial{Z}}\frac{\partial{Z}}{\partial{B}} BJ=AJZABZ = A − Y A ( 1 − A ) A ( 1 − A ) =\frac{A-Y}{A(1-A)}A(1-A) =A(1A)AYA(1A) = A − Y =A-Y =AY

样本数据
下载后拷贝到您要运行的Python文件所在的文件夹。

点击下载训练样本数据X

点击下载训练标签数据Y

样本特征值
X m X_m Xm表示第m个样本值, x m , n x_{m,n} xm,n表示第m个样本的第n个特征值。样本数据集中一共有200个数据,每个数据有两个特征:经度和纬度。所以定义矩阵如下: X = ( X 1 X 2 … X 200 ) = ( x 1 , 1 x 2 , 1 … x 200 , 1   x 1 , 2 x 2 , 2 … x 200 , 2 ) X = \begin{pmatrix} X_1 & X_2 \dots X_{200} \end{pmatrix}= \begin{pmatrix} x_{1,1} & x_{2,1} \dots x_{200,1} \ x_{1,2} & x_{2,2} \dots x_{200,2} \end{pmatrix} X=(X1X2X200)=(x1,1x2,1x200,1 x1,2x2,2x200,2) = ( 0.025 4.109 7.767 … 2.762   3.408 8.012 1.872 … 2.653   ) =\begin{pmatrix} 0.025 & 4.109 & 7.767 & \dots & 2.762 \ 3.408 & 8.012 & 1.872 & \dots & 2.653 \ \end{pmatrix} =(0.0254.1097.7672.762 3.4088.0121.8722.653 )
样本标签值
一般来说,在标记样本时,我们会用1,2,3这样的标记,来指明是哪一类。在本例的二分类情况下,我们只需要把正例标记为1,负例标记为0。这个需要检查原始样本数据的格式,在自己的code中做相应的转化。如果你认为刘邦是“好人”,你就把汉标记为正例。对于一般的疾病分类来说,我们习惯于把阳性(有疾病嫌疑)标记为正例。

Y = ( Y 1 Y 2 … Y m ) = ( 1 1 0 … 1 ) Y = \begin{pmatrix} Y_1 & Y_2 \dots Y_m \end{pmatrix}= \begin{pmatrix} 1 & 1 & 0 \dots 1 \end{pmatrix} Y=(Y1Y2Ym)=(1101)

代码实现
我们先无耻地从第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,属于汉
损失函数值记录
深度学习基础之-3.3线性二分类的神经网络实现_第3张图片

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

你可能感兴趣的:(深度学习,二分类,神经网络,线性,实现,python)