【机器学习实战】-Logistic回归之从疝气病症预测病马的死亡率
1.数据准备
数据还存在一个问题,数据集中有30%的值是缺失的。下面将首先介绍如何处理数据集中的数据缺失问题,然后再利用Logistic回归和随机梯度上升算法来预测病马的生死。
面对数据中有缺失值情况的处理方法:
下面给出了一些可选的做法:
使用可用特征的均值来填补缺失值;
使用特殊值来填补缺失值,如1;
忽略有缺失值的样本;
使用相似样本的均值添补缺失值;
使用另外的机器学习算法预测缺失值。
在本次实验中,第一,所有的缺失值必须用一个实数值来替换,因为我们使用的NumPy数据类型不允许包含缺失值。这里选择实数0来替换所有缺失值,恰好能适用于Logistic回归。这样做的直觉在于,我们需要的是一个在更新时不会影响系数的值。
回归系数更新公式如下:
w
e
i
g
h
t
s
=
w
e
i
g
h
t
s
+
a
l
p
h
a
∗
e
r
r
o
r
∗
d
a
t
a
M
a
t
r
i
x
[
r
a
n
d
I
n
d
e
x
]
weights = weights + alpha * error * dataMatrix[randIndex]
weights=weights+alpha∗error∗dataMatrix[randIndex]
如果dataMatrix的某特征对应值为0,那么该特征的系数不做更新,即:
w
e
i
g
h
t
s
=
w
e
i
g
h
t
s
weights = weights
weights=weights
另外,由于sigmoid(0)=0.5,即它对结果的预测不具有任何倾向性,因此上述做法也不会对误差项造成任何影响。基于上述原因,将缺失值用0代替既可以保留现有数据,也不需要对优化算法进行修改。此外,该数据集中的特征取值一般不为0,因此在某种意义上说它也满足“特殊值”这个要求。
预处理中做的第二件事是,如果在测试数据集中发现了一条数据的类别标签已经缺失,那么我们的简单做法是将该条数据丢弃。这是因为类别标签与特征不同,很难确定采用某个合适的值来替换。采用Logistic回归进行分类时这种做法是合理的,而如果采用类似kNN的方法就可能不太可行。
原始的数据集经过预处理之后保存成两个文件:horseColicTest.txt和horseColicTraining.txt。
2.要训练的算法
使用之前博客中使用的随机梯度上升算法,详细讲解请看之前的博客
代码:
# 改进的随机梯度上升算法
def stocGradAscent1(dataMatrix, classLables, numIter=150):
m, n = np.shape(dataMatrix)
weights = np.ones(n)
for j in range(numIter):
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 = classLables[randIndex] - h
weights = weights + alpha * error * np.array(dataMatrix[randIndex])
del(dataIndex[randIndex])
return weights
3.测试算法:用Logistic回归进行分类
使用Logistic回归方法进行分类并不需要做很多工作,所需做的只是把测试集上每个特征向量乘以最优化方法得来的回归系数,再将该乘积结果求和,最后输入到Sigmoid函数中即可。如果对应的Sigmoid值大于0.5就预测类别标签为1,否则为0。
代码如下:
# Logistic回归分类函数
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')
# trainSet:训练数据的特征。trainingLabels:训练数据集的标签。
trainingSet = []; trainingLabels = []
# 读取训练数据
for line in frTrain.readlines():
currLine = line.strip().split('\t')
lineArr = []
# 每一行都有21个数据,遍历每行数据
for i in range(21):
lineArr.append(float(currLine[i]))
trainingSet.append(lineArr)
trainingLabels.append(float(currLine[21]))
# 逻辑回归生成权重
trainWeights = stocGradAscent1(np.array(trainingSet), trainingLabels, 500)
# errorCount:错误数。numTestVec:测试集数据长度
errorCount = 0; numTestVec = 0.0
for line in frTest.readlines():
numTestVec += 1.0
currLine = line.strip().split('\t')
# lineArr:测试数据特征
lineArr = []
for i in range(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:运行次数, errorsum:错误总数
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)))
第一个函数是classifyVector(),它以回归系数和特征向量作为输入来计算对应的Sigmoid值。如果Sigmoid值大于0.5函数返回1,否则返回0。
接下来的函数是colicTest(),是用于打开测试集和训练集,并对数据进行格式化处理的函数。数据导入之后,便可以使用函数stocGradAscent1()来计算回归系数向量。这里可以自由设定迭代的次数,例如在训练集上使用500次迭代,实验结果表明这比默认迭代150次的效果更好。在系数计算完成之后,导入测试集并计算分类错误率。整体看来,colicTest()具有完全独立的功能,多次运行得到的结果可能稍有不同,这是因为其中有随机的成分在里面。如果在stocGradAscent1()函数中回归系数已经完全收敛,那么结果才将是确定的。
最后一个函数是multiTest(),其功能是调用函数colicTest()10次并求结果的平均值。
运行效果图:
10次迭代之后的平均错误率为36%。事实上,这个结果并不差,因
为有30%的数据缺失。当然,如果调整colicTest()中的迭代次数和stochGradAscent1()中的步长,平均错误率可以降到20%左右。
代码包含了数据的加载训练和预测功能。
4.完整代码:
'''Logistic回归预测马疝气病'''
import numpy as np
# 1.数据处理,缺失值用0填充,已经提前处理好了
# sigmoid函数
def sigmoid(inX):
return 1.0/(1 + np.exp(-inX))
# Logistic回归分类函数
def classifyVector(inX, weights):
# 计算逻辑回归的分类比例
prob = sigmoid(sum(inX*weights))
if prob > 0.5: return 1.0
else: return 0.0
# 改进的随机梯度上升算法
def stocGradAscent1(dataMatrix, classLables, numIter=150):
m, n = np.shape(dataMatrix)
weights = np.ones(n)
for j in range(numIter):
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 = classLables[randIndex] - h
weights = weights + alpha * error * np.array(dataMatrix[randIndex])
del(dataIndex[randIndex])
return weights
def colicTest():
# 加载训练数据集
frTrain = open('horseColicTraining.txt')
# 加载测试数据集
frTest = open('horseColicTest.txt')
# trainSet:训练数据的特征。trainingLabels:训练数据集的标签。
trainingSet = []; trainingLabels = []
# 读取训练数据
for line in frTrain.readlines():
currLine = line.strip().split('\t')
lineArr = []
# 每一行都有21个数据,遍历每行数据
for i in range(21):
lineArr.append(float(currLine[i]))
trainingSet.append(lineArr)
trainingLabels.append(float(currLine[21]))
# 逻辑回归生成权重
trainWeights = stocGradAscent1(np.array(trainingSet), trainingLabels, 500)
# errorCount:错误数。numTestVec:测试集数据长度
errorCount = 0; numTestVec = 0.0
for line in frTest.readlines():
numTestVec += 1.0
currLine = line.strip().split('\t')
# lineArr:测试数据特征
lineArr = []
for i in range(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:运行次数, errorsum:错误总数
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)))
multiTest()
5.本章小结
Logistic回归的目的是寻找一个非线性函数Sigmoid的最佳拟合参数,求解过程可以由最优化算法来完成。在最优化算法中,最常用的就是梯度上升算法,而梯度上升算法又可以简化为随机梯度上升算法。
随机梯度上升算法与梯度上升算法的效果相当,但占用更少的计算资源。此外,随机梯度上升是一个在线算法,它可以在新数据到来时就完成参数更新,而不需要重新读取整个数据集来进行批处理运算。
机器学习的一个重要问题就是如何处理缺失数据。这个问题没有标准答案,取决于实际应用中的需求。现有一些解决方案,每种方案都各有优缺点。
此系列为读书笔记,在学习阶段,如有错误,欢迎批评指正,加油!