Sklearn学习笔记

机器学习

若是一个程序能够在任务T上,随着经验E的增长,效果P也能够随之增长,则称这个程序能够从经验中学习。html

机器学习分类

机器学习能够分为如下两类node

监督学习(Supervised learning)ios

经过大量已知的输入和输出相配对的数据,让计算机从中学习出规律,从而能针对一个新的输入做出合理的输出预测。git

好比,有大量不一样特征(面积、地理位置、朝向、开发商)的房子的价格数据,经过学习这些数据,能预测已知特征的房子价格。这种称为回归学习(Regression learning)即输出结果是一个具体的数值,它的预测模型是一个连续的函数。程序员

再好比咱们有大量的邮件,每一个邮件都已经标记是不是垃圾邮件。经过学习这些已标记的邮件数据,最后得出一个模型,这个模型对新的邮件,能准确地判断出该邮件是不是垃圾邮件,这种称为分类学习(Classfication learning),即输出结果是离散的,即要么输出1表示是垃圾邮件,要么输出0表示不是垃圾邮件。面试

无监督学习(Unsupervised learning)算法

经过学习大量的无标记数据,去分析出数据自己的内在特色和结构。数组

好比有大量的用户购物的历史记录信息,从数据中去分析用户的不一样类别。针对这个问题,最终能划分几个类别?每一个类别有哪些特色?这个称为聚类(Clustering)。与分类学习不一样,分类问题是已知哪几种类别,聚类问题是不知道有哪些类别。答案是未知的,须要利用算法从数据里挖掘出数据的特色和结构。性能优化

两类机器学习的区别:网络

有监督学习的训练数据里有已知的结果来监督,无监督学习的训练数据里没有结果监督,不知道到底能分析出什么样的结果出来。

机器学习开发步骤

假设,咱们要开发一个房价评估系统,对一个已知特征的房子价格进行评估预测。那么须要如下几个步骤

1. 数据采集和标记:须要大量不一样特征的房子和对应的价格信息,如面积、地理位置、朝向、价格等。另外还有房子所在地的学校状况。这些数据叫作训练样本,或者数据集。房子的面积、地理位置等称为特征。数据采集阶段,须要收集尽可能多的特征。特征越全,数据越多,训练出来的模型越准确。

经过这个过程能够感觉到数据采集的成本是很高的。拥有海量数据那么他自己的估值就会很高。

有时候为了避税,房子的评估价格会比房子的真实交易价格低不少。这时,就须要采集房子的实际成交价格,这一过程称为:数据标记。标记能够是人工标记,好比说从房中介打听价格,也能够是自动标记,好比分析数据等。数据标记对监督学习方法是必须的。

2. 数据清洗:假设采集到的数据里,关于房子面积,有按平方米计算的,也有按平方英寸计算的,这时须要对面积单位进行统一。这个过程称为数据清洗。包括去掉复杂的数据及噪声数据,让数据更具有结构化特征。

3. 特征选择:假设采集到100个房子的特征,经过逐个分析这些特征,最终选择了30个特征做为输入。这个过程称为特征选择。特征选择的方法之一是人工选择方法,即对逐个特征进行人员分析,而后选择合适的特征集合。另一个方法是经过模型来自动完成的,例如PAC算法

4. 模型选择:房价评估系统是属于有监督学习的回归学习类型,能够选择最简单的线性方程来模拟。选择哪些模型,和问题领域、数据量大小、训练时长、模型的准确度等多方面有关。

5. 模型训练和测试:把数据集分红训练数据集测试数据集。通常按照8:2或7:3来划分,而后用训练数据集来训练模型。训练出参数后再使用测试数据集来测试模型的准确度。单独分出一个测试数据集,是由于必须确保测试的准确性,即模型的准确性是要用没见过的数据来测试,而不能用训练的数据来测试。理论上更合理的数据集划分方案是分红3个,此外还要再加一个交叉验证数据集

6. 模型性能评估和优化:模型出来后须要对机器学习的算法模型进行性能评估。性能评估包括,训练时长是指

须要花多长时间来训练这个模型。对一些海量数据的机器学习应用,可能须要一个月甚至更长的时间来训练一个模型,这个时候算法的训练性能就变得很重要了。

另外须要判断数据集是否足够多,通常而言,对于复杂特征的系统,训练数据集越大越好。而后还须要判断模型的准确性,即对一个新的数据可否准确地进行预测。最后须要判断模型可否知足应用场景的性能要求,若是不能知足要求,就须要优化,而后继续对模型进行训练和评估,或者更换为其余模型。

7. 模型使用:训练出来的模型能够把参数保存起来,下次使用时直接加载便可,通常来说,模型训练须要的计算量是很大的,也须要较长时间的训练,由于一个好的模型参数,须要对大型数据集进行训练后才能获得。而真正使用模型时,其计算量是比较小的,通常是直接把新样本做为输入,而后调用模型便可获得预测结果。

Scikit-learn简介

scikit-lear是一个开源的Python语言机器学习工具包,涵盖了几乎全部主流机器学习算法的实现,而且提供了一致的调用接口。

给予Numpy和scipy等Python数值计算库,提供了高效的算法实现。优势有如下几点

文档齐全:官方文档齐全,更新及时

接口易用:针对全部的算法提供了一致的接口调用规则

算法全面:涵盖了主流机器学习任务的算法,包括回归算法、分类算法、聚类分析、数据降维处理等

scikit-learn不支持分布式计算,不适用用来处理超大型数据。

数字识别示例

监督学习,数据是标记过的手写数字图片。经过采集足够多的手写数字样本,选择合适的模型,并使用采集到的数据进行模型训练,,最后验证识别程序的正确性。

1. 数据采集和标记

scikit-learn自带了一些数据集,其中一个是手写数字识别图片的数据,使用如下代码来加载并显示出来

from sklearn import datasets
from matplotlib import pyplot as plt # 加载数据 digits = datasets.load_digits() # 能够在notebook环境把数据的图片用Matchplotlib显示出来 images_and_labels = list(zip(digits.images, digits.target)) plt.figure(figsize=(8, 6), dpi=200) for index, (image, label) in enumerate(images_and_labels[:8]): plt.subplot(2, 4, index+1) plt.axis('off') plt.imshow(image, cmap=plt.cm.gray_r, interpolation='nearest') plt.title('Digit:%i' % label, fontsize=20)

图片就是一个个手写的数字

2. 特征选择

针对手写的图片,最直接的方法是使用图片的每一个像素点做为一个特征。好比图片是200*200的分辨率,那么就有40000个特征了,及特征向量的长度是40000

实际上,scikit使用Numpy的array对象来表示数据,全部的图片数据保存在digits.images里,每一个元素都是一个88尺寸的灰阶图片。咱们在进行机器学习时,须要把数据保存为样本个数x特征个数的array对象,针对手写数字识别这个案例,scikit已经为咱们转换好了,就保存在digits.data数据里,能够经过digits.data.shape来查看它们的数据格式

print("shape of raw image data:{0}".format(digits.images.shape))  # (1797, 8, 8)
print("shape of data: {0}".format(digits.data.shape)) # (1797, 64)

总共有1797个训练样本,其中原始的数据是8*8的图片,而寻来你的数据是把图片的64个像素点都转换为特征。 

3. 数据清洗

人们不可能在8*8这种分辨率上写数字,在采集的时候是让用户在一个大图片上写数字。可是若是图片是200*200,那么一个训练样例就有40000个特征,计算量将是巨大的。为了减小计算量,也为了模型的稳定性,须要把200*200图片缩小为8*8。这个过程就是数据清洗,把不适合用来作训练的数据进行预处理,从而转换为适合的数据。

4. 模型选择

不一样的机器学习算法针对特定的机器学习应用有不一样的效率,模型的选择和验证留到后面章节详细介绍。这里使用支持向量机(SVM) 

5. 模型训练

首先须要把数据分为训练数据集和测试数据集。使用8:2的划分。接着使用训练数据集进行训练,完成后clf就会包含咱们训练出来的模型参数,使用这个模型对象进行预测便可。 

from sklearn import datasets, svm
from sklearn.model_selection import train_test_split # 加载数据 digits = datasets.load_digits() # 把数据分红训练数据集和测试数据集 Xtrain, Xtest, Ytrain, Ytest = train_test_split(digits.data, digits.target, test_size=0.20, random_state=2) # 接着使用训练数据集来训练模型 clf = svm.SVC(gamma=0.001, C=100.) clf.fit(Xtrain, Ytrain)

6. 模型测试 

咱们用训练出来的模型测试一下准确度,能够直接把预测结果和真实结果作比较。scikit提供了现成的比较方法

from sklearn import datasets, svm
from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score from matplotlib import pyplot as plt # 加载数据 digits = datasets.load_digits() # 把数据分红训练数据集和测试数据集 Xtrain, Xtest, Ytrain, Ytest = train_test_split(digits.data, digits.target, test_size=0.20, random_state=2) # 接着使用训练数据集来训练模型 clf = svm.SVC(gamma=0.001, C=100.) clf.fit(Xtrain, Ytrain) # 评估模型的准确度 Ypred = clf.predict(Xtest) accuracy_score(Ytest, Ypred)

模型的准确率大概97.8%

除此以外,能够直接把测试数据集里的部分图片显示出来,而且在左下角显示预测值,右下角显示真实值。

% matplotlib inline

from sklearn import datasets, svm from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score from matplotlib import pyplot as plt # 加载数据 digits = datasets.load_digits() # 把数据分红训练数据集和测试数据集 Xtrain, Xtest, Ytrain, Ytest = train_test_split(digits.data, digits.target, test_size=0.20, random_state=2) # 接着使用训练数据集来训练模型 clf = svm.SVC(gamma=0.001, C=100.) clf.fit(Xtrain, Ytrain) # 评估模型的准确度 Ypred = clf.predict(Xtest) accuracy_score(Ytest, Ypred)
clf.scroe(Xtest,Ytext) # 两个同样 # 查看预测状况 fig, axes = plt.subplots(4, 4, figsize=(8, 8)) fig.subplots_adjust(hspace=0.1, wspace=0.1) for i, ax in enumerate(axes.flat): ax.imshow(Xtest[i].reshape(8, 8), cmap=plt.cm.gray_r, interpolation='nearest') ax.text(0.05, 0.05, str(Ypred[i]), fontsize=32, transform=ax.transAxes, color="green" if Ypred[i] == Ytest[i] else 'red') ax.text(0.8, 0.05, str(Ytest[i]), fontsize=32, transform=ax.transAxes, color="black") ax.set_xticks([]) ax.set_yticks([])

7. 模型保存与加载

当咱们对模型的准确度感到满意后,就能够把模型保存下来。这样下次须要预测时,能够直接加载模型进行预测,而不是从新训练一遍模型。能够使用下面的代码来保存模型:

# 保存模型参数
from sklearn.externals import joblib
joblib.dump(clf, 'digits_svm.pk1')

在咱们使用的时候,就能够直接加载成clf使用

from sklearn import datasets
from sklearn.externals import joblib from sklearn.metrics import accuracy_score from sklearn.model_selection import train_test_split # 加载数据 digits = datasets.load_digits() # 把数据分红训练数据集和测试数据集 Xtrain, Xtest, Ytrain, Ytest = train_test_split(digits.data, digits.target, test_size=0.20, random_state=2) clf = joblib.load('digits_svm.pk1') Ypred = clf.predict(Xtest) print(accuracy_score(Ytest, Ypred))

Scikit-Learn通常性原理和通用规则 

scikit包含大部分流行的监督学习算法(分类和回归)和无监督学习算法(聚类和数据降维)的实现

1. 评估模型对象

scikit里的全部算法都以一个评估模型对象来建立对外提供接口。上面的svm.SVC()函数返回的就是一个支持向量机评估模型对象。 

建立评估模型对象时,能够指定不一样的参数,这个称为评估对象参数,评估对象参数直接影响评估模型训练时的效率以及准确性。

学习机器学习算法的原理,其中一项很是重要的任务就是了解不一样的机器学习算法有哪些可调的参数,这些参数表明什么意思。

对机器学习算法的性能以及准确性有没有什么影响。由于在工程应用上,要从头实现一个机器学习算法的可能性很低,除非是数值计算科学家。更多状况下,是分析采集到的数据,根据数据特征选择合适的算法,而且调整算法的参数,从而实现算法效率和准确度之间的平衡。

2. 模型接口

scikit全部的评估模型对象都有fit()这个接口,是用来训练模型的接口。针对有监督学习的机器学习,使用fit(X,y)来进行训练,其中y是标记数据。针对无监督的机器学习算法,使用fit(X)来进行训练,由于无监督机器学习算法的数据集是没有标记的,不须要传入y。

针对全部的监督学习算法,scikit模型对象提供了predict()接口,通过训练的模型,能够用这个接口来进行预测。针对分类问题,有些模型还提供了predict_proba()的接口,用来输出一个待预测的数据,属于各类类型的可能性,而predict()接口直接返回了可能性最高的那个类别。

几乎全部的模型都提供了scroe()接口来评价一个模型的好坏,得分越高越好。不是全部的问题都只有准确度这个评价标准,好比异常检测系统,一些产品不良率能够控制到10%如下,这个时候最简单的模型是无条件地所有预测为合格,即无条件返回1,其准确率将达99.999%以上,但实际上这是一个很差的模型。评价这种模型,就须要使用查准率和召回率来衡量。

针对无监督的机器学习算法,scikit的模型对象也提供了predict()接口,用来对数据进行聚类分析,把新数据纳入某个聚类里。无监督学习算法还有transform()接口,这个接口用来进行转换,好比PCA算法对数据进行降维处理时,把三维数据降为二维数据,此时调用transform()算法便可把一个三维数据转换为对应的二维数据。

模型接口也是scikit工具包的最大优点之一,即把不一样的算法抽象出来,对外提供一致的接口调用。

3. 模型检验

机器学习应用开发的一个很是重要的方面就是模型检验,须要检测咱们训练出来的模型,针对陌生数据其预测准确性如何。除了模型提供的score()接口外,在sklearn.metrics包的下面还有一系列用来检测模型性能的方法。

4. 模型选择

模型选择是个很是重要的课题,根据要处理的问题性质,数据是否通过标记。数据规模多大,等等这些问题,能够对模型有个初步的选择。下面两个图是速查表,能够快速选择要给相对合适的模型。

机器学习理论基础

过拟合和欠拟合

拟合:指已知某函数的若干离散函数值{f1,f2,…,fn},经过调整该函数中若干待定系数f(λ1, λ2,…,λn),使得该函数与已知点集的差异(最小二乘意义)最小。

离散:连续的对应(就是反义词)就是离散 。离散就是不连续。好比人眼看到的图片,就是连续的。计算机里的照片就是离散的二进制比特流,图像(灰度图像)像素的灰度值在计算机里是从0到255(其实是用二进制表示的),即0,1,2,3,...,255,0表明黑色,255表明白色,只有0到255的整数,没有其余整数,并且没有两个整数之间的小数,即不连续的,这就叫离散。

过拟合是指模型能很好地拟合训练样本, 但对新数据的预测准确性不好。欠拟合是指模型不能很好的拟合训练样本,且对新数据的预测准确性也很差。

n_dots = 20
x = np.linspace(0, 1, n_dots) y = np.sqrt(x) + 0.2 * np.random.rand(n_dots) - 0.1

训练样本是,其中r是[-0.1,0.1] 之间的一个随机数。

而后分别用一阶多项式、三阶多项式、十阶多项式3个模型来拟合这个数据集,获得的结果以下

多项式(polynomial)是指由变量系数以及它们之间的加、减、乘、幂运算(非负整数次方)获得的表达式。

图中的点是咱们生成的20个训练样本。虚线为实际的模型,实线是用训练样本拟合出来的模型

左侧是欠拟合(underfitting),也称高误差(high bias),由于试图用一条直线来拟合样本数据。

右侧是过拟合(overfitting),也称高方差(high variance),用了十阶多项式来拟合数据,虽然模型对现有的数据集拟合的很好,但对新数据预测偏差却很大。

中间的模型较好的拟合了数据集,能够看到虚线和实线基本重合。

成本函数

成本是衡量模型与训练样本符合程度的指标。是针对全部的训练样本,模型拟合出来的值与训练样本的真实值的偏差平均值。而成本函数就是成本与模型参数的函数关系。模型训练的过程,就是找出合适的模型参数,使得成本函数的值最小。成本函数记为J(θ),其中θ表示模型参数

用一阶多项式来拟合数据,则获得的模型是y=θ01x。此时[θ01] 构成的向量就是模型参数。训练这个模型的目标,就是找出合适的模型参数[θ01] ,使得全部的点到这条直线上的距离最短。

不一样的模型参数θ对应不一样的直线,明显能够看出L2比L1更好地拟合数据集。根据成本函数的定义,咱们能够容易地得出模型的成本函数公式

m是训练样本个数,20个点,h(x(i)) 就是模型对每一个样本的预测值,y(i) 是每一个样本的真实值。这个公式实际上就是线性回归算法的成本函数简化表达式。

一个数据集可能有多个模型能够用来拟合它,而一个模型有无穷多个模型参数,针对特定的数据集和特定的模型,只有一个模型参数能最好地拟合这个数据集,这就是模型和模型参数的关系

针对一个数据集,能够选择不少个模型来拟合数据,一旦选定了某个模型,就须要从这个模型的无穷多个参数里找出一个最优的参数,使得成本函数的值最小。

多个模型之间怎么评价好坏呢?在例子中,十阶多项式针对训练样本的成本最小,由于它的预测曲线几乎穿过了全部的点,训练样本到曲线的距离的平均值最小。可是十阶多项式不是最好的模型,由于它过拟合了。

模型准确性

测试数据的成本,Jtest(θ) 是评估模型准确性的最直观的指标,Jtest(θ) 值越小说明模型预测出来的值与实际值差别越小,对新数据的预测准确性就越好。须要特别注意,用来测试模型准确性的测试数据集,必须是模型没见过的数据。

这就是须要把数据集分红训练数据集和测试数据集的缘由。通常是按照8:2或7:3来划分,而后用训练数据集来训练模型,再用测试数据集来测试模型的准确性,根据模型的准确性来评价模型的性能。

针对上文介绍的线性回归算法,能够用下面公式计算测试数据集的偏差,其中m是测试数据集的个数。

模型性能的不一样表述方式

在scikit-learn里,不使用成本函数来表达模型的性能,是使用分数来表达,这个分数老是在[0,1]之间,数值越大说明模型准确性越好。当模型训练完成后,调用模型的score(X_test,y_test)便可算出模型的分数值,其中X_test和y_test是测试数据集样本。

模型分数(准确性)与成本成反比。即分数越大,准确性越高,偏差越小,成本越低;反之分数越小、准确性越低、偏差越大、成本越高

交叉验证数据集

一个更科学的方法是把数据集分红3份,分别是训练数据集,交叉验证数据集和测试数据集。推荐比例是6:2:2

为何须要交叉验证数据集呢?以多项式模型选择为例,假设咱们用一阶多项式、二阶多项式、三阶多项式...十阶多项式的模型参数。这10个模型里,那个模型更好呢?

这个时候会用测试数据集算出针对测试数据集的成本Jtest(θ) ,看那个模型的测试数据集成本最低(偏差平均值),咱们就选择这个多项式来拟合数据,但实际上,这是有问题的。测试数据集的最主要功能是测试模型的准确性,须要确保模型没见过这些数据。如今用测试数据集来选择多项式的阶数d,至关于把测试数据集提早让模型见过了。这样选择出来的多项式阶数d自己就是对训练数据集最友好的一个,这样模型的准确性测试就失去意义了。

为了解决这个问题,把数据分红3部分,随机选择60%的数据做为训练数据集,其成本记为J(θ),随机选择20%的数据做为交叉验证数据集,其成本记为Jcv(θ),剩下的20%做为测试数据集,其成本记为Jtest(θ) 

在模型选择时,咱们使用训练数据集来训练算法参数,用交叉验证数据集来验证参数。选择交叉验证数据集的成本Jcv(θ)最小的多项式来做为数据拟合模型,最后再用测试数据集来测试选择出来的模型针对测试数据集的准确性。

在模型选择中,使用交叉验证数据集,因此筛选模型多项式阶数d的过程当中,实际上并无使用测试数据集。保证了使用测试数据集来计算成本衡量模型的准确性,测试数据集没有参与模型选择的过程。

在实践过程当中,不少人直接把数据集分红训练数据集和测试数据集,而没有分出交叉验证数据集。由于不少时候并不须要横向去对比不一样的模型。工程上,咱们最主要的工做不是选择模型,而是获取更多数据、分析数据、挖掘数据。

学习曲线

咱们能够把Jtrain(θ)和Jcv(θ) 做为纵坐标,画出与训练数据集m的大小关系,这就是学习曲线。经过学习曲线,能够直观地观察到模型的准确性与训练数据集大小的关系。

若是数据集的大小为m,则经过下面的流程便可画出学习曲线:

  • 把数据集分红训练数据集和交叉验证数据集。
  • 取训练数据集的20%做为训练样本,训练出模型参数。
  • 使用交叉验证数据集来计算训练出来的模型的准确性。
  • 以训练数据集的准确性,交叉验证的准确性做为纵坐标,训练数据集个数为横坐标,在坐标轴上画出上述步骤计算出来的模型准确性。
  • 训练数据集增长10%,跳到步骤3继续执行,直到训练数据集大小为100%为止

学习曲线要表达的内容是,当训练数据集增长时,模型对训练数据集拟合的准确性以及对交叉验证数据集预测的准确性的变化规律。

经过一个例子来看看scikit-learn里如何画出模型的学习曲线,从而判断模型的准确性以及优化方向。

生成一个在附近波动的点来做为训练样本,不过此次要多生成一些点,由于要考虑当训练样本数量增长的时候,模型的准确性是怎么变化的。

实例画出学习曲线

画出模拟的学习曲线,从而判断模型的准确性及优化方向。

使用开头的例子生成附近波动的点来做为训练样本,要考虑当训练样本数量增长的时候,模型的准确性是怎么变化的。

import matplotlib.pyplot as plt
import numpy as np

n_dots = 200
X = np.linspace(0, 1, n_dots)
y = np.sqrt(X) + 0.2 * np.random.rand(n_dots) - 0.1
# 须要用到n_sample * n_feature的矩阵,因此须要转化为矩阵
X = X.reshape(-1, 1)
y = y.reshape(-1, 1)

# 须要构造一个多项式模型。使用Pipeline来构造多项式模型,这个流水线里能够包含多个数据处理模型,
# 前一个模型处理完,转到下一个模型处理
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression


# 该函数生成一个多项式模型,其中degree表示多项式的阶数。
def polynomial_model(degree=1):
    polynomial_features = PolynomialFeatures(degree=degree, include_bias=False)
    linear_regression = LinearRegression()
    # 先增长多项式阶数,而后用线性回归算法来拟合数据
    pipeline = Pipeline([("polynomial_features", polynomial_features), ("linear_regression", linear_regression)])
    return pipeline  # 使用sklearn.model_selection.learning_curve函数来画出学习曲线,


# 会自动把训练样本的数量按照预约的规则逐渐增长,而后画出不一样训练样本数量时的模型准确性。
# train_sizes参数指定训练样本数量的变化规则。train_sizes=np.linspace(. 1,1.0,5)
# 表示把训练样本数量从0.1~1分红五等分,从序列中取出训练样本数量百分比,
# 逐个计算在当前训练样本数量状况下训练出来的模型准确性
from sklearn.model_selection import learning_curve
from sklearn.model_selection import ShuffleSplit


# 这个函数实现的功能就是画出模型的学习曲线。当计算模型的准确性时,
# 是随机从数据集中分配出训练样本和交叉验证样本,这样会致使数据分布不均匀。
# 一样训练样本数量的模型,因为随机分配,致使每次计算出来的准确性都不同。
# 咱们在计算模型的准确性是屡次计算,并求准确性的平均值和方差。
# plt.fill_between()函数会把模型准确性的平均值的上下方差的空间里用颜色填充。
# 而后用plt.plot()函数画出模型准确性的平均值
def plot_learning_curve(estimator, title, X, y, ylim=None, cv=None, n_jobs=1, train_sizes=np.linspace(.1, 1.0, 5)):
    plt.title(title)
    if ylim is not None:
        plt.ylim(*ylim)
        plt.xlabel("Training examples")
        plt.ylabel("Score")
        train_sizes, train_scores, test_scores = learning_curve(estimator, X, y, cv=cv, n_jobs=n_jobs,
                                                                train_sizes=train_sizes)
        train_scores_mean = np.mean(train_scores, axis=1)
        train_scores_std = np.std(train_scores, axis=1)
        test_scores_mean = np.mean(test_scores, axis=1)
        test_scores_std = np.std(test_scores, axis=1)
        plt.grid()
        plt.fill_between(train_sizes, train_scores_mean - train_scores_std, train_scores_mean + train_scores_std,
                         alpha=0.1, color="r")
        plt.fill_between(train_sizes, test_scores_mean - test_scores_std, test_scores_mean + test_scores_std, alpha=0.1,
                         color="g")
        plt.plot(train_sizes, train_scores_mean, 'o--', color="r", label="Trainning score")
        plt.plot(train_sizes, test_scores_mean, 'o-', color="g", label="Cross-validation score")
        plt.legend(loc="best")
        return plt


