PrivacyJailbreak/easyjailbreak/mutation/rule/Crossover.py
2025-05-15 14:10:22 +08:00

93 lines
3.7 KiB
Python

import importlib.resources
import random
import re
from easyjailbreak.mutation import MutationBase
from easyjailbreak.datasets import JailbreakDataset, Instance
from easyjailbreak.seed import SeedTemplate
class CrossOver(MutationBase):
r"""
The 'CrossOver' class serves to interlace sentences from two distinct texts.
"""
def __init__(self, attr_name='jailbreak_prompt', num_points=5,
seed_pool:JailbreakDataset=None):
r"""
Initializes the CrossOver instance.
:param ~str attr_name: The attribute name in the instance that contains the text to be mutated.
:param ~int num_points: The number of break points when crossover.
:param ~JailbreakDataset seed_pool: The default cross-over seed pool.
"""
self.attr_name = attr_name
self.num_points = num_points
self.seed_pool = seed_pool
if seed_pool is None:
seed_path = importlib.resources.files("easyjailbreak.seed") / "seed_template.json"
self.seed_pool = SeedTemplate().new_seeds(
seeds_num=10,
prompt_usage='attack',
method_list=["AutoDAN-a"],
template_file=seed_path
)
self.seed_pool = JailbreakDataset([Instance(jailbreak_prompt=prompt) for prompt in self.seed_pool])
def _get_mutated_instance(self, instance, **kwargs):
r"""
Generates a mutated instance of the given object.
:param instance: The instance to be mutated.
:param kwargs: Additional keyword arguments.
:return: A list containing the mutated instance.
"""
mutated_instances = []
seed = getattr(instance, self.attr_name)
if 'other_instance' in kwargs:
other_instance = kwargs['other_instance']
other_seed = getattr(other_instance, self.attr_name)
else:
other_instance = random.choice(self.seed_pool._dataset)
other_seed = getattr(other_instance, self.attr_name)
child1, child2 = self.crossover(seed, other_seed, self.num_points)
new_instance1 = instance.copy()
setattr(new_instance1, self.attr_name, child1)
new_instance2 = other_instance.copy()
setattr(new_instance2, self.attr_name, child2)
mutated_instances.append(new_instance1)
mutated_instances.append(new_instance2)
return mutated_instances
def crossover(self, str1, str2, num_points):
r"""
The function determines the feasible points for intertwining or crossing over.
:return: two sentences after crossover
"""
sentences1 = [s for s in re.split('(?<=[.!?])\s+', str1) if s]
sentences2 = [s for s in re.split('(?<=[.!?])\s+', str2) if s]
max_swaps = min(len(sentences1), len(sentences2)) - 1
num_swaps = min(num_points, max_swaps)
if num_swaps >= max_swaps:
return str1, str2
swap_indices = sorted(random.sample(range(1, max_swaps), num_swaps))
new_str1, new_str2 = [], []
last_swap = 0
for swap in swap_indices:
if random.choice([True, False]):
new_str1.extend(sentences1[last_swap:swap])
new_str2.extend(sentences2[last_swap:swap])
else:
new_str1.extend(sentences2[last_swap:swap])
new_str2.extend(sentences1[last_swap:swap])
last_swap = swap
if random.choice([True, False]):
new_str1.extend(sentences1[last_swap:])
new_str2.extend(sentences2[last_swap:])
else:
new_str1.extend(sentences2[last_swap:])
new_str2.extend(sentences1[last_swap:])
return ' '.join(new_str1), ' '.join(new_str2)