贝叶斯网络结构学习之K2算法(基于FullBNT-1.0.4的MATLAB实现)

题目:贝叶斯网络结构学习之K2算法(基于FullBNT-1.0.4的MATLAB实现)

        有关贝叶斯网络结构学习的一基本概念可以参考:贝叶斯网络结构学习方法简介

        有关函数输入输出参数的解释可以参考:贝叶斯网络结构学习若干问题解释

 

        对于应用贝叶斯评分进行结构学习的研究,国外的学者做了很多相关的工作。其中最著名的是Cooper和Herskovits提出的K2算法,他们应用贝叶斯评分和爬山法搜索的方法来优化网络模型,其中的评分函数即为贝叶斯评分的一种简化,又被称为K2评分。(摘自【张鸿勋. 基于K2评分的贝叶斯网结构学习算法的研究[D]. 北京工业大学, 2009.】)

        K2算法于【Cooper G F, Herskovits E. A Bayesian method for the inductionof probabilistic networks from data[J]. Machine Learning, 1992, 9(4):309-347.】中提出,算法流程如下:

 

算法第10行中的Pred(x_i)的意思是根据节点顺序(nodeordering)排在x_i前面的节点:

 

算法第7、10、11行出现的函数g即为评分函数:

 

        为什么取名叫K2算法呢?原文中给出了解释:

 

        本文基于FullBNT-1.0.4工具箱,可自行搜索下载链接,附录1给出了几个参考链接。在MATLAB中设置第三方工具箱的步骤参见附录2,若MATLAB版本不同,可自行搜索添加步骤,大同小异。

        工具箱添加完成后,即可在MATLAB的命令行窗口(commandwindows)使用help命令查看函数的帮助文件,使用edit命令打开函数的源文件。

        函数learn_struct_K2.m目录:\FullBNT-1.0.4\BNT\learning\learn_struct_K2.m,接下来简单解释一下输入和输出参数。

 

        输入:(注意除特别标注外,其余均来自learn_struct_K2.m本身)

       data:大小n*m,其中n为节点个数,m为数据集的大小(即流程中的databaseD)

% data(i,m) = value of node i in case m (can be a cell array).

        ns:大小n*1,每个node可能的取值个数(一般要求是离散值)

        以下注释来自\FullBNT-1.0.4\BNT\general\mk_bnet.m,learn_struct_K2.m中的注释太简单

% node_sizes(i) is the number of values node i can take on,
%   or the length of node i if i is a continuous-valued vector.
% node_sizes(i) = 1 if i is a utility node. 

        order:大小n*1,节点之间顺序,K2算法需要先设定各节点之间的顺序,即专家知识

% order(i) is the i'th node in the topological ordering.

        max_fan_in:大小n*1,每个节点最大的父亲节点个数(即流程中的upper bound u),默认为节点个数

% max_fan_in - this the largest number of parents we allow per node [N]

       scoring_fn:评分函数的类型,默认为贝叶斯评分

% scoring_fn - 'bayesian' or 'bic' [ 'bayesian' ]
%              Currently, only networks with all tabular nodes support Bayesian scoring.

 

        输出:

        dag:大小n*n,若第i个节点是第j个节点的父亲,则dag(i,j)=1,否则dag(i,j)=0

 

        函数\FullBNT-1.0.4\BNT\learning\learn_struct_K2.m的代码:

function dag = learn_struct_K2(data, ns, order, varargin)
% LEARN_STRUCT_K2 Greedily learn the best structure compatible with a fixed node ordering
% best_dag = learn_struct_K2(data, node_sizes, order, ...)
%
% data(i,m) = value of node i in case m (can be a cell array).
% node_sizes(i) is the size of node i.
% order(i) is the i'th node in the topological ordering.
%
% The following optional arguments can be specified in the form of name/value pairs:
% [default value in brackets]
%
% max_fan_in - this the largest number of parents we allow per node [N]
% scoring_fn - 'bayesian' or 'bic' [ 'bayesian' ]
%              Currently, only networks with all tabular nodes support Bayesian scoring.
% type       - type{i} is the type of CPD to use for node i, where the type is a string
%              of the form 'tabular', 'noisy_or', 'gaussian', etc. [ all cells contain 'tabular' ]
% params     - params{i} contains optional arguments passed to the CPD constructor for node i,
%              or [] if none.  [ all cells contain {'prior', 1}, meaning use uniform Dirichlet priors ]
% discrete   - the list of discrete nodes [ 1:N ]
% clamped    - clamped(i,m) = 1 if node i is clamped in case m [ zeros(N, ncases) ]
% verbose    - 'yes' means display output while running [ 'no' ]
%
% e.g., dag = learn_struct_K2(data, ns, order, 'scoring_fn', 'bic', 'params', [])
%
% To be backwards compatible with BNT2, you can also specify arguments as follows
%   dag = learn_struct_K2(data, node_sizes, order, max_fan_in)    
%
% This algorithm is described in
% - Cooper and Herskovits,  "A Bayesian method for the induction of probabilistic
%      networks from data", Machine Learning Journal 9:308--347, 1992

