AdamTT commited on
Commit
08e7590
·
verified ·
1 Parent(s): fd087f2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +183 -59
app.py CHANGED
@@ -1,9 +1,16 @@
 
 
 
1
  import gradio as gr
 
 
 
 
2
  from huggingface_hub import HfApi
3
 
4
  api = HfApi()
5
 
6
- # Minimalnie: twardo wpisane, stabilne propozycje (CPU-friendly) + ewentualnie dynamiczne "bonusy"
7
  RECOMMENDATIONS = {
8
  "instruction": [
9
  ("google/flan-t5-small", "Lekki text2text, dobry na CPU do poleceń i krótkich odpowiedzi."),
@@ -13,39 +20,150 @@ RECOMMENDATIONS = {
13
  "qa": [
14
  ("distilbert/distilbert-base-cased-distilled-squad", "Szybki QA extractive na CPU; klasyczny wybór."),
15
  ("distilbert/distilbert-base-uncased-distilled-squad", "Popularny model SQuAD; dobry default."),
16
- ("deepset/bert-base-cased-squad2", "SQuAD2; potrafi częściej zwrócić 'brak odpowiedzi'.")
17
  ],
18
  "embeddings": [
19
- ("sentence-transformers/all-MiniLM-L6-v2", "Bardzo popularny do similarity search; szybki."),
20
- ("intfloat/e5-small-v2", "Silny embedding do wyszukiwania; dobry kompromis."),
21
  ("sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2", "Multilingual (lepszy przy PL/mix).")
22
  ],
23
  }
24
 
25
- def hub_bonus_models(pipeline_tag: str, limit: int = 5):
26
- """
27
- Opcjonalnie: dociągaj popularne modele z Hub.
28
- Uwaga: to zapytania sieciowe; jeśli wolisz offline, usuń ten fragment.
29
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  try:
31
- # huggingface_hub pozwala listować modele z filtrami (HfApi.list_models). :contentReference[oaicite:1]{index=1}
32
  models = api.list_models(filter=pipeline_tag, sort="downloads", direction=-1, limit=limit)
33
  out = []
34
  for m in models:
35
- if m.modelId:
36
- out.append(m.modelId)
 
 
 
37
  return out
38
  except Exception:
39
  return []
40
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  def recommend(task, has_docs, language, cpu_only, priority):
42
- # Prosta logika decyzyjna
43
  if task == "Semantyczne podobieństwo / duplikaty / wyszukiwanie":
44
  model_type = "embeddings"
45
  why = (
46
- "Chcesz porównywać znaczenie wpisów i wykrywać duplikaty. "
47
- "Do tego używa się embeddingów (wektorów) i miary podobieństwa (np. cosinus). "
48
- "To nie jest generowanie tekstu."
49
  )
50
  pipeline_tag = "sentence-similarity"
51
  elif task == "Odpowiedzi na pytania z dokumentu (tekst wejściowy)":
@@ -58,73 +176,79 @@ def recommend(task, has_docs, language, cpu_only, priority):
58
  else:
59
  model_type = "instruction"
60
  why = (
61
- "Chcesz odpowiedzi 'z polecenia' (chat/wyjaśnianie/streszczanie). "
62
  "Modele instrukcyjne są dostrajane do wykonywania instrukcji."
63
  )
64
  pipeline_tag = "text-generation"
65
 
66
- # Zbuduj wynik: min. 3
67
  recs = RECOMMENDATIONS[model_type].copy()
68
 
69
- # Bonus: dociągnij popularne modele z Hub (nie obowiązkowe)
70
- bonus = hub_bonus_models(pipeline_tag, limit=5)
71
- # Usuń te, które już mamy
72
  existing = {mid for mid, _ in recs}
73
  bonus = [m for m in bonus if m not in existing]
74
- # Dodaj 0–2 bonusy, ale nie kosztem czytelności
 
75
  for m in bonus[:2]:
76
- recs.append((m, "Popularny model z Hub (dobrany po tagu i pobraniach)."))
77
 
78
- # Sformatuj odpowiedź
79
  lines = []
80
  lines.append(f"Rekomendowany typ modelu: {model_type}")
81
  lines.append("")
82
  lines.append("Uzasadnienie:")
83
  lines.append(f"- {why}")
84
  lines.append("")
85
- lines.append("Minimum 3 pasujące modele:")
86
  for mid, note in recs[:5]:
87
  lines.append(f"- {mid} — {note}")
88
 
89
- # Dodatkowe wskazówki „jak użyć” dla embeddings
90
  if model_type == "embeddings":
91
  lines.append("")
92
- lines.append("Jak użyć do duplikatów (zarys):")
93
- lines.append("- Policz embedding dla każdego wpisu.")
94
- lines.append("- Porównuj podobieństwo cosinusowe.")
95
- lines.append("- Ustal próg (np. 0.85–0.95) i grupuj podobne wpisy.")
96
- lines.append("- W każdej grupie zostaw 1 rekord, resztę oznacz jako duplikaty.")
97
-
98
  if language in ["PL", "Mieszany"]:
99
- lines.append("")
100
- lines.append("Uwaga językowa:")
101
- lines.append("- Przy PL lub mieszanych językach preferuj model multilingual z listy.")
102
  return "\n".join(lines)
103
 
 
 
 
104
  with gr.Blocks(title="Model Fit Finder (CPU)") as demo:
105
- gr.Markdown("# Model Fit Finder\nDobiera typ modelu i podaje konkretne propozycje (CPU).")
106
-
107
- task = gr.Dropdown(
108
- choices=[
109
- "Chat / polecenia / generowanie",
110
- "Odpowiedzi na pytania z dokumentu (tekst wejściowy)",
111
- "Semantyczne podobieństwo / duplikaty / wyszukiwanie",
112
- ],
113
- value="Semantyczne podobieństwo / duplikaty / wyszukiwanie",
114
- label="Co chcesz zrobić?"
115
- )
116
- has_docs = gr.Radio(choices=["Tak", "Nie"], value="Tak", label="Czy masz własne dokumenty/teksty do analizy?")
117
- language = gr.Radio(choices=["EN", "PL", "Mieszany"], value="Mieszany", label="Język danych")
118
- cpu_only = gr.Checkbox(value=True, label="CPU only")
119
- priority = gr.Radio(choices=["Szybkość", "Jakość"], value="Szybkość", label="Priorytet")
120
-
121
- btn = gr.Button("Zarekomenduj")
122
- out = gr.Textbox(lines=18, label="Wynik")
123
-
124
- btn.click(
125
- fn=recommend,
126
- inputs=[task, has_docs, language, cpu_only, priority],
127
- outputs=[out],
128
- )
 
 
 
 
 
 
 
 
 
 
 
 
129
 
130
  demo.launch()
 
1
+ import time
2
+ from typing import List, Tuple, Dict
3
+
4
  import gradio as gr
5
+ import numpy as np
6
+ from sklearn.metrics.pairwise import cosine_similarity
7
+ import torch
8
+ from transformers import AutoTokenizer, AutoModel
9
  from huggingface_hub import HfApi
10
 
11
  api = HfApi()
12
 
13
+ # Twarde, stabilne rekomendacje (min. 3)
14
  RECOMMENDATIONS = {
15
  "instruction": [
16
  ("google/flan-t5-small", "Lekki text2text, dobry na CPU do poleceń i krótkich odpowiedzi."),
 
20
  "qa": [
21
  ("distilbert/distilbert-base-cased-distilled-squad", "Szybki QA extractive na CPU; klasyczny wybór."),
22
  ("distilbert/distilbert-base-uncased-distilled-squad", "Popularny model SQuAD; dobry default."),
23
+ ("deepset/bert-base-cased-squad2", "SQuAD2; częściej zwraca 'brak odpowiedzi'.")
24
  ],
25
  "embeddings": [
26
+ ("sentence-transformers/all-MiniLM-L6-v2", "Popularny do similarity search; szybki na CPU."),
27
+ ("intfloat/e5-small-v2", "Mocny embedding do wyszukiwania; dobry kompromis."),
28
  ("sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2", "Multilingual (lepszy przy PL/mix).")
29
  ],
30
  }
31
 
32
+ # -----------------------
33
+ # A) Hub bonus: cache + filtr językowy
34
+ # -----------------------
35
+ _HUB_CACHE: Dict[Tuple[str, str], Tuple[float, List[str]]] = {}
36
+ CACHE_TTL_SEC = 6 * 60 * 60 # 6 godzin
37
+
38
+ def _language_tag_predicate(tags: List[str], language: str) -> bool:
39
+ if language == "Mieszany":
40
+ return True
41
+ # Tagowanie językowe na Hubie nie jest 100% spójne, więc sprawdzamy kilka wariantów.
42
+ lang = language.lower()
43
+ candidates = {lang, f"language:{lang}", f"lang:{lang}"}
44
+ tags_lower = {t.lower() for t in (tags or [])}
45
+ return any(c in tags_lower for c in candidates)
46
+
47
+ def hub_bonus_models(pipeline_tag: str, language: str, limit: int = 12) -> List[str]:
48
+ key = (pipeline_tag, language)
49
+ now = time.time()
50
+
51
+ if key in _HUB_CACHE:
52
+ ts, cached = _HUB_CACHE[key]
53
+ if now - ts < CACHE_TTL_SEC:
54
+ return cached
55
+
56
  try:
57
+ # list_models: filtrujemy po pipeline tagu i sortujemy po pobraniach (popularność).
58
  models = api.list_models(filter=pipeline_tag, sort="downloads", direction=-1, limit=limit)
59
  out = []
60
  for m in models:
61
+ mid = getattr(m, "modelId", None)
62
+ tags = getattr(m, "tags", []) or []
63
+ if mid and _language_tag_predicate(tags, language):
64
+ out.append(mid)
65
+ _HUB_CACHE[key] = (now, out)
66
  return out
67
  except Exception:
68
  return []
69
 
70
+ # -----------------------
71
+ # B) Embeddingi lokalnie (CPU): mean pooling
72
+ # -----------------------
73
+ _MODEL_CACHE: Dict[str, Tuple[AutoTokenizer, AutoModel]] = {}
74
+
75
+ def _load_encoder(model_id: str):
76
+ if model_id in _MODEL_CACHE:
77
+ return _MODEL_CACHE[model_id]
78
+ tok = AutoTokenizer.from_pretrained(model_id)
79
+ mdl = AutoModel.from_pretrained(model_id)
80
+ mdl.eval()
81
+ _MODEL_CACHE[model_id] = (tok, mdl)
82
+ return tok, mdl
83
+
84
+ @torch.no_grad()
85
+ def embed_texts(model_id: str, texts: List[str], batch_size: int = 16) -> np.ndarray:
86
+ tok, mdl = _load_encoder(model_id)
87
+
88
+ all_vecs = []
89
+ for i in range(0, len(texts), batch_size):
90
+ batch = texts[i:i+batch_size]
91
+ enc = tok(batch, padding=True, truncation=True, return_tensors="pt")
92
+ out = mdl(**enc)
93
+
94
+ # Mean pooling po tokenach z maską attention
95
+ token_emb = out.last_hidden_state # [B, T, H]
96
+ mask = enc["attention_mask"].unsqueeze(-1).expand(token_emb.size()).float()
97
+ summed = torch.sum(token_emb * mask, dim=1)
98
+ counts = torch.clamp(mask.sum(dim=1), min=1e-9)
99
+ mean_pooled = summed / counts
100
+
101
+ # Normalizacja L2 pomaga dla cosine similarity
102
+ normed = torch.nn.functional.normalize(mean_pooled, p=2, dim=1)
103
+ all_vecs.append(normed.cpu().numpy())
104
+
105
+ return np.vstack(all_vecs)
106
+
107
+ def deduplicate_notes(model_id: str, raw_notes: str, threshold: float) -> str:
108
+ notes = [n.strip() for n in raw_notes.splitlines() if n.strip()]
109
+ if len(notes) < 2:
110
+ return "Wklej co najmniej 2 wpisy (po jednej linijce)."
111
+
112
+ vecs = embed_texts(model_id, notes)
113
+ sim = cosine_similarity(vecs)
114
+
115
+ # Grupowanie prostym union-find (spójne składowe przy sim >= threshold)
116
+ parent = list(range(len(notes)))
117
+
118
+ def find(x):
119
+ while parent[x] != x:
120
+ parent[x] = parent[parent[x]]
121
+ x = parent[x]
122
+ return x
123
+
124
+ def union(a, b):
125
+ ra, rb = find(a), find(b)
126
+ if ra != rb:
127
+ parent[rb] = ra
128
+
129
+ for i in range(len(notes)):
130
+ for j in range(i + 1, len(notes)):
131
+ if sim[i, j] >= threshold:
132
+ union(i, j)
133
+
134
+ groups: Dict[int, List[int]] = {}
135
+ for idx in range(len(notes)):
136
+ r = find(idx)
137
+ groups.setdefault(r, []).append(idx)
138
+
139
+ # Interesują nas grupy z duplikatami (rozmiar > 1)
140
+ dup_groups = [g for g in groups.values() if len(g) > 1]
141
+ dup_groups.sort(key=len, reverse=True)
142
+
143
+ if not dup_groups:
144
+ return f"Brak duplikatów przy progu {threshold:.2f}."
145
+
146
+ lines = []
147
+ lines.append(f"Znalezione grupy podobnych wpisów (próg {threshold:.2f}):")
148
+ lines.append("")
149
+ for gi, g in enumerate(dup_groups, start=1):
150
+ lines.append(f"Grupa {gi} (rozmiar {len(g)}):")
151
+ for idx in g:
152
+ lines.append(f"- {notes[idx]}")
153
+ lines.append("Sugestia: zostaw 1 wpis, pozostałe oznacz jako duplikaty.")
154
+ lines.append("")
155
+
156
+ return "\n".join(lines).strip()
157
+
158
+ # -----------------------
159
+ # Doradca modeli
160
+ # -----------------------
161
  def recommend(task, has_docs, language, cpu_only, priority):
 
162
  if task == "Semantyczne podobieństwo / duplikaty / wyszukiwanie":
163
  model_type = "embeddings"
164
  why = (
165
+ "Zadanie polega na porównaniu znaczenia wpisów i wykryciu duplikatów. "
166
+ "Najlepsze modele embeddingowe + podobieństwo cosinusowe (sentence similarity)."
 
167
  )
168
  pipeline_tag = "sentence-similarity"
169
  elif task == "Odpowiedzi na pytania z dokumentu (tekst wejściowy)":
 
176
  else:
177
  model_type = "instruction"
178
  why = (
179
+ "Chcesz odpowiedzi sterowane poleceniem (chat/wyjaśnianie/streszczanie). "
180
  "Modele instrukcyjne są dostrajane do wykonywania instrukcji."
181
  )
182
  pipeline_tag = "text-generation"
183
 
 
184
  recs = RECOMMENDATIONS[model_type].copy()
185
 
186
+ # Bonus: dociągamy popularne modele z Hub (filtrowane po języku)
187
+ bonus = hub_bonus_models(pipeline_tag, language, limit=12)
 
188
  existing = {mid for mid, _ in recs}
189
  bonus = [m for m in bonus if m not in existing]
190
+
191
+ # Dodajemy do 2 bonusów, żeby nie zalać użytkownika
192
  for m in bonus[:2]:
193
+ recs.append((m, "Popularny model z Hub (dobrany po tagu zadania, sort po pobraniach)."))
194
 
 
195
  lines = []
196
  lines.append(f"Rekomendowany typ modelu: {model_type}")
197
  lines.append("")
198
  lines.append("Uzasadnienie:")
199
  lines.append(f"- {why}")
200
  lines.append("")
201
+ lines.append("Modele (min. 3):")
202
  for mid, note in recs[:5]:
203
  lines.append(f"- {mid} — {note}")
204
 
 
205
  if model_type == "embeddings":
206
  lines.append("")
207
+ lines.append("Zastosowanie do duplikatów (skrót): embeddingi -> cosine similarity -> próg -> grupy.")
 
 
 
 
 
208
  if language in ["PL", "Mieszany"]:
209
+ lines.append("Wskazówka: preferuj model multilingual przy PL/mix języków.")
210
+
 
211
  return "\n".join(lines)
212
 
213
+ # -----------------------
214
+ # UI (2 zakładki)
215
+ # -----------------------
216
  with gr.Blocks(title="Model Fit Finder (CPU)") as demo:
217
+ gr.Markdown("# Model Fit Finder (CPU)\nDobiera typ modelu i pokazuje minimum 3 propozycje. Zawiera też deduplikację embeddingami.")
218
+
219
+ with gr.Tab("Doradca modeli"):
220
+ task = gr.Dropdown(
221
+ choices=[
222
+ "Chat / polecenia / generowanie",
223
+ "Odpowiedzi na pytania z dokumentu (tekst wejściowy)",
224
+ "Semantyczne podobieństwo / duplikaty / wyszukiwanie",
225
+ ],
226
+ value="Semantyczne podobieństwo / duplikaty / wyszukiwanie",
227
+ label="Co chcesz zrobić?"
228
+ )
229
+ has_docs = gr.Radio(choices=["Tak", "Nie"], value="Tak", label="Czy masz własne dokumenty/teksty do analizy?")
230
+ language = gr.Radio(choices=["EN", "PL", "Mieszany"], value="Mieszany", label="Język danych")
231
+ cpu_only = gr.Checkbox(value=True, label="CPU only")
232
+ priority = gr.Radio(choices=["Szybkość", "Jakość"], value="Szybkość", label="Priorytet")
233
+
234
+ btn = gr.Button("Zarekomenduj")
235
+ out = gr.Textbox(lines=18, label="Wynik")
236
+ btn.click(fn=recommend, inputs=[task, has_docs, language, cpu_only, priority], outputs=[out])
237
+
238
+ with gr.Tab("Deduplikacja wpisów (embeddingi)"):
239
+ gr.Markdown(
240
+ "Wklej wpisy (po jednej linijce). Space policzy embeddingi lokalnie na CPU i pogrupuje duplikaty.\n"
241
+ "Uwaga: przy bardzo krótkich, technicznych wpisach warto testować próg w zakresie 0.85–0.95."
242
+ )
243
+ embed_model = gr.Dropdown(
244
+ choices=[m for m, _ in RECOMMENDATIONS["embeddings"]],
245
+ value=RECOMMENDATIONS["embeddings"][0][0],
246
+ label="Model embeddingowy"
247
+ )
248
+ threshold = gr.Slider(0.70, 0.99, value=0.90, step=0.01, label="Próg podobieństwa (cosine)")
249
+ notes = gr.Textbox(lines=12, label="Wpisy (1 linia = 1 wpis)")
250
+ run = gr.Button("Wykryj duplikaty")
251
+ dup_out = gr.Textbox(lines=18, label="Grupy duplikatów")
252
+ run.click(fn=deduplicate_notes, inputs=[embed_model, notes, threshold], outputs=[dup_out])
253
 
254
  demo.launch()