import os import time from pathlib import Path import pandas as pd import streamlit as st from src.predictor import ArticleTopicPredictor st.set_page_config( page_title="Article Topic Classifier", page_icon="🧠", layout="centered", ) MODEL_DIR = os.getenv("MODEL_DIR", "artifacts/article_topic_model") EXAMPLES = [ { "title": "Attention-based models for scientific document understanding", "abstract": "We propose a transformer-based architecture for classification and retrieval of scientific papers.", }, { "title": "A new benchmark for graph representation learning", "abstract": "This paper introduces a benchmark suite for graph neural networks and evaluates generalization.", }, { "title": "Quantum error correction with surface codes", "abstract": "", }, ] @st.cache_resource(show_spinner=False) def load_predictor(model_dir: str) -> ArticleTopicPredictor: return ArticleTopicPredictor(model_dir=model_dir) def render_intro() -> None: st.title("🧠 ΠšΠ»Π°ΡΡΠΈΡ„ΠΈΠΊΠ°Ρ‚ΠΎΡ€ Ρ‚Π΅ΠΌΠ°Ρ‚ΠΈΠΊ Π½Π°ΡƒΡ‡Π½Ρ‹Ρ… статСй") st.markdown( """ Π’Π²Π΅Π΄ΠΈΡ‚Π΅ Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΎΠΊ ΡΡ‚Π°Ρ‚ΡŒΠΈ ΠΈ, ΠΏΡ€ΠΈ Π½Π°Π»ΠΈΡ‡ΠΈΠΈ, abstract. БСрвис ΠΏΠΎΠΊΠ°ΠΆΠ΅Ρ‚ Π½Π°ΠΈΠ±ΠΎΠ»Π΅Π΅ вСроятныС Ρ‚Π΅ΠΌΠ°Ρ‚ΠΈΠΊΠΈ ΠΈ Π½Π°Π±ΠΎΡ€ **top-95%** классов: классы ΠΏΠΎ ΡƒΠ±Ρ‹Π²Π°Π½ΠΈΡŽ вСроятности, ΠΏΠΎΠΊΠ° суммарная Π²Π΅Ρ€ΠΎΡΡ‚Π½ΠΎΡΡ‚ΡŒ Π½Π΅ прСвысит 95%. """ ) st.caption("Если abstract пустой, классификация выполняСтся Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΏΠΎ title.") def render_sidebar() -> None: with st.sidebar: st.header("О ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠΈ") st.write("МодСль загруТаСтся ΠΎΠ΄ΠΈΠ½ Ρ€Π°Π· ΠΈ ΠΊΡΡˆΠΈΡ€ΡƒΠ΅Ρ‚ΡΡ ΠΌΠ΅ΠΆΠ΄Ρƒ пСрСзапусками интСрфСйса.") st.write("ΠŸΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΈΠ²Π°Π΅Ρ‚ΡΡ Ρ€Π΅ΠΆΠΈΠΌ Ρ€Π°Π±ΠΎΡ‚Ρ‹ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΏΠΎ title.") st.write(f"Папка ΠΌΠΎΠ΄Π΅Π»ΠΈ: `{MODEL_DIR}`") st.divider() st.subheader("БыстрыС ΠΏΡ€ΠΈΠΌΠ΅Ρ€Ρ‹") for i, example in enumerate(EXAMPLES): if st.button(f"ΠŸΠΎΠ΄ΡΡ‚Π°Π²ΠΈΡ‚ΡŒ ΠΏΡ€ΠΈΠΌΠ΅Ρ€ {i + 1}", use_container_width=True): st.session_state["title_input"] = example["title"] st.session_state["abstract_input"] = example["abstract"] def validate_inputs(title: str, abstract: str) -> str | None: if not title.strip() and not abstract.strip(): return "Π’Π²Π΅Π΄ΠΈΡ‚Π΅ хотя Π±Ρ‹ Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΎΠΊ ΡΡ‚Π°Ρ‚ΡŒΠΈ ΠΈΠ»ΠΈ abstract." if len(title.strip()) > 600: return "Π—Π°Π³ΠΎΠ»ΠΎΠ²ΠΎΠΊ слишком Π΄Π»ΠΈΠ½Π½Ρ‹ΠΉ. ΠŸΠΎΠΆΠ°Π»ΡƒΠΉΡΡ‚Π°, сократитС title Π΄ΠΎ 600 символов." if len(abstract.strip()) > 8000: return "Abstract слишком Π΄Π»ΠΈΠ½Π½Ρ‹ΠΉ. ΠŸΠΎΠΆΠ°Π»ΡƒΠΉΡΡ‚Π°, сократитС тСкст Π΄ΠΎ 8000 символов." return None def main() -> None: render_intro() render_sidebar() if "title_input" not in st.session_state: st.session_state["title_input"] = "" if "abstract_input" not in st.session_state: st.session_state["abstract_input"] = "" model_path = Path(MODEL_DIR) if not model_path.exists(): st.error( "Папка с модСлью Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½Π°. Π‘Π½Π°Ρ‡Π°Π»Π° ΠΎΠ±ΡƒΡ‡ΠΈΡ‚Π΅ модСль ΠΈ сохранитС Π΅Ρ‘ Π² " f"`{MODEL_DIR}`, Π»ΠΈΠ±ΠΎ Π·Π°Π΄Π°ΠΉΡ‚Π΅ ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½ΡƒΡŽ окруТСния MODEL_DIR." ) st.stop() title = st.text_input( "НазваниС ΡΡ‚Π°Ρ‚ΡŒΠΈ", key="title_input", placeholder="НапримСр: Attention-based methods for scientific text classification", ) abstract = st.text_area( "Abstract (Π½Π΅ΠΎΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½ΠΎ)", key="abstract_input", placeholder="Π’ΡΡ‚Π°Π²ΡŒΡ‚Π΅ Π°Π½Π½ΠΎΡ‚Π°Ρ†ΠΈΡŽ ΡΡ‚Π°Ρ‚ΡŒΠΈ. ПолС ΠΌΠΎΠΆΠ½ΠΎ ΠΎΡΡ‚Π°Π²ΠΈΡ‚ΡŒ пустым.", height=220, ) col1, col2 = st.columns([1, 1]) with col1: run = st.button("ΠšΠ»Π°ΡΡΠΈΡ„ΠΈΡ†ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ", type="primary", use_container_width=True) with col2: clear = st.button("ΠžΡ‡ΠΈΡΡ‚ΠΈΡ‚ΡŒ", use_container_width=True) if clear: st.session_state["title_input"] = "" st.session_state["abstract_input"] = "" st.rerun() if run: error_message = validate_inputs(title, abstract) if error_message: st.warning(error_message) st.stop() try: predictor = load_predictor(MODEL_DIR) with st.spinner("Π‘Ρ‡ΠΈΡ‚Π°ΡŽ вСроятности классов..."): started = time.perf_counter() result = predictor.predict(title=title, abstract=abstract, top95_threshold=0.95) elapsed = time.perf_counter() - started st.success(f"Π“ΠΎΡ‚ΠΎΠ²ΠΎ. ВрСмя инфСрСнса: {elapsed:.2f} сСк.") st.subheader("Top-95% Ρ‚Π΅ΠΌΠ°Ρ‚ΠΈΠΊΠΈ") top95_df = pd.DataFrame(result["top95"]) top95_df["probability"] = top95_df["probability"].map(lambda x: round(float(x), 4)) top95_df["cumulative_probability"] = top95_df["cumulative_probability"].map(lambda x: round(float(x), 4)) st.dataframe( top95_df.rename( columns={ "label": "Π’Π΅ΠΌΠ°", "probability": "Π’Π΅Ρ€ΠΎΡΡ‚Π½ΠΎΡΡ‚ΡŒ", "cumulative_probability": "НакоплСнная Π²Π΅Ρ€ΠΎΡΡ‚Π½ΠΎΡΡ‚ΡŒ", } ), use_container_width=True, hide_index=True, ) st.subheader("ВсС вСроятности") full_df = pd.DataFrame(result["all_probs"]) full_df["probability"] = full_df["probability"].map(float) st.bar_chart(full_df.set_index("label")["probability"]) st.dataframe( full_df.rename(columns={"label": "Π’Π΅ΠΌΠ°", "probability": "Π’Π΅Ρ€ΠΎΡΡ‚Π½ΠΎΡΡ‚ΡŒ"}), use_container_width=True, hide_index=True, ) st.caption( "Top-95% β€” это ΠΌΠΈΠ½ΠΈΠΌΠ°Π»ΡŒΠ½Ρ‹ΠΉ Π½Π°Π±ΠΎΡ€ классов, Ρ‡Π΅ΠΉ суммарный вСс достигаСт Π½Π΅ ΠΌΠ΅Π½Π΅Π΅ 95%." ) except Exception as exc: # noqa: BLE001 st.error("Π’ΠΎ врСмя инфСрСнса ΠΏΡ€ΠΎΠΈΠ·ΠΎΡˆΠ»Π° ошибка, Π½ΠΎ ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ Π½Π΅ ΡƒΠΏΠ°Π»ΠΎ.") st.exception(exc) if __name__ == "__main__": main()