【问题标题】:Lucene Query (with shingles ? )Lucene 查询(带带状疱疹?)
【发布时间】:2012-01-06 08:13:50
【问题描述】:

我有一个包含以下文档的 Lucene 索引:

_id     |           Name            |        Alternate Names      |    Population

123       Bosc de Planavilla               (some names here in          5000
345       Planavilla                       other languages)             20000
456       Bosc de la Planassa                                           1000
567       Bosc de Plana en Blanca                                       100000

什么是我应该使用的最佳 Lucene 查询类型,考虑到我需要以下内容,我应该如何构建它:

  1. 如果用户查询: “Bosc de Planavilla 附近的意大利餐厅” 我希望返回 id 为 123 的文档,因为它包含与文档名称完全匹配的内容。

  2. 如果用户查询: “Planavilla 附近的意大利餐厅” 我想要 id 为 345 的文档,因为查询包含完全匹配且人口最多。

  3. 如果用户查询“Bosc 附近的意大利餐厅” 我想要 567,因为查询包含“Bosc”,并且在 3 个“Bosc”中它的流行度最高。

可能还有许多其他用例...但您会感觉到我需要什么...

什么样的查询会对我产生这种影响? 我是否应该生成单词 N 克(带状疱疹)并使用带状疱疹创建 ORed 布尔查询然后应用自定义评分?还是常规短语查询会做?我也看到了 DisjunctionMaxQuery 但不知道它是不是我在找什么......

正如您现在可能已经理解的那样,这个想法是找到用户在其查询中暗示的确切位置。从那我可以开始我的地理搜索并围绕它添加一些进一步的查询。

最好的方法是什么?

提前致谢。

