这个分析笔记由Jake Vanderplas编辑汇总。 源代码和license文件在GitHub。 中文翻译由派兰数据在派兰大数据分析平台上完成。 源代码在GitHub上。
现在我们将介绍机器学习的基本原理,以及怎么通过 Scikit-Learn 的API去实现这些算法。
在简单的介绍scikit-learn的Estimator对象后,我们将介绍监督学习,包括分类问题和回归问题。我们还将介绍无监督学习,包括降维和聚类问题。
In [1]:
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
# 设置 seaborn 绘图的默认参数
# 可以安全地注释掉(即不用seaborn)
import seaborn; seaborn.set()
In [2]:
from sklearn.linear_model import LinearRegression
Estimator 参数:一个 Estimator 对象的所有系数可以在它初始化的时候设置,这些系数拥有普适性的初始值。
In [3]:
model = LinearRegression(normalize=True)
print(model.normalize)
In [4]:
print(model)
Estimator 模型参数:当Estimator用数据来拟合系数时,模型的系数是根据数据一步步得到的。所有的模型系数都是Estimator对象的属性,并且以一个下划线(underscore)结尾。
In [5]:
x = np.arange(10)
y = 2 * x + 1
In [6]:
print(x)
print(y)
In [7]:
plt.plot(x, y, 'o');
In [8]:
# sklearn 的输入数据要求是2维的: (samples == 10 x features == 1)
X = x[:, np.newaxis]
print(X)
print(y)
In [9]:
# 用数据来拟合系数(fit the model)
model.fit(X, y)
Out[9]:
In [10]:
# 以下划线结尾,代表了一个拟合的系数
print(model.coef_)
print(model.intercept_)
In [11]:
# 剩余误差(残差)
model.residues_
Out[11]:
这个模型按照我们所预期的,找出了一条斜率为2,截距为1的线。
在监督学习中,我们会有一个同时包含特征(feature)和标签(label)的数据集。我们的任务是,对于给定的对象特征,去建立一个Estimator预测对该对象的标签。一个相对简单的例子是,我们给出对于iris这种花的测量,去预测iris花的具体品种。这是一个相对简单的任务,更多的较为复杂的任务见下方:
这些任务的共同特点就是,我们需要利用事物观测到的各种特征,去预测一个或多个未知的,与这个事物有关的其他特征。
进一步的,监督学习可以被划分成两个大类,即分类(classification)和回归(regression)。在分类问题中,对象的标签是离散的,在回归问题中,对象的标签是连续的。举个例子,在天文学中,判断一个物体是一个恒星,一个类星体或者是一个星系是一个分类的问题:这个三种标签来自于三个独立的类别。另一方面,我们可能希望去根据这些观测,预测物体的年龄,这就是一个回归的问题了,因为这个标签(年龄)是一个连续的数字。
In [12]:
from sklearn import neighbors, datasets
iris = datasets.load_iris()
X, y = iris.data, iris.target
# 建立模型
knn = neighbors.KNeighborsClassifier(n_neighbors=5)
# 训练模型 (fit the model)
knn.fit(X, y)
# 一个 3cm x 5cm 萼片 and 4cm x 2cm 花瓣 的 iris 花,属于哪一个分类?
# 调用"predict"方法:
result = knn.predict([[3, 5, 4, 2],])
print(iris.target_names[result])
您还可以去做分类概率预测:
In [13]:
knn.predict_proba([[3, 5, 4, 2],])
Out[13]:
In [14]:
from fig_code import plot_iris_knn
plot_iris_knn()
In [15]:
from sklearn.svm import SVC
In [16]:
# 创建一些简单的数据
import numpy as np
np.random.seed(0)
X = np.random.random(size=(20, 1))
y = 3 * X.squeeze() + 2 + np.random.randn(20)
plt.plot(X.squeeze(), y, 'o');
像上面说的一样,我们还画出一条最佳的拟合曲线:
In [17]:
model = LinearRegression()
model.fit(X, y)
# 画出数据和模型预测之间的图形
X_fit = np.linspace(0, 1, 100)[:, np.newaxis]
y_fit = model.predict(X_fit)
plt.plot(X.squeeze(), y, 'o')
plt.plot(X_fit.squeeze(), y_fit);
scikit-learn 还有一些更加复杂的算法,可以根据模型中的特征进行更好的拟合。
In [18]:
# 训练一个随机森林(Random Forest)模型
from sklearn.ensemble import RandomForestRegressor
model = RandomForestRegressor()
model.fit(X, y)
# 画出数据和模型预测之间的图形
X_fit = np.linspace(0, 1, 100)[:, np.newaxis]
y_fit = model.predict(X_fit)
plt.plot(X.squeeze(), y, 'o')
plt.plot(X_fit.squeeze(), y_fit);
这些是不是一个好的拟合取决于很多因素;我们之后在教程中会去讨论如何去选择一个模型的细节。
无监督学习从问题的另一个角度去考虑。数据在这里是没有标签的,我们感兴趣的是问题中对象的相似点。从某种程度上说,您可以认为无监督学习是一种从数据本身去发掘标签的方法。无监督学习有降维(dimensionality reduction) 、聚类(clustering)和密度估计(density estimation)等算法。举个例子,在我们之前所说的iris数据中,我们可以利用无监督学习去发现哪一种测量的组合最能体现数据的结构。我们接下来会看到,这样一种数据的投影能够被用来在二维空间中展现四维的数据集。还有一些更贴切的无监督学习的问题:
给出遥远星系的详细的观测数据,决定哪一个或哪几个特征是最能体现其数据的
给出两种声源的混合(比如一个人在音乐声中谈话),分离出两种声音(这叫做 盲源分离 )
给出一段视频,分离出其中运动的物体,并且与其他运动物体比较,给出分类
有些时候监督学习和无监督学习会组合在一起:无监督学习可以用来从有多个类别的数据中找到有用的特征,而获取的这些特征可以被用在一个监督学习的框架中。
In [19]:
X, y = iris.data, iris.target
from sklearn.decomposition import PCA
pca = PCA(n_components=0.95)
pca.fit(X)
X_reduced = pca.transform(X)
print("Reduced dataset shape:", X_reduced.shape)
In [20]:
import matplotlib.pyplot as plt
plt.scatter(X_reduced[:, 0], X_reduced[:, 1],
c=y, cmap='RdYlBu')
print("Meaning of the 2 components:")
for component in pca.components_:
print(" + ".join("%.3f x %s" % (value, name)
for value, name in zip(component,
iris.feature_names)))
In [21]:
from sklearn.cluster import KMeans
k_means = KMeans(n_clusters=3, random_state=0) # Fixing the RNG in kmeans
k_means.fit(X)
y_pred = k_means.predict(X)
plt.scatter(X_reduced[:, 0], X_reduced[:, 1], c=y_pred,
cmap='RdYlBu');
Scikit-learn 致力于为所有的机器学习方法创造一个统一的接口,我们将会在下面的看到这些例子。对于一个叫model
的scikit-learn的Estimator对象,我们有下面这些方法:
model.fit()
: 用训练数据集来训练算法。 对监督学习,这个方法接受两个参数: 特征向量 X
和对应的标签 y
(例如: model.fit(X, y)
)。 对于无监督学习,这个方法只接受一个参数,即特征向量 X
(例如: model.fit(X)
)。model.predict()
: 给定一个训练模型,预测一个新的数据集的返回值。 这个方法接受一个参数,即新的数据集 X_new
(例如: model.predict(X_new)
), 方法返回特征向量中每一组值预测返回的结果。model.predict_proba()
: 对于分类(classification)问题,一些模型提供了这个方法,用于返回给定一个新的数值可能返回不同分类的概率。 在这种情况下,model.predict()
返回概率最高的分类值。model.score()
: 对分类(classification)或者回归(regression)问题,绝大多数的模型都实现了这个得分(score) 方法。 得分数值介于 0 和 1 之间,数值越大说明模型训练的越好。model.predict()
: 在聚类算法中,预测数据的标签。model.transform()
: 给定一个无监督模型,将一组新数据变换(transform)为新的基准(basis)。这个方法接受一个参数 X_new
,将这组新数据根据无监督模型进行数据变换并返回。model.fit_transform()
: 有一些estimators实现这个方法,它更有效的训练数据并根据模型进行数据转换。
In [22]:
from sklearn.neighbors import KNeighborsClassifier
X, y = iris.data, iris.target
clf = KNeighborsClassifier(n_neighbors=1)
clf.fit(X, y)
y_pred = clf.predict(X)
print(np.all(y == y_pred))
一个查看数据更有用的方法是查看混淆矩阵,或者是一个可以显示输入和输出频率的矩阵:
In [23]:
from sklearn.metrics import confusion_matrix
print(confusion_matrix(y, y_pred))
50个训练的数据都被正确的分类了。但是这并不代表我们的模型就是完美的!特别的,这个模型应用于新的数据时会效果会很糟糕。我们可以把数据集划分成训练集(training set)和测试集(testing set),并再次进行训练和测试。 Scikit-learn 拥有方便的划分流程:
In [24]:
# from sklearn.cross_validation import train_test_split
# 从 v0.18 起,使用 sklearn.model_selection 中 train_test_split
from sklearn.model_selection import train_test_split
Xtrain, Xtest, ytrain, ytest = train_test_split(X, y)
clf.fit(Xtrain, ytrain)
ypred = clf.predict(Xtest)
print(confusion_matrix(ytest, ypred))
上面真是的反映了我们的分类器的性能:显然对于第二类和第三类出现了一点混淆,我们也许可以通过上面的数据看出这一点。
这就是为什么将模型划分为训练集和测试集非常重要。我们将会在后续的教程中深入讨论模型的验证。
下面是由sckit-learn的最重要的贡献者 Andreas Mueller 提供的一张图。它给出了什么情况下应该应用什么算法的总结。保存它吧!
In [25]:
from IPython.display import Image
Image("http://scikit-learn.org/stable/_static/ml_map.png", width=800)
Out[25]:
为了在一个更有趣的问题中实践我们的这些准则,让我们考虑光学字符识别问题——也就是,识别手写数字。 从更广的层面来说,这个问题包含了数字定位技术和识别数字的技术。在这里我们会走一个捷径,scikit-learn的库中已经包含了已经预处理好的数字。
In [26]:
from sklearn import datasets
digits = datasets.load_digits()
digits.images.shape
Out[26]:
我们打印出其中的一部分:
In [27]:
fig, axes = plt.subplots(10, 10, figsize=(8, 8))
fig.subplots_adjust(hspace=0.1, wspace=0.1)
for i, ax in enumerate(axes.flat):
ax.imshow(digits.images[i], cmap='binary', interpolation='nearest')
ax.text(0.05, 0.05, str(digits.target[i]),
transform=ax.transAxes, color='green')
ax.set_xticks([])
ax.set_yticks([])
In [28]:
# 这些图像的数字
print(digits.images.shape)
print(digits.images[0])
In [29]:
# 对应的标签(label)
print(digits.target)
所以我们有了1797个样本,其中每个样本有64个维度。
In [30]:
from sklearn.manifold import Isomap
In [31]:
iso = Isomap(n_components=2)
data_projected = iso.fit_transform(digits.data)
In [32]:
data_projected.shape
Out[32]:
In [33]:
plt.scatter(data_projected[:, 0], data_projected[:, 1], c=digits.target,
edgecolor='none', alpha=0.5, cmap=plt.cm.get_cmap('nipy_spectral', 10));
plt.colorbar(label='digit label', ticks=range(10))
plt.clim(-0.5, 9.5)
我们在这里可以看到,这些数字在参数域被非常好的分隔开了。这说明了我们的监督学习分类算法也应当取得非常好的结果。让我们试试看。
In [34]:
# 从 sklearn.cross_validation 中引入 train_test_split
from sklearn.model_selection import train_test_split
Xtrain, Xtest, ytrain, ytest = train_test_split(digits.data, digits.target,
random_state=2)
print(Xtrain.shape, Xtest.shape)
我们用一个简单的逻辑回归算法(logistic regression,尽管名称中含有'回归',但它是一个分类算法)去完成分类:
In [35]:
from sklearn.linear_model import LogisticRegression
clf = LogisticRegression(penalty='l2')
clf.fit(Xtrain, ytrain)
ypred = clf.predict(Xtest)
我们可以通过比较预测结果和真实结果来得到我们的分类准确率:
In [36]:
from sklearn.metrics import accuracy_score
accuracy_score(ytest, ypred)
Out[36]:
单纯的数字没有告诉我们哪里出错了,一个比较推荐的方法是使用混淆矩阵。
In [37]:
from sklearn.metrics import confusion_matrix
print(confusion_matrix(ytest, ypred))
In [38]:
plt.imshow(np.log(confusion_matrix(ytest, ypred)),
cmap='Blues', interpolation='nearest')
plt.grid(False)
plt.ylabel('true')
plt.xlabel('predicted');
我们可以查看我们的输出和其对应的预测的数字。我们会将预测错误的标签置红:
In [39]:
fig, axes = plt.subplots(10, 10, 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='binary')
ax.text(0.05, 0.05, str(ypred[i]),
transform=ax.transAxes,
color='green' if (ytest[i] == ypred[i]) else 'red')
ax.set_xticks([])
ax.set_yticks([])
一个有趣的现象是,即使我们采用的是最简单的逻辑回归算法,很多它预测错误的图片我们自己也很有可能预测错误!
我们有很多方法去提高这个分类器,但是我们现在没有时间了。我们可以通过使用更成熟的模型,运用交叉验证或者采用其他技术去提高这个分类器。 我们会在之后的教程中详细的说明这些。