优点:计算代价小,容易实现和理解;
缺点:容易欠你和,分类精度可能不高
适用:数字型和标称型数据
在只需要两个分类的情况下,我们希望在某个临界分类线下,一侧为正类另一侧为负类,这非常像阶跃函数的性质。但是阶跃函数在非常临近分类线上的数据很容易出现误非类,所以平常往往用与其性质非常类似的sigmoid函数代替其进行分类。其公式如下:
对应图形如下:
从上图可以看出,sigmoid函数的分界线在y=0.5上,小于0.5则x<0,为负类;大于0.5则x>0,为正类。我们可以利用这个性质来对数据进行分类。设定输入为:
其中,b为常数不需要计算,从而可知,只需要计算最优的参数
,就可以对数据进行分类。
这里我们用梯度上升算法来计算最优参数。
梯度上升算法:
1)一个简单二次函数的例子
假设有二次函数如下:
我们可以根据二次函数的性质,求其导数从而获得其最大值:
令其导数等于0,可以知道,这个函数最大值为7。
同样,用梯度上升算法,我们可以设置一个初始值,然后根据学习速率,一点点的去逼近最大值,从而获取到一个较优解:
其中,α为学习速率,决定每次”爬坡“的速度。
python代码:
import matplotlib.pyplot as plt
import numpy as np
"""
梯度上升算法
求函数f(x)=-2x^2+4x+5的最大值
"""
# 函数导数
def Derivatives(old):
return -4*old+4
#梯度上升算法
def GradientAscent():
xcord = [] #保存x坐标
ycord = [] #保存y坐标
xold = -1
xnew = 0 #梯度算法的初始值
alpha = 0.01 # 学习速率(一般根据经验和具体需要设置)
maxCycle = 100 #学习次数
for k in range(maxCycle):
xold = xnew
xnew = xold + alpha *Derivatives(xold) #梯度上升公式
xcord.append(xnew)
ycord.append(-2*np.square(xnew) + 4*xnew+5)
return xcord,ycord #返回每一次学习完后的x,y值
#画图
def plotFig(xcord,ycord):
fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(xcord, ycord, s=4, c='red', marker='s', alpha=.5) #绘制每次学习后x,y值
x = np.arange(0,2,0.01)
y = -2*np.square(x) + 4*x+5
ax.plot(x,y) #绘制-2x^2+4x+5 函数
plt.title('BestFit') # 绘制title
plt.xlabel('X')
plt.ylabel('Y') # 绘制label
plt.show()
if __name__== '__main__':
xcord, ycord = GradientAscent()
plotFig(xcord,ycord)
得到下图:
可以看到经过100次训练后,获取的最优值已经非常接近真实的最大值7了。
2)sigmoid函数例子
根据sigmoid函数性质假定:
我们可以将其合并,合并的函数称为代价函数:
为了方便计算,利用取对数不改变函数单调性的性质,取代价函数的对数:
我们对其求导,以用于计算最优值:
从而我们得到梯度上升算法:
由于,sigmoid函数分类可以有很多数据,推广到多数据:
数据为testSet.txt,如下,共100条训练数据,前两列对应X1,X2的值,第三列是分类0或者1:
-0.017612 14.053064 0-1.395634 4.662541 1
-0.752157 6.538620 0
-1.322371 7.152853 0
0.423363 11.054677 0
0.406704 7.067335 1
0.667394 12.741452 0
-2.460150 6.866805 1
0.569411 9.548755 0
-0.026632 10.427743 0
0.850433 6.920334 1
1.347183 13.175500 0
1.176813 3.167020 1
-1.781871 9.097953 0
-0.566606 5.749003 1
0.931635 1.589505 1
-0.024205 6.151823 1
-0.036453 2.690988 1
-0.196949 0.444165 1
1.014459 5.754399 1
1.985298 3.230619 1
-1.693453 -0.557540 1
-0.576525 11.778922 0
-0.346811 -1.678730 1
-2.124484 2.672471 1
1.217916 9.597015 0
-0.733928 9.098687 0
-3.642001 -1.618087 1
0.315985 3.523953 1
1.416614 9.619232 0
-0.386323 3.989286 1
0.556921 8.294984 1
1.224863 11.587360 0
-1.347803 -2.406051 1
1.196604 4.951851 1
0.275221 9.543647 0
0.470575 9.332488 0
-1.889567 9.542662 0
-1.527893 12.150579 0
-1.185247 11.309318 0
-0.445678 3.297303 1
1.042222 6.105155 1
-0.618787 10.320986 0
1.152083 0.548467 1
0.828534 2.676045 1
-1.237728 10.549033 0
-0.683565 -2.166125 1
0.229456 5.921938 1
-0.959885 11.555336 0
0.492911 10.993324 0
0.184992 8.721488 0
-0.355715 10.325976 0
-0.397822 8.058397 0
0.824839 13.730343 0
1.507278 5.027866 1
0.099671 6.835839 1
-0.344008 10.717485 0
1.785928 7.718645 1
-0.918801 11.560217 0
-0.364009 4.747300 1
-0.841722 4.119083 1
0.490426 1.960539 1
-0.007194 9.075792 0
0.356107 12.447863 0
0.342578 12.281162 0
-0.810823 -1.466018 1
2.530777 6.476801 1
1.296683 11.607559 0
0.475487 12.040035 0
-0.783277 11.009725 0
0.074798 11.023650 0
-1.337472 0.468339 1
-0.102781 13.763651 0
-0.147324 2.874846 1
0.518389 9.887035 0
1.015399 7.571882 0
-1.658086 -0.027255 1
1.319944 2.171228 1
2.056216 5.019981 1
-0.851633 4.375691 1
-1.510047 6.061992 0
-1.076637 -3.181888 1
1.821096 10.283990 0
3.010150 8.401766 1
-1.099458 1.688274 1
-0.834872 -1.733869 1
-0.846637 3.849075 1
1.400102 12.628781 0
1.752842 5.468166 1
0.078557 0.059736 1
0.089392 -0.715300 1
1.825662 12.693808 0
0.197445 9.744638 0
0.126117 0.922311 1
-0.679797 1.220530 1
0.677983 2.556666 1
0.761349 10.693862 0
-2.168791 0.143632 1
1.388610 9.341997 0
0.317029 14.739025 0
Python代码:
import matplotlib.pyplot as plt
import numpy as np
"""
说明:利用sigmoid函数分类
"""
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])]) #添加数据,前面加个1是为了方便矩阵运算
labelMat.append(int(lineArr[2])) #添加分类
fr.close()
return dataMat, labelMat
#sigmoid函数
def sigmoid(inX):
return 1.0 / (1 + np.exp(-inX))
# 梯度上升算法
def gradAscent(dataMatIn, classLabels):
dataMatrix = np.mat(dataMatIn) #转换成numpy的mat,x0=1,x1=第一列数据,x2=第二列数据
labelMat = np.mat(classLabels).transpose() #转换成numpy的mat,并进行转置
m, n = np.shape(dataMatrix) #返回dataMatrix的大小。m为行数,n为列数。
alpha = 0.001 #移动步长,也就是学习速率,控制更新的幅度。
maxCycles = 500 #最大迭代次数
weights = np.ones((n,1)) #设置w^T的初始值为(1,1,1)
for k in range(maxCycles):
h = sigmoid(dataMatrix * weights) #梯度上升矢量化公式
error = labelMat - h
weights = weights + alpha * dataMatrix.transpose() * error
return weights.getA() #将矩阵转换为数组,返回权重数组
#画图
def plotBestFit(weights):
dataMat, labelMat = loadDataSet() #加载数据集
dataArr = np.array(dataMat) #转换成numpy的array数组
n = np.shape(dataMat)[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]) #1为正样本
else:
xcord2.append(dataArr[i,1]); ycord2.append(dataArr[i,2]) #0为负样本
fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(xcord1, ycord1, s = 20, c = 'red', marker = 's',alpha=.5)#绘制正样本
ax.scatter(xcord2, ycord2, s = 20, c = 'green',alpha=.5) #绘制负样本
x1 = np.arange(-3.0, 3.0, 0.001) #生成步长为0.001的X值
x2 = (-weights[0] - weights[1] * x1) / weights[2] #根据w0*x0+w1*x1+w2*x2=0获得x2的值
ax.plot(x1, x2) #根据x1和x2画出分类直线
plt.title('BestFit') #绘制title
plt.xlabel('X1'); plt.ylabel('X2') #绘制label
plt.show()
if __name__ == '__main__':
dataMat, labelMat = loadDataSet()
weights = gradAscent(dataMat, labelMat)
plotBestFit(weights)
得到下图:
3)随机梯度上升算法
梯度上升算法,每次更新回归系数,都需要对整个数据进行计算,在数据量大的情况下效率非常低,因而出现了随机梯度上升算法。我们更新回归系数不再计算整个数据。而是利用随机性,随机的选择一组数据继续计算,并且不断的减小学习速率,已尽可能找到最优解:
Python代码(其他代码同上):
# 随机梯度上升算法
def stocGradAscent(dataMatrix, classLabels, numIter=150):
m,n = np.shape(dataMatrix)
weights = np.ones(n) #初始值w0,w1,w2都设为1
for j in range(numIter):
dataIndex = list(range(m)) #根据数据大小设置随机范围最大值
for i in range(m):
alpha = 4/(1.0+j+i)+0.0001 #学习速率每次都下降
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
if __name__ == '__main__':
dataMat, labelMat = loadDataSet()
weights = stocGradAscent(np.array(dataMat), labelMat)
plotBestFit(weights)
得到下图:
4)用Logistic回归预测病马死亡率
数据准备:
现实环境中,可能会存在多个特征值,也会存在特征值丢失的情况。在这种情况下,我们就需要对特征值进行补全,常见的做法包括:
1,用特征值的均值补全;
2,用特殊值补全,如-1;
3,用相似样本特征值补全;
4,用其他机器学习方法预测特征值;
对于Logistic回归来说,我们可以采用第二种方式,把缺失的特征值置为0,。因为特征值为0,根据计算回归公式weights = weights + alpha * error*dataMatrix[randIndex]的值不会改变,即weights = weights。另外,sigmoid(0)=0.5也不会对结果产生影响。
python代码:
# sigmoid函数分类
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, 1000) #通过随机梯度上升算法确定回归系数
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
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)))