def gammaPosteriorPrecIsoSd(data, means, labelList, gammaShape, gammaRate, scale): """ Inputs: data: (N,D) array means: (K,) list of (D,) arrays labelList: (N,) list gammaShape, gammaRate: scale: scalar Outputs: precShape: scalar precRate: scalar """ z, _ = partitions.labelHelpersToLabelsAndClusts(labelList) N, D = data.shape nClust = len(means) squaredNorm = np.square(means) # (K,D) squaredNormTotal = np.sum(squaredNorm) precShape = gammaShape + (nClust*D)/2 precRate = gammaRate + squaredNormTotal/2 squaredErr = np.square(data - np.array(means)[z]) # (N,D) squaredErrTotal = np.sum(squaredErr) precShape += (N*D)/2 precRate += scale*squaredErrTotal/2 return precShape, precRate
def gammaPosteriorPrec(data, means, labelList, gammaShape, gammaRate, scale): """ Inputs: data: (N,D) array means: (K,) list of (D,) arrays labelList: (N,) list gammaShape, gammaRate: scale: scalar Outputs: precShape: scalar precRate: (D,) array """ z, _ = partitions.labelHelpersToLabelsAndClusts(labelList) N = data.shape[0] nClust = len(means) squaredNorm = np.square(means) squaredNormPerDim = np.sum(squaredNorm, axis=0) # (D,) precShape = gammaShape + nClust/2 precRate = gammaRate + squaredNormPerDim/2 squaredErr = np.square(data - np.array(means)[z]) squaredErrPerDim = np.sum(squaredErr, axis=0) # (D,) precShape += N/2 precRate += scale*squaredErrPerDim/2 return precShape, precRate
def sample(self, i, j): """ Inputs: i, j: scalars (or None), indices of data to localize clusters for split/merge if None, decide i, j in this call Output: Propose either a split or a merge and decide if we switch to that state. """ z, clusts = partitions.labelHelpersToLabelsAndClusts(self.labelList) # make launch state if (i is None) or (j is None): i, j, S = self.makeIJS(z, clusts) # print("clusts ", clusts) # print("i ", i, "j ", j) # print("S ", S) else: S_as_set = (clusts[z[i]] | clusts[z[j]]) - set([i,j]) # convert S to list to ensure iteration order is consistent S = list(S_as_set) launchLabelList = self.makeLaunchState(z, i, j, S, self.splitMergeDict["numRes"]) # print("z_launch ", z_launch) # make split proposal if (z[i] == z[j]): # print("Proposing split") nextLabelList, nextClusts = self.proposeRestrictedGibbs(launchLabelList, i, j, S, 1) # make merge proposal else: # print("Proposing merge") # probably don't actually need to get the merged state to compute the # acceptance probability nextLabelList, _, nextClusts = partitions.reassignClust(z, clusts, z[i], z[j]) # print("Proposed state is z_next ", z_next) logAcc = self.acceptanceProbability(self.labelList, self.clusts, launchLabelList, nextLabelList, nextClusts, i, j, S) # print("log acc is ", logAcc) # accept or reject if (np.log(self.rng.uniform()) < logAcc): # print("Accept proposal") accept = 1 labelList = nextLabelList clusts = nextClusts else: # print("Reject proposal") accept = 0 labelList = self.labelList clusts = self.clusts return accept, labelList, clusts
def proposeRestrictedGibbs(self, labelList, i, j, S, numRes): """ Args: labelList: (N,) list, each is a LabelHelper object i, j: scalars, indices selected for split-merge S: list, numRes: scalar, how many restricted sweeps to do Output: nextLabelList: (N,) list nextClusts: (K,) list of sets, each set is the indices of data in one cluster Remark: this function needs to coordinate with proposal_prob in terms of the ordering of the indices S, since the random MH proposal is determined by both the launch state but also the ordering of S which governs the restricted Gibbs. """ z, _ = partitions.labelHelpersToLabelsAndClusts(labelList) assert z[i] != z[j] # make copy of start state nextLabelList = partitions.zToLabelHelpers(z) nextClusts = partitions.zToClusts(z) for it in range(numRes): for idx in S: # remove idx from clusts. Will not change the labels of any clusters # (which only happens if the popped idx were the only member of its cluster) # since we know that the cluster of i and j will always have at least # i or j removedClust = partitions.pop_idx(nextLabelList, nextClusts, None, None, idx, None) assert removedClust is None # z, clusts, _ = partitions.pop_idx(z, clusts, idx) probs, _ = self.restrictedClusterWeights(nextLabelList, nextClusts, idx, i, j) # print("it = %d" %it, "probs: ", probs) clust_idx = self.rng.choice(len(probs), p=probs) # update clusts and assignments if (clust_idx == 0): newC = nextLabelList[i].getValue() else: assert (clust_idx == 1) newC = nextLabelList[j].getValue() labelObj = partitions.labelOfCluster(nextLabelList, nextClusts, newC) nextLabelList[idx] = labelObj nextClusts[newC].add(idx) return nextLabelList, nextClusts
def CC(labelList, indices): """ Input: z: (N,) vector of labels indices: (M,) indices of data to check co-cluster. Output: mat: (M^2,) flattened matrix whether two observations are in the same cluster Remark: we avoid reporting and saving the whole (N,N) co-clustering matrix for memory reasons """ z, clusts = partitions.labelHelpersToLabelsAndClusts(labelList) mat = partitions.adj_matrix(z, clusts) sub_mat = mat[np.ix_(indices, indices)] # (M,M) res = sub_mat.flatten() # (M^2,) return res
def greedyColoring(g, nColors): """ Greedy coloring of g using nColors using the 0,1,2,... ordering of vertices. If coloring is possible, return coloring. Return possible: boolean labelList: list of LabelHelper objects clusts: (nColors,) list of sets """ nvertices = g.vcount() vertexColors = -np.ones([nvertices], dtype=int) color_ids = set() for ivertex in range(nvertices): n_i = igraph.Graph.neighbors(g, ivertex) legal_colors = color_ids.difference(vertexColors[n_i]) if len(legal_colors) == 0: new_color_id = len(color_ids) color_ids.add(new_color_id) legal_colors.add(new_color_id) vertexColors[ivertex] = min(legal_colors) nColorsUsed = len(set(vertexColors)) if (nColorsUsed <= nColors): possible = True labelList = [0 for _ in range(nvertices)] for label in range(nColorsUsed): labelObj = partitions.LabelHelper(label) equalIndices = np.argwhere(vertexColors == label)[:,0] for idx in equalIndices: labelList[idx] = labelObj _ , clusts = partitions.labelHelpersToLabelsAndClusts(labelList) else: possible = False labelList, clusts = None, None return possible, labelList, clusts
def proposalProbability(self, startLabelList, endLabelList, i, j, S): """ Compute the probability of transitioning from z_start to z_end using restricted_Gibbs. There is only one way of taking restricted_Gibbs move to end at z_split when beginning from z_launch, if the list S has been fixed. Inputs: startLabelList: endLabelList: i, j, S: Outputs: cumu_log_prob: scalar Remark: Jain and Neal 2004 Eq 3.14 doesn't explicitly say what the ordering of the indices in S is. Wallach's code seemingly uses a for comprehension for S (line 59 in conjugate_split_merge.py), but it looks like they compute the z_split and the proposal in the same loop, so that ensures the same ordering of the indices in S. """ startZ, _ = partitions.labelHelpersToLabelsAndClusts(startLabelList) assert startZ[i] != startZ[j] # print("startZ", startZ, "z_end", z_end) # make copy of start state startLabelListCopy = partitions.zToLabelHelpers(startZ) startClustsCopy = partitions.zToClusts(startZ) cumu_log_prob = 0.0 for idx in S: # print("z, clusts before pop", z, clusts) # z, clusts, _ = partitions.pop_idx(z, clusts, idx) removedClust = partitions.pop_idx(startLabelListCopy, startClustsCopy, None, None, idx, None) assert removedClust is None # print("clusts after pop", clusts) _, log_probs = self.restrictedClusterWeights(startLabelListCopy, startClustsCopy, idx, i, j) final_val = endLabelList[idx].getValue() # print("final_val ",final_val) if (final_val == endLabelList[i].getValue()): cumu_log_prob += log_probs[0] else: assert final_val == endLabelList[j].getValue() cumu_log_prob += log_probs[1] # if (final_val == len(startClustsCopy)): # startClustsCopy.append(set()) # labelObj = partitions.LabelHelper(final_val) # else: # labelObj = partitions.labelOfCluster(startLabelListCopy, startClustsCopy, final_val) labelObj = partitions.labelOfCluster(startLabelListCopy, startClustsCopy, final_val) startLabelListCopy[idx] = labelObj startClustsCopy[final_val].add(idx) # print("idx ", idx, "cumu_log_prob after idx", cumu_log_prob) return cumu_log_prob
def initialPartitionAndMeans(data, initZ, alpha, initType, rng): """initialPartitionAndMeans() initializes the Markov chain over DPMM clustering models. Args: data: (N,D) array, observations initZ: (N,) array if None, use initType to set initial state alpha: DPMM concentration initType: str, how to generate first sample rng: bitGenerator Output: labelList: (N,) list, each is a labelHelper object clusts: (K,) list of sets clustSums: (K,) list of (D,) array means: (K,) list of (D,) array """ Ndata = data.shape[0] if initZ is None: if initType == "allSame": z0 = np.zeros(Ndata, dtype=np.int) elif initType == "crpPrior": z0 = crp_prior(Ndata, alpha, rng) else: typ, k_size = initType.split("=") assert typ == "kmeans" z0 = kmeans_init(data, alpha, int(k_size)) else: z0 = initZ.copy() nLabels = len(np.unique(z0)) labelList = [0 for _ in range(Ndata)] clustSums, means = [0 for _ in range(nLabels)], [0 for _ in range(nLabels)] for label in range(nLabels): labelObj = partitions.LabelHelper(label) equalIndices = np.argwhere(z0 == label)[:,0] for idx in equalIndices: labelList[idx] = labelObj clustSums[label] = np.sum(data[equalIndices,:], axis=0) means[label] = np.mean(data[equalIndices,:], axis=0) _, clusts = partitions.labelHelpersToLabelsAndClusts(labelList) return labelList, clusts, clustSums, means # def init_swap(single_transition, pi0): # """ # Jointly initialize the X and Y chains to induce anti-correlated correction terms. # Args: # single_transition: lambda function, marginal Gibbs sweep, typically gibbs_sweep_single() # from sampler_single.py # pi0: lambda function, initial distribution --- each call generates a sample # """ # a0, b0 = pi0(), pi0() # a1 = single_transition(a0) # b1 = single_transition(b0) # b2 = single_transition(b1) # X1,X2 = [a0,a1], [b0] # Y1,Y2 = [b1,b2], [a1] # return X1,X2,Y1,Y2 ## ---------------------------------------------------------------------- ## track means # def init_centers(z, D, sd0, rng): # """ # Draw cluster means from the prior distribution. # """ # nClusts = max(z)+1 # mu0 = np.zeros(D) # centers = [rng.multivariate_normal(mu0,cov=sd0**2*np.eye(D)) for i in range(nClusts)] # assert len(centers) == nClusts # assert centers[0].shape[0] == D # return centers # def pi0_withMeans(data, sd, sd0, alpha, pi0_its, initType, rng): # """ # Draw the partitions and the cluster means separately. # """ # z0 = pi0(data, sd, sd0, alpha, pi0_its, initType, rng) # centers = init_centers(z0, data.shape[1], sd0, rng) # return z0, centers