File size: 5,017 Bytes
5907e4a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
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