Python 机器学习秘籍第二版(二)
原文:
annas-archive.org/md5/1a114450f966ee5154c07d3ee2c9ce43译者:飞龙
第三章:预测建模
在本章中,我们将介绍以下食谱:
-
使用支持向量机(SVMs)构建线性分类器
-
使用 SVM 构建非线性分类器
-
解决类别不平衡问题
-
提取置信度测量
-
寻找最优超参数
-
构建事件预测器
-
估计交通流量
-
使用 TensorFlow 简化机器学习工作流程
-
实现堆叠方法
技术要求
为了处理本章中的食谱,你需要以下文件(可在 GitHub 上找到):
-
svm.py -
data_multivar.txt -
svm_imbalance.py -
data_multivar_imbalance.txt -
svm_confidence.py -
perform_grid_search.py -
building_event_binary.txt -
building_event_multiclass.txt -
`event.py` -
traffic_data.txt -
traffic.py -
IrisTensorflow.py -
stacking.py
简介
预测建模可能是数据分析中最激动人心的领域之一。近年来,由于许多不同领域中有大量数据可用,它受到了很多关注。它在数据挖掘领域非常常用,用于预测未来的趋势。
预测建模是一种分析技术,用于预测系统的未来行为。它是一系列算法,可以识别独立输入变量与目标响应之间的关系。我们根据观察创建一个数学模型,然后使用这个模型来估计未来会发生什么。
在预测建模中,我们需要收集已知响应的数据来训练我们的模型。一旦我们创建了该模型,我们将使用一些指标来验证它,然后使用它来预测未来的值。我们可以使用许多不同类型的算法来创建预测模型。在本章中,我们将使用 SVM 来构建线性和非线性模型。
预测模型是通过使用可能影响系统行为的多个特征构建的。例如,为了估计天气条件,我们可能使用各种类型的数据,如温度、气压、降水和其他大气过程。同样,当我们处理其他类型的系统时,我们需要决定哪些因素可能影响其行为,并在训练模型之前将它们作为特征向量的一部分包括在内。
使用 SVM 构建线性分类器
SVMs(支持向量机)是我们可以用来创建分类器和回归器的监督学习模型。SVM 通过求解一组数学方程式,找到两个点集之间最佳分离边界。让我们看看如何使用 SVM 构建线性分类器。
准备工作
让我们可视化我们的数据,以理解当前的问题。我们将使用svm.py文件来完成这项工作。在我们构建 SVM 之前,让我们了解我们的数据。我们将使用已经提供给你的data_multivar.txt文件。让我们看看如何可视化数据:
- 创建一个新的 Python 文件,并向其中添加以下行(完整的代码在已经提供给你的
svm.py文件中):
import numpy as np
import matplotlib.pyplot as plt
import utilities
# Load input data
input_file = 'data_multivar.txt'
X, y = utilities.load_data(input_file)
- 我们只导入了一些包并命名了输入文件。让我们看看
load_data()方法:
# Load multivar data in the input file
def load_data(input_file):
X = []
y = []
with open(input_file, 'r') as f:
for line in f.readlines():
data = [float(x) for x in line.split(',')]
X.append(data[:-1])
y.append(data[-1])
X = np.array(X)
y = np.array(y)
return X, y
- 我们需要将数据分离成类别,如下所示:
class_0 = np.array([X[i] for i in range(len(X)) if y[i]==0])
class_1 = np.array([X[i] for i in range(len(X)) if y[i]==1])
- 现在我们已经分离了数据,让我们绘制它:
plt.figure()
plt.scatter(class_0[:,0], class_0[:,1], facecolors='black', edgecolors='black', marker='s')
plt.scatter(class_1[:,0], class_1[:,1], facecolors='None', edgecolors='black', marker='s')
plt.title('Input data')
plt.show()
如果你运行此代码,你将看到以下内容:
前面包含两种类型的点——实心方块和空心方块。在机器学习的术语中,我们说我们的数据包含两个类别。我们的目标是构建一个可以将实心方块与空心方块分开的模型。
如何做到这一点...
在这个菜谱中,我们将学习如何使用支持向量机(SVMs)构建线性分类器:
- 我们需要将我们的数据集分成训练集和测试集。向同一 Python 文件中添加以下行:
# Train test split and SVM training
from sklearn import cross_validation
from sklearn.svm import SVC
X_train, X_test, y_train, y_test = cross_validation.train_test_split(X, y, test_size=0.25, random_state=5)
- 让我们使用
linear核初始化 SVM 对象。向文件中添加以下行:
params = {'kernel': 'linear'}
classifier = SVC(**params, gamma='auto')
- 我们现在准备好训练线性 SVM 分类器:
classifier.fit(X_train, y_train)
- 我们现在可以看到分类器的表现:
utilities.plot_classifier(classifier, X_train, y_train, 'Training dataset')
plt.show()
如果你运行此代码,你将得到以下内容:
plot_classifier函数与我们在第一章,“监督学习领域”中讨论的相同。它有一些小的补充。
你可以查看已经提供给你的utilities.py文件以获取更多详细信息。
- 让我们看看它在测试数据集上的表现。向
svm.py文件中添加以下行:
y_test_pred = classifier.predict(X_test)
utilities.plot_classifier(classifier, X_test, y_test, 'Test dataset')
plt.show()
如果你运行此代码,你将看到以下输出:
如你所见,分类器在输入数据上的边界被清楚地识别。
- 让我们计算训练集的准确率。向同一文件中添加以下行:
from sklearn.metrics import classification_report
target_names = ['Class-' + str(int(i)) for i in set(y)]
print("\n" + "#"*30)
print("\nClassifier performance on training dataset\n")
print(classification_report(y_train, classifier.predict(X_train), target_names=target_names))
print("#"*30 + "\n")
如果你运行此代码,你将在你的终端看到以下内容:
- 最后,让我们看看测试数据集的分类报告:
print("#"*30)
print("\nClassification report on test dataset\n")
print(classification_report(y_test, y_test_pred, target_names=target_names))
print("#"*30 + "\n")
- 如果你运行此代码,你将在终端看到以下内容:
从我们可视化数据的输出截图来看,我们可以看到实心方块被空心方块完全包围。这意味着数据不是线性可分的。我们无法画一条漂亮的直线来分离这两组点!因此,我们需要一个非线性分类器来分离这些数据点。
它是如何工作的...
SVMs 是一组监督学习方法,可用于分类和回归。对于两个线性可分的多维模式类别,在所有可能的分离超平面中,SVM 算法确定能够以最大可能间隔分离类别的那个超平面。间隔是训练集中两个类别的点与识别的超平面的最小距离。
边界的最大化与泛化能力相关。如果训练集的模式以大边界被分类,你可以希望即使测试集的模式接近类别之间的边界也能被正确处理。在下面的内容中,你可以看到三条线(l1、l2 和 l3)。线 l1 无法分离两个类别,线 l2 可以分离它们,但边界较小,而线 l3 最大化两个类别之间的距离:
SVMs 可以用来分离那些线性分类器无法分离的类别。对象坐标通过称为特征函数的非线性函数映射到一个称为特征空间的空间中。这个空间是多维的,在这个空间中,两个类别可以用线性分类器分离。因此,初始空间被重新映射到新空间,此时分类器被识别,然后返回到初始空间。
更多内容...
SVMs 是文献中最近引入的一类学习机器。SVMs 来自于关于学习统计理论的观念,并具有理论上的泛化特性。SVMs 的功能机制所遵循的理论是由 Vapnik 在 1965 年(统计学习理论)提出的,后来在 1995 年由 Vapnik 本人和其他人进一步完善。SVMs 是模式分类中最广泛使用的工具之一。Vapnik 建议直接解决感兴趣的问题,即确定类别之间的决策表面(分类边界),而不是估计类别的概率密度。
参见
-
参考
sklearn.svm.SVC()函数的官方文档:scikit-learn.org/stable/modules/generated/sklearn.svm.SVC.html -
参考 支持向量机教程(哥伦比亚大学):
www.cs.columbia.edu/~kathy/cs4701/documents/jason_svm_tutorial.pdf -
参考 支持向量机 - 讲义(由斯坦福大学的 Andrew Ng 提供):
cs229.stanford.edu/notes/cs229-notes3.pdf -
支持向量机教程(华盛顿州立大学):
course.ccs.neu.edu/cs5100f11/resources/jakkula.pdf -
SVM 教程:
web.mit.edu/zoya/www/SVM.pdf
使用 SVMs 构建非线性分类器
SVM 提供了多种选项来构建非线性分类器。我们需要使用各种核函数来构建非线性分类器。在这个例子中,让我们考虑两种情况。当我们想要表示两组点之间的曲线边界时,我们可以使用多项式函数或径向基函数来完成。
准备工作
在这个菜谱中,我们将使用之前菜谱中使用的相同文件,即使用 SVM 构建线性分类器,但在这个情况下,我们将使用不同的核来处理一个明显非线性的问题。
如何做...
让我们看看如何使用 SVM 构建非线性分类器:
- 对于第一种情况,让我们使用多项式核来构建一个非线性分类器。在相同的 Python 文件(
svm.py)中,查找以下行:
params = {'kernel': 'linear'}
将此行替换为以下内容:
params = {'kernel': 'poly', 'degree': 3}
这意味着我们使用一个degree为3的多项式函数。如果我们增加度数,这意味着我们允许多项式曲线更弯曲。然而,曲线的弯曲是有代价的,因为这意味着它将花费更多的时间来训练,因为它更昂贵。
- 如果你现在运行此代码,你将得到以下结果:
- 你还将在你的终端上看到以下分类报告:
- 我们还可以使用径向基函数核来构建一个非线性分类器。在相同的 Python 文件中,查找以下行:
params = {'kernel': 'poly', 'degree': 3}
- 将此行替换为以下一行:
params = {'kernel': 'rbf'}
- 如果你现在运行此代码,你将得到以下结果:
- 你还将在你的终端上看到以下分类报告:
它是如何工作的...
在这个菜谱中,我们使用 SVM 分类器通过解决一组数学方程来找到点数据集的最佳分离边界。为了解决非线性问题,我们使用了核方法。核方法因此得名于核函数,这些函数用于在特征空间中操作,而不是通过计算函数空间中所有数据副本的图像之间的内积来计算数据坐标。内积的计算通常比显式计算坐标更便宜。这种方法被称为核策略。
还有更多...
SVM 的主要观点是,只要仔细选择核及其所有参数,就可以解决任何通用问题——例如,对输入数据集进行完全过拟合。这种方法的问题在于,它与数据集的大小成比例地扩展得相当差,因为它通常归因于 D2 因子,即使在这种情况下,通过优化这一方面可以获得更快的实现。问题在于确定最佳的核并提供最佳的参数。
参见
-
支持向量机和核方法(来自卡内基梅隆大学计算机科学学院):
www.cs.cmu.edu/~ggordon/SVMs/new-svms-and-kernels.pdf -
支持向量机和核方法(来自台湾大学计算机科学系):
www.csie.ntu.edu.tw/~cjlin/talks/postech.pdf
解决类别不平衡问题
到目前为止,我们处理了所有类别中数据点数量相似的问题。在现实世界中,我们可能无法以如此有序的方式获取数据。有时,一个类别的数据点数量可能比其他类别的数据点数量多得多。如果发生这种情况,那么分类器往往会偏向。边界不会反映你数据的真实性质,只是因为两个类别之间数据点的数量存在很大差异。因此,考虑这种差异并中和它是很重要的,这样我们的分类器才能保持公正。
准备工作
在这个食谱中,我们将使用一个新的数据集,名为data_multivar_imbalance.txt,其中每行有三个值;前两个代表点的坐标,第三个是该点所属的类别。我们的目标是,再次,构建一个分类器,但这次,我们必须面对数据平衡问题。
如何做到这一点...
让我们看看如何解决类别不平衡问题:
- 让我们导入库:
import numpy as np
import matplotlib.pyplot as plt
from sklearn.svm import SVC
import utilities
- 让我们加载数据(
data_multivar_imbalance.txt):
input_file = 'data_multivar_imbalance.txt'
X, y = utilities.load_data(input_file)
- 让我们可视化数据。可视化代码与之前的食谱中完全相同。你还可以在名为
svm_imbalance.py的文件中找到它,该文件已经提供给你:
# Separate the data into classes based on 'y'
class_0 = np.array([X[i] for i in range(len(X)) if y[i]==0])
class_1 = np.array([X[i] for i in range(len(X)) if y[i]==1])
# Plot the input data
plt.figure()
plt.scatter(class_0[:,0], class_0[:,1], facecolors='black', edgecolors='black', marker='s')
plt.scatter(class_1[:,0], class_1[:,1], facecolors='None', edgecolors='black', marker='s')
plt.title('Input data')
plt.show()
- 如果你运行它,你会看到以下:
- 让我们使用线性核构建一个 SVM。代码与之前的食谱中相同,使用 SVM 构建非线性分类器:
from sklearn import model_selection
X_train, X_test, y_train, y_test = model_selection.train_test_split(X, y, test_size=0.25, random_state=5)
params = {'kernel': 'linear'}
classifier = SVC(**params, gamma='auto')
classifier.fit(X_train, y_train)
utilities.plot_classifier(classifier, X_train, y_train, 'Training dataset')
plt.show()
- 让我们打印一个分类报告:
from sklearn.metrics import classification_report
target_names = ['Class-' + str(int(i)) for i in set(y)]
print("\n" + "#"*30)
print("\nClassifier performance on training dataset\n")
print(classification_report(y_train, classifier.predict(X_train), target_names=target_names))
print("#"*30 + "\n")
print("#"*30)
print("\nClassification report on test dataset\n")
print(classification_report(y_test, y_test_pred, target_names=target_names))
print("#"*30 + "\n")
- 如果你运行它,你会看到以下:
- 你可能会 wonder 为什么这里没有边界!Well, 这是因为分类器无法将两个类别分开,导致
Class-0的准确率为 0%。你还会在终端上看到打印出的分类报告,如下截图所示:
- 如我们所预期,
Class-0的精确率为 0%,所以让我们继续解决这个问题!在 Python 文件中,搜索以下行:
params = {'kernel': 'linear'}
- 将前面的行替换为以下内容:
params = {'kernel': 'linear', 'class_weight': 'balanced'}
-
class_weight参数将计算每个类别的数据点数量,以调整权重,使不平衡不会对性能产生不利影响。 -
运行此代码后,你会得到以下输出:
- 让我们查看分类报告:
- 如我们所见,
Class-0现在以非零百分比准确率被检测到。
它是如何工作的...
在这个配方中,我们使用 SVM 分类器来找到点数据集之间的最佳分离边界。为了解决数据平衡问题,我们再次使用了线性核方法,但在 fit 方法中实现了一个 class_weight 关键字。class_weight 变量是一个形式为 {class_label: value} 的字典,其中 value 是一个大于 0 的浮点数,它修改了类 (class_label) 的 C 参数,将其设置为通过将旧的 C 值与值属性中指定的值相乘得到的新值 (C * value)。
更多内容...
C 是一个超参数,它决定了观察到的错误分类的惩罚。因此,我们使用权重来管理不平衡的类别。这样,我们将为类别分配一个新的 C 值,定义如下:
其中 C 是惩罚,w[i] 是与类别 i 的频率成反比的权重,C[i] 是类别 i 的 C 值。这种方法建议增加对代表性较小的类别的惩罚,以防止它们被代表性最大的类别超越。
在 scikit-learn 库中,当使用 SVC 时,我们可以通过设置 class_weight='balanced' 来自动设置 C[i] 的值。
参见
- 支持向量机—
scikit-learn库的官方文档:scikit-learn.org/stable/modules/svm.html
提取置信度测量
很好知道我们以多大的置信度对未知数据进行分类。当一个新数据点被分类到已知类别时,我们可以训练 SVM 来计算该输出的置信水平。置信水平指的是参数值落在指定值范围内的概率。
准备工作
在这个配方中,我们将使用 SVM 分类器来找到点数据集之间的最佳分离边界。此外,我们还将对获得的结果的置信水平进行测量。
如何做...
让我们看看如何提取置信度测量:
- 完整的代码在
svm_confidence.py文件中给出,已经提供给你。我们将在下面讨论这个配方的代码。让我们定义一些输入数据:
import numpy as np
import matplotlib.pyplot as plt
from sklearn.svm import SVC
import utilities
# Load input data
input_file = 'data_multivar.txt'
X, y = utilities.load_data(input_file)
- 在这一点上,我们将数据分为训练集和测试集,然后我们将构建分类器:
from sklearn import model_selection
X_train, X_test, y_train, y_test = model_selection.train_test_split(X, y, test_size=0.25, random_state=5)
params = {'kernel': 'rbf'}
classifier = SVC(**params, gamma='auto')
classifier.fit(X_train, y_train)
- 定义输入数据点:
input_datapoints = np.array([[2, 1.5], [8, 9], [4.8, 5.2], [4, 4], [2.5, 7], [7.6, 2], [5.4, 5.9]])
- 让我们测量边界距离:
print("Distance from the boundary:")
for i in input_datapoints:
print(i, '-->', classifier.decision_function([i])[0])
- 你将在你的终端上看到以下内容:
- 边界距离给我们关于数据点的某些信息,但它并没有确切地告诉我们分类器对输出标签的置信度如何。为了做到这一点,我们需要 Platt 缩放。这是一种将距离度量转换为类别之间概率度量的方法。让我们继续使用 Platt 缩放训练一个 SVM:
# Confidence measure
params = {'kernel': 'rbf', 'probability': True}
classifier = SVC(**params, gamma='auto')
probability 参数告诉 SVM 它应该训练以计算概率。
- 让我们训练分类器:
classifier.fit(X_train, y_train)
- 让我们计算这些输入数据点的置信度测量值:
print("Confidence measure:")
for i in input_datapoints:
print(i, '-->', classifier.predict_proba([i])[0])
predict_proba 函数测量置信值。
- 你将在你的终端上看到以下内容:
- 让我们看看点相对于边界的位置:
utilities.plot_classifier(classifier, input_datapoints, [0]*len(input_datapoints), 'Input datapoints', 'True')
- 如果你运行这个,你将得到以下结果:
工作原理...
在这个菜谱中,我们基于 SVM 构建了一个分类器。一旦获得分类器,我们使用一组点来衡量这些点与边界的距离,然后为这些点中的每一个测量置信水平。在估计参数时,简单地识别一个值通常是不够的。因此,建议在估计参数的同时,给出该参数的合理值范围,这被定义为置信区间。因此,它与一个累积概率值相关联,间接地,从概率的角度来看,它描述了相对于随机变量最大值的幅度,该随机变量衡量的是随机事件落在该区间内的概率,并且等于该区间图形上由随机变量的概率分布曲线所围成的面积。
更多内容...
置信区间衡量一个统计量(如民意调查)的可靠性。例如,如果 40%的受访样本表示选择某个产品,那么可以以 99%的置信水平推断,总消费者人口中有 30%到 50%的比例将表示支持该产品。从同一受访样本中,以 90%的置信区间,可以假设该产品受到好评的比例现在在 37%到 43%之间。
参见
-
请参考
sklearn.svm.SVC.decision_函数的官方文档:scikit-learn.org/stable/modules/generated/sklearn.svm.SVC.html#sklearn.svm.SVC.decision_function -
参考以下内容:支持向量机概率输出及其与正则化似然方法的比较:
www.researchgate.net/publication/2594015_Probabilistic_Outputs_for_Support_Vector_Machines_and_Comparisons_to_Regularized_Likelihood_Methods
寻找最优超参数
如前一章所述,超参数对于确定分类器的性能很重要。让我们看看如何提取 SVM 的最优超参数。
准备工作
在机器学习算法中,学习过程中会获得各种参数。相比之下,超参数是在学习过程开始之前设置的。给定这些超参数,训练算法从数据中学习参数。在这个菜谱中,我们将使用网格搜索方法从基于 SVM 算法的模型中提取超参数。
如何做...
让我们看看如何找到最佳超参数:
- 完整的代码在
perform_grid_search.py文件中给出,该文件已经提供给你。我们开始导入库:
from sklearn import svm
from sklearn import model_selection
from sklearn.model_selection import GridSearchCV
from sklearn.metrics import classification_report
import pandas as pd
import utilities
- 然后,我们加载数据:
input_file = 'data_multivar.txt'
X, y = utilities.load_data(input_file)
- 我们将数据分为训练集和测试集:
X_train, X_test, y_train, y_test = model_selection.train_test_split(X, y, test_size=0.25, random_state=5)
- 现在,我们将在这里使用交叉验证,这是我们之前菜谱中提到的。一旦你加载数据并将其分为训练集和测试集,请将以下内容添加到文件中:
# Set the parameters by cross-validation
parameter_grid = {"C": [1, 10, 50, 600],
'kernel':['linear','poly','rbf'],
"gamma": [0.01, 0.001],
'degree': [2, 3]}
- 让我们定义我们想要使用的度量标准:
metrics = ['precision']
- 让我们开始搜索每个度量指标的最佳超参数:
for metric in metrics:
print("#### Grid Searching optimal hyperparameters for", metric)
classifier = GridSearchCV(svm.SVC(C=1),
parameter_grid, cv=5,scoring=metric,return_train_score=True)
classifier.fit(X_train, y_train)
- 让我们看看分数:
print("Scores across the parameter grid:")
GridSCVResults = pd.DataFrame(classifier.cv_results_)
for i in range(0,len(GridSCVResults)):
print(GridSCVResults.params[i], '-->', round(GridSCVResults.mean_test_score[i],3))
- 让我们打印最佳参数集:
print("Highest scoring parameter set:", classifier.best_params_)
- 如果你运行此代码,你将在你的终端看到以下内容:
#### Grid Searching optimal hyperparameters for precision
Scores across the parameter grid:
{'C': 1, 'degree': 2, 'gamma': 0.01, 'kernel': 'linear'} --> 0.676
{'C': 1, 'degree': 2, 'gamma': 0.01, 'kernel': 'poly'} --> 0.527
{'C': 1, 'degree': 2, 'gamma': 0.01, 'kernel': 'rbf'} --> 0.98
{'C': 1, 'degree': 2, 'gamma': 0.001, 'kernel': 'linear'} --> 0.676
{'C': 1, 'degree': 2, 'gamma': 0.001, 'kernel': 'poly'} --> 0.533
...
...
{'C': 600, 'degree': 2, 'gamma': 0.001, 'kernel': 'linear'} --> 0.676
{'C': 600, 'degree': 2, 'gamma': 0.001, 'kernel': 'poly'} --> 0.9
{'C': 600, 'degree': 2, 'gamma': 0.001, 'kernel': 'rbf'} --> 0.983
{'C': 600, 'degree': 3, 'gamma': 0.01, 'kernel': 'linear'} --> 0.676
{'C': 600, 'degree': 3, 'gamma': 0.01, 'kernel': 'poly'} --> 0.884
{'C': 600, 'degree': 3, 'gamma': 0.01, 'kernel': 'rbf'} --> 0.967
{'C': 600, 'degree': 3, 'gamma': 0.001, 'kernel': 'linear'} --> 0.676
{'C': 600, 'degree': 3, 'gamma': 0.001, 'kernel': 'poly'} --> 0.533
{'C': 600, 'degree': 3, 'gamma': 0.001, 'kernel': 'rbf'} --> 0.983
Highest scoring parameter set: {'C': 10, 'degree': 2, 'gamma': 0.01, 'kernel': 'rbf'}
- 如前所述的输出所示,它搜索所有最佳超参数。在这种情况下,超参数是
kernel的类型、C值和gamma。它将尝试这些参数的各种组合以找到最佳参数。让我们在测试数据集上测试它:
y_true, y_pred = y_test, classifier.predict(X_test)
print("Full performance report:\n")
print(classification_report(y_true, y_pred))
- 如果你运行此代码,你将在你的终端看到以下内容:
- 我们之前提到过,优化超参数有不同的技术。我们将应用
RandomizedSearchCV方法。为此,只需使用相同的数据并更改分类器。在刚刚看到的代码中,我们添加了一个额外的部分:
# Perform a randomized search on hyper parameters
from sklearn.model_selection import RandomizedSearchCV
parameter_rand = {'C': [1, 10, 50, 600],
'kernel':['linear','poly','rbf'],
'gamma': [0.01, 0.001],
'degree': [2, 3]}
metrics = ['precision']
for metric in metrics:
print("#### Randomized Searching optimal hyperparameters for", metric)
classifier = RandomizedSearchCV(svm.SVC(C=1),
param_distributions=parameter_rand,n_iter=30,
cv=5,return_train_score=True)
classifier.fit(X_train, y_train)
print("Scores across the parameter grid:")
RandSCVResults = pd.DataFrame(classifier.cv_results_)
for i in range(0,len(RandSCVResults)):
print(RandSCVResults.params[i], '-->',
round(RandSCVResults.mean_test_score[i]
- 如果你运行此代码,你将在你的终端看到以下内容:
#### Randomized Searching optimal hyperparameters for precision
Scores across the parameter grid:
{'kernel': 'rbf', 'gamma': 0.001, 'degree': 2, 'C': 50} --> 0.671
{'kernel': 'rbf', 'gamma': 0.01, 'degree': 3, 'C': 600} --> 0.951
{'kernel': 'linear', 'gamma': 0.01, 'degree': 3, 'C': 50} --> 0.591
{'kernel': 'poly', 'gamma': 0.01, 'degree': 2, 'C': 10} --> 0.804
...
...
{'kernel': 'rbf', 'gamma': 0.01, 'degree': 3, 'C': 10} --> 0.92
{'kernel': 'poly', 'gamma': 0.001, 'degree': 3, 'C': 600} --> 0.533
{'kernel': 'linear', 'gamma': 0.001, 'degree': 2, 'C': 10} --> 0.591
{'kernel': 'poly', 'gamma': 0.01, 'degree': 3, 'C': 50} --> 0.853
{'kernel': 'linear', 'gamma': 0.001, 'degree': 2, 'C': 600} --> 0.591
{'kernel': 'poly', 'gamma': 0.01, 'degree': 3, 'C': 10} --> 0.844
Highest scoring parameter set: {'kernel': 'rbf', 'gamma': 0.01, 'degree': 3, 'C': 600}
- 让我们在测试数据集上测试它:
print("Highest scoring parameter set:", classifier.best_params_)
y_true, y_pred = y_test, classifier.predict(X_test)
print("Full performance report:\n")
print(classification_report(y_true, y_pred))
- 返回以下结果:
它是如何工作的...
在之前的菜谱“使用 SVM 构建非线性分类器”中,我们反复修改 SVM 算法的核以获得数据分类的改进。基于菜谱开头给出的超参数定义,很明显,核代表一个超参数。在这个菜谱中,我们随机设置这个超参数的值并检查结果以找出哪个值决定了最佳性能。然而,随机选择算法参数可能是不够的。
此外,通过随机设置参数来比较不同算法的性能是困难的,因为一个算法可能比另一个算法使用不同的参数集表现更好。而且如果参数改变,算法可能比其他算法有更差的结果。
因此,随机选择参数值不是我们找到模型最佳性能的最佳方法。相反,建议开发一个算法,该算法可以自动找到特定模型的最佳参数。寻找超参数的方法有几种,如下所示:网格搜索、随机搜索和贝叶斯优化。
网格搜索算法
网格搜索算法通过自动寻找降低模型最佳性能的超参数集来实现这一点。
sklearn.model_selection.GridSearchCV()函数对估计器的指定参数值进行穷举搜索。穷举搜索(也称为直接搜索或暴力搜索)是对所有可能性的全面检查,因此代表了一种高效的方法,其中每个可能性都被测试以确定它是否是解决方案。
随机搜索算法
与GridSearchCV方法不同,这种方法并不是测试所有参数值,而是以固定数量对参数设置进行采样。要测试的参数设置是通过n_iter属性设置的。如果参数以列表形式呈现,则执行无放回采样。如果至少提供一个参数作为分布,则使用替换采样。
贝叶斯优化算法
贝叶斯超参数优化器的目标是构建目标函数的概率模型,并使用它来选择最适合用于真实目标函数的超参数。贝叶斯统计学不仅允许我们预见一个值,还可以预见一个分布,这是这种方法成功的关键。
与已处理的两种方法(网格搜索和随机搜索)相比,贝叶斯方法存储过去评估的结果,并使用这些结果形成一个概率模型,将超参数与目标函数得分的概率相关联。
这个模型被称为目标函数的代理,比目标函数本身更容易优化。通过以下程序获得此结果:
-
构建目标函数的代理概率模型。
-
在代理上给出最佳结果的超参数被搜索。
-
这些超参数应用于真实的目标函数。
-
通过结合新的结果来更新代理模型。
-
重复步骤 2-4,直到达到预定的迭代次数或最大时间。
以这种方式,在评估目标函数之后,代理概率模型被更新。要使用贝叶斯超参数优化器,有几种库可用:scikit-optimize、spearmint和SMAC3。
更多...
通常,超参数是指那些用户可以自由设置的值,并且通常通过适当的研究进行优化,以在验证数据上最大化准确性。甚至选择一种技术而不是另一种技术也可以被视为一个分类超参数,其值与我们可选择的方法的数量一样多。
参见
-
sklearn.model_selection.GridSearchCV()函数的官方文档:scikit-learn.org/stable/modules/generated/sklearn.model_selection.GridSearchCV.html#sklearn.model_selection.GridSearchCV -
超参数优化(来自维基百科):
zh.wikipedia.org/wiki/超参数优化 -
Spearmint 贝叶斯优化(来自 GitHub):
github.com/HIPS/Spearmint -
SMAC3 官方文档:
automl.github.io/SMAC3/stable/ -
机器学习贝叶斯优化的教程(来自哈佛大学工程与应用科学学院):
www.iro.umontreal.ca/~bengioy/cifar/NCAP2014-summerschool/slides/Ryan_adams_140814_bayesopt_ncap.pdf
构建事件预测器
让我们将本章中的所有这些知识应用到现实世界的问题中。我们将构建一个 SVM 来预测进出建筑的人数。数据集可在 archive.ics.uci.edu/ml/datasets/CalIt2+Building+People+Counts 找到。我们将使用这个数据集的一个略微修改版本,以便更容易分析。修改后的数据可在提供的 building_event_binary.txt 和 building_event_multiclass.txt 文件中找到。在这个菜谱中,我们将学习如何构建事件预测器。
准备中
在我们开始构建模型之前,让我们先了解数据格式。building_event_binary.txt 中的每一行都由六个以逗号分隔的字符串组成。这六个字符串的顺序如下:
-
天
-
日期
-
时间
-
离开建筑的人数
-
进入建筑的人数
-
指示是否为事件的输出
前五个字符串构成输入数据,我们的任务是预测建筑中是否正在发生事件。
building_event_multiclass.txt 中的每一行也由六个以逗号分隔的字符串组成。这个文件比之前的文件更细粒度,因为输出是建筑中正在发生的确切事件类型。这六个字符串的顺序如下:
-
天
-
日期
-
时间
-
离开建筑的人数
-
进入建筑的人数
-
指示事件类型的输出
前五个字符串形成输入数据,我们的任务是预测建筑物中正在进行的事件类型。
如何操作...
让我们看看如何构建一个事件预测器:
- 我们将使用已经提供给你的
event.py作为参考。创建一个新的 Python 文件,并添加以下行:
import numpy as np
from sklearn import preprocessing
from sklearn.svm import SVC
input_file = 'building_event_binary.txt'
# Reading the data
X = []
count = 0
with open(input_file, 'r') as f:
for line in f.readlines():
data = line[:-1].split(',')
X.append([data[0]] + data[2:])
X = np.array(X)
我们只是将所有数据加载到了X中。
- 让我们将数据转换为数值形式:
# Convert string data to numerical data
label_encoder = []
X_encoded = np.empty(X.shape)
for i,item in enumerate(X[0]):
if item.isdigit():
X_encoded[:, i] = X[:, i]
else:
label_encoder.append(preprocessing.LabelEncoder())
X_encoded[:, i] = label_encoder[-1].fit_transform(X[:, i])
X = X_encoded[:, :-1].astype(int)
y = X_encoded[:, -1].astype(int)
- 让我们使用径向基函数、Platt 缩放和类别平衡来训练 SVM:
# Build SVM
params = {'kernel': 'rbf', 'probability': True, 'class_weight': 'balanced'}
classifier = SVC(**params, gamma='auto')
classifier.fit(X, y)
- 我们现在准备好进行交叉验证:
from sklearn import model_selection
accuracy = model_selection.cross_val_score(classifier,
X, y, scoring='accuracy', cv=3)
print("Accuracy of the classifier: " + str(round(100*accuracy.mean(), 2)) + "%")
- 让我们在新的数据点上测试我们的 SVM:
# Testing encoding on single data instance
input_data = ['Tuesday', '12:30:00','21','23']
input_data_encoded = [-1] * len(input_data)
count = 0
for i,item in enumerate(input_data):
if item.isdigit():
input_data_encoded[i] = int(input_data[i])
else:
input_data_encoded[i] = int(label_encoder[count].transform([input_data[i]]))
count = count + 1
input_data_encoded = np.array(input_data_encoded)
# Predict and print(output for a particular datapoint
output_class = classifier.predict([input_data_encoded])
print("Output class:", label_encoder[-1].inverse_transform(output_class)[0])
- 如果你运行此代码,你将在你的终端上看到以下输出:
Accuracy of the classifier: 93.95%
Output class: noevent
- 如果你使用
building_event_multiclass.txt文件作为输入数据文件而不是building_event_binary.txt,你将在你的终端上看到以下输出:
Accuracy of the classifier: 65.33%
Output class: eventA
它是如何工作的...
在这个菜谱中,我们使用了在 15 周内,每天 48 个时间间隔观察到的进入和离开建筑物的人的数据。因此,我们构建了一个能够预测建筑物中如会议等事件存在的分类器,这决定了在那个时间段内建筑物内人数的增加。
更多内容...
在菜谱的后面,我们在不同的数据库上使用了相同的分类器来预测在建筑物内举行的活动类型。
相关内容
-
sklearn.svm.SVC()函数的官方文档:scikit-learn.org/stable/modules/generated/sklearn.svm.SVC.html -
sklearn.model_selection.cross_validate()函数的官方文档:scikit-learn.org/stable/modules/generated/sklearn.model_selection.cross_validate.html#sklearn.model_selection.cross_validate
估算交通流量
SVMs 的一个有趣的应用是预测交通流量,基于相关数据。在之前的菜谱中,我们使用 SVM 作为分类器。在这个菜谱中,我们将使用 SVM 作为回归器来估算交通流量。
准备工作
我们将使用在archive.ics.uci.edu/ml/datasets/Dodgers+Loop+Sensor可用的数据集。这是一个在洛杉矶道奇主场棒球比赛中统计经过的车辆数量的数据集。我们将使用该数据集的略微修改版,以便更容易分析。你可以使用已经提供给你的traffic_data.txt文件。该文件中的每一行都包含逗号分隔的字符串,格式如下:
-
日期
-
时间
-
对手队伍
-
是否有棒球比赛正在进行
-
经过的车辆数量
如何操作...
让我们看看如何估算交通流量:
- 让我们看看如何构建 SVM 回归器。我们将使用您已提供的作为参考的
traffic.py。创建一个新的 Python 文件,并添加以下行:
# SVM regressor to estimate traffic
import numpy as np
from sklearn import preprocessing
from sklearn.svm import SVR
input_file = 'traffic_data.txt'
# Reading the data
X = []
count = 0
with open(input_file, 'r') as f:
for line in f.readlines():
data = line[:-1].split(',')
X.append(data)
X = np.array(X)
我们将所有输入数据加载到 X 中。
- 让我们编码这些数据:
# Convert string data to numerical data
label_encoder = []
X_encoded = np.empty(X.shape)
for i,item in enumerate(X[0]):
if item.isdigit():
X_encoded[:, i] = X[:, i]
else:
label_encoder.append(preprocessing.LabelEncoder())
X_encoded[:, i] = label_encoder[-1].fit_transform(X[:, i])
X = X_encoded[:, :-1].astype(int)
y = X_encoded[:, -1].astype(int)
- 让我们使用径向基函数构建和训练 SVM 回归器:
# Build SVR
params = {'kernel': 'rbf', 'C': 10.0, 'epsilon': 0.2}
regressor = SVR(**params)
regressor.fit(X, y)
在前面的行中,C 参数指定了误分类的惩罚,而 epsilon 指定了不应用惩罚的限制范围内。
- 让我们执行交叉验证以检查回归器的性能:
# Cross validation
import sklearn.metrics as sm
y_pred = regressor.predict(X)
print("Mean absolute error =", round(sm.mean_absolute_error(y, y_pred), 2))
- 让我们在一个数据点上测试它:
# Testing encoding on single data instance
input_data = ['Tuesday', '13:35', 'San Francisco', 'yes']
input_data_encoded = [-1] * len(input_data)
count = 0
for i,item in enumerate(input_data):
if item.isdigit():
input_data_encoded[i] = int(input_data[i])
else:
input_data_encoded[i] = int(label_encoder[count].transform([input_data[i]]))
count = count + 1
input_data_encoded = np.array(input_data_encoded)
# Predict and print output for a particular datapoint
print("Predicted traffic:", int(regressor.predict([input_data_encoded])[0]))
- 如果你运行此代码,你将在你的终端上看到以下输出:
Mean absolute error = 4.08
Predicted traffic: 29
它是如何工作的...
在这个菜谱中,我们使用了在洛杉矶 101 号北高速公路上收集的数据,靠近道奇队比赛的球场。这个位置足够靠近球场,可以检测到比赛期间交通的增加。
观察是在 25 周内进行的,每天有 288 个时间间隔(每 5 分钟一次)。我们基于 SVM 算法构建了一个回归器来预测道奇球场是否有棒球比赛。特别是,我们可以根据以下预测器的值估计通过该位置的汽车数量:日期、时间、对手球队以及是否正在进行棒球比赛。
更多...
支持向量回归(SVR)与 SVMs 的原理相同。事实上,SVR 是从 SVMs 调整而来的,其中因变量是数值而不是分类的。使用 SVR 的主要优点之一是它是一种非参数技术。
参见
-
sklearn.metrics.mean_absolute_error()函数的官方文档:scikit-learn.org/stable/modules/generated/sklearn.metrics.mean_absolute_error.html -
线性回归和支持向量回归(来自阿德莱德大学):
cs.adelaide.edu.au/~chhshen/teaching/ML_SVR.pdf
使用 TensorFlow 简化机器学习工作流程
TensorFlow 是一个开源的数值计算库。该库由谷歌程序员创建。它提供了构建深度学习模型所需的所有工具,并为开发者提供了一个黑盒接口来编程。
准备工作
在这个菜谱中,我们将介绍 TensorFlow 框架,使用简单的神经网络来分类 iris 物种。我们将使用 iris 数据集,该数据集包含以下物种的 50 个样本:
-
爱丽丝·塞托萨
-
爱丽丝·维吉尼卡
-
爱丽丝·维吉尼卡
从每个样本测量了四个特征,即花萼和花瓣的长度和宽度,单位为厘米。
包含以下变量:
-
花萼长度(厘米)
-
花萼宽度(厘米)
-
花瓣长度(厘米)
-
花瓣宽度(厘米)
-
类别:
setosa、versicolor或virginica
如何做...
让我们看看如何使用 TensorFlow 简化机器学习工作流程:
- 我们像往常一样,首先导入库:
from sklearn import datasets
from sklearn import model_selection
import tensorflow as tf
前两个库仅用于加载数据和分割数据。第三个库加载了tensorflow库。
- 加载
iris数据集:
iris = datasets.load_iris()
- 加载并分割特征和类别:
x_train, x_test, y_train, y_test = model_selection.train_test_split(iris.data,
iris.target,
test_size=0.7,
random_state=1)
数据被分为 70%用于训练和 30%用于测试。random_state=1参数是随机数生成器使用的种子。
- 现在,我们将构建一个包含一个隐藏层和 10 个节点的简单神经网络:
feature_columns = tf.contrib.learn.infer_real_valued_columns_from_input(x_train)
classifier_tf = tf.contrib.learn.DNNClassifier(feature_columns=feature_columns,
hidden_units=[10],
n_classes=3)
- 然后我们调整网络:
classifier_tf.fit(x_train, y_train, steps=5000)
- 然后我们将进行预测:
predictions = list(classifier_tf.predict(x_test, as_iterable=True))
- 最后,我们将计算模型的
accuracy指标:
n_items = y_test.size
accuracy = (y_test == predictions).sum() / n_items
print("Accuracy :", accuracy)
返回以下结果:
Accuracy : 0.9333333333333333
它是如何工作的...
在这个配方中,我们使用了tensorflow库来构建一个简单的神经网络,用于从四个测量的特征中分类鸢尾花种类。这样,我们看到了如何使用tensorflow库实现基于机器学习算法的模型是多么简单。关于这个主题,以及一般深度神经网络,将在第十三章,“深度神经网络”中详细分析。
更多内容...
TensorFlow 为 Python、C、C++、Java、Go 和 Rust 提供了原生 API。可用的第三方 API 包括 C#、R 和 Scala。自 2017 年 10 月起,它已集成即时执行功能,允许立即执行由 Python 引用的操作。
相关内容
-
tensorflow库的官方文档:www.tensorflow.org/tutorials -
Tensorflow for Deep Learning Research(来自斯坦福大学):
web.stanford.edu/class/cs20si/
实现堆叠方法
不同的方法组合可以带来更好的结果:这个说法在我们生活的不同方面都适用,并且也适用于基于机器学习的算法。堆叠是将各种机器学习算法组合的过程。这项技术归功于美国数学家、物理学家和计算机科学家 David H. Wolpert。
在这个配方中,我们将学习如何实现堆叠方法。
准备工作
我们将使用heamy库来堆叠我们在前一个配方中使用的两个模型。heamy库是一套用于竞争性数据科学的有用工具。
如何做...
让我们看看如何实现堆叠方法:
- 我们首先导入库:
from heamy.dataset import Dataset
from heamy.estimator import Regressor
from heamy.pipeline import ModelsPipeline
from sklearn.datasets import load_boston
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestRegressor
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_absolute_error
- 加载
boston数据集,已在第一章,“监督学习领域”,用于估算房价配方:
data = load_boston()
- 分割数据:
X, y = data['data'], data['target']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1, random_state=2)
- 让我们创建数据集:
Data = Dataset(X_train,y_train,X_test)
- 现在,我们可以构建在堆叠过程中使用的两个模型:
RfModel = Regressor(dataset=Data, estimator=RandomForestRegressor, parameters={'n_estimators': 50},name='rf')
LRModel = Regressor(dataset=Data, estimator=LinearRegression, parameters={'normalize': True},name='lr')
- 是时候堆叠这些模型了:
Pipeline = ModelsPipeline(RfModel,LRModel)
StackModel = Pipeline.stack(k=10,seed=2)
- 现在,我们将对堆叠数据进行
LinearRegression模型的训练:
Stacker = Regressor(dataset=StackModel, estimator=LinearRegression)
- 最后,我们将计算结果以验证模型:
Results = Stacker.predict()
Results = Stacker.validate(k=10,scorer=mean_absolute_error)
它是如何工作的...
堆叠泛化通过推断分类器/回归器相对于提供的训练数据集的偏差来工作。这种推断是通过将原始泛化器的假设推广到第二个空间来实现的,该空间的输入是原始泛化器的假设,输出是正确的假设。当与多个生成器一起使用时,堆叠泛化是交叉验证的替代方案。
更多信息...
堆叠泛化试图通过忽略或纠正它们的缺点来利用每个算法的优点。它可以被视为一种纠正你算法中错误的机制。另一个执行堆叠过程的库是 StackNet。
StackNet 是一个基于 Wolpert 的多级堆叠泛化的 Java 框架,旨在提高机器学习预测问题中的准确性。StackNet 模型作为一个神经网络运行,其传递函数的形式可以是任何监督机器学习算法。
参见
-
heamy库的官方文档:heamy.readthedocs.io/en/latest/index.html -
StackNet框架的官方文档:github.com/kaz-Anova/StackNet -
David H. Wolpert 的《堆叠泛化》:
www.machine-learning.martinsewell.com/ensembles/stacking/Wolpert1992.pdf
第四章:使用无监督学习进行聚类
在本章中,我们将涵盖以下食谱:
-
使用 k-means 算法进行聚类数据
-
使用矢量量化压缩图像
-
使用层次聚类对数据进行分组
-
评估聚类算法的性能
-
使用基于密度的空间聚类应用噪声(DBSCAN)算法估计簇的数量
-
在股票市场数据中寻找模式
-
构建客户细分模型
-
使用自动编码器重建手写数字图像
技术要求
为了处理本章中的食谱,你需要以下文件(可在 GitHub 上找到):
-
kmeans.py -
data_multivar.txt -
vector_quantization.py -
flower_image.jpg -
agglomerative.py -
performance.py -
data_perf.txt -
estimate_clusters.py -
stock_market.py -
symbol_map.json -
stock_market_data.xlsx -
customer_segmentation.py -
wholesale.csv -
AutoencMnist.py
引言
无监督学习是机器学习中的一个范例,其中我们构建模型而不依赖于标记的训练数据。到目前为止,我们处理的数据都是以某种方式标记的。这意味着学习算法可以查看这些数据,并学习根据标签对其进行分类。在无监督学习的世界中,我们没有这种机会!这些算法用于当我们想要使用相似度度量在数据集中找到子组时。
在无监督学习中,数据库中的信息会自动提取。所有这些都是在没有分析内容先验知识的情况下发生的。在无监督学习中,没有关于示例所属类别或对应给定输入的输出的信息。我们希望有一个模型能够发现有趣的属性,例如具有相似特征的组,这在聚类中发生。这些算法的应用示例是搜索引擎。这些应用能够根据一个或多个关键词创建与我们的搜索相关的链接列表。
这些算法通过比较数据和寻找相似性或差异来工作。这些算法的有效性取决于它们可以从数据库中提取信息的有用性。可用的数据仅涉及描述每个示例的特征集。
最常见的方法之一是聚类。你可能已经经常听到这个术语被使用;当我们想要在我们的数据中找到簇时,我们主要使用它进行数据分析。这些簇通常是通过使用某种相似度度量来找到的,例如欧几里得距离。无监督学习在许多领域得到广泛应用,例如数据挖掘、医学成像、股票市场分析、计算机视觉和市场细分。
使用 k-means 算法进行聚类数据
k-means 算法是最受欢迎的聚类算法之一。此算法使用数据的各种属性将输入数据划分为 k 个子组。通过一种优化技术实现分组,我们试图最小化数据点与簇对应质心之间的距离平方和。
准备工作
在这个方法中,我们将使用 k-means 算法将数据分组到由相对质心标识的四个簇中。我们还将能够追踪边界以识别每个簇的相关区域。
如何做到这一点...
让我们看看如何使用 k-means 算法进行聚类数据分析:
- 此方法的完整代码在您已经提供的
kmeans.py文件中给出。现在让我们看看它是如何构建的。创建一个新的 Python 文件,并导入以下包:
import numpy as np
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans
- 现在让我们加载输入数据并定义簇的数量。我们将使用您已经提供的
data_multivar.txt文件:
input_file = ('data_multivar.txt')
# Load data
x = []
with open(input_file, 'r') as f:
for line in f.readlines():
data = [float(i) for i in line.split(',')]
x.append(data)
data = np.array(x)
num_clusters = 4
- 我们需要看看输入数据的样子。让我们继续在 Python 文件中添加以下代码行:
plt.figure()
plt.scatter(data[:,0], data[:,1], marker='o',
facecolors='none', edgecolors='k', s=30)
x_min, x_max = min(data[:, 0]) - 1, max(data[:, 0]) + 1
y_min, y_max = min(data[:, 1]) - 1, max(data[:, 1]) + 1
plt.title('Input data')
plt.xlim(x_min, x_max)
plt.ylim(y_min, y_max)
plt.xticks(())
plt.yticks(())
如果你运行此代码,你将得到以下输出:
- 我们现在准备好训练模型了。让我们初始化
kmeans对象并对其进行训练:
kmeans = KMeans(init='k-means++', n_clusters=num_clusters, n_init=10)
kmeans.fit(data)
- 数据训练完成后,我们需要可视化边界。让我们继续在 Python 文件中添加以下代码行:
# Step size of the mesh
step_size = 0.01
# Plot the boundaries
x_min, x_max = min(data[:, 0]) - 1, max(data[:, 0]) + 1
y_min, y_max = min(data[:, 1]) - 1, max(data[:, 1]) + 1
x_values, y_values = np.meshgrid(np.arange(x_min, x_max, step_size), np.arange(y_min, y_max, step_size))
# Predict labels for all points in the mesh
predicted_labels = kmeans.predict(np.c_[x_values.ravel(), y_values.ravel()])
- 我们刚刚在点网格上评估了模型。让我们绘制这些结果以查看边界:
# Plot the results
predicted_labels = predicted_labels.reshape(x_values.shape)
plt.figure()
plt.clf()
plt.imshow(predicted_labels, interpolation='nearest',
extent=(x_values.min(), x_values.max(), y_values.min(), y_values.max()),
cmap=plt.cm.Paired,
aspect='auto', origin='lower')
plt.scatter(data[:,0], data[:,1], marker='o',
facecolors='none', edgecolors='k', s=30)
- 现在让我们在它上面叠加
centroids:
centroids = kmeans.cluster_centers_
plt.scatter(centroids[:,0], centroids[:,1], marker='o', s=200, linewidths=3,
color='k', zorder=10, facecolors='black')
x_min, x_max = min(data[:, 0]) - 1, max(data[:, 0]) + 1
y_min, y_max = min(data[:, 1]) - 1, max(data[:, 1]) + 1
plt.title('Centoids and boundaries obtained using KMeans')
plt.xlim(x_min, x_max)
plt.ylim(y_min, y_max)
plt.xticks(())
plt.yticks(())
plt.show()
如果你运行此代码,你应该看到以下输出:
四个质心和它们的边界被充分突出显示。
它是如何工作的...
K-means 算法是由詹姆斯·麦克昆(James MacQueen)开发的,他在 1967 年设计它是为了根据对象的属性将对象分组到 k 个分区。它是 期望最大化(expectation-maximization,简称 EM)算法的一种变体,其目标是确定由高斯分布生成的 k 个数据点所属的组。两种算法之间的区别在于欧几里得距离计算方法。在 k-means 中,假设对象的属性可以表示为向量,从而形成一个向量空间。目标是使总簇内方差(或标准差)最小化。每个簇由一个质心来标识。
算法遵循以下迭代过程:
-
选择 k 个簇的数量
-
初始时创建 k 个分区,并将每个条目分区随机分配,或者使用一些启发式信息
-
计算每个组的质心
-
计算每个观测值与每个簇质心之间的距离
-
然后,通过将每个数据点与最近的质心关联来构建一个新的分区
-
新簇的质心被重新计算
-
重复步骤 4 到 6,直到算法收敛
更多内容...
该算法的目的是定位k个质心,每个聚类一个。每个质心的位置尤其重要,因为不同的位置会导致不同的结果。最佳选择是将它们尽可能地彼此分开。当这样做的时候,你必须将每个对象与最近的质心关联起来。这样,我们将得到一个初步分组。完成第一次循环后,我们通过重新计算新的k个质心作为簇的重心来进入下一个循环。一旦定位到这些新的k个质心,你需要在新数据集和新的最近质心之间建立新的连接。在这些操作结束时,执行一个新的循环。由于这个循环,我们可以注意到k个质心会逐步改变位置,直到它们被修改。因此,质心不再移动。
参考资料还有
-
参考官方文档中的
sklearn.cluster.KMeans函数:scikit-learn.org/stable/modules/generated/sklearn.cluster.KMeans.html#sklearn.cluster.KMeans.fit -
参考 Giuseppe Ciaburro 的《MATLAB for Machine Learning》,Packt Publishing
-
参考来自斯坦福大学的《K Means》
stanford.edu/~cpiech/cs221/handouts/kmeans.html -
参考 Andrew Moore 的《K-means and Hierarchical Clustering》
www.autonlab.org/tutorials/kmeans.html
使用矢量量化压缩图像
k 均值聚类的最主要应用之一是矢量量化。简单来说,矢量量化是四舍五入的N维版本。当我们处理一维数据,例如数字时,我们使用四舍五入技术来减少存储该值所需的内存。例如,如果我们只想精确到小数点后第二位,我们只需存储 23.73;或者,如果我们不关心小数位,我们可以只存储 24。这取决于我们的需求和愿意做出的权衡。
类似地,当我们将这个概念扩展到N维数据时,它就变成了矢量量化。当然,其中还有很多细微之处!矢量量化在图像压缩中非常流行,我们使用比原始图像更少的位数来存储每个像素,以达到压缩的目的。
准备工作
在这个菜谱中,我们将使用一个示例图像,然后我们将通过减少位数进一步压缩图像。
如何操作...
让我们看看如何使用矢量量化来压缩图像:
- 该菜谱的完整代码在已经提供给你的
vector_quantization.py文件中给出。让我们看看它是如何构建的。我们首先导入所需的包。创建一个新的 Python 文件,并添加以下行:
import argparse
import numpy as np
from scipy import misc
from sklearn import cluster
import matplotlib.pyplot as plt
- 让我们创建一个函数来解析输入参数。我们将能够传递图像和每像素位数作为输入参数:
def build_arg_parser():
parser = argparse.ArgumentParser(description='Compress the input image \
using clustering')
parser.add_argument("--input-file", dest="input_file", required=True,
help="Input image")
parser.add_argument("--num-bits", dest="num_bits", required=False,
type=int, help="Number of bits used to represent each pixel")
return parser
- 让我们创建一个函数来压缩输入图像:
def compress_image(img, num_clusters):
# Convert input image into (num_samples, num_features)
# array to run kmeans clustering algorithm
X = img.reshape((-1, 1))
# Run kmeans on input data
kmeans = cluster.KMeans(n_clusters=num_clusters, n_init=4, random_state=5)
kmeans.fit(X)
centroids = kmeans.cluster_centers_.squeeze()
labels = kmeans.labels_
# Assign each value to the nearest centroid and
# reshape it to the original image shape
input_image_compressed = np.choose(labels, centroids).reshape(img.shape)
return input_image_compressed
- 压缩图像后,我们需要看看它如何影响质量。让我们定义一个函数来绘制输出图像:
def plot_image(img, title):
vmin = img.min()
vmax = img.max()
plt.figure()
plt.title(title)
plt.imshow(img, cmap=plt.cm.gray, vmin=vmin, vmax=vmax)
- 我们现在准备好使用所有这些函数。让我们定义一个主要函数,它接受输入参数,处理它们,并提取输出图像:
if __name__=='__main__':
args = build_arg_parser().parse_args()
input_file = args.input_file
num_bits = args.num_bits
if not 1 <= num_bits <= 8:
raise TypeError('Number of bits should be between 1 and 8')
num_clusters = np.power(2, num_bits)
# Print compression rate
compression_rate = round(100 * (8.0 - args.num_bits) / 8.0, 2)
print("The size of the image will be reduced by a factor of", 8.0/args.num_bits)
print("Compression rate = " + str(compression_rate) + "%")
- 让我们加载输入图像:
# Load input image
input_image = misc.imread(input_file, True).astype(np.uint8)
# original image
plot_image(input_image, 'Original image')
- 现在,让我们使用输入参数来压缩这张图像:
# compressed image
input_image_compressed = compress_image(input_image, num_clusters)
plot_image(input_image_compressed, 'Compressed image; compression rate = '
+ str(compression_rate) + '%')
plt.show()
- 我们现在准备好运行代码;在你的终端上运行以下命令:
$ python vector_quantization.py --input-file flower_image.jpg --num-bits 4
返回以下结果:
The size of the image will be reduced by a factor of 2.0
Compression rate = 50.0%
输入图像看起来如下:
你应该得到一个压缩图像作为输出:
- 让我们进一步压缩图像,通过减少位数到
2。在你的终端上运行以下命令:
$ python vector_quantization.py --input-file flower_image.jpg --num-bits 2
返回以下结果:
The size of the image will be reduced by a factor of 4.0
Compression rate = 75.0%
你应该得到以下压缩图像作为输出:
- 如果你将位数减少到
1,你可以看到它将变成只有黑白两种颜色的二值图像。运行以下命令:
$ python vector_quantization.py --input-file flower_image.jpg --num-bits 1
返回以下结果:
The size of the image will be reduced by a factor of 8.0
Compression rate = 87.5%
你将得到以下输出:
我们已经看到,通过进一步压缩图像,图像质量已经大幅下降。
它是如何工作的...
向量量化是一种用于信号压缩、图像编码和语音的算法。我们使用几何标准(欧几里得距离)来寻找簇。因此,它是一个无监督训练的例子。这是一种通过原型向量的分布来建模概率密度函数的技术。向量量化通过使用与它们更接近的相似数量的点来将大量点(向量)划分为簇。每个簇由其质心点(如 k-means)表示。
更多内容...
向量量化算法可以用来将数据集划分为多个簇。该算法基于计算欧几里得距离来分配样本到它们所属的簇。算法包括以下步骤:
-
在开始时,所有向量都被分配到同一个簇,其质心是所有向量的平均值。
-
对于每个质心,引入一个扰动,生成两个新的簇中心。旧的代表性被丢弃。
-
每个载体根据最小距离标准重新分配到新的簇中。
-
新的代表性是分配给每个簇的向量的平均值。这些将成为簇的新中心。
-
如果满足结束条件,算法终止。如果不满足,返回步骤 2。
参见
-
参考官方文档的
sklearn.cluster.KMeans函数:scikit-learn.org/stable/modules/generated/sklearn.cluster.KMeans.html#sklearn.cluster.KMeans.fit -
参考论文《基于矢量量化算法的图像压缩:综述》:
pdfs.semanticscholar.org/24d2/db6db81f1000b74246d22641e83390fb1065.pdf -
参考官方文档的
Argparse 教程:docs.python.org/2/howto/argparse.html -
参考官方文档的
scipy.misc.imread函数:docs.scipy.org/doc/scipy/reference/generated/scipy.misc.imread.html
使用聚合聚类分组数据
在我们讨论层次聚类之前,我们需要了解层次聚类。层次聚类是指一系列通过连续分割或合并来创建树状聚类的聚类算法,它们使用树来表示。层次聚类算法可以是自底向上或自顶向下。那么,这意味着什么呢?在自底向上的算法中,每个数据点被视为一个单独的聚类,包含一个对象。然后这些聚类依次合并,直到所有聚类合并成一个巨大的聚类。这被称为聚合聚类。另一方面,自顶向下的算法从一个巨大的聚类开始,依次分割这些聚类,直到达到单个数据点。
准备工作
在层次聚类中,我们通过递归地使用自顶向下或自底向上的方式对实例进行分区来构建聚类。我们可以将这些方法分为以下几类:
-
聚合算法(自底向上):在这里,我们从单个统计单元获得解决方案。在每次迭代中,我们聚合最相关的统计单元,直到形成一个单一的聚类为止。
-
划分算法(自顶向下):在这里,所有单元属于同一个类别,并且与其它单元不相似的单元在每个后续迭代中添加到一个新的聚类中。
这两种方法都会产生一个树状图。这代表了一个嵌套的对象组,以及组之间变化的相似性水平。通过在所需的相似性水平处切割树状图,我们可以得到数据对象的聚类。聚类的合并或分割是通过一个相似性度量来完成的,该度量优化了一个标准。
如何做...
让我们看看如何使用聚合聚类来分组数据:
- 这个菜谱的完整代码在提供的
agglomerative.py文件中给出。现在让我们看看它是如何构建的。创建一个新的 Python 文件,并导入必要的包:
import numpy as np
import matplotlib.pyplot as plt
from sklearn.cluster import AgglomerativeClustering
from sklearn.neighbors import kneighbors_graph
- 让我们定义一个执行聚合聚类的函数:
def perform_clustering(X, connectivity, title, num_clusters=3, linkage='ward'):
plt.figure()
model = AgglomerativeClustering(linkage=linkage,
connectivity=connectivity, n_clusters=num_clusters)
model.fit(X)
- 让我们提取标签并指定图形中标记的形状:
# extract labels
labels = model.labels_
`
# specify marker shapes for different clusters
markers = '.vx'
- 遍历数据点并相应地使用不同的标记进行绘图:
for i, marker in zip(range(num_clusters), markers):
# plot the points belong to the current cluster
plt.scatter(X[labels==i, 0], X[labels==i, 1], s=50,
marker=marker, color='k', facecolors='none')
plt.title(title)
- 为了展示聚合聚类的优势,我们需要在空间上连接且空间上彼此靠近的数据点上运行它。我们希望连接的数据点属于同一簇,而不是仅仅空间上彼此靠近的数据点。现在让我们定义一个函数来获取螺旋线上的数据点集:
def get_spiral(t, noise_amplitude=0.5):
r = t
x = r * np.cos(t)
y = r * np.sin(t)
return add_noise(x, y, noise_amplitude)
- 在前面的函数中,我们向曲线添加了一些噪声,因为它增加了一些不确定性。让我们定义这个函数:
def add_noise(x, y, amplitude):
X = np.concatenate((x, y))
X += amplitude * np.random.randn(2, X.shape[1])
return X.T
- 现在让我们定义另一个函数来获取位于玫瑰曲线上的数据点:
def get_rose(t, noise_amplitude=0.02):
# Equation for "rose" (or rhodonea curve); if k is odd, then
# the curve will have k petals, else it will have 2k petals
k = 5
r = np.cos(k*t) + 0.25
x = r * np.cos(t)
y = r * np.sin(t)
return add_noise(x, y, noise_amplitude)
- 为了增加更多多样性,让我们也定义一个
hypotrochoid函数:
def get_hypotrochoid(t, noise_amplitude=0):
a, b, h = 10.0, 2.0, 4.0
x = (a - b) * np.cos(t) + h * np.cos((a - b) / b * t)
y = (a - b) * np.sin(t) - h * np.sin((a - b) / b * t)
return add_noise(x, y, 0)
- 我们现在准备好定义主函数:
if __name__=='__main__':
# Generate sample data
n_samples = 500
np.random.seed(2)
t = 2.5 * np.pi * (1 + 2 * np.random.rand(1, n_samples))
X = get_spiral(t)
# No connectivity
connectivity = None
perform_clustering(X, connectivity, 'No connectivity')
# Create K-Neighbors graph
connectivity = kneighbors_graph(X, 10, include_self=False)
perform_clustering(X, connectivity, 'K-Neighbors connectivity')
plt.show()
如果你运行此代码,如果我们不使用任何连通性,你将得到以下输出:
第二个输出图看起来如下:
如你所见,使用连通性功能使我们能够将相互连接的数据点分组,而不是根据它们的空间位置进行聚类。
它是如何工作的...
在聚合聚类中,每个观测开始于其簇,随后簇被合并。合并簇的策略如下:
-
伍德聚类最小化所有簇内平方差的和。
-
最大或完全链接用于最小化观测对簇之间的最大距离。
-
平均链接用于最小化所有观测对簇之间的距离的平均值。
-
单链接用于最小化观测对簇之间最近观察的距离。
还有更多…
为了决定哪些簇必须合并,有必要定义簇之间差异的度量。在大多数层次聚类方法中,使用特定的指标来量化两个元素对之间的距离,以及一个链接标准,它将两个元素集(簇)之间的差异定义为两个集中元素对之间距离的函数。
这些常用指标如下:
-
欧几里得距离
-
曼哈顿距离
-
均匀规则
-
马哈拉诺比斯距离,它通过变量不同的尺度和相关性来校正数据
-
两个向量之间的角度
-
汉明距离,它衡量将一个成员转换为另一个成员所需的最小替换次数
参见
-
请参阅
sklearn.cluster.AgglomerativeClustering函数的官方文档:scikit-learn.org/stable/modules/generated/sklearn.cluster.AgglomerativeClustering.html -
参考来自斯坦福大学的层次聚类(
nlp.stanford.edu/IR-book/html/htmledition/hierarchical-agglomerative-clustering-1.html)
评估聚类算法的性能
到目前为止,我们已经构建了不同的聚类算法,但还没有测量它们的性能。在监督学习中,将原始标签与预测值进行比较来计算它们的准确率。相比之下,在无监督学习中,我们没有标签,因此我们需要找到一种方法来衡量我们算法的性能。
准备工作
评估聚类算法的一个好方法是看簇之间的分离程度。簇是否分离得很好?簇中的数据点是否足够紧密?我们需要一个可以量化这种行为的指标。我们将使用一个称为轮廓系数的指标。这个分数为每个数据点定义;这个系数的定义如下:
在这里,x是当前数据点到同一簇中所有其他数据点的平均距离,而y是当前数据点到下一个最近簇中所有数据点的平均距离。
如何做...
让我们看看如何评估聚类算法的性能:
- 这个菜谱的完整代码在已经提供给你的
performance.py文件中给出。现在让我们看看它是如何构建的。创建一个新的 Python 文件,并导入以下包:
import numpy as np
import matplotlib.pyplot as plt
from sklearn import metrics
from sklearn.cluster import KMeans
- 让我们从已经提供给你的
data_perf.txt文件中加载输入数据:
input_file = ('data_perf.txt')
x = []
with open(input_file, 'r') as f:
for line in f.readlines():
data = [float(i) for i in line.split(',')]
x.append(data)
data = np.array(x)
- 为了确定最佳簇的数量,让我们遍历一系列值并查看它在何处达到峰值:
scores = []
range_values = np.arange(2, 10)
for i in range_values:
# Train the model
kmeans = KMeans(init='k-means++', n_clusters=i, n_init=10)
kmeans.fit(data)
score = metrics.silhouette_score(data, kmeans.labels_,
metric='euclidean', sample_size=len(data))
print("Number of clusters =", i)
print("Silhouette score =", score)
scores.append(score)
- 现在,让我们绘制图表以查看它在何处达到峰值:
# Plot scores
plt.figure()
plt.bar(range_values, scores, width=0.6, color='k', align='center')
plt.title('Silhouette score vs number of clusters')
# Plot data
plt.figure()
plt.scatter(data[:,0], data[:,1], color='k', s=30, marker='o', facecolors='none')
x_min, x_max = min(data[:, 0]) - 1, max(data[:, 0]) + 1
y_min, y_max = min(data[:, 1]) - 1, max(data[:, 1]) + 1
plt.title('Input data')
plt.xlim(x_min, x_max)
plt.ylim(y_min, y_max)
plt.xticks(())
plt.yticks(())
plt.show()
- 如果你运行这段代码,你将在终端上得到以下输出:
Number of clusters = 2
Silhouette score = 0.5290397175472954
Number of clusters = 3
Silhouette score = 0.5572466391184153
Number of clusters = 4
Silhouette score = 0.5832757517829593
Number of clusters = 5
Silhouette score = 0.6582796909760834
Number of clusters = 6
Silhouette score = 0.5991736976396735
Number of clusters = 7
Silhouette score = 0.5194660249299737
Number of clusters = 8
Silhouette score = 0.44937089046511863
Number of clusters = 9
Silhouette score = 0.3998899991555578
柱状图看起来如下:
就像这些分数一样,最佳配置是五个簇。让我们看看数据实际上看起来是什么样子:
我们可以直观地确认数据实际上有五个簇。我们只是以包含五个不同簇的小数据集为例。当你处理包含无法轻易可视化的高维数据的巨大数据集时,这种方法变得非常有用。
它是如何工作的...
sklearn.metrics.silhouette_score函数计算所有样本的平均轮廓系数。对于每个样本,计算两个距离:平均簇内距离(x)和平均最近簇距离(y)。一个样本的轮廓系数由以下方程给出:
实际上,y是样本与不包含样本的最近簇之间的距离。
更多内容...
最佳值为 1,最差值为-1。0 表示重叠的簇,而小于 0 的值表示该特定样本被错误地附加到簇中。
参见
-
请参考
sklearn.metrics.silhouette_score函数的官方文档:scikit-learn.org/stable/modules/generated/sklearn.metrics.silhouette_score.html -
参考维基百科上的轮廓(来自维基百科):
en.wikipedia.org/wiki/Silhouette_(clustering)
使用 DBSCAN 算法估计簇数
当我们讨论 k-means 算法时,我们看到了必须给出簇的数量作为输入参数之一。在现实世界中,我们不会有这样的信息可用。我们可以肯定地扫描参数空间,使用轮廓系数得分找出最佳簇数,但这将是一个昂贵的流程!一个返回我们数据中簇数的方法将是解决这个问题的绝佳解决方案。DBSCAN 正是为我们做到这一点。
准备工作
在这个菜谱中,我们将使用sklearn.cluster.DBSCAN函数执行 DBSCAN 分析。我们将使用与之前评估聚类算法性能(data_perf.txt)菜谱中相同的相同数据,以比较两种方法。
如何做...
让我们看看如何使用 DBSCAN 算法自动估计簇数:
- 此菜谱的完整代码已在
estimate_clusters.py文件中提供。现在让我们看看它是如何构建的。创建一个新的 Python 文件,并导入必要的包:
from itertools import cycle
import numpy as np
from sklearn.cluster import DBSCAN
from sklearn import metrics
import matplotlib.pyplot as plt
- 从
data_perf.txt文件中加载数据。这是我们在上一个菜谱中使用的相同文件,这将帮助我们比较同一数据集上的方法:
# Load data
input_file = ('data_perf.txt')
x = []
with open(input_file, 'r') as f:
for line in f.readlines():
data = [float(i) for i in line.split(',')]
x.append(data)
X = np.array(x)
- 我们需要找到最佳参数,所以让我们初始化几个变量:
# Find the best epsilon
eps_grid = np.linspace(0.3, 1.2, num=10)
silhouette_scores = []
eps_best = eps_grid[0]
silhouette_score_max = -1
model_best = None
labels_best = None
- 让我们扫描参数空间:
for eps in eps_grid:
# Train DBSCAN clustering model
model = DBSCAN(eps=eps, min_samples=5).fit(X)
# Extract labels
labels = model.labels_
- 对于每次迭代,我们需要提取性能指标:
# Extract performance metric
silhouette_score = round(metrics.silhouette_score(X, labels), 4)
silhouette_scores.append(silhouette_score)
print("Epsilon:", eps, " --> silhouette score:", silhouette_score)
- 我们需要存储最佳得分及其相关的 epsilon 值:
if silhouette_score > silhouette_score_max:
silhouette_score_max = silhouette_score
eps_best = eps
model_best = model
labels_best = labels
- 让我们现在绘制条形图,如下所示:
# Plot silhouette scores vs epsilon
plt.figure()
plt.bar(eps_grid, silhouette_scores, width=0.05, color='k', align='center')
plt.title('Silhouette score vs epsilon')
# Best params
print("Best epsilon =", eps_best)
- 让我们存储最佳模型和标签:
# Associated model and labels for best epsilon
model = model_best
labels = labels_best
- 一些数据点可能保持未分配。我们需要识别它们,如下所示:
# Check for unassigned datapoints in the labels
offset = 0
if -1 in labels:
offset = 1
- 按如下方式提取簇数:
# Number of clusters in the data
num_clusters = len(set(labels)) - offset
print("Estimated number of clusters =", num_clusters)
- 我们需要提取所有核心样本,如下所示:
# Extracts the core samples from the trained model
mask_core = np.zeros(labels.shape, dtype=np.bool)
mask_core[model.core_sample_indices_] = True
- 让我们可视化结果簇。我们将首先提取唯一标签并指定不同的标记:
# Plot resultant clusters
plt.figure()
labels_uniq = set(labels)
markers = cycle('vo^s<>')
- 现在,让我们遍历簇并使用不同的标记绘制数据点:
for cur_label, marker in zip(labels_uniq, markers):
# Use black dots for unassigned datapoints
if cur_label == -1:
marker = '.'
# Create mask for the current label
cur_mask = (labels == cur_label)
cur_data = X[cur_mask & mask_core]
plt.scatter(cur_data[:, 0], cur_data[:, 1], marker=marker,
edgecolors='black', s=96, facecolors='none')
cur_data = X[cur_mask & ~mask_core]
plt.scatter(cur_data[:, 0], cur_data[:, 1], marker=marker,
edgecolors='black', s=32)
plt.title('Data separated into clusters')
plt.show()
- 如果你运行此代码,你将在你的终端上得到以下输出:
Epsilon: 0.3 --> silhouette score: 0.1287
Epsilon: 0.39999999999999997 --> silhouette score: 0.3594
Epsilon: 0.5 --> silhouette score: 0.5134
Epsilon: 0.6 --> silhouette score: 0.6165
Epsilon: 0.7 --> silhouette score: 0.6322
Epsilon: 0.7999999999999999 --> silhouette score: 0.6366
Epsilon: 0.8999999999999999 --> silhouette score: 0.5142
Epsilon: 1.0 --> silhouette score: 0.5629
Epsilon: 1.0999999999999999 --> silhouette score: 0.5629
Epsilon: 1.2 --> silhouette score: 0.5629
Best epsilon = 0.7999999999999999
Estimated number of clusters = 5
这将生成以下条形图:
让我们看看标记的数据点和以下输出中用实心点标记的未分配数据点:
它是如何工作的...
DBSCAN 通过将数据点视为密集簇的组来工作。如果一个点属于一个簇,那么应该有很多其他点也属于同一个簇。我们可以控制的参数之一是此点与其他点之间的最大距离。这被称为epsilon。给定簇中的任意两点之间的距离不应超过 epsilon。这种方法的主要优点之一是它可以处理异常值。如果有一些点位于低密度区域中孤立存在,DBSCAN 将将这些点检测为异常值,而不是强迫它们进入一个簇。
更多内容…
DBSCAN 具有以下优缺点:
| 优点 | 缺点 |
|---|
|
-
它不需要事先知道簇的数量。
-
它可以找到任意形状的簇。
-
它只需要两个参数。
|
-
聚类的质量取决于其距离度量。
-
它无法对密度差异大的数据集进行分类。
|
参考信息
-
参考官方文档中的
sklearn.cluster.DBSCAN函数:scikit-learn.org/stable/modules/generated/sklearn.cluster.DBSCAN.html -
参考信息:DBSCAN(来自维基百科):
en.wikipedia.org/wiki/DBSCAN -
参考信息:基于密度的方法(来自布法罗大学):
cse.buffalo.edu/~jing/cse601/fa12/materials/clustering_density.pdf
在股票市场数据中寻找模式
让我们看看如何使用无监督学习进行股票市场分析。由于我们不知道有多少个簇,我们将在簇上使用一个称为亲和传播(AP)的算法。它试图为我们数据中的每个簇找到一个代表性的数据点,以及数据点对之间的相似度度量,并将所有数据点视为其各自簇的潜在代表,也称为示例。
准备工作
在这个菜谱中,我们将分析公司在指定时间段内的股票市场变化。我们的目标是找出哪些公司在时间上的报价行为相似。
如何操作...
让我们看看如何寻找股票市场数据中的模式:
- 此菜谱的完整代码已在提供的
stock_market.py文件中给出。现在让我们看看它是如何构建的。创建一个新的 Python 文件,并导入以下包:
import json
import sys
import pandas as pd
import numpy as np
from sklearn import covariance, cluster
- 我们需要一个包含所有符号及其相关名称的文件。这些信息位于提供的
symbol_map.json文件中。让我们按照以下方式加载它:
# Input symbol file
symbol_file = 'symbol_map.json'
- 让我们从
symbol_map.json文件中读取数据:
# Load the symbol map
with open(symbol_file, 'r') as f:
symbol_dict = json.loads(f.read())
symbols, names = np.array(list(symbol_dict.items())).T
- 现在让我们加载数据。我们将使用一个 Excel 文件(
stock_market_data.xlsx);这是一个多工作表文件,每个符号对应一个:
quotes = []
excel_file = 'stock_market_data.xlsx'
for symbol in symbols:
print('Quote history for %r' % symbol, file=sys.stderr)
quotes.append(pd.read_excel(excel_file, symbol))
- 由于我们需要一些特征点进行分析,我们将使用每天开盘价和收盘价之间的差异来分析数据:
# Extract opening and closing quotes
opening_quotes = np.array([quote.open for quote in quotes]).astype(np.float)
closing_quotes = np.array([quote.close for quote in quotes]).astype(np.float)
# The daily fluctuations of the quotes
delta_quotes = closing_quotes - opening_quotes
- 让我们构建一个图模型:
# Build a graph model from the correlations
edge_model = covariance.GraphicalLassoCV(cv=3)
- 在我们使用之前,我们需要标准化数据:
# Standardize the data
X = delta_quotes.copy().T
X /= X.std(axis=0)
- 现在让我们使用这些数据来训练模型:
# Train the model
with np.errstate(invalid='ignore'):
edge_model.fit(X)
- 现在我们已经准备好构建聚类模型,如下所示:
# Build clustering model using affinity propagation
_, labels = cluster.affinity_propagation(edge_model.covariance_)
num_labels = labels.max()
# Print the results of clustering
for i in range(num_labels + 1):
print "Cluster", i+1, "-->", ', '.join(names[labels == i])
- 如果你运行此代码,你将在终端得到以下输出:
Cluster 1 --> Apple, Amazon, Yahoo
Cluster 2 --> AIG, American express, Bank of America, DuPont de Nemours, General Dynamics, General Electrics, Goldman Sachs, GlaxoSmithKline, Home Depot, Kellogg
Cluster 3 --> Boeing, Canon, Caterpillar, Ford, Honda
Cluster 4 --> Colgate-Palmolive, Kimberly-Clark
Cluster 5 --> Cisco, Dell, HP, IBM
Cluster 6 --> Comcast, Cablevision
Cluster 7 --> CVS
Cluster 8 --> ConocoPhillips, Chevron
识别出八个簇。从初步分析中,我们可以看到,分组的公司似乎对待相同的产品:IT、银行、工程、洗涤剂和计算机。
它是如何工作的...
AP 是一种基于点(项目)之间传递消息概念的聚类算法。与 k-means 等聚类算法不同,AP 不需要预先定义聚类数量。AP 搜索输入集的代表成员(样本),实际上这些成员代表了各个簇。
AP 算法的核心是识别一组样本。在输入中,取数据对之间的相似性矩阵。数据作为消息交换实值,直到出现合适的样本,从而获得良好的簇。
更多内容…
为了执行 AP 聚类,使用了sklearn.cluster.affinity_propagation()函数。在具有相似相似度和偏好的训练样本的情况下,聚类中心和标签的分配取决于偏好。如果偏好小于相似度,则将返回单个聚类中心和每个样本的 0 标签。否则,每个训练样本将成为其聚类中心,并分配一个唯一的标记。
参见
-
参考官方文档中的
sklearn.cluster.affinity_propagation()函数:scikit-learn.org/stable/modules/generated/sklearn.cluster.affinity_propagation.html -
参考来自多伦多大学的亲和传播:通过传递消息聚类数据(
www.cs.columbia.edu/~delbert/docs/DDueck-thesis_small.pdf)。
构建客户细分模型
无监督学习的主要应用之一是市场细分。这是当我们不是始终有标记数据可用时的情况,但重要的是要对市场进行细分,以便人们可以针对个体群体。这在广告、库存管理、实施分销策略和大众媒体中非常有用。让我们来应用无监督学习到一个这样的用例,看看它如何有用。
准备工作
我们将处理一个批发商及其客户。我们将使用可用的数据archive.ics.uci.edu/ml/datasets/Wholesale+customers。电子表格包含客户对不同类型商品消费的数据,我们的目标是找到簇,以便他们可以优化他们的销售和分销策略。
如何实现它…
让我们看看如何构建一个客户细分模型:
- 这个菜谱的完整代码在已经提供给你的
customer_segmentation.py文件中给出。现在让我们看看它是如何构建的。创建一个新的 Python 文件,并导入以下包:
import csv
import numpy as np
from sklearn.cluster import MeanShift, estimate_bandwidth
import matplotlib.pyplot as plt
- 让我们加载
wholesale.csv文件中的输入数据,该文件已经提供给你:
# Load data from input file
input_file = 'wholesale.csv'
file_reader = csv.reader(open(input_file, 'rt'), delimiter=',')
X = []
for count, row in enumerate(file_reader):
if not count:
names = row[2:]
continue
X.append([float(x) for x in row[2:]])
# Input data as numpy array
X = np.array(X)
- 让我们构建一个均值漂移模型:
# Estimating the bandwidth
bandwidth = estimate_bandwidth(X, quantile=0.8, n_samples=len(X))
# Compute clustering with MeanShift
meanshift_estimator = MeanShift(bandwidth=bandwidth, bin_seeding=True)
meanshift_estimator.fit(X)
labels = meanshift_estimator.labels_
centroids = meanshift_estimator.cluster_centers_
num_clusters = len(np.unique(labels))
print("Number of clusters in input data =", num_clusters)
- 让我们按照以下方式打印我们获得的簇中心:
print("Centroids of clusters:")
print('\t'.join([name[:3] for name in names]))
for centroid in centroids:
print('\t'.join([str(int(x)) for x in centroid]))
- 让我们可视化一些特征以获得输出的感觉:
# Visualizing data
centroids_milk_groceries = centroids[:, 1:3]
# Plot the nodes using the coordinates of our centroids_milk_groceries
plt.figure()
plt.scatter(centroids_milk_groceries[:,0], centroids_milk_groceries[:,1],
s=100, edgecolors='k', facecolors='none')
offset = 0.2
plt.xlim(centroids_milk_groceries[:,0].min() - offset * centroids_milk_groceries[:,0].ptp(),
centroids_milk_groceries[:,0].max() + offset * centroids_milk_groceries[:,0].ptp(),)
plt.ylim(centroids_milk_groceries[:,1].min() - offset * centroids_milk_groceries[:,1].ptp(),
centroids_milk_groceries[:,1].max() + offset * centroids_milk_groceries[:,1].ptp())
plt.title('Centroids of clusters for milk and groceries')
plt.show()
- 如果你运行此代码,你将在终端上得到以下输出:
你将得到以下输出,描述了特征的中心点,牛奶和杂货,其中牛奶位于x轴上,杂货位于y轴上:
在这个输出中,已识别簇的八个中心点被清晰地表示出来。
它是如何工作的…
在这个菜谱中,我们通过使用均值漂移算法来面对聚类问题。这是一种通过移动点到众数的方式来迭代地将数据点分配到簇中的聚类类型。众数是出现频率最高的值。
算法迭代地将每个数据点分配到最近簇的中心。最近簇的中心由大多数邻近点所在的位置确定。因此,在每次迭代中,每个数据点都会接近位于最多点的地方,这即是或会导致簇中心。当算法停止时,每个点都会被分配到一个簇中。与 k-means 算法不同,均值漂移算法不需要事先指定簇的数量;这是由算法自动确定的。均值漂移算法在图像处理和人工智能领域得到了广泛的应用。
还有更多…
为了执行均值漂移聚类,使用了sklearn.cluster.MeanShift()函数。此函数使用平坦核执行均值漂移聚类。均值漂移聚类使我们能够在均匀密度的样本中识别点聚集。候选中心点通过给定区域内点的平均值进行更新。然后,在后期处理阶段过滤这些点以消除可能的重复,形成最终的中心点集。
参考以下内容
-
参考官方文档的
sklearn.cluster.MeanShift()函数:scikit-learn.org/stable/modules/generated/sklearn.cluster.MeanShift.html#sklearn-cluster-meanshift -
参考由 Dorin Comaniciu 和 Peter Meer 撰写的《Mean Shift: A Robust Approach Toward Feature Space Analysis》(
courses.csail.mit.edu/6.869/handouts/PAMIMeanshift.pdf)
使用自动编码器重建手写数字图像
自动编码器是一种神经网络,其目的是将输入编码到小维度,并使获得的结果能够重建输入本身。自动编码器由以下两个子网络组成:编码器和解码器。添加了一个损失函数到这些函数中,它是数据压缩表示和分解表示之间信息损失量的距离。编码器和解码器将与距离函数可微分,因此编码和解码函数的参数可以被优化以最小化重建损失,使用梯度随机化。
准备工作
手写识别(HWR)在现代技术中得到了广泛应用。书写文本图像可以通过光学扫描(光学字符识别,OCR)或智能文字识别从纸张上离线获取。书法识别展示了计算机接收和解释手写输入的能力,这些输入可以从纸张文件、触摸屏、照片和其他设备等来源理解。HWR 包括各种技术,通常需要 OCR。然而,一个完整的脚本识别系统还管理格式,执行正确的字符分割,并找到最可能的单词。
修改后的国家标准与技术研究院(MNIST)是一个包含手写数字的大型数据库。它包含 70,000 个数据示例。它是 MNIST 更大数据集的一个子集。数字的分辨率为 28 x 28 像素,存储在一个 70,000 行和 785 列的矩阵中;784 列形成 28 x 28 矩阵中每个像素值,一个值是实际的数字。这些数字已经被尺寸归一化和居中在固定大小的图像中。
如何做到这一点...
让我们看看如何构建自动编码器来重建手写数字图像:
- 此菜谱的完整代码已提供在
AutoencMnist.py文件中。让我们看看它是如何构建的。创建一个新的 Python 文件,并导入以下包:
from keras.datasets import mnist
- 要导入 MNIST 数据集,必须使用以下代码:
(XTrain, YTrain), (XTest, YTest) = mnist.load_data()
print('XTrain shape = ',XTrain.shape)
print('XTest shape = ',XTest.shape)
print('YTrain shape = ',YTrain.shape)
print('YTest shape = ',YTest.shape)
- 导入数据集后,我们打印了数据的形状,以下结果被返回:
XTrain shape = (60000, 28, 28)
XTest shape = (10000, 28, 28)
YTrain shape = (60000,)
YTest shape = (10000,)
- 数据库中的 70,000 个项目被分为 60,000 个项目用于训练,10,000 个项目用于测试。数据输出由 0 到 9 范围内的整数表示。让我们如下进行检查:
import numpy as np
print('YTrain values = ',np.unique(YTrain))
print('YTest values = ',np.unique(YTest))
- 以下结果将被打印:
YTrain values = [0 1 2 3 4 5 6 7 8 9]
YTest values = [0 1 2 3 4 5 6 7 8 9]
- 分析可用的数组中两个值的分布可能是有用的。首先,我们计算出现的次数:
unique, counts = np.unique(YTrain, return_counts=True)
print('YTrain distribution = ',dict(zip(unique, counts)))
unique, counts = np.unique(YTest, return_counts=True)
print('YTrain distribution = ',dict(zip(unique, counts)))
- 以下结果被返回:
YTrain distribution = {0: 5923, 1: 6742, 2: 5958, 3: 6131, 4: 5842, 5: 5421, 6: 5918, 7: 6265, 8: 5851, 9: 5949}
YTrain distribution = {0: 980, 1: 1135, 2: 1032, 3: 1010, 4: 982, 5: 892, 6: 958, 7: 1028, 8: 974, 9: 1009}
- 我们也可以在图中看到它,如下所示:
import matplotlib.pyplot as plt
plt.figure(1)
plt.subplot(121)
plt.hist(YTrain, alpha=0.8, ec='black')
plt.xlabel("Classes")
plt.ylabel("Number of occurrences")
plt.title("YTrain data")
plt.subplot(122)
plt.hist(YTest, alpha=0.8, ec='black')
plt.xlabel("Classes")
plt.ylabel("Number of occurrences")
plt.title("YTest data")
plt.show()
- 为了比较在两个输出数据集(
YTrain和YTest)上获得的结果,我们绘制并并排显示了两个直方图,如下所示:
-
从前面的输出分析中,我们可以看到在两个数据集中,10 个数字以相同的比例表示。事实上,柱状图似乎具有相同的尺寸,即使垂直轴有不同的范围。
-
现在,我们必须将所有值归一化到 0 和 1 之间:
XTrain = XTrain.astype('float32') / 255
XTest = XTest.astype('float32') / 255
- 为了降低维度,我们将 28 x 28 的图像展平成大小为 784 的向量:
XTrain = XTrain.reshape((len(XTrain), np.prod(XTrain.shape[1:])))
XTest = XTest.reshape((len(XTest), np.prod(XTest.shape[1:])))
- 现在,我们将使用 Keras 功能 API 构建模型。让我们开始导入库:
from keras.layers import Input
from keras.layers import Dense
from keras.models import Model
- 然后,我们可以构建 Keras 模型,如下所示:
InputModel = Input(shape=(784,))
EncodedLayer = Dense(32, activation='relu')(InputModel)
DecodedLayer = Dense(784, activation='sigmoid')(EncodedLayer)
AutoencoderModel = Model(InputModel, DecodedLayer)
AutoencoderModel.summary()
以下输出显示了模型架构:
- 因此,我们必须为训练配置模型。为此,我们将使用
compile方法,如下所示:
AutoencoderModel.compile(optimizer='adadelta', loss='binary_crossentropy')
- 在这一点上,我们可以训练模型,如下所示:
history = AutoencoderModel.fit(XTrain, XTrain,
batch_size=256,
epochs=100,
shuffle=True,
validation_data=(XTest, XTest))
- 我们的模式现在准备好了,我们可以使用它来自动重建手写数字。为此,我们将使用
predict()方法:
DecodedDigits = AutoencoderModel.predict(XTest)
- 现在,我们已经完成了;模型已经被训练,并将随后用于做出预测。因此,我们可以打印出起始的手写数字以及从我们的模型中重建的数字。当然,我们只会对数据集中包含的 60,000 个数字中的部分进行操作。实际上,我们将限制自己只显示前五个;在这种情况下,我们还将使用
matplotlib库:
n=5
plt.figure(figsize=(20, 4))
for i in range(n):
ax = plt.subplot(2, n, i + 1)
plt.imshow(XTest[i+10].reshape(28, 28))
plt.gray()
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
ax = plt.subplot(2, n, i + 1 + n)
plt.imshow(DecodedDigits[i+10].reshape(28, 28))
plt.gray()
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
plt.show()
结果如下所示:
如您在前面的输出中看到的,结果非常接近原始数据,这意味着模型运行良好。
它是如何工作的...
自编码器是一种神经网络,其目的是将输入编码到小维度,并得到的结果以便能够重建输入本身。自编码器由以下两个子网络的组合组成。
首先,我们有一个编码器,它计算以下函数:
给定一个x输入,编码器将其编码到 z 变量中,这个变量也称为潜在变量。z通常比x小得多。
第二,我们有一个解码器,它计算以下函数:
由于 z 是编码器产生的 x 的代码,解码器必须将其解码,以便 x' 与 x 相似。自动编码器的训练旨在最小化输入和结果之间的均方误差。
还有更多...
Keras 是一个 Python 库,它提供了一种简单且清晰的方式来创建各种深度学习模型。Keras 代码是在 MIT 许可证下发布的。Keras 基于简约和简洁的原则构建,它提供了一个无装饰的编程模型,以最大化可读性。它允许以非常模块化的方式表达神经网络,将模型视为一个序列或单个图。
参考以下内容
-
请参阅 Keras 库的官方文档:
keras.io/ -
请参阅《Keras 2.x Projects》,作者 Giuseppe Ciaburro,Packt Publishing 出版。