欢迎关注本人的微信公众号AI_Engine
def build_train_model(x, y):
length_sample = len(x)
simple_weight = np.ones(shape=(length_sample,))
# simple_weight[:length_sample/2] = 2
model = LogisticRegression(penalty='l2', C=1.0, solver='sag', max_iter=3000, multi_class='ovr')
model.fit(x, y, simple_weight)
return model
def cross_val(x, y):
skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=Config.seed)
model = LogisticRegression(penalty='l2', C=1.0, solver='sag')
result = cross_val_score(model, x, y, cv=skf)
print(result)
def report_evaluation_metrics(y_true, y_pred):
accuracy = accuracy_score(y_true=y_true, y_pred=y_pred)
precision = precision_score(y_true=y_true, y_pred=y_pred, labels=[0, 1], pos_label=1)
recall = recall_score(y_true=y_true, y_pred=y_pred, labels=[0, 1], pos_label=1)
average_precision = average_precision_score(y_true=y_true, y_score=y_pred)
f1 = f1_score(y_true=y_true, y_pred=y_pred, labels=[0, 1], pos_label=1)
conf_matrix = confusion_matrix(y_true, y_pred)
print('Accuracy: {0:0.2f}'.format(accuracy))
print('Precision: {0:0.2f}'.format(precision))
print('Recall: {0:0.2f}'.format(recall))
print('Average precision-recall score: {0:0.2f}'.format(average_precision))
print('F1: {0:0.2f}'.format(f1))
print("confusion matrix:", conf_matrix)
总结:
- liblinear适用于小数据集,而sag和saga适用于大数据集因为速度更快,支持二分类,L1,L2。
- 对于多分类问题,只有newton-cg,sag,saga和lbfgs能够处理多项损失,而liblinear得先把一种类别作为一个类别,剩余的所有类别作为另外一个类别。依次遍历所有类别进行分类。
- newton-cg、sag、lbfgs这三种优化算法时都需要损失函数的一阶或者二阶连续导数,因此不能用于没有连续导数的L1正则化,只能用于L2正则化。而liblinear和saga通吃L1正则化和L2正则化。
- sag每次仅仅使用了部分样本进行梯度迭代,所以当样本量少的时候不要选择它,而如果样本量非常大,比如大于10万,sag是第一选择。但是sag不能用于L1正则化,所以当你有大量的样本,又需要L1正则化的话就要自己做取舍了。要么通过对样本采样来降低样本量,要么回到L2正则化。
从上面的描述,大家可能觉得,既然newton-cg, lbfgs和sag这么多限制,如果不是大样本,我们选择liblinear不就行了嘛!错,因为liblinear也有自己的弱点!我们知道,逻辑回归有二元逻辑回归和多元逻辑回归。对于多元逻辑回归常见的有one-vs-rest(OvR)和many-vs-many(MvM)两种。而MvM一般比OvR分类相对准确一些。郁闷的是liblinear只支持OvR,不支持MvM,这样如果我们需要相对精确的多元逻辑回归时,就不能选择liblinear了。也意味着如果我们需要相对精确的多元逻辑回归不能使用L1正则化了
OvR和MvM的不同: OvR的思想很简单,无论你是多少元逻辑回归,我们都可以看做二元逻辑回归。具体做法是,对于第K类的分类决策,我们把所有第K类的样本作为正例,除了第K类样本以外的所有样本都作为负例。然后在上面做二元逻辑回归,得到第K类的分类模型,其他类的分类模型获得以此类推。而MvM则相对复杂,这里举MvM的特例one-vs-one(OvO)作讲解。如果模型有T类,我们每次在所有的T类样本里面选择两类样本出来,不妨记为T1类和T2类,把所有的输出为T1和T2的样本放在一起,把T1作为正例,T2作为负例,进行二元逻辑回归,得到模型参数。我们一共需要T(T-1)/2次分类。可以看出OvR相对简单,但分类效果相对略差(这里指大多数样本分布情况,某些样本分布下OvR可能更好)。而MvM分类相对精确,但是分类速度没有OvR快。如果选择了ovr,则4种损失函数的优化方法liblinear,newton-cg,lbfgs和sag都可以选择。但是如果选择了multinomial,则只能选择newton-cg, lbfgs和sag了。
正则化是用来防止模型过拟合的过程,常用的有L1正则化和L2正则化两种选项,分别通过在损失函数后加上参数向量的L1范式和L2范式的倍数来实现。其中L1范数表现为参数向量中的每个参数的绝对值之和,L2范数表现为参数向量中的每个参数的平方和的开方值。
其中 是我们之前提过的损失函数,C是用来控制正则化程度的超参数,n是方程中特征的总数,也是方程中参数的总数,j代表每个参数。在这里,J要大于等于1,是因为我们的参数向量中第一个参数是 我们的截距,它通常是不参与正则化的。
参数 | 说明 |
---|---|
penalty | 可以输入"l1"或"l2"来指定使用哪一种正则化方式,不填写默认"l2"。 注意,若选择"l1"正则化,参数solver仅能够使用”liblinear",若使用“l2”正则化,参数solver中 所有的求解方式都可以使用。 |
C | C正则化强度的倒数,必须是一个大于0的浮点数,不填写默认1.0,即默认一倍正则项。 C越小,对损失函数的惩罚越重,正则化的效力越强,参数会逐渐被压缩得越来越小。在很多书籍和博客的原理讲解中, 1/c被写作λ。 |
L1正则化和L2正则化虽然都可以控制过拟合,但它们的效果并不相同。当正则化强度逐渐增大(即C逐渐变小), 参数 的取值会逐渐变小,但L1正则化会将参数压缩为0,L2正则化只会让参数尽量小,不会取到0。
在L1正则化在逐渐加强的过程中,携带信息量小的、对模型贡献不大的特征的参数,会比携带大量信息的、对模型有巨大贡献的特征的参数更快地变成0,所以L1正则化本质是一个特征选择的过程,掌管了参数的“稀疏性”。L1正则化越强,参数向量中就越多的参数为0,参数就越稀疏,选出来的特征就越少,以此来防止过拟合。因此,如果特征量很大,数据维度很高,我们会倾向于使用L1正则化。由于L1正则化的这个性质,逻辑回归的特征选择可以由Embedded嵌入法来完成。
L2正则化在加强的过程中,会尽量让每个特征对模型都有一些小的贡献,但携带信息少,对模型贡献不大的特征的参数会非常接近于0。通常来说,如果我们的主要目的只是为了防止过拟合,选择L2正则化就足够了。但是如果选择L2正则化后还是过拟合,模型在未知数据集上的效果表现很差,就可以考虑L1正则化。
coef_ :逻辑回归的重要属性coef_,查看每个特征所对应的参数。
import matplotlib.pyplot as plt
from sklearn.metrics import accuracy_score
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
data = load_breast_cancer()
x = data.data
y = data.target
data.data.shape
(569, 30)
from sklearn.linear_model import LogisticRegression
l1_lr = LogisticRegression(penalty='l1', solver='liblinear', C=0.5, max_iter=1000)
l1_lr.fit(x, y)
l1_lr.coef_
array([[ 4.01278447, 0.03213562, -0.13907204, -0.01621722, 0. ,
0. , 0. , 0. , 0. , 0. ,
0. , 0.50554115, 0. , -0.0712659 , 0. ,
0. , 0. , 0. , 0. , 0. ,
0. , -0.24609249, -0.12851748, -0.01441286, 0. ,
0. , -2.02967981, 0. , 0. , 0. ]])
n_iter_:损失函数梯度下降时迭代的最优次数
由于L1正则化会使得部分特征对应的参数为0,因此L1正则化可以用来做特征选择,结合嵌入法的模块SelectFromModel,我们可以很容易就筛选出让模型十分高效的特征。注意,此时我们的目的是尽量保留原数据上的信息,让模型在降维后的数据上的拟合效果保持优秀,因此我们不考虑训练集测试集的问题,把所有的数据都放入模型进行降维。
fullx = []
fsx = []
threshold = np.linspace(0,abs((LR_.fit(data.data,data.target).coef_)).max(),20)
k=0
for i in threshold:
X_embedded = SelectFromModel(LR_,threshold=i).fit_transform(data.data,data.target) fullx.append(cross_val_score(LR_,data.data,data.target,cv=5).mean()) fsx.append(cross_val_score(LR_,X_embedded,data.target,cv=5).mean()) print((threshold[k],X_embedded.shape[1]))
k+=1
plt.figure(figsize=(20,5)) plt.plot(threshold,fullx,label="full") plt.plot(threshold,fsx,label="feature selection") plt.xticks(threshold) plt.legend() plt.show()
为了让模型的拟合效果更好,在这里我们有两种调整方式:
1.调节SelectFromModel这个类中的参数threshold,这是嵌入法的阈值,表示删除所有参数的绝对值低于这个阈值的特征。现在threshold默认为None,所以SelectFromModel只根据L1正则化的结果来选择了特征,即选择了所有L1正则化后参数不为0的特征。我们此时,只要调整threshold的值(画出threshold的学习曲线),就可以观察不同的threshold下模型的效果如何变化。一旦调整threshold,就不是在使用L1正则化选择特征,而是使用模型的属性.coef_中生成的各个特征的系数来选择。coef_虽然返回的是特征的系数,但是系数的大小和决策树中的feature_ importances_以及降维算法中的可解释性方差explained_vairance_概念相似,其实都是衡量特征的重要程度和贡献度的,因此SelectFromModel中的参数threshold可以设置为coef_的阈值,即可以剔除系数小于threshold中输入的数字的所有特征。然而,这种方法其实是比较无效的,大家可以用学习曲线来跑一跑:当threshold越来越大,被删除的特征越来越多,模型的效果也越来越差,模型效果最好的情况下需要保证有17个以上的特征。实际上我画了细化的学习曲线,如果要保证模型的效果比降维前更好,我们需要保留25个特征,这对于现实情况来说,是一种无效的降维:需要30个指标来判断病情,和需要25个指标来判断病情,对医生来说区别不大。
2.第二种调整方法,是调逻辑回归的类LR,通过画C的学习曲线来实现:
(1)求得c值
def feature_select(x, y):
all_features = []
slt_features = []
c = np.arange(4.0, 5.5, 0.1)
for i in c:
lr = LogisticRegression(solver='liblinear', C=i, random_state=666)
all_features.append(cross_val_score(lr, x, y, cv=10).mean())
x_embedded = SelectFromModel(estimator=lr, norm_order=1).fit_transform(x, y)
slt_features.append(cross_val_score(lr, x_embedded, y, cv=10).mean())
c_value = c[slt_features.index(max(slt_features))]
print(max(slt_features), c[slt_features.index(max(slt_features))])
plt.figure(figsize=(20, 5))
plt.plot(c, all_features, label="all features")
plt.plot(c, slt_features, label="selected features")
plt.xticks(c)
plt.legend()
plt.show()
return c_value
def check_reslut(x, y, c_value):
lr = LogisticRegression(solver='liblinear', C=c_value, random_state=666)
score1 = cross_val_score(lr, x, y, cv=10).mean()
x_embedded = SelectFromModel(estimator=lr, norm_order=1).fit_transform(x, y)
score2 = cross_val_score(lr, x_embedded, y, cv=10).mean()
print(score1, score2)
0.9490849969751964 0.9545004753262466