椰汁笔记
前面学习的内容都是监督学习,这将是我们学习的第一个非监督学习算法。
我们先把这个算法说清楚再说作业。
下面就开始作业吧
首先需要实现,为每个点找到最近的簇中心,作为当前点的标签
c ( i ) = j that minimizes ∣ ∣ x ( i ) − μ j ∣ ∣ 2 c^{(i)}=j\textrm{ that minimizes }||x^{(i)}-\mu_j||^2 c(i)=j that minimizes ∣∣x(i)−μj∣∣2
def find_closet_centroids(X, centroids):
"""
寻找所属簇
:param X: ndarray,所有点
:param centroids: ndarray,上一步计算出或初始化的簇中心
:return: ndarray,每个点所属于的簇
"""
res = np.zeros((1,))
for x in X:
res = np.append(res, np.argmin(np.sqrt(np.sum((centroids - x) ** 2, axis=1))))
return res[1:]
测试一下
data = sio.loadmat("data\\ex7data2.mat")
X = data['X'] # (300,2)
init_centroids = np.array([[3, 3], [6, 2], [8, 5]])
idx = find_closet_centroids(X, init_centroids)
print(idx[0:3]) # [0. 2. 1.]
接着实现第二个部分,重新计算簇中心
μ k = 1 C k ∑ i ∈ C k x ( i ) \mu_k=\frac{1}{C_k}\sum_{i\in C_k}x^{(i)} μk=Ck1i∈Ck∑x(i)
def compute_centroids(X, idx):
"""
计算新的簇中心
:param X: ndarray,所有点
:param idx: ndarray,每个点对应的簇号
:return: ndarray,所有新簇中心
"""
K = int(np.max(idx)) + 1
m = X.shape[0]
n = X.shape[-1]
centroids = np.zeros((K, n))
counts = np.zeros((K, n))
for i in range(m):
centroids[int(idx[i])] += X[i]
counts[int(idx[i])] += 1
centroids = centroids / counts
return centroids
继续使用上个例子测试
print(compute_centroids(X, idx))
# [[2.42830111 3.15792418]
# [5.81350331 2.63365645]
# [7.11938687 3.6166844 ]]
将这两步封装成一个完整的K-means算法,这里用到的随机初始化簇中心,在1.3实现,我直接先用一下,具体的函数看1.3。而且算法的迭代轮数可以指定一个很大的轮数保证计算完后算法收敛。我选择每次计算当前的目标值,当目标值不变时,算法收敛退出。
需要先实现损失函数
def cost(X, idx, centrodis):
c = 0
for i in range(len(X)):
c += np.sum((X[i] - centrodis[int(idx[i])]) ** 2)
c /= len(X)
return c
def k_means(X, K):
"""
k-means聚类算法
:param X: ndarray,所有的数据
:param K: int,聚类的类数
:return: tuple,(idx, centroids_all)
idx,ndarray为每个数据所属类标签
centroids_all,[ndarray,...]计算过程中每轮的簇中心
"""
centroids = random_initialization(X, K)
centroids_all = [centroids]
idx = np.zeros((1,))
last_c = -1
now_c = -2
# iterations = 200
# for i in range(iterations):
while now_c != last_c: # 当收敛时结束算法,或者可以利用指定迭代轮数
idx = find_closet_centroids(X, centroids)
last_c = now_c
now_c = cost(X, idx, centroids)
centroids = compute_centroids(X, idx)
centroids_all.append(centroids)
return idx, centroids_all
对数据集进行聚类
data = sio.loadmat("data\\ex7data2.mat")
X = data['X'] # (300,2)
idx, centroids_all = k_means(X, 3)
这里的聚类算中返回了每轮计算的簇中心,就是为了后面的可视化簇中心变化过程,画出聚类结果和簇中心的移动路线
def visualizing(X, idx, centroids_all):
"""
可视化聚类结果和簇中心的移动过程
:param X: ndarray,所有的数据
:param idx: ndarray,每个数据所属类标签
:param centroids_all: [ndarray,...]计算过程中每轮的簇中心
:return: None
"""
plt.scatter(X[..., 0], X[..., 1], c=idx)
xx = []
yy = []
for c in centroids_all:
xx.append(c[..., 0])
yy.append(c[..., 1])
plt.plot(xx, yy, 'rx--')
plt.show()
看一看结果
visualizing(X, idx, centroids_all)
一般簇中心的初始化是从数据中随机选择K组
def random_initialization(X, K):
"""
随机选择K组数据,作为簇中心
:param X: ndarray,所有点
:param K: int,聚类的类数
:return: ndarray,簇中心
"""
res = np.zeros((1, X.shape[-1]))
m = X.shape[0]
rl = []
while True:
index = random.randint(0, m)
if index not in rl:
rl.append(index)
if len(rl) >= K:
break
for index in rl:
res = np.concatenate((res, X[index].reshape(1, -1)), axis=0)
return res[1:]
接下来将聚类算法用于图片压缩上,先讲一讲思路
图片都是由若干像素点构成的,每个像素点都有颜色,这个颜色一般是RGB编码,意思是所有颜色都可以通过Red,Green,Blue来表示,RGB编码下三种颜色每个通过一个8比特的整数来表示强度,因此一个像素点需要24bit来表示颜色。
图片上的每个像素点都需要使用24bit存储颜色,我们的压缩方法是,通过聚类将图片上的颜色分为16种,我们只存储这十六种颜色,每个像素点上只需要4比特存储对应颜色的序号,即可达到有损压缩图片的目的。
首先引入需要的聚类算法实现,这里单独创建了一个文件
import ex7_K_means_Clustering_and_PCA.k_means_clustering as k_means
进行图片压缩,将rgb颜色作为聚类的点数据,每个颜色的表示是一个列表包含三个数。这里聚类结束后构造新的图片矩阵,不是很规范,应该是直接存储bit。
def compress(image, colors_num):
"""
压缩图片
:param image: ndarray,原始图片
:param colors_num: int,压缩后的颜色数量
:return: (ndarray,ndarray),第一个每个像素点存储一个值,第二个为颜色矩阵
"""
d1, d2, _ = image.shape
raw_image = image.reshape(d1 * d2, -1) # 展开成二维数组
idx, centroids_all = k_means.k_means(raw_image, colors_num)
colors = centroids_all[-1]
compressed_image = np.zeros((1, 1)) # 构造压缩后的图片格式
for i in range(d1 * d2):
compressed_image = np.concatenate((compressed_image, idx[i].reshape(1, -1)), axis=0)
compressed_image = compressed_image[1:].reshape(d1, d2, -1)
return compressed_image, colors
为了可视化效果,还需要将压缩后的图片格式转化为可以显示的标准格式
def compressed_format_to_normal_format(compressed_image, colors):
"""
将压缩后的图片转为正常可以显示的图片格式
:param compressed_image: ndarray,压缩后的图片,存储颜色序号
:param colors: ndarray,颜色列表
:return: ndarray,正常的rgb格式图片
"""
d1, d2, _ = compressed_image.shape
normal_format_image = np.zeros((1, len(colors[0])))
compressed_image = compressed_image.reshape(d1 * d2, -1)
for i in range(d1 * d2):
normal_format_image = np.concatenate((normal_format_image, colors[int(compressed_image[i][0])].reshape(1, -1)),
axis=0)
normal_format_image = normal_format_image[1:].reshape(d1, d2, -1)
return normal_format_image
看一看效果
image = plt.imread("data\\bird_small.png") # (128,128,3)
plt.subplot(1, 2, 1)
plt.imshow(image)
plt.axis('off')
plt.title("raw image")
plt.subplot(1, 2, 2)
compressed_image, colors = compress(image, 16)
print(compressed_image.shape, colors.shape)
plt.imshow(compressed_format_to_normal_format(compressed_image, colors))
plt.axis('off')
plt.title("compressed image")
plt.show()
这部分我觉得是这个课程里面比较难理解的一部分,说实话我的这部分我也理解地不是很好。
data = sio.loadmat("data\\ex7data1.mat")
X = data['X'] # (50,2)
plt.scatter(X[..., 0], X[..., 1], marker='x', c='b')
plt.show()
实现PCA首先要做的就是对数据的处理进行归一化,注意这里的方差的计算,默认ddof为0,通常情况下是使用ddof=1,就是方差计算中最后除以m还是m-1的不同。
def data_preprocess(X):
"""
数据归一化
:param X: ndarray,原始数据
:return: (ndarray.ndarray,ndarray),处理后的数据,每个特征均值,每个特征方差
"""
mean = np.mean(X, axis=0)
std = np.std(X, axis=0, ddof=1) # 默认ddof=0, 这里一定要修改
return (X - mean) / std, mean, std
numpy中有奇异值分解的功能,直接使用
def pca(X):
sigma = X.T.dot(X) / len(X) # (n,m)x(m,n) (n,n)
u, s, v = np.linalg.svd(sigma) # u(n,n) s(n,), v(n,n)
return u, s, v
使用PCA进行降维
def project_data(X, U, K):
"""
数据降维
:param X: ndarray,原始数据
:param U: ndarray,奇异值分解后的U
:param K: int,目标维度
:return: ndarray,降维后的数据
"""
return X.dot(U[..., :K])
逆向思维,还可以进行降维后的升维
def reconstruct_data(Z, U, K):
"""
数据升维
:param Z: ndarray,降维后的数据
:param U: ndarray,奇异值分解后的U
:param K: int,降维的维度
:return: ndarray,原始数据
"""
return Z.dot(U[..., :K].T)
测试一下
data = sio.loadmat("data\\ex7data1.mat")
X = data['X'] # (50,2)
normalized_X, _, _ = data_preprocess(X)
u, _, _ = pca(normalized_X) # (2,2)
Z = project_data(normalized_X, u, 1)
print(Z[0]) # [1.48127391]
rec_X = reconstruct_data(Z, u, 1)
print(rec_X[0]) # [-1.04741883 -1.04741883]
将这个投影可视化
plt.scatter(normalized_X[..., 0], normalized_X[..., 1], marker='x', c='b', label='normalized x')
plt.scatter(rec_X[..., 0], rec_X[..., 1], marker='x', c='r', label='reconstructed x')
plt.title("Visualizing the projections")
for i in range(len(normalized_X)):
plt.plot([normalized_X[i][0], rec_X[i][0]], [normalized_X[i][1], rec_X[i][1]], 'k--')
plt.xlim((-3, 2))
plt.ylim((-3, 2))
plt.legend()
plt.show()
将PCA应用到人类数据集上,当前的每张人脸图片为1024像素,因此为1024维。我们的目标是将数据降维到36像素,也就是36维。
这里单独创建一个文件,先引入实现好的PCA
import ex7_K_means_Clustering_and_PCA.PCA as pca
使用PCA进行降维
data = sio.loadmat("data\\ex7faces.mat")
X = data['X'] # (5000,1024)
nor_X, _, _ = pca.data_preprocess(X)
u, _, _ = pca.pca(nor_X)
Z = pca.project_data(nor_X, u, 36)
rec_X = pca.reconstruct_data(Z, u, 36)
将前后的图片可视化对比一下,记得要想人脸位置为正向需要转置一下
def visualizing_images(X, d):
"""
可视化图片
:param X: ndarray,图片
:param d: int,一行展示多少张图片
:return: None
"""
m = len(X)
n = X.shape[-1]
s = int(np.sqrt(n))
for i in range(1, m + 1):
plt.subplot(m / d, d, i)
plt.axis('off')
plt.imshow(X[i - 1].reshape(s, s).T, cmap='Greys_r') # 要把脸摆正需要转置
plt.show()
visualizing_images(X[:25], 5)
visualizing_images(rec_X[:25], 5)
总结
完整的代码会同步 在我的github
欢迎指正错误