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 (
'
⚠️ 분석할 텍스트를 입력해주세요.
',
"",
"위험도 점수: 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 = '''
판정결과: 오정보 의심 경고
'''
else:
badge_html = '''
Active Dolphin
판정결과: 정상 / 신뢰 기사
'''
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='텍스트를 입력한 후 분석 버튼을 누르면 판정 결과가 표시됩니다.
', label="판정결과")
with gr.Column(elem_classes=["sketch-card"]):
gr.HTML('Highlight Word (xAI 어텐션 분석)
')
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()