详细原理可以参考链接:
https://www.cnblogs.com/pinard/p/6208966.html
这是找到的相对很详细的介绍了,此链接基本仍是周志华《机器学习》中的内容,不过这个链接更通俗一点,且算法流程感觉比《机器学习》书中的伪代码形式算法流程更清晰。
参考《机器学习》中的伪代码编写的算法,具体代码如下:
function [clusterIndex, dataType] = DBSCAN(data, r, minPts)
% 本函数用于基于密度的聚类(DBSCAN)
% 输入data为聚类数据,每行一个数据点
% 输入r为密度聚类的滑动窗口半径参数
% 输入minPts为滑动窗口内数据量阈值参数
% 输出clusterIndex为数据对应的类别号组成的序列
% 输出objType为数据样本类型,0表示噪声样本,1表示边缘对象,2表示核心对象
% 数据量大小
dataSize = length(data(:, 1));
% 预分配输出的内存
clusterIndex = zeros(dataSize, 1);
dataType = zeros(dataSize, 1);
% 获得距离方阵
distanceMatrix = squareform(pdist(data));
% 标记核心对象集合
% 这里直接用距离矩阵判断,相当于把自己到自己的距离0也算进去
for i = 1 : dataSize
if sum(distanceMatrix(:, i) < r) >= minPts
dataType(i) = 2;
end
end
% 保存核心对象
coreObj = data(dataType == 2, :);
% 初始化聚类簇的标记
k = 0;
% 初始化未被访问的样本集合
noData = data;
% 进入迭代,如果核心对象集合非空,则进入迭代过程
while ~isempty(coreObj)
% 记录当前未被访问样本集合,noData数据每次迭代会更新
noDataOld = noData;
% 随机选择一个核心对象作为初始迭代对象
randCoreIndex = randperm(length(coreObj(:, 1)), 1);
initCoreObj = coreObj(randCoreIndex, :);
% 初始化队列,最初Q只有一个初始核心对象
Q = initCoreObj;
% 注:集合运算后默认是将集合排序过的,需要加stable参数来保持原来的顺序
% 从未访问样本中删去随机选择的这个核心对象,只删除这一个,不删除它密度直达的对象
noData = setdiff(noData, initCoreObj, 'stable', 'rows');
% 进入循环,找到Q的所有密度可达对象,并放入队列Q中
while ~isempty(Q)
% 取出Q中的第一个元素
q = Q(1, :);
Q(1, :) = [];
% 再次距离计算,找到点q邻域内的数据个数qSize
qIndex = ismember(data, q, 'rows') == 1;
qSize = sum(distanceMatrix(qIndex, :) <= r);
% q邻域内的数据
qAeraData = data(distanceMatrix(qIndex, :) <= r, :);
% 如果数量超过阈值,则将邻域内的的未访问的数据赋给队列Q
if qSize >= minPts
% 求交集D = qAereData∩noData
D = intersect(qAeraData, noData, 'stable', 'rows');
% 将D中的元素加入队列Q
Q = union(Q, D, 'stable', 'rows');
% 再将这些数据从未访问数据noData中删除掉
noData = setdiff(noData, D, 'stable', 'rows');
end
end
k = k + 1;
% 生成聚类簇
kCluster = setdiff(noDataOld, noData, 'stable', 'rows');
coreObj = setdiff(coreObj, kCluster, 'stable', 'rows');
% 记录类编号
clusterIndex(ismember(data, kCluster, 'rows')) = k;
end
% 遍历得到边缘对象(非核心对象,但在类中)
for i = 1 : dataSize
if (dataType(i) ~= 2) && (clusterIndex(i) ~= 0)
dataType(i) = 1;
end
end
% 函数结束
end
主函数调用:
clear all; close all;
model_class = 3;
dim = 2;
% 期望值
m = [0, 0;
2, 2;
-2, -2];
% 协方差阵
s(:, :, 1) = [0.1, 0;
0, 0.1];
s(:, :, 2) = [0.5, 0.3;
0.3, 0.5];
s(:, :, 3) = [1, -0.5;
-0.5, 1];
num = [500, 500, 500];
data = generate_data_GMM(dim, model_class, m, s, num);
data = data(:, (1:2));
[T, N] = DBSCAN(data, 0.27, 9);
figure(1)
for i = 1 : length(T)
if N(i) == 0
plot(data(i, 1), data(i, 2), '*k');
hold on;
% 边缘对象
elseif N(i) == 1
if T(i) == 1
plot(data(i, 1), data(i, 2), 'or');
hold on;
elseif T(i) == 2
plot(data(i, 1), data(i, 2), 'og');
hold on;
elseif T(i) == 3
plot(data(i, 1), data(i, 2), 'ob');
hold on;
else
plot(data(i, 1), data(i, 2), 'oy');
hold on;
end
else % 核心对象
if T(i) == 1
plot(data(i, 1), data(i, 2), '.r');
hold on;
elseif T(i) == 2
plot(data(i, 1), data(i, 2), '.g');
hold on;
elseif T(i) == 3
plot(data(i, 1), data(i, 2), '.b');
hold on;
else
plot(data(i, 1), data(i, 2), '.y');
hold on;
end
end
grid on;
end
% watermelon4_0 = load('watermelon4.0.txt');
% data = watermelon4_0;
聚类结果如下:
注:主函数给了两个例子,一个是基于高斯分布数据,一个是基于《机器学习》书籍上的西瓜数据集4.0。这里高斯分布数据用到了笔者自编写的generate_data_GMM函数,这个函数详细说明及代码请查看:
https://blog.csdn.net/sangnanpo/article/details/104222339
DBSCAN的主要优点有:
DBSCAN的主要缺点有:
附:西瓜数据集4.0
0.697 0.460
0.774 0.376
0.634 0.264
0.608 0.318
0.556 0.215
0.403 0.237
0.481 0.149
0.437 0.211
0.666 0.091
0.243 0.267
0.245 0.057
0.343 0.099
0.639 0.161
0.657 0.198
0.360 0.370
0.593 0.042
0.719 0.103
0.359 0.188
0.339 0.241
0.282 0.257
0.748 0.232
0.714 0.346
0.483 0.312
0.478 0.437
0.525 0.369
0.751 0.489
0.532 0.472
0.473 0.376
0.725 0.445
0.446 0.459