Metadata-Version: 2.4
Name: madipy
Version: 0.2.1
Classifier: Programming Language :: Rust
Classifier: Programming Language :: Python :: 3
Classifier: Natural Language :: Korean
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: MacOS
Classifier: Operating System :: POSIX :: Linux
Classifier: Topic :: Text Processing :: Linguistic
License-File: LICENSE-APACHE
License-File: LICENSE-MIT
Summary: madi(마디): 순수 Rust 한국어 형태소 분석기
Keywords: korean,morphological analyzer,nlp,형태소 분석
License: MIT OR Apache-2.0
Requires-Python: >=3.9
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Homepage, https://p14jeffwest.github.io/madi-release/
Project-URL: Repository, https://github.com/p14jeffwest/madi-release

# madipy — madi (마디) 한국어 형태소 분석기

순수 Rust 로 만든 한국어 형태소 분석기 **madi(마디)** 의 Python 패키지입니다. 국립국어원 모두의 말뭉치 형태 분석 지침을 따르는 세종 품사 체계로 분석하고, 형태소 분석을 이용한 문장 분리도 합니다. 품사는 기본으로 **한글 축약**(일반명·동사·주격조 …)으로 보여 주고, `tags='code'` 를 주면 NNG·VV·JKS 같은 코드로 보여 줍니다.

**English.** A Korean morphological analyzer written in pure Rust, with Python bindings. It follows the NIKL "Modu" corpus tagging guidelines (Sejong tag set) and also splits sentences using the morphological analysis. Part-of-speech labels are shown as short Korean names by default; pass `tags='code'` for the usual codes (NNG, VV, JKS …). Install with `pip install madipy`, import as `madi`. Models (about 87 MB) are downloaded automatically on first use and cached. Token fields are listed in §3.2 (attribute names are in English).

- 소개 페이지: https://p14jeffwest.github.io/madi-release/
- 모델·이용 조건: https://github.com/p14jeffwest/madi-release
- Rust: https://crates.io/crates/madi · https://docs.rs/madi

---

## 차례

1. 개요 — 설치부터 첫 분석까지
2. 설치와 모델 파일
3. 형태소 분리 방법
4. 문장 분리 방법
5. 여러 분석 후보 보기 — `analyze`
6. madi 가 사용하는 모드
7. 사용자 사전
8. 불용어 · 구간 지정
9. 여러 문장 · 큰 파일
10. 명령줄
11. Kiwi 에서 옮겨 오기
12. 자주 묻는 질문
13. 판본 · 이용 조건 · 인용
14. 성능 요약

---

## 1. 개요 — 설치부터 첫 분석까지

설치하고, 분석기를 하나 만들고, 문장을 넣으면 됩니다. 이 세 줄이 전부입니다.

```
pip install madipy
```
```python
import madi

m = madi.Analyzer()                       # 처음 한 번만 모델 파일(약 87 MB)을 받아 둡니다
m.pos('아버지가 방에 들어가신다.')
# [('아버지', '일반명'), ('가', '주격조'), ('방', '일반명'), ('에', '부사조'),
#  ('들어가', '동사'), ('시', '선어말'), ('ㄴ다', '종결어'), ('.', '마침')]

m.pos('아버지가 방에 들어가신다.', tags='code')      # 품사를 코드로
# [('아버지', 'NNG'), ('가', 'JKS'), ('방', 'NNG'), ('에', 'JKB'),
#  ('들어가', 'VV'), ('시', 'EP'), ('ㄴ다', 'EF'), ('.', 'SF')]
```

**결과를 읽는 법**
- 띄어쓰기로 나뉜 덩이(어절)를 다시 **형태소** 단위로 나눕니다. "들어가신다" = 들어가(동사) + 시(선어말 어미) + ㄴ다(종결 어미).
- 형태는 사전에 올라 있는 기본 꼴로 나옵니다. 활용하면서 모양이 바뀐 말은 원래 꼴로 되돌립니다(들어 → 듣, 무서운 → 무섭 + ㄴ).
- 품사 이름의 뜻은 §3.5 품사표에 있습니다.

`Analyzer` 는 한 번 만들어 계속 쓰는 것이 좋습니다. 만들 때 모델을 메모리에 올리므로(1초 안팎) 문장마다 새로 만들면 느립니다.

