实现 K-means 算法,并将其用于图像压缩。
import numpy as np
import matplotlib.pyplot as plt
from utils import *
%matplotlib inline
K-means 算法是一种自动将相似数据点聚合在一起的方法。
具体而言,给定一个训练集 { x ( 1 ) , . . . , x ( m ) } \{x^{(1)}, ..., x^{(m)}\} {x(1),...,x(m)},您想将数据分成几个连贯的“簇”。
K-means 是一个迭代过程,它
伪代码中,K-means 算法如下:
# 初始化质心
# K 是簇的数量
centroids = kMeans_init_centroids(X, K)
for iter in range(iterations):
# 聚类分配步骤:
# 将每个数据点分配到最近的质心
# idx[i] 对应于分配给第 i 个样本的质心的索引
idx = find_closest_centroids(X, centroids)
# 移动质心步骤:
# 根据质心分配计算平均值
centroids = compute_means(X, idx, K)
算法的内循环反复执行两个步骤:
K K K-means 算法将总是收敛到一些中心点的最终集合。
但是,收敛解决方案可能并不总是理想的,并且取决于质心的初始设置。
您将在接下来的部分单独实现 K-means 算法的两个阶段。
find_closest_centroid
,然后继续完成 compute_centroids
。在 K-means 算法的“聚类分配”阶段,算法会根据质心的当前位置将每个训练样本 x ( i ) x^{(i)} x(i) 分配给其最近的质心。
您的任务是完成find_closest_centroids
函数中的代码。
X
和所有质心的位置 centroids
idx
(它具有与 X
相同数量的元素),该数组保存每个训练示例距离最近质心的索引(取值为 { 1 , . . . , K } \{1,...,K\} {1,...,K},其中 K K K 是总质心数)。idx [i]
),而centroids
中)def find_closest_centroids(x,centroids):
# 变量idx代表每个样本点所属的最近簇(centroids)的索引值
idx = np.zeros(x.shape[0],dtype = int)
# 遍历所有的点
for i in range(x.shape[0]):
distance = []
for j in range(centroids.shape[0]):
# np.linaly.norm() norm:范数。默认是2,计算欧式距离 平方和开根号
norm_ij = np.linalg.norm(x[i] - centroids[j])
distance.append(norm_ij)
# np.argmin返回的是最小距离那个点所对的索引
idx[i] = np.argmin(distance)
return idx
x = load_data()
代码解析
test_x = x[:3]
init = np.array([[3,3], [6,2], [8,5]])
idx = np.zeros(test_x.shape[0],dtype=int)
for i in range(test_x.shape[0]):
distance = []
for j in range(init.shape[0]):
distance.append(np.linalg.norm(x[i]-init[j]))
print(f"第{i+1}个数据点与第{j+1}个簇点之间的距离:{distance[j]}")
idx[i] = np.argmin(distance)
print(f"第{i+1}个数据点属于第{idx[i]+1}个簇点")
第1个数据点与第1个簇点之间的距离:1.981178002041907
第1个数据点与第2个簇点之间的距离:4.907925457663594
第1个数据点与第3个簇点之间的距离:6.170412024053506
第1个数据点属于第1个簇点
第2个数据点与第1个簇点之间的距离:3.2105972663230307
第2个数据点与第2个簇点之间的距离:2.820702783295523
第2个数据点与第3个簇点之间的距离:2.3499462509952416
第2个数据点属于第3个簇点
第3个数据点与第1个簇点之间的距离:3.365171876090534
第3个数据点与第2个簇点之间的距离:1.33813946654494
第3个数据点与第3个簇点之间的距离:2.373852261323399
第3个数据点属于第2个簇点
给定每个点与质心的分配,算法的第二阶段重新计算了每个质心所分配到的点的平均值。
请完成下面的compute_centroids
函数以重新计算每个质心的值:
μ k = 1 ∣ C k ∣ ∑ i ∈ C k x ( i ) \mu_k = \frac{1}{|C_k|} \sum_{i \in C_k} x^{(i)} μk=∣Ck∣1i∈Ck∑x(i)
其中
C k C_k Ck 是分配给质心 k k k的示例集合
∣ C k ∣ |C_k| ∣Ck∣ 是集合 C k C_k Ck中示例的数量
具体来说,如果两个示例 x ( 3 ) x^{(3)} x(3)和 x ( 5 ) x^{(5)} x(5)被分配给质心 k = 2 k=2 k=2,则应更新 μ 2 = 1 2 ( x ( 3 ) + x ( 5 ) ) \mu_2 = \frac{1}{2}(x^{(3)}+x^{(5)}) μ2=21(x(3)+x(5))。
def compute_centroids(x,idx,k):
m,n = x.shape
centroids = np.zeros((k,n))
for k in range(k):
points = x[idx == k]
# np.mean() 按行计算平均值
centroids[k] = np.mean(points,axis=0)
return centroids
m,n = test_x.shape
k_num = 3
test_centroids = np.zeros((k_num,n))
print(idx)
print(f"x:{test_x}")
for k_num in range(k_num):
points = test_x[idx == k_num]
print(f"第{k_num}个簇点,points:{points}")
test_centroids[k_num] = np.mean(points,axis=0)
print(f"簇点:{test_centroids}")
[0 2 1]
x:[[1.84207953 4.6075716 ]
[5.65858312 4.79996405]
[6.35257892 3.2908545 ]]
第0个簇点,points:[[1.84207953 4.6075716 ]]
簇点:[[1.84207953 4.6075716 ]
[0. 0. ]
[0. 0. ]]
第1个簇点,points:[[6.35257892 3.2908545 ]]
簇点:[[1.84207953 4.6075716 ]
[6.35257892 3.2908545 ]
[0. 0. ]]
第2个簇点,points:[[5.65858312 4.79996405]]
簇点:[[1.84207953 4.6075716 ]
[6.35257892 3.2908545 ]
[5.65858312 4.79996405]]
当您完成了上面两个函数(find_closest_centroids
和compute_centroids
)后,下一步是在一个玩具2D数据集上运行K-means算法,以帮助您理解K-means的工作原理。
run_kMeans
)以了解它的工作方式。当您运行下面的代码时,它将产生一个可视化图形,展示算法每次迭代的进展情况。
def run_kMeans(x,initial_centroids,max_iters =10,plot_progress = False):
#初始化
m, n = x.shape
K = initial_centroids.shape[0]
centroids = initial_centroids
previous_centroids = centroids
idx = np.zeros(m)
# 运行K-means
for i in range(max_iters):
#输出过程
print("K-Means iteration %d/%d" % (i, max_iters-1))
# 对于 X 中的每个示例,将其分配给最接近的质心
idx = find_closest_centroids(x, centroids)
# 绘制过程
if plot_progress:
plot_progress_kMeans(x, centroids, previous_centroids, idx, K, i)
previous_centroids = centroids
# 计算新的质点
centroids = compute_centroids(x, idx, K)
plt.show()
return centroids, idx
x = load_data()
initial_centroids = np.array([[3,3],[6,2],[8,5]])
K = 3
max_iters = 10
centroids, idx = run_kMeans(x, initial_centroids, max_iters, plot_progress=True)
K-Means iteration 0/9
K-Means iteration 1/9
K-Means iteration 2/9
K-Means iteration 3/9
K-Means iteration 4/9
K-Means iteration 5/9
K-Means iteration 6/9
K-Means iteration 7/9
K-Means iteration 8/9
K-Means iteration 9/9
为了使您在示例数据集的初始质心分配中看到与“图1”相同的图形,该图形的设计是这样的。在实践中,初始化质心的一个良好策略是从训练集中选择随机样本。
在此练习的这部分中,您应该理解如何实现kMeans_init_centroids
函数。
np.random.permutation()
)。def kMeans_init_centroids(x,k):
# 先将数据随机排列
randidx = np.random.permutation(x.shape[0])
# 初始簇取随机排列数组前K个
centroids = x[randidx[:k]]
return centroids
np.random.permutation(x.shape[0])
array([ 75, 26, 162, 111, 270, 85, 4, 171, 250, 135, 25, 144, 217,
130, 136, 104, 143, 126, 50, 196, 148, 45, 272, 10, 175, 49,
125, 179, 221, 169, 296, 1, 282, 204, 197, 241, 78, 290, 205,
18, 77, 189, 283, 40, 299, 247, 3, 41, 226, 74, 30, 63,
22, 249, 281, 16, 102, 20, 147, 245, 214, 211, 201, 122, 53,
220, 252, 218, 73, 255, 83, 229, 219, 159, 206, 181, 156, 109,
101, 133, 216, 64, 209, 231, 132, 31, 27, 58, 59, 150, 55,
225, 110, 149, 103, 91, 273, 105, 295, 33, 277, 224, 268, 182,
200, 235, 262, 15, 127, 119, 86, 285, 57, 35, 165, 80, 167,
51, 163, 173, 146, 43, 2, 198, 276, 62, 244, 120, 227, 138,
96, 48, 236, 258, 203, 240, 199, 82, 195, 68, 246, 92, 188,
164, 155, 153, 187, 183, 280, 141, 113, 284, 174, 260, 215, 267,
213, 23, 112, 116, 266, 34, 99, 178, 297, 210, 137, 29, 87,
117, 230, 21, 288, 145, 44, 191, 192, 168, 121, 60, 271, 0,
237, 291, 251, 157, 89, 264, 242, 32, 142, 177, 39, 294, 234,
95, 186, 61, 71, 66, 17, 129, 90, 134, 222, 7, 93, 56,
265, 298, 52, 176, 128, 70, 152, 98, 139, 261, 208, 11, 124,
212, 76, 228, 151, 238, 207, 278, 123, 158, 115, 223, 14, 274,
239, 202, 42, 72, 13, 232, 289, 279, 184, 194, 19, 193, 46,
114, 108, 9, 286, 180, 248, 154, 37, 166, 257, 292, 293, 269,
107, 259, 79, 12, 106, 256, 97, 287, 170, 185, 5, 8, 84,
38, 47, 94, 88, 36, 233, 28, 190, 243, 131, 81, 6, 140,
160, 253, 172, 118, 69, 161, 254, 263, 65, 67, 275, 100, 54,
24])
在这个练习中,你将使用K-means算法对图像进行压缩。
在本部分中,您将使用K-means算法来选择用于表示压缩图像的16种颜色。
本练习中提供的照片归Frank Wouters所有,已获得其许可。
加载图片
首先,您将使用matplotlib
读取原始图像,如下所示。
original_img = plt.imread('bird_small.png')
plt.imshow(original_img)
如您所见,这将创建一个三维矩阵original_img
,其中
例如,original_img [50,33,2]
给出了行50和列33处像素的蓝色强度。
要调用run_kMeans
,您需要首先将矩阵original_img
转换为二维矩阵。
original_img
以创建像素颜色的 m × 3 m \times 3 m×3矩阵(其中 m = 16384 = 128 × 128 m=16384 = 128\times128 m=16384=128×128)# 将所有值除以255,使所有值都在0-1范围内
original_img = original_img / 255
# 重新构造图像成为 m x 3矩阵,其中 m = 128 x 128 = 16384
# 每一行将包含红色、绿色和蓝色像素值
# 这给我们提供了我们将在K-Means上使用的数据集矩阵X_img。
X_img = np.reshape(original_img, (original_img.shape[0] * original_img.shape[1], 3))
现在,运行下面的单元格以在预处理的图像上运行 K-均值。
K = 16
max_iters = 10
initial_centroids = kMeans_init_centroids(X_img,K)
centroids,idx = run_kMeans(X_img,initial_centroids,max_iters)
K-Means iteration 0/9
K-Means iteration 1/9
K-Means iteration 2/9
K-Means iteration 3/9
K-Means iteration 4/9
K-Means iteration 5/9
K-Means iteration 6/9
K-Means iteration 7/9
K-Means iteration 8/9
K-Means iteration 9/9
print("Shape of idx:", idx.shape)
print("Closest centroid for the first five elements:", idx[:5])
Shape of idx: (16384,)
Closest centroid for the first five elements: [7 6 6 7 7]
找到用于表示图像的前 K = 16 K=16 K=16种颜色后,现在可以使用find_closest_centroids
函数将每个像素位置分配给它最接近的质心。
# 用索引表示图像
X_recovered = centroids[idx, :]
# 将恢复的图像重新调整为正确的维度
X_recovered = np.reshape(X_recovered, original_img.shape)
最后,您可以基于仅质心赋值来重新构建图像,以查看压缩的效果。
fig, ax = plt.subplots(1,2, figsize=(8,8))
plt.axis('off')
# 原始
ax[0].imshow(original_img*255)
ax[0].set_title('Original')
ax[0].set_axis_off()
# 压缩
ax[1].imshow(X_recovered*255)
ax[1].set_title('Compressed with %d colours'%K)
ax[1].set_axis_off()