# 使用ploynomial_model函数构造出3个模型,分别是一阶多项式、三阶多项式、十阶多项式
# 为了让学习曲线更平滑,计算10次交叉验证数据集的分数
cv = ShuffleSplit(n_splits=10, test_size=0.2, random_state=0)
titles = ['Learning Curves(Under Fitting)', 'Learning Curves', 'Learning Curves(Over Fitting)']
degrees = [1, 3, 10]
plt.figure(figsize=(18, 4), dpi=200)
for i in range(len(degrees)):
    plt.subplot(1, 3, i + 1)
    plot_learning_curve(polynomial_model(degrees[i]), titles[i], X, y, ylim=(0.75, 1.01), cv=cv)

plt.show()

 

左图:一阶多项式,欠拟合;中图:三阶多项式,较好地拟合了数据集;右图:十阶多项式,过拟合。

虚线:针对训练数据集计算出来的分数,即针对训练数据集拟合的准确性

实线:针对交叉验证数据集计算出来的分数,即针对交叉验证数据集预测的准确性

从左图咱们能够观察到,当模型欠拟合时,随着训练数据集的增长,交叉验证数据集的准确性(实线)逐渐增大,逐渐和训练数据集的准确性(虚线)靠近,但其整体水平比较低,收敛在0.88左右。其训练数据集的准确性也比较低,收敛在0.90左右。这就是过拟合的表现。从这个关系能够看出来,当发生高误差时,增长训练样本数量不会对算法准确性有较大的改善。

从右图能够观察到,当模型过拟合时,随着训练数据集的增长,交叉验证数据集的准确性(实线)也在增长,逐渐和训练数据集的准确性(虚线)靠近,但二者之间的间隙比较大。训练数据集的准确性很高,收敛在0.95左右是三者中最高的(高方差),但其交叉验证数据集的准确性值却较低,最终收敛在0.91左右。

中图,咱们选择的三阶多项式较好地拟合了数据,最终训练数据集的准确性(虚线)和交叉验证数据集的准确性(实线)靠的很近,最终交叉验证数据集收敛在0.93附近,训练数据集的准确性收敛在0.94附近。3个模型对比,这个模型的准确性最好。

当须要改进学习算法时,能够画出学习曲线,以便判断算法是处在高误差仍是高方差的问题

高误差:训练偏差很大,训练偏差与测试偏差差距小,随着样本数据增多,训练偏差增大。解决方法:

1.寻找更好的特征(具备表明性的)

2.用更多的特征(增大输入向量的维度)

高方差:训练偏差小,训练偏差与测试偏差差距大,能够经过增大样本集合来减少差距。随着样本数据增多,测试偏差会减少。解决方案:

1.增大数据集合(使用更多的数据)

2.减小数据特征(减少数据维度)

学习曲线是诊断模型算法准确性的一个很是重要的工具。

过拟合和欠拟合的特征

过拟合:模型对训练数据集的准确性比较高,其成本Jtrain(θ)比较低,对交叉验证数据集的准确性比较低,其成本Jcv(θ)比较高。

欠拟合:模型对训练数据集的准确性比较低,其成本Jtrain(θ)比较高,对交叉验证数据集的准确性比较低,其成本Jcv(θ)比较高

一个好的机器学习算法应该是对训练数据集准确性高、成本低,即较准确地拟合数据,同时对交叉验证数据集准确性高、成本低、偏差小,即对未知数据有良好的预测性。

算法模型性能优化

若是咱们的机器学习算法不能很好地预测新数据时,须要先判断这个算法模型是欠拟合仍是过拟合

若是是过拟合,能够采起的措施以下

1. 获取更多的训练数据:从学习曲线的规律来看,更多的数据有助于改善过拟合问题

2. 减小输入的特征数量:好比,针对书写识别系统,原来使用200*200的图片,总共4万个特征。优化后,咱们能够把图片缩小为10*10的图片,总共100个特征。这样能够大大减小模型的计算量,同时也减小模型的复杂度,改善过拟合问题。

若是是欠拟合,说明模型太简单了,须要增长模型的复杂度。

1. 增长有价值的特征:从新解读并理解训练数据,好比针对房产价格预测的机器学习任务,原来只根据房子面积来预测价格,结果模型出现了欠拟合。优化后,咱们增长其余的特征,好比房子的朝向、户型、年代、房子旁边的学校的质量、房子的开发商、房子周边商业街个数、房子周边公园个数等。

2. 增长多项式特征:有时候,从已知数据里挖掘出更多特征不是件容易的事情,这个时候,能够用纯数学的方法,增长多项式特征。好比原来的输入特征只有x1,x2,优化后能够增长特征,变成x1,x2,x1x2,x12,x22。这样也能够增长模型复杂度,从而改善欠拟合的问题。当用一阶多项式拟合数据集时,使用的只有一个特征,而最终咱们用三阶多项式来拟合数据时,用的其实就是增长多项式特征这个方法。

查准率和召回率

模型准确性并不能评价一个算法的好坏。好比针对癌症筛查算法,根据统计,普通肿瘤中癌症的几率是0.5%。有个机器学习算法,测试得出的准确率是99.2%,错误率是0.8%。这个算法究竟是好仍是坏呢?若是努力改进算法,最终获得准确率是99.5%,错误率是0.5%,模型究竟是变好了仍是变坏了呢?

若是单纯从模型准确性的指标上很难判断究竟是变好了仍是变坏了。由于这个事情的先验几率过低了,加入写一个预测函数,老是返回0,即老是认为不会得癌症,那么咱们这个预测函数的准确率是99.5%,错误率是0.5%。由于整体而言,只有那0.5%真正得癌症的却被咱们误判了。

那么怎么来评价这类问题的模型好坏呢?引入了另外两个概念,查准率(Precision)和召回率(Recall)。仍是以癌症筛选为例:

在处理先验几率低的问题时,咱们老是把几率较低的事件定义为1,而且把y=1做为Positive的预测结果。对老是返回0的超级简单的肿瘤筛查预测函数,使用查准率和召回率来验证模型性能时,会发现查准率和召回率都是0,这是由于他永远没法正确地预测出恶性肿瘤,即TruePositive永远为0.

在scikit-learn里,评估模型性能的算法都在sklearn.metrics包里。其中计算查找率和召回率的API分别为sklearn.metrics.precision_score() 和 sklearn.metrics.recall_score()

F1 Score

如今有两个指标,若是一个算法的查准率是0.5,召回率是0.4。另一个算法查准率是0.02,召回率是1.0;那么两个算法到底哪一个好呢?

咱们引入了F1 Score的概念

其中P是查找率,R是召回率。这样就能够用一个数值直接判断哪一个算法性能更好。典型地,若是查找率或召回率有一个为0,那么F1 Score将会为0。而理想的状况下,查准率和召回率都为1,则算出来的F1 Score为1.

在scikit-learn里,计算F1 Score的函数是sklean.metrics.f1_score()

K-近邻算法

它是一个有监督的机器学习算法,k-近邻算法也称为knn算法,能够解决分类问题,也能够解决回归问题。

k-近邻算法的核心思想是未标记样本的类别,由距离其最近的k个邻居来投票决定。

假设,咱们有一个已经标记的数据集,即已经知道了数据集中每一个样本所属的类别。此时,有一个未标记的数据样本,咱们的任务是预测出这个数据样本所属的类别。k-近邻算法的原理是,计算待标记的数据样本和数据集中每一个样本的距离,取距离最近的k个样本。待标记的数据样本所属的类别,就由这k个距离最近的样本投票产生。

假设X_test为待标记的数据样本,X_train为已标记的数据集,须要以下步骤

1. 遍历X_train中的全部样本,计算每一个样本与X_test的距离,并把距离保存在Distance数组中

2. 对Distance数组进行排序,取距离最近的k个点,记为X_knn

3. 在X_knn中统计每一个类别的个数,即class()在X_knn中有几个样本,class1在X_knn中有几个样本等

4. 待标记样本的类别,就是在X_knn中样本个数最多的那个类别

算法优缺点

优势:准确性高,对异常值和噪声有较高的容忍度。缺点:计算量较大,对内存的需求也较大。从算法原理能够看出来,每次对一个未标记样本进行分类时,都须要所有计算一遍距离。

算法参数 

其算法参数是k,参数选择须要根据数据来决定。k值越大,模型的误差越大,对噪声数据越不敏感,当k值很大时,可能形成模型欠拟合; k值越小,模仿的方差就会越大,当k值过小,就会形成模型过拟合

算法的变种

k-近邻算法有一些变种,其中之一就是能够增长邻居的权重。默认状况下,计算距离时,都是使用相同权重。实际上,能够针对不一样的邻居指定不一样的距离权重,如距离越近权重越高。能够经过指定算法的weights参数来实现

另一个变种是,使用必定半径内的点取代距离最近的k个点。RadiusNeighborsClassifier类实现了这个算法的变种。当数据采样不均匀时,该算法变种能够取得更好的性能。

示例:使用k-近邻算法进行分类 

使用KNN进行分类处理的是sklearn.neighbors.KNeighbors Classifier类

1. 生成已标记的数据集

from sklearn.datasets.samples_generator 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.samples_generator包下的make_blobs()函数来生成数据集,上面代码中,生成60个训练样本,这60个样本分布在以centers参数指定中心点周围。cluster_std是标准差,用来指明生成的点分布的松散程度。生成的训练数据集放在变量X里面,数据集的类别标记在y里面。

把生成的点画出来

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')

这些点的分布状况在坐标轴上一目了然,其中三角形的点即各个类别的中心节点

2. 使用KNeighborsClassifier来对算法进行训练,咱们选择的参数是k=5

from sklearn.neighbors import KNeighborsClassifier

k = 5
clf = KNeighborsClassifier(n_neighbors=k)
clf.fit(X, y)

3. 对一个新的样本内进行预测

X_sample = [0, 2]
temp = np.array(X_sample).reshape((1, -1))
y_sample = clf.predict(temp)
neighbors = clf.kneighbors(temp, return_distance=False)

咱们要预测的样本是[0,2],使用kneighbors方法,把这个样本周围距离最近的5个点取出来。取出来的点是训练样本X里的索引,从0开始计算

4. 把待预测的样本以及和其最近的5个点标记出来

plt.figure(figsize=(16, 10), dpi=144)
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], X_sample[1], marker="x", c=y_sample[0], s=100, cmap='cool')
# 待预测的点

for i in neighbors[0]:
plt.plot([X[i][0], X_sample[0]], [X[i][1], X_sample[1]], 'k--', linewidth=0.6)

示例:使用k-近邻算法进行回归拟合

分类问题的预测值是离散的,能够用k-近邻算法在连续区间内对数值进行预测,进行回归拟合。 

使用k-近邻算法进行回归拟合的算法是sklearn.neighbors.KNeighborsRegressor

1. 生成数据集,它在余弦曲线的基础上加入噪声

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

2. 使用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() 方法计算拟合曲线针对训练样本的拟合准确性

3. 把这些预测点连起来,构成拟合曲线

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()

最终生成的拟合曲线以及训练样本数据以下

实例:糖尿病预测

加载数据

data = pd.read_csv('pima-indians-diabetes/diabetes.csv')

总共有768个样本,8个特征,Outcome为标记值,0表示没有糖尿病,1表示有糖尿病

Pregnancies:怀孕次数

Glucose:血浆葡萄糖浓度,采用2小时口服葡萄糖耐量实验测得

BloodPressure:舒张压(毫米汞柱)

SkinThickness:肱三头肌皮肤褶皱厚度(毫米)

Insulin:两小时血清胰岛素

BMI:身体质量指数,体重除以身高的平方

Diabetes Pedigree Function:糖尿病血统指数,糖尿病和家庭遗传相关

Age:年龄

data.groupby("Outcome").size()

进一步观察数据集中阳性和阴性样本的个数

其中阴性样本500例,阳性样本268例。须要对数据集进行简单处理,把8个特征值分离出来,做为训练数据集,把Outcome列分离出来做为目标值。而后把数据集划分为训练数据集和测试数据集

X = data.iloc[:, 0:8]
Y = data.iloc[:, 8]
print('shape of X {}; shape of Y {}'.format(X.shape, Y.shape))

from sklearn.model_selection import train_test_split
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.2)

模型比较

使用普通的k-均值算法、带权重的k-均值算法以及制定半径的k-均值算法分别对数据集进行拟合并计算评分

from sklearn.neighbors import KNeighborsClassifier, RadiusNeighborsClassifier

models = []
models.append(('KNN', KNeighborsClassifier(n_neighbors=2)))
models.append(('KNN with weights', KNeighborsClassifier(n_neighbors=2, weights='distance')))
models.append(('Radius Neighbors', RadiusNeighborsClassifier(n_neighbors=2, radius=500)))

results = []
for name, model in models:
model.fit(X_train, Y_train)
results.append((name, model.score(X_test, Y_test)))
for i in range(len(results)):
print('name:{}; score:{}'.format(results[i][0], results[i][1]))

输出内容以下:

name:KNN; score:0.6753246753246753
name:KNN with weights; score:0.6233766233766234
name:Radius Neighbors; score:0.6428571428571429

权重算法,咱们选择了距离越近,权重越高。

RadiusNeighborsClassifier模型的半径,选择了500。从输出能够看出,普通的k-均值算法性能仍是最好。

可是这个答案是不许确的,由于咱们的训练样本和测试样本是随机分配的,不一样的训练样本和测试样本组合可能致使计算出来的算法准确性是有差别的。

更准确的对比算法准确性,一个方法是屡次随机分配训练数据集和交叉验证数据集,而后求模型准确性评分的平均值。scikit-learn提供了KFold和cross_val_score函数来处理这种问题

from sklearn.model_selection import KFold, cross_val_score

results = []
for name, model in models:
kfold = KFold(n_splits=10)
cv_result = cross_val_score(model, X, Y, cv=kfold)
results.append((name, cv_result))
for i in range(len(results)):
print('name:{},cross val score:{}'.format(results[i][0], results[i][1].mean()))

经过KFold把数据集分红10份,其中1份会做为交叉验证数据集来计算模型准确性,剩余的9份做为训练数据集。cross_val_score函数总共计算出10次不一样训练数据集和交叉验证数据集组合获得的模型准确性评分,最后求平均值。

输出结果为:

name:KNN,cross val score:0.7147641831852358
name:KNN with weights,cross val score:0.6770505809979495
name:Radius Neighbors,cross val score:0.6497265892002735

模型训练及分析

普通的k-均值算法性能更优一些。使用普通的k-均值算法模型对数据集进行训练,并查看对训练样本的拟合状况以及对测试样本的预测准确性状况

knn = KNeighborsClassifier(n_neighbors=2)
knn.fit(X_train, Y_train)
train_score = knn.score(X_train, Y_train)
test_score = knn.score(X_test, Y_test)
print('train score:{}; test score:{}'.format(train_score, test_score))

输出以下

train score:0.8257328990228013; test score:0.7012987012987013

从这个输出中能够看到两个问题,1是对训练样本的拟合状况不佳,评分才0.84多一些,说明算法模型太简单了,没法很好地拟合训练样本。2是模型的准确性欠佳,不到73%的预测准确性。

能够进一步画出学习曲线,证明结论

from sklearn.model_selection import ShuffleSplit
from LearningCurve import plot_learning_curve

knn = KNeighborsClassifier(n_neighbors=2)
cv = ShuffleSplit(n_splits=10, test_size=0.2, random_state=0)
plt.figure(figsize=(10, 6), dpi=200)
plot_learning_curve(knn, "Learn Curve for KNN Diabetes", X, Y, ylim=(0.0, 1.01), cv=cv)

从图中能够看出,训练样本评分较低,且测试样本与训练样本距离较大,这是欠拟合现象。

k-均值算法没有更好的措施来解决欠拟合问题,能够试着用逻辑回归算法、支持向量机等来对比不一样模型的准确性状况

特征选择及数据可视化

只选择2个与输出值相关性最大的特征,在二维平面上画出输入特征与输出值的关系

scikit-learn在sklearn.feature_selection包里提供了丰富的特征选择方法。使用SelectKBest来选择相关性最大的两个特征

from sklearn.feature_selection import SelectKBest
selector = SelectKBest(k=2)
X_new = selector.fit_transform(X,Y)
X_new[0:5]

把相关性最大的两个特征放在X_new变量里,同时输出了前5个数据样本。SelectKBest会自动选择出2个相关性最高的特征

能够将提取到的2个特征,在二维坐标上画出全部的训练样本

selector = SelectKBest(k=2)
X_new = selector.fit_transform(X, Y)
X_new[0:5]
plt.figure(figsize=(10, 6), dpi=200)
plt.ylabel("BMI")
plt.xlabel("Glucose")
plt.scatter(X_new[Y == 0][:, 0], X_new[Y == 0][:, 1], c='r', s=20, marker='o')
plt.scatter(X_new[Y == 1][:, 0], X_new[Y == 1][:, 1], c='g', s=20, marker='^')
plt.show()

横坐标是血糖值,纵坐标是BMI值,反映身体肥胖状况。在中间数据集密集的区域,阳性样本和阴性样本几乎重叠在一块儿了。假设如今有一个待预测的样本在中间密集区域,它的阳性邻居多仍是阴性邻居多呢?

如何提升k-近邻算法的运算效率

根据算法原理,每次须要预测一个点时,都须要计算训练数据集里每一个点到这个点的距离,而后选出距离最近的k个点进行投票。当数据集很大时,这个算法成本很是高。针对N个样本,D个特征的数据集,其算法复杂度为O(DN2) 

为了解决这个问题,K-D Tree的数据结构被发明出来。为了不每次都从新计算一遍距离,算法会把距离信息保存在一棵树里,这样在计算以前从树里查询距离信息,尽可能避免从新计算。其基本原理是,若是A和B距离很远,B和C距离很近,那么A和C的距离也很远。

有了这个信息,就能够在合适的时候跳过距离远的点。这样优化后的算法复杂度能够下降到O(DNlog(N))

相关性测试

经过一个简单的例子来看假设检验问题,判断假设的结论是否成立或成立的几率有多高。

假设,在一个城市随机采样到程序员和性别的关系的数据

假设,咱们的结论是程序员和性别无关,这个假设称为原假设。经过咱们随机采样观测到的数据,原假设是否成立,或者说原假设成立的几率有多高?

卡方检验(chi-squared test) 是检测假设成立与否的一个经常使用的工具。计算公式是

卡方检测的值标记为x2,Oi是观测值,Ei是指望值。针对咱们的例子,若是原假设成立,即程序员职业和性别无关,那么咱们指望的男程序员数量应该为(14/489)*242=6.928

 根据卡方检验的工时,能够算出卡方值为:

算出卡方值后,怎么判断原假设成立的几率呢?涉及到自由度和卡方分布的概念。

自由度是(r-1)*(c-1),其中r是行数,c是列数。针对咱们的问题,其自由度为1。卡方分布是指,若n个互相独立的随机变量均服从正态分布,则这n个随机变量的平方和构成一新的随机变量,其分布规律称为卡方分布。

卡方分布的密度函数和自由度相关,知道自由度和目标几率,就能求出卡方值。

针对咱们的问题,能够查表获得,自由度为1的卡方分布。在99%处的卡方值为6.63。咱们计算出来的卡方值为7.670。因为7.67>6.63,固有99%的把握能够推翻原假设。换个说话,若是原假设成立,即程序员职业和性别无关,那么咱们随机采样到的数据出现的几率将低于1%。

卡方值的大小能够反映变量与目标值的相关性,值越大,相关性越大。利用这个特性,SelectKBest() 函数就能够计算不一样特征的卡方值来判断特征与输出值的相关性大小,从而完成特征选择。

计算卡方值的函数是sklearn.feature_selection.chi2() 

计算F值校验算法的函数是sklearn.feature_selection.f_classif()

线性回归算法

线性回归算法是使用线性方程对数据集进行拟合的算法,是一个很是常见的回归算法。

算法原理

考虑最简单的单变量线性回归算法,只有一个输入特征。

预测函数 

针对数据集x和y,预测函数会根据输入特征x来计算输出值h(x)。其输入和输出的函数关系以下。

这个方程表达的是一条直线。咱们须要构造一个hθ计算出来的值与真实值y的总体偏差最小。

构造hθ函数的关键就是找到合适θ0,θ1的值,称为模型参数。

假设咱们有以下数据集:

假设模型参数θ0=1,θ1=3,则模型函数为hθ(x) = 1+3x。

针对数据集中的第一个样本,输入为1,根据模型函数预测出来的值是4,与输出值y是吻合的。

针对第二个样本,输入为2,根据模型函数预测出来的值是7,与实际输出值y相差1.

模型的求解过程,就是找出一组最合适的模型参数θ0,θ1,以便能最好地拟合数据集

当拟合成本最小时,就是找到了最好的拟合参数

成本函数

单变量线程回归算法的成本函数是

其中h(x(i))-y(i) 是预测值和实际值的差,故成本就是预测值和实际值的差的平方的平均值,之因此乘以1/2是为了计算方便。

这个函数也称为均方差方程,有了成本函数,就能够精确地测量模型对训练样本拟合的好坏程度。

梯度降低算法

有了预测函数,能够精确地测量预测函数对训练样本的拟合状况。咱们要怎么求解模型参数θ0,θ1的值呢?

这个时候梯度降低算法就派上用场了。

咱们的任务是找到合适的θ0,θ1,使得成本函数J(θ0,θ1) 最小。为了便于理解,咱们切换到三维空间来描述这个任务。在一个三维空间里,以θ0做为x轴,以θ1做为y轴,以成本函数J(θ0,θ1)为z轴,咱们须要找出当z轴上的值最小的时候所对应的x轴上的值和y轴上的值。

梯度降低算法的原理是,先随机选择一组θ0,θ1,同时选择一个参数a做为移动的步幅。而后让x轴上的θ0和y轴上的θ1分别向特定的方向移动一小步,这个步幅的大小就由参数a指定。通过屡次迭代以后,x轴和y轴上的值决定的点就慢慢地靠近z轴上的最小值处 

这个是等高线图,在描述的三维空间里,视角在正上方,看到一圈一圈z轴值相同的点构成的线。

随机选择的点在x0处,通过屡次迭代后,慢慢地靠近圆心处,即z轴上最小值附近。

那么怎么知道往哪一个方向移动,才能靠近z轴上最小值附近呢?

答案是往成本函数逐渐变小的方向移动。使用偏导数,能够表达成本函数逐渐变小的方向。

能够简单的把偏导数理解为斜率,咱们要让θj不停地迭代,由当前θj的值,根据J(θ)的偏导数函数,算出J(θ)在θj上的斜率,而后再乘以学习率a,就可让θj 往前J(θ) 变小的方向迈一小步。

用数学来描述上述过程,梯度降低的公式为:

公式中,下标j就是参数的序号,针对单变量线性回归,即0和1。a称为学习率,决定每次要移动的幅度大小,会乘以成本函数对参数θj的偏导数,以这个结果做为参数移动的幅度。

若是幅度过小,意味着要计算不少次才能到达目的地,若是幅度太大,可能会直接跨过目的地,从而没法收敛。

把成本函数J(θ)的定义代入上面的公式中,不难推导出梯度降低算法公式

公式中,a是学习率;m是训练样本的个数;h(x(i))-y(i)是模型预测值和真实值的偏差。须要注意的是,针对θ0和θ1分别求出了其迭代公式,在θ1的迭代公式里,累加器中还须要乘以xi。

多变量线性回归算法

工程应用中每每不止一个输入特征。熟悉了单变量线性回归算法后,来讲一下多变量线性回归算法

预测函数

多个输入特征。此时输出y的值由n个输入特征x1,x2,x3,...,xn决定。那么预测函数模型能够改写以下:

若x0为常数1,用累加器运算符重写上面的预测函数

 

θ0,θ1...θn统称为θ,是预测函数的参数。即一组θ值就决定了一个预测函数,记做hθ(x),简写为h(x)

理论上,预测函数有无穷多个,咱们求解的目标就是找出一个最优的θ值

1. 向量形式的预测函数

根据向量乘法运算法则,成本函数能够重写为:

