Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/check-formatting.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ jobs:
run: |
python -m pip install --upgrade pip setuptools wheel
python setup.py install_egg_info
pip install "click<8.1.0"
pip install -e .[test]
- name: Check code format with black and isort
run: |
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,4 @@ checkpoints/
*.csv
!tests/sample_outputs/csv_attack_log.csv
tests/test_command_line/attack_log.txt
textattack/=22.3.0
.worktrees/
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[tool.black]
line-length = 88
target-version = ["py39", "py310", "py311"]
8 changes: 4 additions & 4 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@
]
# Packages required for formatting code & running tests.
extras["test"] = [
"black==20.8b1",
"black==25.11.0",
"docformatter",
"isort==5.6.4",
"flake8",
"isort==6.1.0",
"flake8==7.3.0",
"pytest",
"pytest-xdist",
]
Expand Down Expand Up @@ -78,6 +78,6 @@
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
],
python_requires=">=3.8",
python_requires=">=3.9",
install_requires=open("requirements.txt").readlines(),
)
5 changes: 3 additions & 2 deletions tests/benchmark_leap_vs_pso.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
(LEAP's Levy-flight/adaptive-inertia/greedy mutation vs. vanilla PSO's
uniform-velocity/linear-decay/probabilistic mutation) differ.
"""

import argparse
import time

Expand All @@ -46,8 +47,8 @@

def build_pso_wordnet(model_wrapper):
"""Vanilla ParticleSwarmOptimization with LEAP2023's transformation,
constraints, and search hyperparameters, so it differs from LEAP2023
only in the search method's internals."""
constraints, and search hyperparameters, so it differs from LEAP2023 only
in the search method's internals."""
transformation = WordSwapWordNet()
constraints = [MaxModificationRate(max_rate=0.16), StopwordModification()]
goal_function = UntargetedClassification(model_wrapper)
Expand Down
9 changes: 5 additions & 4 deletions tests/test_attack_recipes.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"Benchmark" section of ``LEAP2023``'s docstring
(``textattack/attack_recipes/leap_2023.py``) for reproducible results.
"""

import pytest

from textattack.attack_recipes import LEAP2023, PSOZang2020
Expand Down Expand Up @@ -78,10 +79,10 @@ def test_leap_recipe_shares_pso_lineage_with_pso_zang_2020():

@pytest.mark.slow
def test_leap_and_pso_zang_2020_attack_without_error():
"""Run both recipes end-to-end on the same tiny sample and confirm
neither raises -- a regression guard for the LEAP search method's
perform_search wiring (mutation step, omega/velocity bookkeeping)
against the working PSOZang2020 implementation it was adapted from."""
"""Run both recipes end-to-end on the same tiny sample and confirm neither
raises -- a regression guard for the LEAP search method's perform_search
wiring (mutation step, omega/velocity bookkeeping) against the working
PSOZang2020 implementation it was adapted from."""
import transformers

from textattack import AttackArgs, Attacker
Expand Down
2 changes: 1 addition & 1 deletion tests/test_attacked_text.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ def test_window_around_index(self, attacked_text):

def test_big_window_around_index(self, attacked_text):
assert (
attacked_text.text_window_around_index(0, 10 ** 5) + "."
attacked_text.text_window_around_index(0, 10**5) + "."
) == attacked_text.text

def test_window_around_index_start(self, attacked_text):
Expand Down
4 changes: 2 additions & 2 deletions tests/test_word_embedding.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def test_embedding_paragramcf():
word_embedding = WordEmbedding.counterfitted_GLOVE_embedding()
assert pytest.approx(word_embedding[0][0]) == -0.022007
assert pytest.approx(word_embedding["fawn"][0]) == -0.022007
assert word_embedding[10 ** 9] is None
assert word_embedding[10**9] is None


@pytest.mark.skipif(not _gensim_available, reason="gensim is not installed")
Expand All @@ -39,7 +39,7 @@ def test_embedding_gensim():
word_embedding = GensimWordEmbedding(keyed_vectors)
assert pytest.approx(word_embedding[0][0]) == 1
assert pytest.approx(word_embedding["bye-bye"][0]) == -1 / np.sqrt(2)
assert word_embedding[10 ** 9] is None
assert word_embedding[10**9] is None

# test query functionality
assert pytest.approx(word_embedding.get_cos_sim(1, 3)) == 0
Expand Down
10 changes: 5 additions & 5 deletions textattack/attack.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@ def __init__(
constraints: List[Union[Constraint, PreTransformationConstraint]],
transformation: Transformation,
search_method: SearchMethod,
transformation_cache_size=2 ** 15,
constraint_cache_size=2 ** 15,
transformation_cache_size=2**15,
constraint_cache_size=2**15,
):
"""Initialize an attack object.

