embedpick 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- embedpick-0.1.0/LICENSE +21 -0
- embedpick-0.1.0/PKG-INFO +239 -0
- embedpick-0.1.0/README.md +202 -0
- embedpick-0.1.0/embedpick/__init__.py +7 -0
- embedpick-0.1.0/embedpick/__main__.py +3 -0
- embedpick-0.1.0/embedpick/benchmark.py +97 -0
- embedpick-0.1.0/embedpick/cli.py +111 -0
- embedpick-0.1.0/embedpick/data.py +69 -0
- embedpick-0.1.0/embedpick/metrics.py +54 -0
- embedpick-0.1.0/embedpick/retrievers.py +150 -0
- embedpick-0.1.0/embedpick/sample_data/corpus.csv +73 -0
- embedpick-0.1.0/embedpick/sample_data/queries.csv +27 -0
- embedpick-0.1.0/embedpick.egg-info/PKG-INFO +239 -0
- embedpick-0.1.0/embedpick.egg-info/SOURCES.txt +21 -0
- embedpick-0.1.0/embedpick.egg-info/dependency_links.txt +1 -0
- embedpick-0.1.0/embedpick.egg-info/entry_points.txt +2 -0
- embedpick-0.1.0/embedpick.egg-info/requires.txt +11 -0
- embedpick-0.1.0/embedpick.egg-info/top_level.txt +1 -0
- embedpick-0.1.0/pyproject.toml +61 -0
- embedpick-0.1.0/setup.cfg +4 -0
- embedpick-0.1.0/tests/test_data.py +71 -0
- embedpick-0.1.0/tests/test_metrics.py +81 -0
- embedpick-0.1.0/tests/test_retrievers.py +82 -0
embedpick-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mehmet Alper Tuğtekin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
embedpick-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: embedpick
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Benchmark embedding models on your own data, with a keyword-search baseline
|
|
5
|
+
Author: Mehmet Alper Tuğtekin
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Mehmetalpertugtekin/embedpick
|
|
8
|
+
Project-URL: Repository, https://github.com/Mehmetalpertugtekin/embedpick
|
|
9
|
+
Project-URL: Issues, https://github.com/Mehmetalpertugtekin/embedpick/issues
|
|
10
|
+
Keywords: embeddings,benchmark,semantic-search,information-retrieval,sentence-transformers,turkish-nlp
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
21
|
+
Classifier: Topic :: Text Processing :: Indexing
|
|
22
|
+
Classifier: Natural Language :: Turkish
|
|
23
|
+
Classifier: Natural Language :: English
|
|
24
|
+
Requires-Python: >=3.9
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Requires-Dist: pandas>=1.3
|
|
28
|
+
Requires-Dist: numpy>=1.21
|
|
29
|
+
Requires-Dist: rank-bm25>=0.2
|
|
30
|
+
Requires-Dist: sentence-transformers>=2.2
|
|
31
|
+
Requires-Dist: faiss-cpu>=1.7
|
|
32
|
+
Provides-Extra: dev
|
|
33
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
34
|
+
Provides-Extra: ui
|
|
35
|
+
Requires-Dist: gradio>=4.0; extra == "ui"
|
|
36
|
+
Dynamic: license-file
|
|
37
|
+
|
|
38
|
+
# embedpick
|
|
39
|
+