此处,依然假设x0=1,x0称为模型偏置(bias)

写成向量形式的预测函数,是由于在实现算法时,能够使用矩阵计算来提升效率

2. 向量形式的训练样本

假设,输入特征个数是n,即x1,x2,x3...xn,咱们总共有m个训练样本,为了书写方便,假设x0=1。这样训练样本能够写成矩阵的形式,即矩阵里每一行都是一个训练样本,总共有m行,每行有n+1列

最后,把训练样本写成一个矩阵,把预测函数的参数θ写成列向量,其样式以下

理解训练样本矩阵的关键在于理解这些上标和下标的含义。其中带括号上标表示样本序号,从1到m;

下标表示特征序号,从0到n,其中x0为常数1。

好比 x(i)j 表示第i个训练样本的第j个特征的值。而x(i) 只有上标,则表示第i个训练样本所构成的列向量。

若是要一次性计算出全部训练样本的预测值hθ(X),能够使用下面的矩阵运算公式

这个公式能够看到矩阵形式表达的优点。在scikit-learn里,训练样本就是用这个方式表达的,使用m*n维的矩阵来表达训练样本。

成本函数

多变量线性回归算法的成本函数:

其中,模型参数θ为n+1维的向量,h(x(i))-y(i)是预测值和实际值的差。这个形式和单变量线性回归算法相似

成本函数有其对应的矩阵样式的版本

其中,X为m*(n+1)维的训练样本矩阵;上标T表示转置矩阵;y表示由全部的训练样本的输出y(i)构成的向量。

这个公式的优点是:没有累加器,不须要循环,直接使用矩阵运算,就能够一次性计算出针对特定的参数θ下模型的拟合成本

梯度降低算法

根据单变量线性回归算法里的介绍,梯度降低的公式为:

公式中,下标j是参数的序号,其值从0到n;a学习率。把成本函数代入上式,利用偏导数计算法则,推导出梯度降低算法的参数迭代工时

编写机器学习算法的时候,通常步骤以下

1. 肯定学习率:a太大可能会使成本函数没法收敛,过小则计算太多,机器学习算法效率就比较低。

2. 肯定参数起始点:让全部的参数都为1做为起点,即θ0=1,θ1=1。这样就获得了预测函数。根据预测值和成本函数,就能够算出在参数起始位置的成本。须要注意的是,参数起始点能够根据实际状况灵活选择,以便让机器学习算法的性能更高,如选择比较靠近极点的位置。

3. 计算参数的下一组值:根据梯度降低参数迭代公式,分别同时算出新的θj的值。而后用新的θ值获得新的预测函数hθ(x),再根据新的预测函数,代入成本函数就能够算出新的成本。

4. 确认成本函数是否收敛:拿新的成本和旧的成本进行比较,当作本是否是变得愈来愈小。若是两次成本之间的差别小于偏差范围,即说明已经很是靠近最小成本了,就能够近似地认为咱们找到了最小成本。若是两次成本之间的差别在偏差范围以外,重复步骤3继续计算下一组参数,直到找到最优解。

模型优化    

多项式与线型回归

当线型回归模型太简单致使欠拟合时,咱们能够增长特征多项式来让线型回归模型更好地拟合数据。好比有两个特征x1,x2,能够增长两特征的乘积x1,x2做为新特征x3.还能够增长x12 做为另外一个新特征x4。

线型回归是由类sklearn.linear_model.LinearRegression实现,多项式由类sklearn.preprocessing.PolynomialFeatures实现。那么要怎么添加多项式特征呢?

须要用一个管道把两个类串起来,即用sklearn.pipeline.Pipeline把两个模型串起来

好比下面的函数能够建立一个多项式拟合

from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
from sklearn.pipeline import Pipeline


def polynomial_model(degree=1):
    polynomial_features = PolynomialFeatures(degree=degree, include_bias=False)
    linear_regression = LinearRegression()
    # 流水线,先增长多项式阶数,而后再用线性回归算法来拟合数据
    pipeline = Pipeline([("polynomial_features", polynomial_features), ("linear_regression", linear_regression)])
    return pipeline

一个Pipeline 能够包含多个处理节点,在scikit-learn里除了最后一个节点外,其余的节点都必须实现fit()和transform()方法,最后一个节点只须要实现fit()方法便可。当训练样本数据送进Pipeline里进行处理时,会逐个调用节点的fit()和transform()方法,而后调用最后一个节点的fit()方法来拟合数据。

数据归一化

当线型回归模型有多个输入特征时,特别是使用多项式添加特征时,须要对数据进行归一化处理。好比特征x1的范围在[1,4]之间,特征x2的范围在[1,2000]之间,这种状况下,可让x1除以4来做为新特征x1,同时让x2除以2000来做为新特征x2.

该过程称为特征缩放,能够使用特征缩放来对训练样本进行归一化处理,处理后的特征值范围在[0,1]之间

归一化处理的目的是让算法收敛更快,提高模型拟合过程当中的计算效率。进行归一化处理后,当有个新的样本须要计算预测值时,也须要先进行归一化处理,再经过模型来计算预测值,计算出来的预测值要再乘以归一化处理的系数,这样获得的数据才是真实的预测数据

使用LinearRegression进行线性回归时,能够指定normalize=True来对数据进行归一化处理。

示例:使用线性回归算法拟合正弦函数

首先生成200个在区间内的正弦函数点,并加上一些随机的噪音

import numpy as np
import matplotlib.pyplot as plt

n_dots = 200
X = np.linspace(-2 * np.pi, 2 * np.pi, n_dots)
Y = np.sin(X) * 0.2 * np.random.rand(n_dots) - 0.1
X = X.reshape(-1, 1)
Y = Y.reshape(-1, 1)

其中reshape()函数的做用是把Numpy的数组整造成符合scikit-learn输入格式的数组,不然会报错。

接着使用PolynomialFeatures 和 Pipeline建立一个多项式拟合模型

from sklearn.pipeline import Pipeline
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import PolynomialFeatures

def polynomial_model(degree=1):
polynomial_features = PolynomialFeatures(degree=degree, include_bias=False)
linear_regression = LinearRegression(normalize=True)
pipeline = Pipeline([("polynomial_features", polynomial_features),
("linear_regression", linear_regression)])
return pipeline

分别用2,3,5,10阶多项式来拟合数据集

from sklearn.metrics import mean_squared_error

degrees = [2, 3, 5, 10]
results = []
for d in degrees:
model = polynomial_model(degree=d)
model.fit(X, Y)
train_score = model.score(X, Y)
mse = mean_squared_error(Y, model.predict(X))
results.append({"model": model, "degree": d, "score": train_score, "mse": mse})

for r in results:
print("degree:{}; train score:{}; mean squared error:{}"
        .format(r["degree"], r["score"], r["mse"]))

算出每一个模型拟合的评分,此外使用mean_squared_error算出均方根偏差,即实际的点和模型预测的点之间的距离,均方根偏差越小说明模型拟合效果越好

degree:2; train score:0.10468112529330831; mean squared error:0.006269846500589429
degree:3; train score:0.19281625288786197; mean squared error:0.00565264324827472
degree:5; train score:0.664659360758771; mean squared error:0.002348363686782361
degree:10; train score:0.7606029623390451; mean squared error:0.001676478315417776

从输出结果能够看出,多项式的阶数越高,拟合评分越高,均方差偏差越小,拟合效果越好。最后把不一样模型的拟合效果在二维坐标上画出来,能够清楚看到不一样阶数的多项式的拟合效果

使用SubplotParams调整子图的竖直间隔,并用subplot把4个模型拟合状况都画在同一个图形上。

from matplotlib.figure import SubplotParams

plt.figure(figsize=(12, 6), dpi=200, subplotpars=SubplotParams(hspace=0.3))
for i, r in enumerate(results):
fig = plt.subplot(2, 2, i + 1)
plt.xlim(-8, 8)
plt.title("LinearRegression degree={}".format(r["degree"]))
plt.scatter(X, Y, s=5, c='b', alpha=0.5)
plt.plot(X, r["model"].predict(X), 'r-')

plt.show()

在[-2π,2π]区间内,10阶多项式数据拟合很是好,可是其余区间的曲线,就很通常了。

示例:测算房价

使用scikit-learn自带的波士顿房价数据集来训练模型,而后用模型来测算房价

输入特征

房价和那些因素有关?不少人可能对这个问题特别敏感,随时能够列出不少,如房子面积、地理位置、周边教育资源、周边商业资源、朝向、年限、小区状况等。

在scikit-learn中的房价数据集里,总共收集了13个特征

CRIM:城镇人均犯罪率

ZN:城镇超过25000平方英尺的住宅区域的占地比例

INDUS:城镇非零售用占地比例

CHAS:是否靠近河边,1靠近,0远离

NOX:一氧化氮浓度

RM:每套房产的平均房间个数

AGE:1940年以前盖好,且业主自住的房子比例

DIS:与波士顿市中心的距离

RAD:周边高速公道的便利性指数

TAX:每10000美圆的财产税率

PTRATIO:小学老师的比例

B:城镇黑人比例

LSTAT:地位较低的人口比例

从这些指标能够看到中美文化的一些差别。这个数据是1993年以前收集的,可能和如今会有差别。

实际上一个模型的好坏和输入特征的选择关系密切。

先导入数据

from sklearn.datasets import load_boston

boston = load_boston()
X = boston.data
y = boston.target

数据集有506个样本,每一个样本有13个特征。整个训练样本放在一个50613的矩阵里。

模型训练

在scikit-learn里,LinearRegression类实现了线性回归算法。对模型进行训练以前,须要先把数据集分红两份,以便评估算法的准确性

from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=3)

因为数据量较小,咱们只选了20%的样原本做为测试数据集。接着,训练模型并测试模型的准确性评分

import time
from sklearn.linear_model import LinearRegression

model = LinearRegression()
start = time.clock()
model.fit(X_train, y_train)
train_score = model.score(X_train, y_train)
cv_score = model.score(X_test, y_test)
print('elaspe:{0:.6f}; train_score:{1:0.6f};cv_score:{2:.6f}'
.format(time.clock() - start, train_score, cv_score))

除了统计模型的训练时间外,还针对训练样本的准确性得分train_score进行了统计,还统计了模型针对测试样本的得分cv_score

elaspe:0.063575; train_score:0.723941;cv_score:0.794958

模型优化

首先观察一下数据,特征数据的范围相差比较大,最小的在10-3级别,而最大的在102级别,看来咱们须要把数据进行归一化处理,归一化处理最简单的方式是,建立线性回归模型时增长normalize=True参数:

model = LinearRegression(normalize=True)

固然,数据归一化处理只会加快算法收敛速度,优化算法训练的效率,没法提高算法准确性

怎么优化模型准确性呢?回到训练分数上,能够观察到数据针对训练样本的评分比较低(train_score:0.7239)

即数据对训练数据的拟合成本比较高,这是个典型的欠拟合现象。解决欠拟合一是挖掘更多输入特征,二是增长多项式特征。

这个例子中,经过使用低成本的方案,即增长多项多特征来看看可否优化模型的性能。增长多项式特征,其实就是增长模型的复杂度

编写建立多项式模型的函数

from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import Pipeline

def polynomial_model(degree=1):
polynomial_features = PolynomialFeatures(degree=degree, include_bias=False)
linear_regression = LinearRegression(normalize=True)
pipeline = Pipeline([("polynomial_features", polynomial_features),
("linear_regression", linear_regression)])
return pipeline

 接着,使用二阶多项式来拟合数据

model = polynomial_model(degree=2)
start = time.clock()
model.fit(X_train, y_train)
train_score = model.score(X_train, y_train)
cv_score = model.score(X_test, y_test)
print('elaspe:{0:.6f}; train_score:{1:0.6f}; cv_score:{2:.6f}'
.format(time.clock() - start, train_score, cv_score))

输出结果是:

elaspe:0.009711; train_score:0.930547; cv_score:0.860465 

训练样本分数和测试分数都提升了,看来模型确实获得了优化。能够把多项式改成三阶看一下结果

elaspe:0.178630; train_score:1.000000; cv_score:-105.517016

改成三阶多项式后,针对训练样本的分数达到了1,而针对测试样本的分数倒是负数,说明这个模型过拟合了

学习曲线

更好的方法是画出学习曲线,这样对模型的状态以及优化方向就一目了然了

from common_utils import plot_learning_curve
from sklearn.model_selection import ShuffleSplit

cv = ShuffleSplit(n_splits=10, test_size=0.2, random_state=0)
plt.figure(figsize=(18, 4), dpi=200)
title = 'Learning Curves (degree={0})'
degrees = [1, 2, 3]
plt.figure(figsize=(18, 4), dpi=200)
for i in range(len(degrees)):
plt.subplot(1, 3, i + 1)
plot_learning_curve(plt, polynomial_model(degrees[i]), title.format(degrees[i])
, X, y, ylim=(0.01, 1.01), cv=cv)

print('elaspe:{0:.6f}'.format(time.clock() - start))
plt.show()

输出以下图

从图中能够看出,一阶多项式欠拟合,由于针对训练样本的分数比较低。三阶多项式过拟合,由于针对训练样本的分数达到1,却看不到针对交叉验证数据集的分数。针对二阶多项式拟合的状况,虽然比一阶多项式效果好,但从图中能够明显看出来,针对训练数据集的分数和针对交叉验证数据集的分数之间的间隙比较大,这特征说明训练样本数量不够,咱们应该去采集更多的数据,以便提升模型的准确性

随机梯度降低算法

上面介绍的梯度降低迭代公式称为批量梯度降低算法(Batch Gradient Descent),用它对参数进行一次迭代运算,须要遍历全部的训练数据集。当训练数据集比较大时,其算法的效率会很低。

考虑另一个算法

这个算法的关键点是把累加器去掉,不去遍历全部的训练数据集,而是改为每次随机从训练数据集取一个数据进行参数迭代计算,这就是随机梯度降低算法(stochastic gradient descent)。随机梯度降低算法能够大大提升模型训练效率

从数学上证实批量梯度降低算法和随机梯度降低算法的等价性涉及到复杂的数学知识。这里有个直观的解释能够帮助读者理解二者的等价性。回到成本函数

这里累加后除以2是为了方便计算,那么除以m是什么意思呢?

答案是平均值,即全部训练数据集上的点到预测函数的距离的平均值。再回到随机选取训练数据集里的一个数据这个作法来看,若是计算次数足够多,而且是真正随机,那么随机选取出来的这组数据从几率的角度来看,和平均值是至关的。

打个比方,存钱罐里有1角的硬币10个,5角的2个,1元的1个,总计3元、13个硬币。随机从里面取1000次,把每次取出来的硬币币值记录下来,而后将硬币放回存钱罐。这样最后去算着1000次取出来的钱的平均值(1000次取出来的币值总和除以1000)和存钱罐里每一个硬币的平均值应该是近似相等的。

标准方程

梯度降低算法经过不停地迭代,从而不停地逼近成本函数的最小值来求解模型参数。另一个方法是从计算成本函数的微分,令微分算子为0,求解这个方程,便可获得线性回归的解。

线性回归算法的成本函数:

成本函数的斜率为数的点,即为模型参数的解。即令求解这个方程最终能够获得的模型参数:

这就是咱们的标准方程。经过矩阵运算,直接从训练样本里求出参数θ的值。其中X为训练样本的矩阵形式,是m*n的矩阵,y是训练样本的结果数据,是个m维列向量。

逻辑回归算法

逻辑回归算法是用来解决分类问题的算法。

算法原理

假设有一场足球赛,拥有两支球队的全部出场球员信息,历史交锋成绩、比赛时间、主客场、裁判和天气等信息,根据这些信息预测球队的输赢。 

假设比赛结果记为y,赢球标记为1,输出标记为0,这个就是典型的二元分类,能够用逻辑回归算法来解决。

预测函数

须要找出一个预测函数模型,使其值输出在[0,1]之间。而后选择一个基准值,如0.5,若是算出来的预测值大于0.5,就认为其预测值为1,反之则其预测值为0

做为预测函数,其中e是天然对数的底数。函数g(z) 称为Sigmoid函数,也称为Logistic函数。以z为横坐标,以g(z)为纵坐标,画出的图形以下

从图中能够看出,当z=0时,g(z)=0.5,当z>0时,g(z)>0.5。当z愈来愈大时g(z)无限接近于1。当z<0时,g(z)<0.5,当z愈来愈小时,g(z)无限接近于0。这正是咱们想要的针对二元分类算法的预测函数。

结合线性回归函数的预测函数hθ(x) = θTx,假设令z(x)=θTx,则逻辑回归算法的预测函数以下:

下面来解读预测函数

hθ(x) 表示在输入值为x,参数为θ的前提条件下y=1的几率。用几率论的公式能够写成:

上面的几率公式能够读做:在输入x及参数θ条件下y=1的几率。这是个条件几率公式。

由几率论的知识能够推导出

对二元分类法来讲,这是个非黑即白的世界

断定边界

逻辑回归算法预测函数由下面两个公式给出的

假定y=1的断定条件是hθ(x) >= 0.5,y=0的断定条件是hθ(x)<0.5,则能够推导出y=1的断定条件就是θTx>=0,y=0的断定条件就是θTx<0。因此,θTx=0便是咱们的断定边界

假定有两个变量x1,x2,其逻辑回归预测函数是hθ(x)=g(θ0+θ1x1+θ2x2)

假设给定参数

那么能够获得断定边界-3+x1+x2=0,即x1+x2=3,若是以x1为横坐标,x2为纵坐标,则这个函数画出来的就是一个经过(0,3)和(3,0) 两个点的斜线。这条线就是断定边界

其中,直线左下角为y=0,直线右上解为y=1。横坐标为x1,纵坐标为x2

若是预测函数是多项式 且给定

则能够获得断定边界函数x12+x22=1。仍是以x1为横坐标,x2为纵坐标,则这是一个半径为1的圆。圆内部是y=0,圆外部是y=1

成本函数 

为了容易求出成本函数的最小值,咱们分红y=1和y=0两种状况来分别考虑其预测值与真实值的偏差。咱们先考虑最简单的状况,即计算某个样本x,y其预测值与真实值的偏差,咱们选择的成本公式以下

其中,hθ(x) 表示预测为1的几率,log(x)为天然对数。咱们以hθ(x)为横坐标,以成本值Gost(hθ(x),y)为纵坐标,把上述两个公式分别画在二维平面上

成本是预测值与真实值的差别。当差别越大时,成本越大,模型收到的惩罚也越严重

在左图中,当y=1时,随着hθ(x)的值(预测为1的几率)愈来愈大,预测值愈来愈接近真实值,其成本愈来愈小。当y=0时,随着hθ(x)的值(预测为1的几率)愈来愈大,预测值愈来愈偏离真实值,其成本愈来愈大。

逻辑回归模型的预测函数是Sigmoid函数,而Sigmoid函数里有e的n次方运算,天然对数恰好是其逆运算,好比log(en)=n。选择天然对数,最终会推导出形式优美的逻辑回归模型参数的迭代函数,而不须要去涉及对数运算和指数函数运算。这就是选择天然对数函数来做为成本函数的缘由。

把输入值x从负无穷大到正无穷大映射到[0,1]区间的模型不少,逻辑回归算法不必定要选择Sigmoid函数做为预测函数,可是若是不选择它,就须要从新选择性质接近的成本函数。

成本函数为

梯度降低算法

和线型回归相似,使用梯度降低算法来求解逻辑回归模型参数。根据梯度降低算法的定义,能够得出

这里的关键是求解成本函数的偏导数。最终推导出来的梯度降低算法公式为:

多元分类

逻辑回归模型能够解决二元分类问题,即y={0,1},能不能用来解决多元分类问题呢?答案是确定的

针对多元分类问题,y={0,1,2,...,n},总共有n+1个类别 

首先把问题转化为二元分类问题,即y=0是一个类别,y={1,2,3...,n}做为另一个类别,而后计算这两个类别的几率。接着把y=1做为一个类别,把y={0,2,...n}做为另一个类别,再计算这两个类别的几率。

总共须要n+1个预测函数:

预测出来的几率最高的那个类别,就是样本所属的类别

正则化

过拟合是指模型很好地拟合了训练样本,但对新数据预测的准确性不好,这是由于模型太复杂了。解决办法是减小输入特征的个数,或者获取更多的训练样本。

还有一种解决过拟合的方法:正则化

1. 保留全部的特征,减小特征的权重θj的值。确保全部特征对预测值都有少许的贡献

2. 当每一个特征xi对预测值y都有少许的贡献时,这样的模型能够良好地工做,这就是正则化的目的,能够用它来解决特征过多时的过拟合问题

线性回归模型正则化

先来看看线性回归模型的成本函数是如何正则化的

公式中前半部分就是线性回归模型的成本函数,也称为预测值与实际值的偏差。后半部分为加入的正则项。

其中λ的值由两个目的,即要维持对训练样本的拟合,又要避免对训练样本的过拟合。若是λ值太大,则能确保不出现过拟合,但可能会致使对现有训练样本出现欠拟合。

从数学角度来看,成本函数增长了一个正则项后,成本函数再也不惟一地由预测值与真实值的偏差所决定,还和参数θ的大小有关。有了这个限制,要实现成本函数最小的目的,θ就不能随便取值了。好比某个比较大的θ值可能会让预测值与真实值的偏差(hθ(x(i))-y(i))2值很小,但会致使θ很大,最终的结果是成本函数太大。

这样经过调节参数λ,就能够控制正则项的权重,从而避免线性回归算法过拟合

利用正则化的成本函数,能够推导出正则化后的参数迭代函数

因子在每次迭代式都将把θj收缩一点点。由于a和λ是正数,而m是训练样例的个数,是个比较大的正整数。

逻辑回归模型正则化

使用相同的思路,能够对逻辑回归模型的成本函数进行正则化,其方法也是在原来的成本函数基础上加上正则项

正则化后的参数迭代公式为:

上式中j>=1,由于θ0没有参与正则化。逻辑回归和线型回归的参数迭代算法看起来形式是同样的,但其实它们的算法不同,由于两个式子的预测函数hθ(x)不同。针对线型回归hθ(x)=θTx,而针对逻辑回归

算法参数

在scikit-learn里,逻辑回归模型由类sklearn.linear_model.LogisticRegression实现

1. 正则项权重

上面介绍的正则项权重λ,在LogisticRegression里有个参数C与此对应,但成反比。即C值越大,正则项的权重越小,模型容易出现过拟合;C值越小,正则项权重越大,模型容易出现欠拟合。

2. L1/L2范数

建立逻辑回归模型时,有个参数penalty,其取值有'11'或'12',这是什么意思呢?

这个实际上就是指定咱们前面介绍的正则项的形式。在成本函数里添加的正则项为

这个其实是个L2正则项,即把L2范数做为正则项。也能够添加L1范数来做为正则项。

L1范数做为正则项,会让模型参数θ稀疏化,即让模型参数向量里为0的元素尽可能多。而L2范数做为正则项,则是让模型参数尽可能小,但不会为0,尽可能让每一个特征对预测值都有一些小的贡献。

首先了解一下L1范数和L2范数的概念,他们都是针对向量的一种运算。为了简单起见,假设模型只有两个参数,它们构成一个二维向量θ=[θ1,θ2],则L1范数为

即L1范数是向量里元素的绝对值之和,L2范数为元素的平方和的开方根。

定义清楚了以后,来介绍它们做为正则项的效果有什么不一样。梯度降低算法在参数迭代的过程当中,其实是在成本函数的等高线上跳跃,并最终收敛在偏差最小的点上。

正则项的本质是惩罚。模型在训练的过程当中,若是没有遵照正则项所表达的规则,那么其成本会变大,即受到了惩罚,从而往正则项所表达的规则处收敛。成本函数在这两项规则的综合做用下,正则化后的模型参数应该收敛在偏差等值线与正则项等值线相切的点上。

把L1范数和L2范数在二维坐标轴上画出其图形,便可直观地看到它们所表达的规则的不一样

def L1(x):
    return 1 - np.abs(x)

def L2(x):
    return np.sqrt(1 - np.power(x, 2))

def contour(v, x):
    return 5 - np.sqrt(v - np.power(x + 2, 2))    # 4x1^2 + 9x2^2 = v

def format_spines(title):    
    ax = plt.gca()                                  # gca 表明当前坐标轴,即 'get current axis'
    ax.spines['right'].set_color('none')            # 隐藏坐标轴
    ax.spines['top'].set_color('none')
    ax.xaxis.set_ticks_position('bottom')           # 设置刻度显示位置
    ax.spines['bottom'].set_position(('data',0))    # 设置下方坐标轴位置
    ax.yaxis.set_ticks_position('left')
    ax.spines['left'].set_position(('data',0))      # 设置左侧坐标轴位置

    plt.title(title)
    plt.xlim(-4, 4)
    plt.ylim(-4, 4)

