File size: 11,928 Bytes
3c795ed
 
 
 
 
 
 
 
 
 
bcfccb1
3c795ed
 
 
 
 
297bdcf
 
 
 
 
 
 
 
 
3c795ed
 
 
297bdcf
3c795ed
 
 
 
 
 
 
 
 
 
 
 
 
412da1a
3c795ed
 
 
 
 
 
 
 
bcfccb1
 
 
 
 
412da1a
 
 
 
 
 
 
 
 
bcfccb1
 
 
 
412da1a
bcfccb1
 
 
 
 
 
 
 
 
3c795ed
bcfccb1
 
 
 
 
 
 
 
3c795ed
 
 
bcfccb1
3c795ed
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
412da1a
0af0617
3c795ed
0af0617
 
 
412da1a
3c795ed
412da1a
 
 
 
 
 
 
 
 
 
 
 
 
 
3c795ed
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
412da1a
 
 
3c795ed
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
412da1a
3c795ed
 
412da1a
 
3c795ed
 
 
 
 
 
 
 
 
 
 
 
412da1a
 
 
 
 
 
 
 
 
 
 
3c795ed
 
 
 
 
 
 
 
 
 
 
 
 
 
0af0617
 
 
 
 
 
 
 
 
 
 
 
 
 
3c795ed
 
 
 
 
0af0617
 
 
3c795ed
0af0617
 
 
 
 
 
 
 
3c795ed
 
 
 
 
 
 
 
 
0af0617
 
 
 
 
3c795ed
0af0617
 
 
3c795ed
 
0af0617
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
412da1a
0af0617
 
3c795ed
 
 
0af0617
 
 
 
 
 
 
 
3c795ed
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
412da1a
3c795ed
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
"""embedpick web arayüzü.

Açılışta örnek veri setindeki hazır sonuçları gösterir, isteyen kendi
CSV dosyalarını yükleyip canlı çalıştırabilir.

Çalıştırmak için:
    pip install embedpick[ui]
    python app.py
"""

import pathlib
from importlib import resources

# ZeroGPU donanımında çalışırken bu import torch'tan ÖNCE gelmeli.
# Kendi makinenizde `spaces` kurulu olmayacaktır; o durumda sarmalayıcı
# hiçbir şey yapmayan bir dekoratöre dönüşür ve uygulama normal çalışır.
#
# duration bir rezervasyon talebi, üst sınır değil: hesap seviyesinin
# tavanını aşan bir talep işi hiç başlatmadan reddediliyor ve kullanıcı
# "requested GPU duration is larger than the maximum allowed" görüyor.
# Eskiden 300 isteniyordu; bu veri seti için fazlasıyla cömert, çünkü asıl
# maliyet model indirme (GPU'suz) ve 72 dokümanı kodlamak saniyeler sürüyor.
# Kısa talep ayrıca kuyrukta öne geçiriyor.
GPU_SURESI = 120

try:
    import spaces

    gpu_gorevi = spaces.GPU(duration=GPU_SURESI)
except Exception:

    def gpu_gorevi(fn):
        return fn


import gradio as gr
import pandas as pd

from embedpick.benchmark import run_benchmark
from embedpick.data import load_corpus, load_queries, validate
from embedpick.metrics import consensus_failures
from embedpick.retrievers import DEFAULT_MODELS
from embedpick.stemmers import CHOICES as STEMMER_CHOICES

REPO = "https://github.com/Mehmetalpertugtekin/embedpick"


def paket_dosyasi(ad):
    return resources.files("embedpick") / "sample_data" / ad


# Yayınlanmış paket sürümü bu dosyayı içermeyebilir, o yüzden gömülü bir
# yedek tutuyoruz. Sayılar 72 doküman / 26 sorgu ile tek bir makinede,
# yalnızca işlemci üzerinde ölçüldü.
YEDEK_SONUCLAR = pd.DataFrame(
    [
        # model, recall@5, GA, mrr@5, doküman/sn, boyut, MB, göreli hız
        ["BM25 (baseline, simple stem)",
         0.654, "[0.50, 0.81]", 0.622, 32000.0, 0, 0.00, "-"],
        ["paraphrase-multilingual-MiniLM-L12-v2",
         0.615, "[0.46, 0.77]", 0.596, 374.0, 384, 0.11, "1.00x"],
        ["multilingual-e5-small",
         0.692, "[0.56, 0.81]", 0.708, 322.0, 384, 0.11, "0.86x"],
        ["turkish-embedding-model",
         0.846, "[0.73, 0.94]", 0.865, 119.0, 768, 0.21, "0.32x"],
    ],
    columns=[
        "model",
        "recall@5",
        "recall@5_ci",
        "mrr@5",
        "docs_per_sec",
        "dim",
        "index_mb",
        "rel_speed",
    ],
)


def hazir_sonuclar():
    """Önce app.py'nin yanındaki dosyaya, sonra pakete, sonra gömülü yedeğe bakar."""
    yerel = pathlib.Path(__file__).parent / "precomputed_results.csv"
    if yerel.exists():
        try:
            return pd.read_csv(yerel)
        except Exception:
            pass

    try:
        return pd.read_csv(paket_dosyasi("precomputed_results.csv"))
    except Exception:
        return YEDEK_SONUCLAR


