Lucene - 유사어,동의어 필터(SynonymFilter)를 이용한 커스텀 Analyzer

2019. 2. 2. 13:23Search-Engine/Lucene

Lucene - 유사어,동의어필터(SynonymFilter)를 이용한 커스텀 Analyzer



Lucene에는 사용자가 입력한 질의 혹은 색인 할때의 토큰화 과정에서 여러가지 필터를 등록할 수 있다. 토큰의 종류는 아주 많다. StopFiler(불용어처리,불용어처리 단어의 리스트가 필요),SynonymFiler 등 의 필터들이 존재한다. 그 말은 단순히 토큰화된 텀들을 그대로 사용하는 것이 아니라 전처리,후처리를 필터를 이용해서 처리하여 토큰화된 텀에게 여러가지 효과?를 적용할 수 있는 것이다. 여기서는 간단히 유사어필터를 이용한 Custom한 분석기를 만들어 볼 것이며, 유사어 필터의 특징을 간단히 설명할 것이다.









1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
public class SynonymAnalyzerTest {
 
    
 
    
 
    public void testJumps(String text) throws IOException {
 
        System.out.println("Analyzing \"" +text+ "\"");
 
        System.out.println("\n");
 
        
 
        SynonymAnalyzer analyzer = new SynonymAnalyzer();
 
        
 
        String name = analyzer.getClass().getSimpleName();
 
        System.out.println("  "+name+"  ");
 
        System.out.print("    ");
 
        AnalyzerUtils.displayTokens(analyzer,text);
 
        
 
        System.out.println("\n");
 
        
 
    }
 
    
 
    
 
    public static void main(String[] args) throws IOException {
 
        // TODO Auto-generated method stub
 
        SynonymAnalyzerTest t = new SynonymAnalyzerTest();
 
        t.testJumps("나는 jumps 할거야");
 
    }
 
}
 
class SynonymAnalyzer extends Analyzer{
 
    
 
    @Override
 
    protected TokenStreamComponents createComponents(String fieldName) {
 
        // TODO Auto-generated method stub
 
        
 
        SynonymMap.Builder builder = new SynonymMap.Builder(true);
 
        builder.add(new CharsRef("JUPMS"), new CharsRef("점프,뛰다"), true);
 
        
 
        SynonymMap map = null;
 
        
 
        try {
 
            map=builder.build();
 
        }catch (Exception e) {
 
            // TODO: handle exception
 
            e.printStackTrace();
 
        }
 
        
 
        Tokenizer tokenizer = new StandardTokenizer();
 
        TokenStream filter = new LowerCaseFilter(tokenizer);
 
        filter = new SynonymFilter(filter,map,true);
 
        return new TokenStreamComponents(tokenizer,filter);
 
    }
 
    
 
}
 
class AnalyzerUtils{
 
    public static void displayTokens(Analyzer analyzer,String text) throws IOException {
 
        displayTokens(analyzer.tokenStream("content"new StringReader(text)));
 
    }
 
    
 
    public static void displayTokens(TokenStream stream) throws IOException {
 
        
 
        //텀 속성확인
 
        CharTermAttribute cattr = stream.addAttribute(CharTermAttribute.class);
 
        
 
        //위치증가값 속성 확인
 
        PositionIncrementAttribute postAtrr = stream.addAttribute(PositionIncrementAttribute.class);
 
        //오프셋위치확인
 
        OffsetAttribute offsetAttr = stream.addAttribute(OffsetAttribute.class);
 
        //텀타입 속성 확인
 
        TypeAttribute typeAttr = stream.addAttribute(TypeAttribute.class);
 
        
 
        //stream.incrementToken을 위해 필요
 
        stream.reset();
 
        
 
        int position = 0;
 
        
 
        while (stream.incrementToken()) {
 
            int increment = postAtrr.getPositionIncrement();
 
            
 
            position = position + increment;
 
            System.out.println();
 
            System.out.print(position + ": ");
 
            System.out.print("[ "+cattr.toString()+" : " + offsetAttr.startOffset()+"->"+offsetAttr.endOffset()+"                         : "+typeAttr.type()+" ]");
 
        }
 
 
 
        stream.end();
 
        stream.close();
 
        
 
    }
 
}
cs



-> 이 소스를 간단히 설명하면 커스텀한 분석기를 만들고 그 분석기를 이용해 분석된 사용자 입력 문장을 결과로 뿌려주는 역할을 하는 소스이다.

     1)사용자 정의 분석기

- 지금 작성한 커스텀 분석기는 StandardTokenizer에 SynonymFiler를 붙인 것이다. 여기서 빌더패턴을 이용하여 SynonymMap이란 객체를 다루고 있는데, 이것은 유사어 필터에게 유사어 목록이 담긴 맵을 전달해주기 위한 과정이다. 그리고 사용할 Tokenizer 클래스를 생성하고 사용할 필터의 input으로 토크나이저 객체를 전달해준다. 그리고 유사어 목록이 담긴 맵을 전달해주고, 원 단어를 저장할 것인가 안할 것인가를 지정하는 boolean타입의 매개변수까지 전달을 해준다. 그리고 마지막으로 TokenStreamComponents를 리턴해준다. 여기서 하나빼먹은 설명은 filter는 여러개가 될 수 있다는 점이다. 그래서 유사어 필터전 모든 텀을 소문자로 바꿔주는 LowerCaseFilter를 적용했다. 그런데 조금 설명이 필요한 점이라면 컴포지트 패턴을 이용하여 필터를 이어붙이고 있다는 점이다


   2)분석기 적용 결과

- 분석기의 tokenStream 메소드를 호출하면 최종적인 처리가된 TokenStream객체를 리턴해준다. 이 TokenStream 객체를 이용하여 분석된 결과를 출력할 수 있다.(최종적으로 색인에 들어가는 데이터는 TokenStream에 텀과 여러가지 메타데이터가 담기는 데이터이다.) 나머지 소스는 주석으로 충분히 예측가능할 것이다.






마지막 결과를 확인하면 JUMPS라는 단어가 소문자로 되어 유사어 필터가 적용되는 것을 볼 수 있다. 하지만 조금 특이한 점이 있다.


<결과>

Analyzing "나는 JUMPS 할거야"



  SynonymAnalyzer  

    

1: [ 나는 : 0->2 : <HANGUL> ]

2: [ jumps : 3->8 : <ALPHANUM> ]

2: [ 점프,뛰다 : 3->8 : SYNONYM ]

3: [ 할거야 : 9->12 : <HANGUL> ]


원단어와 유사어 처리된 단어가 위치 값이 같은 것이다. 즉, 색인에는 원단어는 물론 유사어까지 같은 포지션을 갖고 색인된다는 것이다. 이 말은 색인과정에서 유사어 필터를 등록한다면 검색에서는 유사어가 포함이 되어 있는 구문으로 구문검색을 해도 색인했던 원문 Document가 검색될 수 있다는 점이다. 아주 좋은 기능일 것 같다. 하지만 유사어 필터는 결코 가벼운 작업이 아니기에 꼭 색인 혹은 검색 둘중하나의 과정에만 적용시키면 된다. 보통 색인과 검색에 둘다 유사어 필터가 담긴 분석기를 사용하기도 하는데, 나중에 아주 데이터가 커지고 애플리케이션이 커지면 영향을 미칠 수도 있을 것같다.