이번에 포스팅할 내용은 More Like This 입니다. More Like This 쿼리란 주어진 질의문과 가장 유사한 문서를 찾는 알고리즘입니다. 그리고 해당 알고리즘을 사용하지 위해서는 쿼리를 날릴 필드가 인덱싱되어야하며 분석 속도를 높이기 위하여 term_vector 속성을 지정해주는 것이 좋습니다. 필자를 참고로 간단한 Q&A를 위한 챗봇을 만들기 위하여 해당 알고리즘을 이용하였습니다. 물론 문맥을 이해하는 것이 아니라 유사도를 판단하는 것이지만 나름 성능이 나오는 알고리즘입니다. 모든 예제는 이전에 이용하였던 High Level Rest Client를 이용하였습니다.

 

Index

한글형태소 분석기가 포함된 인덱스를 생성하는 코드입니다. 참고로 질의의 대상이 되는 "question"필드의 term_vector 속성을 "yes"로 지정하였습니다. 생성 후에 몇개 예제로 문서를 색인해주세요.

 

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
@RequestMapping("/create")
    public Object createIndex() {
        
        boolean acknowledged = false;
        
        try(
                RestHighLevelClient client = createConnection()
        ){
            //index name
            String indexName = "sample-korean-index";
            //type name
            String typeName = "_doc";
            
            //settings
            XContentBuilder settingsBuilder = XContentFactory.jsonBuilder()
                    .startObject()
                        .field("number_of_shards",5)
                        .field("number_of_replicas",1)
                        
                        .startObject("analysis")
                            .startObject("tokenizer")
                                .startObject("sample-nori-tokenizer")
                                    .field("type","nori_tokenizer")
                                    .field("decompound_mode","mixed")
                                    .field("user_dictionary","user_dictionary.txt")
                                .endObject()
                            .endObject()
                            
                            .startObject("analyzer")
                                .startObject("sample-nori-analyzer")
                                    .field("type","custom")
                                    .field("tokenizer","sample-nori-tokenizer")
                                    .array("filter",new String[]{
                                        "sample-nori-posfilter",
                                        "nori_readingform",
                                        "sample-synonym-filter",
                                        "sample-stop-filter"
                                        }
                                    )
                                .endObject()
                            .endObject()
                            
                            .startObject("filter")
                                .startObject("sample-nori-posfilter")
                                    .field("type","nori_part_of_speech")
                                    .array("stoptaags",new String[] {
                                            "E","IC","J","MAG","MM","NA","NR","SC",
                                            "SE","SF","SH","SL","SN","SP","SSC","SSO",
                                            "SY","UNA","UNKNOWN","VA","VCN","VCP","VSV",
                                            "VV","VX","XPN","XR","XSA","XSN","XSV"
                                        }
                                    )
                                .endObject()
                                
                                .startObject("sample-synonym-filter")
                                    .field("type","synonym")
                                    .field("synonyms_path","synonymsFilter.txt")
                                .endObject()
                                
                                .startObject("sample-stop-filter")
                                    .field("type","stop")
                                    .field("stopwords_path","stopFilter.txt")
                                .endObject()
                            .endObject()
                        .endObject()
                    .endObject();
            
            //mapping info
            XContentBuilder indexBuilder = XContentFactory.jsonBuilder()
            .startObject()
                .startObject(typeName)
                    .startObject("properties")
                        .startObject("question")
                            .field("type","text")
                            .field("analyzer","sample-nori-analyzer")
                            .field("term_vector","yes")
                        .endObject()
                        .startObject("answer")
                            .field("type","keyword")
                        .endObject()
                    .endObject()
                .endObject()
            .endObject();
            
            //인덱스생성 요청 객체
            CreateIndexRequest request = new CreateIndexRequest(indexName);
            //세팅 정보
            request.settings(settingsBuilder);
            //매핑 정보
            request.mapping(typeName, indexBuilder);
            
            //별칭설정
            String aliasName = "chatbotInstance";
            request.alias(new Alias(aliasName));
            
            //인덱스생성
            CreateIndexResponse response = client.indices().create(request, RequestOptions.DEFAULT);
            
            acknowledged = response.isAcknowledged();
            
        }catch (Exception e) {
            e.printStackTrace();
            return "인덱스 생성에 실패하였습니다. - catch";
        }
        
        
        return acknowledged == true ? "인덱스가 생성되었습니다.":"인덱스생성에 실패하였습니다.";
    }
