传统机器学习(二)逻辑回归算法(二)
之前在传统机器学习(二)逻辑回归算法(一)中介绍了逻辑回归的原理、公式推导、手动python实现及sklearn工具包的使用详解等内容。继续对逻辑回归的使用细节进行介绍。
一、如何得到逻辑回归模型系数
1.1、一个简单的逻辑回归例子
已采集150组 乳腺癌数据:我们筛选四个特征和乳腺癌类别。
现在需要我们可以通过数据,训练一个逻辑回归,用于预测乳腺癌是良性还是恶性。
# 1. 数据归一化(用sklearn的逻辑回归一般要作数据归一化)
import numpy as np
# 加载乳腺癌数据集
from sklearn.datasets import load_breast_cancer
s_data = load_breast_cancer()
source_X = s_data.data[:,4:8] # 只选择4个变量进行建模
source_y = s_data.target
source_X[:5],source_y[:5]
(array([[0.1184 , 0.2776 , 0.3001 , 0.1471 ],
[0.08474, 0.07864, 0.0869 , 0.07017],
[0.1096 , 0.1599 , 0.1974 , 0.1279 ],
[0.1425 , 0.2839 , 0.2414 , 0.1052 ],
[0.1003 , 0.1328 , 0.198 , 0.1043 ]]),
array([0, 0, 0, 0, 0]))
# 数据归一化操作
x_min = source_X.min(axis=0)
x_max = source_X.max(axis=0)
X_norm = (source_X - x_min) / (x_max - x_min)
X_norm[:5]
array([[0.59375282, 0.7920373 , 0.70313964, 0.73111332],
[0.28987993, 0.18176799, 0.20360825, 0.34875746],
[0.51430893, 0.4310165 , 0.46251172, 0.63568588],
[0.81132075, 0.81136127, 0.5656045 , 0.52286282],
[0.43035118, 0.34789277, 0.46391753, 0.51838966]])
# 2. 用归一化数据训练逻辑回归模型
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression(random_state=0)
lr.fit(X_norm,source_y)
# 3. 用训练好的逻辑回归模型预测
pred_y = lr.predict(X_norm) # 预测类别
pred_prob_y = lr.predict_proba(X_norm) # 预测属于0类以及1类的概率
pred_y[:5],pred_prob_y[:5]
(array([0, 0, 0, 0, 0]),
array([[0.99459698, 0.00540302],
[0.51845413, 0.48154587],
[0.96369187, 0.03630813],
[0.96173328, 0.03826672],
[0.91511832, 0.08488168]]))
print('模型系数',lr.coef_[0]) # 注意:这里的模型系数是归一化后数据对应的模型系数
print('模型截距',lr.intercept_)
print('模型准确率',np.sum(pred_y == source_y) / len(source_y))
模型系数 [ 0.18783816 -1.11495824 -3.6342363 -7.06872446]
模型截距 [3.27959399]
模型准确率 0.8980667838312829
1.2、sklearn提取逻辑回归模型系数
sklearn在使用LogisticRegression方法训练好逻辑回归模型后,如何提取模型的系数?
模型对象clf里就已经存放了系数和截距,只需直接使用 clf.coef_[0]
和 clf.intercept
_提取 即可
#----数据加载------
data = load_breast_cancer()
X = data.data[:,4:8]
y = data.target
#-----训练模型--------------------
clf = LogisticRegression(random_state=0)
clf.fit(X,y)
#------模型预测-------------------------------
pred_y = clf.predict(X)
pred_prob_y = clf.predict_proba(X)[:,1]
#------------提取系数w与阈值b-----------------------
w = clf.coef_[0] # 模型系数(对应归一化数据)
b = clf.intercept_ # 模型阈值(对应归一化数据)
self_prob_y = 1/(1+np.exp(-(X.dot(w)+ b) )) # 用公式预测
self_prob_y[:5]
array([0.12545006, 0.64011229, 0.32410691, 0.19964474, 0.36904298])
#------------打印信息--------------------------
print("\n------模型参数-------")
print( "模型系数:",w)
print( "模型阈值:",b)
print("提取公式计算的概率与sklearn自带预测概率的最大误差", abs(pred_prob_y-self_prob_y).max())
------模型参数-------
模型系数: [-0.53024026 -3.48636783 -6.89132654 -4.37965412]
模型阈值: [1.80112869]
提取公式计算的概率与sklearn自带预测概率的最大误差 0.0
最终的模型表达式如下:
1.3、sklearn提取逻辑回归原始数据的模型系数
我们已经讲解如何得到模型的权重和截距。
然而,如果数据在建模之前做了归一化处理,那么从模型中提取到的系数是对应归一化数据的,在预测时,需要先对X作归一化,再用提取到的公式进行预测,这样显然不太优雅。
基于上述问题,我们希望直接提取对应原始数据的模型表达式,省去模型使用时归一化的步骤
#----数据加载------
data = load_breast_cancer()
X = data.data[:,4:8]
y = data.target
#----数据归一化------
xmin = X.min(axis=0)
xmax = X.max(axis=0)
X_norm=(X-xmin) / (xmax-xmin)
#-----训练模型--------------------
clf = LogisticRegression(random_state=0)
clf.fit(X_norm,y)
#------模型预测-------------------------------
pred_y = clf.predict(X_norm)
pred_prob_y = clf.predict_proba(X_norm)[:,1]
#------------提取系数w与阈值b-----------------------
w_norm = clf.coef_[0] # 模型系数(对应归一化数据)
b_norm = clf.intercept_ # 模型阈值(对应归一化数据)
w = w_norm/(xmax-xmin) # 模型系数(对应原始数据)
b = b_norm - (w_norm/(xmax - xmin)).dot(xmin) # 模型阈值(对应原始数据)
self_prob_y = 1/(1+np.exp(-(X.dot(w)+ b) )) # 用公式预测
#------------打印信息--------------------------
print("\n------对应归一化数据的模型参数-------")
print( "模型系数(对应归一化数据):",clf.coef_[0])
print( "模型截距(对应归一化数据):",clf.intercept_)
print("\n------对应原始数据的模型参数-------")
print("模型系数(对应原始数据):",w)
print("模型解决(对应原始数据):",b)
print("提取公式计算的概率与模型概率的最大误差", abs(pred_prob_y-self_prob_y).max())
------对应归一化数据的模型参数-------
模型系数(对应归一化数据): [ 0.18783816 -1.11495824 -3.6342363 -7.06872446]
模型截距(对应归一化数据): [3.27959399]
------对应原始数据的模型参数-------
模型系数(对应原始数据): [ 1.69574943 -3.4199075 -8.51508037 -35.13282533]
模型解决(对应原始数据): [3.25662451]
提取公式计算的概率与模型概率的最大误差 3.0531133177191805e-16
最终的模型表达式如下
二、逻辑回归过拟合以及归一化
1.1 逻辑回归为什么要归一化
逻辑回归需要归一化的主要原因是因为逻辑回归在求解过程中,使用的是梯度下降之类的算法。所以,我们探讨逻辑回归为什么要归一化,实际是探讨梯度下降法为什么要归一化
举例:求一组w1,w2。令 y = w1∗x1 + w2∗x2
最小,其中x1的范围为[-10000,10000],而x2的范围为[-1,1]
1.1.1 对于下降路径的影响
-
w1调整1单位时,对y1的影响范围为10000,而w2调整1单位只会影响1,即y对w1的调整非常敏感,因此,会极偏向调整w1,而忽略w2。因为在调整同等步长的情况下,w1对w2的影响会更明显。
-
整个过程会成为:先调整w1,直到w1几乎不可调,再调整w2。只要迭代足够多次,这倒也没有问题。但明显的,w1,w2逐个调整比起w1,w2一起调整需要更多步数。
1.1.2 对于步长设置的影响
-
我们需要设置每次调整的步长(学习率),对于w1,由于它的取值范围很多,微小的影响也对y影响很大,因此,我们可能设为0.00001,小步小步地调。对于w2,它对y的影响不是那么的大,我们只需设0.001可能就够了。
-
如此一来,对于不同变量,我们需要调不同的步长,如果我们设为0.000001,则在调w2时明显过小,而设为0.001对调整w1又明显过大。这就麻烦了。而如果我们将所有变量的数据范围归一化到[0,1],所有变量的步长就统一了。
1.1.3 对于正则化的影响
- sklearn的逻辑回归默认是加了L2正则项的,在数据没有归一化的情况下,输入变量的数量级如果差异很大,那么对应的、合理的权重w的数量级差异也会很大,但正则项会惩罚过大的权重
- 因此,那些本就应该很大的权重,就会被惩罚,从而最后输出的模型得不到最合理的解。这就需要数据归一化,正则化才能真正工作。
1.2 逻辑回归过拟合问题的分析及解决方案
只要变量处理好了,逻辑回归基本不会出现过拟合。在生产中我们入模变量基本都是质量较好的变量,因此过拟合基本不会发生。
1.2.1 逻辑回归过拟合的原因分析
-
(1) 单变量塑造能力:逻辑回归单个变量对y就是是S形函数,拟合能力很有限,这个不会产生过拟合。
-
(2) 变量个数:
变量个数是逻辑回归过拟合的主要来源
。 -
(3) 系数过大:由于逻辑回归是S型函数,不会造成数据点间的峰(谷)形态。因此,仅会造成误差放大问题。
因此,逻辑回归避免过拟合,重心应放在变量个数上,同时兼顾系数的合理性。
1.2.2 解决方案
- 控制变量个数
-
业务手段,把逻辑不成立的变量去除。
-
数据分析手段,相关性较大的变量只保留一个。
-
建模手段:采用逐步回归。
逐步回归流程
1. 历遍所有变量,把拟合结果最好的变量作为第一轮选择变量。
2. 在第一轮选择变量的基础上,添加第二个变量,
遍历剩余变量,添加哪个变量能令拟合结果最好,就将其作为第二轮选择变量。
3. 在第二轮的基础上,添加第三个变量......
......
直到变量不再对拟合结果带来明显贡献时,就不再添加变量。
- 控制系数过大
控制系数过大,一般可以采用正则化,添加L1或L2项。
我们知道 ,加入正则项,实际是在“小化权重”与准确率间取平衡点,也即是会牺牲求解的准确率。
因此,我们一开始并不加入正则项,而是训练好模型后,如果各个变量的w有极大值,且与业务逻辑不符合时,再添加正则化重新训练,sklearn默认会加入L2正则项,我们需要在参数中关闭它。
三、对逻辑回归进行逐步回归式建模
3.1、数据加载及归一化
klearn中的逻辑回归使用类似梯度下降之类的算法进行求解,数据归一化对这类求解算法的速度与极大好处。
因此,在使用slearn调用逻辑回归前,需要先对数据进行归一化。
from sklearn.datasets import load_breast_cancer
from sklearn.linear_model import LogisticRegression
import warnings
warnings.filterwarnings('ignore')
import numpy as np
from sklearn import metrics
#----加载数据集
source_data = load_breast_cancer()
X = source_data.data
y = source_data.target
feature_names = source_data.feature_names
X.shape,feature_names
((569, 30),
array(['mean radius', 'mean texture', 'mean perimeter', 'mean area',
'mean smoothness', 'mean compactness', 'mean concavity',
'mean concave points', 'mean symmetry', 'mean fractal dimension',
'radius error', 'texture error', 'perimeter error', 'area error',
'smoothness error', 'compactness error', 'concavity error',
'concave points error', 'symmetry error',
'fractal dimension error', 'worst radius', 'worst texture',
'worst perimeter', 'worst area', 'worst smoothness',
'worst compactness', 'worst concavity', 'worst concave points',
'worst symmetry', 'worst fractal dimension'], dtype='<U23'))
#----数据集归一化
x_max = X.max(axis=0)
x_min = X.min(axis=0)
X_norm = (X - x_min) / (x_max - x_min)
3.2、逐步回归式建模
逻辑回归的过拟合主要来源于过多的特征。在保障模型效果的前提下,尽量选择更少的特征,使模型更简单,避免模型过拟合。
逐步回归可以起到这个作用
。
逐步回归流程
- 历遍所有变量,把拟合结果最好的变量作为第一轮选择变量。
- 在第一轮选择变量的基础上,添加第二个变量,
遍历剩余变量,添加哪个变量能令拟合结果最好,就将其作为第二轮选择变量。 - 在第二轮的基础上,添加第三个变量…
…
直到变量不再对拟合结果带来明显贡献时,就不再添加变量。
sklearn训练时默认会加L2正则项,以避免系数过大。但这样会牺牲一定的求解精确度,
因此我们最好把加L2和不加任何正则项都尝试一下(即参数penalty=’l2‘/‘none’),哪种效果更好,就用哪种。
#-----初始化模型--------------------
# 备注:penalty一开始训练时先用'none',训练出的系数不满意,再切换回l2
clf =LogisticRegression(penalty='l2')
#-----逐步回归挑选变量--------------------
select_var = [] # 已挑选的变量
var_pool = np.arange(X_norm.shape[1]) # 待挑选变量池
auc_rec = []
print("\n===========逐回步归过程===============")
while(len(var_pool)>0):
max_auc = 0
best_var = None
#---选出剩余变量中能带来最好效果的变量--------
for i in var_pool:
# -------将新变量和已选变量一起训练模型------
cur_x = X_norm[:,select_var+[i]] # 新变量和已选变量作为建模数据
clf.fit(cur_x,y) # 训练模型
pred_prob_y = clf.predict_proba(cur_x)[:,1] # 预测概率
cur_auc = metrics.roc_auc_score(y,pred_prob_y) # 计算AUC
# ------更新最佳变量---------------------------
if(cur_auc>max_auc):
max_auc = cur_auc
best_var = i
#-------检验新变量能否带来显著效果---------------------------
last_auc = auc_rec[-1] if len(auc_rec)>0 else 0.0001
valid = True if ((max_auc-last_auc)/last_auc>0.001) else False
# 如果有显著效果,则将该变量添加到已选变量
if(valid):
print("本轮最佳AUC:",max_auc,",本轮最佳变量:",feature_names[best_var])
auc_rec.append(max_auc)
select_var.append(best_var)
var_pool = var_pool[var_pool!=best_var]
# 如果没有显著效果,则停止添加变量
else:
print("本轮最佳AUC:",max_auc,",本轮最佳变量:",feature_names[best_var],',效果不明显,不再添加变量')
break
print("最终选用变量",len(select_var),"个:",feature_names[select_var])
===========逐回步归过程===============
本轮最佳AUC: 0.9754505575815231 ,本轮最佳变量: worst perimeter
本轮最佳AUC: 0.9849637968394904 ,本轮最佳变量: worst smoothness
本轮最佳AUC: 0.9903017810897944 ,本轮最佳变量: mean texture
本轮最佳AUC: 0.9925611754135617 ,本轮最佳变量: radius error
本轮最佳AUC: 0.9934200095132393 ,本轮最佳变量: worst radius ,效果不明显,不再添加变量
最终选用变量 4 个: ['worst perimeter' 'worst smoothness' 'mean texture' 'radius error']
3.3、模型训练及评估
逻辑回归一般选用AUC作为评价指标。
一般来说,AUC达到0.65模型才开始有区分度,达到0.70以上才开始有价值(这只是一个大概,具体看业务)。
#------模型训练--------------------------------
clf.fit(X_norm[:,select_var],y)
#------模型预测-------------------------------
pred_y = clf.predict(X_norm[:,select_var])
pred_prob_y = clf.predict_proba(X_norm[:,select_var])[:,1]
auc = metrics.roc_auc_score(y,pred_prob_y)
print("\n============建模结果=================")
print("选用变量",len(select_var),"个:",feature_names[select_var])
print("AUC:",auc)
============建模结果=================
选用变量 4 个: ['worst perimeter' 'worst smoothness' 'mean texture' 'radius error']
AUC: 0.9925611754135617
3.4、逻辑回归模型系数及截距
#------------提取系数w与阈值b-----------------------
w_norm = clf.coef_[0] # 模型系数(对应归一化数据)
b_norm = clf.intercept_ # 模型阈值(对应归一化数据)
w = w_norm / (x_max[select_var]-x_min[select_var]) # 模型系数(对应原始数据)
b = b_norm - (w_norm/(x_max[select_var] - x_min[select_var])).dot(x_min[select_var]) # 模型阈值(对应原始数据)
self_prob_y = 1 / ( 1 + np.exp(-(X[:,select_var].dot(w)+ b) )) # 用公式预测
#------------打印信息--------------------------
print("\n=========对应归一化数据的模型参数========")
print( "模型系数(对应归一化数据):",clf.coef_[0])
print( "模型截距(对应归一化数据):",clf.intercept_)
print("\n=========对应原始数据的模型参数==========")
print("模型系数(对应原始数据):",w)
print("模型截距(对应原始数据):",b)
print("提取公式计算的概率与模型概率的最大误差", abs(pred_prob_y-self_prob_y).max())
=========对应归一化数据的模型参数========
模型系数(对应归一化数据): [-8.33335391 -3.88143621 -3.00969575 -3.04460052]
模型截距(对应归一化数据): [5.83665519]
=========对应原始数据的模型参数==========
模型系数(对应原始数据): [ -0.04150283 -25.63188408 -0.10178207 -1.10251694]
模型截距(对应原始数据): [10.86426872]
提取公式计算的概率与模型概率的最大误差 6.106226635438361e-16
四、利用逻辑回归进行多分类
4.1 sklearn逻辑回归多分类ovr与multinomial
sklearn中逻辑回归做多分类时有两种选择ovr与multinomial,ovr对每个类别都建立一个模型,每个模型只预测属于某个类别的概率,最后哪个类别的预测概率最高,就是哪个类别。而multinomial则是使用逻辑回归的推广形式softmax回归。
4.1.1 ovr多分类模型
设有K个类别,则建立K个模型,每个模型以类 i 作为1类,其它作为0类。最后得到K个模型,每个模型预测属于i类的概率。
简单的说,OVR模型就是有K个类别,就建K个二分类逻辑回归模型,最后比较哪个类别的预测概率最高,就判为哪一类。
4.1.2 multinomial
sklearn中multinomial用的是softmax回归,属于逻辑回归的推广。多类别输出是一个one-hot编码,它是一个只有0/1的向量,例如有3类,标签为{0,1,2},则类别 0 的输出为[1,0,0], 类别 1 的输出为[0,1,0],类别 2 的输出为[0,0,1]
4.2 鸢尾花数据集多分类案例
from sklearn.linear_model import LogisticRegression
import numpy as np
from sklearn.datasets import load_iris
#----数据加载------
iris = load_iris()
X = iris.data
y = iris.target
#----数据归一化------
xmin = X.min(axis=0)
xmax = X.max(axis=0)
X_norm = (X-xmin)/(xmax-xmin)
#-----训练模型--------------------
clf = LogisticRegression(random_state=0,multi_class='multinomial')
clf.fit(X_norm,y)
#------模型预测-------------------------------
pred_y = clf.predict(X_norm)
pred_prob_y = clf.predict_proba(X_norm)
#------------提取系数w与阈值b-----------------------
w_norm = clf.coef_ # 模型系数(对应归一化数据)
b_norm = clf.intercept_ # 模型阈值(对应归一化数据)
w = w_norm/(xmax-xmin) # 模型系数(对应原始数据)
b = b_norm - (w_norm/(xmax - xmin)).dot(xmin) # 模型阈值(对应原始数据)
# ------------用公式预测------------------------------
self_prob_y = 1/(1+np.exp(-(X.dot(w.T)+ b) ))
self_pred_y = self_prob_y.argmax(axis=1)
#------------打印信息--------------------------
print("\n------模型参数-------")
print( "模型系数:",w)
print( "模型阈值:",b)
print("\n-----验证准确性-------")
print("提取公式计算的概率与sklearn自带预测概率的最大误差", abs(pred_prob_y-self_prob_y).max())
------模型参数-------
模型系数: [[-0.3902573 0.65000868 -0.48485313 -1.16130665]
[ 0.07259933 -0.59884596 0.0709145 -0.19934931]
[ 0.31765797 -0.05116272 0.41393863 1.36065596]]
模型阈值: [ 3.18277053 2.06368594 -5.24645647]
-----验证准确性-------
提取公式计算的概率与sklearn自带预测概率的最大误差 0.6257491128195443
最终的模型公式
多分类预测说明
P中哪个的值大,就是哪一类。例如P(X)=[0.3 0.2 0.5],0.5最大,所以X属于0、1、2类中的类别2。
[本文整理自《老饼讲解-机器学习》ml.bbbdata.com]