import os
import re
import torch
import gradio as gr
from transformers import AutoTokenizer, AutoModelForSequenceClassification
from peft import PeftModel
# ==========================================
# 1. 모델 및 토크나이저 로드 (LoRa Inference Engine)
# ==========================================
LORA_DIR = "./lora_climate_misinfo"
BASE_MODEL_NAME = "klue/roberta-base" # 기본 Base LLM
device = "cuda" if torch.cuda.is_available() else "cpu"
try:
tokenizer = AutoTokenizer.from_pretrained(LORA_DIR)
base_model = AutoModelForSequenceClassification.from_pretrained(
BASE_MODEL_NAME,
num_labels=2
)
model = PeftModel.from_pretrained(base_model, LORA_DIR)
model.to(device)
model.eval()
MODEL_LOADED = True
except Exception as e:
print(f"[경고] 모델 로드 중 오류 발생 (시뮬레이션 모드로 전환): {e}")
MODEL_LOADED = False
# ==========================================
# 2. 알고리즘 파이프라인 함수
# ==========================================
def preprocess_text(text: str) -> str:
"""텍스트 전처리 (Preprocessing)"""
text = re.sub(r'\s+', ' ', text)
return text.strip()
def extract_xai_attention(text: str):
"""
XAI 추출 및 어텐션 맵 분석 (Attention Analysis & Highlight Word)
UI 스케치 양식(WHO -> 소규모 집단 등) 표현
"""
words = text.split()
if len(words) >= 2:
src_word = words[0]
target_word = words[1] if len(words) > 1 else "소규모 집단"
else:
src_word = "WHO"
target_word = "소규모 집단"
return src_word, target_word
def run_pipeline(input_text: str):
"""전체 추론 및 결과 생성 파이프라인"""
if not input_text.strip():
return "⚠️ 텍스트를 입력해주세요.", "", "", "", "내용을 입력하세요."
# Step 1: 텍스트 전처리
cleaned_text = preprocess_text(input_text)
# Step 2 & 3: 토큰화 및 LoRa 추론
if MODEL_LOADED:
inputs = tokenizer(cleaned_text, return_tensors="pt", truncation=True, max_length=512).to(device)
with torch.no_grad():
outputs = model(**inputs)
probs = torch.softmax(outputs.logits, dim=-1).squeeze().cpu().numpy()
fake_prob = float(probs[1]) if len(probs) > 1 else float(probs[0])
else:
# 가상 시뮬레이션 결과 (모델 파일 미로드 시)
fake_prob = 0.98
# Step 4: XAI 및 어텐션 분석
src_w, tgt_w = extract_xai_attention(cleaned_text)
# Step 5: 결과 구성 (UI 스케치 반영)
is_fake = fake_prob >= 0.5
verdict_badge = f"{'🔴 가짜뉴스' if is_fake else '🟢 진짜뉴스'} {fake_prob:.2f}"
model_tag = "Active Dolphin"
highlight_html = f"""
Highlight Word
{src_w}
➔
{tgt_w}
"""
risk_score_text = "위험도 점수 +0.0007%"
disclaimer_text = "⚠️ 풍자-반어법 텍스트 정확도 낮음!"
return verdict_badge, model_tag, highlight_html, risk_score_text, disclaimer_text
def submit_appeal(user_reason: str):
"""이의 제기 처리 함수"""
if not user_reason.strip():
return "이의 제기 사유를 입력해주세요."
return f"✅ 이의 제기가 접수되었습니다. (담당자 검토 예정 - dev-team3@example.com)"
# ==========================================
# 3. Gradio UI 인터페이스 (UI 스케치 구현)
# ==========================================
custom_css = """
.verdict-box {
border: 2px solid #333;
border-radius: 12px;
padding: 8px 15px;
display: inline-block;
font-size: 1.2rem;
font-weight: bold;
}
.risk-box {
border: 2px solid #333;
border-radius: 12px;
padding: 10px;
text-align: center;
font-size: 1.1rem;
margin-top: 10px;
}
.disclaimer-box {
border: 2px solid #ff4d4d;
background-color: #fff2f2;
color: #cc0000;
border-radius: 10px;
padding: 10px;
font-weight: bold;
text-align: center;
}
"""
with gr.Blocks(css=custom_css, title="기후변화 가짜뉴스 판별기") as demo:
gr.Markdown("## 🔍 기후변화 가짜뉴스 AI 판별 및 XAI 분석 시스템")
with gr.Row():
with gr.Column(scale=1):
input_area = gr.Textbox(
label="뉴스 기사 입력 (Preprocess & Tokenize)",
placeholder="검증할 기후변화/과학 관련 뉴스 텍스트를 입력하세요...",
lines=8
)
btn_analyze = gr.Button("🔍 분석 및 판정 실행", variant="primary")
with gr.Accordion("📋 Model Card 및 한계 고지 사항 확인", open=False):
gr.Markdown("""
- **학습 데이터:** 2024~2026년 기후/환경 관련 뉴스 200건
- **주요 한계:**
1. 공신력 있는 기관 위장 시 오판 가능성
2. **풍자/반어법 텍스트 정확도 낮음**
3. 거절 정보 복합 문맥의 기계 오판 가능성
- **개발자 책임 선언:** 본 모델의 판정 결과는 보조 지표이며 최종 결정 근거로 사용할 수 없습니다.
""")
with gr.Column(scale=1):
# 1. 판정 결과 및 모델 태그
with gr.Row():
out_verdict = gr.Textbox(label="판정결과", elem_classes=["verdict-box"], interactive=False)
out_tag = gr.Textbox(label="Model Tag", value="Active Dolphin", interactive=False)
# 2. Highlight Word (어텐션 맵 visual)
out_highlight = gr.HTML(label="Highlight Word")
# 3. 위험도 점수 결과
out_risk = gr.Textbox(label="결과", elem_classes=["risk-box"], interactive=False)
# 4. 한계 고지 경고 문구 (UI 스케치 하단)
out_disclaimer = gr.Textbox(
label="한계 고지 알림",
elem_classes=["disclaimer-box"],
interactive=False
)
# 5. 이의 제기 버튼 및 모달 레이아웃
with gr.Row():
btn_appeal_open = gr.Button("이의 제기", variant="secondary")
with gr.Group(visible=False) as appeal_group:
appeal_input = gr.Textbox(label="이의 제기 사유 입력", placeholder="오판이라 생각하는 이유를 작성해주세요.")
btn_appeal_submit = gr.Button("제출하기")
appeal_status = gr.Markdown()
# 이벤트 리스너 연결
btn_analyze.click(
fn=run_pipeline,
inputs=[input_area],
outputs=[out_verdict, out_tag, out_highlight, out_risk, out_disclaimer]
)
btn_appeal_open.click(
fn=lambda: gr.update(visible=True),
inputs=None,
outputs=[appeal_group]
)
btn_appeal_submit.click(
fn=submit_appeal,
inputs=[appeal_input],
outputs=[appeal_status]
)
if __name__ == "__main__":
demo.launch()