层次聚类
距离度量
层次聚类算法有多种,它们的步骤基本相同,差别在于聚类间距的定义不
同。计算聚类距离间距的计算方法主要有:
凝聚层次聚类
AGNES(Agglomerative Nesting) 是凝聚的层次聚类算法,如果簇C1中的一个对象和簇C2中的一个对象之间的距离是所有属于不同簇的对象间欧式距离中最小的,C1和C2可能被合并。这是一种单连接方法,其每个簇可以被簇中的所有对象代表,两个簇之间的相似度由这两个簇中距离最近的数据点对的相似度来确定。
合并型层次聚类及产生二分树图的基本步骤如下:
- 计算n个对象两两之间的距离。
- 构造n个单成员聚类 C 1 , C 2 , . . . , C n C_1, C_2,..., C_n C1,C2,...,Cn。,每一类的高度都为0。
- 找到两个最近的聚类 C i , C j C_i,C_j Ci,Cj; 合并 C i , C j C_i,C_j Ci,Cj;,聚类的个数减少1,以被合并的两个类间距作为上层的高度。
- 计算新生成的聚类与本层中其他聚类的间距,如果满足终止条件,算法结束,否则转(3)。
算法描述:
input:包含n个对象的数据库,终止条件簇的数目k
output:k个簇
(1) 将每个对象当成一个初始簇
(2) Repeat
(3) 根据两个簇中最近的数据点找到最近的两个簇
(4) 合并两个簇,生成新的簇的集合
(5) Until达到定义的簇的数目
优劣:
- 简单,但遇到合并点选择困难的情况。
- 一旦一组对象被合并,不能撤销
- 算法的复杂度为O(n的平方),不适合大数据集
code
python
def euler_distance(point1: np.ndarray, point2: list) -> float:
"""
计算两点之间的欧拉距离,支持多维
"""
distance = 0.0
for a, b in zip(point1, point2):
distance += math.pow(a - b, 2)
return math.sqrt(distance)
class ClusterNode(object):
def __init__(self, vec, left=None, right=None, distance=-1, id=None, count=1):
"""
:param vec: 保存两个数据聚类后形成新的中心
:param left: 左节点
:param right: 右节点
:param distance: 两个节点的距离
:param id: 用来标记哪些节点是计算过的
:param count: 这个节点的叶子节点个数
"""
self.vec = vec
self.left = left
self.right = right
self.distance = distance
self.id = id
self.count = count
class Hierarchical(object):
def __init__(self, pp, k = 1 ):
# Python assert(断言)用于判断一个表达式,在表达式条件为 false 的时候触发异常。
# 断言可以在条件不满足程序运行的情况下直接返回错误
assert k > 0
# k 聚类数目
self.k = k
# 标签
self.labels = None
# 预处理的数据处理类
self.pp = pp
# 数据表 行为记录
# [
# [1,2,...],
# [1,2,...],
# ...
# ]
def fit(self, x):
# enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)
# 组合为一个索引序列,同时列出数据 v 和数据下标 i
# 初始化所有节点(将所有的实例都当作一个节点)
nodes = [ClusterNode(vec=v, id=i) for i, v in enumerate(x)]
# 距离字典
# {
# (node1,node2):distance,
# (node1,node3):distance,
# ...
# }
distances = {}
# 特征的维度
# point_num:实例数目(贝叶斯网络中就是变量数目)
# future_num :特征(属性)数目(贝叶斯网络中就是实例数目)
point_num, future_num = np.shape(x)
# 初始化特征
self.labels = [ -1 ] * point_num
# 初试化当前节点ID
currentclustid = -1
# 进入凝聚的步骤 第一次循环:每条实例当作一个簇
# 截止条件:nodes中节点数目不大于 K
while len(nodes) > self.k:
# 初始化最小距离为极大值
min_dist = math.inf
# 节点数目
nodes_len = len(nodes)
# 表示最相似的两个聚类
closest_part = None
# 双层循环计算 距离
for i in range(nodes_len - 1):
for j in range(i + 1, nodes_len):
# 为了不重复计算距离,保存在字典内
d_key = (nodes[i].id, nodes[j].id)
if d_key not in distances:
distances[d_key] = euler_distance(nodes[i].vec, nodes[j].vec)
d = distances[d_key]
# 获取所有节点之间的最短距离 和节点对信息
if d < min_dist:
min_dist = d
closest_part = (i, j)
# 合并两个聚类 part1, part2是节点的ID
part1, part2 = closest_part
node1, node2 = nodes[part1], nodes[part2]
# 构建新的节点中心=======================
new_vec = [(node1.vec[ind] * node1.count + node2.vec[ind] * node2.count)
/ (node1.count + node2.count)
for ind in range(future_num)]
# 构建新的节点
new_node = ClusterNode(vec=new_vec,
left=node1,
right=node2,
distance=min_dist,
id=currentclustid,
count=node1.count + node2.count)
currentclustid -= 1
# 一定要先del索引较大的
del nodes[part2], nodes[part1]
# 新节点加入到nodes容器中
nodes.append(new_node)
self.nodes = nodes
# 打标签
self.calc_label()
def calc_label(self):
"""
调取聚类的结果
"""
for i, node in enumerate(self.nodes):
# 将节点的所有叶子节点都分类
self.leaf_traversal(node, i)
def leaf_traversal(self, node: ClusterNode, label):
"""
递归遍历叶子节点
"""
if node.left == None and node.right == None:
self.labels[node.id] = label
if node.left:
self.leaf_traversal(node.left, label)
if node.right:
self.leaf_traversal(node.right, label)
pp = PreProcess()
pp.uses()
# print(type(iris.data),iris)
my = Hierarchical(pp, 25)
# my.fit(iris.data)
my.fit(pp.ndarray())
pp.printVar()
print(np.array(my.labels))
my.calc_label()
matlab
K_cluster = 8;
%% 初始化底层节点
node = generate_node();
% 构建初始节点
nodes = init_nodes(node, varSize);
% 当做非叶子节点的id(没什么用)
currentclustid = -1;
%% j
while length(nodes) > K_cluster
len_nodes = length(nodes);
min_dist = -inf;
min_pair_node = [];
%% 选出最近的两个节点
for ind =1:len_nodes
for jnd =(ind+1):len_nodes
node_A = nodes(ind);
node_B = nodes(jnd);
distance = average_distance(node_A, node_B, MMatrix);
% 这儿是互信息,越大越好
if distance > min_dist
min_dist = distance;
% 记录
min_pair_node = [ind jnd];
end
end
end
%% 合并最近的两个节点
ind = min_pair_node(1);
jnd = min_pair_node(2);
node_A = nodes(ind);
node_B = nodes(jnd);
% 构建新的节点
new_node = merge_two_node(node_A, node_B,distance,currentclustid);
% 删除nodes中的node_A,node_B
nodes(ind) = [];
% ind位置删除之后,jnd需要减一位
nodes(jnd-1) = [];
% 添加 new_node 到 nodes
nodes(length(nodes)+1) = new_node;
% nodes = [nodes [new_node]];
% 更新节点
currentclustid = currentclustid -1;
end
function nodes = init_nodes(node, varSize)
% 模板节点 node
% 变量数目
% 根据一个节点模板生成 varSize 个叶子节点
nodes = repmat(node, varSize, 1);
for ind =1:varSize
nodes(ind).vec = [ind];
nodes(ind).left = [];
nodes(ind).right = [];
nodes(ind).distance = 0;
nodes(ind).id = ind;
nodes(ind).count = 1;
nodes(ind).leafnode = [ind];
end
end
function node = generate_node()
% 保存两个数据聚类后形成新的中心
node.vec = [];
% 左节点
node.left = [];
% 右节点
node.right = [];
% 左右两个节点的距离
node.distance = [];
% 用来标记哪些节点是计算过的
node.id = [];
% 这个节点的叶子节点个数(叶子节点的数目)
node.count = [];
% 这个节点的所以叶子节点(叶子节点集合)
node.leafnode = [];
end
function node = merge_two_node(node_A, node_B,distance,currentclustid)
% 保存两个数据聚类后形成新的中心
% node = generate_node();
node.vec = [];
% 左节点
node.left = node_A;
% 右节点
node.right = node_B;
% 左右两个节点的距离
node.distance = distance;
% 用来标记哪些节点是计算过的
node.id = currentclustid;
% 这个节点的叶子节点个数(叶子节点的数目)
node.count = node_A.count + node_B.count ;
% 这个节点的所以叶子节点(叶子节点集合)
node.leafnode = [node_A.leafnode node_B.leafnode];
end
function distance = average_distance(node_A, node_B, MMatrix)
% Average distance
% A 节点
% B 节点
% 距离矩阵
sum_distance = 0;
% 计算 sum distance
lenA = length(node_A.leafnode) ;
lenB = length(node_B.leafnode);
% 计算平均距离
for i=1:lenA
for j=1:lenB
X = node_A.leafnode(i);
Y = node_B.leafnode(j);
dist = MMatrix(X,Y);
sum_distance = sum_distance + dist;
end
end
% 数 目
n_i_n_j = node_A.count*node_B.count;
distance = sum_distance/n_i_n_j;
end