第3章
基于TensorFlow的线性回归
本章将介绍TensorFlow是如何工作的,以及如何访问本书的数据集和补充学习资源。学完本章将掌握以下知识点:
- 用TensorFlow求逆矩阵
- 用TensorFlow实现矩阵分解
- 用TensorFlow实现线性回归
- 理解线性回归中的损失函数
- 用TensorFlow实现戴明回归(Deming Regression)
- 用TensorFlow实现Lasso回归和岭回归(Ridge Regression)
- 用TensorFlow实现弹性网络回归(Elastic Net Regression)
- 用TensorFlow实现逻辑回归
3.1 简介
线性回归算法是统计分析、机器学习和科学计算中最重要的算法之一,也是最常使用的算法之一,所以需要理解其是如何实现的,以及线性回归算法的各种优点。相对于许多其他算法来讲,线性回归算法是最易解释的。以每个特征的数值直接代表该特征对目标值或者因变量的影响。本章将揭晓线性回归算法的经典实现,然后讲解其在TensorFlow中的实现。
请读者注意,本书的所有源码均可以在GitHub中访问,网址为https://github.com/nfmcclure/tensorflow_cookbook ,也可访问Packt代码库https://github.com/PackPublishing/TensorFlow-Machine-Learning-Cookbook-Second-Edition 。
3.2 用TensorFlow求逆矩阵
本节将使用TensorFlow求逆矩阵的方法解决二维线性回归问题。
3.2.1 开始
线性回归算法能表示为矩阵计算,Ax = b。这里要解决的是用矩阵x来求解系数。注意,如果观测矩阵不是方阵,那求解出的矩阵x为x = (ATA) - 1ATb。为了更直观地展示这种情况,我们将生成二维数据,用TensorFlow来求解,然后绘制最终结果(见图3-1)。
3.2.2 动手做
1.导入必要的编程库,初始化计算图,并生成数据,代码如下:
2.创建后续求逆方法所需的矩阵。创建A矩阵,其为矩阵x_vals_column和ones_column的合并。然后以矩阵y_vals创建b矩阵,代码如下:
3.将A和b矩阵转换成张量,代码如下:
4.现在,使用TensorFlow的tf.matrix_inverse()方法,代码如下:
5.从解中抽取系数、斜率和y截距y-intercept,代码如下:
3.2.3 工作原理
与本书的大部分章节不一样的是,这里的解决方法是通过矩阵操作直接求解结果。大部分TensorFlow算法是通过迭代训练实现的,利用反向传播自动更新模型变量。这里通过实现数据直接求解的方法拟合模型,仅仅是为了说明TensorFlow的灵活用法。
我们使用了二维数据例子来展示数据的拟合。需要注意的是,求取系数的公式(x = (AT A)-1 AT b)能够根据需要扩展到数据中任意数量(但对共线性问题无效)。
3.3 用TensorFlow实现矩阵分解
本节将用TensorFlow为线性回归算法实现矩阵分解。特别地,我们会使用Cholesky矩阵分解法,相关的函数已在TensorFlow中实现。
3.3.1 开始
在上一节中实现的求逆矩阵的方法在大部分情况下是低效率的,特别地,当矩阵非常大时效率更低。另外一种实现方法是矩阵分解,此方法使用TensorFlow内建的Cholesky矩阵分解法。用户对将一个矩阵分解为多个矩阵的方法感兴趣的原因是,结果矩阵的特性使得其在应用中更高效。Cholesky矩阵分解法把一个矩阵分解为上三角矩阵和下三角矩阵,L和L'(L'和L互为转置矩阵)。求解Ax = b,改写成LL'x = b。首先求解Ly = b,然后求解L'x = y得到系数矩阵x。
3.3.2 动手做
1.导入编程库,初始化计算图,生成数据集。接着获取矩阵A和b,代码如下:
2.找到方阵的Cholesky矩阵分解,ATA:
注意,TensorFlow的cholesky()函数仅仅返回矩阵分解的下三角矩阵,因为上三角矩阵是下三角矩阵的转置矩阵。
3.抽取系数:
3.3.3 工作原理
正如你所看到的,最终求解的结果与前一节的相似。记住,通过分解矩阵的方法求解有时更高效并且数值稳定(见图3-2)。
3.4 用TensorFlow实现线性回归算法
虽然使用矩阵和分解方法非常强大,但TensorFlow有另一种方法来求解斜率和截距。它可以通过迭代做到这一点,逐步学习将最小化损失的最佳线性回归参数。
3.4.1 开始
本节将遍历批量数据点并让TensorFlow更新斜率和y截距。这次将使用Scikit Learn的内建iris数据集。特别地,我们将用数据点(x值代表花瓣宽度,y值代表花瓣长度)找到最优直线。选择这两种特征是因为它们具有线性关系,在后续结果中将会看到。下一节将讲解不同损失函数的影响,本节将使用L2正则损失函数。
3.4.2 动手做
1.导入必要的编程库,创建计算图,加载数据集,代码如下:
2.声明学习率、批量大小、占位符和模型变量,代码如下:
3.增加线性模型,y = Ax + b,代码如下:
4.声明L2损失函数,其为批量损失的平均值。初始化变量,声明优化器。注意,学习率设为0.05,代码如下:
5.现在遍历迭代,并在随机选择的批量数据上进行模型训练。迭代100次,每25次迭代输出变量值和损失值。注意,这里保存每次迭代的损失值,将其用于后续的可视化。代码如下:
6.抽取系数,创建最佳拟合直线,代码如下:
7.这里将绘制两幅图。第一幅图(见图3-3)是拟合的直线;第二幅图(见图3-4)是迭代100次的L2正则损失函数,代码如下:
这里很容易看出算法模型是过拟合还是欠拟合。将数据集分割成测试数据集和训练数据集,如果训练数据集的准确度更大,而测试数据集准确度更低,那么该拟合为过拟合;如果在测试数据集和训练数据集上的准确度都一直在增加,那么该拟合是欠拟合,需要继续训练。
3.4.3 工作原理
并不能保证最优直线是最佳拟合的直线。最佳拟合直线的收敛依赖迭代次数、批量大小、学习率和损失函数。最好时刻观察损失函数,它能帮助我们进行问题定位或者超参数
调整。
3.5 理解线性回归中的损失函数
理解各种损失函数在算法收敛的影响是非常重要的。这里将展示L1正则和L2正则损失函数对线性回归算法收敛的影响。
3.5.1 开始
这次继续使用上一节中的iris数据集,通过改变损失函数和学习率来观察收敛性的
变化。
3.5.2 动手做
1.除了损失函数外,程序的开始与以往一样,导入必要的编程库,创建一个会话,加载数据,创建占位符,定义变量和模型。我们将抽出学习率和模型迭代次数,以便展示调整这些参数的影响。代码如下:
2.损失函数改为L1正则损失函数,代码如下:
3.现在继续初始化变量,声明优化器,遍历迭代训练。注意,为了度量收敛性,每次迭代都会保存损失值。代码如下:
3.5.3 工作原理
当选择了一个损失函数时,也要选择对应的学习率。这里展示了两种解决方法,一种是上一节的L2正则损失函数,另一种是L1正则损失函数。
如果学习率太小,算法收敛耗时将更长。但是如果学习率太大,算法有可能产生不收敛的问题。下面绘制iris数据的线性回归问题的L1正则和L2正则损失(见图3-5),其中学习率为0.05。
从图3-5中可以看出,当学习率为0.05时,L2正则损失更优,其有更低的损失值。当学习率增加为0.4时,绘制其损失函数(见图3-6)。
从图3-6中可以发现,学习率大导致L2损失过大,而L1正则损失收敛。
3.5.4 延伸学习
为了更容易地理解上述的情况,这里清晰地展示大学习率和小学习率对L1正则和L2正则损失函数的影响。这里可视化的是L1正则和L2正则损失函数的一维情况,如图3-7所示。
3.6 用TensorFlow实现戴明回归算法
本节将实现戴明回归(Deming Regression),其意味着需要不同的方式来度量模型直线和数据集的数据点间的距离。
戴明回归有很多别名,例如全回归、正交回归(ODR)或者最短路径回归。
3.6.1 开始
如果最小二乘线性回归算法最小化到回归直线的竖直距离(即,平行于y轴方向),则戴明回归最小化到回归直线的总距离(即,垂直于回归直线)。其最小化x值和y值两个方向的误差,具体的对比图如图3-8所示。
为了实现戴明回归算法,我们修改一下损失函数。线性回归算法的损失函数最小化竖直距离;而这里需要最小化总距离。给定直线的斜率和截距,则求解一个点到直线的垂直距离有已知的几何公式。代入几何公式并使TensorFlow最小化距离。
3.6.2 动手做
1.除了损失函数外,其他的步骤跟前面的类似。导入必要的编程库,创建一个计算图会话,加载数据集,声明批量大小,创建占位符、变量和模型输出,代码如下:
2.损失函数是由分子和分母组成的几何公式。给定直线y = mx + b,点(x0, y0),则求两者间的距离的公式为:
3.现在初始化变量,声明优化器,遍历迭代训练集以得到参数,代码如下:
4.绘制输出结果(见图3-9)的代码如下:
3.6.3 工作原理
戴明回归算法与线性回归算法得到的结果基本一致。两者之间的关键不同点在于预测值与数据点间的损失函数度量:线性回归算法的损失函数是竖直距离损失;而戴明回归算法是垂直距离损失(到x轴和y轴的总距离损失)。
注意,这里戴明回归算法的实现类型是总体回归(总的最小二乘法误差)。总体回归算法是假设x值和y值的误差是相似的。我们也可以根据不同的理念使用不同的误差来扩展x轴和y轴的距离计算。
3.7 用TensorFlow实现lasso回归和岭回归算法
也有些正则方法可以限制回归算法输出结果中系数的影响,其中最常用的两种正则方法是lasso回归和岭回归。本节将详细介绍如何实现这两种方法。
3.7.1 开始
lasso回归和岭回归算法跟常规线性回归算法极其相似,有一点不同的是,在公式中增加正则项来限制斜率(或者净斜率)。这样做的主要原因是限制特征对因变量的影响,通过增加一个依赖斜率A的损失函数实现。
对于lasso回归算法,在损失函数上增加一项:斜率A的某个给定倍数。我们使用TensorFlow的逻辑操作,但没有这些操作相关的梯度,而是使用阶跃函数的连续估计,也称作连续阶跃函数,其会在截止点跳跃扩大。一会就可以看到如何使用lasso回归算法。
对于岭回归算法,增加一个L2范数,即斜率系数的L2正则。这个简单的修改将在3.7.4节介绍。
3.7.2 动手做
1.这次还是使用iris数据集,使用方式跟前面的类似。首先,导入必要的编程库,创建一个计算图会话,加载数据集,声明批量大小,创建占位符、变量和模型输出,代码如下:
2.增加损失函数,其为改良过的连续阶跃函数,lasso回归的截止点设为0.9。这意味着限制斜率系数不超过0.9,代码如下:
3.初始化变量和声明优化器,代码如下:
4.遍历迭代运行一段时间,因为需要过一会儿才会收敛。最后结果显示斜率系数小于0.9,代码如下:
3.7.3 工作原理
通过在标准线性回归估计的基础上,增加一个连续的阶跃函数,实现lasso回归算法。由于阶跃函数的坡度,我们需要注意步长,因为太大的步长会导致最终不收敛。对于岭回归算法,将在下一节介绍对其的必要修改。
3.7.4 延伸学习
对于岭回归算法,在上一节的代码基础上稍微改变损失函数即可,代码如下:
3.8 用TensorFlow实现弹性网络回归算法
弹性网络回归算法(Elastic Net Regression)是综合lasso回归和岭回归的一种回归算法,通过在损失函数中增加L1和L2正则项。
3.8.1 开始
在学完前面两节之后,可以轻松地实现弹性网络回归算法。本节使用多线性回归的方法实现弹性网络回归算法,以iris数据集为训练数据,用花瓣长度、花瓣宽度和花萼宽度三个特征预测花萼长度。
3.8.2 动手做
1.导入必要的编程库并初始化一个计算图,代码如下:
2.加载数据集。这次,x_vals数据将是三列值的数组,代码如下:
3.声明批量大小、占位符、变量和模型输出。这里唯一不同的是x_data占位符的大小为3,代码如下:
4.对于弹性网络回归算法,损失函数包含斜率的L1正则和L2正则。创建L1和L2正则项,然后加入到损失函数中,代码如下:
5.现在初始化变量,声明优化器,然后遍历迭代运行,训练拟合得到系数,代码如下:
6.下面是代码运行的输出结果:
7.现在能观察到,随着训练迭代后损失函数已收敛(见图3-10),代码如下:
3.8.3 工作原理
弹性网络回归算法的实现是多线性回归。我们能发现,增加L1和L2正则项后的损失函数中的收敛变慢。
3.9 用TensorFlow实现逻辑回归算法
本节将实现逻辑回归算法,预测低出生体重的概率。
3.9.1 开始
逻辑回归算法可以将线性回归转换成一个二值分类器。通过sigmoid函数将线性回归的输出缩放到0和1之间。目标值是0或者1代表着一个数据点是否属于某一类。如果预测值在截止值以上,则预测值被标记为“1”类;否则,预测值标为“0”类。在本例中,为方便简单起见,将指定截止值设为0.5。
在本例中使用的低出生体重的数据来自本书作者的GitHub数据仓库(https://github. com/nfmcclure/tensorflow_cookbook/raw/master/01_Introduction/07_Working_with_Data_Sources/birthweight_data/birthweight. dat )。我们将从多个因素来预测低出生体重。
3.9.2 动手做
1.导入必要的编程库,包括requests模块,因为我们将通过超链接访问低出生体重数据集。初始化一个计算图,代码如下:
2.通过requests模块加载数据集,指定要使用的特征。实际出生体重特征和ID两列不需要,代码如下:
3.分割数据集为测试集和训练集:
4.将所有特征缩放到0和1区间(min-max缩放),逻辑回归收敛的效果更好。下面将归一化特征,代码如下:
注意,在缩放数据集前,先分割数据集为测试集和训练集,这是相当重要的。我们要确保训练集和测试集互不影响。如果我们在分割数据集前先缩放,就无法保证它们不相互影响。
5.声明批量大小、占位符、变量和逻辑模型。这步不需要用sigmoid函数封装输出结果,因为sigmoid操作是包含在内建损失函数中的,代码如下:
6.声明损失函数,其包含sigmoid函数。初始化变量,声明优化器,代码如下:
7.除记录损失函数外,也需要记录分类器在训练集和测试集上的准确度。所以创建一个返回准确度的预测函数,代码如下:
8.开始遍历迭代训练,记录损失值和准确度,代码如下:
9.绘制损失和准确度,代码如下:
3.9.3 工作原理
这里是迭代过程中的损失,以及训练集和测试集的准确度。数据集只有189个观测值,但训练集和测试集的准确度图由于数据集的随机分割将会变化,如图3-11和图3-12所示。