kdhs commited on
Commit
88f6968
·
verified ·
1 Parent(s): 65cf024

Upload 7 files

Browse files
MODEL_CARD.md ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Model Card — AI 신약 개발 분석 모델
2
+
3
+ ## 1. 모델 개요
4
+ - 모델명: AI 신약 개발 분석 모델
5
+ - 목적: AI 신약 개발 관련 뉴스가 진짜뉴스인지 가짜뉴스인지 판단하기 위한 보조 모델
6
+ - 팀명: Team 3
7
+ - 일자: 2026-08-13
8
+
9
+ ## 2. 의도된 사용
10
+ - 사용 가능: AI 신약개발 관련 뉴스가 진짜인지 가짜인지 구분하는 보조 판별
11
+ - 사용 금지: 가짜뉴스를 배포하기 위해 모델을 우회할 수 있을 정도로 교묘한 가짜뉴스 생성에 사용하는 경우
12
+
13
+ ## 3. 학습 데이터
14
+ - 출처: 동아사이언스 DB
15
+ - 건수: 200건(정상 100건 + 위험 100건)
16
+ - 기간: 2024년~2026년
17
+ - 검수: CDA 200건
18
+
19
+ ## 4. 성능 지표
20
+ Model Card에 기재된 평가 결과:
21
+ - Accuracy: 100
22
+ - F1: 1.0
23
+ - TP: 20
24
+ - TN: 20
25
+ - FP: 0
26
+ - FN: 0
27
+ - 가장 약한 유형: 우회 공격
28
+
29
+ > 위 수치는 Model Card에 기록된 해당 평가 세트의 결과이며, 실제 서비스 환경에서 동일한 성능을 보장하지 않습니다.
30
+
31
+ ## 5. 한계 및 위험
32
+ 스트레스 테스트에서 다음 유형의 오판 가능성이 기록되어 있습니다.
33
+ 1. 공신력 있는 기관의 명칭을 도용하였을 때 오판 가능 — 위험 수준: 높음
34
+ 2. 의약품과 관련 없는 공신력 있는 기관의 명칭을 가져올 때 오판 가능 — 위험 수준: 중간
35
+ 3. 거짓된 정보를 구체적 수치로 작성하였을 때 오판 가능 — 위험 수준: 높음
36
+
37
+ 예시로 우회적·비꼬기 표현에서 오판 가능성이 제시되어 있습니다. 따라서 모델의 확률값이나 attention 강조 토큰을 사실검증의 근거로 단독 사용하면 안 됩니다.
38
+
39
+ ## 6. 개발자 책임 선언
40
+ - 이 AI가 가짜뉴스를 완벽히 판별하지 못한다는 한계를 인정합니다.
41
+ - 사용자에게 화면에서 한계를 고지합니다.
42
+ - 이의 제기 통로는 개발자 이메일을 사용하도록 설계하되, 원본 Model Card에는 실제 이메일 주소가 기재되어 있지 않아 배포자가 `CONTACT_EMAIL`을 설정해야 합니다.
43
+ - 투명성(근거 공개), 책임성(근거 공개), 피해 최소화(고지), 공정성(현황 인식)을 고려합니다.
44
+
45
+ ## XAI 표시 주의
46
+ 웹 인터페이스의 Highlight Word는 마지막 Transformer attention 층의 `[CLS]`→토큰 attention을 평균한 참고 신호입니다. Attention은 인과적 설명이나 외부 사실검증 결과가 아닙니다.
README.md CHANGED
@@ -1,13 +1,69 @@
1
  ---
2
- title: '2416'
3
- emoji: 🏢
4
- colorFrom: pink
5
- colorTo: blue
 
6
  sdk: gradio
7
- sdk_version: 6.24.0
8
- python_version: '3.13'
9
  app_file: app.py
 
 
 
10
  pinned: false
11
  ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: AI 신약개발 뉴스 판별
3
+ description: KoELECTRA + LoRA 기반 AI 신약개발 뉴스 위험 판별과 attention 참고 표시
4
+ emoji: 🧪
5
+ colorFrom: purple
6
+ colorTo: green
7
  sdk: gradio
8
+ sdk_version: 4.19.2
 
9
  app_file: app.py
10
+ python_version: "3.10"
11
+ models:
12
+ - monologg/koelectra-small-v3-discriminator
13
  pinned: false
