diff options
Diffstat (limited to 'sourcecodes/bnt-master/SLP/learning')
31 files changed, 3751 insertions, 0 deletions
diff --git a/sourcecodes/bnt-master/SLP/learning/@jtree_inf_engine2/cliques_from_engine.m b/sourcecodes/bnt-master/SLP/learning/@jtree_inf_engine2/cliques_from_engine.m new file mode 100644 index 00000000..cd9d871d --- /dev/null +++ b/sourcecodes/bnt-master/SLP/learning/@jtree_inf_engine2/cliques_from_engine.m @@ -0,0 +1,5 @@ +function cliques = cliques_from_engine(engine) +% CLIQUES_FROM_ENGINE Return the cliques stored inside the inf. engine (jtree) +% cliques = cliques_from_engine(engine) + +cliques = engine.cliques; diff --git a/sourcecodes/bnt-master/SLP/learning/@jtree_inf_engine2/clq_containing_nodes.m b/sourcecodes/bnt-master/SLP/learning/@jtree_inf_engine2/clq_containing_nodes.m new file mode 100644 index 00000000..8904fa49 --- /dev/null +++ b/sourcecodes/bnt-master/SLP/learning/@jtree_inf_engine2/clq_containing_nodes.m @@ -0,0 +1,24 @@ +function c = clq_containing_nodes(engine, nodes, fam) +% CLQ_CONTAINING_NODES Find the lightest clique (if any) that contains the set of nodes +% c = clq_containing_nodes(engine, nodes, family) +% +% If the optional 'family' argument is specified, it means nodes = family(nodes(end)). +% (This is useful since clq_ass_to_node is not accessible to outsiders.) +% Returns c=-1 if there is no such clique. + +if nargin < 3, fam = 0; else fam = 1; end + +if length(nodes)==1 + c = engine.clq_ass_to_node(nodes(1)); +%elseif fam +% c = engine.clq_ass_to_node(nodes(end)); +else + B = engine.cliques_bitv; + w = engine.clique_weight; + clqs = find(all(B(:,nodes), 2)); % all selected columns must be 1 + if isempty(clqs) + c = -1; + else + c = clqs(argmin(w(clqs))); + end +end diff --git a/sourcecodes/bnt-master/SLP/learning/@jtree_inf_engine2/collect_evidence.m b/sourcecodes/bnt-master/SLP/learning/@jtree_inf_engine2/collect_evidence.m new file mode 100644 index 00000000..03c00edf --- /dev/null +++ b/sourcecodes/bnt-master/SLP/learning/@jtree_inf_engine2/collect_evidence.m @@ -0,0 +1,12 @@ +function [clpot, seppot] = collect_evidence(engine, clpot, seppot) +% COLLECT_EVIDENCE Do message passing from leaves to root (children then parents) +% [clpot, seppot] = collect_evidence(engine, clpot, seppot) + +for n=engine.postorder %postorder(1:end-1) + for p=engine.postorder_parents{n} + %clpot{p} = divide_by_pot(clpot{n}, seppot{p,n}); % dividing by 1 is redundant + seppot{p,n} = marginalize_pot(clpot{n}, engine.separator{p,n}, engine.maximize); + clpot{p} = multiply_by_pot(clpot{p}, seppot{p,n}); + end +end + diff --git a/sourcecodes/bnt-master/SLP/learning/@jtree_inf_engine2/disp.m b/sourcecodes/bnt-master/SLP/learning/@jtree_inf_engine2/disp.m new file mode 100644 index 00000000..9fca2aaa --- /dev/null +++ b/sourcecodes/bnt-master/SLP/learning/@jtree_inf_engine2/disp.m @@ -0,0 +1,40 @@ +% ==== +% disp +% ==== +% +% Description : +% ------------- +% +% disp function for class inf_engine. +% +% Syntax : +% -------- +% +% [] = disp( obj ) +% +% Input(s) : +% ---------- +% +% obj - class inf_engine +% An instance of the class inf_engine +% +% Output(s) : +% ----------- +% +% Example(s) : +% ------------ +% +% disp( obj ); +% +% Reference(s) : +% -------------- +% +% See also : +% ---------- +% +% display +function [] = disp( obj ) + +disp( struct( obj ) ); + +% End of function diff --git a/sourcecodes/bnt-master/SLP/learning/@jtree_inf_engine2/display.m b/sourcecodes/bnt-master/SLP/learning/@jtree_inf_engine2/display.m new file mode 100644 index 00000000..ca40702c --- /dev/null +++ b/sourcecodes/bnt-master/SLP/learning/@jtree_inf_engine2/display.m @@ -0,0 +1,42 @@ +% ======= +% display +% ======= +% +% Description : +% ------------- +% +% display function for class inf_engine. +% +% Syntax : +% -------- +% +% [] = display( obj ) +% +% Input(s) : +% ---------- +% +% obj - class inf_engine +% An instance of the class inf_engine +% +% Output(s) : +% ----------- +% +% Example(s) : +% ------------ +% +% display( obj ); +% +% Reference(s) : +% -------------- +% +% See also : +% ---------- +% +% disp +function [] = display( obj ) + +fprintf( '\n%s =\n\n', inputname( 1 ) ); +disp( struct( obj ) ); + +% End of function + diff --git a/sourcecodes/bnt-master/SLP/learning/@jtree_inf_engine2/distribute_evidence.m b/sourcecodes/bnt-master/SLP/learning/@jtree_inf_engine2/distribute_evidence.m new file mode 100644 index 00000000..403b8970 --- /dev/null +++ b/sourcecodes/bnt-master/SLP/learning/@jtree_inf_engine2/distribute_evidence.m @@ -0,0 +1,11 @@ +function [clpot, seppot] = distribute_evidence(engine, clpot, seppot) +% DISTRIBUTE_EVIDENCE Do message passing from root to leaves (parents then children) +% [clpot, seppot] = distribute_evidence(engine, clpot, seppot) + +for n=engine.preorder + for c=engine.preorder_children{n} + clpot{c} = divide_by_pot(clpot{c}, seppot{n,c}); + seppot{n,c} = marginalize_pot(clpot{n}, engine.separator{n,c}, engine.maximize); + clpot{c} = multiply_by_pot(clpot{c}, seppot{n,c}); + end +end diff --git a/sourcecodes/bnt-master/SLP/learning/@jtree_inf_engine2/enter_evidence.m b/sourcecodes/bnt-master/SLP/learning/@jtree_inf_engine2/enter_evidence.m new file mode 100644 index 00000000..8cda6012 --- /dev/null +++ b/sourcecodes/bnt-master/SLP/learning/@jtree_inf_engine2/enter_evidence.m @@ -0,0 +1,95 @@ +function [engine, loglik] = enter_evidence(engine, evidence, varargin) +% ENTER_EVIDENCE Add the specified evidence to the network (jtree) +% [engine, loglik] = enter_evidence(engine, evidence, ...) +% +% evidence{i} = [] if X(i) is hidden, and otherwise contains its observed value (scalar or column vector). +% +% The following optional arguments can be specified in the form of name/value pairs: +% [default value in brackets] +% +% soft - a cell array of soft/virtual evidence; +% soft{i} is a prob. distrib. over i's values, or [] [ cell(1,N) ] +% +% e.g., engine = enter_evidence(engine, ev, 'soft', soft_ev) + +bnet = bnet_from_engine(engine); +ns = bnet.node_sizes(:); +N = length(bnet.dag); + +engine.evidence = evidence; % store this for marginal_nodes with add_ev option +engine.maximize = 0; + +% set default params +exclude = []; +soft_evidence = cell(1,N); + +% parse optional params +args = varargin; +nargs = length(args); +for i=1:2:nargs + switch args{i}, + case 'soft', soft_evidence = args{i+1}; + case 'maximize', engine.maximize = args{i+1}; + otherwise, + error(['invalid argument name ' args{i}]); + end +end + +onodes = find(~isemptycell(evidence)); +hnodes = find(isemptycell(evidence)); +pot_type = determine_pot_type(bnet, onodes); + if strcmp(pot_type, 'cg') + check_for_cd_arcs(onodes, bnet.cnodes, bnet.dag); +end + +if is_mnet(bnet) + pot = engine.user_pot; + clqs = engine.nums_ass_to_user_clqs; +else + % Evaluate CPDs with evidence, and convert to potentials + pot = cell(1, N); + for n=1:N + fam = family(bnet.dag, n); + e = bnet.equiv_class(n); + if isempty(bnet.CPD{e}) + error(['must define CPD ' num2str(e)]) + else + pot{n} = convert_to_pot(bnet.CPD{e}, pot_type, fam(:), ... + evidence); + end + end + clqs = engine.clq_ass_to_node(1:N); +end + + +% soft evidence +soft_nodes = find(~isemptycell(soft_evidence)); +S = length(soft_nodes); +if S > 0 + assert(pot_type == 'd'); + assert(mysubset(soft_nodes, bnet.dnodes)); +end +for i=1:S + n = soft_nodes(i); + % Modif RD - 2006/12/22 + % Why end+1 it doesn't work for me... + % replace with n and it is ok + pot{ n } = dpot( n, ns( n ), soft_evidence{ n } ); +end +% Modif RD - 2006/12/22 +% But now we have to comment this line to ensure dimension matching +%clqs = [clqs engine.clq_ass_to_node(soft_nodes)]; + + +[clpot, seppot] = init_pot(engine, clqs, pot, pot_type, onodes); +[clpot, seppot] = collect_evidence(engine, clpot, seppot); +[clpot, seppot] = distribute_evidence(engine, clpot, seppot); + +C = length(clpot); +ll = zeros(1, C); +for i=1:C + [clpot{i}, ll(i)] = normalize_pot(clpot{i}); +end +loglik = ll(1); % we can extract the likelihood from any clique + +engine.clpot = clpot; diff --git a/sourcecodes/bnt-master/SLP/learning/@jtree_inf_engine2/enter_soft_evidence.m b/sourcecodes/bnt-master/SLP/learning/@jtree_inf_engine2/enter_soft_evidence.m new file mode 100644 index 00000000..0a4346c6 --- /dev/null +++ b/sourcecodes/bnt-master/SLP/learning/@jtree_inf_engine2/enter_soft_evidence.m @@ -0,0 +1,21 @@ +function [clpot, loglik] = enter_soft_evidence(engine, clique, potential, onodes, pot_type) +% ENTER_SOFT_EVIDENCE Add the specified potentials to the network (jtree) +% [clpot, loglik] = enter_soft_evidence(engine, clique, potential, onodes, pot_type, maximize) +% +% We multiply potential{i} onto clique(i) before propagating. +% We return all the modified clique potentials. + +% only used by BK! + +[clpot, seppot] = init_pot(engine, clique, potential, pot_type, onodes); +[clpot, seppot] = collect_evidence(engine, clpot, seppot); +[clpot, seppot] = distribute_evidence(engine, clpot, seppot); + +C = length(clpot); +ll = zeros(1, C); +for i=1:C + [clpot{i}, ll(i)] = normalize_pot(clpot{i}); +end +loglik = ll(1); % we can extract the likelihood from any clique + + diff --git a/sourcecodes/bnt-master/SLP/learning/@jtree_inf_engine2/find_max_config.m b/sourcecodes/bnt-master/SLP/learning/@jtree_inf_engine2/find_max_config.m new file mode 100644 index 00000000..5053b1e8 --- /dev/null +++ b/sourcecodes/bnt-master/SLP/learning/@jtree_inf_engine2/find_max_config.m @@ -0,0 +1,35 @@ +function [mpe, clpot, seppot] = find_max_config(engine, clpot, seppot, evidence) +% FIND_MAX_CONFIG Backwards pass of Viterbi fro jtree +% function [mpe, clpot, seppot] = find_max_config(engine, clpot, seppot, evidence) +% See Cowell99 p98 + +bnet = bnet_from_engine(engine); +nnodes = length(bnet.dag); +mpe = cell(1, nnodes); +maximize = 1; + +c = engine.root_clq; +pot = struct(clpot{c}); % violate object privacy +dom = pot.domain; +[indices, clpot{c}] = find_most_prob_entry(clpot{c}); +mpe(dom) = num2cell(indices); + +for n=engine.preorder + for c=engine.preorder_children{n} + clpot{c} = divide_by_pot(clpot{c}, seppot{n,c}); + seppot{n,c} = marginalize_pot(clpot{n}, engine.separator{n,c}, maximize); + clpot{c} = multiply_by_pot(clpot{c}, seppot{n,c}); + + pot = struct(clpot{c}); % violate object privacy + dom = pot.domain; + [indices, clpot{c}] = find_most_prob_entry(clpot{c}); + mpe(dom) = num2cell(indices); + end +end + +obs_nodes = find(~isemptycell(evidence)); +% indices for observed nodes will be 1 - need to overwrite these +mpe(obs_nodes) = evidence(obs_nodes); + + + diff --git a/sourcecodes/bnt-master/SLP/learning/@jtree_inf_engine2/find_mpe.m b/sourcecodes/bnt-master/SLP/learning/@jtree_inf_engine2/find_mpe.m new file mode 100644 index 00000000..8a46c1ed --- /dev/null +++ b/sourcecodes/bnt-master/SLP/learning/@jtree_inf_engine2/find_mpe.m @@ -0,0 +1,71 @@ +function mpe = find_mpe(engine, evidence, varargin) +% FIND_MPE Find the most probable explanation of the data (assignment to the hidden nodes) +% function mpe = find_mpe(engine, evidence,...) +% +% evidence{i} = [] if X(i) is hidden, and otherwise contains its observed value (scalar or column vector). +% +% The following optional arguments can be specified in the form of name/value pairs: +% [default value in brackets] +% +% soft - a cell array of soft/virtual evidence; +% soft{i} is a prob. distrib. over i's values, or [] [ cell(1,N) ] +% + +bnet = bnet_from_engine(engine); +ns = bnet.node_sizes(:); +N = length(bnet.dag); + +engine.evidence = evidence; + +% set default params +exclude = []; +soft_evidence = cell(1,N); + +% parse optional params +args = varargin; +nargs = length(args); +for i=1:2:nargs + switch args{i}, + case 'soft', soft_evidence = args{i+1}; + otherwise, + error(['invalid argument name ' args{i}]); + end +end +engine.maximize = 1; + +onodes = find(~isemptycell(evidence)); +hnodes = find(isemptycell(evidence)); +pot_type = determine_pot_type(bnet, onodes); + if strcmp(pot_type, 'cg') + check_for_cd_arcs(onodes, bnet.cnodes, bnet.dag); +end + +hard_nodes = 1:N; +soft_nodes = find(~isemptycell(soft_evidence)); +S = length(soft_nodes); +if S > 0 + assert(pot_type == 'd'); + assert(mysubset(soft_nodes, bnet.dnodes)); +end + +% Evaluate CPDs with evidence, and convert to potentials +pot = cell(1, N+S); +for n=1:N + fam = family(bnet.dag, n); + e = bnet.equiv_class(n); + if isempty(bnet.CPD{e}) + error(['must define CPD ' num2str(e)]) + else + pot{n} = convert_to_pot(bnet.CPD{e}, pot_type, fam(:), evidence); + end +end + +for i=1:S + n = soft_nodes(i); + pot{N+i} = dpot(n, ns(n), soft_evidence{n}); +end +clqs = engine.clq_ass_to_node([hard_nodes soft_nodes]); + +[clpot, seppot] = init_pot(engine, clqs, pot, pot_type, onodes); +[clpot, seppot] = collect_evidence(engine, clpot, seppot); +mpe = find_max_config(engine, clpot, seppot, evidence); % instead of distribute evidence diff --git a/sourcecodes/bnt-master/SLP/learning/@jtree_inf_engine2/get.m b/sourcecodes/bnt-master/SLP/learning/@jtree_inf_engine2/get.m new file mode 100644 index 00000000..af4f7d88 --- /dev/null +++ b/sourcecodes/bnt-master/SLP/learning/@jtree_inf_engine2/get.m @@ -0,0 +1,95 @@ +% === +% get +% === +% +% Description : +% ------------- +% +% Reading method for the class' attributes. +% +% Syntax : +% -------- +% +% val = get( obj, attribute_name ); +% +% Input(s) : +% ---------- +% +% obj - class inf_engine +% An instance of the class inf_engine +% +% attribute_name - string +% The name of a class' attribute +% For details about the attribute's names, see the constructor +% (inf_engine.m) +% +% Output(s) : +% ----------- +% +% val - any +% The value of the specified attribute +% +% Example(s) : +% ------------ +% +% val = get( obj, 'attribute_name' ); +% +% Reference(s) : +% -------------- +% +% See also : +% ---------- +% +% inf_engine +% set +function [ val ] = get( engine, attribute_name ) + +val = []; + +% Processing +val = get( engine.inf_engine, attribute_name ); + +% Processing +arg = upper( attribute_name ); + +switch arg + case upper( 'evidence' ) + val = engine.evidence; + case upper( 'jtree' ) + val = engine.jtree; + case upper( 'cliques' ) + val = engine.cliques; + case upper( 'separator' ) + val = engine.separator; + case upper( 'cliques_bitv' ) + val = engine.cliques_bitv; + case upper( 'clique_weight' ) + val = engine.clique_weight; + case upper( 'clpot' ) + val = engine.clpot; + case upper( 'clq_ass_to_node' ) + val = engine.clq_ass_to_node; + case upper( 'root_clq' ) + val = engine.root_clq; + case upper( 'preorder' ) + val = engine.preorder; + case upper( 'postorder' ) + val = engine.postorder; + case upper( 'preorder_children' ) + val = engine.preorder_children; + case upper( 'postorder_parents' ) + val = engine.postorder_parents; + case upper( 'maximize' ) + val = engine.maximize; + case upper( 'evidence' ) + val = engine.evidence; + + otherwise + % warning_str = [ 'inf_engine - get : attribute ' ... + % arg ' doesn''t exist' ]; + + % warning( warning_str ); +end + +% End of function + diff --git a/sourcecodes/bnt-master/SLP/learning/@jtree_inf_engine2/init_pot.m b/sourcecodes/bnt-master/SLP/learning/@jtree_inf_engine2/init_pot.m new file mode 100644 index 00000000..857e6266 --- /dev/null +++ b/sourcecodes/bnt-master/SLP/learning/@jtree_inf_engine2/init_pot.m @@ -0,0 +1,20 @@ +function [clpot, seppot] = init_pot(engine, clqs, pots, pot_type, onodes, ndx) +% INIT_POT Initialise potentials with evidence (jtree_inf) +% function [clpot, seppot] = init_pot(engine, clqs, pots, pot_type, onodes) + +cliques = engine.cliques; +bnet = bnet_from_engine(engine); +% Set the clique potentials to all 1s +C = length(cliques); +clpot = cell(1,C); +for i=1:C + clpot{i} = mk_initial_pot(pot_type, cliques{i}, bnet.node_sizes(:), bnet.cnodes(:), onodes); +end + +% Multiply on specified potentials +for i=1:length(clqs) + c = clqs(i); + clpot{c} = multiply_by_pot(clpot{c}, pots{i}); +end + +seppot = cell(C,C); % implicitely initialized to 1 diff --git a/sourcecodes/bnt-master/SLP/learning/@jtree_inf_engine2/jtree_inf_engine2.m b/sourcecodes/bnt-master/SLP/learning/@jtree_inf_engine2/jtree_inf_engine2.m new file mode 100644 index 00000000..594833de --- /dev/null +++ b/sourcecodes/bnt-master/SLP/learning/@jtree_inf_engine2/jtree_inf_engine2.m @@ -0,0 +1,239 @@ +function [ engine ] = jtree_inf_engine_rd( bnet, varargin ) +% JTREE_INF_ENGINE2 Junction tree inference engine +% engine = jtree_inf_engine(bnet, ...) +% +% The following optional arguments can be specified in the form of name/value pairs: +% [default value in brackets] +% +% clusters - a cell array of sets of nodes we want to ensure are in the same clique (in addition to families) [ {} ] +% root - the root of the junction tree will be a clique that contains this set of nodes [N] +% stages - stages{t} is a set of nodes we want to eliminate before stages{t+1}, ... [ {1:N} ] +% +% e.g., engine = jtree_inf_engine(bnet, 'maximize', 1); +% +% For more details on the junction tree algorithm, see +% - "Probabilistic networks and expert systems", Cowell, Dawid, Lauritzen and Spiegelhalter, Springer, 1999 +% - "Inference in Belief Networks: A procedural guide", C. Huang and A. Darwiche, +% Intl. J. Approximate Reasoning, 15(3):225-263, 1996. +% +% modification of the calculus of cliques from JTREE_INF_ENGINE + + +% set default params +N = length( bnet.dag ); + +% Optional argument processing +[ b_verb, clusters, root, stages ] = jtree_inf_engine2_varargin_mgt( N, varargin ); + +% --- Verbose --- % +if b_verb + fprintf( '/ ------------------------------- \\\n' ); + fprintf( '| jtree_inf_engine2 - verbose mode |\n' ); + time_i = datenum( clock ); +end +% --------------- % + +% Initialization +% ============== + +% Class initialization +engine = init_fields; +engine = class( engine, 'jtree_inf_engine2', inf_engine( bnet ) ); + +% Default parameters +maximize = 0; +onodes = bnet.observed; + +% Optional parameters given by user +% engine = set( engine, varargin{ : } ); + +% Building of the junction tree +% ============================= + +% Elimination ordering +% -------------------- +% --- Verbose --- % +if b_verb + fprintf( 'Compute elimination constraints ...' ); + time_i_cur = datenum( clock ); +end +% --------------- % +porder = determine_elim_constraints( bnet, onodes ); +strong = ~isempty( porder ); +% --- Verbose --- % +if b_verb + time_f_cur = datenum( clock ); + time_cur_str = datestr( time_f_cur - time_i_cur, 'HH:MM:SS' ); + fprintf( ' [Done] - elapsed time = %s\n', time_cur_str ); +end +% --------------- % + + +% Moralization +% ------------ +% --- Verbose --- % +if b_verb + fprintf( 'Moralization ...' ); + time_i_cur = datenum( clock ); +end +% --------------- % +ns = bnet.node_sizes( : ); +ns( onodes ) = 1; % observed nodes have only 1 possible value +moral_graph = moralize( bnet.dag ); +% --- Verbose --- % +if b_verb + time_f_cur = datenum( clock ); + time_cur_str = datestr( time_f_cur - time_i_cur, 'HH:MM:SS' ); + fprintf( ' [Done] - elapsed time = %s\n', time_cur_str ); +end +% --------------- % + + +% Junction tree building +% ---------------------- +% --- Verbose --- % +if b_verb + fprintf( 'Building the junction tree ...' ); + time_i_cur = datenum( clock ); +end +% --------------- % +[ engine.jtree, root2, engine.cliques, B, w, elim_order ] = ... + graph_to_jtree( moral_graph, ns, porder, stages, clusters ); +% --- Verbose --- % +if b_verb + time_f_cur = datenum( clock ); + time_cur_str = datestr( time_f_cur - time_i_cur, 'HH:MM:SS' ); + fprintf( ' [Done] - elapsed time = %s\n', time_cur_str ); +end +% --------------- % + + +engine.cliques_bitv = B; +engine.clique_weight = w; +C = length( engine.cliques ); +engine.clpot = cell(1,C); + +% Separators computation +% ---------------------- + +% --- Verbose --- % +if b_verb + fprintf( 'Separators computation ...' ); + time_i_cur = datenum( clock ); +end +% --------------- % +% Compute the separators between connected cliques. +[ is, js ] = find( engine.jtree > 0 ); +engine.separator = cell( C, C ); +for k = 1:length( is ) + i = is( k ); j = js( k ); + % intersect(cliques{i}, cliques{j}); + engine.separator{ i, j } = find( B( i, : ) & B( j, : ) ); +end +% --------------- % +if b_verb + time_f_cur = datenum( clock ); + time_cur_str = datestr( time_f_cur - time_i_cur, 'HH:MM:SS' ); + fprintf( ' [Done] - elapsed time = %s\n', time_cur_str ); +end +% --------------- % + +% A node can be a member of many cliques, but is assigned to exactly one, to avoid +% double-counting its CPD. We assign node i to clique c if c is the "lightest" clique that +% contains i's family, so it can accomodate its CPD. + +engine.clq_ass_to_node = zeros(1, N); +for i=1:N + %c = clq_containing_nodes(engine, family(bnet.dag, i)); + % all selected columns must be 1 + clqs_containing_family = find( all( B( :, family( bnet.dag, i ) ), 2 ) ); + c = clqs_containing_family( ... + argmin( w( clqs_containing_family ) ) ); + engine.clq_ass_to_node( i ) = c; +end + +% Make the jtree rooted, so there is a fixed message passing order. +if strong + % the last clique is guaranteed to be a strong root + engine.root_clq = length( engine.cliques ); +else + % jtree_dbn_inf_engine requires the root to contain the interface. + % This may conflict with the strong root requirement! *********** BUG ************* + engine.root_clq = clq_containing_nodes( engine, root ); + if engine.root_clq <= 0 + error( [ 'no clique contains ' num2str( root ) ] ); + end +end + +[ engine.jtree, engine.preorder, engine.postorder ] = ... + mk_rooted_tree( engine.jtree, engine.root_clq ); + +% collect +engine.postorder_parents = cell( 1, length(engine.postorder ) ); +for n = engine.postorder( : )' + engine.postorder_parents{ n } = parents( engine.jtree, n ); +end +% distribute +engine.preorder_children = cell( 1, length( engine.preorder ) ); +for n = engine.preorder( : )' + engine.preorder_children{ n } = children( engine.jtree, n ); +end + +% --- Verbose --- % +if b_verb + time_f = datenum( clock ); + time_str = datestr( time_f - time_i, 'HH:MM:SS' ); + fprintf( 'Elapsed time = %s\n', time_str' ); + fprintf( '\\ ------------------------------- /\n' ); +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [ b_verb, clusters, root, stages ] = jtree_inf_engine2_varargin_mgt( N, parent_varargin ) + +% Number of variable arguments +nb_varargin = length( parent_varargin ); + +% Default parameters +b_verb = 0; +clusters = {}; +root = N; +stages = { 1:N }; + +% Processing +for i = 1:2:nb_varargin + + arg_i = upper( parent_varargin{ i } ); + val_i = parent_varargin{ i + 1 }; + + switch arg_i + case upper( 'EngineVerbose' ) + b_verb = val_i; + case upper( 'Clusters' ) + clusters = val_i; + case upper( 'Root' ) + root = val_i; + case upper( 'Stages' ) + stages = val_i; + otherwise + end + +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function engine = init_fields() + +engine.jtree = []; +engine.cliques = []; +engine.separator = []; +engine.cliques_bitv = []; +engine.clique_weight = []; +engine.clpot = []; +engine.clq_ass_to_node = []; +engine.root_clq = []; +engine.preorder = []; +engine.postorder = []; +engine.preorder_children = []; +engine.postorder_parents = []; +engine.maximize = []; +engine.evidence = []; + diff --git a/sourcecodes/bnt-master/SLP/learning/@jtree_inf_engine2/marginal_family.m b/sourcecodes/bnt-master/SLP/learning/@jtree_inf_engine2/marginal_family.m new file mode 100644 index 00000000..eff60ca2 --- /dev/null +++ b/sourcecodes/bnt-master/SLP/learning/@jtree_inf_engine2/marginal_family.m @@ -0,0 +1,11 @@ +function marginal = marginal_family(engine, i, add_ev) +% MARGINAL_FAMILY Compute the marginal on the specified family (jtree) +% marginal = marginal_family(engine, i) + +if nargin < 3, add_ev = 0; end +assert(~add_ev); + +bnet = bnet_from_engine(engine); +fam = family(bnet.dag, i); +c = engine.clq_ass_to_node(i); +marginal = pot_to_marginal(marginalize_pot(engine.clpot{c}, fam)); diff --git a/sourcecodes/bnt-master/SLP/learning/@jtree_inf_engine2/marginal_nodes.m b/sourcecodes/bnt-master/SLP/learning/@jtree_inf_engine2/marginal_nodes.m new file mode 100644 index 00000000..6413172c --- /dev/null +++ b/sourcecodes/bnt-master/SLP/learning/@jtree_inf_engine2/marginal_nodes.m @@ -0,0 +1,22 @@ +function marginal = marginal_nodes(engine, query, add_ev) +% MARGINAL_NODES Compute the marginal on the specified query nodes (jtree) +% marginal = marginal_nodes(engine, query, add_ev) +% +% 'query' must be a subset of some clique; an error will be raised if not. +% add_ev is an optional argument; if 1, we will "inflate" the marginal of observed nodes +% to their original size, adding 0s to the positions which contradict the evidence + +if nargin < 3, add_ev = 0; end + +c = clq_containing_nodes(engine, query); +if c == -1 + error(['no clique contains ' num2str(query)]); +end +marginal = pot_to_marginal(marginalize_pot(engine.clpot{c}, query, engine.maximize)); + +if add_ev + bnet = bnet_from_engine(engine); + %marginal = add_ev_to_dmarginal(marginal, engine.evidence, bnet.node_sizes); + marginal = add_evidence_to_gmarginal(marginal, engine.evidence, bnet.node_sizes, bnet.cnodes); +end + diff --git a/sourcecodes/bnt-master/SLP/learning/@jtree_inf_engine2/set.m b/sourcecodes/bnt-master/SLP/learning/@jtree_inf_engine2/set.m new file mode 100644 index 00000000..2ade7cb2 --- /dev/null +++ b/sourcecodes/bnt-master/SLP/learning/@jtree_inf_engine2/set.m @@ -0,0 +1,69 @@ +% === +% set +% === +% +% Description : +% ------------- +% +% Writing method for the class' attributes. +% +% Syntax : +% -------- +% +% obj = set( obj, attrib_name_1, val_1, ..., attrib_name_n, val_n ); +% +% Input(s) : +% ---------- +% +% obj - class inf_engine +% An instance of the class inf_engine +% +% > Optionals : 'attrib_name'/value form +% For details about the attribute's names, see the constructor +% (inf_engine.m) +% +% Output(s) : +% ----------- +% +% obj - class inf_engine +% The updated object +% +% Example(s) : +% ------------ +% +% obj = set( obj, 'attrib_1', val_1, ..., 'attrib_n', val_n ); +% +% Reference(s) : +% -------------- +% +% See also : +% ---------- +% +% inf_engine +% get +function [ engine ] = set( engine, varargin ) + +engine.inf_engine = set( engine.inf_engine, varargin{ : } ); + +% Processing +% Number of variable arguments +nb_varargin = length( varargin ); + +for i = 1:2:nb_varargin + + arg_i = upper( varargin{ i } ); + val_i = varargin{ i + 1 }; + + switch arg_i + case upper( 'maximize' ) + engine.maximize = val_i; + otherwise +% warning_str = [ 'inf_engine - set : attribute ' ... +% arg_i ' doesn''t exist' ]; + +% warning( warning_str ); + end + +end + +% End of function \ No newline at end of file diff --git a/sourcecodes/bnt-master/SLP/learning/@jtree_inf_engine2/set_fields.m b/sourcecodes/bnt-master/SLP/learning/@jtree_inf_engine2/set_fields.m new file mode 100644 index 00000000..e75cfa45 --- /dev/null +++ b/sourcecodes/bnt-master/SLP/learning/@jtree_inf_engine2/set_fields.m @@ -0,0 +1,13 @@ +function engine = set_fields(engine, varargin) +% SET_FIELDS Set the fields for a generic engine +% engine = set_fields(engine, name/value pairs) +% +% e.g., engine = set_fields(engine, 'maximize', 1) + +args = varargin; +nargs = length(args); +for i=1:2:nargs + switch args{i}, + case 'maximize', engine.maximize = args{i+1}; + end +end diff --git a/sourcecodes/bnt-master/SLP/learning/learn_struct_EM.m b/sourcecodes/bnt-master/SLP/learning/learn_struct_EM.m new file mode 100644 index 00000000..2dc14a2d --- /dev/null +++ b/sourcecodes/bnt-master/SLP/learning/learn_struct_EM.m @@ -0,0 +1,383 @@ +function [bnet, order, BIC_score, LOGLIKE] = learn_struct_EM(bnet, samplesM, max_loop) +% LEARN_STRUCT_EM(), structural EM algorithm , learn structure and parameters +% from missing data. +% [bnet, order, BIC_score] = learn_struct_EM(bnet, samplesM, max_loop) + +tiny = exp(-700); +improve_factor = 0.001; %when current BIC score is less than old_score+old_score*improve_factor, stop search +N = length(bnet.dag); +ncases = size(samplesM, 2); +log_value = log(ncases); +ns = bnet.node_sizes; +dag = zeros(N,N); +order = zeros(1,N); %save the label of each node in current dag correspond to the original dag +order = 1:N; %original dag has nodes label 1:N +CPT = cell(2); %save the modified node's CPT of the bnet that has the highest score in an iteration + %in cases "del" and "add", there is only one CPT will be save, in "rev" need to save two CPTs. +update_samples = cell(N, ncases); %in this algorithm, because the label of the next dag will be different with the + %last dag, so the label of the trainning data will be modified, too + +loop = 0; +evidence = cell(1,N); +while loop<max_loop % generally set the max_loop to 30 + loop = loop + 1 + engine = jtree_inf_engine(bnet); + [bnet, LOGLIKE] = learn_params_em(engine, samplesM, 10); % default set the parameter EM runs 10 iterations + for i=1:N + s = struct(bnet.CPD{i}); + counts = s.counts(:); + ll(i) = sum(log(s.CPT(:) + tiny) .* counts); + end + [D,d] = compute_bnet_nparams(bnet); + + [nbrs, ops, nodes, orders] = mk_nbrs_of_dag_topo(bnet.dag); + nGs = length(nbrs); + + [ec, ec1, LL] = compute_approx_ess(bnet, samplesM, ops, nodes); + bic_score0 = sum(LL); + bic_score0 = bic_score0 - 0.5 * D * log_value; % bic score of current bnet + + bic_score = zeros(1,nGs); % save each neighbour dag(bnet)'s bic score + for i=1:nGs + bic_score(i) = -inf; + end + for i=1:nGs + edge = nodes(i,:); + switch ops{i} + case 'del' + head = edge(1); + tail = edge(2); + approx_ess = ec{i}.counts; + CPT1 = mk_stochastic(approx_ess); + + LL1 = LL; + LL1(tail) = sum(log(CPT1(:) + tiny) .* approx_ess(:)); + d1 = d; + d1(tail) = d(tail) / ns(head); + D1 = sum(d1); + bic_score(i) = sum(LL1) - 0.5 * D1 * log_value; + [a, j] = max(bic_score); + if j==i % if the current dag has the highest bic score, save it's CPT(s) + CPT{1} = CPT1; + end + + case 'add' + head = edge(1); + tail = edge(2); + approx_ess = ec{i}.counts; + if head>tail % now, the "ess" is in ascent manner, accord with the labels in the "domain" field. + n = length(ec{i}.domain); % need permute , so that "ess" contain the last dimension is about the "tail" node. + approx_ess = permute(approx_ess, [1:n-2, n, n-1]); % because there is only one "edge" modified, only need + end % to exchange the last two dimension if needed. + CPT1 = mk_stochastic(approx_ess); + + LL1 = LL; + d1 = d; + LL1(tail) = sum(log(CPT1(:) + tiny) .* approx_ess(:)); + d1(tail) = d(tail) * ns(head); + D1 = sum(d1); + bic_score(i) = sum(LL1) - 0.5 * D1 * log_value; + [a, j] = max(bic_score); + if j==i + CPT{1} = CPT1; + end + + case 'rev' % ops "rev" influent two family, equals the combination of a "del" and an "add" + % "del" an edge + head = edge(1); + tail = edge(2); + approx_ess = ec1{i}.counts; + CPT1 = mk_stochastic(approx_ess); + LL1 = LL; + LL1(tail) = sum(log(CPT1(:) + tiny) .* approx_ess(:)); + d1 = d; + d1(tail) = d(tail) / ns(head); + + % "add" an edge + head = edge(2); + tail = edge(1); + approx_ess = ec{i}.counts; + if head>tail % now, the "ess" is in ascent manner, accord with the labels in the "domain" field. + n = length(ec{i}.domain); % need permute , so that "ess" contain the last dimension is about the "tail" node. + approx_ess = permute(approx_ess, [1:n-2, n, n-1]); % because there is only one "edge" modified, only need + end % to exchange the last two dimension if needed. + CPT2 = mk_stochastic(approx_ess); + LL1(tail) = sum(log(CPT2(:) + tiny) .* approx_ess(:)); + d1(tail) = d(tail) * ns(head); + + D1 = sum(d1); + bic_score(i) = sum(LL1) - 0.5 * D1 * log_value; + [a, j] = max(bic_score); + if j==i + CPT{1} = CPT1; + CPT{2} = CPT2; + end + end + end + + [BIC_score, i] = max(bic_score); + temp = abs(bic_score0) * improve_factor; % search will be finish when the improvment of bic score + % less than 0.1% compare with the previous best result + if BIC_score > (bic_score0 + temp) + dag1 = nbrs{i}; % new best dag + order1 = orders{i}; % labels of each nodes altered from last iteration + + % the labels of each nodes are altered, so the "data" will need to "re-arrange" according to the new order + for j = 1:N + row = order1(j); + for k = 1:ncases + update_samples{j,k} = samplesM{row,k}; + end + end + samplesM = update_samples; + + dag = dag1(order1, order1); % "reshape" the best dag, make it as an "upper trianglar" + ns = ns(order1); % also must modify the order of "ns" + CPDs = bnet.CPD; + bnet = mk_bnet(dag, ns); % use the best dag now to produce a new bnet, with altered nodes labels + for j=1:N % randomly set the CPTs values of each CPDs + bnet.CPD{j} = tabular_CPD(bnet, j, 'prior_type', 'dirichlet', 'dirichlet_weight', 0); + end + edge = nodes(i,:); + + % update the CPDs of new best bnet(dag) using corresponding CPDs of last iteration. + % copy the old CPTs that not altered. + % set the altered CPTs from the saved "CPT" variables + switch ops{i} + case 'del' + tail = edge(2); + tail = find(order1==tail); + bnet.CPD{tail} = set_fields(bnet.CPD{tail}, 'CPT', CPT{1}); + forbidden = [tail]; + bnet.CPD = copy_CPD(bnet.CPD, CPDs, order1, forbidden); + case 'add' + tail = edge(2); + tail = find(order1==tail); + bnet.CPD{tail} = set_fields(bnet.CPD{tail}, 'CPT', CPT{1}); + forbidden = [tail]; + bnet.CPD = copy_CPD(bnet.CPD, CPDs, order1, forbidden); + case 'rev' + head = edge(2); + head = find(order1==head); + bnet.CPD{head} = set_fields(bnet.CPD{head}, 'CPT', CPT{1}); + tail = edge(1); + tail = find(order1==tail); + bnet.CPD{tail} = set_fields(bnet.CPD{tail}, 'CPT', CPT{2}); + forbidden = [head, tail]; + bnet.CPD = copy_CPD(bnet.CPD, CPDs, order1, forbidden); + end + + % draw a graph for the new best dag with nodes labels are the same as the original + order = order(order1); +% labels = cellstr(int2str(order')); +% figure(loop+1); +% draw_graph(dag,labels); + + clear bic_score D d; % for each iteration, re-compute all the expected counts and bic score + clear ec ec1 LL; + clear nbrs ops nodes orders; + else + BIC_score = bic_score0; % if there is no improvement in bic score, stop the search, and return + break; + end +end +BIC_score + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [D,d]= compute_bnet_nparams(bnet) +% +% +N = length(bnet.dag); +d = zeros(1,N); +for i=1:N + a = struct(bnet.CPD{i}); + d(i) = a.nparams; +end +D = sum(d); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function newCPD = copy_CPD(newCPD, CPDs, order, forbidden) +%copy CPDs from old bnet to new bnet, except those nodes has been modified +% +N = length(order); +for i=1:N + if ~mysubset(i, forbidden) + a = order(i); + s = struct(CPDs{a}); + CPT = s.CPT; + newCPD{i} = set_fields(newCPD{i}, 'CPT', CPT); + end +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [ec, ec1, LL] = compute_approx_ess(bnet, samplesM, ops, nodes) +%compute all neighbours' needed approximate ess based on current bnet. +% +tiny = exp(-700); +N = length(bnet.dag); +ns = bnet.node_sizes; +ncases = size(samplesM, 2); +nGs = length(ops); +ec0 = cell(1,N); +ec = cell(1, nGs); %store each neighbours' altered family's approximate ess. +ec1 = cell(1, nGs); %since operator 'rev' need to alter two families, ec1 store the ess of family deleted an edge +copy = zeros(1, nGs); +copy1 = zeros(1, nGs); +LL = zeros(1, N); %For current bnet, LL store each nodes's LogLike based on approximate ess. +for i =1:nGs + ec{i}.domain = []; + ec{i}.counts = []; + ec1{i}.domain = []; + ec1{i}.counts = []; +end +for i =1:N + parents = bnet.parents{i}; + family = [parents, i]; + ec0{i} = 0 * myones(ns(family)); +end +for i =1:nGs + edge = nodes(i, :); + switch ops{i} + case 'del' + head = edge(1); + tail = edge(2); + parents = bnet.parents{tail}; + parents = mysetdiff(parents, head); + domain = [parents, tail]; + copy(i) = find_same_domain(ec, domain, i); + ec{i}.domain = domain; + ec{i}.counts = 0 * myones(ns(domain)); + case 'add' + head = edge(1); + tail = edge(2); + parents = bnet.parents{tail}; + parents = [parents, head, tail]; + domain = sort(parents); + copy(i) = find_same_domain(ec, domain, i); + ec{i}.domain = domain; + ec{i}.counts = 0 * myones(ns(domain)); + case 'rev' + head = edge(1); + tail = edge(2); + parents = bnet.parents{tail}; + parents = mysetdiff(parents, head); + domain = [parents, tail]; + copy1(i) = find_same_domain(ec, domain, i); + ec1{i}.domain = domain; + ec1{i}.counts = 0 * myones(ns(domain)); + + head = edge(2); + tail = edge(1); + parents = bnet.parents{tail}; + parents = [parents, head, tail]; + domain = sort(parents); + copy(i) = find_same_domain(ec, domain, i); + ec{i}.domain = domain; + ec{i}.counts = 0 * myones(ns(domain)); + end +end + +engine = jtree_inf_engine(bnet); + +for l =1:ncases + evidence = samplesM(:, l); + [engine, ll] = enter_evidence(engine, evidence); + ns_eff = ns; + ns_eff(~isemptycell(evidence)) = 1; + Vmarg = cell(1,N); + for i =1:N + Vmarg{i} = marginal_nodes(engine, i); + end + for i = 1:N + parents = bnet.parents{i}; + family = [parents, i]; + nfamily = length(family); + Fmarg = []; + for j = 1:nfamily + Fmarg = multiply_one_marginal(Fmarg, Vmarg{family(j)}, ns_eff); + end + fullm = add_ev_to_dmarginal(Fmarg, evidence, ns); + ec0{i} = ec0{i} + fullm.T; + end + + for i = 1:nGs + switch ops{i} + case 'del' + if ~copy(i) + domain = ec{i}.domain; + Fmarg = []; + for j=1:length(domain) + Fmarg = multiply_one_marginal(Fmarg, Vmarg{domain(j)}, ns_eff); + end + fullm = add_ev_to_dmarginal(Fmarg, evidence, ns); + ec{i}.counts = ec{i}.counts + fullm.T; + end + case 'add' + if ~copy(i) + domain = ec{i}.domain; + Fmarg = []; + for j=1:length(domain) + Fmarg = multiply_one_marginal(Fmarg, Vmarg{domain(j)}, ns_eff); + end + fullm = add_ev_to_dmarginal(Fmarg, evidence, ns); + ec{i}.counts = ec{i}.counts + fullm.T; + end + case 'rev' + if ~copy1(i) + domain = ec1{i}.domain; + Fmarg = []; + for j=1:length(domain) + Fmarg = multiply_one_marginal(Fmarg, Vmarg{domain(j)}, ns_eff); + end + fullm = add_ev_to_dmarginal(Fmarg, evidence, ns); + ec1{i}.counts = ec1{i}.counts + fullm.T; + end + + if ~copy(i) + domain = ec{i}.domain; + Fmarg = []; + for j=1:length(domain) + Fmarg = multiply_one_marginal(Fmarg, Vmarg{domain(j)}, ns_eff); + end + fullm = add_ev_to_dmarginal(Fmarg, evidence, ns); + ec{i}.counts = ec{i}.counts + fullm.T; + end + end + end + clear Vmarg; +end + +for i =1:nGs + if copy(i) + ec{i}.counts = ec{copy(i)}.counts; + end + if copy1(i) + ec1{i}.counts = ec{copy1(i)}.counts; + end +end + +for i=1:N + s = struct(bnet.CPD{i}); + counts = ec0{i}; + LL(i) = sum(log(s.CPT(:) + tiny) .* counts(:)); +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function index = find_same_domain(ec, domain, length) +% +% +index = 0; +for i = 1:length + if isequal(domain, ec{i}.domain) + index = i; + break; + end +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + + + + diff --git a/sourcecodes/bnt-master/SLP/learning/learn_struct_bnpc.m b/sourcecodes/bnt-master/SLP/learning/learn_struct_bnpc.m new file mode 100644 index 00000000..bddce565 --- /dev/null +++ b/sourcecodes/bnt-master/SLP/learning/learn_struct_bnpc.m @@ -0,0 +1,725 @@ +function [Phase_3, Phase_2, Phase_1, UPhase_3] = learn_struct_bnpc(data,node_sizes,epsilon,star) +% G = learn_struct_bnpc(Data,node_sizes,epsilon,star) +% +% Data(i,m) is node i in case m. +% node_sizes and epsilon are optionnals. +% star = 0 to use try_to_separate_B instead of try_to_separate_B_star +% +% see "Learning bayesian Networks from Data: A Efficient Approach Based on Information Theorie" +% Jie Cheng, David Bell and Weird Liu. +% +% Things to do : rewrite function orient_edges ! +% ! sometimes it causes crashes ! +% +% V0.91 : 18 sept 2003 (olivier.francois@insa-rouen.fr) + +verbose=1; +%if nargin < 5, mwst=0; end +if nargin < 4, star=1; end +if nargin < 3, epsilon=0.05; end +if nargin < 2, node_sizes=max(data'); end + +if verbose + fprintf('================== phase I : \n'); +end +tmp1=cputime; +[Phase_1 II JJ score_mat score_mat2] = phaseI(data, node_sizes, epsilon); +tmp1=cputime-tmp1; + +if verbose + fprintf('Execution time : %2.5f\n',tmp1); + fprintf('\n================== phase II : \n'); +end +tmp1=cputime; +Phase_2 = phaseII(Phase_1, data, node_sizes, epsilon, II, JJ, score_mat); +tmp1=cputime-tmp1; + +if verbose + fprintf('Execution time : %2.5f\n',tmp1); + fprintf('\n================== phase III : \n'); +end +tmp1=cputime; +[Phase_3 UPhase_3] = phaseIII(Phase_2, data, node_sizes, epsilon, score_mat2, star); +tmp1=cputime-tmp1; +if verbose + fprintf('Execution time : %2.5f\n',tmp1); +end + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [G, II, JJ, score_mat, sc2] = phaseI(data,node_sizes,alpha) +% [G, II , JJ, score_mat, s2] = phaseI(data,node_sizes,epsilon) +% +% G is an acyclic graph +% [II JJ] is the list of important edges not processed in phase I (for phase II) +% score_mat is the mutual information score matrix +% +% data(i,m) is node i in case m. +% alpha is the significant level for CI tests ( default=0.05 ). +% node_sizes is the vector of sizes ( default=max(data') ). +% +% see "Learning bayesian Networks from Data: A Efficient Approach Based on Information Theorie" +% Jie Cheng, David Bell and Weird Liu. + +% 0. +if nargin < 3, alpha=0.05; end +if nargin < 2, node_sizes=max(data'); end +[N m] = size(data); +score_mat = zeros(N); +edges=0; + +% 1. +G = zeros(N); +L=[]; + +% 2. Use of Chi2 instead of MI ... allow using a confidence level alpha instead of an arbitrary epsilon +for i=1:(N-1) + for j=(i+1):N + [I score_mat(i,j)] = cond_indep_chisquare(i,j,[],data,'LRT',alpha,node_sizes); + end +end +sc2=score_mat; + + +[tmp ordre]=sort(-score_mat(:)); +ordre2=ordre(find(-tmp>alpha)); +[II JJ]=ind2sub([N N],ordre2); + +pointer=1 ; +fini=length(II); + +% 3. +edges=2; +for pointer=1:min(2,fini), + %fprintf('%d-%d\n',II(pointer),JJ(pointer)); + G(II(pointer),JJ(pointer))=1; + G(JJ(pointer),II(pointer))=1; + score_mat(II(pointer),JJ(pointer))=-inf; +end + +pointer=min(2,fini); +arret=0; + +while pointer<fini & ~arret + % 4. + pointer=pointer+1; + C = ~reachability_graph(G); + if C(II(pointer),JJ(pointer)) + %fprintf('%d-%d\n',II(pointer),JJ(pointer)); + G(II(pointer),JJ(pointer))=1; + G(JJ(pointer),II(pointer))=1; + score_mat(II(pointer),JJ(pointer))=-inf; + edges=edges+1; + if edges==N-1 + arret=1; + end + end + + % 5. +end + +[tmp ordre]=sort(-score_mat(:)); +ordre2=ordre(find(-tmp>alpha)); +[II JJ]=ind2sub([N N],ordre2); + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function G = phaseII(G1,data,node_sizes,alpha,II, JJ, score_mat) +% G = phaseII(G1,data,node_sizes,epsilon,II,JJ, score_mat) +% +% G1, II, JJ,score_mat are given by phaseI. +% data(i,m) is node i in case m. +% node_sizes is the vector of sizes ( default=max(data') ). +% alpha is the significant level for CI tests ( default=0.05 ). +% +% see "Learning bayesian Networks from Data: A Efficient Approach Based on Information Theorie" +% Jie Cheng, David Bell and Weird Liu. + +st{1}='added'; +st{2}=''; +% 0. +[N m] = size(data); + +G=G1; + +% 6. +II=II(end:-1:1); +JJ=JJ(end:-1:1); + +pointer=length(II); + +while pointer>0 + % 7. + trysep = try_to_separate_A(G,II(pointer),JJ(pointer),data,alpha,node_sizes); + %fprintf('Try to separate %d and %d : %s\n',II(pointer),JJ(pointer),st{trysep+1}); + if ~trysep + G(II(pointer),JJ(pointer))=1; + G(JJ(pointer),II(pointer))=1; + %fprintf('%d-%d\n',II(pointer),JJ(pointer)); + end + % 8. + pointer=pointer-1; +end + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [G,U] = phaseIII(G1,data,node_sizes,alpha,s2,star) +% [G] = phaseIII(G1,data,node_sizes,alpha,s2,star) +% +% data(i,m) is node i in case m. +% if star~=0, use try_to_separate_B_star instead of try_to_separate_B (default star=1). +% G is the non-oriented graph and G1 the oriented result. +% +% see "Learning bayesian Networks from Data: A Efficient Approach Based on Information Theorie" +% Jie Cheng, David Bell and Weird Liu. + +% 0. +if nargin < 2, disp('Not enough arguments');return; end +if nargin < 3, node_sizes=max(data'); end +if nargin < 4, alpha = 0.05; end +if nargin < 5, star=1; end +G=G1; +N=length(G); +% reachability_matrix of G +M = expm(full(G)) - eye(length(G)); M = (M>0); + +% 9. +fprintf('Thinning - separateA\n'); + +% Edges are examined in the inverse order of their Chi2 (or MI) score +s2(find(~G))=0; +[tmp ordre]=sort(s2(:)); +ordre2=ordre(find(tmp>0)); +[I J]=ind2sub([N N],ordre2); +ii=1:length(I); +for i=ii, + %fprintf('%d-%d : ',I(i),J(i)); + G(I(i),J(i))=0; + G(J(i),I(i))=0; + trysep = try_to_separate_A(G,I(i),J(i),data,alpha,node_sizes); + + if ~trysep, + G(I(i),J(i))=1; + G(J(i),I(i))=1; + %fprintf(' keep\n'); + %else + %fprintf('delete\n'); + end +end + +% 10. +fprintf('Thinning - separateB'); if star; fprintf('star'); end; fprintf('\n'); +s2(find(~G))=0; +[tmp ordre]=sort(s2(:)); +ordre2=ordre(find(tmp>0)); +[I J]=ind2sub([N N],ordre2); +ii=1:length(I); +for i=ii, + %fprintf('%d-%d : ',I(i),J(i)); + G(I(i),J(i))=0; G(J(i),I(i))=0; + if star==0 + trysep = try_to_separate_B(G,I(i),J(i),data,node_sizes,alpha); + else + trysep = try_to_separate_B_star(G,I(i),J(i),data,node_sizes,alpha); + end + if ~trysep, + G(I(i),J(i))=1; G(J(i),I(i))=1; + %fprintf(' keep\n'); + %else + %fprintf('delete\n'); + end +end + +%11. +fprintf('Thinning - orient_edges\n'); +U=G; +G=orient_edges(U,data,node_sizes,alpha); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [I, N1, N2] = try_to_separate_A(G,node1,node2,data,alpha,node_sizes) +% [I, N1, N2] = try_to_separate_A(G,node1,node2,data,alpha,node_sizess) +% +% G is the current partially directed graph. +% I is a boolean : I=1 <==> separated. +% N1 : neighbors of node1 that are on an adjacency path between node1 and node2 (ditto for N2). +% +% see "Learning bayesian Networks from Data: A Efficient Approach Based on Information Theorie" +% Jie Cheng, David Bell and Weird Liu. + +% 0. +if node1==node2 | length(G)<2 + disp('Error: Check your arguments in try_to_separate_A.');I=0; return +end +N=size(data,1); +if nargin==4, alpha=0.05; node_sizes=max(data'); end +if nargin==5, node_sizes=max(data'); end + +% 1. +N1=find(G(node1,:)==1);N01=N1; +GG1=G(setdiff(1:N,node1),setdiff(1:N,node1)); +node22=node2-(node2>node1); +% reachability_matrix of GG1 +M = expm(full(GG1)) - eye(length(GG1)); M = (M>0); +% N1 is the neighbors of node1 that are on the adjacency between node1 and node2 +for i=N1 + j=i-(i>node1); + if M(j,node22)~=1, N01=setdiff(N01,i); N1=setdiff(N1,i); end + % 2. + if ~G(node1,i), N1=setdiff(N1,i); end +end + +N2=find(G(node2,:)==1);N02=N2; +GG2=G(setdiff(1:N,node2),setdiff(1:N,node2)); +node12=node1-(node2<node1); +% reachability_matrix of GG2 +M = expm(full(GG2)) - eye(length(GG2)); M = (M>0); +% N2 is the neighbors of node2 that are on the adjacency between node1 and node2 +for i=N2 + j=i-(i>node2); + if M(node12,j)~=1, N02=setdiff(N02,i); N2=setdiff(N2,i); end + % 2. + if ~G(node2,i), N2=setdiff(N2,i); end +end + +%fprintf('%d : N1=',node1); fprintf('%d',N1); fprintf('\n'); +%fprintf('%d : N2=',node2); fprintf('%d',N2); fprintf('\n'); +% 3. +if length(N1)>length(N2), tmp=N1; N1=N2; N2=tmp; clear tmp, end +% 4. +C=N1; +for test=1:2 + if test==2, C=N2; end + % 5. + [I v1] = cond_indep_chisquare(node1,node2,C,data,'LRT',alpha,node_sizes); + if I, %fprintf('%d-%d separated (%2.5f) by C=',node1,node2,v1); fprintf('%d',C); fprintf('\n'); + return, + %else + %fprintf('%d-%d not separated (%2.5f) by C=',node1,node2,v1); fprintf('%d',C); fprintf('\n'); + end; + + + % 6. + step6=1; + while step6 + step6=0; + if length(C)>=1 + v=[]; + for i=C + Ci = setdiff(C,i); + [I(i) v(i)] = cond_indep_chisquare(node1,node2,Ci,data,'LRT',alpha,node_sizes); + end + [vm ind] = min(v); + + % 7. + if I(ind) + I = 1; return + else + if vm < v1 + v1 = vm; + C = setdiff(C,ind); + % goto step 6. + step6 = 1; + end + end + end + + % 8. + if test==2, I=0; return, end + end +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function I = try_to_separate_B(G,node1,node2,data,node_sizes,alpha,N1,N2) +% I = try_to_separate_B(G,node1,node2,data,node_sizes,epsilon,N1,N2) +% +% G is the current partially directed graph. +% data(i,m), node i in case m. +% node_sizes is the vector of size of the attributs in data ( default max(data') ) +% N1 (optionnal) is the neighbors of node1 that are on an adjacency path between node1 and node2 (ditto for N2). +% I is a boolean : I+1 <==> separated. +% +% see "Learning bayesian Networks from Data: A Efficient Approach Based on Information Theorie" +% Jie Cheng, David Bell and Weird Liu. + +% 0. +if node1==node2 | length(G)<2 + disp('Error: Verify your arguments in try_to_separate_B.');I=0; return +end +if nargin < 4, + disp('Error : not enougth arguments'); I=0; return; +end +N=size(data,1); + +% 1. +if nargin < 8 + N1=find(G(node1,:)==1); + GG1=G(setdiff(1:N,node1),setdiff(1:N,node1)); + node22=node2-(node2>node1); + M = expm(full(GG1)) - eye(length(GG1)); M = (M>0); + for i=N1 + j=i-(i>node1); + if M(j,node22)~=1, N1=setdiff(N1,i); end + end + N2=find(G(node2,:)==1); + GG2=G(setdiff(1:N,node2),setdiff(1:N,node2)); + node12=node1-(node2<node1); + M = expm(full(GG2)) - eye(length(GG2)); M = (M>0); + for i=N2 + j=i-(i>node2); + if M(node12,j)~=1, N2=setdiff(N2,i); end + end +end +if nargin < 6, alpha=0.05; end +if nargin < 5, node_sizes=max(data'); end +M = expm(full(G)) - eye(length(G)); M = (M>0); + +% 2. +N1b=[]; +for i=N1 + NN1=find(G(i,:)==1); + for j=NN1 + if M(i,j)~=1 & ~ismember(j,N1), N1b=union(N1b,j); end + end +end + +% 3. +N2b=[]; +for i=N2 + NN2=find(G(i,:)==1); + for j=NN2 + if M(i,j)~=1 & ~ismember(j,N2), N2b=union(N2b,j); end + end +end + +% 4. +if length(union(N1,N1b)) < length(union(N2,N2b)) + C=union(N1,N1b); +else + C=union(N2,N2b); +end + +% 5. +continu=1; +while continu + l=length(C); + %fprintf('%d',continu); + [I v] = cond_indep_chisquare(node1,node2,C,data,'LRT',alpha,node_sizes); + if I==1; return, elseif l<2, I=0; return, end + + % 6. + Cb=C; + for i=1:l + Ci=setdiff(C,C(i)); + [I vi] = cond_indep_chisquare(node1,node2,Ci,data,'LRT',alpha,node_sizes); + e = (v+1)/3; % e is a small value... + if I==1,return, elseif vi<v+e, Cb=setdiff(Cb,C(i)); end + end + + % 7. + if length(Cb) < l, C=Cb; else continu==0; I=0; return; end +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function I = try_to_separate_B_star(G,node1,node2,data,node_sizes,alpha,N1,N2) +% I = try_to_separate_B_star(G,node1,node2,data,epsilon,node_sizess) +% +% G is the current partially directed graph. +% I is a boolean. +% N1 is the neighbors of node1 that are on an adjacency path between node1 and node2 (ditto for N2). +% +% see "Learning bayesian Networks from Data: A Efficient Approach Based on Information Theorie" +% Jie Cheng, David Bell and Weird Liu. + +% 0. +if node1==node2 | length(G)<2 + disp('Error: Verify your arguments in try_to_separate_B_star.');I=0; return +end +if nargin < 4, + disp('Error : not enougth arguments');I=0; return; +end +N=size(data,1); + +% 1. +if nargin < 8 + N1=find(G(node1,:)==1); + GG1=G(setdiff(1:N,node1),setdiff(1:N,node1)); + node22=node2-(node2>node1); + M = expm(full(GG1)) - eye(length(GG1)); M = (M>0); + for i=N1 + j=i-(i>node1); + if M(j,node22)~=1, N1=setdiff(N1,i); end + % 2. + if ~G(node1,i), N1=setdiff(N1,i); end + end + N2=find(G(node2,:)==1); + GG2=G(setdiff(1:N,node2),setdiff(1:N,node2)); + node12=node1-(node2<node1); + M = expm(full(GG2)) - eye(length(GG2)); M = (M>0); + for i=N2 + j=i-(i>node2); + if M(node12,j)~=1, N2=setdiff(N2,i); end + % 2. + if ~G(node2,i), N2=setdiff(N2,i); end + end +end +if nargin < 6, alpha=0.05; end +if nargin < 5, node_sizes=max(data'); end + +% 3. +if length(N1)>length(N2), tmp=N1; N1=N2; N2=tmp; end + +% 4. +C=N1; +l=length(C); +I=0; + +% 5. +for test=1:2 + continu=1; + %test + if test==2 & ~isempty(N2) + C=N2; l=length(C); IsInCi=zeros(1,l); IsInCi(l)=1; + else IsInCi=zeros(1,l); + end + s=ones(1,l); + % Pour tous les sous-ensemble Ci de C : + while continu & ~isempty(C) + Ci = setdiff(C.*IsInCi,0); + [I vi] = cond_indep_chisquare(node1,node2,Ci,data,'LRT',alpha,node_sizes); + if I, %fprintf('%d-%d separated (%2.5f) by C=',node1,node2,vi); fprintf('%d',Ci); fprintf('\n'); + return, + %else + %fprintf('%d-%d not separated (%2.5f) by C=',node1,node2,vi); fprintf('%d',Ci); fprintf('\n'); + end; + % if I, return, end + + if IsInCi==s, continu=0; + else + IsInCi(l)=IsInCi(l)+1; + + notOK=1; i=l; + while notOK & i>1 + if IsInCi(i)>s(i), IsInCi(i)=0; IsInCi(i-1)=IsInCi(i-1)+1; else notOK=0; end + i=i-1; + end + end + end +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function G1 = orient_edges(G,data,node_sizes,alpha) +% G1 = orient_edges(G,data,node_sizes) +% +% G is the partially directed graph. +% data(i,m), node i in case m. +% node_sizes is the vector of size of the attributs in data ( default max(data') ) +% +% see "Learning bayesian Networks from Data: A Efficient Approach Based on Information Theorie" +% Jie Cheng, David Bell and Weird Liu. + +% 0. +if nargin < 4, alpha=0.05; end +if nargin < 3, node_sizes=max(data'); end +if nargin < 2, disp(' Require at least two arguments.'); return, end +N=length(G); +G1=G; + +% 1. +[Lnode1 Lnode2]=find(triu(1-triu(G),1)); %[Lnode1 Lnode2]=ind2sub([N N],find(triu(1-triu(G),1))); +for ii = 1:length(Lnode1), + node1=Lnode1(ii); + node2=Lnode2(ii); + %fprintf('%d %d\n',node1,node2); + N1 = find(G(node1,:)==1); + N2 = find(G(node2,:)==1); + if ~isempty(intersect(N1,N2)) + %fprintf('V1='); fprintf('%d ',N1); fprintf('\n'); + %fprintf('V2='); fprintf('%d ',N2); fprintf('\n'); + GG1 = G(setdiff(1:N,node1),setdiff(1:N,node1)); + node22 = node2-(node2>node1); + % reachability_matrix of GG1 + M = expm(full(GG1)) - eye(length(GG1)); M = (M>0); + % N1 is the neighbors of node1 that are on the adjacency between node1 and node2 + for i = N1 + j = i-(i>node1); + if M(j,node22)~=1, N1=setdiff(N1,i); + end + end + GG2 = G(setdiff(1:N,node2),setdiff(1:N,node2)); + node12 = node1-(node2<node1); + % reachability_matrix of GG2 + M = expm(full(GG2)) - eye(length(GG2)); M = (M>0); + % N2 is the neighbors of node2 that are on the adjacency between node1 and node2 + for i=N2 + j = i-(i>node2); + if M(node12,j)~=1, N2=setdiff(N2,i); end + end + %fprintf('%d %d\n',node1,node2); + %fprintf('N1='); fprintf('%d ',N1); fprintf('\n'); + %fprintf('N2='); fprintf('%d ',N2); fprintf('\n'); + + % 2. + M = expm(full(G)) - eye(length(G)); M = (M>0); + N1b=N1; + for i=N1 + NN1 = find(G(i,:)==1); + for j=NN1 + if M(i,j)~=1 & ~ismember(j,N1), N1b=union(N1b,j); end + end + end + %fprintf('N1''='); fprintf('%d ',N1b); fprintf('\n'); + + % 3. + N2b=N2; + for i=N2 + NN2 = find(G(i,:)==1); + for j=NN2 + if M(i,j)~=1 & ~ismember(j,N2), N2b=union(N2b,j); end + end + end + %fprintf('N2''='); fprintf('%d ',N2b); fprintf('\n'); + + % 4. + if length(N1b) < length(N2b) + C = N1b; + else + C = N2b; + end + %l=length(C); + %fprintf('C='); fprintf('%d ',C); fprintf('\n'); + + % 7. + step5=1; + while step5 + step5=0; + %fprintf('.'); + % 5. + l=length(C); + [I v] = cond_indep_chisquare(node1,node2,C,data,'LRT',alpha,node_sizes); + %fprintf('C='); fprintf('%d ',C); + %fprintf(': %d %2.5f\n',I,v); + + step8=0; + if I==1 & v~=0 % v < epsilon + step8=1; + else + if l==1 + G1(C,node1)=0; G1(C,node2)=0; + fprintf('%d -> %d <- %d\n',node1,C,node2); + step8=1; + end + end + %fprintf('%d\n',step8); + + % 6. + if ~step8 + Cb=C; + for i=1:l + Ci=setdiff(C,C(i)); + [I vi] = cond_indep_chisquare(node1,node2,Ci,data,'LRT',alpha,node_sizes); + % e = (v+1)/3; % e is a small value... + if I==1 % vi < v+e + Cb=setdiff(Cb,C(i)); + if ismember(C(i),N1) & ismember(C(i),N2) + G1(C(i),node1)=0; G1(C(i),node2)=0; + fprintf('%d -> %d <- %d\n',node1,C(i),node2); + end + if I==1 % vi < epsilon + step8=1; + end + end + end % for + end % if + + % 7. + if ~step8 + if length(Cb) < length(C), C=Cb; end + if length(C) > 0, step5==1; end + end + end % while step5 + % step8 : passer � la paire de noeud suivant + %fprintf('\n'); + %else + %fprintf(' No common neighbor\n'); + end % if +end % for + +% 11. +step9=0; +fprintf('Infering directions '); +test = pdag_to_dag(G1); +while ~isdag(test) %& step9<N + step9=step9+1; + %if ~isempty(test) + %fprintf('.'); + % 9. + for a=1:N, for b=1:N, for c=1:N, + if a~=b & b~=c & c~=a + if G1(a,b)==1 & G1(b,a)==0 + %fprintf('%d -> %d ... \n',a,b); + if G1(b,c)==1 & G1(c,b)==1 + if G1(a,c)+G1(c,a)==0, + G1(c,b)=0; + fprintf('%d -> %d (9)\n',b,c); + end + end + end + end + end, end, end + + % 10. + for a=1:N-1, for b=a+1:N + if G1(a,b)==1 & G1(b,a)==1 + GGG1=xor(G1,G1'); % matrice des arcs orient�s de G1 + M = expm(full(GGG1)) - eye(length(GGG1)); M = (M>0); + if M(a,b)==1, + G1(b,a)=0; + fprintf('%d -> %d (10)\n',a,b); + end + end + end, end + + test = pdag_to_dag(G1); + if isempty(test), G1=return_one_edge(G1); end + %else + % G1, return, + %end +end % while 11. +G1 = pdag_to_dag(G1); +fprintf('%d boucles\n',step9); + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function b = isdag(G) +b = sum(sum(G.*G')); % How many undirected arcs ? (x2) +b=~b & ~isempty(G); +if b + M = expm(full(G)) - eye(length(G)); M = (M>0); + b = b & find(sum(sum(eye(length(G)).*M))); % is there no cycle ? +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function G=return_one_edge(G) +N=length(G); fam=[]; node2=[]; +permnode = randperm(5); i=0; %fprintf('rev\n'); +while isempty(fam) | isempty(node2) | i==N + i=i+1; + node = permnode(i); %node = ceil(rand(1)*N); + fam=find(G(node,:)==1); + par=find(G(:,node)==1); + fam = myunion(fam, par); + if ~isempty(fam) + node2 = fam(ceil(rand(1)*length(fam))); + par2=find(G(:,node)==1); + if ~isempty(intersect(par2, node)), node2=[]; end + end +end +if isempty(myintersect(node, par2)), + G(node, node2)=0; + G(node2, node)=1; + fprintf('%d -> %d (Rev)\n',node, node2); +else + G(node, node2)=1; + G(node2, node)=0; + fprintf('%d -> %d (Rev)\n',node2, node); +end diff --git a/sourcecodes/bnt-master/SLP/learning/learn_struct_ges.m b/sourcecodes/bnt-master/SLP/learning/learn_struct_ges.m new file mode 100644 index 00000000..e985195e --- /dev/null +++ b/sourcecodes/bnt-master/SLP/learning/learn_struct_ges.m @@ -0,0 +1,122 @@ +function [cpdag, best_score, cache] = learn_struct_ges(data, nodesizes, varargin) +% +% LEARN_STRUCT_GES learns a structure of Bayesian net by Greedy Equivalence Search. +% cpdag = learn_struct_ges(Data, Nodesizes, 'cache', cache, 'scoring_fn', 'bic', 'verbose', 'yes') +% +% cpdag: the final cpdag +% Data : training data, data(i,m) is the m obsevation of node i +% Nodesizes: the size array of different nodes +% cache : data structure used to memorize local score computations +% (cf. SCORE_INIT_CACHE function) +% +% V1.1 : 28 july 2003 (Ph. Leray - philippe.leray@univ-nantes.fr, O. francois - francois.olivier.c.h@gmail.com) +% +% Ref: +% Optimal Structure Identification with Greedy Search, Chickering 2002 +% + +[N ncases] = size(data); +seeddag = zeros(N,N); + +% set default params +scoring_fn = 'bayesian'; +verbose = 0; +cache=[]; + +% get params +args = varargin; +nargs = length(args); +if length(args) > 0 + if isstr(args{1}) + for i = 1:2:nargs + switch args{i} + case 'scoring_fn', scoring_fn = args{i+1}; + case 'verbose', verbose = strcmp(args{i+1},'yes'); + case 'cache', cache=args{i+1} ; + end; + end; + end; +end; + +if verbose + names=cellstr(int2str((1:N)')); + carre=zeros(N,1); +end + +done = 0; +[best_score cache] = score_dags(data,nodesizes, {seeddag},'scoring_fn',scoring_fn,'cache',cache); +cptt=0; + +% First step : INSERT +while ~done + cptt=cptt+1; + [pdags,nodes] = mk_nbrs_of_pdag_add(seeddag); + seedold=seeddag; + sold=best_score; + nbrs = length(pdags); + dags=pdag_to_dag(pdags); + [scores cache] = score_dags(data, nodesizes, dags,'scoring_fn',scoring_fn,'cache',cache); + max_score = max(scores); + new = find(scores == max_score ); + if ~isempty(new) & (max_score > best_score) + p = sample_discrete(normalise(ones(1, length(new)))); + best_score = max_score; + seeddag = dag_to_cpdag(dags{new(p)}); + new=new(p); + if verbose + figure; + subplot(1,2,1), [xx yy]=draw_graph(seedold,names,carre); + set(gca,'color',[1 1 0]); + title(sprintf('current CPDAG (Smax=%5.2f)',sold)); + subplot(1,2,2), draw_graph(seeddag,names,carre,xx,yy); + s=sprintf(' %d',nodes{new,3}); + title([sprintf('Best in N+ = INSERT(%d, %d,',nodes{new,1},nodes{new,2}) s ')' sprintf(' S=%5.2f',max_score)]); + drawnow; + end + + else + done = 1; + end + +end; + +done = 0; +%[best_score cache] = score_dags(data,nodesizes, {seeddag},'scoring_fn',scoring_fn,'cache',cache); +cptt=0; + +if sum(sum(seeddag))==0, done=1;end + +% Second step : DELETE +while ~done + cptt=cptt+1; + [pdags,nodes] = mk_nbrs_of_pdag_del(seeddag); + seedold=seeddag; sold=best_score; + nbrs = length(pdags); + dags=pdag_to_dag(pdags); + [scores cache] = score_dags(data, nodesizes, dags,'scoring_fn',scoring_fn,'cache',cache); + max_score = max(scores); + new = find(scores == max_score ); + if ~isempty(new) & (max_score > best_score) + p = sample_discrete(normalise(ones(1, length(new)))); + best_score = max_score; + seeddag = dag_to_cpdag(dags{new(p)}); + new=new(p); + if verbose + cpdags=dag_to_cpdag(dags); + figure; + subplot(1,2,1), [xx yy]=draw_graph(seedold,names,carre); + set(gca,'color',[1 1 0]); + title(sprintf('current CPDAG (Smax=%5.2f)',best_score)); + subplot(1,2,2), draw_graph(seeddag,names,carre,xx,yy); + s=sprintf('%d',nodes{new,3}); + title([sprintf('Best in N- = DELETE(%d, %d,',nodes{new,1},nodes{new,2}) s ')' sprintf(' S=%5.2f',max_score)]); + drawnow; + end + + else + done = 1; + end + +end + +cpdag = seeddag; diff --git a/sourcecodes/bnt-master/SLP/learning/learn_struct_ges_EM.m b/sourcecodes/bnt-master/SLP/learning/learn_struct_ges_EM.m new file mode 100644 index 00000000..a4c340e7 --- /dev/null +++ b/sourcecodes/bnt-master/SLP/learning/learn_struct_ges_EM.m @@ -0,0 +1,423 @@ +function [bnet,cpdag,BIC_score,nloop] = learn_struct_ges_EM(bnet, data, max_loop, loop_em) +% GES_EM algorithm, learn Bayesian network equivalence classes from incomplete data. +% [bnet,cpdag,BIC_score,nloop] = learn_struct_ges_EM(bnet, data, max_loop) +% +% 2/11/2006: hanene.borchani@gmail.com, francois.olivier.c.h@gmail.com + +[N ncases] = size(data); +ns = bnet.node_sizes; +cpdag = dag_to_cpdag(bnet.dag); +order=zeros(1,N); +order=1:N; +tiny = exp(-700); +improve_factor = 0.001; %stop search if the current expected BIC score is less than old_score+old_score*improve_factor, +log_value = log(ncases); +update_samples = cell(N, ncases); +loop = 0; +converged = 0; +if nargin<4, loop_em=6; end + +% First phase : INSERT +while ~converged & loop < max_loop + loop=loop+1; + + fprintf('\n Loop INSERT number %d \n\n', loop); + engine = jtree_inf_engine(bnet); + [bnet, LOGLIKE] = learn_params_em(engine, data, loop_em); + + for i=1:N + s = struct(bnet.CPD{i}); + counts = s.counts(:); + ll(i) = sum(log(s.CPT(:) + tiny) .* counts); + end + [D,d] = compute_bnet_nparams(bnet); + + [pdags,nodes] =mk_nbrs_of_pdag_add(cpdag,engine); %generate the neighbors of the current network + nbrs = length(pdags); + if nbrs==0 + break; + end + dags = pdag_to_dag(pdags); %extract the set of consistent extensions + nbrs_dags = length(dags); + + if nbrs_dags==0 + disp('The number of consistent extensions of the pdags neighbors is equal to 0'); + break; + else + c=0; + for k=1:nbrs + if ~isempty(dags{k}) + c=c+1; + cdags{c}=dags{k}; + cnodes(c,:) = nodes(k,:); + end + end + if c==0 + fprintf('There is no consistent extension'); + break; + end + end + + [ess,LL] = compute_ess(bnet,data,cdags); %compute the estimations for all consistent extensions based on the current bnet + bic_score0 = sum(LL); + bic_score0 = bic_score0 - 0.5 * D * log_value; %expected BIC score of the current bnet + fprintf('The expected BIC score of the current bnet, bic_score0 =%8.3f \n', bic_score0); + bic_score = -inf*ones(1,c); + + for i=1:c %compute the expected BIC score of each consistent extension neighbor + for compt=1:N + new_CPT{i,compt}=[]; + end + new_LL = LL; + new_d=d; + for num=1:N + if ~isempty(ess{i,num}) %consider only nodes whose parent set has been changed + par= mysetdiff(ess{i,num}.domain, num); + new_d(num)= prod([ns(par) ns(num)-1]); %compute the new dimension of each node + approx = ess{i,num}.counts; + indnum = find(ess{i,num}.domain > num); + ldom=length(ess{i,num}.domain); + lindnum = length(indnum); + if lindnum>0 + approx = permute(approx, [1:ldom-lindnum-1, indnum(1):ldom, indnum(1)-1]); + end + new_CPT{i,num} = mk_stochastic(approx); + new_LL(num) = sum(log(new_CPT{i,num}(:) + tiny) .* approx(:)); %compute the new LL of each node + end + end + new_D = sum(new_d); + bic_score(i) = sum(new_LL) - 0.5 * new_D * log_value; %deduce the expected BIC score of each neighbor + [a, j] = max(bic_score); + end + + [BIC_score, best] = max(bic_score); + fprintf('End computing of the expected BIC scores of all neighbors, the maximal one is Bic_score =%8.3f \n', BIC_score); + temp = abs(bic_score0) * improve_factor; %search will finish when the improvment of the expected BIC score is less than 0.1% compare with the previous best result + + if BIC_score > (bic_score0 + temp) + best_dag = cdags{best}; + new_order=topological_sort(best_dag); + for j = 1:N + row = new_order(j); + for k = 1:ncases + update_samples{j,k} = data{row,k}; + end + end + data = update_samples; + + forbidden=[]; + reversed =[]; + for j=1:N + old_parents= sort((find(bnet.dag(:,j)==1))'); + new_parents= sort((find(best_dag(:,j)==1))'); + if ~isequal(new_parents,old_parents) + reversed =[reversed,j]; + end + end + + new_dag = best_dag(new_order, new_order); %reshape the best DAG according to new_order + ns = ns(new_order); %modify the order of ns + CPDs = bnet.CPD; + bnet = mk_bnet(new_dag, ns); %make the new best BN structure + + %randomly set the CPD values of each node + for j=1:N + bnet.CPD{j} = tabular_CPD(bnet, j, 'prior_type', 'dirichlet', 'dirichlet_weight', 0); + end + + lreversed= length(reversed); + if lreversed ~=0 + for r=1:lreversed %update the CPDs of nodes whose parent set has been altered using the saved new_CPT + reverse=find(new_order==reversed(r)); + forbidden=[forbidden,reverse]; + bnet.CPD{reverse} = set_fields(bnet.CPD{reverse}, 'CPT', new_CPT{best,reversed(r)}); + end + end + + bnet.CPD = copy_CPD(bnet.CPD, CPDs, new_order, forbidden); %copy the CPDs of remaining nodes from CPDs + + cpdag = dag_to_cpdag(bnet.dag); %get the best equivalence class + + clear bic_score D d new_D new_d; %new computations for each iteration + clear ess LL new_LL new_CPT; + clear pdags dags cdags nodes cnodes; + else + fprintf('No improvement of the expected bic score: End of add phase \n \n'); + BIC_score = bic_score0; + converged=1; + end +end + +nloop=loop; + +loop=0; +converged=0; + +if sum(sum(cpdag))==0, converged=1; else cpdag, end + +% Second phase : Delete + fprintf('Start of delete phase \n '); + while ~converged & loop < max_loop + loop=loop+1; + fprintf('\n Loop DELETE number %d \n\n', loop); + engine = jtree_inf_engine(bnet); + [bnet, LOGLIKE] = learn_params_em(engine, data, loop_em); + for i=1:N + s = struct(bnet.CPD{i}); + counts = s.counts(:); + ll(i) = sum(log(s.CPT(:) + tiny) .* counts); + end + + [D,d] = compute_bnet_nparams(bnet); + + [pdags,nodes] = mk_nbrs_of_pdag_del(cpdag,engine); %generate the neighbors of the current network + nbrs = length(pdags); + if nbrs==0 + disp('The number of pdag neighbors is equal to 0'); + break; + end + + dags = pdag_to_dag(pdags); %extract the set of consistent extensions + nbrs_dags = length(dags); + if nbrs_dags==0 + disp('The number of consistent extensions of the pdag neighbors is equal to 0'); + break; + else + c=0; + for k=1:nbrs + if ~isempty(dags{k}) + c=c+1; + cdags{c}=dags{k}; + cnodes(c,:) = nodes(k,:); + end + end + if c==0 + fprintf('There is no consistent extension'); + return; + end + end + + [ess,LL] = compute_ess(bnet, data,cdags); %compute the estimations for all consistent extensions based on the current bnet + bic_score0 = sum(LL); + bic_score0 = bic_score0 - 0.5 * D * log_value; %expected BIC score of the current bnet + fprintf('The expected BIC score of the current bnet, bic_score0 =%8.3f \n', bic_score0); + bic_score = -inf*ones(1,c); + + for i=1:c %Compute the Expected BIC score for each consistent extension neighbor + for compt=1:N + new_CPT{i,compt}=[]; + end + new_LL = LL; + new_d=d; + for num=1:N + if ~isempty(ess{i,num}) %consider only nodes whose parent set has been changed + par= mysetdiff(ess{i,num}.domain, num); + new_d(num)= prod([ns(par) ns(num)-1]); %compute the new dimension of each node + approx = ess{i,num}.counts; + indnum = find(ess{i,num}.domain > num); + ldom=length(ess{i,num}.domain); + lindnum = length(indnum); + if lindnum>0 + approx = permute(approx, [1:ldom-lindnum-1, indnum(1):ldom, indnum(1)-1]); + end + new_CPT{i,num} = mk_stochastic(approx); + new_LL(num) = sum(log(new_CPT{i,num}(:) + tiny) .* approx(:)); %compute the new LL of each node + end + end + new_D = sum(new_d); + bic_score(i) = sum(new_LL) - 0.5 * new_D * log_value; %deduce the expected BIC score of each neighbor + end + + [BIC_score, best] = max(bic_score); + fprintf('End computing of the expected BIC scores of all neighbor, the maximal one is Bic_score =%8.3f \n', BIC_score); + temp = abs(bic_score0) * improve_factor; + + if BIC_score > (bic_score0 + temp) + best_dag = cdags{best}; + new_order=topological_sort(best_dag); + for j = 1:N + row = new_order(j); + for k = 1:ncases + update_samples{j,k} = data{row,k}; + end + end + data = update_samples; + + forbidden=[]; + reversed =[]; + for j=1:N + old_parents= sort((find(bnet.dag(:,j)==1))'); + new_parents= sort((find(best_dag(:,j)==1))'); + if ~isequal(new_parents,old_parents) + reversed =[reversed,j]; + end + end + + new_dag = best_dag(new_order, new_order); %reshape the best DAG according to new_order + ns = ns(new_order); %modify the order of ns + CPDs = bnet.CPD; + + bnet = mk_bnet(new_dag, ns); %make the new best BN structure + %randomly set the CPD values of each node + for j=1:N + bnet.CPD{j} = tabular_CPD(bnet, j, 'prior_type', 'dirichlet', 'dirichlet_weight', 0); + end + + lreversed= length(reversed); + if lreversed ~=0 + for r=1:lreversed %update the CPDs of nodes whose parent set has been altered using the saved new_CPT + reverse=find(new_order==reversed(r)); + forbidden=[forbidden,reverse]; + bnet.CPD{reverse} = set_fields(bnet.CPD{reverse}, 'CPT', new_CPT{best,reversed(r)}); + end + end + + bnet.CPD = copy_CPD(bnet.CPD, CPDs, new_order, forbidden); %copy the CPDs of remaining nodes from CPDs + + cpdag = dag_to_cpdag(bnet.dag); %get the best equivalence class + + clear bic_score D d new_D new_d; % new computations for each iteration + clear ess LL new_LL new_CPT; + clear pdags dags cdags nodes cnodes; + else + fprintf('No improvement of the expected bic score : End of delete phase \n \n'); + BIC_score = bic_score0; + converged=1; + end +end + +cpdag + +nloop=nloop+loop; %total iteration number + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [D,d]= compute_bnet_nparams(bnet) +N = length(bnet.dag); +d = zeros(1,N); + for i=1:N + a = struct(bnet.CPD{i}); + d(i) = a.nparams; + end +D = sum(d); +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [ess, LL] = compute_ess(bnet, samplesM, dags) +%compute the estimations for all consistent extensions based on the current bnet +tiny = exp(-700); +N = length(bnet.dag); +ns = bnet.node_sizes; +ncases = size(samplesM, 2); +ess0 = cell(1,N); +LL = zeros(1, N); +nbrs= length(dags); +copy=zeros(nbrs,N); + +for i =1:nbrs + for x=1:N + ess{i,x}=[]; + end +end + +for i =1:N + parents = bnet.parents{i}; + family = [parents, i]; + ess0{i} = 0 * myones(ns(family)); +end + +for i =1:nbrs + neighbor=dags{i}; + for j=1:N + old_parents = sort((find(bnet.dag(:,j)==1))'); + new_parents = sort((find(neighbor(:,j)==1))'); + if ~isequal(new_parents,old_parents) + domain = sort(myunion(new_parents, j)); + copy(i,j)= find_same_domain(ess,j,domain,i); + ess{i,j}.domain = domain; + ess{i,j}.counts = 0 * myones(ns(domain)); + end + end +end + +engine = jtree_inf_engine(bnet); + +for l =1:ncases + evidence = samplesM(:, l); + [engine, ll] = enter_evidence(engine, evidence); + ns_eff = ns; + ns_eff(~isemptycell(evidence)) = 1; + Vmarg = cell(1,N); + for i =1:N + Vmarg{i} = marginal_nodes(engine, i); + end + for i = 1:N + parents = bnet.parents{i}; + family = [parents, i]; + nfamily = length(family); + Fmarg = []; + for j = 1:nfamily + Fmarg = multiply_one_marginal(Fmarg, Vmarg{family(j)}, ns_eff); + end + fullm = add_ev_to_dmarginal(Fmarg, evidence, ns); + ess0{i} = ess0{i} + fullm.T; + end + + for i = 1:nbrs + for j=1:N + if ~isempty(ess{i,j}) + if ~copy(i,j) + domain = ess{i,j}.domain; + Fmarg = []; + for compteur=1:length(domain) + Fmarg = multiply_one_marginal(Fmarg, Vmarg{domain(compteur)}, ns_eff); + end + fullm = add_ev_to_dmarginal(Fmarg, evidence, ns); + ess{i,j}.counts = ess{i,j}.counts + fullm.T; + end + end + end + end + clear Vmarg; +end + +for i = 1:nbrs + for j=1:N + if copy(i,j) + ess{i,j}.counts = ess{copy(i,j),j}.counts; + end + end +end + +for i=1:N + s = struct(bnet.CPD{i}); + counts = ess0{i}; + LL(i) = sum(log(s.CPT(:) + tiny) .* counts(:)); +end +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function index = find_same_domain(ess, j, domain, length) +index = 0; +for i = 1:length + if ~isempty(ess{i,j}) + if isequal(domain, ess{i,j}.domain) + index= i; + break; + end + end +end +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function newCPD = copy_CPD(newCPD, CPDs, order, forbidden) +%copy CPDs from old bnet to best bnet, except those nodes whose parent set has been altered +N = length(order); +for i=1:N + if ~mysubset(i,forbidden) + a = order(i); + s = struct(CPDs{a}); + CPT = s.CPT; + newCPD{i} = set_fields(newCPD{i}, 'CPT', CPT); + end +end +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/sourcecodes/bnt-master/SLP/learning/learn_struct_gs.m b/sourcecodes/bnt-master/SLP/learning/learn_struct_gs.m new file mode 100644 index 00000000..3dd3ef1c --- /dev/null +++ b/sourcecodes/bnt-master/SLP/learning/learn_struct_gs.m @@ -0,0 +1,114 @@ +function [dag,best_score] = learn_struct_gs(data, nodesizes, seeddag, varargin) +% +% LEARN_STRUCT_GS(data,seeddag) learns a structure of Bayesian net by Greedy Search. +% dag = learn_struct_gs(data, nodesizes, seeddag) +% +% dag: the final structurre matrix +% Data : training data, data(i,m) is the m obsevation of node i +% Nodesizes: the size array of different nodes +% seeddag: given seed Dag for hill climbing, optional +% +% +% by Gang Li @ Deakin University (gli73@hotmail.com) + +[N ncases] = size(data); +if (nargin < 3 ) + seeddag = zeros(N,N); % mk_rnd_dag(N); %call BNT function +elseif ~acyclic(seeddag) + seeddag = mk_rnd_dag(N); %zeros(N,N); +end; + +% set default params +scoring_fn = 'bic'; +verbose = 'yes'; + +% get params +args = varargin; +nargs = length(args); +if length(args) > 0 + if isstr(args{1}) + for i = 1:2:nargs + switch args{i} + case 'scoring_fn', scoring_fn = args{i+1}; + case 'verbose', verbose = strcmp(args{i+1},'yes'); + end; + end; + end; +end; + +done = 0; +best_score = score_dags(data,nodesizes, {seeddag},'scoring_fn',scoring_fn); +while ~done + [dags,op,nodes] = mk_nbrs_of_dag(seeddag); + nbrs = length(dags); + scores = score_dags(data, nodesizes, dags,'scoring_fn',scoring_fn); + max_score = max(scores); + new = find(scores == max_score ); + if ~isempty(new) & (max_score > best_score) + p = sample_discrete(normalise(ones(1, length(new)))); + best_score = max_score; + seeddag = dags{new(p)}; + else + done = 1; + end; +end; + +dag = seeddag; + +outcount = 0; +best_score = score_dags(data,nodesizes, {seeddag},'scoring_fn',scoring_fn); +while outcount < 2 + innercount = 0; + for i=1:N + for j=1:N + if i==j, continue; end; + if seeddag(i,j) == 0 % No edge i-->j, then try to add it + tempdag = seeddag; + tempdag(i,j) = 1; + if acyclic(tempdag) + temp_score = score_dags(data,nodesizes, {tempdag},'scoring_fn',scoring_fn); + if temp_score > best_score + seeddag = tempdag; + best_score= temp_score; + innercount = innercount +1; + end; + end + else % exists edge i--j, then try reverse it or remove it + tempdag = seeddag; + tempdag(i,j) = 0; tempdag(j,i) = 1; + if acyclic(tempdag) + temp_score = score_dags(data,nodesizes, {tempdag},'scoring_fn',scoring_fn); + if temp_score > best_score + seeddag = tempdag; + best_score = temp_score; + innercount = innercount +1; + else + tempdag = seeddag; + tempdag(i,j) = 0; + temp_score = score_dags(data,nodesizes, {tempdag},'scoring_fn',scoring_fn); + if temp_score > best_score + seeddag = tempdag; + best_score= temp_score; + innercount = innercount +1; + end; + end; + else + tempdag = seeddag; + tempdag(i,j)=0; + temp_score = score_dags(data,nodesizes, {tempdag},'scoring_fn',scoring_fn); + if temp_score > best_score + seeddag = tempdag; + best_score= temp_score; + innercount = innercount +1; + end; + end; + end; + end; % end for j + end; % end for i + if innercount == 0 + outcount = outcount +1; + end; +end; % end while + +dag = seeddag; + diff --git a/sourcecodes/bnt-master/SLP/learning/learn_struct_gs2.m b/sourcecodes/bnt-master/SLP/learning/learn_struct_gs2.m new file mode 100644 index 00000000..4e054800 --- /dev/null +++ b/sourcecodes/bnt-master/SLP/learning/learn_struct_gs2.m @@ -0,0 +1,119 @@ +function [dag, best_score, cache] = learn_struct_gs2(data, nodesizes, seeddag, varargin) +% +% LEARN_STRUCT_GS2(data,seeddag) learns a structure of Bayesian net by Greedy Search. +% dag = learn_struct_gs(data, nodesizes, seeddag) +% +% dag: the final structure matrix +% Data : training data, data(i,m) is the m obsevation of node i +% Nodesizes: the size array of different nodes +% seeddag: given seed Dag for hill climbing, optional +% cache : data structure used to memorize local score computations +% (cf. SCORE_INIT_CACHE function) +% +% by Gang Li @ Deakin University (gli73@hotmail.com) +% (use mk_nbrs_of_dag_topo, developped by Wei Hu, instead of mk_nbrs_of_dag) +% (Caching implementation : ofrancois.olivier.c.h@gmail.com, philippe.leray@univ-nantes.fr) +% + +[N ncases] = size(data); +if (nargin < 3 ) + seeddag = zeros(N,N); % mk_rnd_dag(N); %call BNT function +elseif ~acyclic(seeddag) + seeddag = mk_rnd_dag(N); %zeros(N,N); +end; + +% set default params +scoring_fn = 'bic'; +verbose = 'yes'; +cache=[]; + +% get params +args = varargin; +nargs = length(args); +if length(args) > 0 + if isstr(args{1}) + for i = 1:2:nargs + switch args{i} + case 'scoring_fn', scoring_fn = args{i+1}; + case 'verbose', verbose = strcmp(args{i+1},'yes'); + case 'cache', cache=args{i+1} ; + end; + end; + end; +end; + +done = 0; +[best_score cache] = score_dags(data,nodesizes, {seeddag},'scoring_fn',scoring_fn,'cache',cache); +while ~done + [dags,op,nodes] = mk_nbrs_of_dag_topo(seeddag); + nbrs = length(dags); + [scores cache] = score_dags(data, nodesizes, dags,'scoring_fn',scoring_fn,'cache',cache); + max_score = max(scores); + new = find(scores == max_score ); + if ~isempty(new) & (max_score > best_score) + p = sample_discrete(normalise(ones(1, length(new)))); + best_score = max_score; + seeddag = dags{new(p)}; + else + done = 1; + end; +end; + +dag = seeddag; + +outcount = 0; +[best_score cache] = score_dags(data,nodesizes, {seeddag},'scoring_fn',scoring_fn,'cache',cache); +while outcount < 2 + innercount = 0; + for i=1:N + for j=1:N + if i==j, continue; end; + if seeddag(i,j) == 0 % No edge i-->j, then try to add it + tempdag = seeddag; + tempdag(i,j) = 1; + if acyclic(tempdag) + [temp_score cache] = score_dags(data,nodesizes, {tempdag},'scoring_fn',scoring_fn,'cache',cache); + if temp_score > best_score + seeddag = tempdag + best_score= temp_score; + innercount = innercount +1; + end; + end + else % exists edge i--j, then try reverse it or remove it + tempdag = seeddag; + tempdag(i,j) = 0; tempdag(j,i) = 1; + if acyclic(tempdag) + [temp_score cache] = score_dags(data,nodesizes, {tempdag},'scoring_fn',scoring_fn,'cache',cache); + if temp_score > best_score + seeddag = tempdag; + best_score = temp_score; + innercount = innercount +1; + else + tempdag = seeddag; + tempdag(i,j) = 0; + [temp_score cache] = score_dags(data,nodesizes, {tempdag},'scoring_fn',scoring_fn,'cache',cache); + if temp_score > best_score + seeddag = tempdag; + best_score= temp_score; + innercount = innercount +1; + end; + end; + else + tempdag = seeddag; + tempdag(i,j)=0; + [temp_score cache] = score_dags(data,nodesizes, {tempdag},'scoring_fn',scoring_fn,'cache',cache); + if temp_score > best_score + seeddag = tempdag + best_score= temp_score; + innercount = innercount +1; + end; + end; + end; + end; % end for j + end; % end for i + if innercount == 0 + outcount = outcount +1; + end; +end; % end while + +dag = seeddag; diff --git a/sourcecodes/bnt-master/SLP/learning/learn_struct_hc.m b/sourcecodes/bnt-master/SLP/learning/learn_struct_hc.m new file mode 100644 index 00000000..e32dee80 --- /dev/null +++ b/sourcecodes/bnt-master/SLP/learning/learn_struct_hc.m @@ -0,0 +1,56 @@ +function [dag,best_score] = learn_struct_hc(data, nodesizes, seeddag, varargin) +% +% LEARN_STRUCT_HC(data,seeddag) learns a structure of Bayesian net by Hill Climbing. +% dag = learn_struct_hc(data, nodesizes, seeddag) +% +% dag: the final structurre matrix +% Data : training data, data(i,m) is the m obsevation of node i +% Nodesizes: the size array of different nodes +% seeddag: given seed Dag for hill climbing, optional +% +% by Gang Li @ Deakin University (gli73@hotmail.com) + +[N ncases] = size(data); +if (nargin < 3 ) + seeddag = zeros(N,N); % mk_rnd_dag(N); %call BNT function +elseif ~acyclic(seeddag) + seeddag = mk_rnd_dag(N); %zeros(N,N); +end; + +% set default params +scoring_fn = 'bic'; +verbose = 'yes'; + +% get params +args = varargin; +nargs = length(args); +if length(args) > 0 + if isstr(args{1}) + for i = 1:2:nargs + switch args{i} + case 'scoring_fn', scoring_fn = args{i+1}; + case 'verbose', verbose = strcmp(args{i+1},'yes'); + end; + end; + end; +end; + +done = 0; +best_score = score_dags(data,nodesizes, {seeddag},'scoring_fn',scoring_fn); +while ~done + [dags,op,nodes] = mk_nbrs_of_dag(seeddag); + nbrs = length(dags); + scores = score_dags(data, nodesizes, dags,'scoring_fn',scoring_fn); + max_score = max(scores); + new = find(scores == max_score ); + if ~isempty(new) & (max_score > best_score) + p = sample_discrete(normalise(ones(1, length(new)))); + best_score = max_score; + seeddag = dags{new(p)}; + else + done = 1; + end; +end; + +dag = seeddag; + diff --git a/sourcecodes/bnt-master/SLP/learning/learn_struct_mcmc.m b/sourcecodes/bnt-master/SLP/learning/learn_struct_mcmc.m new file mode 100644 index 00000000..36207950 --- /dev/null +++ b/sourcecodes/bnt-master/SLP/learning/learn_struct_mcmc.m @@ -0,0 +1,311 @@ +function [sampled_graphs, accept_ratio, num_edges] = learn_struct_mcmc(data, ns, varargin) +% LEARN_STRUCT_MCMC Monte Carlo Markov Chain search over DAGs assuming fully observed data +% [sampled_graphs, accept_ratio, num_edges] = learn_struct_mcmc(data, ns, ...) +% +% data(i,m) is the value of node i in case m. +% ns(i) is the number of discrete values node i can take on. +% +% sampled_graphs{m} is the m'th sampled graph. +% accept_ratio(t) = acceptance ratio at iteration t +% num_edges(t) = number of edges in model at iteration t +% +% The following optional arguments can be specified in the form of name/value pairs: +% [default value in brackets] +% +% 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) ] +% nsamples - number of samples to draw from the chain after burn-in [ 100*N ] +% burnin - number of steps to take before drawing samples [ 5*N ] +% init_dag - starting point for the search [ zeros(N,N) ] +% +% e.g., samples = my_learn_struct_mcmc(data, ns, 'nsamples', 1000); +% +% +% Modified by Mingyi Wang (mingyiwang@hotmail.com) Sep 18, 2006 (based on Sonia Leach (SML)'s version ( 2/4/02, 9/5/03)) +% +% Some bugs in update_ancestor_matrix() were fixed. This function can call mk_nbrs_of_digraph properly +% + +[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); +nsamples = 100*n; +burnin = 5*n; +dag = zeros(n); + +args = varargin; +nargs = length(args); +for i=1:2:nargs + switch args{i}, + case 'nsamples', nsamples = args{i+1}; + case 'burnin', burnin = args{i+1}; + case 'init_dag', dag = 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 'gconstraint', gconstraint=args{i+1}; %Added by mingyi + case 'params', if isempty(args{i+1}), params = cell(1,n); else params = args{i+1}; end + + end +end + +% We implement the fast acyclicity check described by P. Giudici and R. Castelo, +% "Improving MCMC model search for data mining", submitted to J. Machine Learning, 2001. + +% SML: also keep descendant matrix C +use_giudici = 1; +%use_giudici = 0; %Revised by MIngyi +if use_giudici + [nbrs, ops, nodes, A] = mk_nbrs_of_digraph(dag); +else + [nbrs, ops, nodes] = mk_nbrs_of_dag(dag); + A = []; +end + +num_accepts = 1; +num_rejects = 1; +T = burnin + nsamples; +accept_ratio = zeros(1, T); +num_edges = zeros(1, T); +sampled_graphs = cell(1, nsamples); +%sampled_bitv = zeros(nsamples, n^2); + +for t=1:T + [dag, nbrs, ops, nodes, A, accept] = take_step(dag, nbrs, ops, ... + nodes, ns, data, clamped, A, ... + scoring_fn, discrete, type, params); + num_edges(t) = sum(dag(:)); + num_accepts = num_accepts + accept; + num_rejects = num_rejects + (1-accept); + accept_ratio(t) = num_accepts/num_rejects; + if t > burnin + sampled_graphs{t-burnin} = dag; + %sampled_bitv(t-burnin, :) = dag(:)'; + end + fprintf('MCMC: %d/%d\n',t,T); +end + + +%%%%%%%%% + + +function [new_dag, new_nbrs, new_ops, new_nodes, A, accept] = ... + take_step(dag, nbrs, ops, nodes, ns, data, clamped, A, ... + scoring_fn, discrete, type, params, prior_w) + +global gconstraint; %Added by Mingyi +use_giudici = ~isempty(A); +if use_giudici + [new_dag, op, i, j, new_A] = pick_digraph_nbr(dag, nbrs, ops, nodes,A); % updates A + [new_nbrs, new_ops, new_nodes] = mk_nbrs_of_digraph(new_dag,new_A); +else + d = sample_discrete(normalise(ones(1, length(nbrs)))); + new_dag = nbrs{d}; + op = ops{d}; + i = nodes(d, 1); j = nodes(d, 2); + [new_nbrs, new_ops, new_nodes] = mk_nbrs_of_dag1(new_dag); +end +%For debug +% fprintf('op:%s,i:%d,j:%d\n',op,i,j); +% if ~acyclic(new_dag) +% error('new dag must be acyclic!') +% end +% if size(find(diag(new_A)),1)>0 +% A=A +% new_A=new_A +% error('new A must be acyclic!') +% end +%debug ends + +bf = bayes_factor(dag, new_dag, op, i, j, ns, data, clamped, scoring_fn, discrete, type, params); + +%R = bf * (new_prior / prior) * (length(nbrs) / length(new_nbrs)); +R = bf * (length(nbrs) / length(new_nbrs)); +u = rand(1,1); +if u > min(1,R) % reject the move + accept = 0; + new_dag = dag; + new_nbrs = nbrs; + new_ops = ops; + new_nodes = nodes; +else + accept = 1; + if use_giudici + A = new_A; % new_A already updated in pick_digraph_nbr + end +end + + +%%%%%%%%% + +function bfactor = bayes_factor(old_dag, new_dag, op, i, j, ns, data, clamped, scoring_fn, discrete, type, params) + +u = find(clamped(j,:)==0); +LLnew = score_family(j, parents(new_dag, j), type{j}, scoring_fn, ns, discrete, data(:,u), params{j}); +LLold = score_family(j, parents(old_dag, j), type{j}, scoring_fn, ns, discrete, data(:,u), params{j}); +bf1 = exp(LLnew - LLold); + +if strcmp(op, 'rev') % must also multiply in the changes to i's family + u = find(clamped(i,:)==0); + LLnew = score_family(i, parents(new_dag, i), type{i}, scoring_fn, ns, discrete, data(:,u), params{i}); + LLold = score_family(i, parents(old_dag, i), type{i}, scoring_fn, ns, discrete, data(:,u), params{i}); + bf2 = exp(LLnew - LLold); +else + bf2 = 1; +end +bfactor = bf1 * bf2; + + +%%%%%%%% Giudici stuff follows %%%%%%%%%% + + +% SML: This now updates A as it goes from digraph it choses +function [new_dag, op, i, j, new_A] = pick_digraph_nbr(dag, digraph_nbrs, ops, nodes, A) + +d = sample_discrete(normalise(ones(1, length(digraph_nbrs)))); +%d = myunidrnd(length(digraph_nbrs),1,1); +i = nodes(d, 1); j = nodes(d, 2); +new_dag = digraph_nbrs(:,:,d); + +op = ops{d}; +new_A = update_ancestor_matrix(A, op, i, j, dag); +%for debug +% if op=='add' +% if ~(dag(i,j)==0 & new_dag(i,j)==1) +% fprintf('error add\n'); +% end +% end +% if op=='del' +% if ~(dag(i,j)==1 & new_dag(i,j)==0) +% fprintf('new dag del calculation is error!\n') +% end +% end +% if op=='rev' +% if ~(dag(i,j)==1 & dag(j,i)==0 & new_dag(i,j)==0 & new_dag(j,i)==1) +% fprintf('new dag rev calculation is error!\n') +% end +% end +% new_AA = reachability_graph(new_dag'); +% if find(diag(new_AA)==1) +% fprintf('cyclic\n'); +% end +% if ~isequal(new_A,new_AA) +% fprintf('new A calculation is error!\n') +% end +%debug ends + +%%%%%%%%%%%%%% + +function A = update_ancestor_matrix(A, op, i, j, dag) + +switch op +case 'add', + A = do_addition(A, op, i, j, dag); +case 'del', + A = do_removal(A, op, i, j, dag); +case 'rev', + A = do_removal(A, op, i, j, dag); + A = do_addition(A, op, j, i, dag); +end + + +%%%%%%%%%%%% + +function A = do_addition(A, op, i, j, dag) + +A(j,i) = 1; % i is an ancestor of j +anci = find(A(i,:)); +if ~isempty(anci) + A(j,anci) = 1; % all of i's ancestors are added to Anc(j) +end + +descj = find(A(:,j)); %all the descendants of j are selected +if ~isempty(descj) + for k=descj(:)' + A(k,i) = 1; % i is the ancestor of descj + if ~isempty(anci) % all of i's ancestors are also the ancestor of each descendant of j + A(k,anci)=1; + end + end +end + + +%%%%%%%%%%% + +function A = do_removal(A, op, i, j, dag) +descj = find(A(:,j)); +A = update_row(A,i, j, dag); % compute the A(j,:) row for dag i->j removal + +if ~isempty(descj) + order = topological_sort(dag); %all the parent nodes are before to the children nodes + [junk, perm] = sort(order); %node i is perm(i)-TH in order + descj_topnum = perm(descj); %descj(i) is descj_topnum(i)-th in order + +% SML: now re-sort descj by rank in descj_topnum + [junk, perm] = sort(descj_topnum); + descj = descj(perm); + for k = descj(:)' + A = old_update_row(A, k, dag); + end +end + +%%%%%%%%% + +function A = update_row(A, i,j, dag) +% We compute row j of A +A(j, :) = 0; +ps = parents(dag, j); +ps=setdiff(ps,i); % All the parents except i +if ~isempty(ps) + A(j, ps) = 1; +end +for k=ps(:)' + anck = find(A(k,:)); + if ~isempty(anck) + A(j, anck) = 1; + end +end + +%%%%%%%%% + +function A = old_update_row(A, j, dag) + +% We compute row j of A +A(j, :) = 0; +ps = parents(dag, j); +if ~isempty(ps) + A(j, ps) = 1; +end +for k=ps(:)' + anck = find(A(k,:)); + if ~isempty(anck) + A(j, anck) = 1; + end +end + +%%%%%%%% + +function A = init_ancestor_matrix(dag) + +order = topological_sort(dag); +A = zeros(length(dag)); +for j=order(:)' + A = update_row(A, j, dag); +end diff --git a/sourcecodes/bnt-master/SLP/learning/learn_struct_mwst.m b/sourcecodes/bnt-master/SLP/learning/learn_struct_mwst.m new file mode 100644 index 00000000..8437385e --- /dev/null +++ b/sourcecodes/bnt-master/SLP/learning/learn_struct_mwst.m @@ -0,0 +1,62 @@ +function [T, score_mat] = learn_struct_mwst(data, discrete, node_sizes, node_type, scoring_fn, root) +% LEARN_STRUCT_MWST Learn an oriented tree using the MSWT algorithm +% T = learn_struct_mwst(data, discrete, node_sizes, node_type, scoring_fn, root) +% +% Input : +% data(i,m) is the node i in the case m, +% discrete = [ 1 if discret-node 0 if not ], +% node_sizes = 1 if gaussian node, +% node_type = {'tabular','gaussian',...}, +% score = 'bic' (for complete data and any node types) or 'mutual_info' (tabular nodes), +% root is the futur root-node of the tree T. +% +% Output : +% T = adjacency matrix of the tree +% +% V1.2 : 17 feb 2003 (O. Francois - francois.olivier.c.h@gmail.com, Ph. Leray - philippe.leray@univ-nantes.fr) +% +% +% See Chow&Liu 1968 for the original algorithm using Mutual Information scoring. +% Or Heckerman 1994. + +if nargin <4 + error('Requires at least 4 arguments.') +end + +if nargin == 4 + scoring_fn='bic'; root=1; +end; + +if nargin == 5 + root=1; +end; + + +N=size(data,1); +score_mat=zeros(N,N); + +switch scoring_fn +case 'bic', + for i=1:(N-1) + score2 = score_family(i, [], node_type{i}, scoring_fn, node_sizes, discrete, data,[]); + for j=(i+1):N + score1 = score_family(i, [j], node_type{i}, scoring_fn, node_sizes, discrete, data,[]); + score = score2-score1; + score_mat(i,j)=score; + score_mat(j,i)=score; + end + end +case 'mutual_info', + for i=1:(N-1) + for j=(i+1):N + score_mat(i,j)= -mutual_info_score(i,node_sizes(i),j,node_sizes(j),data); + score_mat(j,i)=score_mat(i,j); + end + end +otherwise, + error(['unrecognized scoring fn ' scoring_fn]); +end + +G = minimum_spanning_tree(score_mat); +T = mk_rooted_tree(G, root); +T=full(T); \ No newline at end of file diff --git a/sourcecodes/bnt-master/SLP/learning/learn_struct_mwst_EM.m b/sourcecodes/bnt-master/SLP/learning/learn_struct_mwst_EM.m new file mode 100644 index 00000000..7dcf531e --- /dev/null +++ b/sourcecodes/bnt-master/SLP/learning/learn_struct_mwst_EM.m @@ -0,0 +1,171 @@ +function [bnet0, Sbest, Obest] = learn_struct_mwst_EM(data, discrete, node_sizes, prior, nbloopmax, thresh) +% LEARN_STRUCT_MWST_EM Learn an oriented tree using the MSWT algorithm +% [bnet, Ebic] = learn_struct_mwstem(data, discrete, node_sizes, prior, +% nbloopmax, thresh) +% +% Input : +% data{i,m} a cell where the node i in the case m, +% discrete = [ 1 if discret-node 0 if not ], (1:N) +% node_sizes = 1 if gaussian node, (max on complete samples) +% prior = 1 to use uniform Dirichlet prior (0) +% nbloopmax = max loop number (ceil(log(N*log(N)))) +% thresh = the convergence test's threshold (1e-3) +% +% Output : +% bnet = the output bayesian network +% Ebic = the espected BIC score of bnet given the data +% +% francois.olivier.c.h@gmail.com + +%%%%%%%%%%%%% +%fprintf('-- INITIALIZATION\n'); +%%%%%%%%%%%%% +[N, m]=size(data); + rand('state',sum(100*clock)) + +if nargin<6, thresh = 1e-4; end +if nargin<5, nbloopmax = 15; end +if nargin<4, prior = 0; end +if nargin<3, + misv = -9999; + data_mat = bnt_to_mat(data,misv); + node_sizes = max(data_mat'), +end +if nargin<2, discrete = ones(1,N); end + +nbloop = 1; last=0; +max_iter = 6; % for learn_struct_params + +%%%%%%%%%%%%% +%fprintf(' Choice of the first bnet ');tic +%%%%%%%%%%%%% +% Random Chain like DAG +T = diag(ones(N-1,1),1); T=T+T'; +order=randperm(N); root=randperm(N); root=root(1); +[tmp order2]=sort(order); order2; +T2 = full(mk_rooted_tree(T,order2(root))); +torder=topological_sort(T2(order2,order2)); +[tmp torder2]=sort(torder); torder2; +ordre = 1:N;torder=ordre;torder2=ordre;order2=ordre; +%figure(2), subplot(3,2,1), draw_graph(T2(order2,order2));drawnow + +bnet0 = mk_bnet(T2(order2(torder),order2(torder)), node_sizes(torder)); +%for i=1:N, bnet0.CPD{i} = tabular_CPD(bnet0, i); end % probleme de log of zeros si tous les cas ne sont pas repr�sent�s dans la base +for i=1:N, bnet0.CPD{i} = tabular_CPD(bnet0, i, 'prior_type', 'dirichlet', 'dirichlet_type', 'unif'); end % a priori -> change les espected counts +%tmp = toc;fprintf(' : %6.2f seconds\n',tmp); + +Sbest = -Inf; Bbest = bnet0; fini=0;T3=T2; +while not(fini) + %%%%%%%%%%%%% + %fprintf('Loop %d : learning parameters...\n',nbloop);tic +% engine0=jtree_sparse_inf_engine(bnet0); + engine0=jtree_inf_engine(bnet0); + [bnet1, LL1, engine1] = learn_params_em(engine0, data(torder,:), max_iter, thresh); + %tmp = toc; + %fprintf(' Parameters learning : %6.2f seconds\n',tmp); + + BIC0=0; + for i=1:N, + xxx=struct(bnet1.CPD{i}); + BIC0=BIC0+bic_score_family(xxx.counts, xxx.CPT, xxx.nsamples); + end + %fprintf('%d ',torder), fprintf('%5.2f\n',BIC0); + %figure(2), subplot(3,2,nbloop), title(sprintf('%5.2f',BIC0)); + + if BIC0 < Sbest+ thresh*abs(Sbest) | nbloop>nbloopmax + fini=1; + else + Sbest = BIC0; + Bbest = bnet1; + Obest = torder2 ; + Tbest = T3; + + %%%%%%%%%%%%% + %tic; + %%%%%%%%%%%%% + + theta_Xi=cell(N,1); + evidence = cell(1,N); + [engine2, loglik] = enter_evidence(engine1, evidence); + for j=1:N, + SS= marginal_nodes (engine2,torder2(j)); + theta_Xi{j} = SS.T; + end + + theta_Xj_given_Xi = cell(N,N); + for i=1:N + for vali = 1:node_sizes(i) + evidence = cell(1,N); evidence{torder2(i)} = vali; + [engine2, loglik] = enter_evidence(engine1, evidence); + for j=mysetdiff(1:N,i) + SS= marginal_nodes (engine2,torder2(j)); + theta_Xj_given_Xi{j,i} = [theta_Xj_given_Xi{j,i}, SS.T]; + end + end + end + %celldisp(theta_Xi); + + BIC_mat=zeros(N,N); + for i=1:N, + BIC_mat(i,i)=bic_score_family(theta_Xi{i}*m,theta_Xi{i},m); + for j=mysetdiff(1:N,i) + theta_XjXi=(ones(node_sizes(i),1)*theta_Xi{j}').*theta_Xj_given_Xi{i,j}; + BIC_mat(i,j)= bic_score_family(m*theta_XjXi,theta_Xj_given_Xi{i,j},m); + end + end + BIC_delta = BIC_mat-diag(BIC_mat)*ones(1,N); + + BIC1=0; + for i = 1:N + j = find(bnet0.dag(:,i)==1); + if isempty(j) + BIC1 = BIC1 + BIC_mat(torder2(i),torder2(i)); + else + BIC1 = BIC1 + BIC_mat(torder2(i),torder2(j)); + end + end + %fprintf('%d ',torder), fprintf('%5.2f (1)\n',BIC1); + + %fprintf(' Creation of the score matrix '); + %tmp = toc;fprintf(' : %6.2f seconds\n',tmp); + + %%%%%%%%%%%%% + %fprintf(' Creation of the new bnet ');tic + %%%%%%%%%%%%% + T2 = minimum_spanning_tree(-BIC_delta); + root=randperm(N); root=root(1); + T3 = full(mk_rooted_tree(T2, root)); + torder=topological_sort(T3); + [tmp torder2]=sort(torder); torder2; + + bnet0 = mk_bnet(T3(torder,torder), node_sizes(torder)); +%bnet0.order + %BIC=0; + for i = 1:N + j = find(T3(:,i)==1); + if isempty(j) + bnet0.CPD{torder2(i)} = tabular_CPD(bnet0, torder2(i), 'CPT', theta_Xi{i}, 'prior_type', 'dirichlet', 'dirichlet_type', 'unif'); + %BIC = BIC + BIC_mat(i,i); + else + bnet0.CPD{torder2(i)} = tabular_CPD(bnet0, torder2(i), 'CPT', theta_Xj_given_Xi{i,j}, 'prior_type', 'dirichlet', 'dirichlet_type', 'unif'); + %BIC=BIC + BIC_mat(i,j); + end + end + nbloop=nbloop+1; + %figure(2), subplot(3,2,nbloop), draw_graph(T3); drawnow + %tmp = toc;fprintf(' : %6.2f seconds\n',tmp); + fprintf('================================================================================\n'); + %fprintf(' --> BIC score = %6.2f\n\n\n',BIC); + theta_Xi_best=theta_Xi; + theta_Xj_given_Xi_best=theta_Xj_given_Xi; + end +end + +% Bbest.dag +% Tbest + +% bnet = mk_bnet(Tbest, node_sizes); +% for i=1:N, bnet.CPD{i} = tabular_CPD(bnet, i, 'prior_type', 'dirichlet', 'dirichlet_type', 'unif'); end +% engine=jtree_sparse_inf_engine(bnet); +% [Bbest, LL1] = learn_params_em(engine, data, max_iter, thresh); + diff --git a/sourcecodes/bnt-master/SLP/learning/learn_struct_pdag_pc_mod.m b/sourcecodes/bnt-master/SLP/learning/learn_struct_pdag_pc_mod.m new file mode 100644 index 00000000..081c70f1 --- /dev/null +++ b/sourcecodes/bnt-master/SLP/learning/learn_struct_pdag_pc_mod.m @@ -0,0 +1,158 @@ +function [pdag, G] = learn_struct_pdag_pc_mod(cond_indep, n, k, varargin) +% LEARN_STRUCT_PDAG_PC Learn a partially oriented DAG (pattern) using the PC algorithm +% P = learn_struct_pdag_pc(cond_indep, n, k, ...) +% +% n is the number of nodes. +% k is an optional upper bound on the fan-in (default: n) +% cond_indep is a boolean function that will be called as follows: +% feval(cond_indep, x, y, S, ...) +% where x and y are nodes, and S is a set of nodes (positive integers), +% and ... are any optional parameters passed to this function. +% +% The output P is an adjacency matrix, in which +% P(i,j) = -1 if there is an i->j edge. +% P(i,j) = P(j,i) = 1 if there is an undirected edge i <-> j +% +% The PC algorithm does structure learning assuming all variables are observed. +% See Spirtes, Glymour and Scheines, "Causation, Prediction and Search", 1993, p117. +% This algorithm may take O(n^k) time if there are n variables and k is the max fan-in, +% but this is quicker than the Verma-Pearl IC algorithm, which is always O(n^n). + + +sep = cell(n,n); +ord = 0; +done = 0; +G = ones(n,n); +G=setdiag(G,0); +while ~done + done = 1; + [X,Y] = find(G); + for i=1:length(X) + x = X(i); y = Y(i); + %nbrs = mysetdiff(myunion(neighbors(G, x), neighbors(G,y)), [x y]); + nbrs = mysetdiff(neighbors(G, y), x); % bug fix by Raanan Yehezkel <raanany@ee.bgu.ac.il> 6/27/04 + if length(nbrs) >= ord & G(x,y) ~= 0 + done = 0; + %SS = subsets(nbrs, ord, ord); % all subsets of size ord + SS = subsets1(nbrs, ord); + for si=1:length(SS) + S = SS{si}; + if feval(cond_indep, x, y, S, varargin{:}) + %if isempty(S) + % fprintf('%d indep of %d ', x, y); + %else + % fprintf('%d indep of %d given ', x, y); fprintf('%d ', S); + %end + %fprintf('\n'); + + % diagnostic + %[CI, r] = cond_indep_fisher_z(x, y, S, varargin{:}); + %fprintf(': r = %6.4f\n', r); + + G(x,y) = 0; + G(y,x) = 0; + sep{x,y} = myunion(sep{x,y}, S); + sep{y,x} = myunion(sep{y,x}, S); + break; % no need to check any more subsets + end + end + end + end + ord = ord + 1; +end + +% Create the minimal pattern, +% i.e., the only directed edges are V structures. +pdag = G; +[X, Y] = find(G); +% We want to generate all unique triples x,y,z +% This code generates x,y,z and z,y,x. +for i=1:length(X) + x = X(i); + y = Y(i); + Z = find(G(y,:)); + Z = mysetdiff(Z, x); + for z=Z(:)' + if G(x,z)==0 & ~ismember(y, sep{x,z}) & ~ismember(y, sep{z,x}) + %fprintf('%d -> %d <- %d\n', x, y, z); + pdag(x,y) = -1; pdag(y,x) = 0; + pdag(z,y) = -1; pdag(y,z) = 0; + end + end +end + +% Convert the minimal pattern to a complete one, +% i.e., every directed edge in P is compelled +% (must be directed in all Markov equivalent models), +% and every undirected edge in P is reversible. +% We use the rules of Pearl (2000) p51 (derived in Meek (1995)) + +old_pdag = zeros(n); +iter = 0; +while ~isequal(pdag, old_pdag) + iter = iter + 1; + old_pdag = pdag; + % rule 1 + [A,B] = find(pdag==-1); % a -> b + for i=1:length(A) + a = A(i); b = B(i); + C = find(pdag(b,:)==1 & G(a,:)==0); % all nodes adj to b but not a + if ~isempty(C) + pdag(b,C) = -1; pdag(C,b) = 0; + %fprintf('rule 1: a=%d->b=%d and b=%d-c=%d implies %d->%d\n', a, b, b, C, b, C); + end + end + % rule 2 + [A,B] = find(pdag==1); % unoriented a-b edge + for i=1:length(A) + a = A(i); b = B(i); + if any( (pdag(a,:)==-1) & (pdag(:,b)==-1)' ); + pdag(a,b) = -1; pdag(b,a) = 0; + %fprintf('rule 2: %d -> %d\n', a, b); + end + end + % rule 3 + [A,B] = find(pdag==1); % a-b + for i=1:length(A) + a = A(i); b = B(i); + % Bug fix by Imme Ebert-Uphoff (ebert@tree.com), Jan 2007 + % C = find( (G(a,:)==1) & (pdag(:,b)==-1)' ); + C = find( (pdag(a,:)==1) & (pdag(:,b)==-1)' ); + % C contains nodes c s.t. a-c->ba + G2 = setdiag(G(C, C), 1); + if any(G2(:)==0) % there are 2 different non adjacent elements of C + pdag(a,b) = -1; pdag(b,a) = 0; + %fprintf('rule 3: %d -> %d\n', a, b); + end + end +end + + +% % Test Rule 3 of PC algorithm +% +% % Define PDAG +% pdag = zeros(4); +% pdag(2,1)=-1; +% pdag(3,1)=-1; +% pdag(2,4)=-1; +% pdag(3,4)=-1; +% pdag(1,4)=1; +% pdag(4,1)=1; +% +% fprintf('\nSample input PDAG:\n'); +% pdag +% +% fprintf('Sample DAG generated from PDAG:\n'); +% dag = abs(pdag_to_dag(pdag)) +% +% fprintf('Output from current PC algorithm:\n'); +% pdag_PC = learn_struct_pdag_pc('dsep', 4, 3, dag) +% +% % Problem can be fixed by changing Line 120 of learn_struct_pdag_pc.m +% % C = find( (G(a,:)==1) & (pdag(:,b)==-1)' ); +% % to +% % C = find( (pdag(a,:)==1) & (pdag(:,b)==-1)' ); +% +% fprintf('Correct version:\n'); +% pdag_PC_mod = learn_struct_pdag_pc_mod('dsep', 4, 3, dag) + diff --git a/sourcecodes/bnt-master/SLP/learning/learn_struct_tan.m b/sourcecodes/bnt-master/SLP/learning/learn_struct_tan.m new file mode 100644 index 00000000..df122b43 --- /dev/null +++ b/sourcecodes/bnt-master/SLP/learning/learn_struct_tan.m @@ -0,0 +1,104 @@ +function dag = learn_struct_tan(data, class_node, root, node_sizes, scoring_fn) +% LEARN_STRUCT_TAN Learn the structure of the tree augmented naive bayesian network +% (with discrete nodes) +% dag = learn_struct_tan(app, class, root, node_sizes) +% +% Input : +% data(i,m) is the value of node i in case m +% class_node is the class node +% root is the root node of the tree part of the dag (must be different from the class node) +% node_sizes = 1 if gaussian node, +% scoring_fn = 'bic' (default value) or 'mutual_info' +% +% Output : +% dag = adjacency matrix of the dag +% +% V1.1 : 21 may 2003, (O. Francois - francois.olivier.c.h@gmail.com, Ph. Leray - philippe.leray@univ-nantes.fr) +% V1.2 : may 2005 bug correction about node types (Navid Serrano <Navid.Serrano@jpl.nasa.gov>) + + +if nargin <4 + error('Requires at least 4 arguments.') +end + +if nargin == 4 + scoring_fn='bic'; +end; + +if class_node==root + error(' The root node can''t be the class node.'); +end + +% if root>class_node +% root=root-1; +% end + +N=size(data,1); +node_types=cell(N-1,1); +notclass=setdiff(1:N,class_node); +for i=1:N + if node_sizes(i)==1 + node_types{i}='gaussian'; + else + node_types{i}='tabular'; + end +end + +dag=zeros(N); +T = learn_struct_mwst4tan(data, ones(1,N), node_sizes, node_types, scoring_fn, root, class_node); +dag=T; +dag(class_node,notclass)=1; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [T, score_mat] = learn_struct_mwst4tan(data, discrete, node_sizes, node_type, scoring_fn, root, class) + +if nargin <4 + error('Requires at least 4 arguments.') +end + +if nargin == 4 + scoring_fn='bic'; root=1; +end; + +if nargin == 5 + root=1; +end; + +N=size(data,1); +score_mat=zeros(N,N); +score_mat(class,:)=Inf; +score_mat(:,class)=Inf; + +switch scoring_fn +case 'bic', + for i=mysetdiff(1:(N-1), class) + score2 = score_family(i, [class], node_type{i}, scoring_fn, node_sizes, discrete, data,[]); + for j=mysetdiff((i+1):N, class) + score1 = score_family(i, [j,class], node_type{i}, scoring_fn, node_sizes, discrete, data,[]); + score = score2-score1; + score_mat(i,j)=score; + score_mat(j,i)=score; + end + end +case 'mutual_info', % tabular nodes only + for i=mysetdiff(1:(N-1), class) + for j=mysetdiff((i+1):N, class) + score_mat(i,j)= -cond_mutual_info_score(i,node_sizes(i),j,node_sizes(j),class,node_sizes(class),data); + score_mat(j,i)=score_mat(i,j); + end + end +otherwise, + error(['unrecognized scoring fn ' scoring_fn]); +end + +variab = mysetdiff(1:N,class); +%score_mat +G = minimum_spanning_tree(score_mat(variab,variab)); +if root>class, root=root-1;end +T = mk_rooted_tree(G, root); +T1 = full(T); +T=zeros(N); +T(variab,variab)=T1; + + + diff --git a/sourcecodes/bnt-master/SLP/learning/learn_struct_tan_EM.m b/sourcecodes/bnt-master/SLP/learning/learn_struct_tan_EM.m new file mode 100644 index 00000000..2fa81ac6 --- /dev/null +++ b/sourcecodes/bnt-master/SLP/learning/learn_struct_tan_EM.m @@ -0,0 +1,172 @@ +function dag = learn_struct_tan_EM(data, class, node_sizes, root, prior, nbloopmax, thresh) +% LEARN_STRUCT_TAN_EM +% dag = learn_struct_tan_EM(data, class, node_sizes, root, prior, nbloopmax, thresh) +% +% Learn TAN classifier for discrete variables from incomplete dataset +% +% Input : +% data{i,m} a cell where the node i in the case m, +% class is the number of the class node, +% node_sizes = 1 if gaussian node, (max on complete samples) +% root is the futur root-node of the tree T. (random) +% prior = 1 to use uniform Dirichlet prior (0) +% nbloopmax = max loop number (ceil(log(N*log(N)))) +% thresh = the convergence test's threshold (1e-3) +% +% Output : +% bnet = the output bayesian network +% Ebic = the espected BIC score of bnet given the data +% +% francois.olivier.c.h@gmail.com + +[N, m]=size(data); +log_m = log(m); +if nargin<7, thresh = 1e-4; end +max_iter = 10; % for learn_struct_params +if nargin<6, nbloopmax = 15, end +if nargin<5, prior = 0; end +if nargin<4, root = ceil(N*rand(1)), end +if nargin<3, + misv = -9999; + data_mat = bnt_to_mat(data,misv); + node_sizes = max(data_mat'), + clear data_mat +end + +discrete = 1:N; +nbloop = 0; +variab = mysetdiff(1:N, class); + +[Bbest1, Sbest, Obest] = learn_struct_mwst_EM4tan(data(variab, :), 1:N-1, node_sizes(variab), prior, nbloopmax, thresh); +dag = zeros(N); +dag(variab, variab) = Bbest1.dag; +dag(class, variab) = 1; + +%Bbest = mk_bnet(dag, discrete); +%for i=discrete, Bbest.CPD{i} = tabular_CPD(Bbest, i, 'prior_type', 'dirichlet', 'dirichlet_type', 'unif'); end +%engine = jtree_inf_engine(Bbest); +%Bbest = learn_params_em(engine, data, max_iter, thresh); + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [Bbest, Sbest, Obest] = learn_struct_mwst_EM4tan(data, discrete, node_sizes, prior, nbloopmax, thresh) + +%fprintf('-- INITIALIZATION\n'); +%%%%%%%%%%%%% +[N, m]=size(data); + rand('state',sum(100*clock)) + +if nargin<6, thresh = 1e-4; end +if nargin<5, nbloopmax = 15; end +if nargin<4, prior = 0; end +if nargin<3, + misv = -9999; + data_mat = bnt_to_mat(data,misv); + node_sizes = max(data_mat'), +end +if nargin<2, discrete = ones(1,N); end + +nbloop = 1; last=0; +max_iter = 15; % for learn_struct_params + +%%%%%%%%%%%%% +% Random Chain like DAG +T = diag(ones(N-1,1),1); T=T+T'; +order=randperm(N); root=randperm(N); root=root(1); +[tmp order2]=sort(order); order2; +T2 = full(mk_rooted_tree(T,order2(root))); +torder=topological_sort(T2(order2,order2)); +[tmp torder2]=sort(torder); torder2; + +bnet0 = mk_bnet(T2(order2(torder),order2(torder)), node_sizes(torder)); +for i=1:N, bnet0.CPD{i} = tabular_CPD(bnet0, i, 'prior_type', 'dirichlet', 'dirichlet_type', 'unif'); end % a priori -> change les espected counts + +%%%%%%%%%%%%% +Sbest = -Inf; Bbest = bnet0; fini=0; + +while not(fini) + %fprintf('Loop %d : learning parameters...\n',nbloop); + engine0=jtree_inf_engine(bnet0); + [bnet1, LL1, engine1] = learn_params_em(engine0, data(torder,:), max_iter, thresh); + + BIC0=0; + for i=1:N, + xxx=struct(bnet1.CPD{i}); + BIC0=BIC0+bic_score_family(xxx.counts, xxx.CPT, xxx.nsamples); + end + + if BIC0 < Sbest+ thresh*abs(Sbest), + fini=1; + else + Sbest = BIC0; + Bbest = bnet1; + Obest = torder2 ; + %%%%%%%%%%%%% + + theta_Xi=cell(N,1); + evidence = cell(1,N); + [engine2, loglik] = enter_evidence(engine1, evidence); + for j=1:N, + SS= marginal_nodes (engine2,torder2(j)); + theta_Xi{j} = SS.T; + end + + theta_Xj_given_Xi = cell(N,N); + for i=1:N + for vali = 1:node_sizes(i) + evidence = cell(1,N); evidence{torder2(i)} = vali; + [engine2, loglik] = enter_evidence(engine1, evidence); + for j=mysetdiff(1:N,i) + SS= marginal_nodes (engine2,torder2(j)); + theta_Xj_given_Xi{j,i} = [theta_Xj_given_Xi{j,i}, SS.T]; + end + end + end + + BIC_mat=zeros(N,N); + for i=1:N, + BIC_mat(i,i)=bic_score_family(theta_Xi{i}*m,theta_Xi{i},m); + for j=mysetdiff(1:N,i) + theta_XjXi=(ones(node_sizes(i),1)*theta_Xi{j}').*theta_Xj_given_Xi{i,j}; + BIC_mat(i,j)= bic_score_family(m*theta_XjXi,theta_Xj_given_Xi{i,j},m); + end + end + BIC_delta = BIC_mat-diag(BIC_mat)*ones(1,N); + + BIC1=0; + for i = 1:N + j = find(bnet0.dag(:,i)==1); + if isempty(j) + BIC1 = BIC1 + BIC_mat(torder2(i),torder2(i)); + else + BIC1 = BIC1 + BIC_mat(torder2(i),torder2(j)); + end + end + + %fprintf(' Creation of the new bnet ');tic + %%%%%%%%%%%%% + T2 = minimum_spanning_tree(-BIC_delta); + root=randperm(N); root=root(1); + T3 = full(mk_rooted_tree(T2, root)); + torder=topological_sort(T3); + [tmp torder2]=sort(torder); torder2; + + bnet0 = mk_bnet(T3(torder,torder), node_sizes(torder)); + %BIC=0; + for i = 1:N + j = find(T3(:,i)==1); + if isempty(j) + bnet0.CPD{torder2(i)} = tabular_CPD(bnet0, torder2(i), 'CPT', theta_Xi{i}, 'prior_type', 'dirichlet', 'dirichlet_type', 'unif'); + %BIC = BIC + BIC_mat(i,i); + else + bnet0.CPD{torder2(i)} = tabular_CPD(bnet0, torder2(i), 'CPT', theta_Xj_given_Xi{i,j}, 'prior_type', 'dirichlet', 'dirichlet_type', 'unif'); + %BIC=BIC + BIC_mat(i,j); + end + end + nbloop=nbloop+1; + fprintf('================================================================================\n'); + if nbloop>nbloopmax + fini=1; + end + end +end diff --git a/sourcecodes/bnt-master/SLP/learning/mk_naive_struct.m b/sourcecodes/bnt-master/SLP/learning/mk_naive_struct.m new file mode 100644 index 00000000..33e6b7d7 --- /dev/null +++ b/sourcecodes/bnt-master/SLP/learning/mk_naive_struct.m @@ -0,0 +1,6 @@ +function S = mk_naive_struct(n,C) +% +% S = mk_naive_struct(Number_of_nodes, Class_node) +% +S = zeros(n); +S(C,setdiff(1:n,C)) = 1; \ No newline at end of file |
