Spaces:
Running
Running
sarizeybekk
commited on
Commit
·
bd97f47
1
Parent(s):
5327ce6
Remove venv from Git tracking and add to .gitignore
Browse files- .gitignore +2 -0
- README.md +5 -3
- app.py +774 -0
- data/sample_data.csv +11 -0
- models/__pycache__/model_loader.cpython-312.pyc +0 -0
- models/model_loader.py +105 -0
- models/model_manager.py +316 -0
- models/model_selector.py +571 -0
- requirements.txt +62 -0
- utils/__pycache__/data_handler.cpython-312.pyc +0 -0
- utils/__pycache__/keyword_extractor.cpython-312.pyc +0 -0
- utils/__pycache__/language_detector.cpython-312.pyc +0 -0
- utils/__pycache__/quality_scorer.cpython-312.pyc +0 -0
- utils/__pycache__/sentiment_analyzer.cpython-312.pyc +0 -0
- utils/__pycache__/text_improver.cpython-312.pyc +0 -0
- utils/__pycache__/toxicity_scorer.cpython-312.pyc +0 -0
- utils/data_handler.py +177 -0
- utils/keyword_extractor.py +273 -0
- utils/language_detector.py +260 -0
- utils/quality_scorer.py +198 -0
- utils/sentiment_analyzer.py +169 -0
- utils/text_improver.py +337 -0
- utils/toxicity_scorer.py +188 -0
.gitignore
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
1 |
+
# Sanal ortam
|
2 |
+
venv/
|
README.md
CHANGED
@@ -1,12 +1,14 @@
|
|
1 |
---
|
2 |
title: Textqualtox
|
3 |
-
emoji:
|
4 |
-
colorFrom:
|
5 |
-
colorTo:
|
6 |
sdk: streamlit
|
7 |
sdk_version: 1.44.0
|
8 |
app_file: app.py
|
9 |
pinned: false
|
|
|
|
|
10 |
---
|
11 |
|
12 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
1 |
---
|
2 |
title: Textqualtox
|
3 |
+
emoji: 🌖
|
4 |
+
colorFrom: green
|
5 |
+
colorTo: red
|
6 |
sdk: streamlit
|
7 |
sdk_version: 1.44.0
|
8 |
app_file: app.py
|
9 |
pinned: false
|
10 |
+
license: mit
|
11 |
+
short_description: Quality, toxicity, and sentiment analysis of text data.
|
12 |
---
|
13 |
|
14 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
app.py
ADDED
@@ -0,0 +1,774 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
import pandas as pd
|
3 |
+
import os
|
4 |
+
import matplotlib.pyplot as plt
|
5 |
+
import plotly.express as px
|
6 |
+
from models.model_loader import ModelManager
|
7 |
+
from utils.quality_scorer import QualityScorer
|
8 |
+
from utils.toxicity_scorer import ToxicityScorer
|
9 |
+
from utils.data_handler import DataHandler
|
10 |
+
from utils.sentiment_analyzer import SentimentAnalyzer
|
11 |
+
from utils.text_improver import TextImprover
|
12 |
+
from utils.keyword_extractor import KeywordExtractor
|
13 |
+
from utils.language_detector import LanguageDetector
|
14 |
+
import logging
|
15 |
+
import time
|
16 |
+
import re
|
17 |
+
|
18 |
+
|
19 |
+
# Loglama ayarları
|
20 |
+
logger = logging.getLogger(__name__)
|
21 |
+
handler = logging.StreamHandler()
|
22 |
+
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
23 |
+
handler.setFormatter(formatter)
|
24 |
+
logger.addHandler(handler)
|
25 |
+
logger.setLevel(logging.INFO)
|
26 |
+
|
27 |
+
# Veri klasörlerini oluştur
|
28 |
+
os.makedirs("data/processed", exist_ok=True)
|
29 |
+
|
30 |
+
# Sabitleri tanımla
|
31 |
+
SAMPLE_TEXT = """Bu bir örnek metindir. Bu metin, sistemin nasıl çalıştığını göstermek için kullanılmaktadır.
|
32 |
+
Metin kalitesi ve zararlılık değerlendirmesi için kullanılabilir."""
|
33 |
+
|
34 |
+
|
35 |
+
def display_model_info(models_dict):
|
36 |
+
"""Model bilgilerini görüntüler"""
|
37 |
+
model_info = models_dict.get("model_info", {})
|
38 |
+
|
39 |
+
st.markdown("---")
|
40 |
+
st.markdown("### Model Bilgileri")
|
41 |
+
|
42 |
+
col1, col2 = st.columns(2)
|
43 |
+
|
44 |
+
with col1:
|
45 |
+
toxicity_info = model_info.get("toxicity", {})
|
46 |
+
st.markdown("#### Zararlılık Modeli")
|
47 |
+
|
48 |
+
model_name = toxicity_info.get("name", "Bilinmiyor")
|
49 |
+
model_description = toxicity_info.get("description", "")
|
50 |
+
model_language = toxicity_info.get("language", "")
|
51 |
+
|
52 |
+
st.code(model_name, language="plaintext")
|
53 |
+
|
54 |
+
|
55 |
+
language_icon = "🇹🇷" if model_language == "tr" else "🇺🇸" if model_language == "en" else "🌐"
|
56 |
+
st.caption(f"{language_icon} {model_description}")
|
57 |
+
|
58 |
+
with col2:
|
59 |
+
quality_info = model_info.get("quality", {})
|
60 |
+
st.markdown("#### Kalite Modeli")
|
61 |
+
|
62 |
+
model_name = quality_info.get("name", "Bilinmiyor")
|
63 |
+
model_description = quality_info.get("description", "")
|
64 |
+
model_language = quality_info.get("language", "")
|
65 |
+
|
66 |
+
st.code(model_name, language="plaintext")
|
67 |
+
|
68 |
+
|
69 |
+
language_icon = "🇹🇷" if model_language == "tr" else "🇺🇸" if model_language == "en" else "🌐"
|
70 |
+
st.caption(f"{language_icon} {model_description}")
|
71 |
+
|
72 |
+
# Optimizasyon bilgisi
|
73 |
+
st.info("""
|
74 |
+
Bu sistem Türkçe metinler için otomatik optimize edilmiştir.
|
75 |
+
En iyi performansı gösteren modeller test sonuçlarına göre seçilmiştir.
|
76 |
+
""")
|
77 |
+
|
78 |
+
|
79 |
+
@st.cache_resource
|
80 |
+
def load_models():
|
81 |
+
"""Modelleri yükler ve önbelleğe alır"""
|
82 |
+
with st.spinner("Modeller değerlendiriliyor ve seçiliyor... Bu işlem birkaç dakika sürebilir."):
|
83 |
+
# Örnek metinlerin bir kısmı
|
84 |
+
sample_texts = [
|
85 |
+
"Türkiye, zengin tarihi ve kültürel mirası ile dünyanın en etkileyici ülkelerinden biridir.",
|
86 |
+
"turkiye guzel bi ulke. cok tarihi yerler var yani. denızleri guzel. yemekleride guzel.",
|
87 |
+
"Bu grup insanlar gerçekten çok aptal! Hepsi geri zekalı ve cahil. Bunlarla konuşmak bile zaman kaybı.",
|
88 |
+
"Kediler harika evcil hayvanlardır. Bağımsız yapıları vardır. Temizlik konusunda çok titizlerdir."
|
89 |
+
]
|
90 |
+
|
91 |
+
try:
|
92 |
+
# Cache dizinini belirle
|
93 |
+
cache_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), ".model_cache")
|
94 |
+
os.makedirs(cache_dir, exist_ok=True)
|
95 |
+
|
96 |
+
# Gelişmiş model yükleme stratejisi
|
97 |
+
model_manager = ModelManager(cache_dir=cache_dir, use_cache=True)
|
98 |
+
success = model_manager.load_models_auto_select(sample_texts)
|
99 |
+
|
100 |
+
if not success:
|
101 |
+
st.error("Otomatik model seçimi başarısız oldu. Varsayılan modeller yükleniyor.")
|
102 |
+
model_manager.load_default_models()
|
103 |
+
|
104 |
+
models_dict = model_manager.get_models()
|
105 |
+
|
106 |
+
toxicity_scorer = ToxicityScorer(
|
107 |
+
model=models_dict["toxicity_model"],
|
108 |
+
tokenizer=models_dict["toxicity_tokenizer"]
|
109 |
+
)
|
110 |
+
|
111 |
+
quality_scorer = QualityScorer(
|
112 |
+
quality_pipeline=models_dict["quality_pipeline"]
|
113 |
+
)
|
114 |
+
|
115 |
+
# Skorlayıcıların model bilgilerini paylaşması için
|
116 |
+
toxicity_scorer.model_info = models_dict["model_info"]["toxicity"]
|
117 |
+
quality_scorer.model_info = models_dict["model_info"]["quality"]
|
118 |
+
|
119 |
+
return toxicity_scorer, quality_scorer, models_dict
|
120 |
+
|
121 |
+
except Exception as e:
|
122 |
+
st.error(f"Model yükleme hatası: {str(e)}")
|
123 |
+
# Yedek (basit) strateji
|
124 |
+
logger.error(f"Model yükleme hatası: {str(e)}, basit modellere dönülüyor")
|
125 |
+
|
126 |
+
toxicity_scorer = ToxicityScorer() # Varsayılan modelle başlat
|
127 |
+
quality_scorer = QualityScorer() # Varsayılan modelle başlat
|
128 |
+
|
129 |
+
models_dict = {
|
130 |
+
"model_info": {
|
131 |
+
"toxicity": {"name": "Varsayılan Model",
|
132 |
+
"description": "Hata nedeniyle varsayılan model kullanılıyor", "language": "unknown"},
|
133 |
+
"quality": {"name": "Varsayılan Model",
|
134 |
+
"description": "Hata nedeniyle varsayılan model kullanılıyor", "language": "unknown"}
|
135 |
+
}
|
136 |
+
}
|
137 |
+
|
138 |
+
return toxicity_scorer, quality_scorer, models_dict
|
139 |
+
|
140 |
+
|
141 |
+
def analyze_single_text(text, toxicity_scorer, quality_scorer):
|
142 |
+
"""Tek bir metin için analiz yapar"""
|
143 |
+
result = {}
|
144 |
+
|
145 |
+
# Zararlılık analizi
|
146 |
+
start_time = time.time()
|
147 |
+
toxicity_score = toxicity_scorer.score_text(text)
|
148 |
+
result["toxicity_score"] = toxicity_score
|
149 |
+
result["toxicity_time"] = time.time() - start_time
|
150 |
+
|
151 |
+
# Kalite analizi
|
152 |
+
start_time = time.time()
|
153 |
+
quality_score, quality_features = quality_scorer.score_text(text)
|
154 |
+
result["quality_score"] = quality_score
|
155 |
+
result["quality_features"] = quality_features
|
156 |
+
result["quality_time"] = time.time() - start_time
|
157 |
+
|
158 |
+
return result
|
159 |
+
|
160 |
+
|
161 |
+
def display_results(result, quality_threshold, toxicity_threshold):
|
162 |
+
"""Analiz sonuçlarını gösterir"""
|
163 |
+
col1, col2 = st.columns(2)
|
164 |
+
|
165 |
+
with col1:
|
166 |
+
st.subheader("Kalite Puanı")
|
167 |
+
quality_score = result["quality_score"]
|
168 |
+
st.metric("Kalite", f"{quality_score:.2f}", delta=f"{quality_score - quality_threshold:.2f}")
|
169 |
+
|
170 |
+
# Kalite özelliklerini görselleştir
|
171 |
+
if "quality_features" in result:
|
172 |
+
features = result["quality_features"]
|
173 |
+
feature_df = pd.DataFrame({
|
174 |
+
"Özellik": list(features.keys()),
|
175 |
+
"Değer": list(features.values())
|
176 |
+
})
|
177 |
+
|
178 |
+
fig = px.bar(feature_df, x="Özellik", y="Değer", title="Kalite Özellikleri")
|
179 |
+
fig.update_layout(height=300)
|
180 |
+
st.plotly_chart(fig, use_container_width=True)
|
181 |
+
|
182 |
+
st.info(f"Hesaplama süresi: {result['quality_time']:.2f} saniye")
|
183 |
+
|
184 |
+
with col2:
|
185 |
+
st.subheader("Zararlılık Puanı")
|
186 |
+
toxicity_score = result["toxicity_score"]
|
187 |
+
# Zararlılıkta düşük değer iyidir
|
188 |
+
st.metric("Zararlılık", f"{toxicity_score:.2f}",
|
189 |
+
delta=f"{toxicity_threshold - toxicity_score:.2f}",
|
190 |
+
delta_color="inverse")
|
191 |
+
|
192 |
+
# Zararlılık rengini göster
|
193 |
+
fig, ax = plt.subplots(figsize=(3, 1))
|
194 |
+
cmap = plt.cm.RdYlGn_r
|
195 |
+
color = cmap(toxicity_score)
|
196 |
+
ax.barh(0, toxicity_score, color=color)
|
197 |
+
ax.barh(0, 1 - toxicity_score, left=toxicity_score, color="lightgrey")
|
198 |
+
ax.set_xlim(0, 1)
|
199 |
+
ax.set_yticks([])
|
200 |
+
ax.set_xticks([0, 0.25, 0.5, 0.75, 1])
|
201 |
+
fig.tight_layout()
|
202 |
+
st.pyplot(fig)
|
203 |
+
|
204 |
+
st.info(f"Hesaplama süresi: {result['toxicity_time']:.2f} saniye")
|
205 |
+
|
206 |
+
# Genel değerlendirme
|
207 |
+
if quality_score >= quality_threshold and toxicity_score <= toxicity_threshold:
|
208 |
+
st.success(" Bu metin kabul edilebilir kalite ve zararlılık seviyesindedir.")
|
209 |
+
else:
|
210 |
+
if quality_score < quality_threshold:
|
211 |
+
st.warning(" Bu metnin kalitesi eşik değerin altındadır.")
|
212 |
+
if toxicity_score > toxicity_threshold:
|
213 |
+
st.error(" Bu metin kabul edilebilir zararlılık seviyesini aşmaktadır.")
|
214 |
+
|
215 |
+
|
216 |
+
def process_file(uploaded_file, toxicity_scorer, quality_scorer, quality_threshold, toxicity_threshold, batch_size):
|
217 |
+
"""Yüklenen dosyayı işler"""
|
218 |
+
try:
|
219 |
+
# Veri işleyici oluştur
|
220 |
+
data_handler = DataHandler(quality_scorer, toxicity_scorer)
|
221 |
+
|
222 |
+
# Veriyi yükle
|
223 |
+
df, text_column = data_handler.load_data(uploaded_file)
|
224 |
+
|
225 |
+
# İlerleme çubuğu göster
|
226 |
+
progress_bar = st.progress(0)
|
227 |
+
status_text = st.empty()
|
228 |
+
|
229 |
+
# Veriyi işle
|
230 |
+
status_text.text("Veriler işleniyor...")
|
231 |
+
|
232 |
+
# Veri işleme işlevini çağır
|
233 |
+
processed_df = data_handler.process_data(
|
234 |
+
df,
|
235 |
+
text_column,
|
236 |
+
quality_threshold,
|
237 |
+
toxicity_threshold,
|
238 |
+
batch_size
|
239 |
+
)
|
240 |
+
|
241 |
+
# İlerleme çubuğunu güncelle
|
242 |
+
progress_bar.progress(100)
|
243 |
+
status_text.text("İşlem tamamlandı!")
|
244 |
+
|
245 |
+
return processed_df, text_column
|
246 |
+
|
247 |
+
except Exception as e:
|
248 |
+
st.error(f"Dosya işleme hatası: {str(e)}")
|
249 |
+
logger.exception(f"Dosya işleme hatası: {str(e)}")
|
250 |
+
return None, None
|
251 |
+
|
252 |
+
|
253 |
+
def main():
|
254 |
+
"""Ana uygulama işlevi"""
|
255 |
+
st.set_page_config(page_title="📊 Veri Kalitesi ve Zararlılık Değerlendirme ",
|
256 |
+
layout="wide")
|
257 |
+
|
258 |
+
st.title(" 📊Veri Kalitesi ve Zararlılık Değerlendirme ")
|
259 |
+
st.markdown("""
|
260 |
+
Bu platform, metin verilerini otomatik olarak kalite ve zararlılık açısından değerlendirir.
|
261 |
+
Tek bir metin veya CSV/Excel dosyası yükleyerek toplu analiz yapabilirsiniz.
|
262 |
+
""")
|
263 |
+
|
264 |
+
# Yan panel
|
265 |
+
with st.sidebar:
|
266 |
+
st.header("Ayarlar")
|
267 |
+
|
268 |
+
st.subheader("Eşik Değerleri")
|
269 |
+
quality_threshold = st.slider("Kalite Eşiği", 0.0, 1.0, 0.5,
|
270 |
+
help="Bu değerin altındaki kalite skoruna sahip metinler düşük kaliteli olarak işaretlenir")
|
271 |
+
toxicity_threshold = st.slider("Zararlılık Eşiği", 0.0, 1.0, 0.5,
|
272 |
+
help="Bu değerin üzerindeki zararlılık skoruna sahip metinler zararlı olarak işaretlenir")
|
273 |
+
|
274 |
+
st.subheader("Toplu İşleme")
|
275 |
+
batch_size = st.slider("Grup Boyutu", 1, 32, 8,
|
276 |
+
help="Toplu değerlendirme için grup boyutu. Bellek sınırlamalarına göre ayarlayın.")
|
277 |
+
|
278 |
+
# Modelleri yükle
|
279 |
+
toxicity_scorer, quality_scorer, models_dict = load_models()
|
280 |
+
|
281 |
+
# Model bilgilerini göster
|
282 |
+
display_model_info(models_dict)
|
283 |
+
|
284 |
+
# Sekmeleri oluştur
|
285 |
+
tab1, tab2, tab3, tab4 = st.tabs(["Tek Metin Analizi", "Toplu Dosya Analizi",
|
286 |
+
"Gelişmiş Metin Analizi", "Anahtar Kelime ve Dil Tespiti"])
|
287 |
+
|
288 |
+
# Tek Metin Analizi sekmesi
|
289 |
+
with tab1:
|
290 |
+
st.subheader("Metin Analizi")
|
291 |
+
|
292 |
+
text_input = st.text_area("Analiz edilecek metni girin:",
|
293 |
+
value=SAMPLE_TEXT,
|
294 |
+
height=150)
|
295 |
+
|
296 |
+
if st.button("Analiz Et", type="primary", key="analyze_single"):
|
297 |
+
if text_input and len(text_input.strip()) > 0:
|
298 |
+
with st.spinner("Metin analiz ediliyor..."):
|
299 |
+
result = analyze_single_text(text_input, toxicity_scorer, quality_scorer)
|
300 |
+
|
301 |
+
st.markdown("---")
|
302 |
+
st.subheader("Analiz Sonuçları")
|
303 |
+
display_results(result, quality_threshold, toxicity_threshold)
|
304 |
+
else:
|
305 |
+
st.error("Lütfen analiz için bir metin girin.")
|
306 |
+
|
307 |
+
# Toplu Dosya Analizi sekmesi
|
308 |
+
with tab2:
|
309 |
+
st.subheader("Dosya Analizi")
|
310 |
+
|
311 |
+
uploaded_file = st.file_uploader("CSV veya Excel dosyası yükleyin:",
|
312 |
+
type=["csv", "xlsx", "xls"])
|
313 |
+
|
314 |
+
if uploaded_file is not None:
|
315 |
+
# Dosya bilgilerini göster
|
316 |
+
file_details = {
|
317 |
+
"Dosya Adı": uploaded_file.name,
|
318 |
+
"Dosya Boyutu": f"{uploaded_file.size / 1024:.2f} KB"
|
319 |
+
}
|
320 |
+
st.write(file_details)
|
321 |
+
|
322 |
+
# Dosyayı işle
|
323 |
+
if st.button("Dosyayı İşle", type="primary", key="process_file"):
|
324 |
+
with st.spinner("Dosya işleniyor... Bu işlem dosya boyutuna bağlı olarak biraz zaman alabilir."):
|
325 |
+
processed_df, text_column = process_file(
|
326 |
+
uploaded_file,
|
327 |
+
toxicity_scorer,
|
328 |
+
quality_scorer,
|
329 |
+
quality_threshold,
|
330 |
+
toxicity_threshold,
|
331 |
+
batch_size
|
332 |
+
)
|
333 |
+
|
334 |
+
if processed_df is not None:
|
335 |
+
st.markdown("---")
|
336 |
+
st.subheader("İşlenmiş Veri")
|
337 |
+
|
338 |
+
# Veri önizlemesi göster
|
339 |
+
st.dataframe(processed_df.head(10), use_container_width=True)
|
340 |
+
|
341 |
+
# İstatistikler
|
342 |
+
st.markdown("### Özet İstatistikler")
|
343 |
+
col1, col2, col3 = st.columns(3)
|
344 |
+
|
345 |
+
with col1:
|
346 |
+
st.metric("Toplam Metin Sayısı", len(processed_df))
|
347 |
+
|
348 |
+
with col2:
|
349 |
+
acceptable_count = processed_df[
|
350 |
+
"acceptable"].sum() if "acceptable" in processed_df.columns else 0
|
351 |
+
acceptable_pct = acceptable_count / len(processed_df) * 100 if len(processed_df) > 0 else 0
|
352 |
+
st.metric("Kabul Edilebilir Metinler", f"{acceptable_count} ({acceptable_pct:.1f}%)")
|
353 |
+
|
354 |
+
with col3:
|
355 |
+
rejected_count = len(processed_df) - acceptable_count
|
356 |
+
rejected_pct = 100 - acceptable_pct
|
357 |
+
st.metric("Reddedilen Metinler", f"{rejected_count} ({rejected_pct:.1f}%)")
|
358 |
+
|
359 |
+
# Görselleştirmeler
|
360 |
+
st.markdown("### Veri Görselleştirme")
|
361 |
+
col1, col2 = st.columns(2)
|
362 |
+
|
363 |
+
with col1:
|
364 |
+
if "quality_score" in processed_df.columns:
|
365 |
+
fig = px.histogram(
|
366 |
+
processed_df,
|
367 |
+
x="quality_score",
|
368 |
+
nbins=20,
|
369 |
+
title="Kalite Skoru Dağılımı",
|
370 |
+
color_discrete_sequence=["#3366cc"]
|
371 |
+
)
|
372 |
+
fig.add_vline(x=quality_threshold, line_dash="dash", line_color="red")
|
373 |
+
st.plotly_chart(fig, use_container_width=True)
|
374 |
+
|
375 |
+
with col2:
|
376 |
+
if "toxicity_score" in processed_df.columns:
|
377 |
+
fig = px.histogram(
|
378 |
+
processed_df,
|
379 |
+
x="toxicity_score",
|
380 |
+
nbins=20,
|
381 |
+
title="Zararlılık Skoru Dağılımı",
|
382 |
+
color_discrete_sequence=["#dc3912"]
|
383 |
+
)
|
384 |
+
fig.add_vline(x=toxicity_threshold, line_dash="dash", line_color="red")
|
385 |
+
st.plotly_chart(fig, use_container_width=True)
|
386 |
+
|
387 |
+
# Scatter plot
|
388 |
+
if "quality_score" in processed_df.columns and "toxicity_score" in processed_df.columns:
|
389 |
+
fig = px.scatter(
|
390 |
+
processed_df,
|
391 |
+
x="quality_score",
|
392 |
+
y="toxicity_score",
|
393 |
+
color="acceptable" if "acceptable" in processed_df.columns else None,
|
394 |
+
title="Kalite vs Zararlılık",
|
395 |
+
color_discrete_sequence=["#dc3912", "#3366cc"],
|
396 |
+
hover_data=[text_column]
|
397 |
+
)
|
398 |
+
fig.add_hline(y=toxicity_threshold, line_dash="dash", line_color="red")
|
399 |
+
fig.add_vline(x=quality_threshold, line_dash="dash", line_color="red")
|
400 |
+
st.plotly_chart(fig, use_container_width=True)
|
401 |
+
|
402 |
+
# Filtrelenmiş veri
|
403 |
+
data_handler = DataHandler(quality_scorer, toxicity_scorer)
|
404 |
+
filtered_df = data_handler.filter_data(processed_df, quality_threshold, toxicity_threshold)
|
405 |
+
|
406 |
+
st.markdown("### Filtrelenmiş Veri")
|
407 |
+
st.write(
|
408 |
+
f"Eşik değerlerini karşılayan {len(filtered_df)} metin ({len(filtered_df) / len(processed_df) * 100:.1f}%)")
|
409 |
+
st.dataframe(filtered_df.head(10), use_container_width=True)
|
410 |
+
|
411 |
+
# İndirme bağlantıları
|
412 |
+
st.markdown("### Verileri İndir")
|
413 |
+
col1, col2 = st.columns(2)
|
414 |
+
|
415 |
+
with col1:
|
416 |
+
# İşlenmiş veriyi CSV olarak dışa aktar
|
417 |
+
csv_processed = processed_df.to_csv(index=False)
|
418 |
+
st.download_button(
|
419 |
+
label="İşlenmiş Veriyi İndir (CSV)",
|
420 |
+
data=csv_processed,
|
421 |
+
file_name=f"processed_{uploaded_file.name.split('.')[0]}.csv",
|
422 |
+
mime="text/csv"
|
423 |
+
)
|
424 |
+
|
425 |
+
with col2:
|
426 |
+
# Filtrelenmiş veriyi CSV olarak dışa aktar
|
427 |
+
csv_filtered = filtered_df.to_csv(index=False)
|
428 |
+
st.download_button(
|
429 |
+
label="Filtrelenmiş Veriyi İndir (CSV)",
|
430 |
+
data=csv_filtered,
|
431 |
+
file_name=f"filtered_{uploaded_file.name.split('.')[0]}.csv",
|
432 |
+
mime="text/csv"
|
433 |
+
)
|
434 |
+
|
435 |
+
# Gelişmiş Metin Analizi sekmesi
|
436 |
+
with tab3:
|
437 |
+
st.subheader("Gelişmiş Metin Analizi")
|
438 |
+
st.write("Bu sekmede duygu analizi ve metin iyileştirme önerilerini görebilirsiniz.")
|
439 |
+
|
440 |
+
# Analizörler oluştur
|
441 |
+
sentiment_analyzer = SentimentAnalyzer()
|
442 |
+
text_improver = TextImprover()
|
443 |
+
|
444 |
+
advanced_text_input = st.text_area("Analiz edilecek metni girin:",
|
445 |
+
value=SAMPLE_TEXT,
|
446 |
+
height=150,
|
447 |
+
key="advanced_text")
|
448 |
+
|
449 |
+
col1, col2 = st.columns(2)
|
450 |
+
|
451 |
+
with col1:
|
452 |
+
sentiment_option = st.checkbox("Duygu Analizi", value=True,
|
453 |
+
help="Metnin duygusal tonunu analiz eder")
|
454 |
+
|
455 |
+
with col2:
|
456 |
+
improvement_option = st.checkbox("Metin İyileştirme", value=True,
|
457 |
+
help="Metin kalitesini artırmak için öneriler sunar")
|
458 |
+
|
459 |
+
if st.button("Gelişmiş Analiz Yap", type="primary", key="advanced_analyze"):
|
460 |
+
if advanced_text_input and len(advanced_text_input.strip()) > 0:
|
461 |
+
with st.spinner("Gelişmiş analiz yapılıyor..."):
|
462 |
+
# Duygu analizi
|
463 |
+
if sentiment_option:
|
464 |
+
sentiment_results = sentiment_analyzer.analyze_sentiment(advanced_text_input)
|
465 |
+
|
466 |
+
st.markdown("### Duygu Analizi Sonuçları")
|
467 |
+
|
468 |
+
# Duygu skoru ve tonu göster
|
469 |
+
sentiment_score = sentiment_results.get('score', 0)
|
470 |
+
dominant_sentiment = sentiment_results.get('dominant', 'neutral')
|
471 |
+
|
472 |
+
# Duygu tonuna göre renk ve emoji belirle
|
473 |
+
if sentiment_score > 0.2:
|
474 |
+
sentiment_color = "green"
|
475 |
+
sentiment_emoji = "😃"
|
476 |
+
elif sentiment_score < -0.2:
|
477 |
+
sentiment_color = "red"
|
478 |
+
sentiment_emoji = "😠"
|
479 |
+
else:
|
480 |
+
sentiment_color = "orange"
|
481 |
+
sentiment_emoji = "😐"
|
482 |
+
|
483 |
+
# Dominant duygu için Türkçe karşılık
|
484 |
+
sentiment_turkish = {
|
485 |
+
'positive': 'Pozitif',
|
486 |
+
'neutral': 'Nötr',
|
487 |
+
'negative': 'Negatif'
|
488 |
+
}.get(dominant_sentiment, 'Nötr')
|
489 |
+
|
490 |
+
st.markdown(f"**Duygu Tonu:** {sentiment_emoji} {sentiment_turkish}")
|
491 |
+
st.markdown(
|
492 |
+
f"**Duygu Skoru:** <span style='color:{sentiment_color}'>{sentiment_score:.2f}</span> (-1 ile 1 arasında)",
|
493 |
+
unsafe_allow_html=True)
|
494 |
+
|
495 |
+
# Duygu dağılımını göster
|
496 |
+
sentiment_df = pd.DataFrame({
|
497 |
+
'Duygu': ['Pozitif', 'Nötr', 'Negatif'],
|
498 |
+
'Skor': [
|
499 |
+
sentiment_results.get('positive', 0),
|
500 |
+
sentiment_results.get('neutral', 0),
|
501 |
+
sentiment_results.get('negative', 0)
|
502 |
+
]
|
503 |
+
})
|
504 |
+
|
505 |
+
fig = px.bar(sentiment_df, x='Duygu', y='Skor',
|
506 |
+
color='Duygu',
|
507 |
+
color_discrete_map={'Pozitif': 'green', 'Nötr': 'gray', 'Negatif': 'red'},
|
508 |
+
title="Duygu Dağılımı")
|
509 |
+
fig.update_layout(yaxis_range=[0, 1])
|
510 |
+
st.plotly_chart(fig, use_container_width=True)
|
511 |
+
|
512 |
+
# Metin iyileştirme
|
513 |
+
if improvement_option:
|
514 |
+
improvement_results = text_improver.improve_text(advanced_text_input)
|
515 |
+
|
516 |
+
st.markdown("### Metin İyileştirme Önerileri")
|
517 |
+
|
518 |
+
# Okunabilirlik göster
|
519 |
+
readability = improvement_results.get('readability', {'score': 0, 'level': 'bilinmiyor'})
|
520 |
+
|
521 |
+
# Okunabilirlik skoru için renk ve seviye belirleme
|
522 |
+
readability_score = readability.get('score', 0)
|
523 |
+
if readability_score >= 70:
|
524 |
+
readability_color = "green"
|
525 |
+
elif readability_score >= 50:
|
526 |
+
readability_color = "orange"
|
527 |
+
else:
|
528 |
+
readability_color = "red"
|
529 |
+
|
530 |
+
level_map = {
|
531 |
+
'çok_kolay': 'Çok Kolay',
|
532 |
+
'kolay': 'Kolay',
|
533 |
+
'orta_kolay': 'Orta-Kolay',
|
534 |
+
'orta': 'Orta',
|
535 |
+
'orta_zor': 'Orta-Zor',
|
536 |
+
'zor': 'Zor',
|
537 |
+
'çok_zor': 'Çok Zor',
|
538 |
+
'bilinmiyor': 'Bilinmiyor'
|
539 |
+
}
|
540 |
+
level_text = level_map.get(readability.get('level', 'bilinmiyor'), 'Bilinmiyor')
|
541 |
+
|
542 |
+
col1, col2 = st.columns(2)
|
543 |
+
|
544 |
+
with col1:
|
545 |
+
st.metric("Okunabilirlik Skoru", f"{readability_score:.1f}/100")
|
546 |
+
st.markdown(
|
547 |
+
f"**Okunabilirlik Seviyesi:** <span style='color:{readability_color}'>{level_text}</span>",
|
548 |
+
unsafe_allow_html=True)
|
549 |
+
|
550 |
+
with col2:
|
551 |
+
if 'avg_sentence_length' in readability:
|
552 |
+
st.metric("Ortalama Cümle Uzunluğu", f"{readability['avg_sentence_length']:.1f} kelime")
|
553 |
+
|
554 |
+
# İyileştirme önerileri
|
555 |
+
improvement_count = improvement_results.get('improvement_count', 0)
|
556 |
+
if improvement_count > 0:
|
557 |
+
st.markdown("#### Öneriler")
|
558 |
+
for i, suggestion in enumerate(improvement_results.get('suggestions', [])):
|
559 |
+
st.markdown(f"{i + 1}. {suggestion}")
|
560 |
+
|
561 |
+
# Düzeltilmiş metni göster
|
562 |
+
if improvement_results.get('corrected_text', '') != advanced_text_input:
|
563 |
+
st.markdown("#### Düzeltilmiş Metin")
|
564 |
+
st.code(improvement_results.get('improved_text', advanced_text_input), language=None)
|
565 |
+
else:
|
566 |
+
st.success("✓ Bu metin için iyileştirme önerisi bulunmamaktadır. Metin kalitesi iyi.")
|
567 |
+
else:
|
568 |
+
st.error("Lütfen analiz için bir metin girin.")
|
569 |
+
|
570 |
+
# Anahtar Kelime ve Dil Tespiti sekmesi
|
571 |
+
with tab4:
|
572 |
+
st.subheader("Anahtar Kelime Çıkarma ve Dil Tespiti")
|
573 |
+
st.write("Bu sekmede metninizin anahtar kelimelerini çıkarabilir ve dilini tespit edebilirsiniz.")
|
574 |
+
|
575 |
+
# Analizörler oluştur
|
576 |
+
keyword_extractor = KeywordExtractor()
|
577 |
+
language_detector = LanguageDetector()
|
578 |
+
|
579 |
+
# Metin giriş alanı
|
580 |
+
keyword_text_input = st.text_area("Analiz edilecek metni girin:",
|
581 |
+
value=SAMPLE_TEXT,
|
582 |
+
height=150,
|
583 |
+
key="keyword_text")
|
584 |
+
|
585 |
+
# Ayarlar
|
586 |
+
col1, col2, col3 = st.columns(3)
|
587 |
+
|
588 |
+
with col1:
|
589 |
+
keyword_method = st.selectbox(
|
590 |
+
"Anahtar Kelime Metodu",
|
591 |
+
["combined", "tfidf", "textrank"],
|
592 |
+
help="Anahtar kelime çıkarma algoritması"
|
593 |
+
)
|
594 |
+
|
595 |
+
with col2:
|
596 |
+
num_keywords = st.slider(
|
597 |
+
"Anahtar Kelime Sayısı",
|
598 |
+
5, 20, 10,
|
599 |
+
help="Çıkarılacak anahtar kelime sayısı"
|
600 |
+
)
|
601 |
+
|
602 |
+
with col3:
|
603 |
+
detect_language = st.checkbox(
|
604 |
+
"Dil Tespiti",
|
605 |
+
value=True,
|
606 |
+
help="Metnin dilini otomatik olarak tespit eder"
|
607 |
+
)
|
608 |
+
|
609 |
+
if st.button("Anahtar Kelime ve Dil Analizi", type="primary", key="keyword_analyze"):
|
610 |
+
if keyword_text_input and len(keyword_text_input.strip()) > 0:
|
611 |
+
with st.spinner("Analiz yapılıyor..."):
|
612 |
+
# Dil tespiti
|
613 |
+
if detect_language:
|
614 |
+
lang_result = language_detector.detect_language(keyword_text_input)
|
615 |
+
|
616 |
+
st.markdown("### Dil Tespiti Sonucu")
|
617 |
+
|
618 |
+
# Dil adı ve güven skoru
|
619 |
+
lang_code = lang_result['language_code']
|
620 |
+
lang_name = lang_result['language_name']
|
621 |
+
confidence = lang_result['confidence']
|
622 |
+
|
623 |
+
# Dil için bayrak ve renk
|
624 |
+
lang_flags = {
|
625 |
+
'tr': '🇹🇷',
|
626 |
+
'en': '🇺🇸',
|
627 |
+
'de': '🇩🇪',
|
628 |
+
'fr': '🇫🇷',
|
629 |
+
'es': '🇪🇸',
|
630 |
+
'unknown': '🌐'
|
631 |
+
}
|
632 |
+
|
633 |
+
lang_flag = lang_flags.get(lang_code, '🌐')
|
634 |
+
|
635 |
+
# Sonuçları göster
|
636 |
+
confidence_color = "green" if confidence > 0.7 else "orange" if confidence > 0.4 else "red"
|
637 |
+
|
638 |
+
st.markdown(f"### {lang_flag} Tespit Edilen Dil: {lang_name}")
|
639 |
+
st.markdown(f"**Güven Skoru:** <span style='color:{confidence_color}'>{confidence:.2f}</span>", unsafe_allow_html=True)
|
640 |
+
|
641 |
+
# Eğer tüm dil skorlarını göstermek istersek
|
642 |
+
if lang_result['scores']:
|
643 |
+
scores_df = pd.DataFrame({
|
644 |
+
'Dil': [language_detector.supported_languages.get(code, code) for code in lang_result['scores'].keys()],
|
645 |
+
'Kod': list(lang_result['scores'].keys()),
|
646 |
+
'Skor': list(lang_result['scores'].values())
|
647 |
+
})
|
648 |
+
|
649 |
+
# Skorlara göre sırala
|
650 |
+
scores_df = scores_df.sort_values(by='Skor', ascending=False).reset_index(drop=True)
|
651 |
+
|
652 |
+
# En yüksek skorlu diğer dillerin skorlarını göster
|
653 |
+
st.markdown("#### Dil Skorları")
|
654 |
+
|
655 |
+
# Geniş çubuk grafik
|
656 |
+
fig = px.bar(
|
657 |
+
scores_df.head(5), # En yüksek 5 skoru göster
|
658 |
+
x='Skor',
|
659 |
+
y='Dil',
|
660 |
+
orientation='h',
|
661 |
+
title="Dil Algılama Skorları",
|
662 |
+
color='Skor',
|
663 |
+
color_continuous_scale='Viridis'
|
664 |
+
)
|
665 |
+
|
666 |
+
fig.update_layout(height=300, yaxis={'categoryorder': 'total ascending'})
|
667 |
+
st.plotly_chart(fig, use_container_width=True)
|
668 |
+
|
669 |
+
# Anahtar kelime çıkarma
|
670 |
+
keyword_results = keyword_extractor.extract_keywords(
|
671 |
+
keyword_text_input,
|
672 |
+
method=keyword_method,
|
673 |
+
num_keywords=num_keywords
|
674 |
+
)
|
675 |
+
|
676 |
+
st.markdown("### Anahtar Kelime Analizi")
|
677 |
+
|
678 |
+
# Kullanılan yöntemi göster
|
679 |
+
method_names = {
|
680 |
+
'tfidf': 'TF-IDF',
|
681 |
+
'textrank': 'TextRank',
|
682 |
+
'combined': 'Birleşik (TF-IDF + TextRank)'
|
683 |
+
}
|
684 |
+
|
685 |
+
st.write(f"Kullanılan yöntem: **{method_names.get(keyword_results['method'], keyword_results['method'])}**")
|
686 |
+
|
687 |
+
# Tekil anahtar kelimeleri göster
|
688 |
+
if keyword_results['keywords']:
|
689 |
+
keywords_df = pd.DataFrame({
|
690 |
+
'Anahtar Kelime': [kw[0] for kw in keyword_results['keywords']],
|
691 |
+
'Skor': [kw[1] for kw in keyword_results['keywords']]
|
692 |
+
})
|
693 |
+
|
694 |
+
# Skorlara göre sırala
|
695 |
+
keywords_df = keywords_df.sort_values(by='Skor', ascending=False).reset_index(drop=True)
|
696 |
+
|
697 |
+
# İki sütunlu düzen
|
698 |
+
col1, col2 = st.columns(2)
|
699 |
+
|
700 |
+
with col1:
|
701 |
+
# Anahtar kelime listesi
|
702 |
+
st.markdown("#### Anahtar Kelimeler")
|
703 |
+
for i, (keyword, score) in enumerate(zip(keywords_df['Anahtar Kelime'], keywords_df['Skor'])):
|
704 |
+
# Skora göre font boyutu ve kalınlığı ayarla
|
705 |
+
font_size = min(18, max(12, 12 + score * 6))
|
706 |
+
font_weight = "bold" if score > 0.6 else "normal"
|
707 |
+
st.markdown(
|
708 |
+
f"<span style='font-size:{font_size}px; font-weight:{font_weight}'>{i+1}. {keyword}</span> <small>({score:.2f})</small>",
|
709 |
+
unsafe_allow_html=True
|
710 |
+
)
|
711 |
+
|
712 |
+
with col2:
|
713 |
+
# Anahtar kelime grafiği
|
714 |
+
fig = px.bar(
|
715 |
+
keywords_df,
|
716 |
+
x='Anahtar Kelime',
|
717 |
+
y='Skor',
|
718 |
+
title="Anahtar Kelime Skorları",
|
719 |
+
color='Skor',
|
720 |
+
color_continuous_scale='Blues'
|
721 |
+
)
|
722 |
+
fig.update_layout(xaxis={'categoryorder': 'total descending'})
|
723 |
+
st.plotly_chart(fig, use_container_width=True)
|
724 |
+
else:
|
725 |
+
st.warning("Metinde anahtar kelime bulunamadı.")
|
726 |
+
|
727 |
+
# İkili kelime gruplarını (bigram) göster
|
728 |
+
if keyword_results['bigrams']:
|
729 |
+
bigrams_df = pd.DataFrame({
|
730 |
+
'İkili Kelime Grubu': [bg[0] for bg in keyword_results['bigrams']],
|
731 |
+
'Skor': [bg[1] for bg in keyword_results['bigrams']]
|
732 |
+
})
|
733 |
+
|
734 |
+
st.markdown("#### İkili Kelime Grupları (Bigrams)")
|
735 |
+
|
736 |
+
# İkili kelimeleri tablo olarak göster
|
737 |
+
st.dataframe(bigrams_df, use_container_width=True)
|
738 |
+
|
739 |
+
# İkili kelime grafiği
|
740 |
+
fig = px.bar(
|
741 |
+
bigrams_df,
|
742 |
+
x='İkili Kelime Grubu',
|
743 |
+
y='Skor',
|
744 |
+
title="İkili Kelime Grubu Skorları",
|
745 |
+
color='Skor',
|
746 |
+
color_continuous_scale='Greens'
|
747 |
+
)
|
748 |
+
st.plotly_chart(fig, use_container_width=True)
|
749 |
+
|
750 |
+
# Metin içinde anahtar kelimeleri vurgula
|
751 |
+
if keyword_results['keywords']:
|
752 |
+
st.markdown("#### Anahtar Kelimeleri Vurgulanmış Metin")
|
753 |
+
|
754 |
+
highlighted_text = keyword_text_input
|
755 |
+
for keyword, _ in keyword_results['keywords']:
|
756 |
+
# Büyük/küçük harfe duyarsız olarak değiştirme yapmak için regex
|
757 |
+
pattern = re.compile(r'\b' + re.escape(keyword) + r'\b', re.IGNORECASE)
|
758 |
+
replacement = f"<mark><b>{keyword}</b></mark>"
|
759 |
+
highlighted_text = pattern.sub(replacement, highlighted_text)
|
760 |
+
|
761 |
+
st.markdown(highlighted_text, unsafe_allow_html=True)
|
762 |
+
|
763 |
+
# Özet bilgilendirme
|
764 |
+
st.success(f"Metinden toplam {len(keyword_results['keywords'])} anahtar kelime ve {len(keyword_results['bigrams'])} ikili kelime grubu çıkarıldı.")
|
765 |
+
else:
|
766 |
+
st.error("Lütfen analiz için bir metin girin.")
|
767 |
+
|
768 |
+
|
769 |
+
if __name__ == "__main__":
|
770 |
+
try:
|
771 |
+
main()
|
772 |
+
except Exception as e:
|
773 |
+
st.error(f"Beklenmeyen bir hata oluştu: {str(e)}")
|
774 |
+
logger.exception(f"Beklenmeyen bir hata: {str(e)}")
|
data/sample_data.csv
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
id,metin,kategori
|
2 |
+
1,"Bu, içeriği zengin ve doğru dilbilgisi kullanılarak yazılmış yüksek kaliteli bir metin örneğidir. Cümleler açık ve anlaşılır, kelime çeşitliliği yeterlidir.",A
|
3 |
+
2,"kısa metin örneği",B
|
4 |
+
3,"Bu metin zararlı içerik örnekleridir ve birçok hakaret ve kötü söz içerir. Seni hiç sevmiyorum, çok aptal ve beceriksizsin!",C
|
5 |
+
4,"Bu metin gereksiz tekrarlar içerir. Aynı kelimeyi tekrar tekrar tekrar tekrar tekrar kullanır. Ayrıca çok fazla dolgu kelimesi kullanır, yani, şey, aslında, gerçekten, sadece tekrar eder durur.",B
|
6 |
+
5,"Oldukça uzun ve detaylı bir metin örneği. Birçok farklı konu içerir ve konular arasındaki geçişler akıcıdır. Cümleler düzgün ve çeşitlidir. Kelime dağarcığı zengindir ve noktalama işaretleri doğru kullanılmıştır. Metinde herhangi bir yazım hatası veya dilbilgisi hatası bulunmamaktadır. Konular tutarlı bir şekilde ele alınmış ve gerekli açıklamalar yapılmıştır. Okuyucunun konuyu anlaması için gerekli bilgiler sağlanmıştır.",A
|
7 |
+
6,"bu metinde büyük harf kullanilmamis ve noktalama isaretleri yok ayrica yazim hatalari var metinn kalitesi dusuk",C
|
8 |
+
7,"Merhaba, bu normal bir metindir. Kalitesi orta düzeydedir, ne çok iyi ne de çok kötü.",B
|
9 |
+
8,"Yarın buluşmaya ne dersin? Bu öneriyi düşüneceğini umuyorum. İyi günler!",A
|
10 |
+
9,"Sen çok kötü bir insansın, senden nefret ediyorum! Aptalsın ve beceriksizsin. Kaybol!",C
|
11 |
+
10,"Metni çok uzatmak için gereksiz dolgu kelimeleri kullanılabilir, aynı şekilde, bu tür kelimeler metnin kalitesini düşürür, yani anlamını zayıflatır, aslında okuyucunun ilgisini kaybetmesine neden olur, bu yüzden mümkün olduğunca az kullanılmalıdır, gerçekten öyle.",B
|
models/__pycache__/model_loader.cpython-312.pyc
ADDED
Binary file (6.02 kB). View file
|
|
models/model_loader.py
ADDED
@@ -0,0 +1,105 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import torch
|
2 |
+
from transformers import AutoTokenizer, AutoModelForSequenceClassification
|
3 |
+
from transformers import pipeline
|
4 |
+
import logging
|
5 |
+
|
6 |
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
7 |
+
|
8 |
+
|
9 |
+
class ModelManager:
|
10 |
+
def __init__(self):
|
11 |
+
self.toxicity_model = None
|
12 |
+
self.toxicity_tokenizer = None
|
13 |
+
self.quality_model = None
|
14 |
+
self.quality_tokenizer = None
|
15 |
+
self.device = "cuda" if torch.cuda.is_available() else "cpu"
|
16 |
+
logging.info(f"Using device: {self.device}")
|
17 |
+
|
18 |
+
def load_toxicity_model(self, model_name="savasy/bert-base-turkish-sentiment"):
|
19 |
+
"""
|
20 |
+
Zararlılık tespiti için model yükleme
|
21 |
+
"""
|
22 |
+
try:
|
23 |
+
logging.info(f"Loading toxicity model: {model_name}")
|
24 |
+
self.toxicity_tokenizer = AutoTokenizer.from_pretrained(model_name)
|
25 |
+
self.toxicity_model = AutoModelForSequenceClassification.from_pretrained(model_name)
|
26 |
+
logging.info("Toxicity model loaded successfully")
|
27 |
+
return True
|
28 |
+
except Exception as e:
|
29 |
+
logging.error(f"Error loading toxicity model: {str(e)}")
|
30 |
+
# Alternatif model deneyelim
|
31 |
+
try:
|
32 |
+
backup_model = "dbmdz/bert-base-turkish-cased"
|
33 |
+
logging.info(f"Trying backup Turkish model: {backup_model}")
|
34 |
+
self.toxicity_tokenizer = AutoTokenizer.from_pretrained(backup_model)
|
35 |
+
self.toxicity_model = AutoModelForSequenceClassification.from_pretrained(backup_model)
|
36 |
+
logging.info("Backup Turkish model loaded successfully")
|
37 |
+
return True
|
38 |
+
except Exception as e2:
|
39 |
+
logging.error(f"Error loading backup Turkish model: {str(e2)}")
|
40 |
+
try:
|
41 |
+
english_model = "distilbert/distilbert-base-uncased-finetuned-sst-2-english"
|
42 |
+
logging.info(f"Trying English sentiment model: {english_model}")
|
43 |
+
self.toxicity_tokenizer = AutoTokenizer.from_pretrained(english_model)
|
44 |
+
self.toxicity_model = AutoModelForSequenceClassification.from_pretrained(english_model)
|
45 |
+
logging.info("English sentiment model loaded successfully")
|
46 |
+
return True
|
47 |
+
except Exception as e3:
|
48 |
+
logging.error(f"Error loading English model: {str(e3)}")
|
49 |
+
return False
|
50 |
+
|
51 |
+
def load_quality_model(self, model_name="sshleifer/distilbart-cnn-6-6"):
|
52 |
+
"""
|
53 |
+
Metin kalitesi değerlendirmesi için model yükleme (özetleme kapasitesi olan bir model)
|
54 |
+
"""
|
55 |
+
try:
|
56 |
+
logging.info(f"Loading quality model: {model_name}")
|
57 |
+
self.quality_pipeline = pipeline(
|
58 |
+
"text2text-generation",
|
59 |
+
model=model_name,
|
60 |
+
tokenizer=model_name,
|
61 |
+
device=0 if self.device == "cuda" else -1
|
62 |
+
)
|
63 |
+
logging.info("Quality model loaded successfully")
|
64 |
+
return True
|
65 |
+
except Exception as e:
|
66 |
+
logging.error(f"Error loading quality model: {str(e)}")
|
67 |
+
|
68 |
+
# Daha hafif bir model deneyelim
|
69 |
+
try:
|
70 |
+
backup_model = "Helsinki-NLP/opus-mt-tc-big-tr-en"
|
71 |
+
logging.info(f"Trying Turkish translation model for quality: {backup_model}")
|
72 |
+
self.quality_pipeline = pipeline(
|
73 |
+
"translation",
|
74 |
+
model=backup_model,
|
75 |
+
tokenizer=backup_model,
|
76 |
+
device=0 if self.device == "cuda" else -1
|
77 |
+
)
|
78 |
+
logging.info("Turkish translation model loaded successfully")
|
79 |
+
return True
|
80 |
+
except Exception as e2:
|
81 |
+
logging.error(f"Error loading Turkish translation model: {str(e2)}")
|
82 |
+
try:
|
83 |
+
light_model = "sshleifer/distilbart-xsum-12-6"
|
84 |
+
logging.info(f"Trying lighter quality model: {light_model}")
|
85 |
+
self.quality_pipeline = pipeline(
|
86 |
+
"text2text-generation",
|
87 |
+
model=light_model,
|
88 |
+
tokenizer=light_model,
|
89 |
+
device=0 if self.device == "cuda" else -1
|
90 |
+
)
|
91 |
+
logging.info("Lighter quality model loaded successfully")
|
92 |
+
return True
|
93 |
+
except Exception as e3:
|
94 |
+
logging.error(f"Error loading lighter quality model: {str(e3)}")
|
95 |
+
return False
|
96 |
+
|
97 |
+
def get_models(self):
|
98 |
+
"""
|
99 |
+
Yüklenen modelleri döndürür
|
100 |
+
"""
|
101 |
+
return {
|
102 |
+
"toxicity_model": self.toxicity_model,
|
103 |
+
"toxicity_tokenizer": self.toxicity_tokenizer,
|
104 |
+
"quality_pipeline": self.quality_pipeline if hasattr(self, 'quality_pipeline') else None
|
105 |
+
}
|
models/model_manager.py
ADDED
@@ -0,0 +1,316 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import torch
|
2 |
+
from transformers import AutoTokenizer, AutoModelForSequenceClassification
|
3 |
+
from transformers import pipeline
|
4 |
+
import logging
|
5 |
+
import os
|
6 |
+
import json
|
7 |
+
import time
|
8 |
+
from typing import List, Dict, Any, Tuple, Optional, Union
|
9 |
+
from models.model_selector import ModelSelector
|
10 |
+
|
11 |
+
# Loglama yapılandırması
|
12 |
+
logger = logging.getLogger(__name__)
|
13 |
+
handler = logging.StreamHandler()
|
14 |
+
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
15 |
+
handler.setFormatter(formatter)
|
16 |
+
logger.addHandler(handler)
|
17 |
+
logger.setLevel(logging.INFO)
|
18 |
+
|
19 |
+
|
20 |
+
class ModelManager:
|
21 |
+
"""
|
22 |
+
Metin kalitesi ve zararlılık değerlendirmesi için NLP modellerini yöneten sınıf.
|
23 |
+
Bu sınıf, modellerin yüklenmesi, değerlendirilmesi ve seçilmesinden sorumludur.
|
24 |
+
"""
|
25 |
+
|
26 |
+
def __init__(self, cache_dir: Optional[str] = None, use_cache: bool = True) -> None:
|
27 |
+
"""
|
28 |
+
Model Yöneticisi sınıfını başlatır.
|
29 |
+
|
30 |
+
Args:
|
31 |
+
cache_dir: Modellerin önbelleğe alınacağı dizin
|
32 |
+
use_cache: Önbellek kullanımını etkinleştir/devre dışı bırak
|
33 |
+
"""
|
34 |
+
self.toxicity_model = None
|
35 |
+
self.toxicity_tokenizer = None
|
36 |
+
self.quality_pipeline = None
|
37 |
+
self.device = "cuda" if torch.cuda.is_available() else "cpu"
|
38 |
+
self.cache_dir = cache_dir or os.path.join(os.path.expanduser("~"), ".text_quality_toxicity_cache")
|
39 |
+
self.use_cache = use_cache
|
40 |
+
self.model_selector = ModelSelector(cache_dir=self.cache_dir, use_cache=self.use_cache)
|
41 |
+
self.model_info = {
|
42 |
+
"toxicity": {
|
43 |
+
"name": "Bilinmeyen Model",
|
44 |
+
"description": "Model henüz yüklenmedi",
|
45 |
+
"language": "unknown"
|
46 |
+
},
|
47 |
+
"quality": {
|
48 |
+
"name": "Bilinmeyen Model",
|
49 |
+
"description": "Model henüz yüklenmedi",
|
50 |
+
"language": "unknown"
|
51 |
+
}
|
52 |
+
}
|
53 |
+
|
54 |
+
# Önbellek dizinini oluştur
|
55 |
+
if self.use_cache and not os.path.exists(self.cache_dir):
|
56 |
+
os.makedirs(self.cache_dir, exist_ok=True)
|
57 |
+
|
58 |
+
logger.info(
|
59 |
+
f"ModelManager başlatıldı. Cihaz: {self.device}, Önbellek: {'Etkin' if use_cache else 'Devre dışı'}")
|
60 |
+
|
61 |
+
def load_models_auto_select(self, sample_texts: Optional[List[str]] = None) -> bool:
|
62 |
+
"""
|
63 |
+
En iyi modelleri otomatik olarak seçerek yükler.
|
64 |
+
|
65 |
+
Args:
|
66 |
+
sample_texts: Model seçimi için örnek metinler
|
67 |
+
|
68 |
+
Returns:
|
69 |
+
bool: Yükleme başarılı mı?
|
70 |
+
"""
|
71 |
+
if sample_texts is None or len(sample_texts) == 0:
|
72 |
+
# Örnek metinler yoksa varsayılan örnekler kullan
|
73 |
+
sample_texts = [
|
74 |
+
"Türkiye, zengin tarihi ve kültürel mirası ile güzel bir ülkedir.",
|
75 |
+
"Bu ürün tam bir hayal kırıklığı! Paramı geri istiyorum!",
|
76 |
+
"Bugün hava çok güzel. Parkta yürüyüş yaptım ve kuşları izledim.",
|
77 |
+
"Sen ne anlarsın ki bu konudan? Boş konuşma artık!"
|
78 |
+
]
|
79 |
+
|
80 |
+
try:
|
81 |
+
logger.info("Otomatik model seçimi başlatılıyor...")
|
82 |
+
start_time = time.time()
|
83 |
+
|
84 |
+
success = self.model_selector.select_best_models(sample_texts)
|
85 |
+
|
86 |
+
if success:
|
87 |
+
best_models = self.model_selector.get_best_models()
|
88 |
+
self.toxicity_model = best_models["toxicity_model"]
|
89 |
+
self.toxicity_tokenizer = best_models["toxicity_tokenizer"]
|
90 |
+
self.quality_pipeline = best_models["quality_pipeline"]
|
91 |
+
self.model_info["toxicity"] = best_models["toxicity_model_info"]
|
92 |
+
self.model_info["quality"] = best_models["quality_model_info"]
|
93 |
+
|
94 |
+
selection_time = time.time() - start_time
|
95 |
+
logger.info(f"Otomatik model seçimi {selection_time:.2f} saniyede tamamlandı")
|
96 |
+
logger.info(f"Seçilen zararlılık modeli: {self.model_info['toxicity']['name']}")
|
97 |
+
logger.info(f"Seçilen kalite modeli: {self.model_info['quality']['name']}")
|
98 |
+
|
99 |
+
# Kullanılan modelleri önbelleğe kaydet
|
100 |
+
if self.use_cache:
|
101 |
+
self._save_models_to_cache()
|
102 |
+
|
103 |
+
return True
|
104 |
+
else:
|
105 |
+
logger.error("Otomatik model seçimi başarısız oldu, varsayılan modellere dönülüyor")
|
106 |
+
return self.load_default_models()
|
107 |
+
|
108 |
+
except Exception as e:
|
109 |
+
logger.error(f"Otomatik model seçimi sırasında hata: {str(e)}")
|
110 |
+
return self.load_default_models()
|
111 |
+
|
112 |
+
def load_default_models(self) -> bool:
|
113 |
+
"""
|
114 |
+
Varsayılan modelleri yükler (otomatik seçim başarısız olursa).
|
115 |
+
|
116 |
+
Returns:
|
117 |
+
bool: Yükleme başarılı mı?
|
118 |
+
"""
|
119 |
+
logger.info("Varsayılan modeller yükleniyor...")
|
120 |
+
|
121 |
+
# Önce önbellekten yüklemeyi dene
|
122 |
+
if self.use_cache and self._load_models_from_cache():
|
123 |
+
logger.info("Modeller önbellekten başarıyla yüklendi")
|
124 |
+
return True
|
125 |
+
|
126 |
+
success_toxicity = self.load_toxicity_model()
|
127 |
+
success_quality = self.load_quality_model()
|
128 |
+
|
129 |
+
overall_success = success_toxicity and success_quality
|
130 |
+
|
131 |
+
if overall_success and self.use_cache:
|
132 |
+
self._save_models_to_cache()
|
133 |
+
|
134 |
+
return overall_success
|
135 |
+
|
136 |
+
def load_toxicity_model(self, model_name: str = "savasy/bert-base-turkish-sentiment") -> bool:
|
137 |
+
"""
|
138 |
+
Zararlılık tespiti için model yükleme.
|
139 |
+
|
140 |
+
Args:
|
141 |
+
model_name: Yüklenecek model ismi
|
142 |
+
|
143 |
+
Returns:
|
144 |
+
bool: Yükleme başarılı mı?
|
145 |
+
"""
|
146 |
+
try:
|
147 |
+
logger.info(f"Zararlılık modeli yükleniyor: {model_name}")
|
148 |
+
self.toxicity_tokenizer = AutoTokenizer.from_pretrained(model_name)
|
149 |
+
self.toxicity_model = AutoModelForSequenceClassification.from_pretrained(model_name)
|
150 |
+
self.model_info["toxicity"]["name"] = model_name
|
151 |
+
self.model_info["toxicity"]["description"] = "Türkçe duygu analizi modeli"
|
152 |
+
self.model_info["toxicity"]["language"] = "tr"
|
153 |
+
logger.info("Zararlılık modeli başarıyla yüklendi")
|
154 |
+
return True
|
155 |
+
except Exception as e:
|
156 |
+
logger.error(f"Zararlılık modeli yüklenirken hata: {str(e)}")
|
157 |
+
# Alternatif model deneyelim
|
158 |
+
try:
|
159 |
+
backup_model = "dbmdz/bert-base-turkish-cased"
|
160 |
+
logger.info(f"Yedek Türkçe model deneniyor: {backup_model}")
|
161 |
+
self.toxicity_tokenizer = AutoTokenizer.from_pretrained(backup_model)
|
162 |
+
self.toxicity_model = AutoModelForSequenceClassification.from_pretrained(backup_model)
|
163 |
+
self.model_info["toxicity"]["name"] = backup_model
|
164 |
+
self.model_info["toxicity"]["description"] = "Genel amaçlı Türkçe BERT modeli"
|
165 |
+
self.model_info["toxicity"]["language"] = "tr"
|
166 |
+
logger.info("Yedek Türkçe model başarıyla yüklendi")
|
167 |
+
return True
|
168 |
+
except Exception as e2:
|
169 |
+
logger.error(f"Yedek Türkçe model yüklenirken hata: {str(e2)}")
|
170 |
+
try:
|
171 |
+
fallback_model = "distilbert/distilbert-base-uncased-finetuned-sst-2-english"
|
172 |
+
logger.info(f"İngilizce duygu analizi modeli deneniyor: {fallback_model}")
|
173 |
+
self.toxicity_tokenizer = AutoTokenizer.from_pretrained(fallback_model)
|
174 |
+
self.toxicity_model = AutoModelForSequenceClassification.from_pretrained(fallback_model)
|
175 |
+
self.model_info["toxicity"]["name"] = fallback_model
|
176 |
+
self.model_info["toxicity"]["description"] = "İngilizce duygu analizi modeli"
|
177 |
+
self.model_info["toxicity"]["language"] = "en"
|
178 |
+
logger.info("İngilizce duygu analizi modeli başarıyla yüklendi")
|
179 |
+
return True
|
180 |
+
except Exception as e3:
|
181 |
+
logger.error(f"İngilizce model yüklenirken hata: {str(e3)}")
|
182 |
+
return False
|
183 |
+
|
184 |
+
def load_quality_model(self, model_name: str = "sshleifer/distilbart-cnn-6-6") -> bool:
|
185 |
+
"""
|
186 |
+
Metin kalitesi değerlendirmesi için model yükleme.
|
187 |
+
|
188 |
+
Args:
|
189 |
+
model_name: Yüklenecek model ismi
|
190 |
+
|
191 |
+
Returns:
|
192 |
+
bool: Yükleme başarılı mı?
|
193 |
+
"""
|
194 |
+
try:
|
195 |
+
logger.info(f"Kalite modeli yükleniyor: {model_name}")
|
196 |
+
self.quality_pipeline = pipeline(
|
197 |
+
"text2text-generation",
|
198 |
+
model=model_name,
|
199 |
+
tokenizer=model_name,
|
200 |
+
device=0 if self.device == "cuda" else -1
|
201 |
+
)
|
202 |
+
self.model_info["quality"]["name"] = model_name
|
203 |
+
self.model_info["quality"]["description"] = "İngilizce metin özetleme modeli"
|
204 |
+
self.model_info["quality"]["language"] = "en"
|
205 |
+
logger.info("Kalite modeli başarıyla yüklendi")
|
206 |
+
return True
|
207 |
+
except Exception as e:
|
208 |
+
logger.error(f"Kalite modeli yüklenirken hata: {str(e)}")
|
209 |
+
|
210 |
+
# Daha hafif bir model deneyelim
|
211 |
+
try:
|
212 |
+
backup_model = "Helsinki-NLP/opus-mt-tr-en"
|
213 |
+
logger.info(f"Türkçe çeviri modeli deneniyor: {backup_model}")
|
214 |
+
self.quality_pipeline = pipeline(
|
215 |
+
"translation",
|
216 |
+
model=backup_model,
|
217 |
+
device=0 if self.device == "cuda" else -1
|
218 |
+
)
|
219 |
+
self.model_info["quality"]["name"] = backup_model
|
220 |
+
self.model_info["quality"]["description"] = "Türkçe-İngilizce çeviri modeli"
|
221 |
+
self.model_info["quality"]["language"] = "tr"
|
222 |
+
logger.info("Türkçe çeviri modeli başarıyla yüklendi")
|
223 |
+
return True
|
224 |
+
except Exception as e2:
|
225 |
+
logger.error(f"Türkçe çeviri modeli yüklenirken hata: {str(e2)}")
|
226 |
+
try:
|
227 |
+
light_model = "sshleifer/distilbart-xsum-12-6"
|
228 |
+
logger.info(f"Daha hafif özetleme modeli deneniyor: {light_model}")
|
229 |
+
self.quality_pipeline = pipeline(
|
230 |
+
"text2text-generation",
|
231 |
+
model=light_model,
|
232 |
+
device=0 if self.device == "cuda" else -1
|
233 |
+
)
|
234 |
+
self.model_info["quality"]["name"] = light_model
|
235 |
+
self.model_info["quality"]["description"] = "Hafif İngilizce özetleme modeli"
|
236 |
+
self.model_info["quality"]["language"] = "en"
|
237 |
+
logger.info("Hafif özetleme modeli başarıyla yüklendi")
|
238 |
+
return True
|
239 |
+
except Exception as e3:
|
240 |
+
logger.error(f"Hafif özetleme modeli yüklenirken hata: {str(e3)}")
|
241 |
+
return False
|
242 |
+
|
243 |
+
def _save_models_to_cache(self) -> None:
|
244 |
+
"""Kullanılan modellerin bilgilerini önbelleğe kaydeder."""
|
245 |
+
if not self.use_cache:
|
246 |
+
return
|
247 |
+
|
248 |
+
try:
|
249 |
+
cache_file = os.path.join(self.cache_dir, "model_manager_state.json")
|
250 |
+
|
251 |
+
cache_data = {
|
252 |
+
"timestamp": time.time(),
|
253 |
+
"model_info": self.model_info
|
254 |
+
}
|
255 |
+
|
256 |
+
with open(cache_file, 'w', encoding='utf-8') as f:
|
257 |
+
json.dump(cache_data, f, ensure_ascii=False, indent=2)
|
258 |
+
|
259 |
+
logger.info(f"Model bilgileri önbelleğe kaydedildi: {cache_file}")
|
260 |
+
except Exception as e:
|
261 |
+
logger.error(f"Önbelleğe kaydetme hatası: {str(e)}")
|
262 |
+
|
263 |
+
def _load_models_from_cache(self) -> bool:
|
264 |
+
"""
|
265 |
+
Önbellekten model bilgilerini yükler.
|
266 |
+
|
267 |
+
Returns:
|
268 |
+
bool: Yükleme başarılı mı?
|
269 |
+
"""
|
270 |
+
if not self.use_cache:
|
271 |
+
return False
|
272 |
+
|
273 |
+
try:
|
274 |
+
cache_file = os.path.join(self.cache_dir, "model_manager_state.json")
|
275 |
+
|
276 |
+
if not os.path.exists(cache_file):
|
277 |
+
return False
|
278 |
+
|
279 |
+
# Önbellek dosyasının yaşını kontrol et (24 saatten eskiyse yok say)
|
280 |
+
file_age = time.time() - os.path.getmtime(cache_file)
|
281 |
+
if file_age > 86400: # 24 saat = 86400 saniye
|
282 |
+
logger.info(f"Önbellek dosyası çok eski ({file_age / 3600:.1f} saat), yeniden yükleme yapılacak")
|
283 |
+
return False
|
284 |
+
|
285 |
+
with open(cache_file, 'r', encoding='utf-8') as f:
|
286 |
+
cache_data = json.load(f)
|
287 |
+
|
288 |
+
# Model bilgilerini önbellekten al
|
289 |
+
self.model_info = cache_data.get("model_info", self.model_info)
|
290 |
+
|
291 |
+
# Önbellekteki bilgilerle yeniden yükleme yap
|
292 |
+
toxicity_name = self.model_info["toxicity"].get("name")
|
293 |
+
quality_name = self.model_info["quality"].get("name")
|
294 |
+
|
295 |
+
toxicity_success = self.load_toxicity_model(toxicity_name) if toxicity_name else False
|
296 |
+
quality_success = self.load_quality_model(quality_name) if quality_name else False
|
297 |
+
|
298 |
+
return toxicity_success and quality_success
|
299 |
+
|
300 |
+
except Exception as e:
|
301 |
+
logger.error(f"Önbellekten model yükleme hatası: {str(e)}")
|
302 |
+
return False
|
303 |
+
|
304 |
+
def get_models(self) -> Dict[str, Any]:
|
305 |
+
"""
|
306 |
+
Yüklenen modelleri ve bilgilerini döndürür.
|
307 |
+
|
308 |
+
Returns:
|
309 |
+
Dict[str, Any]: Model, tokenizer, pipeline ve model bilgileri
|
310 |
+
"""
|
311 |
+
return {
|
312 |
+
"toxicity_model": self.toxicity_model,
|
313 |
+
"toxicity_tokenizer": self.toxicity_tokenizer,
|
314 |
+
"quality_pipeline": self.quality_pipeline,
|
315 |
+
"model_info": self.model_info
|
316 |
+
}
|
models/model_selector.py
ADDED
@@ -0,0 +1,571 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import torch
|
2 |
+
from transformers import AutoTokenizer, AutoModelForSequenceClassification, pipeline
|
3 |
+
import logging
|
4 |
+
import time
|
5 |
+
import numpy as np
|
6 |
+
from sklearn.metrics import accuracy_score, f1_score, precision_score, recall_score
|
7 |
+
import os
|
8 |
+
import json
|
9 |
+
from typing import List, Dict, Any, Tuple, Optional, Union
|
10 |
+
import threading
|
11 |
+
|
12 |
+
# Loglama yapılandırması
|
13 |
+
logger = logging.getLogger(__name__)
|
14 |
+
handler = logging.StreamHandler()
|
15 |
+
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
16 |
+
handler.setFormatter(formatter)
|
17 |
+
logger.addHandler(handler)
|
18 |
+
logger.setLevel(logging.INFO)
|
19 |
+
|
20 |
+
|
21 |
+
class ModelSelector:
|
22 |
+
"""
|
23 |
+
Farklı NLP modellerini değerlendirip en iyi performansı sağlayanı seçen sınıf.
|
24 |
+
"""
|
25 |
+
|
26 |
+
def __init__(self, cache_dir: Optional[str] = None, use_cache: bool = True) -> None:
|
27 |
+
"""
|
28 |
+
Model Seçici sınıfını başlatır.
|
29 |
+
|
30 |
+
Args:
|
31 |
+
cache_dir: Model ve değerlendirme sonuçlarının önbelleğe alınacağı dizin
|
32 |
+
use_cache: Önbellek kullanımını etkinleştir/devre dışı bırak
|
33 |
+
"""
|
34 |
+
self.device = "cuda" if torch.cuda.is_available() else "cpu"
|
35 |
+
self.toxicity_models = []
|
36 |
+
self.quality_models = []
|
37 |
+
self.best_toxicity_model = None
|
38 |
+
self.best_quality_model = None
|
39 |
+
self.use_cache = use_cache
|
40 |
+
self.cache_dir = cache_dir or os.path.join(os.path.expanduser("~"), ".text_quality_toxicity_cache")
|
41 |
+
|
42 |
+
# Önbellek dizinini oluştur
|
43 |
+
if self.use_cache and not os.path.exists(self.cache_dir):
|
44 |
+
os.makedirs(self.cache_dir, exist_ok=True)
|
45 |
+
|
46 |
+
logger.info(
|
47 |
+
f"ModelSelector başlatıldı. Cihaz: {self.device}, Önbellek: {'Etkin' if use_cache else 'Devre dışı'}")
|
48 |
+
|
49 |
+
def load_candidate_models(self) -> bool:
|
50 |
+
"""
|
51 |
+
Değerlendirme için aday modelleri yükler.
|
52 |
+
|
53 |
+
Returns:
|
54 |
+
bool: Yükleme başarılı mı?
|
55 |
+
"""
|
56 |
+
# Önbellekten okunan modeller
|
57 |
+
cached_models = self._load_from_cache()
|
58 |
+
if cached_models:
|
59 |
+
self.toxicity_models = cached_models.get("toxicity_models", [])
|
60 |
+
self.quality_models = cached_models.get("quality_models", [])
|
61 |
+
self.best_toxicity_model = cached_models.get("best_toxicity_model")
|
62 |
+
self.best_quality_model = cached_models.get("best_quality_model")
|
63 |
+
|
64 |
+
# Önbellek bulundu, doğrulama yap
|
65 |
+
if self.best_toxicity_model and self.best_quality_model:
|
66 |
+
logger.info("Önbellekten en iyi modeller yüklendi.")
|
67 |
+
# Tokenizer ve model erişilebilir mi kontrol et
|
68 |
+
if "tokenizer" in self.best_toxicity_model and "model" in self.best_toxicity_model:
|
69 |
+
# En iyi model önbellekten doğru yüklendi
|
70 |
+
return True
|
71 |
+
|
72 |
+
# Zararlılık modelleri - Türkçe için optimize edilmiş
|
73 |
+
toxicity_candidates = [
|
74 |
+
{"name": "savasy/bert-base-turkish-sentiment", "type": "sentiment",
|
75 |
+
"language": "tr", "priority": 1, "description": "Türkçe duygu analizi için BERT modeli"},
|
76 |
+
{"name": "loodos/electra-turkish-sentiment", "type": "sentiment",
|
77 |
+
"language": "tr", "priority": 2, "description": "Türkçe duygu analizi için ELECTRA modeli"},
|
78 |
+
{"name": "dbmdz/bert-base-turkish-cased", "type": "general",
|
79 |
+
"language": "tr", "priority": 3, "description": "Genel amaçlı Türkçe BERT modeli"},
|
80 |
+
{"name": "ytu-ce-cosmos/turkish-bert-uncased-toxicity", "type": "toxicity",
|
81 |
+
"language": "tr", "priority": 4, "description": "Türkçe zararlılık tespiti için BERT modeli",
|
82 |
+
"optional": True},
|
83 |
+
{"name": "unitary/toxic-bert", "type": "toxicity",
|
84 |
+
"language": "en", "priority": 5, "description": "İngilizce zararlılık tespiti BERT modeli"}
|
85 |
+
]
|
86 |
+
|
87 |
+
# Kalite modelleri
|
88 |
+
quality_candidates = [
|
89 |
+
{"name": "Helsinki-NLP/opus-mt-tr-en", "type": "translation",
|
90 |
+
"language": "tr", "priority": 1, "description": "Türkçe-İngilizce çeviri modeli"},
|
91 |
+
{"name": "tuner/pegasus-turkish", "type": "summarization",
|
92 |
+
"language": "tr", "priority": 2, "description": "Türkçe özetleme için PEGASUS modeli", "optional": True},
|
93 |
+
{"name": "dbmdz/t5-base-turkish-summarization", "type": "summarization",
|
94 |
+
"language": "tr", "priority": 3, "description": "Türkçe özetleme için T5 modeli", "optional": True},
|
95 |
+
{"name": "sshleifer/distilbart-cnn-6-6", "type": "summarization",
|
96 |
+
"language": "en", "priority": 4, "description": "İngilizce özetleme için DistilBART modeli"}
|
97 |
+
]
|
98 |
+
|
99 |
+
# Modelleri önceliğe göre sırala
|
100 |
+
toxicity_candidates.sort(key=lambda x: x.get("priority", 999))
|
101 |
+
quality_candidates.sort(key=lambda x: x.get("priority", 999))
|
102 |
+
|
103 |
+
# Zaman aşımını önlemek için asenkron model yükleme
|
104 |
+
self._load_models_async(toxicity_candidates, quality_candidates)
|
105 |
+
|
106 |
+
# Yeterli model yüklendi mi kontrol et
|
107 |
+
return len(self.toxicity_models) > 0 and len(self.quality_models) > 0
|
108 |
+
|
109 |
+
def _load_models_async(self, toxicity_candidates: List[Dict[str, Any]],
|
110 |
+
quality_candidates: List[Dict[str, Any]]) -> None:
|
111 |
+
"""
|
112 |
+
Modelleri asenkron olarak yükler
|
113 |
+
|
114 |
+
Args:
|
115 |
+
toxicity_candidates: Zararlılık modellerinin listesi
|
116 |
+
quality_candidates: Kalite modellerinin listesi
|
117 |
+
"""
|
118 |
+
# Zararlılık modelleri için eş zamanlı yükleme
|
119 |
+
toxicity_threads = []
|
120 |
+
for candidate in toxicity_candidates:
|
121 |
+
thread = threading.Thread(
|
122 |
+
target=self._load_toxicity_model,
|
123 |
+
args=(candidate,)
|
124 |
+
)
|
125 |
+
thread.start()
|
126 |
+
toxicity_threads.append(thread)
|
127 |
+
|
128 |
+
# Kalite modelleri için eş zamanlı yükleme
|
129 |
+
quality_threads = []
|
130 |
+
for candidate in quality_candidates:
|
131 |
+
thread = threading.Thread(
|
132 |
+
target=self._load_quality_model,
|
133 |
+
args=(candidate,)
|
134 |
+
)
|
135 |
+
thread.start()
|
136 |
+
quality_threads.append(thread)
|
137 |
+
|
138 |
+
# Tüm işlemlerin tamamlanmasını bekle (timeout ile)
|
139 |
+
for thread in toxicity_threads:
|
140 |
+
thread.join(timeout=60) # Her model için 60 saniye timeout
|
141 |
+
|
142 |
+
for thread in quality_threads:
|
143 |
+
thread.join(timeout=60) # Her model için 60 saniye timeout
|
144 |
+
|
145 |
+
def _load_toxicity_model(self, candidate: Dict[str, Any]) -> None:
|
146 |
+
"""
|
147 |
+
Bir zararlılık modelini yükler
|
148 |
+
|
149 |
+
Args:
|
150 |
+
candidate: Model bilgilerini içeren sözlük
|
151 |
+
"""
|
152 |
+
try:
|
153 |
+
logger.info(f"Zararlılık modeli yükleniyor: {candidate['name']}")
|
154 |
+
start_time = time.time()
|
155 |
+
|
156 |
+
tokenizer = AutoTokenizer.from_pretrained(candidate["name"])
|
157 |
+
model = AutoModelForSequenceClassification.from_pretrained(candidate["name"])
|
158 |
+
model.to(self.device)
|
159 |
+
|
160 |
+
load_time = time.time() - start_time
|
161 |
+
candidate["tokenizer"] = tokenizer
|
162 |
+
candidate["model"] = model
|
163 |
+
candidate["load_time"] = load_time
|
164 |
+
|
165 |
+
# Liste eşzamanlı erişim için koruma
|
166 |
+
with threading.Lock():
|
167 |
+
self.toxicity_models.append(candidate)
|
168 |
+
|
169 |
+
logger.info(f"{candidate['name']} modeli {load_time:.2f} saniyede başarıyla yüklendi")
|
170 |
+
except Exception as e:
|
171 |
+
if candidate.get("optional", False):
|
172 |
+
logger.warning(f"Opsiyonel model {candidate['name']} atlanıyor: {str(e)}")
|
173 |
+
else:
|
174 |
+
logger.error(f"{candidate['name']} yüklenirken hata: {str(e)}")
|
175 |
+
|
176 |
+
def _load_quality_model(self, candidate: Dict[str, Any]) -> None:
|
177 |
+
"""
|
178 |
+
Bir kalite modelini yükler
|
179 |
+
|
180 |
+
Args:
|
181 |
+
candidate: Model bilgilerini içeren sözlük
|
182 |
+
"""
|
183 |
+
try:
|
184 |
+
logger.info(f"Kalite modeli yükleniyor: {candidate['name']}")
|
185 |
+
start_time = time.time()
|
186 |
+
|
187 |
+
if candidate["type"] == "translation":
|
188 |
+
pipe = pipeline("translation", model=candidate["name"], device=0 if self.device == "cuda" else -1)
|
189 |
+
elif candidate["type"] == "summarization":
|
190 |
+
pipe = pipeline("summarization", model=candidate["name"], device=0 if self.device == "cuda" else -1)
|
191 |
+
|
192 |
+
load_time = time.time() - start_time
|
193 |
+
candidate["pipeline"] = pipe
|
194 |
+
candidate["load_time"] = load_time
|
195 |
+
|
196 |
+
# Liste eşzamanlı erişim için koruma
|
197 |
+
with threading.Lock():
|
198 |
+
self.quality_models.append(candidate)
|
199 |
+
|
200 |
+
logger.info(f"{candidate['name']} modeli {load_time:.2f} saniyede başarıyla yüklendi")
|
201 |
+
except Exception as e:
|
202 |
+
if candidate.get("optional", False):
|
203 |
+
logger.warning(f"Opsiyonel model {candidate['name']} atlanıyor: {str(e)}")
|
204 |
+
else:
|
205 |
+
logger.error(f"{candidate['name']} yüklenirken hata: {str(e)}")
|
206 |
+
|
207 |
+
def evaluate_toxicity_models(self, validation_texts: List[str],
|
208 |
+
validation_labels: List[int]) -> Optional[Dict[str, Any]]:
|
209 |
+
"""
|
210 |
+
Zararlılık modellerini değerlendirir ve en iyisini seçer
|
211 |
+
|
212 |
+
Args:
|
213 |
+
validation_texts: Doğrulama metinleri
|
214 |
+
validation_labels: Doğrulama etiketleri (1=zararlı, 0=zararsız)
|
215 |
+
|
216 |
+
Returns:
|
217 |
+
Dict[str, Any]: En iyi modelin bilgileri veya None
|
218 |
+
"""
|
219 |
+
if not self.toxicity_models:
|
220 |
+
logger.error("Değerlendirme için zararlılık modeli yüklenmemiş")
|
221 |
+
return None
|
222 |
+
|
223 |
+
results = []
|
224 |
+
|
225 |
+
for model_info in self.toxicity_models:
|
226 |
+
logger.info(f"Zararlılık modeli değerlendiriliyor: {model_info['name']}")
|
227 |
+
model = model_info["model"]
|
228 |
+
tokenizer = model_info["tokenizer"]
|
229 |
+
|
230 |
+
predictions = []
|
231 |
+
start_time = time.time()
|
232 |
+
|
233 |
+
try:
|
234 |
+
for text in validation_texts:
|
235 |
+
# Metni tokenize et
|
236 |
+
inputs = tokenizer(text, return_tensors="pt", truncation=True, max_length=512)
|
237 |
+
inputs = {key: val.to(self.device) for key, val in inputs.items()}
|
238 |
+
|
239 |
+
# Tahmin yap
|
240 |
+
with torch.no_grad():
|
241 |
+
outputs = model(**inputs)
|
242 |
+
|
243 |
+
# Sonucu almak için model tipine göre işlem yap
|
244 |
+
if model_info["type"] == "sentiment":
|
245 |
+
# Sentiment modellerinde genellikle 0=negatif, 1=nötr, 2=pozitif
|
246 |
+
# veya 0=negatif, 1=pozitif
|
247 |
+
probs = torch.softmax(outputs.logits, dim=1).cpu().numpy()[0]
|
248 |
+
if len(probs) >= 3:
|
249 |
+
# Negatif olasılığını zararlılık olarak kabul et
|
250 |
+
pred = 1 if probs[0] > 0.5 else 0
|
251 |
+
else:
|
252 |
+
# İki sınıflı model
|
253 |
+
pred = 1 if probs[0] > 0.5 else 0
|
254 |
+
elif model_info["type"] == "toxicity":
|
255 |
+
# Toxicity modelleri genellikle 0=non-toxic, 1=toxic
|
256 |
+
probs = torch.softmax(outputs.logits, dim=1).cpu().numpy()[0]
|
257 |
+
pred = 1 if probs[1] > 0.5 else 0
|
258 |
+
else:
|
259 |
+
# Genel model - varsayılan
|
260 |
+
probs = torch.softmax(outputs.logits, dim=1).cpu().numpy()[0]
|
261 |
+
pred = 1 if probs[0] > 0.5 else 0
|
262 |
+
|
263 |
+
predictions.append(pred)
|
264 |
+
|
265 |
+
eval_time = time.time() - start_time
|
266 |
+
|
267 |
+
# Performans metrikleri hesapla
|
268 |
+
accuracy = accuracy_score(validation_labels, predictions)
|
269 |
+
precision = precision_score(validation_labels, predictions, average='binary', zero_division=0)
|
270 |
+
recall = recall_score(validation_labels, predictions, average='binary', zero_division=0)
|
271 |
+
f1 = f1_score(validation_labels, predictions, average='binary', zero_division=0)
|
272 |
+
|
273 |
+
# F1, precision ve recall'un ağırlıklı ortalaması
|
274 |
+
weighted_score = (f1 * 0.5) + (precision * 0.3) + (recall * 0.2)
|
275 |
+
|
276 |
+
# Sonuçları kaydet
|
277 |
+
evaluation_result = {
|
278 |
+
"model": model_info,
|
279 |
+
"accuracy": float(accuracy),
|
280 |
+
"precision": float(precision),
|
281 |
+
"recall": float(recall),
|
282 |
+
"f1_score": float(f1),
|
283 |
+
"weighted_score": float(weighted_score),
|
284 |
+
"eval_time": float(eval_time),
|
285 |
+
"predictions": predictions
|
286 |
+
}
|
287 |
+
|
288 |
+
results.append(evaluation_result)
|
289 |
+
|
290 |
+
logger.info(
|
291 |
+
f"{model_info['name']} - Doğruluk: {accuracy:.4f}, "
|
292 |
+
f"F1: {f1:.4f}, Precision: {precision:.4f}, "
|
293 |
+
f"Recall: {recall:.4f}, Süre: {eval_time:.2f}s"
|
294 |
+
)
|
295 |
+
|
296 |
+
except Exception as e:
|
297 |
+
logger.error(f"{model_info['name']} değerlendirilirken hata: {str(e)}")
|
298 |
+
|
299 |
+
if not results:
|
300 |
+
logger.error("Hiçbir model değerlendirilemedi")
|
301 |
+
return None
|
302 |
+
|
303 |
+
# Sıralama ve en iyi modeli seç (ağırlıklı skora göre)
|
304 |
+
results.sort(key=lambda x: x["weighted_score"], reverse=True)
|
305 |
+
best_model = results[0]["model"]
|
306 |
+
|
307 |
+
logger.info(
|
308 |
+
f"En iyi zararlılık modeli: {best_model['name']} - "
|
309 |
+
f"Ağırlıklı skor: {results[0]['weighted_score']:.4f}, "
|
310 |
+
f"F1: {results[0]['f1_score']:.4f}"
|
311 |
+
)
|
312 |
+
|
313 |
+
self.best_toxicity_model = best_model
|
314 |
+
|
315 |
+
# Önbelleğe kaydet
|
316 |
+
if self.use_cache:
|
317 |
+
self._save_to_cache()
|
318 |
+
|
319 |
+
return best_model
|
320 |
+
|
321 |
+
def evaluate_quality_models(self, validation_texts: List[str],
|
322 |
+
reference_summaries: Optional[List[str]] = None) -> Optional[Dict[str, Any]]:
|
323 |
+
"""
|
324 |
+
Kalite modellerini değerlendirir ve en iyisini seçer
|
325 |
+
|
326 |
+
Args:
|
327 |
+
validation_texts: Doğrulama metinleri
|
328 |
+
reference_summaries: Referans özetler (opsiyonel)
|
329 |
+
|
330 |
+
Returns:
|
331 |
+
Dict[str, Any]: En iyi modelin bilgileri veya None
|
332 |
+
"""
|
333 |
+
if not self.quality_models:
|
334 |
+
logger.error("Değerlendirme için kalite modeli yüklenmemiş")
|
335 |
+
return None
|
336 |
+
|
337 |
+
results = []
|
338 |
+
|
339 |
+
for model_info in self.quality_models:
|
340 |
+
logger.info(f"Kalite modeli değerlendiriliyor: {model_info['name']}")
|
341 |
+
pipe = model_info["pipeline"]
|
342 |
+
|
343 |
+
start_time = time.time()
|
344 |
+
processing_success = 0
|
345 |
+
avg_processing_time = []
|
346 |
+
|
347 |
+
# Her metni değerlendir
|
348 |
+
for i, text in enumerate(validation_texts[:5]): # Performans için sadece ilk 5 metni değerlendir
|
349 |
+
try:
|
350 |
+
text_start_time = time.time()
|
351 |
+
|
352 |
+
if model_info["type"] == "translation":
|
353 |
+
_ = pipe(text, max_length=100)
|
354 |
+
elif model_info["type"] == "summarization":
|
355 |
+
# Çok kısa metinlerde sorun oluşmaması için metin uzunluğunu kontrol et
|
356 |
+
text_words = len(text.split())
|
357 |
+
max_length = min(100, max(30, text_words // 2))
|
358 |
+
min_length = min(30, max(5, text_words // 4))
|
359 |
+
_ = pipe(text, max_length=max_length, min_length=min_length, do_sample=False)
|
360 |
+
|
361 |
+
text_process_time = time.time() - text_start_time
|
362 |
+
avg_processing_time.append(text_process_time)
|
363 |
+
processing_success += 1
|
364 |
+
|
365 |
+
except Exception as e:
|
366 |
+
logger.warning(f"{model_info['name']} için metin {i} işlenirken hata: {str(e)}")
|
367 |
+
|
368 |
+
eval_time = time.time() - start_time
|
369 |
+
success_rate = processing_success / min(5, len(validation_texts))
|
370 |
+
avg_time = np.mean(avg_processing_time) if avg_processing_time else float('inf')
|
371 |
+
|
372 |
+
# Modelin tipine göre skoru ayarla
|
373 |
+
if model_info["language"] == "tr":
|
374 |
+
# Türkçe modeller için daha yüksek ağırlık
|
375 |
+
language_weight = 1.2
|
376 |
+
else:
|
377 |
+
language_weight = 0.8
|
378 |
+
|
379 |
+
# Sonuçları kaydet
|
380 |
+
evaluation_result = {
|
381 |
+
"model": model_info,
|
382 |
+
"success_rate": float(success_rate),
|
383 |
+
"avg_processing_time": float(avg_time),
|
384 |
+
"eval_time": float(eval_time),
|
385 |
+
"language_weight": float(language_weight)
|
386 |
+
}
|
387 |
+
|
388 |
+
results.append(evaluation_result)
|
389 |
+
|
390 |
+
logger.info(
|
391 |
+
f"{model_info['name']} - Başarı Oranı: {success_rate:.2f}, "
|
392 |
+
f"Ortalama İşleme Süresi: {avg_time:.4f}s, "
|
393 |
+
f"Toplam Süre: {eval_time:.2f}s"
|
394 |
+
)
|
395 |
+
|
396 |
+
if not results:
|
397 |
+
logger.error("Hiçbir kalite modeli değerlendirilemedi")
|
398 |
+
return None
|
399 |
+
|
400 |
+
# Sıralama ve en iyi modeli seç
|
401 |
+
# Başarı oranı, dil ağırlığı ve hız faktörlerini dengeleyen bir formül
|
402 |
+
for result in results:
|
403 |
+
result["score"] = (
|
404 |
+
result["success_rate"] * 0.6 +
|
405 |
+
(1 / (1 + result["avg_processing_time"])) * 0.2 +
|
406 |
+
result["language_weight"] * 0.2
|
407 |
+
)
|
408 |
+
|
409 |
+
results.sort(key=lambda x: x["score"], reverse=True)
|
410 |
+
best_model = results[0]["model"]
|
411 |
+
|
412 |
+
logger.info(
|
413 |
+
f"En iyi kalite modeli: {best_model['name']} - "
|
414 |
+
f"Skor: {results[0]['score']:.4f}, "
|
415 |
+
f"Başarı Oranı: {results[0]['success_rate']:.2f}"
|
416 |
+
)
|
417 |
+
|
418 |
+
self.best_quality_model = best_model
|
419 |
+
|
420 |
+
# Önbelleğe kaydet
|
421 |
+
if self.use_cache:
|
422 |
+
self._save_to_cache()
|
423 |
+
|
424 |
+
return best_model
|
425 |
+
|
426 |
+
def select_best_models(self, sample_texts: List[str]) -> bool:
|
427 |
+
"""
|
428 |
+
En iyi modelleri örnek veriyle otomatik olarak seçer
|
429 |
+
|
430 |
+
Args:
|
431 |
+
sample_texts: Örnek metinler listesi
|
432 |
+
|
433 |
+
Returns:
|
434 |
+
bool: Model seçimi başarılı mı?
|
435 |
+
"""
|
436 |
+
# Modeller önbellekten yüklendiyse doğrudan kullan
|
437 |
+
if self.best_toxicity_model and self.best_quality_model:
|
438 |
+
logger.info("En iyi modeller önbellekten kullanılıyor")
|
439 |
+
return True
|
440 |
+
|
441 |
+
if not self.toxicity_models or not self.quality_models:
|
442 |
+
if not self.load_candidate_models():
|
443 |
+
logger.error("Aday modeller yüklenemedi")
|
444 |
+
return False
|
445 |
+
|
446 |
+
# Basit zararlılık değerlendirmesi için örnekler
|
447 |
+
toxic_samples = [
|
448 |
+
"Bu insanlar gerçekten çok aptal! Hepsi geri zekalı ve cahil.",
|
449 |
+
"Senden nefret ediyorum, kaybol buradan!",
|
450 |
+
"sen ne anlarsın ki bu konudan? gerizekalılar bile senden daha mantıklı konuşur"
|
451 |
+
]
|
452 |
+
|
453 |
+
non_toxic_samples = [
|
454 |
+
"Türkiye, zengin tarihi ve kültürel mirası ile güzel bir ülkedir.",
|
455 |
+
"Bugün hava çok güzel. Parkta yürüyüş yaptım ve kuşları izledim.",
|
456 |
+
"Bilgisayarınızı hızlandırmak için gereksiz programları kaldırın."
|
457 |
+
]
|
458 |
+
|
459 |
+
# Doğrulama verisi hazırla
|
460 |
+
validation_texts = toxic_samples + non_toxic_samples
|
461 |
+
if sample_texts and len(sample_texts) > 0:
|
462 |
+
# Kullanıcı örneklerinden bir kısmını ekle (en fazla 5 tane)
|
463 |
+
validation_texts.extend(sample_texts[:5])
|
464 |
+
|
465 |
+
validation_labels = [1, 1, 1, 0, 0, 0] + [0] * min(5, len(sample_texts))
|
466 |
+
|
467 |
+
# En iyi zararlılık modelini seç
|
468 |
+
best_toxicity = self.evaluate_toxicity_models(validation_texts, validation_labels)
|
469 |
+
|
470 |
+
# En iyi kalite modelini seç
|
471 |
+
best_quality = self.evaluate_quality_models(validation_texts)
|
472 |
+
|
473 |
+
success = best_toxicity is not None and best_quality is not None
|
474 |
+
|
475 |
+
if success and self.use_cache:
|
476 |
+
self._save_to_cache()
|
477 |
+
|
478 |
+
return success
|
479 |
+
|
480 |
+
def get_best_models(self) -> Dict[str, Any]:
|
481 |
+
"""
|
482 |
+
Seçilen en iyi modelleri döndürür
|
483 |
+
|
484 |
+
Returns:
|
485 |
+
Dict[str, Any]: En iyi modellerin bilgileri
|
486 |
+
"""
|
487 |
+
return {
|
488 |
+
"toxicity_model": self.best_toxicity_model["model"] if self.best_toxicity_model else None,
|
489 |
+
"toxicity_tokenizer": self.best_toxicity_model["tokenizer"] if self.best_toxicity_model else None,
|
490 |
+
"quality_pipeline": self.best_quality_model["pipeline"] if self.best_quality_model else None,
|
491 |
+
"toxicity_model_info": {
|
492 |
+
"name": self.best_toxicity_model["name"] if self.best_toxicity_model else "Unknown",
|
493 |
+
"description": self.best_toxicity_model["description"] if self.best_toxicity_model else "Unknown",
|
494 |
+
"language": self.best_toxicity_model["language"] if self.best_toxicity_model else "Unknown"
|
495 |
+
},
|
496 |
+
"quality_model_info": {
|
497 |
+
"name": self.best_quality_model["name"] if self.best_quality_model else "Unknown",
|
498 |
+
"description": self.best_quality_model["description"] if self.best_quality_model else "Unknown",
|
499 |
+
"language": self.best_quality_model["language"] if self.best_quality_model else "Unknown"
|
500 |
+
}
|
501 |
+
}
|
502 |
+
|
503 |
+
def _save_to_cache(self) -> None:
|
504 |
+
"""Modellerin değerlendirme sonuçlarını önbelleğe kaydeder"""
|
505 |
+
if not self.use_cache:
|
506 |
+
return
|
507 |
+
|
508 |
+
try:
|
509 |
+
cache_file = os.path.join(self.cache_dir, "model_selection_results.json")
|
510 |
+
|
511 |
+
# Modeller ve tokenizer'ları hariç tutarak kalan bilgileri kaydet
|
512 |
+
cache_data = {
|
513 |
+
"timestamp": time.time(),
|
514 |
+
"toxicity_models": [
|
515 |
+
{k: v for k, v in model.items() if k not in ["model", "tokenizer"]}
|
516 |
+
for model in self.toxicity_models
|
517 |
+
],
|
518 |
+
"quality_models": [
|
519 |
+
{k: v for k, v in model.items() if k not in ["pipeline"]}
|
520 |
+
for model in self.quality_models
|
521 |
+
],
|
522 |
+
"best_toxicity_model": (
|
523 |
+
{k: v for k, v in self.best_toxicity_model.items() if k not in ["model", "tokenizer"]}
|
524 |
+
if self.best_toxicity_model else None
|
525 |
+
),
|
526 |
+
"best_quality_model": (
|
527 |
+
{k: v for k, v in self.best_quality_model.items() if k not in ["pipeline"]}
|
528 |
+
if self.best_quality_model else None
|
529 |
+
)
|
530 |
+
}
|
531 |
+
|
532 |
+
with open(cache_file, 'w', encoding='utf-8') as f:
|
533 |
+
json.dump(cache_data, f, ensure_ascii=False, indent=2)
|
534 |
+
|
535 |
+
logger.info(f"Model seçim sonuçları önbelleğe kaydedildi: {cache_file}")
|
536 |
+
except Exception as e:
|
537 |
+
logger.error(f"Önbelleğe kaydetme hatası: {str(e)}")
|
538 |
+
|
539 |
+
def _load_from_cache(self) -> Optional[Dict[str, Any]]:
|
540 |
+
"""
|
541 |
+
Önbellekten modellerin değerlendirme sonuçlarını yükler
|
542 |
+
|
543 |
+
Returns:
|
544 |
+
Optional[Dict[str, Any]]: Yüklenen önbellek verisi veya None
|
545 |
+
"""
|
546 |
+
if not self.use_cache:
|
547 |
+
return None
|
548 |
+
|
549 |
+
try:
|
550 |
+
cache_file = os.path.join(self.cache_dir, "model_selection_results.json")
|
551 |
+
|
552 |
+
if not os.path.exists(cache_file):
|
553 |
+
return None
|
554 |
+
|
555 |
+
# Önbellek dosyasının yaşını kontrol et (24 saatten eskiyse yok say)
|
556 |
+
file_age = time.time() - os.path.getmtime(cache_file)
|
557 |
+
if file_age > 86400: # 24 saat = 86400 saniye
|
558 |
+
logger.info(f"Önbellek dosyası çok eski ({file_age / 3600:.1f} saat), tekrar değerlendirme yapılacak")
|
559 |
+
return None
|
560 |
+
|
561 |
+
with open(cache_file, 'r', encoding='utf-8') as f:
|
562 |
+
cache_data = json.load(f)
|
563 |
+
|
564 |
+
logger.info(f"Model seçim sonuçları önbellekten yüklendi: {cache_file}")
|
565 |
+
|
566 |
+
# Önbellekten sadece isim bilgilerini yükle, modellerin kendisini değil
|
567 |
+
return cache_data
|
568 |
+
|
569 |
+
except Exception as e:
|
570 |
+
logger.error(f"Önbellekten yükleme hatası: {str(e)}")
|
571 |
+
return None
|
requirements.txt
ADDED
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
altair==5.5.0
|
2 |
+
attrs==25.3.0
|
3 |
+
blinker==1.9.0
|
4 |
+
cachetools==5.5.2
|
5 |
+
certifi==2025.1.31
|
6 |
+
charset-normalizer==3.4.1
|
7 |
+
click==8.1.8
|
8 |
+
contourpy==1.3.1
|
9 |
+
cycler==0.12.1
|
10 |
+
filelock==3.18.0
|
11 |
+
fonttools==4.56.0
|
12 |
+
fsspec==2025.3.0
|
13 |
+
gitdb==4.0.12
|
14 |
+
GitPython==3.1.44
|
15 |
+
huggingface-hub==0.29.3
|
16 |
+
idna==3.10
|
17 |
+
Jinja2==3.1.6
|
18 |
+
joblib==1.4.2
|
19 |
+
jsonschema==4.23.0
|
20 |
+
jsonschema-specifications==2024.10.1
|
21 |
+
kiwisolver==1.4.8
|
22 |
+
MarkupSafe==3.0.2
|
23 |
+
matplotlib==3.10.1
|
24 |
+
mpmath==1.3.0
|
25 |
+
narwhals==1.32.0
|
26 |
+
networkx==3.4.2
|
27 |
+
numpy==2.2.4
|
28 |
+
packaging==24.2
|
29 |
+
pandas==2.2.3
|
30 |
+
pillow==11.1.0
|
31 |
+
plotly==6.0.1
|
32 |
+
protobuf==5.29.4
|
33 |
+
pyarrow==19.0.1
|
34 |
+
pydeck==0.9.1
|
35 |
+
pyparsing==3.2.3
|
36 |
+
python-dateutil==2.9.0.post0
|
37 |
+
pytz==2025.2
|
38 |
+
PyYAML==6.0.2
|
39 |
+
referencing==0.36.2
|
40 |
+
regex==2024.11.6
|
41 |
+
requests==2.32.3
|
42 |
+
rpds-py==0.24.0
|
43 |
+
safetensors==0.5.3
|
44 |
+
scikit-learn==1.6.1
|
45 |
+
scipy==1.15.2
|
46 |
+
sentencepiece==0.2.0
|
47 |
+
setuptools==78.1.0
|
48 |
+
six==1.17.0
|
49 |
+
smmap==5.0.2
|
50 |
+
streamlit==1.44.0
|
51 |
+
sympy==1.13.1
|
52 |
+
tenacity==9.0.0
|
53 |
+
threadpoolctl==3.6.0
|
54 |
+
tokenizers==0.21.1
|
55 |
+
toml==0.10.2
|
56 |
+
torch==2.6.0
|
57 |
+
tornado==6.4.2
|
58 |
+
tqdm==4.67.1
|
59 |
+
transformers==4.50.3
|
60 |
+
typing_extensions==4.13.0
|
61 |
+
tzdata==2025.2
|
62 |
+
urllib3==2.3.0
|
utils/__pycache__/data_handler.cpython-312.pyc
ADDED
Binary file (7.32 kB). View file
|
|
utils/__pycache__/keyword_extractor.cpython-312.pyc
ADDED
Binary file (10.4 kB). View file
|
|
utils/__pycache__/language_detector.cpython-312.pyc
ADDED
Binary file (8.85 kB). View file
|
|
utils/__pycache__/quality_scorer.cpython-312.pyc
ADDED
Binary file (8.2 kB). View file
|
|
utils/__pycache__/sentiment_analyzer.cpython-312.pyc
ADDED
Binary file (6.89 kB). View file
|
|
utils/__pycache__/text_improver.cpython-312.pyc
ADDED
Binary file (12.6 kB). View file
|
|
utils/__pycache__/toxicity_scorer.cpython-312.pyc
ADDED
Binary file (8.91 kB). View file
|
|
utils/data_handler.py
ADDED
@@ -0,0 +1,177 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import pandas as pd
|
2 |
+
import numpy as np
|
3 |
+
import os
|
4 |
+
import logging
|
5 |
+
from datetime import datetime
|
6 |
+
|
7 |
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
8 |
+
|
9 |
+
|
10 |
+
class DataHandler:
|
11 |
+
def __init__(self, quality_scorer=None, toxicity_scorer=None):
|
12 |
+
"""
|
13 |
+
Veri işleme sınıfını başlatır.
|
14 |
+
|
15 |
+
Args:
|
16 |
+
quality_scorer: Metin kalitesi değerlendirme nesnesi
|
17 |
+
toxicity_scorer: Zararlılık skoru değerlendirme nesnesi
|
18 |
+
"""
|
19 |
+
self.quality_scorer = quality_scorer
|
20 |
+
self.toxicity_scorer = toxicity_scorer
|
21 |
+
|
22 |
+
def load_data(self, file_path, text_column=None):
|
23 |
+
"""
|
24 |
+
CSV veya Excel dosyasını yükler.
|
25 |
+
|
26 |
+
Args:
|
27 |
+
file_path: Yüklenecek dosyanın yolu
|
28 |
+
text_column: Metin sütunu adı (belirtilmezse otomatik tespit edilir)
|
29 |
+
|
30 |
+
Returns:
|
31 |
+
pd.DataFrame: Yüklenen veri
|
32 |
+
"""
|
33 |
+
try:
|
34 |
+
# Dosya uzantısını kontrol et
|
35 |
+
if file_path.endswith('.csv'):
|
36 |
+
df = pd.read_csv(file_path)
|
37 |
+
elif file_path.endswith(('.xls', '.xlsx')):
|
38 |
+
df = pd.read_excel(file_path)
|
39 |
+
else:
|
40 |
+
raise ValueError("Desteklenmeyen dosya formatı. Lütfen CSV veya Excel dosyası yükleyin.")
|
41 |
+
|
42 |
+
# Metin sütununu belirle
|
43 |
+
if text_column is None:
|
44 |
+
# En çok metin içeriği olan sütunu bul
|
45 |
+
text_lengths = {}
|
46 |
+
for col in df.columns:
|
47 |
+
if df[col].dtype == object: # Sadece metin sütunlarını kontrol et
|
48 |
+
# Ortalama metin uzunluğunu hesapla
|
49 |
+
avg_len = df[col].astype(str).str.len().mean()
|
50 |
+
text_lengths[col] = avg_len
|
51 |
+
|
52 |
+
if text_lengths:
|
53 |
+
# En uzun ortalama metne sahip sütunu seç
|
54 |
+
text_column = max(text_lengths.items(), key=lambda x: x[1])[0]
|
55 |
+
else:
|
56 |
+
# Hiçbir metin sütunu bulunamazsa ilk sütunu kullan
|
57 |
+
text_column = df.columns[0]
|
58 |
+
|
59 |
+
logging.info(f"Otomatik tespit edilen metin sütunu: {text_column}")
|
60 |
+
|
61 |
+
return df, text_column
|
62 |
+
|
63 |
+
except Exception as e:
|
64 |
+
logging.error(f"Veri yükleme hatası: {str(e)}")
|
65 |
+
raise e
|
66 |
+
|
67 |
+
def process_data(self, df, text_column, quality_threshold=0.5, toxicity_threshold=0.5, batch_size=8):
|
68 |
+
"""
|
69 |
+
Veriyi işler, kalite ve zararlılık skorlarını hesaplar.
|
70 |
+
|
71 |
+
Args:
|
72 |
+
df: İşlenecek veri çerçevesi
|
73 |
+
text_column: Metin sütunu adı
|
74 |
+
quality_threshold: Kalite eşik değeri
|
75 |
+
toxicity_threshold: Zararlılık eşik değeri
|
76 |
+
batch_size: İşlenecek grup boyutu
|
77 |
+
|
78 |
+
Returns:
|
79 |
+
pd.DataFrame: İşlenmiş veri
|
80 |
+
"""
|
81 |
+
try:
|
82 |
+
# Boş veya NaN değerli satırları kontrol et
|
83 |
+
df = df.copy()
|
84 |
+
df[text_column] = df[text_column].astype(str)
|
85 |
+
df = df[df[text_column].str.strip() != ""]
|
86 |
+
df = df.reset_index(drop=True)
|
87 |
+
|
88 |
+
texts = df[text_column].tolist()
|
89 |
+
|
90 |
+
# Kalite skorlarını hesapla
|
91 |
+
if self.quality_scorer:
|
92 |
+
logging.info("Kalite skorları hesaplanıyor...")
|
93 |
+
quality_scores, quality_features = self.quality_scorer.batch_score(texts, batch_size=batch_size)
|
94 |
+
df['quality_score'] = quality_scores
|
95 |
+
|
96 |
+
# Kalite özelliklerini ekle
|
97 |
+
for i, features in enumerate(quality_features):
|
98 |
+
for feat_name, feat_value in features.items():
|
99 |
+
if i == 0: # İlk satır için sütun oluştur
|
100 |
+
df[feat_name] = np.nan
|
101 |
+
df.at[i, feat_name] = feat_value
|
102 |
+
|
103 |
+
# Zararlılık skorlarını hesapla
|
104 |
+
if self.toxicity_scorer:
|
105 |
+
logging.info("Zararlılık skorları hesaplanıyor...")
|
106 |
+
toxicity_scores = self.toxicity_scorer.batch_score(texts, batch_size=batch_size)
|
107 |
+
df['toxicity_score'] = toxicity_scores
|
108 |
+
|
109 |
+
# Eşik değerlerine göre etiketle
|
110 |
+
if 'quality_score' in df.columns:
|
111 |
+
df['low_quality'] = df['quality_score'] < quality_threshold
|
112 |
+
|
113 |
+
if 'toxicity_score' in df.columns:
|
114 |
+
df['is_toxic'] = df['toxicity_score'] > toxicity_threshold
|
115 |
+
|
116 |
+
# Genel değerlendirme
|
117 |
+
if 'quality_score' in df.columns and 'toxicity_score' in df.columns:
|
118 |
+
df['acceptable'] = (df['quality_score'] >= quality_threshold) & (
|
119 |
+
df['toxicity_score'] <= toxicity_threshold)
|
120 |
+
|
121 |
+
logging.info("Veri işleme tamamlandı.")
|
122 |
+
return df
|
123 |
+
|
124 |
+
except Exception as e:
|
125 |
+
logging.error(f"Veri işleme hatası: {str(e)}")
|
126 |
+
raise e
|
127 |
+
|
128 |
+
def filter_data(self, df, quality_threshold=0.5, toxicity_threshold=0.5):
|
129 |
+
"""
|
130 |
+
Veriyi belirlenen eşik değerlerine göre filtreler.
|
131 |
+
|
132 |
+
Args:
|
133 |
+
df: Filtrelenecek veri çerçevesi
|
134 |
+
quality_threshold: Kalite eşik değeri
|
135 |
+
toxicity_threshold: Zararlılık eşik değeri
|
136 |
+
|
137 |
+
Returns:
|
138 |
+
pd.DataFrame: Filtrelenmiş veri
|
139 |
+
"""
|
140 |
+
filtered_df = df.copy()
|
141 |
+
|
142 |
+
# Kalite filtreleme
|
143 |
+
if 'quality_score' in filtered_df.columns:
|
144 |
+
filtered_df = filtered_df[filtered_df['quality_score'] >= quality_threshold]
|
145 |
+
|
146 |
+
# Zararlılık filtreleme
|
147 |
+
if 'toxicity_score' in filtered_df.columns:
|
148 |
+
filtered_df = filtered_df[filtered_df['toxicity_score'] <= toxicity_threshold]
|
149 |
+
|
150 |
+
return filtered_df
|
151 |
+
|
152 |
+
def save_data(self, df, output_path=None):
|
153 |
+
"""
|
154 |
+
İşlenmiş veriyi kaydeder.
|
155 |
+
|
156 |
+
Args:
|
157 |
+
df: Kaydedilecek veri çerçevesi
|
158 |
+
output_path: Çıktı dosyası yolu (belirtilmezse otomatik oluşturulur)
|
159 |
+
|
160 |
+
Returns:
|
161 |
+
str: Kaydedilen dosyanın yolu
|
162 |
+
"""
|
163 |
+
if output_path is None:
|
164 |
+
# Varsayılan çıktı yolunu oluştur
|
165 |
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
166 |
+
output_dir = "data/processed"
|
167 |
+
|
168 |
+
# Klasörü oluştur
|
169 |
+
os.makedirs(output_dir, exist_ok=True)
|
170 |
+
|
171 |
+
output_path = os.path.join(output_dir, f"processed_data_{timestamp}.csv")
|
172 |
+
|
173 |
+
# Veriyi kaydet
|
174 |
+
df.to_csv(output_path, index=False)
|
175 |
+
logging.info(f"Veri başarıyla kaydedildi: {output_path}")
|
176 |
+
|
177 |
+
return output_path
|
utils/keyword_extractor.py
ADDED
@@ -0,0 +1,273 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import re
|
2 |
+
import string
|
3 |
+
import numpy as np
|
4 |
+
from sklearn.feature_extraction.text import TfidfVectorizer, CountVectorizer
|
5 |
+
from collections import Counter
|
6 |
+
import logging
|
7 |
+
|
8 |
+
logger = logging.getLogger(__name__)
|
9 |
+
|
10 |
+
|
11 |
+
class KeywordExtractor:
|
12 |
+
"""
|
13 |
+
Metin içindeki anahtar kelimeleri çıkaran sınıf.
|
14 |
+
TF-IDF, rakip kelimeler ve diğer metotlarla anahtar kelime çıkarma işlemi yapar.
|
15 |
+
"""
|
16 |
+
|
17 |
+
def __init__(self):
|
18 |
+
"""Anahtar kelime çıkarıcıyı başlatır"""
|
19 |
+
# Türkçe stopwords (durma kelimeleri)
|
20 |
+
self.turkish_stopwords = [
|
21 |
+
've', 'veya', 'ile', 'için', 'bu', 'bir', 'ya', 'de', 'da', 'ki', 'ne', 'her', 'çok',
|
22 |
+
'daha', 'ama', 'fakat', 'lakin', 'ancak', 'gibi', 'kadar', 'sonra', 'önce', 'göre',
|
23 |
+
'nasıl', 'neden', 'şey', 'ben', 'sen', 'o', 'biz', 'siz', 'onlar', 'kendi', 'aynı',
|
24 |
+
'ise', 'mi', 'mı', 'mu', 'mü', 'hem', 'değil', 'hiç', 'olarak', 'evet', 'hayır',
|
25 |
+
'belki', 'tüm', 'yani', 'hep', 'şu', 'şey', 'tabi', 'tamam', 'bunlar', 'şunlar',
|
26 |
+
'böyle', 'öyle', 'şöyle', 'iki', 'üç', 'dört', 'beş', 'altı', 'yedi', 'sekiz', 'dokuz',
|
27 |
+
'on', 'yüz', 'bin', 'milyon', 'milyar', 'var', 'yok', 'oldu', 'olur', 'oluyor', 'olacak'
|
28 |
+
]
|
29 |
+
|
30 |
+
# İngilizce stopwords (durma kelimeleri)
|
31 |
+
self.english_stopwords = [
|
32 |
+
'the', 'and', 'a', 'to', 'of', 'in', 'for', 'with', 'on', 'at', 'by', 'from', 'about',
|
33 |
+
'as', 'into', 'like', 'through', 'after', 'over', 'between', 'out', 'against', 'during',
|
34 |
+
'without', 'before', 'under', 'around', 'among', 'is', 'are', 'was', 'were', 'be', 'been',
|
35 |
+
'being', 'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would', 'shall', 'should',
|
36 |
+
'may', 'might', 'must', 'can', 'could', 'i', 'you', 'he', 'she', 'it', 'we', 'they',
|
37 |
+
'me', 'him', 'her', 'us', 'them', 'who', 'which', 'whose', 'whom', 'this', 'that', 'these',
|
38 |
+
'those', 'am', 'is', 'are', 'was', 'were', 'an', 'my', 'your', 'his', 'its', 'our', 'their'
|
39 |
+
]
|
40 |
+
|
41 |
+
# Tüm stopwords listesini birleştir
|
42 |
+
self.stopwords = set(self.turkish_stopwords + self.english_stopwords)
|
43 |
+
|
44 |
+
# TF-IDF vektörleyici
|
45 |
+
self.tfidf_vectorizer = TfidfVectorizer(
|
46 |
+
max_df=0.9,
|
47 |
+
min_df=2,
|
48 |
+
max_features=200,
|
49 |
+
stop_words=self.stopwords,
|
50 |
+
ngram_range=(1, 2) # Tek kelimeler ve ikili kelime grupları
|
51 |
+
)
|
52 |
+
|
53 |
+
# Sayısal karakter ve noktalama işaretlerini temizlemek için regex pattern
|
54 |
+
self.cleanup_pattern = re.compile(f'[{re.escape(string.punctuation)}]|[0-9]')
|
55 |
+
|
56 |
+
logger.info("Anahtar kelime çıkarıcı başlatıldı")
|
57 |
+
|
58 |
+
def preprocess_text(self, text):
|
59 |
+
"""
|
60 |
+
Metni anahtar kelime çıkarma için ön işleme tabi tutar
|
61 |
+
|
62 |
+
Args:
|
63 |
+
text: İşlenecek metin
|
64 |
+
|
65 |
+
Returns:
|
66 |
+
str: Temizlenmiş metin
|
67 |
+
"""
|
68 |
+
if not text:
|
69 |
+
return ""
|
70 |
+
|
71 |
+
# Küçük harfe çevir
|
72 |
+
text = text.lower()
|
73 |
+
|
74 |
+
# Noktalama işaretlerini temizle
|
75 |
+
text = self.cleanup_pattern.sub(' ', text)
|
76 |
+
|
77 |
+
# Fazla boşlukları temizle
|
78 |
+
text = re.sub(r'\s+', ' ', text).strip()
|
79 |
+
|
80 |
+
return text
|
81 |
+
|
82 |
+
def extract_keywords_tfidf(self, text, num_keywords=5):
|
83 |
+
"""
|
84 |
+
TF-IDF kullanarak metinden anahtar kelimeleri çıkarır
|
85 |
+
|
86 |
+
Args:
|
87 |
+
text: Anahtar kelimeleri çıkarılacak metin
|
88 |
+
num_keywords: Çıkarılacak anahtar kelime sayısı
|
89 |
+
|
90 |
+
Returns:
|
91 |
+
list: [(anahtar_kelime, skor), ...] formatında liste
|
92 |
+
"""
|
93 |
+
try:
|
94 |
+
if not text or len(text.strip()) < 10:
|
95 |
+
return []
|
96 |
+
|
97 |
+
# Metni ön işle
|
98 |
+
processed_text = self.preprocess_text(text)
|
99 |
+
|
100 |
+
# TF-IDF matrix oluştur
|
101 |
+
tfidf_matrix = self.tfidf_vectorizer.fit_transform([processed_text])
|
102 |
+
|
103 |
+
# Feature isimleri (kelimeler)
|
104 |
+
feature_names = self.tfidf_vectorizer.get_feature_names_out()
|
105 |
+
|
106 |
+
# Kelimelerin TF-IDF skorlarını hesapla ve sırala
|
107 |
+
tfidf_scores = zip(feature_names, tfidf_matrix.toarray()[0])
|
108 |
+
sorted_scores = sorted(tfidf_scores, key=lambda x: x[1], reverse=True)
|
109 |
+
|
110 |
+
# En yüksek skorlu kelimeleri seç
|
111 |
+
top_keywords = sorted_scores[:num_keywords]
|
112 |
+
|
113 |
+
return top_keywords
|
114 |
+
|
115 |
+
except Exception as e:
|
116 |
+
logger.error(f"TF-IDF anahtar kelime çıkarma hatası: {str(e)}")
|
117 |
+
return []
|
118 |
+
|
119 |
+
def extract_keywords_textrank(self, text, num_keywords=5):
|
120 |
+
"""
|
121 |
+
TextRank benzeri bir algoritma ile anahtar kelimeleri çıkarır
|
122 |
+
|
123 |
+
Args:
|
124 |
+
text: Anahtar kelimeleri çıkarılacak metin
|
125 |
+
num_keywords: Çıkarılacak anahtar kelime sayısı
|
126 |
+
|
127 |
+
Returns:
|
128 |
+
list: [(anahtar_kelime, skor), ...] formatında liste
|
129 |
+
"""
|
130 |
+
try:
|
131 |
+
if not text or len(text.strip()) < 10:
|
132 |
+
return []
|
133 |
+
|
134 |
+
# Metni ön işle
|
135 |
+
processed_text = self.preprocess_text(text)
|
136 |
+
|
137 |
+
# Kelimeleri ayır
|
138 |
+
words = processed_text.split()
|
139 |
+
|
140 |
+
# Stopwords olmayan kelimeleri filtrele
|
141 |
+
filtered_words = [word for word in words if word not in self.stopwords and len(word) > 2]
|
142 |
+
|
143 |
+
# Kelime frekanslarını hesapla
|
144 |
+
word_freq = Counter(filtered_words)
|
145 |
+
|
146 |
+
# En sık geçen kelimeleri seç
|
147 |
+
most_common = word_freq.most_common(num_keywords * 2) # Daha fazla al, sonra filtreleyeceğiz
|
148 |
+
|
149 |
+
# TF-IDF skorlaması ile benzer bir yaklaşım uygula
|
150 |
+
# Kelime sıklığının logaritması * kelimenin benzersizliği
|
151 |
+
scored_words = []
|
152 |
+
for word, count in most_common:
|
153 |
+
# Benzersizlik faktörü: Toplam kelime sayısı / kelimenin sıklığı
|
154 |
+
uniqueness = len(filtered_words) / (count + 1)
|
155 |
+
# Skor hesapla
|
156 |
+
score = np.log(count + 1) * uniqueness
|
157 |
+
scored_words.append((word, score))
|
158 |
+
|
159 |
+
# Skorlara göre sırala
|
160 |
+
scored_words.sort(key=lambda x: x[1], reverse=True)
|
161 |
+
|
162 |
+
return scored_words[:num_keywords]
|
163 |
+
|
164 |
+
except Exception as e:
|
165 |
+
logger.error(f"TextRank anahtar kelime çıkarma hatası: {str(e)}")
|
166 |
+
return []
|
167 |
+
|
168 |
+
def extract_bigrams(self, text, num_bigrams=3):
|
169 |
+
"""
|
170 |
+
Metinden ikili kelime gruplarını (bigram) çıkarır
|
171 |
+
|
172 |
+
Args:
|
173 |
+
text: Anahtar kelimeleri çıkarılacak metin
|
174 |
+
num_bigrams: Çıkarılacak bigram sayısı
|
175 |
+
|
176 |
+
Returns:
|
177 |
+
list: [(bigram, skor), ...] formatında liste
|
178 |
+
"""
|
179 |
+
try:
|
180 |
+
if not text or len(text.strip()) < 10:
|
181 |
+
return []
|
182 |
+
|
183 |
+
# Metni ön işle
|
184 |
+
processed_text = self.preprocess_text(text)
|
185 |
+
|
186 |
+
# Bigram için vektörleyici
|
187 |
+
bigram_vectorizer = CountVectorizer(
|
188 |
+
ngram_range=(2, 2),
|
189 |
+
stop_words=self.stopwords,
|
190 |
+
max_features=100
|
191 |
+
)
|
192 |
+
|
193 |
+
# Bigram matrix oluştur
|
194 |
+
bigram_matrix = bigram_vectorizer.fit_transform([processed_text])
|
195 |
+
|
196 |
+
# Feature isimleri (bigramlar)
|
197 |
+
feature_names = bigram_vectorizer.get_feature_names_out()
|
198 |
+
|
199 |
+
# Bigramların skorlarını hesapla ve sırala
|
200 |
+
bigram_scores = zip(feature_names, bigram_matrix.toarray()[0])
|
201 |
+
sorted_scores = sorted(bigram_scores, key=lambda x: x[1], reverse=True)
|
202 |
+
|
203 |
+
# En yüksek skorlu bigramları seç
|
204 |
+
top_bigrams = sorted_scores[:num_bigrams]
|
205 |
+
|
206 |
+
return top_bigrams
|
207 |
+
|
208 |
+
except Exception as e:
|
209 |
+
logger.error(f"Bigram çıkarma hatası: {str(e)}")
|
210 |
+
return []
|
211 |
+
|
212 |
+
def extract_keywords(self, text, method='combined', num_keywords=10):
|
213 |
+
"""
|
214 |
+
Metinden anahtar kelimeleri çıkarır
|
215 |
+
|
216 |
+
Args:
|
217 |
+
text: Anahtar kelimeleri çıkarılacak metin
|
218 |
+
method: Kullanılacak metod ('tfidf', 'textrank', 'combined')
|
219 |
+
num_keywords: Toplam çıkarılacak anahtar kelime sayısı
|
220 |
+
|
221 |
+
Returns:
|
222 |
+
dict: {
|
223 |
+
'keywords': [(anahtar_kelime, skor), ...],
|
224 |
+
'bigrams': [(bigram, skor), ...],
|
225 |
+
'method': kullanılan metod
|
226 |
+
}
|
227 |
+
"""
|
228 |
+
if not text or len(text.strip()) < 10:
|
229 |
+
return {
|
230 |
+
'keywords': [],
|
231 |
+
'bigrams': [],
|
232 |
+
'method': method
|
233 |
+
}
|
234 |
+
|
235 |
+
try:
|
236 |
+
keywords = []
|
237 |
+
|
238 |
+
if method == 'tfidf':
|
239 |
+
keywords = self.extract_keywords_tfidf(text, num_keywords)
|
240 |
+
elif method == 'textrank':
|
241 |
+
keywords = self.extract_keywords_textrank(text, num_keywords)
|
242 |
+
else: # combined
|
243 |
+
# TF-IDF ve TextRank sonuçlarını birleştir
|
244 |
+
tfidf_keywords = self.extract_keywords_tfidf(text, num_keywords // 2)
|
245 |
+
textrank_keywords = self.extract_keywords_textrank(text, num_keywords // 2)
|
246 |
+
|
247 |
+
# İki listeyi birleştir
|
248 |
+
combined = {}
|
249 |
+
for keyword, score in tfidf_keywords + textrank_keywords:
|
250 |
+
if keyword in combined:
|
251 |
+
combined[keyword] = max(combined[keyword], score)
|
252 |
+
else:
|
253 |
+
combined[keyword] = score
|
254 |
+
|
255 |
+
# En yüksek skorlu kelimeleri seç
|
256 |
+
keywords = sorted(combined.items(), key=lambda x: x[1], reverse=True)[:num_keywords]
|
257 |
+
|
258 |
+
# Bigramları da ekle
|
259 |
+
bigrams = self.extract_bigrams(text, num_keywords // 3)
|
260 |
+
|
261 |
+
return {
|
262 |
+
'keywords': keywords,
|
263 |
+
'bigrams': bigrams,
|
264 |
+
'method': method
|
265 |
+
}
|
266 |
+
|
267 |
+
except Exception as e:
|
268 |
+
logger.error(f"Anahtar kelime çıkarma hatası: {str(e)}")
|
269 |
+
return {
|
270 |
+
'keywords': [],
|
271 |
+
'bigrams': [],
|
272 |
+
'method': method
|
273 |
+
}
|
utils/language_detector.py
ADDED
@@ -0,0 +1,260 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import re
|
2 |
+
from collections import Counter
|
3 |
+
import logging
|
4 |
+
|
5 |
+
logger = logging.getLogger(__name__)
|
6 |
+
|
7 |
+
|
8 |
+
class LanguageDetector:
|
9 |
+
"""
|
10 |
+
Metin dilini algılayan sınıf.
|
11 |
+
İstatistiksel yöntemlerle metinlerin dilini tespit eder.
|
12 |
+
"""
|
13 |
+
|
14 |
+
def __init__(self):
|
15 |
+
"""Dil algılama sınıfını başlatır"""
|
16 |
+
# Dil tanıma için tipik karakter ve kelime varlıkları
|
17 |
+
self.language_profiles = {
|
18 |
+
'tr': {
|
19 |
+
'chars': 'abcçdefgğhıijklmnoöprsştuüvyz',
|
20 |
+
'unique_chars': 'çğıöşü',
|
21 |
+
'common_words': [
|
22 |
+
've', 'bir', 'bu', 'da', 'de', 'için', 'ile', 'ben', 'sen', 'o',
|
23 |
+
'biz', 'siz', 'ama', 'ki', 'ya', 'çok', 'daha', 'en', 'ne', 'kadar',
|
24 |
+
'var', 'yok', 'mı', 'mi', 'mu', 'mü', 'gibi', 'olarak', 'çünkü',
|
25 |
+
'sonra', 'önce', 'nasıl', 'neden', 'evet', 'hayır', 'ise', 'veya'
|
26 |
+
]
|
27 |
+
},
|
28 |
+
'en': {
|
29 |
+
'chars': 'abcdefghijklmnopqrstuvwxyz',
|
30 |
+
'unique_chars': 'qwxz',
|
31 |
+
'common_words': [
|
32 |
+
'the', 'and', 'a', 'to', 'of', 'in', 'is', 'you', 'that', 'it',
|
33 |
+
'he', 'was', 'for', 'on', 'are', 'as', 'with', 'his', 'they',
|
34 |
+
'at', 'be', 'this', 'have', 'from', 'or', 'one', 'had', 'by',
|
35 |
+
'but', 'not', 'what', 'all', 'were', 'we', 'when', 'your', 'can',
|
36 |
+
'there', 'if', 'more', 'an', 'who'
|
37 |
+
]
|
38 |
+
},
|
39 |
+
'de': {
|
40 |
+
'chars': 'abcdefghijklmnopqrstuvwxyzäöüß',
|
41 |
+
'unique_chars': 'äöüß',
|
42 |
+
'common_words': [
|
43 |
+
'der', 'die', 'das', 'und', 'in', 'zu', 'den', 'mit', 'auf', 'für',
|
44 |
+
'ist', 'im', 'dem', 'nicht', 'ein', 'eine', 'als', 'auch', 'es',
|
45 |
+
'von', 'sich', 'oder', 'so', 'zum', 'bei', 'eines', 'nur', 'am',
|
46 |
+
'werden', 'noch', 'wie', 'einer', 'aber', 'aus', 'wenn', 'doch'
|
47 |
+
]
|
48 |
+
},
|
49 |
+
'fr': {
|
50 |
+
'chars': 'abcdefghijklmnopqrstuvwxyzàâçéèêëîïôùûü',
|
51 |
+
'unique_chars': 'àâçéèêëîïôùûü',
|
52 |
+
'common_words': [
|
53 |
+
'le', 'la', 'les', 'de', 'des', 'un', 'une', 'et', 'est', 'en',
|
54 |
+
'du', 'dans', 'qui', 'que', 'pour', 'pas', 'sur', 'ce', 'vous',
|
55 |
+
'avec', 'au', 'il', 'je', 'sont', 'mais', 'nous', 'si', 'plus',
|
56 |
+
'leur', 'par', 'ont', 'ou', 'comme', 'elle', 'tout', 'même'
|
57 |
+
]
|
58 |
+
},
|
59 |
+
'es': {
|
60 |
+
'chars': 'abcdefghijklmnopqrstuvwxyzáéíóúüñ',
|
61 |
+
'unique_chars': 'áéíóúüñ',
|
62 |
+
'common_words': [
|
63 |
+
'el', 'la', 'los', 'las', 'de', 'del', 'un', 'una', 'unos', 'unas',
|
64 |
+
'y', 'e', 'o', 'u', 'que', 'en', 'a', 'con', 'por', 'para', 'es',
|
65 |
+
'son', 'al', 'lo', 'su', 'sus', 'se', 'mi', 'me', 'te', 'nos',
|
66 |
+
'como', 'pero', 'más', 'este', 'esta', 'esto'
|
67 |
+
]
|
68 |
+
}
|
69 |
+
}
|
70 |
+
|
71 |
+
# Desteklenen diller
|
72 |
+
self.supported_languages = {
|
73 |
+
'tr': 'Türkçe',
|
74 |
+
'en': 'İngilizce',
|
75 |
+
'de': 'Almanca',
|
76 |
+
'fr': 'Fransızca',
|
77 |
+
'es': 'İspanyolca',
|
78 |
+
'unknown': 'Bilinmeyen'
|
79 |
+
}
|
80 |
+
|
81 |
+
logger.info("Dil algılama modülü başlatıldı")
|
82 |
+
|
83 |
+
def _clean_text(self, text):
|
84 |
+
"""
|
85 |
+
Metni temizler
|
86 |
+
|
87 |
+
Args:
|
88 |
+
text: Temizlenecek metin
|
89 |
+
|
90 |
+
Returns:
|
91 |
+
str: Temizlenmiş metin
|
92 |
+
"""
|
93 |
+
if not text:
|
94 |
+
return ""
|
95 |
+
|
96 |
+
# Küçük harfe çevir
|
97 |
+
text = text.lower()
|
98 |
+
|
99 |
+
# Sayıları ve özel karakterleri kaldır (dil karakterleri hariç)
|
100 |
+
text = re.sub(r'[0-9]', '', text)
|
101 |
+
text = re.sub(r'[^\w\s\u00C0-\u00FF\u0100-\u017F\u0400-\u04FF]', '', text)
|
102 |
+
|
103 |
+
return text
|
104 |
+
|
105 |
+
def _get_words(self, text):
|
106 |
+
"""
|
107 |
+
Metinden kelimeleri çıkarır
|
108 |
+
|
109 |
+
Args:
|
110 |
+
text: Kelimesi çıkarılacak metin
|
111 |
+
|
112 |
+
Returns:
|
113 |
+
list: Kelimeler listesi
|
114 |
+
"""
|
115 |
+
words = re.findall(r'\b\w+\b', text.lower())
|
116 |
+
return words
|
117 |
+
|
118 |
+
def _calculate_character_score(self, text, language):
|
119 |
+
"""
|
120 |
+
Metindeki karakterlerin dile uygunluğunu hesaplar
|
121 |
+
|
122 |
+
Args:
|
123 |
+
text: Değerlendirilecek metin
|
124 |
+
language: Dil kodu
|
125 |
+
|
126 |
+
Returns:
|
127 |
+
float: Karakter skoru (0-1 arası)
|
128 |
+
"""
|
129 |
+
if not text:
|
130 |
+
return 0.0
|
131 |
+
|
132 |
+
profile = self.language_profiles.get(language, {})
|
133 |
+
chars = profile.get('chars', '')
|
134 |
+
unique_chars = profile.get('unique_chars', '')
|
135 |
+
|
136 |
+
# Metin içindeki karakterleri say
|
137 |
+
char_count = Counter(text.lower())
|
138 |
+
|
139 |
+
# Dildeki karakterlerin metinde bulunma oranı
|
140 |
+
total_chars = sum(char_count.values())
|
141 |
+
if total_chars == 0:
|
142 |
+
return 0.0
|
143 |
+
|
144 |
+
matched_chars = sum(char_count.get(char, 0) for char in chars)
|
145 |
+
char_ratio = matched_chars / total_chars
|
146 |
+
|
147 |
+
# Dile özgü karakterlerin varlığını kontrol et
|
148 |
+
unique_char_present = any(char in text.lower() for char in unique_chars)
|
149 |
+
unique_bonus = 0.2 if unique_char_present else 0.0
|
150 |
+
|
151 |
+
return min(1.0, char_ratio + unique_bonus)
|
152 |
+
|
153 |
+
def _calculate_word_score(self, words, language):
|
154 |
+
"""
|
155 |
+
Kelimelerin dile uygunluğunu hesaplar
|
156 |
+
|
157 |
+
Args:
|
158 |
+
words: Değerlendirilecek kelimeler listesi
|
159 |
+
language: Dil kodu
|
160 |
+
|
161 |
+
Returns:
|
162 |
+
float: Kelime skoru (0-1 arası)
|
163 |
+
"""
|
164 |
+
if not words:
|
165 |
+
return 0.0
|
166 |
+
|
167 |
+
common_words = self.language_profiles.get(language, {}).get('common_words', [])
|
168 |
+
|
169 |
+
# Yaygın kelimelerin metinde bulunma sayısı
|
170 |
+
matched_words = sum(1 for word in words if word in common_words)
|
171 |
+
|
172 |
+
# Yaygın kelime oranı
|
173 |
+
word_ratio = matched_words / min(len(words), 100) # En fazla 100 kelime değerlendir
|
174 |
+
|
175 |
+
return word_ratio
|
176 |
+
|
177 |
+
def detect_language(self, text):
|
178 |
+
"""
|
179 |
+
Metnin dilini tespit eder
|
180 |
+
|
181 |
+
Args:
|
182 |
+
text: Dili tespit edilecek metin
|
183 |
+
|
184 |
+
Returns:
|
185 |
+
dict: {
|
186 |
+
'language_code': dil kodu (tr, en, vb),
|
187 |
+
'language_name': dil adı,
|
188 |
+
'confidence': güven skoru (0-1 arası),
|
189 |
+
'scores': dil bazında skorlar
|
190 |
+
}
|
191 |
+
"""
|
192 |
+
if not text or len(text.strip()) < 5:
|
193 |
+
return {
|
194 |
+
'language_code': 'unknown',
|
195 |
+
'language_name': self.supported_languages.get('unknown'),
|
196 |
+
'confidence': 0.0,
|
197 |
+
'scores': {}
|
198 |
+
}
|
199 |
+
|
200 |
+
try:
|
201 |
+
clean_text = self._clean_text(text)
|
202 |
+
words = self._get_words(clean_text)
|
203 |
+
|
204 |
+
scores = {}
|
205 |
+
|
206 |
+
# Her dil için skor hesapla
|
207 |
+
for lang_code in self.language_profiles.keys():
|
208 |
+
char_score = self._calculate_character_score(clean_text, lang_code)
|
209 |
+
word_score = self._calculate_word_score(words, lang_code)
|
210 |
+
|
211 |
+
# Ağırlıklı toplam (karakter:0.4, kelime:0.6)
|
212 |
+
total_score = (char_score * 0.4) + (word_score * 0.6)
|
213 |
+
scores[lang_code] = total_score
|
214 |
+
|
215 |
+
# En yüksek skorlu dili bul
|
216 |
+
if not scores:
|
217 |
+
detected_lang = 'unknown'
|
218 |
+
confidence = 0.0
|
219 |
+
else:
|
220 |
+
detected_lang = max(scores, key=scores.get)
|
221 |
+
confidence = scores[detected_lang]
|
222 |
+
|
223 |
+
# Eğer güven skoru çok düşükse "bilinmeyen" olarak işaretle
|
224 |
+
if confidence < 0.15:
|
225 |
+
detected_lang = 'unknown'
|
226 |
+
confidence = 0.0
|
227 |
+
|
228 |
+
return {
|
229 |
+
'language_code': detected_lang,
|
230 |
+
'language_name': self.supported_languages.get(detected_lang, self.supported_languages.get('unknown')),
|
231 |
+
'confidence': confidence,
|
232 |
+
'scores': scores
|
233 |
+
}
|
234 |
+
|
235 |
+
except Exception as e:
|
236 |
+
logger.error(f"Dil algılama hatası: {str(e)}")
|
237 |
+
return {
|
238 |
+
'language_code': 'unknown',
|
239 |
+
'language_name': self.supported_languages.get('unknown'),
|
240 |
+
'confidence': 0.0,
|
241 |
+
'scores': {}
|
242 |
+
}
|
243 |
+
|
244 |
+
def detect_languages_batch(self, texts):
|
245 |
+
"""
|
246 |
+
Birden çok metnin dilini tespit eder
|
247 |
+
|
248 |
+
Args:
|
249 |
+
texts: Dilleri tespit edilecek metinler listesi
|
250 |
+
|
251 |
+
Returns:
|
252 |
+
list: Her metin için dil tespiti sonuçları
|
253 |
+
"""
|
254 |
+
results = []
|
255 |
+
|
256 |
+
for text in texts:
|
257 |
+
result = self.detect_language(text)
|
258 |
+
results.append(result)
|
259 |
+
|
260 |
+
return results
|
utils/quality_scorer.py
ADDED
@@ -0,0 +1,198 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import numpy as np
|
2 |
+
from transformers import pipeline
|
3 |
+
import torch
|
4 |
+
import logging
|
5 |
+
import re
|
6 |
+
from sklearn.feature_extraction.text import CountVectorizer
|
7 |
+
|
8 |
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
9 |
+
|
10 |
+
|
11 |
+
class QualityScorer:
|
12 |
+
def __init__(self, quality_pipeline=None):
|
13 |
+
"""
|
14 |
+
Metin kalitesi değerlendirme sınıfını başlatır.
|
15 |
+
|
16 |
+
Args:
|
17 |
+
quality_pipeline: Metin özetleme pipeline'ı
|
18 |
+
"""
|
19 |
+
self.pipeline = quality_pipeline
|
20 |
+
self.device = "cuda" if torch.cuda.is_available() else "cpu"
|
21 |
+
|
22 |
+
# Temel kalite ölçütleri için yardımcı araçlar
|
23 |
+
self.vectorizer = CountVectorizer(max_features=5000)
|
24 |
+
|
25 |
+
# Türkçeye özgü doldurma (filler) kelimeleri
|
26 |
+
self.turkish_filler_words = [
|
27 |
+
'yani', 'işte', 'şey', 'falan', 'filan', 'hani', 'mesela', 'aslında',
|
28 |
+
'ya', 'ki', 'de', 'da', 'çok', 'ama', 'fakat', 'lakin', 'ancak',
|
29 |
+
'gerçekten', 'kesinlikle', 'tabii', 'tabi', 'şimdi', 'sonra', 'önce'
|
30 |
+
]
|
31 |
+
|
32 |
+
def score_text(self, text):
|
33 |
+
"""
|
34 |
+
Metin için kalite skoru hesaplar.
|
35 |
+
|
36 |
+
Args:
|
37 |
+
text: Değerlendirilecek metin
|
38 |
+
|
39 |
+
Returns:
|
40 |
+
float: 0 ile 1 arasında kalite skoru (1 = yüksek kalite)
|
41 |
+
"""
|
42 |
+
if not text or len(text.strip()) == 0:
|
43 |
+
return 0.0, {}
|
44 |
+
|
45 |
+
# Metni temizle
|
46 |
+
text = text.strip()
|
47 |
+
|
48 |
+
# Çeşitli metin özelliklerini değerlendirelim
|
49 |
+
features = {}
|
50 |
+
|
51 |
+
# 1. Uzunluk puanı - Çok kısa veya çok uzun metinler düşük puan alır
|
52 |
+
length = len(text.split())
|
53 |
+
if length < 3:
|
54 |
+
features['length_score'] = 0.1
|
55 |
+
elif length < 5:
|
56 |
+
features['length_score'] = 0.2
|
57 |
+
elif length < 10:
|
58 |
+
features['length_score'] = 0.4
|
59 |
+
elif length < 20:
|
60 |
+
features['length_score'] = 0.6
|
61 |
+
elif length < 100:
|
62 |
+
features['length_score'] = 0.8
|
63 |
+
elif length < 500:
|
64 |
+
features['length_score'] = 1.0
|
65 |
+
elif length < 1000:
|
66 |
+
features['length_score'] = 0.8
|
67 |
+
else:
|
68 |
+
features['length_score'] = 0.6
|
69 |
+
|
70 |
+
# 2. Gramer ve yazım denetimi (Türkçe için uyarlanmış)
|
71 |
+
# Türkçede noktalama işaretleri ve büyük harf kullanımı
|
72 |
+
sentences = re.split(r'[.!?]+', text)
|
73 |
+
sentences = [s.strip() for s in sentences if s.strip()]
|
74 |
+
|
75 |
+
if not sentences:
|
76 |
+
features['grammar_score'] = 0.0
|
77 |
+
else:
|
78 |
+
# Cümlelerin büyük harfle başlayıp başlamadığını kontrol et
|
79 |
+
correct_caps = sum(1 for s in sentences if s and s[0].isupper())
|
80 |
+
caps_ratio = correct_caps / len(sentences) if sentences else 0
|
81 |
+
|
82 |
+
# Noktalama işaretlerinin varlığını kontrol et
|
83 |
+
punct_count = len(re.findall(r'[.!?,;:]', text))
|
84 |
+
expected_punct = max(1, len(sentences) - 1) # Beklenen minimum noktalama
|
85 |
+
punct_ratio = min(1.0, punct_count / expected_punct) if expected_punct > 0 else 0
|
86 |
+
|
87 |
+
# Türkçe'ye özgü yaygın yazım hatalarını kontrol et
|
88 |
+
common_errors = [
|
89 |
+
('de da', 'de/da ayrı yazılmalı'),
|
90 |
+
('ki', 'ki bağlacı ayrı yazılmalı'),
|
91 |
+
('misin', 'soru eki ayrı yazılmalı'),
|
92 |
+
('geldimi', 'soru eki ayrı yazılmalı'),
|
93 |
+
('bişey', 'bir şey ayrı yazılmalı'),
|
94 |
+
('herşey', 'her şey ayrı yazılmalı'),
|
95 |
+
('hiçbirşey', 'hiçbir şey ayrı yazılmalı')
|
96 |
+
]
|
97 |
+
|
98 |
+
error_count = sum(1 for error, _ in common_errors if error in text.lower())
|
99 |
+
error_ratio = 1.0 - min(1.0, error_count / (len(text.split()) / 10 + 1))
|
100 |
+
|
101 |
+
# Gramer puanını hesapla
|
102 |
+
features['grammar_score'] = (caps_ratio * 0.4 + punct_ratio * 0.3 + error_ratio * 0.3)
|
103 |
+
|
104 |
+
# 3. Kelime çeşitliliği (Türkçe için uyarlanmış)
|
105 |
+
words = re.findall(r'\b\w+\b', text.lower())
|
106 |
+
unique_words = set(words)
|
107 |
+
if not words:
|
108 |
+
features['diversity_score'] = 0.0
|
109 |
+
else:
|
110 |
+
# Türkçe metinler için çeşitlilik oranını ayarla
|
111 |
+
diversity_ratio = len(unique_words) / len(words)
|
112 |
+
# Türkçe'nin çekimli yapısı nedeniyle daha yüksek bir baz çeşitlilik beklenir
|
113 |
+
features['diversity_score'] = min(1.0, diversity_ratio * 1.2)
|
114 |
+
|
115 |
+
# 4. Özetlenebilirlik puanı - eğer pipeline varsa
|
116 |
+
if self.pipeline and len(text.split()) > 20:
|
117 |
+
try:
|
118 |
+
# Pipeline özetleme mi yoksa çeviri mi ona göre işle
|
119 |
+
if hasattr(self.pipeline, 'task') and self.pipeline.task == 'translation':
|
120 |
+
# Çeviri pipeline'ı, bu durumda çevirinin kalitesine bakma
|
121 |
+
translated = self.pipeline(text, max_length=100)[0]['translation_text']
|
122 |
+
features['summary_score'] = 0.7 # Varsayılan olarak iyi bir puan
|
123 |
+
else:
|
124 |
+
# Özetleme pipeline'ı
|
125 |
+
max_length = min(128, max(30, len(text.split()) // 4))
|
126 |
+
summary = self.pipeline(text, max_length=max_length, min_length=10, do_sample=False)[0][
|
127 |
+
'generated_text']
|
128 |
+
|
129 |
+
# Özet ve orijinal metin arasındaki benzerliğe bakarak puan ver
|
130 |
+
summary_len = len(summary.split())
|
131 |
+
orig_len = len(text.split())
|
132 |
+
|
133 |
+
compression_ratio = summary_len / orig_len if orig_len > 0 else 0
|
134 |
+
if compression_ratio > 0.8 or compression_ratio < 0.05:
|
135 |
+
features['summary_score'] = 0.3
|
136 |
+
elif compression_ratio > 0.6 or compression_ratio < 0.1:
|
137 |
+
features['summary_score'] = 0.6
|
138 |
+
else:
|
139 |
+
features['summary_score'] = 0.9
|
140 |
+
except Exception as e:
|
141 |
+
logging.warning(f"Error during summarization: {str(e)}")
|
142 |
+
features['summary_score'] = 0.5
|
143 |
+
else:
|
144 |
+
features['summary_score'] = 0.5
|
145 |
+
|
146 |
+
# 5. Türkçe doldurma kelimeleri (filler words)
|
147 |
+
filler_count = sum(1 for word in words if word.lower() in self.turkish_filler_words)
|
148 |
+
if not words:
|
149 |
+
features['filler_score'] = 1.0
|
150 |
+
else:
|
151 |
+
filler_ratio = filler_count / len(words)
|
152 |
+
# Türkçede bazı doldurma kelimeleri doğal olabilir, bu yüzden daha toleranslı davran
|
153 |
+
features['filler_score'] = 1.0 - min(1.0, filler_ratio * 3)
|
154 |
+
|
155 |
+
# Puanları birleştir - farklı puanları ağırlıklandırarak
|
156 |
+
weights = {
|
157 |
+
'length_score': 0.15,
|
158 |
+
'grammar_score': 0.3, # Türkçe için gramere daha fazla ağırlık
|
159 |
+
'diversity_score': 0.25,
|
160 |
+
'summary_score': 0.2,
|
161 |
+
'filler_score': 0.1
|
162 |
+
}
|
163 |
+
|
164 |
+
final_score = sum(features[key] * weights[key] for key in weights.keys())
|
165 |
+
|
166 |
+
# Puanı 0-1 aralığına normalize et
|
167 |
+
final_score = max(0.0, min(1.0, final_score))
|
168 |
+
|
169 |
+
return final_score, features
|
170 |
+
|
171 |
+
def batch_score(self, texts, batch_size=8):
|
172 |
+
"""
|
173 |
+
Bir metin listesi için toplu kalite skoru hesaplar.
|
174 |
+
|
175 |
+
Args:
|
176 |
+
texts: Değerlendirilecek metin listesi
|
177 |
+
batch_size: İşlenecek grup boyutu
|
178 |
+
|
179 |
+
Returns:
|
180 |
+
list: Kalite skorları listesi
|
181 |
+
"""
|
182 |
+
results = []
|
183 |
+
feature_results = []
|
184 |
+
|
185 |
+
for i in range(0, len(texts), batch_size):
|
186 |
+
batch_texts = texts[i:i + batch_size]
|
187 |
+
batch_results = []
|
188 |
+
batch_features = []
|
189 |
+
|
190 |
+
for text in batch_texts:
|
191 |
+
score, features = self.score_text(text)
|
192 |
+
batch_results.append(score)
|
193 |
+
batch_features.append(features)
|
194 |
+
|
195 |
+
results.extend(batch_results)
|
196 |
+
feature_results.extend(batch_features)
|
197 |
+
|
198 |
+
return results, feature_results
|
utils/sentiment_analyzer.py
ADDED
@@ -0,0 +1,169 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import torch
|
2 |
+
from transformers import AutoTokenizer, AutoModelForSequenceClassification
|
3 |
+
import numpy as np
|
4 |
+
import logging
|
5 |
+
|
6 |
+
logger = logging.getLogger(__name__)
|
7 |
+
|
8 |
+
|
9 |
+
class SentimentAnalyzer:
|
10 |
+
"""
|
11 |
+
Türkçe metinler için duygu analizi yapan sınıf.
|
12 |
+
Pozitif, negatif ve nötr duygu skorları üreterek metnin duygusal tonunu analiz eder.
|
13 |
+
"""
|
14 |
+
|
15 |
+
def __init__(self, model=None, tokenizer=None):
|
16 |
+
"""
|
17 |
+
Duygu analizi modülünü başlatır.
|
18 |
+
|
19 |
+
Args:
|
20 |
+
model: Duygu analizi modeli (isteğe bağlı)
|
21 |
+
tokenizer: Model için tokenizer (isteğe bağlı)
|
22 |
+
"""
|
23 |
+
self.model = model
|
24 |
+
self.tokenizer = tokenizer
|
25 |
+
self.device = "cuda" if torch.cuda.is_available() else "cpu"
|
26 |
+
self.model_info = {"name": "Bilinmeyen Model", "language": "unknown"}
|
27 |
+
|
28 |
+
if model is None or tokenizer is None:
|
29 |
+
logger.info("Duygu analizi için varsayılan model yükleniyor...")
|
30 |
+
self.load_default_model()
|
31 |
+
|
32 |
+
def load_default_model(self):
|
33 |
+
"""Varsayılan duygu analizi modelini yükler"""
|
34 |
+
try:
|
35 |
+
# Türkçe duygu analizi modeli
|
36 |
+
model_name = "savasy/bert-base-turkish-sentiment"
|
37 |
+
logger.info(f"Türkçe duygu analizi modeli yükleniyor: {model_name}")
|
38 |
+
|
39 |
+
self.tokenizer = AutoTokenizer.from_pretrained(model_name)
|
40 |
+
self.model = AutoModelForSequenceClassification.from_pretrained(model_name)
|
41 |
+
self.model.to(self.device)
|
42 |
+
|
43 |
+
self.model_info = {
|
44 |
+
"name": model_name,
|
45 |
+
"description": "Türkçe duygu analizi modeli",
|
46 |
+
"language": "tr"
|
47 |
+
}
|
48 |
+
|
49 |
+
logger.info("Duygu analizi modeli başarıyla yüklendi")
|
50 |
+
return True
|
51 |
+
except Exception as e:
|
52 |
+
logger.error(f"Duygu analizi modeli yüklenemedi: {str(e)}")
|
53 |
+
|
54 |
+
# Yedek model dene
|
55 |
+
try:
|
56 |
+
backup_model = "dbmdz/bert-base-turkish-cased"
|
57 |
+
logger.info(f"Yedek Türkçe model deneniyor: {backup_model}")
|
58 |
+
|
59 |
+
self.tokenizer = AutoTokenizer.from_pretrained(backup_model)
|
60 |
+
self.model = AutoModelForSequenceClassification.from_pretrained(backup_model)
|
61 |
+
self.model.to(self.device)
|
62 |
+
|
63 |
+
self.model_info = {
|
64 |
+
"name": backup_model,
|
65 |
+
"description": "Genel amaçlı Türkçe BERT modeli",
|
66 |
+
"language": "tr"
|
67 |
+
}
|
68 |
+
|
69 |
+
logger.info("Yedek model başarıyla yüklendi")
|
70 |
+
return True
|
71 |
+
except Exception as e2:
|
72 |
+
logger.error(f"Yedek model yüklenemedi: {str(e2)}")
|
73 |
+
raise e2
|
74 |
+
|
75 |
+
def analyze_sentiment(self, text):
|
76 |
+
"""
|
77 |
+
Metindeki duygu tonunu analiz eder.
|
78 |
+
|
79 |
+
Args:
|
80 |
+
text: Analiz edilecek metin
|
81 |
+
|
82 |
+
Returns:
|
83 |
+
dict: {
|
84 |
+
'positive': float, # Pozitif duygu skoru (0-1)
|
85 |
+
'neutral': float, # Nötr duygu skoru (0-1)
|
86 |
+
'negative': float, # Negatif duygu skoru (0-1)
|
87 |
+
'dominant': str # Baskın duygu (positive, neutral, negative)
|
88 |
+
'score': float # -1 (çok negatif) ile 1 (çok pozitif) arasında genel skor
|
89 |
+
}
|
90 |
+
"""
|
91 |
+
if not text or len(text.strip()) == 0:
|
92 |
+
return {
|
93 |
+
'positive': 0.0,
|
94 |
+
'neutral': 1.0,
|
95 |
+
'negative': 0.0,
|
96 |
+
'dominant': 'neutral',
|
97 |
+
'score': 0.0
|
98 |
+
}
|
99 |
+
|
100 |
+
try:
|
101 |
+
# Metni tokenize et
|
102 |
+
inputs = self.tokenizer(text, return_tensors="pt", truncation=True, max_length=512)
|
103 |
+
inputs = {key: val.to(self.device) for key, val in inputs.items()}
|
104 |
+
|
105 |
+
# Tahmin yap
|
106 |
+
with torch.no_grad():
|
107 |
+
outputs = self.model(**inputs)
|
108 |
+
|
109 |
+
# Sonuçları işle
|
110 |
+
logits = outputs.logits
|
111 |
+
probabilities = torch.softmax(logits, dim=1).cpu().numpy()[0]
|
112 |
+
|
113 |
+
# Model çıktısının formatına göre işlem yap
|
114 |
+
if len(probabilities) >= 3:
|
115 |
+
# 3 sınıflı model (negatif, nötr, pozitif)
|
116 |
+
result = {
|
117 |
+
'negative': float(probabilities[0]),
|
118 |
+
'neutral': float(probabilities[1]),
|
119 |
+
'positive': float(probabilities[2])
|
120 |
+
}
|
121 |
+
else:
|
122 |
+
# 2 sınıflı model (negatif, pozitif)
|
123 |
+
result = {
|
124 |
+
'negative': float(probabilities[0]),
|
125 |
+
'neutral': 0.0,
|
126 |
+
'positive': float(probabilities[1])
|
127 |
+
}
|
128 |
+
|
129 |
+
# Baskın duyguyu belirle
|
130 |
+
dominant_sentiment = max(result, key=result.get)
|
131 |
+
result['dominant'] = dominant_sentiment
|
132 |
+
|
133 |
+
# -1 ile 1 arasında genel bir skor hesapla
|
134 |
+
# -1: çok negatif, 0: nötr, 1: çok pozitif
|
135 |
+
weighted_score = result['positive'] - result['negative']
|
136 |
+
result['score'] = float(weighted_score)
|
137 |
+
|
138 |
+
return result
|
139 |
+
|
140 |
+
except Exception as e:
|
141 |
+
logger.error(f"Duygu analizi sırasında hata: {str(e)}")
|
142 |
+
# Hata durumunda nötr sonuç döndür
|
143 |
+
return {
|
144 |
+
'positive': 0.0,
|
145 |
+
'neutral': 1.0,
|
146 |
+
'negative': 0.0,
|
147 |
+
'dominant': 'neutral',
|
148 |
+
'score': 0.0
|
149 |
+
}
|
150 |
+
|
151 |
+
def batch_analyze(self, texts, batch_size=8):
|
152 |
+
"""
|
153 |
+
Bir metin listesi için toplu duygu analizi yapar.
|
154 |
+
|
155 |
+
Args:
|
156 |
+
texts: Analiz edilecek metin listesi
|
157 |
+
batch_size: İşlenecek grup boyutu
|
158 |
+
|
159 |
+
Returns:
|
160 |
+
list: Her metin için duygu analizi sonuçları
|
161 |
+
"""
|
162 |
+
results = []
|
163 |
+
|
164 |
+
for i in range(0, len(texts), batch_size):
|
165 |
+
batch_texts = texts[i:i + batch_size]
|
166 |
+
batch_results = [self.analyze_sentiment(text) for text in batch_texts]
|
167 |
+
results.extend(batch_results)
|
168 |
+
|
169 |
+
return results
|
utils/text_improver.py
ADDED
@@ -0,0 +1,337 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import re
|
2 |
+
import logging
|
3 |
+
import string
|
4 |
+
from collections import Counter
|
5 |
+
|
6 |
+
logger = logging.getLogger(__name__)
|
7 |
+
|
8 |
+
|
9 |
+
class TextImprover:
|
10 |
+
"""
|
11 |
+
Türkçe metinlerde iyileştirme ve öneriler sunan sınıf.
|
12 |
+
Yazım hataları düzeltme, dilbilgisi önerileri ve okunabilirlik analizi yapar.
|
13 |
+
"""
|
14 |
+
|
15 |
+
def __init__(self):
|
16 |
+
"""Metin iyileştirme sınıfını başlatır"""
|
17 |
+
# Türkçe'de yaygın yazım hataları ve düzeltmeleri
|
18 |
+
self.common_typos = {
|
19 |
+
# Büyük küçük harf duyarsız olarak yazım hataları
|
20 |
+
'bişey': 'bir şey',
|
21 |
+
'herşey': 'her şey',
|
22 |
+
'hiçbirşey': 'hiçbir şey',
|
23 |
+
'birsey': 'bir şey',
|
24 |
+
'hersey': 'her şey',
|
25 |
+
'hicbir': 'hiçbir',
|
26 |
+
'hicbirsey': 'hiçbir şey',
|
27 |
+
'yalnız': 'yalnız',
|
28 |
+
'bi': 'bir',
|
29 |
+
'gelicek': 'gelecek',
|
30 |
+
'gidiyom': 'gidiyorum',
|
31 |
+
'yapıyom': 'yapıyorum',
|
32 |
+
'biliyomusun': 'biliyor musun',
|
33 |
+
'napıyorsun': 'ne yapıyorsun',
|
34 |
+
'naber': 'ne haber',
|
35 |
+
'bilmiyomki': 'bilmiyorum ki',
|
36 |
+
'dicek': 'diyecek',
|
37 |
+
'dicem': 'diyeceğim',
|
38 |
+
'yicek': 'yiyecek',
|
39 |
+
'yicem': 'yiyeceğim'
|
40 |
+
}
|
41 |
+
|
42 |
+
# Türkçe'de sık kullanılan doldurma kelimeleri
|
43 |
+
self.filler_words = [
|
44 |
+
'yani', 'işte', 'şey', 'falan', 'filan', 'hani', 'mesela',
|
45 |
+
'aslında', 'ya', 'ki', 'de', 'da', 'ama', 'fakat', 'lakin',
|
46 |
+
'gerçekten', 'kesinlikle', 'tabii', 'tabi', 'şimdi', 'sonra'
|
47 |
+
]
|
48 |
+
|
49 |
+
# Türkçe cümle karmaşıklığını değerlendirmek için parametreler
|
50 |
+
self.max_sentence_length = 25 # Kelime sayısı
|
51 |
+
self.max_word_length = 6 # Ortalama kelime uzunluğu
|
52 |
+
|
53 |
+
# Okunabilirlik için kullanılacak parametreler
|
54 |
+
# Türkçe için uyarlanmış Flesch Reading Ease formülü
|
55 |
+
self.readability_thresholds = {
|
56 |
+
'çok_kolay': 90,
|
57 |
+
'kolay': 80,
|
58 |
+
'orta_kolay': 70,
|
59 |
+
'orta': 60,
|
60 |
+
'orta_zor': 50,
|
61 |
+
'zor': 30,
|
62 |
+
'çok_zor': 0
|
63 |
+
}
|
64 |
+
|
65 |
+
logger.info("Metin iyileştirme modülü başlatıldı")
|
66 |
+
|
67 |
+
def fix_typos(self, text):
|
68 |
+
"""
|
69 |
+
Metindeki yaygın yazım hatalarını düzeltir
|
70 |
+
|
71 |
+
Args:
|
72 |
+
text: Düzeltilecek metin
|
73 |
+
|
74 |
+
Returns:
|
75 |
+
dict: {
|
76 |
+
'corrected_text': str, # Düzeltilmiş metin
|
77 |
+
'corrections': list, # Yapılan düzeltmeler listesi
|
78 |
+
'correction_count': int # Düzeltme sayısı
|
79 |
+
}
|
80 |
+
"""
|
81 |
+
corrected_text = text
|
82 |
+
corrections = []
|
83 |
+
|
84 |
+
# Önce metni kelimelere ayır
|
85 |
+
words = re.findall(r'\b\w+\b', text.lower())
|
86 |
+
|
87 |
+
# Her kelimeyi kontrol et
|
88 |
+
for word in words:
|
89 |
+
if word.lower() in self.common_typos:
|
90 |
+
correct_word = self.common_typos[word.lower()]
|
91 |
+
# Kelimenin metindeki tüm örneklerini düzelt
|
92 |
+
# \b ile kelime sınırlarını belirt
|
93 |
+
pattern = r'\b' + re.escape(word) + r'\b'
|
94 |
+
corrected_text = re.sub(pattern, correct_word, corrected_text, flags=re.IGNORECASE)
|
95 |
+
corrections.append(f"'{word}' -> '{correct_word}'")
|
96 |
+
|
97 |
+
return {
|
98 |
+
'corrected_text': corrected_text,
|
99 |
+
'corrections': corrections,
|
100 |
+
'correction_count': len(corrections)
|
101 |
+
}
|
102 |
+
|
103 |
+
def check_grammar(self, text):
|
104 |
+
"""
|
105 |
+
Metindeki temel dilbilgisi sorunlarını kontrol eder
|
106 |
+
|
107 |
+
Args:
|
108 |
+
text: Kontrol edilecek metin
|
109 |
+
|
110 |
+
Returns:
|
111 |
+
dict: {
|
112 |
+
'issues': list, # Tespit edilen sorunlar listesi
|
113 |
+
'suggestions': list, # Öneriler listesi
|
114 |
+
'issue_count': int # Sorun sayısı
|
115 |
+
}
|
116 |
+
"""
|
117 |
+
issues = []
|
118 |
+
suggestions = []
|
119 |
+
|
120 |
+
# Cümlelere ayır
|
121 |
+
sentences = re.split(r'[.!?]+', text)
|
122 |
+
sentences = [s.strip() for s in sentences if s.strip()]
|
123 |
+
|
124 |
+
for i, sentence in enumerate(sentences):
|
125 |
+
# Büyük harfle başlama kontrolü
|
126 |
+
if sentence and not sentence[0].isupper():
|
127 |
+
issues.append(f"Cümle {i + 1}: Büyük harfle başlamıyor")
|
128 |
+
suggestions.append(f"Cümle {i + 1}: '{sentence[0]}' -> '{sentence[0].upper()}'")
|
129 |
+
|
130 |
+
# Cümle uzunluğu kontrolü
|
131 |
+
words = sentence.split()
|
132 |
+
if len(words) > self.max_sentence_length:
|
133 |
+
issues.append(f"Cümle {i + 1}: Çok uzun ({len(words)} kelime)")
|
134 |
+
suggestions.append(f"Cümle {i + 1}: Daha kısa cümlelere bölmeyi düşünün")
|
135 |
+
|
136 |
+
# Noktalama kontrolü
|
137 |
+
if i < len(sentences) - 1: # Son cümle değilse
|
138 |
+
if not text.find(sentence + ".") and not text.find(sentence + "!") and not text.find(sentence + "?"):
|
139 |
+
issues.append(f"Cümle {i + 1}: Noktalama işareti eksik olabilir")
|
140 |
+
suggestions.append(f"Cümle {i + 1}: Cümle sonuna uygun noktalama işareti ekleyin")
|
141 |
+
|
142 |
+
return {
|
143 |
+
'issues': issues,
|
144 |
+
'suggestions': suggestions,
|
145 |
+
'issue_count': len(issues)
|
146 |
+
}
|
147 |
+
|
148 |
+
def reduce_filler_words(self, text):
|
149 |
+
"""
|
150 |
+
Metindeki doldurma kelimelerini tespit eder ve azaltma önerileri sunar
|
151 |
+
|
152 |
+
Args:
|
153 |
+
text: İyileştirilecek metin
|
154 |
+
|
155 |
+
Returns:
|
156 |
+
dict: {
|
157 |
+
'filler_words': list, # Bulunan doldurma kelimeleri
|
158 |
+
'filler_count': int, # Doldurma kelimesi sayısı
|
159 |
+
'suggested_text': str # Önerilen iyileştirilmiş metin
|
160 |
+
}
|
161 |
+
"""
|
162 |
+
# Metindeki kelimeleri bul
|
163 |
+
words = re.findall(r'\b\w+\b', text.lower())
|
164 |
+
|
165 |
+
# Doldurma kelimelerini ve sayılarını say
|
166 |
+
filler_counter = Counter()
|
167 |
+
for word in words:
|
168 |
+
if word.lower() in self.filler_words:
|
169 |
+
filler_counter[word.lower()] += 1
|
170 |
+
|
171 |
+
# Metni kelime kelime işle ve fazla doldurma kelimelerini kaldır
|
172 |
+
suggested_text = text
|
173 |
+
for filler_word, count in filler_counter.items():
|
174 |
+
if count > 1: # Birden fazla geçiyorsa
|
175 |
+
# Her bir örneği bul
|
176 |
+
occurrences = list(re.finditer(r'\b' + re.escape(filler_word) + r'\b', suggested_text, re.IGNORECASE))
|
177 |
+
|
178 |
+
# İlk geçtiği yer hariç diğerlerini kaldır
|
179 |
+
for occurrence in occurrences[1:]:
|
180 |
+
start, end = occurrence.span()
|
181 |
+
# Eğer kelimenin önünde veya arkasında boşluk varsa, onu da kaldır
|
182 |
+
if start > 0 and suggested_text[start - 1] == ' ':
|
183 |
+
start -= 1
|
184 |
+
suggested_text = suggested_text[:start] + suggested_text[end:]
|
185 |
+
|
186 |
+
return {
|
187 |
+
'filler_words': list(filler_counter.keys()),
|
188 |
+
'filler_count': sum(filler_counter.values()),
|
189 |
+
'suggested_text': suggested_text
|
190 |
+
}
|
191 |
+
|
192 |
+
def calculate_readability(self, text):
|
193 |
+
"""
|
194 |
+
Metnin okunabilirlik skorunu hesaplar (Türkçe'ye uyarlanmış Flesch Reading Ease)
|
195 |
+
|
196 |
+
Args:
|
197 |
+
text: Değerlendirilecek metin
|
198 |
+
|
199 |
+
Returns:
|
200 |
+
dict: {
|
201 |
+
'score': float, # Okunabilirlik skoru (0-100)
|
202 |
+
'level': str, # Okunabilirlik seviyesi
|
203 |
+
'avg_sentence_length': float, # Ortalama cümle uzunluğu
|
204 |
+
'avg_word_length': float # Ortalama kelime uzunluğu
|
205 |
+
}
|
206 |
+
"""
|
207 |
+
# Cümleleri ve kelimeleri ayır
|
208 |
+
sentences = re.split(r'[.!?]+', text)
|
209 |
+
sentences = [s.strip() for s in sentences if s.strip()]
|
210 |
+
|
211 |
+
total_words = 0
|
212 |
+
total_syllables = 0
|
213 |
+
|
214 |
+
for sentence in sentences:
|
215 |
+
words = re.findall(r'\b\w+\b', sentence)
|
216 |
+
total_words += len(words)
|
217 |
+
|
218 |
+
# Türkçe heceleri kabaca hesapla (sesli harf sayısı)
|
219 |
+
for word in words:
|
220 |
+
# Türkçedeki sesli harfler
|
221 |
+
vowels = 'aeıioöuüAEIİOÖUÜ'
|
222 |
+
syllable_count = sum(1 for char in word if char in vowels)
|
223 |
+
# En az bir hece olmalı
|
224 |
+
syllable_count = max(1, syllable_count)
|
225 |
+
total_syllables += syllable_count
|
226 |
+
|
227 |
+
# Hesaplamalar
|
228 |
+
if len(sentences) == 0 or total_words == 0:
|
229 |
+
return {
|
230 |
+
'score': 100, # Boş metin - en kolay
|
231 |
+
'level': 'çok_kolay',
|
232 |
+
'avg_sentence_length': 0,
|
233 |
+
'avg_word_length': 0
|
234 |
+
}
|
235 |
+
|
236 |
+
avg_sentence_length = total_words / len(sentences)
|
237 |
+
avg_syllables_per_word = total_syllables / total_words
|
238 |
+
|
239 |
+
# Türkçe için uyarlanmış Flesch Reading Ease
|
240 |
+
# (orijinal formül: 206.835 - 1.015 * ASL - 84.6 * ASW)
|
241 |
+
# Türkçe için katsayılar ayarlandı
|
242 |
+
readability_score = 206.835 - (1.3 * avg_sentence_length) - (60.0 * avg_syllables_per_word)
|
243 |
+
|
244 |
+
# Skoru 0-100 aralığına sınırla
|
245 |
+
readability_score = max(0, min(100, readability_score))
|
246 |
+
|
247 |
+
# Seviyeyi belirle
|
248 |
+
level = 'çok_zor'
|
249 |
+
for threshold_level, threshold_value in sorted(self.readability_thresholds.items(), key=lambda x: x[1]):
|
250 |
+
if readability_score >= threshold_value:
|
251 |
+
level = threshold_level
|
252 |
+
break
|
253 |
+
|
254 |
+
return {
|
255 |
+
'score': float(readability_score),
|
256 |
+
'level': level,
|
257 |
+
'avg_sentence_length': float(avg_sentence_length),
|
258 |
+
'avg_word_length': float(avg_syllables_per_word)
|
259 |
+
}
|
260 |
+
|
261 |
+
def improve_text(self, text):
|
262 |
+
"""
|
263 |
+
Metni kapsamlı şekilde analiz eder ve iyileştirme önerileri sunar
|
264 |
+
|
265 |
+
Args:
|
266 |
+
text: İyileştirilecek metin
|
267 |
+
|
268 |
+
Returns:
|
269 |
+
dict: Tüm iyileştirme analizlerini içeren sonuçlar
|
270 |
+
"""
|
271 |
+
if not text or len(text.strip()) == 0:
|
272 |
+
return {
|
273 |
+
'corrected_text': text,
|
274 |
+
'suggestions': [],
|
275 |
+
'readability': {
|
276 |
+
'score': 100,
|
277 |
+
'level': 'çok_kolay'
|
278 |
+
},
|
279 |
+
'improvement_count': 0
|
280 |
+
}
|
281 |
+
|
282 |
+
try:
|
283 |
+
# Yazım hatalarını düzelt
|
284 |
+
typo_results = self.fix_typos(text)
|
285 |
+
|
286 |
+
# Dilbilgisi kontrolü
|
287 |
+
grammar_results = self.check_grammar(typo_results['corrected_text'])
|
288 |
+
|
289 |
+
# Doldurma kelimelerini azalt
|
290 |
+
filler_results = self.reduce_filler_words(typo_results['corrected_text'])
|
291 |
+
|
292 |
+
# Okunabilirlik hesapla
|
293 |
+
readability_results = self.calculate_readability(text)
|
294 |
+
|
295 |
+
# Tüm önerileri birleştir
|
296 |
+
all_suggestions = []
|
297 |
+
all_suggestions.extend([f"Yazım düzeltmesi: {correction}" for correction in typo_results['corrections']])
|
298 |
+
all_suggestions.extend(
|
299 |
+
[f"Dilbilgisi önerisi: {suggestion}" for suggestion in grammar_results['suggestions']])
|
300 |
+
|
301 |
+
if filler_results['filler_count'] > 0:
|
302 |
+
all_suggestions.append(f"Doldurma kelimelerini azaltın: {', '.join(filler_results['filler_words'])}")
|
303 |
+
|
304 |
+
# Okunabilirlik önerisi
|
305 |
+
if readability_results['score'] < 60: # Orta seviyenin altında ise
|
306 |
+
if readability_results['avg_sentence_length'] > 15:
|
307 |
+
all_suggestions.append("Daha kısa cümleler kullanın (ortalama cümle uzunluğu yüksek)")
|
308 |
+
if readability_results['avg_word_length'] > 2.5:
|
309 |
+
all_suggestions.append("Daha basit kelimeler kullanmayı deneyin (ortalama hece sayısı yüksek)")
|
310 |
+
|
311 |
+
# Birleştirilmiş sonuç
|
312 |
+
return {
|
313 |
+
'original_text': text,
|
314 |
+
'corrected_text': typo_results['corrected_text'],
|
315 |
+
'improved_text': filler_results['suggested_text'],
|
316 |
+
'suggestions': all_suggestions,
|
317 |
+
'readability': {
|
318 |
+
'score': readability_results['score'],
|
319 |
+
'level': readability_results['level'],
|
320 |
+
'avg_sentence_length': readability_results['avg_sentence_length']
|
321 |
+
},
|
322 |
+
'improvement_count': len(all_suggestions)
|
323 |
+
}
|
324 |
+
|
325 |
+
except Exception as e:
|
326 |
+
logger.error(f"Metin iyileştirme sırasında hata: {str(e)}")
|
327 |
+
return {
|
328 |
+
'original_text': text,
|
329 |
+
'corrected_text': text,
|
330 |
+
'improved_text': text,
|
331 |
+
'suggestions': ["Metin analizi sırasında bir hata oluştu"],
|
332 |
+
'readability': {
|
333 |
+
'score': 0,
|
334 |
+
'level': 'bilinmiyor'
|
335 |
+
},
|
336 |
+
'improvement_count': 0
|
337 |
+
}
|
utils/toxicity_scorer.py
ADDED
@@ -0,0 +1,188 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import torch
|
2 |
+
import numpy as np
|
3 |
+
from transformers import AutoTokenizer, AutoModelForSequenceClassification
|
4 |
+
import logging
|
5 |
+
import re
|
6 |
+
|
7 |
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
8 |
+
|
9 |
+
|
10 |
+
class ToxicityScorer:
|
11 |
+
def __init__(self, model=None, tokenizer=None):
|
12 |
+
"""
|
13 |
+
Toxicity Scorer sınıfını başlatır.
|
14 |
+
|
15 |
+
Args:
|
16 |
+
model: Zararlılık modeli
|
17 |
+
tokenizer: Model için tokenizer
|
18 |
+
"""
|
19 |
+
self.model = model
|
20 |
+
self.tokenizer = tokenizer
|
21 |
+
self.device = "cuda" if torch.cuda.is_available() else "cpu"
|
22 |
+
self.is_turkish_model = False
|
23 |
+
|
24 |
+
if model is None or tokenizer is None:
|
25 |
+
logging.warning("No toxicity model provided. Using default model.")
|
26 |
+
self.load_default_model()
|
27 |
+
|
28 |
+
def load_default_model(self):
|
29 |
+
"""
|
30 |
+
Varsayılan zararlılık modelini yükler
|
31 |
+
"""
|
32 |
+
try:
|
33 |
+
# Öncelikle Türkçe duygu analizi modeli deneyelim
|
34 |
+
model_name = "savasy/bert-base-turkish-sentiment"
|
35 |
+
logging.info(f"Loading Turkish sentiment model: {model_name}")
|
36 |
+
self.tokenizer = AutoTokenizer.from_pretrained(model_name)
|
37 |
+
self.model = AutoModelForSequenceClassification.from_pretrained(model_name)
|
38 |
+
self.model.to(self.device)
|
39 |
+
self.is_turkish_model = True
|
40 |
+
logging.info("Turkish sentiment model loaded successfully")
|
41 |
+
except Exception as e:
|
42 |
+
logging.error(f"Error loading Turkish model: {str(e)}")
|
43 |
+
try:
|
44 |
+
# Yedek olarak genel model yükleyelim
|
45 |
+
backup_model = "dbmdz/bert-base-turkish-cased"
|
46 |
+
logging.info(f"Trying Turkish BERT model: {backup_model}")
|
47 |
+
self.tokenizer = AutoTokenizer.from_pretrained(backup_model)
|
48 |
+
self.model = AutoModelForSequenceClassification.from_pretrained(backup_model)
|
49 |
+
self.model.to(self.device)
|
50 |
+
self.is_turkish_model = True
|
51 |
+
logging.info("Turkish BERT model loaded successfully")
|
52 |
+
except Exception as e2:
|
53 |
+
logging.error(f"Error loading Turkish BERT model: {str(e2)}")
|
54 |
+
try:
|
55 |
+
# Son çare olarak İngilizce model kullanalım
|
56 |
+
english_model = "distilbert/distilbert-base-uncased-finetuned-sst-2-english"
|
57 |
+
logging.info(f"Trying English sentiment model: {english_model}")
|
58 |
+
self.tokenizer = AutoTokenizer.from_pretrained(english_model)
|
59 |
+
self.model = AutoModelForSequenceClassification.from_pretrained(english_model)
|
60 |
+
self.model.to(self.device)
|
61 |
+
self.is_turkish_model = False
|
62 |
+
logging.info("English sentiment model loaded successfully")
|
63 |
+
except Exception as e3:
|
64 |
+
logging.error(f"Error loading English model: {str(e3)}")
|
65 |
+
raise e3
|
66 |
+
|
67 |
+
def _contains_turkish_profanity(self, text):
|
68 |
+
"""
|
69 |
+
Temel Türkçe küfür ve hakaret kontrolü yapar
|
70 |
+
"""
|
71 |
+
# Türkçede yaygın küfür/hakaret içeren kelimelerin listesi
|
72 |
+
turkish_profanity = [
|
73 |
+
'aptal', 'salak', 'gerizekalı', 'ahmak', 'enayi', 'mal', 'geri zekalı',
|
74 |
+
'beyinsiz', 'budala', 'adi', 'ahlaksız', 'şerefsiz', 'haysiyetsiz',
|
75 |
+
'orospu', 'piç', 'yavşak', 'sürtük', 'sürtüğü', 'gavat', 'şerefsiz',
|
76 |
+
'siktir', 'pezevenk', 'namussuz'
|
77 |
+
]
|
78 |
+
|
79 |
+
# Noktalama işaretlerini ve sayıları kaldır
|
80 |
+
text = re.sub(r'[^\w\s]', '', text.lower())
|
81 |
+
text = re.sub(r'\d+', '', text)
|
82 |
+
words = text.split()
|
83 |
+
|
84 |
+
# Metinde küfür/hakaret var mı kontrol et
|
85 |
+
for word in turkish_profanity:
|
86 |
+
if word in words:
|
87 |
+
return True
|
88 |
+
|
89 |
+
return False
|
90 |
+
|
91 |
+
def _contains_negative_words(self, text):
|
92 |
+
"""
|
93 |
+
Temel Türkçe olumsuz kelime kontrolü yapar
|
94 |
+
"""
|
95 |
+
# Türkçede yaygın olumsuz kelimeler
|
96 |
+
negative_words = [
|
97 |
+
'kötü', 'berbat', 'rezalet', 'korkunç', 'iğrenç', 'üzücü', 'acı',
|
98 |
+
'başarısız', 'yetersiz', 'düşük', 'zayıf', 'korkutucu', 'tehlikeli',
|
99 |
+
'nefret', 'öfke', 'saldırgan', 'yanlış', 'hata', 'hayal kırıklığı'
|
100 |
+
]
|
101 |
+
|
102 |
+
text = text.lower()
|
103 |
+
count = sum(1 for word in negative_words if word in text.split())
|
104 |
+
|
105 |
+
# Olumsuz kelime yoğunluğunu hesapla
|
106 |
+
return count / len(text.split()) if text.split() else 0
|
107 |
+
|
108 |
+
def score_text(self, text):
|
109 |
+
"""
|
110 |
+
Metin için zararlılık skoru hesaplar.
|
111 |
+
|
112 |
+
Args:
|
113 |
+
text: Değerlendirilecek metin
|
114 |
+
|
115 |
+
Returns:
|
116 |
+
float: 0 ile 1 arasında zararlılık skoru (1 = çok zararlı)
|
117 |
+
"""
|
118 |
+
if not text or len(text.strip()) == 0:
|
119 |
+
return 0.0
|
120 |
+
|
121 |
+
# Temel kural tabanlı kontroller
|
122 |
+
profanity_detected = self._contains_turkish_profanity(text)
|
123 |
+
negative_ratio = self._contains_negative_words(text)
|
124 |
+
|
125 |
+
if profanity_detected:
|
126 |
+
base_score = 0.8 # Küfür/hakaret varsa yüksek başlangıç skoru
|
127 |
+
else:
|
128 |
+
base_score = negative_ratio * 0.5 # Olumsuz kelime yoğunluğuna göre skor
|
129 |
+
|
130 |
+
try:
|
131 |
+
# Model tabanlı skorlama
|
132 |
+
inputs = self.tokenizer(text, return_tensors="pt", truncation=True, max_length=512)
|
133 |
+
inputs = {key: val.to(self.device) for key, val in inputs.items()}
|
134 |
+
|
135 |
+
with torch.no_grad():
|
136 |
+
outputs = self.model(**inputs)
|
137 |
+
|
138 |
+
# Modele göre doğru şekilde skoru alalım
|
139 |
+
if self.is_turkish_model:
|
140 |
+
# Türkçe duygu analizi modeli için özel işlem
|
141 |
+
probs = torch.softmax(outputs.logits, dim=1).cpu().numpy()[0]
|
142 |
+
|
143 |
+
# savasy/bert-base-turkish-sentiment için:
|
144 |
+
# 0: negative, 1: neutral, 2: positive
|
145 |
+
if len(probs) >= 3:
|
146 |
+
# Negatif olasılığını zararlılık skoru olarak kullan ama çok yüksek değerler üretmemesi için 0.7 ile çarp
|
147 |
+
model_score = probs[0] * 0.7
|
148 |
+
else:
|
149 |
+
# İki sınıflı model için
|
150 |
+
model_score = probs[0] * 0.6
|
151 |
+
else:
|
152 |
+
# İngilizce model için
|
153 |
+
probs = torch.softmax(outputs.logits, dim=1).cpu().numpy()[0]
|
154 |
+
# İngilizce modeller genellikle Türkçe için çok yüksek sonuçlar verir, bu yüzden 0.5 ile çarp
|
155 |
+
model_score = probs[0] * 0.5
|
156 |
+
|
157 |
+
# Kural tabanlı skor ve model skor birleşimi
|
158 |
+
final_score = (base_score * 0.4) + (model_score * 0.6)
|
159 |
+
|
160 |
+
# 0-1 aralığına sınırla
|
161 |
+
final_score = max(0.0, min(1.0, final_score))
|
162 |
+
|
163 |
+
return final_score
|
164 |
+
|
165 |
+
except Exception as e:
|
166 |
+
logging.error(f"Error scoring toxicity: {str(e)}")
|
167 |
+
# Hata durumunda sadece kural tabanlı skoru döndür
|
168 |
+
return min(base_score, 1.0)
|
169 |
+
|
170 |
+
def batch_score(self, texts, batch_size=16):
|
171 |
+
"""
|
172 |
+
Bir metin listesi için toplu zararlılık skoru hesaplar.
|
173 |
+
|
174 |
+
Args:
|
175 |
+
texts: Değerlendirilecek metin listesi
|
176 |
+
batch_size: İşlenecek grup boyutu
|
177 |
+
|
178 |
+
Returns:
|
179 |
+
list: Zararlılık skorları listesi
|
180 |
+
"""
|
181 |
+
results = []
|
182 |
+
|
183 |
+
for i in range(0, len(texts), batch_size):
|
184 |
+
batch_texts = texts[i:i + batch_size]
|
185 |
+
batch_scores = [self.score_text(text) for text in batch_texts]
|
186 |
+
results.extend(batch_scores)
|
187 |
+
|
188 |
+
return results
|