逻辑回归简单说就是将数据拟合到一个
函数中,从而能够完成对事件发生的概率进行预测。虽然名字叫做回归,但是其实质上却是一个分类问题,主要适用于二分类。
逻辑回归算法速度快,适合二分类问题,容易理解,可直接看到各特征的权重,而且很容易更新模型吸收新的数据,但是对数据和场景的适应能力有局限性,不如决策树算法适应性那么强。
注:这里我就不讲逻辑回归具体的推导过程,感兴趣的可以自己下去查一下,可以参考逻辑回归算法原理。
1.
函数
逻辑回归的核心思想是,如果线性回归的结果输出是一个连续值,而值的范围是无法限定的,那我们就想办法把这个结果值映射为可以帮助我们判断的结果。
函数主要就时帮助我们实现这个功能,具体实现参考Sigmoid函数:
函数图像表示为:
从函数图上可以看出,函数 在 的时候取值为1/2,而随着 逐渐变小,函数值趋于0, 逐渐变大的同时函数值逐渐趋于1,而这正是一个概率的范围。
2. 判定边界
这里我们引入一个概念,叫做判定边界,可以理解为是用以对不同类别的数据分割的边界,边界的两旁应该是不同类别的数据。正是因为这个原因,逻辑回归才能解决分类的问题。如下图就是蓝色直线就是我们的判定边界:
<align=center>
逻辑回归是如何根据样本点获得这些判定边界的,我们举个例子:对于
,则
,此时的预估是
,反之,当预测
时,
。
所以我们认为
是一个决策边界,当它大于0或小于0时,逻辑回归模型分别预测不同的分类结果。
3. 梯度上升
要找到某函数的最大值,最好的方法是沿着该函数的梯度方向寻找。如果梯度记为
,则函数
的梯度由下式表示
这个梯度意味着要沿
的方向移动
,沿
的方向移动
。其中,函数
必须要在待计算的点上有定义并且可微(参考机器学习实战)。
上图展示的,梯度上升算法到达每个点后都会重新估计移动的方向。从
开始,计算完该点的梯度,函数就根据梯度移动到下一点
。在
点,梯度再次被重新计算,并沿着新的梯度方向移动到
。如此循环迭代,直到满足停止条件。迭代过程中,梯度算子总是保证我们能选取到最佳的移动方向。
4. 实例分析
了解了那么多理论,这里我们举个经典案例,从疝气病症预测病马的死亡率,数据如下:
2.000000 1.000000 38.500000 66.000000 28.000000 3.000000 3.000000 0.000000 2.000000 5.000000 4.000000 4.000000 0.000000 0.000000 0.000000 3.000000 5.000000 45.000000 8.400000 0.000000 0.000000 0.000000
1.000000 1.000000 39.200000 88.000000 20.000000 0.000000 0.000000 4.000000 1.000000 3.000000 4.000000 2.000000 0.000000 0.000000 0.000000 4.000000 2.000000 50.000000 85.000000 2.000000 2.000000 0.000000
2.000000 1.000000 38.300000 40.000000 24.000000 1.000000 1.000000 3.000000 1.000000 3.000000 3.000000 1.000000 0.000000 0.000000 0.000000 1.000000 1.000000 33.000000 6.700000 0.000000 0.000000 1.000000
1.000000 9.000000 39.100000 164.000000 84.000000 4.000000 1.000000 6.000000 2.000000 2.000000 4.000000 4.000000 1.000000 2.000000 5.000000 3.000000 0.000000 48.000000 7.200000 3.000000 5.300000 0.000000
2.000000 1.000000 37.300000 104.000000 35.000000 0.000000 0.000000 6.000000 2.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 74.000000 7.400000 0.000000 0.000000 0.000000
2.000000 1.000000 0.000000 0.000000 0.000000 2.000000 1.000000 3.000000 1.000000 2.000000 3.000000 2.000000 2.000000 1.000000 0.000000 3.000000 3.000000 0.000000 0.000000 0.000000 0.000000 1.000000
1.000000 1.000000 37.900000 48.000000 16.000000 1.000000 1.000000 1.000000 1.000000 3.000000 3.000000 3.000000 1.000000 1.000000 0.000000 3.000000 5.000000 37.000000 7.000000 0.000000 0.000000 1.000000
1.000000 1.000000 0.000000 60.000000 0.000000 3.000000 0.000000 0.000000 1.000000 0.000000 4.000000 2.000000 2.000000 1.000000 0.000000 3.000000 4.000000 44.000000 8.300000 0.000000 0.000000 0.000000
2.000000 1.000000 0.000000 80.000000 36.000000 3.000000 4.000000 3.000000 1.000000 4.000000 4.000000 4.000000 2.000000 1.000000 0.000000 3.000000 5.000000 38.000000 6.200000 0.000000 0.000000 0.000000
2.000000 9.000000 38.300000 90.000000 0.000000 1.000000 0.000000 1.000000 1.000000 5.000000 3.000000 1.000000 2.000000 1.000000 0.000000 3.000000 0.000000 40.000000 6.200000 1.000000 2.200000 1.000000
- 准备数据
因为数据是传感器采集,所以可能会出现缺失或者不正确的情况,处理方法可以参考我之前的博客或者是机器学习实战(数据处理),在预处理需要做两件事: 所有的缺失值必须用一个实数值来替换,因为我们使用的NumPy数据类型不允许包含缺失值。我们这里选择实数 0 来替换所有缺失值,恰好能适用于 回归。
回归系数的更新公式如下:
如果 的某个特征对应值为0,那么该特征的系数将不做更新,即:
,即它对结果的预测不具有任何倾向性,因此我们上述做法也不会对误差造成任何影响。基于上述原因,将缺失值用 0 代替既可以保留现有数据,也不需要对优化算法进行修改。
def loadDataSet(fileName):
frTrain = open(fileName) #打开数据集
trainingSet = [] #数据集列表
trainingLabels = [] #标签列表
for line in frTrain.readlines():
currLine = line.strip().split('\t') #切割数据
lineArr = []
for i in range(21): #20个特征
lineArr.append(float(currLine[i]))
trainingSet.append(lineArr)
trainingLabels.append(float(currLine[21]))
return trainingSet, trainingLabels #返回特征列表和标签列表
- 函数
def sigmoid(inX):
return 1.0/(1+exp(-inX))
- 梯度上升算法
def gradAscent(dataMatIn, classLabels): #梯度上升算法
dataMatrix = mat(dataMatIn) #转换为矩阵
labelMat = mat(classLabels).transpose() #将数组转换为矩阵,并将行向量转置为列向量
m, n = shape(dataMatrix) # m数据量 n特征数
alpha = 0.001 #向目标移动的步长
maxCycles = 500 #迭代次数
weights = ones((n, 1)) #生成一个全为1的矩阵
for k in range(maxCycles):
h = sigmoid(dataMatrix*weights) #矩阵乘法
error = (labelMat - h) #向量相减
weights = weights + alpha * dataMatrix.transpose() * error #矩阵乘法,最后得到回归系数
return array(weights) #返回回归系数
梯度上升算法在每次更新回归系数时都需要遍历整个数据集,该方法在处理小的数据集时尚可,但如果有数十亿样本和成千上万的特征,那么该方法的计算复杂度就太高了。这是就有人想出一种改进方法,一次仅用一个样本点(样本点每次随机挑选一批数据集)来更新回归系数,该方法称为随机梯度上升算法。
def stocGradAscent(dataMatrix, classLabels, numIter=150): #随机梯度上升算法
m, n = shape(dataMatrix) #获取维度
weights = ones(n) #生成一个全为1的矩阵
for j in range(numIter):
dataIndex = range(m)
for i in range(m):
alpha = 4/(1.0+j+i) + 0.0001 #alpha会随着迭代不断减小,但永远不会减小到0,因为后边还有一个常数项0.0001
randIndex = int(random.uniform(0, len(dataIndex))) #获取随机数据索引
h = sigmoid(sum(dataMatrix[dataIndex[randIndex]]*weights))
error = classLabels[dataIndex[randIndex]] - h
weights = weights + alpha * error * dataMatrix[dataIndex[randIndex]]
#del(dataIndex[randIndex])
return weights
随机梯度上升算法与梯度上升算法在代码上很相似,但也有一些区别: 第一,后者的变量 h 和误差 error 都是向量,而前者则全是数值;第二,前者没有矩阵的转换过程,所有变量的数据类型都是 NumPy 数组。
- 分类
得到回归系数之后,我们可以将带入一组数据,根据回归系数,算出标签值。
def classifyVector(inX, weights):
prob = sigmoid(sum(inX*weights))
if prob > 0.5:
return 1.0
else:
return 0.0
- 测试
根据上面的各函数功能,我们可以带入一组数据集来测试效果,下面是我的测试函数:
def testResult(fileName):
errorCount = 0
numTestVec = 0.0
frTest = open(fileName) #打开测试数据集
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
- 结果
if __name__ == '__main__':
trainFilePath = 'C:\\Users\\John\\Desktop\\DataBooks\\MachineLearning\\Ch05\\horseColicTraining.txt' #训练集
testFilePath = 'C:\\Users\\John\\Desktop\\DataBooks\\MachineLearning\\Ch05\\horseColicTest.txt' #测试集
numTests = 10
errorSum = 0.0
for k in range(numTests): #每组数据计算十次
trainingSet, trainingLabels = loadDataSet(trainFilePath) #获取测试集
trainWeights = stocGradAscent(array(trainingSet), trainingLabels, 500) #逻辑回归训练
errorSum += testResult(testFilePath)
print ("after %d iterations the average error rate is: %f" % (numTests, errorSum / float(numTests))) #计算错误率
4. 实例分析
逻辑回归
注:这里引用了一些博主的博客,如果博主反对。可联系本人删除,谢谢。