14
  ---
15
 
16
+ # AI 신약개발 뉴스 판별 Space
17
+
18
+ 첨부된 알고리즘 흐름도와 UI 스케치를 Gradio로 구현한 Hugging Face Spaces용 프로젝트입니다.
19
+
20
+ ## 실행 흐름
21
+ 1. 사용자 뉴스 텍스트 입력
22
+ 2. 최소 전처리(공백 정리)
23
+ 3. KoELECTRA tokenizer로 토크나이징
24
+ 4. `monologg/koelectra-small-v3-discriminator` + LoRA adapter 추론
25
+ 5. 마지막 attention 층을 이용한 참고용 Highlight Word 추출
26
+ 6. 정상/가짜뉴스 위험 확률 표시
27
+ 7. Model Card 한계를 화면 상단과 하단에 상시 노출
28
+
29
+ ## 런타임 오류 방지 조치
30
+ - Python을 `3.10`으로 고정했습니다.
31
+ - `requirements.txt`는 지정 버전을 그대로 사용합니다.
32
+ - 업로드된 adapter의 원본 `adapter_config.json`은 PEFT 0.20.0에서 생성되어 `alora_invocation_tokens` 등 PEFT 0.9.0이 모르는 필드를 포함하고 있었습니다.
33
+ - 그래서 `lora_climate_misinfo/adapter_config.json`을 PEFT 0.9.0 호환 필드만 남긴 버전으로 교체했고, 원본은 `adapter_config.original_peft020.json`에 보관했습니다.
34
+ - `app.py`도 `LoraConfig`를 명시적으로 생성해 `PEFT 0.9.0`이 새 필드를 파싱하지 않도록 이중으로 방지합니다.
35
+ - LoRA 로드 실패 시 base model만으로 임의 판정하지 않고 UI에 오류를 표시합니다.
36
+
37
+ ## Space 업로드
38
+ 이 폴더의 **내용 전체**를 새 Hugging Face Space 저장소 루트에 업로드하면 됩니다. `README.md`, `app.py`, `requirements.txt`, `lora_climate_misinfo/`가 같은 루트 구조를 유지해야 합니다.
39
+
40
+ ## 이의 제기 이메일
41
+ 원본 Model Card에는 개발자 이메일 주소가 실제로 적혀 있지 않습니다. Space 설정의 Variables에 `CONTACT_EMAIL`을 추가하면 UI의 **이의 제기 안내** 버튼에 해당 주소가 표시됩니다.
42
+
43
+ ## 파일 구조
44
+ ```text
45
+ .
46
+ ├── app.py
47
+ ├── README.md
48
+ ├── requirements.txt
49
+ ├── MODEL_CARD.md
50
+ ├── lora_climate_misinfo/
51
+ │ ├── adapter_config.json
52
+ │ ├── adapter_config.original_peft020.json
53
+ │ ├── adapter_model.safetensors
54
+ │ ├── tokenizer.json
55
+ │ ├── tokenizer_config.json
56
+ │ └── README.md
57
+ └── assets/
58
+ ├── algorithm_flow.png
59
+ ├── ui_sketch.png
60
+ └── model_card_source.pdf
61
+ ```
62
+
63
+ ## Model Card 핵심 한계
64
+ - 공신력 있는 기관명을 도용한 문장: 오판 가능
65
+ - 의약품과 무관한 공신력 기관명을 가져온 문장: 오판 가능
66
+ - 거짓 정보를 구체적 수치로 작성한 문장: 오판 가능
67
+ - 우회적·비꼬기 표현: 취약 가능
68
+
69
+ 자세한 내용은 `MODEL_CARD.md`를 확인하세요.
adapter_config.json ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "alora_invocation_tokens": null,
3
+ "alpha_pattern": {},
4
+ "arrow_config": null,
5
+ "auto_mapping": null,
6
+ "base_model_name_or_path": "monologg/koelectra-small-v3-discriminator",
7
+ "bias": "none",
8
+ "corda_config": null,
9
+ "ensure_weight_tying": false,
10
+ "eva_config": null,
11
+ "exclude_modules": null,
12
+ "fan_in_fan_out": false,
13
+ "inference_mode": true,
14
+ "init_lora_weights": true,
15
+ "layer_replication": null,
16
+ "layers_pattern": null,
17
+ "layers_to_transform": null,
18
+ "loftq_config": {},
19
+ "lora_alpha": 16,
20
+ "lora_bias": false,
21
+ "lora_dropout": 0.1,
22
+ "lora_ga_config": null,
23
+ "megatron_config": null,
24
+ "megatron_core": "megatron.core",
25
+ "modules_to_save": [
26
+ "classifier",
27
+ "score"
28
+ ],
29
+ "monteclora_config": null,
30
+ "peft_type": "LORA",
31
+ "peft_version": "0.20.0",
32
+ "qalora_group_size": 16,
33
+ "r": 8,
34
+ "rank_pattern": {},
35
+ "revision": null,
36
+ "target_modules": [
37
+ "query",
38
+ "value"
39
+ ],
40
+ "target_parameters": null,
41
+ "task_type": "SEQ_CLS",
42
+ "trainable_token_indices": null,
43
+ "use_bdlora": null,
44
+ "use_dora": false,
45
+ "use_qalora": false,
46
+ "use_rslora": false,
47
+ "velora_config": null
48
+ }
adapter_model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:244409276fe68018fc658d111ecf24f29568385d4475ad1cf22a82da7d341e8e
3
+ size 665672
app.py ADDED
@@ -0,0 +1,279 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import html
2
+ import os
3
+ import re
4
+ from pathlib import Path
5
+
6
+ import gradio as gr
7
+ import torch
8
+ from peft import LoraConfig, PeftModel, TaskType
9
+ from transformers import AutoModelForSequenceClassification, AutoTokenizer
10
+
11
+ BASE_MODEL = "monologg/koelectra-small-v3-discriminator"
12
+ LORA_DIR = Path(__file__).parent / "lora_climate_misinfo"
13
+ MAX_LENGTH = 512
14
+ CONTACT_EMAIL = os.getenv("CONTACT_EMAIL", "").strip()
15
+ DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
16
+
17
+ # 학습 데이터의 라벨 정의: 0=정상, 1=위험(가짜뉴스)
18
+ LABELS = {0: "정상", 1: "가짜뉴스 위험"}
19
+
20
+ LIMITATION_TEXT = (
21
+ "이 모델은 AI 신약개발 관련 뉴스의 진위 판별을 돕는 보조 도구이며, 완벽한 사실검증기가 아닙니다. "
22
+ "Model Card의 스트레스 테스트에서는 공신력 있는 기관명을 도용한 문장, 의약품과 무관한 공신력 기관명을 "
23
+ "가져온 문장, 거짓 정보를 구체적 수치로 작성한 문장에서 오판 가능성이 확인되었습니다. "
24
+ "특히 우회적·비꼬기 표현도 취약할 수 있으므로 최종 판단은 원문 출처와 논문·기관 자료를 함께 확인하세요."
25
+ )
26
+
27
+ CUSTOM_CSS = r"""
28
+ .gradio-container {max-width: 980px !important; margin: 0 auto !important;}
29
+ #hero {text-align:center; margin-bottom: 8px;}
30
+ .warning-box {border: 1px solid #e5b94f; background:#fff8df; border-radius:14px; padding:14px 16px; margin:8px 0 18px 0;}
31
+ .result-card {border:2px solid #222; border-radius:16px; padding:16px; background:white; min-height:132px;}
32
+ .result-head {font-size:14px; color:#555; margin-bottom:8px;}
33
+ .result-main {display:flex; align-items:center; gap:10px; font-size:24px; font-weight:700;}
34
+ .dot {width:18px; height:18px; border-radius:50%; display:inline-block; flex:0 0 auto;}
35
+ .dot-risk {background:#c95b4b;}
36
+ .dot-safe {background:#4e9f6a;}
37
+ .score-line {margin-top:10px; font-size:16px;}
38
+ .xai-box {border:2px solid #222; border-radius:20px; padding:14px; background:#fff;}
39
+ .xai-title {display:inline-block; border:2px solid #222; border-radius:20px; padding:2px 12px; font-weight:700; margin-bottom:12px;}
40
+ .token-wrap {display:flex; flex-wrap:wrap; gap:8px;}
41
+ .token-chip {border:1px solid #777; border-radius:12px; padding:6px 9px; background:#f7f7f7;}
42
+ .token-chip strong {font-weight:700;}
43
+ .small-note {font-size:13px; color:#666; margin-top:10px;}
44
+ .disclaimer {border:1px solid #999; border-radius:14px; padding:12px 14px; background:#f7f7f7; font-size:14px;}
45
+ .error-card {border:2px solid #a33; border-radius:14px; padding:14px; background:#fff3f3; color:#7b1f1f;}
46
+ """
47
+
48
+
49
+ def preprocess_text(text: str) -> str:
50
+ """흐름도의 '텍스트 전처리' 단계. 의미를 훼손하지 않는 범위에서 공백만 정리합니다."""
51
+ text = (text or "").strip()
52
+ text = re.sub(r"\s+", " ", text)
53
+ return text
54
+
55
+
56
+ def build_peft_config() -> LoraConfig:
57
+ """PEFT 0.9.0과 호환되는 LoRA 설정을 코드에서 명시적으로 생성합니다.
58
+
59
+ 업로드된 어댑터는 PEFT 0.20.0에서 저장되어 새 필드가 포함되어 있었기 때문에,
60
+ 구버전에서 'unexpected keyword'가 나지 않도록 필요한 설정만 사용합니다.
61
+ """
62
+ return LoraConfig(
63
+ r=8,
64
+ lora_alpha=16,
65
+ target_modules=["query", "value"],
66
+ lora_dropout=0.1,
67
+ bias="none",
68
+ task_type=TaskType.SEQ_CLS,
69
+ modules_to_save=["classifier", "score"],
70
+ inference_mode=True,
71
+ use_rslora=False,
72
+ use_dora=False,
73
+ )
74
+
75
+
76
+ def load_model_bundle():
77
+ tokenizer = AutoTokenizer.from_pretrained(BASE_MODEL, use_fast=True)
78
+ base_model = AutoModelForSequenceClassification.from_pretrained(
79
+ BASE_MODEL,
80
+ num_labels=2,
81
+ )
82
+ peft_config = build_peft_config()
83
+ model = PeftModel.from_pretrained(
84
+ base_model,
85
+ str(LORA_DIR),
86
+ config=peft_config,
87
+ is_trainable=False,
88
+ )
89
+ model.to(DEVICE)
90
+ model.eval()
91
+ return tokenizer, model
92
+
93
+
94
+ TOKENIZER = None
95
+ MODEL = None
96
+ MODEL_LOAD_ERROR = None
97
+ try:
98
+ TOKENIZER, MODEL = load_model_bundle()
99
+ except Exception as exc: # Space 자체가 죽지 않고 UI에서 원인을 확인할 수 있게 함
100
+ MODEL_LOAD_ERROR = f"{type(exc).__name__}: {exc}"
101
+
102
+
103
+ def merge_wordpieces(tokens, scores):
104
+ """WordPiece 토큰을 사람이 읽기 쉬운 단위로 묶어 상위 항목을 반환합니다."""
105
+ merged = []
106
+ for token, score in zip(tokens, scores):
107
+ if token in {"[CLS]", "[SEP]", "[PAD]"}:
108
+ continue
109
+ if token.startswith("##") and merged:
110
+ prev_token, prev_score = merged[-1]
111
+ merged[-1] = (prev_token + token[2:], max(prev_score, float(score)))
112
+ else:
113
+ merged.append((token, float(score)))
114
+ # 같은 표면형이 반복되면 가장 높은 점수만 유지
115
+ best = {}
116
+ for token, score in merged:
117
+ token = token.strip()
118
+ if not token or token in {"[UNK]"}:
119
+ continue
120
+ best[token] = max(score, best.get(token, -1.0))
121
+ return sorted(best.items(), key=lambda x: x[1], reverse=True)
122
+
123
+
124
+ def extract_attention_guide(encoded, attentions, top_k=6):
125
+ """마지막 층의 [CLS]→토큰 평균 attention을 간단한 참고 신호로 사용합니다.
126
+
127
+ Attention은 인과적 설명이 아니므로 UI에도 그 한계를 명시합니다.
128
+ """
129
+ if not attentions:
130
+ return []
131
+ last = attentions[-1][0] # [heads, seq, seq]
132
+ cls_to_tokens = last[:, 0, :].mean(dim=0).detach().cpu().tolist()
133
+ ids = encoded["input_ids"][0].detach().cpu().tolist()
134
+ tokens = TOKENIZER.convert_ids_to_tokens(ids)
135
+ merged = merge_wordpieces(tokens, cls_to_tokens)
136
+ return merged[:top_k]
137
+
138
+
139
+ def result_html(pred_label: int, risk_prob: float, confidence: float):
140
+ is_risk = pred_label == 1
141
+ dot_class = "dot-risk" if is_risk else "dot-safe"
142
+ title = "가짜뉴스 위험" if is_risk else "정상 가능성 높음"
143
+ return f"""
144
+ <div class='result-card'>
145
+ <div class='result-head'>판정결과</div>
146
+ <div class='result-main'><span class='dot {dot_class}'></span>{html.escape(title)} · {confidence:.3f}</div>
147
+ <div class='score-line'><b>위험도 점수</b> {risk_prob * 100:.2f}%</div>
148
+ <div class='small-note'>0=정상, 1=위험 라벨 기준의 모델 확률입니다. 확률값 자체가 사실의 증명은 아닙니다.</div>
149
+ </div>
150
+ """
151
+
152
+
153
+ def xai_html(guide):
154
+ if not guide:
155
+ chips = "<span class='token-chip'>추출된 토큰 없음</span>"
156
+ else:
157
+ max_score = max(score for _, score in guide) or 1.0
158
+ chunks = []
159
+ for token, score in guide:
160
+ relative = score / max_score
161
+ chunks.append(
162
+ f"<span class='token-chip'><strong>{html.escape(token)}</strong> · attention {relative:.2f}</span>"
163
+ )
164
+ chips = "".join(chunks)
165
+ return f"""
166
+ <div class='xai-box'>
167
+ <div class='xai-title'>Highlight Word · Attention Analysis</div>
168
+ <div class='token-wrap'>{chips}</div>
169
+ <div class='small-note'>위 토큰은 마지막 attention 층에서 상대적으로 크게 주목된 항목입니다. 인과적 근거나 사실검증 근거로 해석하면 안 됩니다.</div>
170
+ </div>
171
+ """
172
+
173
+
174
+ def analyze(text):
175
+ cleaned = preprocess_text(text)
176
+ if not cleaned:
177
+ return (
178
+ "<div class='error-card'>분석할 뉴스 문장을 입력하세요.</div>",
179
+ xai_html([]),
180
+ "입력 없음",
181
+ )
182
+ if MODEL_LOAD_ERROR:
183
+ return (
184
+ "<div class='error-card'><b>모델 로드 실패</b><br>Base model로 임의 판정하지 않습니다.<br>"
185
+ + html.escape(MODEL_LOAD_ERROR)
186
+ + "</div>",
187
+ xai_html([]),
188
+ "모델 로드 오류 — README의 런타임 점검 항목을 확인하세요.",
189
+ )
190
+
191
+ encoded = TOKENIZER(
192
+ cleaned,
193
+ return_tensors="pt",
194
+ truncation=True,
195
+ max_length=MAX_LENGTH,
196
+ )
197
+ encoded = {k: v.to(DEVICE) for k, v in encoded.items()}
198
+
199
+ with torch.inference_mode():
200
+ outputs = MODEL(
201
+ **encoded,
202
+ output_attentions=True,
203
+ return_dict=True,
204
+ )
205
+ probs = torch.softmax(outputs.logits, dim=-1)[0].detach().cpu()
206
+
207
+ pred = int(torch.argmax(probs).item())
208
+ confidence = float(probs[pred].item())
209
+ risk_prob = float(probs[1].item())
210
+ guide = extract_attention_guide(encoded, outputs.attentions, top_k=6)
211
+
212
+ status = (
213
+ f"전처리 → 토크나이징 → KoELECTRA + LoRA 추론 → attention 기반 XAI 참고 → 결과 생성 완료. "
214
+ f"판정: {LABELS[pred]}"
215
+ )
216
+ return result_html(pred, risk_prob, confidence), xai_html(guide), status
217
+
218
+
219
+ def appeal_message():
220
+ if CONTACT_EMAIL:
221
+ return f"이의 제기/오판 신고 연락처: {CONTACT_EMAIL}"
222
+ return (
223
+ "Model Card에는 개발자 이메일로 이의를 제기한다고 되어 있지만 실제 이메일 주소는 기재되어 있지 않습니다. "
224
+ "배포 전 Hugging Face Space의 CONTACT_EMAIL 변수에 개발자 이메일을 등록하세요."
225
+ )
226
+
227
+
228
+ with gr.Blocks(css=CUSTOM_CSS, title="AI 신약개발 뉴스 판별") as demo:
229
+ gr.Markdown(
230
+ "# AI 신약개발 뉴스 판별 모델\n"
231
+ "AI 신약개발 관련 뉴스 문장을 입력하면 LoRA 분류 모델이 **정상/위험** 가능성을 표시하고, "
232
+ "attention 기반 참고 토큰을 함께 보여줍니다.",
233
+ elem_id="hero",
234
+ )
235
+
236
+ gr.HTML(f"<div class='warning-box'><b>⚠ Model Card 한계 고지</b><br>{html.escape(LIMITATION_TEXT)}</div>")
237
+
238
+ with gr.Row():
239
+ with gr.Column(scale=3):
240
+ news_input = gr.Textbox(
241
+ label="뉴스 텍스트",
242
+ placeholder="AI 신약개발 관련 뉴스 문장 또는 기사 일부를 입력하세요.",
243
+ lines=10,
244
+ )
245
+ analyze_btn = gr.Button("분석하기", variant="primary")
246
+ gr.Examples(
247
+ examples=[
248
+ ["연구진은 AI를 활용해 신약 후보 물질을 선별했으며, 실제 임상 효과는 추가 검증이 필요하다고 밝혔다."],
249
+ ["AI가 분석한 결과 이 치료제는 모든 암을 100% 치료하며 이미 세계 최고 기관이 효과를 보증했다."],
250
+ ],
251
+ inputs=news_input,
252
+ )
253
+ with gr.Column(scale=2):
254
+ result = gr.HTML("<div class='result-card'><div class='result-head'>판정결과</div><div>분석 전</div></div>")
255
+ status = gr.Textbox(label="처리 단계", value="대기 중", interactive=False)
256
+
257
+ xai = gr.HTML(xai_html([]))
258
+
259
+ gr.HTML(f"<div class='disclaimer'><b>최종 결과 및 한계 고지</b><br>{html.escape(LIMITATION_TEXT)}</div>")
260
+ with gr.Row():
261
+ appeal_btn = gr.Button("이의 제기 안내")
262
+ appeal_output = gr.Textbox(label="이의 제기", interactive=False)
263
+
264
+ with gr.Accordion("Model Card 요약", open=False):
265
+ gr.Markdown(
266
+ "- **모델명:** AI 신약 개발 분석 모델\n"
267
+ "- **목적:** AI 신약개발 관련 뉴스가 진짜뉴스인지 가짜뉴스인지 구분하기 위한 보조 판별\n"
268
+ "- **학습 데이터:** 동아사이언스 DB, 2024~2026년, 200건(정상 100 + 위험 100)\n"
269
+ "- **Model Card 기재 성능:** Accuracy 100, F1 1.0, TP 20, TN 20, FP 0, FN 0\n"
270
+ "- **가장 약한 유형:** 우회 공격\n"
271
+ "- **금지된 사용:** 가짜뉴스 생성을 고도화하거나 모델을 우회하기 위한 용도"
272
+ )
273
+
274
+ analyze_btn.click(analyze, inputs=news_input, outputs=[result, xai, status])
275
+ news_input.submit(analyze, inputs=news_input, outputs=[result, xai, status])
276
+ appeal_btn.click(appeal_message, outputs=appeal_output)
277
+
278
+ if __name__ == "__main__":
279
+ demo.queue().launch()
tokenizer.json ADDED
The diff for this file is too large to render. See raw diff
 
tokenizer_config.json ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "backend": "tokenizers",
3
+ "cls_token": "[CLS]",
4
+ "do_lower_case": false,
5
+ "is_local": false,
6
+ "local_files_only": false,
7
+ "mask_token": "[MASK]",
8
+ "model_max_length": 512,
9
+ "pad_token": "[PAD]",
10
+ "sep_token": "[SEP]",
11
+ "strip_accents": null,
12
+ "tokenize_chinese_chars": true,
13
+ "tokenizer_class": "BertTokenizer",
14
+ "unk_token": "[UNK]"
15
+ }