一、激活函数
在第一节中我们了解到,神经元不是单纯线性的,线性函数是只要有输入\(x\),必定会有一个输出\(y\)与之对应,而神经元接收到信号不会马上做出响应,它会等待输入信号强度增大到超过阈值才会有输出,这就好比往杯子中倒水,只有水超过杯子的上边缘才会溢出来。所以,实际应用中我们会加入一个激活层,该激活层实际上是一些激活函数。在下图中,假设有三个输入,激活函数为\(g(x)\),则最后的输出是\(y=g(\alpha_{_{_1}}x_{{_1}}+\alpha_{_{_2}}x_{{_2}}+\alpha_{_{_3}}x_{{_3}})\),第一个神经元计算输入的加权和,然后通过第二个神经元激活函数的作用输出\(y\)。
图1.2.1
在神经网络中,激活函数的主要作用是引入非线性因素,解决线性模型不能解决的问题。上图中,为什么一定要做非线性变换而不直接输出\(y=\alpha_{_{_1}}x_{{_1}}+\alpha_{_{_2}}x_{{_2}}+\alpha_{_{_3}}x_{{_3}}\)呢?那是因为,线性输出\(y\)(也叫做预测值)会随着输入值的增大而无限增大,但有时候我们并不需要那么大的实际值,比如在做分类的时候,我们仅仅需要知道的是在某个阈值上下,\(y\)是为1(被激活)还是为0(未激活),通过判断1或者0就可以直接做出预测。另外,如果不用激励函数,每一层输出都是上层输入的线性函数,无论神经网络有多少层,输出都是输入的线性组合。如果使用激活函数的话,激活函数给神经元引入了非线性因素,使得神经网络可以任意逼近任何非线性函数,这样神经网络就可以应用到众多的非线性模型中。在上一节中,我们遇到一个特殊的情况,异或函数会让线性分类器无法把输入正确的归类,我们的解决办法是使用两个线性分类器,也许还有另一种办法,那就是使用圆把中间两个值圈起来,这种方法,其实就需要非线性变换。
激活函数在神经网络中的使用频率非常高,我们来看看常用的几个。
阶跃函数
为了模仿真正的生物神经元收到刺激后作出反应,我们在数学中首先想到的是阶跃函数。阶跃函数是在输入值未达到阈值后,输出一直为0,当输入达到阈值,输出值做突然的跳转,但这种冷冰冰,赤裸裸的尖锐边缘不太符合实际的应用,所以使用较少。
图1.2.2
Sigmoid函数
相对于阶跃函数的尖锐突出,Sigmoid函数显得要平滑很多,无论它的输入值是无穷大还是无穷小,输出值范围均为\((0,1)\),很适合二分类问题,且输出的取值也很容易和概率取值联系起来。但是,Sigmoid也存在一些缺点,比如函数饱和使梯度消失,该函数的输出不是零中心的,这两个问题(后面梯度章节会讲解)在很多时候限制了它的使用。它的函数形式为:\[Sigmoid=\frac{1}{1+e^{-x}}\]
图1.2.3
Tanh函数
双曲正切函数跟Sigmoid函数很像,但是输出值范围扩大了,从-1到1,它也存在函数饱和问题,不过输出是零中心的,使得Tanh函数比Sigmoid函数更受欢迎。函数形式为\[Tanh=\frac{e^{x}-e^{-x}}{e^{x}+e^{-x}}\]
图1.2.4
Softmax函数
Softmax函数用于多分类网络中,它将多个神经元的输出,映射到(0,1)区间内,可以看成概率来理解,在最后输出时,选取概率最大的值作为预测值,从而来进行多分类!假设有一个数组,V,Vi表示V中的第i个元素,那么这个元素的softmax值就是:\[S_{_{i}}=\frac{e^{i}}{\sum_{j}^{e^{j}}}\]
用下图更清晰的表示Softmax函数的工作原理。三个输入经过Softmax的处理后,得到不同概率值,一般来说我们选取概率最大的值作为预测目标。
图1.2.4
Relu函数
中文叫线性整流函数,又称修正线性单元,以斜坡函数为代表,当输入小于0时,输出为0,当输入大于0时,输出等于输入。该函数也是仿效生物神经元,最大的特点是可以避免梯度爆炸和梯度消失等问题,计算过程简单,因此,在深度神经网络中应用广泛。它的函数形式为:\[Relux=max(0,x)\]
图1.2.6
二、线性回归与梯度下降
在上一节中我们普及了一次线性回归的概念,现在我们再来回顾一下,假定我们有一大批房屋面积和对应房价的信息,如果我们能得到房屋面积与房屋价格的线性关系,那么给定一个固定面积的房屋时,我们就能推算出其价格。对于这种预测问题我们的第一反应是使用线性回归模型来解决,回归模型定义了输入和输出的关系,输入现有房屋面积,预测房屋价格。
为了普遍性,我们把一个预测问题的步骤总结如下:
1、收集数据。在神经网络中,我们把数据称为训练样本,或训练集。
2、搭建模型,即搭建一个神经网络。
3、训练数据。让神经网络学习如何预测,得到完美的线性关系式。在学习的时候要有合适的指导方针,这种方针我们称为学习算法。
4、预测。学习完成后,使用新数据让模型得到预测输出。
收集数据不必细说,而模型我们知道是用类似$y=\alpha x+\beta $的线性模型,重点就是训练数据以及训练的指导方针,由此,我们不得不引入残差公式和梯度下降。
残差公式
线性模型可以用一条线性线段表示,在数学中线性线段为\(y=\alpha x+\beta\),\(x\)代表宽度,\(y\)代表长度,\(\alpha\)和\(\beta\)分别为直线的斜率和截距。而在感知器中,我们把\(x\)称为输入,\(y\)称为输出,或者叫预测值,\(\alpha\)称为权重,\(\beta\)称为随机扰动项。如下图1.2.2所示的线性模型。
图1.2.7
这样我们就把问题转化为分析两个变量\(x\)和\(y\)之间的关系,即分析\(y=\alpha x+\beta\),比如一个程序员的收入\((x)\)与支出\((y)\)之间的关系,房子大小\((x)\)与价格\((y)\)之间的关系等等。
如上1.2.7的数据分布图,我们大概可以评估一下它们之间是一种线性的关系,但这种线性关系可以是多种,怎么才是最好的呢,那就要抓住关键点,即找出预测值和真实值之间的差异,并缩小这种差异。下图1.2.8我们可视化了这种差异,图中的\(Δyi\)称为误差或残差,是预测值和真实值之差的绝对值,用公式表示即为:\(|Δy_{_i}|=|y'-y_{_i}|=|\alpha x_{_i}+\beta-y_{_i}|\),等式中\(\alpha x_{_i}+\beta=y'\)为预测值,\(y_{_i}\)为真实值。要想预测值越接近真实值,那么必然残差就越小,在已知\(x\)和\(y\)的情况下(\(x\)和\(y\)为收集到的训练数据),问题最后转化为求解\(\alpha\)、\(\beta\)值。
图1.2.8
由于残差带有绝对值,计算起来比较麻烦,所以大家一致公认使用残差的平方,在多样本的情况下,上面的残差可以表示为\[\varepsilon _{i}^2=\frac{1}{2}\sum_{i=1}^{N}(\alpha_{i} x_{i}+\beta-y_{i})^2\]
这就是有名的残差公式,有时候也叫损失函数(Cost),只要能够使得损失函数值达到最小,那么问题就迎刃而解。
梯度下降法
现在我们先忽略多样本的情况,把这个让人凌乱的求和符号\(\sum_{i=1}^{N}\)给去掉,看看一个样本的情况。假设某个样本的残差为\(\epsilon =(\alpha x+\beta-y)^2\)(令\(\epsilon=\varepsilon_{i}^2\)),做\(\alpha\)和Є的坐标图如下1.2.9,这是一条曲线,形状好比一条峡谷,而且可以看到该曲线仅存在一个极小值点,只有在该点,才是峡谷的底部,Є的值才为最小。如何找到峡谷底部呢?图中,我们在曲线的任意位置(除极小值点)放置一颗小球,并让红色小球一步一步沿着“下坡路”往下滚,毫无疑问,当小球停止滚动的时候,也就是我们到达峡谷底部的时候。
图1.2.9
如果用数学语言来描述的话,就是对\(\alpha\)求偏导!高数告诉我们偏导数即为某点的斜率,下图中红色小球的起点,即\(\alpha_{_1}\)点的斜率用绿色的箭头表示,该线段在\(\alpha=\alpha_{_1}\)点处与曲线相切,箭头反方向为小球滚动的方向。仔细看看这个图,只要我们沿着导数(斜率)相反的方向移动\(\alpha\)(对于\(\beta\)来说也是一样),就会找到极小值点了!
图1.2.10
上面的偏导\(\frac{\partial \epsilon }{\partial \alpha }\)我们也称为梯度,残差方程我们也称为损失函数,刚才讲的沿着导数的反方向向下移动寻找曲线极小值点的方法就叫梯度下降算法,这就好比我们所说的学习指导方针。在小球逐步往下移动的时候,每次移动的速率,我们用\(\eta\)表示,称作学习率,学习率的大小对于小球逼近极小值点有着至关重要的作用。较大的学习率会让小球错过极小值点,因为过大的学习率导致小球每次滚动的速率过大,在快要接近极小值点的时候却错过了极小值点。在实际应用中,这种情况会使我们的残差突然增大,来回摆动,梯度下降算法失效,所以我们通常会选取比较小的学习率,以避免逼近极小值点时来回震荡的情况。
图1.2.11
通过梯度和学习率我们可以得到\(\alpha\)的新公式:\[\alpha_{_1}=\alpha_{_0}-\eta \frac{\partial \epsilon }{\partial \alpha}\]
延伸为一般的情况为:\[\alpha_{_n+1}=\alpha_{_n}-\eta \frac{\partial \epsilon }{\partial \alpha}\]
下面用一些具体值来说明梯度下降算法是如何工作的,首先为了简化计算,假设\(\beta=0\),当前样本值为(x,y)=(1,0),则损失函数表示为\(\epsilon=(\alpha -1)^2\),参数\(\alpha\)的梯度为\(\bigtriangledown =\frac{\partial \epsilon }{\partial \alpha }=2 (\alpha -1)\),那么使用梯度下降算法每次对参数\(\alpha\)的更新公式为\(\alpha_{_n+1}=\alpha_{_n}-\eta \bigtriangledown n\),设置参数的初始值为5,学习率为0.3,那么这个优化过程可以总结为下表1.2.1。
表1.2.1
可以看到,经过了10次迭代后,参数\(\alpha\)的值变成了1.00043,这个和参数最优值1已经比较接近了,虽然这里给出的是一个非常简单的样例,但是我相信你已经完全可以理解梯度下降算法的工作原理了。需要注意的是,梯度下降算法并不能保证被优化的函数达到全局最优解,即不保证小球能滚动到真正的谷底。如下图所示,山路弯曲,颠簸,凹凸不平,真正的谷底还未到,小球就停止了滚动。从数学上来解释,可以这样说,红色箭头处的斜率为0,梯度也为0,于是参数就停止更新。所以在实际应用中,我们必须保证损失函数为凸函数才能使用梯度下降算法,而只有在这种情况下梯度下降算法才能保证达到全局最优解。什么是凸函数?简单来理解就是曲线平滑,全局仅存在一个极小值点,不存在多个最小值点。(极小值点和最小值点是有区别的哦)
图1.2.12
三、矩阵乘法
上面我们仅描述了一个输入的情况,即一个\(x\)对应一个\(y\),而实际上,要进行二分类或者多分类,物体的特征值一定是多样的,否则无法达到分类的效果。就好比上面黄豆和蚕豆的例子,我们需要对其高、宽、颜色、重量等进行计算推导才能区别两种豆子。
现在,我们来演示一下把多输入转化为矩阵,并在模型中进行计算的过程。如下图所示,我们需要四个输入信号\(x_{1},x_{2},x_{3},x_{4}\),和两个输出信号;每一个输入信号与输入层的每一个节点都有权重相连,可以看到\(x_{1}\)与输入层的第一个节点权重为\(ω_{1,1}\),\(x_{2}\)和输入层的第一个节点间的权重为\(ω_{2,1}\),\(x_{2}\)和输入层的第二个节点间的权重为\(ω_{3,2}\)等等,以此类推......
问题来了,一个线性模型,怎么会有多个输出信号呢?而且你也注意到了,我们在第一层输入层的后面我们加入了Sigmoid激活函数,这让模型看上去是两层神经网络,而线性模型仅有一层感知器才对!这里,我们当然需要解释一下,我们在此例中加入的激活函数并不增加一层网络深度,而仅仅是为了读者直观的感受而已!记住,无论在何种网络下,激活函数都不构成网络层数。所以,该模型也只是一层而已!同时,正是因为我们加入了Sigmoid激活函数,才会把线性模型的结果映射为两个输出,即我们把线性模型非线性化了。这种模型,我们称为逻辑回归。
图1.2.13
理清思路后,我们继续往下看。
我们把多输入信号用例向量表示:
\[X=\begin{bmatrix} x{_{1}}\\ x{_{2}}\\ x{_{3}}\\ x{_{4}} \end{bmatrix}\]
权重用2X4维矩阵表示:
\[W=\begin{bmatrix} w_{1,1} &w_{2,1} &w_{3,1} &w_{4,1} \\ w_{1,2} &w_{2,2} &w_{3,2} &w_{4,2} \\ \end{bmatrix}\]
第一层的第一个节点表示四个输入的加权和,第二个节点同理。那么,输出则可表示为输入和权重的点积,点积后的结果用\(X_{output}\)表示。
\[X_{output}=W\cdot X =\begin{bmatrix} w_{1,1} &w_{2,1} &w_{3,1} &w_{4,1} \\ w_{1,2} &w_{2,2} &w_{3,2} &w_{4,2} \\ \end{bmatrix} \cdot \begin{bmatrix} x{_{1}}\\ x{_{2}}\\ x{_{3}}\\ x{_{4}} \end{bmatrix}\]
即:\[X_{output}= \begin{bmatrix} X{_{1}}\\ X{_{2}}\\ \end{bmatrix}\]
其中:
\[X_{1}=w_{1,1}x{_{1}}+w_{2,1}x{_{2}}+w_{3,1}x{_{3}}+w_{4,1}x{_{4}}\]
\[X_{2}=w_{1,2}x{_{1}}+w_{2,2}x{_{2}}+w_{3,2}x{_{3}}+w_{4,2}x{_{4}}\]
得到了输入加权和以后,让该\(X_{output}\)通过\(Sigmoid\)激活函数得到最终的输出,用\(O_{x}\)表示:
\[O_{x}=Sigmoid(X_{output})=Sigmoid(\begin{bmatrix} X{_{1}}\\ X{_{2}}\\ \end{bmatrix})\]
以上便是四个输入信号,两个输出的信号的计算过程。这个过程相对来说比较简单,如果有四个输出节点的话,那么权重为4X4维矩阵;如果中间还有隐藏层,那么每一层之间的权重又不一样了。
也许这样生硬的数学描述你可能会觉得眼花缭乱,不如我们设置一些具体的值来具体地计算一下,这样会有更好的理解。设输入
\[X=\begin{bmatrix} 5\\ 3.5\\ 1.4\\ 0.2 \end{bmatrix}\]
权重\(W\)的值为随机选取值
\[W=\begin{bmatrix} 0.3 &0.6 &0.5 &0.8 \\ 0.4 &0.2 &0.9 &0.7 \\ \end{bmatrix}\]
\[X_{output}=W\cdot X =\begin{bmatrix} 0.3 &0.6 &0.5 &0.8 \\ 0.4 &0.2 &0.9 &0.7 \\ \end{bmatrix} \cdot \begin{bmatrix} 5\\ 3.5\\ 1.4\\ 0.2 \end{bmatrix}\]
\[O_{x}=\begin{bmatrix} 4.46\\ 4.1\\ \end{bmatrix}\]
套用上面的点积运算公式可得结果,但我建议不要自己手动计算,使用计算机,让它发挥自己最好的计算优势。在之后的代码中,我们会使用Python的数学库numpy来进行大量的运算(以上公式使用numpy.dot()做点积运算),不论什么复杂计算,计算机的速度毫无疑问地完美超越人类。
在得到\(X_{output}\)后,我们把它输入到激活函数中。
\[Sigmoid(X_{output})=Sigmoid(\begin{bmatrix} 4.46\\ 4.1\\ \end{bmatrix})\]
记住,复杂的计算仍然交给计算机。经过Sigmoid函数后,得到最后输出\(O_{x}\)。
\[O_{x}=\begin{bmatrix} 0.9885\\ 0.9836\\ \end{bmatrix}\]
计算完成,至此,一组信号输入模型及最后输出的过程就全部清晰的展现出来了。矩阵乘法在神经网络中使用非常普遍,我们简单讲解一下应该很容易理解。不过,这一节引入了一个逻辑回归,那么逻辑回归和线性回归到底什么关系呢,衔接上下文,我们不难发现,其实逻辑回归就是在线性回归的基础上使用了激活函数。通过激活函数把输出映射成两个输出,然后使用两个输出做相应的预测,因此逻辑回归模型才是拿来做实际应用,而线性回归更常拿来做理论推导。
你也许会问还有一个重要的问题没有解决,就是权重如何优化?上面的矩阵乘法,权重都是事先设置好的,在实际训练中呢?当然,权重的优化离不开梯度下降算法的作用,而权重的初始值也是我们随机定义的,通过梯度下降,不断的更新权重,最后得到完美的线性模型。下一节我们会用一个实际的例子并附上代码来展示一次。
四、实例:逻辑回归实现鸢尾花分类
我们收集到了一些鸢尾花数据集,部分数据如下表,在该数据集中有两种类别的鸢尾花各五十条,一共100条数据,数据集包括4个属性,分别是萼片长度,宽度,花瓣长度,宽度,其中在100条数据中,两类分别提取10条数据,共20条数据作为验证集,另外80条是训练集。采用Sigmoid函数做激活函数,再规定分类器输出1表示Iris-setosa(山鸢尾)品种,输出0表示Iris-Versicolour(变色鸢尾)。
表1.2.2
在进行机器学习之前,对数据集的可视化分析尤为重要,适当的可视化可以帮助我们快速有效的了解数据信息,并且大概地评估一下本次训练应使用何种模型,特别是对于二分类或多分类而言,节省数据的分析时间。在Python中,强大的matplotlib工具可以帮我们做到。
import matplotlib.pyplot as plt
import xlrd
iris = xlrd.open_workbook('iris.xlsx')
table = iris.sheets()[0]
sepal_length_setosa = table.col_values(0)[1:50]
sepal_width_setosa = table.col_values(1)[1:50]
sepal_length_versicolor = table.col_values(0)[51:100]
sepal_width_versicolor = table.col_values(1)[51:100]
fig = plt.figure()
ax1 = fig.add_subplot(111)
ax1.set_title('sepal length and width scatter')
plt.xlabel('sepal length')
plt.ylabel('sepal width')
ax1.scatter(sepal_length_setosa, sepal_width_setosa, c='r', marker='o')
ax1.scatter(sepal_length_versicolor, sepal_width_versicolor, c='g', marker='o')
plt.show()
图1.2.14
上面的代码中我们使用了一个“xlrd”的工具来读取下载到的excel文件,但我建议把文件转化为csv类型,这样我们就可以使用readlines()读取文件,简化代码哦。
言归正传,我们仅仅使用了萼片长度和宽度两种特征来描绘鸢尾花的散点图,从上图可以看到两个品种的鸢尾花呈现出明显的线性关系,也就是说,可以使用一个线性分类器把两类别数据划分开,这正是我们希望看到的情形。
那么从现在起,我们会经常使用Python来表达数学问题和训练模型过程,按照以上分析的结果首先定义一个线性模型,我们把模型分为两层,第一层为输入层,第二层为输出层,没有隐藏层,同时初始化输入、输出、权重、学习率和Sigmoid激活函数。
import numpy
import scipy.special
# linear network classify definition
class linearNetwork:
# initialise the linear network
def __init__(self, inputnodes, outputnodes, learningrate):
# set number of nodes in each input, output layer
self.inodes = inputnodes
self.onodes = outputnodes
self.wih = numpy.random.normal(0.0, pow(self.inodes, -0.5), (self.onodes, self.inodes))
# learning rate
self.lr = learningrate
# activation function is the sigmoid function
self.activation_function = lambda x: scipy.special.expit(x)
pass
再来描绘一下我们的模型,如下图所示,第一层有4️个节点,第二层输出层有两个节点,并使用两个激活函数Sigmoid,在模型内部,输入层和输出层之间,我们分别用绿线代表输入信号和输出层第一个节点的连接,并且把输入信号赋予的权重改成绿色;同理,红线表示输入信号和输出层中第二个节点的连接,把输入信号赋予的权重用改成红色,这样方便我们看清信号在网络中的流动状态。这不是以上矩阵乘法中的线性分类器吗,太好了!矩阵的乘法将会又一次出现。
图1.2.15
处理样本数据时,我们把下载到的数据分成训练集和测试集,并分别放在两个csv文档中,训练集数据80条,测试集数据20条,数据量不大,这个工作需要我们手动来完成。
定义好了线性模型后,我们需要定义如何训练模型和测试模型。如下代码所示,记得把整个模型保存在一个python文件中,为后续的训练和测试调用。
import numpy
import scipy.special
# linear network classify definition
class linearNetwork:
# initialise the linear network
def __init__(self, inputnodes, outputnodes, learningrate):
# set number of nodes in each input, output layer
self.inodes = inputnodes
self.onodes = outputnodes
self.wih = numpy.random.normal(0.0, pow(self.inodes, -0.5), (self.onodes, self.inodes))
# learning rate
self.lr = learningrate
# activation function is the sigmoid function
self.activation_function = lambda x: scipy.special.expit(x)
pass
# train the linear network
def train(self, inputs_list, targets_list):
inputs = numpy.array(inputs_list, ndmin=2).T
targets = numpy.array(targets_list, ndmin=2).T
# calculate signals into final output layer
final_inputs = numpy.dot(self.wih, inputs)
# calculate the signals emerging from final output layer
final_outputs = self.activation_function(final_inputs)
# output layer error is the (target - actual)
output_errors = targets - final_outputs
# update the weights for the links between the hidden and output layers
self.wih += self.lr * numpy.dot((output_errors * final_outputs * (1.0 - final_outputs)),
numpy.transpose(inputs))
# query the linear network
def query(self, inputs_list):
# calculate signals into linear output layer
linear_output = numpy.dot(self.wih, inputs_list)
# calculate the signals emerging from final output layer
final_outputs = self.activation_function(linear_output)
return final_outputs
紧接着就是训练模型,我们先把存放在csv文件上的数据导入一个list中,即使得输入信号为如下形式:
\[ \begin{bmatrix} [5.1& 3.5&1.4 &0.2 ]\\ [4.9& 3&1.4 &0.2 ]\\ [4.7&3.2 &1.3 &0.2] \\ ...&... &... &... \end{bmatrix} \]
# load the iris training data into a list
training_data_file = open("iris_train.csv", 'r')
training_data_list = training_data_file.readlines()
training_data_file.close()
数据准备好后,可以开始训练模型,首先我们根据原数据特性来设置输入节点个数(4个输入信号,分别是萼片长度、萼片宽度、花瓣长度、花瓣宽度)、输出节点个数(2个,用于最后判断是0还是1,1表示山鸢尾花,0表示变色鸢尾花)、学习率(0.01)、训练的迭代次数(1000次)、以及调用之前定义的模型。
# number of input, output nodes,and learning rate
input_nodes = 4
output_nodes = 2
learning_rate = 0.01
# epochs is the number of times the training data set is used for training
epochs = 1000
# create instance of linear network
n = linear_classify.linearNetwork(input_nodes, output_nodes, learning_rate)
训练正式开始,一定要记住,训练完成后需要把最优的权重值保存为一个文件,以供验证模型时使用。这里,我们保存的文件名为win.npy。以下代码中的target表示目标预测值。
# train the linear network
for e in range(epochs):
# go through all records in the training data set
print('start training, epoch:', e)
for record in training_data_list:
# split the record by the ',' commas
record = record.replace('\n', '').split(',')
record = [float(data) for data in record]
input_data = (np.asfarray(record[0:4]) / 10.0 * 0.99) + 0.01
true_lable = int(record[-1])
# create the target output values (all 0.01, except the desired label which is 0.99)
targets = np.zeros(output_nodes) + 0.01
targets[true_lable] = 0.99
n.train(input_data, targets)
pass
np.save('wih.npy', n.wih)
pass
训练的迭代次数会影响模型的好坏,我们在本次训练中,由于训练数据仅有几十条,因此,迭代1000次完全足够了。在训练完模型后,我们保存了最优的权重值,在验证模型时,需要使用这些最优权重来做测试集数据的验证。但是如何判断模型的好坏呢?一般我们用几个指标来衡量,准确率,召回率,F值等等。
准确率和召回率是广泛用于信息检索和统计学分类领域的两个度量值,用来评价结果的质量。其中准确率是指正确预测出的样本数与全部样本数的比率,衡量的是模型的查准率,即预测出的类别有多少是准确的(包括正负两种样本);召回率是指对所关注的某个类别,预测出的正确样本数和该类别样本总数的比率,衡量的是模型的查全率,即所有正样本或负样本有多少被预测正确了;F值指当准确率和召回率存在矛盾时,综合准确率和召回率这两种评估指标,用于综合反映整体的指标,是两者的调和平均值。
准确率(Precision)
召回率(Recall)
F值(F-Measure)
比如,以上鸢尾花的测试集有正样本数山鸢尾花10条,负样本数变色鸢尾花10条,总共20条样本,假设我们的模型正确预测出了山鸢尾花9条,变色鸢尾花8条,那么我们模型的准确率就为:
\[P=\frac{9+8}{20}=\frac{17}{20}=0.85\]
如果关注的是模型能够正确预测出山鸢尾花,则模型召回率为:
\[R=\frac{9}{10}=0.9\]
如果关注的是模型能够正确预测出变色鸢尾花,则模型召回率为:
\[R=\frac{8}{10}=0.8\]
至于F值,按照验证结果,使用准确率和召回率带入计算就可以了。因此了解到评估模型的几个性能指标后,我们才可以开始正式验证线性模型的表现力。
以下代码中,省略了数据的导入,该部分与训练模型的时候一样。验证时,我们把模型最后输出节点上的两个概率值取最大概率值的索引坐标,并与真实label做对比,如果和真实值一样,则统计为1,如果不相同,则统计为0;最后,用统计的0,1值来计算模型的准确率。
# go through all the records in the test dataset
n.wih = np.load('wih.npy')
scorecard = []
for record in testing_data_list:
record = record.replace('\n', '').split(',')
record = [float(data) for data in record]
input_data = (np.asfarray(record[0:4]) / 10.0 * 0.99) + 0.01
# true label is the last one in record
true_label = int(record[-1])
# query the network
outputs = n.query(input_data)
# the index of the highest value corresponds to the label
label = np.argmax(outputs)
# append correct or incorrect to list
if label == true_label:
scorecard.append(1)
else:
scorecard.append(0)
pass
# calculate the performance score, the fraction of correct answers
scorecard_array = np.asarray(scorecard)
print("performance = ", scorecard_array.sum() / scorecard_array.size)
不出所料,我们得出的准确率为:
0.95
这个结果对仅用100多条数据来进行训练过的模型来说应该很不错了,如果我们还想提高准确率,一个办法是增加样本数量,这种方法毫无疑问能够提高模型的准确率,并且这种方法适用于任何网络或模型。另外一种办法就是改变我们的模型,比如采用SVM,以及适当增加神经网络的深度等等,不过,不同的方法还是要根据不同的分类要求来使用才能发挥最好的效果。
好了,通过这一节我们逐步清晰了逻辑回归模型的工作原理,输入信号是怎么在网络中流动的,以及最后输出是些什么信号,这些细节无疑对于我们后续了解更复杂的网络打下坚实的基础。
最后,请闭上眼睛,让神经网络在脑海里再漫游一次,让我们再编织一次网络模型,让数据再一次在模型中流动。睁开眼,带着愉悦的心情走向下一个里程。
附录:
完整代码:https://github.com/stephen-v/simple-nerual-network/tree/master/linear_classify
iris数据集下载,百度云盘:https://pan.baidu.com/s/1Ia9PHzJOMv6BAGTEj0m9uQ 密码:ytn6
参考文献:
1、《Python神经网络编程》
2、https://www.zhihu.com/question/22334626
3、https://zhuanlan.zhihu.com/p/21462488
4、https://www.zhihu.com/question/23765351
5、https://blog.csdn.net/ppn029012/article/details/8775597
6、《Tensorflow实战Google深度学习框架》
7、http://bookshadow.com/weblog/2014/06/10/precision-recall-f-measure/