浅入浅出:PageRank算法 使用 TextRank 算法为文本生成关键字和摘要 基于物品的协同过滤 如何使用MapReduce实现基于物品的协同过滤(1) 如何使用MapReduce实现基于物品的协同过滤(2) 浅入浅出:K近邻算法 使用mahout下的朴素贝叶斯分类器对新闻分类 使用Affinity Propagation进行聚类 K-medoids聚类 矩阵分解在推荐系统中的应用:NMF和经典SVD实战 使用特征递归消除筛选特征 如何分配权重 比较NMF、PCA和VQ 方差和协方差 基于SVD的协同过滤 逻辑斯谛回归代码实现 隐语义模型和NMF(非负矩阵分解) 使用PCA处理MNIST数据集 使用GBDT选取特征 基于贝叶斯的文本分类系统的数据库设计 在hadoop1.2.1上安装mahout 0.9 Hadoop 2.4 实现Kmeans聚类算法 在Iris数据集上对比PCA、LDA、NMF 基于贝叶斯的文本分类实战 单层决策树 Logistic regression(逻辑斯蒂回归) 基于用户的协同过滤 词袋模型与文档-词矩阵 如何实现拼音与汉字的互相转换 梯度下降法 如何判定相似度 MovieLens数据集介绍 基于KNN的文本分类实战 Jasper文本分类系列博客阅读摘录 使用 Mean Shift进行聚类 朴素贝叶斯的三个常用模型:高斯、多项式、伯努利 使用决策树处理iris数据集 浅入浅出:从Kmeans到Kmeans++ 如何持久化scikit-learn中训练好的模型 浅入浅出:DBSCAN聚类算法(1) 浅入浅出:DBSCAN聚类算法(2) 2015阿里移动推荐算法比赛第一赛季总结 爬山算法 使用朴素贝叶斯分类器划分邮件 层次聚类 基于MapReduce的频繁项集挖掘 搜狗实体关系提取比赛

浅入浅出:DBSCAN聚类算法(2)


#机器学习


2014-03-14

上篇: 浅入浅出:DBSCAN聚类算法(1)

本文讲述DBSCAN的matlab实现,笔者使用的是Matlab R2012b。请在资料[1]的链接中下载源码DBSCAN.M。

1、先解决一个错误

源代码若以DBSCAN.M为文件名,使用时会出现以下错误:

>> x=[randn(30,2)*.4;randn(40,2)*.5+ones(40,1)*[4 4]];
>> [class,type]=dbscan(x,5,[])
Cannot find an exact (case-sensitive) match for 'dbscan.m'

The closest match is F:\project\clustering\DBSCAN.M

To change the file extension, cd to the file's folder, type:
   movefile DBSCAN.M DBSCAN.m_bad; movefile DBSCAN.m_bad DBSCAN.m
and then cd back.

将DBSCAN.M更名为dbscan.m即可。

2、示例

将dbscan.m放入当前工作区间,

>> x=[randn(3,2)*.4;randn(4,2)*.5+ones(4,1)*[4 4]]
x =
    0.0092   -0.1143
   -0.1048   -0.3325
   -0.7001   -0.3917
    3.4218    4.2600
    3.7332    3.9900
    2.9987    3.9826
    4.4821    3.6009

>> [class,type]=dbscan(x,3,2)
class =
    -1    -1    -1     1     1     1     1
type =
    -1    -1    -1     1     1     1     1

>> [class,type]=dbscan(x,2,2)
class =
     1     1     1     2     2     2     2
type =
     1     1     1     1     1     1     1

>> [class,type]=dbscan(x,2,[])
class =
     1     1     1     2     2     2     2
type =
     1     1     1     1     1     1     1

