Spaces:
Build error
Build error
Upload folder using huggingface_hub
Browse files- .gitattributes +7 -0
- .gitignore +164 -0
- .gradio/certificate.pem +31 -0
- LICENSE.md +28 -0
- README.md +86 -7
- app.py +5 -0
- assets/bino-logo.svg +189 -0
- assets/binoculars.jpg +0 -0
- binoculars/__init__.py +3 -0
- binoculars/detector.py +100 -0
- binoculars/metrics.py +57 -0
- binoculars/utils.py +10 -0
- datasets/core/cc_news/cc_news-falcon7.jsonl +0 -0
- datasets/core/cc_news/cc_news-llama2_13.jsonl +3 -0
- datasets/core/cnn/cnn-falcon7.jsonl +0 -0
- datasets/core/cnn/cnn-llama2_13.jsonl +3 -0
- datasets/core/pubmed/pubmed-falcon7.jsonl +0 -0
- datasets/core/pubmed/pubmed-llama2_13.jsonl +3 -0
- datasets/robustness/open_orca/carl-sagan-llama2-13b-chat.jsonl +3 -0
- datasets/robustness/open_orca/default-llama2-13b-chat.jsonl +3 -0
- datasets/robustness/open_orca/no-robotic-words-llama2-13b-chat.jsonl +3 -0
- datasets/robustness/open_orca/pirate-llama2-13b-chat.jsonl +3 -0
- demo/demo.py +132 -0
- experiments/jobs.sh +22 -0
- experiments/run.py +111 -0
- experiments/utils.py +48 -0
- main.py +14 -0
- requirements.txt +9 -0
- setup.py +15 -0
.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 |
-
|
|
|
|
| 10 |
---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
)
|