Daniel@0: function [pdag, G] = dn_learn_struct_pdag_pc_constrain(adj, cond_indep, n, k, varargin) Daniel@0: % LEARN_STRUCT_PDAG_PC Learn a partially oriented DAG (pattern) using the PC algorithm Daniel@0: % Pdag = learn_struct_pdag_pc_constrain(adj, cond_indep, n, k, ...) Daniel@0: % Daniel@0: % adj = adjacency matrix learned from dependency network P(i,j) = 1 => i--j; 0 => i j Daniel@0: % n is the number of nodes. Daniel@0: % k is an optional upper bound on the fan-in (default: n) Daniel@0: % cond_indep is a boolean function that will be called as follows: Daniel@0: % feval(cond_indep, x, y, S, ...) Daniel@0: % where x and y are nodes, and S is a set of nodes (positive integers), Daniel@0: % and ... are any optional parameters passed to this function. Daniel@0: % Daniel@0: %Output Daniel@0: % pdag Partially directed graph Daniel@0: % G Resulting adjacency graph prior to setting direction arrows Daniel@0: % Daniel@0: % The output P is an adjacency matrix, in which Daniel@0: % P(i,j) = -1 if there is an i->j edge. Daniel@0: % P(i,j) = P(j,i) = 1 if there is an undirected edge i <-> j Daniel@0: % Daniel@0: % The PC algorithm does structure learning assuming all variables are observed. Daniel@0: % See Spirtes, Glymour and Scheines, "Causation, Prediction and Search", 1993, p117. Daniel@0: % This algorithm may take O(n^k) time if there are n variables and k is the max fan-in, Daniel@0: % but this is quicker than the Verma-Pearl IC algorithm, which is always O(n^n). Daniel@0: % Daniel@0: %% Example Daniel@0: %% Given data in a comma separated, filename starting with the variable labels, then the data in rows. Daniel@0: %% filename test.txt consists of: Daniel@0: %% Daniel@0: %% Earthquake,Burglar,Radio,Alarm,Call Daniel@0: %% 1,2,2,2,1 Daniel@0: %% 1,1,2,1,2 Daniel@0: %% . . . Daniel@0: %[CovMatrix, obs, varfields] = CovMat('test.txt',5); Daniel@0: % Daniel@0: %dn = zeros(5,5); Daniel@0: %dn(1,2) = 1; % This was the known Markov blanket of the system that generated test.txt Daniel@0: %dn(2,1) = 1; Daniel@0: %dn(2,4) = 1; Daniel@0: %dn(4,2) = 1; Daniel@0: %dn(1,3) = 1; Daniel@0: %dn(3,1) = 1; Daniel@0: %dn(1,4) = 1; Daniel@0: %dn(4,1) = 1; Daniel@0: %dn(4,5) = 1; Daniel@0: %dn(5,4) = 1; Daniel@0: %dn(3,5) = 1; %loop r->c Daniel@0: %dn(5,3) = 1; %loop c-r Daniel@0: %dn(3,4) = 1; Daniel@0: %dn(4,3) = 1; Daniel@0: % Daniel@0: %max_fan_in = 4; Daniel@0: %alpha = 0.05; Daniel@0: % Daniel@0: %[pdag G] = learn_struct_pdag_pc_constrain(dn,'cond_indep_fisher_z', 5, max_fan_in, CovMatrix, obs, alpha); Daniel@0: %% Daniel@0: %% Daniel@0: %% Gary Bradski, 7/2002 Modified this to take an adjacency matrix from a dependency network. Daniel@0: Daniel@0: Daniel@0: sep = cell(n,n); Daniel@0: ord = 0; Daniel@0: done = 0; Daniel@0: G = ones(n,n); Daniel@0: G=setdiag(G,0); Daniel@0: Daniel@0: while ~done Daniel@0: done = 1; Daniel@0: [X,Y] = find(G); Daniel@0: for i=1:length(X) Daniel@0: x = X(i); y = Y(i); Daniel@0: % nbrs = mysetdiff(myunion(neighbors(G, x), neighbors(G,y)), [x y]);%parents, children, but not self Daniel@0: nbrs = mysetdiff(myunion(neighbors(adj, x), neighbors(adj,y)), [x y]);%parents, children, but not self Daniel@0: Daniel@0: if length(nbrs) >= ord & G(x,y) ~= 0 Daniel@0: done = 0; Daniel@0: SS = subsets(nbrs, ord, ord); % all subsets of size ord Daniel@0: for si=1:length(SS) Daniel@0: S = SS{si}; Daniel@0: %if (feval(dsep,x,y,S,adj)) | (feval(cond_indep, x, y, S, varargin{:})) Daniel@0: if feval(cond_indep, x, y, S, varargin{:}) Daniel@0: %if isempty(S) Daniel@0: % fprintf('%d indep of %d ', x, y); Daniel@0: %else Daniel@0: % fprintf('%d indep of %d given ', x, y); fprintf('%d ', S); Daniel@0: %end Daniel@0: %fprintf('\n'); Daniel@0: Daniel@0: % diagnostic Daniel@0: %[CI, r] = cond_indep_fisher_z(x, y, S, varargin{:}); Daniel@0: %fprintf(': r = %6.4f\n', r); Daniel@0: Daniel@0: G(x,y) = 0; Daniel@0: G(y,x) = 0; Daniel@0: adj(x,y) = 0; %make sure found cond. independencies are marked out Daniel@0: adj(y,x) = 0; Daniel@0: sep{x,y} = myunion(sep{x,y}, S); Daniel@0: sep{y,x} = myunion(sep{y,x}, S); Daniel@0: break; % no need to check any more subsets Daniel@0: end Daniel@0: end Daniel@0: end Daniel@0: end Daniel@0: ord = ord + 1; Daniel@0: end Daniel@0: Daniel@0: Daniel@0: Daniel@0: Daniel@0: % Create the minimal pattern, Daniel@0: % i.e., the only directed edges are V structures. Daniel@0: Daniel@0: pdag = G; Daniel@0: [X, Y] = find(G); Daniel@0: % We want to generate all unique triples x,y,z Daniel@0: % This code generates x,y,z and z,y,x. Daniel@0: for i=1:length(X) Daniel@0: x = X(i); Daniel@0: y = Y(i); Daniel@0: Z = find(G(y,:)); Daniel@0: Z = mysetdiff(Z, x); Daniel@0: for z=Z(:)' Daniel@0: if G(x,z)==0 & ~ismember(y, sep{x,z}) & ~ismember(y, sep{z,x}) Daniel@0: %fprintf('%d -> %d <- %d\n', x, y, z); Daniel@0: pdag(x,y) = -1; pdag(y,x) = 0; Daniel@0: pdag(z,y) = -1; pdag(y,z) = 0; Daniel@0: end Daniel@0: end Daniel@0: end Daniel@0: Daniel@0: % Convert the minimal pattern to a complete one, Daniel@0: % i.e., every directed edge in P is compelled Daniel@0: % (must be directed in all Markov equivalent models), Daniel@0: % and every undirected edge in P is reversible. Daniel@0: % We use the rules of Pearl (2000) p51 (derived in Meek (1995)) Daniel@0: Daniel@0: old_pdag = zeros(n); Daniel@0: iter = 0; Daniel@0: while ~isequal(pdag, old_pdag) Daniel@0: iter = iter + 1; Daniel@0: old_pdag = pdag; Daniel@0: % rule 1 Daniel@0: [A,B] = find(pdag==-1); % a -> b Daniel@0: for i=1:length(A) Daniel@0: a = A(i); b = B(i); Daniel@0: C = find(pdag(b,:)==1 & G(a,:)==0); % all nodes adj to b but not a Daniel@0: if ~isempty(C) Daniel@0: pdag(b,C) = -1; pdag(C,b) = 0; Daniel@0: %fprintf('rule 1: a=%d->b=%d and b=%d-c=%d implies %d->%d\n', a, b, b, C, b, C); Daniel@0: end Daniel@0: end Daniel@0: % rule 2 Daniel@0: [A,B] = find(pdag==1); % unoriented a-b edge Daniel@0: for i=1:length(A) Daniel@0: a = A(i); b = B(i); Daniel@0: if any( (pdag(a,:)==-1) & (pdag(:,b)==-1)' ); Daniel@0: pdag(a,b) = -1; pdag(b,a) = 0; Daniel@0: %fprintf('rule 2: %d -> %d\n', a, b); Daniel@0: end Daniel@0: end Daniel@0: % rule 3 Daniel@0: [A,B] = find(pdag==1); % a-b Daniel@0: for i=1:length(A) Daniel@0: a = A(i); b = B(i); Daniel@0: C = find( (G(a,:)==1) & (pdag(:,b)==-1)' ); Daniel@0: % C contains nodes c s.t. a-c->ba Daniel@0: G2 = setdiag(G(C, C), 1); Daniel@0: if any(G2(:)==0) % there are 2 different non adjacent elements of C Daniel@0: pdag(a,b) = -1; pdag(b,a) = 0; Daniel@0: %fprintf('rule 3: %d -> %d\n', a, b); Daniel@0: end Daniel@0: end Daniel@0: end Daniel@0: Daniel@0: Daniel@0: Daniel@0: