File size: 8,669 Bytes
d8bd190
5c6d28a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2f00951
 
 
5c6d28a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2f00951
5c6d28a
d8bd190
5c6d28a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
307103b
 
 
5c6d28a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6ef776a
5b2d3bc
2f00951
5b2d3bc
5c6d28a
 
 
 
 
 
 
 
 
 
 
 
6ef776a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5c6d28a
 
 
 
 
 
 
 
 
 
2f00951
5c6d28a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d8bd190
 
b852eca
d8bd190
 
 
b852eca
5c6d28a
b852eca
5c6d28a
b852eca
 
d8bd190
5c038ba
a37dea8
6ef776a
5c6d28a
 
6ef776a
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
import numpy as np
from transformers import GPT2LMHeadModel, GPT2TokenizerFast
import torch
import re
import streamlit as st
import pdfplumber
import pandas as pd
import base64
from docx import Document
import streamlit.components.v1 as components


# Define the device, model, and tokenizer
device = "cpu"
model = GPT2LMHeadModel.from_pretrained("gpt2").to(device)
tokenizer = GPT2TokenizerFast.from_pretrained("gpt2")
max_length = 1024
stride = 256
ai_perplexity_threshold = 55
human_ai_perplexity_threshold = 80

def get_perplexity(sentence):
    """
    Calculate the perplexity of a given sentence using the GPT-2 model.
    """
    # Encode the sentence using the tokenizer
    input_ids = tokenizer.encode(
        sentence,
        add_special_tokens=True,
        truncation=True,
        max_length=max_length,
        return_tensors="pt"
    ).to(device)

    total_nll = 0
    total_tokens = 0

    for start_pos in range(0, input_ids.shape[1], stride):
        # Determine the end position of the current sequence
        end_pos = min(start_pos + max_length, input_ids.shape[1])
        target_len = end_pos - start_pos

        # Create target_ids by detaching input_ids and filling non-target tokens with -100
        target_ids = input_ids[:, start_pos:end_pos].detach()
        target_ids[:, :-target_len].fill_(-100)

        # Compute the negative log likelihood loss
        outputs = model(input_ids[:, start_pos:end_pos], labels=target_ids)
        neg_log_likelihood = outputs.loss * target_len

        total_nll += neg_log_likelihood.sum()
        total_tokens += target_len

    if total_tokens == 0:
        perplexity = float('inf')  # Assign infinity perplexity as a default value
    else:
        perplexity = round(float(torch.exp(total_nll / total_tokens)),2)

    return perplexity

def analyze_text(sentence):
    """
    Analyze the given text and determine the perplexity and label of the text.
    """
    results = {}

    # Count the total number of valid characters in the sentence
    total_valid_char = sum(len(x) for x in re.findall(r"[a-zA-Z0-9]+", sentence))

    if total_valid_char < 200:
        results["Label"] = -1
        results["Output"] = "Insufficient Content"
        results["Percent_ai"] = "-"
        results["Perplexity"] = "-"
        results["Burstiness"] = "-"

        return results

    # Split the sentence into lines based on punctuation and newlines
    lines = re.split(r'(?<=[.?!][ \[\(])|(?<=\n)\s*', sentence)
    lines = [line for line in lines if re.search(r"[a-zA-Z0-9]+", line) is not None]
    perplexities = []
    total_characters = 0
    ai_characters = 0
    for line in lines:
        total_characters += len(line)
        perplexity = get_perplexity(line)
        perplexities.append(perplexity)
        if perplexity < ai_perplexity_threshold:
            ai_characters += len(line)
        
    results["Percent_ai"] = str(round((ai_characters/total_characters)*100,2))+"%"
    results["Perplexity"] = round(sum(perplexities) / len(perplexities),2)
    results["Burstiness"] = round(np.var(perplexities),2)

    if results["Perplexity"] <= ai_perplexity_threshold:
        results["Label"] = 0
        results["Output"] = "AI"
    elif results["Perplexity"] <= human_ai_perplexity_threshold:
        results["Label"] = 1
        results["Output"] = "Human + AI"
    else:
        results["Label"] = 2
        results["Output"] = "Human"

    return results



