ErzhanAb commited on
Commit
fb9ddca
·
verified ·
1 Parent(s): 783893f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +28 -54
app.py CHANGED
@@ -14,32 +14,25 @@ _WS_RE_TFIDF = re.compile(r'\s+')
14
 
15
  def clean_text(s: str) -> str:
16
  """Эта функция нужна для корректной загрузки model.joblib."""
17
- if not isinstance(s, str):
18
- s = str(s)
19
  s = unescape(s).lower()
20
- s = _URL_RE_TFIDF.sub(' <url> ', s)
21
- s = _TAG_RE_TFIDF.sub(' <tag> ', s)
22
- s = _NUM_RE_TFIDF.sub(' <num> ', s)
23
- s = s.replace('\n', ' ').replace('\t', ' ')
24
  s = _WS_RE_TFIDF.sub(' ', s).strip()
25
  return s
26
 
27
  # ==============================
28
  # 2. ЗАГРУЗКА МОДЕЛЕЙ
29
  # ==============================
30
- # TF-IDF + LR
31
  PIPE, PIPE_PATH = None, "model.joblib"
32
  try:
33
  import joblib
34
- if os.path.exists(PIPE_PATH):
35
- PIPE = joblib.load(PIPE_PATH)
36
- else:
37
- print(f"[WARN] TF-IDF model not found at {PIPE_PATH}")
38
  except Exception as e:
39
  print(f"[WARN] Не удалось инициализировать TF-IDF: {e}")
40
  PIPE = None
41
 
42
- # Transformer (ruBERT-tiny2)
43
  TRANSFORMER = {"model": None, "tokenizer": None, "device": "cpu"}
44
  try:
45
  import torch
@@ -88,17 +81,14 @@ def clean_and_stem(s: str) -> str:
88
  # 5. ФУНКЦИИ ИНФЕРЕНСА
89
  # ==============================
90
  def infer_tfidf(text: str):
91
- if PIPE is None:
92
- return None, f"TF-IDF модель не загружена (ожидался файл '{PIPE_PATH}'). Проверьте логи."
93
  try:
94
  proba = PIPE.predict_proba([text])[0, 1]
95
  return float(np.clip(proba, 0.0, 1.0)), None
96
- except Exception as e:
97
- return None, f"Ошибка инференса TF-IDF: {e}"
98
 
99
  def infer_transformer(text: str):
100
- if TRANSFORMER["model"] is None:
101
- return None, "Модель ruBERT не загружена."
102
  import torch
103
  text_prep = clean_and_stem(text)
104
  if not text_prep: return 0.0, None
@@ -109,14 +99,13 @@ def infer_transformer(text: str):
109
  logits = TRANSFORMER["model"](**tok).logits
110
  proba = torch.softmax(logits, dim=1)[0, 1].detach().cpu().item()
111
  return float(proba), None
112
- except Exception as e:
113
- return None, f"Ошибка инференса ruBERT: {e}"
114
 
115
- # ИЗМЕНЕНО: теперь возвращает 3 значения, включая вердикт
116
  def predict(model_name: str, comment: str, threshold: float):
117
  comment = (comment or "").strip()
118
  if not comment:
119
- return "—", {"Токсичный": 0.0, "Не токсичный": 1.0}, "Введите текст для анализа"
120
 
121
  if "ruBERT" in model_name:
122
  p_toxic, err = infer_transformer(comment)
@@ -124,23 +113,16 @@ def predict(model_name: str, comment: str, threshold: float):
124
  p_toxic, err = infer_tfidf(comment)
125
 
126
  if err is not None or p_toxic is None:
127
- dist = {"Токсичный": 0.0, "Не токсичный": 1.0}
128
- expl = f"Модель: **{model_name}**\n\n⚠️ {err}"
129
- return "Ошибка", dist, expl
130
 
131
- # ГЛАВНОЕ ИЗМЕНЕНИЕ: вердикт определяется порогом
132
  verdict = "Токсичный" if p_toxic >= threshold else "Не токсичный"
133
  dist = {"Токсичный": p_toxic, "Не токсичный": 1 - p_toxic}
134
- expl = (
135
- f"**Модель:** {model_name}\n\n"
136
- f"**Вероятность токсичности:** `{p_toxic:.3f}`\n\n"
137
- f"**Порог:** `{threshold:.2f}`"
138
- )
139
- return verdict, dist, expl
140
-
141
- # ИЗМЕНЕНО: добавлено начальное значение для нового поля вердикта
142
  def clear_all():
143
- return "ruBERT-tiny2 (трансформер)", "", DEFAULT_THRESHOLD, "—", None, "—"
144
 
145
  # ==============================
146
  # 6. ИНТЕРФЕЙС (UI)
@@ -161,32 +143,24 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"), cs
161
 
162
  with gr.Row(variant="panel"):
163
  with gr.Column(scale=2):
