机器学习之人工神经网络原理、公式推导即TensorFlow代码实践(ANN)

目录

神经元

神经元模型

神经网络模型

模型参数及反向传播算法

公式证明

TensorFlow代码实现


神经元

        神经元是大脑处理信息的基本单元,以细胞体为主体,由许多向周围延伸的不规则树枝状纤维构成的神经细胞,其形状很像一棵枯树的枝干。它主要由细胞体、树突、轴突和突触组成。示意图如下:

一个神经元通常具有多个树突,主要接受其他神经元传入的信号;细胞体是神经元的核心,它把各个树突传递过来的信号“加总”起来,形成一个总的刺激信号,这个总的信号会刺激与细胞体连着的轴突,当这个刺激信号超过某个强度阈值,轴突会将信号通过尾端连着的多个“突触”(图中的轴突末梢)向其他神经元传递出去。 信号的整个接收传递过程,可如下图示意:

神经元模型

        可将上述神经元信号处理机理抽象成如下数学模型,如图:

(1)将信号抽象成一个n维向量,x=(x_1,x_2,...,x_n),每个分量代表信号的一个特征,图中给出的是一个3维向量;

(2)将细胞体抽象成图中的黄色方块,对信号进行线性加总,如上图,线性公式为y=\sum_{i=1}^{3}w_ix_i+b,其中w_i表示每个分量x_i的权重,b为截距项(或称为偏置项),y为加总后的输出;

