前言
在上一篇文章《机器学习之Logistic回归(非线性回归)》中我们对Logistic回归算法做了简单的描述,同时也说到了梯度下降算法,我们之前说过,Logistic回归是解决分类问题的模型,同线性回归形似但从根本上来说还是有很大区别的。在Logistic回归求最小值的时候我们使用的方法是梯度下降算法,上一篇理论性的文章中我们没有给出具体的代码,下面我们就Logistic回归利用两种梯度下降算法(随机梯度下降BGD,批量梯度下降)。
数据集介绍
首先,我们介绍一下本文中用到的数据集。该数据集是一个二分类数据集,其lable就是1和0,该数据集的特征值有两列,下面是该数据集的部分截图:
该数据集的特征值是线性的,标记lable是分类别的,满足了非线性回归的基本要求,可以直接建立Logistic回归模型。
数据集下载:[Downlink href='/wp-content/uploads/file/20170824/1503555357371702.txt']点击下载[/Downlink]
读取数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#coding:utf8 import random import numpy as np from sklearn.metrics import classification_report #读取数据 def loadData(): train_x = [] train_y = [] fileIn = open('./data/test.txt') for line in fileIn.readlines(): lineArr = line.strip().split() train_x.append([1.0, float(lineArr[0]), float(lineArr[1])]) #1.0代表x的0次项 train_y.append(float(lineArr[2])) return np.array(train_x), np.array(train_y) |
上面的代码中,我们从test.txt中读取了我们的数据集,这里需要注意的是我们的数据第1列是我们自己定义的浮点数1.0,这个代表x的0次项,为什么设置为1呢?我们回想一下我们的Logistic回归方程,如下:
观察方程发现,方程中没有常数项哎,哦,这就明白了吧,我们设置的1正好可以将该方程的第一项设置为常数项,妙啊。
批量梯度下降算法(BGD)
梯度下降算法的理论我们在上一篇文章中已经说过了,这里直接上代码了,看不懂的可以结合前面的理论知识看。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#批量梯度下降算法,参数:训练集,标记,学习率,最大迭代次数 def SGD(x, y, alpha, maxIterations): #将训练集和标记都转换成矩阵 dataMatrix = np.mat(x) #m*n labelMat = np.mat(y).T #m*1 m, n = dataMatrix.shape #定义参数theta theta = np.ones((n, 1)) #n*1 for i in range(maxIterations): h = sigmoid(dataMatrix.dot(theta)) #m*n.dot(n*1) = m*1 #计算误差 error = h - labelMat #m*1 - m*1 = m*1 #更新参数 theta = theta - alpha * (dataMatrix.T * error) return np.asarray(theta) |
批量梯度下降算法的批量的意思我们在上篇文章中提过了,批量的意思在这里是指扫描完全部的数据后再更新参数,这里主要体现在倒数第二行更新参数那,我们可以看到,我们将dataMatrix中的数据全部都点乘了error,最后更新theta参数。而随机梯度下降算法稍有不同,我们每次计算都是使用一个实例,每一个实例更新一次参数。下面给出随机梯度下降算法的程序实现。
随机梯度下降算法(SGD)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#随机梯度下降算法 def BGD(x, y, alpha, maxIterations): m, n = x.shape theta = np.ones(n) for i in range(maxIterations): for k in range(m): #产生随机索引 randIndex = int(random.uniform(0, m)) h = sigmoid(x[randIndex].dot(theta)) #对应位置相乘再相加,也可写成sum(x[randIndex]*theta) #计算错误率 error = h -y[randIndex] #更新参数 theta = theta - alpha * (error * x[randIndex]) return theta |
随机梯度下降算法中,关键在于随机二字,每次循环都产生一个随机的索引,这一点可以减小训练过程中噪点对参数的影响致使参数上下波动。
训练Logistic回归模型
下面我们将使用前面数据来训练Logistic回归方程的参数,下面的代码紧接着上面的代码,代码如下:
1 2 3 4 5 6 7 8 9 10 11 |
#预测函数 def predict(x, theta): return sigmoid(np.dot(x, theta)).T[0] x ,y= loadData() m, n = np.shape(x) theta = BGD(x,y, 0.001, 1000) theta = np.atleast_2d(theta).reshape((3,1)) pre = np.round(predict(x, theta)) ###############模型评测##################### print classification_report(y, pre) |
上面的预测函数我为了同时适应SGD和BGD所以就简单的写了,传入的theta是个二维数组,是m*1的形状。 因为预测出来的数据都是小数,所以我们这里使用了np.round()函数将所有的数据进行了四舍五入的运算。在最打印了一下预测报告,报告如下:
上面的建模中我们使用的学习率是0.001,训练迭代次数是1000次,这两个参数是我随便取的,没有经过调优,不过准确率已经是挺高的了。
全部代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
#coding:utf8 import random import numpy as np from sklearn.metrics import classification_report #定义sigmoid函数 def sigmoid(x): return 1.0/(1 + np.exp(-x)) #批量梯度下降算法,参数:训练集,标记,学习率,最大迭代次数 def SGD(x, y, alpha, maxIterations): #将训练集和标记都转换成矩阵 dataMatrix = np.mat(x) #m*n labelMat = np.mat(y).T #m*1 m, n = dataMatrix.shape #定义参数theta theta = np.ones((n, 1)) #n*1 for i in range(maxIterations): h = sigmoid(dataMatrix.dot(theta)) #m*n.dot(n*1) = m*1 #计算误差 error = h - labelMat #m*1 - m*1 = m*1 #更新参数 theta = theta - alpha * (dataMatrix.T * error) return np.asarray(theta) #随机梯度下降算法 def BGD(x, y, alpha, maxIterations): m, n = x.shape theta = np.ones(n) for i in range(maxIterations): for k in range(m): #产生随机索引 randIndex = int(random.uniform(0, m)) h = sigmoid(x[randIndex].dot(theta)) #对应位置相乘再相加,也可写成sum(x[randIndex]*theta) #计算错误率 error = h -y[randIndex] #更新参数 theta = theta - alpha * (error * x[randIndex]) return theta #预测函数 def predict(x, theta): return sigmoid(np.dot(x, theta)).T[0] #读取数据 def loadData(): train_x = [] train_y = [] fileIn = open('./data/test.txt') for line in fileIn.readlines(): lineArr = line.strip().split() train_x.append([1.0, float(lineArr[0]), float(lineArr[1])]) #1.0代表x的0次项 train_y.append(float(lineArr[2])) return np.array(train_x), np.array(train_y) x ,y= loadData() m, n = np.shape(x) theta = BGD(x,y, 0.001, 1000) theta = np.atleast_2d(theta).reshape((3,1)) pre = np.round(predict(x, theta)) ###############模型评测##################### print classification_report(y, pre) |
补充(梯度上升算法)
梯度下降算法在线性回归和非线性回归中的计算公式是不一样到的,这里我们所有程序都是适用于Logistic非线性回归的,线性回归的梯度下降算法我们并没有给出。这一点需要注意,切不可将二者混用。
梯度下降从功能上是有两种的,一种是梯度下降算法,另一种是梯度上升算法,前者用来计算最小值,后者用来计算最大值,不过有时候求最大值的问题可以转化为求最小值的问题,一个负号的问题而已,但是在编程的时候也是需要注意的。其实二者的公式根本上就是一个,只要将梯度下降算法中最后更新参数的公式中的减变换成加号就可以了,当然,不要忘记括号里的参数要交换位置。下面给出随机梯度上升的更新参数公式,批量梯度上升公式可以根据这个自己推算。
结束语
回归问题中最重要的就是确定合适的参数,如何确定合适的参数有多种方式,而寻找一种合适的算法则是最重要的。上文中的代码均参考《Python机器学习实战》这本书,书上讲的还是比较清楚的,强烈建议看一下这本书。另外,本文中的代码均未优化和严格测试,若发现问题还请指正。
2017年11月9日 11:14 沙发
随机SGD, 批量BGD~
2017年11月9日 14:40 1层
@vbaymax 谢谢提醒,已纠正。