| import gradio as gr |
| import torch |
| import torch.nn.functional as F |
| import os |
| import zipfile |
| from transformers import AutoTokenizer, AutoModelForSequenceClassification |
| from peft import PeftModel |
|
|
| |
| |
| |
| |
| |
| |
| BASE_MODEL = "monologg/koelectra-small-v3-discriminator" |
|
|
| LORA_WEIGHTS = "" |
|
|
| NUM_LABELS = 2 |
| LABEL_MAP = { |
| 0: "์ ๋ขฐ ๊ฐ๋ฅ (๊ธฐํ ์ค์ ๋ณด๋ก ํ๋จ๋์ง ์์)", |
| 1: "๊ธฐํ ์ค์ ๋ณด ๊ฐ๋ฅ์ฑ ์์ (์ฃผ์ ํ์)" |
| } |
|
|
| print("Loading model and tokenizer...") |
| try: |
| tokenizer = AutoTokenizer.from_pretrained(BASE_MODEL) |
| base_model = AutoModelForSequenceClassification.from_pretrained( |
| BASE_MODEL, |
| num_labels=NUM_LABELS, |
| torch_dtype=torch.float32, |
| ) |
| model = PeftModel.from_pretrained(base_model, LORA_WEIGHTS) |
| model.eval() |
| MODEL_LOADED = True |
| print("LoRA Model successfully loaded!") |
| except Exception as e: |
| print(f"Warning: Model loading failed ({e}). Running in Simulation Mode.") |
| MODEL_LOADED = False |
|
|
|
|
| |
| |
| |
| def run_pipeline(user_input): |
| """ |
| 1. Text Preprocessing |
| 2. Tokenizing |
| 3. LoRA Model Inference (๋ถ๋ฅ) |
| 4. XAI / Decision Guide Extraction |
| 5. Attention / Keyword Map Analysis |
| 6. Output Generation & Limitations |
| """ |
| if not user_input.strip(): |
| return "์
๋ ฅ ํ
์คํธ๋ฅผ ์
๋ ฅํด์ฃผ์ธ์.", "N/A", "N/A", "N/A" |
|
|
| |
| cleaned_input = user_input.strip() |
|
|
| if MODEL_LOADED: |
| |
| inputs = tokenizer(cleaned_input, return_tensors="pt", truncation=True, max_length=512) |
|
|
| |
| with torch.no_grad(): |
| outputs = model(**inputs, output_attentions=True) |
|
|
| logits = outputs.logits |
| probs = F.softmax(logits, dim=-1)[0] |
| pred_id = int(torch.argmax(probs).item()) |
| confidence = float(probs[pred_id].item()) |
|
|
| generated_text = ( |
| f"[๋ถ์ ๊ฒฐ๊ณผ]\n" |
| f"ํ๋จ: {LABEL_MAP.get(pred_id, f'ํด๋์ค {pred_id}')}\n" |
| f"์ ๋ขฐ๋(ํ๋ฅ ): {confidence * 100:.1f}%\n\n" |
| f"ํด๋์ค๋ณ ํ๋ฅ -> ์ ์: {probs[0]*100:.1f}% / ์ค์ ๋ณด ์์ฌ: {probs[1]*100:.1f}%" |
| ) |
|
|
| |
| decision_guide = ( |
| "๐ **xAI ํ๋จ ๊ฐ์ด๋ ์ถ์ถ**\n" |
| f"- ๋ชจ๋ธ ์์ธก ํด๋์ค: {pred_id} ({LABEL_MAP.get(pred_id, '')})\n" |
| f"- ์์ธก ์ ๋ขฐ๋: {confidence * 100:.1f}%\n" |
| f"- ํด๋์ค ํ๋ฅ ๋ถํฌ: ์ ์ {probs[0]*100:.1f}% / ์ค์ ๋ณด ์์ฌ {probs[1]*100:.1f}%" |
| ) |
|
|
| |
| try: |
| |
| last_layer_attn = outputs.attentions[-1][0] |
| cls_attn = last_layer_attn.mean(dim=0)[0] |
| tokens = tokenizer.convert_ids_to_tokens(inputs["input_ids"][0]) |
|
|
| scored = [ |
| (tok, float(w)) for tok, w in zip(tokens, cls_attn.tolist()) |
| if tok not in tokenizer.all_special_tokens |
| ] |
| scored.sort(key=lambda x: x[1], reverse=True) |
| top_k = scored[:5] |
|
|
| lines = "\n".join(f"{i+1}. '{tok}' -> ๊ฐ์ค์น {w:.3f}" for i, (tok, w) in enumerate(top_k)) |
| attention_analysis = "๐ **์ดํ
์
๋งต ํต์ฌ ๊ฐ์ค์น (Attention Weights)**\n" + (lines or "ํ์ํ ํ ํฐ์ด ์์ต๋๋ค.") |
| except Exception as e: |
| attention_analysis = f"๐ ์ดํ
์
๋ถ์ ์ค ์ค๋ฅ ๋ฐ์: {e}" |
|
|
| else: |
| |
| generated_text = ( |
| "[์๋ฎฌ๋ ์ด์
๊ฒฐ๊ณผ - ๋ชจ๋ธ ๋ฏธ๋ก๋ฉ]\n" |
| "ํ์ฌ ์๋ฒ์ LoRA ๊ฐ์ค์น ๋๋ base ๋ชจ๋ธ์ ๋ถ๋ฌ์ค์ง ๋ชปํด ์์ ์ถ๋ ฅ์ ํ์ํฉ๋๋ค.\n" |
| "requirements.txt ๋ฐ ๋ชจ๋ธ ๊ฒฝ๋ก ์ค์ ์ ํ์ธํด์ฃผ์ธ์." |
| ) |
| decision_guide = "๐ ๋ชจ๋ธ์ด ๋ก๋ฉ๋์ง ์์ xAI ๊ฐ์ด๋๋ฅผ ์์ฑํ ์ ์์ต๋๋ค." |
| attention_analysis = "๐ ๋ชจ๋ธ์ด ๋ก๋ฉ๋์ง ์์ ์ดํ
์
๋ถ์์ ์ํํ ์ ์์ต๋๋ค." |
|
|
| |
| disclaimer = ( |
| "โ ๏ธ **Model Card ํ๊ณ ๊ณ ์ง ๋ฐ ์ฌ์ ์๋ด (Disclaimer)**\n" |
| "โข **์ฑ
์ ์ ์ธ**: ๋ณธ ๋ชจ๋ธ(Team 3, 2026)์ ๊ธฐํ๋ณํ ์ค์ ๋ณด ํ์ง ๋ชฉ์ ์ผ๋ก ๊ฐ๋ฐ๋์์ต๋๋ค.\n" |
| "โข **์๋๋ ์ฌ์ฉ**: ์ผ๋ฐ์ ์ธ ๊ธฐํ ์ ๋ณด ํ์ ๋ฐ ๊ต์ ํ๋จ ์ฐธ๊ณ ์ฉ.\n" |
| "โข **์ฌ์ฉ ๊ธ์ง**: ์ํ์ ํ๋จ, ๋ฒ์ ๊ท์ ๊ทผ๊ฑฐ, ์๋ ์ฐจ๋จ/์ ์ฌ ์์คํ
์ ๋จ๋
๊ทผ๊ฑฐ๋ก ์ฌ์ฉ ๋ถ๊ฐ.\n" |
| "โข **์ฝ์ ๋ฐ ํ๊ณ**: ๊ณต์ ๋ ฅ ์๋ ๊ธฐ๊ด์ ํ์ ์๋ฃ๊ฐ ์๋ ์์
๋ฏธ๋์ด ํน์ ์ ์ ์กฐ์ด, ๊ทน๋จ์ ๋น์ , ํ
์คํธ ํํ๊ฐ ์๊ณก๋ ์กฐ๊ฑด์์๋ ์คํ๋ฅ ์ด ์์นํ ์ ์์ต๋๋ค.\n" |
| "โข **์ด์ ์ ๊ธฐ**: ๊ฒฐ๊ณผ์ ๋ํ ์ด์ ์ ์ฒญ ๋ฐ ์ค๋ฅ ์ ๋ณด๋ ๊ฐ๋ฐํ(Team 3) ํต๋ก๋ฅผ ์ด์ฉํด ์ฃผ์ธ์." |
| ) |
|
|
| return generated_text, decision_guide, attention_analysis, disclaimer |
|
|
|
|
| |
| |
| |
| theme = gr.themes.Soft( |
| primary_hue="teal", |
| secondary_hue="slate", |
| ) |
|
|
| custom_css = """ |
| .model-card-box { |
| background-color: #f8fafc; |
| border: 1px solid #e2e8f0; |
| border-radius: 8px; |
| padding: 15px; |
| margin-bottom: 15px; |
| } |
| .disclaimer-box { |
| background-color: #fffbe2; |
| border-left: 4px solid #f59e0b; |
| padding: 12px; |
| border-radius: 4px; |
| margin-top: 10px; |
| } |
| """ |
|
|
| with gr.Blocks(theme=theme, css=custom_css, title="๊ธฐํ๋ณํ ์ค์ ๋ณด ํ์ง AI ๋ชจ๋ธ") as demo: |
| gr.Markdown( |
| """ |
| # ๐ ๊ธฐํ๋ณํ ์ค์ ๋ณด ํ์ง AI ๋ชจ๋ธ (Climate Misinfo Detector) |
| **Team 3 | ์ฑ
์์์ AI ํ๋ก์ ํธ (High School Module 3-9)** |
| LoRA ์ด๋ํฐ ๊ฐ์ค์น๊ฐ ์ ์ฉ๋ KoELECTRA ๊ธฐ๋ฐ ๋ถ๋ฅ AI ์๊ณ ๋ฆฌ์ฆ ์น ์ธํฐํ์ด์ค์
๋๋ค. |
| """ |
| ) |
|
|
| with gr.Row(): |
| |
| with gr.Column(scale=1): |
| gr.Markdown("### ๐ฅ ์ฌ์ฉ์ ์
๋ ฅ (Input)") |
| user_input = gr.Textbox( |
| lines=5, |
| placeholder="๊ฒ์ฆํ๊ณ ์ถ์ ๊ธฐํ๋ณํ ๊ด๋ จ ๋ฌธ์ฅ์ด๋ ์ฃผ์ฅ์ ์
๋ ฅํ์ธ์...\n ์: ์ง๊ตฌ์จ๋ํ๋ ์ธ๊ฐ ํ๋ ๋๋ฌธ์ด ์๋๋ผ ์์ฐ์ ์ธ ์ฃผ๊ธฐ์ผ ๋ฟ์ด๋ค.", |
| label="์
๋ ฅ ํ
์คํธ (Text Preprocessing & Tokenizing)" |
| ) |
|
|
| submit_btn = gr.Button("๐ AI ์ถ๋ก ๋ฐ ๋ถ์ ์คํ", variant="primary") |
|
|
| gr.Markdown("### ๐ Model Card ๊ฐ์") |
| gr.Markdown( |
| """ |
| <div class="model-card-box"> |
| <b>โข ๋ชจ๋ธ๋ช
:</b> Team 3 LoRA Climate Misinfo LLM (KoELECTRA-small-v3 ๊ธฐ๋ฐ)<br> |
| <b>โข ํ์ต ๋ฐ์ดํฐ:</b> ๊ธฐํ ๋ฐ์ดํฐ๋ฒ ์ด์ค (2024~2026, 200๊ฑด ๊ฒ์์๋ฃ)<br> |
| <b>โข ์ฑ๋ฅ ์งํ:</b> Acc 85%, F1 0.82 (TP:100, TN:100, FP:10, FN:20)<br> |
| <b>โข ์ค๋ฆฌ ์ฒดํฌ:</b> ์ํ/๋ฒ๋ฅ ์๋ ํ๋จ ๊ธ์ง |
| </div> |
| """ |
| ) |
|
|
| |
| with gr.Column(scale=1): |
| gr.Markdown("### ๐ค ์ถ๋ก ๋ฐ XAI ๋ถ์ ๊ฒฐ๊ณผ (Output)") |
|
|
| output_text = gr.Textbox( |
| label="์ต์ข
ํ๋ณ ๊ฒฐ๊ณผ (Output Generation)", |
| lines=6, |
| interactive=False |
| ) |
|
|
| with gr.Tabs(): |
| with gr.TabItem("๐ ํ๋จ ๊ฐ์ด๋ (xAI)"): |
| xai_output = gr.Markdown("๋ถ์ ์คํ ํ ํ์๋ฉ๋๋ค.") |
| with gr.TabItem("๐ ์ดํ
์
๋งต ๋ถ์"): |
| attention_output = gr.Markdown("๋ถ์ ์คํ ํ ํ์๋ฉ๋๋ค.") |
|
|
| gr.Markdown("### โ ๏ธ ํ๊ณ ๊ณ ์ง ๋ฐ ์ฑ
์ ์ ์ธ (Model Card Section 5 & 6)") |
| disclaimer_output = gr.Markdown( |
| """ |
| <div class="disclaimer-box"> |
| <b>โข ํ๊ณ ์ฌ์ ๊ณ ์ง:</b> ๋ณธ ๋ชจ๋ธ์ ์คํธ๋ ์ค ํ
์คํธ ๊ฒฐ๊ณผ ์คํ ์ํฉ ๋ฐ ์๊ณก ๋ฌธ๋งฅ์์ ์คํ ์ํ์ด ์กด์ฌํฉ๋๋ค.<br> |
| <b>โข ์ด์ ์ ๊ธฐ ํต๋ก:</b> ๊ฒฐ๊ณผ์ ๋ํ ๋ฌธ์ ๋ฐ ํผ๋๋ฐฑ์ ๊ณต์ ๋ชจ๋ 3-9 ์ด์์ ๊ธฐ ์ฐฝ๊ตฌ๋ฅผ ํตํด ์ ์ ๊ฐ๋ฅํฉ๋๋ค. |
| </div> |
| """, |
| elem_classes=["disclaimer-box"] |
| ) |
|
|
| submit_btn.click( |
| fn=run_pipeline, |
| inputs=[user_input], |
| outputs=[output_text, xai_output, attention_output, disclaimer_output] |
| ) |
|
|
| if __name__ == "__main__": |
| demo.launch() |