第1关:什么是质心
根据提示,在右侧编辑器 Begin-End 部分补充代码,计算样本间距离 distance(x, y, p=2) 方法:
x:第一个样本的坐标
y:第二个样本的坐标
p:等于1时为曼哈顿距离,等于 2 时为欧氏距离
构造计算所有样本质心的方法 cal_Cmass(data):
data:数据样本
与将所有样本到质心距离按从小到大排序的方法 sorted_list(data,Cmass):
data:数据样本
Cmass:数据样本质心
import numpy as np
#计算样本间距离
def distance(x, y, p=2):
'''
input:x(ndarray):第一个样本的坐标
y(ndarray):第二个样本的坐标
p(int):等于1时为曼哈顿距离,等于2时为欧氏距离
output:distance(float):x到y的距离
'''
#********* Begin *********#
if p == 1:
# 曼哈顿距离(Manhattan distance)
return np.sum(np.abs(x - y))
elif p == 2:
# 欧氏距离(Euclidean distance)
return np.sqrt(np.sum((x - y) ** 2))
else:
# 通用闵可夫斯基距离(Minkowski distance)
return np.power(np.sum(np.power(np.abs(x - y), p)), 1/p)
#********* End *********#
#计算质心
def cal_Cmass(data):
'''
input:data(ndarray):数据样本
output:mass(ndarray):数据样本质心
'''
#********* Begin *********#
# 质心是所有样本坐标的平均值
Cmass = np.mean(data, axis=0)
#********* End *********#
return Cmass
#计算每个样本到质心的距离,并按照从小到大的顺序排列
def sorted_list(data, Cmass):
'''
input:data(ndarray):数据样本
Cmass(ndarray):数据样本质心
output:dis_list(list):排好序的样本到质心距离
'''
#********* Begin *********#
# 创建空列表存储每个样本到质心的距离
dis_list = []
# 计算每个样本到质心的距离
for i in range(len(data)):
dis = distance(data[i], Cmass)
dis_list.append(dis)
# 按照从小到大的顺序排列
dis_list.sort()
#********* End *********#
return dis_list
第二关:动手实现 k- 均值
根据提示,在右侧编辑器 Begin-End 部分补充代码,实现 kmeans 方法,其中距离设为欧氏距离。
import numpy as np
# 计算一个样本与数据集中所有样本的欧氏距离的平方
def euclidean_distance(one_sample, X):
'''
input:
one_sample(ndarray):单个样本
X(ndarray):所有样本
output:
distances(ndarray):单个样本到所有样本的欧氏距离平方
'''
#*********Begin*********#
# 计算单个样本与所有样本的欧氏距离平方
distances = np.sum((X - one_sample)**2, axis=1)
#*********End*********#
return distances
# 从所有样本中随机选取k个样本作为初始的聚类中心
def init_random_centroids(k, X):
'''
input:
k(int):聚类簇的个数
X(ndarray):所有样本
output:
centroids(ndarray):k个簇的聚类中心
'''
#*********Begin*********#
# 获取样本数量和特征维度
n_samples, n_features = X.shape
# 创建k个聚类中心的空数组
centroids = np.zeros((k, n_features))
# 随机选择k个样本作为初始中心
random_indices = np.random.choice(n_samples, k, replace=False)
centroids = X[random_indices]
#*********End*********#
return centroids
# 返回距离该样本最近的一个中心索引
def _closest_centroid(sample, centroids):
'''
input:
sample(ndarray):单个样本
centroids(ndarray):k个簇的聚类中心
output:
closest_i(int):最近中心的索引
'''
#*********Begin*********#
# 计算样本到各个中心的距离
distances = euclidean_distance(sample, centroids)
# 返回距离最小的中心索引
closest_i = np.argmin(distances)
#*********End*********#
return closest_i
# 将所有样本进行归类,归类规则就是将该样本归类到与其最近的中心
def create_clusters(k, centroids, X):
'''
input:
k(int):聚类簇的个数
centroids(ndarray):k个簇的聚类中心
X(ndarray):所有样本
output:
clusters(list):列表中有k个元素,每个元素保存相同簇的样本的索引
'''
#*********Begin*********#
# 初始化k个空列表,用于存储样本索引
clusters = [[] for _ in range(k)]
# 遍历每个样本,将其分配到最近的中心所在的簇
for i, sample in enumerate(X):
# 找到离样本最近的中心
centroid_idx = _closest_centroid(sample, centroids)
# 将样本索引添加到对应簇
clusters[centroid_idx].append(i)
#*********End*********#
return clusters
# 对中心进行更新
def update_centroids(k, clusters, X):
'''
input:
k(int):聚类簇的个数
clusters(list):每个簇包含的样本索引列表
X(ndarray):所有样本
output:
centroids(ndarray):k个簇的聚类中心
'''
#*********Begin*********#
# 获取特征维度
n_features = X.shape[1]
# 初始化聚类中心数组
centroids = np.zeros((k, n_features))
# 计算每个簇的新中心(平均值)
for i in range(k):
# 如果簇为空,保留原中心或随机选择一个样本作为中心
if len(clusters[i]) == 0:
# 选择一个随机样本作为中心
centroids[i] = X[np.random.randint(0, X.shape[0])]
else:
# 计算簇内所有样本的平均值作为新中心
cluster_samples = X[clusters[i]]
centroids[i] = np.mean(cluster_samples, axis=0)
#*********End*********#
return centroids
# 将所有样本进行归类,其所在的类别的索引就是其类别标签
def get_cluster_labels(clusters, X):
'''
input:
clusters(list):列表中有k个元素,每个元素保存相同簇的样本的索引
X(ndarray):所有样本
output:
y_pred(ndarray):所有样本的类别标签
'''
#*********Begin*********#
# 初始化标签数组,大小为样本数量
n_samples = X.shape[0]
y_pred = np.zeros(n_samples)
# 为每个簇的样本分配标签
for cluster_idx, cluster in enumerate(clusters):
for sample_idx in cluster:
y_pred[sample_idx] = cluster_idx
#*********End*********#
return y_pred
# 对整个数据集X进行Kmeans聚类,返回其聚类的标签
def predict(k, X, max_iterations, varepsilon):
'''
input:
k(int):聚类簇的个数
X(ndarray):所有样本
max_iterations(int):最大训练轮数
varepsilon(float):最小误差阈值
output:
y_pred(ndarray):所有样本的类别标签
'''
#*********Begin*********#
# 从所有样本中随机选取k样本作为初始的聚类中心
centroids = init_random_centroids(k, X)
# 迭代,直到算法收敛(上一次的聚类中心和这一次的聚类中心几乎重合)或者达到最大迭代次数
for _ in range(max_iterations):
# 将所有进行归类,归类规则就是将该样本归类到与其最近的中心
clusters = create_clusters(k, centroids, X)
# 保存当前的聚类中心,用于后续比较是否收敛
prev_centroids = centroids.copy()
# 计算新的聚类中心
centroids = update_centroids(k, clusters, X)
# 如果聚类中心几乎没有变化,说明算法已经收敛,退出迭代
diff = np.sum((centroids - prev_centroids)**2)
if diff < varepsilon:
break
# 根据最终的簇获取标签
y_pred = get_cluster_labels(clusters, X)
#*********End*********#
return y_pred