plt.figure(figsize=(8.4, 4), dpi=144)

x = np.linspace(-1, 1, 100)
cx = np.linspace(-3, 1, 100)

plt.subplot(1, 2, 1)
format_spines('L1 norm')
plt.plot(x, L1(x), 'r-', x, -L1(x), 'r-')
plt.plot(cx, contour(20, cx), 'r--', cx, contour(15, cx), 'r--', cx, contour(10, cx), 'r--')

plt.subplot(1, 2, 2)
format_spines('L2 norm')
plt.plot(x, L2(x), 'b-', x, -L2(x), 'b-')
plt.plot(cx, contour(19, cx), 'b--', cx, contour(15, cx), 'b--', cx, contour(10, cx), 'b--')

 

左图中,用的是L1范数来做为正则项,L1范数表示的是元素的绝对值之和,图中L1范数的值为1,其在θ1,θ2坐标轴上的等值线是个正方形,虚线表示的是偏差等值线。能够看到,偏差等值线和L1范数等值线相切的点位于坐标轴上。

右图中,用的是L2范数来做为正则项,L2范数的值为1,在θ1,θ2坐标轴上,它的等值线是一个圆。和模型偏差等值线相切的点,通常不在坐标轴上。

L1范数做为正则项,会让模型参数稀疏化,而L2范数做为正则项,则会使模型的特征对预测值都有少许的贡献,避免模型过拟合

做为推论,L1范数做为正则项,有如下几个用途

特征选择

会让模型参数向量里的元素为0的点尽可能多。所以能够排除掉那些对预测值没有什么影响的特征。从而简化问题,因此L1范数解决过拟合的措施,其实是减小特征数量

可解释性

模型参数向量稀疏化后,只会留下那些对预测值有重要影响的特征。这样咱们就容易解释模型的因果关系。针对某种癌症的筛查,若是有100个特征,那么咱们无从解释到底那些特征对阳性呈关键做用。稀疏化后,只留下几个关键的特征,就容易看到因果关系

L1范数做为正则项,更多的是一个分析工具,而适合用来对模型求解。由于它会把不重要的特征直接去除。大部分状况下,咱们解决过拟合问题,仍是选择L2范数做为正则项,这也是scikit-learn里的默认值

实例:乳腺癌检测

使用逻辑回归算法解决乳腺癌检测问题。

首先采集肿瘤病灶造影图片,而后对图片进行分析,从图片中提取特征,再根据特征来训练模型。

最终使用模型来检测新采集到的肿瘤病灶造影,以便判断肿瘤是良性的仍是恶性的。这是典型的二元分类问题

数据采集及特征提取 

直接加载scikit-learn自带的乳腺癌数据集

from sklearn.datasets import load_breast_cancer

cancer = load_breast_cancer()
X = cancer.data
y = cancer.target
print('data shape:{0}; no.positive:{1}; no.negative:{2}'
      .format(X.shape, y[y == 1].shape[0], y[y == 0].shape[0]))
print(cancer.data[0])

数据集总共有569个样本,其中阳性357个,212个阴性。每一个样本有30个特征

这个数据集总共提取了如下10个关键属性

radius:半径,即病灶中心点离边界的平均距离

texture:纹理,灰度值的标准误差

perimeter:周长,即病灶的大小

area:面积,反映病灶大小的一个指标

smoothness:平滑度,即半径的变化幅度

compactness:密实度,周长的平方除以面积的熵,再减一

concavity:凹度,凹陷部分轮廓的严重程度

concave points:凹点,凹陷轮廓的数量

symmetry:对称性

fractal dimension:分形维度

从这些指标里,能够看出,有些指标属于复合指标,即由其余的指标通过运算获得的。好比密实度,是由周长和面积计算出来的。这种运算构建出来的新特征,是事物内在逻辑关系的体现

举个例子,须要监控数据中心中每台物理主机的运行状况,其中CPU占用率、内存占用率,网络吞吐量是几个重要指标。

问:有台主机CPU占用率80%,这个主机状态是否正常?要不要发布告警?答:看状况,仅从CPU占用率来看还不能判断主机是否正常,还要看内存占用状况和网络吞吐量状况。若是此时内存占用也成比例上升,且网络吞吐量状况。那么形成这一状态的多是用户访问的流量过大,致使主机负荷增长,不须要告警。

但若是内存占用,网络吞量和CPU占用不在同一量级,那么这台主机就可能处于不正常的状态。

因此,咱们须要构建一个复合特征,如CPU占用率和内存占用率的比值,以及CPU占用率和网络吞吐量的比值,这样构造出来的特征更真实地体现出了现实问题中的内在规则。

提取特征时,不妨从事物的内在逻辑关系入手,分析已有特征之间的关系,从而构造出新的特征。

乳腺癌数据集的特征问题,实际上就只有10个特征,而后构造出了每一个特征的标准差及最大值。这样每一个特征就又衍生了两个特征,总共就30个特征。能够经过cancer.feature_names变量来查看这些特征的名称

模型训练

scikit-learn提供了一致的接口调用,使用起来很是方便

首先把数据集分红训练数据集和测试数据集8:2

from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

而后,使用LogisticRegression模型来训练,并计算训练数据集的评分数据和测试数据集的评分数据

from sklearn.linear_model import LogisticRegression

model = LogisticRegression()
model.fit(X_train, y_train)
train_score = model.score(X_train, y_train)
test_score = model.score(X_test, y_test)
print(
'train score:{train_score:.6f}; test score:{test_score:.6f}'
.format(train_score=train_score, test_score=test_score))

输出的内容以下:

train score:0.962637; test score:0.938596

还能够看一下测试样本中,有几个是预测正确的:

y_pred = model.predict(X_test)
print('matchs:{0}/{1}'.format(np.equal(y_pred, y_test).shape[0], y_test.shape[0]))

输出以下

matchs:114/114

总共114个测试样本,所有测试正确。这里所有都预测正确,而testscore却只有0.973684,而不是1呢?

由于scikir-learn不是使用这个数据来计算分数,由于这个数据不能彻底反映偏差状况,而是使用预测几率数据来计算模型评分

针对二元分类问题,LogisticRegression模型会针对每个样本输出两个几率, 即为0的几率和为1的几率,哪一个几率高就预测为哪一个类别

能够找出针对测试数据集,模型预测的自信度低于90%的样本。

首先计算出测试数据集里每一个样本的预测几率数据,针对每一个样本,会有两个数据,一是预测其为阳性的几率,另一个是预测其为阴性的几率。接着找出预测为阴性几率大于0.1的样本,而后在结果里,找出预测为阳性的几率也大于0.1的样本,这样就找出了模型预测自信度低于90%的样本。

y_pred_proba = model.predict_proba(X_test)
print('sample of predict probability:{0}'.format(y_pred_proba[0]))
y_pred_proba_0 = y_pred_proba[:, 0] > 0.1
result = y_pred_proba[y_pred_proba_0]
y_pred_proba_1 = result[:, 1] > 0.1
print(result[y_pred_proba_1])

看一下输出结果

[0.35747299 0.64252701]

使用model.predict_proba() 来计算几率,同时找出那些预测自信度低于90%的样本。能够看到最没有把握的是这个0.64几率的

模型优化

使用LogisticRegression模型的默认参数训练出来的模型,准确性看起来仍是挺高的。问题是,有没有优化空间呢?若是有,往哪一个方向优化呢?

先尝试增长多项式特征,实际上,多项式特征和上文介绍的人为添加的复合特征相似,都是从已有特征通过数学运算得来的。只是这里的逻辑关系没有那么明显。

虽然咱们不能直观地理解多项式特征的逻辑关系,可是有一些方法和工具能够用来过滤那些对模型准确性有帮助的特征

首先使用Pipeline来增长多项式特征,

from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import Pipeline

def polynomial_model(degree=1, **kwarg):
polynomial_features = PolynomialFeatures(degree=degree, include_bias=False)
logistic_regression = LogisticRegression(**kwarg)
pipeline = Pipeline([("polynomial_features", polynomial_features)
, ("logistic_regression", logistic_regression)])
return pipeline

接着,增长二阶多项式特征,建立并训练模型

import time

model = polynomial_model(degree=2, penalty='l1')
start = time.clock()
model.fit(X_train, y_train)
train_score = model.score(X_train, y_train)
cv_score = model.score(X_test, y_test)
print('elaspe:{0:.6f}; train_score:{1:.6f}; cv_score:{2:.6f}'
.format(time.clock() - start, train_score, cv_score))

使用L1范数做为正则项(参数penalty='l1')输出以下

elaspe:0.374181; train_score:1.000000; cv_score:0.956140

能够看到,训练数据集评分和测试数据集评分都增长了。为何使用L1范数做为正则项呢?

前面介绍过,L1范数做为正则项,能够实现参数的稀疏化,即自动帮助咱们选择出那些对模型有关联的特征。

能够观察一下有多少个特征没有被丢弃,即其对应的模型参数θj非0:

logistic_regression = model.named_steps['logistic_regression']
print('model parameters shape:{0}; count of non-zero element:{1}'
.format(logistic_regression.coef_.shape,np.count_nonzero(logistic_regression.coef_)))

输出以下

model parameters shape:(1, 495); count of non-zero element:116

逻辑回归模型的coef_属性里保存的就是模型参数。从输出结果能够看到,增长二阶多项式特征后,输入特征由原来的30个增长到了495个,最终大多数特征都被丢弃,只保留了94个有效特征

学习曲线

怎么知道使用L1=范数做为正则项能提升算法的准确性?答案是:画出学习曲线。

学习曲线是有效的诊断工具之一,也是以前章节一直强调的内容

首先画出使用L1范数做为正则项所对应的一阶和二阶多项式的学习曲线

from common_utils import plot_learning_curve
from sklearn.model_selection import ShuffleSplit

cv = ShuffleSplit(n_splits=10, test_size=0.2, random_state=0)
title = 'Learning Curves (degree={0},penalty={1})'
degrees = [1, 2]
penalty = 'l1'
start = time.clock()
plt.figure(figsize=(12, 4), dpi=144)
for i in range(len(degrees)):
    plt.subplot(1, len(degrees), i + 1)
    plot_learning_curve(plt, polynomial_model(degree=degrees[i], penalty=penalty),
                        title.format(degrees[i], penalty), X, y, ylim=(0.8, 1.01), cv=cv)
print('eleaspe:{0:.6f}'.format(time.clock() - start))
plt.show()

决策树

决策树是最经典的机器学习模型之一。它的预测结果容易理解,易于向业务部门解释,预测速度快,能够处理类别型数据和连续型数据。在机器学习的数据挖掘类求职面试中,决策树是面试官最喜欢的面试题之一。

算法原理

决策树是一个相似于流程图的树结构,分支节点表示对一个特征进行测试,根据测试结果进行分类,树叶节点表明一个类别。下图用决策树来决定下班后的安排 

咱们分别对精力指数、情绪指数两个特征进行测试,并根据测试结果决定行为的类别。每选择一个特征进行测试,数据集就被划分红多个子数据集。接着继续在子数据集上选择特征,并进行数据集划分,直到建立出一个完整的决策树。

建立好决策树模型后,只要根据下班后的精力和情绪情况,从根节点一路往下便可预测出下班后的行为

在建立决策树的时候,要先对哪一个特征进行分裂?针对上图的例子,先判断精力指数进行分裂仍是先判断情绪指数进行分裂。须要从信息的量化谈起

信息增益

1948年,香农在他注明的《通讯的数学原理》中提出了信息熵(Entropy)的概念,从而解决了信息的量化问题。香农认为,一条信息的信息量和它的不肯定性有直接关系。一个问题不明肯定性越大,要搞清楚这个问题,须要了解的信息就越多,其信息熵就越大。信息熵的计算公式为:

其中,P(x)表示事件x出现的几率。例如,一个盒子里分别有5个白球和5个红球,随机取出一个球。问:这个球是红色的仍是白色的?这个问题的信息量多大呢?因为红球和白球出现的几率都是1/2,代入信息熵公式,能够获得其信息熵为

这个问题的信息量是1bit,信息量的单位就是比特。须要肯定这个球是红色的仍是白色的,只须要1比特的信息就够了。

再举一个例子,一个盒子里有10个白球,随机取出一个球,这个球是什么颜色的。这个问题的信息量是0,由于这是一个肯定的事件,其几率P(x)=1,咱们代入香农的信息熵公式,便可获得其信息熵为0。即不须要再获取任何新的信息,便可知道这个球必定是白色

回到决策的构建问题上,当咱们要构建一个决策树时。应该遍历全部的特征,分别计算,使用这个特征划分数据集先后的信息熵的变化值,而后选择信息熵变化幅度最大的那个特征,来优先做为数据集划分依据。

则选择信息增益最大的特征做为分裂节点

好比,一个盒子里共有红、白、黑、蓝4种颜色的球共计16个,其中红球2个,白球2个,黑球4个,篮球8个。

红球和黑球的体积同样,都为1个单位。白球和篮球的体积同样,都是2个单位。红球、白球和黑球的质量同样,都是1个单位,蓝球的质量为2个单位。

应该优先选择哪一个特征呢?先计算基础信息熵,即划分数据集前的信息熵。从已知信息容易知道,红球、白球、黑球、蓝球出现的几率分别为2/1六、2/1六、4/1六、8/16,所以基础信息熵为

接着使用体积来划分数据集,此时会划分出两个数据集,第一个子数据集里是红球和黑球,第二个子数据集里是白球和蓝球,咱们计算这种划分方式的信息熵。

其中第一个子数据集里,红球2个,黑球4个,其几率分别为2/6和4/6,所以第一个子数据集的信息熵为

第二个子数据集里,白球2个,蓝球8个,其几率分别为2/10和8/10,所以第二个子数据集的信息熵为

所以,使用体积来划分数据集后,其信息熵为H(D1) = H(D1sub1)+H(D1sub2)=1.640224

其信息增益为H(Dbase)-H(D1)=1.75-1.640224=0.109776

若是咱们使用质量来划分数据集,也会划分出两个数据集,第一个子数据集是红球、白球、黑球。第二个子数据集只有蓝球。咱们计算这种划分方式的信息熵。针对第一个子数据集,红球、白球、黑球出现的几率分别是2/八、2/八、4/8其信息熵为

第二个子数据集里只有蓝球,其几率为1,所以其信息熵H(D2sub2)=0。咱们得出使用使用质量来划分数据集时的信息熵为1.5,其信息增益为1.75-1.5=0.25.因为使用质量划分数据集比使用体积划分数据集获得了更高的信息增益,因此咱们优先选择质量这个特征来划分数据集。

下面来讨论信息增益的物理意义

以几率P(x)为横坐标,以信息熵Entropy为纵坐标,把信息熵和几率的函数关系Entropy=-P(x)log2P(x)在二维坐标轴上画出来

import numpy as np
from matplotlib import pyplot as plt


def entropy(px):
    return -px * np.log2(px)

x = np.linspace(0.001, 1, 1000)
plt.title('$Entropy(x) = - P(x) * log_2(P(x))$')
plt.xlim(0, 1)
plt.ylim(0, 0.6)
plt.xlabel('P(x)')
plt.ylabel('Entropy')
plt.plot(x, entropy(x), 'r-')
plt.show()

从这个函数关系能够看出来,当几率P(x)月接近0或月越接近1时,信息熵的值越小,其不肯定性越小,即数据越“纯”。典型地,当几率为1时,此时数据是最纯净的,由于只有一种类别的数据,已经消除了不肯定性,其信息熵为0.

咱们在特征选择时,选择信息增益最大的特征,在物理上,即让数据尽可能往更纯净的方向上变换。所以,信息增益是用来衡量数据变得更有序、更纯净的程度的指标

熵是热力学中表征物质状态的参量之一,其物理意义是体系混乱程度的度量,被香农借用过来,做为信息量的度量。注明的熵增原理是这样描述的:

熵增原理就是孤立热力学系统的熵不减小,老是增大或者不变。一个孤立系统不可能朝低熵的状态发展,即不会变的有序

用白话讲就是,若是没有外力的做用,这个世界将是愈来愈无序的。人活着,在于尽可能让熵变低,即让世界变的更有序,下降不肯定性。咱们在消费资源时,是一个增熵的过程。咱们把有序的食物变成了无序的垃圾。

决策树的建立

决策树的构建过程,就是从训练数据集中概括出一组分类规则,使它与训练数据矛盾较小的同时具备较强的泛化能力。有了信息增益来量化地选择数据集的划分特征,使决策树的建立过程变得容易了。决策树的建立基本上分为如下几步:

1. 计算数据集划分前的信息熵

2. 遍历全部未做为划分条件的特征,分别计算根据每一个特征划分数据集后的信息熵

3. 选择信息增益最大的特征,并使用这个特征做为数据划分节点来划分数据

4. 递归地处理被划分后的全部子数据集,从未被选择的特征里继续选择最优数据划分特征来划分子数据集

递归的结束,通常来说有两个终止条件。

一是全部的特征都用完了,即没有新的特征能够用来进一步划分数据集。

二是划分后的信息增益足够小了,这个时候就能够中止递归划分了。

针对这个中止条件,须要事先选择信息增益的门限值来做为结束递归的条件。

使用信息增益做为特征选择指标的决策树构建算法,称为ID3算法

1. 离散化

若是一个特征是连续值怎么办呢?假设有一个精力测试仪器,测出来的是一个0~100的数字,这个是连续值,这个时候要怎么用决策树来建模呢?

答案是:离散化

咱们须要对数据进行离散化处理。例如当精力指数小于等于40时标识为低,当大于40且小于等于70时标识为中,当大于70时标识为高。

通过离散处理后,就能够用来构建决策树了。要离散化成几个类别,这个每每和具体的业务相关。

2.正则项

最大化信息增益来选择特征,决策树的构建过程当中,容易形成优先选择类别最多的特征来进行分类。举一个极端的例子,把某个产品的惟一标识符ID做为特征之一加入到数据集中,那么构建决策树时,就会优先选择产品ID来做为划分特征,由于这样划分出来的数据,每一个叶子节点只有一个样本,划分后的子数据最纯净,其信息增益最大

计算划分后的子数据集的信息熵时,加上一个与类别个数成正比的正则项,来做为最后的信息熵。这样,当算法选择的某个类别较多的特征,使信息熵较小时,因为受到类别个数的正则项惩罚,致使最终的信息熵也比较大。这样经过合适的参数,能够使算法训练获得某种程度的平衡。

另一个解决办法是使用信息增益比来做为特征选择的标准

3.基尼不纯度

信息熵是衡量信息不肯定性的指标,实际上也是衡量信息纯度的指标。除此以外,基尼不纯度(GIni impurity)也是衡量信息不纯度的指标,其计算工时以下

其中,P(x)是样本属于这个类别的几率。若是全部的样本都属于一个类别,此时P(x) = 1,则Gini(D)=0,即数据不纯度最低,纯度最高。咱们以几率P(x) 做为横坐标,以这个类别的基尼不纯度Gini(D)=P(x)(1-P(x)) 做为纵坐标,在坐标轴上画出其函数关系

def gini_impurity(px):
    return px * (1 - px)

x = np.linspace(0.01, 1, 100)
plt.figure(figsize=(5, 3), dpi=200)
plt.title('$Gini(x) = P(x) (1 - P(x))$')
plt.xlim(0, 1)
plt.ylim(0, 0.6)
plt.xlabel('P(x)')
plt.ylabel('Gini Impurity')
plt.plot(x, entropy(x), 'r-');

CART算法使用基尼不纯度来做为特征选择标准,CART也是一种决策树构建算法。

剪枝算法

使用决策树模型拟合数据时,容易形成过拟合。解决过拟合的方法是对决策树进行剪枝处理。决策树的剪枝有两种思路:前剪枝(Pre-Pruning)和后剪枝(Post-Pruning)

1.前剪枝

前剪枝是在构造决策树的同时进行剪枝。在决策树的构建过程当中,若是没法进一步下降信息熵的状况下,就会中止建立分支,为了不过拟合,能够设定一个阈值,信息熵减少的数量小于这个阈值,即便还能够继续下降熵,也中止继续建立分支。这种方法成为前剪枝。有一些简单的前剪枝方法,如限制叶子节点的样本个数,当样本个数小于必定的阈值时,既再也不继续建立分支

2.后剪枝

后剪枝是指决策树构造完成以后进行剪枝。剪枝的过程是对拥有一样父节点的一组节点进行检查,判断若是将其合并,信息熵的增长量是否小于某一阈值。若是小于阈值,则这一组节点能够合并一个节点。后剪枝是目前较广泛的作法。后剪枝的过程是删除一些子树,而后用子树的根节点代替,来做为新的叶子节点。这个新的叶子节点所标识的类别经过大多数原则来肯定,即把这个叶子节点里样本最多的类别,做为这个叶子节点的类别。

后剪枝算法有不少种,其中经常使用一种称为下降错误率剪枝法(Reduced-Error Pruning)其思路是,自底向上,从已经构建好的彻底决策树中找出一个子树,而后用子树的根节点代替这课子树,做为新的叶子节点。叶子节点锁标识的类别经过大多数原则来肯定。这样就构建了一个新的简化版的决策树。而后使用交叉验证数据集来测试简化版本的决策树,看看其错误率是否是下降了。若是错误率下降了,则能够使用这个简化版的决策树代替彻底决策树,不然仍是采用原来的决策树。经过遍历全部子树,直到针对交叉验证数据集,没法进一步下降错误率为止。

算法参数

scikit-learn使用sklearn.tree.DecisionTreeClassifier类来实现决策树分类算法。其中几个典型的参数解释以下

criterion:特征选择算法。一种是基于信息熵,另一种是基于基尼不纯度。这两种算法的差别性不大,对模型的准确值没有太大的影响。相对而言,信息熵运算效率会低一些,由于它有对数运算。

splitter:建立决策树分支的选择,一种是选择最优分支建立原则,另一种是从排名靠前的特征中,随机选择一个特征来建立分支,这个方法和正则项的效果相似,能够避免过拟合问题。

max_depth:指定决策树的最大深度。经过指定该参数,用来解决模型过拟合问题

min_samples_split:这个参数指定能建立分支的数据集的大小,默认是2。若是一个节点的数据样本个数小于这个数值,则再也不建立分支。这也是一种前剪枝方法

min_samples_leaf:建立分支后的节点样本数量必须大小等于这个数值,不然再也不建立分支。这也是一种前剪枝的方法

max_leaf_nodes:除了限制最小的样本节点个数,改参数能够限制最大的样本节点个数

min_impurity_split:能够使用该参数来指定信息增益的阈值。决策树在建立分支时,信息增益必须大于这个阈值,不然不建立分支

从这些参数能够看到,scikit-learn有一系列的参数用来控制决策树生成的过程,从而解决过拟合问题。

实例:预测泰坦尼克号幸存者

经过决策树来预测泰坦尼克号那些人可能成为幸存者

数据集总共两个文件。train.csv是训练数据集,包含已标注的训练样本数据。test.csv是咱们模型要进行幸存者预测的数据。咱们的任务就是根据train.csv里的数据训练模型,使用这个模型预测test.csv里的数据

数据分析

train.csv是一个892行、12列的数据表格。意味着咱们有891个训练样本,每一个样本有12个特征,须要先分析这些特征,以便决定哪一个特征能够用来进行模型训练

PassengerId:乘客的ID号,这是个顺序编号,用来惟一地标识一名乘客。这个特征和幸存与否无关,不使用这个特征。

Survived:1 表示幸存,0 表示遇难。这是咱们标注的数据

Pclass:仓位等级,是很重要的特征。高仓位等级的乘客能更快地到达甲板,从而更容易获救

Name:乘客名字,这个特征和幸存与否无关,丢弃

Sex:乘客性别,船长让妇女和儿童先上,很重要的特征

Age:乘客年龄,儿童会优先上船

SibSp:兄弟姐妹同在船上的数量

Parch:同船的父辈人员数量

Ticket:乘客票号,不使用这个特征

Fare:乘客体热指标

Cabin:乘客所在的船舱号。实际上这个特征和幸存与否有必定关系,好比最先被水淹没的船舱位置,其乘客的幸存几率要低一些。但因为这个特征由大量丢失数据,因此丢弃这个特征

Embarked:乘客登船的港口,须要把港口数据转换为数值型数据

咱们须要加载csv数据,并作一些预处理,包括:

1. 提取Survived列的数据做为模型的标注数据

2. 丢弃不须要的特征数据

3. 对数据进行转换,以便模型处理。好比性别数据,咱们须要转换为0和1

4. 处理缺失数据,好比年龄、有不少确实的数据

使用pandas完成这些任务

