PocketSkye commited on
Commit
c3d9b32
·
1 Parent(s): 52b7c7c

Deploy Fake News Detector

Browse files
.gitignore ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Python cache
2
+ __pycache__/
3
+
4
+ # Virtual environment
5
+ .venv/
6
+ venv/
7
+ env/
8
+ ENV/
9
+
10
+ data/WELFake_Dataset.csv
11
+ models\random_forest_model.pkl
app.py ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from pipeline.predict import predict_fake_news, load_model_and_predict
3
+ from collections import Counter
4
+
5
+ # Model label mapping
6
+ model_labels = {
7
+ 0: "BiLSTM Neural Network",
8
+ 1: "Logistic Regression",
9
+ 2: "Naive Bayes",
10
+ 3: "XGBoost",
11
+ 4: "LightGBM",
12
+ 5: "Random Forest"
13
+ }
14
+
15
+ model_choices = [(name, idx) for idx, name in model_labels.items()]
16
+
17
+
18
+ def run_single_model(text, model_index):
19
+ if model_index == 0:
20
+ result = predict_fake_news(text)
21
+ else:
22
+ result = load_model_and_predict(model_index, [text])[0]
23
+
24
+ return f"Model: {model_labels[model_index]}\n" \
25
+ f"Prediction: {result['verdict']}\n" \
26
+ f"Confidence: {result['confidence']}"
27
+
28
+
29
+ def run_all_models(text=None, file=None):
30
+ if not text and not file:
31
+ return "Please enter text or upload a file."
32
+
33
+ if not text and file:
34
+ with open(file.name, 'r', encoding='utf-8') as f:
35
+ text = f.read()
36
+
37
+ results = {}
38
+ predictions = []
39
+
40
+ for idx in range(6):
41
+ if idx == 0:
42
+ res = predict_fake_news(text)
43
+ else:
44
+ res = load_model_and_predict(idx, [text])[0]
45
+ results[model_labels[idx]] = res
46
+ predictions.append(res["label"])
47
+
48
+ majority_label = Counter(predictions).most_common(1)[0][0]
49
+ majority_verdict = "Real News" if majority_label == 1 else "Fake News"
50
+
51
+ result_table = "\n".join(
52
+ f"{model}: {res['verdict']} (Confidence: {res['confidence']})"
53
+ for model, res in results.items()
54
+ )
55
+
56
+ return f"{result_table}\n\n🗳️ Overall Majority Verdict: **{majority_verdict}**"
57
+
58
+
59
+ # Gradio UI
60
+ with gr.Blocks() as demo:
61
+ gr.Markdown("## 📰 Fake News Classifier - Multi-Model Evaluation")
62
+
63
+ with gr.Tab("🔍 Single Model Prediction"):
64
+ with gr.Row():
65
+ text_input = gr.Textbox(label="Enter News Text", lines=10, placeholder="Paste news text here...")
66
+ model_dropdown = gr.Dropdown(choices=model_choices, label="Select Model", value=0)
67
+ single_output = gr.Textbox(label="Prediction", lines=5)
68
+ predict_btn = gr.Button("Predict")
69
+ predict_btn.click(fn=run_single_model, inputs=[text_input, model_dropdown], outputs=single_output)
70
+
71
+ with gr.Tab("📊 All Models Comparison"):
72
+ gr.Markdown("Paste news text below **or** upload a `.txt` file")
73
+ text_input_all = gr.Textbox(label="Paste News Text (optional)", lines=10, placeholder="Paste here...")
74
+ file_input_all = gr.File(label="Upload .txt File (optional)", file_types=[".txt"])
75
+ compare_btn = gr.Button("Run All Models")
76
+ multi_output = gr.Textbox(label="Model-wise Prediction & Majority Verdict", lines=12)
77
+ compare_btn.click(fn=run_all_models, inputs=[text_input_all, file_input_all], outputs=multi_output)
78
+
79
+
80
+ if __name__ == "__main__":
81
+ demo.launch()
models/bilstm_fake_news_float16.tflite ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ed26fe011ef944c880d27247ad5524d9b140763ef3b8760616e81dbc35566701
3
+ size 1520076
models/bilstm_fake_news_model.h5 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d4342417a803ac7db8c78280b7c7933d7a5f2f6b2d273e1183113d1b652f6981
3
+ size 9016600
models/bilstm_tokenizer.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7fb9dc8fdf2668f360d586269afad7b7b5e18d1fec0cc1d29fac8127778c819d
3
+ size 16393129
models/lightgbm_model.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:304d76a4e8c0e173b5151f07a076ad74460e4e1baaef59958c892f0245ce86eb
3
+ size 549988
models/logistic_regression_model.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:abc8cf75db269428c6a4dfaf165c8a20bf14c5c52bb49cc9f2f8e51e2bab09b1
3
+ size 40863
models/naive_bayes_model.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:66363fa9b3617e65ca4657036f2584102e89ee0e59bcd2cfc8e4baf734cddbd5
3
+ size 160791
models/random_forest_model.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c24d07cf987ee5dc9fd7c05fb6759dd18d4bfd99befca92e404f04578107c8ce
3
+ size 74762217
models/tfidf_vectorizer.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3da05659b87c351ef2d000811839cd93124c6a7505ea1afd4f92117cc5e9d644
3
+ size 4589922
models/xgboost_model.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:bfb29374b9c0ac2d0391041dd0b8a8acf95a2400c7494e6f5d6be08adbf72e0e
3
+ size 353503
notebooks/eda_and_testing.ipynb ADDED
The diff for this file is too large to render. See raw diff
 
