about summary refs log tree commit diff
path: root/sourcecodes/bnt-master/SLP/learning/learn_struct_EM.m
diff options
context:
space:
mode:
Diffstat (limited to 'sourcecodes/bnt-master/SLP/learning/learn_struct_EM.m')
-rw-r--r--sourcecodes/bnt-master/SLP/learning/learn_struct_EM.m383
1 files changed, 383 insertions, 0 deletions
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
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
+
+
+