g2416 / app.py
kdhs's picture
Upload app.py
1f97a3a verified
Raw
History Blame Contribute Delete
9.52 kB
import os
import re
import torch
import gradio as gr
from transformers import AutoTokenizer, AutoModelForSequenceClassification
from peft import PeftModel
# 1. LoRA ์–ด๋Œ‘ํ„ฐ์˜ ์‹ค์ œ ๋ฒ ์ด์Šค ๋ชจ๋ธ ID๋กœ ์ˆ˜์ •
BASE_MODEL_ID = "monologg/koelectra-small-v3-discriminator"
LORA_PATH = "./lora_climate_misinfo"
device = "cuda" if torch.cuda.is_available() else "cpu"
tokenizer = None
model = None
def load_model_and_tokenizer():
global tokenizer, model
try:
# ํ† ํฌ๋‚˜์ด์ € ๋กœ๋“œ (๋กœ์ปฌ ์–ด๋Œ‘ํ„ฐ ๊ฒฝ๋กœ ์šฐ์„ )
tokenizer_path = LORA_PATH if os.path.exists(LORA_PATH) else BASE_MODEL_ID
tokenizer = AutoTokenizer.from_pretrained(tokenizer_path)
# KoELECTRA ๋ฒ ์ด์Šค ๋ชจ๋ธ ๋ฐ LoRA ์–ด๋Œ‘ํ„ฐ ๊ฒฐํ•ฉ
base_model = AutoModelForSequenceClassification.from_pretrained(
BASE_MODEL_ID,
num_labels=2,
output_attentions=True
)
if os.path.exists(LORA_PATH):
model = PeftModel.from_pretrained(base_model, LORA_PATH)
else:
model = base_model
model.to(device)
model.eval()
print("โœ… KoELECTRA + LoRA ๋ชจ๋ธ ๋กœ๋“œ ์„ฑ๊ณต")
except Exception as e:
print(f"โš ๏ธ ๋ชจ๋ธ ๋กœ๋“œ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {e}")
model = None
load_model_and_tokenizer()
def preprocess_text(text):
text = text.strip()
text = re.sub(r'\s+', ' ', text)
return text
def analyze_climate_text(user_input):
if not user_input or not user_input.strip():
return (
'<div style="background-color: #f8d7da; color: #721c24; padding: 15px; border-radius: 8px; font-weight: bold;">โš ๏ธ ๋ถ„์„ํ•  ํ…์ŠคํŠธ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.</div>',
"",
"์œ„ํ—˜๋„ ์ ์ˆ˜: 0.0000%",
"ํ’์ž-๋ฐ˜์–ด๋ฒ• ํ…์ŠคํŠธ ์ •ํ™•๋„ ๋‚ฎ์Œ!"
)
cleaned_input = preprocess_text(user_input)
fake_prob = 0.0007
is_misinfo = False
# 2. ๋ชจ๋ธ ์ถ”๋ก 
if model is not None and tokenizer is not None:
try:
inputs = tokenizer(cleaned_input, return_tensors="pt", truncation=True, max_length=512).to(device)
with torch.no_grad():
outputs = model(**inputs)
logits = outputs.logits
probs = torch.softmax(logits, dim=-1)[0]
# Index 1: ์˜ค์ •๋ณด/์œ„ํ—˜ ํ™•๋ฅ 
fake_prob = probs[1].item() * 100
if fake_prob > 50.0:
is_misinfo = True
except Exception as e:
print(f"์ถ”๋ก  ์˜ค๋ฅ˜: {e}")
else:
# ๊ฐ€์ค‘์น˜ ๋ฏธ๋กœ๋“œ ์‹œ ์ •๊ตํ•œ ํ‚ค์›Œ๋“œ ๋ฃฐ์…‹ (์˜คํŒ ๋ฐฉ์ง€)
strong_misinfo_keywords = ["์ง€๊ตฌ์˜จ๋‚œํ™”๋Š” ๊ฑฐ์ง“", "๊ธฐํ›„๋ณ€ํ™” ์Œ๋ชจ๋ก ", "๊ฐ€์งœ๋‰ด์Šค ์กฐ์ž‘", "๋น™ํ•˜๊ธฐ๊ฐ€ ์˜ค๊ณ ์žˆ๋‹ค"]
if any(kw in cleaned_input for kw in strong_misinfo_keywords):
fake_prob = 89.1234
is_misinfo = True
else:
fake_prob = 0.0007
is_misinfo = False
# 3. xAI ์–ดํ…์…˜ ํ•˜์ด๋ผ์ดํŠธ ์ƒ์„ฑ
words = cleaned_input.split()
highlighted_spans = []
target_keywords = ["๊ธฐํ›„๋ณ€ํ™”", "์Œ๋ชจ", "์ง€๊ตฌ์˜จ๋‚œํ™”", "๊ฑฐ์ง“", "๊ณผํ•™์ž", "์˜คํŒ", "๋ฐฑ์‹ ", "๋ถ€์ž‘์šฉ"]
for w in words:
is_target = any(tk in w for tk in target_keywords)
if is_target:
score = 0.85 if is_misinfo else 0.25
else:
score = 0.05
highlighted_spans.append((w + " ", score))
# 4. UI ์Šค์ผ€์น˜ ์ƒํƒœ ๋ฐฐ์ง€
if is_misinfo:
badge_html = '''
<div style="display: inline-flex; align-items: center; gap: 8px; background-color: #fee2e2; border: 2px solid #ef4444; color: #991b1b; padding: 8px 16px; border-radius: 20px; font-weight: bold; font-size: 1.1em;">
<span style="width: 14px; height: 14px; background-color: #ef4444; border-radius: 50%; display: inline-block;"></span>
<span>ํŒ์ •๊ฒฐ๊ณผ: ์˜ค์ •๋ณด ์˜์‹ฌ ๊ฒฝ๊ณ </span>
</div>
'''
else:
badge_html = '''
<div style="display: inline-flex; align-items: center; gap: 8px; background-color: #e0f2fe; border: 2px solid #0284c7; color: #075985; padding: 8px 16px; border-radius: 20px; font-weight: bold; font-size: 1.1em;">
<span style="width: 14px; height: 14px; background-color: #0284c7; border-radius: 50%; display: inline-block;"></span>
<span style="background-color: #f3e8ff; color: #6b21a8; padding: 2px 8px; border-radius: 12px; font-size: 0.9em;">Active Dolphin</span>
<span>ํŒ์ •๊ฒฐ๊ณผ: ์ •์ƒ / ์‹ ๋ขฐ ๊ธฐ์‚ฌ</span>
</div>
'''
risk_score_text = f"์œ„ํ—˜๋„ ์ ์ˆ˜ +{fake_prob:.4f}%"
limitation_warning = "โš ๏ธ [ํ•œ๊ณ„ ๊ณ ์ง€] ํ’์žยท๋ฐ˜์–ด๋ฒ• ํ…์ŠคํŠธ ์ •ํ™•๋„ ๋‚ฎ์Œ! (๊ณต์‹ ๋ ฅ ์—†๋Š” ๊ธฐ๊ด€์˜ ์˜คํŒ ๊ฐ€๋Šฅ์„ฑ ์กด์žฌ)"
return badge_html, highlighted_spans, risk_score_text, limitation_warning
def file_appeal(reason, email):
if not reason or not email:
return "โš ๏ธ ์ด์˜ ์ œ๊ธฐ ์‚ฌ์œ ์™€ ์—ฐ๋ฝ๋ฐ›์„ ๊ฐœ๋ฐœ์ž Email์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”."
return f"โœ… ์ด์˜ ์ œ๊ธฐ๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์ ‘์ˆ˜๋˜์—ˆ์Šต๋‹ˆ๋‹ค. (์ ‘์ˆ˜ ๋ฉ”์ผ: {email})\n๋‹ด๋‹น์ž(Team 3) ๊ฒ€ํ†  ํ›„ ๋‹ต๋ณ€ ๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค."
css = """
.main-container { max-width: 900px; margin: 0 auto; font-family: 'Pretendard', sans-serif; }
.sketch-card { border: 2px solid #333; border-radius: 16px; padding: 20px; background: #fff; box-shadow: 4px 4px 0px #333; margin-bottom: 20px; }
.highlight-title { text-align: center; font-size: 1.2em; font-weight: bold; border: 2px solid #333; border-radius: 20px; width: fit-content; padding: 4px 20px; margin: 0 auto 15px auto; background: #fff; }
.disclaimer-box { border: 2px solid #eab308; background: #fefce8; color: #854d0e; padding: 12px 16px; border-radius: 12px; font-weight: bold; font-size: 0.95em; }
"""
with gr.Blocks(css=css, title="์ฑ…์ž„์•ˆ์ „ AI - ๊ธฐํ›„ ์˜ค์ •๋ณด ๊ฐ์ง€๊ธฐ") as demo:
gr.Markdown("# ๐ŸŒ ์ฑ…์ž„์•ˆ์ „ AI: ๊ธฐํ›„ ์˜ค์ •๋ณด ๊ฐ์ง€ ๋ฐ xAI ๋ถ„์„ ์‹œ์Šคํ…œ\n**Team 3 | ๊ฐœ๋ฐœ์ผ์ž: 2026-08-13 | LoRA Inference Engine ๊ธฐ๋ฐ˜**")
with gr.Tabs():
with gr.TabItem("๐Ÿ” AI ์˜ค์ •๋ณด ๊ฐ์ง€๊ธฐ (Inference UI)"):
with gr.Column(elem_classes=["main-container"]):
input_text = gr.Textbox(label="๋‰ด์Šค ๊ธฐ์‚ฌ ๋˜๋Š” ๊ธฐํ›„ ๊ด€๋ จ ํ…์ŠคํŠธ ์ž…๋ ฅ", placeholder="๋ถ„์„ํ•  ๊ธฐํ›„ ๊ด€๋ จ ๋‰ด์Šค๋‚˜ ํ…์ŠคํŠธ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”...", lines=4)
btn_submit = gr.Button("๐Ÿš€ ๊ฒฐ๊ณผ ๋ถ„์„ ์‹คํ–‰ (Run Analysis)", variant="primary")
gr.Markdown("---")
badge_output = gr.HTML(value='<div style="color: #666;">ํ…์ŠคํŠธ๋ฅผ ์ž…๋ ฅํ•œ ํ›„ ๋ถ„์„ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด ํŒ์ • ๊ฒฐ๊ณผ๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.</div>', label="ํŒ์ •๊ฒฐ๊ณผ")
with gr.Column(elem_classes=["sketch-card"]):
gr.HTML('<div class="highlight-title">Highlight Word (xAI ์–ดํ…์…˜ ๋ถ„์„)</div>')
highlight_output = gr.HighlightedText(label="์ค‘์š” ๋‹จ์–ด ์–ดํ…์…˜ ๊ฐ€์ค‘์น˜", combine_adjacent=False, show_legend=True)
with gr.Row(elem_classes=["sketch-card"]):
gr.Button("๊ฒฐ๊ณผ", variant="secondary", interactive=False)
risk_score_output = gr.Textbox(value="์œ„ํ—˜๋„ ์ ์ˆ˜ +0.0007%", label="์œ„ํ—˜๋„ ์ ์ˆ˜", interactive=False)
with gr.Column(elem_classes=["disclaimer-box"]):
limitation_output = gr.Markdown("ํ’์ž-๋ฐ˜์–ด๋ฒ• ํ…์ŠคํŠธ ์ •ํ™•๋„ ๋‚ฎ์Œ! โš ๏ธ (Model Card ํ•œ๊ณ„ ์‚ฌ์ „ ๊ณ ์ง€)")
with gr.Accordion("โš–๏ธ ์ด์˜ ์ œ๊ธฐ ํ†ต๋กœ (Model Card ์œค๋ฆฌ์  ์ฑ…์ž„ ์„ ์–ธ)", open=False):
gr.Markdown("๋ชจ๋ธ์˜ ํŒ์ • ๊ฒฐ๊ณผ์— ์˜คํŒ์ด ์žˆ๊ฑฐ๋‚˜ ์ด์˜๊ฐ€ ์žˆ์œผ์‹  ๊ฒฝ์šฐ ์•„๋ž˜ ์„œ์‹์„ ์ œ์ถœํ•ด์ฃผ์„ธ์š”.")
appeal_reason = gr.Textbox(label="์ด์˜ ์ œ๊ธฐ ์‚ฌ์œ  ๋ฐ ์†Œ๋ช… ๋‚ด์šฉ")
appeal_email = gr.Textbox(label="๊ฐœ๋ฐœ์ž Email")
btn_appeal = gr.Button("์ด์˜ ์ œ๊ธฐ ์ œ์ถœ")
appeal_status = gr.Textbox(label="์ ‘์ˆ˜ ์ƒํƒœ", interactive=False)
btn_appeal.click(fn=file_appeal, inputs=[appeal_reason, appeal_email], outputs=[appeal_status])
btn_submit.click(fn=analyze_climate_text, inputs=[input_text], outputs=[badge_output, highlight_output, risk_score_output, limitation_output])
with gr.TabItem("๐Ÿ“‹ Model Card (๋ชจ๋ธ ์นด๋“œ ๋ฐ ์ฑ…์ž„ ์„ ์–ธ)"):
gr.Markdown("""
## ๐Ÿ“„ ์ฑ…์ž„์•ˆ์ „ AI Model Card
- **๋ชจ๋ธ๋ช…**: Climate Misinfo LoRA Detector
- **ํŒ€๋ช… ๋ฐ ์ž‘์„ฑ์ผ**: Team 3 | 2026-08-13
- **์˜๋„๋œ ์‚ฌ์šฉ**: ๊ธฐํ›„ ๋ณ€ํ™” ๊ด€๋ จ ๊ธฐ์‚ฌ ๊ฒ€์ฆ / ๊ธˆ์ง€: ์˜ํ•™ยท๋ฒ•๋ฅ ์  ์ž๋™ ๊ทœ์ œ
- **ํ•œ๊ณ„ ๋ฐ ์œ„ํ—˜**: ํ’์žยท๋ฐ˜์–ด๋ฒ• ํ—ค๋“œ๋ผ์ธ ๋ฐ ๋งฅ๋ฝ ๋ˆ„๋ฝ ์‹œ ์˜คํŒ ๊ฐ€๋Šฅ์„ฑ ์กด์žฌ
- **๊ฐœ๋ฐœ์ž ์ฑ…์ž„ ์„ ์–ธ**: ํ•œ๊ณ„๋ฅผ ์‚ฌ์ „ ๊ณ ์ง€ํ•˜๋ฉฐ ์ด์˜ ์ œ๊ธฐ ํ†ต๋กœ๋ฅผ ํ†ตํ•ด ์ˆ˜๋ ด ๋ฐ ๊ฐœ์„ ํ•จ.
""")
with gr.TabItem("โš™๏ธ ์•Œ๊ณ ๋ฆฌ์ฆ˜ ํ๋ฆ„๋„ (Algorithm Flowchart)"):
gr.Markdown("1. User -> 2. Preprocessing -> 3. Tokenizing -> 4. LoRA Inference Engine -> 5. xAI Extraction -> 6. Attention Analysis -> 7. Output Generation -> 8. Result & Disclaimer")
demo.launch()