Update app.py
Browse files
app.py
CHANGED
|
@@ -23,11 +23,12 @@ def clean_text(s: str) -> str:
|
|
| 23 |
return s
|
| 24 |
|
| 25 |
# ---------------------------------
|
| 26 |
-
# 2) Загрузка пайплайна и конфига (
|
| 27 |
# ---------------------------------
|
| 28 |
PIPE = joblib.load("model.joblib")
|
| 29 |
|
| 30 |
-
|
|
|
|
| 31 |
try:
|
| 32 |
with open("config.json", "r", encoding="utf-8") as f:
|
| 33 |
cfg = json.load(f)
|
|
@@ -36,35 +37,35 @@ except Exception:
|
|
| 36 |
pass
|
| 37 |
|
| 38 |
# ---------------------------------
|
| 39 |
-
# 3)
|
| 40 |
# ---------------------------------
|
| 41 |
def predict(comment: str, threshold: float):
|
| 42 |
"""
|
| 43 |
-
|
| 44 |
-
|
|
|
|
|
|
|
| 45 |
"""
|
| 46 |
if not comment or not comment.strip():
|
| 47 |
-
|
|
|
|
| 48 |
|
| 49 |
proba_toxic = float(PIPE.predict_proba([comment])[0, 1])
|
| 50 |
proba_not_toxic = 1 - proba_toxic
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
|
| 52 |
-
# gr.Label автоматически выделит класс с большей вероятностью,
|
| 53 |
-
# но мы также можем сделать это наглядно, сравнив с порогом.
|
| 54 |
-
# Для простоты и наглядности, вернем вероятности для обоих классов.
|
| 55 |
-
# Компонент gr.Label сам подсветит тот, у которого значение выше.
|
| 56 |
-
if proba_toxic >= threshold:
|
| 57 |
-
# Если превышен порог, то "Токсичный" должен быть основным результатом
|
| 58 |
-
return {"Токсичный": proba_toxic, "Не токсичный": proba_not_toxic}
|
| 59 |
-
else:
|
| 60 |
-
# Иначе - "Не токсичный"
|
| 61 |
-
return {"Не токсичный": proba_not_toxic, "Токсичный": proba_toxic}
|
| 62 |
-
|
| 63 |
# ---------------------------------
|
| 64 |
-
# 4)
|
| 65 |
# ---------------------------------
|
| 66 |
|
| 67 |
-
# Описание выносим в отдельную переменную для чистоты
|
| 68 |
TITLE = "Анализатор токсичности комментариев"
|
| 69 |
DESCRIPTION = "Введите комментарий на русском языке, чтобы определить его токсичность. Модель вернет вероятность принадлежности к классу 'Токсичный'."
|
| 70 |
ARTICLE = """
|
|
@@ -75,13 +76,18 @@ ARTICLE = """
|
|
| 75 |
* **Разработано для**: Демонстрации работы простой, но эффективной baseline-модели.
|
| 76 |
"""
|
| 77 |
|
| 78 |
-
#
|
| 79 |
-
|
| 80 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
gr.Markdown(f"# {TITLE}")
|
| 82 |
gr.Markdown(DESCRIPTION)
|
| 83 |
|
| 84 |
-
# Основная раскладка в две колонки
|
| 85 |
with gr.Row():
|
| 86 |
# Левая колонка для ввода
|
| 87 |
with gr.Column(scale=2):
|
|
@@ -90,61 +96,46 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky")) as
|
|
| 90 |
lines=5,
|
| 91 |
placeholder="Напр��мер: Ты полный идиот!",
|
| 92 |
)
|
| 93 |
-
|
| 94 |
with gr.Row():
|
| 95 |
clear_btn = gr.Button("Очистить", variant="secondary")
|
| 96 |
analyze_btn = gr.Button("Анализ", variant="primary")
|
| 97 |
-
|
| 98 |
-
# Примеры для быстрого тестирования
|
| 99 |
-
gr.Examples(
|
| 100 |
-
examples=[
|
| 101 |
-
"Ты полный идиот!",
|
| 102 |
-
"Спасибо большое за помощь!",
|
| 103 |
-
"Посмотри это <url> и скажи, что думаешь",
|
| 104 |
-
"Что за бред ты несешь?",
|
| 105 |
-
"Отличная работа, продолжайте в том же духе!",
|
| 106 |
-
],
|
| 107 |
-
inputs=comment_input,
|
| 108 |
-
)
|
| 109 |
|
| 110 |
# Правая колонка для вывода
|
| 111 |
with gr.Column(scale=1):
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
with gr.Accordion("Настройки", open=False):
|
| 116 |
threshold_slider = gr.Slider(
|
| 117 |
-
minimum=0.0,
|
| 118 |
-
maximum=1.0,
|
| 119 |
-
value=DEFAULT_THRESHOLD,
|
| 120 |
-
step=0.01,
|
| 121 |
label="Порог классификации",
|
| 122 |
info="Комментарий считается токсичным, если вероятность превышает это значение."
|
| 123 |
)
|
| 124 |
|
| 125 |
-
# Техническая информация о модели в самом низу
|
| 126 |
gr.Markdown(ARTICLE)
|
| 127 |
|
| 128 |
# --- Логика взаимодействия компонентов ---
|
| 129 |
|
| 130 |
-
#
|
| 131 |
def clear_all():
|
| 132 |
-
return "", None
|
| 133 |
|
| 134 |
-
#
|
| 135 |
analyze_btn.click(
|
| 136 |
fn=predict,
|
| 137 |
inputs=[comment_input, threshold_slider],
|
| 138 |
-
outputs=
|
| 139 |
)
|
| 140 |
-
# Также запускаем анализ по нажатию Enter в текстовом поле
|
| 141 |
comment_input.submit(
|
| 142 |
fn=predict,
|
| 143 |
inputs=[comment_input, threshold_slider],
|
| 144 |
-
outputs=
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 145 |
)
|
| 146 |
-
clear_btn.click(fn=clear_all, inputs=[], outputs=[comment_input, result_label])
|
| 147 |
-
|
| 148 |
|
| 149 |
if __name__ == "__main__":
|
| 150 |
-
demo.launch(
|
|
|
|
| 23 |
return s
|
| 24 |
|
| 25 |
# ---------------------------------
|
| 26 |
+
# 2) Загрузка пайплайна и конфига (порог изменен)
|
| 27 |
# ---------------------------------
|
| 28 |
PIPE = joblib.load("model.joblib")
|
| 29 |
|
| 30 |
+
# Устанавливаем новый порог по умолчанию
|
| 31 |
+
DEFAULT_THRESHOLD = 0.5
|
| 32 |
try:
|
| 33 |
with open("config.json", "r", encoding="utf-8") as f:
|
| 34 |
cfg = json.load(f)
|
|
|
|
| 37 |
pass
|
| 38 |
|
| 39 |
# ---------------------------------
|
| 40 |
+
# 3) Инференс, возвращающий вердикт и вероятности отдельно
|
| 41 |
# ---------------------------------
|
| 42 |
def predict(comment: str, threshold: float):
|
| 43 |
"""
|
| 44 |
+
Возвращает три значения:
|
| 45 |
+
1. Вердикт (строка) на основе порога.
|
| 46 |
+
2. Вероятности (словарь) для gr.Label.
|
| 47 |
+
3. Вероятность токсичности (число) для наглядности.
|
| 48 |
"""
|
| 49 |
if not comment or not comment.strip():
|
| 50 |
+
# Возвращаем пустые значения для всех трех полей вывода
|
| 51 |
+
return "", None, None
|
| 52 |
|
| 53 |
proba_toxic = float(PIPE.predict_proba([comment])[0, 1])
|
| 54 |
proba_not_toxic = 1 - proba_toxic
|
| 55 |
+
|
| 56 |
+
# 1. Определяем вердикт на основе порога
|
| 57 |
+
verdict = "Токсичный" if proba_toxic >= threshold else "Не токсичный"
|
| 58 |
+
|
| 59 |
+
# 2. Готовим словарь для gr.Label
|
| 60 |
+
probabilities = {"Токсичный": proba_toxic, "Не токсичный": proba_not_toxic}
|
| 61 |
+
|
| 62 |
+
# 3. Возвращаем все три результата
|
| 63 |
+
return verdict, probabilities, proba_toxic
|
| 64 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
# ---------------------------------
|
| 66 |
+
# 4) Обновленный интерфейс с кастомным шрифтом и рабочим порогом
|
| 67 |
# ---------------------------------
|
| 68 |
|
|
|
|
| 69 |
TITLE = "Анализатор токсичности комментариев"
|
| 70 |
DESCRIPTION = "Введите комментарий на русском языке, чтобы определить его токсичность. Модель вернет вероятность принадлежности к классу 'Токсичный'."
|
| 71 |
ARTICLE = """
|
|
|
|
| 76 |
* **Разработано для**: Демонстрации работы простой, но эффективной baseline-модели.
|
| 77 |
"""
|
| 78 |
|
| 79 |
+
# CSS для подключения и применения шрифта "Inter"
|
| 80 |
+
CUSTOM_CSS = """
|
| 81 |
+
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap');
|
| 82 |
+
gradio-app {
|
| 83 |
+
font-family: 'Inter', sans-serif;
|
| 84 |
+
}
|
| 85 |
+
"""
|
| 86 |
+
|
| 87 |
+
with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"), css=CUSTOM_CSS) as demo:
|
| 88 |
gr.Markdown(f"# {TITLE}")
|
| 89 |
gr.Markdown(DESCRIPTION)
|
| 90 |
|
|
|
|
| 91 |
with gr.Row():
|
| 92 |
# Левая колонка для ввода
|
| 93 |
with gr.Column(scale=2):
|
|
|
|
| 96 |
lines=5,
|
| 97 |
placeholder="Напр��мер: Ты полный идиот!",
|
| 98 |
)
|
|
|
|
| 99 |
with gr.Row():
|
| 100 |
clear_btn = gr.Button("Очистить", variant="secondary")
|
| 101 |
analyze_btn = gr.Button("Анализ", variant="primary")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
|
| 103 |
# Правая колонка для вывода
|
| 104 |
with gr.Column(scale=1):
|
| 105 |
+
verdict_output = gr.Textbox(label="Вердикт (с учетом порога)", interactive=False)
|
| 106 |
+
probabilities_output = gr.Label(label="Распределение вероятностей", num_top_classes=2)
|
| 107 |
+
|
| 108 |
with gr.Accordion("Настройки", open=False):
|
| 109 |
threshold_slider = gr.Slider(
|
| 110 |
+
minimum=0.0, maximum=1.0, value=DEFAULT_THRESHOLD, step=0.01,
|
|
|
|
|
|
|
|
|
|
| 111 |
label="Порог классификации",
|
| 112 |
info="Комментарий считается токсичным, если вероятность превышает это значение."
|
| 113 |
)
|
| 114 |
|
|
|
|
| 115 |
gr.Markdown(ARTICLE)
|
| 116 |
|
| 117 |
# --- Логика взаимодействия компонентов ---
|
| 118 |
|
| 119 |
+
# Обновленная функция очистки для трех полей вывода
|
| 120 |
def clear_all():
|
| 121 |
+
return "", "", None
|
| 122 |
|
| 123 |
+
# Привязываем predict к трем компонентам вывода
|
| 124 |
analyze_btn.click(
|
| 125 |
fn=predict,
|
| 126 |
inputs=[comment_input, threshold_slider],
|
| 127 |
+
outputs=[verdict_output, probabilities_output]
|
| 128 |
)
|
|
|
|
| 129 |
comment_input.submit(
|
| 130 |
fn=predict,
|
| 131 |
inputs=[comment_input, threshold_slider],
|
| 132 |
+
outputs=[verdict_output, probabilities_output]
|
| 133 |
+
)
|
| 134 |
+
clear_btn.click(
|
| 135 |
+
fn=clear_all,
|
| 136 |
+
inputs=[],
|
| 137 |
+
outputs=[comment_input, verdict_output, probabilities_output]
|
| 138 |
)
|
|
|
|
|
|
|
| 139 |
|
| 140 |
if __name__ == "__main__":
|
| 141 |
+
demo.launch()
|