[n ncases] = size(data);

% set default params
type = cell(1,n);
params = cell(1,n);
for i=1:n
  type{i} = 'tabular';
  %params{i} = { 'prior', 1 };
  params{i} = { 'prior_type', 'dirichlet', 'dirichlet_weight', 1 };
end
scoring_fn = 'bayesian';
discrete = 1:n;
clamped = zeros(n, ncases);

max_fan_in = n;
verbose = 0;

args = varargin;
nargs = length(args);
if length(args) > 0 
  if isstr(args{1})
    for i=1:2:nargs
      switch args{i},
       case 'verbose',    verbose = strcmp(args{i+1}, 'yes');
       case 'max_fan_in', max_fan_in = args{i+1}; 
       case 'scoring_fn', scoring_fn = args{i+1};
       case 'type',       type = args{i+1}; 
       case 'discrete',   discrete = args{i+1}; 
       case 'clamped',    clamped = args{i+1}; 
       case 'params',     if isempty(args{i+1}), params = cell(1,n); else params = args{i+1};  end
      end
    end
  else
    max_fan_in = args{1};
  end
end

dag = zeros(n,n);

for i=1:n
  ps = [];
  j = order(i);
  u = find(clamped(j,:)==0);    
  score = score_family(j, ps, type{j}, scoring_fn, ns, discrete, data(:,u), params{j});
  if verbose, fprintf('\nnode %d, empty score %6.4f\n', j, score); end
  done = 0;
  while ~done & (length(ps) <= max_fan_in)
    pps = mysetdiff(order(1:i-1), ps); % potential parents
    nps = length(pps);
    pscore = zeros(1, nps);
    for pi=1:nps
      p = pps(pi);
      pscore(pi) = score_family(j, [ps p], type{j}, scoring_fn, ns, discrete, data(:,u), params{j});
      if verbose, fprintf('considering adding %d to %d, score %6.4f\n', p, j, pscore(pi)); end
    end
    [best_pscore, best_p] = max(pscore);
    best_p = pps(best_p);
    if best_pscore > score
      score = best_pscore;
      ps = [ps best_p];
      if verbose, fprintf('* adding %d to %d, score %6.4f\n', best_p, j, best_pscore); end
    else
      done = 1;
    end
  end
  if ~isempty(ps) % need this check for matlab 5.2
    dag(ps, j) = 1;
  end
end

 

        这个函数很简单,基本与上图中的K2流程一模一样,函数调用了评分函数score_family.m,可以通过参数设置不同类型的评分函数。

 

 

        工具箱提供了函数learn_struct_K2.m的使用例子k2demo1.m,文件目录:\FullBNT-1.0.4\BNT\examples\static\StructLearn\k2demo1.m

N = 4;
dag = zeros(N,N);
%C = 1; S = 2; R = 3; W = 4;
C = 4; S = 2; R = 3; W = 1; % arbitrary order
dag(C,[R S]) = 1;
dag(R,W) = 1;
dag(S,W)=1;

false = 1; true = 2;
ns = 2*ones(1,N); % binary nodes

bnet = mk_bnet(dag, ns);
bnet.CPD{C} = tabular_CPD(bnet, C, [0.5 0.5]);
bnet.CPD{R} = tabular_CPD(bnet, R, [0.8 0.2 0.2 0.8]);
bnet.CPD{S} = tabular_CPD(bnet, S, [0.5 0.9 0.5 0.1]);
bnet.CPD{W} = tabular_CPD(bnet, W, [1 0.1 0.1 0.01 0 0.9 0.9 0.99]);

seed = 0;
rand('state', seed);
randn('state', seed);
ncases = 100;
data = zeros(N, ncases);
for m=1:ncases
  data(:,m) = cell2num(sample_bnet(bnet));
end

order = [C S R W];
max_fan_in = 2;

%dag2 = learn_struct_K2(data, ns, order, 'max_fan_in', max_fan_in, 'verbose', 'yes');
  
sz = 5:5:50;
for i=1:length(sz)
  dag2 = learn_struct_K2(data(:,1:sz(i)), ns, order, 'max_fan_in', max_fan_in);
  correct(i) = isequal(dag, dag2);
end
correct

for i=1:length(sz)
  dag3 = learn_struct_K2(data(:,1:sz(i)), ns, order, 'max_fan_in', max_fan_in, 'scoring_fn', 'bic', 'params', []);
  correct(i) = isequal(dag, dag3);
