K-means
K-means 是一种基于聚类的无监督学习算法,它的目标是将一组数据点分成 k 个簇,使得每个簇内的数据点之间的距离尽可能小,而不同簇之间的距离尽可能大。K-means 算法是一种迭代算法,它的核心是通过交替进行两个步骤来逐步优化簇的分配和簇中心的位置,直到满足一定的收敛条件为止。
具体来说,K-means 算法的步骤如下:
- 随机初始化 k 个簇中心,其中 k 是指定的聚类个数,一般是根据经验或者领域知识来确定的。
- 对每个数据点找到最近的簇中心,并将其分配到对应的簇中。
- 对每个簇,计算其所有数据点的平均值,并将该平均值作为新的簇中心。
- 重复步骤 2 和步骤 3,直到满足一定的收敛条件为止(例如,迭代次数达到指定值,簇中心不再发生变化等)。
K-means 算法是一种简单而高效的聚类算法,它广泛应用于数据挖掘、图像处理、信号处理、自然语言处理等领域。算法的原理简单易懂,实现起来也比较简单,但由于其对初始簇中心的选取比较敏感,容易陷入局部最优解,因此需要对多组初始点进行试验,选取平均 SSE 最小的那组作为最终结果。另外,K-means 算法还需要指定聚类个数 k,而这个 k 的选取有时是比较主观的,需要根据实际问题进行调整。
K-means代码实现
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)")
DESCAN算法
对于大部分点都聚集在一块区域而剩下的点距离它们很远的情况,使用传统的 K-means 算法可能不太合适,因为 K-means 算法倾向于将所有点聚类到最近的中心点附近,这会导致远离簇心的点很难被正确地归类。因此,更适合这种情况的聚类算法是 DBSCAN(Density-Based Spatial Clustering of Applications with Noise,带有噪声的基于密度的空间聚类)算法。
DBSCAN 算法用于在高维空间中聚类具有足够密集性(即密度大)的点,并将那些无法划分到任何类别中的稀疏点标记为噪声。该算法利用了局部密度的概念,将每个点分为核心点、边界点和噪声点,具体规则如下:
- 核心点(core point):指一个点,如果在它的周围半径 ϵ (即以该点为圆心,以 ϵ 为半径的圆)内有至少 MinPts 个其他点,则该点是一个核心点。
- 边界点(border point):指一个点,如果在它的周围半径 ϵ 内没有足够数量的点,但它仍然属于某个核心点的邻域,则该点是一个边界点。
- 噪声点(noise point):指一个点,如果它不是核心点也不是边界点,则它就是一个噪声点。
该算法从任一点开始,并迭代地建立一个领域集和簇。具体来说,算法的流程如下:
- 初始化所有点的标记为未分配,并选择下一个未访问的点 p。
- 如果 p 周围的点的数量大于MinPts,将 p 与周围的点标记为核心点,并将它们添加到簇中。
- 如果 p 是边界点,则将它添加到与其相关的簇中。
- 如果 p 是噪声点,则将其标记为噪声点。
- 继续遍历 p 的邻域,递归处理簇,并将其标记为已处理。
- 重复步骤 1-5 直到所有点都被访问。
代码实例
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)")