import os import pandas as pd import gradio as gr import torch from transformers import AutoTokenizer, AutoModelForSequenceClassification # ---------------------------------------------------- # 1. Model & LoRA Adapter Setup # ---------------------------------------------------- # 기본 모델 지정 (예: kcbert-base 또는 klue/bert-base 등) BASE_MODEL_NAME = "beomi/kcbert-base" LORA_ADAPTER_DIR = "./lora_adapter" # LoRA 어댑터 폴더가 없을 경우 시뮬레이션용 더미 파일 생성 예시 if not os.path.exists(LORA_ADAPTER_DIR): os.makedirs(LORA_ADAPTER_DIR, exist_ok=True) tokenizer = None model = None def load_ai_model(): global tokenizer, model try: tokenizer = AutoTokenizer.from_pretrained(BASE_MODEL_NAME) # Sequence Classification (0: 정상, 1: 위험) base_model = AutoModelForSequenceClassification.from_pretrained(BASE_MODEL_NAME, num_labels=2) # LoRA 어댑터 적용 if os.path.exists(os.path.join(LORA_ADAPTER_DIR, "adapter_model.bin")) or os.path.exists(os.path.join(LORA_ADAPTER_DIR, "adapter_model.safetensors")): from peft import PeftModel model = PeftModel.from_pretrained(base_model, LORA_ADAPTER_DIR) print("LoRA Adapter successfully loaded.") else: model = base_model print("Base model loaded (LoRA adapter fallback).") model.eval() except Exception as e: print(f"Model load notice: {e}") tokenizer = None model = None # 모델 로드 시도 load_ai_model() # ---------------------------------------------------- # 2. Inference Logic # ---------------------------------------------------- def analyze_text(text): if not text or len(text.strip()) == 0: return "텍스트를 입력해주세요.", "대기 중", 0.0, "입력값이 없습니다." # 16자 이상 제한 가이드 체크 (프롬프트 설계 규칙 적용) length_warning = "" if len(text) < 16: length_warning = "⚠️ [주의] 입력된 텍스트가 16자 미만입니다. 프롬프트 규칙(16자 이상)을 확인하세요." else: length_warning = "✅ 프롬프트 제약조건(16자 이상) 만족" if model is not None and tokenizer is not None: try: inputs = tokenizer(text, return_tensors="pt", truncation=True, max_length=128) with torch.no_grad(): outputs = model(**inputs) probs = torch.softmax(outputs.logits, dim=-1)[0] risk_score = probs[1].item() # 1: 위험 확률 label_code = 1 if risk_score > 0.5 else 0 label_str = "1 (위험)" if label_code == 1 else "0 (정상)" confidence = risk_score if label_code == 1 else (1 - risk_score) return label_str, f"{confidence*100:.1f}%", risk_score, length_warning except Exception as e: pass # 모델 준비 전 또는 로컬 폴백 모드 (Rule-based / Keyword heuristic) danger_keywords = ["위험", "유해", "폭력", "편향", "공격", "욕설", "불법", "차별"] has_danger = any(kw in text for kw in danger_keywords) if has_danger: return "1 (위험)", "88.5%", 0.885, length_warning else: return "0 (정상)", "94.2%", 0.058, length_warning # 데이터 저장용 글로벌 데이터프레임 (검수 이력 관리) review_dataset = [] def add_to_dataset(input_text, ai_label, user_label, edit_note): if not input_text: return "❌ 입력 텍스트가 없습니다.", pd.DataFrame(review_dataset) # 규칙 체크: text, label 명칭 준수 / label은 0 또는 1 숫자 try: clean_label = int(user_label) except: clean_label = 1 if "위험" in str(user_label) or "1" in str(user_label) else 0 record = { "text": input_text, "label": clean_label, # 0(정상) 또는 1(위험) "ai_predicted": ai_label, "user_edited": "수정됨" if edit_note else "원본 유지", "review_note": edit_note if edit_note else "인간 검수 완료" } review_dataset.append(record) df = pd.DataFrame(review_dataset) return f"✅ 성공적으로 검수 데이터셋에 반영되었습니다. (총 {len(review_dataset)}건)", df def export_csv(): if not review_dataset: df_empty = pd.DataFrame(columns=["text", "label"]) df_empty.to_csv("dataset.csv", index=False, encoding="utf-8-sig") return "dataset.csv" # 8차시 pd.read_csv 호환 표준 규격 export (text, label 두 컬럼 필수) export_df = pd.DataFrame(review_dataset)[["text", "label"]] file_path = "dataset_export.csv" export_df.to_csv(file_path, index=False, encoding="utf-8-sig") return file_path # ---------------------------------------------------- # 3. Gradio Interface Definition # ---------------------------------------------------- custom_css = """ .model-card-box { background-color: #fff3cd; border: 1px solid #ffeeba; border-radius: 8px; padding: 15px; margin-bottom: 15px; } .rule-box { background-color: #e2e3e5; border-left: 4px solid #383d41; padding: 10px 15px; font-size: 0.9em; } """ with gr.Blocks(title="책임·안전 AI 데이터 검수 웹 인터페이스", css=custom_css) as demo: gr.Markdown("# 🛡️ 책임·안전 AI (고등학교 모듈 3-1) - 데이터 수집 & 전략적 검수") gr.Markdown("### 내가 설계한 기준으로 AI의 연료를 만들다") # === [Model Card 한계 고지 영역] === with gr.Accordion("⚠️ [필독] Model Card & 한계 고지 (Limitations & Disclaimers)", open=True): gr.HTML("""
0(정상), 1(위험) 판정은 절대적이지 않습니다. 반드시 학생(인간)이 비판적으로 검찰, 수정, 삭제해야 합니다.text, label 이며, label 값은 0(정상) 또는 1(위험)의 숫자 형식이어야 합니다. (UTF-8 인코딩)