한글 문장 분리기
2020년 12월 21일 공지 추가
2019년 8월 18일 초안 작성
공지
작년 즈음에 카카오에서 사용해오던 한국어 문장 분리기에 착안해 C++로 한국어 문장 분리기를 새롭게 만들었고, 오픈소스로 공개하여 좋은 반응을 얻은 바 있습니다. 그러나 지속적인 문의나 요청 사항에는 거의 대응을 하지 못했고, 결정적으로 저 또한 전혀 다른 프로젝트를 맡게 되어 더 이상 유지보수를 할 수가 없었는데요. 특히 C++로 구현하다 보니 빌드 문의가 정말 많았고, 원래는 Windows, Mac, Linux 각 OS별로 빌드하여 바이너리를 업로드하여 배포하도록 권장하고 있는데, 상용 프로젝트도 아니고 그렇게 까지 관리할 수는 없었습니다.
결정적으로 문장 분리 자체가 엄청난 고성능을 요구해서 꼭 C++로 작성해야 하는건 아니었기에 빌드의 번거로움과 개발 유지보수를 감안하면 이제 다른 언어로 바꾸는게 좋겠다고 생각하던 차, 마침 고현웅님께서 파이썬으로 모두 포팅해주셨고, 또 꾸준히 개선해 나가는 모습을 보면서 이제는 프로젝트를 넘겨줄때가 됐음을 깨달았습니다.
제가 올 초에 1.3.1까지 올렸었고, 오늘부터 고현웅님이 만드신 파이썬 포팅 버전으로 2.0.0이 시작됩니다. 그동안 저에게 들어와있던, 제가 처리하지 못했던 모든 이슈와 PR이 반영된 최종 개선 버전이고, 아마 앞으로도 잘 개선해주시리라 기대가 큽니다.
팩키지 설치는 기존과 동일하게
pip install kss
로 가능하며, 버그나 개선과 관련한 이슈는 새로운 레포인 https://github.com/hyunwoongko/kss 에 올려주시면 됩니다.앞으로도 많은 응원 부탁드립니다.
감사합니다.1
서론
딥러닝 시대에 왜 굳이 패턴 기반의 분리기 인가 의문이 들 수 있다. 그러나 딥러닝 이야 말로 통계 기반이 아닌 전혀 다른 방식의 접근이 필요하다. LSTM 방식의 형태소 분석기를 이용해, LSTM 방식의 분류를 진행하고, LSTM 방식의 NLG를 하는 것이 과연 어떤 의미가 있을지. 딥러닝 시대에, 정교하게 다듬은 패턴 기반의 분리기는 오히려 활용 가치가 높을 것으로 기대된다.
관련 연구
문장 분리의 경우 형태소 분석으로 종결 어미를 구분한다던지, 문장의 CRF 결과로 판단하는 방법이 있다.
Kotlin으로 구현된 KoalaNLP의 문장 분리 인터페이스 코드 주석에 명시된 문장 분리 알고리즘은 다음과 같다.
다음 조건에 따라 문장을 분리합니다:
- 열린 괄호나 인용부호가 없고,
- 숫자나 외국어로 둘러싸이지 않은 문장부호([POS.SF])가 어절의 마지막에 왔을 경우.
사용법은 별도로 문서화 되어 있다.
그러나, 한국어 형태소 분석기 별 문장 분리 성능비교 글을 보면 대부분 비슷한 성능을 보인다.
한나눔과 Open Korean Text는 형태소 분석기 자체에서 문장 분리 기능을 제공하고 있지만, 나머지 형태소 분석기들은 문장 분리 기능을 제공하고 있지 않다. 따라서 나머지 형태소 분석기에 대해서는 형태소 분석을 수행한 후 이를 기반으로 문장을 분리할 수 있도록 지원하는 KoalaNLP를 이용할 것이다. 이는 어찌보면 정확한 형태소 분석기의 문장 분리 성능이라 하기에 부족한 측면이 있을 수 있다. 하지만 대안이 부족한 상황에서 다양한 대안을 마련하기 위한 하나의 접근법으로 이해하면 될 것 같다.
이는 KoalaNLP 문장 분리기의 알고리즘으로만 문장을 구분하기 때문이며, 태그 SF
. 즉, 형태소 분석 결과, 문장 부호의 위치에 따라 문장을 구분하는 알고리즘을 사용하고 있다.
hangul-utils 팩키지 또한 비슷한 접근을 취하고 있다.
알고리즘
여기서는 성능을 획기적으로 개선할 수 있는 다른 방식의 접근이 필요하다. 이에 따라 종결형에 사용되는 음절을 골라내어 이전/이후 음절을 매칭하여 문장을 구분했다. 여기서 골라낸 음절은 다/요/.!?
이며, 이전/이후 패턴을 미리 정의해 두고 이후 패턴은 2음절까지 확장해 나가며, 어느 부위에서 정확히 끊어낼지 Sentence Boundary를 지정했다. 해당 알고리즘은 내가 알고 있는 국내 최고의 NLP 엔지니어 중 한 명이자 카카오 NLP 리더인 김응균님의 알고리즘에 큰 영향을 받았다.
속도
정교한 패턴 기반의 문장 분리기 라는 장점과 함께 통계 기반에 비해 월등히 뛰어난 속도를 보인다는 장점이 있다. 실제로 이 라이브러리는 기존 문장 분리기가 넘어서지 못한 탁월한 성능을 보임과 동시에, 속도 또한 비교가 불가능할 정도의 빠른 속도를 보여준다. 또한 더 높은 속도를 위해 C++로 구현했고, 파이썬 인터페이스 또한 가장 속도가 빠른 Cython으로 연동했다.
오픈소스
내부에서 적극적으로 쓰이는 것이 무엇보다 우선이지만, 지속적으로 성능을 개선하고, 코드 품질을 높이기 위해서는 오픈소스가 필수적이라 판단했다. 특히 이런 독립 라이브러리의 경우 내부에서 아무리 잘 만들어도 사장 되는 경우가 많은데, 공개하고 오픈할 경우 오히려 지속적으로 살아 숨쉬는 라이브러리가 될 수 있다는 판단에 따라 모든 코드를 공개하고 파이썬에서 쉽게 쓸 수 있도록 팩키징 하고 PyPI에 리스팅 했다.
팩키징
당초 C++에서 사용하기 편하도록 header only 라이브러리로 구현했으나, 파이썬에서는 header only 라이브러리 빌드에 어려움이 있어 헤더와 구현을 구분하였고, 보다 파이썬에 적합하도록 구조를 변경하였다.
바이너리
manylinux 이미지에서 바이너리를 빌드해서 제공하면 설치가 간편하고, 실제로 대부분의 C++ 기반 라이브러리는 미리 빌드된 바이너리를 제공하지만 개발하는 입장에서 보면 OS 종류별, 파이썬 버전별 빌드 제공은 쉽지 않은 일이다. 여기서는 소스 코드를 배포하고 로컬에서 직접 빌드하도록 했고, 따라서 로컬에는 반드시 C++11을 지원하는 gcc 또는 clang이 필요하다. 빌드는 pip가 직접 진행하므로 빌드 툴만 있다면 별도로 신경 쓸 필요가 없으며, 손쉽게 설치가 가능하다.
인터페이스
한글 변환 인터페이스 작성의 번거로움으로 인해 제공되는 파이썬 버전은 3 이상으로 제한했다. 이 글을 쓰는 시점에서 파이썬 2.7의 지원 종료가 이제 겨우 4달 남짓 밖에 남지 않았기 때문에 큰 문제는 없을거라 생각된다.
실행
팩키지명은 Korean Sentence Splitter, 줄여서 kss이다.
Python
파이썬에서는 pip로 쉽게 설치할 수 있다.
$ pip install kss
이후 다음과 같이 파이썬 코드로 실행할 수 있다.
import kss
s = "회사 동료 분들과 다녀왔는데 분위기도 좋고 음식도 맛있었어요 다만, 강남 토끼정이 강남 쉑쉑버거 골목길로 쭉 올라가야 하는데 다들 쉑쉑버거의 유혹에 넘어갈 뻔 했답니다 강남역 맛집 토끼정의 외부 모습."
for sent in kss.split_sentences(s):
print(sent)
결과는 분리된 문장에 대한 리스트로 제공되며, 아래와 같이 출력된다.
회사 동료 분들과 다녀왔는데 분위기도 좋고 음식도 맛있었어요
다만, 강남 토끼정이 강남 쉑쉑버거 골목길로 쭉 올라가야 하는데 다들 쉑쉑버거의 유혹에 넘어갈 뻔 했답니다
강남역 맛집 토끼정의 외부 모습.
C++
C++로 만들었으므로, 당연히 C++에서도 실행 가능하다.
#include <iostream>
#include "sentence_splitter.h"
int main() {
std::string s = "회사 동료 분들과 다녀왔는데 분위기도 좋고 음식도 맛있었어요 다만, 강남 토끼정이 강남 쉑쉑버거 골목길로 쭉 올라가야 하는데 다들 쉑쉑버거의 유혹에 넘어갈 뻔 했답니다 강남역 맛집 토끼정의 외부 모습.";
for (auto sent : splitSentences(s)) {
std::cout << sent << std::endl;
}
return 0;
}
결론
PyPI 리스팅을 위해 팩키징을 진행하면서, 많은 부분을 익힐 수 있었고 큰 도움이 됐다.
- C++ 개발에 있어 Google Test의 편리함과 Mock의 강력함을 다시 한 번 실감했다.
setuptools
팩키징은 강력하다.- twine과 https://test.pypi.org은 팩키징을 실험하는 매우 유용한 도구다.
- manylinux를 통해(PEP 513, PEP 571) 빌드된 바이너리를 제공하면 이종 언어로 작성해도 아무런 제약 없이 설치가 가능하다.
- 바이너리 제공이 어렵다면 소스 배포도 가능하다.
pyproject.toml
(PEP 518)을 이용하면 Cython 없이도 Cython 팩키징이 가능하다.
기술에 대한 믿음
『총, 균, 쇠』에는 다음과 같은 문장이 등장한다.
기술은 발명된 이후에 그 용도가 발견된다. 기술이란 대개 어떤 필요를 미리 내다보고 발명되는 것이 아니라 발명된 이후에 그 용도가 새로이 발견된다.
…
학자들은 휘발성이 가장 높은 분류층(휘발유)을 쓸모없는 폐기물로 간주하여 내버렸지만, 나중에는 그것이 내연기관의 이상적인 연료라는 점이 밝혀졌다. 현대 문명의 추진 연료라고 할 수 있는 휘발유가 처음에는 용도를 갖지 못한 또 하나의 발명품이었다는 사실을 기억하고 있는 사람이 누가 있을까?
아직까지 패턴 기반의 문장 분리기는 활용도가 제한적이다. 그러나 중요해질 것이란 믿음이 있다. 기술은 발명된 이후에 용도가 발견될 수 있기 때문이다. 이 작은 노력, 날개짓이 언젠가 나비 효과가 될 수 있길 바라며.
참고
- 모든 소스 코드는 깃헙에 공개했다.
- C++ 버전(1.3.1 까지) likejazz/korean-sentence-splitter - GitHub
- Python 버전(현재) hyunwoongko/kss - GitHub
- Java 버전 sangdee/kss-java - GitHub
- Korean Sentence Splitter PyPI 팩키지: