Metadata-Version: 2.4
Name: madipy
Version: 0.2.0
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 on first use. Token fields are listed in the table in §3 (English names are the attribute names).

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

---

## 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 의 품사표를 보세요.

## 2. 설치와 모델

- Windows · macOS(Intel·Apple Silicon) · Linux(x86_64·aarch64), Python 3.9 이상. 설치 이름은 `madipy`, 코드에서는 `import madi`.
- 모델 파일은 패키지에 들어 있지 않고 **처음 `madi.Analyzer()` 를 만들 때 한 번** 받습니다.

| 무엇 | 받기 | 풀면 | 언제 |
|---|---|---|---|
| 사전 · n-gram LM · 신경망 LM | 87 MB | 240 MB | 기본 |
| 정밀 모드 교사 | +27 MB | +38 MB | `precise=True` 를 처음 쓸 때 |

- 두는 곳: Windows `%LOCALAPPDATA%\madi\models\models-kword5`, 그 밖 `~/.cache/madi/models/models-kword5`. 환경 변수 `MADI_MODEL_DIR` 또는 `Analyzer(model_dir=…)` 로 바꿉니다.
- 미리 받기·확인: `python -m madi download [--precise] [--dir 폴더]`, `python -m madi verify`, `python -m madi info`.
- 자동으로 받지 않게: `madi.Analyzer(download=False)` — 모델이 없으면 받지 않고 오류를 냅니다.
- **인터넷이 막힌 곳**: 다른 PC 에서 [Release](https://github.com/p14jeffwest/madi-release/releases) 의 `.xz` 파일을 받아 한 폴더에 둔 뒤 `MADI_MODEL_URL=그 폴더` 로 `python -m madi download` 를 하거나, `.xz` 를 풀어 경로를 직접 줍니다:
  ```python
  m = madi.Analyzer('madi.dict', lm='knlm.bin', nlm='model.nlm')   # 내려받기 없이 파일을 직접
  ```
- **프록시**: 표준 환경 변수 `HTTPS_PROXY` 를 따릅니다.
- 받은 파일은 sha256 으로 확인합니다(`python -m madi verify`).

## 3. 분석 기본

### 3.1 `tokenize` · `pos` · `morphs`

```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`

| 필드 (field) | 뜻 | 예 (`들어가`) |
|---|---|---|
| `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)`** 로 튜플처럼 풀 수 있습니다(0.1 코드 호환): `for form, tag, start, end in m.tokenize(s): …`
- 축약된 활용형은 여러 형태소가 **같은 구간**을 가리킵니다: "신다" → 시(11–13) + ㄴ다(11–13). 원문을 자를 때는 `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 호환)
```
- 종결 부호 뒤뿐 아니라 **마침표가 없어도 종결 어미 뒤**에서 끊습니다(구어·메신저 글). 괄호·따옴표 안은 끊지 않고, 줄바꿈은 늘 경계입니다.
- 모델 없이 쓰는 규칙 분리기 `madi.split_sentences(text)` 도 있습니다. 종결 부호 + 공백에서만 끊습니다: `madi.split_sentences('밥 먹었니 나는 먹었어. 너는?')` → `['밥 먹었니 나는 먹었어.', '너는?']`.

## 5. 여러 후보 — `analyze`

```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 에 가까울수록) 좋습니다. 설정(n-gram 만·신경망·정밀)에 따라 눈금이 달라 설정끼리는 비교하지 않습니다.
- 1번 후보는 늘 `tokenize` 와 같습니다. 정밀 모드에서는 교사가 고른 것이고, 그 점수는 교사 재채점 전 1위의 점수입니다.
- 입력 전체를 한 문장으로 봅니다(줄 나눔 없음).

## 6. 정밀 모드 · 2025년판 출력 · 가벼운 모드

| 설정 | 언제 | 속도 (KB/s) | 메모리 (MB) |
|---|---|---|---|
| `madi.Analyzer()` 기본 (사전 + n-gram + 신경망) | 대부분 | 284 | 469 |
| `madi.Analyzer(precise=True)` 정밀 | 동사/형용사·불규칙 활용처럼 헷갈리는 말이 중요할 때, 양이 적을 때 | 26 | 525 |
| `madi.Analyzer(nlm=False)` n-gram 만 | 빠르게·메모리 적게, 정확도는 조금 낮음 | 398 | 338 |

(노트북 1스레드, 2026-10 측정)

- `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.0+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` 를 함께 써도 됩니다.
- 메모리는 모델(위 표)이 대부분이고 입력 크기에 따라 거의 늘지 않습니다. 아주 큰 파일은 줄 묶음(수천 줄)씩 넘기세요.
- `tokenize_batch(texts, threads)` 는 0.1 이름 그대로 둔 것입니다.

## 10. 명령줄

```
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 폴더]
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 의 "인터넷이 막힌 곳" 방법을 쓰세요. `python -m madi info` 가 받는 곳과 두는 곳을 보여 줍니다.
- **처음이 느려요.** 첫 실행은 모델을 받고 풉니다(수십 초). 그다음부터 `Analyzer()` 만들기는 1초 안팎입니다. 한 번 만든 `Analyzer` 를 계속 쓰세요.
- **결과가 Kiwi 와 달라요.** 대부분 관행 차이(국회의원 vs 국회/의원, 숫자 쪼개기 등)입니다. 모두의 말뭉치 지침을 따릅니다. 2025년판 지침이 필요하면 `std_2025=True`.
- **품사가 한글로 나와요.** 0.2.0 부터 기본입니다. `tags='code'` 로 바꾸세요.
- **메모리가 많이 들어요.** `nlm=False`(n-gram 만)로 약 130 MB 줄어듭니다.
- **같은 결과인지 확인하고 싶어요.** `m.version` 이 같으면 사전·모델·설정이 같습니다.

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

- 판본 문자열 `m.version`: `0.2.0+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).

## 성능 요약

같은 잣대(세종 태그 사상, 관행 차이 정규화)로 다른 분석기와 비교했습니다. 차이는 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% 빨라집니다.
- 자세한 표·방법은 논문(준비 중)에 싣습니다. 평가에는 국립국어원 모두의 말뭉치를 활용하였음.