end
correct

        这个例子很简单:定义贝叶斯网络-->根据贝叶斯网生成数据集-->根据数据集使用K2算法学习贝叶斯网络结构,并与定义的真实网络结构作对比,以判断K2算法重构性能。

        前7行代码是自己定义一个贝叶斯网络结构,第9~16行代码是给出这个贝叶斯网的条件概率表(即,参数),第18~25行是根据前16行定义的贝叶斯网来生成数据集。剩下的代码是分别采用了两种评分函数(第34行采用的是默认的贝叶斯评分,第41行采用的是BIC评分)根据数据集使用K2算法学习网络结构,并比较网络结构与真实结构的差异。值得注意的是,两处调用learn_struct_K2均未直接使用整个数据集,而是将数据集前50个每5个分成一小份,第1次使用数据集前5个,第2次使用数据集前10个,第3次使用数据集前15个,依此类推,每次将学得的网络结构与真实的网络结构作对比。通过结果可以发现,当数据集大于30时即可基本学习到正确的贝叶斯网络结构。

        运行输出结果如下:

 

correct =

     0     0     0     0     0     1     1     1     1     1


correct =

     0     0     0     0     0     1     0     1     1     1

        (@20180922更新)鉴于评论中有网友问这个结果什么意思,那今天就补充这一段。注意sz=5:5:50,共包含10个元素值,而后面分别调用了10次learn_struct_K2学习dag2和dag3,尤其注意输入的data在每次循环时取前sz(i)个,因此共得到了10个dag2和10个dag3,每次循环会把学习到的dag2(或dag3)与真实的dag比较(isequal函数),若两个矩阵相等则输出为1,反之为0,比较结果存入correct。所以上面两个correct分别代表了10个dag2和10个dag3与真实dag的关系。从第1个correct输出可以看出,当learn_struct_K2输入的data样本小于等于25时学到的dag2与真实的dag均不相等(correct前五个值为0),当learn_struct_K2输入的data样本大于等于30时学到的dag2与真实的dag均相等(correct前五个值为1);同理解释第2个correct即可。
         有了以上例子,可进一步琢磨每一个输入参数的含义,进而可将learn_struct_K2函数用起来了,但是K2算法需要事先给定节点顺序,好处是可以充分利用专家知识,坏处是如果无法事先确定节点顺序怎么办?所以我们还需要掌握一些不需要输入节点顺序的贝叶斯网络结构学习函数(当不知道节点顺序但又想用K2算法时,有一种做法是随机生成若干个顺序如1000个分别学习多个贝叶斯网络结构,然后选择一个最好的结构)。另外,在learn_struct_K2中调用了评分函数score_family,既然评分函数都有了,那么就可以自行设计搜索策略例如模拟退火、遗传算法等进而得到新的基于评分的贝叶斯网络结构学习算法。

 

 

附录1:BNT下载链接

        原来作者(http://www.cs.ubc.ca/~murphyk/)的主页的BNT工具箱链接已经失效,在网上搜了几个下载链接,我用的是FullBNT-1.0.4.zip,在CSDN下载的。

        1、FullBNT-1.0.4.zip

        http://download.csdn.net/download/yinzuooguigui/9014175

        http://www.pudn.com/downloads464/sourcecode/math/detail1949349.html

        https://pan.baidu.com/s/1o7X2Sqe 密码vted

        https://github.com/bayesnet/bnt

        2、FullBNT-1.0.7.zip

        https://pan.baidu.com/share/link?shareid=3829960033&uk=2955152529

        3、作者murphyk主页Bayes工具箱汇总

        http://www.cs.ubc.ca/~murphyk/Bayes/junk/bnsoft.html

        上面链接列出了很多Bayes工具箱,虽然很多链接已经失效了。

附录2:在MATLAB中添加工具箱的步骤

        以我正在使用的MATLAB R2014a为例,其它版本请自行在网上搜索,大同小异。

        第1步:单击“设置路径”(matlab版本不同,主要是这一步不同,只要找到“设置路径”一般后面步骤都差不多)

 

        第2步:单击“添加并包含子文件夹”

 

 

        第3步:找到BNT工具箱目录,例如FullBNT-1.0.4,单击“选择”,如下图所示,再单击“保存”后再单击“关闭”即可。

 

 

        此时,你就可以像使用MATLAB自带的函数一样使用BNT工具箱里的函数了,若想查看某函数的代码,可以在MATLAB的命令行窗口(command windows)使用edit命令,语法与help命令相同,例如“edit mk_bnet”即可打开mk_bnet.m源文件。

 

  • 35
    点赞
  • 145
    收藏
    觉得还不错? 一键收藏
  • 18
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 18
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值