본문 바로가기

Lucene

[lucene] 복합명사 추출. Filter Class

일단 이름 Term을 사전에서 찾아내고 그 다음에 하는 일이

명사를 추출하는 것이다.

어케 보면 별거 없는데 이따 참으로 고민을 많이했다.

일단 명사 사전이 있다 치고, 과연 어떻게 만족스러운 명사 추출을 할 것인가...

대표적인 예가..

"검색엔진개발자" 라는 단어가 있고 사전에는

"검색" , "엔진", "개발자" 라는 단어들이 있다고 하자..

원하는 결과는

"검색엔진개발자" , "검색", "엔진" ,"개발자", "검색엔진", "엔진개발자" 이런식의 결과가 나오기를 원했다.

처음에는 앞에서 부터 하나씩 서치해가는 방식이었는데..

즉..


검색
검색엔진
검색엔진개...
.
.

색엔
색엔진
색엔진개..

이런식.. 이게 너무 하나의 단어에 대해서 loop를 많이 돌고 결정적으로 위의 경우에서

사전에 "검" 이라는 단어가 있다면 전혀 상관없는 단어가 나올 수 있다는 문제가 있었다.

그리고 "서핑클럽" 의 예도 나올 수 있었다.. 핑클로 검색하면 서핑클럽이 나오는..

그래서 해본 두번째 방식이 가장 큰 단어부터 찾아보자였다.

즉, "검색엔진개발자"

검색엔진개발
색엔진개발자
검색엔진개
엔진개발자
.
.
이런식으로 해봤더니 마찬가지로 불필요한 단어들이 많이 나오는 경우가 있었다.

그래서 한 것이.. "순차적으로 찾되 가장 길게 매칭되는 단어 추출" + "추출된 명사 합치기" 였다.

즉..


검색
검색엔
검색엔진

이런식으로 탐색을 하지만, 사전에 "검"이 있다고 검을 추출하는 것이 아니라
한번 더 탐색을 해봐서 (여기서는 "검색") 사전에 맞는 것이 있으면 그 단어를 추출 하는
방식이었다.

위의 경우에서는 "검"이 사전에 있지만 그 다음 단어인 "검색"이 사전에 존재하고

"검색엔"이라는 단어는 사전에 없기 때문에 "검색"을 추출하는 방식이었다.

"엔진" 마찬가지로 그렇게 추출이 되고 그러면 4개의 term이 추출되는데

"검색" "엔진" "개발" "자" (혹은 "개발자")

이 term을 합친다. 그래서 위 term 집합에 아래의 term 목록이 더해진다.

"검색엔진" "엔진개발자"

그리고, 나는 의도적으로 사용자가 입력한 단어 그 자체 혹은, 탐색 대상이 되는 단어 그 자체 (위에서는 검색엔진개발자)를 그대로 리턴하는 로직을 추가했기 때문에
최종적으로 아래와 같은 term이 추출되게 된다.

"검색엔진개발자" "검색" "엔진" "개발자" "검색엔진" "엔진개발자"

여기에 추가로 구현하려고 하는 것은 Token의 Type을 주어서 그 Type에 따라서
가중치를 부여하는 방안도 생각하고 있다.

아무튼, 위와 같은 로직으로 구현을 해보았다.

(물론 사용자가 입력한 키워드에서 복합명사를 추출하는 과정은 좀 더 단순하다. 사용자가 입력한 키워드가 "검색엔진개발자" 라면, 단순히 "검색" or "엔진" or "개발자"
이 키워드로 검색을 하면 될거 같다라는 생각이기 때문이다.)

그리고 의도적으로 입력된 단어 그 자체를 리턴하는 것은 만약

"아키텍처에서" 라는 문장이 들어오면

아키텍처 라는 키워드도 추출하겠지만, 저 문장 자체로 검색 할 경우

즉, "아키텍처에서" 라는 키워드로 검색을 하는 경우도 있을거라 생각을 해서..

그런 로직을 넣어보았다.

현재 Token의 타입은 WORD,NOUN,NAME 정도를 사용하고 있는데,  색인 할때 가중치를 Term에 부여하는 방법을 아직 찾지 못 하여, 검색 질의 키워드가 들어왔을때
Query를 만드는 과정에서 각 Term에 가중치를 주는 방식을 사용하고 있다.

이부분은 차후에 위에서 언급한대로 Type에 따른 색인시에 가중치를 다르게 주는 방안으로 꼭 구현을 해보고 싶다.

소스 전부를 공개하지는 못 해도..(창피하다. -_-)

위 클래스를 작성하기 위해서는 TokenFilter를 상속받아 next() 메서드를 오버라이드 하면 된다.

이 필터 역시 루씬 인 액션 책의 소스가 참고가 되었는데

불용어 Filter같은 경우 while문을 사용해서 불용어가 들어왔을 경우 무시하고 다음 Token을 가져오는 로직을 태우지만 이 클래스 같은 경우는

Token token = input.next(); 로 받아서
token이 null이면 끝내버리고

위 로직을 구현한 메서드 (혹은 클래스)를 사용하여 복합명사를 stack등에 넣어 놓은 후

그 스택에 있는 token을 내보내는 방식으로 구현하였다.

public Token next() throws IOException {
    if(seperatedStack.size() > 0) {
       return (Token) seperatedStack.pop();
    }

 if(token == null) {
   return null;
  }

 seperator(token); //명사를 분리하여 stack에 push.

 return token;
}
}