一、Logistic回归原理
(1)从线性回归到Logistic回归
假设我们给定d个属性描述的样本
,则对应的的线性回归模型为:
令
,则上式可以写成 :
现在考虑二分类问题,样本的标记 ,而线性回归函数 输出的是实值,于是我们希望将线性回归的输出映射到0/1值,最理想的映射就是“单位阶跃函数”(unit-step funciton):
但是单位阶跃函数不连续,不利于我们使用机器学习中的优化迭代算法求最优解,因此我们使用单调可微的对数几率函数(logistic function)作为单位阶跃函数的代替函数:
它们对应的图为:
于是我们使用对数几率函数对线性回归方程进行映射,令
,得到Logistic回归的表达式:
如果 ,则分类结果 ;如果 ,则分类结果 .
(2)用最大似然估计法MLE(Maximum Likelihood Estimate)求最优解
在给定某组参数
时,样本
的分类结果
的概率为:
相应的分类结果
的概率为:
由于现在是二分类问题,即是0/1分布,所以样本
属于类别
的概率为:
假设我们有m个样本
,则对应的似然函数为:
似然函数取对数,得到对数似然函数:
使用梯度上升法求解最大似然估计值:
为了方便求解,令
。这里只是简单的符号变换,先忽略上标
,并将
,则
,所以函数
沿第 j 个参数
方向的梯度为:
结合
,并添加上标,则某个样本
的对数似然函数
沿第 j 个参数
方向的梯度为:
其中 是 个属性的下标, ,由于是线性模型,因此样本 是多少维,就有多少个参数 ; 是 个样本的数量,
1.使用MLE的角度求解最优参数组合
因为目的是求对数似然函数
的最大值,沿梯度上升的方向逐步逼近极大值点即可。
下面分别列出3种梯度下降法对应的参数
更新公式(虽然使用的是梯度下降法,但是求MLE是沿梯度方向上升求最大值,所以公式中是使用加法 ‘+’):
假设我们有m个样本
,每个样本都有
个 特征。
批量梯度下降法BGD(Batch Gradient Descent)
批量梯度下降法,在更新参数时使用所有的样本来进行更新。由于我们有m个样本,这里求梯度的时候就用了所有m个样本的梯度数据。
随机梯度下降法SGD(Stochastic Gradient Descent)
与BGD的区别在于求梯度时,没有用所有的m个样本的数据,而是仅仅随机选取一个样本 来求梯度。
小批量梯度下降法MBGD(Mini-batch Gradient Descent)
小批量梯度下降法是批量梯度下降法和随机梯度下降法的折衷,也就是从m个样本中,随机选取一部分来迭代。假设从m个样本中,随机选取了 T 个样本,则对应的公式为:
2.使用损失函数 的角度求解最优参数组合
损失函数
等于负对数似然函数NLL(Negative Log-Likelihood):
所以求解最优参数组合 就是沿 梯度下降的方向求极小值(与MLE角度求解相反,因为二者是负号’-‘的关系)。因此对应的三种梯度下降公式对应地使用负号’-‘:
批量梯度下降法BGD(Batch Gradient Descent)
随机梯度下降法SGD(Stochastic Gradient Descent)
小批量梯度下降法MBGD(Mini-batch Gradient Descent)
二、代码实战
这一部分是参考《机器学习实战》的代码,这里就公式符号及最优解求解过程给出必要的解释:
1. 《机器学习实战》使用的 代表本文的 ,相应的线性回归方程为:
2. 对应的梯度上升公式为:
a. 批量梯度下降法BGD
b. 随机梯度下降法SGD
c. 小批量梯度下降法MBGD
(1)读入含有100个样本的数据集,并建立Logistic方程,使用批量梯度上升降法BGD求最优解
1.数据集的样子
2.读入数据集
'''
由于目标函数是z=b+w1x1+w2x2,令x0=1,b=w0x0。将每个样本的第一个特征作为x1,第二个特征作为x2。
所以矩阵dataMat的第一列都为1(代表x0=1)
'''
def loadDataSet():
dataMat = []; labelMat = []
fr = open('testSet.txt')
for line in fr.readlines():
lineArr = line.strip().split()
dataMat.append([1.0, float(lineArr[0]), float(lineArr[1])])
labelMat.append(int(lineArr[2]))
return dataMat,labelMat
if __name__=='__main__':
dataMat, labelMat=loadDataSet()
print(dataMat)
print(labelMat)
3.BGD求最优解
'''
def sigmoid(inX):如果inX是一个数字,则返回sigmoid(x)。如果inX是一个向量如[1,2,3],则返回的
是[sigmoid(1),sigmoid(2),sigmoid(3)]
'''
def sigmoid(inX):
return 1.0/(1+exp(-inX))
'''
def gradAscent(dataMatIn, classLabels): 使用Logistic回归的BGD公式,设置步长为0.001,迭代次数为500
'''
def gradAscent(dataMatIn, classLabels):
dataMatrix = mat(dataMatIn) #convert to NumPy matrix
labelMat = mat(classLabels).transpose() #convert to NumPy matrix
m,n = shape(dataMatrix)
alpha = 0.001 #梯度上升的步长设置为0.001
maxCycles = 500 #重复迭代500次
weights = ones((n,1))
for k in range(maxCycles): #heavy on matrix operations
h = sigmoid(dataMatrix*weights) #matrix mult
error = (labelMat - h) #vector subtraction
weights = weights + alpha * dataMatrix.transpose()* error #matrix mult
return weights
if __name__=='__main__':
dataMat, labelMat=loadDataSet()
best_weights=gradAscent(dataMat,labelMat)
print('最优W*=[w0,w1,w2]^T:\n',best_weights)
4.画出BGD求出的决策边界
因为当sigmoid函数 ,则分类为 ,当 ,则分类为 ,所以决策边界为 ,即 。当 时,对应的决策边界直线为:
'''
def plotBestFit(weights):先将文件'testSet.txt'中的数据画在图中,并根据输入的weights画出决策边界
'''
def plotBestFit(weights):
import matplotlib.pyplot as plt
weights=weights.getA()
dataMat,labelMat=loadDataSet()
dataArr = array(dataMat)
n = shape(dataArr)[0]
xcord1 = []; ycord1 = []
xcord2 = []; ycord2 = []
for i in range(n):
if int(labelMat[i])== 1:
xcord1.append(dataArr[i,1]); ycord1.append(dataArr[i,2])
else:
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 = arange(-3.0, 3.0, 0.1)
y = (-weights[0]-weights[1]*x)/weights[2]
ax.plot(x, y)
plt.xlabel('X1'); plt.ylabel('X2');
plt.show()
if __name__=='__main__':
dataMat, labelMat=loadDataSet()
weights=gradAscent(dataMat,labelMat)
plotBestFit(weights)
(2)使用SGD求最优解
1. 画出迭代步长固定为0.01的SGD对应的决策边界
def stocGradAscent0(dataMatIn, classLabels):
dataMatrix = mat(dataMatIn)
m,n = shape(dataMatrix)
alpha = 0.01
weights = ones((n,1)) #initialize to all ones
for i in range(m):
h = sigmoid(dataMatrix[i]*weights)
error = classLabels[i] - h
weights = weights + alpha *(dataMatrix[i].transpose())*error
return weights
if __name__=='__main__':
dataMat, labelMat=loadDataSet()
best_weights=stocGradAscent0(dataMat,labelMat)
print('最优W*=[w0,w1,w2]^T:\n', best_weights)
plotBestFit(best_weights)
2.画出迭代步长逐渐减小的SGD对应的决策边界
def stocGradAscent1(dataMatIn, classLabels, numIter=150):
dataMatrix = mat(dataMatIn)
m,n = shape(dataMatrix)
weights = ones((n, 1)) # initialize to all ones
for j in range(numIter):
dataIndex = list(range(m))
for i in range(m):
alpha = 4/(1.0+j+i)+0.0001 #apha decreases with iteration, does not go to 0 because of the constant
randIndex = int(random.uniform(0,len(dataIndex)))
h = sigmoid(sum(dataMatrix[randIndex]*weights))
error = classLabels[randIndex] - h
weights = weights + alpha*(dataMatrix[randIndex].transpose())* error
del(dataIndex[randIndex])
return weights
if __name__=='__main__':
dataMat, labelMat=loadDataSet()
best_weights=stocGradAscent1(dataMat,labelMat)
print('最优W*=[w0,w1,w2]^T:\n', best_weights)
plotBestFit(best_weights)
(3)从疝气病症预测病马的死亡率
1.疝气病数据集介绍
2.处理数据中的缺失值
3.使用Logistic回归分类函数进行分类
'''
def classifyVector(inX, weights):inX是待分类样本的特征组成的向量,weights是Logistic回归的参数。
如果Logistic回归结果>0.5则分类为1;如果回归结果<=0.5,则分类为0
'''
def classifyVector(inX, weights):
prob = sigmoid(sum(inX*weights))
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 =[]
for i in range(21):
lineArr.append(float(currLine[i]))
trainingSet.append(lineArr)
trainingLabels.append(float(currLine[21]))
trainWeights = stocGradAscent1(trainingSet, trainingLabels, 1000)
errorCount = 0; numTestVec = 0.0
for line in frTest.readlines():
numTestVec += 1.0
currLine = line.strip().split('\t')
lineArr =[]
for i in range(21):
lineArr.append(float(currLine[i]))
if int(classifyVector(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():调用函数colicTest()10次求结果的平均值
'''
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)) )
if __name__=='__main__':
multiTest()