Spaces:
Sleeping
Sleeping
Commit ·
c3d9b32
1
Parent(s): 52b7c7c
Deploy Fake News Detector
Browse files- .gitignore +11 -0
- app.py +81 -0
- models/bilstm_fake_news_float16.tflite +3 -0
- models/bilstm_fake_news_model.h5 +3 -0
- models/bilstm_tokenizer.pkl +3 -0
- models/lightgbm_model.pkl +3 -0
- models/logistic_regression_model.pkl +3 -0
- models/naive_bayes_model.pkl +3 -0
- models/random_forest_model.pkl +3 -0
- models/tfidf_vectorizer.pkl +3 -0
- models/xgboost_model.pkl +3 -0
- notebooks/eda_and_testing.ipynb +0 -0
- pipeline/__init__.py +0 -0
- pipeline/predict.py +131 -0
- pipeline/preprocessing.py +50 -0
- pipeline/quantization.py +23 -0
- pipeline/train_standard_models.py +37 -0
- pipeline/vectorizer.py +16 -0
- requirements.txt +12 -0
.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
|