cs

 

More Like This Query

 

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
@RequestMapping("/mlt")
    public Object mlt() {
        String aliasName = "chatbotInstance";
        String typeName = "_doc";
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(QueryBuilders.moreLikeThisQuery
                (new String[] {"question"},new String[]{"사용자 발화 입니다. "},null)
                .minTermFreq(2)
                .minDocFreq(1)
                );
        searchSourceBuilder.from(0);
        searchSourceBuilder.size(5);
        searchSourceBuilder.sort(new FieldSortBuilder("answer").order(SortOrder.DESC));
        
        SearchRequest request = new SearchRequest(aliasName);
        request.types(typeName);
        request.source(searchSourceBuilder);
        
        
        SearchResponse response = null;
        SearchHits searchHits = null;
        List<Answer> resultMap = new ArrayList<>();
        
        try(RestHighLevelClient client = createConnection();){
            response = client.search(request, RequestOptions.DEFAULT);
            searchHits = response.getHits();
            for( SearchHit hit : searchHits) {
                Map<String, Object> sourceAsMap = hit.getSourceAsMap();
                Answer a = new Answer();
                a.setQuestion(sourceAsMap.get("question")+"");
                a.setAnswer(sourceAsMap.get("answer")+"");
                resultMap.add(a);
            }
        }catch (Exception e) {
            e.printStackTrace();
            /*
             * 예외처리
             */
        }
        return resultMap; 
    }
    private class Answer{
        private String question;
        private String answer;
        private String score;
        
        public String getQuestion() {
            return question;
        }
        public void setQuestion(String question) {
            this.question = question;
        }
        public String getAnswer() {
            return answer;
        }
        public void setAnswer(String answer) {
            this.answer = answer;
        }
        public String getScore() {
            return score;
        }
        public void setScore(String score) {
            this.score = score;
        }
    }
cs

 

파라미터 설명

 

Parameters

 

Document Input Parameters

  - like

    작성된 문서 또는 text를 바탕으로 문서를 검색 합니다.

  - unlike

    작성된 문서 또는 text에서 제외 시킬 term을 작성 합니다.

  - fields

    문서에서 analyzed text 를 가져올 필드를 지정 합니다.

    이 필드를 대상으로 질의 수행이 이루어 집니다.

  - like_text

    like 와 더불어 문서를 검색 할떄 추가적으로 사용할 text를 작성 합니다.

  - ids or docs

    @deprecated

 

Term Selection Parameters

  - max_query_terms

    작성된 문서 또는 text에서 추출하여 사용할 최대 query term size 입니다. (default 25)

  - min_term_freq

    작성된 문서 또는 text의 최소 TF 값으로 이 값보다 작을 경우 작성된 문서와 text는 무시 됩니다. (default 2)

  - min_doc_freq

    입력된 개별 term들에 대해서 각각 matching 된 문서의 최소 크기로 해당 크기 보다 작은 term의 결과는 무시 됩니다. (default 5)

  - max_doc_freq

    입력된 개별 term들에 대해서 각각 matching 된 문서의 최대 크기로 해당 크기 보다 큰 term의 결과는 무시 됩니다. (default unbounded 0)

  - min_word_length

    입력된 개별 term들의 최소 길이로 정의한 값보다 작은 term은 무시 됩니다. (default 0)

  - max_word_length

    입력된 개별 term들의 최대 길이로 정의한 값보다 큰 term은 무시 됩니다. (default unbounded 0)

  - stop_words

    불용어 목록을 등록 합니다.

  - analyzer

    입력한 문서와 text에 대한 analyzer 를 지정 합니다. 지정 하지 않을 경우 first field 의 analyzer 를 사용하게 됩니다.

 

Query Formation Parameters

  - minimum_should_match

    작성된 문서 또는 text에서 추출된 term matching 에 대한 minimum_should_match 정보를 구성 합니다. (default 30%)

  - boost_terms

    tems boost value 를 지정 합니다.

  - include

    검색 결과로 입력 문서를 포함 할지 말지를 결정 합니다. (default false)

  - boost

    전체 질의에 대한 boost value 를 지정 합니다. (default 1.0)

 

posted by 여성게
: