Pynori
Pynori is python version of Nori, Korean Analyzer in Apache Lucene and Elasticsearch.
- Nori
- ์ํ์น ๋ฃจ์ฌ ๋ฐ ์๋ผ์คํฑ์์น์ ํฌํจ๋ ํ๊ตญ์ด ํํ์ ๋ถ์๊ธฐ ํ๋ฌ๊ทธ์ธ (์๋ฐ๋ก ์์ฑ)
- mecab / kuromoji ๊ธฐ๋ฐ์ ํ๊ตญ์ด ํํ์ ๋ถ์๊ธฐ (mecab-ko-dic-2.1.1-20180720 ์ฌ์ฉ)
- ๋ฃจ์ฌ ๋๋ ์๋ผ์คํฑ์์น ์์ง์ ์ข ์๋ ํ๊ตญ์ด ํํ์ ๋ถ์๊ธฐ
- Pynori
- Nori์ ํ์ด์ฌ ๋ฒ์ & ์์ ํ์ด์ฌ ์คํฌ๋ฆฝํธ๋ก ์์ฑ (ref.Property & Comparision Study)
- ์๋ณธ๊ณผ ๊ฐ์ ์ ๋ํ ์คํธ๋ฅผ ์ค์ํ์ฌ ๋์ผํ ๊ฒฐ๊ณผ๋ฅผ ์ป์. (ref.Test)
- ๋ ๋ฆฝ๋ ๋ชจ๋๋ก ํ์ด์ฌ ํ๋ก์ ํธ ํ์ฉ ๊ฐ๋ฅ
- ์๋ณธ Nori ๋๋น ๊ฐ์ ๊ธฐ๋ฅ (ref.Property)
๋ ธ๋ฆฌ ํํ์ ๋ถ์๊ธฐ์ ๋ํ ๋ด์ฉ์ ๋ ธ๋ฆฌ Deep Dive ๋ธ๋ก๊ทธ๋ฅผ ์ฐธ๊ณ ํด์ฃผ์ธ์.
pynori์ ๋ํ ์ด์ ์ฌํญ์ issue์ ๋ฑ๋กํด์ฃผ์ธ์.
Install
pip install pynori
Usage
# ๋ํดํธ ์ต์
: /pynori/config.ini ํ์ผ ์ฐธ๊ณ
from pynori.korean_analyzer import KoreanAnalyzer
nori = KoreanAnalyzer(decompound_mode='DISCARD', # DISCARD or MIXED or NONE
infl_decompound_mode='DISCARD', # DISCARD or MIXED or NONE
discard_punctuation=True,
output_unknown_unigrams=False,
pos_filter=False, stop_tags=['JKS', 'JKB', 'VV', 'EF'],
synonym_filter=False, mode_synonym='NORM') # NORM or EXTENSION
print(nori.do_analysis("์๋น ๊ฐ ๋ฐฉ์ ๋ค์ด๊ฐ์ ๋ค."))
{'termAtt': ['์๋น ', '๊ฐ', '๋ฐฉ', '์', '๋ค์ด๊ฐ', '์', 'แซ๋ค'],
'offsetAtt': [(0, 2), (2, 3), (4, 5), (5, 6), (7, 10), (10, 12), (10, 12)],
'posLengthAtt': [1, 1, 1, 1, 1, 1, 1],
'posTypeAtt': ['MORP', 'MORP', 'MORP', 'MORP', 'MORP', 'MORP', 'MORP'],
'posTagAtt': ['NNG', 'JKS', 'NNG', 'JKB', 'VV', 'EP', 'EF'],
'dictTypeAtt': ['KN', 'KN', 'KN', 'KN', 'KN', 'KN', 'KN']}
-
KoreanAnalyzer
arg.-
decompound_mode
/infl_decompound_mode
- ๋ณตํฉ๋ช ์ฌ / ๊ตด์ ์ด ์ฒ๋ฆฌ ๋ฐฉ์ ๊ฒฐ์ - 'MIXED': ์ํ๊ณผ ์๋ธ๋จ์ด ๋ชจ๋ ์ถ๋ ฅ
- 'DISCARD': ์๋ธ๋จ์ด๋ง ์ถ๋ ฅ
- 'NONE': ์ํ๋ง ์ถ๋ ฅ
-
discard_punctuation
- ๊ตฌ๋์ ์ ๊ฑฐ ์ฌ๋ถ -
output_unknown_unigrams
- ์ธ๋ ผ ๋จ์ด๋ฅผ ์์ ๋จ์๋ก ์ชผ๊ฐฌ ์ฌ๋ถ -
pos_filter
- POS ํํฐ ์คํ ์ฌ๋ถ -
stop_tags
- ํํฐ๋ง๋๋ POS ํ๊ทธ ๋ฆฌ์คํธ (pos_filter=True์ผ ๋๋ง ํ์ฑ) -
synonym_filter
- ๋์์ด ํํฐ ์คํ ์ฌ๋ถ -
mode_synonym
- ๋์์ด ์ฒ๋ฆฌ ๋ชจ๋ (NORM or EXTENSION) (synonym_filter=True์ผ ๋๋ง ํ์ฑ)
-
๋ค์๊ณผ ๊ฐ์ด KoreanAnalyzer์ ์ต์ ์ ๋์ ์ผ๋ก ์ ์ดํ ์ ์์ต๋๋ค.
print(nori.do_analysis("๊ฐ๋ฒผ์ด ๋์ฅ๊ณ ")['termAtt'])
# ['๊ฐ๋ณ', 'แซ', '๋์ฅ', '๊ณ ']
## ํ ํฌ๋์ด์ ์ต์
์ธํ
nori.set_option_tokenizer(decompound_mode='MIXED', infl_decompound_mode='MIXED')
print(nori.do_analysis("๊ฐ๋ฒผ์ด ๋์ฅ๊ณ ")['termAtt'])
# ['๊ฐ๋ฒผ์ด', '๊ฐ๋ณ', 'แซ', '๋์ฅ๊ณ ', '๋์ฅ', '๊ณ ']
## POS ํํฐ ์ต์
์ธํ
nori.set_option_filter(pos_filter=True, stop_tags=['ETM', 'VA'])
print(nori.do_analysis("๊ฐ๋ฒผ์ด ๋์ฅ๊ณ ")['termAtt'])
# ['๋์ฅ๊ณ ', '๋์ฅ', '๊ณ ']
## ๋์์ด ํํฐ ์ต์
์ธํ
nori.set_option_filter(synonym_filter=True, mode_synonym='NORM')
print(nori.do_analysis("NLP ๊ฐ๋ฐ์")['termAtt'])
# ['์์ฐ์ด์ฒ๋ฆฌ', '์์ฐ์ด', '์ฒ๋ฆฌ', '๊ฐ๋ฐ์', '๊ฐ๋ฐ', '์']
nori.set_option_tokenizer(decompound_mode='DISCARD', infl_decompound_mode='DISCARD') # DISCARD ๋ก ๋ณ๊ฒฝ.
nori.set_option_filter(mode_synonym='EXTENSION')
print(nori.do_analysis("AI ๊ฐ๋ฐ์")['termAtt'])
# ['์ธ๊ณต', '์ง๋ฅ', 'ai', 'aritificial', 'intelligence', '๊ฐ๋ฐ', '์', 'developer']
Usage - Multiprocessing
# ๋ํดํธ ์ต์
: /pynori/config.ini ํ์ผ ์ฐธ๊ณ
from pynori.multiprocessor import KoreanAnalyzerMultiprocessing
nori_mp = KoreanAnalyzerMultiprocessing(decompound_mode='MIXED', # DISCARD or MIXED or NONE
infl_decompound_mode='DISCARD', # DISCARD or MIXED or NONE
#discard_punctuation=True,
#output_unknown_unigrams=False,
#pos_filter=False, stop_tags=['JKS', 'JKB', 'VV', 'EF'],
#synonym_filter=False, mode_synonym='NORM'
)
nori_mp.run(num_workers=3,
read_path="your/read/file/path",
write_path="your/write/file/path")
# multiprocessing ์ file-to-file ํฌ๋งท์ผ๋ก ์คํ
# num_workers ๋ฅผ ํตํด ๋ณ๋ ฌ ํ๋ก์ธ์ค ๊ฐ์ ์ค์
# line-by-line ์ผ๋ก read. ๊ฐ line ์ ํ
์คํธ๋ ๋ฌธ์ฅ์ผ๋ก ๊ฐ์ฃผ (๋ฌธ์์ผ ๊ฒฝ์ฐ ์ฒ๋ฆฌ ์๊ฐ์ด ์ค๋ ๊ฑธ๋ฆฌ๋ ๋ฌธ์ฅ ๋ถ๋ฆฌ๊ธฐ ํ์ฉ ์ถ์ฒ)
# termAtt ๋ง ์ถ๋ ฅ. ๋ฐ์ด์ฐ๊ธฐ๊ฐ ๋ ํ ํฌ๋์ด์ง๋ ๋ฌธ์์ด์ ์ถ๋ ฅ (๋ค๋ฅธ key ํ์ํ๋ฉด multiprocessor.py ์ 24 line ์์ )
Resources
- ์์คํ
์ฌ์ ์
~/pynori/resources/mecab-ko-dic-2.1.1-20180720
์์ ์์ - ์ฌ์ ๋ณ๊ฒฝ์ฌํญ์ ๋ค์ ๋ ํญ๋ชฉ์ ์ค์ํ๋ฉด ๊ณง๋ฐ๋ก ์ ์ฉ ๊ฐ๋ฅ
- ๊ธฐ์กด csv ํ์ผ ์์ /์ญ์ or ์๋ก์ด csv ํ์ผ ์ถ๊ฐ (์ฃผ์. mecab ๋จ์ด ์์ฑ ๊ท์น)
- ๊ธฐ์กด
~/pynori/resources/pkl_mecab_csv/mecab_csv.pkl
์ญ์ - (์ฐธ๊ณ .
mecab_csv.pkl
ํ์ผ์ด ์์ผ๋ฉด KoreanAnalyzer ์ด๊ธฐํ ์์ ์ต์ csv ํ์ผ์ ๊ธฐ๋ฐ์ผ๋ก ์ฌ์์ฑ) - (์ฐธ๊ณ .
~/pynori/resources/pkl_mecab_matrix/matrix_def.pkl
ํ์ผ์ ์์ /์ญ์ ํ์ง ๋ง ๊ฒ) - (์ฐธ๊ณ . ๋ค๋ฅธ ๋ฒ์ ์ mecab-ko-dic ์ ์ฉ์ ์ํด์๋ ์ฝ๋ ๋ด์ path ์์ ํ์)
- ์ฌ์ ๋ณ๊ฒฝ์ฌํญ์ ๋ค์ ๋ ํญ๋ชฉ์ ์ค์ํ๋ฉด ๊ณง๋ฐ๋ก ์ ์ฉ ๊ฐ๋ฅ
- ์ฌ์ฉ์ ์ฌ์ ์
~/pynori/resources/userdict_ko.txt
์์ ์์ (๊ณง๋ฐ๋ก ์ ์ฉ ๊ฐ๋ฅ) - ๋์์ด ์ฌ์ ์
~/pynori/resources/synonyms.txt.txt
์์ ์์ (๊ณง๋ฐ๋ก ์ ์ฉ ๊ฐ๋ฅ)
Test
git clone https://github.com/gritmind/python-nori.git
cd python-nori
python -m unittest -v pynori.tests.test_korean_analyzer
python -m unittest -v pynori.tests.test_korean_tokenizer
Property
- [์๋ณธ] ๋ฃจ์ฌ(lucene), ๋ ธ๋ฆฌ(nori) ํํ์ ๋ถ์๊ธฐ (ref.1)
- ์๋ณธ ์ฝ๋์ ์ต๋ํ ๋น์ทํ๊ฒ ๊ตฌํ (๋ณ์/ํ์ผ๋ช , ์ฝ๋ ํจํด ๋ฑ)
- ์ธ์ด ๋ฆฌ์์ค๋ก
mecab-ko-dic-2.1.1-20180720
์ฌ์ฉ - ์ฌ์ ๋ฃฉ์ ์ ์ํด Trie ์๋ฃ๊ตฌ์กฐ ์ฌ์ฉ (FST ๋ณด์ ํ์)
- token & dictionary objects ์์
- circular buffer & wordID ๋ฏธํ์ฉ
์๋ณธ Nori ๋๋น ๊ฐ์ ๊ธฐ๋ฅ
- ํ ํฐ ์ ๋ณด (Unknown/Known/User, POS type) ์ถ๋ ฅ
- ํน์๋ฌธ์๋ก ์์/ํฌํจํ๋ ์ฌ์ฉ์ ๋จ์ด๊ฐ ์์ ์ ๋์์ด ํ์ฑ ์ค๋ฅ ํด๊ฒฐ
- infl_decompound_mode ๋ชจ๋ ์ถ๊ฐ
- KoreanAnalyzer ์ต์ ์ ๋์ ์ผ๋ก ์ ์ดํ๋ ๊ธฐ๋ฅ ์ถ๊ฐ
- ๋์์ด ํํฐ๋ง - ๋ํ์ด ์ฒ๋ฆฌ ๊ธฐ๋ฅ ์ถ๊ฐ
- Unknown ๊ธธ์ด๊ฐ ๋ฌด๋ถ๋ณํ๊ฒ ๊ธธ์ด์ง๋ ํ์ ํด๊ฒฐ
- ๋ณ๋ ฌ ์ฒ๋ฆฌ ์ง์
TODO
- ํํฐ ํ ํ ํฐ ์ธ๋ฑ์ค/ํฌ์ง์ ์ฌ๋ฐฐ์ด
- KoreanTokenizer TODO List (MAX_BACKTRACE_GAP, isLowSurrogate, UnicodeScript ...)
- ์๋ ํฅ์์ ์ํ ์๊ณ ๋ฆฌ์ฆ ๋ฐ ์๋ฃ๊ตฌ์กฐ ์ต์ ํ
Comparision Study
ํ๋๋ 0.8.4 | ๊ผฌ๊ผฌ๋ง 2.0 | ํธ์ํฐ 1.14.7 | Pynori 0.1.0 | |
---|---|---|---|---|
1 ๊ฐ | 0.00138 sec | 0.00244 sec | 0.00051 sec | 0.00279 sec |
10 ๊ฐ | 0.03467 sec | 0.07546 sec | 0.01188 sec | 0.09655 sec |
100 ๊ฐ | 0.28960 sec | 0.70480 sec | 0.09319 sec | 0.72207 sec |
1000 ๊ฐ | 2.59061 sec | 6.38031 sec | 0.94029 sec | 6.46660 sec |
10000 ๊ฐ | 27.61180 sec | 77.73616 sec | 11.43677 sec | 68.20249 sec |
100000 ๊ฐ | 262.72305 sec | 699.70416 sec | 95.79926 sec | 672.83272 sec |
- ๋ฐ์ดํฐ๋ฅผ ์ฆ๊ฐ์ํค๋ฉด์ ๋ค์ํ ์ข
๋ฅ์ ํ๊ตญ์ด ํํ์ ๋ถ์๊ธฐ์ ์ฒ๋ฆฌ ์๋๋ฅผ ๋น๊ต. (์ฐธ๊ณ
./tests/test_compare_morphs.py
). - ๋น๊ต ๋์์ ๋ชจ๋ ํ์ด์ฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ(konlpy)์ ๋ชจ๋ ์ํด ์์ง๋ง ๋ด๋ถ์ ์ผ๋ก JVM ๊ธฐ๋ฐ์ผ๋ก ๋์ํจ.
- pynori๋ ์์ ํ์ด์ฌ ์คํฌ๋ฆฝํธ๋ก ์คํ๋์ง๋ง, ํธ์ํฐ๋ฅผ ์ ์ธํ๊ณ ๋ ํฐ ์ฐจ์ด๊ฐ ๋ฐ์ํ์ง ์๊ณ , ๊ผฌ๊ผฌ๋ง 2.0๋ณด๋ค๋ ๋น ๋ฆ.
Release History
๋ฒ์ | ์ฃผ์ ๋ด์ฉ | ๋ ์ง |
---|---|---|
pynori 0.1.0 | ๋ ธ๋ฆฌ ๊ธฐ๋ณธ ๋ชจ๋ ํ์ด์ฌ ํฌํ & ์ ๋ํ ์คํธ ๊ตฌํ ์๋ฃ | Nov 17, 2019 |
pynori 0.1.1 | KoreanAnalyzer ์ด๊ธฐํ ์๋ ํฅ์ (1min 15s -> 12.9s) | Apr 16, 2020 |
pynori 0.1.2 | infl_decompound_mode ๋ชจ๋ ์ถ๊ฐ | Apr 23, 2020 |
pynori 0.1.3 | KoreanAnalyzer ์ต์ ์ ๋์ ์ผ๋ก ์ ์ดํ๋ ๊ธฐ๋ฅ ์ถ๊ฐ | Apr 25, 2020 |
pynori 0.2.0 | ๋์์ด ์ฒ๋ฆฌ ๋ชจ๋ (SynonymGraphFilter) ์ถ๊ฐ | Jun 6, 2020 |
pynori 0.2.1 | Long Unknown ํ ํฐ ์ํ ๋ก์ง ์ถ๊ฐ | Jul 19, 2020 |
pynori 0.2.4 | gc.disable๋ก ์ด๊ธฐํ ์๋ ํฅ์ (-> 5s) & ๋ณ๋ ฌ ์ฒ๋ฆฌ ์ง์ | Aug 18, 2021 |
License
- Apache License 2.0
Reference
- (Github) Lucene-solr - Nori
- (Github) Mecab-ko-dic
- (Blog) ์๋ผ์คํฑ์์น ๊ณต์ ํ๊ตญ์ด ๋ถ์ ํ๋ฌ๊ทธ์ธ '๋ ธ๋ฆฌ'
- (Blog) ๋ ธ๋ฆฌ(Nori) ํํ์ ๋ถ์๊ธฐ Deep Dive