164
- model_sel = gr.Dropdown(
165
- ["ruBERT-tiny2 (трансформер)", "TF-IDF + Логистическая регрессия"],
166
- value="ruBERT-tiny2 (трансформер)", label="Модель для анализа"
167
- )
168
- comment_input = gr.Textbox(
169
- label="Текст комментария", lines=6, placeholder="Напишите что-нибудь…"
170
- )
171
- thr = gr.Slider(
172
- label="Порог классификации", minimum=0.0, maximum=1.0,
173
- value=DEFAULT_THRESHOLD, step=0.01
174
- )
175
  with gr.Row():
176
  analyze_btn = gr.Button("Анализ", variant="primary", scale=2)
177
  clear_btn = gr.Button("Очистить", variant="secondary", scale=1)
178
 
179
  with gr.Column(scale=1):
180
- # ИЗМЕНЕНО: Добавлено отдельное поле для вердикта
181
  verdict_output = gr.Text(label="Вердикт", elem_id="verdict-output", value="—")
182
  result_label = gr.Label(label="Распределение вероятностей", num_top_classes=2)
183
- result_md = gr.Markdown(value="—", label="Детали")
184
 
185
- # ИЗМЕНЕНО: Описание моделей теперь в красивых выпадающих блоках
186
  with gr.Accordion("Параметры и описание моделей", open=False):
187
  gr.Markdown(
188
  """
189
- ### 🧠 Модель 1: ruBERT-tiny2 (трансформер)
190
  - **Архитектура:** Нейросеть `cointegrated/rubert-tiny2` (облегченная версия BERT для русского языка), дообученная на задаче классификации.
191
  - **Особенности:** Хорошо понимает контекст и семантику, но медленнее и требовательнее к ресурсам.
192
  - **Предобработка:** Удаление пунктуации, стемминг (приведение слов к основе), нормализация URL, тегов и чисел.
@@ -195,7 +169,7 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"), cs
195
  )
196
  gr.Markdown(
197
  """
198
- ### 📊 Модель 2: TF-IDF + Логистическая регрессия
199
  - **Архитектура:** Классический ML-пайплайн. `TfidfVectorizer` анализирует текст на уровне символьных n-грамм (4-5 символа), а `LogisticRegression` принимает решение.
200
  - **Особенности:** Очень быстрая и легковесная модель. Хорошо улавливает "токсичные" слова и фразы, но не понимает сложный контекст.
201
  - **Регуляризация:** L1 (Lasso) для отбора наиболее важных признаков.
@@ -204,14 +178,14 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"), cs
204
  )
205
  gr.Markdown("> Порог можно свободно менять слайдером, чтобы найти нужный баланс между точностью (precision) и полнотой (recall) для вашей задачи.")
206
 
207
- # ИЗМЕНЕНО: Обработчики событий теперь обновляют 3 поля вывода
208
- outputs_list = [verdict_output, result_label, result_md]
209
  inputs_list = [model_sel, comment_input, thr]
210
 
211
  analyze_btn.click(predict, inputs_list, outputs_list)
212
  comment_input.submit(predict, inputs_list, outputs_list)
213
 
214
- clear_outputs_list = [model_sel, comment_input, thr, verdict_output, result_label, result_md]
215
  clear_btn.click(clear_all, [], clear_outputs_list)
216
 
217
  if __name__ == "__main__":
 
14
 
15
  def clean_text(s: str) -> str:
16
  """Эта функция нужна для корректной загрузки model.joblib."""
17
+ if not isinstance(s, str): s = str(s)
 
18
  s = unescape(s).lower()
19
+ s = _URL_RE_TFIDF.sub(' <url> ', s); s = _TAG_RE_TFIDF.sub(' <tag> ', s)
20
+ s = _NUM_RE_TFIDF.sub(' <num> ', s); s = s.replace('\n', ' ').replace('\t', ' ')
 
 
21
  s = _WS_RE_TFIDF.sub(' ', s).strip()
22
  return s
23
 
24
  # ==============================
25
  # 2. ЗАГРУЗКА МОДЕЛЕЙ
26
  # ==============================
 
27
  PIPE, PIPE_PATH = None, "model.joblib"
28
  try:
29
  import joblib
30
+ if os.path.exists(PIPE_PATH): PIPE = joblib.load(PIPE_PATH)
31
+ else: print(f"[WARN] TF-IDF model not found at {PIPE_PATH}")
 
 
32
  except Exception as e:
33
  print(f"[WARN] Не удалось инициализировать TF-IDF: {e}")
34
  PIPE = None
35
 
 
36
  TRANSFORMER = {"model": None, "tokenizer": None, "device": "cpu"}
37
  try:
38
  import torch
 
81
  # 5. ФУНКЦИИ ИНФЕРЕНСА
82
  # ==============================
83
  def infer_tfidf(text: str):
84
+ if PIPE is None: return None, f"TF-IDF модель не загружена (ожидался файл '{PIPE_PATH}')."
 
85
  try:
86
  proba = PIPE.predict_proba([text])[0, 1]
87
  return float(np.clip(proba, 0.0, 1.0)), None
88
+ except Exception as e: return None, f"Ошибка инференса TF-IDF: {e}"
 
