gopiium commited on
Commit
ba866d1
·
verified ·
1 Parent(s): 825313b

Upload folder using huggingface_hub

Browse files
.gitattributes CHANGED
@@ -33,3 +33,10 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ datasets/core/cc_news/cc_news-llama2_13.jsonl filter=lfs diff=lfs merge=lfs -text
37
+ datasets/core/cnn/cnn-llama2_13.jsonl filter=lfs diff=lfs merge=lfs -text
38
+ datasets/core/pubmed/pubmed-llama2_13.jsonl filter=lfs diff=lfs merge=lfs -text
39
+ datasets/robustness/open_orca/carl-sagan-llama2-13b-chat.jsonl filter=lfs diff=lfs merge=lfs -text
40
+ datasets/robustness/open_orca/default-llama2-13b-chat.jsonl filter=lfs diff=lfs merge=lfs -text
41
+ datasets/robustness/open_orca/no-robotic-words-llama2-13b-chat.jsonl filter=lfs diff=lfs merge=lfs -text
42
+ datasets/robustness/open_orca/pirate-llama2-13b-chat.jsonl filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py,cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ #Pipfile.lock
96
+
97
+ # poetry
98
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
102
+ #poetry.lock
103
+
104
+ # pdm
105
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
106
+ #pdm.lock
107
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
108
+ # in version control.
109
+ # https://pdm.fming.dev/#use-with-ide
110
+ .pdm.toml
111
+
112
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
113
+ __pypackages__/
114
+
115
+ # Celery stuff
116
+ celerybeat-schedule
117
+ celerybeat.pid
118
+
119
+ # SageMath parsed files
120
+ *.sage.py
121
+
122
+ # Environments
123
+ .env
124
+ .venv
125
+ env/
126
+ venv/
127
+ ENV/
128
+ env.bak/
129
+ venv.bak/
130
+
131
+ # Spyder project settings
132
+ .spyderproject
133
+ .spyproject
134
+
135
+ # Rope project settings
136
+ .ropeproject
137
+
138
+ # mkdocs documentation
139
+ /site
140
+
141
+ # mypy
142
+ .mypy_cache/
143
+ .dmypy.json
144
+ dmypy.json
145
+
146
+ # Pyre type checker
147
+ .pyre/
148
+
149
+ # pytype static type analyzer
150
+ .pytype/
151
+
152
+ # Cython debug symbols
153
+ cython_debug/
154
+
155
+ # PyCharm
156
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
157
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
158
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
159
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
160
+ #.idea/
161
+
162
+ samples/
163
+
164
+ **.*ipynb
.gradio/certificate.pem ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ -----BEGIN CERTIFICATE-----
2
+ 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
31
+ -----END CERTIFICATE-----
LICENSE.md ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2023, Abhimanyu Hans, Avi Schwarzschild, Tom Goldstein
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
README.md CHANGED
@@ -1,12 +1,91 @@
1
  ---
2
  title: Binoculars
3
- emoji: 📊
4
- colorFrom: blue
5
- colorTo: yellow
6
- sdk: gradio
7
- sdk_version: 5.13.1
8
  app_file: app.py
9
- pinned: false
 
10
  ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
1
  ---
2
  title: Binoculars
 
 
 
 
 
3
  app_file: app.py
4
+ sdk: gradio
5
+ sdk_version: 5.13.0
6
  ---