Expand Down Expand Up @@ -372,9 +372,9 @@ def filter_transformations(
uncached_texts.append(transformed_text)
else:
# promote transformed_text to the top of the LRU cache
self.constraints_cache[
(current_text, transformed_text)
] = self.constraints_cache[(current_text, transformed_text)]
self.constraints_cache[(current_text, transformed_text)] = (
self.constraints_cache[(current_text, transformed_text)]
)
if self.constraints_cache[(current_text, transformed_text)]:
filtered_texts.append(transformed_text)
filtered_texts += self._filter_transformations_uncached(
Expand Down
4 changes: 2 additions & 2 deletions textattack/attack_args.py
Original file line number Diff line number Diff line change
Expand Up @@ -522,8 +522,8 @@ class _CommandLineAttackArgs:
interactive: bool = False
parallel: bool = False
model_batch_size: int = 32
model_cache_size: int = 2 ** 18
constraint_cache_size: int = 2 ** 18
model_cache_size: int = 2**18
constraint_cache_size: int = 2**18

@classmethod
def _add_parser_args(cls, parser):
Expand Down
2 changes: 1 addition & 1 deletion textattack/attack_recipes/bad_characters_2021.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def build(
perturbs=1,
popsize=32,
maxiter=10,
**goal_function_kwargs
**goal_function_kwargs,
):
"""Builds an imperceptible attack instance.
Expand Down
1 change: 1 addition & 0 deletions textattack/attack_recipes/leap_2023.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
(LEAP: Efficient and Automated Test Method for NLP Software)
"""

from textattack import Attack
from textattack.constraints.pre_transformation import (
MaxModificationRate,
Expand Down
2 changes: 1 addition & 1 deletion textattack/constraints/grammaticality/cola.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def __init__(

self.max_diff = max_diff
self.model_name = model_name
self._reference_score_cache = lru.LRU(2 ** 10)
self._reference_score_cache = lru.LRU(2**10)
model = AutoModelForSequenceClassification.from_pretrained(model_name)
tokenizer = AutoTokenizer.from_pretrained(model_name)
self.model = HuggingFaceModelWrapper(model, tokenizer)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def __init__(self):
self.sess, self.graph, self.PBTXT_PATH, self.CKPT_PATH
)

self.lm_cache = lru.LRU(2 ** 18)
self.lm_cache = lru.LRU(2**18)

def clear_cache(self):
self.lm_cache.clear()
Expand Down
2 changes: 1 addition & 1 deletion textattack/constraints/grammaticality/part_of_speech.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def __init__(
self.language_nltk = language_nltk
self.language_stanza = language_stanza

self._pos_tag_cache = lru.LRU(2 ** 14)
self._pos_tag_cache = lru.LRU(2**14)
if tagger_type == "flair":
if tagset == "universal":
self._flair_pos_tagger = SequenceTagger.load("upos-fast")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def __init__(
threshold=0.7,
metric="cosine",
model_name="bert-base-nli-stsb-mean-tokens",
**kwargs
**kwargs,
):
super().__init__(threshold=threshold, metric=metric, **kwargs)
self.model = sentence_transformers.SentenceTransformer(model_name)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def __init__(self, embedding=None, **kwargs):
def clear_cache(self):
self._get_thought_vector.cache_clear()

@functools.lru_cache(maxsize=2 ** 10)
@functools.lru_cache(maxsize=2**10)
def _get_thought_vector(self, text):
"""Sums the embeddings of all the words in ``text`` into a "thought
vector"."""
Expand Down
2 changes: 1 addition & 1 deletion textattack/goal_functions/goal_function.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def __init__(
use_cache=True,
query_budget=float("inf"),
model_batch_size=32,
model_cache_size=2 ** 20,
model_cache_size=2**20,
allow_skip=True,
):
validators.validate_model_goal_function_compatibility(
Expand Down
2 changes: 1 addition & 1 deletion textattack/goal_functions/text/minimize_bleu.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def extra_repr_keys(self):
return ["maximizable", "target_bleu"]


@functools.lru_cache(maxsize=2 ** 12)
@functools.lru_cache(maxsize=2**12)
def get_bleu(a, b):
ref = a.words
hyp = b.words
Expand Down
4 changes: 2 additions & 2 deletions textattack/goal_functions/text/non_overlapping_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,12 @@ def _get_score(self, model_output, _):
return num_words_diff / len(get_words_cached(self.ground_truth_output))


@functools.lru_cache(maxsize=2 ** 12)
@functools.lru_cache(maxsize=2**12)
def get_words_cached(s):
return np.array(words_from_text(s))


@functools.lru_cache(maxsize=2 ** 12)
@functools.lru_cache(maxsize=2**12)
def word_difference_score(s1, s2):
"""Returns the number of words that are non-overlapping between s1 and
s2."""
Expand Down
8 changes: 4 additions & 4 deletions textattack/metrics/attack_metrics/words_perturbed.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def calculate(self, results):
self.total_attacks = len(self.results)
self.all_num_words = np.zeros(len(self.results))
self.perturbed_word_percentages = np.zeros(len(self.results))
self.num_words_changed_until_success = np.zeros(2 ** 16)
self.num_words_changed_until_success = np.zeros(2**16)
self.max_words_changed = 0

for i, result in enumerate(self.results):
Expand Down Expand Up @@ -65,9 +65,9 @@ def calculate(self, results):
self.all_metrics["avg_word_perturbed"] = self.avg_number_word_perturbed_num()
self.all_metrics["avg_word_perturbed_perc"] = self.avg_perturbation_perc()
self.all_metrics["max_words_changed"] = self.max_words_changed
self.all_metrics[
"num_words_changed_until_success"
] = self.num_words_changed_until_success
self.all_metrics["num_words_changed_until_success"] = (
self.num_words_changed_until_success
)

return self.all_metrics

Expand Down
6 changes: 3 additions & 3 deletions textattack/models/wrappers/remote_model_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@


class RemoteModelWrapper(ModelWrapper):
"""This model wrapper queries a remote model with a list of text inputs.
It sends each input to a remote HTTP endpoint provided in ``api_url``
and parses the JSON response into class scores.
"""This model wrapper queries a remote model with a list of text inputs. It
sends each input to a remote HTTP endpoint provided in ``api_url`` and
parses the JSON response into class scores.

Since the request and response format of a remote model is
API-specific, ``request_fn`` and ``response_fn`` can be provided to
Expand Down
45 changes: 26 additions & 19 deletions textattack/search_methods/particle_swarm_optimization.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,27 +83,30 @@ def _equal(self, a, b):
def _initialize_velocities(self, num_words):
"""Return the initial ``(pop_size, num_words)`` velocity matrix.

Overridable so subclasses (e.g. LEAP) can use a different sampling
scheme for the initial per-particle velocity.
Overridable so subclasses (e.g. LEAP) can use a different
sampling scheme for the initial per-particle velocity.
"""
v_init = np.random.uniform(-self.v_max, self.v_max, self.pop_size)
return np.array(
[[v_init[t] for _ in range(num_words)] for t in range(self.pop_size)]
)

def _pre_iteration_setup(self, population):
"""Hook called once, after the initial population/elites are built
and before the main iteration loop starts. No-op by default;
overridable for subclasses that need to cache statistics (e.g.
fitness mean/min) computed from the initial population."""
"""Hook called once, after the initial population/elites are built and
before the main iteration loop starts.

No-op by default; overridable for subclasses that need to cache
statistics (e.g. fitness mean/min) computed from the initial
population.
"""

def _compute_omega(self, i, population):
"""Return the per-particle inertia weight for iteration `i`, as an
array of length ``len(population)``.

The base implementation applies the same linearly-decaying weight
to every particle; overridable for subclasses (e.g. LEAP) that
instead compute a per-particle, fitness-adaptive weight.
The base implementation applies the same linearly-decaying
weight to every particle; overridable for subclasses (e.g. LEAP)
that instead compute a per-particle, fitness-adaptive weight.
"""
omega = (self.omega_1 - self.omega_2) * (
self.max_iters - i
Expand All @@ -112,17 +115,21 @@ def _compute_omega(self, i, population):

def _compute_turn_prob(self, velocities_k):
"""Convert a particle's per-word velocities into per-word turn
probabilities. The base implementation treats each word
independently via sigmoid; overridable for subclasses (e.g. LEAP)
that instead normalize turn probability across the whole sentence.
probabilities.

The base implementation treats each word independently via
sigmoid; overridable for subclasses (e.g. LEAP) that instead
normalize turn probability across the whole sentence.
"""
return utils.sigmoid(velocities_k)

def _compute_change_ratio(self, pop_member, local_elite, initial_result):
"""Return the change-rate used to decide whether `pop_member`
undergoes mutation this iteration. The base implementation measures
drift from the original input; overridable for subclasses (e.g.
LEAP) that instead measure drift from the particle's local elite.
"""Return the change-rate used to decide whether `pop_member` undergoes
mutation this iteration.

The base implementation measures drift from the original input;
overridable for subclasses (e.g. LEAP) that instead measure
drift from the particle's local elite.
"""
return initial_result.attacked_text.words_diff_ratio(pop_member.attacked_text)

Expand Down Expand Up @@ -166,9 +173,9 @@ def _turn(self, source_text, target_text, prob, original_text):
& 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"]
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
Expand Down
7 changes: 5 additions & 2 deletions textattack/search_methods/particle_swarm_optimization_leap.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,11 @@ class ParticleSwarmOptimizationLEAP(ParticleSwarmOptimization):
per-particle-adaptive-inertia variant of the Particle Swarm Optimization
(PSO) algorithm implemented by the parent class
:class:`~textattack.search_methods.ParticleSwarmOptimization` (used by
:class:`~textattack.attack_recipes.PSOZang2020`). See the module-level
docstring above for what specifically differs from the parent class."""
:class:`~textattack.attack_recipes.PSOZang2020`).
See the module-level docstring above for what specifically differs
from the parent class.
"""

def _perturb(self, pop_member, original_result):
"""LEAP's mutation step: replace `pop_member` with the single best
Expand Down
2 changes: 1 addition & 1 deletion textattack/shared/utils/strings.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def words_from_text(s, words_to_ignore=[]):
"""Lowercases a string, removes all non-alphanumeric characters, and splits
into words."""
try:
if re.search("[\u4e00-\u9FFF]", s):
if re.search("[\u4e00-\u9fff]", s):
seg_list = jieba.cut(s, cut_all=False)
s = " ".join(seg_list)
else:
Expand Down
5 changes: 4 additions & 1 deletion textattack/shared/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@
r"^textattack.models.helpers.word_cnn_for_classification.*",
r"^transformers.modeling_\w*\.\w*ForSequenceClassification$",
],
(NonOverlappingOutput, MinimizeBleu,): [
(
NonOverlappingOutput,
MinimizeBleu,
): [
r"^textattack.models.helpers.t5_for_text_to_text.*",
],
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def __init__(
confidence_score=0.7,
language="en",
consistent=False,
**kwargs
**kwargs,
):
"""Transforms an input by replacing names of recognized name entity.
Expand Down
Loading
Loading