def process_text_file(file):
    """
    Process the input text file (PDF or Word) and analyze the content.
    """
    if file.type == "application/pdf":
        with pdfplumber.open(file) as pdf:
            text = ""
            for page in pdf.pages:
                extracted_text = page.extract_text()
                text += extracted_text if extracted_text is not None else ""
                
    elif file.type == "application/vnd.openxmlformats-officedocument.wordprocessingml.document":
        doc = Document(file)
        text = ""
        for para in doc.paragraphs:
            text += para.text
    else:
        st.error("Unsupported file format. Please upload a PDF or Word document.")
        return

    results = analyze_text(text)
    return results



def main():
    st.title("CheckGPT")
    st.write("CheckGPT is an AI-powered text analysis tool that predicts the content generated by AI by evaluating the perplexity and burstiness scores of GPT model, and provides insights for investigating text authenticity.")
    st.write("This app is hosted on the free tier of HuggingFace Scapes, resulting in slow performance ๐Ÿข. Check out the GitHub repo to quickly run the app on your device and to enjoy a much faster experience! ๐Ÿš€๐Ÿš€")
    st.write("Checkout the code on GitHub <a href='https://github.com/Ank-Cha/CheckGPT' target='_blank'><img src='https://cdn-icons-png.flaticon.com/512/536/536452.png' alt='GitHub Logo' width='25' height='25' style='margin-left: 5px;'></a>", unsafe_allow_html=True)
    st.write("Feel free to explore and star โญ๏ธ the repo if you find it helpful!")
    
    # Create an empty placeholder for the uploaded files
    uploaded_files_placeholder = st.empty()

    results_list = []

    # Process the files only when the "Start" button is pressed
    uploaded_files = uploaded_files_placeholder.file_uploader("Upload PDF or Word documents", type=["pdf", "docx"], accept_multiple_files=True)
    
    # Create a button to start processing
    start_button = st.button("Start Checking")

    st.markdown(
        """
        <style>
        .footer {
            position: fixed;
            bottom: 0;
            left: 0;
            width: 100%;
            text-align: center;
            padding: 10px;
            background-color: #0A2742;
            color: white;
        }
        </style>
       <div class="footer">
    Made by Ankush &nbsp;&nbsp;&nbsp;
    <a href="https://www.linkedin.com/in/ankush-chaudhari/" target="_blank">
    <img src="https://cdn-icons-png.flaticon.com/512/174/174857.png" alt="LinkedIn" width="20" height="20">
    </a>
    </div>
        """,
        unsafe_allow_html=True
    )

    if start_button:
        with st.spinner("Processing..."):
            for uploaded_file in uploaded_files:
                results = process_text_file(uploaded_file)
                results["file_name"] = uploaded_file.name
                results_list.append(results)

        if results_list:
            df = pd.DataFrame(results_list)
            df = df[["file_name", "Percent_ai", "Perplexity", "Burstiness", "Output"]]
            df = df.astype(str)
            df = df.rename(columns={"Percent_ai": "Predicted AI percent", "Perplexity": "Perplexity Score", "Output": "Predicted Output"})
            st.write("Results:")

            # Apply conditional formatting to the "Output" cell only
            df_styled = df.style.applymap(
                lambda value: "background-color: purple" if value == "Insufficient Content" else
                "background-color: green" if value == "Human" else
                "background-color: orange" if value == "Human + AI" else
                "background-color: red",
                subset=["Predicted Output"]
            )
            
            st.dataframe(df_styled)

            # Add a button to download the results as a CSV file
            csv_data = df.to_csv(index=False)
            b64 = base64.b64encode(csv_data.encode()).decode()
            href = f'<a href="data:file/csv;base64,{b64}" download="results.csv">Download CSV</a>'
            st.markdown(href, unsafe_allow_html=True)

            # Display the description of columns and disclaimer
            st.markdown(
                """
            <div class="small-text">
            <strong>Column Descriptions:</strong><br>
            - <strong>Predicted AI percent:</strong> Percentage of the text predicted to be generated by AI.<br>
            - <strong>Perplexity Score:</strong> Measurement the model's confidence in generating the text.<br>
            - <strong>Burstiness:</strong> Measurment of variation in perplexity scores for the analyzed text.<br>
            - <strong>Predicted Output:</strong> The predicted label for the text: 'AI', 'Human + AI', 'Human', or 'Insufficient Content'.<br><br>
            
            <strong>Disclaimer:</strong><br>
            These results are generated by an AI model and may not be 100% accurate. Please use them for investigation purposes and exercise caution when making decisions based on the results.
            </div>
            """,
                unsafe_allow_html=True
            )
    
    

if __name__ == "__main__":
    main()