INF442 Amphi 4: Hierarchical Clustering | Classes 2/2
一些词汇:
- agglomerative:凝结的,从小的cluster到大的cluster
- divisive: 从大的cluster到小的cluster
1. Hierarchical Clustering
目的:
输入n个点,输出k个Homogeneous的子类。
Rq:
- 我们的类别数量和每一个类别的尺寸有关。
- 人群,人,人脸,就是不同分类的尺度。
例如,在下面的图中,如果我们尺度小的话看到的就是11个B,大尺度就是一个A。
因此,我们产生了Hierarchical Clustering,我们引入了dendrogram 系统树图。
1.0 Dendrogram
输入一个尺度(scale,下图中的横轴),输出一个partition。
下面的图中每一次partition改进都只改进1。
1.1 构建Hierarchy的两种方法:AHC,DHC
我们一般用得更多的是AHC
Agglomerative hierarchical clustering (AHC):
- 从小的cluster到大的cluster
- 一般从每一个点出发,逐渐变为一个root
- 有点类似Kruskal’s Minimum Spanning Tree Algorithm
效率分析:
- 要n-1次merges,因为我们每次将两个分类合并,以减少一个分类。
- 在第k步的时候,
- 有 n − k n-k n−k个cluster,我们一共有 C n − k 2 C_{n-k}^2 Cn−k2种选择将两个分类合并的方法。
- 因此在第k步的时间复杂度为 Θ ( n 2 ) \Theta(n^2) Θ(n2)
Divisive hierarchical clustering (DHC)::
- 从大的cluster分割到小的cluster
- 从所有的结果出发直到叶子节点。
- 类似于kd-tree的构建。(每次换一个dimension选中位数)
效率分析:
- 要n-1次Divise,因为我们每次将一个分类分裂,以增加一个分类。
- 在第k步的时候,
- 我们有k个分类,每个分类有 n 1 , . . . , n k n_1,...,n_k n1,...,nk个元素
- 一共有 ∑ i = 1 k ( 2 n i − 1 − 1 ) \sum_{i=1}^k(2^{n_i-1}-1) ∑i=1k(2ni−1−1)种分类方法
- 因此在第k步的时间复杂度为 Θ ( 2 n ) \Theta(2^n) Θ(2n)
1.2 AHC伪代码
我们每次将两个cluster合并为一个cluster,然后提升scale的值。
- 为了快速实现合并的操作:我们将使用UnionFind
- 为了选择cluster和height,我们需要定义两个cluster之间的距离,并将这个距离作为新的height。
1.3 Cluster间距离的定义
- Single-Linkage: 两个Cluster间所有点对(Pair)的最小值。[生成的树不均衡]
- Complete-Linkage:两个Cluster间所有点对(Pair)的最大值。[生成的树均衡,但是会受到离群值的影响]
- Average-Linkage:所有点对的平均距离。
不同距离的比较:
1.4 Single-Linkage的AHC
因为两个Cluster之间的距离由他们所含有的点的最小距离确定,因此我们
- 将所有的点 P P P的边从小到大排序 O ( n 2 l o g n ) O(n^2logn) O(n2logn),因为边的数目上限为 C n 2 ∼ O ( n 2 ) C_n^2\sim O(n^2) Cn2∼O(n2)
- 每次取最小的一条边
(
P
,
P
′
)
(P,P')
(P,P′)
- 若 P , P ′ P,P' P,P′属于不同的Cluster,则将它们合并为一个。 O ( α ( n ) ) O(\alpha(n)) O(α(n))
因为每条边都要判断一次(find),因此总时间为
O
(
n
2
α
(
n
)
)
O(n^2 \alpha(n))
O(n2α(n))
引入
C
s
(
p
)
C_s(p)
Cs(p):在所有长度都小于s的边都pop掉之后得到的含有p的cluster。显然pop掉越多的边,s越大,
C
s
(
p
)
C_s(p)
Cs(p)包含p的cluster越大。
下面的图片说明了connected component,即他们两之间存在一条路。这里的直观想象就是以每一个点为圆心画一个圆,如果两个圆有交集,则这两个类联通。
Dem:
- s=0,显然成立
- 验证每一次改变CC的时候,我们都会满足这一个条件。( d ( p k , p l ) ≤ s d(p_k,p_l)\leq s d(pk,pl)≤s)
Least Common Ancestor (LCA)
合并两个点所属Cluster的最小高度。
1.5 Ultrametric
感觉就是定义一个distance
d : P × P → R d:P\times P \to \mathbb{R} d:P×P→R is an ultrametric on P P P if:
- non-negativity : d ≥ 0 d\geq 0 d≥0
- symmetry : d ( p i , p j ) = d ( p j , p i ) d(p_i,p_j)=d(p_j,p_i) d(pi,pj)=d(pj,pi)
- identity : d ( p i , p j ) = 0 → d(p_i,p_j)=0 \rightarrow d(pi,pj)=0→ p i = p j p_i = p_j pi=pj
- ultrametric inequality: d ( p i , p k ) ≤ m a x { d ( p i , p j ) , d ( p j , p k ) } d(p_i,p_k) \leq max\{d(p_i,p_j),d(p_j,p_k)\} d(pi,pk)≤max{d(pi,pj),d(pj,pk)}
我们可以证明在dendrogram中给定一个
t
h
e
t
a
theta
theta,即只考虑
[
0
,
θ
]
[0,\theta]
[0,θ]的部分,则
d
L
C
A
θ
(
p
i
,
p
j
)
:
=
height of LCA in
θ
d_{LCA}^\theta(p_i,p_j):=\text{height of LCA in } \theta
dLCAθ(pi,pj):=height of LCA in θ is an ultrametric on P。
Dem:
因为
p
i
→
p
k
⊂
p
i
→
p
j
→
p
k
p_i \to p_k \subset p_i \to p_j \to p_k
pi→pk⊂pi→pj→pk
几个重要结论:
从
θ
\theta
θ到对应的距离
d
L
C
A
θ
d^\theta_{LCA}
dLCAθ是一个bijection。
对于任意的metrics,对应的
d
L
C
A
θ
S
L
d
d_{LCA}^{\theta^d_{SL}}
dLCAθSLd不会改变太多,height of LCA is stable,但是dendrogram的结构不是稳定的。
1.6 Single-Linkage缺点:Chaining Effect
即:Clusters get merged far earlier than expected.
1.7 Phylogenetic trees
phylogenetic: 动植物种类史的
展示了动物的进化过程。
1.8 UPGMA Algorithm | Average-Linkage
输入:n个Species的集合P。
这里的
θ
\theta
θ类似于上图中的进化时刻。
所以最后输出的高度
d
L
C
A
θ
A
L
d
′
d_{LCA}^\theta\\'_{{AL}^d}
dLCAθALd′是一开始的一半。
1.9 其他的距离:Ward’s criterion
Weighted版本
Unweighted版本:
2. Classes
2.1 引入其他文件中的函数
// file fact.cpp
int fact (int n) {
if (n<=0) return 1;
else return n * fact(n-1);
}
假设我们想要使用上面这个fact.cpp
中的代码,我们需要写
#include "fact.cpp"
Rq:
“”
表示我们在当前路径下搜索,<>
表示在系统路径下搜索。- 每次我们include一个cpp,对应的文件都要重新编译一次
问题一 预先编译和 extern
Sol:预先编译
extern int fact(int);
- 告诉了编译器
fact
这个函数在外面被定义了。 - 但具体在哪里寻找需要在编译的时候指定。````
- declaration ≠ \neq = definition
总过程:
// 编译过程 Compilation
// 得到object file xxx.o文件
// 在个文件中如果有extern关键词就会生成一个link,且暂时指向null
g++ -c fact.cpp
g++ -c appli1.cpp
// 链接过程 Link edition
// 将xxx.o文件变为可执行文件
// 在这里把之前null的link用真的函数填上。
g++ -o appli1 appli1.o fact.o
问题二 weak type checking 和header files
⚠️ linker performs weak type checking
- 有可能会有越界的问题如 int 2^16 -> char 2 8 2^8 28
- 如果外面是char,内部是
extern int i;
则会读取非法内存。
Sol: header files
file1.hpp
// file1.hpp
extern char i, j, k, l; // declared but not defined
file1.cpp
// file1.cpp
#include "file1.hpp" //这里需要include头文件
// redeclared (ok) and defined:
char i = 1, j = 1, k = 1, l = 1;
file2.cpp
#include "file1.hpp"
int main(){
std::cout << (int)i; // prints 1
return 0;
}
具体使用和之前类似:
g++ -c file1.cpp
g++ -o file2 file2.cpp file1.o // 这里file2不是.o而是.cpp
Rq:
- 如果我们在引用的文件里面已经定义了一个变量,则我们不能重新再定义其类型了。
有函数的header files
- 普通的函数
- 在一个Class中的函数
底下的::
是一个scope operator。
2.2 Static members
Static data members:
- do not depend on a specific object
a1.x==a2.x
- are shared among all instances of the class
Static变量的两种定义方式:
- 定义在A.cpp中
- 定义在main.cpp中
但是不能定义在A.hpp
中,因为所有引用A.hpp的文件都会使得变量多次定义(int i多次调用)。
2.3 Access modifiers | friend func
分为3类:
- Public : everyone outside class has access
- Private : no one outside class has access (everyone inside still has access)
- Protected : used in inheritance mechanism (next lecture)
默认:
public (struct), private (class)
Friend Functions
- 对于指定的一些函数,它可以得到classe内部private的值。
- declared inside the class (anywhere, access modifiers do not matter)
- defined outside the class (possibly in other classes)
甚至整个class也可以是friend:
friend class X; // not transitive nor inherited
2.4 Nested Classes
可以利用A::B b来获取class A中另一个class B中的变量的值。
2.5 Namespaces
-
可以在多个hpp文件中定义MyNameSpace,从而在main函数中只要用MyNameSpace::A,MyNameSpace::B就可以调用对应的类别。
-
Namespaces可以nested
-
可以使用
using namespace XXX
来简化代码,后面的函数都来自XXX,如在using namespace std
之后,所有打印的代码可以只用cout
实现。