class BN(object): '''A BN is a bidirectional NeuralNet. It is equivelant to two opposite direction feed forward nets.''' def __init__(self, layers, up_weights=None, down_weights=None): '''Initializes a BN from the layers given''' self.numlayers = len(layers) down_layers = [layer.__class__.from_layer(layer) for layer in layers[::-1]]#Copy list so that upnet and downnet layers are different objects self.upnet = NeuralNet(layers, up_weights) self.downnet = NeuralNet(down_layers, down_weights) @classmethod def from_rbms(cls, rbms): '''Initializes a BN from a list of RBMS. NOTE: the down weights and upweights are tied. Modifying one, modifies the other. To untie, call the __untie__ method.''' layers = [] # First layer of dbn is the visible layer of the bottom rbm layers.append(rbms[0].get_vislayer()) # Keep all hidden layers for rbm in rbms: layers.append(rbm.get_hidlayer()) up_weights = [rbm.get_vishid() for rbm in rbms] down_weights = [rbm.get_vishid().transpose() for rbm in rbms[::-1]] return cls(layers, up_weights, down_weights) def bottom_up(self, data): '''Expects data to be probabilities''' self.upnet.layers[0].probs = data self.upnet.layers[0].activities = sample_binary_stochastic(data) return self.upnet.forward_pass(data, 1) def top_down(self, data): '''Expects data to be binary''' self.downnet.layers[0].activities = data return self.downnet.forward_pass(data, 1) def top_down_prob(self, data): '''Expects data to be binary''' self.downnet.layers[0].activities = data last = len(self.downnet.weights) data = dot(data, self.downnet.weights[0]) for i in range(1, self.downnet.numlayers): self.downnet.layers[i].process(data) data = self.downnet.layers[i].probs if i < last: data = dot(data, self.downnet.weights[i]) return [layer.activities for layer in self.downnet.layers] def __untie_weights__(self): '''This is an ugly step, and is only necessary when the db is initialized from RBMs. It unties the recognition weights from the generative ones.''' numweights = self.numlayers - 1 for i in range(numweights): self.upnet.weights[i] = self.upnet.weights[i].copy() def wake_phase(self, data): '''The first step of wake-sleep and contrastive wake-sleep. Returns wake_deltas, a list of matrices by which the the weights of the down net should be adjusted. Also returns wake_bias_deltas, wake_visbias_delta, and hidden states of top layer. Assumes DBN layers are binary stochastic layers.''' #Get the states and probabilities of every layer after doing a bottom-up pass hid_states = self.bottom_up(data) hid_probs = [] for layer in self.upnet.layers: hid_probs.append(layer.probs) wake_deltas = [] wake_bias_deltas = [] #Bias deltas for the generative visible units wake_visbias_delta = (data - self.upnet.layers[0].probs).sum(0)/data.shape[0] #Iterate over each layer excluding bottom layer for i in range(self.upnet.numlayers - 1): upper_state = hid_states[i+1] upper_activity = hid_probs[i+1] lower_state = hid_states[i] lower_activity = hid_probs[i] delta = dot(upper_state.transpose(), (lower_state - lower_activity))/data.shape[0] #Get bias deltas as well to update hidden biases in downnet delta_bias = array([(upper_state - upper_activity).sum(0)/data.shape[0]]) wake_deltas.insert(0,delta) wake_bias_deltas.insert(0, delta_bias) return wake_deltas, wake_bias_deltas, wake_visbias_delta, hid_states[-1] def sleep_phase(self, data): '''The last step of wake-sleep and contrastive wake-sleep. Returns sleep_deltas, a list of matrices by which the the weights of the up net should be adjusted. Also returns sleep_bias_deltas. Assumes DBN layers are binary stochastic layers.''' #Get the states and probabilities of every layer after doing a top-down pass hid_states = self.top_down(data) hid_probs = [] for layer in self.downnet.layers: hid_probs.append(layer.probs) sleep_deltas = [] sleep_bias_deltas = [] #Iterate over each layer excluding top layer for i in range(self.downnet.numlayers -1): lower_state = hid_states[i+1] upper_state = hid_states[i] upper_activity = hid_probs[i] delta = dot(lower_state.transpose(), (upper_state - upper_activity))/data.shape[0] #Get bias deltas to update hidden biases in upnet delta_bias = array([(upper_state - upper_activity).sum(0)/data.shape[0]]) sleep_deltas.insert(0,delta) sleep_bias_deltas.insert(0, delta_bias) return sleep_deltas, sleep_bias_deltas def wake_sleep(self, data, learning_rate): '''Combines wake and sleep phases''' downnet_deltas, downnet_hidbias_deltas, downnet_visbias_delta, top_state = self.wake_phase(data) upnet_deltas, upnet_bias_deltas = self.sleep_phase(top_state) #The top state is the input for the top-down pass recons_error = square(data - self.upnet.layers[0].probs).sum() print 'BN Reconstruction Error', recons_error self.downnet.layers[-1].bias += learning_rate*downnet_visbias_delta for i in range(len(downnet_deltas)): self.downnet.weights[i] += learning_rate*downnet_deltas[i] self.downnet.layers[i+1].bias += learning_rate*downnet_hidbias_deltas[i] for i in range(len(upnet_deltas)): self.upnet.weights[i] += learning_rate*upnet_deltas[i] self.upnet.layers[i+1].bias += learning_rate*upnet_bias_deltas[i] return recons_error