# RM-DETECT — 기술 아키텍처 설명서 > AI 작성 텍스트 판별기의 내부 구조, 데이터 흐름, 피처 사양, 모델, API, 배포를 정리한 기술 문서입니다. > 개요는 [README](README.md)를, 아래는 구현 세부를 다룹니다. ![RM-DETECT architecture](docs/architecture.png) --- ## 1. 개요 RM-DETECT는 입력 텍스트가 대규모 언어모델(LLM)로 작성됐을 가능성을 추정하는 **독립적으로 구현한 판별기**입니다. 두 가지 상호 보완적 신호를 하나의 분류기로 결합합니다: - **토큰 예측 가능성(perplexity)** — LLM이 만든 글은 더 "예측 가능"하다는, AI 텍스트 탐지 문헌(GLTR, DetectGPT 등)에서 확립된 원리 - **문체·통계 분석(stylometry)** — 언어모델 없이 계산하는 문장 리듬·어휘·문장부호·어미 특징 한국어와 영어를 모두 지원하며, 문서 전체 점수와 **문단별 점수**를 동시에 산출합니다. 모든 추론이 서버 내부에서 실행되어 **외부 유료 API를 호출하지 않습니다.** --- ## 2. 컴포넌트 구조 ``` rmdetect/ ├── app.py # FastAPI 백엔드 + RMDetector 오케스트레이션 ├── aidetect/ # 탐지 엔진 (프레임워크 독립) │ ├── perplexity.py # ① 토큰 예측 가능성 (GPT-2 / KoGPT2) │ ├── stylometry.py # ② 문체·통계 피처 (model-free) │ ├── langid.py # 언어 감지 (Hangul-ratio router) │ ├── features.py # ①+② 병합 → 28차원 벡터 │ └── __init__.py ├── ai_detector_model.joblib # 학습된 분류기 (StandardScaler + LogReg) ├── static/ # 단일 페이지 웹 UI │ ├── index.html │ ├── app.js │ └── style.css ├── requirements.txt ├── run.sh # 로컬 실행 스크립트 └── Dockerfile # 컨테이너/HF Spaces 배포 ``` 계층은 세 겹입니다: **UI(static) → API(app.py) → 엔진(aidetect)**. 엔진은 웹 프레임워크에 의존하지 않으므로 CLI·배치·다른 서비스에서도 그대로 재사용할 수 있습니다. --- ## 3. 요청 처리 흐름 (`POST /detect`) 1. **언어 감지** — `langid.detect_lang()` 가 한글/라틴 문자 비율로 `ko`/`en` 판정 (요청에 `lang`이 명시되면 그대로 사용). 2. **문단 분할** — 빈 줄 기준으로 문단을 나누되, 각 문단의 **원문 문자 오프셋 `(start, end)`** 을 보존. 이 오프셋 덕분에 UI가 원문의 정확한 구간을 하이라이트할 수 있음. 3. **피처 추출** — 문서 전체와 각 문단에 대해 `features.full_features()` 를 호출해 28차원 벡터를 만듦. 4. **분류** — `StandardScaler → LogisticRegression` 파이프라인이 AI 확률을 산출하고, 판정 밴드에 매핑. 5. **기여 신호 계산** — 각 피처의 표준화 값 × 회귀 계수로 "이 판정을 AI/인간 쪽으로 민 상위 신호"를 추출. 6. **응답 직렬화** — 문서 점수 + 문단 배열(오프셋·확률·판정·flagged·상위 신호)을 JSON으로 반환. 문단이 `paragraph_min_chars`(기본 30자)보다 짧으면 `low_confidence=true`로 표시하고 flagged 대상에서 제외합니다. --- ## 4. 신호 계열 ① — 토큰 예측 가능성 (`perplexity.py`) 작은 causal LM을 언어별로 로드합니다: 영어 **GPT-2**, 한국어 **KoGPT2(skt/kogpt2-base-v2)**, 각각 ~125M 파라미터. - **원리** — LLM은 다음 토큰으로 높은 확률의 토큰을 고르므로, 기계 생성 텍스트는 perplexity가 낮고 모델이 상위로 꼽았을 토큰의 비율(top-k 적중률)이 높음. 인간 글은 "burstier"해서 낮은 확률의 단어를 더 자주 씀. - **산출 피처** (6개): `ppl`, `log_ppl`, `mean_logprob`, `std_logprob`, `median_logprob`, `topk_hit_rate` - **긴 문서 처리** — 컨텍스트 창(≤1024 토큰)을 넘는 텍스트는 **sliding window**로 처리하고, 겹치는 구간을 마스킹해 중복 집계를 방지. - **모델 로딩** — 로컬 `models/gpt2`·`models/kogpt2`가 있으면 오프라인으로 로드, 없으면 HuggingFace 허브에서 자동 다운로드(예: HF Spaces 첫 실행). --- ## 5. 신호 계열 ② — 문체·통계 (`stylometry.py`) 언어모델이 필요 없는 22개 피처. 한국어·영어 모두에 동작하는 정규식 기반 문장 분할을 사용합니다. | 그룹 | 피처 | 신호 방향 | |---|---|---| | 문장 리듬 | `burstiness`, `cv_sent_len`, `std_sent_len`, `mean_sent_len`, `mean_succ_diff`, `n_sents` | 인간은 문장 길이 변동이 큼; AI는 균일 | | 어휘 | `type_token_ratio`, `rep_bigram_rate`, `word_entropy`, `avg_word_len` | AI는 접속 구조를 반복 | | 문장부호 | `excl_per100`, `ques_per100`, `ellipsis_per100`, `comma_per100`, `quote_per100`, `emoji_per100`, `punct_variety` | 인간은 `!!`, `…`, 이모지 사용 ↑ | | 구어체 | `informal_per100` (ㅋㅋ, lol, 진짜…) | 인간 신호 | | 접속어 | `connective_per100` (종합적으로, 따라서, furthermore…) | AI 신호 | | 한국어 어미 | `ko_formal_per100`, `ko_casual_per100`, `ko_formal_minus_casual` | 격식체(하였습니다)는 AI 쪽, 구어체(~했음)는 인간 쪽 | --- ## 6. 분류기 (`ai_detector_model.joblib`) - **구조** — scikit-learn `Pipeline([StandardScaler, LogisticRegression(class_weight="balanced")])` - **입력** — `FEATURE_ORDER`로 고정된 **28차원** 벡터 (perplexity 6 + stylometry 22) - **성능** — 보정 데이터셋에서 **5-fold 교차검증 정확도 97.2%, AUC 0.994** (한국어 100%, 영어 93.8%) - **설명 가능성** — 선형 모델이라 각 피처의 (표준화 값 × 계수) 기여도를 그대로 노출. 측정된 상위 신호: 인간 쪽은 구어체·인용부호·말줄임표 밀도와 문장 burstiness, AI 쪽은 한국어 격식 어미·긴 단어·균일한 문장 리듬. --- ## 7. 판정 밴드 | AI 확률 | verdict | 문단 flagged | |---|---|---| | ≥ 0.85 | AI-generated | ✓ | | ≥ 0.60 | Likely AI | ✓ | | ≥ 0.40 | Mixed | | | ≥ 0.15 | Likely human | | | < 0.15 | Human-written | | 문단은 `ai_probability ≥ 0.60` 이고 `low_confidence`가 아닐 때 **flagged**(의심 영역)로 표시됩니다. --- ## 8. API 레퍼런스 ### `GET /health` ```json { "status": "ok", "model_loaded": true, "load_error": null, "uptime_s": 159.9, "max_chars": 20000 } ``` ### `POST /detect` 요청: ```json { "text": "검사할 글 ...", "lang": "ko" } ``` `lang`은 선택(`"ko"` | `"en"` | 생략 시 자동 감지). 응답(요약): ```jsonc { "language": "ko", "overall_ai_probability": 0.50, "verdict": "Mixed", "n_paragraphs": 3, "n_flagged": 1, "top_features": [ {"feature": "informal_per100", "contribution": -1.17, "direction": "human"} ], "paragraphs": [ { "index": 0, "start": 0, "end": 40, "text": "...", "ai_probability": 0.35, "verdict": "Likely human", "flagged": false, "low_confidence": false, "top_features": [ ... ] } ], "elapsed_ms": 2640.3 } ``` **오류 응답**: 빈 입력 `400` · 잘못된 `lang` `400` · 20000자 초과 `413` · 모델 미로딩 `503`. --- ## 9. 웹 UI (`static/`) 의존성 없는 순수 HTML/CSS/바닐라 JS. 텍스트 입력 → `fetch("/detect")` → 결과 렌더: - 전체 AI 확률을 SVG 도넛 게이지 + verdict로 표시 - 문서 수준 상위 기여 신호를 칩으로 표시 - 문단을 확률에 따라 초록→노랑→빨강으로 색상 등급화한 카드로 재현, 카드를 클릭하면 그 문단의 상위 신호가 펼쳐짐 - 한국어·영어 라벨 병기, Cmd/Ctrl+Enter 단축키 --- ## 10. 배포 ### 로컬 ```bash pip install -r requirements.txt ./run.sh # http://127.0.0.1:8000 ``` 로컬 `models/`가 있으면 오프라인으로 즉시 로드, 없으면 허브에서 모델을 받음. ### Docker / Hugging Face Spaces `Dockerfile`은 HF Spaces 규격(비루트 UID 1000, 포트 7860, HF 캐시 경로)에 맞춰져 있습니다. `README.md` 상단의 YAML 헤더(`sdk: docker`, `app_port: 7860`)로 Spaces가 설정을 읽습니다. 컨테이너에는 코드만 담고, 언어모델은 첫 실행 시 허브에서 받아 캐시합니다. --- ## 11. 한계 - **작은 보정셋(n=36)** 과 **LLM이 모사한 "인간" 샘플** — 형식적인 실제 인간 글(논문, 정제된 자기소개서)은 실제보다 더 자주 오탐될 수 있음. - **작은 참조 LM(GPT-2/KoGPT2, ~125M)** — 더 크거나 instruction-tuned 모델이면 perplexity 신호가 더 선명해짐. - **패러프레이즈에 취약** — burstiness·구어체에 반응하므로, 구어체를 섞고 문장 길이를 흩뜨리는 "휴머나이징" 편집이 AI 점수를 낮춤. - **CPU 지연** — 멀티코어에서 요청당 1~2초, 공유 무료 CPU에서는 더 느림. RM-DETECT는 **연구용 프로토타입**이며 확률적 추정입니다. 학문적 부정행위·채용 등 중대한 결정의 단독 근거로 사용하지 말고, 여러 신호 중 하나로만 활용하세요.