import pandas as pd import numpy as np from sklearn.naive_bayes import MultinomialNB, ComplementNB from sklearn.linear_model import LogisticRegression from sklearn.svm import LinearSVC from sklearn.calibration import CalibratedClassifierCV from sklearn.ensemble import ( VotingClassifier, RandomForestClassifier, StackingClassifier, GradientBoostingClassifier, HistGradientBoostingClassifier ) from sklearn.feature_selection import SelectKBest, f_classif from sklearn.pipeline import Pipeline from sklearn.compose import ColumnTransformer from sklearn.preprocessing import MinMaxScaler from sklearn.feature_extraction.text import TfidfVectorizer from dl_model import DeepNewsClassifier # §3.3.2 Deep Learning Module class FakeNewsModels: def __init__(self): # All hand-crafted numeric columns (original + new deception features) num_cols = [ # Original features 'lexical_density', 'capital_ratio', 'noun_ratio', 'speaker_reliability', 'sentiment_score', 'complexity_score', 'subjectivity_score', 'verb_ratio', 'adj_ratio', 'adv_ratio', 'punctuation_aggression', 'entity_density', 'formal_cadence', 'official_marker', # Hedging & Certainty 'hedge_ratio', 'certainty_ratio', 'hedge_certainty_diff', # Emotional 'emotional_intensity', 'sentiment_extremity', 'negativity_score', 'positivity_score', # Numerical 'number_density', 'has_percentage', 'has_dollar_amount', 'number_count', # Source / Attribution 'attribution_ratio', 'has_source_citation', # Readability 'ari_score', 'gunning_fog', 'avg_sentence_length', # Pronoun & Modality 'first_person_ratio', 'modal_ratio', 'negation_ratio', # Comparison 'comparative_ratio', 'superlative_ratio', # Voice 'passive_ratio', # Urgency / Style 'caps_word_ratio', 'ellipsis_count', 'exclamation_marks', 'question_marks', 'quotes_count', 'total_words', 'avg_word_length', # Social context history 'false_history_ratio', 'true_history_ratio', 'history_volume', 'is_republican', 'is_democrat', ] preprocessor = ColumnTransformer( transformers=[ ('text', TfidfVectorizer( ngram_range=(1, 3), max_features=12000, min_df=3, max_df=0.75, sublinear_tf=True, stop_words='english' ), 'statement'), ('meta', MinMaxScaler(), num_cols), ], remainder='drop' ) # Feature selection: keep top 5000 features selector = SelectKBest(f_classif, k=5000) # Individual classifiers — tuned for better performance and calibrated uncertainty nb_clf = CalibratedClassifierCV(ComplementNB(alpha=0.1)) lr_clf = LogisticRegression( C=0.8, max_iter=3000, solver='lbfgs', class_weight='balanced', l1_ratio=0 ) svm_clf = CalibratedClassifierCV( LinearSVC(C=0.5, max_iter=5000, class_weight='balanced', dual='auto') ) rf_clf = CalibratedClassifierCV( RandomForestClassifier( n_estimators=600, max_depth=22, min_samples_leaf=3, class_weight='balanced', random_state=42, n_jobs=-1 ) ) self.models = { 'nb': Pipeline([('pre', preprocessor), ('sel', selector), ('clf', nb_clf)]), 'lr': Pipeline([('pre', preprocessor), ('sel', selector), ('clf', lr_clf)]), 'svm': Pipeline([('pre', preprocessor), ('sel', selector), ('clf', svm_clf)]), 'rf': Pipeline([('pre', preprocessor), ('sel', selector), ('clf', rf_clf)]), # §3.3.2 Deep Learning Module — BiLSTM-approximating dual encoder 'dl': DeepNewsClassifier(), } # STACKING ENSEMBLE with LogisticRegression meta-learner (§3.3) # Includes Deep Learning module as 5th base estimator self.models['ensemble'] = StackingClassifier( estimators=[ ('nb', self.models['nb']), ('lr', self.models['lr']), ('svm', self.models['svm']), ('rf', self.models['rf']), ('dl', self.models['dl']), # §3.3.2 ], final_estimator=LogisticRegression( C=0.5, class_weight='balanced', max_iter=1000 ), stack_method='predict_proba', cv=5, n_jobs=-1 ) def get_model(self, name): return self.models.get(name) def get_all_models(self): return self.models