def read_dataset(fname):
    # 指定第一列为行索引
    data = pd.read_csv(fname, index_col=0)
    # 丢弃无用的数据,axis=1列
    data.drop(['Name', 'Ticket', 'Cabin'], axis=1, inplace=True)
    # 处理性别数据
    data['Sex'] = (data['Sex'] == 'male').astype('int')
    # 处理登船港口数据
    labels = data['Embarked'].unique().tolist()
    data['Embarked'] = data['Embarked'].apply(lambda n: labels.index(n))
    # 处理缺失数据
    data = data.fillna(0)
    data.head()
    return data

train = read_dataset('titanic/train.csv')

模型训练

首先须要把Survived列提取出来做为标签,而后在原数据集中将其丢弃。同时把数据集分红训练数据集和交叉验证数据集

from sklearn.model_selection import train_test_split

y = train['Survived'].values
x = train.drop(['Survived'], axis=1).values
X_train, X_test, y_train, y_test = train_test_split(x, y, test_size=0.2)
print('train dataset:{0}; test dataset:{1}'.format(X_train.shape, X_test.shape))

输出内容以下

train dataset:(712, 7); test dataset:(179, 7)

接下来,使用scikit-learn的决策树模型对数据进行拟合

from sklearn.tree import DecisionTreeClassifier

clf = DecisionTreeClassifier()
clf.fit(X_train, y_train)
train_score = clf.score(X_train, y_train)
test_score = clf.score(X_test, y_test)
print('train score: {0}; test score: {1}'.format(train_score, test_score))

输出内容以下

train score: 0.9803370786516854; test score: 0.7932960893854749

能够看出,针对训练样本评分很高,但针对交叉验证数据集评分比较低,二者差距较大。

很明显的过拟合的特征。解决决策树过拟合的方法是剪枝,包括前剪枝和后剪枝。可是scikit-learn不支持后剪枝,但提供一系列的模型参数进行前剪枝。

优化模型参数

一个最直观的解决办法是选择一系列参数的值,而后分别计算用指定参数训练出来的模型和评分数据。还能够把二者的关系画出来,直观地看到参数值与模型准确度的关系 

以模型深度max_depth为例,咱们先建立一个函数,使用不一样的模型深度训练,并计算评分数据

# 参数选择 max_depth
def cv_score(d):
clf = DecisionTreeClassifier(max_depth=d)
clf.fit(X_train, y_train)
tr_score = clf.score(X_train, y_train)
cv_score = clf.score(X_test, y_test)
return (tr_score, cv_score)

接着构造参数范围,在这个范围内分别计算模型评分,并找出评分最高的模型所对应的参数

depths = range(2, 15)
scores = [cv_score(d) for d in depths]
tr_scores = [s[0] for s in scores]
cv_scores = [s[1] for s in scores]
# 找出交叉验证数据集评分最高的索引
best_score_index = np.argmax(cv_score)
best_score = cv_scores[best_score_index]
best_param = depths[best_score_index]  # 找出对应的参数
print('best param:{0}; best score:{1}'.format(best_param, best_score))

输出结果以下

best param:2; best score:0.7486033519553073 

针对模型深度这个参数,最优的值是2,其对应的交叉验证数据集评分为0.74.能够把模型参数和模型评分画出来,更直观地观察齐变化规律

plt.figure(figsize=(6, 4), dpi=144)
plt.grid()
plt.xlabel('max depth of decision tree')
plt.ylabel('score')
plt.plot(depths, cv_scores, '.g-', label='cross-validation score')
plt.plot(depths, tr_scores, '.r--', label='training score')
plt.legend()
plt.show()

使用一样的方法,也能够考察参数min_impurity_split。这个参数用来指定信息熵或基尼不纯度的阈值,当决策树分裂后,其信息增益低于这个阈值时,则再也不分裂

def cv_score(val):
    clf = DecisionTreeClassifier(criterion='gini', min_impurity_split=val)
    clf.fit(X_train, y_train)
    tr_score = clf.score(X_train, y_train)
    cv_score = clf.score(X_test, y_test)
    return (tr_score, cv_score)


# 指定参数范围,分别训练模型并计算评分
values = np.linspace(0, 0.5, 50)
scores = [cv_score(v) for v in values]
tr_scores = [s[0] for s in scores]
cv_scores = [s[1] for s in scores]

# 找出评分最高的模型参数
best_score_index = np.argmax(cv_scores)
best_score = cv_scores[best_score_index]
best_param = values[best_score_index]
print('best param:{0}; best score:{1}'.format(best_param, best_score))

# 画出模型参数与模型评分的关系
plt.figure(figsize=(6, 4), dpi=144)
plt.grid()
plt.xlabel('threshold of entropy')
plt.ylabel('score')
plt.plot(values, cv_scores, '.g-', label='cross-validation score')
plt.plot(values, tr_scores, '.r--', label='training score')
plt.legend()
plt.show()

模型参数选择工具包

介绍的模型参数优化方法有两个问题。

1. 数据不稳定,每次从新把数据集划分红训练数据集和交叉验证数据集后,选择出来的模型参数就不是最优的了 

2. 不能一次选择多个参数

问题1的缘由是每次把数据集划分都是随机划分,这样致使每次的训练数据集是有差别的,训练出来的模型也是有差别的。解决这个问题的方法是屡次计算,求平均值

scikit-learn在sklearn.model_selection包里提供了大量的模型选择和评估工具供咱们使用

针对上面的问题能够使用GridSearchCV类来解决。

from sklearn.model_selection import GridSearchCV

thresholds = np.linspace(0, 0.5, 50)
param_grid = {'min_impurity_split': thresholds}
clf = GridSearchCV(DecisionTreeClassifier(), param_grid, cv=5)
clf.fit(x, y)
print('best param:{0}; best score:{1}'.format(clf.best_params_, clf.best_score_))

输出结果以下

best param:{'min_impurity_split': 0.21428571428571427}; best score:0.8226711560044894

其中关键的参数是param_grid,是一个字典,字典关键字所对应的值是一个列表。GridSearchCV会枚举列表里全部的值来构建模型,屡次计算训练模型,并计算模型评分,最终得出指定参数值的平均评分及标准差。

另一个关键的参数是cv,它用来指定交叉验证数据集的生成规则,代码中的cv=5表示每次计算都把数据集分红5份,拿其中一份做为交叉验证数据集,其余的做为训练数据集。

最终得出的最优参数及最优评分保存在clf.best_params和clf_best_score_里。

此外clf.cv_results保存了计算过程当中的全部中间结果。能够拿这个数据来画出模型参数与模型评分的关系图

def plot_curve(train_sizes, cv_results, xlabel):
    train_scores_mean = cv_results['mean_train_score']
    train_scores_std = cv_results['std_train_score']
    test_scores_mean = cv_results['mean_test_score']
    test_scores_std = cv_results['std_test_score']
    plt.figure(figsize=(6, 4), dpi=144)
    plt.title('parameters turning')
    plt.grid()
    plt.xlabel(xlabel)
    plt.ylabel('score')
    plt.fill_between(train_sizes,
                     train_scores_mean - train_scores_std,
                     train_scores_mean + train_scores_std, alpha=0.1,
                     color='r')
    plt.fill_between(train_sizes,
                     test_scores_mean - test_scores_std,
                     test_scores_mean + test_scores_std, alpha=0.1,
                     color='g')
    plt.plot(train_sizes, train_scores_mean, '.--', color='r', label='Training score')
    plt.plot(train_sizes, test_scores_mean, '.-', color='g', label='Cross-validation score')
    plt.legend(loc='best')
    plt.show()

plot_curve(thresholds, clf.cv_results_, xlabel='gini thresholds')

 

接下来看一下如何在多组参数之间选择最优的参数

entropy_thresholds = np.linspace(0, 1, 50)
gini_thresholds = np.linspace(0, 0.5, 50)
# 设置参数矩阵
param_grid = [{'criterion': ['entropy'], 'min_impurity_split': entropy_thresholds},
              {'criterion': ['gini'], 'min_impurity_split': gini_thresholds},
              {'max_depth': range(2, 10)},
              {'min_samples_split': range(2, 30, 2)}]
clf = GridSearchCV(DecisionTreeClassifier(), param_grid, cv=5)
clf.fit(x, y)
print('best param:{0}; best score:{1}'.format(clf.best_params_, clf.best_score_))

输出结果

best param:{'criterion': 'entropy', 'min_impurity_split': 0.5306122448979591}; best score:0.8271604938271605 

关键部分仍是param_grid参数,是一个列表,列表中的每个元素都是一个字典。

针对列表中的第一个字典,选择信息熵做为决策树特征的判断标准,同时其阈值范围是[0,1]之间分了50等分。

GridSearchCV会针对列表中的每一个字典进行迭代,最终比较列表中每一个字典所对应的参数组合,选择出最优的参数

支持向量机

支持向量机简称SVM,是Support Vector Machine的缩写。SVM是一种分类算法,在工业界和学术界都有普遍的应用。特别是针对数据集较小的状况下,每每其分类效果比神经网络好

算法原理

SVM的最大特色是能构造出最大间距的决策边界,从而提升分类算法的鲁棒性

大间距分类算法 

假设要对一个数据集进行分类,能够构造一个分割线把圆形的点和方形的点分开。这个分隔线称为分隔超平面(Separating hyperplance)

从上图中能够明显看出,实现的分隔线比虚线的分隔线更好,由于使用实线的分隔线进行分类时,离分隔线最近的点到分隔线上的距离更大,即margin2>margin1。这段距离的两倍,称为间距(margin)。那些离分隔超平面最近的点,称为支持向量(support vector)。为了达到最好的分类效果,SVM的算法原理就是找到一个分隔超平面,能把数据集正确分类,而且间距最大

首先来看怎么计算间距,二维空间里,能够使用方程w1x1+w2x2+b=0来表示分隔超平面。针对高维度空间,可写成通常化的向量形式,即wTx+b=0。画出与分隔超平面平行的两条直线,分别穿过两个类别的支持向量(离分隔超平面距离最近的点)。这两条直线的方程分别为wTx+b=-1和wTx+b=1

根据点到直线的距离公式,能够容易地计算出支持向量A到分隔超平面的距离为

因为点A在直线wTx+b=1上,所以wTA+b=1,代入便可得,支持向量A到分隔超平面的距离为

为了使间距最大,咱们只须要找到合适的参数w和b,使最大便可。|| w || 是向量w的L2范数,其计算公式为:

由此可得,求的最大值,等价于求|| w || 2最小值

其中n为向量w的维度。除了间距最大外,选出来的分隔超平面还要能正确地把数据集分类。

针对方形的点,一定知足wTx+b>=1的约束条件。针对圆形的点x,一定知足wTx+b<=-1的约束条件。类别是离散的值,咱们使用-1来表示圆形的类别,用1来表示方形的类别,即y∈{-1,1}。针对数据集中的全部样本x(i),y(i),只要他们都知足如下的约束条件,则由参数w和b定义的分隔超平面便可正确地把数据集分类

技巧在于使用1和-1来定义类别标签。针对y(i)=1的状况,因为其知足wTx(i)=b>=1的约束,两边都乘以y(i)后,大于号保持不变。针对y(i)=-1的状况,因为其知足wTx(i)+b<=-1的约束,两边都乘以y(i)后,负负得正,而且小于号变成了大于号。这样,就能够用一个公式来表达针对两个不一样类别的约束函数。

逻辑回归算法里,使用0和1做为类别标签,而在这里咱们使用-1和1做为类别标签。其目的都是为了让数学表达尽可能简洁。

总结:求解SVM算法,就是在知足约束条件y(i)(wTx(i)+b)>=1的前提下,求解||w||2的最小值。

松弛系数

针对线性不可分的数据集,上面介绍的方法就失灵了,由于没法找到最大间距的分隔超平面

解决这个问题的办法是引入一个参数ε,称为松弛系数。而后把优化的目标函数变为

其中m为数据集的个数,R为算法参数。其约束条件相应地变为

怎么理解松弛系数呢?能够把εi理解为数据样本x(i)违反最大间距规则的程度,如上图右侧所示,针对大部分正常的样本,即知足约束条件的样本ε=0。而对部分违反最大间距规则的样本ε>0。而参数R则表示对违反最大间距规则的样本的惩罚力度。

当R选择一个很大的值时,咱们的目标函数对违反最大间距规则的点的惩罚力度将变得很大。

当R选择一个比较小的值时,针对那些违反最大间距规则的样本,其付出的代价不是特别大,咱们的规模就会倾向于容许部分点违反最大间距规则。能够把y(i)(wTx(i)+b)做为横坐标,把样本因为违反约束条件所付出的代价Ji做为纵坐标。能够画出下图

能够清楚地看出来,针对那些没有违反约束条件y(i)(wTx(i)+b)>=1的样本,其成本为0。而针对那些违反了约束条件的样本y(i)(wTx(i)+b)>=1-εi,其成本与ε成正比,如图中的斜线所示,斜线的斜率为R。

从这里的描述可知,引入松弛系数相似于逻辑回归算法里的成本函数引入正则项,目的都是为了纠正过拟合问题,让支持向量机对噪声数据有更强的适用性。

当出现一些违反大间距规则的噪声样本时,仍然但愿咱们的分隔超平面是原来的样子,这就是松弛系数的做用

核函数

核函数是特征转换函数。是很是抽象的描述

咱们的任务是找出合适的参数w,b使得由它们决定的分隔超平面、间距最大,且能正确地对数据集进行分类。间距最大是咱们的优化目标,正确地对数据集进行分类是约束条件。用数学来表达,在知足约束条件y(i)(wTx(i)+b)>=1,即y(i)(wTx(i)+b)-1>=0的前提下求0.5||w||2的最小值

拉格朗日乘子法是解决约束条件下,求函数极值的理想方法。其方法是引入非负系数a来做为约束条件的权重

公式中,针对数据集中的每一个样本x(i),y(i)都有一个系数ai与之对应。学习过微积分的都知道,极值处的偏导数为0。咱们先求L对w的偏导数

从而获得w和a的关系

把求的最小值,目的是为了使w的数学表达式尽可能简洁优美。接着咱们继续先求L对b的偏导数

经过代数运算可得

m是数据集的个数,a是拉格朗日乘子法引入的一个系数,针对数据集中的每一个样本x^(i),都有对应的ai。x^(i)是数据集中第i个样本的输入,它是一个向量,y^(i)是数据集第i个样本的输出标签,其值为y^(i)∈{-1,1}

使用数值分析能够求公式的最小值,这是一个典型的二次规划问题。目前普遍应用的是一个称为SMO(序列最小优化)的算法

最后求解出来的a有个明显的特色,即大部分ai=0,这个结论背后的缘由很直观,由于只有那些支持向量所对应的样本,直接决定了间隙的大小,其余离分隔超平面太远的样本,对间隙大小根本没有影响。

L里的x^(i)T x^(j)部分,其中x^(i)是一个特征向量,因此x^(i)T x^(j)是一个数值,它是两个输入特征向量的内积。咱们的预测函数为

当y>0咱们预测为类别1,当y<0时,咱们预测为类别-1。注意到预测函数里也包含式子x^(i)Tx。咱们把K(x^(i),x^(j)) = x^(i)^Tx^(j)称为核函数。x^(i)^Tx^(j)是两个向量内积,物理含义是衡量两个向量的类似性,当着两个向量互相垂直时,即彻底线性无关,此时x^(i)^Tx^(j)=0.引入核函数后,咱们的预测函数就变成

类似性函数

假设咱们有一个数据集,只有一个输入特征,要对这个数据集进行分类。因为只有一个输入特征,这些训练样本分布在一条直线上,此时咱们很难找出一个分隔超平面来分隔这个数据集

为了解决这个问题,能够想办法用必定的规则把这些没法进行线性分隔的样本,映射到更高维度的空间里,而后在高维度空间里找出分隔超平面。

把一维空间上的样本映射到二维空间,这样很容易就能找出一个分隔超平面把这些样本分离开

SVM的核函数就是为了实现这种类似性映射。最简单的核函数是K(x^(i),x^(j))=x^(i)^Tx^(j),它衡量的是两个输入特征向量的类似性。能够经过核函数K(x^(i),x^(j))来从新定义类似性,从而获得想要的映射。

怎么把低维度的空间映射到高维度的空间呢?能够使用多项式来增长特征数,这个本质上就是从低维度映射到高维度。针对上图的例子,咱们的输入特征是一维的,即只有[x1]变量,若是咱们要变成二维的,一个方法是把输入特征变为[x1,2x1^2],此时的输入特征就变成了一个二维向量。定义这种特征映射的哈函数为Φ(x),称之为类似性函数。针对输入特征向量x,通过Φ(x)做用后,会变成一个新的、更高维度的输入特征向量。这样在原来低维度计算类似性的运算x^(i)^Tx^(j),就能够转换为高维度空间里进行类似性运算Φ(x^(i))^TΦ(x^(j)) 

类似性函数是特征映射函数,好比针对二维的特征向量[x1,x2],咱们能够定义类似性函数

通过类似性函数转换后,二维的特征向量就变成了五维的特征向量。而核函数定义为特征向量的内积,通过类似性函数Φ(x)转换后,核函数即变为两个五维特征向量的内积,即K(x^(i),x^(j))=Φ(x^(i))^TΦ(x^(j))

经常使用的核函数

核函数通常和应用场景相关,好比在基因测序领域和文本处理领域,核函数多是不同的,有专门针对特定应用领域进行核函数开发和建模的科研人员在从事这方面的研究。虽然核函数和应用场景相关,但实际上仍是有一些通用的万金油式的核函数。经常使用的有两种

1. 多项式核函数,是对输入特征向量增长多项式的一种类似性映射函数,其数学表达式为 

其中y为正数,c为非负数。介绍过的线性核函数是多项式核函数在n=1,y=1,c=0处的一种特例。在二维空间里K(x^(i),x^(j))=x^(i)^Tx^(j)只能表达直线的分隔超平面,而多项式核函数K(x^(i),x^(j))=(yx^(i)^Tx^(j)+c)^n在n>1时,能够表达更复杂的、非直线的分隔超平面

2. 高斯核函数,其数学表达式为

若是咱们的输入特征是一维的标量,那么高斯核函数对应的形状就是一个反钟形的曲线,其参数σ控制反钟形的宽度

因为K(x^(i),x^(j))=Φ(x^(i))^TΦ(x^(j)),通过合适的数学变换,可得高斯核函数对应的特征转换函数为

注意前面无限多项的累加器,其物理意义就是把特征向量转换到无限多维向量空间里,即高斯核函数能够把输入特征向量扩展到无限维空间里。公式的推到过程会用到泰勒展开式

接下来看一下高斯核函数对应的预测函数

其中K(x^(i),x)是高斯核函数,而ai只是支持向量对应的样本处不为0,其余的样本为0

预测函数是中心点在支持向量处的高斯函数的线性组合,其线性组合的系数为aiy^(i)。所以,高斯函数也称为RBF(Radial Basis Function)核函数,即反钟形函数的线性组合

核函数的对比

线性函数

接触到底最简单的核函数,直接计算两个输入特征向量的内积。优势是简单、运算效率高。由于不涉及复杂的变换;结果容易解释,由于老是能生成一个最简洁的线性分隔超平面。缺点是对线性不可分的数据集没有很好的办法

多项式核函数

经过多项式来做为特征映射函数,优势是能够拟合出复杂的分隔超平面。缺点是可选的参数太多,有Y,c,n这3个参数要选择,实践过程当中,选择一组合适的参数会变得比较困难。另一个缺点是多项式的阶数n不宜过高,不然会给模型求解带来一些计算的困难。

当x^(i)^Tx^(j)<1时,通过n次方运算后,会接近于0,而x^(i)^Tx^(j)>1时,通过n次方运算后,又会变得很是大,这样核函数就会变得不够稳定

高斯核函数

高斯核函数能够把输入特征映射到无限多维,因此会比线性核函数功能上要强大不少,而且没有多项式核函数的数值计算那么困难,由于它的核函数计算出来的值永远在[0,1]之间。高斯核函数还有一个优势是参数容易选择,由于只有一个参数σ。缺点是不容易解释,由于映射到无限多维向量空间这个事情显得不太直观,计算速度比较慢,容易过拟合。缘由是映射到无限维向量空间,这个是很是复杂的模型,会试图去拟合全部的样本,从而形成过拟合

在实践中怎么选择核函数呢?逻辑回归算法也能够用来解决分类问题,究竟是用逻辑回归仍是SVM算法呢?

假设n是特征个数,m是训练数据集的样本个数,通常能够按照下面的规则来选择算法

若是n相对m来讲比较大,例如n=10000,m=10~1000,如文本处理问题,这个时候使用逻辑回归或线性函数的SVM算法均可以。

若是n比较小,m中等大小,例如n=1~1000,m=10~10000,那么能够使用高斯核函数的SVM算法

若是n比较小,m比较大,例如n=1~1000,m=50000+,那么通常须要添加特征,此时须要使用多项式核函数或高斯核函数的SVM算法

更通常性的算法选择原则是,针对数据量很大的问题,能够选择复杂一点的模型。虽然复杂模型容易形成过拟合,但因为数据量和好难打,能够有效地弥补过拟合问题。若是数据量比较小,通常须要选择简单一点的模型,不然很容易形成过拟合,此时须要注意模型是否欠拟合,若是出现了欠拟合,能够使用增长多项式特征的方法纠正欠拟合问题。

scikit-learn里的SVM

scikit-learn里对SVM的算法实现都在包sklearn.svm下面,其中SVC类是用来进行分类的任务,SVR是用来进行数值回归任务的。

以SVC为例,首先须要选择SVM的核函数,由参数kernel指定,其中linear表示本章介绍的线性函数,只能产生直线形状的分隔超平面;poly表示本章介绍的多项式核函数,用它能够构建出复杂形状的分隔超平面,rbf表示高斯核函数

不一样的核函数须要指定不一样的参数。针对线性函数,只须要指定参数C,它表示对不符合最大间距规则的样本的惩罚力度。针对多项式核函数,除了参数C外,还须要指定degree,表示多项式的阶数

针对高斯核函数,除了参数C外,还须要指定gamma值,这个值对应的是高斯核函数公式里的

看一个最简单的例子,咱们生成一个有两个特征,包含两种类别的数据集,而后用线性核函数的SVM算法进行分类

import numpy as np
import matplotlib.pyplot as plt
from sklearn import svm
from sklearn.datasets import make_blobs


def plot_hyperplance(clf, X, y, h=0.02, draw_sv=True, title='hyperplan'):
    x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
    y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
    xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
    plt.title(title)
    plt.xlim(xx.min(), xx.max())
    plt.ylim(yy.min(), yy.max())
    plt.xticks(())
    plt.yticks(())

    Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)
    plt.contourf(xx, yy, Z, cmap='hot', alpha=0.5)

    makers = ['o', 's', '^']
    colors = ['b', 'r', 'c']
    labels = np.unique(y)
    for label in labels:
        plt.scatter(X[y == label][:, 0], X[y == label][:, 1], 
              c=colors[label], marker=makers[label]) if draw_sv: sv = clf.support_vectors_ plt.scatter(sv[:, 0], sv[:, 1], c='y', marker='x') X, y = make_blobs(n_samples=100, centers=2, random_state=0, cluster_std=0.3) clf = svm.SVC(C=1.0, kernel='linear') clf.fit(X, y) plt.figure(figsize=(12, 4), dpi=144) plot_hyperplance(clf, X, y, h=0.01, title='Maximum Margin Hyperplan') plt.show()

输出图形以下

带有X标记的点即为支持向量,,保存在模型的support_vectors_里

接着来看另一个例子,生成一个有两个特征、包含三种类别的数据集,而后分别构造4个SVM算法来拟合数据集,分别是线性核函数、三阶多项式核函数,y=0.5的高斯核函数,y=0.1的高斯核函数

最后把这4个SVM算法拟合出来的分隔超平面画出来

X, y = make_blobs(n_samples=100, centers=3, random_state=0, cluster_std=0.8)
clf_linear = svm.SVC(C=1.0, kernel='linear')
clf_poly = svm.SVC(C=1.0, kernel='poly', degree=3)
clf_rbf = svm.SVC(C=1.0, kernel='rbf', gamma=0.5)
clf_rbf2 = svm.SVC(C=1.0, kernel='rbf', gamma=0.1)

plt.figure(figsize=(10, 10), dpi=144)
clfs = [clf_linear, clf_poly, clf_rbf, clf_rbf2]
titles = ['Linear Kernel', 'Polynomial Kernel with Degree=3',
          'Gaussian Kernel with $\gamma=0.5$',
          'Gaussian Kernel with $\gamma=0.1$']
for clf, i in zip(clfs, range(len(clfs))):
    clf.fit(X, y)
    plt.subplot(2, 2, i + 1)
    plot_hyperplance(clf, X, y, title=titles[i])

