'CharTokenizer'에 해당되는 글 2건

  1. 2008.07.18 [lucene] CharTokenizer를 살펴보자.
  2. 2008.05.07 [lucene] 모르겠다.. CharTokenizer..ㅠㅠ

한글 색인어 추출기 작업을 위해서 가장 먼저 살펴봐야 했던 class가

 CharTokenizer였다.

정말 아무것도 모르는 상태에서 시작하게 된거라서 난감하기도 했고

오픈소스라는 놈을 처음 열어보는거라..(그냥 쓰기만했지... 이걸 까볼줄은...;;)

CharTokenizer를 열어보았을때 느낌은..

"이 뭥미?"

이해가 전혀 되지 않았다 -_-

그냥 일단 한손에 삽들고 팠다.


CharTokenizer는 루씬에서 모든 Analyzer의 기본이 된다.

이놈의 역할은 문장을 읽어들여서 Token을 만들어 반환하는 것이다.

그럼 어떻게 Token을 만드느냐..

한글자씩 읽어와서 문자냐 아니냐를 판단하여 만들어내는데 그 역할 하는 것이

protected abstract boolean isTokenChar(char c);

이 메서드이다. 추상클래스이기 때문에 구현을 해줘야 하는데 기본적으로 제공되는

SimpleAnalyzer를 보면 아래와 같이 구현되어 있다.

  protected boolean isTokenChar(char c) {
    return Character.isLetter(c);
  }

문자냐 아니냐....

자 그럼 대충 감이 올것이다.

"안녕하세요?용식입니다."
"안녕하세요 용식입니다."
"안녕하세요yongsik용식입니다."
"안녕하세요12345용식입니다."

위에서부터 1~4번이라 하고
1번을 한글자씩 읽어오면.. [안녕하세요]까지 읽어온 후 ?가 나오는 시점에서
문자가 아니기 때문에 [안녕하세요]까지가 하나의 토큰이 된다.

2번은 스페이스가 있는 부분에서 토큰으로 나뉘어진다.
3번은? 전부 문자이기 때문에 [안녕하세요yongsik용식입니다]가 하나의 토큰이 되고
4번은 숫자가 빠지고 [안녕하세요] [용식입니다]가 된다.

그리고 또하나의 메서드가 있는데

 protected char normalize(char c) {
    return c;
  }
메서드이다. 정규화? 응?

영어로 검색을 할 경우  abc로 검색하는 경우와 ABC로 검색하는 경우에 결과가 서로
달라서야 쓰겠는가!

일반적으로 저 메서드는 아래와 같이 오버라이드 되어있다.

  protected char normalize(char c) {
    return Character.toLowerCase(c);
  }

소문자...

자 여기까지 봤을때 느껴지는 것이 있을것이다. 바로..

"영어권에서는 꽤... 결과가 좋겠네.. 영어에서 유용하겠네.." 이다.

영어는 일반적으로 단어마다 스페이스가 들어가있고, 대소문자가 다르기에 저런 정규화과정이 필요할 것이다. 한글을 처리하기 위해서는 우리에게 맞게 이 클래스를 수정해 주어야 한다.

처음에 나는 상품명을 대상으로 색인어를 추출 할 생각이었다.
(지금은 복합명사추출까지...)

일반적인 상품명을 보면..

"나이키 청바지", "dvd플레이어 모델명 mlkd-112009" , "삼성mp3 yepp-1011" 등으로 나누어지는데

