逻辑回归的来龙去脉
逻辑回归面试常考点
简单介绍
逻辑回归假设数据服从伯努利分布,通过极大化似然函数的方法,运用梯度下降来求解参数,来达到将数据二分类的目的。
假设
- 假设1:数据服从伯努利分布
- 假设2:假设模型的输出值是样本为正例的概率。
- 假设3:线性-自变量和logit函数是线性关系
损失函数
- 损失函数:对数损失函数
具体解释:
-
逻辑回归的极大似然推导结果:
-
逻辑回归通过对数损失函数推出的表达式:
-
结论:对数损失函数与上面的极大似然估计的对数似然函数本质上是等价的。所以逻辑回归直接采用对数损失函数来求参数,实际上与采用极大似然估计来求参数是一致的。
-
为什么不用极大似然直接求出参数的显示解呢?因为求不出来?是的!所以用了梯度下降法
-
为什么线性回归用的是平方损失函数?而不是1次方 3次方呢?防止正负误差抵消。
逻辑回归的求解方法
由于该极大似然函数无法直接求解,我们一般通过对该函数进行梯度下降来不断逼近最优解。
具体有以下三种方式:
-
批梯度下降(BGD)。批梯度下降会获得全局最优解,缺点是在更新每个参数的时候需要遍历所有的数据,计算量会很大,并且会有很多的冗余计算,导致的结果是当数据量大的时候,每个参数的更新都会很慢。
-
随机梯度下降(SGD)。随机梯度下降是以高方差频繁更新,优点是使得sgd会跳到新的和潜在更好的局部最优解,缺点是使得收敛到局部最优解的过程更加的复杂。
-
小批量梯度下降(MGBD)。小批量梯度下降结合了sgd和batch gd的优点,每次更新的时候使用n个样本。减少了参数更新的次数,可以达到更加稳定收敛结果,一般在深度学习当中我们采用这种方法。
批梯度下降
优缺点:
- 优点:全局最优解;易于并行实现;
- 缺点:当样本数目很多时,训练过程会很慢。
示意图:
随机梯度下降(SGD)
优缺点
- 优点:训练速度快;
- 缺点:准确度下降,并不是全局最优;不易于并行实现。
示意图:
小批量梯度下降(MGBD)
逻辑回归是如何分类的
逻辑回归作为一个回归(也就是y值是连续的),如何应用到分类上去呢。y值确实是一个连续的变量。逻辑回归的做法是划定一个阈值,y值大于这个阈值的是一类,y值小于这个阈值的是另外一类。阈值具体如何调整根据实际情况选择。一般会选择0.5做为阈值来划分。
逻辑回归优缺点
优点:
- 形式简单,模型的可解释性非常好。从特征的权重可以看到不同的特征对最后结果的影响,某个特征的权重值比较高,那么这个特征最后对结果的影响会比较大。
- 模型效果不错。在工程上是可以接受的(作为baseline),如果特征工程做的好,效果不会太差,并且特征工程可以大家并行开发,大大加快开发的速度。
- 训练速度较快。分类的时候,计算量仅仅只和特征的数目相关。并且逻辑回归的分布式优化sgd发展比较成熟,训练的速度可以通过堆机器进一步提高,这样我们可以在短时间内迭代好几个版本的模型。
- 资源占用小,尤其是内存。因为只需要存储各个维度的特征值。
- 方便输出结果调整。逻辑回归可以很方便的得到最后的分类结果,因为输出的是每个样本的概率分数,我们可以很容易的对这些概率分数进行cutoff,也就是划分阈值(大于某个阈值的是一类,小于某个阈值的是一类)。
缺点:
- 准确率并不是很高。因为形式非常的简单(非常类似线性模型),很难去拟合数据的真实分布。
- 很难处理数据不平衡的问题。举个例子:如果我们对于一个正负样本非常不平衡的问题比如正负样本比 10000:1.我们把所有- - 样本都预测为正也能使损失函数的值比较小。但是作为一个分类器,它对正负样本的区分能力不会很好。
- 处理非线性数据较麻烦。逻辑回归在不引入其他方法的情况下,只能处理线性可分的数据,或者进一步说,处理二分类的问题 。
- 逻辑回归本身无法筛选特征。有时候,我们会用gbdt来筛选特征,然后再上逻辑回归。
逻辑回归优化算法实现
总结:
- 本部分代码参考《机器学习实战》
- 找函数的最大值采用梯度上升法,即每一步沿着梯度的方向前进更新参数
- 找函数的最小值采用梯度下降法,即每一步沿着负梯度方向前进更新参数
- 上述两种方法本质一样 无法是求极值的函数前面加一个负号而已。
批量梯度上升法
from numpy import *
import matplotlib.pyplot as plt
import numpy as np
def loadDataSet():
dataMat = []; labelMat = []
fr = open('./data/ch05/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]))
return dataMat,labelMat
def sigmoid(inX):
return 1.0/(1+exp(-inX))
def gradAscent(dataMatIn, classLabels):
loss_num = []
# dataMatIn:训练样本
# classLabels:类别
dataMatrix = mat(dataMatIn) #convert to NumPy matrix 100 * 3
labelMat = mat(classLabels).transpose() #convert to NumPy matrix
# 最后转置了一下 由行向量 1 * 100 转为 列向量100 * 1
m,n = shape(dataMatrix) # m = 100 n = 3
alpha = 0.001
maxCycles = 500 # 迭代次数
weights = ones((n,1)) # 3行1列 全为1 初始权重 3*1
for k in range(maxCycles): #heavy on matrix operations
h = sigmoid(dataMatrix*weights) #matrix mult 100*3 3*1 = 100*1
error = (labelMat - h) #vector subtraction 100*1
weights = weights + alpha * dataMatrix.transpose()* error #matrix mult
print(('第 %d 次迭代,平均误差为: %.2f, 权重为 %s' % (k+1, mean(error), weights)))
# 3*1 + alpha * (3*100) * (100*1) = (3*1)
loss_num.append(mean(error))
return weights, loss_num
def plotBestFit(weights):
dataMat,labelMat=loadDataSet()
dataArr = array(dataMat) # 100 * 3
n = shape(dataArr)[0] # n=100
# 分两个分别储存正负样本
xcord1 = []; ycord1 = []
xcord2 = []; ycord2 = []
for i in range(n):
if int(labelMat[i])== 1:
xcord1.append(dataArr[i,1]); ycord1.append(dataArr[i,2]) # 忽略第0个因为是自己构造的 只考虑x1 和 x2
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='s')
ax.scatter(xcord2, ycord2, s=30, c='green')
x = arange(-3.0, 3.0, 0.1)
y = ((-weights[0]-weights[1]*x)/weights[2]).transpose()
ax.plot(x, y)
plt.xlabel('X1'); plt.ylabel('X2');
plt.show()
plt.close()
# 定义一个函数绘制 折线图 就是损失error的变化图
def plot_error(errors):
# x = range(len(errors))
# y = np.linspace(0,1,100)
# for i in range(len(errors)):
# print(errors)
plt.plot(errors)
plt.ylim(ymin = 0)
plt.xlabel('epochs')
plt.ylabel('loss')
plt.show()
# 第一次运行
dataMat,labelMat = loadDataSet()
weights_,error_ = gradAscent(dataMat, labelMat)
weights = weights_
plotBestFit(weights)
plot_error(error_)
第 1 次迭代,平均误差为: -0.36, 权重为 [[0.96358575]
[0.98285296]
[0.48828325]]
第 2 次迭代,平均误差为: -0.35, 权重为 [[ 0.92843587]
[ 0.96479391]
[-0.01257165]]
第 3 次迭代,平均误差为: -0.14, 权重为 [[ 0.9144239 ]
[ 0.93854799]
[-0.31973253]]
第 4 次迭代,平均误差为: 0.21, 权重为 [[ 0.93587268]
[ 0.92455021]
[-0.28140145]]
......
第 495 次迭代,平均误差为: 0.03, 权重为 [[ 4.10693568]
[ 0.47859984]
[-0.61468163]]
第 496 次迭代,平均误差为: 0.03, 权重为 [[ 4.1103868 ]
[ 0.47889557]
[-0.61511611]]
第 497 次迭代,平均误差为: 0.03, 权重为 [[ 4.11383312]
[ 0.47919077]
[-0.61555001]]
第 498 次迭代,平均误差为: 0.03, 权重为 [[ 4.11727467]
[ 0.47948546]
[-0.61598332]]
第 499 次迭代,平均误差为: 0.03, 权重为 [[ 4.12071146]
[ 0.47977963]
[-0.61641605]]
第 500 次迭代,平均误差为: 0.03, 权重为 [[ 4.12414349]
[ 0.48007329]
[-0.6168482 ]]
/Users/apple/anaconda3/lib/python3.6/site-packages/matplotlib/axes/_base.py:3604: MatplotlibDeprecationWarning:
The `ymin` argument was deprecated in Matplotlib 3.0 and will be removed in 3.2. Use `bottom` instead.
alternative='`bottom`', obj_type='argument')
随机梯度上升
- 改进方式:之前是基于向量,现在是基于值。然后没有上面梯度上升迭代次数那么多,因为迭代次数就是行数
上述算法,要进行maxCycles次循环,每次循环中矩阵会有mn次乘法计算,所以时间复杂度(开销)是maxCyclesm*n,当数据量较大时,时间复杂度就会很大。因此,可以是用随机梯度上升法来进行算法改进。
随机梯度上升法的思想是:每次只使用一个数据样本点来更新回归系数。这样就大大减小计算开销。
def loadDataSet():
dataMat = []; labelMat = []
fr = open('./data/ch05/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]))
return dataMat,labelMat
def sigmoid(inX):
return 1.0/(1+exp(-inX))
def stocGradAscent0(dataMatrix, classLabels):
loss = []
m,n = shape(dataMatrix) # 100*3
alpha = 0.01
weights = ones(n) #initialize to all ones (1*3)
for i in range(m):
h = sigmoid(sum(dataMatrix[i]*weights)) # (1*3)*(1*3) = (1*3) 对应元素相乘
error = classLabels[i] - h
weights = weights + alpha * error * np.array(dataMatrix[i]) #(1*3)+(1*3)
loss.append(error)
print(('第 %d 次迭代,平均误差为: %.2f, 权重为 %s' % (i+1, (error), weights)))
return weights, loss
def plotBestFit(weights):
import matplotlib.pyplot as plt
dataMat,labelMat=loadDataSet()
dataArr = array(dataMat) # 100 * 3
n = shape(dataArr)[0] # n=100
xcord1 = []; ycord1 = []
xcord2 = []; ycord2 = []
for i in range(n):
if int(labelMat[i])== 1:
xcord1.append(dataArr[i,1]); ycord1.append(dataArr[i,2]) # 忽略第0个因为是自己构造的 只考虑x1 和 x2
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='s')
ax.scatter(xcord2, ycord2, s=30, c='green')
x = arange(-3.0, 3.0, 0.1)
y = ((-weights[0]-weights[1]*x)/weights[2]).transpose()
ax.plot(x, y)
plt.xlabel('X1'); plt.ylabel('X2');
plt.show()
# 定义一个函数绘制 折线图 就是损失error的变化图
def plot_loss(errors):
# x = range(len(errors))
# y = np.linspace(0,1,100)
# for i in range(len(errors)):
# print(errors)
plt.plot(errors)
# plt.ylim(ymin = 0)
plt.xlabel('epochs')
plt.ylabel('loss')
plt.show()
# 第二次运行
dataMat,labelMat = loadDataSet()
weights, loss = stocGradAscent0(dataMat, labelMat)
plot_loss(loss)
plotBestFit(weights)
第 1 次迭代,平均误差为: -1.00, 权重为 [0.99 1.00017612 0.8594694 ]
第 2 次迭代,平均误差为: 0.03, 权重为 [0.9902656 0.99980544 0.86070777]
第 3 次迭代,平均误差为: -1.00, 权重为 [0.98029386 1.00730575 0.79550635]
第 4 次迭代,平均误差为: -1.00, 权重为 [0.97034167 1.02046625 0.72431976]
第 5 次迭代,平均误差为: -1.00, 权重为 [0.96034249 1.01623296 0.61378205]
......
第 95 次迭代,平均误差为: 0.50, 权重为 [ 1.0111106 0.87769103 -0.33034234]
第 96 次迭代,平均误差为: 0.32, 权重为 [ 1.0142936 0.87984905 -0.32220448]
第 97 次迭代,平均误差为: -0.15, 权重为 [ 1.0128275 0.87873284 -0.3378827 ]
第 98 次迭代,平均误差为: 0.72, 权重为 [ 1.02002151 0.86313055 -0.33684941]
第 99 次迭代,平均误差为: -0.28, 权重为 [ 1.01718876 0.85919696 -0.36331297]
第 100 次迭代,平均误差为: -0.02, 权重为 [ 1.01702007 0.85914348 -0.36579921]
使用改进的随机梯度上升
改进方式:
- 先迭代,然后再对每一行的,同时不失按照顺序选行,而是随机选,也是不基于向量的,而是基于值。
- 调整了alpha,保证多次迭代后新数据仍然具有一定影响力
def stocGradAscent1(dataMatrix, classLabels, numIter=150):
loss = []
m,n = shape(dataMatrix)
weights = ones(n) #initialize to all ones
for j in range(numIter): # 150
dataIndex = list(range(m))
for i in range(m): # 100
# 迭代了alpha 上面是固定不变的
alpha = 4/(1.0+j+i)+0.0001 #apha decreases with iteration, does not
randIndex = int(random.uniform(0,len(dataIndex)))#go to 0 because of the constant
# random.uniform是生成括号内两个数之间任意一个随机数
h = sigmoid(sum(dataMatrix[randIndex]*weights))
error = classLabels[randIndex] - h
loss.append(error)
weights = weights + alpha * error * np.array(dataMatrix[randIndex])
del(dataIndex[randIndex]) # 删去改值 也就是只用一次!
return weights, loss
# 共有150 * 100 = 15000个loss
# 第三次运行
dataMat,labelMat = loadDataSet()
weights, loss = stocGradAscent1(dataMat, labelMat)
plotBestFit(weights)
plot_loss(loss)
问题:为啥随机梯度下降和批量梯度下降都能起到一样的效果呢?
SGD就是每次只用到了一个样本,噪声较批量梯度下降更大,使得SGD并不是每次迭代都向着整体最优化方向,在解空间的搜索过程看起来很盲目。但是大体上是往着最优值方向移动。
实际案例分析
def classifyVector(inX, weights):
# 最终用来分类的模型
prob = sigmoid(sum(inX*weights))
if prob > 0.5:
return 1.0
else:
return 0.0
def colicTest():
frTrain = open('./data/ch05/horseColicTraining.txt')
frTest = open('./data/ch05/horseColicTest.txt')
trainingSet = []; trainingLabels = []
for line in frTrain.readlines():
currLine = line.strip().split('\t')
lineArr =[]
for i in range(21): # 前21个是x 22个是y
lineArr.append(float(currLine[i]))
trainingSet.append(lineArr)
trainingLabels.append(float(currLine[21]))
trainWeights, loss = stocGradAscent1(array(trainingSet), trainingLabels, 500)
errorCount = 0; numTestVec = 0.0
test_y = []
pre_y = []
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(array(lineArr), trainWeights))!= int(currLine[21]):
errorCount += 1 # 分错的样本
test_y.append(currLine[21])
pre_y.append(int(classifyVector(array(lineArr), trainWeights)))
# 计算分错率;
errorRate = (float(errorCount)/numTestVec)
# print("the error rate of this test is: %.2f" % errorRate)
# 输出预测结果 lineArr为测试集的X
# model_pre = classifyVector(array(lineArr), trainWeights)
return errorRate, test_y, pre_y
def multiTest():
# 多次检验 看训练出来的参数是否稳定
numTests = 10; errorSum=0.0
for k in range(numTests):
errorRate = colicTest()[0]
errorSum += errorRate
print('第 %d 次训练模型的测试误差为: %.2f ' % (k+1, errorRate))
print("after %d iterations the average error rate is: %f" % (numTests, errorSum/float(numTests)))
# errorRate = colicTest()[0]
# pre = colicTest()[1]
multiTest()
/Users/apple/anaconda3/lib/python3.6/site-packages/ipykernel/__main__.py:11: RuntimeWarning: overflow encountered in exp
第 1 次训练模型的测试误差为: 0.37
第 2 次训练模型的测试误差为: 0.37
第 3 次训练模型的测试误差为: 0.34
第 4 次训练模型的测试误差为: 0.28
第 5 次训练模型的测试误差为: 0.40
第 6 次训练模型的测试误差为: 0.30
第 7 次训练模型的测试误差为: 0.40
第 8 次训练模型的测试误差为: 0.39
第 9 次训练模型的测试误差为: 0.37
第 10 次训练模型的测试误差为: 0.28
after 10 iterations the average error rate is: 0.352239
查看预测结果和实际情况的一个对比
errorRate, test_y, pre_y = colicTest()
print('实际数据为: ', [int(x) for x in test_y])
print('预测结果为: ', pre_y)
/Users/apple/anaconda3/lib/python3.6/site-packages/ipykernel/__main__.py:11: RuntimeWarning: overflow encountered in exp
实际数据为: [1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0]
预测结果为: [1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0]
使用sklearn去分析
读入数据
import pandas as pd
train = pd.read_table('./data/ch05/horseColicTraining.txt', header=None)
print(train.shape)
test = pd.read_table('./data/ch05/horseColicTest.txt', header=None)
print(test.shape)
(299, 22)
(67, 22)
train.head()
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | ... | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 2.0 | 1.0 | 38.5 | 66.0 | 28.0 | 3.0 | 3.0 | 0.0 | 2.0 | 5.0 | ... | 0.0 | 0.0 | 0.0 | 3.0 | 5.0 | 45.0 | 8.4 | 0.0 | 0.0 | 0.0 |
1 | 1.0 | 1.0 | 39.2 | 88.0 | 20.0 | 0.0 | 0.0 | 4.0 | 1.0 | 3.0 | ... | 0.0 | 0.0 | 0.0 | 4.0 | 2.0 | 50.0 | 85.0 | 2.0 | 2.0 | 0.0 |
2 | 2.0 | 1.0 | 38.3 | 40.0 | 24.0 | 1.0 | 1.0 | 3.0 | 1.0 | 3.0 | ... | 0.0 | 0.0 | 0.0 | 1.0 | 1.0 | 33.0 | 6.7 | 0.0 | 0.0 | 1.0 |
3 | 1.0 | 9.0 | 39.1 | 164.0 | 84.0 | 4.0 | 1.0 | 6.0 | 2.0 | 2.0 | ... | 1.0 | 2.0 | 5.0 | 3.0 | 0.0 | 48.0 | 7.2 | 3.0 | 5.3 | 0.0 |
4 | 2.0 | 1.0 | 37.3 | 104.0 | 35.0 | 0.0 | 0.0 | 6.0 | 2.0 | 0.0 | ... | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 74.0 | 7.4 | 0.0 | 0.0 | 0.0 |
5 rows × 22 columns
查看是否有缺失
train.isnull().sum() # 训练集无缺失
0 0
1 0
2 0
3 0
4 0
5 0
6 0
7 0
8 0
9 0
10 0
11 0
12 0
13 0
14 0
15 0
16 0
17 0
18 0
19 0
20 0
21 0
dtype: int64
test.isnull().sum() # 测试集变量也无缺失
0 0
1 0
2 0
3 0
4 0
5 0
6 0
7 0
8 0
9 0
10 0
11 0
12 0
13 0
14 0
15 0
16 0
17 0
18 0
19 0
20 0
21 0
dtype: int64
为建模准备数据 分x y
训练集
X = train.drop([21], axis=1).values
y = train[21].values.reshape(-1,1)
print(X.shape, y.shape)
# y = train[:, 21:22].values()
(299, 21) (299, 1)
测试集
test_need = test.drop([21], axis=1).values
test_label = test[21].values.reshape(-1,1)
print(test_need.shape)
print(test_label.shape)
(67, 21)
(67, 1)
开始建模
import numpy as np
from sklearn.linear_model import LogisticRegression
import warnings
warnings.filterwarnings('ignore')
# 初始建模
lr = LogisticRegression(C=1000, random_state = 23)
print(lr)
lr.fit(X, y)
pre = lr.predict(test_need)
LogisticRegression(C=1000, class_weight=None, dual=False, fit_intercept=True,
intercept_scaling=1, max_iter=100, multi_class='warn',
n_jobs=None, penalty='l2', random_state=23, solver='warn',
tol=0.0001, verbose=0, warm_start=False)
# 方法1 计算分错率
def CalError(test_label, pre):
error_num = 0
for i in range(len(pre)):
if test_label[i] != pre[i]:
error_num += 1
error_ratio = error_num/len(pre)
print('分错率为:%.2f' % error_ratio)
return error_ratio
CalError(test_label, pre)
分错率为:0.28
0.2835820895522388
# 方法2 直接在test的DataFrame里去考虑
test['pre_label'] = pre
test.head()
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | ... | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | pre_label | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 2 | 1 | 38.5 | 54 | 20 | 0 | 1 | 2 | 2 | 3 | ... | 2 | 5.9 | 0 | 2 | 42.0 | 6.3 | 0 | 0.0 | 1 | 1.0 |
1 | 2 | 1 | 37.6 | 48 | 36 | 0 | 0 | 1 | 1 | 0 | ... | 0 | 0.0 | 0 | 0 | 44.0 | 6.3 | 1 | 5.0 | 1 | 1.0 |
2 | 1 | 1 | 37.7 | 44 | 28 | 0 | 4 | 3 | 2 | 5 | ... | 1 | 0.0 | 3 | 5 | 45.0 | 70.0 | 3 | 2.0 | 1 | 1.0 |
3 | 1 | 1 | 37.0 | 56 | 24 | 3 | 1 | 4 | 2 | 4 | ... | 1 | 0.0 | 0 | 0 | 35.0 | 61.0 | 3 | 2.0 | 0 | 1.0 |
4 | 2 | 1 | 38.0 | 42 | 12 | 3 | 0 | 3 | 1 | 1 | ... | 0 | 0.0 | 0 | 2 | 37.0 | 5.8 | 0 | 0.0 | 1 | 1.0 |
5 rows × 23 columns
# 对21列和最后一列进行求差取绝对值 再求和除以总的长度即可
(np.abs(test[21] - test['pre_label'])).sum() / len(test)
0.2835820895522388
调参部分
# 调参建模
lr_clf = LogisticRegression(C=0.2, # 正则化系数 越小表明正则化效果越强 默认为1
class_weight=None, # 用于标示分类模型中各种类型的权重 解决训练集样本不平衡问题
dual=False, # 对偶或原始方法 当样本数量>样本特征的时候,dual通常设置为False。
fit_intercept=True,
intercept_scaling=1, # 没啥用 默认就好
max_iter=100, # 算法收敛最大迭代次数
multi_class='ovr', # 多元逻辑回归时才有区别
n_jobs=1, # 并行数 1表示1个cpu的核运行
penalty='l2', # l2惩罚项
random_state=23,
solver='sag', # 随机梯度下降优化算法求解损失函数最小时的参数
tol=0.0001, # 停止求解的标准
verbose=0, # 日志冗长度,int类型。默认为0。就是不输出训练过程
warm_start=False # 热启动参数,bool类型。默认为False。
)
print(lr_clf)
lr_clf.fit(X, y)
pre = lr_clf.predict(test_need)
LogisticRegression(C=0.2, class_weight=None, dual=False, fit_intercept=True,
intercept_scaling=1, max_iter=100, multi_class='ovr', n_jobs=1,
penalty='l2', random_state=23, solver='sag', tol=0.0001,
verbose=0, warm_start=False)
CalError(test_label, pre)
分错率为:0.22
0.22388059701492538
总结: 仅手动调了下参数 就可以看到错误率有一个降低 后面陆续推出网格调参等方法!
详细参数解释见:
https://blog.csdn.net/jark_/article/details/78342644
https://www.cnblogs.com/pinard/p/6035872.html
个人总结一下主要的参数,可能会用到的:
-
C:正则化系数λ的倒数,float类型,默认为1.0。必须是正浮点型数。像SVM一样,越小的数值表示越强的正则化。
-
max_iter:迭代次数
-
penalty:惩罚项。默认l2正则。以下情况除外:
① l2下模型预测效果差。在调参时如果我们主要的目的只是为了解决过拟合,一般penalty选择L2正则化就够了。但是如果选择L2正则化发现还是过拟合,即预测效果差的时候,就可以考虑L1正则化。
② 希望实现特征的降维。另外,如果模型的特征非常多,我们希望一些不重要的特征系数归零,从而让模型系数稀疏化的话,也可以使用L1正则化。
同时penalty参数会影响solver参数的选取。
如果是L2正则化,那么solver4种可选的算法{‘newton-cg’, ‘lbfgs’, ‘liblinear’, ‘sag’}都可以选择。但是如果penalty是L1正则化的话,就只能选择‘liblinear’了。 -
solver:决定了我们对逻辑回归损失函数的优化方法。
- liblinear:坐标轴下降法
- lbfgs:拟牛顿法
- newton-cg:牛顿法家族的一种
- sag:随机梯度下降。
交叉验证
待补充
ROC曲线 定义一个方法
from sklearn.metrics import roc_curve, auc
from sklearn.model_selection import cross_val_predict
y_score = lr_clf.fit(X, y).decision_function(test_need)
# print(y_score)
fpr,tpr,threshold = roc_curve(test_label, y_score) ###计算真正率和假正率
roc_auc = auc(fpr,tpr) ###计算auc的值
# print(roc_auc)
plt.figure()
lw = 2
plt.figure(figsize=(10,10))
plt.plot(fpr, tpr, color='darkorange',
lw=lw, label='ROC curve (area = %0.2f)' % roc_auc) ###假正率为横坐标,真正率为纵坐标做曲线
plt.plot([0, 1], [0, 1], color='navy', lw=lw, linestyle='--')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('ROC Curve')
plt.legend(loc="lower right")
plt.show()
0.7297872340425532
<Figure size 432x288 with 0 Axes>
总结:
sklearn.metrics有roc_curve, auc两个函数,ROC曲线上的点主要就是通过这两个函数计算出来的。
- fpr, tpr, thresholds = roc_curve(y_test, scores)
其中y_test为测试集的结果,scores为模型预测的测试集得分(注意:通过decision_function(x_test)计算scores的值);fpr,tpr,thresholds 分别为假正率、真正率和阈值。(应该是不同阈值下的真正率和假正率)。
- roc_auc =auc(fpr, tpr)
roc_auc为计算的acu的值。
封装成函数
from sklearn.metrics import roc_curve, auc
from sklearn.model_selection import cross_val_predict
def plot_roc(train_x, train_y, test_x, test_y, clf):
y_score = clf.fit(train_x, train_y).decision_function(test_x)
# print(y_score)
fpr,tpr,threshold = roc_curve(test_y, y_score) ###计算真正率和假正率
roc_auc = auc(fpr,tpr) ###计算auc的值
# 开始绘图
plt.figure()
lw = 2
plt.figure(figsize=(10,10))
plt.plot(fpr, tpr, color='darkorange',
lw=lw, label='ROC curve (area = %0.2f)' % roc_auc) ###假正率为横坐标,真正率为纵坐标做曲线
plt.plot([0, 1], [0, 1], color='navy', lw=lw, linestyle='--')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('ROC Curve')
plt.legend(loc="lower right")
plt.show()
return roc_auc
plot_roc(X, y, test_need, test_label, lr_clf)
<Figure size 432x288 with 0 Axes>
0.7297872340425532
参考链接
- https://www.cnblogs.com/ModifyRong/p/7739955.html
- https://zhuanlan.zhihu.com/p/25765735
- https://blog.csdn.net/saltriver/article/details/63683092
- https://blog.csdn.net/jark_/article/details/78342644
- https://blog.csdn.net/lz_peter/article/details/78054914