[Python] NLTK(Natural Language Toolkit)와 WordNet으로 자연어 처리하기 맛보기
Deep Learning (TF, Keras, PyTorch)/Natural Language Processing 2020. 8. 2. 22:22이번 포스팅에서는 Python의 NLTK (Natural Language Toolkit) 라이브러리와 WordNet 말뭉치를 사용하여 자연어 처리 (Natural Language Processing) 하는 몇 가지 방법을 맛보기로 소개하겠습니다.
(저는 Python 3.8 버전에 NLTK 3.5 버전을 사용하였습니다. 자연어 처리, NLTK를 제대로 살펴보려면 책 한권, 한 학기 수업 분량이며, 이번 포스팅은 말 그대로 맛보기 정도의 소개입니다. ^^;)
Python NLTK (Natural Language Toolkit) 라이브러리는 텍스트 분류, 토큰화, 단어 stemming, 품사 태킹, 텍스트 파싱, semantic reasoning 등을 하는데 사용하는 텍스트 자연어 처리 파이썬 모듈입니다.
(* reference: https://www.nltk.org/book/ch02.html )
WordNet은 어휘에 중점을 둔 영어 사전 Database 로서, 전통적인 Thesaurus (유의어 사전)와 유사한 측면이 있지만 보다 풍성한 구조를 가지고 있습니다. 1985년부터 프린스턴 대학교에서 구축하기 시작하였으며, 약 16만개의 단어(155287 words)와 12만개의 유사어 집합(117,659 synonym sets)을 포함하고 있는 방대한 Thesaurus 입니다.
(* reference: https://www.nltk.org/howto/wordnet.html )
NLTK와 WordNet에 대한 간단한 소개를 마쳤으니 이제 직접 코드를 짜보면서 예를 들어보겠습니다. 먼저, 명령 프롬프트 창에서 pip install로 NLTK 라이브러리를 설치하고 pip show로 버전과 내용을 확인해보겠습니다. (Anaconda 배포판으로 Python 가상 환경을 만들었다면 아마 디폴트로 NLTK 모듈이 설치되어 있을 겁니다.)
-- (명령 프롬프트 창에서) NLTK 모듈 설치 $ pip install nltk -- NLTK 모듈 확인 $ pip show nltk Name: nltk Version: 3.5 Summary: Natural Language Toolkit Home-page: http://nltk.org/ Author: Steven Bird Author-email: stevenbird1@gmail.com License: Apache License, Version 2.0 Location: /Users/ihongdon/anaconda3/envs/py3.8_tf2.3/lib/python3.8/site-packages Requires: regex, click, tqdm, joblib Required-by: |
(1) NLTK로 Word Tokenization 하기 |
Token 이란 더이상 나눌 수 없는 언어요소를 말하며, Tokenization은 텍스트 문자열을 Token으로 분해하여 쪼개는 것입니다.
처음 사용하는 경우라면 먼저 nltk.download('punkt') 를 실행하여 Punket Tokenizer Models (13MB) 를 다운로드 해줍니다.
nltk.word_tokenize() 함수를 사용해서 괄호 안에 텍스트 문자열 객체를 넣어주면 Token으로 쪼개줍니다.
(영어는 띄어쓰기 기준으로 단어 토큰화가 되었는데요, 한글은 단순히 띄어쓰기 기준만으로 토큰화가 되지 않는 어려움이 있습니다.)
#%% word tokenization import nltk # Please use the NLTK Downloader to obtain the resource: nltk.download('punkt') sentense = "NLTK is a leading platform for building Python programs to work with human language data." tokens = nltk.word_tokenize(sentense) tokens Out[1]: ['NLTK', 'is', 'a', 'leading', 'platform', 'for', 'building', 'Python', 'programs', 'to', 'work', 'with', 'human', 'language', 'data', '.']
|
* 참고로, keras 라이브러리로의 text_to_word_seqence() 메소드로 단어 토큰화를 할 수 있습니다.
(from tensorflow.keras.preprocessing.text import text_to_word_sequence)
(2) NLTK로 품사 태깅하기 (Tagging) |
품사 태깅을 하려면 먼저 nltk.download('averaged_perceptron_tagger') 로 태깅에 필요한 자원을 다운로드 해줍니다. 그리고 nltk.pos_tag() 메소드를 사용해서 위의 (1)번에서 만든 단어 토큰들에 대해서 품사 태킹을 할 수 있습니다.
#%% word tagging # Please use the NLTK Downloader to obtain the resource: nltk.download('averaged_perceptron_tagger') tagged = nltk.pos_tag(tokens) tagged Out[2]: [('NLTK', 'NNP'), ('is', 'VBZ'), ('a', 'DT'), ('leading', 'VBG'), ('platform', 'NN'), ('for', 'IN'), ('building', 'VBG'), ('Python', 'NNP'), ('programs', 'NNS'), ('to', 'TO'), ('work', 'VB'), ('with', 'IN'), ('human', 'JJ'), ('language', 'NN'), ('data', 'NNS'), ('.', '.')]
|
위의 단어 태킹 예에서 보면, ('NLTK', 'NNP') 처럼 (단어 토큰, 품사) 의 쌍을 이룬 튜플들의 리스트로 되어있습니다. 품사의 약어가 무엇을 의미하는지는 아래의 품사 약어를 참고하시기 바랍니다.
(예: ('is', 'VBZ') 에서 'VBZ'는 verb, 3rd person sing. present takes 입니다)
POS tag list: CC coordinating conjunction CD cardinal digit DT determiner EX existential there (like: "there is" ... think of it like "there exists") FW foreign word IN preposition/subordinating conjunction JJ adjective 'big' JJR adjective, comparative 'bigger' JJS adjective, superlative 'biggest' LS list marker 1) MD modal could, will NN noun, singular 'desk' NNS noun plural 'desks' NNP proper noun, singular 'Harrison' NNPS proper noun, plural 'Americans' PDT predeterminer 'all the kids' POS possessive ending parent\'s PRP personal pronoun I, he, she PRP$ possessive pronoun my, his, hers RB adverb very, silently, RBR adverb, comparative better RBS adverb, superlative best RP particle give up TO to go 'to' the store. UH interjection errrrrrrrm VB verb, base form take VBD verb, past tense took VBG verb, gerund/present participle taking VBN verb, past participle taken VBP verb, sing. present, non-3d take VBZ verb, 3rd person sing. present takes WDT wh-determiner which WP wh-pronoun who, what WP$ possessive wh-pronoun whose WRB wh-abverb where, when * source: https://pythonprogramming.net/part-of-speech-tagging-nltk-tutorial/ |
(3) WordNet에서 동의어 (Synonyms) 찾기 |
WordNet을 처음 사용하는 사용자라면 먼저 nltk.download('wordnet') 을 다운로드 해줍니다. 그리고 from nltk.corpus import wordnet as wn 으로 WordNet을 wn 이라는 alias로 importing 해주었습니다.
단어의 의미가 여러개가 있을 수 있는데요, NLTK WordNet에서 wordnet.synsets() 함수를 사용해서 동의어 집합을 찾을 수 있습니다. 아래 예에서는 'car'라는 단어가 5개의 단어 집합을 가지고 있네요.
#%% Senses and Synonyms from Wordnet # Please use the NLTK Downloader to obtain the resource: nltk.download('wordnet') from nltk.corpus import wordnet as wn wn.synsets('car') Out[3]: [Synset('car.n.01'), Synset('car.n.02'), Synset('car.n.03'), Synset('car.n.04'), Synset('cable_car.n.01')] |
가령, Synset('car.n.01') 는 '단어.품사.그룹인덱스' 를 나타내는데요, 특정 의미의 단어를 보려면 '그룹인덱스'를 사용해서 명시적으로 지정을 해줘야 합니다. 아래 예에서는 첫번째 인덱스의 'car.n.01' 표제어의 c단어 정의(definition())와, 동의어 단어 집합(lemma_names())을 알아보겠습니다.
car = wn.synset('car.n.01') car.definition() Out[4]: 'a motor vehicle with four wheels; usually propelled by an internal combustion engine' car.lemma_names() Out[5]: ['car', 'auto', 'automobile', 'machine', 'motorcar'] |
for loop 을 사용해서 car의 5개 표제어 모두에 대해서 차례대로 모두 동의어 단어집합을 인쇄해볼 수도 있습니다.
for synset in wn.synsets('car'): print(synset.lemma_names()) ['car', 'auto', 'automobile', 'machine', 'motorcar'] ['car', 'railcar', 'railway_car', 'railroad_car'] ['car', 'gondola'] ['car', 'elevator_car'] ['cable_car', 'car'] |
(4) WordNet에서 반대말 (Antonyms) 찾기 |
위의 (3)번에서는 WordNet에서 동의어(Synonym)를 찾아보았다면, 이제는 WordNet에서 반대말, 반의어(Antonym)를 찾아보겠습니다. 가령, 아래 예에서는 공급(supply)의 반대말은 수요(demand), 왕(king)의 반대말은 여왕(queen) 이라고 대답해주네요.
#%% antonym wn.lemma('supply.n.02.supply').antonyms() Out[7]: [Lemma('demand.n.02.demand')] wn.lemma('king.n.01.king').antonyms() Out[8]: [Lemma('queen.n.02.queen')]
|
(5) WordNet에서 단어 위계구조 (Hierarchy) 찾기 |
WordNet에는 단어 간의 관계를 상/하 위계구조로 정리가 되어있습니다. 'car.n.01' 표제어에 대해서 상/하 위계구조를 hypernym_paths() 메소드를 사용해서 찾아보면 아래와 같이 2개가 나옵니다. car.hypernym_paths()[0] 으로 '0' 번째 것만 indexing 해서 볼 수 있습니다.
#%% The Wordnet Hierarchy # 상위어 (hypernym) from nltk.corpus import wordnet as wn car = wn.synset('car.n.01') car.hypernym_paths() Out[9]: [[Synset('entity.n.01'), Synset('physical_entity.n.01'), Synset('object.n.01'), Synset('whole.n.02'), Synset('artifact.n.01'), Synset('instrumentality.n.03'), Synset('container.n.01'), Synset('wheeled_vehicle.n.01'), Synset('self-propelled_vehicle.n.01'), Synset('motor_vehicle.n.01'), Synset('car.n.01')], [Synset('entity.n.01'), Synset('physical_entity.n.01'), Synset('object.n.01'), Synset('whole.n.02'), Synset('artifact.n.01'), Synset('instrumentality.n.03'), Synset('conveyance.n.03'), Synset('vehicle.n.01'), Synset('wheeled_vehicle.n.01'), Synset('self-propelled_vehicle.n.01'), Synset('motor_vehicle.n.01'), Synset('car.n.01')]] # indexing car.hypernym_paths()[0] Out[10]: [Synset('entity.n.01'), Synset('physical_entity.n.01'), Synset('object.n.01'), Synset('whole.n.02'), Synset('artifact.n.01'), Synset('instrumentality.n.03'), Synset('container.n.01'), Synset('wheeled_vehicle.n.01'), Synset('self-propelled_vehicle.n.01'), Synset('motor_vehicle.n.01'), Synset('car.n.01')]
|
위에 위계구조 리스트를 상/하 네트워크 그래프로 시각화해서 보면 좀더 직관적으로 이해할 수 있습니다. 이때 노드(nodes, vertex)는 동의어 집합(synsets)에 해당하며, 연결선(edges, links)은 단어 개념 상의 상/하 관계(hypernym/hyponym relation)을 나타냅니다.
* image source: https://www.nltk.org/book/ch02.html
(6) WordNet을 이용한 단어 간 의미 유사도 (Semantic Similarity) 측정 |
단어 간 의미 유사도를 측정하는 방법에는 다양한 기준이 있을 수 있는데요, 아래의 3가지 방법을 소개하겠습니다.
- 경로 거리 기반 유사도 (Path Distance Similarity)
- Leacock Chordorow 유사도 (Leacock Chordorow Similarity)
- Wu-Palmer 유사도 (Wu-Palmer Similarity)
(6-1) 경로 거리 기반 유사도 (Path Distance Similarity)
먼저, 경로 거리 유사도는 위의 (5)번에서 소개했던 단어 간 상/하 위계구조에서의 최단 경로 (Shortest Path)의 거리를 기반으로 유사도를 0~1 사이의 실수로 측정합니다. (즉, 경로 거리가 가까울 수록 유사하고, 거리가 멀 수록 유사하지 않게 평가하며, 유사도가 1에 가까울 수록 유사한 것을 의미함).
아래 예에서는 고래 right whale 과 orca, minke whale 는 경로 거리 유사도가 높게 나왔고, 거북이 tortoise 는 좀 낮게 나왔으며, 소설 novel 은 매우 낮게 나왔습니다. 상식과 어느정도 부합하네요.
#%% Semantic Similarity # (1) Path Distance Similarity right_whale = wn.synset('right_whale.n.01') orca = wn.synset('orca.n.01') right_whale.path_similarity(orca) Out[11]: 0.16666666666666666 minke = wn.synset('minke_whale.n.01') right_whale.path_similarity(minke) Out[12]: 0.25 tortoise = wn.synset('tortoise.n.01') right_whale.path_similarity(tortoise) Out[13]: 0.07692307692307693 novel = wn.synset('novel.n.01') right_whale.path_similarity(novel) Out[14]: 0.043478260869565216 |
(6-2) Leacock Chordorow 유사도 (Leacock Chordorow Similarity)
Leacock Chodorow 유사도는 단어 간 위계구조에서의 최단 거리(shortest path)와 단어 의미가 발생하는 최대 깊이(maximum depth)에 기반해서 유사도를 계산합니다. 위의 path_similarity() 가 0~1 사이의 표준화된 값을 반환하는 반면에, lch_similarity() 는 표준화되지 않은 차이가 있습니다.
아래 예에서 right whale 과 minke whale, tortoise, novel 간의 Leacock Chordorow 유사도와 위의 (6-1) Path distance similarity 의 경향, 유사도 순서는 비슷하게 나왔습니다.
# (3) Leacock Chordorow (LCH) Similarity right_whale.lch_similarity(orca) Out[15]: 1.845826690498331 right_whale.lch_similarity(minke) Out[16]: 2.2512917986064953 right_whale.lch_similarity(tortoise) Out[17]: 1.072636802264849 right_whale.lch_similarity(novel) Out[18]: 0.5020919437972361
|
(6-3) Wu-Palmer 유사도 (Wu-Palmer Similarity)
Wu-Palmer 유사도는 단어 위계구조에서 두 단어의 깊이(depth of the tow senses in the taxonomy)와 단어 간의 최소 공통 포함(Least Common Subsumer)에 기반해서 유사도를 계산합니다.
# (2) Wu-Palmer Similarity right_whale.wup_similarity(orca) Out[19]: 0.8484848484848485 right_whale.wup_similarity(minke) Out[20]: 0.9090909090909091 right_whale.wup_similarity(tortoise) Out[21]: 0.6 right_whale.wup_similarity(novel) Out[22]: 0.08333333333333333 |
위의 3가지 외에도 NLTK 라이브러리는 최소 공통 포함 Information Content (IC of the Least Common Subsumer) 기반의 Resnik Similarity, Lin Similarity, Jiang-Conrath Similarity 등의 알고리즘을 제공합니다. (* reference: http://jaganadhg.github.io/wornet-nltk-sense/)
많은 도움이 되었기를 바랍니다.
이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾹 눌러주세요. :-)