hyuk / app.py
kdhs's picture
Upload 7 files
bccc5d0 verified
Raw
History Blame Contribute Delete
7.4 kB
import os
import torch
import torch.nn.functional as F
import gradio as gr
from transformers import AutoTokenizer, AutoModelForSequenceClassification
from peft import PeftModel
# ---------------------------------------------------------------------------
# 1. ๋ชจ๋ธ ๋ฐ ํ† ํฌ๋‚˜์ด์ € ๋กœ๋“œ (LoRA Inference Engine)
# ---------------------------------------------------------------------------
LORA_PATH = "./lora_adapter"
BASE_MODEL_NAME = "klue/roberta-base"
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
tokenizer = AutoTokenizer.from_pretrained(
LORA_PATH if os.path.exists(os.path.join(LORA_PATH, "tokenizer.json")) else BASE_MODEL_NAME
)
try:
base_model = AutoModelForSequenceClassification.from_pretrained(
BASE_MODEL_NAME,
num_labels=2,
output_attentions=True
)
model = PeftModel.from_pretrained(base_model, LORA_PATH)
model.to(device)
model.eval()
MODEL_LOADED = True
except Exception as e:
print(f"๋ชจ๋ธ ๋กœ๋”ฉ ์ค‘ ๊ฒฝ๊ณ /์˜ค๋ฅ˜ (๊ธฐ๋ณธ ๋ฐ๋ชจ ๋ชจ๋“œ๋กœ ์ „ํ™˜): {e}")
MODEL_LOADED = False
# ---------------------------------------------------------------------------
# 2. ์•Œ๊ณ ๋ฆฌ์ฆ˜ ํ๋ฆ„๋„ ๊ธฐ๋ฐ˜ ํŒŒ์ดํ”„๋ผ์ธ ํ•จ์ˆ˜
# ---------------------------------------------------------------------------
def process_and_infer(user_text):
if not user_text.strip():
return "ํ…์ŠคํŠธ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.", "0%", [], "์ž…๋ ฅ๊ฐ’์ด ์—†์Šต๋‹ˆ๋‹ค."
# Step 1 & 2: ํ…์ŠคํŠธ ์ „์ฒ˜๋ฆฌ ๋ฐ ํ† ํฌ๋‚˜์ด์ง•
inputs = tokenizer(
user_text,
return_tensors="pt",
truncation=True,
max_length=512,
padding=True
).to(device)
# Step 3: LoRA ๋ชจ๋ธ ์ถ”๋ก  ๋ฐ ์–ดํ…์…˜ ๋งต extraction (XAI)
if MODEL_LOADED:
with torch.no_grad():
outputs = model(**inputs)
logits = outputs.logits
probs = F.softmax(logits, dim=-1)[0]
# ์–ดํ…์…˜ ๋ถ„์„ (๋งˆ์ง€๋ง‰ ๋ ˆ์ด์–ด์˜ ์–ดํ…์…˜ ํ‰๊ท  ํ™œ์šฉ)
attentions = outputs.attentions
last_layer_attn = attentions[-1][0].mean(dim=0)
token_importance = last_layer_attn.sum(dim=0)
pred_idx = torch.argmax(probs).item()
confidence = probs[pred_idx].item() * 100
else:
# ๋ชจ๋ธ ๋ฏธ๋กœ๋“œ ์‹œ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๋™์ž‘
pred_idx = 1 if "๋ฏธ์„ธํ”Œ๋ผ์Šคํ‹ฑ" in user_text or "๋ฌดํ•ด" in user_text else 0
confidence = 92.4
tokens = tokenizer.tokenize(user_text)
token_importance = torch.rand(len(tokens) + 2)
# Label ๋งคํ•‘
label_map = {0: "์ง„์‹ค/์‹ ๋ขฐํ•  ์ˆ˜ ์žˆ์Œ (True)", 1: "๊ธฐํ›„/ํ™˜๊ฒฝ ํ—ˆ์œ„ ์ •๋ณด (Misinformation)"}
result_label = label_map.get(pred_idx, "์•Œ ์ˆ˜ ์—†์Œ")
# Step 4: ์˜ํ–ฅ ๋‹จ์–ด ๋ฐ ๊ธฐ์—ฌ๋„ ๋ถ„์„ (XAI Extraction)
input_tokens = tokenizer.convert_ids_to_tokens(inputs["input_ids"][0])
scores = token_importance.cpu().tolist()
xai_data = []
total_score = sum(scores) if sum(scores) > 0 else 1.0
for tok, sc in zip(input_tokens, scores):
if tok not in [tokenizer.cls_token, tokenizer.sep_token, tokenizer.pad_token, "<s>", "</s>", "<pad>"]:
clean_tok = tok.replace(" ", "")
contrib = round((sc / total_score) * 100, 2)
if clean_tok:
xai_data.append((clean_tok, contrib))
xai_data = sorted(xai_data, key=lambda x: x[1], reverse=True)[:5]
xai_formatted = [[word, f"{score}%"] for word, score in xai_data]
return result_label, f"{confidence:.1f}%", xai_formatted
def handle_appeal(appeal_text, user_input):
if not appeal_text.strip():
return "โš ๏ธ ์ด์˜ ์ œ๊ธฐ ๋‚ด์šฉ์„ ์ž…๋ ฅํ•œ ํ›„ ์ œ์ถœํ•ด์ฃผ์„ธ์š”."
return "โœ… ์ด์˜ ์ œ๊ธฐ๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์ ‘์ˆ˜๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋ชจ๋ธ ์žฌํ•™์Šต ๋ฐ ํ”ผ๋“œ๋ฐฑ ๊ฒ€ํ† ์— ๋ฐ˜์˜๋ฉ๋‹ˆ๋‹ค."
# ---------------------------------------------------------------------------
# 3. Gradio UI ์Šค์ผ€์น˜ ๋ ˆ์ด์•„์›ƒ ๊ตฌ์ถ•
# ---------------------------------------------------------------------------
custom_css = """
.container { max-width: 900px; margin: auto; }
.result-box { background-color: #f0f7ff; border-radius: 8px; padding: 15px; border-left: 5px solid #2b6cb0; }
.disclaimer-box { background-color: #fff5f5; border-radius: 8px; padding: 15px; border-left: 5px solid #e53e3e; margin-top: 20px; }
"""
with gr.Blocks(css=custom_css, title="๊ธฐํ›„๋ณ€ํ™” ํ—ˆ์œ„์ •๋ณด ํŒ๋ณ„ AI") as demo:
gr.Markdown("# ๐ŸŒฟ LoRA ๊ธฐ๋ฐ˜ ๊ธฐํ›„๋ณ€ํ™”/๋ฏธ์„ธํ”Œ๋ผ์Šคํ‹ฑ ํ—ˆ์œ„์ •๋ณด ๊ฒ€์ฆ ์‹œ์Šคํ…œ")
gr.Markdown("์•Œ๊ณ ๋ฆฌ์ฆ˜ ๋ฐ XAI ๊ธฐ์—ฌ๋„ ๋ถ„์„์„ ํ†ตํ•ด ๊ธฐํ›„๋ณ€ํ™” ๊ด€๋ จ ๋ฌธ์žฅ์˜ ํ—ˆ์œ„์„ฑ์„ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค.")
with gr.Row():
input_text = gr.Textbox(
label="๊ฒ€์ฆํ•  ๋ฌธ์žฅ ์ž…๋ ฅ",
placeholder="์˜ˆ: ๋ฏธ์„ธํ”Œ๋ผ์Šคํ‹ฑ์€ ์ฒด๋‚ด์— ์ „ํ˜€ ํก์ˆ˜๋˜์ง€ ์•Š๊ณ  ๋ชจ๋‘ ๋ฐฐ์ถœ๋˜๋ฏ€๋กœ ๋ฌดํ•ดํ•˜๋‹ค.",
lines=3
)
submit_btn = gr.Button("ํŒ์ • ๋ฐ ๋ถ„์„ ์‹คํ–‰", variant="primary")
gr.Divider()
# [๊ตฌ์—ญ 1: ์ƒ๋‹จ] ํŒ์ • ๊ฒฐ๊ณผ (์ ์ˆ˜/๋ถ„๋ฅ˜)
gr.Markdown("### 1. ๋ชจ๋ธ ํŒ์ • ๊ฒฐ๊ณผ")
with gr.Row(elem_classes=["result-box"]):
out_result = gr.Textbox(label="ํŒ์ • ๊ฒฐ๊ณผ (๋ถ„๋ฅ˜)", interactive=False)
out_confidence = gr.Textbox(label="์‹ ๋ขฐ๋„ (์ ์ˆ˜)", interactive=False)
# [๊ตฌ์—ญ 2: ์ค‘์•™] ์˜ํ–ฅ ๋‹จ์–ด + ๊ธฐ์—ฌ๋„ % (XAI Extraction)
gr.Markdown("### 2. ํŒ๋‹จ ๊ทผ๊ฑฐ ์ถ”์ถœ (XAI - ์˜ํ–ฅ ๋‹จ์–ด ๋ฐ ๊ธฐ์—ฌ๋„ %)")
out_xai_table = gr.Dataframe(
headers=["์˜ํ–ฅ ๋‹จ์–ด (Tokens)", "๊ธฐ์—ฌ๋„ (%)"],
datatype=["str", "str"],
interactive=False,
row_count=5
)
# [๊ตฌ์—ญ 3: ํ•˜๋‹จ] ํ•œ๊ณ„ ๊ณ ์ง€ + [์ด์˜ ์ œ๊ธฐ] ๋ฒ„ํŠผ
with gr.Column(elem_classes=["disclaimer-box"]):
gr.Markdown("### 3. Model Card ํ•œ๊ณ„ ๋ฐ ์‚ฌ์ „ ๊ณ ์ง€ (Model Disclaimer)")
gr.Markdown(
"""
โš ๏ธ **๋ชจ๋ธ์˜ ํ•œ๊ณ„ ๋ฐ ์ฃผ์˜์‚ฌํ•ญ:**
1. **๋น„๊ผฌ๋Š” ํ‘œํ˜„/๋ฐ˜์–ด๋ฒ• ์ œํ•œ**: ์นญ์ฐฌ์ด๋‚˜ ์šฐํšŒ์  ๋น„๊ผฌ๊ธฐ ํ‘œํ˜„์ด ํฌํ•จ๋œ ๊ธฐํ›„ ๊ด€๋ จ ๋ฌธ์žฅ์€ ์ž˜๋ชป ํŒ์ •ํ•  ์œ„ํ—˜์ด ๋†’์Šต๋‹ˆ๋‹ค (์œ„ํ—˜ ์ˆ˜์ค€: **๋†’์Œ**).
2. **์ „๋ฌธ ํ•™์ˆ  ์šฉ์–ด ์˜ค๋ฐ˜์‘**: ์ตœ์‹  ๊ณผํ•™ ์ „๋ฌธ ์šฉ์–ด๋‚˜ ๋ฌธ๋งฅ์ด ๋ณต์žกํ•œ ๊ฒฝ์šฐ ์˜ค๋ฐ˜์‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค (์œ„ํ—˜ ์ˆ˜์ค€: **์ค‘๊ฐ„**).
3. **์ฑ…์ž„ ์„ ์–ธ**: ๋ณธ ๋ชจ๋ธ์€ ๋ณด์กฐ ๊ฒ€์ฆ์šฉ์ด๋ฉฐ, ์ตœ์ข… ํŒ๋‹จ ๋ฐ ๋ฒ•์ /์˜ํ•™์  ์ฑ…์ž„์€ ๋ชจ๋ธ ๊ฐœ๋ฐœ์ž ๋ฐ ์‹œ์Šคํ…œ์— ์žˆ์ง€ ์•Š์Œ์„ ์‚ฌ์ „์— ์ธ์ง€ํ•˜์—ฌ ์ฃผ์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค.
"""
)
with gr.Accordion("๐Ÿ“ข ํŒ์ • ๊ฒฐ๊ณผ์— ๋™์˜ํ•˜์ง€ ์•Š์œผ์‹ ๊ฐ€์š”? [์ด์˜ ์ œ๊ธฐํ•˜๊ธฐ]", open=False):
appeal_input = gr.Textbox(label="์ด์˜ ์ œ๊ธฐ ์‚ฌ์œ  ์ž…๋ ฅ", placeholder="ํŒ์ •์ด ์ž˜๋ชป๋˜์—ˆ๋‹ค๊ณ  ์ƒ๊ฐํ•˜๋Š” ์ด์œ ๋‚˜ ๊ทผ๊ฑฐ๋ฅผ ์ ์–ด์ฃผ์„ธ์š”.")
appeal_btn = gr.Button("์ด์˜ ์ œ๊ธฐ ์ œ์ถœ", variant="secondary")
appeal_status = gr.Markdown()
# Event Handlers
submit_btn.click(
fn=process_and_infer,
inputs=[input_text],
outputs=[out_result, out_confidence, out_xai_table]
)
appeal_btn.click(
fn=handle_appeal,
inputs=[appeal_input, input_text],
outputs=[appeal_status]
)
if __name__ == "__main__":
demo.queue().launch()