일단 스페이스를 기준으로 분리를 하면 어느정도 모양새는 갖춰진다.
(http://devyongsik.tistory.com/21 참조)

하지만 위에서 3,4번의 예를 봤듯이 영문과 한글을 떨어트려야 하고
영문과 숫자는 붙여놓아야 그나마 좀 더 제대로 된 상품명과 모델명을 가져 올 수가 있게 된다.
(dvd플레이어 -> dvd, 플레이어, mp3 -> mp3)

그렇다면 어떻게 하면 좋을라나...

CharTokenizer에서 한글한글자 읽어올때 그 character의 값을 비교하여
토큰으로 분리시켜 낼 것인지 아닌지를 판단해서  return 해주면 되는 것이다.

이를 위해서 isTokenChar를 오버라이드 했고,
next(Token token) 메서드를 오버라이드 했다.

 protected boolean isTokenChar(char c) {
  return (Character.isLetter(c) || Character.isDigit(c) || (c == '-'));
 }

일단 이렇게 해놓으면 숫자가 걸러지는 일도 없고 모델명을 위해서 -도 토큰에 들어가게 된다.

그리고  next(Token) 메서드에 저 c를 비교하여 토큰을 끊는 로직을 넣어주면 되는 것이다.


이런식으로 대상이 되는 데이터에 따라서 조금씩 클래스를 수정하여 입맛대로 (어느정도) 바꿔낼 수 가 있다. (블로그같은 광범위한 데이터라면 많이 복잡해 질테지만..)

아무튼 일단 이렇게 토큰을 잘라내고나서 이제 Filter에서

어미제거/불용어제거/복합명사추출/이름추출/동의어추출 등의 작업을 하게 된다.

Posted by 용식

CharTokenizer..

Analyzer WhitespaceAnalyzer = new WhitespaceAnalyzer();
TokenStream stream = analyzer.tokenStream("contents", new StringReader(text));

Token token = stream.next();

바로 이 next...!!

stream.next는 TokenStream의 next 메서드가 호출되고 이는

public Token next() throws IOException {
    Token result = next(new Token());

    if (result != null) { //이것들은 무엇을 하기 위한 로직인고..;;
      Payload p = result.getPayload();
      if (p != null) {
        result.setPayload((Payload) p.clone());
      }
    }

    return result;
  }

이렇게 생겼다.

첫 라인을 보면, next(Token) 메서드를 호출하는데 이는 CharTokenizer 클래스에서
오버라이드한 메서드가 호출된다.

보면, 문장을 읽어들여서 isTokenChar(char)를 사용하여 토큰단위로 분리해내고
Token.termLength, Token.startOffset, enfOffset을 설정해 주는듯 하다...

그런데..

소스 중
buffer = token.resizeTermBuffer(1+length);

이부분이 무엇을 위한 부분인지 이해가 되지 않고...

token.termText() 를 실행했을때
어떻게 토큰으로 잘려진 단어가 나오는지도 잘 모르겠다..

token.termText()는

public final String termText() {
    if (termText == null && termBuffer != null)
      termText = new String(termBuffer, 0, termLength);
    return termText;
  }

으로 되는데..
메서드를 봐서는 termBuffer에 잘려진 토큰 단어가
들어가 있어야 한다는 것인데.. (0부터 termLength만큼 가져오니까...)

그것을 어디서 설정해 주는지 당최 모르겠다..어흑..


알았다!!!!

CharTokenizer에서
public final Token next(Token token) throws IOException {
  char[] buffer = token.termBuffer();
...

이 부분을 통해 buffer에 Token 클래스의 termBuffer의 메모리를 할당하여
CharTokenizer에서 각각의 캐릭터를 하나하나 buffer에 할당하게 되면
자동적으로 Token의 termBuffer도 같은 값을 갖도록 되어있다.

그래서
char[] buffer = token.termBuffer();
이거 타고 들어가면 주구장창 Token 클래스의 termBuffer를 초기화 하는거라..


위에 모르겠다고 한
buffer = token.resizeTermBuffer(1+length);
이 부분은 단순히 처음 들어온 단어(토큰)가 너무 길때 이것을 새로 리사이징
해주는 것이고..

첫 스페이스나 char가 아닌 글자가 들어오면
    token.termLength = length;
    token.startOffset = start;
    token.endOffset = start+length;
이 값들을 할당하면서
Token을 리턴하는데 이때 이미

char[] buffer 와 Token의 char[] termBuffer는 같은 메모리를 참조하고 있기 때문에
자동적으로 리턴된 Token은 잘려진 termText를 가질 수 있게 된다.

Posted by 용식