逻辑回归
逻辑回归是一种经典的二分类算法,它的决策边界可以是非线性的(包含高阶项)
Sigmoid函数
公式:,其中自变量取值为任意实数
解释:将任意的输入映射到了[0,1]区间
比如在线性回归中可以得到一个预测值(),再将该值映射到Sigmoid函数,就完成了由值到概率的转换,也就是分类任务
假设预测函数
分类任务:
相当于0-1分布,整合上述式子得
似然函数:
取对数似然函数:
现在要求上述对数似然函数的最大值,如果要用梯度下降法,需要将目标函数转化为求最小值【梯度的方向是值越来越大的方向,求最小值,应该朝着梯度反方向走】,因此引入
对上式求偏导
确定了梯度方向之后,利用上式更新参数,得到
按照上式继续迭代
下面是一个关于实现逻辑回归的小例子
目标:建立一个逻辑回归模型来预测一个大学生是否被大学录取。
特征:两次考试成绩
导入数据
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
pdData=pd.read_csv('C:/Users/lenovo/Desktop/LogiReg_data.txt',header=None,names=['Exam 1','Exam 2','Admitted'])#header=None不让数据自己指定列名
pdData.insert(0,'ones',1) #为了考虑常数项
pdData.head()
数据如以下所示
通常在做数据处理之前,需要做一些数据预处理,比如数据标准化等,这边先不做,看看不做标准化会对结果什么影响。
逻辑回归的关键是计算损失值和梯度方向
为了计算这两个函数,需要引入sigmoid函数【将值映射到概率】
#定义sigmoid函数
def sigmoid_2(z):
return 1/(1+np.exp(-z))
#预测函数模型
def model(X,theta):
return sigmoid_2(np.dot(X,theta.T))
切片,分出X和y
orig_data=pdData.as_matrix()# 转换成array类型
cols=orig_data.shape[1]
X=orig_data[:,:cols-1]#前三列,切片012
y=orig_data[:,cols-1:]#和y=orig_data[:,cols-1]结果额维度不一样
theta=np.zeros([1,3]) #theta初始化是0
注:这里要注意一个关于数组切片的问题
上面画圈的两个语句一个返回一维数组,一个返回三维数组
损失函数
将对数似然函数取相反数
求平均损失
目的是求平均损失的最小值
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)
计算梯度
def gradient(X,y,theta):
grad=np.zeros(theta.shape)
error=(model(X,theta)-y).ravel()
# print(error.shape)
for j in range(len(theta.ravel())):#求每个参数的偏导
term=np.multiply(error,X[:,j])
grad[0,j]=np.sum(term)/len(X)
return grad
参数更新
首先设定三种停止策略【迭代次数、损失值差距、梯度】
def stopCriterion(type,value,threshold):
if type==STOP_ITER:
return value>threshold #返回逻辑值#迭代次数
elif type==STOP_COST:
return abs(value[-1]-value[-2])
为了避免原数据存在某种规律【比如先收集女生成绩再收集男生成绩】,将原数据顺序打乱
import numpy.random
#洗牌,将数据顺序打乱
def shuffleData(data):
np.random.shuffle(data) #shuffle() 方法将序列的所有元素随机排序
cols=data.shape[1]
X=data[:,:cols-1]
y=data[:,cols-1:]
return X,y
然后进行参数更新【引入time库是为了比较不同策略的运行时间】
import time
def descent(data,theta,batchSize,stopType,thresh,alpha):
#batchSize等于总样本数,即批量梯度下降;batchSize等于1,即随机梯度下降;batchSize取1~n之间的数据,即小批量梯度下降
#梯度下降求解
#初始化
init_time=time.time() #比较不同梯度下降方法的运行速度
i=0 #迭代次数,第0次开始
k=0 #batch
X,y=shuffleData(data)
grad=np.zeros(theta.shape) #计算的梯度
costs=[cost(X,y,theta)] #计算损失值
while True:
grad=gradient(X[k:k+batchSize],y[k:k+batchSize],theta)
k += batchSize #取样本数据
if k >= n:
k = 0
X,y=shuffleData(data) #重新洗牌
theta = theta - alpha*grad # 参数更新
costs.append(cost(X,y,theta))
i += 1
#停止策略
if stopType==STOP_ITER:
value = i
elif stopType==STOP_COST:
value=costs
elif stopType==STOP_GRAD:
value=grad
if stopCriterion(stopType,value,thresh):#如果if语句为真,就跳出整个循环
break
#print(grad,np.linalg.norm(grad))
return theta,i-1,costs,grad,time.time()-init_time
注:np.linalg.norm([4,3])表示
def runExpe(data,theta,batchSize,stopType,thresh,alpha):
theta,iter,costs,grad,dur=descent(data,theta,batchSize,stopType,thresh,alpha)
# name='Original' if (data[:,2]>2).sum()>1 else 'Scaled'
# name += 'data-learning rate: {} -'.format(alpha)
# if batchSize==n: strDescType='Gradient'
# elif batchSize==1: strDescType='Stochastic'
# else:strDescType='mini-batch {} '.format(batchSize)
# 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 += strStop
fig,ax=plt.subplots(figsize=(12,4))
ax.plot(np.arange(len(costs)),costs,c='r')
ax.set_xlabel('Iteration')
ax.set_ylabel('Cost')
#ax.set_title(name.upper())
print('iter: {}, last cost: {:03.2f}, duration: {:03.2f}s'.format(iter,costs[-1],dur))
return theta
不同的停止策略
以批量梯度下降为例
- 停止条件为迭代次数
n=100
runExpe(orig_data,theta,n,STOP_ITER,thresh=5000,alpha=0.000001)
返回
看似损失值已经稳定在最低点0.63
- 停止条件为损失值
设定阈值为0.000001,需要迭代110000次左右
runExpe(orig_data,theta,n,STOP_COST,thresh=0.000001,alpha=0.001)
返回
损失值最低为0.38,似乎还可以进一步收敛
- 停止条件为梯度大小
设定阈值0.05,需要迭代40000次左右
runExpe(orig_data,theta,n,STOP_GRAD,thresh=0.05,alpha=0.001)
返回
损失值最小为0.49,似乎还可以进一步收敛
综上,基于批量梯度下降方法,上述三种停止条件得到的损失函数值为0.63、0.38和0.49,迭代次数分别为5000次、110000次和40000次,迭代次数越多,损失值越小
对比不同的梯度下降方法
停止策略为迭代次数
- 随机梯度下降
runExpe(orig_data,theta,1,STOP_ITER,thresh=5000,alpha=0.001)
返回
波动非常大,迭代过程不稳定,这也是随机梯度下降的主要缺点
尝试降低学习率为0.000001,增加迭代次数为15000
runExpe(orig_data,theta,1,STOP_ITER,thresh=15000,alpha=0.000001)
返回
效果要好一些,损失值似乎稳定在0.63,根据上面的结果可知,0.63不算是一个特别合理的值
- 小批量梯度下降
#取样本为16
runExpe(orig_data,theta,16,STOP_ITER,thresh=15000,alpha=0.001)
返回
上下波动,迭代过程不稳定
尝试调低学习率为0.000001
runExpe(orig_data,theta,16,STOP_ITER,thresh=15000,alpha=0.001)
返回
降低学习率之后没有效果,迭代过程依旧不稳定
因此,可能不是模型本身的问题,而是数据本身的问题,尝试着对数据做一些变换,此处对数据进行标准化,用标准化后的数据求解
#标准化
from sklearn import preprocessing as pp
scaled_data=orig_data.copy()
scaled_data[:,1:3]=pp.scale(scaled_data[:,1:3])
#用标准化后的数据求解
runExpe(scaled_data,theta,16,STOP_ITER,thresh=15000,alpha=0.001)
返回
损失值收敛到0.28,比0.63好很多
再尝试一下梯度为停止条件的情况
runExpe(scaled_data,theta,16,STOP_GRAD,thresh=0.004,alpha=0.001)
返回
迭代次数由15000增加到60000多,损失值由0.28降低到0.22,又改善了一步
计算模型分类结果的精确率
#设定阈值为0.5,大于0.5就可以入学
def predict(X,theta):
return [1 if x >= 0.5 else 0 for x in model(X,theta)]
scaled_X=scaled_data[:,:3]
y=scaled_data[:,3]
theta = runExpe(scaled_data,theta,16,STOP_GRAD,thresh=0.004,alpha=0.001)
predictions = predict(scaled_X,theta)
correct = [1 if a==b else 0 for (a,b) in zip(predictions,y)] #真实值与预测值相等,同为1或者同为0
accuracy = sum(correct)/len(correct)
返回accuracy等于0.89
通过这个例子,算是对逻辑回归的基本原理有一个比较清晰的认识了。
- sigmoid函数:将值映射到概率的函数
- model:返回预测结果值
- cost:根据参数计算损失
- gradient:计算每个参数的梯度方向
- descent:进行每个参数的更新
- accuracy:计算精度