作者:John Wittenauer
翻译:GreatX
源:Machine Learning Exercises In Python, Part 7
这篇文章是一系列 Andrew Ng 在 Coursera 上的机器学习课程的练习的一部分。这篇文章的原始代码,练习文本,数据文件可从这里获得。
Part 1 简单线性回归(Simple Linear Regression)
Part 2 多元线性回归(Multivariate Linear Regression)
Part 3 逻辑回归(Logistic Regression)
Part 4 多元逻辑回归(Multivariate Logistic Regression)
Part 5 神经网络(Neural Networks)
Part 6 支持向量机(Support Vector Machines)
Part 7 K-均值聚类与主成分分析(K-Means Clustering & PCA)
Part 8 异常检测与推荐(Anomaly Detection & Recommendation)
现在我们到了本系列最后两篇文章了!在本部分,我们将会讨论两个有趣的主题: K-means 聚类和主成分分析(PCA)。K-means 和 PCA 都属于无监督学习( unsupervised learning)技术。 无监督学习问题没有任何标签或目标让我们学习,从而不能从中作出预测,因此无监督算法试图从数据本身学习到一些感兴趣的内容。 我们将首先实现 K-means,并看看如何使用它进行图像压缩。 我们还将用 PCA 进行实验,来找到面部图像的低维表示。 一如往常,使用练习文本将有助于我们更好的理解(上传在此)。
首先,我们将实现和应用 K-means 到一个简单的 2 维数据集,以获得一些关于它工作原理的直觉。 K-means 是一种迭代的,无监督的聚类算法,将相似的实例组成簇。 该算法通过猜测每个聚类的中心开始,然后重复地将实例分配给最近的聚类并重新计算该聚类的中心。 我们首先要实现是一个找出每个实例最近的中心的函数。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sb
from scipy.io import loadmat
%matplotlib inline
def find_closest_centroids(X, centroids):
m = X.shape[0]
k = centroids.shape[0]
idx = np.zeros(m)
for i in range(m):
min_dist = 1000000
for j in range(k):
dist = np.sum((X[i,:] - centroids[j,:]) ** 2)
if dist < min_dist:
min_dist = dist
idx[i] = j
return idx
让我们来测试一下这个函数以确认它按照我们预期的方式运行。我们将使用本次练习中提供的测试样例。
data = loadmat('data/ex7data2.mat')
X = data['X']
initial_centroids = initial_centroids = np.array([[3, 3], [6, 2], [8, 5]])
idx = find_closest_centroids(X, initial_centroids)
idx[0:3]
array([ 0., 2., 1.])
该输出和文本中的期望值相匹配(记住,我们的数组索引是从 0 开始,而不是从 1 开始, 所以数组中值比练习中的低一位)。 接下来,我们需要一个函数来计算聚类中心。中心仅仅意味着当前分配给聚类的所有样本点的均值。
def compute_centroids(X, idx, k):
m, n = X.shape
centroids = np.zeros((k, n))
for i in range(k):
indices = np.where(idx == i)
centroids[i,:] = (np.sum(X[indices,:], axis=1) / len(indices[0])).ravel()
return centroids
compute_centroids(X, idx, 3)
array([[ 2.42830111, 3.15792418],
[ 5.81350331, 2.63365645],
[ 7.11938687, 3.6166844 ]])
此输出也与练习中的预期值相匹配。 到现在为止,一切进展顺利。 下一部分就是实际运行算法来进行几次迭代和可视化结果。 这个步骤在练习中已经实现了,但由于它不是那么复杂,我将从头开始实现它。 为了运行算法,我们只需交替进行分配样本点到最近的聚类和重新计算聚类中心这两步。
def run_k_means(X, initial_centroids, max_iters):
m, n = X.shape
k = initial_centroids.shape[0]
idx = np.zeros(m)
centroids = initial_centroids
for i in range(max_iters):
idx = find_closest_centroids(X, centroids)
centroids = compute_centroids(X, idx, k)
return idx, centroids
idx, centroids = run_k_means(X, initial_centroids, 10)
我们现在可以使用颜色编码来绘制结果,以显示聚类成员。
cluster1 = X[np.where(idx == 0)[0],:]
cluster2 = X[np.where(idx == 1)[0],:]
cluster3 = X[np.where(idx == 2)[0],:]
fig, ax = plt.subplots(figsize=(12,8))
ax.scatter(cluster1[:,0], cluster1[:,1], s=30, color='r', label='Cluster 1')
ax.scatter(cluster2[:,0], cluster2[:,1], s=30, color='g', label='Cluster 2')
ax.scatter(cluster3[:,0], cluster3[:,1], s=30, color='b', label='Cluster 3')
ax.legend()
在这里,我们跳过了初始化聚类中心这一步。 这会影响算法的收敛。 我们的任务是创建一个选择随机样本点并将它们用作初始中心的函数。
def init_centroids(X, k):
m, n = X.shape
centroids = np.zeros((k, n))
idx = np.random.randint(0, m, k)
for i in range(k):
centroids[i,:] = X[idx[i],:]
return centroids
init_centroids(X, 3)
array([[ 1.15354031, 4.67866717],
[ 6.27376271, 2.24256036],
[ 2.20960296, 4.91469264]]
我们的下一个任务是将 K-means 应用于图像压缩。 这里凭直觉我们感到,可以使用聚类来找到图像中最具代表性的少量颜色,并使用聚类分配将原始的 24 位颜色映射到较低维的颜色空间。 下面是我们要压缩的图像:
原始像素数据已经预先加载了,让我们 pull 一下。
image_data = loadmat('data/bird_small.mat')
image_data
{'A': array([[[219, 180, 103],
[230, 185, 116],
[226, 186, 110],
...,
[ 14, 15, 13],
[ 13, 15, 12],
[ 12, 14, 12]],
...,
[[ 15, 19, 19],
[ 20, 20, 18],
[ 18, 19, 17],
...,
[ 65, 43, 39],
[ 58, 37, 38],
[ 52, 39, 34]]], dtype=uint8),
'__globals__': [],
'__header__': 'MATLAB 5.0 MAT-file, Platform: GLNXA64, Created on: Tue Jun 5 04:06:24 2012',
'__version__': '1.0'}
我们可以快速地看一下数据的 shape,以验证它与我们所期望的图像一致。
A = image_data['A']
A.shape
(128L, 128L, 3L)
现在,我们将数据进行一些预处理,然后将它送入 K-means 算法。
# normalize value ranges
A = A / 255.
# reshape the array
X = np.reshape(A, (A.shape[0] * A.shape[1], A.shape[2]))
# randomly initialize the centroids
initial_centroids = init_centroids(X, 16)
# run the algorithm
idx, centroids = run_k_means(X, initial_centroids, 10)
# get the closest centroids one last time
idx = find_closest_centroids(X, centroids)
# map each pixel to the centroid value
X_recovered = centroids[idx.astype(int),:]
# reshape to the original dimensions
X_recovered = np.reshape(X_recovered, (A.shape[0], A.shape[1], A.shape[2]))
plt.imshow(X_recovered)
Cool! 你可以看到我们在压缩中创造了一些工件,但是图像的主要特征还在,尽管原始图像被映射为了 16 位颜色。这就是 K-means。接下来,我们进行主成分分析(PCA)。
PCA 是一种线性变换,其目的是找到数据集中的“主分量”或最大方差方向。 除此之外,它还可以用于降维。 在本练习中,我们首先要实现并应用 PCA 于一个简单的 2 维数据集,以了解它是如何工作的。 首先,让我们加载和可视化数据集。
data = loadmat('data/ex7data1.mat')
X = data['X']
fig, ax = plt.subplots(figsize=(12,8))
ax.scatter(X[:, 0], X[:, 1])
PCA 的算法相当简单。 在确保数据被归一化之后,其输出就是原始数据协方差矩阵的奇异值分解(singular value decomposition)。 由于 numpy 已经有内置函数来计算矩阵的协方差和 SVD ,我们将使用这些函数而不是从头构建。
def pca(X):
# normalize the features
X = (X - X.mean()) / X.std()
# compute the covariance matrix
X = np.matrix(X)
cov = (X.T * X) / X.shape[0]
# perform SVD
U, S, V = np.linalg.svd(cov)
return U, S, V
U, S, V = pca(X)
U, S, V
(matrix([[-0.79241747, -0.60997914],
[-0.60997914, 0.79241747]]),
array([ 1.43584536, 0.56415464]),
matrix([[-0.79241747, -0.60997914],
[-0.60997914, 0.79241747]]))
现在我们有了主成分(矩阵 U),我们就可以用它们将原始数据投影到低维空间。 对于这个任务,我们将实现一个计算该投影并只选择前 K 个分量的函数,从而有效地减少了维数。
def project_data(X, U, k):
U_reduced = U[:,:k]
return np.dot(X, U_reduced)
Z = project_data(X, U, 1)
Z
matrix([[-4.74689738],
[-7.15889408],
[-4.79563345],
[-4.45754509],
[-4.80263579],
...,
[-6.44590096],
[-2.69118076],
[-4.61386195],
[-5.88236227],
[-7.76732508]])
我们也可以试图从相反的步骤来恢复原始数据。
def recover_data(Z, U, k):
U_reduced = U[:,:k]
return np.dot(Z, U_reduced.T)
X_recovered = recover_data(Z, U, 1)
X_recovered
matrix([[ 3.76152442, 2.89550838],
[ 5.67283275, 4.36677606],
[ 3.80014373, 2.92523637],
[ 3.53223661, 2.71900952],
[ 3.80569251, 2.92950765],
...,
[ 5.10784454, 3.93186513],
[ 2.13253865, 1.64156413],
[ 3.65610482, 2.81435955],
[ 4.66128664, 3.58811828],
[ 6.1549641 , 4.73790627]])
如果我们试图可视化恢复后的数据,那么算法工作原理背后的直觉就变得非常明显。
fig, ax = plt.subplots(figsize=(12,8))
ax.scatter(X_recovered[:, 0], X_recovered[:, 1])
注意,这些点似乎被压缩到一条隐形的线上。 该隐形的线本质上是第一主成分。 第二主成分,在我们将数据减少到一个维度时被丢弃,可以被认为是与该线正交的变量。 由于我们失去了这些信息,因此我们的重建只是相对于第一主成分而言的(只有与第一分量相关的点)。
在这个练习中,最后一个任务是对面部图像应用 PCA。 通过使用相同的降维技术,我们可以使用比原始图像少得多的数据捕获图像的“本质”。
faces = loadmat('data/ex7faces.mat')
X = faces['X']
X.shape
(5000L, 1024L)
练习代码中包含了一个以网格形式呈现数据集中的前 100 个面部图像的函数。 这里我们就不再重建该函数了,你可以在练习文本中查看他们的样子。 但我们至少可以很容易地呈现一个图像。
face = np.reshape(X[3,:], (32, 32))
plt.imshow(face)
呀,这看上去有点可怕! 这些只是 32×32 的灰度图像(它也呈现了侧面,但我们现在可以忽略)。 我们下一步是在面部数据集上运行 PCA,并获取前 100 个主成分。
U, S, V = pca(X)
Z = project_data(X, U, 100)
现在我们可以尝试恢复原始结构并再次呈现它。
X_recovered = recover_data(Z, U, 100)
face = np.reshape(X_recovered[3,:], (32, 32))
plt.imshow(face)
注意,我们失去了一些细节,虽然没有像预期的那样维度减少 10 倍。
练习 7 结束! 在最后一个练习中,我们将实现异常检测的算法,并使用协同过滤(collaborative filtering)构建一个推荐系统。