数据集x含有7个2维的数据点,函数dbscan有三个参数,第一个参数是数据集;第二个参数是核心对象应该满足的其邻域内最少的数据点的个数;第三个参数用来指定邻域半径,如果值为[],则函数自己判断半径。返回值中class是来标识每个数据点的所属簇的编号,编号从1开始,0表示尚未被分类,-1表示该数据点是噪声,type用来表示数据点是否分类了,0表示未被0表示尚未被分类,-1表示噪声,1表示已经分类。 由x本身的特点可以看出,dbscan(x,2,2)dbscan(x,2,[])都达到了效果;dbscan(x,3,2)因为第二个参数选的不好,效果不好。

3、分析dbscan.m

下面是dbscan.m去掉大部分注释后的内容:

% -------------------------------------------------------------------------
% Function: [class,type]=dbscan(x,k,Eps)
% -------------------------------------------------------------------------
% Input: 
% x - data set (m,n); m-objects, n-variables
% k - number of objects in a neighborhood of an object 
% Eps - neighborhood radius, if not known avoid this parameter or put []
% -------------------------------------------------------------------------
% Output: 
% class - vector specifying assignment of the i-th object to certain 
% cluster (m,1)
% type - vector specifying type of the i-th object 
% -------------------------------------------------------------------------
% Example of use:
% x=[randn(30,2)*.4;randn(40,2)*.5+ones(40,1)*[4 4]];
% [class,type]=dbscan(x,5,[])
% clusteringfigs('Dbscan',x,[1 2],class,type)
% -------------------------------------------------------------------------

function [class,type]=dbscan(x,k,Eps)

[m,n]=size(x);

if nargin<3 | isempty(Eps)
   [Eps]=epsilon(x,k);   %分析可能的邻域半径Eps,关于函数epsilon,见下面的论述
end