plt.show()

输出图形以下,其中带有标记的点即为支持向量

左上角是线性核函数,只能拟合出直线分隔超平面。

右上角是三阶多项式核函数,能拟合出复杂曲线分隔超平面

左下角是y=0.5的高斯核函数,右下角是y=0.1的高斯核函数

经过调整参数y的值,能够调整分隔超平面的形状。y值太大,容易形成过拟合,y值过小,高斯核函数会退化成线性核函数。

实例:乳腺癌检测

使用逻辑回归算法进行了乳腺癌检测模型的学习和训练。此次使用支持向量机来解决这个问题

from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split

cancer = load_breast_cancer()
X = cancer.data
y = cancer.target
print('data shape:{0}; no. positive:{1}; no. negative:{2}'
.format(X.shape, y[y == 1].shape[0], y[y == 0].shape[0]))
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

输出结果以下

data shape:(569, 30); no. positive:357; no. negative:212 

能够看出来,咱们的数据集很小。高斯核函数太复杂,容易形成过拟合,模型效果应该不会很好。

先使用高斯核函数试一下

from sklearn.svm import SVC

clf = SVC(C=1.0, kernel='rbf', gamma=0.1)
clf.fit(X_train, y_train)
train_score = clf.score(X_train, y_train)
test_score = clf.score(X_test, y_test)
print('train score:{0};test score:{1}'.format(train_score, test_score))

输出结果以下

train score:1.0;test score:0.7280701754385965 

训练数据集分数接近满分,而交叉验证数据集的评分很低,这是典型的过拟合现象。代码中选择了gamma参数为0.1,这个值相对已经比较小了

固然,能够自动来选择参数。使用GridSearchCV来自动选择参数。来看看若是使用高斯模型,最优的gamma参数值是多少

def plot_param_curve(plt, train_sizes, cv_results, xlabel):
    train_scores_mean = cv_results['mean_train_score']
    train_scores_std = cv_results['std_train_score']
    test_scores_mean = cv_results['mean_test_score']
    test_scores_std = cv_results['std_test_score']
    plt.title('parameters turning')
    plt.grid()
    plt.xlabel(xlabel)
    plt.ylabel('score')
    plt.fill_between(train_sizes,
                     train_scores_mean - train_scores_std,
                     train_scores_mean + train_scores_std,
                     alpha=0.1, color="r")
    plt.fill_between(train_sizes,
                     test_scores_mean - test_scores_std,
                     test_scores_mean + test_scores_std,
                     alpha=0.1, color="g")
    plt.plot(train_sizes, train_scores_mean, '.--', color="r",
             label="Training score")
    plt.plot(train_sizes, test_scores_mean, '.-', color="g",
             label="Cross-validation score")

    plt.legend(loc="best")
    return plt

from sklearn.svm import SVC
from sklearn.model_selection import GridSearchCV

gammas = np.linspace(0,0.0003,30)
param_grid = {'gamma':gammas}
clf = GridSearchCV(SVC(),param_grid,cv=5)
clf.fit(X,y)
print('best param :{0}; best score:{1}'.format(clf.best_params_,clf.best_score_))
plt.figure(figsize=(10,4),dpi=144)
plot_param_curve(plt,gammas,clf.cv_results_,xlabel='gamma')
plt.show()

输出结果以下

best param :{'gamma': 0.00011379310344827585}; best score:0.9367311072056239 

因而可知,即便最好的gamma参数下,平均最优得分也只是0.93, 咱们选择在gamma为0.01时,画出学习曲线

import time
from sklearn.svm import SVC
from common_utils import plot_learning_curve
from sklearn.model_selection import ShuffleSplit

cv = ShuffleSplit(n_splits=10, test_size=0.2, random_state=0)
title = 'Learning Curves for Gaussian Kernel'
start = time.clock()
plt.figure(figsize=(10, 4), dpi=144)
plot_learning_curve(plt, SVC(C=1.0, kernel='rbf', gamma=0.01),
                    title, X, y, ylim=(0.5, 1.01), cv=cv)
print('elaspe:{0:.6f}'.format(time.clock() - start))
plt.show()

画出来的图形以下

这是明显的过拟合现象,交叉验证数据集的评分很是低,且离训练数据集评分很是远

接下来换一个模型,使用二阶多项式核函数来拟合模型

from sklearn.svm import SVC

clf = SVC(C=1.0, kernel='poly', degree=2)
clf.fit(X_train, y_train)
train_score = clf.score(X_train, y_train)
test_score = clf.score(X_test, y_test)
print('train score:{0}; test score:{1};'.format(train_score, test_score))

输出结果以下

train score:0.9758241758241758; test score:0.956140350877193; 

结果好多了,做为对比,咱们画出一阶多项式和二阶多项式的学习曲线

cv = ShuffleSplit(n_splits=5, test_size=0.2, random_state=0)
    title = 'Learning Curves with degree={0}'
    degrees = [1, 2]

    start = time.clock()
    plt.figure(figsize=(12, 4), dpi=144)
    for i in range(len(degrees)):
        plt.subplot(1, len(degrees), i + 1)
        plot_learning_curve(plt, SVC(C=1.0, kernel='poly', degree=degrees[i]),
                            title.format(degrees[i]), X, y, 
                  ylim
=(0.8, 1.01), cv=cv, n_jobs=4) print('elaspe: {0:.6f}'.format(time.clock()-start)) plt.show()

注意,须要使用if __name__ == '__main__' 来说代码括起来

上图中能够看出,二阶多项式核函数的拟合效果更好。平均交叉验证数据集评分可达0.950,最高时候达到0.975。二阶多项式核函数计算代价很高

咱们使用逻辑回归算法来处理乳腺癌检测问题时,使用二阶多项式增长特征,同时使用L1范数做为正则项,其拟合效果比这里的支持向量机效果好。

更重要的是逻辑回归算法的运算效率远远高于二阶多项式核函数的支持向量机算法。这里的支持向量机算法的效果仍是比使用L2范数做为正则项的逻辑回归算法好的。

朴素贝叶斯算法

朴素贝叶斯是一种基于几率统计分类方法。在条件独立假设的基础上,使用贝叶斯定理构建算法在文本处理领域有普遍的应用。

贝叶斯定理

某警察使用一个假冒伪劣的呼吸测试仪来测试司机是否醉驾。假设这个仪器有5%的几率会把一个正常的司机判断为醉驾,但对真正醉驾的司机其测试结果是100%准确的。从过往的统计的值,大概有0.1%的司机为醉驾。假设该警察随机拦下一个司机,让他作呼气测试,仪器测试结果为醉驾。仅凭这一结果判断,这位司机真的是醉驾的几率有多高?

真实的结果是不到2%,若是没有其余方式,仅凭这个仪器的测试结果来判断,其实准确率是很是低的。

假设咱们的样本里有1000人,这1000人里面有0.1%的几率为醉驾,既有1位是真正醉驾的司机,999位正常。这999位司机,有5%的几率会被误判,因此醉驾的几率是1/(1+999*5%)=1.96%

贝叶斯定理是计算这类条件几率问题的绝佳方法。记P(A|B) 表示观察到事件B发生时事件A发生的几率,则贝叶斯定理的数学表达式为

回到例子里,记事件A为司机真正醉驾事件B为仪器显示司机醉驾。则例子里要求解的问题即为P(A|B),观察到仪器显示实际醉驾时,司机真正醉驾时的几率是多少。

P(A) 表示司机真正醉驾的几率,这是先验几率,例子里的数值是0.1%。P(B|A) 表示当司机真正醉驾时,仪器显示司机醉驾的几率是多少,从例子里的数据得知是100%。P(B)表示仪器显示司机醉驾的几率,这里有两部分数据,针对真正醉驾的司机(0.1%),仪器能100%检测出来,所以这部分的数值为0.1%*100%

针对正常的司机(1-0.1%),仪器显示醉驾的几率为(1-0.1%)*5%,代入贝叶斯定理便可得

P(A|B)=0.1%*100%/[0.1%*100%+(1-0.1%)*5%]=1.96%

朴素贝叶斯分类法

假设有一个已标记的数据集[x^i,y^i],其中y^i∈[C1,C2,.....Cb],即数据集总共有b个类别;x^i∈[x1,x2,....,xn],即总共有n个输入特征。针对一个新的样本x,咱们要预测y的值,即对x进行分类。这是典型的机器学习里的分类问题。

使用统计学的语言能够描述为:当观察到输入样本是x时,其所属于的类别y=Ck的几率,使用条件几率公式表示为

其中,Ck∈[C1,C2,....,Cb],只须要分别求出全部b个类别的几率,而后取几率最大的那个Ck便是x所属的类别。直接求解上述公式比较困难,能够应用贝叶斯定理进行一次变换

对于一个肯定的数据集,Ck,P(x)都是固定的值。所以

其中,∞表示成正比的意思。所以,只须要求解,针对不一样的Ck的状况下,p(Ck)P(x|Ck)的最大值便可知道,x属于哪一个类别。根据联合几率公式可得

联合几率表示的是一种几率叠加。例如,你走在路上碰见美女的一个随机事件,美女对你一见倾心是另外一个随机事件,那么你在路上遇到美女且对你一见倾心的几率要怎么计算呢?

使用几率叠加来计算,遇到美女的几率乘以美女对你一见倾心的几率(条件几率)

由于x是有n个特征向量,即x=[x1,x2,...,xn]可得

根据链式法则及条件几率的定义,能够进一步推导公式

朴素指的是条件独立假设,即事件之间没有关联关系。例如,掷一个质地均匀的骰子两次,先后之间出现的数字是独立、不相关,咱们称这两个事件是条件独立。朴素贝叶斯算法的前提是,输入特征须要知足条件独立假设。即当i != j时,xi和xj是不相关的,通俗来讲就是xi事件是否发生和xj不要紧。根据条件独立的原则

其中∏是连乘符号。P(Ck) 表示每种类别出现的几率,这个值能够很容易地从数据集里统计出来。P(xi|Ck) 表示当类别为Ck时,特征xi出现的几率,这个值也能够从数据集中统计出来。这就是朴素贝叶斯分类法的数学原理。

简单的例子

假设有如下关于驾龄、平均车速和性别的统计数据

如今观察到一个驾龄为2年的人,平均车速为80。问这我的的性别是什么?

假设C0表示女,C1表示男,x0表示驾龄,x1表示平均车速。先计算这我的为女性的几率相对值。根据统计数据,女司机的几率P(C0)=5/10=0.5。驾龄为2年的女司机几率即P(x0|C0)=1/10=0.1。平均车速为80的女性司机几率P(x1|C0)=1/10=0.1.根据朴素贝叶斯分类法的数学公式

接着计算这我的为男性的几率相对值。根据统计数据,不可贵出男性司机的几率P(C1)=5/10=0.5。驾龄为2年的男性司机的几率P(x0|C1)=2/10=0.2。平均车速为80的男性司机几率P(x1|C1)=3/10=0.3。根据朴素贝叶斯分类法的数学公式

从相对几率来看,这我的是男性的几率是女性的6倍,据此判断这我的是男性。咱们也能够从相对几率里算出绝对几率,这我的是男性的几率是0.03/(0.02+0.005)=0.857

几率分布

朴素贝叶斯分类法是根据数据集里的数据,计算出绝对几率来进行求解。在看一遍数学公式

其中,P(xi|Ck) 表示在类别Ck里特征xi出现的几率。这里有个最大的问题,若是数据集过小,那么从数据集里计算出来的几率误差将很是严重。

例如,观察一个质地均匀的骰子投掷6次的结果是[1,3,1,5,3,3]。每一个点的出现几率都是1/6,若是根据观察到的数据集取计算每一个点的几率,和真实的几率相差将是很是大的。

能够使用几率分布来计算几率,而不是从数据集里计算几率

几率统计的基本概念 

人的身高是一个连续的随机变量,而投掷一个骰子获得的点数则是一个离散随机变量。

若是咱们随便找一我的,那么这我的身高170的可能性是多大呢?若是能描述人类身高的可能性,那么直接把170代入便可求出这个可能性。这个函数就是几率密度函数,也称为PDF(Probability Density Function)。

典型的几率密度函数是高斯分布函数,如人类的身高就知足高斯分布的规律

好比,投掷一个骰子,获得6的几率是多少呢?1/6

假若有一个函数f(x),能描述骰子出现x点数([x∈[1,6]])的几率,那么把x代入便可获得几率,这个函数称为几率质量函数,即PMF(Probability Mass Function)。

那么为何还要使用几率质量函数呢?

一是在数据上追求统一性,二是并非全部的离散随机变量的几率分布都像投掷骰子那么直观。

总结一下:随机变量分红两种,一种是连续随机变量,另外一种是离散随机变量。几率密度函数描述的是连续随机变量在某个特定值的可能性,几率质量函数描述的是离散随机变量在某个特定值的可能性。而几率分布则是描述随机变量取值的几率规律。

多项式分布

抛一个硬币,要么正面朝上,要么反面朝上。假如出现正面的几率是p,则出现反面的几率就是1-p。

符合这种规律的几率分布,称为伯努利分布(Bernoulli Distribution),其几率质量函数为

其中,k∈[0,1],p是出现1的几率。一枚质地均匀的硬币被抛一次,获得正面的几率为0.5.代入上述公式,也能够获得相同的结果,即f(1;0.5)=0.51*(1-0.5)0=0.5*1=0.5

更通常的状况,即不止两种可能性时,假设每种可能性时pi,则知足Σpi=1条件的几率分布,称为类别分布(Categorical Distributtion)

例如,投掷一个骰子,则会出现6种可能性,全部的可能性加起来的几率为1.类别分布的几率质量函数为:

其中,π是连乘符号,k是类别的数量,pi是第i种类别的几率,xi当且仅当类别x为类别i时,其值为1,其余状况其值为0。例如,针对质地均匀的骰子,k的值为6,pi的值为1/6.

问:投掷这个骰子获得3的几率是多少,答案是1/6。代入几率质量函数验算一下

针对全部i!=3的状况,xi=0,针对i=3的状况,xi=1,因此f(3|p)=1/6

问:一枚硬币被抛10次,出现3次正面的几率是多少。这是典型的二项式分布问题。二项式分布指的是把符合伯努利分布的实验作了n次,结果1出现0次,1次...n次的几率分别是多少,几率质量函数为:

其中,k是结果1出现的次数,k∈[0,1,....,n],n是实验的总次数,p是在一次实验中结果1出现的几率。怎么理解这个公式呢?

总共进行了n次实验,那么出现k次结果1的几率为pk,剩下的一定是结果0的次数,即出现了n-k次,其几率为(1-p)n-k。公式前面的系数表示的是组合,即k次结果1能够是任意的组合,好比多是前k次是结果1,也多是后k次出现的结果是1.

那么硬币被抛10次,出现3次正面的几率是多少呢?代入二项式分布的几率质量函数,获得

再看一个更简单的例子。问:一枚硬币被抛出1次,出现0次正面的几率是多少?代入二项式分布的几率质量函数,获得:

其中,0的阶乘为1,即0!=1。结果跟咱们预期的相符,当实验只作一次时,二项式分布退化为伯努利分布

多项式分布是指知足类别分布的实验,连续作n次后,每种类别出现的特定次数组合的几率分布状况。

假设xi表示类别i出现的次数,pi表示类别i在单次实验中出现的几率。当知足前提条件

时,由随机变量xi构成的随机向量X=[x1,...,xk] 知足如下分布函数

其中,P是由各个类别的几率构成的向量,即P=[p1,...,pk],k表示类别的总数,n表示实验进行的总次数。

按照特定顺序,全部类别出现的某个特定的次数组合的几率,例如投6次骰子,出现(1,2,3,4,5,6)这样特定顺序组合的几率。前面的系数表示组合的个数,如投6次骰子,每一个点数都出现一次,能够是(1,2,3,4,5,6),也能够是1,3,2,4,5,6

再看一个例子,同时投掷6个骰子,出现1,2,3,4,5,6这种组合的几率是多少?能够把这个问题转换成连续6次投掷骰子,每一个类别都出现一次的几率。这是典型的多项式分布问题,其中随机向量X=[1,1,1,1,1,1],代入多项式分布的几率质量函数可得

将质地均匀的骰子投掷6次,获得4个4的几率是多少?把这个问题转换为二项式分布问题,投掷1次骰子时,获得4的几率是1/6,获得其余点数的几率是5/6.如今须要计算投掷6次骰子获得4个4的几率,代入二项式分布几率质量函数可得

再来算一下同时投掷6个质地均匀的骰子,出现5个1的几率是多少?仍是转换为二项式分布问题:

总结一下,二项式分布描述的是屡次伯努利实验中,某个结果出现次数的几率。多项式分布描述的是屡次进行知足类别分布的实验中,全部类别出现的次数组合的分布

二项式分布和多项式分布组合朴素贝叶斯算法,常常被用来实现文章分类算法。

例如:有一个论坛须要对用户的评论进行过滤,屏蔽不文明的评论。首先须要一个通过标记的数据集,称为语料库。假设使用人工标记的方法对评论进行人工标记,标记为1表示包含不文明用语的评论,标记为0表示正常评论。

假设咱们的词库大小为k,则文章中出现的某个词能够当作是一次知足k个类别的类别分布实验。咱们知道一篇评论是由n个词组成的,所以一篇文章能够当作是进行n次符合类别分布的试验后的产物。由此得知,一篇评论文章服从多项式分布,它是词库里的全部词语出现的次数组合构成的随机向量。

通常状况下,词库比较大,评论文章只是由少许词组成,因此这个随机向量是很稀疏的,即大部分元素为0.经过分析语料库,容易统计出每一个词出现不文明评论及正常评论文章里的几率,即pi的值。同时针对带预测的评论文章,能够统计出词库里的全部词在这篇文章里的出现次数,即xi的值及评论文章的词语个数n。代入多项式分布的几率质量函数

能够求出,待预测的评论文章构成的随即向量X,其为不文明评论的相对几率。同理也可求出其为正常评论的相对几率,经过比较两个相对几率,就能够对这篇文章输出一个预测值。固然,实际应用中,涉及大量的天然语言处理的手段,包括中文分词技术,词的数学表示等。

高斯分布

连续值怎么用朴素贝叶斯算法来处理呢?

能够用区间把连续值转换为离散值。例如把[0,40]之间的平均车速做为一个级别,把[40,80]之间的平均车速做为一个级别,再把80以上的车速做为另一个级别。这样就能够把连续的值变成离散的值,从而使用朴素贝叶斯分类法进行处理。另一个方法,是使用连续随机变量的几率密度函数,把数值转换为一个相对几率。

高斯分布(Gaussian Distribution)也称为正态分布(Normal Distribution)是天然界最多见的一种几率密度函数。人的身高知足高斯分布,特别高和特别矮的人出现的相对几率都比较低。人的智商也符合高斯分布,特别聪明和特别笨的人出现的相对几率都比较低。高斯分布的几率密度函数为:

其中x为随机变量的值,f(x)为随机变量的相对几率,μ为样本的平均值,其决定了高斯分布曲线的位置,σ为标准差,其决定了高斯分布的幅度,σ值越大,分布越分散,值越小,分布越集中。典型的高斯分布

这里须要提醒读者注意高斯分布的几率密度函数和支持向量机里的高斯核函数的区别。两者的核心数学模型是相同的,但目的是不一样的

连续值的处理

假设有一组身体特征的统计数据以下

假设某人身高6英尺、体重130英镑、脚掌8英寸,请问此人的性别是什么?

根据朴素贝叶斯公式

针对带预测的这我的的数据x,咱们只须要分别求出男性和女性的相对几率

而后去相对几率较高的性别为预测值便可。

这里的困难在于,全部的特征都是连续变量,没法根据统计数据计算几率。咱们能够使用区间法,把连续变量转换为离散变量,而后再计算几率。可是因为数据量较小,这不是一个好办法。

因为人类身高、体重、脚掌尺寸知足高斯分布,所以更好的办法是使用高斯分布的几率密度函数来求相对几率。

首先针对男性和女性,分别求出每一个特征的平均值和方差

接着利用高斯分布的几率密度函数,来求解男性身高6英尺的相对几率

这里的关键是把连续值(身高)做为输入,经过高斯分布的几率密度函数的处理,直接转换为相对几率。这里是相对几率,因此其值大于1并为违反几率论规则

使用相同的方法,能够算出如下数值

因为p(Male)=0.5,所以这我的是男性的相对几率为:

使用相同的办法,能够算出这我的为女性的相对几率为5.3778*10-4.从数据可知,这我的为女性的几率比男性的几率高了5个数量级,所以判断这我的为女性。

实例:文档分类

在scikit-lerarn里,朴素贝叶斯算法在sklearn.naive_bayes包里实现,包含了本章介绍的几种典型的几率分布算法。

其中GussianNB实现了高斯分布的朴素贝叶斯算法,

MultinomialNB实现了多项式分布的朴素贝叶斯算法,

BernoulliNB实现了伯努利分布的朴素贝叶斯算法。 

朴素贝叶斯算法在天然语言领域有普遍的应用,本节咱们用MultinomialNB来实现文档自动分类

获取数据集 

datasets/mlcomp/dataset-379-20news-18828.zip,解压到当前目录下,会生成一个379的目录

使用train子目录下的文档进行模型训练,而后使用test子目录下的文档进行模型预测。

文档的数学表达

怎么把一个文档表达为计算机能够理解并处理的信息,是天然语言处理中的一个重要课题,完整的内容能够写成鸿篇巨著

TF-IDF是一种统计方法,用以评估一个词语对一份文档的重要程度。TF表示词频(Term Frequency),对一份文档而言,词频是特定词语,在这篇文档里出现的次数除以文档的词语总数。

例如:一篇文档总共有1000个词,其中朴素贝叶斯出现了5次,的 出现了25次,应用出现了12次,那么他们的词频分别是0.005,0.025,0.012

IDF表示一个词的逆向文档频率指数(Inverse Document Frequency),能够由总文档数目除以包含该词语的文档的数目,再将获得的商取对数获得,它表达的是词语的权重指数。

例如,咱们的数据集总共有1万篇文档,其中朴素贝叶斯 只出如今10篇文档中,则其权重指数

的 在全部的文档中都出现过,则其权重指数IDF=log(1)=0.应用 在1000篇文档中出现,则其权重指数

计算出每一个词的词频和权重指数后,二者相乘,便可获得这个词在文档中的重要程度。词语的重要性随着它在文档中出现的次数呈正比例增长,但同时会随着它在语料库中出现的频率呈反比降低。

有了TF-IDF这个工具,能够把一篇文档转换为一个向量。能够从数据集(天然语言处理领域也称为corpus,即语料库)里提取出全部出现的词语,咱们称为词典。假设词典里总共有10000个词语,则每一个文档均可以转化为一个10000维的向量。其次,针对咱们要转换的文档里出现的每一个词语,都去计算器TF-IDF的值,并把这个值填入文档向量里这个词所对应的元素上。这样就完成了把一篇文档转换为一个向量的过程。一个文档每每只会由词典里的一小部分词语构成,这就意味着这个向量里的大部分元素都是0.

scikit-learn软件包里实现了把文档转换为向量的过程。

首先把训练用的语料库读入内存

from time import time
from sklearn.datasets import load_files

print('loading train dataset ...')
t = time()
news_train = load_files('mlcomp/379/train')
print('summary:{0} documents in {1} categories.'
      .format(len(news_train.data), len(news_train.target_names)))
print('done in {0} seconds'.format(time() - t))

其中379/train目录下放的就是咱们的语料库,其中包含20个子目录,每一个子目录的名字表示的是文档的类别,子目录下包含这种类别的全部文档。load_files() 函数会从这个目录里把全部的文档都读入内存,而且自动根据所在的子目录名称打上标签。其中news_train.data是一个数组,里面包含了全部文档的文本信息。new_train.target也是一个数组,包含了全部文档所属的类别,而news_train.target_names则是类别的名称,所以,若是咱们想知道第一篇文档所属的类别名称,只须要经过代码news_train.target_names[news_tarin.target[0]] 便可获得 

输出以下

summary:13180 documents in 20 categories.
done in 1.1021101474761963 seconds

语料库里总共有13180个文档,其中分红20个类别。须要吧这些文档所有转换为由TF-IDF表达的权重信息构成的向量

from sklearn.feature_extraction.text import TfidfVectorizer

print('vectorizing train dataset ...')
t = time()
vectorizer = TfidfVectorizer(encoding='latin-1')
X_train = vectorizer.fit_transform((d for d in news_train.data))
print('n_samples: %d, n_features: %d' % X_train.shape)
print('number of non-zero features in sample [{0}]:{1}'
      .format(news_train.filenames[0], X_train[0].getnnz()))
