from src.deepeval.base_task import BaseTask from collections import defaultdict from src.deepeval.utils import accuracy, accuracy_standard_error from typing import Any import re from datasets import load_dataset import os from dotenv import load_dotenv import openai from transformers import AutoModelForCausalLM, AutoTokenizer, LogitsProcessorList, LogitsProcessor import torch from typing import List from scipy.stats import pearsonr class STSTask(BaseTask): def __init__(self, model_name): super().__init__("metunlp/sts_tr", model_name=model_name) self.metric = "pearson" def load_dataset_from_hf(self): dataset = super().load_dataset_from_hf() return dataset def generate_response_sts_multi_token(self, msg, max_new_tokens=5, choices: list = []): """ Handles multiple-choice questions where answers might have multiple tokens. """ # Ensure tokenizer has proper special tokens set if self.tokenizer.pad_token is None: self.tokenizer.pad_token = self.tokenizer.eos_token if self.model.config.pad_token_id is None: self.model.config.pad_token_id = self.tokenizer.pad_token_id chat = [ {"role": "user", "content": "Cümleler arası benzerlik puanlamakla görevli bir modelsin. Aşağıdaki iki cümle için puan ver."}, {"role": "assistant", "content": "Hazırım cümleleri gönderebilirsin."}, {"role": "user", "content": f"{msg}"}, ] formatted_chat = self.tokenizer.apply_chat_template(chat, tokenize=False, add_generation_prompt=True) #print(formatted_chat) inputs = self.tokenizer(formatted_chat, return_tensors="pt", padding=True, truncation=True) input_ids = inputs.input_ids.to(self.model.device) attention_mask = inputs.attention_mask.to(self.model.device) # Generate the sequence of letters starting from 'A' letters = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ".", ","] encoded_choices = [self.tokenizer.encode(letter, add_special_tokens=False) for letter in letters] flattened_encoded_choices = [item for sublist in encoded_choices for item in sublist] # Flatten the list #print(flattened_encoded_choices) allowed_tokens = flattened_encoded_choices allowed_tokens += self.get_chat_template_tokens() # Get the special chat tokens allowed_token_ids = set(allowed_tokens) # Ensure uniqueness # Custom LogitsProcessor to restrict generation class RestrictToABCDLogitsProcessor(LogitsProcessor): def __call__(self, input_ids, scores): mask = torch.full_like(scores, float("-inf")) # Block all tokens mask[:, list(allowed_token_ids)] = scores[:, list(allowed_token_ids)] # Allow only A, B, C, D tokens return mask logits_processor = LogitsProcessorList([RestrictToABCDLogitsProcessor()]) # Generate response output = self.model.generate( input_ids, do_sample=True, attention_mask=attention_mask, max_new_tokens=max_new_tokens, eos_token_id=self.tokenizer.eos_token_id, pad_token_id=self.tokenizer.pad_token_id, temperature=0.4, logits_processor=logits_processor, ) generated_ids = output[0] # The generated sequence including the prompt generated_tokens = generated_ids[len(input_ids[0]):] # Exclude the input_ids part generated_text = self.tokenizer.decode(generated_tokens, skip_special_tokens=True) return generated_text def evaluate(self) -> dict[str, Any]: responses = [] model_scores = [] corr_answers = [] total_count = 0 true = 0 for row in self.dataset: total_count += 1 # Get values from row answer = row["score"] cümle1 = row["sentence_1"] cümle2 = row["sentence_2"] # Prints for debugging # print(f"Answer: {answer}") # print("Type of answer:", type(answer)) # Construct the prompt/message instruction = f""" Görev: İki cümle arasındaki anlamsal benzerliği, aşağıda verilen ölçeğe göre 0'dan 5'e kadar bir skorla cinsinden puanlayın. Cümleler ne kadar benzerse, puan o kadar yüksek olmalıdır. Cümleler ne kadar alakasızsa, puan o kadar düşük olmalıdır. Alakasız cümlelere 0 veya düşük puan vermekten çekinme. Aşağıda her puan için açıklamalar verilmiştir. Bu açıklamalara göre puanlamanızı yapın. Skor Ölçeği: 5 – Tamamen Eşdeğer 4 – Önemsiz küçük Farklılıklarla Eşdeğer 3 – Önemli Farklılıklar, Büyük Orande Eşdeğer 2 – Yakın Konu, Farklı Anlam 1 – Çok Az Benzerlik, Sadece Konu Yakınlığı 0 – Anlamsal Olarak Alakasız Talimat: Aşağıda verilen iki cümle için yukarıdaki ölçütleri dikkate alarak bir puan verin. **Sadece bir float verin.** """ prompt = f"""{instruction}\nCümle 1: {cümle1}\nCümle 2: {cümle2}\nSadece tek bir float söyleyin, ek bir kelime ya da sembol kullanmayın.""" message = prompt # Get/format answer of the model model_answer = self.generate_response_sts_multi_token(message, max_new_tokens=3) responses.append(model_answer) model_answer_cleaned = model_answer.strip().replace('\n', '').replace(' ', '').upper().replace(':','') # Print answers # print(f"Correct Answer: {answer}") # print(f"Model Answer: {model_answer}") # print(f"Model Answer Cleaned: {model_answer_cleaned}") # print(f"Result: {answer == model_answer_cleaned}") # ACCURACY if self.metric == "acc": if str(answer) == model_answer_cleaned: true += 1 elif self.metric == "pearson": try: float_model_answer = float(model_answer_cleaned) model_scores.append(float_model_answer) corr_answers.append(answer) except: print("Unable to turn the model answer into float: ", model_answer_cleaned) # Print results if self.metric =="pearson": try: # print("Calculating pearson corr") # print(f"Model answers: {model_scores}") # print(f"Correct answers: {corr_answers}") pearson_corr, _ = pearsonr(model_scores, corr_answers) normalized_corr = (pearson_corr + 1) / 2 # Normalize to [0, 1] except: print("No right answer, unable to calculate pearson correlation") print(f"Accuracy: {normalized_corr:.2%}") print("Results:", responses) acc_stderr = accuracy_standard_error(normalized_corr, total_count) return {"acc": normalized_corr, "acc_stderr": acc_stderr}