시소러스를 활용한 단어 의미 파악

단어는 내부에 의미를 지니며, 그 의미는 개념과 같아서 계층적 구조를 가진다고 했습니다. 아쉽게도 원핫 벡터로는 그런 단어 의미의 특징을 잘 반영할 수 없었습니다. 만약 그러한 계층 구조를 잘 분석하고 분류하여 데이터베이스로 구축한다면 자연어 처리를 할 때 매우 큰 도움이 될 것입니다. 이런 용도로 구축된 데이터베이스를 시소러스(어휘분류사전)thesaurus라고 부릅니다. 이번 절에서는 시소러스의 대표인 워드넷WordNet에 대해 다루어보겠습니다.

워드넷

워드넷(WordNet)은 1985년부터 심리학 교수인 조지 아미티지 밀러George Armitage Miller 교수의 지도 아래 프린스턴 대학교에서 만드는 프로그램입니다. 처음에는 주로 기계번역을 돕기 위한 목적으로 만들어졌으며, 따라서 '동의어 집합' 또는 '상위어'나 '하위어'에 관한 정보가 특히 잘 구축되어 있다는 장점이 있습니다. 단어에 대한 상위어와 하위어 정보를 구축함으로써, 유향 비순환 그래프directed acyclic graph(DAG)를 이루게 됩니다. 트리 구조가 아닌 이유는 하나의 노드가 여러 상위 노드를 가질 수 있기 때문입니다.

워드넷은 프로그램으로 제공되므로 내려받아 설치할 수도 있고, 워드넷 웹사이트(http:// wordnetweb.princeton.edu/perl/webwn)에서 바로 이용할 수도 있습니다. 또한 NLTK에 랩핑wrapping되어 포함되므로 임포트하여 사용 가능합니다. 다음은 워드넷 웹사이트에서 'bank' 를 검색한 결과입니다.

그림을 보면 'bank'라는 단어에 대해 명사noun일 때의 의미 10개, 동사verb일 때의 의미 8개를 정의했습니다. 명사 'bank#2'의 경우에는 여러 다른 표현(depository finaancial institution#1, banking concern#1)들도 같이 게시되어 있는데, 이것이 동의어 집합입니다.

이처럼 워드넷은 단어별 여러 가지 가능한 의미를 미리 정의하고 번호를 매겨 놓았습니다. 또한 의미별로 비슷한 뜻의 동의어를 링크해 동의어 집합을 제공합니다. 이것은 단어 중의성 해소에 매우 좋은 레이블 데이터가 될 수 있습니다. 만약 워드넷이 없다면 단어별 의미가 전부 몇 개나 되는지조차 알 수 없을 것입니다. 즉, 워드넷이 제공하는 이들 데이터를 바탕으로 지도학습supervised learning을 통해 단어 중의성 해소 문제를 풀 수 있습니다.

한국어 워드넷

다행히 영어 워드넷처럼 한국어를 위한 워드넷도 존재합니다. 다만 아직까지 표준이라고 할 만큼 정해진 것은 없고, 몇 개의 워드넷이 존재하는 정도입니다. 지속적으로 발전하고 있는 만큼, 작업에 따라 필요한 한국어 워드넷을 이용하면 좋습니다.

워드넷을 활용한 단어간 유사도 비교

from nltk.corpus import wordnet as wn

def hypernyms(word):
    current_node = wn.synsets(word)[0]
    yield current_node

    while True:
        try:
            current_node = current_node.hypernyms()[0]
            yield current_node
        except IndexError:
            break

앞의 코드를 사용하면 워드넷에서 특정 단어의 최상위 부모 노드까지의 경로를 구할 수 있습니다. 다음과 같이 'policeman'은 'firefighter', 'sheriff'와 매우 비슷한 경로를 가짐을 알 수 있 습니다. 'student'와도 매우 비슷하지만 'mailman'과 더욱 비슷함을 알 수 있습니다.

>>> get_hypernyms(wn.synsets('policeman')[0])
Synset('policeman.n.01')
Synset('lawman.n.01')
Synset('defender.n.01')
Synset('preserver.n.03')
Synset('person.n.01')
Synset('causal_agent.n.01')
Synset('physical_entity.n.01')
Synset('entity.n.01')
>>> get_hypernyms(wn.synsets('firefighter')[0])
Synset('fireman.n.04')
Synset('defender.n.01')
Synset('preserver.n.03')
Synset('person.n.01')
Synset('causal_agent.n.01')
Synset('physical_entity.n.01')
Synset('entity.n.01')
>>> get_hypernyms(wn.synsets('sheriff')[0])
Synset('sheriff.n.01')
Synset('lawman.n.01')
Synset('defender.n.01')
Synset('preserver.n.03')
Synset('person.n.01')
Synset('causal_agent.n.01')
Synset('physical_entity.n.01')
Synset('entity.n.01')
>>> get_hypernyms(wn.synsets('mailman')[0])
Synset('mailman.n.01')
Synset('deliveryman.n.01')
Synset('employee.n.01')
Synset('worker.n.01')
Synset('person.n.01')
Synset('causal_agent.n.01')
Synset('physical_entity.n.01')
Synset('entity.n.01')
>>> get_hypernyms(wn.synsets('student')[0])
Synset('student.n.01')
Synset('enrollee.n.01')
Synset('person.n.01')
Synset('causal_agent.n.01')
Synset('physical_entity.n.01')
Synset('entity.n.01')

이로부터 얻어낸 정보들을 취합하여 그래프로 나타내면 다음과 같습니다. 그림에서 각 최하단 노드들은 코드에서 쿼리로 주어진 단어들입니다.

이때 각 노드 간 거리를 구할 수 있습니다. 다음 그림에 따르면 'student'에서 'fireman'으로 가는 최단 거리에는 'enrollee', 'person', 'preserver', 'defender' 노드들이 위치합니다. 따라서 'student'와 'fireman'의 거리는 5임을 알 수 있습니다.

이처럼 우리는 각 최하단 노드 간의 최단 거리를 알 수 있고, 이것을 유사도로 치환하여 활용할 수 있습니다. 당연히 거리가 멀수록 단어간의 유사도는 떨어질 테니, 다음과 같은 공식을 적용해 볼 수 있습니다.

이처럼 시소러스 기반의 정보를 활용하여 단어 간 유사도를 구할 수 있습니다. 하지만 사전을 구축하는 데는 너무 큰 비용과 시간이 소요됩니다. 또한 상위어와 하위어가 잘 반영된 사전이어야만 할 것입니다. 이와 같이 사전에 기반한 유사도를 구하는 방식은 비교적 정확한 값을 구할 수 있으나 그 한계가 뚜렷합니다.

Last updated