print("done in {0} seconds".format(time() - t))

其中,TfidfVectorizer类是用来把全部的文档转换为矩阵,该矩阵每行都表明一个文档,一行中的每一个元素表明一个对应的词语的重要性,词语的重要性由TF-IDF来表示。其fit_transform() 方法是fit() 和 transform() 合并起来。其中fit()会先完成语料库分析、提取词典等操做,transform()会把对每篇文档转换为向量,最终构成一个矩阵,保存在X_train变量里。

输出内容以下

vectorizing train dataset ...
n_samples: 13180, n_features: 130274
number of non-zero features in sample [mlcomp/379/train\talk.politics.misc\17860-178992]:108
done in 6.095609426498413 seconds

词典总共有130274个词语,即每篇文档均可以转换为一个130274维的向量。第一篇文档中,只有108个非零元素,即这篇文档总共由108个不重复的单词组成,在这篇文档中出现的这108个单词的TF-IDF值会被计算出来,并保存在向量中的指定位置上。X_train是一个维度为13180130274的稀疏矩阵。

模型训练

矩阵的每一行表示一个数据样本,矩阵的每一列表示一个特征。能够直接使用MultinomialNB对数据集进行训练

from sklearn.naive_bayes import MultinomialNB

print('traning models ...'.format(time() - t))
t = time()
y_train = news_train.target
clf = MultinomialNB(alpha=0.0001)
clf.fit(X_train, y_train)
train_score = clf.score(X_train, y_train)
print('train score:{0}'.format(train_score))
print("done in {0} seconds".format(time() - t))

其中alpha表示平滑参数,其值越小,越容易形成过拟合,值太大,容易形成欠拟合。

输出以下

train score:0.9978755690440061
done in 0.49102783203125 seconds

接着,加载测试数据集,并用一篇文档来预测是否准确。测试数据集在379/test目录下

print('loading test dataset ...')
t = time()
news_test = load_files('mlcomp/379/test')
print('summary:{0} documents in {1} categories.'
      .format(len(news_test.data), len(news_test.target_names)))
print('done in {0} seconds'.format(time() - t))

输出结果以下

summary:5648 documents in 20 categories.
done in 61.90554094314575 seconds

咱们的测试数据集共有5648篇文档。接着,咱们把文档向量化

print('vectorizing test dataset ...')
t = time()
X_test = vectorizer.transform((d for d in news_test.data))
y_test = news_test.target
print('n_samples: %d, n_features: %d' % X_test.shape)
print('number of non-zero features in sample [{0}]: {1}'
      .format(news_test.filenames[0], X_test[0].getnnz()))
print('done in %fs' % (time() - t))

 vectorizer变量是咱们处理训练数据集时用到的向量化的类的实例,此处咱们只须要调用transform()进行TF-DF数值计算便可,不须要再调用fit()进行语料库分析了。

输出内容以下

n_samples: 5648, n_features: 130274
number of non-zero features in sample [mlcomp/379/test\rec.autos\7429-103268]: 61
done in 1.642094s

这样测试数据集也转换为一个维度为5648130274的稀疏矩阵。能够取测试数据集里的第一篇文档初步验证一下,看训练出来的模型可否正确地预测这个文档所属类别

pred = clf.predict(X_test[0])
print('predict:{0} is in category {1}'
      .format(news_test.filenames[0], news_test.target_names[pred[0]]))
print('actually:{0} is in category {1}'
      .format(news_test.filenames[0], news_test.target_names[news_test.target[0]]))

输出以下

predict:mlcomp/379/test\rec.autos\7429-103268 is in category rec.autos
actually:mlcomp/379/test\rec.autos\7429-103268 is in category rec.autos

预测的结果和实际结果是相符的

模型评价

虽然经过验证,说明训练的模型是可用的,可是不能经过一个样本的预测来评价模型的准确性。须要对模型有个全方位的评价,scikit-learn软件包提供了全方位的模型评价工具

首先对测试数据集进行预测

print('predicting test dataset ...')
t0 = time()
pred = clf.predict(X_test)
print('done in %fs' % (time() - t0))

 输出以下

done in 0.040971s

接着使用classification_report()函数来查看一下针对每一个类别的预测准确性:

from sklearn.metrics import classification_report

print('classification report on test set for classifier: ')
print(clf)
print(classification_report(y_test, pred, target_names=news_test.target_names))

从输出结果来看,针对每种类别都统计了查准率、召回率和F1-Score。此外,还能够经过confusion_matrix()函数生成混淆矩阵,观察每种类别被错误分类的状况。例如,这些被错误分类的文档是被错误分类到那些类别里的

from sklearn.metrics import confusion_matrix

cm = confusion_matrix(y_test,pred)
print('confusion matrix:')
print(cm)

能够把混淆矩阵进行数据可视化处理

plt.figure(figsize=(8, 8), dpi=144)
plt.title('Confusion matrix of the classifier')
ax = plt.gca()
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
ax.spines['bottom'].set_color('none')
ax.spines['left'].set_color('none')
ax.xaxis.set_ticks_position('none')
ax.yaxis.set_ticks_position('none')
ax.set_xticklabels([])
ax.set_yticklabels([])
plt.matshow(cm, fignum=1, cmap='Greens')
plt.show()

除对角线外,其余地方颜色越浅,说明此处错误越多。经过这些数据,能够详细分析样本数据,找出为何某种类别会被错误地分类到另外一种类别里,从而进一步优化模型

PCA算法

PCA是Principal Component Analysis的缩写,中文称为主成分分析法。它是一种维数约减(Dimensionality Reduction)算法,即把高维度数据在损失最小的状况下转换为低维度数据的算法。显然,PCA能够用来对数据进行压缩,能够在可控的失真范围内提升运算速度。

算法原理

假设须要把一个二维数据减为一维数据。能够想办法找出一个向量u(1)以便让二维数据的点(方形点)到这个向量所在的直线上的平均距离最短,即投射偏差最小。

这样就能够在失真最小的状况下,把二维数据转换为向量u^(1)所在直线上的一维数据。加入须要把三维数据降为二维数据时,须要找出两个向量u^(1),u^(2),以便让三维数据的点在这两个向量所决定的平面上的投射偏差最小。

若是从数学角度来描述PCA算法就是,当须要从n维数据降为k维数据时,须要找出k个向量u^(1),u^(2),...u^(k),把n维的数据投射到这k个向量决定的线性空间里,最终使投射偏差最小化的过程。

假设有一个数据集,用m*n维的矩阵A表示。矩阵中每一行表示一个样本,每一列表示一个特征,总共有m个样本,每一个样本有n个特征。目标是减小特征个数,只保留最重要的k个特征

数据归一化和缩放

数据归一化和缩放是一种数学技巧,提升PCA运算时的效率。数据归一化的目标是使特征的均值为0。数据归一化公式为:

其中 aj^(i)是指i个样本的第j个特征的值,μj表示的是第j个特征的均值。当不一样的特征值不在同一个数量级上的时候,还须要对数据进行缩放。数据归一化再缩放的公式为:

sj表示第j个特征的范围,即sj=max(aj^i)-min(aj^i)

计算协方差矩阵的特征向量

针对预处理后的矩阵X,先计算其协方差矩阵(Covariance Matrix)

其中,Σ表示协方差矩阵,用大写的Sigma表示,大写的Sigma和累加运算符看起来几乎同样,但这里实际上是一个数学符号而已,不是累加运算。计算结果Σ将是一个n*n的矩阵。

接着经过奇异值分解来计算协方差举着你的特征向量(eigenvectors)

其中,svd是奇异值分解(Singular Value Decomposition)运算,是高级线性代数的内容。通过奇异值分解后,有3个返回值,其中矩阵U是个n*n的矩阵,若是咱们选择U的列做为向量,那么咱们将获得n个列向量u^(1),u^(2),...,u^(n),这些向量就是协方差矩阵的特征向量。表示的物理意义是,协方差矩阵Σ能够由这些特征向量进行线性组合获得。

数据降维和恢复

获得特征矩阵后,就能够对数据进行降维处理了。假设降维前的值为x^(i),降维后为z^(i),那么

其中,Ureduct=[u(1),u(2),...,u(k)],它选取自矩阵U的前k个变量,Ureduce称为主成分特征矩阵,它是数据降维和恢复的关键中间变量。看一下数据维度,Ureduce是n*k的矩阵,所以是k*n的矩阵,x^(i)是n*1的向量,所以z^(i)是k*1的向量。这样即完成了数据的降维操做。

也能够用矩阵运算一次性转换多个向量,提升效率。假设X是行向量x^(i)组成的矩阵,则

其中,X是m*n的矩阵,所以降维后的矩阵Z也是一个m*k的矩阵。

Z^(i)就是x^(i)在Ureduct构成的线性空间投射,而且其投射偏差最小。

数据降维后,怎么恢复呢?降维的数据计算公式。因此,若是要还原数据,能够使用下面的公式

其中,Ureduct是n*k维矩阵,Z^(i)是k维列向量。这样算出来的x^(i)就是n维列向量。

矩阵化数据恢复运算公式为:

其中,Xapprox是还原回来的数据,是一个m*n的矩阵,每行表示一个训练样例。Z是一个m*k的矩阵,是降维后的数据。

PCA算法示例

假设咱们的数据集总共有5个记录,每一个记录有2个特征,这样构成的矩阵A为:

咱们的目标是把二维数据降为一维数据。为了更好地理解PCA的计算过程,分别使用Numpy和sklearn对同一个数据进行PCA降维处理。

使用Numpy模拟PCA计算过程

下面用Numpy来模拟PCA降维的过程。首先对数据进行预处理

import numpy as np

A = np.array([[3, 2000], [2, 3000], [4, 5000], [5, 8000], [1, 2000]], dtype='float')
# 数据归一化
mean = np.mean(A, axis=0)
norm = A - mean
# 数据缩放
scope = np.max(norm, axis=0) - np.min(norm, axis=0)
norm = norm / scope
print(norm)

两个特征的均值不在同一个数量级,同时对数据进行了缩放。输出以下

[[ 0.     -0.33333333]
[-0.25    -0.16666667]
[ 0.25   0.16666667]
[ 0.5     0.66666667]
[-0.5    -0.33333333]]

接着对协方差矩阵进行奇异值分解,求解其特征向量:

U, S, V = np.linalg.svd(np.dot(norm.T, norm))

输出以下

[[-0.67710949 -0.73588229]
[-0.73588229 0.67710949]] 

因为须要把二维数组降为一维,所以只取特征矩阵的第一列来构造出Ureduce

U_reduce = U[:, 0].reshape(2, 1)

输出以下

[[-0.67710949]
[-0.73588229]]

有了主成分特征矩阵,就能够对数据进行降维了

R = np.dot(norm, U_reduce)

其输出以下

[[ 0.2452941 ]
[ 0.29192442]
[-0.29192442]
[-0.82914294]
[ 0.58384884]]

这样就把二维的数据降维成一维的数据了。若是须要还原数据,依照PCA数据恢复的计算公式,可得:

Z = np.dot(R, U_reduce.T)

输出结果以下

[[-0.16609096 -0.18050758]
[-0.19766479 -0.21482201]
[ 0.19766479 0.21482201]
[ 0.56142055 0.6101516 ]
[-0.39532959 -0.42964402]]

在数据预处理阶段对数据进行了归一化,而且作了缩放处理,因此须要进一步还原才能获得原始数据,这一步是数据预处理的逆运算

B = np.multiply(Z, scope) + mean

输出结果以下

[[2.33563616e+00 2.91695452e+03]
[2.20934082e+00 2.71106794e+03]
[3.79065918e+00 5.28893206e+03]
[5.24568220e+00 7.66090960e+03]
[1.41868164e+00 1.42213588e+03]]

其中np.multiply是矩阵的点乘运算,即对应的元素相乘。对矩阵基础不熟悉的读者,能够搜索矩阵点乘和叉乘的区别。

与原始矩阵A相比,恢复后数据仍是存在必定程序的失真,这种失真是不可避免的。e+0.3表示的是10的3次方

点乘:向量的内积

叉乘:向量的外积

例如:点乘的结果是一个实数 a·b=|a|·|b|·cos<a,b <a,b表示a,b的夹角
叉乘:叉乘的结果是一个向量
当向量a和b不平行的时候
其模的大小为 |a×b|=|a|·|b|·sin<a,b (其实是ab所构成的平行四边形的面积) 方向为 a×b和a,b都垂直 且a,b,a×b成右手系。当a和b平行的时候,结果为0向量

使用sklearn进行PCA降维运算

sklearn.decomposition.PCA实现了PCA算法,使用方便,不须要了解具体的PCA运算步骤。

但须要注意的是,数据的预处理须要本身完成,其PCA算法实现自己不进行数据预处理(归一化和缩放)

咱们选择MinMaxScaler类进行数据预处理

import numpy as np
from sklearn.decomposition import PCA
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import MinMaxScaler

def std_PCA(**argv):
    scaler = MinMaxScaler()
    pca = PCA(**argv)
    pipeline = Pipeline([('scaler', scaler), ('pca', pca)])
    return pipeline

A = np.array([[3, 2000], [2, 3000], [4, 5000], [5, 8000], [1, 2000]], dtype='float')
pca = std_PCA(n_components=1)
R2 = pca.fit_transform(A)
print(R2)

Pipeline做用是把数据预处理和PCA算法组成一个串行流水线。输出以下

[[-0.2452941 ]
[-0.29192442]
[ 0.29192442]
[ 0.82914294]
[-0.58384884]]

这个输出值就是矩阵A通过预处理及PCA降维后的数值。

接着把数据恢复回来

R2_R = pca.inverse_transform(R2)

这里的pca是一个Pipeline实例,其逆运算inverse_transform()是逐级进行的,即先进行PCA还原,再执行预处理的逆运算。具体来讲就是先调用PCA.inverse_transform(),而后再调用MinMaxScaler,inverse_transform()

PCA的物理含义

咱们能够把前面的例子在一个坐标轴上所有画出来,从而观察PCA降维过程的物理含义

图中正方形的点是原始数据通过预处理后(归一化、缩放)的数据,圆形的点是从一维恢复到二维后的数据。同时,咱们画出主成分特征向量u^(1),u^(2),根据上图直观印象,介绍几个有意思的结论

首先,圆点实际上就是放点在向量u^(1)所在直线的投射点,所谓的降维,实际上就是方形的点在主成分特征向量u^(1)上的投影。所谓的PCA数据恢复,并非真正的恢复,只是把降维后的坐标转换为原坐标系中的坐标而已。

针对咱们的例子,只是把向量u^(1)决定的一维坐标系中的坐标转换为原始二维坐标系中的坐标。其次,主成分特征向量u^(1),u^(2)是互相垂直的。再次,方形点和圆形点之间的距离,就是PCA数据降维后的偏差。

PCA的数据还原率及应用

PCA算法能够用来对数据进行压缩,能够在可控的失真范围内提升运算速度。

数据还原率 

使用PCA对数据进行压缩时,涉及失真的度量问题,即压缩后的数据能在多大程度上还原原数据,称这一指标为数据还原率,用百分比表示。假设咱们要求失真度不超过1%,即数据还原率达到99%,怎么来实现这个要求呢?

k是主成分分析法中主成分的个数。用下面的公式做为约束条件,从而选择合适的偏差范围下,最合适的k值

其中,分子部分表示平均投射偏差的平方;分母部分表示全部训练样例到原点距离的平均值。这里的物理意义用术语能够描述为99%的数据真实性被保留下来了。简单地理解为压缩后的数据还原出元数据的准确度为99%。

另外,经常使用的比率还有0.05,这个时候数据还原率就是95%。实际应用中,能够根据要解决问题的场景来决定这个比率

假设,咱们的还原率要求是99%,那么用下面的算法来选择参数k

1. 让k=1

2. 运行PCA算法,计算出

3. 利用计算投射偏差率,并判断是否知足要求,若是不知足要求,k=k+1,继续步骤2。若是知足要求,k便是咱们选择的参数

这个算法较容易理解,但实际上效率很是低,由于没作一次循环都须要运行一遍PCA算法。另外一个更高效的方法是,利用协方差矩阵进行奇异值分解返回的S矩阵:[U,S,V]=svd(Σ)。其中S是个nx n对角矩阵,即只有对角线上值非零时其余元素均为0

从数学上能够证实,投射偏差率也能够使用下面的公式计算

这样运算效率大大提升了,只须要进行一次svd运算便可。

加快监督机器学习算法的运算速度

PCA的一个典型应用是用来加快监督学习的速度

例如,有m个训练数据(x^1,y^1),(x^2,y^2),... ,(x^m,y^m)。其中,x^1是10000维的数据,想象一下,若是这是个图片分类问题,若是输入的图片是100*100分辨率的,那么咱们就有10000维的输入数据

使用PCA来加快算法运算速度时,把输入的数据分解出来x^1,x^2,...,x^m,而后运用PCA算法对输入数据进行降维压缩,获得降维后的数据z^1,z^2,...,z^m,最后获得新的训练样例,利用新的训练样例训练出关于压缩后的变量z的预测函数hΘ(z)

须要注意,PCA算法只用来处理训练样例,运算PCA算法获得的转换参数Ureduce能够用来对交叉验证数据集

及测试数据集进行转换。固然,还须要相应地对数据进行归一化处理或对数据进行缩放

实例:人脸识别

使用自拍的一组照片,来开发一个特定的人脸识别系统。人脸识别,本质上是一个分类问题,须要把人脸图片当成训练数据集,对模型进行训练。训练好的模型,就能够对新的人脸照片进行类别预测,这就是人脸识别系统的原理。

加载数据集

数据集总共包含40位人员的照片,每一个人10张照片。datasets/olivetti.pkz

使用下面的代码来加载这些照片

import logging
from sklearn.datasets import fetch_olivetti_faces

logging.basicConfig(level=logging.INFO, format='%(asctime)s %(message)s')
data_home = 'datasets/'
logging.info('Start to load dataset')
faces = fetch_olivetti_faces(data_home=data_home)
logging.info('Done with load dataset')

加载的图片数据集保存在faces变量里,scikit-learn已经替咱们把每张照片作了初步的处理,剪裁成64*64大小且人脸居中显示。这一步相当重要,不然咱们的模型将被大量的噪声数据,即图片背景干扰。由于人脸识别的关键是五官纹理和特征,每张照片的背景都不一样,人的发型也可能常常变化, 这些特征都应该尽可能排队在输入特征以外,最后要成功加载数据集,还须要安装Python的图片处理工具包Pillow

成功加载数据后,其data里保存的就是按照scikit-learn要求的训练数据集,target里保存的就是类别目标索引。经过下面的代码,将数据集的概要信息显示出来

X = faces.data
y = faces.target
targets = np.unique(faces.target)
target_names = np.array(['c%d' % t for t in targets])
n_targets = target_names.shape[0]
n_samples, h, w = faces.images.shape
print('Sample count:{}; Target count:{}'.format(n_samples, n_targets))
print('Images size:{} x {} Dataset shape: {}'.format(w, h, X.shape))

输出内容以下

Sample count:400; Target count:40
Images size:64 x 64 Dataset shape: (400, 4096)

从输出可知,总共有40位人物的照片,图片总数是400张,输入特征由4096。为了后续区分不一样人物,用索引号给目标人物命名,并保存在变量target_names里。为了更直观地观察数据,从每一个人物的照片里随机选择一张显示出来。先定义一个函数来显示照片阵列

def plot_gallery(images, titles, h, w, n_row=2, n_col=5):
    plt.figure(figsize=(2 * n_col, 2.2 * n_row), dpi=144)
    plt.subplots_adjust(bottom=0, left=.01, right=.99, top=.90, hspace=.01)
    for i in range(n_row * n_col):
        plt.subplot(n_row, n_col, i + 1)
        plt.imshow(images[i].reshape((h, w)), cmap=plt.cm.gray)
        plt.title(titles[i])
        plt.axis('off')

输入参数images是一个二维数据,每一行都是一个图片数据。加载数据时,fetch_olivetti_faces()函数已经帮咱们作了预处理,图片的每一个像素的RGB值都转换成了[0,1]的浮点数。所以,咱们画出来的照片将是黑白的,而不是彩色的。在图片识别领域,通常状况下用黑白照片就能够了,能够减小计算量,也会让模型更准确。

接着分红两行显示这些人物的照片:

n_row = 2
n_col = 6
sample_images = None
sample_titles = []
for i in range(n_targets):
    people_images = X[y == i]
    people_sample_index = np.random.randint(0, people_images.shape[0], 1)
    people_sample_image = people_images[people_sample_index, :]
    if sample_images is not None:
        sample_images = np.concatenate((sample_images, people_sample_image), axis=0)
    else:
        sample_images = people_sample_image
    sample_titles.append(target_names[i])

plot_gallery(sample_images, sample_titles, h, w, n_row, n_col)
plt.show()

代码中,X[y==1] 能够选择出属于特定人物的全部照片,随机选择出来的照片都放在sample_images数组对象里,最后使用咱们以前定义的函数plot_gallery()把照片画出来

从图中能够看到,fetch_olivetti_faces()函数帮咱们剪裁了中间部分,只留下脸部特征。

最后把数据集划分红训练数据集和测试数据集

from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=4)

失败的尝试

使用支持向量机SVM来实现人脸识别

from sklearn.svm import SVC

start = time.clock()
print('Fitting train datasets ...')
clf = SVC(class_weight='balanced')
clf.fit(X_train, y_train)
print('Done in {0:.2f}s'.format(time.clock() - start))

指定SVC的class_weight参数,让SVC模型能根据训练样本的数量来均衡地调整权重,这对不均匀的数据集,即目标人物的照片数量相差较大的状况是很是有帮助的。因为总共只有400张照片,数据规模较小,模型运行时间不长

接着,针对测试数据集进行预测

start = time.clock()
print('Predicting test dataset ...')
y_pred = clf.predict(X_test)
print('Done in {0:.2f}s'.format(time.clock() - start))

 最后,分别使用confusion_matrix和classification_report来查看模型分类的准确性

from sklearn.metrics import confusion_matrix

cm = confusion_matrix(y_test, y_pred, labels=range(n_targets))
print('confusion matrix')
np.set_printoptions(threshold=np.nan)
print(cm)

np.set_printoptions()是为了确保完整地输出cm数组的内容,这是由于这个数组是40*40的,默认状况下不会所有输出。

confusion matrix理想的输出,是矩阵的对角线上有数字,其余地方都没有数字。但咱们的结果显示不是这样的。能够明显看出不少图片都被预测成索引为12的类别了。结果看起来彻底不对。再看一下classification_report的结果

from sklearn.metrics import classification_report
print(classification_report(y_test, y_pred, target_names=target_names))

输出结果以下

40个类别离,查准率、召回率、F1 Score全为0,不能有更差的预测结果了。为何这么差

由于,咱们吧每一个像素都做为要给输入特征来处理,这样的数据噪声太严重了,模型根本没有办法对训练数据集进行拟合。总共有4096个特征,但是数据集大小才400个,比特征个数还少,并且咱们还须要吧数据集分出20%来做为测试数据集,这样训练数据集更小了。这样的情况下,模型根本没法进行准确地训练和预测。

使用PCA来处理数据集

解决上述问题的一个办法是使用PCA来给数据降维,只选择前k个最重要的特征。选择多少个特征合适呢?怎么肯定k的值呢?PCA算法能够经过下面的公式来计算失真幅度

在scikit-learn里,能够从PCA模型的explained_variance_ration_变量里获取PCA处理后的数据还原率。这是一个数组,全部元素求和便可知道咱们选择的k值的数据还原率,数值越大说明失真越小,随着k值的增大,数值会无限接近于1.

利用这一特征,可让k取值10~300之间,每隔30进行一次取样。在全部的k值样本下,计算通过PCA算法处理后的数据还原率。而后根据数据还原率要求,来肯定合理的k值。针对咱们的状况,选择失真度小于5%,即PCA处理后能保留95%的原数据信息。

from sklearn.decomposition import PCA

print('Exploring explained variance ratio for dataset ...')
candidate_components = range(10, 300, 30)
explained_ratios = []
start = time.clock()
for c in candidate_components:
    pca = PCA(n_components=c)
    X_pca = pca.fit_transform(X)
    explained_ratios.append(np.sum(pca.explained_variance_ratio_))
print('Done in {0:.2f}s'.format(time.clock() - start))

根据不一样的k值,构建PCA模型,而后调用fit_transform函数来处理数据集,再把模型处理后数据还原率,放入explained_ratios数组。接着吧这个数组画出来

plt.figure(figsize=(10, 6), dpi=144)
plt.grid()
plt.plot(candidate_components, explained_ratios)
plt.xlabel('Number of PCA Components')
plt.ylabel('Explained variance ratio for PCA')
plt.title('Explained variance ratio for PCA')
plt.yticks(np.arange(0.5, 1.05, .05))
plt.xticks(np.arange(0, 300, 20))
plt.show()