89
 
90
  def infer_transformer(text: str):
91
+ if TRANSFORMER["model"] is None: return None, "Модель ruBERT не загружена."
 
92
  import torch
93
  text_prep = clean_and_stem(text)
94
  if not text_prep: return 0.0, None
 
99
  logits = TRANSFORMER["model"](**tok).logits
100
  proba = torch.softmax(logits, dim=1)[0, 1].detach().cpu().item()
101
  return float(proba), None
102
+ except Exception as e: return None, f"Ошибка инференса ruBERT: {e}"
 
103
 
104
+ # ИЗМЕНЕНО: функция возвращает только 2 значения
105
  def predict(model_name: str, comment: str, threshold: float):
106
  comment = (comment or "").strip()
107
  if not comment:
108
+ return "—", {"Токсичный": 0.0, "Не токсичный": 1.0}
109
 
110
  if "ruBERT" in model_name:
111
  p_toxic, err = infer_transformer(comment)
 
113
  p_toxic, err = infer_tfidf(comment)
114
 
115
  if err is not None or p_toxic is None:
116
+ # Выводим ошибку прямо в поле вердикта
117
+ return f"⚠️ {err}", {"Токсичный": 0.0, "Не токсичный": 1.0}
 
118
 
 
119
  verdict = "Токсичный" if p_toxic >= threshold else "Не токсичный"
120
  dist = {"Токсичный": p_toxic, "Не токсичный": 1 - p_toxic}
121
+ return verdict, dist
122
+
123
+ # ИЗМЕНЕНО: функция очистки стала короче
 
 
 
 
 
124
  def clear_all():
125
+ return "ruBERT-tiny2 (трансформер)", "", DEFAULT_THRESHOLD, "—", None
126
 
127
  # ==============================
128
  # 6. ИНТЕРФЕЙС (UI)
 
143
 
144
  with gr.Row(variant="panel"):
145
  with gr.Column(scale=2):
146
+ model_sel = gr.Dropdown(["ruBERT-tiny2 (трансформер)", "TF-IDF + Логистическая регрессия"],
147
+ value="ruBERT-tiny2 (трансформер)", label="Модель для анализа")
148
+ comment_input = gr.Textbox(label="Текст комментария", lines=6, placeholder="Напишите что-нибудь…")
149
+ thr = gr.Slider(label="Порог классификации", minimum=0.0, maximum=1.0, value=DEFAULT_THRESHOLD, step=0.01)
 
 
 
 
 
 
 
150
  with gr.Row():
151
  analyze_btn = gr.Button("Анализ", variant="primary", scale=2)
152
  clear_btn = gr.Button("Очистить", variant="secondary", scale=1)
153
 
154
  with gr.Column(scale=1):
 
155
  verdict_output = gr.Text(label="Вердикт", elem_id="verdict-output", value="—")
156
  result_label = gr.Label(label="Распределение вероятностей", num_top_classes=2)
157
+ # ИЗМЕНЕНО: блок с деталями (result_md) полностью удален
158
 
159
+ # ИЗМЕНЕНО: убраны смайлики из описания
160
  with gr.Accordion("Параметры и описание моделей", open=False):
161
  gr.Markdown(
162
  """
163
+ ### Модель 1: ruBERT-tiny2 (трансформер)
164
  - **Архитектура:** Нейросеть `cointegrated/rubert-tiny2` (облегченная версия BERT для русского языка), дообученная на задаче классификации.
165
  - **Особенности:** Хорошо понимает контекст и семантику, но медленнее и требовательнее к ресурсам.
166
  - **Предобработка:** Удаление пунктуации, стемминг (приведение слов к основе), нормализация URL, тегов и чисел.
 
169
  )
170
  gr.Markdown(
171
  """
172
+ ### Модель 2: TF-IDF + Логистическая регрессия
173
  - **Архитектура:** Классический ML-пайплайн. `TfidfVectorizer` анализирует текст на уровне символьных n-грамм (4-5 символа), а `LogisticRegression` принимает решение.
174
  - **Особенности:** Очень быстрая и легковесная модель. Хорошо улавливает "токсичные" слова и фразы, но не понимает сложный контекст.
175
  - **Регуляризация:** L1 (Lasso) для отбора наиболее важных признаков.
 
178
  )
179
  gr.Markdown("> Порог можно свободно менять слайдером, чтобы найти нужный баланс между точностью (precision) и полнотой (recall) для вашей задачи.")
180
 
181
+ # ИЗМЕНЕНО: обновлены списки для обработчиков событий
182
+ outputs_list = [verdict_output, result_label]
183
  inputs_list = [model_sel, comment_input, thr]
184
 
185
  analyze_btn.click(predict, inputs_list, outputs_list)
186
  comment_input.submit(predict, inputs_list, outputs_list)
187
 
188
+ clear_outputs_list = [model_sel, comment_input, thr, verdict_output, result_label]
189
  clear_btn.click(clear_all, [], clear_outputs_list)
190
 
191
  if __name__ == "__main__":