一. 目标描述
聚类算法是一类无监督机器学习算法,即在使用该算法时不需要知道数据的标签,而是通过数据各个维度之间的某些特征对数据集进行划分。
现在已有的聚类算法很多,划分方式也多种多样。这里主要是针对使用聚类中心来直接划分聚类的算法进行优化,同时对不适用聚类中心直接划分的聚类算法优化方式进行一个探究。
将使用以下两个聚类算法来进行说明。
1. k均值聚类(k-means)
2. 密度峰值聚类(dpc)
二. 优化k均值聚类
1. K均值聚类简介
k-means中的k表示聚类中心数,确定k后,其聚类过程如下啊:
(1). 群体中随机设置k个聚类中心。
(2). 所有个体自动归类为距自己最近的中心。
(3). 更新聚类中心为该聚类的重心。
(4). 直到聚类中心不再变动,否则跳至步骤2。
(5). 输出4中得到的聚类中心所划分的聚类。
k-means的聚类过程比较简洁,也容易实现,只是对初始化的中心比较敏感,初始位置较差时,容易陷入局部最优。同时还有循环中心的问题,即通过T代聚类中心划分的聚类计算除了T+1代聚类中心,通过T+1代聚类中心划分的聚类计算出了T+2代聚类中心,但是T代与T+1代聚类中心不同,T代与T+1代聚类中心相同。
使用优化算法来确定聚类中心大致是解决了上述两个问题。
优化算法中的一个个体表示聚类算法中的一组聚类中心。优化算法中的适应度函数可以通过计算各个数据到其聚类中心的距离和来计算,并求其最小值。可以简单描述为使用优化算法找到一组聚类中心,使所有数据与其相对应的聚类中心的距离之和最小。
如k=3,数据维度为2,数据数量为n,为(a,b),中心为(x,y),距离使用欧式距离进行计算,则适应度函数计算如下:
其中n1+n2+n3=n,表示划分聚类后的各类数据。
2. 代码实现
文件名 ..\optimization algorithm\application_kmeans\Kmeans_Model.m
该类未实现原k-means算法迭代过程,如有需要可自行实现。
% k-means 模型
classdef Kmeans_Model < handle
properties
% 聚类数量,默认2个
k = 2;
% 数据维度,默认为2
dim = 2;
% 数据集m行*n列的矩阵,n = dim
dataset = [];
% 每一维度的取值范围上下界
bound_up = [];
bound_low = [];
end
methods
% 构造函数
function self = Kmeans_Model(data)
self.dataset = data;
self.bound_up = zeros(1,self.dim);
self.bound_low = zeros(1,self.dim);
for i = 1:self.dim
self.bound_up(i) = max(self.dataset(:,i));
self.bound_low(i) = min(self.dataset(:,i));
end
end
% 输入为向量,输出为适应度值
function value = fit_function(self,x)
% 将向量x,转化为数个点坐标
point_list = self.get_point_from_vector(x);
value = 0;
% 遍历所有样本,求其到最近的聚类中心的距离和
for i = 1:length(self.dataset)
[dist,id] = self.get_distance(self.dataset(i,:),point_list);
value = value + dist;
end
end
% 计算样本到各中心的最小距离,默认为欧式距离
function [dist_min,id] = get_distance(self,pos,point_list)
dist_min = realmax('double');
id = 1;
% 返回到最近的中心的距离和中心id
for i = 1:self.k
dist = sqrt(sum((pos-point_list(i,:)).^2));
if dist < dist_min
dist_min = dist;
id = i;
end
end
end
% 从输入的向量中获取点的坐标
function point_list = get_point_from_vector(self,x)
point_list = [];
for i = 1:(length(x)/self.dim)
% x中相邻数个值组成一个点
point = x((i-1)*self.dim+1:i*self.dim);
% 将该点加入列表
point_list = [point_list;point];
end
end
% 绘图,num为图片编号
function draw(self,input,num)
set(gca,'XLim',[self.bound_low(1) self.bound_up(1)]);
set(gca,'YLim',[self.bound_low(2) self.bound_up(2)]);
% 没有数据则绘制数据点
if isempty(input)
for i = 1:length(self.dataset)
scatter(self.dataset(i,1),self.dataset(i,2),10,'b','filled');
hold on;
end
return;
end
% 有数据则绘制各数据点属于哪个聚类中心
point_list = self.get_point_from_vector(input);
color_list = linspace(1,10,self.k);
% 按照x坐标,从小到大排序
[value,index] = sort([point_list(:,1)]);
% 绘制数据点
for i = 1:length(self.dataset)
data = self.dataset(i,:);
[dist,id] = self.get_distance(data,point_list);
center_id = find(index==id,1);
color = color_list(center_id);
scatter(data(1),data(2),10,color,'filled','MarkerEdgeColor','k');
hold on;
end
% 绘制聚类中心
for i = 1:self.k
center_id = find(index==i,1);
color = color_list(center_id);
scatter(point_list(i,1),point_list(i,2),100,color,'h','filled','MarkerEdgeColor','k');
hold on;
end
text(self.bound_up(1)*0.9+self.bound_low(1)*0.1,self.bound_up(2)*0.9+self.bound_low(2)*0.1,num2str(num),'FontSize',20);
axis square;
end
end
end
- 测试代码
文件名 ..\optimization algorithm\application_kmeans\Test.m
%% 清理之前的数据
% 清除所有数据
clear all;
% 清除窗口输出
clc;
%% 随机生成数据集
a = unifrnd(-pi,pi,100,1);
ra = unifrnd(0,10,100,1);
b = unifrnd(-pi,pi,100,1);
rb = unifrnd(0,5,100,1);
c = unifrnd(-pi,pi,100,1);
rc = unifrnd(0,6,100,1);
data = [
sin(a(:)).*ra(:)+10,cos(a(:)).*ra(:)+10;
sin(b(:)).*rb(:)-10,cos(b(:)).*rb(:)+5;
sin(c(:)).*rc(:),cos(c(:)).*rc(:);
];
model = Kmeans_Model(data);
model.k = 3;
% 获取每一维的取值范围
range_max = repmat(model.bound_up,1, model.k);
range_max = reshape(range_max, 1, numel(range_max));
range_min = repmat(model.bound_low, 1,model.k);
range_min = reshape(range_min, 1, numel(range_min));
%% 添加目录
% 将上级目录中的frame文件夹加入路径
addpath('../frame')
% 引入差分进化算法
addpath('../algorithm_differential_evolution')
%% 算法实例
dim = model.dim*model.k;
% 种群数量
size = 50;
% 最大迭代次数
iter_max = 100;
% 取值范围上界
range_max_list = range_max;
% 取值范围下界
range_min_list = range_min;
% 实例化差分进化算法类
base = DE_Impl(dim,size,iter_max,range_min_list,range_max_list);
base.is_cal_max = false;
% 确定适应度函数
base.fitfunction = @model.fit_function;
% 运行
base.run();
disp(['复杂度',num2str(base.cal_fit_num)]);
%% 下面绘制动态图
% 绘制每一代的路径
for i = 1:length(base.position_best_history)
model.draw(base.position_best_history(i,:),i);
% 每0.01绘制一次
pause = 0.01;
%下面是保存为GIF的程序
frame=getframe(gcf);
% 返回单帧颜色图像
imind=frame2im(frame);
% 颜色转换
[imind,cm] = rgb2ind(imind,256);
filename = ['kmeans_',num2str(model.k),'.gif'];
if i==1
imwrite(imind,cm,filename,'gif', 'Loopcount',inf,'DelayTime',1e-4);
else
imwrite(imind,cm,filename,'gif','WriteMode','append','DelayTime',pause);
end
if i
运行效果如下:
三. 优化密度峰值聚类
1. 密度峰值聚类简介
密度峰值聚类是通过定义和计算数据的密度来进行聚类的。其具体实现步骤如下:
(1) 确定密度计算半径dc
半径dc(或者截距)是一个经验值,需要手动输入,后面将使用优化算法计算。
(2) 计算所有数据的密度
在该数据半径dc范围内的数据数量称为该数据的密度。
如图,当dc=1时A的密度为2(半径内有B,C),当dc=2时,A的密度为3(半径内有B,C,D)。
密度rho可表示为
其中num(dist
使用公式(2)计算当dc = 1时,可得出,A的密度为2,当dc=1.5时, A的密度依然为2。
使用公式(3)时,当dc =1时,A的密度=2+1/1=3,当dc = 1.5时,A的密度=2+1/1.5=8/3;当dc = 2时A的密度=3+2/2=4,当dc=100时,A的密度=3+2/100=3.02。可以看出,使用公式(3)时,密度不在一定是一个整数,它会随着dc的大小有着一定变化。
(3) 计算相对距离
密度最大的点的相对距离delta为它到最远点的距离。
其他点的相对距离delta为到密度大于自己的点的最近距离。
(4) 确定聚类中心
一般的,将选取密度和相对距离均较大的点作为聚类中心。可以通过计算rho*delta的值来确定,将离群较远的点作为聚类中心。
(5) 划分聚类
如果该点不是(4)中的聚类中心,那么它的父节点是密度大于自己且距离自己最近的点。
如1-10个点,1,4为聚类中心,划分聚类的结果如下
节点 | 父节点 |
---|---|
1 | 无 |
2 | 5 |
3 | 2 |
4 | 无 |
5 | 4 |
6 | 1 |
7 | 10 |
8 | 1 |
9 | 7 |
10 | 7 |
(6) 递归合并
遍历各个节点将其赋给其父节点,直至父节点为聚类中心
节点 | 父节点 | 聚类链 | 聚类中心 |
---|---|---|---|
1 | 无 | 无 | 1 |
2 | 5 | 2-5-4 | 4 |
3 | 2 | 3-2-5-4 | 4 |
4 | 无 | 无 | 4 |
5 | 4 | 5-4 | 4 |
6 | 1 | 6-1 | 1 |
7 | 10 | 7-10-1 | 1 |
8 | 1 | 8-1 | 1 |
9 | 7 | 9-7-10-1 | 1 |
10 | 1 | 10-1 | 1 |
2. 适应度函数设计
个人的思考,无严格证明,仅供参考。
从密度峰值聚类流程可以看出,过程中有两个需要手动确定的步骤:dc的值,聚类中心的数量。只要确定了这两个值,就能确定聚类的划分情况,所以适应度函数的输入定为dc和聚类数量。
输入已经确定,下面就要思考适应度输出的计算了。
思路1:输出为各类之间最近距离,使该值最大化,即各类之间的最近距离越大越好。
这是一个不错的值,当聚类中心数为2时,效果很好,但是使用该值时,计算得出的聚类中心数也必定是2。
如图,A,B,C表示三个聚类中距离其他类最近的点(A类中距B类最近的点和距C类最近的点不一定重合,假设重合,方便说明)。当分3类(正解)时,可知最近距离的分别为0.5,1,1.2。故最近距离的最小值为0.5。此时分2类时会出现将AB划分为一类的情况,该情况下,最近距离变为了1。
思路2:计算各类到其他类的最近距离之和,使其最大化,同样是希望各类之间的距离越大越好。
此时会出现另一种情况,会产生无数距离很接近的聚类划分。如上图,按照3类划分为A,B,C,在此规则下C类分裂为了 CDEF。
划分3类时,计算适应度函数为AB+AB+AC=2。分裂后适应度函数为 AB+AB+ CD+CD+CF+EF=2.2。此时聚类中心的数量会非常的多,聚类过度划分。
思路3:在思路2的基础上,计算最近距离时减去半径作为结果。
上图中,假设半径dc为0.1,那么聚类中心数为3时,适应度函数AB+BC+AC-dc3=1.7,划分为6类时AB+AB+ CD+CD+CF+EF-60.1=1.6。
计算距离时减去dc的主要目的是防止聚类过于集中,当聚类很集中时,其计算的最近距离可能为负数,同时也会让dc取较小值。
为了保证dc的值不会过大或者过小,并使所有个体的密度不为0(极端dc=0的情况),同时所有个体的密度不全为数据总数(极端为dc=正无穷),dc的取值范围将设置为:数据点距其他点最近距离的最大值——数据点距其他店最远距离的最小值。
3.代码实现
文件名:..\optimization algorithm\application_dpc\DPC_Model.m
% dpc 模型
classdef DPC_Model < handle
properties
% 数据维度
dim = 2;
% 数据集m行*n列的矩阵,n = dim
dataset = [];
% 距离矩阵,为n*n的方阵,n = dataset的长度
dist_mat=[];
% 局部密度矩阵,为(1行*n列)的矩阵,n = dataset的长度
rho_mat=[];
% 相对距离矩阵,为(1行*n列)的矩阵,n = dataset的长度
delta_mat=[];
% 记录各个数据的父类id,为(1行*n列)的矩阵,n = dataset的长度
members_parent_id=[];
% 记录各个数据的聚类中心id,为(1行*n列)的矩阵,n = dataset的长度
members_center_index=[];
center_num = 2;
dc = 1;
% 各聚类中心的id
center_ids = [];
% 每一维度的取值范围上下界
bound_up = [];
bound_low = [];
end
methods
% 构造函数
function self = DPC_Model(data)
self.dataset = data;
self.bound_up = zeros(1,self.dim);
self.bound_low = zeros(1,self.dim);
for i = 1:self.dim
self.bound_up(i) = max(self.dataset(:,i));
self.bound_low(i) = min(self.dataset(:,i));
end
% 初始化距离方阵
self.dist_mat = zeros(length(self.dataset));
% 计算距离矩阵
self.cal_dist_mat();
self.init();
end
% 初始化数据
function init(self)
self.delta_mat = zeros(1,length(self.dataset));
% 取出聚类中心在样本中的id
self.center_ids = ones(1,self.center_num);
self.members_parent_id = zeros(1,length(self.dataset));
self.members_center_index = zeros(1,length(self.dataset));
end
% 计算dc的取值范围:最近距离的最大值--最远距离的最小值
function [min_dc,max_dc]=get_dc_range(self)
% 求距离矩阵中每一行的最小值
% 将对角线设置为最大,方便求最小值
self.dist_mat(logical(eye(size(self.dist_mat)))) = realmax('double');
% 计算每一行的最小值
[dist_min,index_min] = min(self.dist_mat,[],2);
% 计算最小值中的最大值
min_dc = max(dist_min);
% 将对角线设置为0,方便求最大值
self.dist_mat(logical(eye(size(self.dist_mat)))) = 0;
% 计算每一行的最大值
[dist_max,index_max] = max(self.dist_mat,[],2);
% 计算最大值中的最小值
max_dc = min(dist_max);
end
% 输入为向量,输出为适应度值
function value = fit_function(self,x)
% 第一维作为截距
self.dc = x(1);
% 第二维向下取整作为聚类数
self.center_num = floor(x(2));
% 初始化数据
self.init();
% 计算密度矩阵
self.cal_rho_mat();
% 计算相对距离矩阵
self.cal_delta_mat();
% 聚类
self.cluster();
% 计算适应度值
dist_min = self.get_dist_min();
value = dist_min-self.dc*self.center_num;
end
% 绘图
function draw(self)
set(gca,'XLim',[self.bound_low(1) self.bound_up(1)]);
set(gca,'YLim',[self.bound_low(2) self.bound_up(2)]);
color_list = linspace(1,10,self.center_num);
% 绘制数据点
for i = 1:length(self.dataset)
data = self.dataset(i,:);
center_index = self.members_center_index(i);
color = color_list(center_index);
scatter(data(1),data(2),10,color,'filled','MarkerEdgeColor','k');
hold on;
end
for i = 1:self.center_num
scatter(self.dataset(self.center_ids(i),1),self.dataset(self.center_ids(i),2),40,'r','filled','MarkerEdgeColor','k');
hold on;
end
axis square;
end
end
% 受保护的方法,继承用
methods (Access = protected)
% 计算样本到之间距离,默认为欧式距离
function cal_dist_mat(self)
for i = 1:length(self.dataset)
for j = i+1:length(self.dataset)
% 计算各个数据之间的欧式距离
dist = sqrt(sum((self.dataset(i,:)-self.dataset(j,:)).^2));
self.dist_mat(i,j) = dist;
self.dist_mat(j,i) = dist;
end
end
end
% 计算局部密度矩阵
function cal_rho_mat(self)
% 计算距离小于截距的点的数量
a = self.dist_mat < self.dc;
self.rho_mat = sum(a);
max_dist = zeros(1,length(self.dataset));
for i = 1:length(self.dataset)
max_dist(i) = max(self.dist_mat(i,a(i,:)));
end
self.rho_mat = self.rho_mat+max_dist/self.dc;
end
% 计算相对距离矩阵
function cal_delta_mat(self)
% 将密度矩阵从大到小排序
[value,index] = sort([self.rho_mat],'descend');
% 局部密度最大的数据id
id_max = index(1);
self.delta_mat(id_max) = max(self.dist_mat(id_max,:));
for i = 2:length(self.dataset)
self.delta_mat(index(i)) = min(self.dist_mat(index(i),index(1:i-1)));
end
end
% 对数据进行聚类
function cluster(self)
% 将rho与delta相乘,取值最大的数个为聚类中心
[value,index] = sort([self.rho_mat.*self.delta_mat],'descend');
for i = 1:self.center_num
self.center_ids(i) = index(i);
end;
% 遍历样本,将其分类给最近的且密度大于自己的数据
for i = 1:length(self.dataset)
dist = realmax('double');
% 取出大于该个体密度的数据id
better_ids = self.rho_mat > self.rho_mat(i);
for id = 1:length(self.dataset)
if better_ids(id) == 0
% 密度小于当前数据,跳过
continue
end
% 如果该数据是聚类中心则跳过
if ismember(i,self.center_ids)
continue
end
% 记录密度大于自己的最近数据id
if self.dist_mat(i,id) < dist
dist = self.dist_mat(i,id);
self.members_parent_id(i) = id;
end
end
if self.members_parent_id(i)==0
for id = 1:self.center_num
% 如果自己是聚类中心,则赋值自己
if self.center_ids(id) == i
self.members_parent_id(i) = i;
else
% 否则赋值最近的聚类中心
% 记录密度大于自己的最近数据id
if self.dist_mat(i,self.center_ids(id)) < dist
dist = self.dist_mat(i,self.center_ids(id));
self.members_parent_id(i) = self.center_ids(id);
end
end
end
end
self.members_parent_id(index(1)) = index(1);
end
% 遍历样本,将其分类给最近的且密度大于自己的数据
for i = 1:length(self.dataset)
self.members_center_index(i) = self.get_center_id(i);
end
end
% 递归查找聚类中心
function center_index=get_center_id(self,data_id)
for i = 1:self.center_num
% 如果自己是聚类中心直接返回
if data_id == self.center_ids(i)
center_index = i;
return
end
if self.center_ids(i) == self.members_parent_id(data_id)
center_index = i;
return;
end
end
center_index = self.get_center_id(self.members_parent_id(data_id));
return ;
end
% 计算各类到其他类的最小距离之和
function dist_min = get_dist_min(self)
% 创建一个临时矩阵,1*center_num,记录各聚类与其他类的最小距离
dist_min_mat = ones(1,self.center_num)*realmax('double');
for i = 1:length(self.dataset)
% 获取该数据的聚类中心
center_index = self.members_center_index(i);
% 获取与该数据不是一个中心的数据
other_ids = center_index~=self.members_center_index;
dist = min(self.dist_mat(i,other_ids));
if dist < dist_min_mat(center_index)
dist_min_mat(center_index) = dist;
end
end
dist_min = sum(dist_min_mat);
end
end
end
4.测试代码
文件名:..\optimization algorithm\application_dpc\Test.m
随机生成了5个簇,优化算法中,聚类中心数的取值范围设置为了2-10
%% 清理之前的数据
% 清除所有数据
clear all;
% 清除窗口输出
clc;
a = unifrnd(-pi,pi,100,1);
ra = unifrnd(0,5,100,1);
b = unifrnd(-pi,pi,100,1);
rb = unifrnd(0,5,100,1);
c = unifrnd(-pi,pi,100,1);
rc = unifrnd(0,6,100,1);
data = [
sin(a(:)).*ra(:)+10,cos(a(:)).*ra(:)+10;
sin(b(:)).*rb(:)-10,cos(b(:)).*rb(:)-10;
sin(c(:)).*rc(:),cos(c(:)).*rc(:);
sin(c(:)).*rc(:)-10,cos(c(:)).*rc(:)+10;
sin(b(:)).*rb(:)+10,cos(b(:)).*rb(:)-10;
];
model = DPC_Model(data);
model.get_dc_range();
[min_dc,max_dc] = model.get_dc_range();
range_max = [max_dc,10];
range_min = [min_dc,2];
%% 添加目录
% 将上级目录中的frame文件夹加入路径
addpath('../frame')
% 引入差分进化算法
addpath('../algorithm_differential_evolution')
%% 算法实例
dim = 2;
% 种群数量
size = 10;
% 最大迭代次数
iter_max = 20;
% 取值范围上界
range_max_list = range_max;
% 取值范围下界
range_min_list = range_min;
% 实例化差分进化算法类
base = DE_Impl(dim,size,iter_max,range_min_list,range_max_list);
base.is_cal_max = true;
% 确定适应度函数
base.fitfunction = @model.fit_function;
% 运行
base.run();
disp(['复杂度',num2str(base.cal_fit_num)]);
disp(model.fit_function(base.position_best));
model.draw();
结果如下:
四. 总结
本文介绍了使用优化算法优化k均值聚类和密度峰值聚类这两个聚类算法。
其中k均值聚类的核心在于聚类中心的确定,对于由迭代计算聚类中心来完成聚类的算法我们可以直接使用优化算法确定其聚类中心。
密度峰值聚类中聚类的结果有密度半径dc以及聚类数确定,但它没有直接可用的值作为适应度函数,因此需要根据实际情况构造适应度函数,文中的适应度函数仅供参考,实际应用需要自行构造。
文中使用的差分进化算法实现可以看优化算法matlab实现(七)差分进化算法matlab实现。如果想使用其他优化算法,则引入相关的优化算法路径后,实例化即可。
文件目录如下:
..\optimization algorithm\application_kmeans\Kmeans_Model.m |
..\optimization algorithm\application_kmeans\Test.m |
..\optimization algorithm\application_dpc\DPC_Model.m |
..\optimization algorithm\application_dpc\Test.m |
..\optimization_algorithm\frame\Unit.py |
..\optimization_algorithm\frame\Algorithm_Impl.py |
..\optimization_algorithm\algorithm_differential_evolution\DE_Unit.py |
..\optimization_algorithm\algorithm_differential_evolution\DE_Base.py |
..\optimization_algorithm\algorithm_differential_evolution\DE_Impl.py |