class MetaApprox(BaseMeta): def __init__(self, adj, x, labels, idx_train, idx_unlabeled, lr=0.1, epochs=100, lambda_=0., hidden_layers=[16], use_relu=True, self_training_labels=None, seed=None, name=None, device='CPU:0', **kwargs): self.lr = lr self.epochs = epochs self.lambda_ = lambda_ if lambda_ not in (0., 0.5, 1.): raise ValueError( 'Invalid value of `lanbda_`, allowed values [0: (meta-self), 1: (meta-train), 0.5: (meta-both)].' ) super().__init__(adj, x, labels, idx_train, idx_unlabeled, hidden_layers=hidden_layers, use_relu=use_relu, self_training_labels=self_training_labels, seed=seed, name=name, device=device, **kwargs) def build(self, hidden_layers): weights = [] zeros_initializer = zeros() pre_hid = self.n_attrs for hid in hidden_layers + [self.n_classes]: shape = (pre_hid, hid) # use zeros_initializer temporary to save time weight = tf.Variable( zeros_initializer(shape=shape, dtype=self.floatx)) weights.append(weight) pre_hid = hid self.weights = weights self.adj_grad_sum = tf.Variable(tf.zeros_like(self.tf_adj)) self.x_grad_sum = tf.Variable(tf.zeros_like(self.tf_x)) self.optimizer = Adam(self.lr, epsilon=1e-8) def initialize(self): w_initializer = glorot_uniform() zeros_initializer = zeros() for w in self.weights: w.assign(w_initializer(w.shape, dtype=self.floatx)) if self.structure_attack: self.adj_grad_sum.assign( zeros_initializer(self.adj_grad_sum.shape, dtype=self.floatx)) if self.feature_attack: self.x_grad_sum.assign( zeros_initializer(self.x_grad_sum.shape, dtype=self.floatx)) # reset optimizer for var in self.optimizer.variables(): var.assign(tf.zeros_like(var)) @tf.function def meta_grad(self): self.initialize() modified_adj, modified_x = self.tf_adj, self.tf_x adj_grad_sum, x_grad_sum = self.adj_grad_sum, self.x_grad_sum optimizer = self.optimizer for _ in tf.range(self.epochs): with tf.GradientTape(persistent=True) as tape: if self.structure_attack: modified_adj = self.get_perturbed_adj( self.tf_adj, self.adj_changes) if self.feature_attack: modified_x = self.get_perturbed_x(self.tf_x, self.x_changes) adj_norm = normalize_adj_tensor(modified_adj) output = self.forward(modified_x, adj_norm) / 5.0 logit_labeled = tf.gather(output, self.idx_train) logit_unlabeled = tf.gather(output, self.idx_unlabeled) loss_labeled = self.loss_fn(self.labels_train, logit_labeled) loss_unlabeled = self.loss_fn(self.self_training_labels, logit_unlabeled) attack_loss = self.lambda_ * loss_labeled + ( 1 - self.lambda_) * loss_unlabeled adj_grad, x_grad = None, None gradients = tape.gradient(loss_labeled, self.weights) optimizer.apply_gradients(zip(gradients, self.weights)) if self.structure_attack: adj_grad = tape.gradient(attack_loss, self.adj_changes) adj_grad_sum.assign_add(adj_grad) if self.feature_attack: x_grad = tape.gradient(attack_loss, self.x_changes) x_grad_sum.assign_add(x_grad) del tape return adj_grad_sum, x_grad_sum def attack(self, n_perturbations=0.05, structure_attack=True, feature_attack=False, ll_constraint=False, ll_cutoff=0.004, disable=False): super().attack(n_perturbations, structure_attack, feature_attack) if ll_constraint: raise NotImplementedError( '`log_likelihood_constraint` has not been well tested.' ' Please set `ll_constraint=False` to achieve a better performance.' ) if feature_attack and not is_binary(self.x): raise ValueError( "Attacks on the node features are currently only supported for binary attributes." ) with tf.device(self.device): modified_adj, modified_x = self.tf_adj, self.tf_x adj_changes, x_changes = self.adj_changes, self.x_changes structure_flips, feature_flips = self.structure_flips, self.feature_flips for _ in tqdm(range(self.n_perturbations), desc='Peturbing Graph', disable=disable): adj_grad, x_grad = self.meta_grad() adj_meta_score = tf.constant(0.0) x_meta_score = tf.constant(0.0) if structure_attack: modified_adj = self.get_perturbed_adj( self.tf_adj, adj_changes) adj_meta_score = self.structure_score( modified_adj, adj_grad, ll_constraint, ll_cutoff) if feature_attack: modified_x = self.get_perturbed_x(self.tf_x, x_changes) x_meta_score = self.feature_score(modified_x, feature_grad) if tf.reduce_max(adj_meta_score) >= tf.reduce_max( x_meta_score): adj_meta_argmax = tf.argmax(adj_meta_score) row, col = divmod(adj_meta_argmax.numpy(), self.n_nodes) adj_changes[row, col].assign(-2. * modified_adj[row, col] + 1.) adj_changes[col, row].assign(-2. * modified_adj[col, row] + 1.) structure_flips.append((row, col)) else: x_meta_argmax = tf.argmax(x_meta_score) row, col = divmod(x_meta_argmax.numpy(), self.n_attrs) x_changes[row, col].assign(-2 * modified_x[row, col] + 1) feature_flips.append((row, col))
class MinMax(PGD): '''MinMax cannot ensure that there is not singleton after attack.''' def __init__(self, adj, x, labels, idx_train, idx_unlabeled=None, surrogate=None, surrogate_args={}, surrogate_lr=5e-3, seed=None, name=None, device='CPU:0', **kwargs): super().__init__(adj, x, labels, idx_train=idx_train, idx_unlabeled=idx_unlabeled, surrogate=surrogate, surrogate_args=surrogate_args, seed=seed, device=device, **kwargs) with tf.device(self.device): self.stored_weights = tf.identity_n(self.surrogate.weights) self.optimizer = Adam(surrogate_lr) def reset(self): super().reset() weights = self.surrogate.weights # restore surrogate weights for w1, w2 in zip(weights, self.stored_weights): w1.assign(w2) # reset optimizer for var in self.optimizer.variables(): var.assign(tf.zeros_like(var)) def attack(self, n_perturbations=0.05, sample_epochs=20, CW_loss=True, epochs=100, update_per_epoch=20, structure_attack=True, feature_attack=False, disable=False): super(PGD, self).attack(n_perturbations, structure_attack, feature_attack) self.CW_loss = CW_loss if CW_loss: C = 0.1 else: C = 200 with tf.device(self.device): trainable_variables = self.surrogate.trainable_variables for epoch in tqdm(range(epochs), desc='MinMax Training', disable=disable): if (epoch + 1) % update_per_epoch == 0: self.update_surrogate(trainable_variables, self.idx_attack) gradients = self.compute_gradients(self.idx_attack) lr = C / np.sqrt(epoch + 1) self.adj_changes.assign_add(lr * gradients) self.projection() best_s = self.random_sample(sample_epochs) self.structure_flips = np.transpose(np.where(best_s > 0.)) @tf.function def update_surrogate(self, trainable_variables, idx): with tf.GradientTape() as tape: adj = self.get_perturbed_adj() adj_norm = normalize_adj_tensor(adj) logit = self.surrogate([self.tf_x, adj_norm, idx]) logit = softmax(logit) loss = self.compute_loss(logit) gradients = tape.gradient(loss, trainable_variables) self.optimizer.apply_gradients(zip(gradients, trainable_variables))
class MinMax(PGD): """MinMax cannot ensure that there is not singleton after attack.""" def process(self, surrogate, train_nodes, unlabeled_nodes=None, lr=5e-3, reset=True): super().process(surrogate, train_nodes, unlabeled_nodes, reset=False) with tf.device(self.device): self.stored_weights = tf.identity_n(self.surrogate.weights) self.optimizer = Adam(lr) if reset: self.reset() return self def reset(self): super().reset() weights = self.surrogate.weights # restore surrogate weights for w1, w2 in zip(weights, self.stored_weights): w1.assign(w2) # reset optimizer for var in self.optimizer.variables(): var.assign(tf.zeros_like(var)) return self def attack(self, num_budgets=0.05, sample_epochs=20, C=None, CW_loss=False, epochs=100, update_per_epoch=20, structure_attack=True, feature_attack=False, disable=False): super(PGD, self).attack(num_budgets, structure_attack, feature_attack) self.CW_loss = CW_loss if not C: if CW_loss: C = 0.1 else: C = 200 with tf.device(self.device): for epoch in tqdm(range(epochs), desc='MinMax Training', disable=disable): if (epoch + 1) % update_per_epoch == 0: self.update_surrogate(self.victim_nodes) gradients = self.compute_gradients(self.victim_nodes) lr = C / np.sqrt(epoch + 1) self.adj_changes.assign_add(lr * gradients) self.projection() best_s = self.random_sample(sample_epochs) self.adj_flips = np.transpose(np.where(best_s > 0.)) return self @tf.function def update_surrogate(self, victim_nodes): trainable_variables = self.surrogate.trainable_variables with tf.GradientTape() as tape: adj = self.get_perturbed_adj() loss = self.compute_loss(victim_nodes) gradients = tape.gradient(loss, trainable_variables) self.optimizer.apply_gradients(zip(gradients, trainable_variables))