Class MoreLikeThis


  • public final class MoreLikeThis
    extends java.lang.Object
    Generate "more like this" similarity queries. Based on this mail:
    
     Lucene does let you access the document frequency of terms, with IndexReader.docFreq().
     Term frequencies can be computed by re-tokenizing the text, which, for a single document,
     is usually fast enough.  But looking up the docFreq() of every term in the document is
     probably too slow.
    
     You can use some heuristics to prune the set of terms, to avoid calling docFreq() too much,
     or at all.  Since you're trying to maximize a tf*idf score, you're probably most interested
     in terms with a high tf. Choosing a tf threshold even as low as two or three will radically
     reduce the number of terms under consideration.  Another heuristic is that terms with a
     high idf (i.e., a low df) tend to be longer.  So you could threshold the terms by the
     number of characters, not selecting anything less than, e.g., six or seven characters.
     With these sorts of heuristics you can usually find small set of, e.g., ten or fewer terms
     that do a pretty good job of characterizing a document.
    
     It all depends on what you're trying to do.  If you're trying to eek out that last percent
     of precision and recall regardless of computational difficulty so that you can win a TREC
     competition, then the techniques I mention above are useless.  But if you're trying to
     provide a "more like this" button on a search results page that does a decent job and has
     good performance, such techniques might be useful.
    
     An efficient, effective "more-like-this" query generator would be a great contribution, if
     anyone's interested.  I'd imagine that it would take a Reader or a String (the document's
     text), analyzer Analyzer, and return a set of representative terms using heuristics like those
     above.  The frequency and length thresholds could be parameters, etc.
    
     Doug
     

    Initial Usage

    This class has lots of options to try to make it efficient and flexible. The simplest possible usage is as follows. The bold fragment is specific to this class.

     IndexReader ir = ...
     IndexSearcher is = ...
    
     MoreLikeThis mlt = new MoreLikeThis(ir);
     Reader target = ... // orig source of doc you want to find similarities to
     Query query = mlt.like( target);
    
     Hits hits = is.search(query);
     // now the usual iteration thru 'hits' - the only thing to watch for is to make sure
     //you ignore the doc if it matches your 'target' document, as it should be similar to itself
    
     

    Thus you:

    1. do your normal, Lucene setup for searching,
    2. create a MoreLikeThis,
    3. get the text of the doc you want to find similarities to
    4. then call one of the like() calls to generate a similarity query
    5. call the searcher to find the similar docs

    More Advanced Usage

    You may want to use setFieldNames(...) so you can examine multiple fields (e.g. body and title) for similarity.

    Depending on the size of your index and the size and makeup of your documents you may want to call the other set methods to control how the similarity queries are generated:



     Changes: Mark Harwood 29/02/04
     Some bugfixing, some refactoring, some optimisation.
     - bugfix: retrieveTerms(int docNum) was not working for indexes without a termvector -added missing code
     - bugfix: No significant terms being created for fields with a termvector - because
     was only counting one occurrence per term/field pair in calculations(ie not including frequency info from TermVector)
     - refactor: moved common code into isNoiseWord()
     - optimise: when no termvector support available - used maxNumTermsParsed to limit amount of tokenization
     
    • Nested Class Summary

      Nested Classes 
      Modifier and Type Class Description
      private static class  MoreLikeThis.FreqQ
      PriorityQueue that orders words by score.
      private static class  MoreLikeThis.Int
      Use for frequencies and to avoid renewing Integers.
      private static class  MoreLikeThis.ScoreTerm  
    • Field Summary

      Fields 
      Modifier and Type Field Description
      private Analyzer analyzer
      Analyzer that will be used to parse the doc.
      private boolean boost
      Should we apply a boost to the Query based on the scores?
      private float boostFactor
      Boost factor to use when boosting the terms
      static boolean DEFAULT_BOOST
      Boost terms in query based on score.
      static java.lang.String[] DEFAULT_FIELD_NAMES
      Default field names.
      static int DEFAULT_MAX_DOC_FREQ
      Ignore words which occur in more than this many docs.
      static int DEFAULT_MAX_NUM_TOKENS_PARSED
      Default maximum number of tokens to parse in each example doc field that is not stored with TermVector support.
      static int DEFAULT_MAX_QUERY_TERMS
      Return a Query with no more than this many terms.
      static int DEFAULT_MAX_WORD_LENGTH
      Ignore words greater than this length or if 0 then this has no effect.
      static int DEFAULT_MIN_DOC_FREQ
      Ignore words which do not occur in at least this many docs.
      static int DEFAULT_MIN_TERM_FREQ
      Ignore terms with less than this frequency in the source doc.
      static int DEFAULT_MIN_WORD_LENGTH
      Ignore words less than this length or if 0 then this has no effect.
      static java.util.Set<?> DEFAULT_STOP_WORDS
      Default set of stopwords.
      private java.lang.String[] fieldNames
      Field name we'll analyze.
      private IndexReader ir
      IndexReader to use
      private int maxDocFreq
      Ignore words which occur in more than this many docs.
      private int maxNumTokensParsed
      The maximum number of tokens to parse in each example doc field that is not stored with TermVector support
      private int maxQueryTerms
      Don't return a query longer than this.
      private int maxWordLen
      Ignore words if greater than this len.
      private int minDocFreq
      Ignore words which do not occur in at least this many docs.
      private int minTermFreq
      Ignore words less frequent that this.
      private int minWordLen
      Ignore words if less than this len.
      private TFIDFSimilarity similarity
      For idf() calculations.
      private java.util.Set<?> stopWords
      Current set of stop words.
    • Method Summary

      All Methods Instance Methods Concrete Methods 
      Modifier and Type Method Description
      private void addTermFrequencies​(java.io.Reader r, java.util.Map<java.lang.String,​java.util.Map<java.lang.String,​MoreLikeThis.Int>> perFieldTermFrequencies, java.lang.String fieldName)
      Adds term frequencies found by tokenizing text from reader into the Map words
      private void addTermFrequencies​(java.util.Map<java.lang.String,​java.util.Map<java.lang.String,​MoreLikeThis.Int>> field2termFreqMap, Terms vector, java.lang.String fieldName)
      Adds terms and frequencies found in vector into the Map termFreqMap
      private Query createQuery​(PriorityQueue<MoreLikeThis.ScoreTerm> q)
      Create the More like query from a PriorityQueue
      private PriorityQueue<MoreLikeThis.ScoreTerm> createQueue​(java.util.Map<java.lang.String,​java.util.Map<java.lang.String,​MoreLikeThis.Int>> perFieldTermFrequencies)
      Create a PriorityQueue from a word->tf map.
      java.lang.String describeParams()
      Describe the parameters that control how the "more like this" query is formed.
      Analyzer getAnalyzer()
      Returns an analyzer that will be used to parse source doc with.
      float getBoostFactor()
      Returns the boost factor used when boosting terms
      java.lang.String[] getFieldNames()
      Returns the field names that will be used when generating the 'More Like This' query.
      int getMaxDocFreq()
      Returns the maximum frequency in which words may still appear.
      int getMaxNumTokensParsed()  
      int getMaxQueryTerms()
      Returns the maximum number of query terms that will be included in any generated query.
      int getMaxWordLen()
      Returns the maximum word length above which words will be ignored.
      int getMinDocFreq()
      Returns the frequency at which words will be ignored which do not occur in at least this many docs.
      int getMinTermFreq()
      Returns the frequency below which terms will be ignored in the source doc.
      int getMinWordLen()
      Returns the minimum word length below which words will be ignored.
      TFIDFSimilarity getSimilarity()  
      java.util.Set<?> getStopWords()
      Get the current stop words being used.
      private int getTermsCount​(java.util.Map<java.lang.String,​java.util.Map<java.lang.String,​MoreLikeThis.Int>> perFieldTermFrequencies)  
      boolean isBoost()
      Returns whether to boost terms in query based on "score" or not.
      private boolean isNoiseWord​(java.lang.String term)
      determines if the passed term is likely to be of interest in "more like" comparisons
      Query like​(int docNum)
      Return a query that will return docs like the passed lucene document ID.
      Query like​(java.lang.String fieldName, java.io.Reader... readers)
      Return a query that will return docs like the passed Readers.
      Query like​(java.util.Map<java.lang.String,​java.util.Collection<java.lang.Object>> filteredDocument)  
      java.lang.String[] retrieveInterestingTerms​(int docNum)  
      java.lang.String[] retrieveInterestingTerms​(java.io.Reader r, java.lang.String fieldName)
      Convenience routine to make it easy to return the most interesting words in a document.
      private PriorityQueue<MoreLikeThis.ScoreTerm> retrieveTerms​(int docNum)
      Find words for a more-like-this query former.
      private PriorityQueue<MoreLikeThis.ScoreTerm> retrieveTerms​(java.io.Reader r, java.lang.String fieldName)
      Find words for a more-like-this query former.
      private PriorityQueue<MoreLikeThis.ScoreTerm> retrieveTerms​(java.util.Map<java.lang.String,​java.util.Collection<java.lang.Object>> field2fieldValues)  
      void setAnalyzer​(Analyzer analyzer)
      Sets the analyzer to use.
      void setBoost​(boolean boost)
      Sets whether to boost terms in query based on "score" or not.
      void setBoostFactor​(float boostFactor)
      Sets the boost factor to use when boosting terms
      void setFieldNames​(java.lang.String[] fieldNames)
      Sets the field names that will be used when generating the 'More Like This' query.
      void setMaxDocFreq​(int maxFreq)
      Set the maximum frequency in which words may still appear.
      void setMaxDocFreqPct​(int maxPercentage)
      Set the maximum percentage in which words may still appear.
      void setMaxNumTokensParsed​(int i)  
      void setMaxQueryTerms​(int maxQueryTerms)
      Sets the maximum number of query terms that will be included in any generated query.
      void setMaxWordLen​(int maxWordLen)
      Sets the maximum word length above which words will be ignored.
      void setMinDocFreq​(int minDocFreq)
      Sets the frequency at which words will be ignored which do not occur in at least this many docs.
      void setMinTermFreq​(int minTermFreq)
      Sets the frequency below which terms will be ignored in the source doc.
      void setMinWordLen​(int minWordLen)
      Sets the minimum word length below which words will be ignored.
      void setSimilarity​(TFIDFSimilarity similarity)  
      void setStopWords​(java.util.Set<?> stopWords)
      Set the set of stopwords.
      • Methods inherited from class java.lang.Object

        clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
    • Field Detail

      • DEFAULT_MAX_NUM_TOKENS_PARSED

        public static final int DEFAULT_MAX_NUM_TOKENS_PARSED
        Default maximum number of tokens to parse in each example doc field that is not stored with TermVector support.
        See Also:
        getMaxNumTokensParsed(), Constant Field Values
      • DEFAULT_FIELD_NAMES

        public static final java.lang.String[] DEFAULT_FIELD_NAMES
        Default field names. Null is used to specify that the field names should be looked up at runtime from the provided reader.
      • stopWords

        private java.util.Set<?> stopWords
        Current set of stop words.
      • analyzer

        private Analyzer analyzer
        Analyzer that will be used to parse the doc.
      • minTermFreq

        private int minTermFreq
        Ignore words less frequent that this.
      • minDocFreq

        private int minDocFreq
        Ignore words which do not occur in at least this many docs.
      • maxDocFreq

        private int maxDocFreq
        Ignore words which occur in more than this many docs.
      • boost

        private boolean boost
        Should we apply a boost to the Query based on the scores?
      • fieldNames

        private java.lang.String[] fieldNames
        Field name we'll analyze.
      • maxNumTokensParsed

        private int maxNumTokensParsed
        The maximum number of tokens to parse in each example doc field that is not stored with TermVector support
      • minWordLen

        private int minWordLen
        Ignore words if less than this len.
      • maxWordLen

        private int maxWordLen
        Ignore words if greater than this len.
      • maxQueryTerms

        private int maxQueryTerms
        Don't return a query longer than this.
      • boostFactor

        private float boostFactor
        Boost factor to use when boosting the terms
    • Constructor Detail

      • MoreLikeThis

        public MoreLikeThis​(IndexReader ir)
                     throws java.io.IOException
        Constructor requiring an IndexReader.
        Throws:
        java.io.IOException
      • MoreLikeThis

        public MoreLikeThis​(IndexReader ir,
                            TFIDFSimilarity sim)
                     throws java.io.IOException
        Throws:
        java.io.IOException
    • Method Detail

      • getBoostFactor

        public float getBoostFactor()
        Returns the boost factor used when boosting terms
        Returns:
        the boost factor used when boosting terms
        See Also:
        setBoostFactor(float)
      • setBoostFactor

        public void setBoostFactor​(float boostFactor)
        Sets the boost factor to use when boosting terms
        See Also:
        getBoostFactor()
      • setSimilarity

        public void setSimilarity​(TFIDFSimilarity similarity)
      • getAnalyzer

        public Analyzer getAnalyzer()
        Returns an analyzer that will be used to parse source doc with. The default analyzer is not set.
        Returns:
        the analyzer that will be used to parse source doc with.
      • setAnalyzer

        public void setAnalyzer​(Analyzer analyzer)
        Sets the analyzer to use. An analyzer is not required for generating a query with the like(int) method, all other 'like' methods require an analyzer.
        Parameters:
        analyzer - the analyzer to use to tokenize text.
      • getMinTermFreq

        public int getMinTermFreq()
        Returns the frequency below which terms will be ignored in the source doc. The default frequency is the DEFAULT_MIN_TERM_FREQ.
        Returns:
        the frequency below which terms will be ignored in the source doc.
      • setMinTermFreq

        public void setMinTermFreq​(int minTermFreq)
        Sets the frequency below which terms will be ignored in the source doc.
        Parameters:
        minTermFreq - the frequency below which terms will be ignored in the source doc.
      • getMinDocFreq

        public int getMinDocFreq()
        Returns the frequency at which words will be ignored which do not occur in at least this many docs. The default frequency is DEFAULT_MIN_DOC_FREQ.
        Returns:
        the frequency at which words will be ignored which do not occur in at least this many docs.
      • setMinDocFreq

        public void setMinDocFreq​(int minDocFreq)
        Sets the frequency at which words will be ignored which do not occur in at least this many docs.
        Parameters:
        minDocFreq - the frequency at which words will be ignored which do not occur in at least this many docs.
      • getMaxDocFreq

        public int getMaxDocFreq()
        Returns the maximum frequency in which words may still appear. Words that appear in more than this many docs will be ignored. The default frequency is DEFAULT_MAX_DOC_FREQ.
        Returns:
        get the maximum frequency at which words are still allowed, words which occur in more docs than this are ignored.
      • setMaxDocFreq

        public void setMaxDocFreq​(int maxFreq)
        Set the maximum frequency in which words may still appear. Words that appear in more than this many docs will be ignored.
        Parameters:
        maxFreq - the maximum count of documents that a term may appear in to be still considered relevant
      • setMaxDocFreqPct

        public void setMaxDocFreqPct​(int maxPercentage)
        Set the maximum percentage in which words may still appear. Words that appear in more than this many percent of all docs will be ignored.

        This method calls setMaxDocFreq(int) internally (both conditions cannot be used at the same time).

        Parameters:
        maxPercentage - the maximum percentage of documents (0-100) that a term may appear in to be still considered relevant.
      • isBoost

        public boolean isBoost()
        Returns whether to boost terms in query based on "score" or not. The default is DEFAULT_BOOST.
        Returns:
        whether to boost terms in query based on "score" or not.
        See Also:
        setBoost(boolean)
      • setBoost

        public void setBoost​(boolean boost)
        Sets whether to boost terms in query based on "score" or not.
        Parameters:
        boost - true to boost terms in query based on "score", false otherwise.
        See Also:
        isBoost()
      • getFieldNames

        public java.lang.String[] getFieldNames()
        Returns the field names that will be used when generating the 'More Like This' query. The default field names that will be used is DEFAULT_FIELD_NAMES.
        Returns:
        the field names that will be used when generating the 'More Like This' query.
      • setFieldNames

        public void setFieldNames​(java.lang.String[] fieldNames)
        Sets the field names that will be used when generating the 'More Like This' query. Set this to null for the field names to be determined at runtime from the IndexReader provided in the constructor.
        Parameters:
        fieldNames - the field names that will be used when generating the 'More Like This' query.
      • getMinWordLen

        public int getMinWordLen()
        Returns the minimum word length below which words will be ignored. Set this to 0 for no minimum word length. The default is DEFAULT_MIN_WORD_LENGTH.
        Returns:
        the minimum word length below which words will be ignored.
      • setMinWordLen

        public void setMinWordLen​(int minWordLen)
        Sets the minimum word length below which words will be ignored.
        Parameters:
        minWordLen - the minimum word length below which words will be ignored.
      • getMaxWordLen

        public int getMaxWordLen()
        Returns the maximum word length above which words will be ignored. Set this to 0 for no maximum word length. The default is DEFAULT_MAX_WORD_LENGTH.
        Returns:
        the maximum word length above which words will be ignored.
      • setMaxWordLen

        public void setMaxWordLen​(int maxWordLen)
        Sets the maximum word length above which words will be ignored.
        Parameters:
        maxWordLen - the maximum word length above which words will be ignored.
      • setStopWords

        public void setStopWords​(java.util.Set<?> stopWords)
        Set the set of stopwords. Any word in this set is considered "uninteresting" and ignored. Even if your Analyzer allows stopwords, you might want to tell the MoreLikeThis code to ignore them, as for the purposes of document similarity it seems reasonable to assume that "a stop word is never interesting".
        Parameters:
        stopWords - set of stopwords, if null it means to allow stop words
        See Also:
        getStopWords()
      • getMaxQueryTerms

        public int getMaxQueryTerms()
        Returns the maximum number of query terms that will be included in any generated query. The default is DEFAULT_MAX_QUERY_TERMS.
        Returns:
        the maximum number of query terms that will be included in any generated query.
      • setMaxQueryTerms

        public void setMaxQueryTerms​(int maxQueryTerms)
        Sets the maximum number of query terms that will be included in any generated query.
        Parameters:
        maxQueryTerms - the maximum number of query terms that will be included in any generated query.
      • getMaxNumTokensParsed

        public int getMaxNumTokensParsed()
        Returns:
        The maximum number of tokens to parse in each example doc field that is not stored with TermVector support
        See Also:
        DEFAULT_MAX_NUM_TOKENS_PARSED
      • setMaxNumTokensParsed

        public void setMaxNumTokensParsed​(int i)
        Parameters:
        i - The maximum number of tokens to parse in each example doc field that is not stored with TermVector support
      • like

        public Query like​(int docNum)
                   throws java.io.IOException
        Return a query that will return docs like the passed lucene document ID.
        Parameters:
        docNum - the documentID of the lucene doc to generate the 'More Like This" query for.
        Returns:
        a query that will return docs like the passed lucene document ID.
        Throws:
        java.io.IOException
      • like

        public Query like​(java.util.Map<java.lang.String,​java.util.Collection<java.lang.Object>> filteredDocument)
                   throws java.io.IOException
        Parameters:
        filteredDocument - Document with field values extracted for selected fields.
        Returns:
        More Like This query for the passed document.
        Throws:
        java.io.IOException
      • like

        public Query like​(java.lang.String fieldName,
                          java.io.Reader... readers)
                   throws java.io.IOException
        Return a query that will return docs like the passed Readers. This was added in order to treat multi-value fields.
        Returns:
        a query that will return docs like the passed Readers.
        Throws:
        java.io.IOException
      • createQueue

        private PriorityQueue<MoreLikeThis.ScoreTerm> createQueue​(java.util.Map<java.lang.String,​java.util.Map<java.lang.String,​MoreLikeThis.Int>> perFieldTermFrequencies)
                                                           throws java.io.IOException
        Create a PriorityQueue from a word->tf map.
        Parameters:
        perFieldTermFrequencies - a per field map of words keyed on the word(String) with Int objects as the values.
        Throws:
        java.io.IOException
      • getTermsCount

        private int getTermsCount​(java.util.Map<java.lang.String,​java.util.Map<java.lang.String,​MoreLikeThis.Int>> perFieldTermFrequencies)
      • describeParams

        public java.lang.String describeParams()
        Describe the parameters that control how the "more like this" query is formed.
      • retrieveTerms

        private PriorityQueue<MoreLikeThis.ScoreTerm> retrieveTerms​(int docNum)
                                                             throws java.io.IOException
        Find words for a more-like-this query former.
        Parameters:
        docNum - the id of the lucene document from which to find terms
        Throws:
        java.io.IOException
      • retrieveTerms

        private PriorityQueue<MoreLikeThis.ScoreTerm> retrieveTerms​(java.util.Map<java.lang.String,​java.util.Collection<java.lang.Object>> field2fieldValues)
                                                             throws java.io.IOException
        Throws:
        java.io.IOException
      • addTermFrequencies

        private void addTermFrequencies​(java.util.Map<java.lang.String,​java.util.Map<java.lang.String,​MoreLikeThis.Int>> field2termFreqMap,
                                        Terms vector,
                                        java.lang.String fieldName)
                                 throws java.io.IOException
        Adds terms and frequencies found in vector into the Map termFreqMap
        Parameters:
        field2termFreqMap - a Map of terms and their frequencies per field
        vector - List of terms and their frequencies for a doc/field
        Throws:
        java.io.IOException
      • addTermFrequencies

        private void addTermFrequencies​(java.io.Reader r,
                                        java.util.Map<java.lang.String,​java.util.Map<java.lang.String,​MoreLikeThis.Int>> perFieldTermFrequencies,
                                        java.lang.String fieldName)
                                 throws java.io.IOException
        Adds term frequencies found by tokenizing text from reader into the Map words
        Parameters:
        r - a source of text to be tokenized
        perFieldTermFrequencies - a Map of terms and their frequencies per field
        fieldName - Used by analyzer for any special per-field analysis
        Throws:
        java.io.IOException
      • isNoiseWord

        private boolean isNoiseWord​(java.lang.String term)
        determines if the passed term is likely to be of interest in "more like" comparisons
        Parameters:
        term - The word being considered
        Returns:
        true if should be ignored, false if should be used in further analysis
      • retrieveTerms

        private PriorityQueue<MoreLikeThis.ScoreTerm> retrieveTerms​(java.io.Reader r,
                                                                    java.lang.String fieldName)
                                                             throws java.io.IOException
        Find words for a more-like-this query former. The result is a priority queue of arrays with one entry for every word in the document. Each array has 6 elements. The elements are:
        1. The word (String)
        2. The top field that this word comes from (String)
        3. The score for this word (Float)
        4. The IDF value (Float)
        5. The frequency of this word in the index (Integer)
        6. The frequency of this word in the source document (Integer)
        This is a somewhat "advanced" routine, and in general only the 1st entry in the array is of interest. This method is exposed so that you can identify the "interesting words" in a document. For an easier method to call see retrieveInterestingTerms().
        Parameters:
        r - the reader that has the content of the document
        fieldName - field passed to the analyzer to use when analyzing the content
        Returns:
        the most interesting words in the document ordered by score, with the highest scoring, or best entry, first
        Throws:
        java.io.IOException
        See Also:
        retrieveInterestingTerms(int)
      • retrieveInterestingTerms

        public java.lang.String[] retrieveInterestingTerms​(java.io.Reader r,
                                                           java.lang.String fieldName)
                                                    throws java.io.IOException
        Convenience routine to make it easy to return the most interesting words in a document. More advanced users will call retrieveTerms() directly.
        Parameters:
        r - the source document
        fieldName - field passed to analyzer to use when analyzing the content
        Returns:
        the most interesting words in the document
        Throws:
        java.io.IOException
        See Also:
        retrieveTerms(java.io.Reader, String), setMaxQueryTerms(int)