目录
一 简介
Logistic回归是机器学习中最常用最经典的分类方法之一,有的人称为逻辑回归或逻辑斯蒂回归。虽然它称为回归模型,但是却处理的是分类问题,这主要是因为它的本质是一个线性模型加上一个映射函数sigmoid,将线性模型得到的连续结果映射到离散型上。它常用于二分类问题,在多分类问题的推广叫做softmax。
二 理论基础
2.1 拟合和回归
- 拟合:拟合就是把平面上一系列的点,用一条光滑的曲线连接起来。因为这条曲线有无数种可能,从而有不同的拟合方法。拟合的曲线一般可以用函数表示,根据函数的不同可以有不同的拟合名字(注:针对本实验,拟合可以理解为对于给定的空间中的一些点,找到一条连续的直线来最大限度地逼近这些点)。
- 回归:假设我们有一些数据点,我们使用一条直线对这些点进行拟合,这条直线称为最佳拟合直线,拟合过程称为回归。
2.2 逻辑回归假设函数
- 逻辑回归一般用于分类问题较多,但是叫做“regression”,而线性回归一般不建议用于分类,因为输出的y的值可能超出0/1范围。这也就是为什么逻辑回归假设函数里面有sigmoid函数的原因了。
-
2.3 成本函数
- List item逻辑回归问题不在采用“最小均方”误差,因为里面含有非线性的sigmiod函数,也就是h θ ( x ) ,使得成本函数J ( θ )不再是一个平滑的“碗”,术语叫做非凸函数,(如左图)。可以看到,如果将梯度下降法用在这样的函数上,不能保证收敛到全局最小值,容易导致“局部最优”。所以我们希望,我们的代价函数J ( θ )是一个凸函数,也就是(右图)。
所以采用如下cost function:
最终写成:这个式子是从统计学中极大似然法得来的,最好的性质就是,他是凸函数。
2.4 参数学习(梯度下降)
你会发现,这个跟线性回归模型的梯度下降表达上一模一样,但是,你要知道,其中的h(x)是不一样的!
三 Logistic回归的一般过程
1.收集数据:采用任意方法收集
2.准备数据:由于需要进行距离计算,因此要求数据类型为数值型。另外,结构化数据格式则最佳
3.分析数据:采用任意方法对数据进行分析
4.训练算法:大部分时间将用于训练,训练的目的是为了找到最佳的分类回归系数
5.测试算法:一旦训练步骤完成,分类将会很快。
6.使用算法:首 先,我们需要输入一些数据,并将其转换成对应的结构化数值;接着,基于训练好的回归系数就可以对这些数值进行简单回归计算,判定它们属于哪个类别;在这之后,我们就可以在输出的类别上做一些其他分析工作。
四 基于Logistic回归和Sigmoid函数的分类
4.1 logistic回归的优缺点
- 优点:计算代价不高,易于理解和实现
- 缺点:容易欠拟合,分类精度可能不高
- 适用数据类型:数值型和标称型数据
4.2 Sigmoid函数
-
当x为0时,函数值为0.5。随着x的增大,对应的函数值将逼近于1;而随着x的减小,函数值将逼近于0。如果横坐标刻度足够大,Sigmoid函数看起来很像一个阶跃函数。
-
为了实现Logistic回归分类器,我们使用Sigmoid函数。我们可以在每个特征上都乘以一个回归系数,然后把所有的结果值相加,将这个总和代入Sigmoid函数中,进而得到一个范围在0~1之间的数值。任何大于0.5的数据被分入1类,小于0.5的即被归入0类。
-
优点: -
Sigmoid函数的输出映射在(0,1)之间,单调连续,输出范围有限,优化稳定,可以用作输出层。
-
求导容易。
缺点: -
.由于其软饱和性,容易产生梯度消失,导致训练出现问题。
-
其输出并不是以0为中心的。
五 基于最优化方法的最佳回归系数确定
5.1 理论公式
Sigmoid函数的输入为z,由下面的公式得出
梯度上升法的迭代公式
5.2 训练算法:使用梯度上升找到最佳参数
有100个样本点,每个点包含两个数值型特征:X1和X2。在此数据庥上,将通过使用梯度上升法找到最佳回归系数,也就是拟合出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])]) #将每一行的两个特征x1,x2,加上x0 = 1,组成列表并添加到数据集列表中
labelMat.append(int(lineArr[2])) # 将当前的行标签添加到标签列表,数据的类别号列表
return dataMat, labelMat # 返回数据列表,标签列表
# 定义sigmoid函数
def sigmoid(inX):
return 1.0/(1 + exp(-inX))
# Logistic 回归梯度上升优化算法
# @dataMatIn:数据集
# @classLabels:数据标签
def gradAscent(dataMatIn, classLabels):
dataMatrix = mat(dataMatIn) # 将数据集列表转化成numpy矩阵
labelMat = mat(classLabels).transpose() # 将数据集标签列表转化成矩阵并转置成列向量
m,n = shape(dataMatIn) # 获得数据集矩阵dataMatrix的行、列数
alpha = 0.001 # 向目标移动的步长,学习步长
maxCycles = 500 # 最大迭代次数
weights = ones((n,1)) # 生成n行1列的矩阵且值为1
# print(weights)
for k in range(maxCycles): # 循环迭代次数
h = sigmoid(dataMatrix * weights) # dataMatrix * weights 是m * 1的矩阵,其每一个元素都会调用sigmoid()函数,h也是一个m * 1的矩阵,求当前sigmoid函数的预测概率
# 此处计算真实类别和预测类别的差值
# 对logistic回归函数的对数释然函数的参数项求偏导
error = (labelMat - h)
# 更新权*值参数
weights = weights + alpha * dataMatrix.transpose() * error # 每步weights该变量
return weights
5.3 分析数据:画出决策边界
代码实现:
def plotBestFit(weights):
dataMat, labelMat = loadDataSet()
dataArr = array(dataMat)
n = shape(dataArr)[0] # 数据的行数,即对象的个数
xcord1 = []; ycord1 = [] # 对类别号为 1 的对象,分 X 轴和 Y 轴的数据
xcord2 = []; ycord2 = [] # 对类别号为 0 的对象,分 X 轴和 Y 轴的数据
for i in range(n): # 对所有的对象进行遍历
if int(labelMat[i]) == 1: # 对象的类别为: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 = '*') # 对散点的格式的设置,坐标号、点的大小、颜色、点的图形(方块)
ax.scatter(xcord2, ycord2, s = 30, c = 'green', marker = '*') # 点的图形默认为圆
x = arange(-3.0, 3.0, 0.1)
y = (-weights[0]-weights[1]*x) / weights[2] # 线性方程 y = aX + b,y 是数据第三列的特征,X 是数据第二列的特征
ax.plot(x, y)
plt.xlabel('X1')
plt.ylabel('X2')
plt.show()
运行结果:
5.4 训练算法随机梯度上升
- 梯度上升算法在每次更新回归系数时都需要遍历整个数据集,该方法在处理100个左右的数据集时尚可,但如果有数十亿样本和成千上万的特征,那么该方法的计算复杂度就太高了。一种改进方法是一次仅用一个样本点来更新回归系数,该方法称为随机梯度上升算法。
代码实现
def stocGradAscent0(dataMatrix, classLabels):
m, n = shape(dataMatrix) # 获取数据集的行数和列数
alpha = 0.01 # 设置步长为0.01
weights = ones(n)# 初始化权值向量各个参数为1.0
# print(weights)
for i in range(m): # 循环m次,每次选取数据集一个样本更新参数
h = sigmoid(sum(dataMatrix[i] * weights)) # 计算当前样本的sigmoid函数值
error = classLabels[i] - h # 计算当前样本的残差(代替梯度)
weights = weights + alpha * error * dataMatrix[i] # 更新权值参数
return weights
运行结果:
与梯度下降算法的区别:
- 后者的变量h和误差error都是向量,而前者全部是数值。
- 前者没有矩阵的转换过程,所有变量的数据类型都是NumPy数组。
5.5 改进的随机梯度上升算法
代码实现:
def stocGradAscent1(dataMatrix, classLabels, numInter = 150):
# 将数据集列表转化为numpy数组
# dataMat = array(dataMatrix)
m, n = shape(dataMatrix) # 获取数据集的行数和列数
weights = ones(n) # 初始化权值参数向量每个维度均为1
for j in range(numInter): # 迭代次数
dataIndex =list( range(m)) # 获取数据集行下表列表
for i in range(m): # 对所有对象的遍历
alpha = 4 / (1.0 + j + i) + 0.01 # 对步长的调整,添加了固定步长0.01
randIndex = int(random.uniform(0, len(dataIndex))) # 随机生成一个整数,介于0到m
h = sigmoid(sum(dataMatrix[randIndex] * weights)) # 对随机选择的对象计算类别的数值(回归系数值)
error = classLabels[randIndex] - h # 根据实际类型与计算类型值的误差,损失函数
weights = weights + alpha * error * dataMatrix[randIndex] # 每步weights的改变值,权值更新
del(dataIndex[randIndex]) # 去除已经选择过的对象,避免下次选中
return weights
实现效果:
该程序在原有基础上做了三点优化:
- alpha每次迭代的时候都会调整,4/(1+j+i)+0.01,使得alpha随着迭代次数不断减小,但永远不会减小到0。这样做的原因是为了保证在多次迭代之后新数据仍然具有一定的影响。
- 随机选取样本来更新回归系数,这种做法会减少周期性波动
- 增加了迭代参数的设置。默认为150次,如果给定,将会按照新的参数值进行迭代。
六 示例:从疝气病症预测病马的死亡率
6.1 代码实现
"""
函数说明:分类函数
Parameters:
inx:输入的特征向量
weights:回归系数
Returns:
类别标签
"""
def classifyVector(inx,weights):
prob = sigmoid(sum(inx*weights))
if prob > 0.5:
return 1.0
else:
return 0.0
"""
函数说明:使用Logistic分类器进行预测
Parameters:
无
Returns:
无
"""
def colicTest():
frTrain = open("python/ch05/horseColicTraining.txt")
frTest = open("python/ch05/horseColicTest.txt")
trainList = [];trainLabels = []
for line in frTrain.readlines():
lineArr = []
currLine = line.strip().split('\t')
for i in range(21):
lineArr.append(float(currLine[i]))
trainList.append(lineArr)
trainLabels.append(float(currLine[21]))
weight = stocGradAscent1(np.array(trainList),trainLabels,500)
errorCount = 0;numTest = 0.0
for line in frTest.readlines():
lineArr = []
numTest += 1.0
currLine = line.strip().split('\t')
for i in range(21):
lineArr.append(float(currLine[i])) #处理测试集数据
if int(classifyVector (np.array(lineArr), weight)) != int( currLine[21] ):
errorCount += 1
errorRate = (float(errorCount) / numTest) *100
print("单次分类测试的错误率为:%.2f%%" % errorRate)
return errorRate
"""
函数说明:计算迭代numtests次后的错误率
"""
def multiTest():
numtests = 10;errorsum = 0.0
for k in range(numtests):
errorsum += colicTest()
print("%d次分类测试的平均错误率为:%.2f%%" % (numtests, errorsum/float(numtests)))
6.2 实现效果
七 小结
对于逻辑回归而言,其实际就是一个函数套上一个回归模型,对于输入有输出,所以只需要对其中的参数进行估计即可,对于参数估计问题,涉及到代价函数,而逻辑回归的代价函数可以用最大似然估计得到。