(一)、什么是Logistic回归
Logistic回归(logistic regression)是统计学习中的经典分类方法,属于对数线性模型,所以也被称为对数几率回归。这里要注意,虽然带有回归的字眼,但是该模型是一种分类算法,Logistic回归是一种线性分类器,针对的是线性可分问题。利用logistic回归进行分类的主要思想是:根据现有的数据对分类边界线建立回归公式,以此进行分类。
(二)、基于Logistic回归和Sigmoid函数的分类
1.Sigmoid函数
我们想要的函数应该是能接受所有的输入,然后预测出类别。例如,在二类情况下,输出0和1。像单位阶跃函数就可以实现,但该函数在跳跃点上从0->1是瞬间跳跃,这个瞬间跳跃过程有时很难处理。于是另一个有类似性质且数学上更易处理的函数出现了-----sigmoid函数。
sigmoid函数表达式如下:
σ
(
z
)
=
1
1
+
e
−
z
σ(z) = \frac{1}{1+e^{-z}}
σ(z)=1+e−z1
下图给出了Sigmoid函数在不同坐标尺度下的两条曲线图,当x为0时,Sigmoid函数值为0.5。随着x的增大,对应的Sigmoid值将逼近于1;随着x的减小则逼近于0.当横坐标尺度较小时,曲线变化较为平滑,当尺度扩大到一定程度,Sigmoid看起来像一个阶跃函数。
那么对于Sigmoid函数计算公式,我们可以这样解释:为了实现logistic回归分类器,我们可以在每个特征上都乘以一个回归系数,然后把所有的结果值相加,将这个总和带入sigmoid函数中。进而得到一个范围在0-1之间的数值。最后设定一个阈值,在大于阈值时判定为1,否则判定为0。以上便是逻辑斯谛回归算法是思想,公式就是分类器的函数形式。
"""
Sigmoid函数实现
"""
def sigmoid(inX):
return 1.0 / (1 + np.exp(-inX))
2.Logistic回归的优缺点
优点:计算代价不高,易于理解和实现。
缺点:容易欠拟合,分类精度可能不高。
使用数据类型:数值型和标称型数据。
3.logistic回归的一般过程
1、收集数据:任何方式
2、准备数据:由于要计算距离,因此要求数据都是数值型的,另外结构化数据格式最佳。
3、分析数据:采用任一方是对数据进行分析
4、训练算法:大部分时间将用于训练,训练的目的为了找到最佳的分类回归系数
5、测试算法:一旦训练步骤完成,分类将会很快
6、使用算法:首先,我们需要输入一些数据,并将其转化成对应的结构化数值;接着基于训练好的回归系数就可以对这些数值进行简单的回归计算,判定它们属于哪一类别;在这之后,我们就可以在输出的类别上做一些其他的分析工作。
(三)、梯度上升最优算法python实现
1.梯度上升法的基本思想
要找到某函数的最大值,最好的方法是沿着该函数的梯度方向探寻。如果梯度记为▽,则函数f(x,y)的梯度由下式表示:
这个梯度意味着要沿x的方向移动:
∂
f
(
x
,
y
)
∂
x
\frac{\partial f(x, y)}{\partial x}
∂x∂f(x,y) 沿y的方向移动:
∂
f
(
x
,
y
)
∂
y
\frac{\partial f(x, y)}{\partial y}
∂y∂f(x,y)
其中,函数f(x,y)必须要在待计算的点上有定义并且可微。
2.普通梯度上升算法实现
梯度上升算法在每次更新回归系数(最优参数)时,都需要遍历整个数据集。
def gradAscent(dataMatIn, classLabels):
dataMatrix = np.mat(dataMatIn) #转换成numpy的mat
labelMat = np.mat(classLabels).transpose() #转换成numpy的mat,并进行转置
m, n = np.shape(dataMatrix) #返回dataMatrix的大小。m为行数,n为列数。
alpha = 0.01 #移动步长,也就是学习速率,控制更新的幅度。
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.getA(),weights_array #将矩阵转换为数组,返回权重数组
3.随机梯度上升算法实现
普通梯度上升算法在每次更新回归数据时都需要遍历整个数据集,该方法处理100个左右的数据集尚可,但如果有数十亿的样本和成千上万的特征,那么该方法的复杂度就太高了。因此就引入了随机梯度算法。相较于普通的梯度上升算法来说,随机梯度算法一次仅用一个样本点来更新回归系数。
算法代码如下:
def gradAscent(dataMatIn, classLabels):
dataMatrix = np.mat(dataMatIn) #转换成numpy的mat
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))
for k in range(maxCycles):
h = sigmoid(dataMatrix * weights) #梯度上升矢量化公式
error = labelMat - h
weights = weights + alpha * dataMatrix.transpose() * error
return weights.getA() #将矩阵转换为数组,返回权重数组
alpha在每次迭代的时候都会调整,并且,虽然alpha会随着迭代次数不断减小,但永远不会减小到0,因为这里还存在一个常数项。必须这样做的原因是为了保证在多次迭代之后新数据仍然具有一定的影响。如果需要处理的问题是动态变化的,那么可以适当加大上述常数项,来确保新的值获得更大的回归系数。另一点值得注意的是,在降低alpha的函数中,alpha每次减少1/(j+i),其中j是迭代次数,i是样本点的下标。第二个改进的地方在于跟新回归系数(最优参数)时,只使用一个样本点,并且选择的样本点是随机的,每次迭代不使用已经用过的样本点。这样的方法,就有效地减少了计算量,并保证了回归效果。
3.随机梯度算法运行测试
这里用到了100条测试数据。
下图截选其中的部分数据:
完整代码:
import matplotlib.pyplot as plt
import numpy as np
"""
函数说明:梯度上升算法测试函数
求函数f(x) = -x^2 + 4x的极大值
"""
def Gradient_Ascent_test():
def f_prime(x_old): #f(x)的导数
return -2 * x_old + 4
x_old = -1 #初始值,给一个小于x_new的值
x_new = 0 #梯度上升算法初始值,即从(0,0)开始
alpha = 0.01#步长,也就是学习速率,控制更新的幅度
presision = 0.00000001 #精度,也就是更新阈值
while abs(x_new - x_old) > presision:
x_old = x_new
x_new = x_old + alpha * f_prime(x_old)#上面提到的公式
print(x_new)#打印最终求解的极值近似值
"""
函数说明:加载数据
"""
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]))
fr.close()
return dataMat, labelMat
"""
函数说明:sigmoid函数
"""
def sigmoid(inX):
return 1.0 / (1 + np.exp(-inX))
"""
函数说明:梯度上升算法
"""
def gradAscent1(dataMatrix, classLabels, numIter=150):
m,n = np.shape(dataMatrix) #返回dataMatrix的大小。m为行数,n为列数。
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.01 #降低alpha的大小,每次减小1/(j+i)。
randIndex = int(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
"""
函数说明:绘制数据集
"""
def plotBestFit(weights):
dataMat, labelMat = loadDataSet()
dataArr = np.array(dataMat)
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])
else:
xcord2.append(dataArr[i,1]); ycord2.append(dataArr[i,2])
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)
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()
if __name__ == '__main__':
dataMat, labelMat = loadDataSet()
weights = gradAscent(np.array(dataMat), labelMat)
plotBestFit(weights)
运行截图:
可以看到随机梯度上升法在经过500次的迭代后的到了一条Logistic回归最佳拟合直线。
(四)、Logistic实战之预测糖尿病的发作
1.数据收集
数据收集来源:http://archive.ics.uci.edu/ml/index.php
这里的数据包含了768个样本和8个属性特征。然后通过0,1来判断用户是否会换上糖尿病。
数据部分内容展示:
2.核心算法
这里通过分类函数把样本数据分成五份,然后采用随机梯度下降法(随机下降与随机上升法核心内容一样)对这五份样本进行训练,并通过sigmoid函数对训练好的样本进行分类。
def predict(row, coefficients):
yhat = coefficients[0]
for i in range(len(row)-1):
yhat += coefficients[i + 1] * row[i]
return 1.0 / (1.0 + exp(-yhat))
def coefficients_sgd(train, l_rate, n_epoch):
coef = [0.0 for i in range(len(train[0]))]
for epoch in range(n_epoch):
for row in train:
yhat = predict(row, coef)
error = row[-1] - yhat
coef[0] = coef[0] + l_rate * error * yhat * (1.0 - yhat)
for i in range(len(row)-1):
coef[i + 1] = coef[i + 1] + l_rate * error * yhat * (1.0 - yhat) * row[i]
return coef
def logistic_regression(train, test, l_rate, n_epoch):
predictions = list()
coef = coefficients_sgd(train, l_rate, n_epoch)
for row in test:
yhat = predict(row, coef)
yhat = round(yhat)
predictions.append(yhat)
return(predictions)
3.测试运行
这里把学习率设为0.4,训练次数设置为训练1000次,seed(1)是为了保证五份样本每次执行都能产生同一个随机数,防止结果偏差。 n_folds = 5被用于交叉验证,给每次迭代 768/5 = 条记录来进行评估。
if __name__ == '__main__':
seed(1)#每一次执行本文件时都能产生同一个随机数
filename = 'pima-indians-diabetes.csv'
dataset = load_csv(filename)
for i in range(len(dataset[0])):
str_column_to_float(dataset, i)
minmax = dataset_minmax(dataset)
normalize_dataset(dataset, minmax)
n_folds = 5
l_rate = 0.4
n_epoch = 1000
scores = evaluate_algorithm(dataset, logistic_regression, n_folds, l_rate, n_epoch)
print('五份样本的正确率: %s' % scores)
print('总样本平均正确率: %.3f%%' % (sum(scores)/float(len(scores))))
运行这个样本,为 5 次交叉验证的每一次 输出 一个分数,然后输出这五个分数的平均数。我们可以看到平均正确率为77.988%。
运行结果
(四)、总结
Logistic回归的目的是寻找一个非线性函数Sigmoid的最佳拟合参数,求解过程可以由最优化算法完成。
改进的一些最优化算法,比如sag。它可以在新数据到来时就完成参数更新,而不需要重新读取整个数据集来进行批量处理。
参考资料
[1]: 《机器学习实战》Peter Harrington 著
[2]: https://blog.youkuaiyun.com/c406495762/article/details/77851973