Python机器学习分类算法
通过训练已知分类的数据集,从中可以发现分类规则,并以此预测新数据的所属类别,这被称为分类算法。按照类别标签的多少,分类算法可分为二分类算法和多分类算法。分类算法属于监督学习。Scikit-learn中最常用的分类算法包括支持向量机(SVM)、最近邻、朴素贝叶斯、随机森林、决策树等。尽管Scikit-learn也支持多层感知器(MLP),不过Scikit-learn本身既不支持深度学习,也不支持GPU加速,因此MLP不适用于大规模数据应用。如果想借助GPU提高运行速度,建议使用Tensorflow、Keras、PyTorch等深度学习框架。
k-近邻算法
k-近邻算法是一个有监督的机器学习算法。k-近邻算法也称为knn算法,可以解决分类问题,也可以解决回归问题。
涵盖的内容如下:
- k-近邻算法的原理、优缺点及参数k取值对算法性能的影响;
- 使用k-近邻算法处理分类问题的示例;
- 使用k-近邻算法解决回归问题的示例;
- 使用k-近邻算法进行糖尿病检测的实例;
- 基于统计学的特征选择;
- 扩展阅读之k-近邻算法性能优化;
- 扩展阅读之卡方检测及F值检测。
算法原理
近邻算法,或者说K最近邻(kNN,k-NearestNeighbor)分类算法是数据挖掘分类技术中最简单的方法之一。所谓K最近邻,就是k个最近的邻居的意思,说的是每个样本都可以用它最接近的k个邻居来代表。k-近邻算法的核心思想是未标记样本的类别,由距离其最近的k个邻居投票来决定。
假设,我们有一个已经标记的数据集,即已经知道了数据集中每个样本所属的类别。此时,有一个未标记的数据样本,我们的任务是预测出这个数据样本所属的类别。
knn算法的原理比较简单,它是被用来做分类的一个算法,它的数据集是有标记的,即knn属于监督学习的算法。基本原理是,对于一个新给的数据,找到与其最近的k个数据,这k个数据中哪个类别的数据最多,则认为该数据也属于这个类别。用一个词语来理解就是物以类聚或者近朱者赤近墨者黑。
KNN算法的核心思想是如果一个样本在特征空间中的k个最相邻的样本中的大多数属于某一个类别,则该样本也属于这个类别,并具有这个类别上样本的特性。该方法在确定分类决策上只依据最邻近的一个或者几个样本的类别来决定待分样本所属的类别。kNN方法在类别决策时,只与极少量的相邻样本有关。由于kNN方法主要靠周围有限的邻近的样本,而不是靠判别类域的方法来确定所属类别的,因此对于类域的交叉或重叠较多的待分样本集来说,kNN方法较其他方法更为适合。
假设X_test为待标记的数据样本,X_train为已标记的数据集,算法原理的伪代码如下:
- 遍历X_train中的所有样本,计算每个样本与X_test的距离,并把距离保存在Distance数组中。
- 对Distance数组进行排序,取距离最近的k个点,记为X_knn。
- 在X_knn中统计每个类别的个数,即class0在X_knn中有几个样本,class1在X_knn中有几个样本等。
- 待标记样本的类别,就是在X_knn中样本个数最多的那个类别。
使用场景
k邻近值算法适合于分类问题。分类问题是机器学习非常重要的一个组成部分,目标是根据已知样本的某些特征,判断样本属于哪一类,具体细分如下:
- 二分类问题:表示分类任务中有两个类别的样本属于哪种已知样本类。
- 多类分类问题:表示分类任务中有多类别。
- 多标签分类问题:给每个样本一系列的目标标签。
分类问题在数学上就是把数据映射到n维空间的样本点(n指特征维度),我们需要做的是对n维样本空间的点进行类别区分,某些点会归属到某个类别。常见的实际应用场景有如下:
- 垃圾邮件识别:可以作为二分类问题,将邮件分为你「垃圾邮件」或者「正常邮件」。
- 图像内容识别:因为图像的内容种类不止一个,图像内容可能是猫、狗、人等等,因此是多类分类问题。
- 文本情感分析:既可以作为二分类问题,将情感分为褒贬两种,还可以作为多类分类问题,将情感种类扩展,比如分为:十分消极、消极、积极、十分积极等。 像机器学习常见的一些任务:鸢尾花分类、手写数字识别、猫狗分类都可以使用k邻近算法解决。鸢尾花分类就是一项多分类任务,按照鸢尾花4个特征(四维)对鸢尾花数据样本再空间进行映射,相似或相同的数据点会大致分布到一起,这样,在已知这部分样本点后,再把未知的样本按照4个特征放入空间中,未知样本周围是哪一类样本占多数就归为这一类。手写数字识别亦是如此。具有相似多个特征的样本会大致分布在一个位置,未知样本再投影到空间中会位于相近的位置。
而以上提到的任务也都与分类有关,故k邻近算法一般应用于机器学习中的分类问题/任务。当然肯定不会只有机器学习有分类任务,还有邮件识别等具体场景领域。
算法优缺点
优点:准确性高,对异常值和噪声有较高的容忍度。缺点:计算量较大,对内存的需求也较大。从算法原理可以看出来,每次对一个未标记样本进行分类时,都需要全部计算一遍距离。
算法参数
其算法参数是k,参数选择需要根据数据来决定。k值越大,模型的偏差越大,对噪声数据越不敏感,当k值很大时,可能造成模型欠拟合;k值越小,模型的方差就会越大,当k值太小,就会造成模型过拟合。
算法的变种
k-近邻算法有一些变种,其中之一就是可以增加邻居的权重。默认情况下,在计算距离时,都是使用相同权重。实际上,我们可以针对不同的邻居指定不同的距离权重,如距离越近权重越高。这个可以通过指定算法的weights参数来实现。
另外一个变种是,使用一定半径内的点取代距离最近的k个点。在scikit-learn里,RadiusNeighborsClassifier类实现了这个算法的变种。当数据采样不均匀时,该算法变种可以取得更好的性能。
示例:使用k-近邻算法进行分类
在scikit-learn里,使用k-近邻算法进行分类处理的是sklearn.neighbors.KNeighbors Classifier类。
生成已标记的数据集:
from sklearn.datasets import make_blobs
# 生成数据
centers = [[-2, 2], [2, 2], [0, 4]]
X, y = make_blobs(n_samples=60, centers=centers,
random_state=0, cluster_std=0.60)
我们使用sklearn.datasets包下的make_blobs()函数来生成数据集,上面代码中,生成60个训练样本,这60个样本分布在以centers参数指定中心点周围。 cluster_std是标准差,用来指明生成的点分布的松散程度。生成的训练数据集放在变量X里面,数据集的类别标记放在y里面。 使用matplotlib库,它可以很容易地把生成的点画出来:
# 画出数据
plt.figure(figsize=(16, 10), dpi=144)
c = np.array(centers)
plt.scatter(X[:, 0], X[:, 1], c=y, s=100, cmap='cool'); # 画出样本
plt.scatter(c[:, 0], c[:, 1], s=100, marker='^', c='orange'); # 画出中心点
plt.show()
使用KNeighborsClassifier来对算法进行训练,我们选择的参数是k=5:
from sklearn.neighbors import KNeighborsClassifier
# 模型训练
k = 5
clf = KNeighborsClassifier(n_neighbors=k)
clf.fit(X, y);
对一个新的样本进行预测:
# 进行预测
X_sample = np.array([[0, 2]])
y_sample = clf.predict(X_sample)
neighbors = clf.kneighbors(X_sample, return_distance=False)
我们要预测的样本是[0,2],使用kneighbors()方法,把这个样本周围距离最近的5个点取出来。取出来的点是训练样本X里的索引,从0开始计算。
把待预测的样本以及和其最近的5个点标记出来:
# 画出示意图
plt.figure(figsize=(16,10), dpi=144)
c = np.array(centers)
plt.scatter(X[:,0], X[:,1], c=y, s=100, cmap='cool') # 出样本
plt.scatter(c[:,0], c[:,1], s=100, marker='^',c='k') # 中心点
plt.scatter(X_sample[0][0], X_sample[0][1],c=y_sample, marker="x",
s=100, cmap='cool') # 待预测的点
for i in neighbors[0]:
plt.plot([X[i][0], X_sample[0][0]], [X[i][1], X_sample[0][1]],
'k--', linewidth=0.6) # 预测点与距离最近的5个样本的连线
# plt.savefig('knn_predict.png')
plt.show()
完整代码如下
from sklearn.datasets import make_blobs
from sklearn.neighbors import KNeighborsClassifier
from matplotlib import pyplot as plt
import numpy as np
# 生成数据
centers = [[-2, 2], [2, 2], [0, 4]]
X, y = make_blobs(n_samples=60, centers=centers,
random_state=0, cluster_std=0.60)
print(X)
print("---------")
print(y)
# 模型训练
k = 5
clf = KNeighborsClassifier(n_neighbors=k)
clf.fit(X, y);
# 进行预测
X_sample = np.array([[0, 2]])
y_sample = clf.predict(X_sample)
neighbors = clf.kneighbors(X_sample, return_distance=False)
print(X_sample)
print(y_sample)
print(neighbors)
# 画出示意图
plt.figure(figsize=(16,10), dpi=144)
c = np.array(centers)
plt.scatter(X[:,0], X[:,1], c=y, s=100, cmap='cool') # 出样本
plt.scatter(c[:,0], c[:,1], s=100, marker='^',c='k') # 中心点
plt.scatter(X_sample[0][0], X_sample[0][1],c=y_sample, marker="x",
s=100, cmap='cool') # 待预测的点
for i in neighbors[0]:
plt.plot([X[i][0], X_sample[0][0]], [X[i][1], X_sample[0][1]],
'k--', linewidth=0.6) # 预测点与距离最近的5个样本的连线
# plt.savefig('knn_predict.png')
plt.show()
示例:使用k-近邻算法进行回归拟合
分类问题的预测值是离散的,我们也可以用k-近邻算法在连续区间内对数值进行预测,进行回归拟合。在scikit-learn里,使用k-近邻算法进行回归拟合的算法是sklearn.neighbors.KNeighborsRegressor类。
生成数据集,它在余弦曲线的基础上加入了噪声:
import numpy as np
n_dots = 40
X = 5 * np.random.rand(n_dots, 1)
y = np.cos(X).ravel()
# 添加一些噪声
y += 0.2 * np.random.rand(n_dots) - 0.1
使用KNeighborsRegressor来训练模型:
# 训练模型
from sklearn.neighbors import KNeighborsRegressor
k = 5
knn = KNeighborsRegressor(k)
knn.fit(X, y);
我们要怎么样来进行回归拟合呢?
一个方法是,在X轴上的指定区间内生成足够多的点,针对这些足够密集的点,使用训练出来的模型进行预测,得到预测值y_pred,然后在坐标轴上,把所有的预测点连接起来,这样就画出了拟合曲线。
我们针对足够密集的点进行预测:
# 生成足够密集的点并进行预测
T = np.linspace(0, 5, 500)[:, np.newaxis]
y_pred = knn.predict(T)
knn.score(X, y)
可以用score()方法计算拟合曲线针对训练样本的拟合准确性,在笔者的环境下输出结果为:
0.99000494130215722 # 在读者的环境运行时,值会略有差异
把这些预测点连起来,构成拟合曲线:
# 画出拟合曲线
plt.figure(figsize=(16, 10), dpi=144)
plt.scatter(X, y, c='g', label='data', s=100) # 画出训练样本
plt.plot(T, y_pred, c='k', label='prediction', lw=4) # 画出拟合曲线
plt.axis('tight')
plt.title("KNeighborsRegressor (k = %i)" % k)
plt.show()
下面以Scikit-learn内置的鸢尾花数据集为例,演示k-近邻分类模型的使用。
在研究机器学习算法时,通常从样本集中随机抽取部分样本作为测试集,其余样本作为训练集。Scikit-learn提供了train_test_split( ),使用它可以将样本集按照指定的比例随机分割成训练集和测试集。下面使用10%的样本作为测试集对鸢尾花进行分类,模型精度在93%左右。
from sklearn.datasets import load_iris
from sklearn.neighbors import KNeighborsClassifier # 导入k-近邻分类模型
from sklearn.model_selection import train_test_split as tsplit
X, y = load_iris(return_X_y=True) # 获取鸢尾花数据集,返回样本集和标签集
X_train, X_test, y_train, y_test = tsplit(X, y, test_size=0.1) # 拆分
m = KNeighborsClassifier() # 实例化模型。n_neighbors参数指定k值,默认k=5
m.fit(X_train, y_train) # 模型训练
KNeighborsClassifier(algorithm='auto', leaf_size=30, metric='minkowski',
metric_params=None, n_jobs=None, n_neighbors=5, p=2,
weights='uniform')
t=m.score(X_test, y_test) # 模型测试精度(介于0~1)
print(t)
k-近邻分类模型理论成熟,计算精度高,对异常值不敏感,但相对其他分类模型而言计算量大,占用内存多。由于k-近邻分类模型主要靠周围有限的邻近样本,而不是靠判别类域的方法来确定所属类别,因此对于类域交叉或重叠较多的待分类样本集来说,k-近邻分类模型相较其他模型来说更为合适。k-近邻分类模型比较适用于样本数量比较大的自动分类,当样本数量较小时容易产生错误分类。
决策树
决策树是最经典的机器学习模型之一。它的预测结果容易理解,易于向业务部门解释,预测速度快,可以处理类别型数据和连续型数据。
- 信息熵及信息增益的概念,以及决策树的分裂的原则;
- 决策树的创建及剪枝算法;
- scikit-learn中决策树算法的相关参数;
- 使用决策树预测泰坦尼克号幸存者实例;
- scikit-learn中模型参数选择的工具及使用方法;
- 聚合算法及随机森林算法的原理。
算法原理
决策树是一个类似于流程图的树结构,分支节点表示对一个特征进行测试,根据测试结果进行分类,树叶节点代表一个类别。 每选择一个特征进行测试,数据集就被划分成多个子数据集。接着继续在子数据集上选择特征,并进行数据集划分,直到创建出一个完整的决策树。创建好决策树模型后,从根节点一路往下即可预测行为。 问题来了,在创建决策树的过程中,要先对哪个特征进行分裂?
决策树是一种树形结构,用于为决策提供依据。决策树的每一个节点都是可以用是或否来回答的问题,节点的分支表示一次选择(是或否),每片树叶对应一个决策。决策树通过树形结构将各种情况组合表示出来,直到所有组合都表示完毕,最终给出正确答案。决策树分类就是基于使用训练样本构建的决策树对测试样本进行分类的过程。
信息增益
我们天天在谈论信息,那么信息要怎么样来量化呢?1948年,香农在他著名的《通信的数学原理》中提出了信息熵(Entropy)的概念,从而解决了信息的量化问题。香农认为,一条信息的信息量和它的不确定性有直接关系。一个问题不确定性越大,要搞清楚这个问题,需要了解的信息就越多,其信息熵就越大。信息熵的计算公式为:
H(x) = E[I(xi)] = E[ log(2,1/P(xi)) ] = -∑P(xi)log(2,P(xi)) (i=1,2,..n)
其中,P(x)表示事件x出现的概率。例如,一个盒子里分别有5个白球和5个红球,随机取出一个球。问:这个球是红色的还是白色的?这个问题的信息量多大呢?由于红球和白球出现的概率都是1/2,代入信息熵公式,可以得到其信息熵为: 即,这个问题的信息量是1 bit。对,你没有看错,信息量的单位就是比特。我们要确定这个球是红色的还是白色的,只需要1比特的信息就够了。再举一个极端的例子,一个盒子里有10个白球,随机取出一个球,这个球是什么颜色的?这个问题的信息量是多少呢?答案是0,因为这是一个确定的事件,其概率P(x)=1,我们代入香农的信息熵公式,即可得到其信息熵为0。即,我们不需要再获取任何新的信息,即可知道这个球一定是白色的。
回到决策树的构建问题上,当我们要构建一个决策树时,应该优先选择哪个特征来划分数据集呢?答案是:遍历所有的特征,分别计算,使用这个特征划分数据集前后信息熵的变化值,然后选择信息熵变化幅度最大的那个特征,来优先作为数据集划分依据。即选择信息增益最大的特征作为分裂节点。
比如,一个盒子里共有红、白、黑、蓝4种颜色的球共16个,其中红球2个,白球2个,黑球4个,蓝球8个。红球和黑球的体积一样,都为1个单位;白球和蓝球的体积一样,都为2个单位。红球、白球和黑球的质量一样,都是1个单位,蓝球的质量为2个单位。
我们应该优先选择体积这个特征,还是优先选择质量这个特征来作为数据集划分依据呢?根据前面介绍的结论,我们先计算基础信息熵,即划分数据集前的信息熵。从已知信息容易知道,红球、白球、黑球、蓝球出现的概率分别为2/16,、2/16、4/16、8/16,因此基础信息熵为:1.75
接着使用体积来划分数据集,此时会划分出两个数据集,第一个子数据集里是红球和黑球,第二个子数据集里是白球和蓝球,我们计算这种划分方式的信息熵。其中第一个子数据集里,红球2个,黑球4个,其概率分别为2/6和4/6,因此第一个子数据集的信息熵为:0.918296
第二个子数据集里,白球2个,蓝球8个,其概率分别为2/10和8/10,因此第二个子数据集的信息熵为:0.721928
因此,使用体积来划分数据集后,其信息熵为H(D1)=H(D1sub1)+H(D1sub2)=1.640224,其信息增益为H(Dbase)-H(D1)=1.75-1.640224=0.109776
如果我们使用质量来划分数据集,也会划分出两个数据集,第一个子数据集里是红球、白球和黑球,第二个子数据集里是只有蓝球。我们计算这种划分方式的信息熵。针对第一个子数据集,红球、白球和黑球出现的概率分别是2/8,、2/8、4/8,其信息熵为:1.5
第二个子数据集里只有蓝球,其概率为1,因此其信息熵H(D2sub2)=0。我们得出使用使用质量来划分数据集时的信息熵为1.5,其信息增益为1.75-1.5=0.25。由于使用质量划分数据集比使用体积划分数据集得到了更高的信息增益,所以我们优先选择质量这个特征来划分数据集。
熵是热力学中表征物质状态的参量之一,其物理意义是体系混乱程度的度量,被香农借用过来,作为信息量的度量。著名的熵增原理是这样描述的:
熵增原理就是孤立热力学系统的熵不减少,总是增大或者不变。一个孤立系统不可能朝低熵的状态发展,即不会变得有序。
用白话讲就是,如果没有外力的作用,这个世界将是越来越无序的。人活着,在于尽量让熵变低,即让世界变得更有序,降低不确定性。当我们在消费资源时,是一个增熵的过程。我们把有序的食物变成了无序的垃圾。例如,笔者在写书或读者在看书的过程,可以理解为减熵过程。我们通过写作和阅读,减少了不确定的信息,从而实现了减熵的过程。人生价值的实现,在于消费资源(增熵过程)来获取能量,经过自己的劳动付出(减熵过程),让世界变得更加纯净有序,信息增益(减熵量-增熵量)即是衡量人生价值的尺度。希望笔者在暮年之时,回首往事,能自信地说,我给这个世界带来的信息增益是正数,且已经尽力做到最大了。
决策树的创建
决策树的构建过程,就是从训练数据集中归纳出一组分类规则,使它与训练数据矛盾较小的同时具有较强的泛化能力。有了信息增益来量化地选择数据集的划分特征,使决策树的创建过程变得容易了。决策树的创建基本上分以下几步。
- 计算数据集划分前的信息熵。
- 遍历所有未作为划分条件的特征,分别计算根据每个特征划分数据集后的信息熵。
- 选择信息增益最大的特征,并使用这个特征作为数据划分节点来划分数据。
- 递归地处理被划分后的所有子数据集,从未被选择的特征里继续选择最优数据划分特征来划分子数据集。 问题来了,递归过程什么时候结束呢?一般来讲,有两个终止条件,一是所有的特征都用完了,即没有新的特征可以用来进一步划分数据集。二是划分后的信息增益足够小了,这个时候就可以停止递归划分了。针对这个停止条件,需要事先选择信息增益的门限值来作为结束递归的条件。
使用信息增益作为特征选择指标的决策树构建算法,称为ID3算法。
离散化
细心的读者可能会发现一个问题:如果一个特征是连续值怎么办呢?假设我们有个精力测试仪器,测出来的是一个0~100的数字,这是个连续值,这个时候怎么用决策树来建模呢?答案是:离散化。我们需要对数据进行离散化处理。例如,当精力指数小于等于40时标识为低,当大于40且小于等于70时标识为中,当大于70时标识为高。经过离散处理后,就可以用来构建决策树了。要离散化成几个类别,这个往往和具体的业务相关。
正则项
最大化信息增益来选择特征,在决策树的构建过程中,容易造成优先选择类别最多的特征来进行分类。举一个极端的例子,我们把某个产品的唯一标识符ID作为特征之一加入到数据集中,那么构建决策树时,就会优先选择产品ID来作为划分特征,因为这样划分出来的数据,每个叶子节点只有一个样本,划分后的子数据集最“纯净”,其信息增益最大。
这不是我们希望看到的结果。解决办法是,计算划分后的子数据集的信息熵时,加上一个与类别个数成正比的正则项,来作为最后的信息熵。这样,当算法选择的某个类别较多的特征,使信息熵较小时,由于受到类别个数的正则项惩罚,导致最终的信息熵也比较大。这样通过合适的参数,可以使算法训练得到某种程度的平衡。
另外一个解决办法是使用信息增益比来作为特征选择的标准。
基尼不纯度
我们知道,信息熵是衡量信息不确定性的指标,实际上也是衡量信息“纯度”的指标。除此之外,基尼不纯度(Gini impurity)也是衡量信息不纯度的指标,其计算公式如下:
其中,P(x)是样本属于这个类别的概率。如果所有的样本都属于一个类别,此时P(x)=1,则Gini(D)=0,即数据不纯度最低,纯度最高。我们以概率P(x)作为横坐标,以这个类别的基尼不纯度Gini(D)=P(x)(1-P(x))作为纵坐标,在坐标轴上画出其函数关系
剪枝算法
使用决策树模型拟合数据时,容易造成过拟合。解决过拟合的方法是对决策树进行剪枝处理。决策树的剪枝有两种思路:前剪枝(Pre-Pruning)和后剪枝(Post-Pruning)。
前剪枝
前剪枝是在构造决策树的同时进行剪枝。在决策树的构建过程中,如果无法进一步降低信息熵的情况下,就会停止创建分支。为了避免过拟合,可以设定一个阈值,信息熵减小的数量小于这个阈值,即使还可以继续降低熵,也停止继续创建分支。这种方法称为前剪枝。还有一些简单的前剪枝方法,如限制叶子节点的样本个数,当样本个数小于一定的阈值时,即不再继续创建分支。
后剪枝
后剪枝是指决策树构造完成后进行剪枝。剪枝的过程是对拥有同样父节点的一组节点进行检查,判断如果将其合并,信息熵的增加量是否小于某一阈值。如果小于阈值,则这一组节点可以合并一个节点。后剪枝是目前较普遍的做法。后剪枝的过程是删除一些子树,然后用子树的根节点代替,来作为新的叶子节点。这个新叶子节点所标识的类别通过大多数原则来确定,即把这个叶子节点里样本最多的类别,作为这个叶子节点的类别。
后剪枝算法有很多种,其中常用的一种称为降低错误率剪枝法(Reduced-Error Pruning)。其思路是,自底向上,从已经构建好的完全决策树中找出一个子树,然后用子树的根节点代替这棵子树,作为新的叶子节点。叶子节点所标识的类别通过大多数原则来确定。这样就构建出一个新的简化版的决策树。然后使用交叉验证数据集来测试简化版本的决策树,看看其错误率是不是降低了。如果错误率降低了,则可以使用这个简化版的决策树代替完全决策树,否则还是采用原来的决策树。通过遍历所有的子树,直到针对交叉验证数据集,无法进一步降低错误率为止。
算法参数
Scikit-learn的决策树子模块tree提供了DecisionTreeClassifier分类模型,还提供了一个树形结构导出工具export_graphviz( )函数。借助graphviz绘图库,可以将决策树绘制成树形图。不过graphviz绘图库的安装有些复杂,不仅要安装graphviz模块(使用pip命令安装即可),还要安装graphviz软件。
scikit-learn使用sklearn.tree.DecisionTreeClassifier类来实现决策树分类算法。其中几个典型的参数解释如下。
- criterion:特征选择算法。一种是基于信息熵,另外一种是基于基尼不纯度。有研究表明,这两种算法的差异性不大,对模型的准确性没有太大的影响。相对而言,信息熵运算效率会低一些,因为它有对数运算。更详细的信息,可通过搜索decision tree gini vs.entropy获取。
- splitter:创建决策树分支的选项,一种是选择最优的分支创建原则,另外一种是从排名靠前的特征中,随机选择一个特征来创建分支,这个方法和正则项的效果类似,可以避免过拟合问题。
- max_depth:指定决策树的最大深度。通过指定该参数,用来解决模型过拟合问题。
- min_samples_split:这个参数指定能创建分支的数据集的大小,默认是2。如果一个节点的数据样本个数小于这个数值,则不再创建分支。这也是一种前剪枝的方法。
- min_samples_leaf:创建分支后的节点样本数量必须大于等于这个数值,否则不再创建分支。这也是一种前剪枝的方法。
- max_leaf_nodes:除了限制最小的样本节点个数,该参数可以限制最大的样本节点个数。
- min_impurity_split:可以使用该参数来指定信息增益的阈值。决策树在创建分支时,信息增益必须大于这个阈值,否则不创建分支。 从这些参数可以看到,scikit-learn有一系列的参数用来控制决策树生成的过程,从而解决过拟合问题。
下面以Scikit-learn内置的葡萄酒数据集为例,演示决策树分类模型的使用。葡萄酒数据集共有178个葡萄酒样本,每个样本都有酒精度、苹果酸、总酚类化合物、类黄酮、原花青素、脯氨酸等13项指标。这些样本分属3种不同的类型,每个葡萄酒样本都对应一个分类编号。以下代码使用90%的葡萄酒样本训练模型,使用10%的样本测试模型,模型精度在94%左右。
from sklearn.datasets import load_wine # 导入葡萄酒数据集模块
from sklearn import tree # 导入决策树子模块
from sklearn.model_selection import train_test_split as tsplit
from sklearn.metrics import classification_report
X, y = load_wine(return_X_y=True) # 获取葡萄酒数据集和分类标签集
x_train, x_test, y_train, y_test = tsplit(X, y, test_size=0.1)
m = tree.DecisionTreeClassifier() # 实例化决策树分类模型
m.fit(x_train, y_train)
# DecisionTreeClassifier(ccp_alpha=0.0, class_weight=None, criterion='gini',
# max_depth=None, max_features=None, max_leaf_nodes=None,
# min_impurity_decrease=0.0, min_impurity_split=None,
# min_samples_leaf=1, min_samples_split=2,
# min_weight_fraction_leaf=0.0, presort='deprecated',
# random_state=None, splitter='best')
precision = m.score(x_test, y_test)
print('测试集分类准确率:%0.2f'%precision)
# 测试集分类准确率:0.94
y_pred = m.predict(x_test)
report = classification_report(y_test, y_pred)
print('测试集分类结果报告:\n', report)
# 测试集分类结果报告:
# precision recall f1-score support
#
# 0 0.88 0.88 0.88 8
# 1 0.83 0.83 0.83 6
# 2 1.00 1.00 1.00 4
#
# accuracy 0.89 18
# macro avg 0.90 0.90 0.90 18
# weighted avg 0.89 0.89 0.89 18
决策树分类模型非常直观,易于理解,模型训练也不需要太多的数据。不过,决策树分类模型的稳定性比较低,因为数据中的微小变化可能会导致生成完全不同的决策树。另外,决策树分类模型容易产生一个过于复杂的模型,因此模型的泛化性不是很理想。
随机森林
随机森林是一种相对较新的机器学习算法,几乎能预测任何数据类型的问题,拥有广泛的应用前景。熟悉了决策树就能很容易理解随机森林。随机森林就是将多棵决策树集成在一起的算法,它的基本单元是决策树,而它本质上属于机器学习的一大分支—集成学习(Ensemble Learning)方法。
随机森林包含的每棵决策树都是一个分类模型,对于一个输入样本,每个分类模型都会产生一个分类结果,类似投票表决。随机森林集成了所有的“投票”分类结果,并将“投票”次数最多的类别指定为最终的输出类别。随机森林每颗决策树的训练样本都是随机的,决策树中训练集的特征列也是随机选择确定的。正是因为这两个随机性的存在,使得随机森林不容易陷入过拟合,并且具有很好的抗噪能力。
考虑到随机森林的每一棵决策树中训练集的特征列是随机选择确定的,因此它更适合处理具有多特征列的数据,所以选择Scikit-learn内置的威斯康星州乳腺癌数据集来演示随机森林分类模型的使用。
该数据集有569个乳腺癌样本,每个样本包含半径、纹理、周长、面积、是否平滑、是否紧凑、是否凹凸等30个特征。
from sklearn.datasets import load_breast_cancer
ds = load_breast_cancer() # 加载威斯康星州乳腺癌数据集
ds.data.shape # 569个乳腺癌样本,每个样本包含30个特征
# (569, 30)
下面的代码使用交叉验证函数cross_val_score( )来评估决策树分类模型和随机森林分类模型的精度。交叉验证的原理是将样本分成n份,每次用其中的n-1份作训练集,剩余1份作测试集,训练n次,返回每次的训练结果。随机森林分类模型从Scikit-learn的集成子模块(ensemble)导入。
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
dtc = DecisionTreeClassifier() # 实例化决策树分类模型
rfc = RandomForestClassifier() # 实例化随机森林分类模型
dtc_scroe = cross_val_score(dtc, ds.data, ds.target, cv=10) # 交叉验证
dtc_scroe # 决策树分类模型交叉验证10次的结果
# array([0.94736842, 0.87719298, 0.92982456, 0.89473684, 0.94736842,
# 0.87719298, 0.89473684, 0.94736842, 0.92982456, 0.91071429])
dtc_scroe.mean() # 决策树分类模型交叉验证10次的平均精度
# 0.9156328320802005
# rfc_scroe = cross_val_score(rfc, ds.data, ds.target, cv=10) # 交叉验证
# rfc_scroe # 随机森林分类模型交叉验证10次的结果
# array([0.98245614, 0.89473684, 0.94736842, 0.94736842, 0.98245614,
# 0.98245614, 0.96491228, 0.98245614, 0.94736842, 0.96428571])
rfc_scroe.mean()# 随机森林分类模型交叉验证10次的平均精度
# 0.9595864661654134
决策树分类模型和随机森林分类模型的交叉验证结果表明,随机森林分类模型的分类精度明显优于决策树分类模型。而且合理选择参数(参数调优),随机森林分类模型的分类精度还有提升空间。
在当前所有算法中,随机森林分类模型具有很高的准确率,能够处理很高维度(特征很多)的数据,并且不用做特征选择(因为特征子集是随机选择的)。随机森林分类模型包含的各个决策树之间是相互独立的,可以通过并行训练提升训练速度。不过,随机森林分类模型也不是完美无缺的,它的参数调优相对复杂,在某些噪声较大的分类或回归问题上会出现过拟合。
支持向量机分类
支持向量机(Support Vector Machine,SVM)的基本原理是找到一个将所有数据样本分割成两部分的超平面,使所有样本到这个超平面的累计距离最短。什么是超平面呢?超平面是指n维线性空间中维度为n-1的子空间。例如,在二维平面中,一维的直线可以将二维平面分割成两部分;在三维空间中,二维的平面可以将空间分成两部分。
显然,SVM是一种二分类模型。SVM在解决小样本、非线性及高维模式识别中表现出许多特有的优势,并且还能够推广应用到函数拟合等其他机器学习问题中。不过,SVM解决多元分类问题时效率较低,也难以对大规模训练样本实施训练。
Scikit-learn的支持向量机子模块(svm)提供了三个分类模型:LinearSVC、NuSVC和SVC。SVC分类模型和NuSVC分类模型的方法类似,都是基于libsvm实现,它们的区别是损失函数的度量方式不同(NuSVC分类模型中的nu参数和SVC分类模型中的C参数);LinearSVC分类模型实现了线性分类支持向量机,基于liblinear实现,既可以用于二类分类,也可以用于多类分类。
使用的威斯康星州乳腺癌数据集只有良性和恶性两个标签,正好适合测试支持向量机分类模型。以下代码使用交叉验证函数测试了三个分类模型的分类精度。其中SVC分类模型和NuSVC分类模型均使用默认参数,而LinearSVC分类模型指定了参数dual为False(当样本数量大于特征数量时,参数dual倾向为False,否则liblinear不收敛)。
>>> from sklearn.datasets import load_breast_cancer
>>> from sklearn.model_selection import cross_val_score
>>> from sklearn import svm
>>> ds = load_breast_cancer() # 加载威斯康星州乳腺癌数据集
>>> msvc = svm.SVC() # 实例化SVC分类模型
>>> mnusvc = svm.NuSVC() # 实例化NuSVC分类模型
>>> mlsvc = svm.LinearSVC(dual=False) # 实例化LinearSVC分类模型
>>> score_msvc = cross_val_score(msvc, ds.data, ds.target, cv=10)
>>> score_mnusvc = cross_val_score(mnusvc, ds.data, ds.target, cv=10)
>>> score_mlsvc = cross_val_score(mlsvc, ds.data, ds.target, cv=10)
>>> score_msvc.mean() # SVC分类模型交叉验证10次的平均精度
0.9138784461152882
>>> score_mnusvc.mean() # NuSVC分类模型交叉验证10次的平均精度
0.8734962406015038
>>> score_mlsvc.mean() # LinearSVC分类模型交叉验证10次的平均精度
0.9542931402580526
针对威斯康星州乳腺癌数据集的分类,LinearSVC分类模型的表现最好,准确率超过95%,NuSVC分类模型和SVC分类模型的表现只能算差强人意。