def transition_model(state): # given a hidden state, return the Distribution for the next hidden state x, y, action = state next_states = Distribution() # we can always stay where we are if action == 'stay': next_states[(x, y, 'stay')] = .2 else: next_states[(x, y, 'stay')] = .1 if y > 0: # we can go up if action == 'stay': next_states[(x, y-1, 'up')] = .2 if action == 'up': next_states[(x, y-1, 'up')] = .9 if y < GRID_HEIGHT - 1: # we can go down if action == 'stay': next_states[(x, y+1, 'down')] = .2 if action == 'down': next_states[(x, y+1, 'down')] = .9 if x > 0: # we can go left if action == 'stay': next_states[(x-1, y, 'left')] = .2 if action == 'left': next_states[(x-1, y, 'left')] = .9 if x < GRID_WIDTH - 1: # we can go right if action == 'stay': next_states[(x+1, y, 'right')] = .2 if action == 'right': next_states[(x+1, y, 'right')] = .9 next_states.renormalize() return next_states
def transition_model(state): # given a hidden state, return the Distribution for the next hidden state x, y, action = state next_states = Distribution() # we can always stay where we are if action == 'stay': next_states[(x, y, 'stay')] = .2 else: next_states[(x, y, 'stay')] = .1 if y > 0: # we can go up if action == 'stay': next_states[(x, y - 1, 'up')] = .2 if action == 'up': next_states[(x, y - 1, 'up')] = .9 if y < GRID_HEIGHT - 1: # we can go down if action == 'stay': next_states[(x, y + 1, 'down')] = .2 if action == 'down': next_states[(x, y + 1, 'down')] = .9 if x > 0: # we can go left if action == 'stay': next_states[(x - 1, y, 'left')] = .2 if action == 'left': next_states[(x - 1, y, 'left')] = .9 if x < GRID_WIDTH - 1: # we can go right if action == 'stay': next_states[(x + 1, y, 'right')] = .2 if action == 'right': next_states[(x + 1, y, 'right')] = .9 next_states.renormalize() return next_states
def uniform_transition_model(state): next_state_distribution = Distribution() valid_next_states = get_valid_next_states(state) for next_state in valid_next_states: next_state_distribution[next_state] += 1 next_state_distribution.renormalize() return next_state_distribution
def compute_marginal(message, node_potential): marginal = Distribution() for hidden_state in all_possible_hidden_states: if hidden_state in message and \ hidden_state in node_potential: value = message[hidden_state] * node_potential[hidden_state] if value > 0: # only store entries with nonzero prob. marginal[hidden_state] = value marginal.renormalize() return marginal
def spread_observation_model(state, radius=1): # given a hidden state, return the Distribution for its observation x, y, action = state observed_states = Distribution() for x_new in range(x - radius, x + radius + 1): for y_new in range(y - radius, y + radius + 1): if x_new >= 0 and x_new <= GRID_WIDTH - 1 and \ y_new >= 0 and y_new <= GRID_HEIGHT - 1: observed_states[(x_new, y_new)] = 1. observed_states.renormalize() return observed_states
def discretized_gaussian_observation_model(state, sigma=1): # given a hidden state, return the Distribution for its observation x, y, action = state observed_states = Distribution() # x_new, y_new = np.meshgrid(range(GRID_WIDTH), range(GRID_HEIGHT)) # values = np.exp( -( (x_new - x)**2 + (y_new -y)**2 )/(2.*sigma) ) for x_new in range(GRID_WIDTH): for y_new in range(GRID_HEIGHT): observed_states[(x_new, y_new)] = \ np.exp(-( (x_new - x)**2 + (y_new - y)**2 )/(2.*sigma)) observed_states.renormalize() return observed_states
def forward_backward(all_possible_hidden_states, all_possible_observed_states, prior_distribution, transition_model, observation_model, observations): """ Inputs ------ all_possible_hidden_states: a list of possible hidden states all_possible_observed_states: a list of possible observed states prior_distribution: a distribution over states transition_model: a function that takes a hidden state and returns a Distribution for the next state observation_model: a function that takes a hidden state and returns a Distribution for the observation from that hidden state observations: a list of observations, one per hidden state (a missing observation is encoded as None) Output ------ A list of marginal distributions at each time step; each distribution should be encoded as a Distribution (see the Distribution class in robot.py and see how it is used in both robot.py and the function generate_data() above, and the i-th Distribution should correspond to time step i """ num_time_steps = len(observations) #------------------------------------------------------------------------- # Fold observations into singleton potentials # phis = [] # phis[n] is the singleton potential for node n for n in range(num_time_steps): potential = Distribution() observed_state = observations[n] if n == 0: for hidden_state in prior_distribution: value = prior_distribution[hidden_state] if observed_state is not None: value *= observation_model(hidden_state)[observed_state] if value > 0: # only store entries with nonzero prob. potential[hidden_state] = value else: for hidden_state in all_possible_hidden_states: if observed_state is None: # singleton potential should be identically 1 potential[hidden_state] = 1. else: value = observation_model(hidden_state)[observed_state] if value > 0: # only store entries with nonzero prob. potential[hidden_state] = value assert len(potential.keys()) > 0 , \ "Invalid observation at time %d. Maybe you \ forgot the --use-spread-output argument?" %n phis.append(potential) # we need not recompute edge potentials since they're given by the # transition model: phi(x_i, x_j) = transition_model[x_i](x_j), # where j = i+1 #------------------------------------------------------------------------- # Forward pass # forward_messages = [] # compute message from non-existent node -1 to node 0 message = Distribution() for hidden_state in all_possible_hidden_states: message[hidden_state] = 1. message.renormalize() forward_messages.append(message) for n in range(num_time_steps - 1): # compute message from node n to node n+1 message = Distribution() ## the commented block below is easier to understand but is slow; ## a faster version is below that switches the order of the for loops ## and reduces the number of states that we iterate over #for next_hidden_state in all_possible_hidden_states: # value = 0. # # only loop over hidden states with nonzero singleton potential! # for hidden_state in phis[n]: # value += phis[n][hidden_state] * \ # transition_model(hidden_state)[next_hidden_state] * \ # forward_messages[-1][hidden_state] # if value > 0: # only store entries with nonzero prob. # message[next_hidden_state] = value ## faster version of the commented block above # 1. only loop over hidden states with nonzero singleton potential! for hidden_state in phis[n]: # 2. only loop over possible next hidden states given current # hidden state for next_hidden_state in transition_model(hidden_state): factor = phis[n][hidden_state] * \ transition_model(hidden_state)[next_hidden_state] * \ forward_messages[-1][hidden_state] if factor > 0: # only store entries with nonzero prob. if next_hidden_state in message: message[next_hidden_state] += factor else: message[next_hidden_state] = factor message.renormalize() forward_messages.append(message) #------------------------------------------------------------------------- # Pre-processing to speed up the backward pass: cache for each hidden # state what the possible previous hidden states are # possible_prev_hidden_states = {} for hidden_state in all_possible_hidden_states: for next_hidden_state in transition_model(hidden_state): if next_hidden_state in possible_prev_hidden_states: possible_prev_hidden_states[next_hidden_state].add( \ hidden_state) else: possible_prev_hidden_states[next_hidden_state] = \ set([hidden_state]) #------------------------------------------------------------------------- # Backward pass # backward_messages = [] # compute message from non-existent node <num_time_steps> to node # <num_time_steps>-1 message = Distribution() for hidden_state in all_possible_hidden_states: message[hidden_state] = 1. message.renormalize() backward_messages.append(message) for n in range(num_time_steps - 2, -1, -1): # compute message from node n+1 to n message = Distribution() ## again, I've commented out a block that's easier to understand but ## slow; the faster version is below #for hidden_state in all_possible_hidden_states: # value = 0. # for next_hidden_state in transition_model(hidden_state): # value += phis[n+1][next_hidden_state] * \ # transition_model(hidden_state)[next_hidden_state] * \ # backward_messages[0][next_hidden_state] # if value > 0: # only store entries with nonzero prob. # message[hidden_state] = value ## faster version # 1. only loop over next hidden states with nonzero potential! for next_hidden_state in phis[n + 1]: # 2. only loop over possible previous hidden states for hidden_state in possible_prev_hidden_states[next_hidden_state]: factor = phis[n+1][next_hidden_state] * \ transition_model(hidden_state)[next_hidden_state] * \ backward_messages[0][next_hidden_state] if factor > 0: # only store entries with nonzero prob. if hidden_state in message: message[hidden_state] += factor else: message[hidden_state] = factor message.renormalize() backward_messages.insert(0, message) #------------------------------------------------------------------------- # Compute marginals # marginals = [] for n in range(num_time_steps): marginal = Distribution() for hidden_state in all_possible_hidden_states: if hidden_state in forward_messages[n] and \ hidden_state in backward_messages[n] and \ hidden_state in phis[n]: value = forward_messages[n][hidden_state] * \ backward_messages[n][hidden_state] * \ phis[n][hidden_state] if value > 0: # only store entries with nonzero prob. marginal[hidden_state] = value marginal.renormalize() marginals.append(marginal) # vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv ### YOUR CODE HERE: Estimate marginals & pairwise marginals pairwise_marginals = [None] * (num_time_steps - 1) # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ return (marginals, pairwise_marginals)
def forward(all_possible_hidden_states, prior_distribution, transition_model, observation_model, observations): """ Inputs ------ all_possible_hidden_states: a list of possible hidden states prior_distribution: a distribution over states transition_model: a function that takes a hidden state and returns a Distribution for the next state observation_model: a function that takes a hidden state and returns a Distribution for the observation from that hidden state observations: a list of observations, one per hidden state (a missing observation is encoded as None) Output ------ This function is a Python generator! It calculates outputs "on demand"; the i-th output should be the marginal distribution for time step i. """ num_time_steps = len(observations) #------------------------------------------------------------------------- # Forward pass # def compute_marginal(message, node_potential): marginal = Distribution() for hidden_state in all_possible_hidden_states: if hidden_state in message and \ hidden_state in node_potential: value = message[hidden_state] * node_potential[hidden_state] if value > 0: # only store entries with nonzero prob. marginal[hidden_state] = value marginal.renormalize() return marginal # compute message from non-existent node -1 to node 0 message = Distribution() for hidden_state in all_possible_hidden_states: message[hidden_state] = 1. message.renormalize() # compute node potential for time step 0 node_potential = Distribution() observed_state = observations[0] for hidden_state in prior_distribution: value = prior_distribution[hidden_state] if observed_state is not None: value *= observation_model(hidden_state)[observed_state] if value > 0: node_potential[hidden_state] = value yield compute_marginal(message, node_potential) prev_message = message prev_node_potential = node_potential for n in range(1, num_time_steps): message = Distribution() node_potential = Distribution() observed_state = observations[n] # compute message from node n-1 to n and fill in node potential for # time step n for hidden_state in all_possible_hidden_states: # only loop over possible next hidden states given current # hidden state for next_hidden_state in transition_model(hidden_state): factor = prev_node_potential[hidden_state] * \ transition_model(hidden_state)[next_hidden_state] * \ prev_message[hidden_state] if factor > 0: # only store entries with nonzero prob. if next_hidden_state in message: message[next_hidden_state] += factor else: message[next_hidden_state] = factor if observed_state is not None: value = observation_model(hidden_state)[observed_state] if value > 0: node_potential[hidden_state] = value else: node_potential[hidden_state] = 1. message.renormalize() yield compute_marginal(message, node_potential) prev_message = message prev_node_potential = node_potential
def forward_backward( all_possible_hidden_states, all_possible_observed_states, prior_distribution, transition_model, observation_model, observations, ): """ Inputs ------ all_possible_hidden_states: a list of possible hidden states all_possible_observed_states: a list of possible observed states prior_distribution: a distribution over states transition_model: a function that takes a hidden state and returns a Distribution for the next state observation_model: a function that takes a hidden state and returns a Distribution for the observation from that hidden state observations: a list of observations, one per hidden state (a missing observation is encoded as None) Output ------ A list of marginal distributions at each time step; each distribution should be encoded as a Distribution (see the Distribution class in robot.py and see how it is used in both robot.py and the function generate_data() above, and the i-th Distribution should correspond to time step i """ num_time_steps = len(observations) # ------------------------------------------------------------------------- # Fold observations into singleton potentials # phis = [] # phis[n] is the singleton potential for node n for n in range(num_time_steps): potential = Distribution() observed_state = observations[n] if n == 0: for hidden_state in prior_distribution: value = prior_distribution[hidden_state] if observed_state is not None: value *= observation_model(hidden_state)[observed_state] if value > 0: # only store entries with nonzero prob. potential[hidden_state] = value else: for hidden_state in all_possible_hidden_states: if observed_state is None: # singleton potential should be identically 1 potential[hidden_state] = 1.0 else: value = observation_model(hidden_state)[observed_state] if value > 0: # only store entries with nonzero prob. potential[hidden_state] = value assert len(potential.keys()) > 0, ( "Invalid observation at time %d. Maybe you \ forgot the --use-spread-output argument?" % n ) phis.append(potential) # we need not recompute edge potentials since they're given by the # transition model: phi(x_i, x_j) = transition_model[x_i](x_j), # where j = i+1 # ------------------------------------------------------------------------- # Forward pass # forward_messages = [] # compute message from non-existent node -1 to node 0 message = Distribution() for hidden_state in all_possible_hidden_states: message[hidden_state] = 1.0 message.renormalize() forward_messages.append(message) for n in range(num_time_steps - 1): # compute message from node n to node n+1 message = Distribution() ## the commented block below is easier to understand but is slow; ## a faster version is below that switches the order of the for loops ## and reduces the number of states that we iterate over # for next_hidden_state in all_possible_hidden_states: # value = 0. # # only loop over hidden states with nonzero singleton potential! # for hidden_state in phis[n]: # value += phis[n][hidden_state] * \ # transition_model(hidden_state)[next_hidden_state] * \ # forward_messages[-1][hidden_state] # if value > 0: # only store entries with nonzero prob. # message[next_hidden_state] = value ## faster version of the commented block above # 1. only loop over hidden states with nonzero singleton potential! for hidden_state in phis[n]: # 2. only loop over possible next hidden states given current # hidden state for next_hidden_state in transition_model(hidden_state): factor = ( phis[n][hidden_state] * transition_model(hidden_state)[next_hidden_state] * forward_messages[-1][hidden_state] ) if factor > 0: # only store entries with nonzero prob. if next_hidden_state in message: message[next_hidden_state] += factor else: message[next_hidden_state] = factor message.renormalize() forward_messages.append(message) # ------------------------------------------------------------------------- # Pre-processing to speed up the backward pass: cache for each hidden # state what the possible previous hidden states are # possible_prev_hidden_states = {} for hidden_state in all_possible_hidden_states: for next_hidden_state in transition_model(hidden_state): if next_hidden_state in possible_prev_hidden_states: possible_prev_hidden_states[next_hidden_state].add(hidden_state) else: possible_prev_hidden_states[next_hidden_state] = set([hidden_state]) # ------------------------------------------------------------------------- # Backward pass # backward_messages = [] # compute message from non-existent node <num_time_steps> to node # <num_time_steps>-1 message = Distribution() for hidden_state in all_possible_hidden_states: message[hidden_state] = 1.0 message.renormalize() backward_messages.append(message) for n in range(num_time_steps - 2, -1, -1): # compute message from node n+1 to n message = Distribution() ## again, I've commented out a block that's easier to understand but ## slow; the faster version is below # for hidden_state in all_possible_hidden_states: # value = 0. # for next_hidden_state in transition_model(hidden_state): # value += phis[n+1][next_hidden_state] * \ # transition_model(hidden_state)[next_hidden_state] * \ # backward_messages[0][next_hidden_state] # if value > 0: # only store entries with nonzero prob. # message[hidden_state] = value ## faster version # 1. only loop over next hidden states with nonzero potential! for next_hidden_state in phis[n + 1]: # 2. only loop over possible previous hidden states for hidden_state in possible_prev_hidden_states[next_hidden_state]: factor = ( phis[n + 1][next_hidden_state] * transition_model(hidden_state)[next_hidden_state] * backward_messages[0][next_hidden_state] ) if factor > 0: # only store entries with nonzero prob. if hidden_state in message: message[hidden_state] += factor else: message[hidden_state] = factor message.renormalize() backward_messages.insert(0, message) # ------------------------------------------------------------------------- # Compute marginals # marginals = [] for n in range(num_time_steps): marginal = Distribution() for hidden_state in all_possible_hidden_states: if hidden_state in forward_messages[n] and hidden_state in backward_messages[n] and hidden_state in phis[n]: value = forward_messages[n][hidden_state] * backward_messages[n][hidden_state] * phis[n][hidden_state] if value > 0: # only store entries with nonzero prob. marginal[hidden_state] = value marginal.renormalize() marginals.append(marginal) # vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv ### YOUR CODE HERE: Estimate marginals & pairwise marginals pairwise_marginals = [None] * (num_time_steps - 1) # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ return (marginals, pairwise_marginals)