def _initialize_population(self, initial_result, pop_size):
        """
        Initialize a population of size `pop_size` with `initial_result`
        Args:
            initial_result (GoalFunctionResult): Original text
            pop_size (int): size of population
        Returns:
            population as `list[PopulationMember]`
        """
        words = initial_result.attacked_text.words
        # For IGA, `num_replacements_left` represents the number of times the word at each index can be modified
        num_replacements_left = np.array(
            [self.max_replace_times_per_index] * len(words)
        )
        population = []

        # IGA initializes the first population by replacing each word by its optimal synonym
        for idx in range(len(words)):
            pop_member = PopulationMember(
                initial_result.attacked_text,
                initial_result,
                attributes={"num_replacements_left": np.copy(num_replacements_left)},
            )
            pop_member = self._perturb(pop_member, initial_result, index=idx)
            population.append(pop_member)

        return population[:pop_size]
Пример #2
0
    def _crossover(self, pop_member1, pop_member2, original_text):
        """Generates a crossover between pop_member1 and pop_member2.

        If the child fails to satisfy the constraints, we re-try crossover for a fix number of times,
        before taking one of the parents at random as the resulting child.
        Args:
            pop_member1 (PopulationMember): The first population member.
            pop_member2 (PopulationMember): The second population member.
            original_text (AttackedText): Original text
        Returns:
            A population member containing the crossover.
        """
        x1_text = pop_member1.attacked_text
        x2_text = pop_member2.attacked_text

        num_tries = 0
        passed_constraints = False
        while num_tries < self.max_crossover_retries + 1:
            new_text, attributes = self._crossover_operation(
                pop_member1, pop_member2)

            replaced_indices = new_text.attack_attrs["newly_modified_indices"]
            new_text.attack_attrs["modified_indices"] = (
                x1_text.attack_attrs["modified_indices"] -
                replaced_indices) | (x2_text.attack_attrs["modified_indices"]
                                     & replaced_indices)

            if "last_transformation" in x1_text.attack_attrs:
                new_text.attack_attrs[
                    "last_transformation"] = x1_text.attack_attrs[
                        "last_transformation"]
            elif "last_transformation" in x2_text.attack_attrs:
                new_text.attack_attrs[
                    "last_transformation"] = x2_text.attack_attrs[
                        "last_transformation"]

            if self.post_crossover_check:
                passed_constraints = self._post_crossover_check(
                    new_text, x1_text, x2_text, original_text)

            if not self.post_crossover_check or passed_constraints:
                break

            num_tries += 1

        if self.post_crossover_check and not passed_constraints:
            # If we cannot find a child that passes the constraints,
            # we just randomly pick one of the parents to be the child for the next iteration.
            pop_mem = pop_member1 if np.random.uniform() < 0.5 else pop_member2
            return pop_mem
        else:
            new_results, self._search_over = self.get_goal_results([new_text])
            return PopulationMember(new_text,
                                    result=new_results[0],
                                    attributes=attributes)
 def _modify_population_member(self, pop_member, new_text, new_result, word_idx):
     """Modify `pop_member` by returning a new copy with `new_text`,
     `new_result`, and `num_replacements_left` altered appropriately for
     given `word_idx`"""
     num_replacements_left = np.copy(pop_member.attributes["num_replacements_left"])
     num_replacements_left[word_idx] -= 1
     return PopulationMember(
         new_text,
         result=new_result,
         attributes={"num_replacements_left": num_replacements_left},
     )
Пример #4
0
 def _modify_population_member(self, pop_member, new_text, new_result,
                               word_idx):
     """Modify `pop_member` by returning a new copy with `new_text`,
     `new_result`, and `num_candidate_transformations` altered appropriately
     for given `word_idx`"""
     num_candidate_transformations = np.copy(
         pop_member.attributes["num_candidate_transformations"])
     num_candidate_transformations[word_idx] = 0
     return PopulationMember(
         new_text,
         result=new_result,
         attributes={
             "num_candidate_transformations": num_candidate_transformations
         },
     )
 def _initialize_population(self, initial_result, pop_size):
     """
     Initialize a population of size `pop_size` with `initial_result`
     Args:
         initial_result (GoalFunctionResult): Original text
         pop_size (int): size of population
     Returns:
         population as `list[PopulationMember]`
     """
     best_neighbors, prob_list = self._get_best_neighbors(
         initial_result, initial_result)
     population = []
     for _ in range(pop_size):
         # Mutation step
         random_result = np.random.choice(best_neighbors, 1, p=prob_list)[0]
         population.append(
             PopulationMember(random_result.attacked_text, random_result))
     return population