|
|
40
|
+
|
|
41
|
+
Benchmark embedding models on **your own data**, not on someone else's leaderboard.
|
|
42
|
+
|
|
43
|
+
[Türkçe README](README.tr.md)
|
|
44
|
+
|
|
45
|
+
## Why
|
|
46
|
+
|
|
47
|
+
Picking an embedding model usually goes like this: open Hugging Face, sort by
|
|
48
|
+
downloads, take the top result. The scores on the model card were measured on
|
|
49
|
+
general-purpose English benchmarks. Your data is not that.
|
|
50
|
+
|
|
51
|
+
embedpick answers a narrower and more useful question: *given my documents and
|
|
52
|
+
my queries, which model actually finds the right thing, and what does it cost
|
|
53
|
+
me in time and memory?*
|
|
54
|
+
|
|
55
|
+
It reports quality, speed and memory side by side, and — unlike most benchmark
|
|
56
|
+
tooling — it puts a plain keyword-search baseline in the same table.
|
|
57
|
+
|
|
58
|
+
## The baseline is the point
|
|
59
|
+
|
|
60
|
+
Every run includes BM25, a classic keyword-matching algorithm with no neural
|
|
61
|
+
network involved. If an embedding model cannot beat it, running that model is
|
|
62
|
+
wasted compute.
|
|
63
|
+
|
|
64
|
+
Results on the included Turkish customer-support dataset (72 documents,
|
|
65
|
+
26 queries, k=5):
|
|
66
|
+
|
|
67
|
+
| Model | recall@5 | MRR@5 | docs/sec | dim | rel. speed |
|
|
68
|
+
|---|---|---|---|---|---|
|
|
69
|
+
| BM25 (baseline) | 0.564 | 0.564 | ~226,000 | — | — |
|
|
70
|
+
| paraphrase-multilingual-MiniLM-L12-v2 | 0.615 | 0.596 | 340 | 384 | 1.00x |
|
|
71
|
+
| multilingual-e5-small | 0.692 | 0.708 | 304 | 384 | 0.89x |
|
|
72
|
+
| trmteb/turkish-embedding-model | **0.846** | **0.865** | 103 | 768 | 0.30x |
|
|
73
|
+
|
|
74
|
+
Read that first two rows carefully. MiniLM buys a 5-point recall gain over
|
|
75
|
+
keyword matching, and pays roughly 600x the indexing time for it. On this
|
|
76
|
+
dataset that trade is hard to justify.
|
|
77
|
+
|
|
78
|
+
The Turkish-specific model is a different story: +28 points over the baseline
|
|
79
|
+
is a real jump, and worth the 3x slowdown against the other neural models.
|
|
80
|
+
|
|
81
|
+
So the lesson is not "embeddings are overrated." It is: **a badly chosen
|
|
82
|
+
embedding model is not better than keyword search, and nobody checks.**
|
|
83
|
+
|
|
84
|
+
## Findings from the sample dataset
|
|
85
|
+
|
|
86
|
+
**Documented configuration is not always the right configuration.** The e5
|
|
87
|
+
model card specifies `query: ` and `passage: ` prefixes on input text. Applying
|
|
88
|
+
them on this dataset *lowered* recall@5 from 0.750 to 0.692 and MRR from 0.792
|
|
89
|
+
to 0.708 — same model, same data, same hardware.
|
|
90
|
+
|
|
91
|
+
A plausible reason: these documents are short, five to eight words each, so an
|
|
92
|
+
English prefix takes up a large share of every one of them. Whatever the cause,
|
|
93
|
+
the recommended setting cost about six points here, and nothing short of
|
|
94
|
+
measuring on your own data would have shown it.
|
|
95
|
+
|
|
96
|
+
embedpick applies known prefixes by default, following the model authors'
|
|
97
|
+
instructions, and `--no-presets` turns them off so you can check. Across 26
|
|
98
|
+
queries the gap is worth roughly one and a half queries, so read the direction
|
|
99
|
+
as suggestive rather than settled.
|
|
100
|
+
|
|
101
|
+
**BM25 and embeddings fail on different queries.** On `ödeme yaparken sorun`
|
|
102
|
+
("problem while paying") BM25 scored 1.00 and MiniLM scored 0.00. On
|
|
103
|
+
`sahte ürün şüphesi` ("suspected counterfeit") it was the reverse. They are
|
|
104
|
+
complementary, which is the empirical case for hybrid retrieval.
|
|
105
|
+
|
|
106
|
+
**Small corpora hide everything.** On a 16-document pilot all three models
|
|
107
|
+
scored 1.0 and looked identical. At 72 documents a 23-point spread appeared.
|
|
108
|
+
A benchmark that cannot separate models tells you nothing about them.
|
|
109
|
+
|
|
110
|
+
**Absolute timings are unreliable; ratios are not.** Across repeated runs on
|
|
111
|
+
the same machine, `docs/sec` varied by up to 4x while the ratio between models
|
|
112
|
+
stayed within about 20%. Report `rel_speed` when comparing across machines.
|
|
113
|
+
|
|
114
|
+
## Install
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
git clone https://github.com/Mehmetalpertugtekin/embedpick.git
|
|
118
|
+
cd embedpick
|
|
119
|
+
python -m venv .venv
|
|
120
|
+
.venv\Scripts\activate # Windows
|
|
121
|
+
source .venv/bin/activate # macOS / Linux
|
|
122
|
+
pip install -r requirements.txt
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Python 3.9+.
|
|
126
|
+
|
|
127
|
+
## Usage
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
# run with the bundled sample dataset
|
|
131
|
+
python -m embedpick
|
|
132
|
+
|
|
133
|
+
# your own data
|
|
134
|
+
python -m embedpick --corpus my_docs.csv --queries my_queries.csv
|
|
135
|
+
|
|
136
|
+
# pick models, evaluate top 10, show per-query detail
|
|
137
|
+
python -m embedpick --models intfloat/multilingual-e5-base -k 10 --verbose
|
|
138
|
+
|
|
139
|
+
# save the table
|
|
140
|
+
python -m embedpick --out results.csv
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
`python -m embedpick --help` lists every flag.
|
|
144
|
+
|
|
145
|
+
## Development
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
pip install -r requirements-dev.txt
|
|
149
|
+
python -m pytest
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Tests do not download any models, so they run in seconds. Make sure your
|
|
153
|
+
virtual environment is active first — a missing `rank_bm25` in the test output
|
|
154
|
+
usually means you installed into the system Python by mistake.
|
|
155
|
+
|
|
156
|
+
## Data format
|
|
157
|
+
|
|
158
|
+
**corpus.csv** — the documents to search over:
|
|
159
|
+
|
|
160
|
+
```csv
|
|
161
|
+
id,text
|
|
162
|
+
1,Kargom hala elime ulaşmadı
|
|
163
|
+
2,Sipariş takip numaram sistemde görünmüyor
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**queries.csv** — queries and their correct answers:
|
|
167
|
+
|
|
168
|
+
```csv
|
|
169
|
+
query,relevant_ids
|
|
170
|
+
kargo gecikmesi,1;5
|
|
171
|
+
paketim nerede,1;2
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Separate multiple correct answers with `;`. A query can have any number of
|
|
175
|
+
them. Files must be UTF-8.
|
|
176
|
+
|
|
177
|
+
Twenty to thirty queries is usually enough to see real differences. Include
|
|
178
|
+
queries that share no words with their correct answer — those are the ones
|
|
179
|
+
that test semantic matching rather than lucky keyword overlap.
|
|
180
|
+
|
|
181
|
+
## Metrics
|
|
182
|
+
|
|
183
|
+
**recall@k** — of the correct documents, what fraction appeared in the top k.
|
|
184
|
+
Use this when you show the user a list of results.
|
|
185
|
+
|
|
186
|
+
**MRR@k** — 1.0 if the first correct answer is at rank 1, 0.5 at rank 2, 0.33
|
|
187
|
+
at rank 3, averaged over queries. Use this when you show one answer, or feed
|
|
188
|
+
the top hit to an LLM.
|
|
189
|
+
|
|
190
|
+
These can disagree, and the disagreement is informative. In an earlier run
|
|
191
|
+
e5 had higher recall than MiniLM but lower MRR: it found more, but ranked
|
|
192
|
+
worse. Which model is "better" depends on what you are building.
|
|
193
|
+
|
|
194
|
+
**docs/sec** — indexing throughput. Machine-dependent, see the caveat above.
|
|
195
|
+
|
|
196
|
+
**dim** and **index_mb** — vector width and index size. A 768-dimensional
|
|
197
|
+
model needs twice the memory of a 384-dimensional one at the same corpus size.
|
|
198
|
+
|
|
199
|
+
## Label checking
|
|
200
|
+
|
|
201
|
+
Two things happen automatically:
|
|
202
|
+
|
|
203
|
+
- If `relevant_ids` points at a document id that is not in the corpus, the run
|
|
204
|
+
stops and names the query. Otherwise that query silently scores zero forever
|
|
205
|
+
and you blame the model.
|
|
206
|
+
- If *no* method — including BM25 — finds a query's answer, embedpick flags it.
|
|
207
|
+
When everything fails on the same query, the label is usually wrong. This
|
|
208
|
+
caught a mislabelled query during development.
|
|
209
|
+
|
|
210
|
+
## Sample dataset
|
|
211
|
+
|
|
212
|
+
`embedpick/sample_data/` contains 72 synthetic Turkish customer-support messages across nine
|
|
213
|
+
themes (shipping, returns, payment, account, product quality, promotions,
|
|
214
|
+
warranty, order management, support) and 26 labelled queries. It is written,
|
|
215
|
+
not scraped, so it carries no licensing or privacy constraints.
|
|
216
|
+
|
|
217
|
+
Several queries deliberately share no vocabulary with their answers — for
|
|
218
|
+
example `güvenlik ihlali şüphesi` ("suspected security breach") maps to
|
|
219
|
+
*"Hesabıma başkası girmiş olabilir"* ("someone else may have accessed my
|
|
220
|
+
account"). Keyword search cannot solve these; that is the point.
|
|
221
|
+
|
|
222
|
+
## Caveats
|
|
223
|
+
|
|
224
|
+
- Timing on a 72-document corpus is close to measurement noise, particularly
|
|
225
|
+
for BM25. Treat the throughput column as indicative.
|
|
226
|
+
- BM25 and neural encoders scale differently. The ratio at 72 documents is not
|
|
227
|
+
the ratio at 100,000.
|
|
228
|
+
- Results are from one machine, CPU only, no GPU.
|
|
229
|
+
|
|
230
|
+
## Roadmap
|
|
231
|
+
|
|
232
|
+
- Hybrid retrieval (BM25 + embedding score fusion) as a fourth row
|
|
233
|
+
- Hub detection: flag documents that surface for nearly every query
|
|
234
|
+
- Markdown report export
|
|
235
|
+
- Optional Gradio interface
|
|
236
|
+
|
|
237
|
+
## License
|
|
238
|
+
|
|
239
|
+
MIT
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# embedpick
|
|
2
|
+

|
|
3
|
+
|
|
4
|
+
Benchmark embedding models on **your own data**, not on someone else's leaderboard.
|
|
5
|
+
|
|
6
|
+
[Türkçe README](README.tr.md)
|
|
7
|
+
|
|
8
|
+
## Why
|
|
9
|
+
|
|
10
|
+
Picking an embedding model usually goes like this: open Hugging Face, sort by
|
|
11
|
+
downloads, take the top result. The scores on the model card were measured on
|
|
12
|
+
general-purpose English benchmarks. Your data is not that.
|
|
13
|
+
|
|
14
|
+
embedpick answers a narrower and more useful question: *given my documents and
|
|
15
|
+
my queries, which model actually finds the right thing, and what does it cost
|
|
16
|
+
me in time and memory?*
|
|
17
|
+
|
|
18
|
+
It reports quality, speed and memory side by side, and — unlike most benchmark
|
|
19
|
+
tooling — it puts a plain keyword-search baseline in the same table.
|
|
20
|
+
|
|
21
|
+
## The baseline is the point
|
|
22
|
+
|
|
23
|
+
Every run includes BM25, a classic keyword-matching algorithm with no neural
|
|
24
|
+
network involved. If an embedding model cannot beat it, running that model is
|
|
25
|
+
wasted compute.
|
|
26
|
+
|
|
27
|
+
Results on the included Turkish customer-support dataset (72 documents,
|
|
28
|
+
26 queries, k=5):
|
|
29
|
+
|
|
30
|
+
| Model | recall@5 | MRR@5 | docs/sec | dim | rel. speed |
|
|
31
|
+
|---|---|---|---|---|---|
|
|
32
|
+
| BM25 (baseline) | 0.564 | 0.564 | ~226,000 | — | — |
|
|
33
|
+
| paraphrase-multilingual-MiniLM-L12-v2 | 0.615 | 0.596 | 340 | 384 | 1.00x |
|
|
34
|
+
| multilingual-e5-small | 0.692 | 0.708 | 304 | 384 | 0.89x |
|
|
35
|
+
| trmteb/turkish-embedding-model | **0.846** | **0.865** | 103 | 768 | 0.30x |
|
|
36
|
+
|
|
37
|
+
Read that first two rows carefully. MiniLM buys a 5-point recall gain over
|
|
38
|
+
keyword matching, and pays roughly 600x the indexing time for it. On this
|
|
39
|
+
dataset that trade is hard to justify.
|
|
40
|
+
|
|
41
|
+
The Turkish-specific model is a different story: +28 points over the baseline
|
|
42
|
+
is a real jump, and worth the 3x slowdown against the other neural models.
|
|
43
|
+
|
|
44
|
+
So the lesson is not "embeddings are overrated." It is: **a badly chosen
|
|
45
|
+
embedding model is not better than keyword search, and nobody checks.**
|
|
46
|
+
|
|
47
|
+
## Findings from the sample dataset
|
|
48
|
+
|
|
49
|
+
**Documented configuration is not always the right configuration.** The e5
|
|
50
|
+
model card specifies `query: ` and `passage: ` prefixes on input text. Applying
|
|
51
|
+
them on this dataset *lowered* recall@5 from 0.750 to 0.692 and MRR from 0.792
|
|
52
|
+
to 0.708 — same model, same data, same hardware.
|
|
53
|
+
|
|
54
|
+
A plausible reason: these documents are short, five to eight words each, so an
|
|
55
|
+
English prefix takes up a large share of every one of them. Whatever the cause,
|
|
56
|
+
the recommended setting cost about six points here, and nothing short of
|
|
57
|
+
measuring on your own data would have shown it.
|
|
58
|
+
|
|
59
|
+
embedpick applies known prefixes by default, following the model authors'
|
|
60
|
+
instructions, and `--no-presets` turns them off so you can check. Across 26
|
|
61
|
+
queries the gap is worth roughly one and a half queries, so read the direction
|
|
62
|
+
as suggestive rather than settled.
|
|
63
|
+
|
|
64
|
+
**BM25 and embeddings fail on different queries.** On `ödeme yaparken sorun`
|
|
65
|
+
("problem while paying") BM25 scored 1.00 and MiniLM scored 0.00. On
|
|
66
|
+
`sahte ürün şüphesi` ("suspected counterfeit") it was the reverse. They are
|
|
67
|
+
complementary, which is the empirical case for hybrid retrieval.
|
|
68
|
+
|
|
69
|
+
**Small corpora hide everything.** On a 16-document pilot all three models
|
|
70
|
+
scored 1.0 and looked identical. At 72 documents a 23-point spread appeared.
|
|
71
|
+
A benchmark that cannot separate models tells you nothing about them.
|
|
72
|
+
|
|
73
|
+
**Absolute timings are unreliable; ratios are not.** Across repeated runs on
|
|
74
|
+
the same machine, `docs/sec` varied by up to 4x while the ratio between models
|
|
75
|
+
stayed within about 20%. Report `rel_speed` when comparing across machines.
|
|
76
|
+
|
|
77
|
+
## Install
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
git clone https://github.com/Mehmetalpertugtekin/embedpick.git
|
|
81
|
+
cd embedpick
|
|
82
|
+
python -m venv .venv
|
|
83
|
+
.venv\Scripts\activate # Windows
|
|
84
|
+
source .venv/bin/activate # macOS / Linux
|
|
85
|
+
pip install -r requirements.txt
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Python 3.9+.
|
|
89
|
+
|
|
90
|
+
## Usage
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
# run with the bundled sample dataset
|
|
94
|
+
python -m embedpick
|
|
95
|
+
|
|
96
|
+
# your own data
|
|
97
|
+
python -m embedpick --corpus my_docs.csv --queries my_queries.csv
|
|
98
|
+
|
|
99
|
+
# pick models, evaluate top 10, show per-query detail
|
|
100
|
+
python -m embedpick --models intfloat/multilingual-e5-base -k 10 --verbose
|
|
101
|
+
|
|
102
|
+
# save the table
|
|
103
|
+
python -m embedpick --out results.csv
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
`python -m embedpick --help` lists every flag.
|
|
107
|
+
|
|
108
|
+
## Development
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
pip install -r requirements-dev.txt
|
|
112
|
+
python -m pytest
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Tests do not download any models, so they run in seconds. Make sure your
|
|
116
|
+
virtual environment is active first — a missing `rank_bm25` in the test output
|
|
117
|
+
usually means you installed into the system Python by mistake.
|
|
118
|
+
|
|
119
|
+
## Data format
|
|
120
|
+
|
|
121
|
+
**corpus.csv** — the documents to search over:
|
|
122
|
+
|
|
123
|
+
```csv
|
|
124
|
+
id,text
|
|
125
|
+
1,Kargom hala elime ulaşmadı
|
|
126
|
+
2,Sipariş takip numaram sistemde görünmüyor
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**queries.csv** — queries and their correct answers:
|
|
130
|
+
|
|
131
|
+
```csv
|
|
132
|
+
query,relevant_ids
|
|
133
|
+
kargo gecikmesi,1;5
|
|
134
|
+
paketim nerede,1;2
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Separate multiple correct answers with `;`. A query can have any number of
|
|
138
|
+
them. Files must be UTF-8.
|
|
139
|
+
|
|
140
|
+
Twenty to thirty queries is usually enough to see real differences. Include
|
|
141
|
+
queries that share no words with their correct answer — those are the ones
|
|
142
|
+
that test semantic matching rather than lucky keyword overlap.
|
|
143
|
+
|
|
144
|
+
## Metrics
|
|
145
|
+
|
|
146
|
+
**recall@k** — of the correct documents, what fraction appeared in the top k.
|
|
147
|
+
Use this when you show the user a list of results.
|
|
148
|
+
|
|
149
|
+
**MRR@k** — 1.0 if the first correct answer is at rank 1, 0.5 at rank 2, 0.33
|
|
150
|
+
at rank 3, averaged over queries. Use this when you show one answer, or feed
|
|
151
|
+
the top hit to an LLM.
|
|
152
|
+
|
|
153
|
+
These can disagree, and the disagreement is informative. In an earlier run
|
|
154
|
+
e5 had higher recall than MiniLM but lower MRR: it found more, but ranked
|
|
155
|
+
worse. Which model is "better" depends on what you are building.
|
|
156
|
+
|
|
157
|
+
**docs/sec** — indexing throughput. Machine-dependent, see the caveat above.
|
|
158
|
+
|
|
159
|
+
**dim** and **index_mb** — vector width and index size. A 768-dimensional
|
|
160
|
+
model needs twice the memory of a 384-dimensional one at the same corpus size.
|
|
161
|
+
|
|
162
|
+
## Label checking
|
|
163
|
+
|
|
164
|
+
Two things happen automatically:
|
|
165
|
+
|
|
166
|
+
- If `relevant_ids` points at a document id that is not in the corpus, the run
|
|
167
|
+
stops and names the query. Otherwise that query silently scores zero forever
|
|
168
|
+
and you blame the model.
|
|
169
|
+
- If *no* method — including BM25 — finds a query's answer, embedpick flags it.
|
|
170
|
+
When everything fails on the same query, the label is usually wrong. This
|
|
171
|
+
caught a mislabelled query during development.
|
|
172
|
+
|
|
173
|
+
## Sample dataset
|
|
174
|
+
|
|
175
|
+
`embedpick/sample_data/` contains 72 synthetic Turkish customer-support messages across nine
|
|
176
|
+
themes (shipping, returns, payment, account, product quality, promotions,
|
|
177
|
+
warranty, order management, support) and 26 labelled queries. It is written,
|
|
178
|
+
not scraped, so it carries no licensing or privacy constraints.
|
|
179
|
+
|
|
180
|
+
Several queries deliberately share no vocabulary with their answers — for
|
|
181
|
+
example `güvenlik ihlali şüphesi` ("suspected security breach") maps to
|
|
182
|
+
*"Hesabıma başkası girmiş olabilir"* ("someone else may have accessed my
|
|
183
|
+
account"). Keyword search cannot solve these; that is the point.
|
|
184
|
+
|
|
185
|
+
## Caveats
|
|
186
|
+
|
|
187
|
+
- Timing on a 72-document corpus is close to measurement noise, particularly
|
|
188
|
+
for BM25. Treat the throughput column as indicative.
|
|
189
|
+
- BM25 and neural encoders scale differently. The ratio at 72 documents is not
|
|
190
|
+
the ratio at 100,000.
|
|
191
|
+
- Results are from one machine, CPU only, no GPU.
|
|
192
|
+
|
|
193
|
+
## Roadmap
|
|
194
|
+
|
|
195
|
+
- Hybrid retrieval (BM25 + embedding score fusion) as a fourth row
|
|
196
|
+
- Hub detection: flag documents that surface for nearly every query
|
|
197
|
+
- Markdown report export
|
|
198
|
+
- Optional Gradio interface
|
|
199
|
+
|
|
200
|
+
## License
|
|
201
|
+
|
|
202
|
+
MIT
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""Benchmark akışını yürütür ve sonuç tablosunu üretir."""
|
|
2
|
+
|
|
3
|
+
import pandas as pd
|
|
4
|
+
|
|
5
|
+
from .metrics import consensus_failures, recall_at_k, score_all
|
|
6
|
+
from .retrievers import BM25Retriever, EmbeddingRetriever
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def run_benchmark(
|
|
10
|
+
corpus,
|
|
11
|
+
queries,
|
|
12
|
+
model_names,
|
|
13
|
+
k=5,
|
|
14
|
+
repeats=5,
|
|
15
|
+
include_baseline=True,
|
|
16
|
+
use_presets=True,
|
|
17
|
+
verbose=False,
|
|
18
|
+
):
|
|
19
|
+
retrievers = []
|
|
20
|
+
if include_baseline:
|
|
21
|
+
retrievers.append(BM25Retriever())
|
|
22
|
+
|
|
23
|
+
for name in model_names:
|
|
24
|
+
if use_presets:
|
|
25
|
+
retrievers.append(EmbeddingRetriever(name))
|
|
26
|
+
else:
|
|
27
|
+
# Önekler kapalı: modelin belgelenmiş kullanım kuralı yok sayılıyor.
|
|
28
|
+
# Yanlış yapılandırmanın maliyetini ölçmek için.
|
|
29
|
+
retrievers.append(EmbeddingRetriever(name, query_prefix="", doc_prefix=""))
|
|
30
|
+
|
|
31
|
+
satirlar = []
|
|
32
|
+
sorgu_bazli = {}
|
|
33
|
+
|
|
34
|
+
for retriever in retrievers:
|
|
35
|
+
print(f"\n-> {retriever.label}")
|
|
36
|
+
maliyet = retriever.index(corpus, repeats)
|
|
37
|
+
bulunanlar = retriever.search(queries["query"].tolist(), k)
|
|
38
|
+
|
|
39
|
+
recall, mrr = score_all(bulunanlar, queries, k, verbose=verbose)
|
|
40
|
+
|
|
41
|
+
sorgu_bazli[retriever.label] = {
|
|
42
|
+
q: recall_at_k(bulunan, dogru, k)
|
|
43
|
+
for q, bulunan, dogru in zip(
|
|
44
|
+
queries["query"], bulunanlar, queries["relevant_ids"]
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
satirlar.append(
|
|
49
|
+
{
|
|
50
|
+
"model": retriever.label,
|
|
51
|
+
f"recall@{k}": round(recall, 3),
|
|
52
|
+
f"mrr@{k}": round(mrr, 3),
|
|
53
|
+
"docs_per_sec": round(maliyet["docs_per_sec"], 1),
|
|
54
|
+
"dim": maliyet["dim"],
|
|
55
|
+
"index_mb": round(maliyet["index_mb"], 2),
|
|
56
|
+
}
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
_add_relative_speed(satirlar)
|
|
60
|
+
return pd.DataFrame(satirlar), sorgu_bazli
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _add_relative_speed(satirlar):
|
|
64
|
+
"""Mutlak hız makineye ve o anki işlemci durumuna göre kat kat oynar.
|
|
65
|
+
Aynı çalıştırma içindeki oranlar ise sabit kalır."""
|
|
66
|
+
sinirsel = [r for r in satirlar if r["dim"] > 0]
|
|
67
|
+
if not sinirsel:
|
|
68
|
+
return
|
|
69
|
+
|
|
70
|
+
en_hizli = max(r["docs_per_sec"] for r in sinirsel)
|
|
71
|
+
for r in satirlar:
|
|
72
|
+
r["rel_speed"] = f"{r['docs_per_sec'] / en_hizli:.2f}x" if r["dim"] > 0 else "-"
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def report(df, sorgu_bazli, k):
|
|
76
|
+
print()
|
|
77
|
+
print(df.to_string(index=False))
|
|
78
|
+
|
|
79
|
+
print(
|
|
80
|
+
"\nNot: docs_per_sec makineye ve o anki işlemci durumuna göre değişir."
|
|
81
|
+
"\n Makineler arası kıyas için rel_speed sütununu kullanın"
|
|
82
|
+
"\n (en hızlı sinirsel model = 1.00x)."
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
# Tek yöntem varken "hiçbiri bulamadı" demek anlamsız; uyarının değeri
|
|
86
|
+
# birden fazla yöntemin aynı sorguda batmasından geliyor.
|
|
87
|
+
ortak = consensus_failures(sorgu_bazli) if len(sorgu_bazli) > 1 else []
|
|
88
|
+
if ortak:
|
|
89
|
+
print(
|
|
90
|
+
f"\nHiçbir yöntemin ilk {k} sonuçta bulamadığı sorgular:"
|
|
91
|
+
)
|
|
92
|
+
for q in ortak:
|
|
93
|
+
print(f" - {q}")
|
|
94
|
+
print(
|
|
95
|
+
" Bütün yöntemler aynı sorguda batıyorsa sorun genelde modelde değil,"
|
|
96
|
+
"\n etiketlerdedir. Bu sorguların relevant_ids değerlerini gözden geçirin."
|
|
97
|
+
)
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""embedpick komut satırı arayüzü."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import sys
|
|
5
|
+
from importlib import resources
|
|
6
|
+
|
|
7
|
+
from .benchmark import report, run_benchmark
|
|
8
|
+
from .data import load_corpus, load_queries, validate
|
|
9
|
+
from .retrievers import DEFAULT_MODELS
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def sample_path(name):
|
|
13
|
+
"""Pakete gömülü örnek veri dosyasının yolu.
|
|
14
|
+
|
|
15
|
+
Paket pip ile kurulduğunda çalışma dizininde data/ klasörü olmaz,
|
|
16
|
+
o yüzden varsayılan veri kurulum dizininden okunuyor.
|
|
17
|
+
"""
|
|
18
|
+
return str(resources.files("embedpick") / "sample_data" / name)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def build_parser():
|
|
22
|
+
p = argparse.ArgumentParser(
|
|
23
|
+
prog="embedpick",
|
|
24
|
+
description=(
|
|
25
|
+
"Kendi verinizde embedding modellerini kıyaslayın. "
|
|
26
|
+
"Kalite, hız ve bellek maliyetini tek tabloda gösterir."
|
|
27
|
+
),
|
|
28
|
+
)
|
|
29
|
+
p.add_argument(
|
|
30
|
+
"--corpus",
|
|
31
|
+
help="Aranacak dokümanlar (sütunlar: id,text). "
|
|
32
|
+
"Verilmezse pakete gömülü örnek veri kullanılır.",
|
|
33
|
+
)
|
|
34
|
+
p.add_argument(
|
|
35
|
+
"--queries",
|
|
36
|
+
help="Sorgular ve doğru cevaplar (sütunlar: query,relevant_ids). "
|
|
37
|
+
"Verilmezse pakete gömülü örnek veri kullanılır.",
|
|
38
|
+
)
|
|
39
|
+
p.add_argument(
|
|
40
|
+
"--models",
|
|
41
|
+
nargs="+",
|
|
42
|
+
default=DEFAULT_MODELS,
|
|
43
|
+
help="Hugging Face model adları, boşlukla ayrılmış",
|
|
44
|
+
)
|
|
45
|
+
p.add_argument("-k", type=int, default=5, help="İlk kaç sonuç değerlendirilsin")
|
|
46
|
+
p.add_argument(
|
|
47
|
+
"--repeats", type=int, default=5, help="Hız ölçümü kaç kez tekrarlansın"
|
|
48
|
+
)
|
|
49
|
+
p.add_argument(
|
|
50
|
+
"--no-baseline",
|
|
51
|
+
action="store_true",
|
|
52
|
+
help="BM25 kelime bazlı baseline'ı atla (önerilmez)",
|
|
53
|
+
)
|
|
54
|
+
p.add_argument(
|
|
55
|
+
"--no-presets",
|
|
56
|
+
action="store_true",
|
|
57
|
+
help=(
|
|
58
|
+
"Bilinen model öneklerini uygulama. e5 gibi önek bekleyen modeller "
|
|
59
|
+
"düşük skor alır; yanlış yapılandırmanın maliyetini ölçmek için."
|
|
60
|
+
),
|
|
61
|
+
)
|
|
62
|
+
p.add_argument(
|
|
63
|
+
"--verbose",
|
|
64
|
+
action="store_true",
|
|
65
|
+
help="Her sorgunun sonucunu tek tek yazdır",
|
|
66
|
+
)
|
|
67
|
+
p.add_argument("--out", help="Sonuç tablosunu CSV olarak kaydet")
|
|
68
|
+
return p
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def main(argv=None):
|
|
72
|
+
args = build_parser().parse_args(argv)
|
|
73
|
+
|
|
74
|
+
corpus_path = args.corpus or sample_path("corpus.csv")
|
|
75
|
+
queries_path = args.queries or sample_path("queries.csv")
|
|
76
|
+
|
|
77
|
+
if args.corpus is None and args.queries is None:
|
|
78
|
+
print("Kendi veriniz verilmedi, pakete gömülü örnek veri kullanılıyor.")
|
|
79
|
+
|
|
80
|
+
try:
|
|
81
|
+
corpus = load_corpus(corpus_path)
|
|
82
|
+
queries = load_queries(queries_path)
|
|
83
|
+
validate(corpus, queries)
|
|
84
|
+
except (FileNotFoundError, ValueError) as exc:
|
|
85
|
+
print(f"Hata: {exc}", file=sys.stderr)
|
|
86
|
+
return 1
|
|
87
|
+
|
|
88
|
+
print(f"{len(corpus)} doküman, {len(queries)} sorgu, k={args.k}")
|
|
89
|
+
|
|
90
|
+
df, sorgu_bazli = run_benchmark(
|
|
91
|
+
corpus,
|
|
92
|
+
queries,
|
|
93
|
+
model_names=args.models,
|
|
94
|
+
k=args.k,
|
|
95
|
+
repeats=args.repeats,
|
|
96
|
+
include_baseline=not args.no_baseline,
|
|
97
|
+
use_presets=not args.no_presets,
|
|
98
|
+
verbose=args.verbose,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
report(df, sorgu_bazli, args.k)
|
|
102
|
+
|
|
103
|
+
if args.out:
|
|
104
|
+
df.to_csv(args.out, index=False, encoding="utf-8")
|
|
105
|
+
print(f"\nTablo kaydedildi: {args.out}")
|
|
106
|
+
|
|
107
|
+
return 0
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
if __name__ == "__main__":
|
|
111
|
+
raise SystemExit(main())
|