目录
一.名为“回归”的分类器
二.逻辑回归的优点
三.sklearn中的逻辑回归
四.linear_model.LogisticRegression
五.penalty & C(正则化)
六.逻辑回归中的特征工程
1.业务选择
2.PCA和SVD一般不用
3.统计方法可以使用,但不是非常必要
4.高效的嵌入法embedded
1)调节SelectFromModel这个类中的参数threshold
2)调逻辑回归的类LR_,通过画C的学习曲线来实现
3)比较麻烦的系数累加法
4)简单快速的包装法
七.梯度下降:重要参数max_iter
八. 二元回归与多元回归:重要参数solver & multi_class
九.样本不平衡与参数class_weight
十.逻辑回归代码实现
一.名为“回归”的分类器
线性回归的任务,就是构造一个预测函数来映射输入的特征矩阵x和标签值y的线性关系,而构造预测函数的核心就是找出模型的参数,著名的最小二乘法就是用来求解线性回归中参数的数学方法。
通过函数z,线性回归使用输入的特征矩阵X来输出一组连续型的标签值y_pred,以完成各种预测连续型变量的任务(比如预测产品销量,预测股价等等)。那如果我们的标签是离散型变量,尤其是,如果是满足0-1分布的离散型变量,我们要怎么办呢?我们可以通过引入联系函数(link function),将线性回归方程z变换为g(z),并且令g(z)的值分布在(0,1)之间,且当g(z)接近0时样本的标签为类别0,当g(z)接近1时样本的标签为类别1,这样就得到了一个分类模型。而这个联系函数对于逻辑回归来说,就是Sigmoid函数:
Sigmoid函数是一个S型的函数,当自变量z趋近正无穷时,因变量g(z)趋近于1,而当z趋近负无穷时,g(z)趋近于0,它能够将任何实数映射到(0,1)区间,使其可用于将任意值函数转换为更适合二分类的函数。
因为这个性质,Sigmoid函数也被当作是归一化的一种方法,与我们之前学过的MinMaxSclaer同理,是属于数据预处理中的“缩放”功能,可以将数据压缩到[0,1]之内。区别在于,MinMaxScaler归一化之后,是可以取到0和1的(最大值归一化后就是1,最小值归一化后就是0),但Sigmoid函数只是无限趋近于0和1。
线性回归中,于是我们将z带入,就得到了二元逻辑回归模型的一般形式:
不难发现,y(x)的形似几率取对数的本质其实就是我们的线性回归z,我们实际上是在对线性回归模型的预测结果取对数几率来让其的结果无限逼近0和1。因此,其对应的模型被称为”对数几率回归“(logistic Regression),也就是我们的逻辑回归,这个名为“回归”却是用来做分类工作的分类器。
二.逻辑回归的优点
1. 逻辑回归对线性关系的拟合效果好到丧心病狂,特征与标签之间的线性关系极强的数据,比如金融领域中的信用卡欺诈,评分卡制作,电商中的营销预测等等相关的数据,都是逻辑回归的强项。虽然现在有了梯度提升树GDBT,比逻辑回归效果更好,也被许多数据咨询公司启用,但逻辑回归在金融领域,尤其是银行业中的统治地位依然不可动摇(相对的,逻辑回归在非线性数据的效果很多时候比瞎猜还不如,所以如果你已经知道数据之间的联系是非线性的,千万不要迷信逻辑回归)
2. 逻辑回归计算快:对于线性数据,逻辑回归的拟合和计算都非常快,计算效率优于SVM和随机森林,亲测表示在大型数据上尤其能够看得出区别
3. 逻辑回归返回的分类结果不是固定的0,1,而是以小数形式呈现的类概率数字:我们因此可以把逻辑回归返回的结果当成连续型数据来利用。比如在评分卡制作时,我们不仅需要判断客户是否会违约,还需要给出确定的”信用分“,而这个信用分的计算就需要使用类概率计算出的对数几率,而决策树和随机森林这样的分类器,可以产出分类结果,却无法帮助我们计算分数(当然,在sklearn中,决策树也可以产生概率,使用接口predict_proba调用就好,但一般来说,正常的决策树没有这个功能)。
另外,逻辑回归还有抗噪能力强的优点。福布斯杂志在讨论逻辑回归的优点时,甚至有着“技术上来说,最佳模型的AUC面积低于0.8时,逻辑回归非常明显优于树模型”的说法。并且,逻辑回归在小数据集上表现更好,在大型的数据集上,树模型有着更好的表现。
三.sklearn中的逻辑回归
四.linear_model.LogisticRegression
二元逻辑回归的损失函数
在学习决策树和随机森林时,我们曾经提到过两种模型表现:在训练集上的表现,和在测试集上的表现。我们建模,是追求模型在测试集上的表现最优,因此模型的评估指标往往是用来衡量模型在测试集上的表现的。然而,逻辑回归有着基于训练数据求解参数的需求,并且希望训练出来的模型能够尽可能地拟合训练数据,即模型在训练集上的预测准确率越靠近100%越好。
因此,我们使用”损失函数“这个评估指标,来衡量参数为θ的模型拟合训练集时产生的信息损失的大小,并以此衡量参数θ的优劣。如果用一组参数建模后,模型在训练集上表现良好,那我们就说模型拟合过程中的损失很小,损失函数的值很小,这一组参数就优秀;相反,如果模型在训练集上表现糟糕,损失函数就会很大,模型就训练不足,效果较差,这一组参数也就比较差。即是说,我们在求解参数θ时,追求损失函数最小,让模型在训练数据上的拟合效果最优,即预测准确率尽量靠近100%。
衡量参数θ的优劣的评估指标,用来求解最优参数的工具
损失函数小,模型在训练集上表现优异,拟合充分,参数优秀
损失函数大,模型在训练集上表现差劲,拟合不足,参数糟糕
我们追求,能够让损失函数最小化的参数组合
注意:没有”求解参数“需求的模型没有损失函数,比如KNN,决策树
逻辑回归的损失函数是由极大似然估计推导出来的,具体结果可以写作:
在逻辑回归的本质函数y(x)里,特征矩阵x是自变量,参数是θ。但在损失函数中,参数θ是损失函数的自变量,x和y都是已知的特征矩阵和标签,相当于是损失函数的参数。
由于我们追求损失函数的最小值,让模型在训练集上表现最优,可能会引发另一个问题:如果模型在训练集上表示优秀,却在测试集上表现糟糕,模型就会过拟合。虽然逻辑回归和线性回归是天生欠拟合的模型,但我们还是需要控制过拟合的技术来帮助我们调整模型,对逻辑回归中过拟合的控制,通过正则化来实现。
五.penalty & C(正则化)
正则化是用来防止模型过拟合的过程,常用的有L1正则化和L2正则化两种选项,分别通过在损失函数后加上参数向量θ的L1范式和L2范式的倍数来实现。这个增加的范式,被称为“正则项”,也被称为"惩罚项"。损失函数改变,基于损失函数的最优化来求解的参数取值必然改变,我们以此来调节模型拟合的程度。其中L1范式表现为参数向量中的每个参数的绝对值之和,L2范数表现为参数向量中的每个参数的平方和的开方值。
penalty:可以输入"l1"或"l2"来指定使用哪一种正则化方式,不填写默认"l2"。 注意,若选择"l1"正则化,参数solver仅能够使用求解方式”liblinear"和"saga“,若使用“l2”正则化,参数solver中所有的求解方式都可以使用。
C:C正则化强度的倒数,必须是一个大于0的浮点数,不填写默认1.0,即默认正则项与损失函数的比值是1:1。C越小,损失函数会越小,模型对损失函数的惩罚越重,正则化的效力越强,参数θ会逐渐被压缩得越来越小。
L1正则化和L2正则化虽然都可以控制过拟合,但它们的效果并不相同。当正则化强度逐渐增大(即C逐渐变小),参数θ的取值会逐渐变小,但L1正则化会将参数压缩为0,L2正则化只会让参数尽量小,不会取到0。
在L1正则化在逐渐加强的过程中,携带信息量小的、对模型贡献不大的特征的参数,会比携带大量信息的、对模型有巨大贡献的特征的参数更快地变成0,所以L1正则化本质是一个特征选择的过程,掌管了参数的“稀疏性”。L1正则化越强,参数向量中就越多的参数为0,参数就越稀疏,选出来的特征就越少,以此来防止过拟合。因此,如果特征量很大,数据维度很高,我们会倾向于使用L1正则化。由于L1正则化的这个性质,逻辑回归的特征选择可以由Embedded嵌入法来完成。
相对的,L2正则化在加强的过程中,会尽量让每个特征对模型都有一些小的贡献,但携带信息少,对模型贡献不大的特征的参数会非常接近于0。通常来说,如果我们的主要目的只是为了防止过拟合,选择L2正则化就足够了。但是如果选择L2正则化后还是过拟合,模型在未知数据集上的效果表现很差,就可以考虑L1正则化。
六.逻辑回归中的特征工程
当特征的数量很多的时候,我们出于业务考虑,也出于计算量的考虑,希望对逻辑回归进行特征选择来降维。
1.业务选择
说到降维和特征选择,首先要想到的是利用自己的业务能力进行选择,肉眼可见明显和标签有关的特征就是需要留下的。当然,如果我们并不了解业务,或者有成千上万的特征,那我们也可以使用算法来帮助我们。或者,可以让算法先帮助我们筛选过一遍特征,然后在少量的特征中,我们再根据业务常识来选择更少量的特征。
2.PCA和SVD一般不用
说到降维,我们首先想到的是之前提过的高效降维算法,PCA和SVD,遗憾的是,这两种方法大多数时候不适用于逻辑回归。逻辑回归是由线性回归演变而来,线性回归的一个核心目的是通过求解参数来探究特征X与标签y之间的关系,而逻辑回归也传承了这个性质,我们常常希望通过逻辑回归的结果,来判断什么样的特征与分类结果相关,因此我们希望保留特征的原貌。PCA和SVD的降维结果是不可解释的,因此一旦降维后,我们就无法解释特征和标签之间的关系了。当然,在不需要探究特征与标签之间关系的线性数据上,降维算法PCA和SVD也是可以使用的。
3.统计方法可以使用,但不是非常必要
既然降维算法不能使用,我们要用的就是特征选择方法。逻辑回归对数据的要求低于线性回归,由于我们不是使用最小二乘法来求解,所以逻辑回归对数据的总体分布和方差没有要求,也不需要排除特征之间的共线性,但如果我们确实希望使用一些统计方法,比如方差,卡方,互信息等方法来做特征选择,也并没有问题。过滤法中所有的方法,都可以用在逻辑回归上。
4.高效的嵌入法embedded
但是更有效的方法,毫无疑问会是我们的embedded嵌入法。我们已经说明了,由于L1正则化会使得部分特征对应的参数为0,因此L1正则化可以用来做特征选择,结合嵌入法的模块SelectFromModel,我们可以很容易就筛选出让模型十分高效的特征。注意,此时我们的目的是,尽量保留原数据上的信息,让模型在降维后的数据上的拟合效果保持优秀,因此我们不考虑训练集测试集的问题,把所有的数据都放入模型进行降维。
特征数量被减小到个位数,并且模型的效果却没有下降太多,如果我们要求不高,在这里其实就可以停下了。但是,能否让模型的拟合效果更好呢?在这里,我们有两种调整方式:
1)调节SelectFromModel这个类中的参数threshold
这是嵌入法的阈值,表示删除所有参数的绝对值低于这个阈值的特征。现在threshold默认为None,所以SelectFromModel只根据L1正则化的结果来选择了特征,即选择了所有L1正则化后参数不为0的特征。我们此时,只要调整threshold的值(画出threshold的学习曲线),就可以观察不同的threshold下模型的效果如何变化。一旦调整threshold,就不是在使用L1正则化选择特征,而是使用模型的属性.coef_中生成的各个特征的系数来选择。coef_虽然返回的是特征的系数,但是系数的大小和决策树中的feature_importances_以及降维算法中的可解释性方差explained_vairance_概念相似,其实都是衡量特征的重要程度和贡献度的,因此SelectFromModel中的参数threshold可以设置为coef_的阈值,即可以剔除系数小于threshold中输入的数字的所有特征。
然而,这种方法其实是比较无效的.
2)调逻辑回归的类LR_,通过画C的学习曲线来实现
3)比较麻烦的系数累加法
系数累加法的原理非常简单。在PCA中,我们通过绘制累积可解释方差贡献率曲线来选择超参数,在逻辑回归中我们可以使用系数coef_来这样做,并且我们选择特征个数的逻辑也是类似的:找出曲线由锐利变平滑的转折点,转折点之前被累加的特征都是我们需要的,转折点之后的我们都不需要。不过这种方法相对比较麻烦,因为我们要先对特征系数进行从大到小的排序,还要确保我们知道排序后的每个系数对应的原始特征的位置,才能够正确找出那些重要的特征。如果要使用这样的方法,不如直接使用嵌入法来得方便。
4)简单快速的包装法
相对的,包装法可以直接设定我们需要的特征个数,逻辑回归在现实中运用时,可能会有”需要5~8个变量”这种需求,包装法此时就非常方便了。不过逻辑回归的包装法的使用和其他算法一样,并不具有特别之处.
七.梯度下降:重要参数max_iter
那梯度有什么含义呢?梯度是一个向量,因此它有大小也有方向。它的大小,就是偏导数组成的向量的大小,又叫做向量的模,记作d。它的方向,几何上来说,就是损失函数的值增加最快的方向,就是小球每次滚动的方向的反方向。只要沿着梯度向量的反方向移动坐标,损失函数的取值就会减少得最快,也就最容易找到损失函数的最小值。
记得我们在看小球运动时注意到,小球在进入深蓝色区域后,并没有直接找到某个点,而是在深蓝色区域中来回震荡了数次才停下,这种”震荡“其实就是因为我们设置的步长太大的缘故。但是在我们开始梯度下降之前,我们并不知道什么样的步长才合适,但梯度下降一定要在某个时候停止才可以,否则模型可能会无限地迭代下去。因此,在sklearn当中,我们设置参数max_iter最大迭代次数来代替步长,帮助我们控制模型的迭代速度并适时地让模型停下。max_iter越大,代表步长越小,模型迭代时间越长,反之,则代表步长设置很大,模型迭代时间很短。
八. 二元回归与多元回归:重要参数solver & multi_class
之前我们对逻辑回归的讨论,都是针对二分类的逻辑回归展开,其实sklearn提供了多种可以使用逻辑回归处理多分类问题的选项。比如说,我们可以把某种分类类型都看作1,其余的分类类型都为0值,和”数据预处理“中的二值化的思维类似,这种方法被称为"一对多"(One-vs-rest),简称OvR,在sklearn中表示为“ovr"。又或者,我们可以把好几个分类类型划为1,剩下的几个分类类型划为0值,这是一种”多对多“(Many-vs-Many)的方法,简称MvM,在sklearn中表示为"Multinominal"。每种方式都配合L1或L2正则项来使用。
在sklearn中,我们使用参数multi_class来告诉模型,我们的预测标签是什么样的类型。
multi_class
输入"ovr", "multinomial", "auto"来告知模型,我们要处理的分类问题的类型。默认是"ovr"。
'ovr':表示分类问题是二分类,或让模型使用"一对多"的形式来处理多分类问题。
'multinomial':表示处理多分类问题,这种输入在参数solver是'liblinear'时不可用。
"auto":表示会根据数据的分类情况和其他参数来确定模型要处理的分类问题的类型。比如说,如果数据是二分类,或者solver的取值为"liblinear","auto"会默认选择"ovr"。反之,则会选择"nultinomial"。
注意:默认值将在0.22版本中从"ovr"更改为"auto"。
我们之前提到的梯度下降法,只是求解逻辑回归参数
的一种方法,并且我们只讲解了求解二分类变量的参数θ时的各种原理。sklearn为我们提供了多种选择,让我们可以使用不同的求解器来计算逻辑回归。求解器的选择,由参数"solver"控制,共有五种选择。其中“liblinear”是二分类专用,也是现在的默认求解器。
九.样本不平衡与参数class_weight
因此我们要使用参数class_weight对样本标签进行一定的均衡,给少量的标签更多的权重,让模型更偏向少数类,向捕获少数类的方向建模。该参数默认None,此模式表示自动给与数据集中的所有标签相同的权重,即自动1:1。当误分类的代价很高的时候,我们使用”balanced“模式,我们只是希望对标签进行均衡的时候,什么都不填就可以解决样本不均衡问题。
但是,sklearn当中的参数class_weight变幻莫测,大家用模型跑一跑就会发现,我们很难去找出这个参数引导的模型趋势,或者画出学习曲线来评估参数的效果,因此可以说是非常难用。我们有着处理样本不均衡的各种方法,其中主流的是采样法,是通过重复样本的方式来平衡标签,可以进行上采样(增加少数类的样本),比如SMOTE,或者下采样(减少多数类的样本)。对于逻辑回归来说,上采样是最好的办法。在案例中,会给大家详细来讲如何在逻辑回归中使用上采样。
十.逻辑回归代码实现
from sklearn.linear_model import LogisticRegression as LR#逻辑回归
from sklearn.datasets import load_breast_cancer
import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score#精确性分数
data = load_breast_cancer()#乳腺癌数据集
X = data.data
y = data.target
print(X.data.shape)
lrl1 = LR(penalty="l1", solver="liblinear", C=0.5, max_iter=1000)
lrl2 = LR(penalty="l2", solver="liblinear", C=0.5, max_iter=1000)
lrl1 = lrl1.fit(X,y)
print(lrl1.coef_)#coef_查看每个特征所对应的参数
print((lrl1.coef_ != 0).sum(axis=1))#array([10]),30个特征中有10个特征的系数不为0;由此可见l1正则化会让参数的系数为0
lrl2 = lrl2.fit(X,y)
print(lrl2.coef_)#没有一个参数的系数为0,由此可见l2会尽量让每一个参数都能有贡献
l1 = []
l2 = []
l1test = []
l2test = []
Xtrain, Xtest, Ytrain, Ytest = train_test_split(X, y, test_size=0.3, random_state=420)
for i in np.linspace(0.05, 1.5, 19):#取了19个数
lrl1 = LR(penalty="l1", solver="liblinear", C=i, max_iter=1000)
lrl2 = LR(penalty="l2", solver="liblinear", C=i, max_iter=1000)
lrl1 = lrl1.fit(Xtrain, Ytrain)#对模型训练
l1.append(accuracy_score(lrl1.predict(Xtrain), Ytrain))#训练的结果
l1test.append(accuracy_score(lrl1.predict(Xtest), Ytest))#测试的结果
lrl2 = lrl2.fit(Xtrain, Ytrain)#对模型训练
l2.append(accuracy_score(lrl2.predict(Xtrain), Ytrain))#训练的结果
l2test.append(accuracy_score(lrl2.predict(Xtest), Ytest))#测试的结果
graph = [l1, l2, l1test, l2test]
color = ["green", "black", "lightgreen", "gray"]
label = ["L1", "L2", "L1test", "L2test"]
# plt.figure(figsize=(6, 6))
# for i in range(len(graph)):
# plt.plot(np.linspace(0.05, 1.5, 19), graph[i], color[i], label=label[i])#折线图
# plt.legend(loc=4) # 图例的位置在哪里?4表示,右下角
# plt.show()
from sklearn.linear_model import LogisticRegression as LR#逻辑回归
from sklearn.datasets import load_breast_cancer
import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import cross_val_score
from sklearn.feature_selection import SelectFromModel
data = load_breast_cancer()
print(data.data.shape)
LR_ = LR(solver="liblinear", C=0.9, random_state=420)
print(cross_val_score(LR_, data.data, data.target, cv=10).mean())
X_embedded = SelectFromModel(LR_, norm_order=1).fit_transform(data.data, data.target)#norm_order=1及使用l1范式进行筛选
print(X_embedded.shape) # (569, 9),特征数量减少了21个
print(cross_val_score(LR_, X_embedded, data.target, cv=10).mean()) # 0.9368323826808401
fullx = []
fsx = []
threshold = np.linspace(0, abs((LR_.fit(data.data, data.target).coef_)).max(), 20)#coef_查看所有的系数,abs取绝对值
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]))#打印threshold及筛选留下的特征数
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()#可以看到threshold越来越大,留下的特征越来越小,得到的交叉验证分数越来越低
# fullx = []
# fsx = []
# C = np.arange(0.01, 10.01, 0.5)
# for i in C:
# LR_ = LR(solver="liblinear", C=i, random_state=420)
# fullx.append(cross_val_score(LR_, data.data, data.target, cv=10).mean())
# X_embedded = SelectFromModel(LR_, norm_order=1).fit_transform(data.data, data.target)#norm_order=1及使用l1范式进行筛选
# fsx.append(cross_val_score(LR_, X_embedded, data.target, cv=10).mean())
# print(max(fsx), C[fsx.index(max(fsx))])
# plt.figure(figsize=(20, 5))
# plt.plot(C, fullx, label="full")
# plt.plot(C, fsx, label="feature selection")
# plt.xticks(C)
# plt.legend()
# plt.show()
# fullx = []
# fsx = []
# C = np.arange(6.05, 7.05, 0.005)
# for i in C:
# LR_ = LR(solver="liblinear", C=i, random_state=420)
# fullx.append(cross_val_score(LR_, data.data, data.target, cv=10).mean())
# X_embedded = SelectFromModel(LR_, norm_order=1).fit_transform(data.data, data.target)
# fsx.append(cross_val_score(LR_, X_embedded, data.target, cv=10).mean())
# print(max(fsx), C[fsx.index(max(fsx))])
# plt.figure(figsize=(20, 5))
# plt.plot(C, fullx, label="full")
# plt.plot(C, fsx, label="feature selection")
# plt.xticks(C)
# plt.legend()
# plt.show()
# # 验证模型效果:降维之前
# LR_ = LR(solver="liblinear", C=6.069999999999999, random_state=420)
# cross_val_score(LR_, data.data, data.target, cv=10).mean() # 0.947360859044162
# # 验证模型效果:降维之后
# LR_ = LR(solver="liblinear", C=6.069999999999999, random_state=420)
# X_embedded = SelectFromModel(LR_, norm_order=1).fit_transform(data.data, data.target)
# print(cross_val_score(LR_, X_embedded, data.target, cv=10).mean()) # 0.9580405755768732
# print(X_embedded.shape) # (569, 10),减少了20个特征,并且分数提升了
# l2 = []
# l2test = []
# Xtrain, Xtest, Ytrain, Ytest = train_test_split(X, y, test_size=0.3, random_state=420)
# for i in np.arange(1, 201, 10):#为max_iter的学习曲线做准备
# lrl2 = LR(penalty="l2", solver="liblinear", C=0.9, max_iter=i)#实例化逻辑回归
# lrl2 = lrl2.fit(Xtrain, Ytrain)#训练模型
# l2.append(accuracy_score(lrl2.predict(Xtrain), Ytrain))
# l2test.append(accuracy_score(lrl2.predict(Xtest), Ytest))
# graph = [l2, l2test]
# color = ["black", "gray"]
# label = ["L2", "L2test"]
# plt.figure(figsize=(20, 5))
# for i in range(len(graph)):
# plt.plot(np.arange(1, 201, 10), graph[i], color[i], label=label[i])
# plt.legend(loc=4)
# plt.xticks(np.arange(1, 201, 10))
# plt.show()
# # 我们可以使用属性.n_iter_来调用本次求解中真正实现的迭代次数
# lr = LR(penalty="l2", solver="liblinear", C=0.9, max_iter=300).fit(Xtrain, Ytrain)
# print(lr.n_iter_) # array([24], dtype=int32) 只迭代了24次就达到收敛
from sklearn.datasets import load_iris
iris = load_iris()
print(iris.target) # 三分类数据集
for multi_class in ('multinomial', 'ovr'):
clf = LR(solver='sag', max_iter=100, random_state=42,
multi_class=multi_class).fit(iris.data, iris.target)#创建逻辑回归类并导入数据,设置不同的multi_class的值
# 打印两种multi_class模式下的训练分数
# %的用法,用%来代替打印的字符串中,想由变量替换的部分。%.3f表示,保留三位小数的浮点数。%s表示,字符串。
# 字符串后的%后使用元祖来容纳变量,字符串中有几个%,元祖中就需要有几个变量
print("training score : %.3f (%s)" % (clf.score(iris.data, iris.target), multi_class))#%的用法