def _contrib_adaptive_avg_pool2d(self, x_in, y_out, ydx, layer, threshold=0.1): ''' Return the contributing synapses for a torch.nn.AdaptiveAveragePool layer Parameters ---------- x_in : torch.Tensor dimensions: batchsize,channels,height,width y_out : torch.Tensor dimensions: batchsize,channels,height,width ydx : tuple (channel,row,column) position of an output neuron layer : list(str) list containing single key in self.model.available_modules() dictionary threshold : float Returns ------- neuron_counts synapse_counts synapse_weights ''' neuron_counts = Counter() synapse_counts = Counter() synapse_weights = set() avgpool = self.model.available_modules()[layer[0]] '''Grab the dimensions used by an adaptive pooling layer''' output_size = avgpool.output_size[0] input_size = x_in.shape[-1] stride = (input_size // output_size) kernel_size = input_size - (output_size - 1) * stride if len(ydx) == 1: ch, i, j = get_index(ydx[0], kernel_size) else: ch, i, j = ydx scalar = 1 / (kernel_size**2) # multiplier for computing average goal = threshold * y_out[0, ch, i, j] xmat = submatrix_generator(x_in, stride, kernel_size)(i, j)[ch] ordsmat = sorted([(v * scalar, c) for c, v in enumerate(xmat.flatten()) if v != 0], key=lambda t: t[0], reverse=True) if len(ordsmat) > 0: cumsum = torch.cumsum(torch.Tensor([v[0] for v in ordsmat]), dim=0).detach() assert np.allclose(cumsum[-1].detach().numpy(), y_out[0, ch, i, j].detach().numpy(), rtol=1e-04, atol=1e-4), f'avgpool failure: {cumsum[-1] - y_out[0,ch,i,j]}' for idx, t in enumerate(cumsum): if t > goal: break totalsum = cumsum[-1] for v, c in ordsmat[:idx + 1]: neuron = (ch, c // kernel_size + stride * i, c % kernel_size + stride * j) neuron_counts.update([neuron]) synapse_counts.update([(neuron, ydx)]) weight = round(float((v / totalsum).detach().numpy()), 6) synapse_weights.update([(neuron, ydx, weight)]) return neuron_counts, synapse_counts, synapse_weights
def _contrib_max2d(self, x_in, y_out, ydx, layer, threshold=None): ''' Return the contributing synapse for a torch.nn.Max2D layer Parameters ---------- x_in : torch.Tensor dimensions: batchsize,channels,height,width y_out : torch.Tensor dimensions: batchsize,channels,height,width ydx : tuple (channel,row,column) position of an output neuron layer : list(str) list containing single key in self.model.available_modules() dictionary threshold : None or float not used, placeholder for uniformity in arguments. Returns ------- neuron_counts synapse_counts synapse_weights ''' neuron_counts = Counter() synapse_counts = Counter() synapse_weights = set() maxpool = self.model.available_modules()[layer[0]] # Grab dimensions of maxpool from parameters stride = maxpool.stride kernel_size = maxpool.kernel_size if len(ydx) == 1: ch, i, j = get_index(ydx[0], kernel_size) else: ch, i, j = ydx xmat = submatrix_generator(x_in, stride, kernel_size)(i, j)[ch] c, v = max(list(enumerate(xmat.flatten())), key=lambda x: x[1]) assert np.allclose( v.detach().numpy(), y_out[0, ch, i, j].detach().numpy(), rtol=1e-04, atol=1e-4), f'maxpool failure: {v - y_out[0,ch,i,j]}' neuron = (ch, c // kernel_size + stride * i, c % kernel_size + stride * j) neuron_counts.update([neuron]) synapse_counts.update([(neuron, ydx)]) synapse_weights.update([ (neuron, ydx, 1) ]) # 1 is just a placeholder since it is a direct contribution return neuron_counts, synapse_counts, synapse_weights
def _contrib_linear(self, x_in, y_out, ydx, layers, threshold=0.1): ''' Profile a single output neuron from a linear layer Pattern ------- x_in : torch.tensor 2dimensions: batchsize,i y_out : torch.Tensor 2dimensions: batchsize,j ydx : tuple 1d position of an output neuron layers : list() list containing keys in self.model.available_modules() dictionary for linear layer these will refer to a linear module and an activation module or just a linear module threshold : float Returns ------- neuron_counts synapse_counts synapse_weights ''' j = ydx[0] neuron_counts = Counter() synapse_counts = Counter() synapse_weights = set() if len(layers) == 1: linear = layers[0] def actf(x): return x else: linear, actf = layers actf = self.model.available_modules()[actf] linear = self.model.available_modules()[linear] W = linear._parameters['weight'] B = linear._parameters['bias'] # TODO make the lines below loop over a list of ydx positions goal = threshold * y_out[0, j] if goal <= 0: warnings.warn(f'output neuron at position {ydx} is less than 0') xdims = x_in[0].shape if len(xdims) > 1: holdx = torch.Tensor(x_in) x_in = x_in[0].flatten().unsqueeze(0) z = x_in[0] * W[j] # Confirm we haven't changed anything y_predict = (torch.sum(z) + B[j]).detach().numpy() y_true = y_out[0, j].detach().numpy() assert np.allclose( y_predict, y_true, rtol=1e-04, atol=1e-4), f'linear failed {y_predict-y_true} {layers}' ordsmat = sorted([(v, idx) for idx, v in enumerate(z) if v != 0], key=lambda t: t[0], reverse=True) if len(ordsmat): cumsum = torch.cumsum(torch.Tensor([v[0] for v in ordsmat]), dim=0).detach() + B[j] values = [actf(v) for v in cumsum] assert np.allclose( values[-1].detach().numpy(), y_true, rtol=1e-04, atol=1e-4 ), f'linear failed[2]: {values[-1].detach().numpy() - y_true}' for idx, v in enumerate(values): if v >= goal: break totalsum = cumsum[-1] - B[j] for v, jdx in ordsmat[:idx + 1]: if len(xdims) > 1: neuron = get_index(jdx, xdims[-1]) # is this producing 3 tuple? else: neuron = tuple([jdx]) neuron_counts.update([ neuron ]) # this shows where the index was in original input synapse_counts.update([(neuron, ydx)]) weight = round(float((v / totalsum).detach().numpy()), 6) synapse_weights.update([(neuron, ydx, weight)]) return neuron_counts, synapse_counts, synapse_weights
def _contrib_conv2d(self, x_in, y_out, ydx, layers, threshold=0.1): ''' Profile a single output neuron from a 2d conv layer Pattern ------- x_in : torch.tensor dimensions: batchsize,channels,height,width y_out : torch.Tensor dimensions: batchsize,channels,height,width ydx : tuple (channel,row,column) 3d position of an output neuron layers : list([str,str]) list containing keys in self.model.available_modules() dictionary for conv2d these will refer to a convolutional module and an activation module threshold : float Returns ------- neuron_counts synapse_counts synapse_weights Note ---- Only implemented for convolution using filters with same height and width and strides equal in both dimensions and padding equal in all dimensions Synapse profiles for conv2d are indexed by 3 sets of tuples one for each neuron and on one for the index of the filter used. ''' neuron_counts = Counter() synapse_counts = Counter() synapse_weights = set() conv, actf = layers conv = self.model.available_modules()[conv] actf = self.model.available_modules()[actf] # assumption is that kernel size, stride are equal in both dimensions # and padding preserves input size kernel_size = conv.kernel_size[0] stride = conv.stride[0] padding = conv.padding[0] W = conv._parameters['weight'] B = conv._parameters['bias'] if len(ydx) == 1: d, i, j = get_index(ydx[0], kernel_size) else: d, i, j = ydx # TODO make the lines below loop over a list of ydx positions try: y_true = y_out[0, d, i, j].detach().numpy() except: print(d, i, j) goal = threshold * y_true if goal <= 0: warnings.warn(f'output neuron at position {ydx} is less than 0') xmat = submatrix_generator(x_in, stride, kernel_size, padding=padding)( i, j) # TODO generate sbmat before the loop z = torch.mul(W[d], xmat) ordsmat = sorted([(v, idx) for idx, v in enumerate(z.flatten()) if v != 0], key=lambda t: t[0], reverse=True) if len(ordsmat) > 0: cumsum = torch.cumsum(torch.Tensor([v[0] for v in ordsmat]), dim=0).detach() + B[d] values = [actf(v) for v in cumsum] assert np.allclose( values[-1].detach().numpy(), y_true, rtol=1e-04, atol=1e-4 ), f'conv2d failure: {values[-1].detach().numpy() - y_true}' for idx, v in enumerate(values): if v >= goal: break totalsum = cumsum[-1] - B[ d] # this is the sum of all the values before bias and activation for v, jdx in ordsmat[:idx + 1]: wdx = get_index(jdx, kernel_size) neuron = tuple( np.array(wdx, dtype=int) + np.array( (0, stride * i - padding, stride * j - padding), dtype=int)) # need the unpadded index neuron_counts.update([ neuron ]) # this shows where the index was in original input synapse_counts.update([(neuron, ydx, wdx)]) weight = round(float((v / totalsum).detach().numpy()), 6) synapse_weights.update([(neuron, ydx, weight)]) return neuron_counts, synapse_counts, synapse_weights