(3)经过细胞体加总的信号y将作为轴突的输入,如图中黄色三角块,阈值判断函数为z=f(y),比如二值函数f(x)=\left\{\begin{matrix} 1, & x> 0& \\ 0, & x\leq 0 & \end{matrix}\right.

也就是如果y大于0,输出信号z=1,否则为0,当然阈值判断函数f可以很多,比如常见的sigmoid函数,softmax函数等,通常是非线性函数,f被称之为激活函数。

(4)信号z将作为输出通过突触传递给下一个神经元。

        实际上,神经元的上述抽象依次对信号x进行了两步处理,第一步对信号的各分量线性加权,第二步对第一步加权后的结果进行非线性处理作为输出。在图形表示中,通常将这两步仅表示为一个圆,如图中包含黄色方块和三角块的圆,后面不再做区分,这个圆的输入是x,输出是z

神经网络模型

        有了上面单个神经元的数学模型后,搭建神经网络也就顺理成章了。神经网络,顾名思义就是由一系列神经元构成的网络。但是神经网络比人的神经系统要简单的多,在神经网络中,神经元之间按层(layer)组织。每一层包含若干个神经元,层内部的神经元之间互相独立,它们之间没有信号传递。而相邻的两层之间是全连接的,即任意两个神经元都是有连接的。并且不相邻的两层神经元之间没有直接连接。神经网络的拓扑结构如图:

一般地,神经网络分为一个输入层、一个或多个隐藏层和一个输出层,如下:

(1)输入层

        输入层是神经网络的数据入口,一个神经元表示数据x的一个分量,也就是说如果一个数据有n个特征,则输入层有n个神经元,输入层不对数据进行处理,只负责将数据传递给后面的隐藏层;

(2)隐藏层

        隐藏层的每个神经元就是前面介绍的神经元模型,它同时包含了线性和激活函数。整个神经网络的核心也在隐藏层,负责数据处理,处理后的数据送入下一个隐藏层或者输出层;

(3)输出层

        输出层的每个神经元也要对数据进行处理,对于输出层,本文采用的是该层只对数线性加权处理,和隐藏层中神经元的第一步线性加权方式相同。输出层的结果并不总是模型的最终结果,后面还需要做一步处理。对于分类问题中,将数据x分为k类,假设为c_1,c_2,...,c_k,那么输出层就有k个神经元,每个神经元对应的一个类别,假设k个神经元的输出构成一个k维向量,z=(z_1,z_2,...,z_k),需要使用softmax函数将c向量作为输入,经过softmax函数计算后结果如下:

\sigma(z)=(\frac{e^{z_1}}{\sum_{q=1}^{k} e^{z_q}},\frac{e^{z_2}}{\sum_{q=1}^{k} e^{z_q}},...,\frac{e^{z_k}}{\sum_{q=1}^{k} e^{z_q}})

\sigma(z)的每个分量都在(0,1),且各分量的和为1,也就是说softmax函数将一个k为向量z变换成了同样k维的向量,只不过每个分量可以看成概率。也即:

\sigma(z)=(p_1,...,p_k)

下面就是比较向量r的每个分量大小,值最大的分量,比如第i个分量p_i最大,那么说明数据x经过神经网络训练后的类别是c_i(属于该类的概率最大)。上面通过softmax函数变换从概率的角度解释还算合理,实际上,最大值分量通过softmax变换后仍然是最大值分量,所以可不通过softmax函数变换,直接比较z向量的分量就可以决定分类。对于回归问题,z=(z_1,z_2,...,z_k)就可以直接作为模型回归的值。我们也可以把softmax函数理解成该层的激活函数,这样输出层输出的结果就是最终结果了。

        从数学角度来看,神经网络其实做了一个空间变换,假设整个神经网络的变换函数记为h,那么空间变换如下:

h:\mathbb{R}^n\rightarrow \mathbb{R}^k

其中\mathbb{R}^n是模型的输入空间,\mathbb{R}^k对应模型的输出空间,即神经网络模型将n维空间变为一个k为空间,如果k<n的话,那么这就是一个降维变换。

         对于神经网络,通常会以它拥有的层数来命名,但输入层不计算在内,如上图就是一个3-层神经网络。输入层也称之为0层,后面依次为第1层,第2层等等。通常使用每层的神经元个数组成的向量表示神经网络的网络结构(输入层不计算在内),比如上图网络结构为(3,2,2)。

模型参数及反向传播算法

        从上面神经网络模型中可以看到,实际上模型中有三个量是我们建立模型引入的,第一个是权重向量w,第二是偏置项b,第三个是激活函数f,通常情况下我们会事先选定某个激活函数f,比如二值函数,sogmoid函数,softmax函数和relu函数等。所以总的来说,模型的参数有两块,一块是权重向量w,另一块是偏置项b。神经网络模型训练也就是求解出这两个量,然后使用训练出的模型,对输入数据x进行分类或者预测等。模型的训练是让“误差”最小,也就是所谓的损失函数最小,下面我们建立模型的损失函数。假设有m个训练样本\mathbf{x_i},每个训练样本有n个特征,即\mathbf{x_i}=(x_{i1},x_{i2},...,x_{in})\mathbf{y_i}=(y_{i1},y_{i2},...,y_{ik})是对应的标签向量。样本\mathbf{x_i}经过神经网络模型训练后的输出为h(\mathbf{x_i}),它是与\mathbf{y_i}同维的标签变量,那么数据\mathbf{x_i}经过训练后产生的误差其实可以使用\mathbf{y_i}h(\mathbf{x_i})的距离来度量,总误差也就可以如下计算:

L(w,b)=\frac{1}{2m}\sum_{i=1}^{m}||h(\mathbf{x_i})-\mathbf{y_i}||^2          (1)

这就是模型的一个损失函数,为了模型参数表示方便,使用w表示模型中所有权重,b表示所有偏置项。模型训练其实就是求解w,b使得损失函数L(w,b)最小。对于同一个模型,损失函数并不是唯一的,角度不同,损失函数也不同。下面我们对于分类问题再推导出一个损失函数。对于分类问题,如果数据\mathbf{x_i}属于类别c_j,用\theta_i=(0,...,1,...,0)表示数据\mathbf{x_i}所属类别,\theta_i只有第j个分量为1,其余分量为0,也就是\mathbf{x_i}属于哪个类,\theta_i的哪个分量为1,这也称之为One-Hot编码,将\mathbf{x_i}的最终输出记为\sigma(z_i)=(p_1,...,p_k)看成列向量,那么\theta_i\sigma(z_i)相乘就得到了\mathbf{x_i}所属类别的概率,为了减小概率引起的精度误差,通常对概率取对数,即\theta_i ln\sigma(z_i)。那么我们希望经过神经网络训练和softmax计算后的结果向量\sigma(c)的第j个分量的概率\theta_i ln\sigma(z_i)应该要最大(概率最大,最可能属于该类),那么对该项添加负号后,-\theta_i ln\sigma(z_i)就可以看成是数据\mathbf{x_i}的损失了,m个样本总损失就可以如下定义

L(w,b)=-\sum_{i=1}^{m}\theta_i ln\sigma(z_i)  

无论选择哪个损失函数,都可以对模型进行训练,后面我们代码实现时两种方式都可以采用。下面为了方便起见,只考虑损失函数(1)式的单个数据点的损失函数,即:

L(w,b)=\frac{1}{2}||h(\mathbf{x_i})-\mathbf{y_i}||^2

假设第l-1层有s个神经元,第l层有q个神经元,并且都是隐藏层,s维向量是x^{(l-1)}=(x^{(l-1)}_{1},x^{(l-1)}_{2},...,x^{(l-1)}_{s})^T层的输出,q维向量x^{(l)}=(x^{(l)}_{1},x^{(l)}_{2},...,x^{(l)}_{q})^T为第l层输出。神经元到第l层的权重向量矩阵为W^{(l)},如下:

W^{(l)}=\begin{bmatrix} w^{(l)}_{1,1} &w^{(l)}_{2,1} &... &w^{(l)}_{s,1} \\ w^{(l)}_{1,2} &w^{(l)}_{2,2} &... &w^{(l)}_{s,2} \\ ...&... &... &... \\ w^{(l)}_{1,q} &w^{(l)}_{2,q} &... &w^{(l)}_{s,q} \end{bmatrix}_{q\times s}

W^{(l)}是一个q\times s解矩阵,其中矩阵元素w^{(l)}_{i,j}表示l-1层第i个神经元到l层第j个神经元的权重。

b^{(l)}=\begin{bmatrix} b^{(l)}_1\\ b^{(l)}_2\\ ...\\ b^{(l)}_q\end{bmatrix}为第l-1层到第l层偏置项组成的向量。那么由神经网路模型容易得到如下矩阵关系:

                                                                u^{(l)}=W^{(l)}x^{(l-1)}+b^{(l)}

                                                                x^{(l)}=f(u^{(l)})

同理,第l层到第l+1层间的矩阵关系如下:

                                                               u^{(l+1)}=W^{(l+1)}x^{(l)}+b^{(l+1)}

                                                                x^{(l+1)}=f(u^{(l+1)})

损失函数(1)式两队分别对矩阵W^{(l)}b^{(l)}求导,这里用到了标量-矩阵求导和标量-向量求导,过程比较复杂,这里不再证明,以后有时间再补上,可参见机器学习之矩阵微积分及其性质,最后得到

\frac{\partial L}{\partial W^{(l)}}=\frac{\partial L}{\partial u^{(l)}}(x^{(l-1)})^T                     (2)

\frac{\partial L}{\partial b^{(l)}}=\frac{\partial L}{\partial u^{(l)}}                                       (3)

\frac{\partial L}{\partial u^{(l)}}是一个列向量,每个分量是L对u^{(l)}分量求导,比如第i个分量为\frac{\partial L}{\partial u^{(l)}_i}。这个向量满足如下递推关系:

\frac{\partial L}{\partial u^{(l)}}=(W^{(l+1)})^T\frac{\partial L}{\partial u^{(l+1)}}\bigodot f^{'}(u^{(l)})     (4)

\bigodot表示向量或者矩阵对应元素相乘。

(4)式表明由后一层的\frac{\partial L}{\partial u^{(l+1)}_i}可以递归算出前一层的\frac{\partial L}{\partial u^{(l)}_i}。将\frac{\partial L}{\partial u^{(l)}_i}代入(2)和(3)式就可以计算出模型参数的偏导数了。

还有一个问题需要解决。就是\frac{\partial L}{\partial u^{(l)}_i}的初始值,它由后往前递推,初始值也就是神经网络的最后一层输出层。前面假设的l和l+1层都是隐藏层,现在假设l+1层是输出层,并且假设神经网络总共有T层(输入层不计算在内),直接得到:

\frac{\partial L}{\partial u^{(T)}}=\frac{\partial L}{\partial x^{(T)}}

右边这个是损失函数对自变量的偏导,是已知的。

我们给出一个损失函数的理解:

在输出层有\frac{\partial L}{\partial u^{(T)}}=\frac{\partial L}{\partial x^{(T)}},损失函数L可以理解成模型在输出层的预测错误,接着递推公式(4)将这个误差传递到了前一层的各个神经元,因为误差是因为引入了模型参数才引起的,最后这个误差被传递到了公式(2)和(3)表示的模型参数上。这就是误差的反向传播,称之为反向传播算法。这个算法步骤如下:

(1)给定模型参数初始值W=W_0,b=b_0和模型训练数据,按照正向传播方向,可计算出每一层输入样本的输出值

(2)对输出层的每个神经元,计算误差\frac{\partial L}{\partial u^{(T)}}=\frac{\partial L}{\partial x^{(T)}}

(3)对l=T-1,T-2,...,2的各层,计算每层的误差,

\frac{\partial L}{\partial u^{(l)}}=((W^{(l+1)})^T\frac{\partial L}{\partial u^{(l+1)}})\bigodot f^{'}(u^{(l)})

根据误差,计算对应层的权重矩阵和偏置项

\frac{\partial L}{\partial W^{(l)}}=\frac{\partial L}{\partial u^{(l)}}(x^{(l-1)})^T              

\frac{\partial L}{\partial b^{(l)}}=\frac{\partial L}{\partial u^{(l)}}                         

(4)根据梯度下降法更新权重和偏置项

W^{(l)}_{new}=W^{(l)}-\eta \frac{\partial L}{\partial W^{(l)}}

b^{(l)}_{new}=b^{(l)}-\eta \frac{\partial L}{\partial b^{(l)}}

然后重复步骤(1)。

公式证明

        上面为了叙述方便,直接使用了递推结论,下面给出详细证明。先证明公式(2),也就是

\frac{\partial L}{\partial W^{(l)}}=\frac{\partial L}{\partial u^{(l)}}(x^{(l-1)})^T.

先将损失函数L看成是u^{(l)}的函数,即L=L(u^{(l)}),且u^{(l)}=W^{(l)}x^{(l-1)}+b^{(l)},将u^{(l)}看成是W^{(l)}的函数。这里求导采用分子布局。如下:

\frac{\partial L}{\partial W^{(l)}}是标量-矩阵求导,也就是:

\frac{\partial L}{\partial W^{(l)}}=\begin{bmatrix} \frac{\partial L}{\partial w^{(l)}_{1,1}} &\frac{\partial L}{\partial w^{(l)}_{1,2}} &... &\frac{\partial L}{\partial w^{(l)}_{1,q}} \\ \frac{\partial L}{\partial w^{(l)}_{2,1}} &\frac{\partial L}{\partial w^{(l)}_{2,2}} &... &\frac{\partial L}{\partial w^{(l)}_{2,q}} \\ ...&... &... &... \\ \frac{\partial L}{\partial w^{(l)}_{s,1}} &\frac{\partial L}{\partial w^{(l)}_{s,2}} &... &\frac{\partial L}{\partial w^{(l)}_{s,q}} \end{bmatrix}_{q\times s}

根据求导链式法则,这个矩阵的第i行第j列元素\frac{\partial L}{\partial w^{(l)}_{i,j}}满足:

\frac{\partial L}{\partial w^{(l)}_{i,j}}=\sum_{k=1}^{q}\frac{\partial L}{\partial u^{(l)}_k}\frac{\partial u^{(l)}_k}{\partial w^{(l)}_{i,j}}

注意w^{(l)}_{i,j}W^{(l)}的第j行第i列元素,而且在u^{(l)}中只有第j个分量中有w^{(l)}_{i,j},所以上式化为

\frac{\partial L}{\partial w^{(l)}_{i,j}}=\frac{\partial L}{\partial u^{(l)}_j}\frac{\partial u^{(l)}_j}{\partial w^{(l)}_{i,j}}=\frac{\partial L}{\partial u^{(l)}_j}x_i

再写出矩阵形式

\frac{\partial L}{\partial W^{(l)}}=\begin{bmatrix} \frac{\partial L}{\partial u^{(l)}_1}x_1^{(l-1)} & \frac{\partial L}{\partial u^{(l)}_2}x_1^{(l-1)} &... & \frac{\partial L}{\partial u^{(l)}_q}x_1^{(l-1)} \\ \frac{\partial L}{\partial u^{(l)}_1}x_2^{(l-1)} & \frac{\partial L}{\partial u^{(l)}_2}x_2^{(l-1)} &... & \frac{\partial L}{\partial u^{(l)}_q}x_2^{(l-1)} \\ ...&... &... &... \\ \frac{\partial L}{\partial u^{(l)}_1}x_s^{(l-1)} & \frac{\partial L}{\partial u^{(l)}_2}x_2^{(l-1)} &... & \frac{\partial L}{\partial u^{(l)}_q}x_s^{(l-1)}\end{bmatrix}_{q\times s}=\begin{bmatrix}x_1^{(l-1)}\\x_2^{(l-1)} \\ ... \\x_s^{(l-1)} \end{bmatrix}( \frac{\partial L}{\partial u^{(l)}_1} , \frac{\partial L}{\partial u^{(l)}_2},...,\frac{\partial L}{\partial u^{(l)}_q})

=x^{(l-1)}\frac{\partial L}{\partial u^{(l)}}

如果采用分母布局就是上面的写法:

\frac{\partial L}{\partial W^{(l)}}=\frac{\partial L}{\partial u^{(l)}}(x^{(l-1)})^T

同理得到:

\frac{\partial L}{\partial b^{(l)}}=\frac{\partial L}{\partial u^{(l)}}

下面来证明(4)式,即:

\frac{\partial L}{\partial u^{(l)}}=(W^{(l+1)})^T\frac{\partial L}{\partial u^{(l+1)}}\bigodot f^{'}(u^{(l)}).

x^{(l)}=f(u^{(l)})

写成向量形式,也就是

\begin{bmatrix} x_1^{(l)}\\ x_2^{(l)}\\ ...\\ x_q^{(l)}\end{bmatrix}=\begin{bmatrix} f(u_1^{(l)})\\ f(u_2^{(l)})\\ ...\\ f(u_q^{(l)})\end{bmatrix}

所以得到:

\frac{\partial L}{\partial u^{(l)}}=\begin{bmatrix} \frac{\partial L}{\partial u^{(l)}_1} \\ \frac{\partial L}{\partial u^{(l)}_2} \\ ... \\ \frac{\partial L}{\partial u^{(l)}_q}\end{bmatrix}=\begin{bmatrix} \frac{\partial L}{\partial x^{(l)}_1}\frac{\partial x^{(l)}_1}{\partial u^{(l)}_1} \\ \frac{\partial L}{\partial x^{(l)}_2}\frac{\partial x^{(l)}_2}{\partial u^{(l)}_2} \\ ... \\ \frac{\partial L}{\partial x^{(l)}_q}\frac{\partial x^{(l)}_q}{\partial u^{(l)}_q}\end{bmatrix}=\begin{bmatrix} \frac{\partial L}{\partial x^{(l)}_1} \\ \frac{\partial L}{\partial x^{(l)}_2} \\ ... \\ \frac{\partial L}{\partial x^{(l)}_q}\end{bmatrix}\bigodot \begin{bmatrix} f'(u_1^{(l)})\\ f'(u_2^{(l)})\\ ...\\ f'(u_q^{(l)})\end{bmatrix}=\frac{\partial L}{\partial x^{(l)}}\bigodot f'(u^{(l)})       (5)

下面计算\frac{\partial L}{\partial x^{(l)}},由

u^{(l+1)}=W^{(l+1)}x^{(l)}+b^{(l+1)}

得到

\frac{\partial u^{(l+1)}}{\partial x^{(l)}}=W^{(l+1)}  (注:这一步参见《机器学习之矩阵微积分及其性质》中向量-向量求导(3))

\frac{\partial L}{\partial x^{(l)}}=\frac{\partial L}{\partial u^{(l+1)}}(\frac{\partial u^{(l+1)}}{\partial x^{(l)}})=\frac{\partial L}{\partial u^{(l+1)}}W^{(l+1)}(注:这一步参见《机器学习之矩阵微积分及其性质》中向量-向量求导(7))

该式代入(5)式得到

\frac{\partial L}{\partial u^{(l)}}=(\frac{\partial L}{\partial u^{(l+1)}}W^{(l+1)})\bigodot f^{'}(u^{(l)})

如果采用分母布局,就得到如下结论:

\frac{\partial L}{\partial u^{(l)}}=((W^{(l+1)})^T\frac{\partial L}{\partial u^{(l+1)}})\bigodot f^{'}(u^{(l)})

TensorFlow代码实现

        这一节我们产生四种类型数据,然后使用自定义神经网络模型对每种类型进行分类,四种类型数据如图:

使用神经网络训练后,分类效果如图:

从图中分类效果来看,是很不错的。各类型数据训练损失函数收敛情况如图:

四类数据损失函数的收敛速度从快到慢依次是:第一种 > 第三种 > 第四种 > 第二种。也就是通过直线分类的数据最快,同心圆类型的数据分类最慢。上面结果是通过如下结构的3-层神经网络训练分类的结果,如图:

具体代码如下:

ann_demo.py自定义一个神经网络模型,如下:

"""
自定义神经网络
"""

import numpy as np
import tensorflow as tf


class ANN(object):

    def __init__(self, struct_size, log_path):
        """
        ANN神经网络初始化
        :param struct_size: 网络结构
        :param log_path: 训练日志数据保存路径
        """
        # 重置神经网络,保证神经网络多次运行
        tf.reset_default_graph()

        self._struct_size = struct_size
        self._log_path = log_path

    def define_ann(self, X):
        """
        定义神经网络结构:依次定义神经网络的输入层、隐藏层、输出层和损失函数
        :return:
        """

        # 定义输入层,输入层大小=输入数据的特征数,输入层不对数据处理,输出等于输入
        self._input = tf.placeholder(tf.float32, shape=[None, X.shape[1]], name="X")
        self._labels = tf.placeholder(tf.int64, shape=[None, self._struct_size[-1]], name="Y")
        pre_level_size = self._input.shape[1].value
        pre_out = self._input

        # 神经网络结构,每层的神经元个数,
        # 比如struct=[2,3,2]表示第1层2个神经元,第2层3个神经元,第3等2个神经元
        struct_size = self._struct_size

        # 定义所有隐藏层(剔除输出层struct[-1])
        for current_level_size in struct_size[:-1]:
            # 使用正态分布初始化权重矩阵pre_level_size到current_level_size层的权重矩阵W
            # 这里W的维数是(pre_level_size,current_level_size),
            # 这是因为,在数学上通常使用列向量表示一个向量,而在代码实现中使用列表,也就是行向量来表示,后面均是如此
            weights = tf.Variable(
                tf.truncated_normal([pre_level_size, current_level_size],
                                    stddev=1.0 / np.sqrt(float(pre_level_size))))
            # 初始化偏置项向量,大小和当前隐藏层的神经元个数一致
            biases = tf.Variable(tf.zeros(current_level_size))
            # pre_out和biases都是行向量
            pre_out = tf.nn.sigmoid(tf.matmul(pre_out, weights) + biases)
            pre_level_size = current_level_size
        # 定义输出层,权重矩阵和偏置项向量
        weights = tf.Variable(
            tf.truncated_normal([pre_level_size, struct_size[-1]],
                                stddev=1.0 / np.sqrt(float(pre_level_size))))
        biases = tf.Variable(tf.zeros(struct_size[-1]))
        # 输出层输出
        self._out = tf.matmul(pre_out, weights) + biases

        # 交叉熵损失函数
        loss = tf.nn.softmax_cross_entropy_with_logits(labels=self._labels, logits=self._out, name='loss')
        self._loss = tf.reduce_mean(loss, name='average_loss')

        # 欧氏距离损失函数
        # self._loss = tf.reduce_mean(tf.square(tf.to_float(self._labels) - self._out), name='average_loss')

        return self

    def sgd(self, X, Y, learning_rate=0.1, mini_batch_fraction=0.2, epoch=1):
        """
        随机梯度下降法:
        :param X: 输入样本
        :param Y: 样本标签
        :param learning_rate: 学习率,默认值0.1
        :param mini_batch_fraction: 批量占比
        :param epoch: 训练轮次
        :return:
        """

        # 定义随机梯度优化器
        method = tf.train.GradientDescentOptimizer(learning_rate)
        optimizer = method.minimize(self._loss)
        # 每批次样本大小=样本数*批量占比
        batch_size = int(X.shape[0] * mini_batch_fraction)
        # 批次数
        batch_num = int(np.ceil(1 / mini_batch_fraction))
        # 创建会话并初始化
        session = tf.Session()
        init = tf.global_variables_initializer()
        session.run(init)
        # 定义日志记录
        summary_writer = tf.summary.FileWriter(self._log_path, graph=tf.get_default_graph())
        tf.summary.scalar("loss", self._loss)
        summary = tf.summary.merge_all()
        step = 0
        while step < epoch:
            for i in range(batch_num):
                batch_x = X[i * batch_size:(i + 1) * batch_size]
                batch_y = Y[i * batch_size:(i + 1) * batch_size]
                session.run([optimizer], feed_dict={self._input: batch_x, self._labels: batch_y})
            step += 1
            # 将日志写入文件
            summary_str = session.run(summary, feed_dict={self._input: X, self._labels: Y})
            summary_writer.add_summary(summary_str, step)
            summary_writer.flush()
        self._session = session

        return self

    def fit(self, X, Y, learning_rate=0.3, mini_batch_fraction=0.1, epoch=500):
        print("开始拟合")
        self.define_ann(X)
        self.sgd(X, Y, learning_rate, mini_batch_fraction, epoch)
        print("结束拟合")

    def predict(self, X):
        """
        使用神经网络对未知数据进行预测
        :param X:
        :return: 概率分布
        """

        session = self._session
        predict = tf.nn.softmax(logits=self._out, name='predict')
        prob = session.run(predict, feed_dict={self._input: X})

        return prob

classification_demo.py利用自定义神经网络模型分类,如下:

"""
利用自定义神经网络分类
"""

from src.tensorflow.ann_demo import ANN

import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_blobs, make_circles, make_moons
from sklearn.preprocessing import StandardScaler, OneHotEncoder


def generate_data(samples_num):
    """
    生成待分类的四种类型数据
    :param samples_num: 数据点个数
    :return: 四种数据类型
    """

    # 固定种子,保证每次随机生成数据相同
    np.random.seed(1000)
    # 生成聚类类型数据点
    blobs = make_blobs(n_samples=samples_num, centers=[[-2, -2], [2, 2]])
    # 生成环形类型数据点
    circles = make_circles(n_samples=samples_num, factor=.4, noise=.05)
    # 生成月牙类型数据点,形状像月亮
    moons = make_moons(n_samples=samples_num, noise=.05)
    # 生成双曲类型数据点
    blocks = np.random.rand(samples_num, 2) - 0.5
    y = (blocks[:, 0] * blocks[:, 1] < 0) + 0
    blocks = (blocks, y)
    # 数据做归一化处理
    scaler = StandardScaler()
    blobs = (scaler.fit_transform(blobs[0]), blobs[1])
    circles = (scaler.fit_transform(circles[0]), circles[1])
    moons = (scaler.fit_transform(moons[0]), moons[1])
    blocks = (scaler.fit_transform(blocks[0]), blocks[1])

    return blobs, circles, moons, blocks


def draw_raw_data(plt, data):
    """
    原始数据可视化:绘制数据散点图
    :param plt:
    :param data: 数据点(包含特征和类别)
    :return:
    """

    X, y = data
    # 绘制类别1的数据散点图,圆圈表示
    label1 = X[y > 0]
    plt.scatter(label1[:, 0], label1[:, 1], marker="o")
    # 绘制类别0的数据散点图,三角形表示
    label0 = X[y == 0]
    plt.scatter(label0[:, 0], label0[:, 1], marker="^", color="k")

    return plt


def draw_model(plt, model):
    """
    绘制分类后的模型的分离超平面:
    基本思路:(1)使用100条水平线和垂直线将图形区域分割,线的交点(10000个)作为已训练模型的输入
            (2)对10000个点进行预测,得到每个点属于各个类别的概率(p0,p1),取属于类别1的概率p1
            (3)在平面中绘制等高线
    """

    # 生成网格数据
    x = np.linspace(plt.get_xlim()[0], plt.get_xlim()[1], 100)
    y = np.linspace(plt.get_ylim()[0], plt.get_ylim()[1], 100)
    mesh_x, mesh_y = np.meshgrid(x, y)
    # ravel()数据拉平操作,np.c_列拼接操作
    # np.c_[mesh_x.ravel(), mesh_y.ravel()]正好构成网格上的点
    # predict对网格上的每个点计算属于各个点的概率,取出所有点属于类别1的概率p
    pre_prob = model.predict(np.c_[mesh_x.ravel(), mesh_y.ravel()])[:, 1]
    # 这样mesh_x和mesh_y,pre_prob就是维数相同的
    pre_prob = pre_prob.reshape(mesh_x.shape)
    # 绘制等高线,灰色填充概率小于等于0.5的
    plt.contourf(mesh_x, mesh_y, pre_prob, levels=[0, 0.5], colors=["gray"], alpha=0.4)

    return plt


def train_ann(X, y, log_path):
    """
    使用ann神经网络训练模型
    :param X: 输入样本
    :param y: 样本标签
    :param log_path: 训练过程日志数据存放路径,保存日志是为了同Tensorboard可视化数据
    :return:
    """

    # 对标签数据y进行One-Hot编码
    enc = OneHotEncoder()
    y = enc.fit_transform(y.reshape(-1, 1)).toarray()
    # 定义一个(4,4,4)结构的神经网络,训练模型
    model = ANN([4, 4, 2], log_path)
    model.fit(X, y)

    return model


def visualize(data):
    """
    可视化最终的训练结果
    :param data:
    :return:
    """
    # 创建图形框(分类前和分类后)
    raw_data_figure = plt.figure(figsize=(8, 8), dpi=80)
    train_model_figure = plt.figure(figsize=(8, 8), dpi=80)
    # 在图形框中绘制图
    for i in range(len(data)):
        raw_plt = raw_data_figure.add_subplot(2, 2, i + 1)
        train_plt = train_model_figure.add_subplot(2, 2, i + 1)
        # 绘制分类前图形
        draw_raw_data(raw_plt, data[i])
        # 训练模型
        X, y = data[i]
        model = train_ann(X, y, "logs/data_%s" % (i + 1))
        # 绘制分类后的图形
        draw_raw_data(train_plt, data[i])
        draw_model(train_plt, model)
        raw_plt.get_xaxis().set_visible(False)
        raw_plt.get_yaxis().set_visible(False)
        train_plt.get_xaxis().set_visible(False)
        train_plt.get_yaxis().set_visible(False)
    plt.show()


if __name__ == "__main__":
    data = generate_data(samples_num=200)
    visualize(data)

运行classification_demo.py即可得到上面的分类结果。

        最后一并感谢参考的各类文献和书籍。

发布了89 篇原创文章 · 获赞 79 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/L_15156024189/article/details/105114992