本文参考了《机器学习实战》书中代码,结合该书讲解,并加之自己的理解和阐述
利用Logistic回归进行分类的主要思想是:根据现有数据对分类边界线建立回归公式,以此进行分类。这里的“回归”一词源于最佳拟合,表示要找到最佳拟合参数集。我们想要的函数应该是,能接受所有的输入然后预测出类别。例如,在两个类的情况下,上述函数输出0或1,阶跃函数满足这个要求,只是阶跃函数的跳跃瞬间又是很难处理,幸好,另一个函数也有类似的性质,且数学上更易处理,这就是Sigmoid函数。
当x为0时, Sigmoid函数值为0.5。随着x的增大,对应的Sigmoid值将逼近于1;而随着x的减小, Sigmoid值将逼近于0。如果横坐标刻度足够大,Sigmoid函数看起来很像一个阶跃函数:
Sigmoid函数的输入记为Z,其中Z=W’X,W’是W的转置,其中的向量X是分类器的输入数据,向量W也就是我们要找到的最佳参数(系数),从而使得分类器尽可能地精确。寻找最优参数常用的方法是梯度上升法/梯度下降法,他们两种方法本质一样,只是一个求最大值,一个求最小值。
下边以一个小例子来说明Logistic回归是怎么实现的,以及效果如何
导入数据
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
从文件中将数据导入,制作数据集
sigmoid函数
def sigmoid(inX):
return 1.0/(1+np.exp(-inX))
梯度上升
def gradAscent(dataMatIn,classLabels):
dataMatrix = np.mat(dataMatIn)
labelMat = np.mat(classLabels).transpose()
m,n = np.shape(dataMatrix)
alpha = 0.001
maxCycles = 500
weights = np.ones((n,1))
for k in range(maxCycles):
h = sigmoid(dataMatrix*weights)
error = (labelMat-h)
weights = weights+alpha*dataMatrix.transpose()*error
return weights
函数返回经过训练之后参数,下边就要展示一下训练的效果,由于我们这次使用的训练集是二维的,所以可以在一张图片中画出,下边是划分情况:
def plotBestFit(weights):
dataMat,labelMat = loadDataSet()
dataArr = np.array(dataMat)
n = dataArr.shape[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 = np.arange(-3.0,3.0,0.1)
y = (-weights[0]-weights[1]*x)/weights[2]
ax.plot(x,y.transpose())
plt.xlabel('X1');plt.ylabel('X2')
plt.show()
划分效果
这个是迭代了500次之后的结果,划分还是很分明的,效果较好,我们还可以将alpha进行调整,不停的改变步长,代码如下:
def stocGradAscent(dataMatrix, classLabels, numIter=150):
m,n = np.shape(dataMatrix)
weights = np.ones(n) #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
randIndex = int(random.uniform(0,len(dataIndex)))#go to 0 because of the constant
h = sigmoid(sum(dataMatrix[randIndex]*weights))
error = classLabels[randIndex] - h
weights = weights + alpha * error * dataMatrix[randIndex]
del(dataIndex[randIndex])
return weights
效果为:
这个只用了150次迭代也取得了不错的划分结果
这里将使用Logistic回归来预测患有疝病的马的存活问题。这里的数据包含368个样本和28个特征,疝病是描述马胃肠痛的术语。然而,这种病不一定源自马的胃肠问题,其他问题也可能引发马疝病。该数据集中包含了医院检测马疝病的一些指标,有的指标比较主观,有的指标难以测量,例如马的疼痛级别,另外需要说明的是,除了部分指标主观和难以测量外,该数据还存在一个问题,数据集中有30%的值是缺失的,所以介绍如何处理数据集中的数据缺失问题:
假设有100个样本和20个特征,这些数据都是机器收集回来的。若机器上的某个传感器损坏导致一个特征无效时该怎么办?此时是否要扔掉整个数据?这种情况下,另外19个特征怎么办?它们是否还可用?答案是肯定的。因为有时候数据相当昂贵,扔掉和重新获取都是不可取的,所以必须采用一些方法来解决这个问题,下面给出了一些可选的做法:
在预处理阶段需要做两件事:第一,所有的缺失值必须用一个实数值来替换,因为我们使用的NumPy数据类型不允许包含缺失值。这里选择实数0来替换所有缺失值,恰好能适用于Logistic回归。这样做的直觉在于,我们需要的是一个在更新时不会影响系数的值。回归系数的更新公式如下:
weights = weights + alpha * error * dataMatrix[randIndex]
如果dataMatrix的某特征对应值为0,那么该特征的系数将不做更新,即
weights = weights
另外,由于sigmoid(0)=0.5,即它对结果的预测不具有任何倾向性,因此上述做法也不会对误差项造成任何影响
预处理中做的第二件事是,如果在测试数据集中发现了一条数据的类别标签已经缺失,那么我们的简单做法是将该条数据丢弃。这是因为类别标签与特征不同,很难确定采用某个合适的值来替换。采用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")
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 = stocGradAscent(np.array(trainingSet),trainingLabels,20)
errorCount = 0.0;numTestVec= 0.0
print("finished train")
for line in frTest.readlines():
numTestVec += 1.0
# print(numTestVec)
currLine = line.strip().split('\t')
# print(currLine)
lineArr = []
for i in range(21):
lineArr.append(float(currLine[i]))
if(int(classifyVector(np.array(lineArr),trainWeights))!=int(currLine[21])):
errorCount += 1.0
errorRate = (float(errorCount)/numTestVec)
print("the error rate of this test is: %f" % errorRate)
return errorRate
分类器判定大于0.5输出1,小于0.5输出0,然后对数据进行训练与检验
the error rate of this test is: 0.343284
在多次调整参数错误率也很难降到30%以下,事实上,这个结果并不差,因为有30%的数据缺失。逻辑回归的优点是计算快,时间空间开销低,但是由于模型的简单容易产生欠拟合,精度不高。