def emissionProb(self, node, is_partial_graph_index=False): # Access the emission matrix with the full graph indices node_full = self.partialGraphIndexToFullGraphIndex( node) if is_partial_graph_index == True else node prob = self.L[node_full].reshape((-1, )) if (self.inFeedbackSet(node_full, is_partial_graph_index=False)): return fbsData(prob, 0) return fbsData(prob, -1)
def emissionProb(self, node, is_partial_graph_index=False): # Access the emission matrix with the full graph indices node_full = self.partialGraphIndexToFullGraphIndex( node) if is_partial_graph_index == True else node y = self.ys[int(node_full)] if (not np.any(np.isnan(y))): prob = self.recognizerFunc(y).reshape((-1, )) else: prob = np.zeros_like(self.pi0).reshape((-1, )) if (self.inFeedbackSet(node_full, is_partial_graph_index=False)): return fbsData(prob, 0) return fbsData(prob, -1)
def vData( self, U, V, node, edges=None ): # THESE MUST NOT MODIFY U OR V!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # VERY IMPORTANT!!!!!! PROBABLY ENFORCE THIS SOMEHOW IN THE FURURE V_row, V_col, _ = V if( self.inFeedbackSet( node, is_partial_graph_index=True ) ): # This is going to be empty because by construction, # if a node is in the fbs, all the information from # going down the down edges can be gained by going # up an up edge. if( edges is None ): return [] elif( isinstance( edges, Iterable ) ): return [] else: return fbsData( np.array( [] ), -1 ) if( ~np.any( np.in1d( V_row, node ) ) ): # If the node isn't in the V object (node is a leaf) ans = [ fbsData( np.array( [] ), -1 ) ] elif( edges is None ): # Return the data over all down edges ans = self.vDataFromMask( V, np.in1d( V_row, node ) ) elif( isinstance( edges, Iterable ) and len( edges ) > 0 ): # Return over only certain edges mask = np.in1d( V_row, node ) for e in edges: mask &= np.in1d( V_col, e ) ans = self.vDataFromMask( V, mask ) elif( isinstance( edges, Iterable ) and len( edges ) == 0 ): # This happens when we're not passed a down edge. In this # case, we should return an empty v value, not a leaf v value return [ fbsData( np.array( [] ), -1 ) ] else: # Only looking at one edge ans = self.vDataFromMask( V, np.in1d( V_col, edges ) ) if( len( ans ) == 0 ): # If we're looking at a leaf, return all 0s nVals = 1 if edges is None else len( edges ) ans = [] for _ in range( nVals ): ans.append( fbsData( np.array( [] ), -1 ) ) for v in ans: data = v.data assert np.any( np.isnan( data ) ) == False, data return ans
def integrate(cls, integrand, axes): # Check if we need to use the regular integrate if (not isinstance(integrand, fbsData)): return GraphHMM.integrate(integrand, axes) # Need adjusted axes because the relative axes in integrand change as we reduce # over each axis assert isinstance(axes, Iterable) if (len(axes) == 0): return integrand integrand, fbs_axis = (integrand.data, integrand.fbs_axis) assert max(axes) < integrand.ndim axes = np.array(axes) axes[axes < 0] = integrand.ndim + axes[axes < 0] adjusted_axes = np.array(sorted(axes)) - np.arange(len(axes)) for ax in adjusted_axes: integrand = logsumexp(integrand, axis=ax) if (fbs_axis > -1): fbs_axis -= len(adjusted_axes) # assert fbs_axis > -1, adjusted_axes return fbsData(integrand, fbs_axis)
def genFilterProbs(self): # Initialize U and V U = [] for node in self.partial_graph.nodes: U.append(fbsData(np.zeros(self.K), -1)) V_row = self.partial_graph.pmask.row V_col = self.partial_graph.pmask.col V_data = [] for node in self.partial_graph.pmask.row: V_data.append(fbsData(np.zeros(self.K), -1)) # Invalidate all data elements for node in self.partial_graph.nodes: U[node].data[:] = np.nan self.assignV((V_row, V_col, V_data), node, np.nan, keep_shape=True) return U, (V_row, V_col, V_data)
def emissionProb(self, node, is_partial_graph_index=False): # Access the emission matrix with the full graph indices node_full = self.partialGraphIndexToFullGraphIndex( node) if is_partial_graph_index == True else node group = self.node_groups[int(node_full)] y = self.ys[int(node_full)] if (not np.any(np.isnan(y))): prob = self.emission_dists[group][:, y].sum(axis=-1) else: prob = np.zeros_like(self.emission_dists[group][:, 0]) if (self.inFeedbackSet(node_full, is_partial_graph_index=False)): fbs_index = self.fbsIndex(node_full, is_partial_graph_index=False, within_graph=True) for _ in range(fbs_index): prob = prob[None] return fbsData(prob, 0) return fbsData(prob, -1)
def initialProb(self, node, is_partial_graph_index=False): pi = np.copy(self.pi0) node_full = self.partialGraphIndexToFullGraphIndex( node) if is_partial_graph_index == True else node if (int(node_full) in self.possible_latent_states): states = self.possible_latent_states[int(node_full)] impossible_states = np.setdiff1d(np.arange(pi.shape[-1]), states) for state in impossible_states: pi[impossible_states] = np.NINF pi[states] -= logsumexp(pi) return fbsData(pi, -1)
def emissionProb(self, node, is_partial_graph_index=False): # Access the emission matrix with the full graph indices node_full = self.partialGraphIndexToFullGraphIndex( node) if is_partial_graph_index == True else node group = self.node_groups[int(node_full)] y = self.ys[int(node_full)] cond = self.conds[int(node_full)] if (not np.any(np.isnan(y))): prob = self.recognizerFuncs[group](y, cond).reshape((-1, )) else: prob = np.zeros_like(self.pi0s[group][:, 0]).reshape((-1, )) if (self.inFeedbackSet(node_full, is_partial_graph_index=False)): fbs_index = self.fbsIndex(node_full, is_partial_graph_index=False, within_graph=True) for _ in range(fbs_index): prob = prob[None] return fbsData(prob, 0) return fbsData(prob, -1)
def extendAxes( self, node, term, target_axis, max_dim ): # Push the first axis out to target_axis, but don't change # the axes past max_dim term, fbs_axis = ( term.data, term.fbs_axis ) # Add axes before the fbsAxes for _ in range( max_dim - target_axis - 1 ): term = np.expand_dims( term, 1 ) if( fbs_axis != -1 ): fbs_axis += 1 # Prepend axes for _ in range( target_axis ): term = np.expand_dims( term, 0 ) if( fbs_axis != -1 ): fbs_axis += 1 return fbsData( term, fbs_axis )
def transitionProb(self, child, is_partial_graph_index=False): parents, parent_order = self.getFullParents( child, get_order=True, is_partial_graph_index=is_partial_graph_index, return_partial_graph_index=True) child_full = self.partialGraphIndexToFullGraphIndex( child) if is_partial_graph_index == True else child ndim = len(parents) + 1 group = self.node_groups[int(child_full)] shape = [] for p, _ in sorted(zip(parents, parent_order), key=lambda po: po[1]): full_p = int(self.partialGraphIndexToFullGraphIndex(p)) shape.append(self.getNodeDim(full_p)) shape.append(self.getNodeDim(int(child_full))) shape = tuple(shape) pi = np.copy(self.pis[group][shape]) # Reshape pi's axes to match parent order assert len(parents) + 1 == pi.ndim assert parent_order.shape[0] == parents.shape[0] # Sort the parent dimensions by parent order pi = np.moveaxis(pi, np.arange(ndim), np.hstack((parent_order, ndim - 1))) # If we know the latent state for child, then ensure that we # transition there. This is intervention! modified = False for parent, order in zip(parents, parent_order): parent_full = self.partialGraphIndexToFullGraphIndex(parent) if (int(parent_full) in self.possible_latent_states): parent_states = self.possible_latent_states[int(parent_full)] impossible_parent_axes = np.setdiff1d( np.arange(pi.shape[order]), parent_states) index = [slice(0, s) for s in pi.shape] index[order] = impossible_parent_axes pi[tuple(index)] = np.NINF modified = True if (int(child_full) in self.possible_latent_states): states = self.possible_latent_states[int(child_full)] impossible_axes = np.setdiff1d(np.arange(pi.shape[-1]), states) pi[..., impossible_axes] = np.NINF modified = True # In case entire rows summed to -inf pi[np.isnan(pi)] = np.NINF # Check if there are nodes in [ child, *parents ] that are in the fbs. # If there are, then move their axes fbsOffset = lambda x: self.fbsIndex( x, is_partial_graph_index=True, within_graph=True) + 1 fbs_indices = [ fbsOffset(parent) for parent in parents if self.inFeedbackSet(parent, is_partial_graph_index=True) ] if (self.inFeedbackSet(child, is_partial_graph_index=is_partial_graph_index)): fbs_indices.append( self.fbsIndex(child, is_partial_graph_index=is_partial_graph_index, within_graph=True) + 1) if (len(fbs_indices) > 0): expand_by = max(fbs_indices) for _ in range(expand_by): pi = pi[..., None] # If there are parents in the fbs, move them to the appropriate axes for i, parent in enumerate(parents): if (self.inFeedbackSet(parent, is_partial_graph_index=True)): pi = np.swapaxes(pi, i, fbsOffset(parent) + ndim - 1) if (self.inFeedbackSet( child, is_partial_graph_index=is_partial_graph_index)): # If the child is in the fbs, then move it to the appropriate axis pi = np.swapaxes(pi, ndim - 1, fbsOffset(child) + ndim - 1) return fbsData(pi, ndim) return fbsData(pi, -1)
def multiplyTerms(cls, terms): # Basically np.einsum but in log space assert isinstance(terms, Iterable) # Check if we should use the multiply for fbsData or for regular data fbs_data_count, non_fbs_data_count = (0, 0) for t in terms: if (isinstance(t, fbsData)): fbs_data_count += 1 else: non_fbs_data_count += 1 # Can't mix types if (not (fbs_data_count == 0 or non_fbs_data_count == 0)): print('fbs_data_count', fbs_data_count) print('non_fbs_data_count', non_fbs_data_count) print(terms) for t in terms: if (isinstance(t, fbsData)): print('this ones good', t, type(t)) else: print('this ones bad', t, type(t)) assert 0 # Use the regular multiply if we don't have fbs data if (fbs_data_count == 0): return GraphHMM.multiplyTerms(terms) # Remove the empty terms terms = [t for t in terms if np.prod(t.shape) > 1] if (len(terms) == 0): return fbsData(np.array([]), 0) # Separate out where the feedback set axes start and get the largest fbs_axis. # Need to handle case where ndim of term > all fbs axes # terms, fbs_axes_start = list( zip( *terms ) ) fbs_axes_start = [term.fbs_axis for term in terms] terms = [term.data for term in terms] if (max(fbs_axes_start) != -1): max_fbs_axis = max([ ax if ax != -1 else term.ndim for ax, term in zip(fbs_axes_start, terms) ]) if (max_fbs_axis > 0): # Pad extra dims at each term so that the fbs axes start the same way for every term for i, ax in enumerate(fbs_axes_start): if (ax == -1): for _ in range(max_fbs_axis - terms[i].ndim + 1): terms[i] = terms[i][..., None] else: for _ in range(max_fbs_axis - ax): terms[i] = np.expand_dims(terms[i], axis=ax) else: max_fbs_axis = -1 ndim = max([len(term.shape) for term in terms]) axes = [[i for i, s in enumerate(t.shape) if s != 1] for t in terms] # Get the shape of the output shape = np.ones(ndim, dtype=int) for ax, term in zip(axes, terms): shape[np.array(ax)] = term.squeeze().shape total_elts = shape.prod() if (total_elts > 1e8): assert 0, 'Don\'t do this on a cpu! Too many terms: %d' % ( int(total_elts)) # Build a meshgrid out of each of the terms over the right axes # and sum. Doing it this way because np.einsum doesn't work # for matrix multiplication in log space - we can't do np.einsum # but add instead of multiply over indices ans = np.zeros(shape) for ax, term in zip(axes, terms): for _ in range(ndim - term.ndim): term = term[..., None] ans += np.broadcast_to(term, ans.shape) return fbsData(ans, max_fbs_axis)
def vBaseCase( self, node ): return fbsData( np.array( [] ), -1 )