## 2. 설치와 모델 파일

### 2.1 설치

- Windows · macOS(Intel·Apple Silicon) · Linux(x86_64·aarch64), Python 3.9 이상.
- 설치 이름은 `madipy`, 코드에서는 `import madi` 입니다.

```
pip install madipy
```

### 2.2 모델 파일은 자동으로 받습니다 — 따로 할 일이 없습니다

madi 가 분석에 쓰는 사전과 언어 모델은 크기 때문에 패키지에 들어 있지 않습니다. 대신 이렇게 동작합니다.

1. **처음** `madi.Analyzer()` 를 만들 때 모델 파일을 인터넷에서 받아 사용자 캐시 폴더에 풀어 둡니다(약 87 MB 내려받기, 풀면 240 MB, 수십 초).
2. **그다음부터** `madi.Analyzer()` 는 받아 둔 파일을 그대로 씁니다. 다시 받지 않습니다.
3. 정밀 모드(`precise=True`, §6)를 처음 쓸 때 교사 모델(+27 MB)을 한 번 더 받습니다. 이것도 한 번뿐입니다.

보통의 사용자는 **여기까지 알면 됩니다.** 아래 2.3·2.4 는 특별한 환경에서만 필요합니다.

### 2.3 미리 받아 두거나 확인하고 싶을 때 (선택)

프로그램을 처음 돌릴 때 모델을 받느라 기다리는 것이 싫거나, 배포·CI 환경에서 미리 준비해 두고 싶을 때 씁니다. 하지 않아도 됩니다.

```
python -m madi download              # 기본 모델 미리 받기
python -m madi download --precise    # 정밀 모드 교사까지
python -m madi verify                # 받아 둔 파일이 온전한지(sha256) 확인
python -m madi info                  # 모델이 어디에 있고 무엇을 받았는지 보기
```

모델이 놓이는 곳은 Windows `%LOCALAPPDATA%\madi\models\models-kword5`, 그 밖에서는 `~/.cache/madi/models/models-kword5` 입니다. 다른 폴더에 두고 싶으면 환경 변수 `MADI_MODEL_DIR` 을 쓰거나 `madi.Analyzer(model_dir='폴더')` 로 지정합니다.

### 2.4 인터넷이 막힌 곳에서 쓸 때 (선택)

분석할 PC 에서 GitHub 에 접속할 수 없을 때만 해당합니다.