GIRIS = f"""
# embedpick

Embedding modellerini **kendi verinizde** kıyaslayın, başkasının sıralamasında değil.

Model kartlarındaki skorlar genel amaçlı İngilizce veri setlerinde ölçülür.
Sizin verinizde hangi modelin işe yaradığını ancak kendi verinizde ölçerek
bulabilirsiniz. embedpick kaliteyi, hızı ve belleği yan yana raporlar — ve
aynı tabloya düz kelime aramasını (BM25) da bir satır olarak koyar.

[Kaynak kod]({REPO}) · `pip install embedpick`
"""

HAZIR_ACIKLAMA = """
### Örnek veri setindeki sonuçlar

72 sentetik Türkçe müşteri destek mesajı, 26 etiketli sorgu, k=5.
Baseline gövdelenmiş halde (`--stemmer simple`) çalıştırıldı.
"""

# Yorum kısmı katlanmış duruyor: tablo ve çalıştırma paneli ilk ekranda
# kalsın diye. Metin değerli ama aracı kullanmanın önüne geçmemeli.
YORUM = """
Baseline'a göre eşleştirilmiş fark, %95 güven aralığıyla:

| Model | fark | %95 GA | sonuç |
|---|---|---|---|
| paraphrase-multilingual-MiniLM-L12-v2 | −0.038 | [−0.192, +0.115] | gösterilemedi |
| multilingual-e5-small | +0.038 | [−0.115, +0.212] | gösterilemedi |
| trmteb/turkish-embedding-model | **+0.192** | [+0.058, +0.346] | **anlamlı** |

Üç modelden ikisinin bu veride kelime aramasını geçtiği gösterilemiyor;
MiniLM'in nokta tahmini sıfırın yanlış tarafında. Yani çıkarım "embedding
gereksiz" değil — **kötü seçilmiş bir embedding modeli kelime aramasından iyi
değil ve kimse bunu kontrol etmiyor.**

"Gösterilemedi", fark yok demek değil; 26 sorgunun o farkı göstermeye
yetmediği demek. `docs_per_sec` makineye göre değişir; makineler arası kıyas
için `rel_speed` sütununu kullanın.
"""

NASIL = """
**corpus.csv** — aranacak dokümanlar

```
id,text
1,Kargom hala elime ulaşmadı
2,Sipariş takip numaram sistemde görünmüyor
```

**queries.csv** — sorgular ve doğru cevaplar

```
query,relevant_ids
kargo gecikmesi,1;5
paketim nerede,1;2
```

Birden fazla doğru cevabı `;` ile ayırın. Dosyalar UTF-8 olmalı.

Gerçek farkları görmek için 20-30 sorgu genelde yeterli. Doğru cevabıyla hiç
ortak kelime paylaşmayan sorgular da koyun — anlamsal eşleştirmeyi asıl test
edenler onlar.
"""


@gpu_gorevi
def calistir(
    corpus_dosya, queries_dosya, secili, ek_modeller, k, baseline, presets, stemmer
):
    if corpus_dosya is None or queries_dosya is None:
        return None, None, "İki CSV dosyasını da yükleyin."

    modeller = list(secili or [])
    if ek_modeller and ek_modeller.strip():
        modeller += [m.strip() for m in ek_modeller.split(",") if m.strip()]

    if not modeller:
        return None, None, "En az bir model seçin veya yazın."

    try:
        corpus = load_corpus(corpus_dosya)
        queries = load_queries(queries_dosya)
        validate(corpus, queries)
    except (ValueError, FileNotFoundError) as exc:
        return None, None, f"Veri hatası:\n\n```\n{exc}\n```"

    try:
        df, sorgu_bazli = run_benchmark(
            corpus,
            queries,
            model_names=modeller,
            k=int(k),
            repeats=3,
            include_baseline=baseline,
            use_presets=presets,
            stemmer=stemmer,
        )
    except Exception as exc:
        hata = f"{type(exc).__name__}: {exc}"
        return None, None, f"Çalıştırma hatası:\n\n```\n{hata}\n```"

    detay = pd.DataFrame(sorgu_bazli)
    detay.insert(0, "sorgu", detay.index)
    detay = detay.reset_index(drop=True)

    notlar = [
        f"{len(corpus)} doküman, {len(queries)} sorgu, k={int(k)} ile çalıştırıldı."
    ]
    if not presets:
        notlar.append(
            "Model önekleri kapalı. e5 gibi önek bekleyen modeller düşük skor alır."
        )
    if baseline and stemmer == "none":
        notlar.append(
            "BM25 gövdelemesiz çalıştı. Türkçe sondan eklemeli olduğu için "
            "baseline olduğundan zayıf görünür ve modellerle arasındaki fark "
            "olduğundan büyük çıkar. `simple` ile tekrar deneyin."
        )
    notlar.append(
        f"`recall@{int(k)}_ci`, {len(queries)} sorgu üzerinden bootstrap ile "
        "hesaplanmış yüzde 95 güven aralığıdır. Aralıklar üst üste biniyorsa "
        "aradaki fark bu veri setinde gösterilememiştir."
    )

    ortak = consensus_failures(sorgu_bazli) if len(sorgu_bazli) > 1 else []
    if ortak:
        liste = "\n".join(f"- {q}" for q in ortak)
        notlar.append(
            "**Hiçbir yöntemin bulamadığı sorgular:**\n"
            f"{liste}\n\n"
            "Bütün yöntemler aynı sorguda batıyorsa sorun genelde modelde değil, "
            "etiketlerdedir. Bu sorguların `relevant_ids` değerlerini gözden geçirin."
        )

    return df, detay, "\n\n".join(notlar)


