📄 Paper
We propose the Zero-CoT Probe (ZCP), a black-box method for detecting data contamination in LLMs — including evasive contamination via paraphrasing. ZCP truncates the chain-of-thought (CoT) and forces the model to emit the final answer directly, exposing memorized shortcut mappings that surface-level detectors miss. Contamination strength is quantified by Contamination Confidence (
Figure 1. Reasoning masks data contamination. Under Full-CoT (top), memorization is indistinguishable from genuine reasoning. Our Zero-CoT Probe (bottom) forces the model to bypass intermediate reasoning — the model fails on clean questions but still answers contaminated ones via the learned shortcut, exposing memorization.
ZCP/
├── assets/
│ ├── CoT_truncation_illustration.pdf # Figure 1 source (PDF, for LaTeX use)
│ └── CoT_truncation_illustration.png # Figure 1 (rendered, used in this README)
│
├── dataset/
│ ├── gsm8k_train/paraphrased_dataset.jsonl # 500 (paraphrased GSM8K train)
│ ├── math/paraphrased_dataset.jsonl # 700 (paraphrased MATH, 100 × 7 types)
│ ├── omnimath/
│ │ ├── dataset_c/dataset_c.jsonl # 2,172 (Dataset C)
│ │ ├── dataset_c/paraphrased_dataset_c.jsonl # 13,032 = 6 × 2,172 (paraphrased C, for fine-tuning)
│ │ └── dataset_u/dataset_u.jsonl # 2,172 (Dataset U, held-out)
│ └── multi_domain_dataset/
│ ├── dataset_c/dataset_c.jsonl # 1,325 (Dataset C, MMLU-Pro + XFinBench)
│ ├── dataset_c/paraphrased_dataset_c.jsonl # 7,950 = 6 × 1,325 (paraphrased C)
│ └── dataset_u/dataset_u.jsonl # 1,325 (Dataset U)
│
├── statistical_significance_test.py # ZCP evaluation — open-weight models (vLLM)
├── statistical_significance_test_api_model.py # ZCP evaluation — closed-source APIs (OpenAI / Gemini / Anthropic)
│
├── clean_numerical_dataset_multi_models.py # Build reference data D̃_eval — math / MMLU-Pro / numerical / finance (generator + 2-judge consensus)
├── paraphrase_general_dataset_multi_version.py # Build evasive contamination D'_eval (--domain selects style)
│
├── math_grade.py # Math answer equivalence (sympy + LaTeX)
├── math_normalize.py # Math answer surface-form normalization
│
└── environment.yml
conda env create -f environment.yml
conda activate truncateKey dependencies: vllm, torch, transformers, peft, scipy, statsmodels, openai, anthropic, google-genai.
CUDA_VISIBLE_DEVICES=0 python statistical_significance_test.py \
--model deepseek-ai/deepseek-math-7b-rl \
--dataset-path-a dataset/math/paraphrased_dataset.jsonl \
--dataset-path-b <path/to/reference_dataset.jsonl> \
--data-type-a paraphrased \
--data-type-b modified \
--truncate-ratio 0 \
--output-dir runs/deepseek_math_zcp \
--batch-size 512 \
--gpu-memory-utilization 0.8| Argument | Description |
|---|---|
--model |
HuggingFace model ID or local path |
--lora-path |
LoRA adapter path (optional, for fine-tuned models) |
--dataset-path-a/b |
The two JSONL files ZCP compares. Pair paraphrased vs reference to probe evasive data contamination; pair original vs reference to probe standard data contamination. |
--data-type-a/b |
Field prefix used to read each JSONL row. Each value selects a <prefix>_problem / <prefix>_answer field pair: original → original_problem / original_answer; paraphrased → paraphrased_problem / answer; modified → modified_problem / modified_answer; clean → bare question / answer (no prefix). |
--truncate-ratio |
0 = Zero-CoT Probe (the ZCP setting); 1 = Full-CoT baseline. Multiple values sweep. |
--max-samples |
Cap on evaluation samples (useful for smoke tests) |
--output-dir |
Results directory |
export OPENAI_API_KEY="..."
export GEMINI_API_KEY="..."
export ANTHROPIC_API_KEY="..."
python statistical_significance_test_api_model.py \
--model gpt-4o \
--model-type openai \
--dataset-path-a dataset/math/paraphrased_dataset.jsonl \
--dataset-path-b <path/to/reference_dataset.jsonl> \
--data-type-a paraphrased \
--data-type-b modified \
--truncate-ratio 0 \
--output-dir runs/gpt4o_zcpSupports OpenAI, Google Gemini, and Anthropic Claude via --model-type {openai, google, anthropic}.
The reference data o4-mini (generator), gpt-4o-mini (GPT judge), gemini-2.0-flash-exp (Gemini judge). The paper used o3-mini + o4-mini + gemini-2.5-flash — pass --model, --verify-model-gpt, --verify-model-gemini to reproduce.
# Reference data D̃_eval (isomorphic numerical perturbation):
# --mode {paraphrased, original, both, clean} selects which fields the loader reads.
# Use --mode clean for our shipped dataset_c.jsonl / dataset_u.jsonl files (works on both omnimath and multi_domain splits).
python clean_numerical_dataset_multi_models.py \
--dataset-path dataset/omnimath/dataset_c/dataset_c.jsonl \
--mode clean \
--output-dir runs/reference_data \
--api-key $OPENAI_API_KEY \
--gemini-api-key $GEMINI_API_KEY
# Evasive contamination D'_eval (paraphrased, for fine-tuning the contaminated model):
python paraphrase_general_dataset_multi_version.py \
--local-file <path/to/original_dataset.jsonl> \
--output-dir runs/paraphrased_data \
--domain math \
--num-versions 6| Ratio | Setting |
|---|---|
0 |
Zero-CoT Probe — the ZCP setting from the paper. Model is forced to emit the final answer with no intermediate reasoning. |
1 |
Full-CoT baseline — full solution provided; forced-answer generation skipped. Used as a sanity check that |
For each ZCP truncation ratio, the scripts compute the four metrics from paper Section 3.4 and run the corresponding significance tests:
| Metric | Type | Test |
|---|---|---|
| Accuracy (Acc) | binary | McNemar's Test |
| Consistency (Con) | binary | McNemar's Test |
| First-token Probability ( |
continuous | Bootstrap (10,000 resamples) |
| All-token Probability ( |
continuous | Bootstrap (10,000 resamples) |
The resulting
| Split | Size | Use |
|---|---|---|
gsm8k_train/paraphrased_dataset.jsonl |
500 | GSM8K train, paraphrased (Section 4.1) |
math/paraphrased_dataset.jsonl |
700 | MATH benchmark, paraphrased (100 per type, Section 4.1) |
omnimath/dataset_c/dataset_c.jsonl |
2,172 | OmniMath contaminated split, Dataset C (Section 4.2) |
omnimath/dataset_c/paraphrased_dataset_c.jsonl |
13,032 | 6× paraphrased Dataset C (for fine-tuning) |
omnimath/dataset_u/dataset_u.jsonl |
2,172 | OmniMath held-out clean split, Dataset U |
multi_domain_dataset/dataset_c/dataset_c.jsonl |
1,325 | Multi-domain Dataset C (MMLU-Pro + XFinBench) |
multi_domain_dataset/dataset_c/paraphrased_dataset_c.jsonl |
7,950 | 6× paraphrased Multi-domain Dataset C |
multi_domain_dataset/dataset_u/dataset_u.jsonl |
1,325 | Multi-domain Dataset U |
Note on reference data (
$\tilde{D}_{\text{eval}}$ ). We intentionally do not release the isomorphically perturbed reference data, in order to keep it strictly clean and uncontaminated for future contamination-detection studies. We encourage users to build their own reference data withclean_numerical_dataset_multi_models.py(the generator + 2-judge consensus pipeline from paper Appendix B; small per-sample API cost). If you genuinely need our exact reference splits for direct comparison, please contact the authors.
Fine-tuning code is intentionally not included — please follow the SFT + GRPO recipe in paper Appendix E.
math_grade.py and math_normalize.py are adapted from the Hendrycks MATH release (math_equivalence.py) and extended to support additional answer formats.
If you find our work useful, please consider citing:
@misc{lan2026illusionreasoningexposingevasive,
title={The Illusion of Reasoning: Exposing Evasive Data Contamination in LLMs via Zero-CoT Truncation},
author={Yifan Lan and Yuanpu Cao and Hanyu Wang and Lu Lin and Jinghui Chen},
year={2026},
eprint={2605.21856},
archivePrefix={arXiv},
primaryClass={cs.LG},
url={https://arxiv.org/abs/2605.21856},
}This repository is released under the MIT License.