目前,机器学习的主流框架之一是Google开源的三方库TensorFlow。本节将从以下三个章节讲解如何用TensorFlow建立逻辑回归模型。
一、TensorFlow基本用法
二、待解决预测问题的数据集
三、TensorFlow建立逻辑回归模型
【前提】便于理解本文所述内容,请准备好如下开发环境与知识储备:
一、TensorFlow基本用法
TensorFlow程序通常被组织成一个构建阶段和一个执行阶段。在构建阶段,用图表示计算任务,图中的节点被称之为op(operation的缩写)。在执行阶段,使用会话执行图中的op。执行阶段TensorFlow将图形定义转换成分布式执行的操作,会话将图的op分发到诸如CPU或GPU之类的设备上,同时提供执行op的方法,以充分利用可用的计算资源。
TensorFlow使用tensor(张量)表示数据,一个op可以获得个或多个Tensor,执行计算,产生个或多个tensor。每个tensor是一个类型化的多维数组。执行阶段,op的方法执行后,将产生的tensor返回,在Python语言中,返回的tensor是numpy ndarray对象;在C和C++语言中,返回的tensor是tensorflow::Tensor实例。
下面以一个计数器为例,作为TensorFlow的入门代码。如图1所示,①~⑥属于构建阶段;⑦~⑩是执行阶段。
图1tensorFlow实现计数器
首先要导入tensorflow的包,见①。本例中,构建阶段创建了5个tensor:state、one、newState、update、initOp;5个op:变量op、常量op、add相加op、 assign赋值op、和init初始化变量op。
tensor不负责存储值,只是作为参数传递给op,想要得到值,必须在执行阶段的会话session中调用run()。如果在构建阶段②将state打印出来,得到的结果并不是数值,见图2中的②对应的输出结果。只有在执行阶段⑨,run()后打印出来是初始化的数值;在执行阶段⑩,打印出来是加1后重新赋值后的数值1、2、3,见图2中⑨、⑩为对应代码的输出结果。
图2计数器程序运行结果
TensorFlow启动图后,变量必须先经过初始化,所以⑥增加一个“初始化”的op到图中。
构建阶段完成后,才能启动图。启动图的第一步是创建一个Session对象,在op任务完成后,Session对象必须关闭以释放资源。图1的程序中,⑦使用“with”代码块来自动完成关闭动作。如果不用“with”,必须显示关闭会话,释放资源,⑦~⑩代码要改为图3所示的代码。
图3显示关闭Session
从良好的编程习惯角度考虑,建议最好用“with”代码块。另外,在执行阶段,想要打印tensor state的数值,不可以直接print(state),可以用图4所示的代码打印变量值。
图4打印张量的数值
二、待解决预测问题的数据集
现在回到预测患有疝病的马是否存活的问题,对该预测问题,使用到的数据集说明如下
1、数据集出处
数据集仍然使用UCI机器学习数据库[1],请自行前往http://archive.ics.uci.edu/ml/datasets/Horse+Colic下载原始数据集。
这个数据集包含368个样本,每个样本有28个数据。其中300个样本作为训练集,见horse-colic.data;68个样本作为测试集,即horse-colic.test。
数据集中,一行数据代表一个样本,所以horse-colic.data有300行,horse-colic.test有68行。
在每一个样本中(每一行),有27个数据是特征,1个数据是标记数据(即实际输出)。
文件horse-colic.names是说明文件,解释数据集中27个特征代表的含义,以及取值是什么。该说明文件中有一段说明,如图5所示,即原始数据集中,每一行的第23个数据是标记数据(实际输出)。但是,习惯的做法是在每一个样本中,前面的连续若干数据是特征,标记数据放到样本的最后,因为这样做,加载数据集的程序才具有通用性(如下文中图12所示的loadSampleSet()方法)。所以对这个原始数据集,需要做一些预处理工作。
图5 horse-colic.names中的部分说明文字
2、对数据集的预处理
将训练集和测试集中每一行的第23个数据移至每一行的最后,请自行写代码处理。
原始数据中,有的特征没有数据,用?表示。因为这个原始的数据集本意是模拟特征缺失的场景。因为在实际应用中,数据都是机器收集回来的,如果机器(例如某个传感器)损坏,导致某些特征无效是时常碰到的场景。在本文的例子中,简单的将缺失值?替换为。这么处理也是适合逻辑回归模型的,其物理意义为:当某个特征xj为时,它对更新wj不产生影响。公式如下(参考《
逻辑回归模型的原理与代码实现(二)-梯度下降算法及实现
》公式(23)的推导过程):
在原始数据集中,输出值取值是3个,即1=仍存活;2=已死亡;3=安乐死。为了转换为二分类问题,对标记数据(实际输出值)做了预处理,将输出值2和3都转为0=死亡,处理程序如图6所示:
图6患有疝病的马标记数据预处理
将预处理后的训练集、测试集文件分别重新命名为horseColicTraining.txt,horseColicTest.txt。在下一章节中的程序中会用到。
三、TensorFlow建立逻辑回归模型
作为机器学习的主流框架,Tensorflow提供了机器学习算法的各种API。本节重点说明如何使用这些API做进一步的封装,自己构建逻辑回归模型。
1、模型实现
7训练逻辑回归模型的主程序
如图7所示,在训练逻辑回归模型的主程序中,实例化了一个类:LogisticByTensorFlow,该类是用tensorflow API封装的,其具体实现参考图8,和图9。
其中方法train()是LogisticByTensorFlow的入口,其作用是训练逻辑回归模型。
而setDatas(),用来设置初始化类LogisticByTensorFlow所需要的各种参数,如存放训练数据的文件名:trainingDataFileName;特征的个数:numOfFeatures;多分类问题中的类别个数:numOfLables;训练好的权值w与b存放的文件名:weightsFileName,biasFileName;以及从样本集中一次随机抓取的样本个数:batchsize。上述参数都放在单独的模块:constantVariables中,见图10。
图8 LogisticByTensorFlow类的主要方法
图9 LogisticByTensorFlow类的其他方法
图10 constantVariables模块
2、代码详解
在实际应用中,机器学习的特点是样本集非常大,当训练样本成千上万(m很大),样本特征又很多时(n很大),如果使用全量的训练集来计算对wj,j∈[1,2,…,n]迭代更新的贡献时,如下图公式所示,其计算资源的消耗是很大的。
因此,人们想到是否可以一次从全量训练集中随机抓取小数量的样本,通过多次随机抓取同样小数量的样本,多次循环训练逻辑回归模型,从而达到全量训练集训练模型一样的效果,这就产生了梯度下降算法的变体:随机梯度下降法。
图8中,代码块③是从训练集所在的文件中读取全量样本的特征与标记数据。代码块④是用来存放从全量样本中一次随机抓取的样本数据。
在构建阶段,抓了哪些样本是不确定的,所以用tensorflow的占位符placeholder[3]来表示,matXTrain和matYTrain需要在运行的时候赋值。
在运行阶段,代码块⑧、⑨从全量样本中随机抓取batchsize个数的样本,并且通过字典关键字feed_dict赋值给matXTain和matYTrain。
占位符placeholder的作用类似于c++的cin。图8中代码块③调用的方法parseFeaturesAndLablesFromFile(),其实现代码见图9的③。图8中代码块⑧调用的方法loadSamplesByBatch(),其实现代码见图9的⑧。
【拓展】如果将迭代训练次数设为1,随机抓取的样本个数为全量样本的个数,即trainingEpochs = 1,batchsize = numOfSamples,则随机梯度下降法退化为常规的梯度下降算法。
图8中,代码块⑤初始化参数w和b,并计算z=wTx+b。其中,
随机数生成方法选用的是tensorflow的random_uniform(shape, minval=0.0, maxval=1.0,dtype=tf.float32, seed=None, name=None)。
调用该方法生成2个矩阵:numOfFeatures×numOfLables的w和numOfLables×1的b。矩阵中的元素是取值为(-1.0,1.0)之间的随机数,且这些随机数是均匀分布的。
计算z时,调用tensorflow的API:
matmul(a, b,
transpose_a=False,
transpose_b=False,
adjoint_a=False,
adjoint_b=False,
a_is_sparse=False,
b_is_sparse=False,
name=None)
Z = tf.matmul(W, matXTain, transpose_a=True)+b的效果同numpy的Z =np.dot(W.T, matXTain)+ b。
【拓展】图8所示的代码不仅可以解决二分类问题,还可以解决多类分类问题。
例如预测一张图片上的数字是0~9中的哪一个数字(即10个数字的分类问题),则numOfLables=10。
样本集中,实际输出可以编码为,其中yi取值为或1。如果实际输出是数字,则。如果实际输出是数字1,则。即实际输出数字是num,则向量y中,只有y[num]=1,其他为。
在预测数字时,用训练好的模型计算出预测值,寻找预测值向量中取值最大的索引index,则判定为index对应的那个数。
图8中,代码块⑥调用了tensorflow的API:nn.sigmoid_cross_entropy_with_logits(_sentinel=None,labels=None,logits=None, name=None)。
该API是将sigmoid和cross_entropy(即逻辑回归的代价函数)放在一起计算。因此,该API的输入参数logits为输入特征的加权和,即logits =wTx+b。代码中不需再单独调用nn.sigmoid(x,name=None)。
图8中,代码块⑦调用了tensorflow提供的梯度下降算法的优化器:GradientDescentOptimizer,训练的目的是使代价函数最小。这个API提供的功能与《逻辑回归模型的原理与代码实现(二)-梯度下降算法及实现》一文中图20所示的代码提供的功能类似。
除了梯度下降算法的优化器外,tensorflow还提供了其他的优化器,如AdadeltaOptimizer:Adadelta算法的优化器;AdagradOptimizer:Adagrad算法的优化器等。如果要替换模型训练的核心算法,只需替换这一行代码即可。
由此可见,在学习机器学习算法的过程中,理解数学公式推导,物理意义,并尝试自行实现算法是非常重要的,否则调用tensorflow的API,就是一个黑盒。
图8中,代码块⑩将模型训练好的参数w和b保存到文件中,以便在预测时,直接load训练好的参数。saveTrainingWeightsToFile()的实现代码如图11所示,作为dataFunctions模块中的内容已经在LogisticByTensorFlow类中import了。
图11 dataFunctions模块中的saveTrainingWeightsToFile方法
其他处理数据的方法,如从文件中读取并解析样本集,读取模型训练的结果等方法,都放在dataFunctions模块中,如图12、图13所示。
图12 dataFunctions模块中loadSampleSet()方法
图13 dataFunctions模块中loadTrainingWeightsAndBiasFromFile()方法
3、运行结果
使用《逻辑回归模型的原理与代码实现(二)-梯度下降算法及实现》一文中图24所示的程序在测试集上进行预测,得到误判率为30.9%。这个结果与自己写的算法误判率差不多。之所以有这样的误判率,与原始数据特征缺失有很重要的关系。
结束语:通过3篇连载文章,逻辑回归模型已经讲解完毕,希望能够帮助读者朋友理解模型的原理。从下一篇开始,将讲解人工神经网络。
四、参考文献
1、预测患有疝病的马是否存活问题的数据集来自2010年1月11日的UCI机器学习数据库:http://archive.ics.uci.edu/mldatasets/Horse+Colic
2、python的官方文档
3、TensorFlow官方文档
http://tensorflow.org/
4、吴恩达《深度学习》
----------------------- End --------------------
前一篇:《逻辑回归模型的原理与代码实现(二)-梯度下降算法及实现》
前二篇:《逻辑回归模型的原理与代码实现(一)-数学模型》
前三篇:《人工智能入口》
对作者所写文章有兴趣,可继续关注公众号:点金要术(goldfinger4u),或扫描下面的二维码。也欢迎读者朋友在互联网上分享你认为有帮助的文章。
领取专属 10元无门槛券
私享最新 技术干货