def learn(self, init_weight_array=None, eta_minus=0.5, eta_plus=1.2, refresh_updater=True): """ Compute a model according to the given LTF Array parameters and training set. Note that this function can take long to return. :return: pypuf.simulation.arbiter_based.LTFArray The computed model. """ # log format def log_state(): """ This method is used to log a snapshot of learning variables while running. """ if self.logger is None: return self.logger.debug('%i\t%f\t%f\t%s' % ( self.iteration_count, distance, norm(self.updater.step), ','.join(map(str, model.weight_array.flatten())), )) # let numpy raise exceptions seterr(all='raise') # Prepare challenges self.efba_sub_challenges = LTFArray.efba_bit( self.transformation(self.training_set.challenges, self.k)) # we start with a random model model = LTFArray( weight_array=LTFArray.normal_weights(self.n, self.k, self.weights_mu, self.weights_sigma, self.weights_prng), transform=self.transformation, combiner=self.combiner, bias=0.0, ) if init_weight_array is not None: model.weight_array = init_weight_array if refresh_updater: self.updater = self.RPropModelUpdate(model, eta_minus=eta_minus, eta_plus=eta_plus) converged = False distance = 1 self.iteration_count = 0 log_state() number_of_batches = (self.training_set.N + 1) // self.minibatch_size efba_challenge_batches = [] response_batches = [] if not self.shuffle: efba_challenge_batches = array_split(self.efba_sub_challenges, number_of_batches) response_batches = array_split(self.training_set.responses, number_of_batches) while not converged and self.iteration_count < self.iteration_limit: self.iteration_count += 1 self.epoch_count += 1 if self.shuffle: if self.epoch_count > 1: RandomState(seed=self.epoch_count).shuffle( self.efba_sub_challenges) RandomState(seed=self.epoch_count).shuffle( self.training_set.responses) efba_challenge_batches = array_split(self.efba_sub_challenges, number_of_batches) response_batches = array_split(self.training_set.responses, number_of_batches) # compute gradient & update model for batch in range(number_of_batches): gradient = self.gradient(model, efba_challenge_batches[batch], response_batches[batch]) model.weight_array += self.updater.update(gradient) self.gradient_step_count += 1 # check convergence converged = norm( self.updater.step) < 10**-self.convergence_decimals # log log_state() if converged: break if not converged: self.converged = False else: self.converged = True return model
def learn(self, init_weight_array=None, eta_minus=0.5, eta_plus=1.2, refresh_updater=True): """ Compute a model according to the given LTF Array parameters and training set. Note that this function can take long to return. :return: pypuf.simulation.arbiter_based.LTFArray The computed model. """ self.logger.debug('LR learner started') test_set_accuracies = [] # log format def log_state(step_size): """ This method is used to log a snapshot of learning variables while running. """ if self.logger is None: return self.logger.debug( '%i\t%s\t%f\t%f\t%f\t%s' % ( self.iteration_count, f'{self.test_set_dist:.4f}' if self.test_set else '<no test set given>', self.training_set_dist_sign, self.training_set_dist, step_size, ','.join(map(str, model.weight_array.flatten())) if self.n <= 1024 else '<weight array too large>', ) ) # let numpy raise exceptions seterr(all='raise') # Prepare challenges self.logger.debug(f'Challenge bit type {self.training_set.challenges.dtype}') self.logger.debug(f'Transforming {len(self.training_set.challenges)} given {self.n}-bit ' f'challenges using {self.transformation.__name__} for k={self.k} ...') transformed_challenges = self.transformation(self.training_set.challenges, self.k) if self.bias: self.logger.debug(f'Efba\'ing {len(self.training_set.challenges)} given {self.n}-bit challenges') self.efba_sub_challenges = LTFArray.efba_bit(transformed_challenges) else: self.logger.debug(f'Not efba\'ing {len(self.training_set.challenges)} challenges, assuming unbiased target') self.efba_sub_challenges = transformed_challenges # we start with a random model self.logger.debug(f'Initializing random unbiased model') model = LTFArray( weight_array=LTFArray.normal_weights(self.n, self.k, self.weights_mu, self.weights_sigma, self.weights_prng), transform=self.transformation, combiner=self.combiner, bias=0.0, ) if init_weight_array is not None: model.weight_array = init_weight_array if refresh_updater: self.updater = self.RPropModelUpdate(model, bias=self.bias, eta_minus=eta_minus, eta_plus=eta_plus) converged = False self.iteration_count = 0 log_state(0) number_of_batches = ceil(self.training_set.N / (self.minibatch_size or self.training_set.N)) self.logger.debug(f'using {self.training_set.N} examples with batches of size ' f'{self.minibatch_size}, i.e. {number_of_batches} batches') efba_challenge_batches = [] response_batches = [] if not self.shuffle: efba_challenge_batches = array_split(self.efba_sub_challenges, number_of_batches) response_batches = array_split(self.training_set.responses, number_of_batches) self.logger.debug(f'Starting learning loop!') self.logger.debug(f'stopping when step size smaller than {10**-self.convergence_decimals} or ' f'{self.iteration_limit} epochs') while not converged and self.iteration_count < self.iteration_limit: self.iteration_count += 1 self.epoch_count += 1 if self.shuffle: if self.epoch_count > 1: RandomState(seed=self.epoch_count).shuffle(self.efba_sub_challenges) RandomState(seed=self.epoch_count).shuffle(self.training_set.responses) efba_challenge_batches = array_split(self.efba_sub_challenges, number_of_batches) response_batches = array_split(self.training_set.responses, number_of_batches) # compute gradient & update model for batch in range(number_of_batches): gradient = self.gradient(model, efba_challenge_batches[batch], response_batches[batch]) if self.bias: model.weight_array += self.updater.update(gradient) else: model.weight_array[:, :-1] += self.updater.update(gradient) self.gradient_step_count += 1 # check convergence current_step_size = norm(self.updater.step) if self.test_set and self.test_set.N: self.test_set_dist = approx_dist_nonrandom(model, self.test_set) test_set_accuracies.append(1 - self.test_set_dist) converged = ( current_step_size < 10**-self.convergence_decimals or (self.target_test_accuracy and 1 - self.test_set_dist > self.target_test_accuracy) or ( self.test_accuracy_improvement and self.test_accuracy_patience and len(test_set_accuracies) >= self.test_accuracy_patience and ( abs( min(test_set_accuracies[-self.test_accuracy_patience:]) - max(test_set_accuracies[-self.test_accuracy_patience:]) ) < self.test_accuracy_improvement ) ) ) and ( self.iteration_count > self.min_iterations ) # log log_state(current_step_size) if converged: break self.efba_sub_challenges = None # del ref to training set memory to allow GC if the t-set is also dereferenced self.converged = converged return model
def __init__(self, n, k, training_set, validation_set, weights_mu=0, weights_sigma=1, weights_prng=RandomState(), lr_iteration_limit=1000, mini_batch_size=0, convergence_decimals=2, shuffle=False, logger=None): """ Initialize a Correlation Attack Learner for the specified LTF Array which uses transform_lightweight_secure. :param n: Input length :param k: Number of parallel LTFs in the LTF Array :param training_set: The training set, i.e. a data structure containing challenge response pairs :param validation_set: The validation set, i.e. a data structure containing challenge response pairs. Used for approximating accuracies of permuted models (can be smaller e.g. 0.1*training_set_size) :param weights_mu: mean of the Gaussian that is used to choose the initial model :param weights_sigma: standard deviation of the Gaussian that is used to choose the initial model :param weights_prng: PRNG to draw the initial model from. Defaults to fresh `numpy.random.RandomState` instance. :param lr_iteration_limit: Iteration limit for a single LR learner run :param logger: logging.Logger Logger which is used to log detailed information of learn iterations. """ self.n = n self.k = k self.validation_set_efba = ChallengeResponseSet( challenges=LTFArray.efba_bit( LTFArray.transform_lightweight_secure( validation_set.challenges, k)), responses=validation_set.responses) self.logger = logger self.lr_learner = LogisticRegression( t_set=training_set, n=n, k=k, transformation=LTFArray.transform_lightweight_secure, combiner=LTFArray.combiner_xor, weights_mu=weights_mu, weights_sigma=weights_sigma, weights_prng=weights_prng, logger=logger, iteration_limit=lr_iteration_limit, minibatch_size=mini_batch_size, convergence_decimals=convergence_decimals, shuffle=shuffle) self.initial_accuracy = .5 self.initial_lr_iterations = 0 self.initial_model = None self.total_lr_iterations = 0 self.best_permutation_iteration = 0 self.total_permutation_iterations = 0 self.best_permutation = None self.best_accuracy = None assert n in ( 64, 128 ), 'Correlation attack for %i bit is currently not supported.' % n assert validation_set.N >= 1000, 'Validation set should contain at least 1000 challenges.' self.correlation_permutations = loadmat( 'data/correlation_permutations_lightweight_secure_%i_10.mat' % n)['shiftOverviewData'][:, :, 0].astype('int64')