导读:机器学习是一门复杂的交叉学科。针对普通程序员来讲,里面有太多的数学概念,我尝试机器学习断断续续约半年,公式和概念这两只拦路虎多次挡道,再加上自己数学太渣,毫无悬念的多次败下阵来。
最近,我买了几门机器学习的视频课程,硬着头皮从概率、微积分、统计分析、矩阵、凸优化开始看起,逐渐有了点儿感觉,虽有很多还是看不懂,但不至于看睡着。然后结合赵志勇的《Python机器学习算法》这本书,渐渐的也能上手了。如果你不是算法工程师,那就选择看视频了解概念,在写代码中使用成熟的机器学习算法包。
本文翻译自scikit-learn v0.19.0的tutorial,希望能让不熟悉机器学习的同学在代码层面上有个直观的了解。
虽然scikit-learn帮我们封装了很多算法,可以黑盒调用,但是作为一个程序员,一定要清楚,会调包和只会调包是有很大的区别。
scikit-learn是Python(>= 2.7 or >= 3.3)中的机器学习包(库),其构建在NumPy(>= 1.8.2), SciPy(>= 0.13.3)和 matplotlib之上。采用BSD授权,可放心的用在商业应用中。
- 进行数据挖掘和数据分析的简单而高效的工具
- 任何人都可使用,可在多种场景/应用上下文中重复使用
- 基于NumPy,SciPy和matplotlib构建
- 开放源代码,可用于商业用途-采用BSD协议
使用pip install -U scikit-learn
命令在线安装scikit-learn包。
安装完毕后,执行以下示例,验证安装是否正确。
from sklearn import datasets
import matplotlib.pyplot as plt
#加载数字数据集
digits = datasets.load_digits()
#展示第一个数字
plt.figure(1, figsize=(3, 3))
plt.imshow(digits.images[-1], cmap=plt.cm.gray_r, interpolation='nearest')
plt.show()
在Windows 10上安装完成后,sklearn版本显示为0.0,原因未知,但是能正常使用。
本教程使用版本:v0.19.0
原文:scikit-learn tutorial
本文参考了Tacey Wong在cnblogs上的译文,针对最新版本做了调整,文中代码全部经过验证。
该章节,我们将介绍贯穿scikit-learn使用中的“ 机器学习(Machine Learning) ”这个词汇,并给出一些简单的学习示例。
0. 前言
机器学习的几个关键主题有:分类、回归、聚类、降维和预处理。
0.1 分类
分类算法,在实际工作中有很多中应用场景,如
- 客户标签:描述客户的风险等级程度,高、中或低
- 欺诈评级:描述一笔交易的欺诈可能性,高、中或低
目的:识别一个对象属于那一种类别
应用:垃圾邮件检测,图像识别
算法:SVM(支持向量机),KNN(K近邻),随机森林
0.2 回归
目的:预测与某个对象相关联的连续值属性
应用:药物反应,股票价格
算法:线性回归,SVR(支持向量回归),ridge regression(岭回归),LASSO回归
0.3 聚类
目的:将相似的对象自动聚集到不同的集合中
应用:顾客细分,分组试验结果
算法:K-Means,谱聚类,mean-shift中值移动
0.4 降维
降维打击:请给我一片二向箔,清理用。
目的:降低随机变量的数目
应用:可视化,提高效率
算法:PCA(主成分分析),特征选取,非负矩阵分解
0.5 预处理
目的:特征提取和正则化
应用:转换数据以便机器学习算法使用
模块:预处理,特征提取
1. 机器学习:问题设定
通常,一个学习问题是通过分析一些数据样本来尝试预测未知数据的属性。如果每一个样本不仅仅是一个单独的数字,比如一个多维的实例(multivariate data),也就是说有着多个属性特征。
我们可以把学习问题分成如下的几个大类:
-
有监督学习,数据带有我们要预测的属性。这种问题主要有如下几种:
- 分类: 样例属于两类或多类,我们想要从已经带有标签(分类结果,如好/坏)的数据中学习以预测未带标签的数据。识别手写数字就是一个分类问题,这个问题的主要目标就是把每一个输出指派到一个有限的类别中的一类。另一种思路去思考分类问题,其实分类问题是有监督学习中的离散形式问题。每一个都有一个有限的分类。对于样例提供的多个标签,我们要做的就是把未知类别的数据划分到其中的一种。
- 回归:如果预期的输出包含连续的变量,那么这样的任务叫做回归。根据三文鱼的年龄和重量预测其长度就是一个回归的例子。
无监督学习,针对那些训练数据只包含输入向量x但是不带有目标值的场景,机器学习的目标就是根据数据发现样本中相似的群组--聚类,或者是在输入空间中判定数据的分布--密度估计,或者把数据从高维空间转换到低维空间以用于可视化。
训练集和测试集:机器学习是学习一些数据集的特征属性并将其应用于新的数据集。这就是为什么在机器学习过程中用来评估算法时,一般会把手头的数据分成两部分。一部分我们称之为训练集,用以学习数据的特征属性。另外一部分我们称之为测试集,用以检验学习到的特征属性。
2. 加载样本数据集
scikit-learn本身带有一些标准数据集。比如用来分类的iris(鸢尾花)数据集、digits(手写数字图像)数据集;用来回归的boston house price(波士顿房屋价格) 数据集。
接下来,我们我们从shell开启一个Python解释器并加载iris和digits两个数据集。本教程中的符号约定:$
表示操作系统(Linux)提示符,>>>
表示Python解释器提示符。
$ python
>>> from sklearn import datasets #从sklearn包中加载数据集模块
>>> iris = datasets.load_iris() #加载鸢尾花数据集
>>> digits = datasets.load_digits() #加载数字图像数据集
一个数据集是一个包含数据所有元数据的类字典对象。这个数据存储(X)在 '.data'成员变量中,是一个n_samples
乘n_features
的数组,行表示样例,列表示特征。在有监督学习问题中,一个或多个响应变量(Y)存储在‘.target’成员变量中。不同数据集的更多细节可以在sklearn文档的专属章节中找到。
例如,对于digits数据集,digits.data
可以访问用来对数字进行分类的特征:
>>> print(digits.data)
[[ 0. 0. 5. ..., 0. 0. 0.]
[ 0. 0. 0. ..., 10. 0. 0.]
[ 0. 0. 0. ..., 16. 9. 0.]
...,
[ 0. 0. 1. ..., 6. 0. 0.]
[ 0. 0. 2. ..., 12. 0. 0.]
[ 0. 0. 10. ..., 12. 1. 0.]]
digits.target
就是数字数据集中各样例对应的真实数字值。也就是我们的程序要学习的。
>>> digits.target
array([0, 1, 2, ..., 8, 9, 8])
数据数组的形状
尽管原始数据也许有不同的形状,但实际使用的数据通常是一个二维数组(n个样例,n个特征)。对于digits数据集,每一个原始的样例是一张(8 x 8)的图片,使用以下的数组进行存取:digits.images[0] array([[ 0., 0., 5., 13., 9., 1., 0., 0.], [ 0., 0., 13., 15., 10., 15., 5., 0.], [ 0., 3., 15., 2., 0., 11., 8., 0.], [ 0., 4., 12., 0., 0., 8., 8., 0.], [ 0., 5., 8., 0., 0., 9., 8., 0.], [ 0., 4., 11., 0., 1., 12., 7., 0.], [ 0., 2., 14., 5., 10., 12., 0., 0.], [ 0., 0., 6., 13., 10., 0., 0., 0.]])
3. 学习和预测
对于数字数据集(digits dataset),任务是预测一张图片中的数字是什么。数字数据集提供了0-9每一个数字的可能样例,在此之上,我们选择一个算法(estimator)对其进行拟合分类。
在scikit-learn中,用以分类的拟合(评估)函数是一个Python对象,具体有fit(X,y)和predic(T)两种成员方法。
其中一个拟合(评估)样例是sklearn.svmSVC类,它实现了支持向量分类(SVC)。一个拟合(评估)函数的构造函数需要模型的参数,但是时间问题,我们将会把这个拟合(评估)函数作为一个黑箱:
>>> from sklearn import svm
>>> clf = svm.SVC(gamma=0.001, C=100.)
选择模型参数
在这个例子中,我们手动设定γ值。通常也可以使用网格搜索(grid search)
和交叉验证(cross validation)
等工具,自动找到较合适的参数值。
我们调用拟合(估测)实例clf作为我们的分类器。它现在必须要拟合模型,也就是说,它必须要学习模型。这可以通过把我们的训练集传递给fit方法。作为训练集,我们使用除最后一组的所有图像。我们可以通过Python的分片语法[:-1]
来选取训练集,这个操作将产生一个新数组,这个数组包含digits.data中除最后一组数据的所有实例。
>>> clf.fit(digits.data[:-1], digits.target[:-1])
SVC(C=100.0, cache_size=200, class_weight=None, coef0=0.0,
decision_function_shape='ovr', degree=3, gamma=0.001, kernel='rbf',
max_iter=-1, probability=False, random_state=None, shrinking=True,
tol=0.001, verbose=False)
现在你就可以预测新的数值了。我们可以让这个分类器预测没有作为训练数据使用的最后一张图像是什么数字。
>>> clf.predict(digits.data[-1:])
array([8])
相应的图片如下图:
正如你所看到的,这是一个很有挑战的任务:这张图片的分辨率很低。你同意分类器给出的答案吗?
这个分类问题的完整示例在这里:识别手写数字,你可以学习并运行它。
4. 模型持久化
可以使用Python的自带模块——pickle来保存scikit中的模型:
>>> from sklearn import svm
>>> from sklearn import datasets
>>> clf = svm.SVC()
>>> iris = datasets.load_iris()
>>> X, y = iris.data, iris.target
>>> clf.fit(X, y)
SVC(C=1.0, cache_size=200, class_weight=None, coef0=0.0,
decision_function_shape='ovr', degree=3, gamma='auto', kernel='rbf',
max_iter=-1, probability=False, random_state=None, shrinking=True,
tol=0.001, verbose=False)
>>> import pickle
>>> s = pickle.dumps(clf)
>>> clf2 = pickle.loads(s)
>>> clf2.predict(X[0:1])
array([0])
>>> y[0]
0
对于scikit,也许使用joblib的pickle替代--(joblib.dump&joblib.load)更有趣。因为它在处理大数据时更高效。但是遗憾的是它只能把数据持久化到硬盘而不内存。
>>> from sklearn.externals import joblib
>>> joblib.dump(clf, 'filename.pkl')
之后,你就可以重新加载这个持久化后的模型(也能在另一个Python进程中使用),如下:
>>> clf = joblib.load('filename.pkl')
注意:
joblib.dump和 joblib.load函数除了可以接受文件名参数外,也可接受文件类的对象。更多有关Joblib持久化数据的信息,请参考这里。
可以返回一个文件名的列表,每一个numpy数组元素包含一个clf在文件系统上的名字,在用joblib.load加载的时候所有的文件需要在相同的文件夹下
注意:pickle有一些安全性和可维护性方面的问题。更多信息,请参考Model persistent ,以获得在scikit-learn中模型持久化的更细节的信息。
5. 惯例约定
scikit-learn的各种拟合(评估)函数遵循一些确定的规则以使得他们的用法能够更加统一。
5.1 类型转换
除非明确指定,输入将被转换为float64
>>> import numpy as np
>>> from sklearn import random_projection
>>> rng = np.random.RandomState(0)
>>> X = rng.rand(10, 2000)
>>> X = np.array(X, dtype='float32')
>>> X.dtype
dtype('float32')
>>> transformer = random_projection.GaussianRandomProjection()
>>> X_new = transformer.fit_transform(X)
>>> X_new.dtype
dtype('float64')
在这个例子中,X是float32,被fit_transform(X)转换成float64。
回归的结果被转换成float64,分类结果维持不变:
>>> from sklearn import datasets
>>> from sklearn.svm import SVC
>>> iris = datasets.load_iris()
>>> clf = SVC()
>>> clf.fit(iris.data, iris.target)
SVC(C=1.0, cache_size=200, class_weight=None, coef0=0.0,
decision_function_shape='ovr', degree=3, gamma='auto', kernel='rbf',
max_iter=-1, probability=False, random_state=None, shrinking=True,
tol=0.001, verbose=False)
>>> list(clf.predict(iris.data[:3]))
[0, 0, 0]
>>> clf.fit(iris.data, iris.target_names[iris.target])
SVC(C=1.0, cache_size=200, class_weight=None, coef0=0.0,
decision_function_shape='ovr', degree=3, gamma='auto', kernel='rbf',
max_iter=-1, probability=False, random_state=None, shrinking=True,
tol=0.001, verbose=False)
>>> list(clf.predict(iris.data[:3]))
['setosa', 'setosa', 'setosa']
这里的第一个predict()返回一个整数数组,是因为iris.target(一个整数数组)被用于拟合。第二个predict()返回一个字符串数组,因为iris.target_names被用于拟合。
5.2 重拟合和更新参数
一个拟合(评估)函数的混合参数(超参数)能够在通过sklearn.pipeline.Pipeline.set_params方法构造之后被更新。多次调用fit()能够覆写之前fit()学习的内容:
>>> import numpy as np
>>> from sklearn.svm import SVC
>>> rng = np.random.RandomState(0)
>>> X = rng.rand(100, 10)
>>> y = rng.binomial(1, 0.5, 100)
>>> X_test = rng.rand(5, 10)
>>> clf = SVC()
>>> clf.set_params(kernel='linear').fit(X, y)
SVC(C=1.0, cache_size=200, class_weight=None, coef0=0.0,
decision_function_shape='ovr', degree=3, gamma='auto', kernel='linear',
max_iter=-1, probability=False, random_state=None, shrinking=True,
tol=0.001, verbose=False)
>>> clf.predict(X_test)
array([1, 0, 1, 1, 0])
>>> clf.set_params(kernel='rbf').fit(X, y)
SVC(C=1.0, cache_size=200, class_weight=None, coef0=0.0,
decision_function_shape='ovr', degree=3, gamma='auto', kernel='rbf',
max_iter=-1, probability=False, random_state=None, shrinking=True,
tol=0.001, verbose=False)
>>> clf.predict(X_test)
array([0, 0, 0, 1, 0])
这里的拟合函数用SVC()构造之后,默认内核rbf
被设置成'linear'拟合后做了第一次预测,后来又改回rbf
去重拟合做第二次的预测。
5.3 Multiclass与multilabel拟合
当我们使用multiclass分类器时,学习和预测任务的执行就取决于拟合的目标数据格式:
>>> from sklearn.svm import SVC
>>> from sklearn.multiclass import OneVsRestClassifier
>>> from sklearn.preprocessing import LabelBinarizer
>>> X = [[1, 2], [2, 4], [4, 5], [3, 2], [3, 1]]
>>> y = [0, 0, 1, 1, 2]
>>> classif = OneVsRestClassifier(estimator=SVC(random_state=0))
>>> classif.fit(X, y).predict(X)
array([0, 0, 1, 1, 2])
在上述情况下,分类器在一个适合multiclass标签的一维数组上进行拟合,
因此predict()方法提供multiclass相应的分类预测。
它还可以拟合二维二元标签指标(binary label indicators)数组:
>>> y = LabelBinarizer().fit_transform(y)
>>> classif.fit(X, y).predict(X)
array([[1, 0, 0],
[1, 0, 0],
[0, 1, 0],
[0, 0, 0],
[0, 0, 0]])
这里,分类器使用LabelBinarizer在二元标签(binary label,表示为y)上拟合。所以相应的预测也返回multilabel二维数组。
注意,第4和5个元素返回的都是0,表明它们与三个标签中的任何一个都不匹配。用多标签输出时,对一个元素,有可能被打上多个标签:
>>> from sklearn.preprocessing import MultiLabelBinarizer
>>> y = [[0, 1], [0, 2], [1, 3], [0, 2, 3], [2, 4]]
>>> y = MultiLabelBinarizer().fit_transform(y)
>>> classif.fit(X, y).predict(X)
array([[1, 1, 0, 0, 0],
[1, 0, 1, 0, 0],
[0, 1, 0, 1, 0],
[1, 0, 1, 1, 0],
[0, 0, 1, 0, 1]])
这种情况下,分类器拟合后,为每个元素都打上了多个标签。MultiLabelBinarizer被用来二分拟合多标签的二维数组。结果就是predict() 方法为返回的二维数组中的每个元素打上了多个标签。
6. 示例
一个展示怎样用scikit-learn识别手写数字的样例。
6.1 绘制数字
from sklearn import datasets
import matplotlib.pyplot as plt
#加载数字数据集
digits = datasets.load_digits()
#展示第一个数字
plt.figure(1, figsize=(3, 3))
plt.imshow(digits.images[-1], cmap=plt.cm.gray_r, interpolation='nearest')
plt.show()
6.2 识别手写数字
以下是sklearn官方示例中的代码,基于自带的数据,使用支持向量分类器(svm.SVC(gamma=0.001))学习后进行预测。
# coding:UTF-8
print(__doc__)
# Author: Gael Varoquaux <gael dot varoquaux at normalesup dot org>
# License: BSD 3 clause
# Standard scientific Python imports
import matplotlib.pyplot as plt
# Import datasets, classifiers and performance metrics
from sklearn import datasets, svm, metrics
# The digits dataset
digits = datasets.load_digits()
# The data that we are interested in is made of 8x8 images of digits, let's
# have a look at the first 4 images, stored in the `images` attribute of the
# dataset. If we were working from image files, we could load them using
# matplotlib.pyplot.imread. Note that each image must have the same size. For these
# images, we know which digit they represent: it is given in the 'target' of
# the dataset.
images_and_labels = list(zip(digits.images, digits.target))
for index, (image, label) in enumerate(images_and_labels[:4]):
plt.subplot(2, 4, index + 1)
plt.axis('off')
plt.imshow(image, cmap=plt.cm.gray_r, interpolation='nearest')
plt.title('Training: %i' % label)
# To apply a classifier on this data, we need to flatten the image, to
# turn the data in a (samples, feature) matrix:
n_samples = len(digits.images)
data = digits.images.reshape((n_samples, -1))
# Create a classifier: a support vector classifier
classifier = svm.SVC(gamma=0.001)
# We learn the digits on the first half of the digits
classifier.fit(data[:n_samples // 2], digits.target[:n_samples // 2])
# Now predict the value of the digit on the second half:
expected = digits.target[n_samples // 2:]
predicted = classifier.predict(data[n_samples // 2:])
print("Classification report for classifier %s:\n%s\n"
% (classifier, metrics.classification_report(expected, predicted)))
print("Confusion matrix:\n%s" % metrics.confusion_matrix(expected, predicted))
images_and_predictions = list(zip(digits.images[n_samples // 2:], predicted))
for index, (image, prediction) in enumerate(images_and_predictions[:4]):
plt.subplot(2, 4, index + 5)
plt.axis('off')
plt.imshow(image, cmap=plt.cm.gray_r, interpolation='nearest')
plt.title('Prediction: %i' % prediction)
plt.show()
以上代码保存到python源文件中,在PyCharm中执行,结果如下: