def backward_log_probabilities(self, sequence, normalize=True): """We override this to provide a faster implementation. @see: forward_log_probability Returns a numpy 2d array instead of a list of lists. """ T = len(sequence) N = len(self.label_dom) beta = numpy.zeros((T, N), numpy.float64) # Initialize beta[T-1, :] = numpy.log2(1.0/N) # Iterate backwards over the other timesteps for t in range(T-2, -1, -1): for i,si in enumerate(self.label_dom): # Multiply each next state's prob by the transition prob # from this state to that and the emission prob in that next # state log_probs = [ beta[t+1, j] + self.transition_log_probability(sj, si) + \ self.emission_log_probability(sequence[t+1], sj) \ for j,sj in enumerate(self.label_dom)] beta[t, i] = sum_logs(log_probs) # Normalize by dividing all values by the total probability if normalize: total = sum_logs(beta[t,:]) for j in range(N): beta[t,j] -= total return beta
def backward_log_probabilities(self, sequence, normalize=True): """We override this to provide a faster implementation. @see: forward_log_probability Returns a numpy 2d array instead of a list of lists. """ T = len(sequence) N = len(self.label_dom) beta = numpy.zeros((T, N), numpy.float64) # Initialize beta[T - 1, :] = numpy.log2(1.0 / N) # Iterate backwards over the other timesteps for t in range(T - 2, -1, -1): for i, si in enumerate(self.label_dom): # Multiply each next state's prob by the transition prob # from this state to that and the emission prob in that next # state log_probs = [ beta[t+1, j] + self.transition_log_probability(sj, si) + \ self.emission_log_probability(sequence[t+1], sj) \ for j,sj in enumerate(self.label_dom)] beta[t, i] = sum_logs(log_probs) # Normalize by dividing all values by the total probability if normalize: total = sum_logs(beta[t, :]) for j in range(N): beta[t, j] -= total return beta
def forward_log_probabilities(self, sequence, normalize=True, array=False): """We override this to provide a faster implementation. It might also be possible to speed up the superclass' implementation using numpy, but it's easier here because we know we're using an HMM, not a higher-order ngram. This is based on the fwd prob calculation in NLTK's HMM implementation. @type array: bool @param array: if True, returns a numpy 2d array instead of a list of dicts. """ T = len(sequence) N = len(self.label_dom) alpha = numpy.zeros((T, N), numpy.float64) # Prepare the first column of the matrix: probs of all states in the # first timestep for i,state in enumerate(self.label_dom): alpha[0,i] = self.transition_log_probability(state, None) + \ self.emission_log_probability(sequence[0], state) # Iterate over the other timesteps for t in range(1, T): for j,sj in enumerate(self.label_dom): # Multiply each previous state's prob by the transition prob # to this state and sum them all together log_probs = [ alpha[t-1, i] + self.transition_log_probability(sj, si) \ for i,si in enumerate(self.label_dom)] # Also multiply this by the emission probability alpha[t, j] = sum_logs(log_probs) + \ self.emission_log_probability(sequence[t], sj) # Normalize by dividing all values by the total probability if normalize: for t in range(T): total = sum_logs(alpha[t,:]) for j in range(N): alpha[t,j] -= total if not array: # Convert this into a list of dicts matrix = [] for t in range(T): timestep = {} for (i,label) in enumerate(self.label_dom): timestep[label] = alpha[t,i] matrix.append(timestep) return matrix else: return alpha
def emission_log_probability(self, emission, state): """ Gives the probability P(emission | label). Returned as a base 2 log. The emission should be a pair of (root,label), together defining a chord. There's a special case of this. If the emission is a list, it's assumed to be a I{distribution} over emissions. The list should contain (prob,em) pairs, where I{em} is an emission, such as is normally passed into this function, and I{prob} is the weight to give to this possible emission. The probabilities of the possible emissions are summed up, weighted by the I{prob} values. """ if type(emission) is list: # Average probability over the possible emissions probs = [] for (prob,em) in emission: probs.append(logprob(prob) + \ self.emission_log_probability(em, state)) return sum_logs(probs) # Single chord label state_root,schema = state chord_root,label = emission # Probability is 0 if the roots don't match if state_root != chord_root: return float('-inf') else: return self.emission_dist[schema].logprob(label)
def _get_transition_backoff_scaler(self, context): # This is just for the schema distribution if context not in self._discount_cache: # The prob mass reserved for unseen events can be computed by # summing probabilities over all seen events and subtracting # from 1. # Our discounting model distributes this probability evenly over # the unseen events, so we can compute the discounted mass by # getting the probability of one unseen event and multiplying it. seen_labels = set([lab for lab in self.schemata+[None] if self.schema_transition_counts[context][lab] > 0]) if len(seen_labels) == 0: # Not seen anything in this context. All mass is discounted! self._discount_cache[context] = 0.0 else: unseen_labels = set(self.schemata+[None]) - seen_labels # Try getting some event that won't have been seen # Compute how much mass is reserved for unseen events discounted_mass = self.schema_transition_dist[context].prob( "%%% UNSEEN LABEL %%%") \ * len(unseen_labels) # Compute how much probability the n-1 order model assigns to # things unseen by this model backoff_context = context[:-1] backoff_seen_mass = sum_logs([ self.backoff_model.schema_transition_log_probability_schemata(lab, *backoff_context) for lab in unseen_labels]) self._discount_cache[context] = logprob(discounted_mass) - \ backoff_seen_mass return self._discount_cache[context]
def emission_log_probability(self, emission, state): """ Gives the probability P(emission | label). Returned as a base 2 log. The emission should be a pair of (root,label), together defining a chord. There's a special case of this. If the emission is a list, it's assumed to be a I{distribution} over emissions. The list should contain (prob,em) pairs, where I{em} is an emission, such as is normally passed into this function, and I{prob} is the weight to give to this possible emission. The probabilities of the possible emissions are summed up, weighted by the I{prob} values. """ if type(emission) is list: # Average probability over the possible emissions probs = [] for (prob, em) in emission: probs.append(logprob(prob) + \ self.emission_log_probability(em, state)) return sum_logs(probs) # Single chord label state_root, schema = state chord_root, label = emission # Probability is 0 if the roots don't match if state_root != chord_root: return float('-inf') else: return self.emission_dist[schema].logprob(label)
def _get_transition_backoff_scaler(self, context): # This is just for the schema distribution if context not in self._discount_cache: # The prob mass reserved for unseen events can be computed by # summing probabilities over all seen events and subtracting # from 1. # Our discounting model distributes this probability evenly over # the unseen events, so we can compute the discounted mass by # getting the probability of one unseen event and multiplying it. seen_labels = set([ lab for lab in self.schemata + [None] if self.schema_transition_counts[context][lab] > 0 ]) if len(seen_labels) == 0: # Not seen anything in this context. All mass is discounted! self._discount_cache[context] = 0.0 else: unseen_labels = set(self.schemata + [None]) - seen_labels # Try getting some event that won't have been seen # Compute how much mass is reserved for unseen events discounted_mass = self.schema_transition_dist[context].prob( "%%% UNSEEN LABEL %%%") \ * len(unseen_labels) # Compute how much probability the n-1 order model assigns to # things unseen by this model backoff_context = context[:-1] backoff_seen_mass = sum_logs([ self.backoff_model. schema_transition_log_probability_schemata( lab, *backoff_context) for lab in unseen_labels ]) self._discount_cache[context] = logprob(discounted_mass) - \ backoff_seen_mass return self._discount_cache[context]
def backward_log_probabilities(self, sequence, normalize=True, array=False): """We override this to provide a faster implementation. @see: forward_log_probability @type array: bool @param array: if True, returns a numpy 2d array instead of a list of dicts. """ T = len(sequence) N = len(self.label_dom) beta = numpy.zeros((T, N), numpy.float64) # Initialize with the probabilities of transitioning to the final state for i,si in enumerate(self.label_dom): beta[T-1, i] = self.transition_log_probability(None, si) # Iterate backwards over the other timesteps for t in range(T-2, -1, -1): for i,si in enumerate(self.label_dom): # Multiply each next state's prob by the transition prob # from this state to that and the emission prob in that next # state log_probs = [ beta[t+1, j] + self.transition_log_probability(sj, si) + \ self.emission_log_probability(sequence[t+1], sj) \ for j,sj in enumerate(self.label_dom)] beta[t, i] = sum_logs(log_probs) # Normalize by dividing all values by the total probability if normalize: total = sum_logs(beta[t,:]) for j in range(N): beta[t,j] -= total if not array: # Convert this into a list of dicts matrix = [] for t in range(T): timestep = {} for (i,label) in enumerate(self.label_dom): timestep[label] = beta[t,i] matrix.append(timestep) return matrix else: return beta
def emission_log_probability(self, emission, state): """ Gives the probability P(emission | label). Returned as a base 2 log. The emission should be a pair of (root,label), together defining a chord. There's a special case of this. If the emission is a list, it's assumed to be a I{distribution} over emissions. The list should contain (prob,em) pairs, where I{em} is an emission, such as is normally passed into this function, and I{prob} is the weight to give to this possible emission. The probabilities of the possible emissions are summed up, weighted by the I{prob} values. """ if type(emission) is list: # Average probability over the possible emissions probs = [] for (prob, em) in emission: probs.append(logprob(prob) + \ self.emission_log_probability(em, state)) return sum_logs(probs) # Single chord label point, function = state chord_root, label = emission X, Y, x, y = point # Work out the chord substitution subst = (chord_root - coordinate_to_et_2d((x, y))) % 12 # Generate the substitution given the chord function subst_prob = self.subst_emission_dist[function].logprob(subst) # Generate the label given the subst and chord function label_prob = self.type_emission_dist[(subst, function)].logprob(label) return subst_prob + label_prob
def emission_log_probability(self, emission, state): """ Gives the probability P(emission | label). Returned as a base 2 log. The emission should be a pair of (root,label), together defining a chord. There's a special case of this. If the emission is a list, it's assumed to be a I{distribution} over emissions. The list should contain (prob,em) pairs, where I{em} is an emission, such as is normally passed into this function, and I{prob} is the weight to give to this possible emission. The probabilities of the possible emissions are summed up, weighted by the I{prob} values. """ if type(emission) is list: # Average probability over the possible emissions probs = [] for (prob,em) in emission: probs.append(logprob(prob) + \ self.emission_log_probability(em, state)) return sum_logs(probs) # Single chord label point,function = state chord_root,label = emission X,Y,x,y = point # Work out the chord substitution subst = (chord_root - coordinate_to_et_2d((x,y))) % 12 # Generate the substitution given the chord function subst_prob = self.subst_emission_dist[function].logprob(subst) # Generate the label given the subst and chord function label_prob = self.type_emission_dist[(subst,function)].logprob(label) return subst_prob + label_prob