"""
在这个练习中,您将实现K-means算法并将其用于图像压缩。
通过减少图像中出现的颜色的数量,只剩下那些在图像中最常见的颜色。
"""
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy.io import loadmat
numpy的广播机制:
def findClosestCentroids(X, centroids):
"""
分配簇阶段,算法将每一个训练样本分配给最接近的簇中心。
Args:
X: 训练集
centroids: 簇中心
Returns:
输出一个一维数组idx,该数组保存距离每一个训练样本最近的簇中心的索引。
"""
idx = []
max_dist = 1000000 # 限制最大距离
# 遍历训练集
for i in range(len(X)):
# 计算当前样本与各个聚簇中心的距离(这里的减法运用了numpy的广播机制)
minus = X[i] - centroids
dist = minus[:, 0] ** 2 + minus[:, 1] ** 2
# 选择距离当前样本最近的聚簇中心
if dist.min() < max_dist:
ci = np.argmin(dist) # 返回距离最近的聚簇中心的索引值
idx.append(ci) # 放入返回值列表
return np.array(idx) # 返回一维数组
# 读取数据
mat = loadmat('data/ex7data2.mat')
# print(mat.keys()) # dict_keys(['__header__', '__version__', '__globals__', 'X'])
# 训练集
X = mat['X']
# 初始化聚簇中心的位置
init_centroids = np.array([[3, 3], [6, 2], [8, 5]])
# 为每一个训练样本分配簇中心
idx = findClosestCentroids(X, init_centroids)
print(idx)
'''
[0,2,1,0,...,1,0]
'''
重新计算每个簇中心,求这个簇里面所有点位置的平均值。
def computeCentroids(X, idx):
'''
重新计算聚簇中心
Args:
X: 训练集
idx: 当前给训练集中的每个样本分配的聚簇索引
Returns:
新的聚簇中心位置
'''
centroids = []
# print(np.unique(idx)) # [0 1 2]
for i in range(len(np.unique(idx))): # 一共3个聚簇中心,共循环3次
u_k = X[idx == i].mean(axis=0) # 按列求平均值(新聚簇中心的位置)
centroids.append(u_k)
return np.array(centroids)
print(computeCentroids(X, idx))
'''
[[2.42830111 3.15792418]
[5.81350331 2.63365645]
[7.11938687 3.6166844 ]]
'''
可视化训练集和聚簇中心
def plotData(X, centroids, idx=None):
'''
可视化训练集和聚簇中心,并自动分开着色。
Args:
X: 训练集
centroids: 每次聚簇中心点的所有历史记录,type:list (注意:是每次)
idx: 最后一次迭代生成的idx向量,存储每个样本分配的簇中心点的索引值
Returns:
None
'''
# 颜色列表
colors = ['b', 'g', 'gold', 'darkorange', 'salmon', 'olivedrab',
'maroon', 'navy', 'sienna', 'tomato', 'lightgray', 'gainsboro',
'coral', 'aliceblue', 'dimgray', 'mintcream', 'mintcream']
# 断言:聚簇中心的数量是否大于颜色的数量
assert len(centroids[0]) <= len(colors), 'colors not enough'
subX = [] # 存储每个聚簇中心的样本集,subX的结构:[X[idx==1]列表, X[idx==2]列表,..., X[idx==K]列表]
if idx is not None:
for i in range(centroids[0].shape[0]): # i:(1,2,3,..,K)
x_i = X[idx == i]
subX.append(x_i)
else:
subX = [X]
# 分别画出每个簇的点,并着不同的颜色
plt.figure(figsize=(8, 5))
for i in range(len(subX)): # i:(0,1,2,...,k)
xx = subX[i] # xx是一个list,包含所有属于第i聚类的样本元素列表
plt.scatter(xx[:, 0], xx[:, 1], c=colors[i], label='Cluster %d' % i)
plt.legend()
plt.grid(True)
plt.xlabel('x1', fontsize=14)
plt.ylabel('x2', fontsize=14)
plt.title('Plot of X Points', fontsize=16)
# 画聚簇中心的移动轨迹
xx, yy = [], [] # xx存聚簇中心的横坐标值,yy存纵坐标值
for centroid in centroids:
xx.append(centroid[:, 0])
yy.append(centroid[:, 1])
plt.plot(xx, yy, 'rx--', markersize=8) # 'rx--':红色带小叉的虚线
# 可视化未分类前的数据情况
plotData(X, [init_centroids])
# plt.show()
无监督分类,画出聚簇中心移动轨迹:
def runKmeans(X, centroids, max_iters):
'''
无监督分类,画出聚簇中心移动轨迹
Args:
X: 训练集
centroids: 初始聚簇中心位置
max_iters: 迭代次数
Returns:
每个样本最终所属的聚簇中心索引号 ids
每次聚簇中心点的所有历史记录 centroids_all 。 type:[二维数组,二维数组,...]
'''
K = len(centroids) # 聚簇中心数量
centroids_all = []
centroids_all.append(centroids)
centroid_i = centroids # 当前聚簇中心位置
for i in range(max_iters):
idx = findClosestCentroids(X, centroid_i) # 分配簇,idx存放最后一次迭代生成的idx向量
centroid_i = computeCentroids(X, idx) # 更新聚簇中心位置
centroids_all.append(centroid_i)
return idx, centroids_all
idx, centroids_all = runKmeans(X, init_centroids, 20)
plotData(X, centroids_all, idx)
#plt.show()
聚簇中心位置的随机初始化
def initCentroids(X, K):
'''
聚簇中心位置的随机初始化:随机选择样本点作为聚簇中心
Args:
X:训练集
K:聚簇中心的数量
Returns:
聚簇中心的位置
'''
m = X.shape[0] # 样本总数
idx = np.random.choice(m, K) # 在训练集样本中随机选择3个样本的索引号 print(type(idx)) # 一维数组
centroids = X[idx] # print(type(centroids)) # 二维数组
return centroids
for i in range(3):
centroids = initCentroids(X, 3) # 随机初始化后的聚簇中心
idx, centroids_all = runKmeans(X, centroids, 10)
plotData(X, centroids_all, idx)
#plt.show()
有的结果会出现局部最优情况:
实现图片压缩
读入图片:
'''
用Kmeans来进行图片压缩。
在一个简单的24位颜色表示图像。每个像素被表示为三个8位无符号整数(从0到255),指定了红、绿和蓝色的强度值。
这种编码通常被称为RGB编码。我们的图像包含数千种颜色,在这一部分的练习中,你将把颜色的数量减少到16种颜色。
这可以有效地压缩照片。
具体地说,您只需要存储16个选中颜色的RGB值,
而对于图中的每个像素,现在只需要将该颜色的索引存储在该位置(只需要4 bits就能表示16种可能性)。因为2的4次幂是16。
接下来我们要用K-means算法选16种颜色,用于图片压缩。
你将把原始图片的每个像素看作一个数据样本,然后利用K-means算法去找分组最好的16种颜色。
'''
from skimage import io
A = io.imread('data/bird_small.png')
# print(A.shape) # (128, 128, 3) 图片大小 :行,列,颜色通道
plt.imshow(A) # 显示图片
# plt.show()
A = A / 255 # 令图片像素值都处于0~1之间
压缩图片:
'''
将图像重塑为(N,3)矩阵,其中N=像素个数。
每行将包含红色、绿色和蓝色的像素值
这就相当于给了我们数据集矩阵X,我们将使用K-Means来进行图片压缩。
'''
X = A.reshape(-1, 3) # 将图像重塑为(N,3)矩阵
K = 16 # 聚簇中心个数设置为16(压缩至只有16种颜色)
centroids = initCentroids(X, K) # 随机初始化聚簇中心,即随机选择16个颜色块
idx, centroids_all = runKmeans(X, centroids, 10) # 执行K-Means算法
img = np.zeros(X.shape) # 存储压缩后的图片
centroids = centroids_all[-1] # 获取最终的聚簇中心位置
for i in range(K):
img[idx == i] = centroids[i] # 压缩后的图片,进行像素块赋值
img = img.reshape((128, 128, 3)) # 像图片矩阵重塑回原来的结构
# 画压缩后的图片
fig, axes = plt.subplots(1, 2, figsize=(12, 6)) # 画一个1行2列的子图
axes[0].imshow(A)
axes[1].imshow(img)
plt.show()