【问题讨论】:

    标签: lucene


    【解决方案1】:

    这里也是排序的代码。尽管我认为考虑到城市规模而不是强制对人口进行排序,添加自定义评分会更有意义。另请注意,这使用了 FieldCache,这可能不是关于内存使用的最佳解决方案。

    public class ShingleFilterTests {
        private Analyzer analyzer;
        private IndexSearcher searcher;
        private IndexReader reader;
        private QueryParser qp;
        private Sort sort;
    
        public static Analyzer createAnalyzer(final int shingles) {
            return new Analyzer() {
                @Override
                public TokenStream tokenStream(String fieldName, Reader reader) {
                    TokenStream tokenizer = new WhitespaceTokenizer(reader);
                    tokenizer = new StopFilter(false, tokenizer, ImmutableSet.of("de", "la", "en"));
                    if (shingles > 0) {
                        tokenizer = new ShingleFilter(tokenizer, shingles);
                    }
                    return tokenizer;
                }
            };
        }
    
        public class PopulationComparatorSource extends FieldComparatorSource {
            @Override
            public FieldComparator newComparator(String fieldname, int numHits, int sortPos, boolean reversed) throws IOException {
                return new PopulationComparator(fieldname, numHits);
            }
    
            private class PopulationComparator extends FieldComparator {
                private final String fieldName;
                private Integer[] values;
                private int[] populations;
                private int bottom;
    
                public PopulationComparator(String fieldname, int numHits) {
                    values = new Integer[numHits];
                    this.fieldName = fieldname;
                }
    
                @Override
                public int compare(int slot1, int slot2) {
                    if (values[slot1] > values[slot2]) return -1;
                    if (values[slot1] < values[slot2]) return 1;
                    return 0;
                }
    
                @Override
                public void setBottom(int slot) {
                    bottom = values[slot];
                }
    
                @Override
                public int compareBottom(int doc) throws IOException {
                    int value = populations[doc];
                    if (bottom > value) return -1;
                    if (bottom < value) return 1;
                    return 0;
                }
    
                @Override
                public void copy(int slot, int doc) throws IOException {
                    values[slot] = populations[doc];
                }
    
                @Override
                public void setNextReader(IndexReader reader, int docBase) throws IOException {
                    /* XXX uses field cache */
                    populations = FieldCache.DEFAULT.getInts(reader, "population");
                }
    
                @Override
                public Comparable value(int slot) {
                    return values[slot];
                }
            }
        }
    
        @Before
        public void setUp() throws Exception {
            Directory dir = new RAMDirectory();
            analyzer = createAnalyzer(3);
    
            IndexWriter writer = new IndexWriter(dir, analyzer, IndexWriter.MaxFieldLength.UNLIMITED);
            ImmutableList<String> cities = ImmutableList.of("Bosc de Planavilla", "Planavilla", "Bosc de la Planassa",
                                                                   "Bosc de Plana en Blanca");
            ImmutableList<Integer> populations = ImmutableList.of(5000, 20000, 1000, 100000);
    
            for (int id = 0; id < cities.size(); id++) {
                Document doc = new Document();
                doc.add(new Field("id", String.valueOf(id), Field.Store.YES, Field.Index.NOT_ANALYZED));
                doc.add(new Field("city", cities.get(id), Field.Store.YES, Field.Index.ANALYZED));
                doc.add(new Field("population", String.valueOf(populations.get(id)),
                                         Field.Store.YES, Field.Index.NOT_ANALYZED));
                writer.addDocument(doc);
            }
            writer.close();
    
            qp = new QueryParser(Version.LUCENE_30, "city", createAnalyzer(0));
            sort = new Sort(new SortField("population", new PopulationComparatorSource()));
            searcher = new IndexSearcher(dir);
            searcher.setDefaultFieldSortScoring(true, true);
            reader = searcher.getIndexReader();
        }
    
        @After
        public void tearDown() throws Exception {
            searcher.close();
        }
    
        @Test
        public void testShingleFilter() throws Exception {
            System.out.println("shingle filter");
    
            printSearch("city:\"Bosc de Planavilla\"");
            printSearch("city:Planavilla");
            printSearch("city:Bosc");
        }
    
        private void printSearch(String query) throws ParseException, IOException {
            Query q = qp.parse(query);
            System.out.println("query " + q);
            TopDocs hits = searcher.search(q, null, 4, sort);
            System.out.println("results " + hits.totalHits);
            int i = 1;
            for (ScoreDoc dc : hits.scoreDocs) {
                Document doc = reader.document(dc.doc);
                System.out.println(i++ + ". " + dc + " \"" + doc.get("city") + "\" population: " + doc.get("population"));
            }
            System.out.println();
        }
    }
    

    这给出了以下结果:

    query city:"Bosc Planavilla"
    results 1
    1. doc=0 score=1.143841[5000] "Bosc de Planavilla" population: 5000
    
    query city:Planavilla
    results 2
    1. doc=1 score=1.287682[20000] "Planavilla" population: 20000
    2. doc=0 score=0.643841[5000] "Bosc de Planavilla" population: 5000
    
    query city:Bosc
    results 3
    1. doc=3 score=0.375[100000] "Bosc de Plana en Blanca" population: 100000
    2. doc=0 score=0.5[5000] "Bosc de Planavilla" population: 5000
    3. doc=2 score=0.5[1000] "Bosc de la Planassa" population: 1000
    

    【讨论】:

    • 非常感谢!您的方法与我最终采用的方法相似,并且效果很好。但它并不完美......在 300 万个文档索引上,我得到的响应时间高达 1 秒(在单台机器上)。此外,我经常遇到一些奇怪的事情,例如在搜索“Indian Bar Paris”时返回的“Rich Bar Indian Reserve”并不是真正的意图:)。如果可能的话,我将尝试使用评分和索引时间提升来进一步完善这一点,具体取决于特征类型。感谢您的热心帮助!
    • 300 万份文档的 1 秒声音听起来太多了。你怎么排序?您可以使用分析器来检查 CPU 的去向。我在大约 70 毫秒内搜索了 4000 万个包含复杂查询、分面和自定义排序的文档索引。
    【解决方案2】:

    如何标记字段?您是否将它们存储为完整的字符串?另外,如何解析查询?

    好的,所以我正在玩这个。我一直在使用 StopFilter 来删除 la、en、de。然后,我使用 shingle 过滤器来获得多个组合,以便进行“精确匹配”。例如,Bosc de Planavilla 被标记为 [Bosc] [Bosc Planavilla],Bosc de Plana en Blanca 被标记为 [Bosc] [Bosc Plana] [Plana Blanca] [Bosc Plana Blanca]。这样您就可以对部分查询进行“完全匹配”。

    然后我查询用户传递的确切字符串,尽管那里也可能有一些调整。我选择了简单的案例,以使结果更符合您的要求。

    这是我正在使用的代码(lucene 3.0.3):

    public class ShingleFilterTests {
        private Analyzer analyzer;
        private IndexSearcher searcher;
        private IndexReader reader;
    
        public static Analyzer createAnalyzer(final int shingles) {
            return new Analyzer() {
                @Override
                public TokenStream tokenStream(String fieldName, Reader reader) {
                    TokenStream tokenizer = new WhitespaceTokenizer(reader);
                    tokenizer = new StopFilter(false, tokenizer, ImmutableSet.of("de", "la", "en"));
                    if (shingles > 0) {
                        tokenizer = new ShingleFilter(tokenizer, shingles);
                    }
                    return tokenizer;
                }
            };
        }
    
        @Before
        public void setUp() throws Exception {
            Directory dir = new RAMDirectory();
            analyzer = createAnalyzer(3);
    
            IndexWriter writer = new IndexWriter(dir, analyzer, IndexWriter.MaxFieldLength.UNLIMITED);
            ImmutableList<String> cities = ImmutableList.of("Bosc de Planavilla", "Planavilla", "Bosc de la Planassa",
                                                                   "Bosc de Plana en Blanca");
            ImmutableList<Integer> populations = ImmutableList.of(5000, 20000, 1000, 100000);
    
            for (int id = 0; id < cities.size(); id++) {
                Document doc = new Document();
                doc.add(new Field("id", String.valueOf(id), Field.Store.YES, Field.Index.NOT_ANALYZED));
                doc.add(new Field("city", cities.get(id), Field.Store.YES, Field.Index.ANALYZED));
                doc.add(new Field("population", String.valueOf(populations.get(id)),
                                         Field.Store.YES, Field.Index.NOT_ANALYZED));
                writer.addDocument(doc);
            }
            writer.close();
    
            searcher = new IndexSearcher(dir);
            reader = searcher.getIndexReader();
        }
    
        @After
        public void tearDown() throws Exception {
            searcher.close();
        }
    
        @Test
        public void testShingleFilter() throws Exception {
            System.out.println("shingle filter");
    
            QueryParser qp = new QueryParser(Version.LUCENE_30, "city", createAnalyzer(0));
    
            printSearch(qp, "city:\"Bosc de Planavilla\"");
            printSearch(qp, "city:Planavilla");
            printSearch(qp, "city:Bosc");
        }
    
        private void printSearch(QueryParser qp, String query) throws ParseException, IOException {
            Query q = qp.parse(query);
    
            System.out.println("query " + q);
            TopDocs hits = searcher.search(q, 4);
            System.out.println("results " + hits.totalHits);
            int i = 1;
            for (ScoreDoc dc : hits.scoreDocs) {
                Document doc = reader.document(dc.doc);
                System.out.println(i++ + ". " + dc + " \"" + doc.get("city") + "\" population: " + doc.get("population"));
            }
            System.out.println();
        }
    }
    

    我现在正在研究按人口分类。

    打印出来:

    query city:"Bosc Planavilla"
    results 1
    1. doc=0 score=1.143841 "Bosc de Planavilla" population: 5000
    
    query city:Planavilla
    results 2
    1. doc=1 score=1.287682 "Planavilla" population: 20000
    2. doc=0 score=0.643841 "Bosc de Planavilla" population: 5000
    
    query city:Bosc
    results 3
    1. doc=0 score=0.5 "Bosc de Planavilla" population: 5000
    2. doc=2 score=0.5 "Bosc de la Planassa" population: 1000
    3. doc=3 score=0.375 "Bosc de Plana en Blanca" population: 100000
    

    【讨论】:

    • 感谢您的回复。实际上,名称字段是使用标准标记器索引的,具有标准标记过滤器、小写标记过滤器和停止标记过滤器。但这很容易改变。我的问题实际上也是我应该如何索引和查询解析?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-13
    • 2015-02-09
    • 2019-01-04
    • 1970-01-01
    • 2017-07-06
    • 1970-01-01
    相关资源
    最近更新 更多