本节将使用Logistic回归来预测患有疝病的马的存活问题。这里的数据1包含368个样本和28个特征。我并非育马专家,从一些文献中了解到,疝病是描述马胃肠痛的术语。然而,这种病不一定源自马的胃肠问题,其他问题也可能引发马疝病。该数据集中包含了医院检测马疝病的一些指标,有的指标比较主观,有的指标难以测量,例如马的疼痛级别。
1. 数据集来自2010年1月11日的UCI机器学习数据库(http://archive.ics.uci.edu/ml/datasets/Horse+Colic)。该数据最早由加拿大安大略省圭尔夫大学计算机系的Mary McLeish和Matt Cecile收集。
示例:使用Logistic回归估计马疝病的死亡率
- 收集数据:给定数据文件。
- 准备数据:用Python解析文本文件并填充缺失值。
- 分析数据:可视化并观察数据。
- 训练算法:使用优化算法,找到最佳的系数。
- 测试算法:为了量化回归的效果,需要观察错误率。根据错误率决定是否回退到训练阶段,通过改变迭代的次数和步长等参数来得到更好的回归系数。
- 使用算法:实现一个简单的命令行程序来收集马的症状并输出预测结果并非难事,这可以做为留给读者的一道习题。
另外需要说明的是,除了部分指标主观和难以测量之外,该数据集还存在一个问题,数据集中有30%的数据值是缺失的。下面将首先介绍如何处理数据集中的数据缺失问题,然后再利用Logistic回归和随机梯度上升算法来预测病马的生死。
5.3.1 准备数据:处理数据中的缺失值
数据中的缺失值是个非常棘手的问题,有很多文献都致力于解决这个问题。那么,数据缺失究竟带来了什么问题?假设有100个样本和20个特征,这些数据都是机器收集回来的。若机器上的某个传感器损坏导致一个特征无效时该怎么办?此时是否要扔掉整个数据?这种情况下,另外19个特征怎么办?它们是否还可用?答案是肯定的。因为有时候数据相当昂贵,扔掉和重新获取都是不可取的,所以必须采用一些方法来解决这个问题。
下面给出了一些可选的做法:
- 使用可用特征的均值来填补缺失值;
- 使用特殊值来填补缺失值,如-1;
- 忽略有缺失值的样本;
- 使用相似样本的均值添补缺失值;
- 使用另外的机器学习算法预测缺失值。
现在,我们对下一节要用的数据集进行预处理,使其可以顺利地使用分类算法。在预处理阶段需要做两件事:第一,所有的缺失值必须用一个实数值来替换,因为我们使用的NumPy数据类型不允许包含缺失值。这里选择实数0来替换所有缺失值,恰好能适用于Logistic回归。原因在于,我们需要的是一个在更新时不会影响系数的值。回归系数的更新公式如下:
weights = weights + alpha * error * dataMatrix[randIndex
如果dataMatrix
的某特征对应值为0,那么该特征的系数将不做更新,即:
weights = weights
另外,由于sigmoid(0)=0.5
,即它对结果的预测不具有任何倾向性,因此上述做法也不会对误差项造成任何影响。基于上述原因,将缺失值用0代替既可以保留现有数据,也不需要对优化算法进行修改。此外,该数据集中的特征取值一般不为0,因此在某种意义上说它也满足“特殊值”这个要求。
预处理中做的第二件事是,如果在测试数据集中发现了一条数据的类别标签已经缺失,那么我们的简单做法是将该条数据丢弃。这是因为类别标签与特征不同,很难确定采用某个合适的值来替换。采用Logistic回归进行分类时这种做法是合理的,而如果采用类似kNN的方法就可能不太可行。
原始的数据集经过预处理之后保存成两个文件:horseColicTest.txt
和horseColicTraining.txt
。如果想对原始数据和预处理后的数据做个比较,可以在http://archive.ics.uci.edu/ml/datasets/Horse+Colic浏览这些数据。
现在我们有一个“干净”可用的数据集和一个不错的优化算法,下面将把这些部分融合在一起训练出一个分类器,然后利用该分类器来预测病马的生死问题。
5.3.2 测试算法:用Logistic回归进行分类
本章前面几节介绍了优化算法,但目前为止还没有在分类上做任何实际尝试。使用Logistic回归方法进行分类并不需要做很多工作,所需做的只是把测试集上每个特征向量乘以最优化方法得来的回归系数,再将该乘积结果求和,最后输入到Sigmoid函数中即可。如果对应的Sigmoid值大于0.5就预测类别标签为1,否则为0。
下面看看实际运行效果,打开文本编辑器并将下列代码添加到logRegres.py
文件中。
程序清单5-5 logistic回归分类函数
def classifyVector(inX, weights): prob = sigmoid(sum(inX*weights)) if prob > 0.5: return 1.0 else: return 0.0def 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(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): 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 errorRatedef 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))
程序清单5-5的第一个函数是classifyVector
,它以回归系数和特征向量作为输入来计算对应的Sigmoid值。如果Sigmoid值大于0.5函数返回1,否则返回0。
接下来的函数是colicTest
,是用于打开测试集和训练集,并对数据进行格式化处理的函数。该函数首先导入训练集,同前面一样,数据的最后一列仍然是类别标签。数据最初有三个类别标签,分别代表马的三种情况:“仍存活”、“已经死亡”和“已经安乐死”。这里为了方便,将“已经死亡”和“已经安乐死”合并成“未能存活”这个标签 。数据导入之后,便可以使用函数stocGradAscent1
来计算回归系数向量。这里可以自由设定迭代的次数,例如在训练集上使用500次迭代,实验结果表明这比默认迭代150次的效果更好。在系数计算完成之后,导入测试集并计算分类错误率。整体看来,colicTest
具有完全独立的功能,多次运行得到的结果可能稍有不同,这是因为其中有随机的成分在里面。如果在stocGradAscent1
函数中回归系数已经完全收敛,那么结果才将是确定的。
最后一个函数是multiTest
,其功能是调用函数colicTest
10次并求结果的平均值。下面看一下实际的运行效果,在Python提示符下输入:
>>> reload(logRegres)<module /'logRegres/' from /'logRegres.py/'>>>> logRegres.multiTestthe error rate of this test is: 0.358209the error rate of this test is: 0.432836the error rate of this test is: 0.373134 . .the error rate of this test is: 0.298507the error rate of this test is: 0.313433after 10 iterations the average error rate is: 0.353731
从上面的结果可以看到,10次迭代之后的平均错误率为35%。事实上,这个结果并不差,因为数据集有30%的数据已经缺失。当然,如果调整colicTest
中的迭代次数和stochGradAscent1
中的步长,平均错误率可以降到20%左右。第7章中我们还会再次使用到这个数据集。