pipeline/__init__.py ADDED
File without changes
pipeline/predict.py ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import joblib
2
+ import numpy as np
3
+ from tensorflow.keras.models import load_model
4
+ from tensorflow.keras.preprocessing.sequence import pad_sequences
5
+ from pipeline.preprocessing import clean_text
6
+ import tensorflow as tf
7
+ MAX_LEN = 300
8
+
9
+ bilstm_model = load_model("models/bilstm_fake_news_model.h5")
10
+ bilstm_tokenizer = joblib.load("models/bilstm_tokenizer.pkl")
11
+
12
+ #Function Pipeline for using BILSTM Model
13
+ def predict_fake_news(text: str):
14
+ """
15
+ Predict using the BiLSTM neural network.
16
+ Input: Raw text
17
+ Output: label (0/1), confidence, verdict
18
+ """
19
+ cleaned = clean_text(text)
20
+ sequence = bilstm_tokenizer.texts_to_sequences([cleaned])
21
+ padded = pad_sequences(sequence, maxlen=MAX_LEN)
22
+
23
+ prediction = bilstm_model.predict(padded)[0][0]
24
+ label = int(prediction >= 0.5)
25
+ confidence = round(float(prediction), 4)
26
+
27
+ return {
28
+ "label": label,
29
+ "confidence": confidence,
30
+ "verdict": "Real News" if label == 1 else "Fake News"
31
+ }
32
+
33
+ #Function pipeline for using BILSTM Model
34
+ def load_model_and_predict(model_choice: int, texts: list[str]):
35
+ """
36
+ Predict using a saved standard ML model (LogReg, XGB, etc.)
37
+
38
+ Args:
39
+ model_choice: int from 1 to 5
40
+ 1 - Logistic Regression
41
+ 2 - Naive Bayes
42
+ 3 - XGBoost
43
+ 4 - LightGBM
44
+ 5 - Random Forest
45
+ texts: list of raw news texts
46
+
47
+ Returns:
48
+ List of prediction dictionaries with label, confidence, and verdict.
49
+ """
50
+
51
+ import joblib
52
+ import numpy as np
53
+ from pipeline.preprocessing import clean_text
54
+
55
+ # Mapping
56
+ model_map = {
57
+ 1: "logistic_regression",
58
+ 2: "naive_bayes",
59
+ 3: "xgboost",
60
+ 4: "lightgbm",
61
+ 5: "random_forest"
62
+ }
63
+
64
+ if model_choice not in model_map:
65
+ raise ValueError(f"Invalid model_choice {model_choice}. Choose between 1-5.")
66
+
67
+ model_name = model_map[model_choice]
68
+
69
+ # Load model and vectorizer
70
+ model = joblib.load(f"models/{model_name}_model.pkl")
71
+ vectorizer = joblib.load("models/tfidf_vectorizer.pkl")
72
+
73
+ # Preprocess and vectorize
74
+ texts_cleaned = [clean_text(text) for text in texts]
75
+ X_vec = vectorizer.transform(texts_cleaned)
76
+
77
+ # Prediction
78
+ if hasattr(model, "predict_proba"):
79
+ probs = model.predict_proba(X_vec)[:, 1]
80
+ else:
81
+ decision = model.decision_function(X_vec)
82
+ probs = 1 / (1 + np.exp(-decision)) # sigmoid
83
+
84
+ preds = (probs >= 0.5).astype(int)
85
+
86
+ return [
87
+ {
88
+ "label": int(p),
89
+ "confidence": round(float(prob), 4),
90
+ "verdict": "Real News" if p == 1 else "Fake News"
91
+ }
92
+ for p, prob in zip(preds, probs)
93
+ ]
94
+
95
+
96
+
97
+
98
+ #For quantized model to reduce inference time
99
+
100
+ interpreter = tf.lite.Interpreter(model_path="models/bilstm_fake_news_float16.tflite")
101
+ interpreter.allocate_tensors()
102
+
103
+ # input/output tensor details
104
+ input_details = interpreter.get_input_details()
105
+ output_details = interpreter.get_output_details()
106
+
107
+ def predict_fake_news_tflite(text: str):
108
+ """
109
+ Predict using the float16 quantized BiLSTM TFLite model.
110
+ Input: Raw text
111
+ Output: label (0/1), confidence, verdict
112
+ """
113
+ # Preprocessing
114
+ cleaned = clean_text(text)
115
+ sequence = bilstm_tokenizer.texts_to_sequences([cleaned])
116
+ padded = pad_sequences(sequence, maxlen=MAX_LEN).astype(np.float32)
117
+
118
+ # Set input tensor
119
+ interpreter.set_tensor(input_details[0]['index'], padded)
120
+ interpreter.invoke()
121
+
122
+ # Get prediction
123
+ prediction = interpreter.get_tensor(output_details[0]['index'])[0][0]
124
+ label = int(prediction >= 0.5)
125
+ confidence = round(float(prediction), 4)
126
+
127
+ return {
128
+ "label": label,
129
+ "confidence": confidence,
130
+ "verdict": "Real News" if label == 1 else "Fake News"
131
+ }
pipeline/preprocessing.py ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+ import nltk
3
+ import pandas as pd
4
+ from nltk.tokenize import wordpunct_tokenize
5
+ from nltk.corpus import stopwords
6
+ from nltk.stem import WordNetLemmatizer
7
+
8
+ nltk.download('stopwords')
9
+ nltk.download('punkt')
10
+ nltk.download('wordnet')
11
+ nltk.download('omw-1.4')
12
+
13
+ # Initializing stopwords and lemmatizer once
14
+ stop_words = set(stopwords.words('english'))
15
+ lemmatizer = WordNetLemmatizer()
16
+
17
+
18
+ def clean_text(text: str) -> str:
19
+ """
20
+ Preprocesses input text:
21
+ - Lowercase conversion
22
+ - Removing non-alphanumeric characters
23
+ - Tokenization
24
+ - Stopword removal
25
+ - Lemmatization
26
+
27
+ Returns a cleaned string.
28
+ """
29
+ text = text.lower()
30
+ text = re.sub(r"[^a-zA-Z0-9\s]", '', text)
31
+ tokens = wordpunct_tokenize(text)
32
+ tokens = [t for t in tokens if t not in stop_words]
33
+ tokens = [lemmatizer.lemmatize(t) for t in tokens]
34
+ return ' '.join(tokens)
35
+
36
+ def load_and_preprocess(filepath: str) -> pd.DataFrame:
37
+ """
38
+ Loads dataset from a CSV file, merges title + text, applies cleaning,
39
+ and adds num_words column.
40
+
41
+ Returns:
42
+ Cleaned pandas DataFrame.
43
+ """
44
+ df = pd.read_csv(filepath)
45
+ df.dropna(subset=['text', 'title'], inplace=True)
46
+ df['text'] = df['title'] + ' ' + df['text']
47
+ df.drop(columns=['title'], inplace=True)
48
+ df['clean_text'] = df['text'].apply(clean_text)
49
+ df['num_words'] = df['clean_text'].apply(lambda x: len(x.split()))
50
+ return df
pipeline/quantization.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #For reducing the size of model while keeping accuracy same for faster inference
2
+ import tensorflow as tf
3
+ from tensorflow.keras.models import load_model
4
+
5
+ # Model Loading
6
+ model = load_model("models/bilstm_fake_news_model.h5")
7
+
8
+ # Conversion with TFLiteConverter
9
+ converter = tf.lite.TFLiteConverter.from_keras_model(model)
10
+ converter.optimizations = [tf.lite.Optimize.DEFAULT]
11
+ converter.target_spec.supported_types = [tf.float16]
12
+
13
+ converter.target_spec.supported_ops = [
14
+ tf.lite.OpsSet.TFLITE_BUILTINS,
15
+ tf.lite.OpsSet.SELECT_TF_OPS
16
+ ]
17
+ converter._experimental_lower_tensor_list_ops = False
18
+
19
+ tflite_model = converter.convert()
20
+
21
+ # Saving
22
+ with open("models/bilstm_fake_news_float16.tflite", "wb") as f:
23
+ f.write(tflite_model)
pipeline/train_standard_models.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import joblib
3
+ from sklearn.model_selection import train_test_split
4
+ from sklearn.linear_model import LogisticRegression
5
+ from sklearn.naive_bayes import MultinomialNB
6
+ from sklearn.ensemble import RandomForestClassifier
7
+ from xgboost import XGBClassifier
8
+ from lightgbm import LGBMClassifier
9
+
10
+ from pipeline.preprocess import load_and_clean_data
11
+ from pipeline.vectorizer import fit_and_save_vectorizer
12
+
13
+ def train_and_save_standard_models():
14
+ df = load_and_clean_data()
15
+ X_tfidf = fit_and_save_vectorizer(df['clean_text']) # saves vectorizer
16
+ y = df['label']
17
+
18
+ X_train, X_test, y_train, y_test = train_test_split(
19
+ X_tfidf, y, test_size=0.2, stratify=y, random_state=42
20
+ )
21
+
22
+ models = {
23
+ "logistic_regression": LogisticRegression(max_iter=200),
24
+ "naive_bayes": MultinomialNB(),
25
+ "random_forest": RandomForestClassifier(n_estimators=100, n_jobs=-1),
26
+ "xgboost": XGBClassifier(use_label_encoder=False, eval_metric='logloss'),
27
+ "lightgbm": LGBMClassifier()
28
+ }
29
+
30
+ for name, model in models.items():
31
+ print(f"Training {name}...")
32
+ model.fit(X_train, y_train)
33
+ joblib.dump(model, f"models/{name}_model.pkl")
34
+ print(f"Saved {name} model ✔")
35
+
36
+ if __name__ == "__main__":
37
+ train_and_save_standard_models()
pipeline/vectorizer.py ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import joblib
3
+ from sklearn.feature_extraction.text import TfidfVectorizer
4
+
5
+ VECTORIZER_PATH = "models/tfidf_vectorizer.pkl"
6
+
7
+ def fit_and_save_vectorizer(texts, max_features=5000):
8
+ """Fits TF-IDF vectorizer and saves it to disk."""
9
+ vectorizer = TfidfVectorizer(max_features=max_features)
10
+ X_tfidf = vectorizer.fit_transform(texts)
11
+ joblib.dump(vectorizer, VECTORIZER_PATH)
12
+ return X_tfidf
13
+
14
+ def load_vectorizer():
15
+ """Loads the TF-IDF vectorizer from disk."""
16
+ return joblib.load(VECTORIZER_PATH)
requirements.txt ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ tensorflow
2
+ scikit-learn
3
+ xgboost
4
+ lightgbm
5
+ pandas
6
+ numpy
7
+ matplotlib
8
+ joblib
9
+ nltk
10
+ spacy
11
+ wordcloud
12
+ gradio