图形以下

横坐标表示k值,纵坐标表示数据还原率。要保留95%以上的数据还原率,k值选择140便可。也很是容易地找出不一样的数据还原率对应的k值。为了更直观地观察和对比在不一样数据还原率下的数据,咱们选择数据还原率分别在95%、90%、80%、70%、60%的状况下,画出经PCA处理后的图片,对应的k值分别是140、7五、3七、1九、8

为了方便,这里直接选择在上图里画出的人物前5位做为咱们的样本图片。每行画出5个图片,先画出原图,接着再画出每行在不一样数据还原率下对应的图片

def title_prefix(prefix, title):
    return "{}: {}".format(prefix, title)

n_row, n_col = 1, 5
sample_images = sample_images[0:5]
sample_titles = sample_titles[0:5]
plotting_images = sample_images
plotting_titles = [title_prefix('orig', t) for t in sample_titles]
candidate_components = [140, 75, 37, 19, 8]
for c in candidate_components:
    print('Fitting and projecting on PCA(n_components={}) ...'.format(c))
    start = time.clock()
    pca = PCA(n_components=c)
    pca.fit(X)
    X_sample_pca = pca.transform(sample_images)
    X_sample_inv = pca.inverse_transform(X_sample_pca)
    plotting_images = np.concatenate((plotting_images, X_sample_inv), axis=0)
    sample_title_pca = [title_prefix('{}'.format(c), t) for t in sample_titles]
    plotting_titles = np.concatenate((plotting_titles, sample_title_pca), axis=0)
    print('Done in {0:.2f}s'.format(time.clock() - start))

print('Plotting sample images with different number of PCA conpoments ...')
plot_gallery(plotting_images, plotting_titles, h, w, n_row * (len(candidate_components) + 1), n_col)

代码里,把全部的图片收集金plotting_images数组,而后调用前面定义的plot_gallery()函数一次性地画出来。 

代码里,把全部的图片收集进plotting_images数组,而后调用前面定义的plot_gallery()函数一次性地画出来。

第一行显示的原图,第二行显示的是数据还原度在95%处,即k=140的图片。第三行显示的是数据还原度在90%,即k=90的图片;

即便在k=8时,图片依然能比较清楚地反映出人物的脸部特征轮廓

最终结果

选择k=140做为PCA参数,对训练数据集和测试数据集进行特征提取

n_components = 140
print('Fitting PCA by using training data ...')
start = time.clock()
pca = PCA(n_components=n_components, svd_solver='randomized', whiten=True).fit(X_train)
print('Done in {0:.2f}s'.format(time.clock() - start))
print('Projecting input data for PCA ...')
start = time.clock()
X_train_pca = pca.transform(X_train)
X_test_pca = pca.transform(X_test)
print('Done in {0:.2f}s'.format(time.clock() - start))

接着使用GridSearchCV来选择一个最佳的SVC模型参数,而后使用最佳参数对模型,而后使用最佳参数模型进行训练。

from sklearn.svm import SVC
    from sklearn.model_selection import GridSearchCV

    print('Searching the best parameters for SVC ...')
    param_grid = {'C': [1, 5, 10, 50, 100],
                  'gamma': [0.0001, 0.0005, 0.001, 0.005, 0.01]}
    clf = GridSearchCV(SVC(kernel='rbf', class_weight='balanced'), param_grid, verbose=2, n_jobs=4)
    clf = clf.fit(X_train_pca, y_train)
    print('Best parameters found by grid search:')
    print(clf.best_params_)

经过设置n_jobs=4来启动4个线程并发执行,同时设置verbose=2来输出一些过程信息。最终选择出来的最佳模型参数以下

Best parameters found by grid search:
{'C': 10, 'gamma': 0.001}

接着使用这一模型对测试样本进行预测,而且使用confusion_matrix输出预测准确性信息。

start = time.clock()
    print('Predict test dataset ...')
    y_pred = clf.best_estimator_.predict(X_test_pca)
    cm = confusion_matrix(y_test,y_pred,labels=range(n_targets))
    print('Done in {0:.2f}s'.format(time.clock() - start))
    print('confusion matrix:')
    np.set_printoptions(threshold=np.nan)
    print(cm)

大部分预测结果都正确。再使用classification_report输出分类报告,查看测准率、召回率、F1 Score

print(classification_report(y_test,y_pred,target_names=target_names))

总共有400张图片,每位目标人物只有10张图片的状况下,测准率和召回率平均达到了0.95以上。

k-均值算法

k-均值算法是一种典型的无监督机器学习算法,用来解决聚类问题(Clustering)。因为数据标记须要耗费巨大的资源,无监督或半监督的学习算法嘴来逐渐受到了学者青睐,由于不须要对数据进行标记,能够大大减小工做量。

算法原理

针对监督式学习算法,如k-近邻算法,其输入数据是已经标记的,目标是找出分类边界,而后对新的数据进行分类。而无监督式学习算法,如k-均值算法,值给出一组无标记的数据集x^1,x^2,...,x^m,目标是找出这组数据的模型特征,如那些数据是同一种类型的,那些数据是另一种类型。典型的无监督学习包括市场细分,即经过分析用户数据,把一个产品的市场进行细分, 找出细分人群。另一个是社交网络分析,分析社交网络中参与人员的不一样特色,根据特色区分出不一样群体。这些都是无监督式学习力的聚类(Clustering)问题

k-均值算法包含如下两个步骤:

1. 给聚类中心分配点。计算全部的训练样例,把每一个训练样例分配到距离其最近的聚类中心所在的类别里。

2. 移动聚类中心。新的聚类中心移动到这个聚类全部的点的平均值处

一直重复作上面的动做,直到聚类中心再也不移动为止,这时就探索出了数据集的结构了。

也能够用数学方式来描述k-均值算法。算法有两个输入信息,一是k,表示选取的聚类个数;另外一个是训练数据集x^1,x^2,...,x^m

1. 随机选择k个聚类中心,u1,u2,...,uk

2. 从1~m中遍历全部的数据集,计算x^i分别到u1,u2,...,uk的距离,记录距离最短的聚类中心点uj(1<=j<=k),而后把x^i这个点分配给这个聚类。即令c^i=j。计算距离时,通常使用 ||x^i-uj|| 来计算。

3. 从1~k中遍历全部的聚类中心,移动聚类中心的新位置到这个聚类的均值处。即

4. 重复步骤2,直到聚类中心再也不移动为止

k-均值算法成本函数

根据成本函数的定义,成本即模型预测值与实际值的偏差,据此不可贵出k-均值算法的成本函数:

其中,c^i是训练样例x^i分配的聚类序号;是x^i所属聚类的中心点。k-均值算法的成本函数的物理意义就是,训练样例到其所属的聚类中心点的距离的平均值。

随机初始化聚类中心点

假设k是聚类的个数,m是训练样本的个数,那么一定有k<m。在随机初始化时,随机从m个训练数据集里选择k个样本做为聚类中心点。这是正式推荐的随机初始化聚类中心的作法。

最终聚类结果会和随机初始化的聚类中心点有关。即不一样的随机初始化的聚类中心点可能获得不一样的最终聚类结果。由于成本函数可能会收敛在一个局部最优解,而不是全局最优解上。有一个解决方法就是多作几回随机初始化的动做,而后训练出不一样的聚类中心点和聚类节点分配方案,而后用这些值算出成本函数,从中选择成本最小的那个函数。

假设咱们作1000次运算,步骤以下

1. 随机选择k个聚类中心点

2. 运行k-均值算法,算出c^1,c^2,...,c^m和u1,u2,...,uk

3. 使用c^1,c^2,...,c^m和u1,u2,...,uk算出最终的成本值

4. 记录最小的成本值,而后调回步骤1,直到达到最大运算次数

这样就能够适当加大运算次数,从而求出全局最优解

选择聚类的个数

怎么选择合适的聚类个数呢?实际上聚类个数和业务有紧密的关联。例如咱们要对运动鞋的尺码大小进行聚类分析,那么分红5个尺寸等级好仍是分红10个尺寸等级好呢?这是业务问题

从技术角度来说,也有一些方法能够用来作一些判断的。能够把聚类个数做为横坐标,成本函数做为纵坐标,把成本和聚类个数的数据画出来。大致的趋势是随着k值愈来愈大,成本会愈来愈低。找出一个拐点,在这个拐点以前成本降低比较快,在这个拐点以后,成本降低比较慢,那么颇有可能这个拐点所在的k值就是寻求的最优解。

scikit-learn里的k-均值算法

由sklearn.cluster.KMeans类实现。下面来看一下如何使用

from sklearn.datasets import make_blobs

X, y = make_blobs(n_samples=200, n_features=2, centers=4,
                  cluster_std=1, center_box=(-10.0, 10.0), shuffle=True,
                  random_state=1)

而后把样本画出来

plt.figure(figsize=(6, 4), dpi=144)
plt.xticks(())
plt.yticks(())
plt.scatter(X[:, 0], X[:, 1], s=20, marker='o')
plt.show()

接着使用KMeans模型来拟合。设置类别数量为3,并计算出其拟合后的成本

from sklearn.cluster import KMeans

n_clusters = 3
kmean = KMeans(n_clusters=n_clusters)
kmean.fit(X)
print('kmean:k={},cost={}'.format(n_clusters, int(kmean.score(X))))

输出结果以下

kmean:k=3,cost=-668

KMeans.score()函数计算k-均值算法拟合后的成本,用负数表示,其绝对值越大,说明成本越高。

k-均值算法成本的物理意义为训练样例到其所属的聚类中心点的距离的平均值,在scikit-learn里,其计算成本的方法略有不一样,是计算训练样例到其所属的聚类中心点的距离的总和。

固然,还能够把分类后的样本及所属的聚类中心都画出来,这样能够更直观地观察算法的拟合结果。

labels = kmean.labels_
centers = kmean.cluster_centers_
markers = ['o', '^', '*']
colors = ['r', 'b', 'y']
plt.figure(figsize=(6, 4), dpi=144)
plt.xticks(())
plt.yticks(())
for c in range(n_clusters):
    cluster = X[labels == c]
    plt.scatter(cluster[:, 0], cluster[:, 1], marker=markers[c], s=20, c=colors[c])
plt.scatter(centers[:, 0], centers[:, 1], marker='o', c='white', alpha=0.9, s=300)
for i, c in enumerate(centers):
    plt.scatter(c[0], c[1], marker='$%d$' % i, s=50, c=colors[i])

plt.show()

k-均值算法的一个关键参数是k,即聚类个数。从技术角度来说,k值越大,算法成本越低,这个很容易理解。

但从业务角度来看,不是k值越大越好。针对这个例子,分别选择k2,3,4这三种不一样的聚类个数,来观察一下k-均值算法最终拟合的结果及其成本。

画出K-均值聚类结果的代码稍稍改造一下,变成一个函数。这个函数会使用k-均值算法来进行聚类拟合,同时会画出按照这个聚类个数拟合后的分类状况

from sklearn.cluster import KMeans
def fit_plot_kmean_model(n_clusters, X):
    plt.xticks(())
    plt.yticks(())
    # 使用k-均值算法进行拟合
    kmean = KMeans(n_clusters=n_clusters)
    kmean.fit_predict(X)
    labels = kmean.labels_
    centers = kmean.cluster_centers_
    markers = ['o', '^,', '*', 's']
    colors = ['r', 'b', 'y', 'k']
    # 计算成本
    score = kmean.score(X)
    plt.title('K={},score={}'.format(n_clusters, int(score)))
    # 画样本
    for c in range(n_clusters):
        cluster = X[labels == c]
        plt.scatter(cluster[:, 0], cluster[:, 1], marker=markers[c], s=20, c=colors[c])
    # 画出中心点
    plt.scatter(centers[:, 0], centers[:, 1], marker='o', c='white', alpha=0.9, s=300)
    for i, c in enumerate(centers):
        plt.scatter(c[0], c[1], marker='$%d$' % i, s=50, c=colors[i])

函数接受两个参数,一个是聚类个数,即K的值。另外一个是数据样本。有了这个函数,就能够分别对3中不一样的k值进行聚类分析了,而且把聚类结果可视化

n_cluster = [2, 3, 4]
plt.figure(figsize=(10, 3), dpi=144)
for i, c in enumerate(n_cluster):
    plt.subplot(1, 3, i + 1)
    fit_plot_kmean_model(c, X)

使用k-均值对文档进行聚类分析

假设有一个博客平台,用户在平台上发布博客,如何对博客进行聚类分析,以方便展现不一样类别下的热门文章呢?

准备数据集 

为了简化问题,避免进行中文分词,仍是使用PCA的语料库。

只选择语料库里的部份内容来进行聚类分析。假设选择sci.crypt、sci.electronics、sci.med、sci.space4个类别的文档进行聚类分析。咱们复制这4个文件夹的内容。

加载数据集

准备好数据集有,把目录下的文档进行聚类分析。

首先导入数据

from time import time
from sklearn.datasets import load_files

print('loading document ...')
t = time()
docs = load_files('clustering/data')
print('summary:{0} documents in {1} categories.'
      .format(len(docs.data), len(docs.target_names)))
print('done in {0} seconds'.format(time() - t))

输出内容以下

loading document ...
summary:3949 documents in 4 categories.
done in 0.28702831268310547 seconds

总共有3949篇文档,人工标记在4个类别里。接着把文档转化为TF-IDF向量

from sklearn.feature_extraction.text import TfidfVectorizer

max_features = 20000
print('vectorizing documents ...')
t = time()
vectorizer = TfidfVectorizer(max_df=0.4, min_df=2,
                             max_features=max_features, encoding='latin-1')
X = vectorizer.fit_transform((d for d in docs.data))
print('n_samples:%d,n_features:%d' % X.shape)
print('number of non-zero features in sample [{0}]:{1}'
      .format(docs.filenames[0], X[0].getnnz()))
print('done in {0} seconds'.format(time() - t))

须要注意TfidfVectorizer的几个参数的选择,其中max_df=0.4表示若是一个单词在40%的文档里都出现过,则认为这是一个高频词,对文档聚类没有帮助,在生成词典时就会剔除这个词。min_df=2表示,若是一个单词的词频过低,只在两个一下(包含两个)的文档里吃醋县,则也把这个单词从词典里剔除。max_features能够进一步过滤词典的大小,会根据TF-IDF权重从高到低进行排序,而后去前面权重高的单词构成词典

vectorizing documents ...
n_samples:3949,n_features:20000
number of non-zero features in sample [clustering/data\sci.electronics\11902-54322]:56
done in 1.5791575908660889 seconds

一篇文章构成的向量是一个稀疏向量,其大部分元素都为0。词典大小为20000个,而实例文章中不重复的单词却只有56个

文本聚类分析

下面使用KMeans算法对文档进行聚类分析

from sklearn.cluster import KMeans

print('clustering documents ...')
t = time()
n_clusters = 4
kmean = KMeans(n_clusters=n_clusters, max_iter=100, tol=0.01, verbose=1, n_init=3)
kmean.fit(X)
print('kmean:k={},cost={}'.format(n_clusters,int(kmean.inertia_)))

选择的聚类个数为4,max_iter=100表示最多进行100次k-均值迭代。tol=0.1表示中心点移动距离小于0.1时就认为算法已经收敛,中止迭代。verbose=1表示输出迭代的过程信息。n_init=3表示进行3次k-均值运算后求平均值,在算法刚开始迭代时,会随机选择聚类中心点,不一样的中心点可能致使不一样的收敛效果,所以屡次运算求平均值的方法能够提供算法的稳定性。

clustering documents ...
Initialization complete
Iteration 0, inertia 7599.134
Converged at iteration 43: center shift 0.000000e+00 within tolerance 4.896692e-07
Initialization complete
Iteration 0, inertia 7520.830
Iteration 14, inertia 3822.959
Converged at iteration 14: center shift 0.000000e+00 within tolerance 4.896692e-07
Initialization complete
Iteration 0, inertia 7542.723
Iteration 17, inertia 3817.601
Converged at iteration 17: center shift 0.000000e+00 within tolerance 4.896692e-07
kmean:k=4,cost=3817

输出信息能够看到,总共进行了3次k-均值聚类分析,分别做了195424次迭代后收敛。这样就把3949个文档自动分类了。kmean.labels_里保存的就是这些文档的类别信息。len(kmean.labels_)的值是3949,还能够经过kmean.labels_[:100]查看前100个文档的分类状况。

print(kmean.labels_[:10])

还能够查看文档对应的文件名

print(docs.filenames[:10])

聚类分析过程当中,那些单词的权重最高,从而较容易地决定一个文章的类别?

能够查看每种类别文档中,其权限最高的10个单词分别是什么

from __future__ import print_function
print('Top terms per cluster:')
order_centroids = kmean.cluster_centers_.argsort()[:,:: -1]
terms = vectorizer.get_feature_names()
for i in range(n_clusters):
    print('Cluster %d:' % i,end='')
    for ind in order_centroids[i,:10]:
        print(' %s ' % terms[ind],end='')
    print()

关键在于argsort函数,做用是把一个Numpy数组进行升序排列,返回的是排序后的索引。

因为kmean.cluster_centers是一个二维数组,所以[:,:: -1]语句的含义就是把聚类中心点的不一样份量,按照从大到小的顺序进行排序,而且把排序后的元素索引保存在二维数组order_centroids里。

vectorizer.get_feature_names()将获得咱们的词典单词,根据索引便可获得每一个类别里权重最高的那些单词了

聚类算法性能评估 

聚类性能评估比较复杂。针对分类问题,能够直接计算被错误分类的样本数量,这样能够直接算出分类算法的准确率。聚类问题不能使用绝对数量的方法进行性能评估,直接的缘由是,聚类分析后的类别与原标记的类别之间不存在必然的一一对应关系。针对k-均值算法,能够选择k的数值不等于已标记的类别个数。

信息论是最重要的基础概念。熵表示一个系统的有序程度,而聚类问题的性能评估,就是对比通过聚类算法处理后的数据的有序程度,与人工标记的累呗的有序程度之间的差别。

Adjust Rand Index

一种衡量两个序列类似性的算法。优势是,针对两个随机序列,它的值为负数或接近于0,而针对两个结构相同的序列,它的值接近于1.并且对类别标签不敏感。

from sklearn import metrics

label_true = np.random.randint(1, 4, 6)
label_pred = np.random.randint(1, 4, 6)
print('Adjusted Rand-Index for random sample: %.3f'
      % metrics.adjusted_rand_score(label_true, label_pred))
label_true = [1, 1, 3, 3, 2, 2]
label_pred = [3, 3, 2, 2, 1, 1]
print('Adjusted Rand-Index for same structure sample:%.3f'
      % metrics.adjusted_rand_score(label_true, label_pred))

输出内容以下

Adjusted Rand-Index for random sample: -0.176
Adjusted Rand-Index for same structure sample:1.000

齐次性和完整性

根据条件熵分析,能够获得另外两个衡量聚类算法性能的指标,分别是齐次性(homogeneity)和完整性(completeness)。齐次性表示一个聚类元素只由一个钟类别的元素组成。完整性表示给定的已标记的类别,所有分配到一个聚类里。值均介于[0,1]之间。

label_true = [1, 1, 2, 2]
label_pred = [2, 2, 1, 1]
print('Homogeneity score for same structure sample: %.3f'
      % metrics.homogeneity_score(label_true, label_pred))
label_true = [1, 1, 2, 2]
label_pred = [0, 1, 2, 3]
print('Homogeneity score for each clulster come from only one class: %.3f'
      % metrics.homogeneity_score(label_true, label_pred))
label_true = [1, 1, 2, 2]
label_pred = [0, 1, 2, 3]
print('Homogeneity score for each cluster come from two class : %.3f'
      % metrics.homogeneity_score(label_true, label_pred))
label_true = np.random.randint(1,4,6)
label_pred = np.random.randint(1,4,6)
print('Homogeneity score for each cluster come from two class : %.3f'
      % metrics.homogeneity_score(label_true, label_pred))

输出以下

Homogeneity score for same structure sample: 1.000
Homogeneity score for each clulster come from only one class: 1.000
Homogeneity score for each cluster come from two class : 1.000
Homogeneity score for each cluster come from two class : 0.159

针对第一组序列,其结构相同,所以齐次性输出1.

可是第二组样本为何也是输出1呢?

齐次性的定义,聚类元素只由一种已标记的类别元素组成时,其值为1。

例子中,已标记为2个类别,而输出了4个聚类,这样就知足每一个聚类元素均来自一种已标记的类别元素这一条件。针对第三组样本,因为每一个聚类元素都来自2个类别的元素,所以其值为0;而针对随机元素序列,不为0,这是与Adjust Rand Index不一样的地方。

接着看一组完整性的例子

label_true = [1, 1, 2, 2]
label_pred = [2, 2, 1, 1]
print('Completeness score for same structure sample: %.3f'
      % metrics.completeness_score(label_true, label_pred))
label_true = [1, 1, 2, 2]
label_pred = [0, 1, 2, 3]
print('Completeness score for each class assign to only one cluster: %.3f'
      % metrics.completeness_score(label_true, label_pred))
label_true = [1, 1, 2, 2]
label_pred = [1, 2, 1, 2]
print('Completeness score for each class assign to two class : %.3f'
      % metrics.completeness_score(label_true, label_pred))
label_true = np.random.randint(1,4,6)
label_pred = np.random.randint(1,4,6)
print('Completeness score for random sample : %.3f'
      % metrics.completeness_score(label_true, label_pred))

输出以下

Completeness score for same structure sample: 1.000
Completeness score for each class assign to only one cluster: 0.500
Completeness score for each class assign to two class : 0.000
Completeness score for random sample : 0.457

针对第一组序列,其结构相同,输出为1。针对第二组序列,因为符合完整性定义,即每一个类别的元素都被分配进了同一个聚类里,所以其完整性也为1.针对第三组序列,每一个类别的元素都被分配进两个不一样的聚类里,所以其完整性为0.和齐次性同样,对随机类别的判断能力也比较弱

齐次性和完整性是一组互补的关系,能够把两个指标综合起来,称为V-measure分数

label_true = [1, 1, 2, 2]
label_pred = [2, 2, 1, 1]
print('V-measure score for same structure sample: %.3f'
      % metrics.v_measure_score(label_true, label_pred))
label_true = [0, 1, 2, 3]
label_pred = [1, 1, 2, 2]
print('V-measure score for each class assign to only one cluster: %.3f'
      % metrics.v_measure_score(label_true, label_pred))
label_true = [1, 1, 2, 2]
label_pred = [1, 2, 1, 2]
print('V-measure score for each class assign to two class : %.3f'
      % metrics.v_measure_score(label_true, label_pred))

输出内容以下

V-measure score for same structure sample: 1.000
V-measure score for each class assign to only one cluster: 0.667
V-measure score for each class assign to two class : 0.000

针对第一组序列,其结构相同,V-measure输出的值也为1,表示同时知足齐次性和完整性。第二行和第三行的输出,代表V-measure符合对称性法则。

轮廓系数

聚类性能评估方法都须要有已标记的类别数据,这个很难作到。若是已经标记了数据,就会直接用有监督的学习算法,而无监督学习算法的最大优势就是不须要对数据集进行标记。轮廓系数能够在不须要已标记的数据集的前提下,对聚类算法的性能进行评估。

轮廓系数由如下两个指标构成

a:一个样本与其所在相同聚类的平均距离

b:一个样本与其距离最近的下一个聚类里的点的平均距离

则针对这个样本,其轮廓系数S的值为:

针对一个数据集,其轮廓系数s为其全部样本的轮廓系数的平均值。轮廓系数的数值介于[-1,1]之间,-1表示彻底错误的聚类,1表示完美的聚类,0表示聚类重叠

针对前面的例子,能够分别计算本节介绍的几个聚类算法性能评估指标,综合来看聚类算法的性能

labels = docs.target
print('Homogeneity:%0.3f' % metrics.homogeneity_score(labels, kmean.labels_))
print('Completeness:%0.3f' % metrics.completeness_score(labels, kmean.labels_))
print('V-measure:%0.3f' % metrics.v_measure_score(labels, kmean.labels_))
print('Adjusted Rand-Index:%.3f' % metrics.adjusted_rand_score(labels, kmean.labels_))
print('Silhouette Coefficient: %0.3f'
      % metrics.silhouette_score(X, kmean.labels_, sample_size=1000))

输出内容以下

Homogeneity:0.389
Completeness:0.547
V-measure:0.455
Adjusted Rand-Index:0.253
Silhouette Coefficient: 0.005

 

#

相关文章
相关标签/搜索