'검색'에 해당되는 글 2건
- 2019.05.25 :: Elasticsearch - More Like This Query(High Level Rest Client)
- 2019.01.03 :: Lucene library를 이용한 간단한 색인/검색(루씬 라이브러리이용)
이번에 포스팅할 내용은 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)
'Search-Engine > Elasticsearch&Solr' 카테고리의 다른 글
Elasticsearch - 엘라스틱서치와 루씬의 관계 - 2(세그먼트 불변성,Translog) (1) | 2019.05.25 |
---|---|
Elasticsearch - 엘라스틱서치와 루씬의 관계 - 1 (1) | 2019.05.25 |
Elasticsearch - High Level Rest Client API Doc (0) | 2019.05.25 |
Elasticsearch - 6. Elasticsearch Java Client !(엘라스틱서치 자바 클라이언트,High-Level Rest Client) (2) | 2019.05.25 |
Elasticsearch - 5. 고급 검색(검색결과 하이라이트,검색 템플릿,별칭(Alias ,백업&복구 등) (0) | 2019.05.11 |
Lucene library를 이용한 간단한 색인/검색(루씬 라이브러리이용)
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 | package com.lucene.study; import java.io.File; import java.io.FileFilter; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.IndexWriterConfig; import org.apache.lucene.store.Directory; import org.apache.lucene.store.FSDirectory; import org.apache.lucene.util.Version; //yeoseong_yoon,2019/1/2->txt파일을 색인하는 유틸클래스 public class Indexer { private IndexWriter writer; //루씬의 IndexWriter 생성 public Indexer(String indexDir) throws IOException { Directory dir = FSDirectory.open(new File(indexDir).toPath()); writer = new IndexWriter(dir,new IndexWriterConfig(new StandardAnalyzer())); } public void close() throws IOException { writer.close(); } public int index(String dataDir,FileFilter filter) throws IOException { File[] files = new File(dataDir).listFiles(); for(File f : files) { if(!f.isDirectory() && !f.isHidden() && f.exists() && f.canRead() && (filter == null || filter.accept(f))) { indexFile(f); } } //색인된 문서건수 리턴 return writer.numDocs(); } private void indexFile(File file) throws IOException { System.out.println("Indexing "+file.getCanonicalPath()); Document doc = getDocument(file); writer.addDocument(doc); } @SuppressWarnings("deprecation") protected Document getDocument(File file) throws IOException { Document doc = new Document(); //파일의 내용추가 doc.add(new Field("content", new FileReader(file))); //파일 이름추가 doc.add(new Field("filename",file.getName(),Field.Store.YES,Field.Index.NOT_ANALYZED)); //파일 전체경로 추가 doc.add(new Field("fullpath",file.getCanonicalPath(),Field.Store.YES,Field.Index.NOT_ANALYZED)); return doc; } //FileFilter를 이용하여 해당 확장자만 걸러낸다. private static class TextFilesFilter implements FileFilter{ public boolean accept(File path) { return path.getName().toLowerCase().endsWith(".txt"); } } public static void main(String[] args) throws IOException { // TODO Auto-generated method stub if(args.length!=2) { throw new IllegalArgumentException("please input index dir & data dir"); } String indexDir = args[0]; String dataDir = args[1]; long start = System.currentTimeMillis(); Indexer indexer = new Indexer(indexDir); int numIndexed = 0; try { numIndexed = indexer.index(dataDir, new TextFilesFilter()); }catch (Exception e) { // TODO: handle exception System.out.println(e.getMessage()); }finally { indexer.close(); } long end = System.currentTimeMillis(); System.out.println("Indexing " + numIndexed + " files took " + (end-start) + " milliseconds"); } } | cs |
=>특정 디렉토리에 들어있는 txt파일을 특정 디렉토리 위치에 색인하는 코드이다.(실행시 arguments로 2개의 인자를 전달해야한다.)
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 | package com.lucene.study; import java.io.File; import java.io.IOException; import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.document.Document; import org.apache.lucene.index.DirectoryReader; import org.apache.lucene.index.IndexReader; import org.apache.lucene.queryparser.classic.ParseException; import org.apache.lucene.queryparser.classic.QueryParser; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.Query; import org.apache.lucene.search.ScoreDoc; import org.apache.lucene.search.TopDocs; import org.apache.lucene.store.Directory; import org.apache.lucene.store.FSDirectory; public class Searcher { public static void search(String indexDir, String query) throws IOException, ParseException { Directory dir = FSDirectory.open(new File(indexDir).toPath()); //색인을 연다. IndexSearcher indexSearcher = new IndexSearcher(DirectoryReader.open(dir)); //질의를 분석한다. QueryParser parser = new QueryParser("content", new StandardAnalyzer()); Query q = parser.parse(query); long start = System.currentTimeMillis(); TopDocs hits = indexSearcher.search(q, 10); long end = System.currentTimeMillis(); System.out.println("Found "+ hits.totalHits +" document(s) (in "+(end-start) +" millisecond) that matched query '" + q + "' :"); for(ScoreDoc scoreDoc : hits.scoreDocs) { Document doc = indexSearcher.doc(scoreDoc.doc); System.out.println(String.format("content - %s\nfullpath = %s" ,doc.get("content") ,doc.get("fullpath"))); } } public static void main(String[] args) throws IOException, ParseException { // TODO Auto-generated method stub if(args.length !=2) { throw new IllegalArgumentException("please input index dir & search query String"); } String indexDir = args[0]; String query = args[1]; search(indexDir, query); } } | cs |
=>위에서 색인한 색인 목록에서 검색어에 해당하는 문서를 찾는 코드이다. 이도 동일하게 2개의 아규먼트를 전달해야한다.
2개의 간단한 색인/검색을 예제로 짜보았다. 이 코드를 짜기전에 루씬 라이브러리를 dependency 해야하는 선과정이 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <!-- START - Lucene Dependencies --> <dependency> <groupId>org.apache.lucene</groupId> <artifactId>lucene-core</artifactId> <version>5.3.0</version> </dependency> <dependency> <groupId>org.apache.lucene</groupId> <artifactId>lucene-analyzers-common</artifactId> <version>5.3.0</version> </dependency> <dependency> <groupId>org.apache.lucene</groupId> <artifactId>lucene-queryparser</artifactId> <version>5.3.0</version> </dependency> <!-- END - Lucene Dependencies --> | cs |
'Search-Engine > Lucene' 카테고리의 다른 글
Lucene - 인메모리버퍼(In-Memory-Buffer) 역할, 세그먼트 병합(Merge) (0) | 2019.05.25 |
---|---|
Lucene - 유사어,동의어 필터(SynonymFilter)를 이용한 커스텀 Analyzer (0) | 2019.02.02 |
Lucene - 분석기(Analyzer)로 분석한 토큰(Token)결과 출력 (0) | 2019.01.29 |