K-means 是一种基于聚类的无监督学习算法,它的目标是将一组数据点分成 k 个簇,使得每个簇内的数据点之间的距离尽可能小,而不同簇之间的距离尽可能大。K-means 算法是一种迭代算法,它的核心是通过交替进行两个步骤来逐步优化簇的分配和簇中心的位置,直到满足一定的收敛条件为止。
具体来说,K-means 算法的步骤如下:
K-means 算法是一种简单而高效的聚类算法,它广泛应用于数据挖掘、图像处理、信号处理、自然语言处理等领域。算法的原理简单易懂,实现起来也比较简单,但由于其对初始簇中心的选取比较敏感,容易陷入局部最优解,因此需要对多组初始点进行试验,选取平均 SSE 最小的那组作为最终结果。另外,K-means 算法还需要指定聚类个数 k,而这个 k 的选取有时是比较主观的,需要根据实际问题进行调整。
import math
import random
def k_means(points, num_clusters, max_iterations=100):
clusters = []
clusters_old=[]
# 随机初始化聚类中心
for i in range(num_clusters):
clusters.append(random.choice(points))
clusters_old.append(random.choice(points))
for i in range(max_iterations):
# 分配所有点到最近的聚类中心
assignments = [[] for _ in range(num_clusters)]
for point in points:
distances = [math.sqrt((point[0]-c[0])**2 + (point[1]-c[1])**2 + (point[2]-c[2])**2) for c in clusters]
min_index = distances.index(min(distances))
assignments[min_index].append(point)
# 计算每个聚类的新中心点
for j in range(num_clusters):
if assignments[j]:
clusters[j] = (sum(p[0] for p in assignments[j])/len(assignments[j]),
sum(p[1] for p in assignments[j])/len(assignments[j]),
sum(p[2] for p in assignments[j])/len(assignments[j]))
# 如果聚类中心点不再变化,则退出循环
if all(clusters[j] == clusters_old[j] for j in range(num_clusters)):
break
clusters_old = list(clusters)
# 过滤掉距离聚类中心太远的点
filtered_points = []
for assignment in assignments:
center = clusters[assignments.index(assignment)]
for point in assignment:
if math.sqrt((point[0]-center[0])**2 + (point[1]-center[1])**2 + (point[2]-center[2])**2) < 1.0:
filtered_points.append(point)
return filtered_points
import random
# 生成一组随机的三维点
points = [(random.uniform(-2, 2), random.uniform(-2, 2), random.uniform(-2, 2))
for _ in range(100)]
# 测试 K-means 算法
filtered_points = k_means(points, 1)
# 打印聚类结果
for i, point in enumerate(points):
if point in filtered_points:
print(f"Point {i}: {point} (in a cluster)")
#else:
#print(f"Point {i}: {point} (isolated)")
对于大部分点都聚集在一块区域而剩下的点距离它们很远的情况,使用传统的 K-means 算法可能不太合适,因为 K-means 算法倾向于将所有点聚类到最近的中心点附近,这会导致远离簇心的点很难被正确地归类。因此,更适合这种情况的聚类算法是 DBSCAN(Density-Based Spatial Clustering of Applications with Noise,带有噪声的基于密度的空间聚类)算法。
DBSCAN 算法用于在高维空间中聚类具有足够密集性(即密度大)的点,并将那些无法划分到任何类别中的稀疏点标记为噪声。该算法利用了局部密度的概念,将每个点分为核心点、边界点和噪声点,具体规则如下:
该算法从任一点开始,并迭代地建立一个领域集和簇。具体来说,算法的流程如下:
import numpy as np
import random
import matplotlib.pyplot as plt
from sklearn.cluster import DBSCAN
%matplotlib inline
# 生成一组随机的三维点,大部分聚集在一块区域,少部分离得很远
points = [(random.uniform(0, 8), random.uniform(0, 2), random.uniform(0, 3)) for _ in range(100)]
for i in range(100):
points.append((random.uniform(8, 10), random.uniform(10, 20), random.uniform(20, 25)))
for i in range(10):
points.append((random.uniform(20, 25), random.uniform(20, 25), random.uniform(20, 25)))
# 将点转换为 numpy 数组
X = np.array(points)
# 初始化 DBSCAN 聚类器
dbscan = DBSCAN(eps=2, min_samples=10)
# 对点进行聚类
labels = dbscan.fit_predict(X)
# 绘图
fig = plt.figure()
ax = fig.add_subplot(projection='3d')
colors = ['r', 'g', 'b', 'c', 'm', 'y', 'k']
for i, label in enumerate(labels):
if label == -1:
ax.scatter(X[i][0], X[i][1], X[i][2], c='k', marker='o')
else:
ax.scatter(X[i][0], X[i][1], X[i][2], c=colors[label % len(colors)], marker='o')
plt.show()
以下是一个 Python 函数,该函数将输入一组三维激光雷达点,使用 DBSCAN 算法对它们进行聚类,并过滤掉与簇中心距离太远的孤立点。该函数使用 Scikit-Learn 库实现 DBSCAN 算法。运行该测试代码将会把所有点打印出来,如果某个点被聚类到一个簇中,就会在输出中显示该点属于某个簇,否则将显示该点为孤立点。
import numpy as np
from sklearn.cluster import DBSCAN
def filter_laser_points(laser_points, eps, min_samples, distance_threshold):
"""
对一组空间中的激光雷达点进行聚类,并过滤掉与簇中心距离太远的孤立点。
Args:
laser_points: 一组三维激光雷达点,格式为 [(x_1, y_1, z_1), ..., (x_n, y_n, z_n)]。
eps: DBSCAN 算法中半径参数。
min_samples: DBSCAN 算法中最小样本点数参数。
distance_threshold: 簇中心点和孤立点之间的最大距离阈值。
Returns:
过滤后的激光雷达点集,格式为 [(x_1, y_1, z_1), ..., (x_m, y_m, z_m)],其中 m <= n。
"""
# 将点转换为 numpy 数组
X = np.array(laser_points)
# 初始化 DBSCAN 聚类器
dbscan = DBSCAN(eps=eps, min_samples=min_samples)
# 对点进行聚类
labels = dbscan.fit_predict(X)
# 提取每个簇的中心点
clusters = {}
for i, label in enumerate(labels):
if label == -1: # 忽略噪声点
continue
if label not in clusters:
clusters[label] = {'points': [], 'center': None}
clusters[label]['points'].append(X[i])
# 计算每个簇的中心点
for label in clusters:
cluster_points = np.array(clusters[label]['points'])
center = np.mean(cluster_points, axis=0)
clusters[label]['center'] = center
# 过滤掉与簇中心距离太远的孤立点
filtered_points = []
for i, point in enumerate(laser_points):
if labels[i] == -1: # 忽略噪声点
continue
distance_to_center = np.linalg.norm(point - clusters[labels[i]]['center'])
if distance_to_center <= distance_threshold:
filtered_points.append(point)
return filtered_points
该函数接受四个参数:
laser_points: 一组三维激光雷达点,格式为 [(x_1, y_1, z_1), ..., (x_n, y_n, z_n)]。
eps: DBSCAN 算法中半径参数。
min_samples: DBSCAN 算法中最小样本点数参数。
distance_threshold: 簇中心点和孤立点之间的最大距离阈值。
返回过滤后的激光雷达点集,格式为 [(x_1, y_1, z_1), ..., (x_m, y_m, z_m)],其中 m <= n。
以下是用于测试该函数的示例代码:
import random
# 生成一组随机的三维点,让大部分点聚集在一个区域中,少部分离得很远
points = [(random.uniform(0, 5), random.uniform(0, 5), random.uniform(0, 5)) for _ in range(100)]
for i in range(10):
points.append((random.uniform(20, 25), random.uniform(20, 25), random.uniform(20, 25)))
# 对点进行聚类并过滤掉孤立点
filtered_points = filter_laser_points(points, eps=2, min_samples=5, distance_threshold=5)
# 打印聚类结果
for i, point in enumerate(points):
if point in filtered_points:
print(f"Point {i}: {point} (in a cluster)")
else:
print(f"Point {i}: {point} (isolated)")