x=[[1:m]' x];  %为每个数据点添加编号,其实是合并两个矩阵
[m,n]=size(x);
type=zeros(1,m);
no=1;
touched=zeros(m,1); %标记某数据点是否已被访问

for i=1:m
    if touched(i)==0;
       ob=x(i,:);
       D=dist(ob(2:n),x(:,2:n));  %使用下面的dist函数生成针对当前点的距离矩阵
       ind=find(D<=Eps);   %见下面的论述

       if length(ind)>1 & length(ind)<k+1       
          type(i)=0;
          class(i)=0;
       end
       if length(ind)==1
          type(i)=-1;
          class(i)=-1;  
          touched(i)=1; 
       end

       if length(ind)>=k+1; 
          type(i)=1;
          class(ind)=ones(length(ind),1)*max(no);

          while ~isempty(ind)
                ob=x(ind(1),:);
                touched(ind(1))=1;
                ind(1)=[];
                D=dist(ob(2:n),x(:,2:n));
                i1=find(D<=Eps);

                if length(i1)>1
                   class(i1)=no;
                   if length(i1)>=k+1;
                      type(ob(1))=1;
                   else
                      type(ob(1))=0;
                   end

                   for i=1:length(i1)
                       if touched(i1(i))==0
                          touched(i1(i))=1;
                          ind=[ind i1(i)];   
                          class(i1(i))=no;
                       end                    
                   end
                end
          end
          no=no+1; 
       end
   end
end

i1=find(class==0);
class(i1)=-1;
type(i1)=-1;

%...........................................
function [Eps]=epsilon(x,k)

% Function: [Eps]=epsilon(x,k)
% Input: 
% x - data matrix (m,n); m-objects, n-variables
% k - number of objects in a neighborhood of an object

[m,n]=size(x);

Eps=((prod(max(x)-min(x))*k*gamma(.5*n+1))/(m*sqrt(pi.^n))).^(1/n);

%............................................
function [D]=dist(i,x)

% function: [D]=dist(i,x)						    
% Input: 
% i - an object (1,n)
% x - data matrix (m,n); m-objects, n-variables	    
%                                                                 
% Output: 
% D - Euclidean distance (m,1)

[m,n]=size(x);
D=sqrt(sum((((ones(m,1)*i)-x).^2)'));

if n==1
   D=abs((ones(m,1)*i-x))';
end

3.1、epsilon函数如何分析出可能的邻域半径r(或者是Eps)

epsilon函数中有调用了4个函数,maxmin用来获取矩阵每一列的最大、最小值,prod将矩阵的每一列进行列内相乘。示例:

>> x=[1 3 ; 2 4]
x =
     1     3
     2     4

>> max(x)
ans =
     2     4

>> min(x)
ans =
     1     3

>> prod(x)
ans =
     2    12

gamma函数有两个重要的特性,若n为正整数,那么gamma(n) = (n-1)!,另外一个特征就是它与pi的关系:

gamma(1/2) = pi.^(1/2)
gamma(3/2) = 1/2*(pi.^(1/2))
gamma(5/2) = 3/4*(pi.^(1/2))
...  

详细请参考资料[2]。

为了更好的理解,对下面的代码(或者说是等式)进行变换:

Eps=((prod(max(x)-min(x))*k*gamma(.5*n+1))/(m*sqrt(pi.^n))).^(1/n);

把Eps换成r方便些:

r=((prod(max(x)-min(x))*k*gamma(.5*n+1))/(m*sqrt(pi.^n))).^(1/n);

最终变换:

r.^n * sqrt(pi.^n)  / gamma(.5*n+1) * (m / k)=  prod(max(x)-min(x))          %(公式1)

公式1中r代表邻域半径,n是数据的维度,m是数据点的个数,k是核心对象的邻域内至少应具有的数据点数。

在二维空间中,n=2,公式左边r.^n * sqrt(pi.^n) / gamma(.5*n+1)代表半径为r的圆的面积;假定所有核心对象的邻域互相没有交集,m/k则是各个核心对象的个数,也是最终的不想交的由邻域构成的圆的个数。公式右边则代表着这样一个矩形的面积:矩形的各个边平行于横坐标轴或者纵坐标轴,切这个矩形是能涵盖所有数据点的最小矩形。由此可见公式1的合理性。同理,n的值更大时,也有相同的结论。所以,epsilon函数求得的邻域半径是合理的。

3.2、距离矩阵与find函数

dbscan.m中查找数据点邻域中数据点的方法是使用距离矩阵,find函数用来查找符合某个条件的索引,例如:

>> a = [4,2,5,3,9,7]
a =
     4     2     5     3     9     7

>> find(a>7)
ans =
     5

>> find(a>3)
ans =
     1     3     5     6

>> a = [4,2,5 ; 3,9,7]
a =
     4     2     5
     3     9     7

>> find(a>3)
ans =
     1
     4
     5
     6

4、可视化聚类结果

要可视化聚类结果很简单,笔者实现了一个可视化2D数据聚类结果的函数:

% -------------
% 输入:
% data:数据集
% class:每个数据点的分类,-1表示噪点,分类从1开始
% -------------

function color_cluster(data, class)
color_theme = {'ro','g+','bx','ks','r+','go','k*','r+','gv','bs','k+', ...
    'rv','gx','bo','kx','rs','gs','b+','ko',...
    'rx','g+','bv','kv'};
non_class_color = 'b*'; %噪点
[m, n] = size(data);
subplot(1,2,1);
plot(data(:,1),data(:,2),'ko');
title('raw data');
subplot(1,2,2);

for x = 1:m
    if class(x) == -1
        plot(data(x,1),data(x,2) ,non_class_color)
    else
        plot(data(x,1),data(x,2) ,cell2mat(color_theme(class(x))))
    end
    hold on;
end
title('result');

我们测试一下:

x=[randn(30,2)*.4;randn(40,2)*.5+ones(40,1)*[4 4]];
[class,type]=dbscan(x,5,[]);
color_cluster(x,class);

结果如下:

5、资料

[1] DBSCAN的matlab实现 https://code.google.com/p/dmfa07/downloads/detail?name=DBSCAN.M

[2] Γ函数 http://zh.wikipedia.org/wiki/Γ函数



( 本文完 )