Generates a paraphrased variant of each example (question + positive answer + negative answer) while preserving its meaning, stance, relationship to the Schwartz value, and — importantly — its length. Used to test how stable a steering direction is under semantic-preserving rewrites.
The same pipeline can also produce an adversarial variant, but this README
focuses on the paraphraser.
| File | Purpose |
|---|---|
pipeline.py |
Loads the model and runs the perturbation loop (resumable). |
prompts.py |
System + paraphrase/adversarial prompt templates. |
parser.py |
Extracts the JSON fields from model output. |
config.py |
Paths, model id, and defaults (all overridable via .env). |
- Python deps:
torch,transformers,pandas,tqdm,python-dotenv,huggingface_hub. - A Hugging Face token with access to the (gated) Gemma model.
- A GPU large enough for the model.
google/gemma-4-31B-itin bf16 needs roughly ~60 GB of VRAM (or multi-GPU / offload viadevice_map="auto").
HF_TOKEN=hf_xxx # required for gated Gemma
PERTURBATION_MODEL_ID=google/gemma-4-31B-it # model to use
PERTURBATION_MAX_NEW_TOKENS=512 # plenty for short rewrites
DEVICE_MAP=auto
# Point this at the dataset you want to paraphrase:
PERTURBATION_INPUT_CSV=final_dataset_v3.csv # resolved under dataset_construction/data/
PARAPHRASE_OUTPUT_CSV=final_dataset_paraphrased.csvNotes:
PERTURBATION_INPUT_CSVis resolved relative todataset_construction/data/. To use a file elsewhere (e.g. your 200-row subset), pass an absolute path directly in code (see below) instead of relying on this variable.- Outputs are written to
dataset_construction/data/perturb_result/.
The input CSV must contain these columns:
value, question, positive_answer, negative_answer
Any other columns (id, source, …) are carried through untouched.
Option A — one perturbation type, explicit paths (recommended):
from value_stability.pipeline import PerturbationPipeline
pipe = PerturbationPipeline() # loads the model from PERTURBATION_MODEL_ID
pipe.run_single_perturbation(
"paraphrase",
input_csv="value_stability/subseting/final_dataset_200_matched.csv",
output_csv="dataset_construction/data/perturb_result/final_dataset_paraphrased.csv",
)Option B — use the configured defaults (runs paraphrase + adversarial):
python -m value_stability.pipelineThe __main__ block uses the DEBUG_* paths in config.py. Check those point
where you want before running, since by default DEBUG_INPUT_CSV is the full
dataset, not a small sample.
Three new columns are appended to a copy of the input, saved at
PARAPHRASE_OUTPUT_CSV:
paraphrased_question, paraphrased_positive_answer, paraphrased_negative_answer
The run is resumable and checkpointed per row — the output CSV is written
after every row. If it is interrupted, just run the same command again: rows that
already have a paraphrased_question are skipped and generation continues from
where it stopped.