机器学习实战—逻辑回归
逻辑回归进行分类
有这么几个关键词:最佳拟合曲线,利用逻辑回归模型进行分类,最佳拟合参数(w和b),sigmoid函数(S函数),最优化方法,梯度上升(下降),随机梯度下降法,参数迭代公式(梯度,步长)
梯度上升找到最佳参数:
伪代码:
每个回归系数初值定为1
for i in steps:
计算整个数据集的梯度
w=w+alpha*梯度
更新回归系数
#梯度上升算法的实现
import numpy as np
def loadDataSet():
dataMat=[]#创建数据空列表
labelMat=[]#创建类别空列表
fr=open('logRegres_testSet.txt')#打开训练集文本
for line in fr.readlines():#读取每行
lineArr=line.strip().split()#分割数据
#合并每行数据(不包括类别列),并在每一行的前面添加一列1
dataMat.append([1.0,float(lineArr[0]),float(lineArr[1])])
labelMat.append(int(lineArr[2]))#合并类别
return dataMat,labelMat
#定义S函数
def sigmoid(inX):
return 1.0/(1+np.exp(-inX))
#定义梯度上升算法
def gradAscent(dataMatIn,classLabels):
dataMatrix=np.mat(dataMatIn)#list型转换为mat型(矩阵型)
labelMat=np.mat(classLabels).transpose()#list型转换为mat型并转置
m,n=np.shape(dataMatrix)#得到矩阵的行列数
alpha=0.001#定义步长
maxCycles=500#定义迭代步数
weights=np.ones((n,1))#定义参数矩阵初值(全1)
for k in range(maxCycles):#步数循环
h=sigmoid(dataMatrix*weights)#求S函数的值
error=(labelMat-h)#求误差
weights=weights+alpha*dataMatrix.transpose()*error#更新参数数值
return weights
#画出决策边界
def plotBestFit(wei):
import matplotlib.pyplot as plt
weights=wei.getA()#getA():返回自己,但是作为ndarray返回,即一个矩阵
dataMat,labelMat=loadDataSet()#得到数据和类别
dataArr=np.array(dataMat)#得到矩阵
n=np.shape(dataArr)[0]#得到行数
xcord1=[];ycord1=[]
xcord2=[];ycord2=[]
for i in range(n):#循环数据行数
if int(labelMat[i])==1:
#如果类别为1,将dataArr[i,1]的数据添加给xcord1,...
xcord1.append(dataArr[i,1])
ycord1.append(dataArr[i,2])
else:#否则,将dataArr[i,1]的数据添加给xcord2,...
xcord2.append(dataArr[i,1])
ycord2.append(dataArr[i,2])
fig=plt.figure()
#画出散点图,即训练集数据点
ax=fig.add_subplot(111)
ax.scatter(xcord1,ycord1,s=30,c='red',marker='s')
ax.scatter(xcord2,ycord2,s=30,c='green')
x=np.arange(-3.0,3.0,0.1)#创建-3到3,间隔为0.1的点
y=(-weights[0]-weights[1]*x)/weights[2]#由x求出Y
ax.plot(x,y)#绘制分界线
plt.xlabel('X1');plt.ylabel('Y1')
plt.show()
对上述梯度上升(下降)算法的解释,在这里有算法在每次更新回归系数时,都要遍历整个数据集。
命令窗口输入:
data,label=loadDataSet()
weights=gradAscent(data,label)
weights
Out[4]:
matrix([[ 4.12414349],
[ 0.48007329],
[-0.6168482 ]])
plotBestFit(weights)
图形显示:
训练算法-随机梯度上升(SGD)
对之前的梯度算法就行改进,一次仅用一个样本点来更新回归系数,就是随机梯度上升算法。
伪代码
所有回归系数初始化为1
对数据集中每个样本
计算该样本的梯度
w=w+alpha*梯度
更新回归系数
#s随机梯度上升算法
def stocGradAscent0(dataMatrix,classLabels):
m,n=np.shape(dataMatrix)
alpha=0.001
weights=np.ones(n)
for i in range(m):
h=sigmoid(sum(dataMatrix[i]*weights))
error=classLabels[i]-h
weights=weights+alpha * error * dataMatrix[i]
#提前修改weights的类型
temp=np.mat(weights)
weights=temp.transpose()
return weights
#随机梯度算法中,出现的数据都是数值格式,而之前的梯度算法中基本都是numpy数组格式
#在这里输出的weights时1*3的列表,需要在使用画图操作前进行np.mat()操作和转置操作
#而GD输出的weights是3*1的数组(array)
命令程序:
data,labels=loadDataSet()
weights=stocGradAscent0(np.array(data),labels)
weights
Out[82]:
matrix([[ 0.963951 ],
[ 0.9826866 ],
[ 0.49153886]])
plotBestFit(weights)
发现结果并没有之前好的,原因在于之前的结果是迭代了500次之后,而现在的SGD仅仅遍历了数据行数。当然我们可以发现SGD中的步长因子是0.01,而不是0.001(GD),SGD在用0.001时效果更差。
下一步对SGD进行改进。
#改进的SGD算法
def stocGradAscent1(dataMatrix,classLabels,numIter=150):
m,n=np.shape(dataMatrix)
weights=np.ones(n)
for j in range(numIter):#迭代次数
#对于range(m)不存在的操作,需要用list(range(m))
dataIndex=list(range(m))
for i in range(m):
alpha=4/(1.0+j+i)+0.01
#随机选取一个样本点(第几行)
randIndex=int(np.random.uniform(0,len(dataIndex)))
h=sigmoid(sum(dataMatrix[randIndex]*weights))
error=classLabels[randIndex]-h
weights=weights+alpha*error*dataMatrix[randIndex]
del(dataIndex[randIndex])#下次迭代前删除该值
temp=np.mat(weights)
weights=temp.transpose()
return weights
#改进后的算法在于这两处:1,每次迭代的时候alpha都会改变,不断减小但不会为0
#通过随机选取样本更新回归系数,每次随机选一个,并在进行下一迭代前删掉该值
得到的结果是:
上面是150迭代结果,下面是500次迭代结果
实例:从疝气病病症预测病马的死亡率
数据的前期处理,如果数据(特征数据)存在缺失,应该采取的处理措施:
使用可用特征的均值填补缺失的特征数据,使用特殊值来填补缺失值,忽略有缺失值的样本,使用相似样本的均值填补缺失值,使用另外的机器学习算法预测缺失值。
如果是类别值缺失,那么该数据行应该丢弃,因为类别值很难确定用哪个类别来填补。
现在数据已经没有问题,可以利用来预测。前面的数据已经可以通过训练集得到回归系数,所以只要我们再利用测试集的数据乘上回归系数,再取S函数,就可以判断死亡与否,继而得到死亡率。
#利用以上算法从疝气病症预测病马的死亡率
#因为上面的算法已经得出了回归系数,所以只要将测试集的数据乘上回归系数代入S函数即可
def classifyVector(inX,weights):
prob=sigmoid(sum(inX*weights))#S函数
if prob>0.5:
return 1.0
else:
return 0.0
def colicTest():
frTrain=open('horseColicTraining.txt')#打开训练集
frTest=open('horseColicTest.txt')#打开测试集
trainingSet=[]
trainingLabels=[]
for line in frTrain.readlines():#遍历所有行数据
currLine=line.strip().split('\t')
lineArr=[]
#注:每一行数据有22个数据,其中最后一个表示类别
for i in range(21):#因为有21个特征(0~20),特征数据赋给lineArr
lineArr.append(float(currLine[i]))
trainingSet.append(lineArr)#今儿赋给trainingSet
trainingLabels.append(float(currLine[21]))#类别数据转赋
#通过训练集求回归系数,也即是求出映射关系
trainWeights=stocGradAscent1(np.array(trainingSet),trainingLabels,500)
errorCount=0#分类错误数初始化
numTestVec=0.0#已测试数据数目初始化
for line in frTest.readlines():#读取测试集每一行数据
numTestVec+=1.0#测试数据数目递增
currLine=line.strip().split('\t')#每行数据的切割
lineArr=[]
for i in range(21):#只取每行数据前21个特征数据
lineArr.append(float(currLine[i]))
if int(classifyVector(np.array(lineArr),trainWeights))!=int(currLine[21]):
errorCount+=1#如果测试出的类别与原测试集类别不同,则分类错误数递增
errorRate=(float(errorCount)/numTestVec)#求错误率
print("the error rate of this test is;%f" %errorRate)
return errorRate
def multiTest():#求结果平均值
numTests=10
errorSum=0.0
for k in range(numTests):
errorSum+=colicTest()
print("after %d iterations the average error rate is: %f" %(numTests,errorSum/float(numTests)))
要知道,利用python打开文件后,一般都要对数据进行格式化处理,基本上都是切割,定义两个空列表,分别将数据中的特征列和类别列添加到其中,即分成两个部分。
结果:
multiTest()
__main__:24: RuntimeWarning: overflow encountered in exp
the error rate of this test is;0.373134
the error rate of this test is;0.283582
the error rate of this test is;0.313433
the error rate of this test is;0.238806
the error rate of this test is;0.417910
the error rate of this test is;0.402985
the error rate of this test is;0.358209
the error rate of this test is;0.432836
the error rate of this test is;0.343284
the error rate of this test is;0.313433
after 10 iterations the average error rate is: 0.347761
总结:逻辑回归是一种分类算法,利用S函数,将参数和特征代入,实现分类。而任务主要在于寻找回归系数,用的方法是最优化。其中最常用的是梯度上升(下降),GD和SGD。SGD是一种在线学习算法,与之对应的一次处理所有数据被称为批处理。