7
+ # <img src="./assets/bino-logo.svg" width=40 style="padding-top: 0px"/> Binoculars: Zero-Shot Detection of LLM-Generated Text [[paper]](https://arxiv.org/abs/2401.12070)[[demo]](https://huggingface.co/spaces/tomg-group-umd/Binoculars)
8
+
9
+ <p align="center">
10
+ <img src="assets/binoculars.jpg" width="300" height="300" alt="ool Binoculars with Falcon on Top">
11
+ </p>
12
+
13
+ We introduce Binoculars, a state-of-the-art method for detecting AI-generated text. Binoculars is a
14
+ zero-shot and domain-agnostic (requires no training data) method. It is based on a simple idea: most
15
+ decoder-only, causal language models have a huge overlap in pretraining datasets, for e.g. Common Crawl, Pile, etc.
16
+ More details about the method and results can be found in our paper **Spotting LLMs with Binoculars: Zero-Shot
17
+ Detection of Machine-Generated Text**.
18
+
19
+ ## Getting Started
20
+
21
+ ### Installation
22
+
23
+ To run the implementation of Binoculars, you can clone this repository and install the package using pip. This code was
24
+ developed and tested on Python This code was developed and tested with Python 3.9. To install the package, run the
25
+ following commands:
26
+
27
+ ```bash
28
+ $ git clone https://github.com/ahans30/Binoculars.git
29
+ $ cd Binoculars
30
+ $ pip install -e .
31
+ ```
32
+
33
+ ### Usage
34
+
35
+ Please note, this implementation comes with a fixed global threshold that is used to classify the input as AI-generated
36
+ or not. This threshold is selected using _Falcon-7B_ and _Falcon-7B-Instruct_ models for scoring. If you want to
37
+ use different scoring models, you can pass it as an argument to the `Binoculars` class. Please read the paper for more
38
+ details about the Binoculars work.
39
+
40
+ To detect AI-generated text, please use the following code snippet:
41
+
42
+ ```python
43
+ from binoculars import Binoculars
44
+
45
+ bino = Binoculars()
46
+
47
+ # ChatGPT (GPT-4) output when prompted with “Can you write a few sentences about a capybara that is an astrophysicist?"
48
+ sample_string = '''Dr. Capy Cosmos, a capybara unlike any other, astounded the scientific community with his
49
+ groundbreaking research in astrophysics. With his keen sense of observation and unparalleled ability to interpret
50
+ cosmic data, he uncovered new insights into the mysteries of black holes and the origins of the universe. As he
51
+ peered through telescopes with his large, round eyes, fellow researchers often remarked that it seemed as if the
52
+ stars themselves whispered their secrets directly to him. Dr. Cosmos not only became a beacon of inspiration to
53
+ aspiring scientists but also proved that intellect and innovation can be found in the most unexpected of creatures.'''
54
+
55
+ print(bino.compute_score(sample_string)) # 0.75661373
56
+ print(bino.predict(sample_string)) # 'Most likely AI-Generated'
57
+ ```
58
+
59
+ In the above code, user can also pass a `list` of `str` to `compute_score` and `predict` methods to get results for
60
+ the entire batch of samples.
61
+
62
+ ### Demo
63
+
64
+ We have also made a demo available to predict AI-generated text interactively with a simple UI
65
+ using [gradio](https://github.com/gradio-app/gradio). You can run the demo using the following command:
66
+
67
+ ```bash
68
+ $ python app.py
69
+ ```
70
+
71
+ ## Limitations
72
+
73
+ All AI-generated text detectors aim for accuracy, but none are perfect and can have multiple failure modes (e.g.,
74
+ Binoculars is more proficient in detecting English language text compared to other languages). This implementation is
75
+ for academic purposes only and should not be considered as a consumer product. We also strongly caution against using
76
+ Binoculars (or any detector) without human supervision.
77
+
78
+ ## Cite our work
79
+
80
+ If you find this work useful, please cite our paper:
81
 
82
+ ```bibtex
83
+ @misc{hans2024spotting,
84
+ title={Spotting LLMs With Binoculars: Zero-Shot Detection of Machine-Generated Text},
85
+ author={Abhimanyu Hans and Avi Schwarzschild and Valeriia Cherepanova and Hamid Kazemi and Aniruddha Saha and Micah Goldblum and Jonas Geiping and Tom Goldstein},
86
+ year={2024},
87
+ eprint={2401.12070},
88
+ archivePrefix={arXiv},
89
+ primaryClass={cs.CL}
90
+ }
91
+ ```
app.py ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ from demo.demo import app
2
+
3
+ if __name__ == "__main__":
4
+ # Launch the Gradio interface
5
+ app.launch(show_api=False, debug=True, share=True)
assets/bino-logo.svg ADDED
assets/binoculars.jpg ADDED
binoculars/__init__.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ from .detector import Binoculars
2
+
3
+ __all__ = ["Binoculars"]
binoculars/detector.py ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Union
2
+
3
+ import os
4
+ import numpy as np
5
+ import torch
6
+ import transformers
7
+ from transformers import AutoModelForCausalLM, AutoTokenizer
8
+
9
+ from .utils import assert_tokenizer_consistency
10
+ from .metrics import perplexity, entropy
11
+
12
+ torch.set_grad_enabled(False)
13
+
14
+ huggingface_config = {
15
+ # Only required for private models from Huggingface (e.g. LLaMA models)
16
+ "TOKEN": os.environ.get("HF_TOKEN", None)
17
+ }
18
+
19
+ # selected using Falcon-7B and Falcon-7B-Instruct at bfloat16
20
+ BINOCULARS_ACCURACY_THRESHOLD = 0.9015310749276843 # optimized for f1-score
21
+ BINOCULARS_FPR_THRESHOLD = 0.8536432310785527 # optimized for low-fpr [chosen at 0.01%]
22
+
23
+ DEVICE_1 = "cuda:0" if torch.cuda.is_available() else "cpu"
24
+ DEVICE_2 = "cuda:1" if torch.cuda.device_count() > 1 else DEVICE_1
25
+
26
+
27
+ class Binoculars(object):
28
+ def __init__(self,
29
+ observer_name_or_path: str = "tiiuae/falcon-7b",
30
+ performer_name_or_path: str = "tiiuae/falcon-7b-instruct",
31
+ use_bfloat16: bool = True,
32
+ max_token_observed: int = 512,
33
+ mode: str = "low-fpr",
34
+ ) -> None:
35
+ assert_tokenizer_consistency(observer_name_or_path, performer_name_or_path)
36
+
37
+ self.change_mode(mode)
38
+ self.observer_model = AutoModelForCausalLM.from_pretrained(observer_name_or_path,
39
+ device_map="auto",
40
+ torch_dtype="auto",
41
+ token=huggingface_config["TOKEN"]
42
+ )
43
+ self.performer_model = AutoModelForCausalLM.from_pretrained(performer_name_or_path,
44
+ device_map="auto",
45
+ torch_dtype="auto",
46
+ token=huggingface_config["TOKEN"]
47
+ )
48
+ self.observer_model.eval()
49
+ self.performer_model.eval()
50
+
51
+ self.tokenizer = AutoTokenizer.from_pretrained(observer_name_or_path)
52
+ if not self.tokenizer.pad_token:
53
+ self.tokenizer.pad_token = self.tokenizer.eos_token
54
+ self.max_token_observed = max_token_observed
55
+
56
+ def change_mode(self, mode: str) -> None:
57
+ if mode == "low-fpr":
58
+ self.threshold = BINOCULARS_FPR_THRESHOLD
59
+ elif mode == "accuracy":
60
+ self.threshold = BINOCULARS_ACCURACY_THRESHOLD
61
+ else:
62
+ raise ValueError(f"Invalid mode: {mode}")
63
+
64
+ def _tokenize(self, batch: list[str]) -> transformers.BatchEncoding:
65
+ batch_size = len(batch)
66
+ encodings = self.tokenizer(
67
+ batch,
68
+ return_tensors="pt",
69
+ padding="longest" if batch_size > 1 else False,
70
+ truncation=True,
71
+ max_length=self.max_token_observed,
72
+ return_token_type_ids=False).to(self.observer_model.device)
73
+ return encodings
74
+
75
+ @torch.inference_mode()
76
+ def _get_logits(self, encodings: transformers.BatchEncoding) -> torch.Tensor:
77
+ observer_logits = self.observer_model(**encodings.to(DEVICE_1)).logits
78
+ performer_logits = self.performer_model(**encodings.to(DEVICE_2)).logits
79
+ if DEVICE_1 != "cpu":
80
+ torch.cuda.synchronize()
81
+ return observer_logits, performer_logits
82
+
83
+ def compute_score(self, input_text: Union[list[str], str]) -> Union[float, list[float]]:
84
+ batch = [input_text] if isinstance(input_text, str) else input_text
85
+ encodings = self._tokenize(batch)
86
+ observer_logits, performer_logits = self._get_logits(encodings)
87
+ ppl = perplexity(encodings, performer_logits)
88
+ x_ppl = entropy(observer_logits.to(DEVICE_1), performer_logits.to(DEVICE_1),
89
+ encodings.to(DEVICE_1), self.tokenizer.pad_token_id)
90
+ binoculars_scores = ppl / x_ppl
91
+ binoculars_scores = binoculars_scores.tolist()
92
+ return binoculars_scores[0] if isinstance(input_text, str) else binoculars_scores
93
+
94
+ def predict(self, input_text: Union[list[str], str]) -> Union[list[str], str]:
95
+ binoculars_scores = np.array(self.compute_score(input_text))
96
+ pred = np.where(binoculars_scores < self.threshold,
97
+ "Most likely AI-generated",
98
+ "Most likely human-generated"
99
+ ).tolist()
100
+ return pred
binoculars/metrics.py ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import torch
3
+ import transformers
4
+
5
+ ce_loss_fn = torch.nn.CrossEntropyLoss(reduction="none")
6
+ softmax_fn = torch.nn.Softmax(dim=-1)
7
+
8
+
9
+ def perplexity(encoding: transformers.BatchEncoding,
10
+ logits: torch.Tensor,
11
+ median: bool = False,
12
+ temperature: float = 1.0):
13
+ shifted_logits = logits[..., :-1, :].contiguous() / temperature
14
+ shifted_labels = encoding.input_ids[..., 1:].contiguous()
15
+ shifted_attention_mask = encoding.attention_mask[..., 1:].contiguous()
16
+
17
+ if median:
18
+ ce_nan = (ce_loss_fn(shifted_logits.transpose(1, 2), shifted_labels).
19
+ masked_fill(~shifted_attention_mask.bool(), float("nan")))
20
+ ppl = np.nanmedian(ce_nan.cpu().float().numpy(), 1)
21
+
22
+ else:
23
+ ppl = (ce_loss_fn(shifted_logits.transpose(1, 2), shifted_labels) *
24
+ shifted_attention_mask).sum(1) / shifted_attention_mask.sum(1)
25
+ ppl = ppl.to("cpu").float().numpy()
26
+
27
+ return ppl
28
+
29
+
30
+ def entropy(p_logits: torch.Tensor,
31
+ q_logits: torch.Tensor,
32
+ encoding: transformers.BatchEncoding,
33
+ pad_token_id: int,
34
+ median: bool = False,
35
+ sample_p: bool = False,
36
+ temperature: float = 1.0):
37
+ vocab_size = p_logits.shape[-1]
38
+ total_tokens_available = q_logits.shape[-2]
39
+ p_scores, q_scores = p_logits / temperature, q_logits / temperature
40
+
41
+ p_proba = softmax_fn(p_scores).view(-1, vocab_size)
42
+
43
+ if sample_p:
44
+ p_proba = torch.multinomial(p_proba.view(-1, vocab_size), replacement=True, num_samples=1).view(-1)
45
+
46
+ q_scores = q_scores.view(-1, vocab_size)
47
+
48
+ ce = ce_loss_fn(input=q_scores, target=p_proba).view(-1, total_tokens_available)
49
+ padding_mask = (encoding.input_ids != pad_token_id).type(torch.uint8)
50
+
51
+ if median:
52
+ ce_nan = ce.masked_fill(~padding_mask.bool(), float("nan"))
53
+ agg_ce = np.nanmedian(ce_nan.cpu().float().numpy(), 1)
54
+ else:
55
+ agg_ce = (((ce * padding_mask).sum(1) / padding_mask.sum(1)).to("cpu").float().numpy())
56
+
57
+ return agg_ce
binoculars/utils.py ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ from transformers import AutoTokenizer
2
+
3
+
4
+ def assert_tokenizer_consistency(model_id_1, model_id_2):
5
+ identical_tokenizers = (
6
+ AutoTokenizer.from_pretrained(model_id_1).vocab
7
+ == AutoTokenizer.from_pretrained(model_id_2).vocab
8
+ )
9
+ if not identical_tokenizers:
10
+ raise ValueError(f"Tokenizers are not identical for {model_id_1} and {model_id_2}.")
datasets/core/cc_news/cc_news-falcon7.jsonl ADDED
The diff for this file is too large to render. See raw diff
 
datasets/core/cc_news/cc_news-llama2_13.jsonl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9226f42549fbdab8f8443e147b733a28afb7cedc581a3014617163426dbed376
3
+ size 38526357
datasets/core/cnn/cnn-falcon7.jsonl ADDED
The diff for this file is too large to render. See raw diff
 
datasets/core/cnn/cnn-llama2_13.jsonl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:73a7c9529114b05efdf9c938f5c359357f4cc2a1177737e7630efba7a5539594
3
+ size 20188348
datasets/core/pubmed/pubmed-falcon7.jsonl ADDED
The diff for this file is too large to render. See raw diff
 
datasets/core/pubmed/pubmed-llama2_13.jsonl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8555120ad5318ec6f4f8572dd36976d6721ab911a1516a5d26d43fd3fa885760
3
+ size 52871752
datasets/robustness/open_orca/carl-sagan-llama2-13b-chat.jsonl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7d721347290b901ae9825c89ca8809d1f74f31de28d25d39be78b7e414380cbb
3
+ size 19395215
datasets/robustness/open_orca/default-llama2-13b-chat.jsonl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6ea54239f48f2f5b5e7d2bd477c8a388bea316014aeaa6578a464e592a42bc0e
3
+ size 18329664
datasets/robustness/open_orca/no-robotic-words-llama2-13b-chat.jsonl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4cda05c16a550baf7cb02eeb7252b4184877d7a833304a9023c63a972c703a37
3
+ size 16006774
datasets/robustness/open_orca/pirate-llama2-13b-chat.jsonl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:36364c4cd497ffc6c7c7e45c0118987020f333e786838740c125a66631d8acd0
3
+ size 17366901
demo/demo.py ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ __all__ = ["app"]
2
+
3
+ import gradio as gr
4
+ from binoculars import Binoculars
5
+
6
+ BINO = Binoculars()
7
+ TOKENIZER = BINO.tokenizer
8
+ MINIMUM_TOKENS = 64
9
+
10
+
11
+ def count_tokens(text):
12
+ return len(TOKENIZER(text).input_ids)
13
+
14
+
15
+ def run_detector(input_str):
16
+ if count_tokens(input_str) < MINIMUM_TOKENS:
17
+ gr.Warning(f"Too short length. Need minimum {MINIMUM_TOKENS} tokens to run Binoculars.")
18
+ return ""
19
+ return f"{BINO.predict(input_str)}"
20
+
21
+
22
+ def change_mode(mode):
23
+ if mode == "Low False Positive Rate":
24
+ BINO.change_mode("low-fpr")
25
+ elif mode == "High Accuracy":
26
+ BINO.change_mode("accuracy")
27
+ else:
28
+ gr.Error(f"Invalid mode selected.")
29
+ return mode
30
+
31
+
32
+ # def load_set(progress=gr.Progress()):
33
+ # tokens = [None] * 24
34
+ # for count in progress.tqdm(tokens, desc="Counting Tokens..."):
35
+ # time.sleep(0.01)
36
+ # return ["Loaded"] * 2
37
+
38
+
39
+ css = """
40
+ .green { color: black!important;line-height:1.9em; padding: 0.2em 0.2em; background: #ccffcc; border-radius:0.5rem;}
41
+ .red { color: black!important;line-height:1.9em; padding: 0.2em 0.2em; background: #ffad99; border-radius:0.5rem;}
42
+ .hyperlinks {
43
+ display: flex;
44
+ align-items: center;
45
+ align-content: center;
46
+ padding-top: 12px;
47
+ justify-content: flex-end;
48
+ margin: 0 10px; /* Adjust the margin as needed */
49
+ text-decoration: none;
50
+ color: #000; /* Set the desired text color */
51
+ }
52
+ """
53
+
54
+ # Most likely human generated, #most likely AI written
55
+
56
+ capybara_problem = '''Dr. Capy Cosmos, a capybara unlike any other, astounded the scientific community with his groundbreaking research in astrophysics. With his keen sense of observation and unparalleled ability to interpret cosmic data, he uncovered new insights into the mysteries of black holes and the origins of the universe. As he peered through telescopes with his large, round eyes, fellow researchers often remarked that it seemed as if the stars themselves whispered their secrets directly to him. Dr. Cosmos not only became a beacon of inspiration to aspiring scientists but also proved that intellect and innovation can be found in the most unexpected of creatures.'''
57
+
58
+ with gr.Blocks(css=css,
59
+ theme=gr.themes.Default(font=[gr.themes.GoogleFont("Inconsolata"), "Arial", "sans-serif"])) as app:
60
+ with gr.Row():
61
+ with gr.Column(scale=3):
62
+ gr.HTML("<p><h1> binoculars: zero-shot llm-text detector</h1>")
63
+ with gr.Column(scale=1):
64
+ gr.HTML("""
65
+ <p>
66
+ <a href="https://arxiv.org/abs/2401.12070" target="_blank">paper</a>
67
+
68
+ <a href="https://github.com/AHans30/Binoculars" target="_blank">code</a>
69
+
70
+ <a href="mailto:[email protected]" target="_blank">contact</a>
71
+ """, elem_classes="hyperlinks")
72
+ with gr.Row():
73
+ input_box = gr.Textbox(value=capybara_problem, placeholder="Enter text here", lines=8, label="Input Text", )
74
+ with gr.Row():
75
+ # dropdown option for mode
76
+ dropdown_mode = gr.Dropdown(["Low False Positive Rate", "High Accuracy"],
77
+ label="Mode",
78
+ show_label=True,
79
+ value="Low False Positive Rate"
80
+ )
81
+ submit_button = gr.Button("Run Binoculars", variant="primary")
82
+ clear_button = gr.ClearButton()
83
+ with gr.Row():
84
+ output_text = gr.Textbox(label="Prediction", value="Most likely AI-Generated")
85
+
86
+ with gr.Row():
87
+ gr.HTML("<p><p><p>")
88
+ with gr.Row():
89
+ gr.HTML("<p><p><p>")
90
+ with gr.Row():
91
+ gr.HTML("<p><p><p>")
92
+
93
+ with gr.Accordion("Disclaimer", open=False):
94
+ gr.Markdown(
95
+ """
96
+ - `Accuracy` :
97
+ - AI-generated text detectors aim for accuracy, but no detector is perfect.
98
+ - If you choose "high accuracy" mode, then the threshold between human and machine is chosen to maximize the F1 score on our validation dataset.
99
+ - If you choose the "low false-positive rate" mode, the threshold for declaring something to be AI generated will be set so that the false positive (human text wrongly flagged as AI) rate is below 0.01% on our validation set.
100
+ - The provided prediction is for demonstration purposes only. This is not offered as a consumer product.
101
+ - Users are advised to exercise discretion, and we assume no liability for any use.
102
+ - `Recommended detection Use Cases` :
103
+ - In this work, our focus is on achieving a low false positive rate, crucial for sensitive downstream use cases where false accusations are highly undesireable.
104
+ - The main focus of our research is on content moderation, e.g., detecting AI-generated reviews on Amazon/Yelp, detecting AI generated social media posts and news, etc. We feel this application space is most compelling, as LLM detection tools are best used by professionals in conjunction with a broader set of moderation tools and policies.
105
+ - `Known weaknesses` :
106
+ - As noted in our paper, Binoculars exhibits superior detection performance in the English language compared to other languages. Non-English text makes it more likely that results will default to "human written."
107
+ - Binoculars considers verbatim memorized texts to be "AI generated." For example, most language models have memorized and can recite the US constitution. For this reason, text from the constitution, or other highly memorized sources, may be classified as AI written.
108
+ - We recommend using 200-300 words of text at a time. Fewer words make detection difficult, as can using more than 1000 words. Binoculars will be more likely to default to the "human written" category if too few tokens are provided.
109
+ """
110
+ )
111
+
112
+ with gr.Accordion("Cite our work", open=False):
113
+ gr.Markdown(
114
+ """
115
+ ```bibtex
116
+ @misc{hans2024spotting,
117
+ title={Spotting LLMs With Binoculars: Zero-Shot Detection of Machine-Generated Text},
118
+ author={Abhimanyu Hans and Avi Schwarzschild and Valeriia Cherepanova and Hamid Kazemi and Aniruddha Saha and Micah Goldblum and Jonas Geiping and Tom Goldstein},
119
+ year={2024},
120
+ eprint={2401.12070},
121
+ archivePrefix={arXiv},
122
+ primaryClass={cs.CL}
123
+ }
124
+ """
125
+ )
126
+
127
+ # confidence_bar = gr.Label(value={"Confidence": 0})
128
+
129
+ # clear_button.click(lambda x: input_box., )
130
+ submit_button.click(run_detector, inputs=input_box, outputs=output_text)
131
+ clear_button.click(lambda: ("", ""), outputs=[input_box, output_text])
132
+ dropdown_mode.change(change_mode, inputs=[dropdown_mode], outputs=[dropdown_mode])
experiments/jobs.sh ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Experiments for the CC News, CNN and PubMed datasets with generations from LLaMA-2-13B model
2
+
3
+ python run.py \
4
+ --dataset_path ../datasets/core/cc_news/cc_news-llama2_13.jsonl \
5
+ --dataset_name CC-News \
6
+ --human_sample_key text \
7
+ --machine_sample_key meta-llama-Llama-2-13b-hf_generated_text_wo_prompt \
8
+ --machine_text_source LLaMA-2-13B
9
+
10
+ python run.py \
11
+ --dataset_path ../datasets/core/cnn/cnn-llama2_13.jsonl \
12
+ --dataset_name CNN \
13
+ --human_sample_key article \
14
+ --machine_sample_key meta-llama-Llama-2-13b-hf_generated_text_wo_prompt \
15
+ --machine_text_source LLaMA-2-13B
16
+
17
+ python run.py \
18
+ --dataset_path ../datasets/core/pubmed/pubmed-llama2_13.jsonl \
19
+ --dataset_name PubMed \
20
+ --human_sample_key article \
21
+ --machine_sample_key meta-llama-Llama-2-13b-hf_generated_text_wo_prompt \
22
+ --machine_text_source LLaMA-2-13B
experiments/run.py ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from binoculars.detector import Binoculars
2
+ from binoculars.detector import BINOCULARS_ACCURACY_THRESHOLD as THRESHOLD
3
+ from experiments.utils import convert_to_pandas, save_experiment
4
+
5
+ import os
6
+ import argparse
7
+ import datetime
8
+
9
+ import torch
10
+ from datasets import Dataset, logging as datasets_logging
11
+ import numpy as np
12
+ from sklearn import metrics
13
+
14
+
15
+ def main(args):
16
+ # Initialize Binoculars (experiments in paper use the "accuracy" mode threshold wherever applicable)
17
+ bino = Binoculars(mode="accuracy", max_token_observed=args.tokens_seen)
18
+
19
+ # Load dataset
20
+ ds = Dataset.from_json(f"{args.dataset_path}")
21
+
22
+ # Set (non) default values
23
+ args.dataset_name = args.dataset_name or args.dataset_path.rstrip("/").split("/")[-2]
24
+ machine_sample_key = (
25
+ args.machine_sample_key
26
+ or [x for x in list(ds.features.keys())[::-1] if "generated_text" in x][0]
27
+ )
28
+ args.machine_text_source = args.machine_text_source or machine_sample_key.rstrip("_generated_text_wo_prompt")
29
+
30
+ # Set job name, experiment path and create directory
31
+ args.job_name = (
32
+ args.job_name
33
+ or f"{args.dataset_name}-{args.machine_text_source}-{args.tokens_seen}-tokens"
34
+ .strip().replace(' ', '-')
35
+ )
36
+ breakpoint()
37
+ args.experiment_path = f"results/{args.job_name}"
38
+ os.makedirs(f"{args.experiment_path}", exist_ok=True)
39
+
40
+ # Score human and machine generated text
41
+ print(f"Scoring human text")
42
+ human_scores = ds.map(
43
+ lambda batch: {"score": bino.compute_score(batch[args.human_sample_key])},
44
+ batched=True,
45
+ batch_size=args.batch_size,
46
+ remove_columns=ds.column_names
47
+ )
48
+
49
+ print(f"Scoring machine text")
50
+ machine_scores = ds.map(
51
+ lambda batch: {"score": bino.compute_score(batch[args.machine_sample_key])},
52
+ batched=True,
53
+ batch_size=args.batch_size,
54
+ remove_columns=ds.column_names
55
+ )
56
+
57
+ score_df = convert_to_pandas(human_scores, machine_scores)
58
+ score_df["pred"] = np.where(score_df["score"] < THRESHOLD, 1, 0)
59
+
60
+ # Compute metrics
61
+ f1_score = metrics.f1_score(score_df["class"], score_df["pred"])
62
+ score = -1 * score_df["score"] # We negative scale the scores to make the class 1 (machine) the positive class
63
+ fpr, tpr, thresholds = metrics.roc_curve(y_true=score_df["class"], y_score=score, pos_label=1)
64
+ roc_auc = metrics.auc(fpr, tpr)
65
+ # Interpolate the TPR at FPR = 0.01%, this is a fixed point in roc curve
66
+ tpr_at_fpr_0_01 = np.interp(0.01 / 100, fpr, tpr)
67
+
68
+ # Save experiment
69
+ save_experiment(args, score_df, fpr, tpr, f1_score, roc_auc, tpr_at_fpr_0_01)
70
+
71
+
72
+ if __name__ == "__main__":
73
+ print("=" * 60, "START", "=" * 60)
74
+
75
+ # Set logging at the CRITICAL level to avoid seeing loaded datasets from cache
76
+ datasets_logging.set_verbosity_error()
77
+
78
+ parser = argparse.ArgumentParser(
79
+ description="Run (default) Binoculars on a dataset and compute/plot relevant metrics.",
80
+ )
81
+
82
+ # Dataset arguments
83
+ parser.add_argument("--dataset_path", type=str, help="Path to the jsonl file")
84
+ parser.add_argument("--dataset_name", type=str, default=None, help="name of the dataset")
85
+ parser.add_argument("--human_sample_key", type=str, help="key for the human-generated text")
86
+ parser.add_argument("--machine_sample_key", type=str, default=None,
87
+ help="key for the machine-generated text")
88
+ parser.add_argument("--machine_text_source", type=str, default=None,
89
+ help="name of model used to generate machine text")
90
+
91
+ # Scoring arguments
92
+ parser.add_argument("--tokens_seen", type=int, default=512, help="Number of tokens seen by the model")
93
+
94
+ # Computational arguments
95
+ parser.add_argument("--batch_size", type=int, default=32)
96
+
97
+ # Job arguments
98
+ parser.add_argument("--job_name", type=str, default=None)
99
+
100
+ args = parser.parse_args()
101
+
102
+ print("Using device:", "cuda" if torch.cuda.is_available() else "cpu")
103
+
104
+ if torch.cuda.is_available():
105
+ print(f"Number of GPUs: {torch.cuda.device_count()}")
106
+ print(f"GPU Type: {torch.cuda.get_device_name(0)}")
107
+
108
+ args.start_time = datetime.datetime.now().strftime("%I:%M%p on %B %d, %Y")
109
+ main(args)
110
+
111
+ print("=" * 60, "END", "=" * 60)
experiments/utils.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import os
3
+ import datetime
4
+
5
+ import pandas as pd
6
+ import matplotlib as mpl
7
+ import seaborn as sns
8
+ import matplotlib.pyplot as plt
9
+ from sklearn import metrics
10
+
11
+ COLOR = "black"
12
+
13
+ mpl.rcParams["text.color"] = COLOR
14
+ mpl.rcParams["axes.labelcolor"] = COLOR
15
+ mpl.rcParams["xtick.color"] = COLOR
16
+ mpl.rcParams["ytick.color"] = COLOR
17
+ mpl.rcParams["figure.dpi"] = 200
18
+ sns.set(style="darkgrid")
19
+
20
+
21
+ def convert_to_pandas(human_scores, machine_scores):
22
+ human_scores = human_scores["score"]
23
+ machine_scores = machine_scores["score"]
24
+
25
+ df = pd.DataFrame(
26
+ {"score": human_scores + machine_scores, "class": [0] * len(human_scores) + [1] * len(machine_scores)}
27
+ )
28
+ return df
29
+
30
+
31
+ def save_json(data, save_path):
32
+ data.end_time = datetime.datetime.now().strftime("%I:%M%p on %B %d, %Y")
33
+ with open(os.path.join(save_path, "experiments_details.json"), "w", encoding="utf-8") as f:
34
+ json.dump(data.__dict__, f, ensure_ascii=False, indent=4)
35
+
36
+
37
+ def save_experiment(args, score_df, fpr, tpr, f1_score, roc_auc, tpr_at_fpr_0_01):
38
+ fig, ax = plt.subplots(1, 1)
39
+ ax.set_xscale("log")
40
+
41
+ annotation = f"ROC AUC: {roc_auc:.4f}\nF1 Score: {f1_score:.2f}\nTPR at 0.01% FPR:{100 * tpr_at_fpr_0_01:.2f}%"
42
+ display = metrics.RocCurveDisplay(fpr=fpr, tpr=tpr, estimator_name=annotation)
43
+ display.plot(ax=ax, linestyle="--")
44
+ ax.set_title(f"{args.dataset_name} (n={len(score_df)})\nMachine Text from {args.machine_text_source}")
45
+
46
+ fig.savefig(f"{args.experiment_path}/performance.png", bbox_inches='tight')
47
+ score_df.to_csv(f"{args.experiment_path}/score_df.csv", index=False)
48
+ save_json(args, args.experiment_path)
main.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from binoculars import Binoculars
2
+
3
+ bino = Binoculars()
4
+
5
+ # ChatGPT (GPT-4) output when prompted with “Can you write a few sentences about a capybara that is an astrophysicist?"
6
+ sample_string = '''Dr. Capy Cosmos, a capybara unlike any other, astounded the scientific community with his
7
+ groundbreaking research in astrophysics. With his keen sense of observation and unparalleled ability to interpret
8
+ cosmic data, he uncovered new insights into the mysteries of black holes and the origins of the universe. As he
9
+ peered through telescopes with his large, round eyes, fellow researchers often remarked that it seemed as if the
10
+ stars themselves whispered their secrets directly to him. Dr. Cosmos not only became a beacon of inspiration to
11
+ aspiring scientists but also proved that intellect and innovation can be found in the most unexpected of creatures.'''
12
+
13
+ print(bino.compute_score(sample_string)) # 0.75661373
14
+ print(bino.predict(sample_string)) # 'Most likely AI-Generated'
requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ sentencepiece
2
+ transformers[torch] @ https://github.com/huggingface/transformers/archive/refs/tags/v4.31.0.zip
3
+ datasets
4
+ numpy
5
+ gradio
6
+ gradio_client
7
+ scikit-learn
8
+ seaborn
9
+ pandas
setup.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from setuptools import setup, find_packages
2
+
3
+ setup(
4
+ name='Binoculars',
5
+ version='0.0.10',
6
+ packages=find_packages(),
7
+ url='https://github.com/ahans30/Binoculars',
8
+ license=open("LICENSE.md", "r", encoding="utf-8").read(),
9
+ author='Authors of "Binoculars: Zero-Shot Detection of LLM-Generated Text"',
10
+ author_email='[email protected]',
11
+ description='A language model generated text detector.',
12
+ long_description=open("README.md", "r", encoding="utf-8").read(),
13
+ long_description_content_type="text/markdown",
14
+ install_requires=open("requirements.txt", "r", encoding="utf-8").read().splitlines(),
15
+ )