- 회사·학교 프록시를 거치는 경우: 표준 환경 변수 `HTTPS_PROXY` 를 설정하면 자동 내려받기가 그대로 됩니다.
- 아예 접속이 안 되는 경우: 다른 PC 에서 [Release 페이지](https://github.com/p14jeffwest/madi-release/releases)의 `.xz` 파일 네 개를 받아 한 폴더에 옮긴 뒤, 그 폴더를 가리켜 받게 합니다.
  ```
  set MADI_MODEL_URL=D:\madi-models      (Windows; Linux·macOS 는 export)
  python -m madi download
  ```
  또는 `.xz` 를 풀어 파일 경로를 직접 줍니다: `madi.Analyzer('madi.dict', lm='knlm.bin', nlm='model.nlm')`.
- 자동으로 받는 것을 막고 싶으면 `madi.Analyzer(download=False)` — 모델이 없으면 받지 않고 오류를 냅니다.

## 3. 형태소 분리 방법

### 3.1 세 가지 기본 메서드 — `tokenize` · `pos` · `morphs`

같은 분석을 돌려주는 모양만 다릅니다.

| 메서드 | 돌려주는 것 | 언제 |
|---|---|---|
| `tokenize(text)` | `Token` 객체 목록(형태·품사·위치 …) | 위치나 세부 정보가 필요할 때 |
| `pos(text)` | `[(형태, 품사), …]` | 품사만 필요할 때 |
| `morphs(text)` | `[형태, …]` | 형태소 열만 필요할 때 |

```python
m.tokenize('아버지가 방에 들어가신다.')
# [Token(form='아버지', tag='일반명', start=0, end=3), Token(form='가', tag='주격조', start=3, end=4),
#  Token(form='방', tag='일반명', start=5, end=6), Token(form='에', tag='부사조', start=6, end=7),
#  Token(form='들어가', tag='동사', start=8, end=11), Token(form='시', tag='선어말', start=11, end=13),
#  Token(form='ㄴ다', tag='종결어', start=11, end=13), Token(form='.', tag='마침', start=13, end=14)]

m.pos('아버지가 방에 들어가신다.')       # [('아버지', '일반명'), ('가', '주격조'), …]
m.morphs('아버지가 방에 들어가신다.')    # ['아버지', '가', '방', '에', '들어가', '시', 'ㄴ다', '.']
```

여러 줄을 한 번에 주면 줄마다 분석합니다. 이때 위치(`start`·`end`)는 입력 전체 안의 글자 위치입니다.

### 3.2 `Token` 의 필드

| 필드 | 뜻 | 예 (`들어가`) |
|---|---|---|
| `form` | 형태(사전 표기) | `'들어가'` |
| `tag` | 품사 — 분석기에 설정한 표시 방식대로 | `'동사'` |
| `tag_code` · `tag_ko` · `tag_name` | 코드 · 한글 축약 · 정식 이름(설정과 상관없이 늘 있음) | `'VV'` · `'동사'` · `'동사'` |
| `start` · `end` · `len` · `span` | 입력 안 글자 위치 [start, end), 길이 | `8` · `11` · `3` · `(8, 11)` |
| `word_position` | 문장 안 어절 번호(0부터) | `2` |
| `sent_position` | 입력 안 문장 번호(0부터) | `0` |
| `raw_form` | 원문 표기 | `'들어가'` |
| `lemma` | 원형(용언은 어간 + 다) | `'들어가다'` |
| `tagged_form` | `'형태/코드'` | `'들어가/VV'` |
| `oov` | 사전에 없는 말로 분석했는가 | `False` |

- `Token` 은 앞 네 칸 `(form, tag, start, end)` 로 튜플처럼 풀 수 있습니다: `for form, tag, start, end in m.tokenize(s): …`
- 축약된 활용형은 여러 형태소가 **같은 구간**을 가리킵니다: "신다" → 시(11–13) + ㄴ다(11–13). 원문을 자르려면 §3.3 의 `split_into_forms` 를 쓰세요.
- 품사를 비교하거나 거를 때는 표시 방식을 바꿔도 변하지 않는 **`tag_code`** 를 쓰는 것이 안전합니다.

### 3.3 원문을 형태소 경계로 자르기 — `split_into_forms`

원문 글자를 그대로 두고 경계만 나눕니다. 글자를 바꾸지 않고 자르기만 해야 할 때(형광펜 표시, 검색어 추출 등) 씁니다.

```python
m.split_into_forms('아버지가 방에 들어가신다.')
# [SplitForm(form='아버지', tag='일반명', start=0, end=3), SplitForm(form='가', tag='주격조', start=3, end=4),
#  SplitForm(form='방', tag='일반명', start=5, end=6), SplitForm(form='에', tag='부사조', start=6, end=7),
#  SplitForm(form='들어가', tag='동사', start=8, end=11), SplitForm(form='신다', tag='선어말+종결어', start=11, end=13),
#  SplitForm(form='.', tag='마침', start=13, end=14)]
```
한 원문 구간에 형태소가 여럿이면 한 조각으로 묶고 품사를 `+` 로 잇습니다.

### 3.4 품사 표시 바꾸기

```python
madi.Analyzer(tags='code')   # NNG 같은 코드
madi.Analyzer(tags='name')   # 정식 이름(일반명사)
m.pos(text, tags='code')     # 호출마다 바꾸기 (tokenize·analyze·split_into_sents 도 같음)

madi.tag_code('일반명')   # 'NNG'
madi.tag_ko('JKS')       # '주격조'
madi.tag_name('ETM')     # '관형사형 전성어미'
madi.TAGS                # [(코드, 한글 축약, 정식 이름), …] 전체 표
```
사용자 사전·불용어·`pretokenized` 에 품사를 적을 때는 코드·한글 축약·정식 이름 어느 것이든 됩니다.

### 3.5 품사표

| 코드 | 한글 | 이름 | 예 | | 코드 | 한글 | 이름 | 예 |
|---|---|---|---|---|---|---|---|---|
| NNG | 일반명 | 일반명사 | 학교 | | JKS | 주격조 | 주격조사 | 가 |
| NNP | 고유명 | 고유명사 | 서울 | | JKC | 보격조 | 보격조사 | 이(되다) |
| NNB | 의존명 | 의존명사 | 것, 수 | | JKG | 관형조 | 관형격조사 | 의 |
| NR | 수사 | 수사 | 하나 | | JKO | 목적조 | 목적격조사 | 를 |
| NP | 대명사 | 대명사 | 나, 그 | | JKB | 부사조 | 부사격조사 | 에, 로 |
| VV | 동사 | 동사 | 가, 듣 | | JKV | 호격조 | 호격조사 | 아 |
| VA | 형용사 | 형용사 | 무섭 | | JKQ | 인용조 | 인용격조사 | 라고 |
| VX | 보조용 | 보조용언 | (들어) 보 | | JX | 보조사 | 보조사 | 는, 도 |
| VCP | 긍정지 | 긍정지정사 | 이(다) | | JC | 접속조 | 접속조사 | 와 |
| VCN | 부정지 | 부정지정사 | 아니 | | EP | 선어말 | 선어말어미 | 시, 았 |
| MM | 관형사 | 관형사 | 새, 그 | | EF | 종결어 | 종결어미 | 다, ㄴ다 |
| MAG | 일반부 | 일반부사 | 어제 | | EC | 연결어 | 연결어미 | 고, 어 |
| MAJ | 접속부 | 접속부사 | 그리고 | | ETN | 명사어 | 명사형 전성어미 | 기, ㅁ |
| IC | 감탄사 | 감탄사 | 아 | | ETM | 관형어 | 관형사형 전성어미 | ㄴ, 는 |
| XPN | 체접두 | 체언 접두사 | 맨 | | SF | 마침 | 마침표·물음표·느낌표 | . ? ! |
| XSN | 명접미 | 명사 파생 접미사 | 들 | | SP | 쉼표 | 쉼표·가운뎃점·콜론·빗금 | , · |
| XSV | 동접미 | 동사 파생 접미사 | 하(다) | | SS | 묶음 | 따옴표·괄호표·줄표 | " ( |
| XSA | 형접미 | 형용사 파생 접미사 | 스럽 | | SE · SO · SW | 줄임 · 붙임 · 기호 | 줄임표 · 붙임표 · 기타 기호 | … - * |
| XR | 어근 | 어근 | 깨끗 | | SL · SH · SN | 외국 · 한자 · 숫자 | 외국어 · 한자 · 숫자 | ABC 漢 3 |
| NA · NF · NV | 불능 · 명추 · 용추 | 분석불능 · 명사추정 · 용언추정 | | | UN | 모름 | 미등록어 | |

`madi.TAGS` 에는 세분 관형사(MMD·MMN·MMA)와 URL·이메일 같은 특수 표지까지 전부 들어 있습니다.

## 4. 문장 분리 방법

글을 문장 단위로 나눕니다. 마침표가 있는 글은 물론, **마침표 없이 이어 쓴 글**(메신저·댓글·구어)도 형태소 분석으로 종결 어미를 찾아 끊습니다.

```python
m.split_into_sents('밥 먹었니 나는 먹었어')
# [Sentence(text='밥 먹었니', start=0, end=5, tokens=None), Sentence(text='나는 먹었어', start=6, end=12, tokens=None)]

m.split_into_sents('비가 온다. 우산을 챙겨라', return_tokens=True)[1].tokens
# [Token(form='우산', tag='일반명', start=7, end=9), Token(form='을', tag='목적조', start=9, end=10),
#  Token(form='챙기', tag='동사', start=11, end=13), Token(form='어라', tag='종결어', start=13, end=14)]

m.tokenize('비가 온다. 우산을 챙겨라', split_sents=True)   # 문장별 [[Token…], [Token…]]
m.split_sentences('밥 먹었니 나는 먹었어')                  # 문자열 목록만 (0.1 호환)
```
- 종결 부호 뒤에서 끊고, 부호가 없어도 **종결 어미 뒤**에서 끊습니다. 괄호·따옴표 안에서는 끊지 않습니다. 줄바꿈은 언제나 경계입니다.
- `Sentence.start`·`end` 는 입력 안의 글자 위치라 원문에서 그 문장을 그대로 잘라 낼 수 있습니다.
- 모델 없이 쓰는 규칙 분리기 `madi.split_sentences(text)` 도 있습니다. 종결 부호 + 공백에서만 끊으므로 가볍지만 부호 없는 글은 못 나눕니다: `madi.split_sentences('밥 먹었니 나는 먹었어. 너는?')` → `['밥 먹었니 나는 먹었어.', '너는?']`.

## 5. 여러 분석 후보 보기 — `analyze`

한 문장은 여러 가지로 분석될 수 있습니다. `tokenize` 는 그중 가장 좋은 것 하나만 돌려주지만, `analyze` 는 **상위 n 개 후보를 점수와 함께** 돌려줍니다. 애매한 문장이 어떻게 갈렸는지 확인하거나, 뒤에서 다른 정보로 후보 가운데 고르거나, 분석이 이상할 때 원인을 볼 때 씁니다.

```python
for tokens, score in m.analyze('감기는 무서운 병이다', top_n=3):
    print(' '.join(t.tagged_form for t in tokens), round(score, 2))
# 감기/NNG 는/JX 무섭/VA ㄴ/ETM 병/NNG 이/VCP 다/EF -34.92
# 감기/NNG 는/JX 무섭/VA ㄴ/ETM 병/NNG 이/VCP 다/EC -42.5
# 감기/NNG 는/JX 무섭/VA ㄴ/ETM 병/NNG 이/JKS 다/EF -44.2
```
- 돌려주는 꼴은 `[([Token, …], 점수), …]` 이고 좋은 것부터입니다(Kiwi 의 `analyze` 와 같은 꼴). 후보가 `top_n` 보다 적을 수 있습니다.
- **점수**는 탐색의 로그 확률 합이라 클수록(0 에 가까울수록) 좋습니다. 모드(§6)에 따라 눈금이 다르므로 다른 모드끼리 점수를 비교하지는 마세요.
- 1번 후보는 늘 `tokenize` 의 결과와 같습니다. 정밀 모드에서는 교사가 고른 후보가 1번이고, 점수는 교사가 다시 채점하기 전의 값입니다.
- 입력 전체를 한 문장으로 봅니다(문장을 나누지 않습니다).

## 6. madi 가 사용하는 모드

madi 에는 세 가지 분석 모드가 있습니다. 보통은 **기본 모드**면 충분합니다. 동사/형용사 구별이나 불규칙 활용처럼 헷갈리는 말이 중요하고 양이 적으면 **정밀 모드**, 속도와 메모리가 더 중요하면 **가벼운 모드**를 고릅니다.

| 모드 | 만드는 법 | 무엇이 다른가 | 언제 쓰나 | 속도 (KB/s) | 메모리 (MB) |
|---|---|---|---|---|---|
| **기본** | `madi.Analyzer()` | 사전 + n-gram 언어 모델 + 신경망 언어 모델 | 대부분의 경우 | 284 | 469 |
| **정밀** | `madi.Analyzer(precise=True)` | 기본 결과 가운데 애매한 어절을 작은 트랜스포머 교사가 다시 채점 | 모호한 어절이 중요할 때, 양이 적을 때 (약 10배 느림) | 26 | 525 |
| **가벼운** | `madi.Analyzer(nlm=False)` | 신경망 언어 모델 없이 n-gram 만 | 빠르게·메모리 적게 쓰고 싶을 때(정확도는 조금 낮음) | 398 | 338 |

(노트북 1스레드, 2026-10 측정. 정밀 모드는 교사 모델 +27 MB 를 처음 쓸 때 한 번 받습니다.)

모드와 별도로 **출력 방식 옵션**이 둘 있습니다.
- `std_2025=True`: 2025년판 형태 분석 지침식 출력 — 사전에 있는 복합 명사 합치기(국회+의원 → 국회의원), 소수·천 단위 숫자를 통째로, `X하/VV` 를 통째로 등.
- `split_complex=True`: 사전의 복합어 항목을 구성 형태소로 나눠 냅니다.

## 7. 사용자 사전

사전에 없는 말(신조어·제품명·전문어)이나 늘 잘못 분석되는 말을 바로잡습니다. 가장 흔한 방법은 파일을 만들어 생성자에 주는 것입니다.

```python
m = madi.Analyzer(user=['내사전.tsv'])
```
파일 형식(탭 구분, `#` 뒤는 주석, 품사는 코드·한글 어느 것이든):

```
케데헌	NNP                       # 단어 ⇥ 품사 [⇥ 보너스]   (기본 보너스 3.0)
먹깨비	고유명	5
달라고	=	달/VX 라고/EC            # 표면형 ⇥ = ⇥ 형태/품사 …   기분석 (기본 보너스 6.0)
운주사	운주사/NNG	=>	운주사/NNP   # 표면형 ⇥ 틀린 분석 ⇥ => ⇥ 맞는 분석   (분석 뒤 고치기)
```

- 단어·기분석 줄은 **후보를 더하는 것**이라 문맥이 분명히 다른 쪽을 가리키면 질 수 있습니다. 보너스를 올리거나(로그 확률 눈금, 클수록 강함), 반드시 바꿔야 하면 `=>` 고치기 줄을 쓰세요.
- 실행 중에 더할 수도 있습니다(다음 분석부터 적용되며, 그때 엔진을 다시 만드므로 몇 초 걸립니다):

```python
m = madi.Analyzer()
m.pos('케데헌을 봤다')                 # [('케데헌', '고유명'), ('을', '목적조'), ('보', '동사'), …]
m.add_user_word('케데헌', 'NNG', 5)
m.pos('케데헌을 봤다')                 # [('케데헌', '일반명'), ('을', '목적조'), ('보', '동사'), …]
m.add_pre_analyzed_word('달라고', [('달', 'VX'), ('라고', 'EC')], score=30)
m.load_user_dictionary('내사전.tsv')   # 읽은 항목 수를 돌려준다
m.version                              # '0.2.1+decdb.r3p.f11.k838c.n15e6.s-.u401f' — 사용자 사전이 붙으면 .u 가 생긴다
```
- 흔한 실수: 품사를 틀리게 적음(오류가 납니다 — `madi.TAGS` 확인), 용언을 활용형이나 '-다' 꼴로 넣음(용언은 어간만: `달리다`·`달려` 가 아니라 `달리 ⇥ VV`), 보너스가 너무 작음.

## 8. 불용어 · 구간 지정

### 8.1 불용어 — `madi.utils.Stopwords`

조사·어미·기호처럼 의미 분석에 필요 없는 형태소를 결과에서 뺍니다.

```python
from madi.utils import Stopwords
sw = Stopwords()                                   # 기본 목록
m.tokenize('나는 그것을 먹을 수 있다.', stopwords=sw)
# [Token(form='나', tag='대명사', start=0, end=1), Token(form='먹', tag='동사', start=7, end=8)]

sw.add('나/대명사'); sw.add(('수', 'NNB')); sw.add_tag('MAG')   # 낱말·품사 통째 더하기
sw.remove('것/NNB'); sw.remove_tag('XSN')
list(sw.filter(tokens)); ('것', 'NNB') in sw
sw.save('sw.txt'); Stopwords('sw.txt', default=False)          # 파일: '형태/품사' 또는 '@품사' 한 줄씩
```
기본 목록은 품사 통째(조사·어미·접사·지정사·기호)와 뜻이 약한 흔한 낱말(것/NNB·수/NNB·등/NNB, 하/VV·있/VA·되/VV, 보조 용언, 이·그·저 …)입니다. 전체는 `madi.utils.DEFAULT_TAGS`·`DEFAULT_WORDS` 에 있습니다.

### 8.2 구간 지정 — `pretokenized`

입력의 특정 글자 구간을 내가 정한 형태소로 분석하게 합니다(Kiwi 의 `pretokenized` 와 같은 쓰임). 이미 알고 있는 고유 명사 구간을 고정하거나, 분석을 일부러 다른 쪽으로 보내 볼 때 씁니다.

```python
m.tokenize('감기는 무서운 병이다', pretokenized=[(0, 3, ('감', 'VV'))], tags='code')
# [Token(form='감', tag='VV', start=0, end=1), Token(form='기', tag='ETN', start=1, end=2), Token(form='는', tag='JX', …), …]
toks, ok = m.analyze_pretokenized(text, [(0, 2, 'NNG'), (5, 8)])   # ok = 구간마다 지켰는가
```
구간은 `(시작, 끝)`(그 원문 그대로 한 형태소) · `(시작, 끝, 품사)` · `(시작, 끝, (형태, 품사))`. 품사 코드는 앞부분만 맞아도 됩니다(`'NN'` → NNG·NNP·NNB). 사전에 그런 후보가 없으면 지키지 못할 수 있고, 그때 `ok` 가 `False` 입니다.

## 9. 여러 문장 · 큰 파일

```python
m = madi.Analyzer(num_workers=4)
results = m.tokenize(['첫 문장', '둘째 문장', …])     # 입력마다 [Token, …] — 4 스레드로
with open('big.txt', encoding='utf-8') as f:
    lines = [l.rstrip('\n') for l in f]
for toks in m.tokenize(lines):
    ...
```
- 분석은 문장마다 독립이라 스레드 수와 관계없이 결과가 같습니다. 분석 중에는 GIL 을 놓으므로 파이썬 스레드에서 한 `Analyzer` 를 함께 써도 됩니다.
- 메모리는 모델(§6 표)이 대부분이고 입력 크기에 따라 거의 늘지 않습니다. 아주 큰 파일은 줄 묶음(수천 줄)씩 넘기세요.
- `tokenize_batch(texts, threads)` 는 0.1 의 이름을 그대로 둔 것입니다.

## 10. 명령줄

설치하면 `python -m madi` 로 바로 써 볼 수 있습니다.

```
python -m madi "아버지가 방에 들어가신다."            # 아버지/일반명 가/주격조 …
python -m madi --tags code "아버지가 방에 들어가신다."
python -m madi --split < 글.txt                          # 한 줄에 한 문장
python -m madi --precise --threads 4 < 글.txt
python -m madi download [--precise] [--dir 폴더]       # §2.3
python -m madi verify | info | version
```

## 11. Kiwi 에서 옮겨 오기

| Kiwi (kiwipiepy) | madi | 비고 |
|---|---|---|
| `Kiwi()` | `madi.Analyzer()` | 모델은 처음 쓸 때 자동으로 받음 |
| `tokenize(text)` · `tokenize(texts)` | 같음 | `Token` 필드 이름도 같음(§3.2). 목록 입력은 목록을 돌려줌 |
| `tokenize(…, split_sents=True, stopwords=…)` | 같음 | `madi.utils.Stopwords` |
| `analyze(text, top_n)` | 같음 | `[(tokens, score)]` |
| `split_into_sents(text, return_tokens)` | 같음 | `Sentence(text, start, end, tokens)` |
| `split_into_forms(text)` | 같음 | |
| `add_user_word` · `add_pre_analyzed_word` · `load_user_dictionary` | 같음 | 파일 형식은 §7 |
| `pretokenized=` | 같음 | §8.2 |
| `num_workers` | 같음 | |
| `split_complex=True` | 생성자 인자 | |
| **품사 표시** | **기본이 한글 축약** | Kiwi 용 코드가 `t.tag == 'NNG'` 를 쓰면 `Analyzer(tags='code')` 로 만들거나 `t.tag_code` 로 바꾸세요 |
| `VA-I` 같은 불규칙 표시 | 없음 | 품사는 VA, 형태는 원래 어간(무섭) |
| `join(morphs)` · `glue` · `extract_words` · URL/이메일 통째(`match_options`) · `blocklist` | 0.3 계획 | |
| `space` · `typos` | 넣지 않음 | 띄어쓰기·오타 교정은 madi 맞춤법 검사기(별도 배포)의 기능 |
| 형태소 유사도 · 다음 형태소 예측 · 방언 | 넣지 않음 | Kiwi 고유 모델이 필요한 기능 |

분석 관행은 모두의 말뭉치 지침을 따르므로 결과가 Kiwi 와 다른 곳이 있습니다(복합 명사 단위, 숫자, 문장 분리 등). 없는 메서드를 부르면 대신 쓸 수 있는 것을 오류 문구로 알려 줍니다.

## 12. 자주 묻는 질문

- **모델을 못 받아요.** 회사·학교 망이면 `HTTPS_PROXY` 를 설정하거나 §2.4 의 방법을 쓰세요. `python -m madi info` 가 어디서 받아 어디에 두는지 보여 줍니다.
- **처음이 느려요.** 첫 실행은 모델을 받고 푸는 시간(수십 초)입니다. 그다음부터 `Analyzer()` 만들기는 1초 안팎입니다. 한 번 만든 `Analyzer` 를 계속 쓰세요.
- **결과가 Kiwi 와 달라요.** 대부분 관행 차이(국회의원 vs 국회/의원, 숫자 나누기 등)입니다. madi 는 모두의 말뭉치 지침을 따릅니다. 2025년판 지침이 필요하면 `std_2025=True`.
- **품사가 한글로 나와요.** 0.2.0 부터 기본입니다. `tags='code'` 로 바꾸세요.
- **메모리가 많이 들어요.** `nlm=False`(가벼운 모드)로 약 130 MB 줄어듭니다.
- **같은 결과인지 확인하고 싶어요.** `m.version` 이 같으면 사전·모델·설정이 같습니다.

## 13. 판본 · 이용 조건 · 인용

- 판본 문자열 `m.version`: `0.2.1+decdb.r3p.f11.k838c.n15e6.s-` = 패키지 판 + 사전·점수 가중·탐색·LM·신경망 해시. 정밀 모드는 `.p…`, 사용자 사전은 `.u…` 가 붙습니다.
- 패키지(코드): MIT 또는 Apache-2.0.
- 모델·사전 파일은 CC BY-SA 2.0 KR. 국립국어원 모두의 말뭉치(형태 분석 말뭉치 2018~2020, 형태 분석 말뭉치 2025, 어휘 의미 분석 말뭉치 2020)와 우리말샘을 활용하였음(국립국어원 공개 승인 2026-10-17). 말뭉치 원문을 담거나 복원할 수 없는 통계·가중치임. 신문 기사 원문으로 자기 라벨 학습.
- 사전은 mecab-ko-dic(Apache-2.0) 기반. 모델 이용 조건 전문·학습 입력 목록: https://github.com/p14jeffwest/madi-release (`LICENSE-MODELS.txt`, `MODEL-PROVENANCE.md`).
- 인용: madi(마디) 한국어 형태소 분석기, 박희진, https://p14jeffwest.github.io/madi-release/ (논문 준비 중).

Code: MIT OR Apache-2.0. Downloaded model files: CC BY-SA 2.0 KR (see the release repository).

## 14. 성능 요약

같은 잣대(세종 태그 사상, 관행 차이 정규화)로 다른 분석기와 비교했습니다. 차이는 Kiwi CoNg(kiwipiepy 0.24.0) 대비, 괄호는 95% 구간입니다.

| | madi 기본 | madi 정밀 | Kiwi CoNg |
|---|---|---|---|
| 모두의 말뭉치 형태 분석 2025 문어(학습과 독립, 6,953문장), 형태소 F1 | **0.9774** (+0.32p [+0.24, +0.39]) | 0.9784 (+0.42p) | 0.9742 |
| 같은 셋 구어(16,439문장) | **0.9640** (+0.53p [+0.44, +0.64]) | 0.9656 (+0.69p) | 0.9586 |
| 신문 1990~2025 평가셋(420문장, 손 교정), 중립 F1 | 0.9772 (+0.35p, 유의하지 않음) | 0.9786 (+0.49p) | 0.9737 |
| Kiwi 모호성 벤치마크 kiwi510, 정확도 | 0.7471 (**열세**, p 0.0002) | 0.8137 (차이 유의하지 않음) | 0.8275 |
| 속도(노트북 1스레드, Python 바인딩, KB/s) | 284 | 26 | 268 |

- 기본 모드는 모호성 벤치마크에서 Kiwi 보다 낮고, 정밀 모드(`precise=True`)는 같은 수준입니다. 일반 문장의 형태소 F1 은 기본 모드도 앞섭니다.
- 공개판의 기본 설정은 위 측정 설정에 전후향 가지치기(τ 11)를 더한 것입니다. 측정 셋 1만 7천 문장 중 5문장만 출력이 달라지고, 2단계 LM 질의가 44% 줄어 랩탑 CLI 에서 약 17% 빨라집니다.
- 자세한 표·방법은 논문(준비 중)에 싣습니다. 평가에는 국립국어원 모두의 말뭉치를 활용하였음.

