无监督学习的数据没有标签,希望计算机自行学习其中的知识。比如聚类模型会通过数据的内在性质,把数据划分为不同的关系紧密的子集。而K均值就是一个聚类模型。
聚类模型的用途:
假设我们有绿色的这些数据点,希望把这些数据点分为两个子集,应该怎么做?
算法的输入有两个:K(想要分几类)和训练数据,而且约定不使用x0=1。
ci代表xi离第i个聚类中心最近;
μk代表第k个聚类中心。
有时候,我们遇到的数据并不都像左边的数据点那样各个聚类间有明显的分割,而是像右边的数据点一样看上去不容易分离。但是K均值算法依旧能够取得不错的结果。
假设xi和第5个聚类中心最近,那么ci=5,μci=μ5。
K均值的优化目标:找到合适的ci和μk,使得xi和它被分到的聚类的中心(μci)的距离最近。
可以证明在K均值算法中,第一步把xi分到离它最近的聚类中心,其实就是调整ci使得损失函数最小化;
第二步中重新计算新的聚类中心,其实就是调整μk使得损失函数最小化。
Q:如何初始化K均值?
A:随机选择K个训练数据点作为初始化的聚类中心。
Q:K均值在不同的初始值下会有不同的结果,也就是说可能会陷入局部最优。如何避免陷入局部最优?
A:解决这个问题的方法是进行多次初始化。
一般来说,可以进行50到1000次随机初始化,然后计算每次的损失J,选择损失最小的模型。
多次随机初始化在K比较小的时候比较有用,比如K在2到10左右时,但是,如果K很大,比如成百上千,那么多次随机初始化可能并不会有太大的改善。
并没有一个很好的方法选择K,比如下图中的数据,有的人会把它分成两个聚类,也有人会把它分成四个聚类。
肘部法则:画出损失函数J随K的改变而改变的图,选择从急速下降到平缓下降的点(比如左边图中的K=3),但是有时候得到的图是右边的样子,并没有一个明确的点可供选择,这时候肘部法则就没什么用了。
总而言之,肘部法则是一个值得尝试的方法,但是不能期望它能解决所有问题。
通过下游目标确定。有时候,人们运用K均值算法是有一个目标的,比如可以考虑:如果把T-shirt分为3个尺码,它会买得怎么样?把它分为5个尺码会得到更高的销售量吗?这样就得到了K的数值。
另一种无监督学习是降维,降维不仅能使数据占用更少的内存,还能加速算法。
假设现在有一个二维的数据,两个维度的数据都是代表物体的长度,其中一维用厘米表示,另一个维度用英寸表示。我们可以让它们合并成一个特征。
这个例子有点勉强,但是在实际应用中,如果有成百上千个特征,就不容易知道哪些特征是冗余的。
假设有三个工程小组,第一个工程小组给你200个特征,第二个工程小组给你300个特征,第三个工程小组给你500个特征,总共有1000个特征。这时候就容易产生冗余数据。
这是一个三维数据点投影到二维平面的例子。
假设我们有关于各个国家的数据,每个国家有50个特征,我们怎么样才能看出这些国家之间的关联呢?
我们不可能画出50维的数据,但是如果我们能够把这50维的数据降到2维,并且保留数据的主要特征,那么我们就可以在二维的平面把它们画出来了。当然,直接看这个二维的数据可能并不知道每个特征表示的是什么。
当你把它们都画出来后,有可能就能知道每一维大概代表什么。
主成分分析想要找到一个低维空间,使得数据点和它们在低维空间上投影的距离最短。
所以算法会选择投影到红色的线上而不是粉红色的线。
主成分分析看起来和线性回归很像,但是它们实际上是不同的:
线性回归中有一个特殊的分量y,而主成分分析中,每一个分量都是平等的。
而且线性回归中,数据到预测的y的距离是垂直于x轴的;而主成分分析中数据到投影点的距离是垂直于低维平面的。
在进行主成分分析之前,要对数据进行特征缩放(房子的面积和房间的数量相差较大)和归一化处理。
主成分分析想求的数据有两个:低维空间的坐标轴和数据点投影到低维空间后的坐标。
假设我们要把数据从n维降到k维,首先要求数据X间的协方差矩阵,然后对这个协方差矩阵进行奇异值分解,分解后能得到三个矩阵U,S,V,然后把U的前k个列向量取出来,记为Ureduce。
Ureduce就是降维后的空间坐标轴,降维后的数据坐标Z=UreduceTX
如何从降维后的数据重现原数据?
X≈Xapprox=UreduceZ,可以看到重现的数据完全是在一条直线上的。
分别计算投影误差平方的平均值和数据的方差的平均值,我们会选择一个使得下图中的式子成立的k。也就是说,希望降维后的数据依旧保持原数据99%以上的方差。
一个直接的想法就是,从小到大选择k,计算Ureduce,Z,Xapprox等,然后计算上面的等式是否成立,直到找到使等式成立的最小的k。
但是这个方法比较麻烦,有一个更简便的方法是计算下图右边的这个式子是否成立。比如说k=3时,计算奇异值分解得到的S的对角线上前3个元素的和比上对角线上所有元素的和,看看它是否大于等于99%,如果满足,k=3就是我们所要求的k。
假设一个分类问题的输入X有10000维,我们可以把X拿出来,降维到1000维,然后用这个1000维的数据去进行训练。
注意,降维所需的矩阵Ureduce应该是在训练集里学到的,不应该使用验证集和测试集去学习Ureduce。当然,训练完的Ureduce验证集和测试集都是可以用的。
主成分分析的一个误用是用于防止过拟合,这并不是正确的用法,并不是说这样做的结果不好,而是说主成分分析不会考虑y的取值,所以有可能丢失一些重要的信息。
一个正确防止过拟合的方法是使用正则化项,这种方法会考虑y的取值,而且效果也比较好。
另一个主成分分析的误用就是,人们在刚开始构造模型的时候就把主成分分析考虑进去,但是建议直接使用原数据进行训练。如果没有必要的理由(运行过慢或者占用内存过大等),就不应该使用主成分分析。
主成分分析应该被用在加速算法或可视化数据上。
导包
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sb
from skimage import io
from scipy.io import loadmat
找到离数据点最近的聚类中心
def find_closest_centroids(X, centroids):
# INPUT:数据X,初始聚类中心centroids
# OUTPUT:数据点应该被分到的类
m = X.shape[0]
k = centroids.shape[0]
idx = np.zeros(m)
for i in range(m):
min_dist = 1000000
# 求离Xi最近的聚类中心,并保存在idx[i]
for j in range(k):
dist = np.sum((X[i]-centroids[j])**2)
if dist < min_dist:
min_dist = dist
# 数据点Xi应该被分到第j类
idx[i] = j
return idx
# 测试一下上面的函数
data = loadmat('data/ex7data2.mat')
X = data['X']
initial_centroids = np.array([[3, 3], [6, 2], [8, 5]])
idx = find_closest_centroids(X, initial_centroids)
# array([0., 2., 1.])
idx[0:3]
展示原始数据
data2 = pd.DataFrame(data.get('X'), columns=['X1', 'X2'])
# 展示前五行数据
data2.head()
# 画出散点图
sb.set(context="notebook", style="white")
sb.lmplot('X1', 'X2', data=data2, fit_reg=False)
plt.show()
X1 | X2 | |
---|---|---|
0 | 1.842080 | 4.607572 |
1 | 5.658583 | 4.799964 |
2 | 6.352579 | 3.290854 |
3 | 2.904017 | 4.612204 |
4 | 3.231979 | 4.939894 |
计算新的聚类中心
def compute_centroids(X, idx, k):
# INPUT:数据X,聚类中心idx,簇的个数k
# OUTPUT:当前簇的聚类中心
m, n = X.shape
centroids = np.zeros((k, n))
for i in range(k):
# 找到被分到第i个类的数据点的下标
indices = np.argwhere(idx==i)
centroids[i,:] = (np.sum(X[indices],axis=0))/len(indices)
return centroids
# array([[2.42830111, 3.15792418],
# [5.81350331, 2.63365645],
# [7.11938687, 3.6166844 ]])
compute_centroids(X, idx, 3)
实现K均值
def run_k_means(X, initial_centroids, max_iters):
# INPUT:数据X,初始化的聚类中心,最大迭代次数
# OUTPUT:当前簇的聚类中心
# 初始化
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='y', 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.scatter(centroids[:,0],centroids[:,1], s=80, color = 'r', label='center')
# 显示数据点的含义
ax.legend()
plt.show()
def init_centroids(X, k):
m, n = X.shape
centroids = np.zeros((k, n))
# m个样本点中任取k个
idx = np.random.randint(0, m, k)
for i in range(k):
centroids[i,:] = X[idx[i],:]
return centroids
# array([[3.81422865, 4.73526796],
# [5.74036233, 3.10391306],
# [2.68499376, 0.35344943]])
init_centroids(X, 3)
使用K均值压缩图片
from IPython.display import Image
Image(filename='data/bird_small.png')
image_data = loadmat('data/bird_small.mat')
image_data
A = image_data['A']
# (128, 128, 3)
A.shape
# 归一化数据
A = A / 255.
# 重置矩阵大小
X = np.reshape(A, (A.shape[0] * A.shape[1], A.shape[2]))
# (16384, 3)
X.shape
# 随机初始化聚类中心(16,3),16个像素点,每个点RGB三原色
initial_centroids = init_centroids(X, 16)
# 运行之前写好的聚类算法,迭代10次
idx, centroids = run_k_means(X, initial_centroids, 10)
# 得到最终的聚类中心
idx = find_closest_centroids(X, centroids)
# 把每一个像素值分到各个聚类中心
X_recovered = centroids[idx.astype(int),:]
# (16384, 3)
X_recovered.shape
# 为了方便展示,转化回原始大小
X_recovered = np.reshape(X_recovered, (A.shape[0], A.shape[1], A.shape[2]))
# (128, 128, 3)
X_recovered.shape
pic = io.imread('data/bird_small.png') / 255.
fig, ax = plt.subplots(1, 2)
# 原图像
ax[0].imshow(pic)
# 压缩后的图像
ax[1].imshow(X_recovered)
plt.show()
看得出来,还是保留大量信息的。
使用sklearn来实现K均值
# 导入图像和初始化
pic = io.imread('data/bird_small.png') / 255.
io.imshow(pic)
plt.show()
# (128, 128, 3)
pic.shape
# 重置图像大小
data = pic.reshape(128*128, 3)
# (16384, 3)
data.shape
#导入k-means库
from sklearn.cluster import KMeans
model = KMeans(n_clusters=16, n_init=100, n_jobs=-1)
# Out[25]:KMeans(algorithm='auto', copy_x=True, init='k-means++', max_iter=300,
# n_clusters=16, n_init=100, n_jobs=-1, precompute_distances='auto',
# random_state=None, tol=0.0001, verbose=0)
model.fit(data)
centroids = model.cluster_centers_
# (16, 3)
print(centroids.shape)
C = model.predict(data)
# (16384,)
print(C.shape)
# (16384,3)
centroids[C].shape
compressed_pic = centroids[C].reshape((128,128,3))
fig, ax = plt.subplots(1, 2)
ax[0].imshow(pic)
ax[1].imshow(compressed_pic)
plt.show()
和上面自编程的比较,可以看到鸟嘴的颜色更丰富,而黑色羽毛的层次减少了。
加载并展示数据
data = loadmat('data/ex7data1.mat')
data
X = data['X']
fig, ax = plt.subplots(figsize=(12,8))
ax.scatter(X[:, 0], X[:, 1])
plt.show()
实现主成分分析
# 实现奇异值分解
def pca(X):
# INPUT:数据X
# OUTPUT:矩阵U,S,V
# 归一化数据
X = (X-X.mean())/X.std()
# 计算协方差矩阵
X = np.matrix(X)
cov = X.T@X
# 进行奇异值分解
U, S, V = np.linalg.svd(cov)
return U, S, V
U, S, V = pca(X)
# (matrix([[-0.79241747, -0.60997914],
# [-0.60997914, 0.79241747]]),
# array([71.79226819, 28.20773181]),
# matrix([[-0.79241747, -0.60997914],
# [-0.60997914, 0.79241747]]))
U, S, V
# 使数据投影到低维空间
def project_data(X, U, k):
# 取前k个列向量,因为低维空间的维数是k
U_reduced = U[:,:k]
return np.dot(X, U_reduced)
Z = project_data(X, U, 1)
Z
# 降维后的数据还原回高维空间
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
fig, ax = plt.subplots(figsize=(12,8))
ax.scatter(list(X_recovered[:, 0]), list(X_recovered[:, 1]))
plt.show()
可以看到,还原回高维空间后,数据都在一条直线上了,什么这个压缩是有损压缩(也就是说不能完全还原回原来的样子)。
使用主成分分析压缩图像
faces = loadmat('data/ex7faces.mat')
X = faces['X']
# (5000, 1024)
X.shape
face = np.reshape(X[3,:], (32, 32))
plt.imshow(face)
plt.show()
U, S, V = pca(X)
Z = project_data(X, U, 100)
X_recovered = recover_data(Z, U, 100)
face_pca = np.reshape(X_recovered[3,:], (32, 32))
fig, ax = plt.subplots(1, 2)
ax[0].imshow(face)
ax[1].imshow(face_pca)
plt.show()