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()