目录
4 谱聚类
谱聚类是一种利用无向图进行聚类的方法。实际上,就是让相似的点之间进行连线,从而形成一个无向图进行分析的方法,具体如下图所示。
建一个图 G = ( V , E ) G = (V, E) G=(V,E),存放方式用的是邻接矩阵 w w w。如果两点不相连,则 w i j = 0 w_{ij} = 0 wij=0,否则 w i j = d ( i , j ) w_{ij} = d(i, j) wij=d(i,j), d d d函数的基本特性就是离得越近,函数值越大,可以是反比例,也可以是用很大的数去减掉距离,总之只要满足要求就可以。特别地, w i i = 0 w_{ii} = 0 wii=0。
4.1 建图方法
我们首先要知道,每一个点我们都设为是一个节点。那么建图方法主要分为以下三种:
- ** ϵ \epsilon ϵ-邻居建图。**每一个节点而言,在一定半径范围内的点都连边,令 w i j = d ( v i , v j ) w_{ij} = d(v_i, v_j) wij=d(vi,vj)
- **k-邻居建图。**实际上就是KNN,K邻近的点就连边,令
w
i
j
=
d
(
v
i
,
v
j
)
w_{ij} = d(v_i, v_j)
wij=d(vi,vj)
- 需要满足 v i v_i vi是 v j v_j vj的K邻近点,同时 v j v_j vj也是 v i v_i vi的K邻近点,才可以连边。
- 满足 v i v_i vi是 v j v_j vj的K邻近点或者 v j v_j vj是 v i v_i vi的K邻近点其中之一就连边。
- **全连接。**顾名思义,两点之间全部连边。
4.2 一些数学概念
- **度矩阵 D D D。**这是一个对角矩阵,其中 d i i d_{ii} dii就等于 w w w矩阵第 i i i行的和,也就是 d i i = ∑ j = 1 n w i j d_{ii} = \sum_{j = 1}^nw_{ij} dii=∑j=1nwij。它实际上就是说这个点连向其他点的权重和,也就是总共出去(进来)了多少。
- 拉普拉斯矩阵 L L L(未标准化的)。 L = D − W L = D - W L=D−W
- 标准化拉普拉斯矩阵。
- L s y m = D − 1 / 2 W D − 1 / 2 L_{sym} = D^{-1/2}WD^{-1/2} Lsym=D−1/2WD−1/2
- L r w = D − 1 L = I − D − 1 W L_{rw} = D^{-1}L = I - D^{-1}W Lrw=D−1L=I−D−1W
4.3 无标准化的谱聚类步骤
- 首先建图,生成邻接矩阵 W ∈ R n ∗ n W\in\R^{n*n} W∈Rn∗n
- 计算未标准化的拉普拉斯矩阵 L = D − W L = D - W L=D−W
- 计算拉普拉斯矩阵 L L L的 k k k(聚类数量)个最小的特征向量 v 1 , v 2 , . . . , v k v_1, v_2, ..., v_k v1,v2,...,vk
- 这些特征向量作为列向量拼接成一个新的矩阵 V ∈ R n ∗ k V\in\R^{n * k} V∈Rn∗k
- 之后,将矩阵 V V V中的每一行作为向量进行分析,即 y i y_i yi为矩阵 V V V的第 i i i行
- 使用K-Means算法将 { y i ∈ R k } \{y_i \in \R^k\} {yi∈Rk}进行聚类,分为 C 1 , C 2 , . . . , C k C_1, C_2, ..., C_k C1,C2,...,Ck
- 最终的聚类结果就是 A 1 , A 2 , . . . , A k A_1, A_2, ..., A_k A1,A2,...,Ak,其中 A i = { j ∣ y j ∈ C i } A_i = \{j|y_j \in C_i\} Ai={j∣yj∈Ci}。实际上就是说一个类里面有 y y y几就说明点几属于这个类。
4.4 标准化的谱聚类步骤
和4.3一样,只不过第二步和第三步有一点区别:
- 首先建图,生成邻接矩阵 W ∈ R n ∗ n W\in\R^{n*n} W∈Rn∗n
- 计算标准化的拉普拉斯矩阵 L ′ = L r w L' = L_{rw} L′=Lrw
- 计算拉普拉斯矩阵 L ′ L' L′的 k k k(聚类数量)个最小的特征向量 v 1 , v 2 , . . . , v k v_1, v_2, ..., v_k v1,v2,...,vk
- 这些特征向量作为列向量拼接成一个新的矩阵 V ∈ R n ∗ k V\in\R^{n * k} V∈Rn∗k
- 之后,将矩阵 V V V中的每一行作为向量进行分析,即 y i y_i yi为矩阵 V V V的第 i i i行
- 使用K-Means算法将 { y i ∈ R k } \{y_i \in \R^k\} {yi∈Rk}进行聚类,分为 C 1 , C 2 , . . . , C k C_1, C_2, ..., C_k C1,C2,...,Ck
- 最终的聚类结果就是 A 1 , A 2 , . . . , A k A_1, A_2, ..., A_k A1,A2,...,Ak,其中 A i = { j ∣ y j ∈ C i } A_i = \{j|y_j \in C_i\} Ai={j∣yj∈Ci}。实际上就是说一个类里面有 y y y几就说明点几属于这个类。
4.5 归一化和不归一化(标准化)的区别
不标准化的结果更倾向于类之间点的数量接近
标准化的结果更倾向于类里面点的密度更加接近
具体如下图:
4.6 k的选定
谱聚类的好处就是可以确定 k k k的值。
拉普拉斯矩阵在计算特征向量的时候,顺便计算一下特征值。如果特征值有突变,那就可以确定 k k k为突变前的值了。
比如说上面这张图所示的情况,4和5之间有突变,所以最终可以确定 k = 4 k = 4 k=4。
其实就是找最大的 Δ k = ∣ λ k − λ k − 1 ∣ \Delta k = |\lambda_k - \lambda_{k - 1}| Δk=∣λk−λk−1∣
4.7 原理
简单来说,将各个点之间连线之后,要对其进行分类,实际上就是进行图的最小割操作。在这其中,最重要的就是拉普拉斯矩阵 L L L。我们就以未经过标准化的拉普拉斯矩阵为例进行讲解。
4.7.1 拉普拉斯矩阵特点
拉普拉斯矩阵 L L L具有以下特点:
- 对于每一个向量 f ∈ R n f \in \R^n f∈Rn,我们有 f T L f = 1 2 ∑ i = 1 n ∑ i = 1 n w i j ( f i − f j ) 2 f^TLf = \frac{1}{2}\sum_{i = 1}^n\sum_{i = 1}^nw_{ij}(f_i - f_j)^2 fTLf=21∑i=1n∑i=1nwij(fi−fj)2
- 拉普拉斯矩阵 L L L是对称矩阵且是半正定的
- 拉普拉斯矩阵 L L L的最小特征值为 0 0 0,对应的特征向量为矩阵 1 1 1
- 拉普拉斯矩阵 L L L有 n n n个非负特征值,并且 0 = λ 1 ≤ λ 2 ≤ . . . ≤ λ n 0 = \lambda_1 \leq \lambda_2 \leq ... \leq \lambda_n 0=λ1≤λ2≤...≤λn
下面来对这些特点进行证明。
对于每一个向量
f
∈
R
n
f \in \R^n
f∈Rn,我们有
f
T
L
f
=
1
2
∑
i
=
1
n
∑
i
=
1
n
w
i
j
(
f
i
−
f
j
)
2
f^TLf = \frac{1}{2}\sum_{i = 1}^n\sum_{i = 1}^nw_{ij}(f_i - f_j)^2
fTLf=21∑i=1n∑i=1nwij(fi−fj)2:
f
T
L
f
=
f
T
D
f
−
f
T
W
f
=
∑
i
=
1
n
f
i
2
d
i
−
∑
i
=
1
n
∑
j
=
1
n
f
i
f
j
w
i
j
=
1
2
(
∑
i
=
1
n
d
i
f
i
2
−
2
∑
i
=
1
n
∑
j
=
1
n
f
i
f
j
w
i
j
+
∑
j
=
1
n
d
j
f
j
2
)
=
1
2
(
∑
i
=
1
n
∑
j
=
1
n
w
i
j
f
i
2
−
∑
i
=
1
n
∑
j
=
1
n
f
i
f
j
w
i
j
+
∑
i
=
1
n
∑
j
=
1
n
w
j
i
f
j
2
)
=
1
2
∑
i
=
1
n
∑
i
=
1
n
w
i
j
(
f
i
−
f
j
)
2
\begin{align*} f^TLf &= f^TDf - f^TWf = \sum_{i = 1}^nf_i^2d_i -\sum_{i = 1}^n\sum_{j = 1}^nf_if_jw_{ij} \\ &= \frac{1}{2}(\sum_{i = 1}^nd_if_i^2 - 2\sum_{i = 1}^n\sum_{j = 1}^nf_if_jw_{ij} + \sum_{j = 1}^nd_jf_j^2) \\ &= \frac{1}{2}(\sum_{i = 1}^n\sum_{j = 1}^nw_{ij}f_i^2 - \sum_{i = 1}^n\sum_{j = 1}^nf_if_jw_{ij} + \sum_{i = 1}^n\sum_{j = 1}^nw_{ji}f_j^2)\\ &= \frac{1}{2}\sum_{i = 1}^n\sum_{i = 1}^nw_{ij}(f_i - f_j)^2 \end{align*}
fTLf=fTDf−fTWf=i=1∑nfi2di−i=1∑nj=1∑nfifjwij=21(i=1∑ndifi2−2i=1∑nj=1∑nfifjwij+j=1∑ndjfj2)=21(i=1∑nj=1∑nwijfi2−i=1∑nj=1∑nfifjwij+i=1∑nj=1∑nwjifj2)=21i=1∑ni=1∑nwij(fi−fj)2
拉普拉斯矩阵
L
L
L是对称矩阵且是半正定的:
- L = D − W L = D - W L=D−W,且 D D D和 W W W矩阵为对称的,因此矩阵 L L L也是对称的
- f T L f ≥ 0 f^TLf \geq 0 fTLf≥0,因为平方肯定大于等于 0 0 0,且 w i j ≥ 0 w_{ij} \geq 0 wij≥0
拉普拉斯矩阵 L L L的最小特征值为 0 0 0,对应的特征向量为矩阵 1 1 1:
L f = ( D − W ) f = [ . . . , d i f i − ∑ j = 1 n w i j f j , . . . ] T = 0 ⋅ f , f = 1 Lf = (D-W)f = [...,d_if_i - \sum_{j = 1}^nw_{ij}f_j,...]^T = 0·f, f = 1 Lf=(D−W)f=[...,difi−∑j=1nwijfj,...]T=0⋅f,f=1
为什么是 L f Lf Lf呢,因为我们知道有 L f = λ f Lf = \lambda f Lf=λf,求解之后 λ \lambda λ就是特征值, f f f就是特征向量。而我们注意到, d i = ∑ j = 1 n w i j d_i = \sum_{j = 1}^nw_{ij} di=∑j=1nwij,那么实际上 L f Lf Lf中的每一个值 d i f i − ∑ j = 1 n w i j f j d_if_i - \sum_{j = 1}^nw_{ij}f_j difi−∑j=1nwijfj,如果 f f f是常量,那么这个值无论 i i i和 j j j取几,它都是 0 0 0,所以最小特征值肯定是 0 0 0, f = 1 f = 1 f=1
拉普拉斯矩阵 L L L有 n n n个非负特征值,并且 0 = λ 1 ≤ λ 2 ≤ . . . ≤ λ n 0 = \lambda_1 \leq \lambda_2 \leq ... \leq \lambda_n 0=λ1≤λ2≤...≤λn
这个直接就是上面两条的直接结果。
4.7.2 另一个关于拉普拉斯矩阵的小特点
对于一个无向图 G G G,如果它的每一条边都是非负数,那么它有多少个特征值为 0 0 0,取决于它有多少个连通域。特征值为 0 0 0对应的特征向量为常数向量 1 1 1。如下图所示:
可问题是,你这也没有常熟向量 1 1 1啊?别急,看下面:
L x = λ x Lx = \lambda x Lx=λx
L x 1 + L x 2 = λ 1 x 1 + λ 2 x 2 Lx_1 + Lx_2 = \lambda_1x_1 + \lambda_2x_2 Lx1+Lx2=λ1x1+λ2x2
λ 1 = λ 2 = 0 \lambda_1 = \lambda_2 = 0 λ1=λ2=0
L ( x 1 + x 2 ) = λ ( x 1 + x 2 ) L(x_1 + x_2) = \lambda(x_1 + x_2) L(x1+x2)=λ(x1+x2)
因此, x 1 + x 2 x_1 + x_2 x1+x2同样也是一个特征向量。
证明:
首先注意只有一个连通域的情况,假设 f f f是特征值为 0 0 0所对应的特征向量,那么就有 f T L f = 1 2 ∑ i = 1 n ∑ i = 1 n w i j ( f i − f j ) 2 = 0 ⋅ f T = 0 f^TLf = \frac{1}{2}\sum_{i = 1}^n\sum_{i = 1}^nw_{ij}(f_i - f_j)^2 = 0 · f^T = 0 fTLf=21∑i=1n∑i=1nwij(fi−fj)2=0⋅fT=0
唯一满足的情况就是 f i = f j f_i = f_j fi=fj,所以对于任意 i , j i,j i,j来说,都有 f i = f j f_i = f_j fi=fj.
举例来说,有
而对于有多个连通域的情况来说,其实也是一样的。我们通过交换这个大矩阵的行和列,让每一个连通域的拉普拉斯矩阵凑在一起,也就是变成:
L
=
[
L
1
L
2
⋱
L
k
]
L = \begin{bmatrix} L_1 & & & \\ & L_2 & & \\ & & \ddots & \\ & & & L_k \end{bmatrix}
L=
L1L2⋱Lk
这样,它的特征值为
0
0
0所对应的特征向量就变成了
[
1
1
0
⋯
0
0
1
2
⋯
0
⋮
⋮
⋱
⋮
0
0
⋯
1
k
]
∈
R
n
∗
k
\begin{bmatrix} \bold{1}_1 & 0 & \cdots & 0\\ 0 & \bold{1}_2 & \cdots & 0\\ \vdots & \vdots & \ddots & \vdots \\ 0 & 0 & \cdots & \bold{1}_k \end{bmatrix} \in\R^{n * k}
110⋮0012⋮0⋯⋯⋱⋯00⋮1k
∈Rn∗k
4.7.3 图的割
我们定义将图分成两个连通域,它的割就是$cut(A, B) = \sum_{i \in A, j \in B}w_{ij} \$
那么让如果是分成多个连通域,那么它的割就是 c u t ( A 1 , . . . , A k ) = ∑ i = 1 k c u t ( A i , A ˉ i ) cut(A_1,..., A_k) = \sum_{i = 1}^k cut(A_i, \bar A_i) cut(A1,...,Ak)=∑i=1kcut(Ai,Aˉi)
对于非标准化的谱聚类,我们有 R a t i o C u t ( A 1 , . . . , A k ) = ∑ i = 1 k c u t ( A i , A ˉ i ) ∣ A i ∣ RatioCut(A_1,..., A_k) = \sum_{i = 1}^k\frac{cut(A_i, \bar A_i)}{|A_i|} RatioCut(A1,...,Ak)=∑i=1k∣Ai∣cut(Ai,Aˉi)
对于标准化的谱聚类,我们有 N c u t ( A 1 , . . . , A k ) = ∑ i = 1 k c u t ( A i , A ˉ i ) v o l ( A i ) Ncut(A_1,..., A_k) = \sum_{i = 1}^k\frac{cut(A_i, \bar A_i)}{vol(A_i)} Ncut(A1,...,Ak)=∑i=1kvol(Ai)cut(Ai,Aˉi)
首先考虑只有两类的情况:
我们要求最小割,也就是如下的优化问题:
$\min_{A \subset V} RatioCut(A, \bar{A}) = \min_{A \subset V}(\frac{cut(A, \bar{A})}{|A|} + \frac{cut(\bar{A}, A)}{|\bar A|})\$
那么我们构造一个向量 f = [ f 1 , . . . , f n ] T ∈ R n f = [f_1,...,f_n]^T \in \R^n f=[f1,...,fn]T∈Rn
f i = { ∣ A ˉ ∣ / ∣ A ∣ v i ∈ A − ∣ A ∣ / ∣ A ˉ ∣ v i ∈ A ˉ f_i=\left\{ \begin{array}{rcl} \sqrt{|\bar A|/|A|} & & {v_i \in A}\\ \sqrt{-|A|/|\bar A|} & & {v_i \in \bar{A}} \end{array} \right. fi={∣Aˉ∣/∣A∣−∣A∣/∣Aˉ∣vi∈Avi∈Aˉ
通过我们构造的向量的这个特性,我们就可以有:
f i = { v i ∈ A f i ≥ 0 v i ∈ A ˉ f i < 0 f_i=\left\{ \begin{array}{rcl} \ v_i \in A & & {f_i \geq 0}\\ \ v_i \in \bar A & & {f_i < 0} \end{array} \right. fi={ vi∈A vi∈Aˉfi≥0fi<0
然后我们利用之前所得到的结论进行推论:
f
T
L
f
=
1
2
∑
i
,
j
=
1
n
w
i
j
(
f
i
−
f
j
)
2
=
1
2
∑
i
∈
A
,
j
∈
A
ˉ
w
i
j
(
∣
A
ˉ
∣
∣
A
∣
+
∣
A
∣
∣
A
ˉ
∣
)
2
+
1
2
∑
i
∈
A
ˉ
,
j
∈
A
w
i
j
(
−
∣
A
ˉ
∣
∣
A
∣
−
∣
A
∣
∣
A
ˉ
∣
)
2
=
c
u
t
(
A
,
A
ˉ
)
(
∣
A
ˉ
∣
∣
A
∣
+
∣
A
∣
∣
A
ˉ
∣
+
2
)
=
c
u
t
(
A
,
A
ˉ
)
(
∣
A
∣
+
∣
A
ˉ
∣
∣
A
∣
+
∣
A
∣
+
∣
A
ˉ
∣
∣
A
ˉ
∣
)
=
∣
V
∣
⋅
R
a
t
i
o
C
u
t
(
A
,
A
ˉ
)
\begin{align*} f^TLf &= \frac{1}{2}\sum_{i,j = 1}^nw_{ij}(f_i - f_j)^2 \\ &= \frac{1}{2}\sum_{i \in A, j \in \bar A} w_{ij}(\frac{|\bar A|}{|A|} + \frac{|A|}{|\bar A|})^2 + \frac{1}{2}\sum_{i \in \bar A, j \in A} w_{ij}(-\frac{|\bar A|}{|A|} - \frac{|A|}{|\bar A|})^2 \\ &= cut(A, \bar A)(\frac{|\bar A|}{|A|} + \frac{|A|}{|\bar A|} + 2) \\ &= cut(A, \bar A)(\frac{|A| + |\bar A|}{|A|} + \frac{|A| + |\bar A|}{|\bar A|})\\ &= |V|·RatioCut(A, \bar A) \end{align*}
fTLf=21i,j=1∑nwij(fi−fj)2=21i∈A,j∈Aˉ∑wij(∣A∣∣Aˉ∣+∣Aˉ∣∣A∣)2+21i∈Aˉ,j∈A∑wij(−∣A∣∣Aˉ∣−∣Aˉ∣∣A∣)2=cut(A,Aˉ)(∣A∣∣Aˉ∣+∣Aˉ∣∣A∣+2)=cut(A,Aˉ)(∣A∣∣A∣+∣Aˉ∣+∣Aˉ∣∣A∣+∣Aˉ∣)=∣V∣⋅RatioCut(A,Aˉ)
另外,我们所设计的
f
f
f还有两个特点:
-
f
f
f与常数向量
1
1
1垂直
- 证明:$\sum_{i = 1} ^ n f_i = \sum_{i \in A}\frac{|\bar A|}{|A|} - \sum_{i \in \bar A}\frac{|A|}{|\bar A|} = |A|\frac{|\bar A|}{|A|} - |\bar A|\frac{|A|}{|\bar A|} = 0\$
-
∣
∣
f
∣
∣
=
n
||f|| = \sqrt{n}
∣∣f∣∣=n
- 证明: ∣ ∣ f ∣ ∣ 2 = ∑ i = 1 n f i 2 = ∣ A ∣ ∣ A ˉ ∣ ∣ A ∣ − ∣ A ˉ ∣ ∣ A ∣ ∣ A ˉ ∣ = ∣ A ∣ + ∣ A ˉ ∣ = n ||f||^2 = \sum_{i = 1} ^n f_i^2 = |A|\frac{|\bar A|}{|A|} - |\bar A|\frac{|A|}{|\bar A|} = |A| + |\bar A| = n ∣∣f∣∣2=∑i=1nfi2=∣A∣∣A∣∣Aˉ∣−∣Aˉ∣∣Aˉ∣∣A∣=∣A∣+∣Aˉ∣=n
那么实际上,我们就是要求解一个优化问题:
min A ⊂ V f T L f , s . t . , f ⊥ 1 , ∣ ∣ f ∣ ∣ = n \min _{A \sub V} f^TLf, s.t.,f \perp 1, ||f|| = \sqrt n minA⊂VfTLf,s.t.,f⊥1,∣∣f∣∣=n
f i = { ∣ A ˉ ∣ / ∣ A ∣ v i ∈ A − ∣ A ∣ / ∣ A ˉ ∣ v i ∈ A ˉ f_i=\left\{ \begin{array}{rcl} \sqrt{|\bar A|/|A|} & & {v_i \in A}\\ \sqrt{-|A|/|\bar A|} & & {v_i \in \bar{A}} \end{array} \right. fi={∣Aˉ∣/∣A∣−∣A∣/∣Aˉ∣vi∈Avi∈Aˉ
但是,第二个条件也就是我们构造的向量是很难满足的,因此我们采取近似值,抛弃第二个条件,只要求
min A ⊂ V f T L f , s . t . , f ⊥ 1 , ∣ ∣ f ∣ ∣ = n \min _{A \sub V} f^TLf, s.t.,f \perp 1, ||f|| = \sqrt n minA⊂VfTLf,s.t.,f⊥1,∣∣f∣∣=n
那这个很明显就可以用到瑞利商定理(具体可以看PCA章节),但别忘了我们有两个约束条件在,其中第一个 f ⊥ 1 f \perp 1 f⊥1这个,由于最小的特征值对应的特征向量一定是常数向量,所以就找特征值第二小的。
如果不近似,那么结果就是上图左边的情况,严谨地就只有两个值。但由于我们近似了,就会出现右边的情况。那应该怎么分开呢?跑K-Means就好了!
**对于有两类以上的情况:**也是同样的道理,只是 f f f向量变为了 h h h矩阵,其他的都一样。
4.8 代码
import numpy as np
import open3d as o3d
from sklearn.neighbors import kneighbors_graph
from sklearn import datasets
n_sample = 500
noisy_moon = datasets.make_moons(n_samples=n_sample, noise=.05)
def dis(a, b) :
return (a[0] - b[0]) * (a[0] - b[0]) + (a[1] - b[1]) * (a[1] - b[1])
def kmeans(data, k, tolerance, maxIter) :
idx = np.random.choice(len(data), k, replace=False)
centroids = data[idx]
cluster = np.zeros(len(data))
dist = np.zeros(len(data))
for i in range(maxIter) :
for j in range(len(data)) :
cluster[j] = -1
dist[j] = 1e10
for j in range(len(data)) :
for k in range(len(centroids)) :
tmp = dis(data[j], centroids[k])
if tmp < dist[j] :
dist[j] = tmp
cluster[j] = k
old_centroids = np.copy(centroids)
for j in range(len(centroids)) :
dataTmp = data[cluster == j]
if len(dataTmp) > 0 :
centroids[j][0] = np.mean(dataTmp[:, 0])
centroids[j][1] = np.mean(dataTmp[:, 1])
else :
centroids[j] = data[np.random.choice(len(data), 1, replace=False)]
distortion = np.linalg.norm(centroids - old_centroids)
if distortion < tolerance :
break
return cluster, centroids
def spectral(data, k, maxIter = 100, n_neighbour = 20) :
weight = kneighbors_graph(data, n_neighbour, mode='connectivity', include_self=False)
weight = 0.5 * (weight + weight.T)
W = weight.toarray()
D = np.diag(np.sum(W, axis=1))
L = D - W
w, v = np.linalg.eig(L)
idx = np.argsort(w)[:k]
vec = v[:, idx]
cluster, centroids = kmeans(vec, k, 0.0001, maxIter)
return cluster
datas = np.zeros([len(noisy_moon[0]), 3])
for i in range(len(noisy_moon[0])) :
datas[i][0] = noisy_moon[0][i][0]
datas[i][1] = noisy_moon[0][i][1]
datas[i][2] = 0
pcd = o3d.geometry.PointCloud()
pcd.points = o3d.utility.Vector3dVector(datas)
cluster = spectral(datas, 2)
color = np.zeros([len(noisy_moon[0]), 3])
for i in range(len(noisy_moon[0])) :
if cluster[i] == 0 :
color[i][0] = 1
else :
color[i][1] = 1
pcd.colors = o3d.utility.Vector3dVector(color)
o3d.visualization.draw_geometries([pcd])
5 Mean Shift
5.1 使用方法
- 随机选择一个半径为 r r r的圆
- 将圆的圆心移动到里面点的平均值上
- 重复步骤二,直到圆心不再移动
- 重复上面三个步骤,并去掉重叠的圆(若重叠,留着包含点最多的圆)
- 每个点选择最近的圆心作为它的分类
这个方法主打一个简单,所需要设计的参数只有半径 r r r,也不用提前知道需要几类
6 DBSCAN
6.1 使用方法
- 随机选择一个没有被访问过的点 p p p,然后找到它半径为 r r r范围内的邻居点
- 判断邻居点的数量是否大于
m
i
n
_
s
a
m
p
l
e
s
min\_samples
min_samples
- 如果大于,那么我们就认为 p p p是一个核心点。此时,我们创建一个聚类 C C C,标记点 p p p为被访问过的,并继续步骤3
- 如果小于,那我们就标记点 p p p为访问过的,并标记它为噪声点
- 将这些邻居都标记为 C C C类的。如果这个点是核心点,那我们就把它作为新的点 p p p,重复该步骤
- 完成后从数据库移除聚类 C C C,然后返回步骤一
- 直到所有的点都被标记
所以这里面需要设计的参数为半径 r r r和 m i n _ s a m p l e min\_sample min_sample,而且好处就是类别数量不需要提前定好。
6.2 各种点
这就是一个 r = E p s , m i n _ s a m p l e = 4 r = Eps, min\_sample = 4 r=Eps,min_sample=4的例子。可以看到,蓝色的点没有邻居点,因此就是噪声。
红色的点则会不停的去访问其他的点,由于这个图实在是太过清晰了,不再赘述。
6.3 代码
import numpy as np
import open3d as o3d
from sklearn.neighbors import kneighbors_graph
from sklearn import datasets
from scipy.spatial import cKDTree
n_sample = 500
noisy_moon = datasets.make_moons(n_samples=n_sample, noise=.05)
class DBSCAN() :
def __init__(self, data, r, min_sample):
self.r = r
self.min_sample = min_sample
self.data = data
self.n, self.k = self.data.shape
self.vis = np.zeros(self.n)
self.kdtree = cKDTree(self.data)
self.now = 0
self.ans = np.zeros(self.n)
def check(self, p):
self.vis[p] = True
index = self.kdtree.query_ball_point(self.data[p], self.r)
if len(index) < self.min_sample :
self.ans[p] = 0
self.vis[p] = True
return
else :
self.ans[p] = self.now
for point in index :
if not self.vis[point] :
self.check(point)
def fit(self):
while(1):
if np.sum(self.vis) == self.n :
break
self.now += 1
tmp = np.where(self.vis == False)[0]
self.check(np.random.choice(tmp, 1, replace=False)[0])
return
def predict(self):
return self.ans
dbscan = DBSCAN(noisy_moon[0], 0.1, 5)
dbscan.fit()
cluster = dbscan.predict()
print(cluster)
datas = np.zeros([len(noisy_moon[0]), 3])
for i in range(len(noisy_moon[0])) :
datas[i][0] = noisy_moon[0][i][0]
datas[i][1] = noisy_moon[0][i][1]
datas[i][2] = 0
pcd = o3d.geometry.PointCloud()
pcd.points = o3d.utility.Vector3dVector(datas)
color = np.zeros([len(noisy_moon[0]), 3])
for i in range(len(noisy_moon[0])) :
if cluster[i] == 0 :
color[i][0] = 1
elif cluster[i] == 1:
color[i][1] = 1
else :
color[i][2] = 1
pcd.colors = o3d.utility.Vector3dVector(color)
o3d.visualization.draw_geometries([pcd])
7 聚类方法总结
所以一般首选DBSCAN,它准确率略差于谱聚类,但是谱聚类实在是太慢了。