一.Logistic回归
1.1Logistic基本概念
Logistic回归:是统计学习中的经典分类方法,属于对数线性模型,所以也被称为对数几率回归。虽然是叫做回归,但其实这是一种分类算法,Logistic回归是一种线性分类器,针对的是线性可分问题。
1.2Logistic回归的主要思想
logistic回归进行分类的主要思想:根据现有的数据对分类边界线建立回归公式,以此进行分类。
二.Sigmoid函数
2.1Sigmoid函数简单了解
1)sigmoid函数,又称为Logistic 曲线,是一个线性函数,由统计学家皮埃尔·弗朗索瓦·韦吕勒发明,其函数表达式如下:
2)函数图像如下:
从图像上可以看出,对于 Sigmoid函数而言,x = 0 是一个有着特殊意义坐标,越靠近 0 和越远离 0 会出现两种截然不同的情况:任何y > 0.5 的数据可以划分到 “1”类中;而y < 0.5 的数据可以划分到 “0”类。因此可以利用Sigmoid函数做解决二分类问题的分类器。如果想要 Sigmoid分类器预测准确,那么 x 的取值距离 0 越远越好,这样结果值才能无限逼近于 0 或者 1。
三.基于最优化方法的最佳回归系数确定
3.1问题引出
通过上面的描述,我们发现,为了实现logistic回归分类,我们可以在每一个特征上乘以一个回归系数,然后把相乘的结果累加,代入sigmoid函数中就可以得到一个在[ 0,1 ]范围的值y,我们就可以通过判别y的值来实现分类。(例:y > 0.5为1类,y < 0.5为0类 )
公式描述:
z:分类特征与回归系数计算值
w:回归系数矩阵(最佳系数) - ->待求
x:分类的输入数据
b:转置向量(常数) - ->待求
带入z可得:
y:样本x作为正例的可能性
1 - y:样本x作为反例的可能性
y /(1 - y):几率,反映了x作为正例的相对可能性
y的函数有分式和指数e的,所以我们采用对数函数对其进行一种映射:
p(y = 0|x):表示反例的概率
P(y = 1|x):表示正例的概率
通过上述的计算结果,我们发现,这变成了求最佳系数w以及转置常数b。
3.2极大似然估计
原理:通过若干次试验,观察其结果,利用试验结果得到某个参数值能够使样本出现的概率为最大,则称为极大似然估计。即利用已知的样本结果,反推最有可能(最大概率)导致这样结果的参数值。总之,极大似然估计的原理就是通过最大化观察到的样本结果出现的概率来估计模型的参数值。
举例理解:假设我们有一枚硬币,想要估计这枚硬币正面朝上的概率。我们进行了100次抛硬币的实验,其中有60次是正面朝上的结果。我们可以使用极大似然估计来估计这枚硬币正面朝上的概率。
在这个例子中,我们假设硬币正面朝上的概率为p,那么抛100次硬币,观察到60次正面朝上的结果的概率可以用二项分布来表示。我们可以通过计算不同概率值下观察到60次正面朝上结果的概率,找到使得这个概率最大的概率值,这个值就是极大似然估计得到的硬币正面朝上的概率。
假设:
3.3梯度上升法
基本思想:要找到某函数的最大值,最好的方法是沿着该函数的梯度方向探寻。如果梯度记为▽,则函数f(x,y)的梯度由下式表示:
这个梯度意味着要沿x的方向移动:
沿y的方向移动:
其中,函数f(x,y) 必须要在待计算的点上有定义并且可微。如下图:
说明:如上图,梯度上升算法到达每个点后都会重新估计移动的方向。从P0开始,计算完该点的梯度,函数就根据梯度移动到下一点P1。在P1点,梯度再次被重新计算,并沿新的梯度方向移动到P2。如此循环迭代,直到满足停止条件。迭代的过程中,梯度算子总是保证我们能选取到最佳的移动方向。
梯度上升算法沿梯度方向移动了一步。可以看到,梯度算子总是指向函数值增长最快的方向。这里所说的是移动方向,而未提到移动量的大小。该量值称为步长,记做α。用向 量来表示的话,梯度上升算法的迭代公式如下:
上述公式将一直被迭代执行,直至达到某个停止条件为止,比如迭代次数达到某个指定值或算 法达到某个可以允许的误差范围。
3.4梯度下降法
按梯度上升的反方向迭代公式即可
大同小异,梯度上升算法用来求函数的最大值,而梯度下降算法用来求函数的最小值
四.利用Logistic分类器进行分类测试
4.1数据准备
有50个样本点,每个数据有两个特征维度,将第一列看做x1的值,第二列看做x2的值,第三列作为分类的标签
4.2编写代码绘制数据集分布情况
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
from sklearn.preprocessing import StandardScaler
"""
函数说明:加载数据
Returns:
dataMat - 数据列表
labelMat - 标签列表
"""
dataMat = [] #创建数据列表
labelMat = []
def loadDataSet():
data = pd.read_excel(r'C:\Users\86158\Desktop\机器学习\第六讲 Logistic回归\Logistic数据准备.xls')
# 将数据转换为列表
data_list = data.values.tolist()
for line in data_list: # line is a list
dataMat.append([1.0, float(line[0]), float(line[1])]) # 添加数据
labelMat.append(int(line[2])) # 添加标签
return dataMat, labelMat
"""
函数说明:绘制数据集
"""
def plotDataSet():
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) #添加subplot
ax.scatter(xcord1, ycord1, s = 20, c = 'black', marker = 's',alpha=.5)#绘制正样本
ax.scatter(xcord2, ycord2, s = 20, c = 'red',alpha=.5) #绘制负样本
plt.title('DataSet') #绘制title
plt.xlabel('x1'); plt.ylabel('x2') #绘制label
plt.show() #显示
if __name__ == '__main__':
plotDataSet()
结果图展示:
如图所示:假设Sigmoid函数的输入记为z,那么,即可将数据分割开。其中,x0为全是1的向量,x1为数据集的第一列数据,x2为数据集的第二列数据。因此,我们需要求出这个方程未知的参数w0,w1,w2,也就是我们需要求的回归系数(最优参数)。
4.3训练算法:使用梯度上升找到最佳参数
def sigmoid(inX):
return 1.0 / (1 + np.exp(-inX))
"""
函数说明:梯度上升算法
Parameters:
dataMatIn - 数据集
classLabels - 数据标签
Returns:
weights.getA() - 求得的权重数组(最优参数)
"""
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
# print(weights.getA())
return weights.getA() #将矩阵转换为数组,返回权重数组
weights = gradAscent(dataMat, labelMat)
length = len(labelMat)
weights,labelMat , length,labelMat
结果展示:
我们已经求出了未知的参数w0=-0.30611313,w1=-1.82931075,w2=1.75714795,通过刚刚求解出的参数,我们就可以确定不同类别数据之间的分隔线,画出决策边界。
4.4分析数据:画出决策边界
画出数据集和Logistic回归最佳拟合直线的函数
# 绘制数据集和Logistic回归最佳拟合直线
def plotBestFit(weights):
# dataMat, labelMat = loadDataSet() # 加载数据集,标签
dataArr = np.array(dataMat) # 转换成numPy的数组
print(dataArr)
n = np.shape(dataArr)[0] # 获取数据总数
print(n)
xcord1 = []; ycord1 = [] # 存放正样本
xcord2 = []; ycord2 = [] # 存放负样本
for i in range(length): # 依据数据集的标签来对数据进行分类
if int(labelMat[i]) == 1: # 数据的标签为1,表示为正样本
xcord1.append(dataArr[i, 1]); ycord1.append(dataArr[i, 2])
else: # 否则,若数据的标签不为1,表示为负样本
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(-5.0, 20.0, 0.1) # x区间
y = (-weights[0] - weights[1] * x) / weights[2] # 最佳拟合直线
ax.plot(x, y)
plt.title('BestFit') # 标题
plt.xlabel('X1'); plt.ylabel('X2') # x,y轴的标签
plt.show()
if __name__ == '__main__':
weights = gradAscent(dataMat, labelMat)
plotBestFit(weights)
结果展示:
从图中可以看出,几乎全部被分开,拟合的很好
4.5训练算法:随机梯度上升
4.5.1随机梯度上升算法
# 随机梯度上升算法
def stocGradAscent0(dataMatrix, classLabels): # dataMatIn数据集、classLabels数据标签
m, n = np.shape(dataMatrix) # 获取数据集矩阵的大小,m为行数,n为列数
alpha = 0.01 # 目标移动的步长
weights = np.ones(n) # 所以初始化为1
for i in range(m): # 重复矩阵运算
h = sigmoid(sum(dataMatrix[i] * weights)) # 矩阵相乘,计算sigmoid函数
error = classLabels[i] - h # 计算误差
weights = weights + alpha * error * dataMatrix[i] # 矩阵相乘,更新权重
return weights
# 运行测试代码
dataMat, labelMat = loadDataSet()
weigths = stocGradAscent0(np.array(dataMat), labelMat)
plotBestFit(weigths)
print("w0: %f, w1: %f, W2: %f" % (weigths[0], weigths[1], weigths[2]))
结果展示
拟合的也还行
4.5.2改进的随机梯度上升算法
# 改进的随机梯度上升算法
def stocGradAscent1(dataMatrix, classLabels, numIter=150): # dataMatIn数据集、classLabels数据标签、numIter迭代次数
m, n = np.shape(dataMatrix) # 获取数据集矩阵的大小,m为行数,n为列数
weights = np.ones(n) # 所以初始化为1
for j in range(numIter):
dataIndex = list(range(m)) # 创建数据下标列表
for i in range(m):
alpha = 4 / (1.0 + j + i) + 0.0001 # apha目标移动的步长,每次迭代调整
randIndex = int(random.uniform(0, len(dataIndex))) # 随机选取更新样本
h = sigmoid(sum(dataMatrix[randIndex] * weights)) # 矩阵相乘,计算sigmoid函数
error = classLabels[randIndex] - h # 计算误差
weights = weights + alpha * error * dataMatrix[randIndex] # 矩阵相乘,更新权重
del (dataIndex[randIndex]) # 删除已使用过的样本
return weights
# 测试
dataMat, labelMat = loadDataSet()
weigths = stocGradAscent1(np.array(dataMat), labelMat)
plotBestFit(weigths)
print("w0: %f, w1: %f, W2: %f" % (weigths[0], weigths[1], weigths[2]))
图像:
改进点:
1)alpha = 4 / (1.0 + j + i) + 0.0001,alpha即学习率在每次迭代的时候都会调整,虽然alpha会随着迭代次数不断减小,但永远不会减小到0。这样做的目的是为了保证在多次迭代之后新数据仍然具有一定的影响。
2) randIndex = int(random.uniform(0, len(dataIndex))),这里通过随机选取样本来更新回归系数,每一次迭代都是未用过的样本点。这种方法将减少周期性的波动,计算量减少了,而且从上述的运行结果可以看出,回归效果也挺好。
五.用Logistic回归判断银行客户是否违约
5.1数据集准备
我是自己随机生成1000条数据,700个用于训练,300个用于测试
具体数据是这样的,训练集有700个样本点,测试集有300个样本点。每个数据有两个特征维度,将第一列看做x1信用记录,为负值则表示违约的次数,正值则表示按时守约的次数,第二列看做x2财务信息,单位为万,表示在银行的存款,第三列作为分类的标签,1表示不会违约,0表示违约
5.2代码实现
sigmoid函数:
def sigmoid(inX):
return 1.0 / (1 + np.exp(-inX))
改进的随机梯度上升算法获得最佳回归系数:
# 改进的随机梯度上升算法
def stocGradAscent1(dataMatrix, classLabels, numIter=150): # dataMatIn数据集、classLabels数据标签、numIter迭代次数
m, n = np.shape(dataMatrix) # 获取数据集矩阵的大小,m为行数,n为列数
weights = np.ones(n) # 所以初始化为1
for j in range(numIter):
dataIndex = list(range(m)) # 创建数据下标列表
for i in range(m):
alpha = 4 / (1.0 + j + i) + 0.0001 # apha目标移动的步长,每次迭代调整
randIndex = int(random.uniform(0, len(dataIndex))) # 随机选取更新样本
h = sigmoid(sum(dataMatrix[randIndex] * weights)) # 矩阵相乘,计算sigmoid函数
error = classLabels[randIndex] - h # 计算误差
weights = weights + alpha * error * dataMatrix[randIndex] # 矩阵相乘,更新权重
del (dataIndex[randIndex]) # 删除已使用过的样本
return weights
# 测试
dataMat, labelMat = loadDataSet()
weigths = stocGradAscent1(np.array(dataMat), labelMat)
plotBestFit(weigths)
print("w0: %f, w1: %f, W2: %f" % (weigths[0], weigths[1], weigths[2]))
判断分类0或1
def classifyVector(inX, weights):
prob = sigmoid(sum(inX*weights))
if prob > 0.5: return 1.0
else: return 0.0
准备数据
def colicTest():
trainingSet = []; trainingLabels = []
data_train = pd.read_excel(r'C:\Users\86158\Desktop\机器学习\第六讲 Logistic回归\训练集.xls')
data_list_train = data_train.values.tolist()
for line in data_list_train:
trainingSet.append([1.0, float(line[0]), float(line[1])])
trainingLabels.append(int(line[2]))
# 数据归一化
scaler = StandardScaler()
trainingSet = scaler.fit_transform(trainingSet)
trainWeights = stocGradAscent1(np.array(trainingSet), trainingLabels, 500)
errorCount = 0; numTestVec = 0.0
data_test = pd.read_excel(r'C:\Users\86158\Desktop\机器学习\第六讲 Logistic回归\测试集.xls')
data_list_test = data_test.values.tolist()
for line in data_list_test:
numTestVec += 1.0
lineArr = [1.0, float(line[0]), float(line[1])]
# 注意:对测试数据也要进行同样的归一化处理
lineArr = scaler.transform([lineArr])[0]
if int(classifyVector(np.array(lineArr), trainWeights))!= int(line[2]):
errorCount += 1
errorRate = (float(errorCount)/numTestVec) * 100
print("测试集错误率为: %.2f%%" % errorRate)
return errorRate
计算出平均错误率
def multiTest():
numTests = 10; errorSum = 0.0
for k in range(numTests):
errorSum += colicTest()
print(f"经过{numTests} 次迭代,平均错误率为:{errorSum/float(numTests)}")
结果展示:
我截取了前十个预测的和真实值的比较,第一个是预测的结果,第二个是真实的标签
发现结果还行,0.106的错误率,因为我的数据集比较小,且具有随机性,0.106还不错
六.小结
Logistic回归的一般过程:
- 收集数据:采用任意方法收集数据
- 准备数据:由于需要进行距离计算,因此要求数据类型为数值型。另外,结构化数据格式则最佳。
- 分析数据:采用任意方法对数据进行分析。
- 训练算法:大部分时间将用于训练,训练的目的是为了找到最佳的分类回归系数。
- 测试算法:一旦训练步骤完成,分类将会很快。
- 使用算法:首先,我们需要输入一些数据,并将其转换成对应的结构化数值;接着,基于训练好的回归系数就可以对这些数值进行简单的回归计算,判定它们属于哪个类别;在这之后,我们就可以在输出的类别上做一些其他分析工作。
logistic的优缺点:
实现简单,容易理解。但是我们也容易发现其也容易发生欠拟合,并且在确定回归系数时的梯度算法容易导致梯度爆炸,导致欠拟合效果。