File size: 8,209 Bytes
4d6e8c2
 
df46342
 
a9f8367
df46342
a9f8367
df46342
 
 
 
 
4d6e8c2
 
 
296146e
df46342
d878add
4d6e8c2
 
 
70f5f26
1c33274
70f5f26
4f1a07f
c422e81
 
 
4f1a07f
 
 
3b83e0c
 
b562460
3b83e0c
 
 
 
 
 
 
7688055
9bcb67c
3b83e0c
 
296146e
 
f5aa578
3b83e0c
 
 
8f462bc
3b83e0c
 
 
 
7688055
 
3b83e0c
 
9012700
a9f8367
c0a2424
a9f8367
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7688055
a9f8367
 
 
 
 
 
5225d97
 
 
 
 
 
df46342
a9f8367
 
 
 
 
 
 
 
 
 
 
 
 
 
 
af86903
a9f8367
 
7688055
 
a9f8367
 
03a22c3
 
f7c276d
 
6737c70
03a22c3
f7c276d
56d7bf2
03a22c3
6737c70
 
03a22c3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
af86903
03a22c3
 
 
 
 
9012700
2b85173
3b83e0c
76b5c0d
4d6e8c2
70f5f26
 
 
 
 
4d6e8c2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70f5f26
 
 
 
 
4d6e8c2
 
 
243d40e
3b83e0c
 
 
320940c
a9f8367
 
03a22c3
 
70f5f26
 
 
 
 
4d6e8c2
 
 
 
 
 
 
 
 
 
 
 
4f1a07f
4d6e8c2
 
 
 
1c33274
4d6e8c2
 
 
 
 
 
 
 
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
229
230
231
232
233
234
235
236
237
238
239
240
241
242
from datetime import datetime
import random

import numpy as np
import torch
from torch import nn
from torch.utils.data import DataLoader, Dataset
from transformers import AutoModel, AutoModelForSequenceClassification, AutoTokenizer
from fastapi import APIRouter
from datasets import load_dataset
from sklearn.metrics import accuracy_score
from skops.io import load

from .utils.evaluation import TextEvaluationRequest
from .utils.emissions import tracker, clean_emissions_data, get_space_info
from .utils.text_preprocessor import preprocess
from accelerate.test_utils.testing import get_backend
from .custom_classifiers import SentenceBERTClassifier, MoEClassifier

router = APIRouter()

DESCRIPTION = "Random Baseline"
ROUTE = "/text"

MODEL_DESCRIPTIONS = {
    "baseline": "random baseline", # Baseline
    "tfidf_xgb": "TF-IDF vectorizer and XGBoost classifier", # Submitted
    "bert_base_pruned": "Pruned BERT base model", # Submitted
    # 'climate_bert_pruned': "Fine-tuned and pruned DistilRoBERTa pre-trained on climate texts", # Not working
    "sbert_distilroberta": "Fine-tuned sentence transformer DistilRoBERTa", # working, not submitted
    "embedding_moe": "Mixture of expert classifier with DistilBERT Embeddings" # working, not submitted
}


def baseline_model(dataset_length: int):
    # Make random predictions (placeholder for actual model inference)
    predictions = [random.randint(0, 7) for _ in range(dataset_length)]

    return predictions

def tree_classifier(test_dataset: dict, model: str):
    print("Starting tree model run")

    texts = test_dataset["quote"]

    texts = preprocess(texts)

    model_path = f"tasks/text_models/{model}.skops"

    model = load(model_path, 
                 trusted=[
                     'scipy.sparse._csr.csr_matrix',
                     'xgboost.core.Booster',
                     'xgboost.sklearn.XGBClassifier'])

    predictions = model.predict(texts)
    print("Finished tree model run")


    return predictions

class TextDataset(Dataset):
    def __init__(self, texts, tokenizer, max_length=512):
        self.texts = texts
        self.tokenized_texts = tokenizer(
            texts,
            truncation=True,
            padding=True,
            max_length=max_length,
            return_tensors="pt",
        )

    def __getitem__(self, idx):
        item = {key: val[idx] for key, val in self.tokenized_texts.items()}
        return item

    def __len__(self) -> int:
        return len(self.texts)


def bert_classifier(test_dataset: dict, model: str):
    print("Starting BERT model run")
    texts = test_dataset["quote"]

    model_repo = f"theterryzhang/frugal_ai_{model}"

    tokenizer = AutoTokenizer.from_pretrained(model_repo)

    if model in ["bert_base_pruned"]:
        model = AutoModelForSequenceClassification.from_pretrained(model_repo)
    elif model in ["sbert_distilroberta"]:
        model = SentenceBERTClassifier.from_pretrained(model_repo)
    else:
        raise(ValueError)

    device, _, _ = get_backend()

    model = model.to(device)

    # Prepare dataset
    dataset = TextDataset(texts, tokenizer=tokenizer)
    dataloader = DataLoader(dataset, batch_size=32, shuffle=False)

    model.eval()
    with torch.no_grad():
        predictions = np.array([])
        for batch in dataloader:
            test_input_ids = batch["input_ids"].to(device)
            test_attention_mask = batch["attention_mask"].to(device)
            outputs = model(test_input_ids, test_attention_mask)
            p = torch.argmax(outputs.logits, dim=1)
            predictions = np.append(predictions, p.cpu().numpy())
    
    print("Finished BERT model run")
    
    return predictions

