假设有一些数据点,我们利用一条直线对这些点进行拟合(该直线为最佳拟合直线),这个拟合过程称为回归。
根据根据现有数据集对分类边界线建立回归公式,以此进行分类。
设X是连续随机变量,X服从逻辑斯谛分布是指X具有下面的分布函数和密度函数:
式中,u为位置参数,y>0为形状参数。这里可以类比正太分布
逻辑斯谛分布的密度函数f(x)和分布函数F(X)的图形如下:分布函数属于logistic函数,其图形属于一条S型曲线(sigmoid curve,神经网络里常用的一种激活函数)。该曲线以点(u,1/2)为中心对称。
即满足:
曲线在中心附近增长速度较快,在两端的增长速度较慢。形状参数y的值越小,曲线在中心附近增长的越快。
二项逻辑斯蒂回归模型(binary logistic regression model)是一种分类模型,由条件概率P(Y|X)表示,形式为条件化的逻辑斯蒂分。随机变量X取值为实数,随机变量Y取值为1或者0
w称为权值参数,b称为偏置,w*x是w和x的內积。
对于给定的输入实例x,按照式子(6.3)和(6.4)可以求得P(Y=1|x)和P(Y=0|x)。逻辑斯蒂回归比较两个条件概率的大小,将实例x划分到概率大的分类。
即我们将每一个特征乘以一个回归系数,将所有结果值相加,得到的总和带入到sigmoid函数中,进而得到一个0-1之间的数据,如果数值大于0.5,则被划分到1类中,否则将被划分到0类中。所以logistic回归也可以看做是一种概率估计。
一个时间的几率(odds)是指该事件发生的概率与不发生的概率的比值。如果该事件发生的概率为p,那么该事件发生的几率为p/(1-p),该事件的对数几率或者logit函数是
对于逻辑斯蒂回归而言,由式(6.5)与式(6.6)得
这就是说,在逻辑斯蒂回归模型当中,输出Y=1的对对书几率时输入x的线性函数或者说,输出Y=1的对数几率是由输入x的线性函数表示的模型。
逻辑斯蒂回归模型学习时,对于给定的训练集T={(x1,y1),(x2,y3),(x3,y3)…..(xn,yn)},其中xi属于R,yi属于{0,1},可以应用极大似然发估计参数模型,从而得到逻辑斯蒂回归模型:
对L(w)求极大值,得到w的估计值
梯度上升法的基本思想:要找到函数的最大值,最好的方法是沿着该函数的梯度方向探寻,如果梯度标记为,则函数f(x,y)的梯度由下面式子表示
这个梯度意味着沿着x方向的梯度为 ∂f(x,y)∂x
沿着y方向的梯度为 ∂f(x,y)∂y
函数f(x,y)在(x,y)处可微。
梯度算子总是指向函数值增长最快的方向。我们规定一个步长a表示的是移动量的大小。所以梯度上升算法的迭代公式为
该公式将一直被迭代执行,直到到达某个停止条件为止比如迭代次数下降到某个指定值或者误差在允许范围内。
梯度下降算法就是沿着反方向执行迭代,实际原理是一样的。
梯度上升算法的伪代码
每个回归系数初始化为1
重复R次:
计算整个数据集的梯度
使用alpha*gradient跟新回归系数的向量
返回回归系数
Logistic回归梯度上升优化算法
def loadDataSet():
dataMat = []
labelMat = []
fr = open('testSet.txt')
for line in fr.readlines():
lineArr = line.strip().split()#默认删除转移符制表符,然后分割
#讲三个数据加入到dataMat中,X0=1
#X1和X2去testSet.txt的每行前两个数据
dataMat.append([1.0,float(lineArr[0]),float(lineArr[1])])
labelMat.append(int(lineArr[2]))#第三行表示的是标签
return dataMat,labelMat
首先loadDataset函数从test.txt里面讲数据读取出来,testSet.txt每行包括三个数据,每行的前两个值分别为X1和X2,第三个值表示的对应的标签类别。
下面是梯度上升的代码
def sigmoid(intX):
return 1.0/(1+np.exp(-intX))
#alpha是移动步长
#maxCycle是训练次数
def gradAscent(dataMatIn,classLabels):
dataMatrix = np.mat(dataMatIn)#将数组转变成矩阵,然后就是矩阵相乘
labelMat = np.mat(classLabels).transpose()#W将标签转化成矩阵之后求转置
m,n = np.shape(dataMatrix)#返回矩阵的大小
alpha = 0.001
maxCycles = 500
weights = np.ones((n,1))
for k in range(maxCycles):
t = dataMatrix * weights#t为一个m*1的矩阵
h = sigmoid(t)#h经过sigmoid处理之后还是一个m*1的矩阵
error = labelMat-h#计算出他们的差
weights = weights + alpha *dataMatrix.transpose()*error
return weights
最后weights = weights + alpha *dataMatrix.transpose()*error的理解
w=w+αf(x)△x
其中 α 对应alpha,error对应 △x
#画出决策边界
def plotBestFit(weights):
dataMat,labelMat = loadDataSet()#加载数据和标签
dataArr = np.array(dataMat)#将矩阵转化成数组
n = np.shape(dataArr)[0]
xcord1 = [];ycord1 = []
xcord2 = [];ycord2 = []
for i in range(n):
if int(labelMat[i]) == 1:#将分类为1的点取出来
xcord1.append(dataArr[i,1])
ycord1.append(dataArr[i,2])
else:#将分类为0的点取出来
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)
plt.xlabel('X1')
plt.ylabel('X2')
plt.show()
需要注意的是我们,我们设置了sigmoid函数为0,0是两个分类的分界线。因此我们设定 0=w0x0+w1x1+w2x2 ( x0=1 ),所以可以解出X2与X1的关系
最后拟合的直线如图所示
梯度上升算法每次在更新回归系数的时候需要遍历整个数据集,如果数据量过大,计算复杂度会过高。一种改进方法时每一次只用一个样本更新回归系数,该方法成为随机梯度上升算法。由于新的样本可以不断的更新回归系数因此随机梯度上升算法是一种在线学习
随机梯度上升算法的伪代码
所有回归系数初始化为1
对数据集中每一个样本:
计算该样本的梯度
使用alpha*gradient更新回归系数
返回回归系数
下面是随机梯度上升算法的代码
def stocGradAscent0(dataMatrix,classLabels):
m,n = np.shape(dataMatrix)
alpha = 0.01
weights = np.ones(n)
for i in range(m):
h = sigmoid(sum(dataMatrix[i]*weights))
error = classLabels[i] - h
weights = weights + alpha*error *dataMatrix[i]
return weights
和梯度上升算法的区别就是变量以及误差都是数值,其他的地方差异性不大。
最后迭代100次后的效果(有100组数据),由于迭代次数的原因,导致有一部分被分错了。
对于随机梯度算法进行改进
def stocGradAscent1(dataMatrix,classLabels,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.0001#每次迭代时alpha需要调整
randIndex = int(np.random.uniform(0,len(dataIndex)))#随机选取更新
h = sigmoid(sum(dataMatrix[randIndex]*weights))
error = classLabels[randIndex] - h
weights = weights+alpha*error*dataMatrix[randIndex]
del(dataIndex[randIndex])
return weights
我们有一个数据集包含368个样本和28个特征。但是有接近30%的数据是缺失的。下面介绍如何处理数据集的缺失。
数据有时候某些特征会缺失或者损坏,但是因此丢掉整个数据集代价十分昂贵,十分不可取。所以必须采用一些方法解决这个问题:
下面给出一些可选的做法:
这里我们选择实数0来代替所有的缺失值,,这样恰好能够适用于logistic回归。
weights = weights + alpha * error * dataMatrix[randIndex]
如果dataMatrix的特征值为0,那么就不更新权值。
#使用sigmoid函数求出概率,如果为概率大于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(np.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(np.array(lineArr),trainWeights)) != int(currLine[21]):#判断最后预测标签是否和实际标签一样
errorCount += 1#如果不一样错误样本+1
errorRate = (float(errorCount)/numTestVec)#统计出错的概率
print('the error rate of this test is %f' % errorRate)
return errorRate
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))))
参考资料:
另外看到了两篇很好的博客,便于更好的理解其中的数学原理,传送门:
Logistic回归基础之梯度上升算法
机器学习算法与Python实践之(七)逻辑回归(Logistic Regression)
源代码