# Gradio'nun ana kabı varsayılan olarak görünür alan yüksekliğinde ve
# overflow-y: hidden geliyor; sayfa büyüdüğünde alt kısım kırpılıyor ve
# kullanıcı fareyle aşağı kaydıramıyor. Space bir iframe içinde gömülü
# gösterildiğinde özellikle can sıkıcı: Çalıştır butonu erişilemez oluyor.
# Kabı içeriğe göre büyümeye bırakıyoruz.
CSS = """
.gradio-container {
    height: auto !important;
    min-height: 100vh;
    overflow-y: visible !important;
}
"""

with gr.Blocks(title="embedpick", css=CSS) as demo:
    gr.Markdown(GIRIS)

    gr.Markdown(HAZIR_ACIKLAMA)
    gr.Dataframe(value=hazir_sonuclar(), interactive=False, wrap=True)

    with gr.Accordion("Bu tablo ne anlatıyor?", open=False):
        gr.Markdown(YORUM)

    with gr.Accordion("Kendi verinizle çalıştırın", open=False):
        # Düzen kasten sıkışık: iki yükleme kutusu, sonra doğrudan Çalıştır.
        # Önceki sürümde biçim açıklaması ve bütün ayarlar butonun üstündeydi,
        # buton ekranın altında kalıyordu ve kullanıcı aracı çalıştıramadan
        # vazgeçiyordu. Varsayılanlar zaten makul; ayarlar katlanmış duruyor.
        gr.Markdown(
            "İki CSV yükleyin ve **Çalıştır**'a basın. Varsayılan ayarlar makul; "
            "biçim örneği ve ayarlar aşağıdaki başlıklarda."
        )

        with gr.Row():
            corpus_yukle = gr.File(
                label="corpus.csv", file_types=[".csv"], type="filepath"
            )
            queries_yukle = gr.File(
                label="queries.csv", file_types=[".csv"], type="filepath"
            )

        with gr.Accordion("Ayarlar", open=False):
            model_secimi = gr.CheckboxGroup(
                choices=DEFAULT_MODELS,
                value=DEFAULT_MODELS[:2],
                label="Modeller",
            )
            ek_model = gr.Textbox(
                label="Ek modeller (Hugging Face adı, virgülle ayırın)",
                placeholder="intfloat/multilingual-e5-base, BAAI/bge-m3",
            )

            with gr.Row():
                k_secimi = gr.Slider(1, 20, value=5, step=1, label="k (ilk kaç sonuç)")
                baseline_secimi = gr.Checkbox(
                    value=True, label="BM25 baseline'ı dahil et"
                )
                preset_secimi = gr.Checkbox(
                    value=True, label="Bilinen model öneklerini uygula"
                )

            stemmer_secimi = gr.Radio(
                choices=list(STEMMER_CHOICES),
                value="simple",
                label="BM25 baseline'ında Türkçe gövdeleme",
                info=(
                    "Gövdelemesiz baseline 'iade' ile 'iadesi'yi ilgisiz iki terim "
                    "sayar ve olduğundan zayıf görünür. zemberek ayrı kurulum ister."
                ),
            )

        with gr.Accordion("CSV biçimi", open=False):
            gr.Markdown(NASIL)

        calistir_dugmesi = gr.Button("Çalıştır", variant="primary")

        gr.Markdown(
            "İlk çalıştırmada modeller indirilir, birkaç dakika sürebilir; "
            "sonraki çalıştırmalar önbellekten okur. Her çalıştırma için "
            f"{GPU_SURESI} saniyelik bir GPU payı ayrılıyor — çok büyük bir "
            "korpusta süre dolarsa model sayısını azaltın ya da paketi kendi "
            "makinenizde çalıştırın: `pip install embedpick`."
        )

        durum = gr.Markdown()
        sonuc_tablosu = gr.Dataframe(label="Sonuçlar", interactive=False, wrap=True)
        detay_tablosu = gr.Dataframe(
            label="Sorgu bazlı recall", interactive=False, wrap=True
        )

    calistir_dugmesi.click(
        fn=calistir,
        inputs=[
            corpus_yukle,
            queries_yukle,
            model_secimi,
            ek_model,
            k_secimi,
            baseline_secimi,
            preset_secimi,
            stemmer_secimi,
        ],
        outputs=[sonuc_tablosu, detay_tablosu, durum],
    )


if __name__ == "__main__":
    demo.launch(server_name="0.0.0.0", server_port=7860, ssr_mode=False)