def moe_classifier(test_dataset: dict, model: str):
    print("Starting MoE run")

    device, _, _ = get_backend()

    texts = test_dataset["quote"]

    model_path = f"tasks/text_models/0131_MoE_final.pt"

    embedding_model = AutoModel.from_pretrained("sentence-transformers/all-distilroberta-v1")
    embedding_model = embedding_model.to(device)
    tokenizer = AutoTokenizer.from_pretrained("sentence-transformers/all-distilroberta-v1")

    dataset = TextDataset(texts, tokenizer=tokenizer, max_length=512)
    dataloader = DataLoader(dataset, batch_size=64, shuffle=False)

    model = MoEClassifier(3, 0.05)
    model.load_state_dict(torch.load(model_path))
    model = model.to(device)
    
    print("Starting MoE Classifier")

    model.eval()
    with torch.no_grad():
        predictions = np.array([])
        for batch in dataloader:
            input_ids = batch['input_ids'].to(device)
            attn_mask = batch['attention_mask'].to(device)
            embedding_outputs = embedding_model(input_ids, attn_mask)
            embeddings = embedding_outputs.last_hidden_state[:, 0, :]

            outputs = model(embeddings)
            p = torch.argmax(outputs, dim=1)
            predictions = np.append(predictions, p.cpu().numpy())

    print("Finished running MoE Classifier")

    return predictions

@router.post(ROUTE, tags=["Text Task"])
async def evaluate_text(request: TextEvaluationRequest,
                        model: str = "embedding_moe"):
    """
    Evaluate text classification for climate disinformation detection.
    
    Current Model: Random Baseline
    - Makes random predictions from the label space (0-7)
    - Used as a baseline for comparison
    """
    # Get space info
    username, space_url = get_space_info()

    # Define the label mapping
    LABEL_MAPPING = {
        "0_not_relevant": 0,
        "1_not_happening": 1,
        "2_not_human": 2,
        "3_not_bad": 3,
        "4_solutions_harmful_unnecessary": 4,
        "5_science_unreliable": 5,
        "6_proponents_biased": 6,
        "7_fossil_fuels_needed": 7
    }

    # Load and prepare the dataset
    dataset = load_dataset(request.dataset_name)

    # Convert string labels to integers
    dataset = dataset.map(lambda x: {"label": LABEL_MAPPING[x["label"]]})

    # Split dataset
    train_test = dataset["train"].train_test_split(test_size=request.test_size, seed=request.test_seed)
    test_dataset = train_test["test"]
    
    # Start tracking emissions
    tracker.start()
    tracker.start_task("inference")

    #--------------------------------------------------------------------------------------------
    # YOUR MODEL INFERENCE CODE HERE
    # Update the code below to replace the random baseline by your model inference within the inference pass where the energy consumption and emissions are tracked.
    #--------------------------------------------------------------------------------------------   
    
    # Make random predictions (placeholder for actual model inference)
    true_labels = test_dataset["label"]

    if model == "baseline":
        predictions = baseline_model(len(true_labels))
    elif model == "tfidf_xgb":
        predictions = tree_classifier(test_dataset, model='xgb_pipeline')
    elif 'bert' in model:
        predictions = bert_classifier(test_dataset, model)
    elif 'moe' in model:
        predictions = moe_classifier(test_dataset, model)

    #--------------------------------------------------------------------------------------------
    # YOUR MODEL INFERENCE STOPS HERE
    #--------------------------------------------------------------------------------------------   

    
    # Stop tracking emissions
    emissions_data = tracker.stop_task()
    
    # Calculate accuracy
    accuracy = accuracy_score(true_labels, predictions)
    
    # Prepare results dictionary
    results = {
        "username": username,
        "space_url": space_url,
        "submission_timestamp": datetime.now().isoformat(),
        "model_description": MODEL_DESCRIPTIONS[model],
        "accuracy": float(accuracy),
        "energy_consumed_wh": emissions_data.energy_consumed * 1000,
        "emissions_gco2eq": emissions_data.emissions * 1000,
        "emissions_data": clean_emissions_data(emissions_data),
        "api_route": ROUTE,
        "dataset_config": {
            "dataset_name": request.dataset_name,
            "test_size": request.test_size,
            "test_seed": request.test_seed
        }
    }
    
    return results