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