Пример #6
0
    def _initialize_population(self, initial_result, pop_size):
        """
        Initialize a population of size `pop_size` with `initial_result`
        Args:
            initial_result (GoalFunctionResult): Original text
            pop_size (int): size of population
        Returns:
            population as `list[PopulationMember]`
        """
        words = initial_result.attacked_text.words
        num_candidate_transformations = np.zeros(len(words))
        transformed_texts = self.get_transformations(
            initial_result.attacked_text,
            original_text=initial_result.attacked_text)
        for transformed_text in transformed_texts:
            diff_idx = next(
                iter(transformed_text.attack_attrs["newly_modified_indices"]))
            num_candidate_transformations[diff_idx] += 1

        # Just b/c there are no replacements now doesn't mean we never want to select the word for perturbation
        # Therefore, we give small non-zero probability for words with no replacements
        # Epsilon is some small number to approximately assign small probability
        min_num_candidates = np.amin(num_candidate_transformations)
        epsilon = max(1, int(min_num_candidates * 0.1))
        for i in range(len(num_candidate_transformations)):
            num_candidate_transformations[i] = max(
                num_candidate_transformations[i], epsilon)

        population = []
        for _ in range(pop_size):
            pop_member = PopulationMember(
                initial_result.attacked_text,
                initial_result,
                attributes={
                    "num_candidate_transformations":
                    np.copy(num_candidate_transformations)
                },
            )
            # Perturb `pop_member` in-place
            pop_member = self._perturb(pop_member, initial_result)
            population.append(pop_member)

        return population
    def _turn(self, source_text, target_text, prob, original_text):
        """
        Based on given probabilities, "move" to `target_text` from `source_text`
        Args:
            source_text (PopulationMember): Text we start from.
            target_text (PopulationMember): Text we want to move to.
            prob (np.array[float]): Turn probability for each word.
            original_text (AttackedText): Original text for constraint check if `self.post_turn_check=True`.
        Returns:
            New `Position` that we moved to (or if we fail to move, same as `source_text`)
        """
        assert len(source_text.words) == len(
            target_text.words), "Word length mismatch for turn operation."
        assert len(source_text.words) == len(
            prob), "Length mismatch for words and probability list."
        len_x = len(source_text.words)

        num_tries = 0
        passed_constraints = False
        while num_tries < self.max_turn_retries + 1:
            indices_to_replace = []
            words_to_replace = []
            for i in range(len_x):
                if np.random.uniform() < prob[i]:
                    indices_to_replace.append(i)
                    words_to_replace.append(target_text.words[i])
            new_text = source_text.attacked_text.replace_words_at_indices(
                indices_to_replace, words_to_replace)
            indices_to_replace = set(indices_to_replace)
            new_text.attack_attrs["modified_indices"] = (
                source_text.attacked_text.attack_attrs["modified_indices"] -
                indices_to_replace) | (
                    target_text.attacked_text.attack_attrs["modified_indices"]
                    & indices_to_replace)
            if "last_transformation" in source_text.attacked_text.attack_attrs:
                new_text.attack_attrs[
                    "last_transformation"] = source_text.attacked_text.attack_attrs[
                        "last_transformation"]

            if not self.post_turn_check or (new_text.words
                                            == source_text.words):
                break

            if "last_transformation" in new_text.attack_attrs:
                passed_constraints = self._check_constraints(
                    new_text,
                    source_text.attacked_text,
                    original_text=original_text)
            else:
                passed_constraints = True

            if passed_constraints:
                break

            num_tries += 1

        if self.post_turn_check and not passed_constraints:
            # If we cannot find a turn that passes the constraints, we do not move.
            return source_text
        else:
            return PopulationMember(new_text)