本文共 10327 字,大约阅读时间需要 34 分钟。
问题说明:
这里将建立一个逻辑回归模型来预测一个学生是否被大学录取。 假设你是一个大学的管理员,你想根据两次考试的结果来决定每个申请人的录取机会,你有以前的申请人的历史数据。可以用历史数据作为逻辑回归的训练集。对于每一个样本,有两次考试的申请人的成绩和录取决定。建立一个分类模型,根据考试成绩估计入学概率。数据链接:
链接:https://pan.baidu.com/s/1-pjwe1ogk30WpzN4Qg1NZA 密码:wqmt完整代码实现如下:
import numpy as npimport pandas as pdimport matplotlib.pyplot as pltpdData = pd.read_csv('/Users/hxx/Downloads/LogiReg_data.txt', header=None, names=['Exam1', 'Exam2', 'Admitted'], engine='python')#header: 指定第几行作为列名(忽略注解行),如果没有指定列名,默认header=0; 如果指定了列名header=None#names 指定列名,如果文件中不包含header的行,应该显性表示header=None。这边列名为'Exam1', 'Exam2', 'Admitted'三列print(pdData.head())#head()函数默认读取前五行print(pdData.shape)#数据文件100行3列positive = pdData[pdData['Admitted']==1]negative = pdData[pdData['Admitted']==0]plt.figure(figsize=(10,5))#设置画布plt.scatter(positive['Exam1'], positive['Exam2'], c='b', marker='o', label='Admitted')#绘制散点图positive的点,Exam1和Exam2组成一个点plt.scatter(negative['Exam1'], negative['Exam2'], c='r', marker='x', label='Not Admitted')#绘制散点图negative的点plt.legend()# 添加图例(也就是图中右上角positive和negative的解释)plt.xlabel('Exam1 Score')#添加x轴标签plt.ylabel('Exam2 Score')#添加y轴标签plt.show()#定义sigmoid函数def sigmoid(z): return 1/(1+np.exp(-z))nums = np.arange(-10,10,step=1)#随机取-10到10之间数值步长为1plt.figure(figsize=(12,4))#设置画布plt.plot(nums,sigmoid(nums),c='r')#sigmoid函数画图展示,nums表示x,sigmoid(nums)表示的y,x和y确定一点组成sigmoid函数图像plt.show()#定义模型h(x)def model(X,theta): return sigmoid(np.dot(X,theta.T))#将theta转置与x相乘,在代入sigmoid函数中得到模型。X是100*3列,theta是一行三列#取数据pdData.insert(0,'ones',1) #给数据添加一列,列名为ones,在第0列添加,数值为1,也就相当于x_0的系数theta_0orig_data = pdData .as_matrix() # 将数据转变成矩阵形式cols = orig_data .shape[1]#shape[0]就是读取矩阵第一维度的长度,相当于行数;shape[1]就是读取矩阵第二维度的长度,相当于列数,shape[1]=4X = orig_data[:,0:cols-1]#逗号前冒号表示所有值,0:cols-1表示取第0列到(clos-1)列不包括cols-1这一列y = orig_data[:,cols-1:cols]theta = np.zeros([1,3])#这边设置theta值均为0,一行三列和x的一行三列相对应print(X)print(y)print(theta)#定义损失函数#这边就是逻辑回归中的损失函数,逻辑回归详情可以参考:https://blog.csdn.net/hxx123520/article/details/104313032第四部分详细说明def cost(X,y,theta):#对应于J(theta) left = np.multiply(-y,np.log(model(X,theta)))#np.multiply对应位置相乘 right = np.multiply(1-y,np.log(1-model(X,theta))) return np.sum(left-right)/(len(X))print(cost(X,y,theta))#根据梯度计算公式计算梯度#也就是损失函数对theta求偏导,得到的就是梯度def gradient(X,y,theta): grad = np.zeros(theta.shape) # 定义初始化梯度,一行三列 #print(grad.shape) error = (model(X, theta) - y).ravel()# 一行一百列。error=(预测y-真实值y) #print(error) # print(len(theta .ravel()))#3 for j in range(len(theta.ravel())): term = np.multiply(error, X[:, j])#x[:,j] 取第j列的所有x样本 grad[0, j] = np.sum(term) / len(X)#这边的grad就是一行三列。grad[0,0]表示第0行第0列,for循环不断更新j,最终就是[0,2]表示0行2列 #print(grad) return grad#numpy中的ravel()、flatten()、squeeze()都有将多维数组转换为一维数组的功能,区别:#ravel():如果没有必要,不会产生源数据的副本。这边的error就是通过ravel函数将一百行一列拉成一行100列#flatten():返回源数据的副本#squeeze():只能对维数为1的维度降维#三种梯度下降方法与三种停止策略#三种停止策略STOP_ITER=0 #根据指定的迭代次数来停止,更新一次参数相当于一次迭代STOP_COST=1 #根据损失,每次迭代看一下迭代之前和之后的目标函数(损失值)的变化,没啥变化的话,可以停止STOP_GRAD=2 #根据梯度,梯度变化贼小贼小#设定三种不同的停止策略def stopCriterion(type,value,threshold): if type == STOP_ITER: return value > threshold #threshold指的是指定的迭代次数 elif type ==STOP_COST: return abs(value[-1]-value[-2]) < threshold#threshold指的损失函数的阈值,如果小于这个值就停止 elif type == STOP_GRAD: return np.linalg.norm(value) < threshold#threshold指的梯度值,梯度小于这个值停止。np.linalg.norm()表示范数,首先需要注意的是范数是对向量(或者矩阵)的度量,是一个标量(scalar)#对数据进行洗牌,使模型的泛化能力更强def shuffleData(data): np.random.shuffle(data)#shuffle() 方法将序列的所有元素随机排序。 cols=data. shape[1] X = data[:,0:cols-1] y = data[:,cols-1:] return X,yimport timedef descent(data,theta,batchsize,stoptype,thresh,alpha): init_time = time.time() #比较不同梯度下降方法的运行速度 i=0 #迭代次数 k=0 #batchsize初始值 X,y=shuffleData(data)#shuffleData函数对数据重新洗牌作用 grad = np.zeros(theta.shape)#计算梯度 costs= [cost(X,y,theta)]#计算损失 while True: grad=gradient(X[k:k+batchsize],y[k:k+batchsize],theta)#grad的输出梯度结果为一行三列, k+=batchsize #取样本数据 if k>= n : k=0 X,y=shuffleData(data) theta=theta-alpha * grad#theta是一行三列,减去alpha*grad的一行三列,也就是更新后的theta costs.append(cost(X,y,theta))#将损失值添加到costs中。append() 方法向列表的尾部添加一个新的元素。 i+=1; if stoptype==STOP_ITER: value=i if stoptype==STOP_COST:value=costs if stoptype==STOP_GRAD:value=grad if stopCriterion(stoptype,value,thresh):break return theta,i-1,costs,grad,time.time()-init_timedef runExpe(data,theta,batchsize,stoptype,thresh,alpha): theta, iter, costs, grad, dur = descent(data,theta,batchsize,stoptype,thresh,alpha) if (data[:,1]>2).sum()>1: name="Original" else: name="Scaled" name=name + " data - learning rate: {} -".format(alpha) if batchsize==n: strDescType = "Gradient" #batchsize等于n是全局随机梯度下降 elif batchsize==1: strDescType = "Stochastic" #batchsize等于1是随机梯度下降 else: strDescType = "Mini-batch({})".format(batchsize)#小批量梯度下降 name = name + strDescType + " descent - stop :" if stoptype==STOP_ITER: strstop = "{} iterations".format(thresh) elif stoptype==STOP_COST: strstop = "cost change < {}".format(thresh) else: strstop = "gradient norm < {}".format(thresh) name = name + strstop print("*{}\nTheta: {} - Iter: {} - Last cost: {:03.2f} - Duration: {:03.2f}s ".format(name,theta,iter,costs[-1],dur)) p=plt.subplots(figsize=(12,4)) plt.plot(np.arange(len(costs)),costs,'r') plt.xlabel('Iterations') plt.ylabel('costs') plt.title(name.upper() + ' - Error vs. Iteration')# upper() 方法将字符串中的小写字母转为大写字母 plt.show() return thetan=100;#梯度计算时选取多少个样本 基于所有的样本#对比不同的停止策略runExpe(orig_data, theta, n, STOP_ITER, thresh=5000, alpha=0.000001) #迭代5000次,即停止策略基于迭代次数# 不指定迭代次数,停止策略基于损失函数runExpe(orig_data, theta, n, STOP_COST, thresh=0.000001, alpha=0.001)# 停止策略基于梯度值runExpe(orig_data, theta, n, STOP_GRAD, thresh=0.05, alpha=0.001)# 对比不同的梯度下降方法runExpe(orig_data, theta, 1, STOP_ITER, thresh=5000, alpha=0.001) # 随机梯度下降,浮动大,稳定性差runExpe(orig_data, theta, 1, STOP_ITER, thresh=15000, alpha=0.000002)runExpe(orig_data, theta, 16, STOP_ITER, thresh=15000, alpha=0.001) # 小批量梯度下降#数据标准化问题,当数据发生浮动,先在数据层面上处理,先处理数据,再处理模型from sklearn import preprocessing as ppscaled_data = orig_data.copy()scaled_data[:, 1:3] = pp.scale(orig_data[:, 1:3])#scale 零均值单位方差,将数据转化为标准正态分布runExpe(scaled_data, theta, n, STOP_ITER, thresh=5000, alpha=0.001) # 0.38runExpe(scaled_data, theta, n, STOP_GRAD, thresh=0.02, alpha=0.001) # 0.22runExpe(scaled_data, theta, 1, STOP_GRAD, thresh=0.002 / 5, alpha=0.001) # 0.22theta = runExpe(scaled_data, theta, 16, STOP_GRAD, thresh=0.002 * 2, alpha=0.001)#以下——得到精度def predict(X,theta): return [1 if x >=0.5 else 0 for x in model(X,theta)]#设定阈值为0.5,大于0.5就可以入学scaled_X = scaled_data[:,:3]y = scaled_data[:,3]prediction = predict(scaled_X,theta)correct = [1 if ((a==1 and b==1) or (a==0 and b==0)) else 0 for (a,b) in zip(prediction,y)]#a对应的prediction值,b对应的真实值yaccuracy = (sum(map(int,correct)) % len(correct))#map() 会根据提供的函数对指定序列做映射。这边将correct映射为整数型在进行求和print("accuracy {0}%".format(accuracy))
runExpe(orig_data, theta, n, STOP_ITER, thresh=5000, alpha=0.000001)
返回
*Original data - learning rate: 1e-06 -Gradient descent - stop :5000 iterationsTheta: [[-0.00027127 0.00705232 0.00376711]] - Iter: 5000 - Last cost: 0.63 - Duration: 0.95s
看似损失值已经稳定在最低点0.63
设定阈值为0.000001,需要迭代110000次左右
runExpe(orig_data, theta, n, STOP_COST, thresh=0.000001, alpha=0.001)
返回
*Original data - learning rate: 0.001 -Gradient descent - stop :cost change < 1e-06Theta: [[-5.13364014 0.04771429 0.04072397]] - Iter: 109901 - Last cost: 0.38 - Duration: 20.22s
损失值最低为0.38,似乎还可以进一步收敛
设定阈值0.05,需要迭代40000次左右
runExpe(orig_data, theta, n, STOP_GRAD, thresh=0.05, alpha=0.001)
返回
*Original data - learning rate: 0.001 -Gradient descent - stop :gradient norm < 0.05Theta: [[-2.37033409 0.02721692 0.01899456]] - Iter: 40045 - Last cost: 0.49 - Duration: 7.67s
损失值最小为0.49,似乎还可以进一步收敛
综上,基于批量梯度下降方法,上述三种停止条件得到的损失函数值为0.63、0.38和0.49,迭代次数分别为5000次、110000次和40000次,迭代次数越多,损失值越小停止策略为迭代次数
runExpe(orig_data, theta, 1, STOP_ITER, thresh=5000, alpha=0.001)
返回
*Original data - learning rate: 0.001 -Stochastic descent - stop :5000 iterationsTheta: [[-0.38740891 0.09055956 -0.06431339]] - Iter: 5000 - Last cost: 0.91 - Duration: 0.31s
波动非常大,迭代过程不稳定,这也是随机梯度下降的主要缺点
尝试降低学习率为0.000002,增加迭代次数为15000runExpe(orig_data, theta, 1, STOP_ITER, thresh=15000, alpha=0.000002)
返回
*Original data - learning rate: 2e-06 -Stochastic descent - stop :15000 iterationsTheta: [[-0.00202428 0.00981444 0.00076997]] - Iter: 15000 - Last cost: 0.63 - Duration: 0.93s
效果要好一些,损失值似乎稳定在0.63,根据上面的结果可知,0.63不算是一个特别合理的值
#取样本为16runExpe(orig_data, theta, 16, STOP_ITER, thresh=15000, alpha=0.001)
返回
*Original data - learning rate: 0.001 -Mini-batch(16) descent - stop :15000 iterationsTheta: [[-1.03955915 0.01512354 0.00209486]] - Iter: 15000 - Last cost: 0.60 - Duration: 1.18s
上下波动,迭代过程不稳定
对于一些数据,降低学习率之后没有效果,迭代过程依旧不稳定
因此,可能不是模型本身的问题,而是数据本身的问题,尝试着对数据做一些变换,此处对数据进行标准化,用标准化后的数据求解数据标准化问题,当数据发生浮动,先在数据层面上处理,先处理数据,再处理模型
from sklearn import preprocessing as ppscaled_data = orig_data.copy()scaled_data[:, 1:3] = pp.scale(orig_data[:, 1:3])#scale 零均值单位方差,将数据转化为标准正态分布
接下来再来看看经过标准化处理后的数据得到的损失值图
runExpe(scaled_data, theta, n, STOP_ITER, thresh=5000, alpha=0.001) # 0.38
返回
*Scaled data - learning rate: 0.001 -Gradient descent - stop :5000 iterationsTheta: [[0.3080807 0.86494967 0.77367651]] - Iter: 5000 - Last cost: 0.38 - Duration: 0.98s
runExpe(scaled_data, theta, n, STOP_GRAD, thresh=0.02, alpha=0.001) # 0.22
返回
*Scaled data - learning rate: 0.001 -Gradient descent - stop :gradient norm < 0.02Theta: [[1.0707921 2.63030842 2.41079787]] - Iter: 59422 - Last cost: 0.22 - Duration: 12.18s
runExpe(scaled_data, theta, 1, STOP_GRAD, thresh=0.002 / 5, alpha=0.001) # 0.22
返回
*Scaled data - learning rate: 0.001 -Stochastic descent - stop :gradient norm < 0.0004Theta: [[1.1486333 2.79230152 2.56637779]] - Iter: 72596 - Last cost: 0.22 - Duration: 5.63s
runExpe(scaled_data, theta, 16, STOP_GRAD, thresh=0.002 * 2, alpha=0.001)
返回
*Scaled data - learning rate: 0.001 -Mini-batch(16) descent - stop :gradient norm < 0.004Theta: [[1.09946538 2.6858268 2.46623512]] - Iter: 63755 - Last cost: 0.22 - Duration: 6.40s
可以发现,经过标准化处理的数据得到的损失值得到明显的改善。