Spaces:
Running
Running
import torch | |
from transformers import AutoTokenizer, AutoModelForSequenceClassification, pipeline | |
import logging | |
import time | |
import numpy as np | |
from sklearn.metrics import accuracy_score, f1_score, precision_score, recall_score | |
import os | |
import json | |
from typing import List, Dict, Any, Tuple, Optional, Union | |
import threading | |
# Loglama yapılandırması | |
logger = logging.getLogger(__name__) | |
handler = logging.StreamHandler() | |
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') | |
handler.setFormatter(formatter) | |
logger.addHandler(handler) | |
logger.setLevel(logging.INFO) | |
class ModelSelector: | |
""" | |
Farklı NLP modellerini değerlendirip en iyi performansı sağlayanı seçen sınıf. | |
""" | |
def __init__(self, cache_dir: Optional[str] = None, use_cache: bool = True) -> None: | |
""" | |
Model Seçici sınıfını başlatır. | |
Args: | |
cache_dir: Model ve değerlendirme sonuçlarının önbelleğe alınacağı dizin | |
use_cache: Önbellek kullanımını etkinleştir/devre dışı bırak | |
""" | |
self.device = "cuda" if torch.cuda.is_available() else "cpu" | |
self.toxicity_models = [] | |
self.quality_models = [] | |
self.best_toxicity_model = None | |
self.best_quality_model = None | |
self.use_cache = use_cache | |
self.cache_dir = cache_dir or os.path.join(os.path.expanduser("~"), ".text_quality_toxicity_cache") | |
# Önbellek dizinini oluştur | |
if self.use_cache and not os.path.exists(self.cache_dir): | |
os.makedirs(self.cache_dir, exist_ok=True) | |
logger.info( | |
f"ModelSelector başlatıldı. Cihaz: {self.device}, Önbellek: {'Etkin' if use_cache else 'Devre dışı'}") | |
def load_candidate_models(self) -> bool: | |
""" | |
Değerlendirme için aday modelleri yükler. | |
Returns: | |
bool: Yükleme başarılı mı? | |
""" | |
# Önbellekten okunan modeller | |
cached_models = self._load_from_cache() | |
if cached_models: | |
self.toxicity_models = cached_models.get("toxicity_models", []) | |
self.quality_models = cached_models.get("quality_models", []) | |
self.best_toxicity_model = cached_models.get("best_toxicity_model") | |
self.best_quality_model = cached_models.get("best_quality_model") | |
# Önbellek bulundu, doğrulama yap | |
if self.best_toxicity_model and self.best_quality_model: | |
logger.info("Önbellekten en iyi modeller yüklendi.") | |
# Tokenizer ve model erişilebilir mi kontrol et | |
if "tokenizer" in self.best_toxicity_model and "model" in self.best_toxicity_model: | |
# En iyi model önbellekten doğru yüklendi | |
return True | |
# Zararlılık modelleri - Türkçe için optimize edilmiş | |
toxicity_candidates = [ | |
{"name": "savasy/bert-base-turkish-sentiment", "type": "sentiment", | |
"language": "tr", "priority": 1, "description": "Türkçe duygu analizi için BERT modeli"}, | |
{"name": "loodos/electra-turkish-sentiment", "type": "sentiment", | |
"language": "tr", "priority": 2, "description": "Türkçe duygu analizi için ELECTRA modeli"}, | |
{"name": "dbmdz/bert-base-turkish-cased", "type": "general", | |
"language": "tr", "priority": 3, "description": "Genel amaçlı Türkçe BERT modeli"}, | |
{"name": "ytu-ce-cosmos/turkish-bert-uncased-toxicity", "type": "toxicity", | |
"language": "tr", "priority": 4, "description": "Türkçe zararlılık tespiti için BERT modeli", | |
"optional": True}, | |
{"name": "unitary/toxic-bert", "type": "toxicity", | |
"language": "en", "priority": 5, "description": "İngilizce zararlılık tespiti BERT modeli"} | |
] | |
# Kalite modelleri | |
quality_candidates = [ | |
{"name": "Helsinki-NLP/opus-mt-tr-en", "type": "translation", | |
"language": "tr", "priority": 1, "description": "Türkçe-İngilizce çeviri modeli"}, | |
{"name": "tuner/pegasus-turkish", "type": "summarization", | |
"language": "tr", "priority": 2, "description": "Türkçe özetleme için PEGASUS modeli", "optional": True}, | |
{"name": "dbmdz/t5-base-turkish-summarization", "type": "summarization", | |
"language": "tr", "priority": 3, "description": "Türkçe özetleme için T5 modeli", "optional": True}, | |
{"name": "sshleifer/distilbart-cnn-6-6", "type": "summarization", | |
"language": "en", "priority": 4, "description": "İngilizce özetleme için DistilBART modeli"} | |
] | |
# Modelleri önceliğe göre sırala | |
toxicity_candidates.sort(key=lambda x: x.get("priority", 999)) | |
quality_candidates.sort(key=lambda x: x.get("priority", 999)) | |
# Zaman aşımını önlemek için asenkron model yükleme | |
self._load_models_async(toxicity_candidates, quality_candidates) | |
# Yeterli model yüklendi mi kontrol et | |
return len(self.toxicity_models) > 0 and len(self.quality_models) > 0 | |
def _load_models_async(self, toxicity_candidates: List[Dict[str, Any]], | |
quality_candidates: List[Dict[str, Any]]) -> None: | |
""" | |
Modelleri asenkron olarak yükler | |
Args: | |
toxicity_candidates: Zararlılık modellerinin listesi | |
quality_candidates: Kalite modellerinin listesi | |
""" | |
# Zararlılık modelleri için eş zamanlı yükleme | |
toxicity_threads = [] | |
for candidate in toxicity_candidates: | |
thread = threading.Thread( | |
target=self._load_toxicity_model, | |
args=(candidate,) | |
) | |
thread.start() | |
toxicity_threads.append(thread) | |
# Kalite modelleri için eş zamanlı yükleme | |
quality_threads = [] | |
for candidate in quality_candidates: | |
thread = threading.Thread( | |
target=self._load_quality_model, | |
args=(candidate,) | |
) | |
thread.start() | |
quality_threads.append(thread) | |
# Tüm işlemlerin tamamlanmasını bekle (timeout ile) | |
for thread in toxicity_threads: | |
thread.join(timeout=60) # Her model için 60 saniye timeout | |
for thread in quality_threads: | |
thread.join(timeout=60) # Her model için 60 saniye timeout | |
def _load_toxicity_model(self, candidate: Dict[str, Any]) -> None: | |
""" | |
Bir zararlılık modelini yükler | |
Args: | |
candidate: Model bilgilerini içeren sözlük | |
""" | |
try: | |
logger.info(f"Zararlılık modeli yükleniyor: {candidate['name']}") | |
start_time = time.time() | |
tokenizer = AutoTokenizer.from_pretrained(candidate["name"]) | |
model = AutoModelForSequenceClassification.from_pretrained(candidate["name"]) | |
model.to(self.device) | |
load_time = time.time() - start_time | |
candidate["tokenizer"] = tokenizer | |
candidate["model"] = model | |
candidate["load_time"] = load_time | |
# Liste eşzamanlı erişim için koruma | |
with threading.Lock(): | |
self.toxicity_models.append(candidate) | |
logger.info(f"{candidate['name']} modeli {load_time:.2f} saniyede başarıyla yüklendi") | |
except Exception as e: | |
if candidate.get("optional", False): | |
logger.warning(f"Opsiyonel model {candidate['name']} atlanıyor: {str(e)}") | |
else: | |
logger.error(f"{candidate['name']} yüklenirken hata: {str(e)}") | |
def _load_quality_model(self, candidate: Dict[str, Any]) -> None: | |
""" | |
Bir kalite modelini yükler | |
Args: | |
candidate: Model bilgilerini içeren sözlük | |
""" | |
try: | |
logger.info(f"Kalite modeli yükleniyor: {candidate['name']}") | |
start_time = time.time() | |
if candidate["type"] == "translation": | |
pipe = pipeline("translation", model=candidate["name"], device=0 if self.device == "cuda" else -1) | |
elif candidate["type"] == "summarization": | |
pipe = pipeline("summarization", model=candidate["name"], device=0 if self.device == "cuda" else -1) | |
load_time = time.time() - start_time | |
candidate["pipeline"] = pipe | |
candidate["load_time"] = load_time | |
# Liste eşzamanlı erişim için koruma | |
with threading.Lock(): | |
self.quality_models.append(candidate) | |
logger.info(f"{candidate['name']} modeli {load_time:.2f} saniyede başarıyla yüklendi") | |
except Exception as e: | |
if candidate.get("optional", False): | |
logger.warning(f"Opsiyonel model {candidate['name']} atlanıyor: {str(e)}") | |
else: | |
logger.error(f"{candidate['name']} yüklenirken hata: {str(e)}") | |
def evaluate_toxicity_models(self, validation_texts: List[str], | |
validation_labels: List[int]) -> Optional[Dict[str, Any]]: | |
""" | |
Zararlılık modellerini değerlendirir ve en iyisini seçer | |
Args: | |
validation_texts: Doğrulama metinleri | |
validation_labels: Doğrulama etiketleri (1=zararlı, 0=zararsız) | |
Returns: | |
Dict[str, Any]: En iyi modelin bilgileri veya None | |
""" | |
if not self.toxicity_models: | |
logger.error("Değerlendirme için zararlılık modeli yüklenmemiş") | |
return None | |
results = [] | |
for model_info in self.toxicity_models: | |
logger.info(f"Zararlılık modeli değerlendiriliyor: {model_info['name']}") | |
model = model_info["model"] | |
tokenizer = model_info["tokenizer"] | |
predictions = [] | |
start_time = time.time() | |
try: | |
for text in validation_texts: | |
# Metni tokenize et | |
inputs = tokenizer(text, return_tensors="pt", truncation=True, max_length=512) | |
inputs = {key: val.to(self.device) for key, val in inputs.items()} | |
# Tahmin yap | |
with torch.no_grad(): | |
outputs = model(**inputs) | |
# Sonucu almak için model tipine göre işlem yap | |
if model_info["type"] == "sentiment": | |
# Sentiment modellerinde genellikle 0=negatif, 1=nötr, 2=pozitif | |
# veya 0=negatif, 1=pozitif | |
probs = torch.softmax(outputs.logits, dim=1).cpu().numpy()[0] | |
if len(probs) >= 3: | |
# Negatif olasılığını zararlılık olarak kabul et | |
pred = 1 if probs[0] > 0.5 else 0 | |
else: | |
# İki sınıflı model | |
pred = 1 if probs[0] > 0.5 else 0 | |
elif model_info["type"] == "toxicity": | |
# Toxicity modelleri genellikle 0=non-toxic, 1=toxic | |
probs = torch.softmax(outputs.logits, dim=1).cpu().numpy()[0] | |
pred = 1 if probs[1] > 0.5 else 0 | |
else: | |
# Genel model - varsayılan | |
probs = torch.softmax(outputs.logits, dim=1).cpu().numpy()[0] | |
pred = 1 if probs[0] > 0.5 else 0 | |
predictions.append(pred) | |
eval_time = time.time() - start_time | |
# Performans metrikleri hesapla | |
accuracy = accuracy_score(validation_labels, predictions) | |
precision = precision_score(validation_labels, predictions, average='binary', zero_division=0) | |
recall = recall_score(validation_labels, predictions, average='binary', zero_division=0) | |
f1 = f1_score(validation_labels, predictions, average='binary', zero_division=0) | |
# F1, precision ve recall'un ağırlıklı ortalaması | |
weighted_score = (f1 * 0.5) + (precision * 0.3) + (recall * 0.2) | |
# Sonuçları kaydet | |
evaluation_result = { | |
"model": model_info, | |
"accuracy": float(accuracy), | |
"precision": float(precision), | |
"recall": float(recall), | |
"f1_score": float(f1), | |
"weighted_score": float(weighted_score), | |
"eval_time": float(eval_time), | |
"predictions": predictions | |
} | |
results.append(evaluation_result) | |
logger.info( | |
f"{model_info['name']} - Doğruluk: {accuracy:.4f}, " | |
f"F1: {f1:.4f}, Precision: {precision:.4f}, " | |
f"Recall: {recall:.4f}, Süre: {eval_time:.2f}s" | |
) | |
except Exception as e: | |
logger.error(f"{model_info['name']} değerlendirilirken hata: {str(e)}") | |
if not results: | |
logger.error("Hiçbir model değerlendirilemedi") | |
return None | |
# Sıralama ve en iyi modeli seç (ağırlıklı skora göre) | |
results.sort(key=lambda x: x["weighted_score"], reverse=True) | |
best_model = results[0]["model"] | |
logger.info( | |
f"En iyi zararlılık modeli: {best_model['name']} - " | |
f"Ağırlıklı skor: {results[0]['weighted_score']:.4f}, " | |
f"F1: {results[0]['f1_score']:.4f}" | |
) | |
self.best_toxicity_model = best_model | |
# Önbelleğe kaydet | |
if self.use_cache: | |
self._save_to_cache() | |
return best_model | |
def evaluate_quality_models(self, validation_texts: List[str], | |
reference_summaries: Optional[List[str]] = None) -> Optional[Dict[str, Any]]: | |
""" | |
Kalite modellerini değerlendirir ve en iyisini seçer | |
Args: | |
validation_texts: Doğrulama metinleri | |
reference_summaries: Referans özetler (opsiyonel) | |
Returns: | |
Dict[str, Any]: En iyi modelin bilgileri veya None | |
""" | |
if not self.quality_models: | |
logger.error("Değerlendirme için kalite modeli yüklenmemiş") | |
return None | |
results = [] | |
for model_info in self.quality_models: | |
logger.info(f"Kalite modeli değerlendiriliyor: {model_info['name']}") | |
pipe = model_info["pipeline"] | |
start_time = time.time() | |
processing_success = 0 | |
avg_processing_time = [] | |
# Her metni değerlendir | |
for i, text in enumerate(validation_texts[:5]): # Performans için sadece ilk 5 metni değerlendir | |
try: | |
text_start_time = time.time() | |
if model_info["type"] == "translation": | |
_ = pipe(text, max_length=100) | |
elif model_info["type"] == "summarization": | |
# Çok kısa metinlerde sorun oluşmaması için metin uzunluğunu kontrol et | |
text_words = len(text.split()) | |
max_length = min(100, max(30, text_words // 2)) | |
min_length = min(30, max(5, text_words // 4)) | |
_ = pipe(text, max_length=max_length, min_length=min_length, do_sample=False) | |
text_process_time = time.time() - text_start_time | |
avg_processing_time.append(text_process_time) | |
processing_success += 1 | |
except Exception as e: | |
logger.warning(f"{model_info['name']} için metin {i} işlenirken hata: {str(e)}") | |
eval_time = time.time() - start_time | |
success_rate = processing_success / min(5, len(validation_texts)) | |
avg_time = np.mean(avg_processing_time) if avg_processing_time else float('inf') | |
# Modelin tipine göre skoru ayarla | |
if model_info["language"] == "tr": | |
# Türkçe modeller için daha yüksek ağırlık | |
language_weight = 1.2 | |
else: | |
language_weight = 0.8 | |
# Sonuçları kaydet | |
evaluation_result = { | |
"model": model_info, | |
"success_rate": float(success_rate), | |
"avg_processing_time": float(avg_time), | |
"eval_time": float(eval_time), | |
"language_weight": float(language_weight) | |
} | |
results.append(evaluation_result) | |
logger.info( | |
f"{model_info['name']} - Başarı Oranı: {success_rate:.2f}, " | |
f"Ortalama İşleme Süresi: {avg_time:.4f}s, " | |
f"Toplam Süre: {eval_time:.2f}s" | |
) | |
if not results: | |
logger.error("Hiçbir kalite modeli değerlendirilemedi") | |
return None | |
# Sıralama ve en iyi modeli seç | |
# Başarı oranı, dil ağırlığı ve hız faktörlerini dengeleyen bir formül | |
for result in results: | |
result["score"] = ( | |
result["success_rate"] * 0.6 + | |
(1 / (1 + result["avg_processing_time"])) * 0.2 + | |
result["language_weight"] * 0.2 | |
) | |
results.sort(key=lambda x: x["score"], reverse=True) | |
best_model = results[0]["model"] | |
logger.info( | |
f"En iyi kalite modeli: {best_model['name']} - " | |
f"Skor: {results[0]['score']:.4f}, " | |
f"Başarı Oranı: {results[0]['success_rate']:.2f}" | |
) | |
self.best_quality_model = best_model | |
# Önbelleğe kaydet | |
if self.use_cache: | |
self._save_to_cache() | |
return best_model | |
def select_best_models(self, sample_texts: List[str]) -> bool: | |
""" | |
En iyi modelleri örnek veriyle otomatik olarak seçer | |
Args: | |
sample_texts: Örnek metinler listesi | |
Returns: | |
bool: Model seçimi başarılı mı? | |
""" | |
# Modeller önbellekten yüklendiyse doğrudan kullan | |
if self.best_toxicity_model and self.best_quality_model: | |
logger.info("En iyi modeller önbellekten kullanılıyor") | |
return True | |
if not self.toxicity_models or not self.quality_models: | |
if not self.load_candidate_models(): | |
logger.error("Aday modeller yüklenemedi") | |
return False | |
# Basit zararlılık değerlendirmesi için örnekler | |
toxic_samples = [ | |
"Bu insanlar gerçekten çok aptal! Hepsi geri zekalı ve cahil.", | |
"Senden nefret ediyorum, kaybol buradan!", | |
"sen ne anlarsın ki bu konudan? gerizekalılar bile senden daha mantıklı konuşur" | |
] | |
non_toxic_samples = [ | |
"Türkiye, zengin tarihi ve kültürel mirası ile güzel bir ülkedir.", | |
"Bugün hava çok güzel. Parkta yürüyüş yaptım ve kuşları izledim.", | |
"Bilgisayarınızı hızlandırmak için gereksiz programları kaldırın." | |
] | |
# Doğrulama verisi hazırla | |
validation_texts = toxic_samples + non_toxic_samples | |
if sample_texts and len(sample_texts) > 0: | |
# Kullanıcı örneklerinden bir kısmını ekle (en fazla 5 tane) | |
validation_texts.extend(sample_texts[:5]) | |
validation_labels = [1, 1, 1, 0, 0, 0] + [0] * min(5, len(sample_texts)) | |
# En iyi zararlılık modelini seç | |
best_toxicity = self.evaluate_toxicity_models(validation_texts, validation_labels) | |
# En iyi kalite modelini seç | |
best_quality = self.evaluate_quality_models(validation_texts) | |
success = best_toxicity is not None and best_quality is not None | |
if success and self.use_cache: | |
self._save_to_cache() | |
return success | |
def get_best_models(self) -> Dict[str, Any]: | |
""" | |
Seçilen en iyi modelleri döndürür | |
Returns: | |
Dict[str, Any]: En iyi modellerin bilgileri | |
""" | |
return { | |
"toxicity_model": self.best_toxicity_model["model"] if self.best_toxicity_model else None, | |
"toxicity_tokenizer": self.best_toxicity_model["tokenizer"] if self.best_toxicity_model else None, | |
"quality_pipeline": self.best_quality_model["pipeline"] if self.best_quality_model else None, | |
"toxicity_model_info": { | |
"name": self.best_toxicity_model["name"] if self.best_toxicity_model else "Unknown", | |
"description": self.best_toxicity_model["description"] if self.best_toxicity_model else "Unknown", | |
"language": self.best_toxicity_model["language"] if self.best_toxicity_model else "Unknown" | |
}, | |
"quality_model_info": { | |
"name": self.best_quality_model["name"] if self.best_quality_model else "Unknown", | |
"description": self.best_quality_model["description"] if self.best_quality_model else "Unknown", | |
"language": self.best_quality_model["language"] if self.best_quality_model else "Unknown" | |
} | |
} | |
def _save_to_cache(self) -> None: | |
"""Modellerin değerlendirme sonuçlarını önbelleğe kaydeder""" | |
if not self.use_cache: | |
return | |
try: | |
cache_file = os.path.join(self.cache_dir, "model_selection_results.json") | |
# Modeller ve tokenizer'ları hariç tutarak kalan bilgileri kaydet | |
cache_data = { | |
"timestamp": time.time(), | |
"toxicity_models": [ | |
{k: v for k, v in model.items() if k not in ["model", "tokenizer"]} | |
for model in self.toxicity_models | |
], | |
"quality_models": [ | |
{k: v for k, v in model.items() if k not in ["pipeline"]} | |
for model in self.quality_models | |
], | |
"best_toxicity_model": ( | |
{k: v for k, v in self.best_toxicity_model.items() if k not in ["model", "tokenizer"]} | |
if self.best_toxicity_model else None | |
), | |
"best_quality_model": ( | |
{k: v for k, v in self.best_quality_model.items() if k not in ["pipeline"]} | |
if self.best_quality_model else None | |
) | |
} | |
with open(cache_file, 'w', encoding='utf-8') as f: | |
json.dump(cache_data, f, ensure_ascii=False, indent=2) | |
logger.info(f"Model seçim sonuçları önbelleğe kaydedildi: {cache_file}") | |
except Exception as e: | |
logger.error(f"Önbelleğe kaydetme hatası: {str(e)}") | |
def _load_from_cache(self) -> Optional[Dict[str, Any]]: | |
""" | |
Önbellekten modellerin değerlendirme sonuçlarını yükler | |
Returns: | |
Optional[Dict[str, Any]]: Yüklenen önbellek verisi veya None | |
""" | |
if not self.use_cache: | |
return None | |
try: | |
cache_file = os.path.join(self.cache_dir, "model_selection_results.json") | |
if not os.path.exists(cache_file): | |
return None | |
# Önbellek dosyasının yaşını kontrol et (24 saatten eskiyse yok say) | |
file_age = time.time() - os.path.getmtime(cache_file) | |
if file_age > 86400: # 24 saat = 86400 saniye | |
logger.info(f"Önbellek dosyası çok eski ({file_age / 3600:.1f} saat), tekrar değerlendirme yapılacak") | |
return None | |
with open(cache_file, 'r', encoding='utf-8') as f: | |
cache_data = json.load(f) | |
logger.info(f"Model seçim sonuçları önbellekten yüklendi: {cache_file}") | |
# Önbellekten sadece isim bilgilerini yükle, modellerin kendisini değil | |
return cache_data | |
except Exception as e: | |
logger.error(f"Önbellekten yükleme hatası: {str(e)}") | |
return None |