2012年11月8日 星期四

[ InAction Note ] Ch3. Adding search - Understanding Lucene scoring


Preface:
Every time a document matches during search, it’s assigned a score that reflects how good the match is. This score computes how similar the document is to the query, with higher scores reflecting stronger similarity and thus stronger matches. We chose to discuss this complex topic early in this chapter so you’ll have a general sense of the various factors that go into Lucene scoring as you continue to read. We’ll start with details on Lucene’s scoring formula, and then show how you can see the full explanation of how a certain document arrived at its score.

How Lucene scores:
Without further ado, meet Lucene’s similarity scoring formula, shown in figure 3.3. It’s called the similarity scoring formula because its purpose is to measure the similarity between a query and each document that matches the query. The score is computed for each document (d) matching each term (t) in a query (q):


This score is the raw score, which is a floating-point number >= 0.0. Typically, if an application presents the score to the end user, it’s best to first normalize the scores by dividing all scores by the maximum score for the query. The larger the similarity score, the better the match of the document to the query. By default Lucene returns documents reverse-sorted by this score, meaning the top documents are the best matching ones. Table 3.5 describes each of the factors in the scoring formula.


Boost factors are built into the equation to let you affect a query or field’s influence on score. Field boosts come in explicitly in the equation as the boost(t.field in d)factor, set at indexing time. The default value of field boosts, logically, is 1.0. During indexing, a document can be assigned a boost, too. A document boost factor implicitly sets the starting field boost of all fields to the specified value. Field-specific boosts are multiplied by the starting value, giving the final value of the field boost factor. It’s possible to add the same named field to a document multiple times, and in such situations the field boost is computed as all the boosts specified for that field and document multiplied together.

In addition to the explicit factors in this equation, other factors can be computed on a per-query basis as part of the queryNorm factor. Queries themselves can have an impact on the document score. Boosting a Query instance is sensible only in a multiple-clause query; if only a single term is used for searching, changing its boost would impact all matched documents equally. In a multiple-clause Boolean query, some documents may match one clause but not another, enabling the boost factor to discriminate between matching documents. Queries also default to a 1.0 boost factor.

Most of these scoring formula factors are controlled and implemented as a subclass of the abstract Similarity class. DefaultSimilarity is the implementation used unless otherwise specified. More computations are performed under the covers of DefaultSimilarity; for example, the term frequency factor is the square root of the actual frequency. In practice, it’s extremely rare to need a change in these factors. Should you need to change them, please refer to Similarity’s Javadocs, and be prepared with a solid understanding of these factors and the effect your changes will have.

Using explain() to understand hit scoring:
Whew! The scoring formula seems daunting—and it is. We’re talking about factors that rank one document higher than another based on a query; that in and of itself deserves the sophistication going on. If you want to see how all these factors play out, Lucene provides a helpful feature called Explanation. IndexSearcher has anexplain method, which requires a Query and a document ID and returns an Explanation object.

The Explanation object internally contains all the gory details that factor into the score calculation. Each detail can be accessed individually if you like; but generally, dumping out the explanation in its entirety is desired. The .toString() method dumps a nicely formatted text representation of the Explanations. We wrote a simple program to dump Explanations, shown in listing 3.4.

- Listing 3.4 The explain() method
  1. package ch3;  
  2.   
  3. import java.io.File;  
  4. import java.io.IOException;  
  5.   
  6. import junit.framework.TestCase;  
  7.   
  8. import org.apache.lucene.analysis.Analyzer;  
  9. import org.apache.lucene.analysis.SimpleAnalyzer;  
  10. import org.apache.lucene.analysis.standard.StandardAnalyzer;  
  11. import org.apache.lucene.document.Document;  
  12. import org.apache.lucene.document.Field;  
  13. import org.apache.lucene.index.IndexReader;  
  14. import org.apache.lucene.index.IndexWriter;  
  15. import org.apache.lucene.index.IndexWriterConfig;  
  16. import org.apache.lucene.index.Term;  
  17. import org.apache.lucene.queryParser.QueryParser;  
  18. import org.apache.lucene.search.IndexSearcher;  
  19. import org.apache.lucene.search.Query;  
  20. import org.apache.lucene.search.TermQuery;  
  21. import org.apache.lucene.search.TopDocs;  
  22. import org.apache.lucene.store.Directory;  
  23. import org.apache.lucene.store.FSDirectory;  
  24. import org.apache.lucene.util.Version;  
  25.   
  26. public class ListExams extends TestCase{  
  27.     public static Version LUCENE_VERSION = Version.LUCENE_30;  
  28.     public Directory directory = null;  
  29.     public IndexSearcher searcher = null;  
  30.     public File indexpath = new File("./test");  
  31.     protected String[] ids = { "1", "2" };  
  32.     protected String[] unindexed = { "Ant in Action", "Junit in Action" };  
  33.     protected String[] unstored = { "Amsterdam has lots of bridges",  
  34.             "Venice has lots of canals" };  
  35.     protected String[] subject = { "Ant in Action with Junit", "JUnit in Action, Second Edition" };  
  36.       
  37.     @Override  
  38.     protected void tearDown() throws Exception  
  39.     {  
  40.         //System.out.printf("\t[Test] tearDown...\n");  
  41.         searcher.close();  
  42.         directory.close();  
  43.         searcher = null;      
  44.         /*Thread.sleep(1000); 
  45.         File fs[] = indexpath.listFiles(); 
  46.         for(File f:fs)  
  47.         { 
  48.             System.out.printf("\t[Test] Delete %s...\n", f.getAbsolutePath()); 
  49.             f.delete(); 
  50.         } 
  51.         Thread.sleep(1000);*/  
  52.     }  
  53.       
  54.     @Override  
  55.     protected void setUp() throws Exception {  
  56.         //System.out.printf("\t[Test] setUp...\n");  
  57.         // 1) Run before every test  
  58.         directory = FSDirectory.open(indexpath);  
  59.         buildIndex();                 
  60.     }  
  61.       
  62.     protected void buildIndex() throws Exception  
  63.     {  
  64.         // 2) Cretae IndexWriter  
  65.         IndexWriter writer = getWriter();  
  66.   
  67.         // 3) Add document  
  68.         for (int i = 0; i < ids.length; i++) {  
  69.             Document doc = new Document();  
  70.             doc.add(new Field("id", ids[i], Field.Store.YES,  
  71.                     Field.Index.NOT_ANALYZED));  
  72.             doc.add(new Field("title", unindexed[i], Field.Store.YES,  
  73.                     Field.Index.NO));  
  74.             doc.add(new Field("contents", unstored[i], Field.Store.NO,  
  75.                     Field.Index.ANALYZED));  
  76.             doc.add(new Field("subject", subject[i], Field.Store.YES,  
  77.                     Field.Index.ANALYZED));  
  78.             writer.addDocument(doc);  
  79.         }  
  80.         writer.commit();  
  81.         writer.close();  
  82.     }  
  83.       
  84.     /** 
  85.      * BD: The StandardAnalyzer applies a LowerCaseFilter that would make search insensitive. 
  86.      * Reference: 
  87.      *      - How to make lucene be case-insensitive 
  88.      *        http://stackoverflow.com/questions/5512803/how-to-make-lucene-be-case-insensitive 
  89.      * @return 
  90.      * @throws IOException 
  91.      */  
  92.     private IndexWriter getWriter() throws IOException {  
  93.         Analyzer alyz = new StandardAnalyzer(LUCENE_VERSION);         
  94.         IndexWriterConfig iwConfig = new IndexWriterConfig(LUCENE_VERSION, alyz);  
  95.         iwConfig.setOpenMode(IndexWriterConfig.OpenMode.CREATE);  
  96.         return new IndexWriter(directory, iwConfig);              
  97.     }  
  98.       
  99.     private IndexSearcher getSearcher() throws IOException  
  100.     {  
  101.         if(searcher==null)  
  102.         {  
  103.             IndexReader idxReader = IndexReader.open(directory);  
  104.             searcher = new IndexSearcher(idxReader);  
  105.         }  
  106.         return searcher;  
  107.     }  
  108.       
  109.     /** 
  110.      * BD: List 3.1 
  111.      * @throws Exception 
  112.      */  
  113.     public void testTerm() throws Exception {                 
  114.         // 1) Create IndexSearcher -> directory is built during setUp()  
  115.         IndexSearcher searcher = getSearcher();  
  116.           
  117.         // 2) Build Single Term Query  
  118.         Term t = new Term("subject", "ant");  
  119.         Query query = new TermQuery(t);  
  120.           
  121.         // 3) Search  
  122.         TopDocs docs = searcher.search(query, 10);  
  123.           
  124.         // 4) Confirm one hit for 'ant' query.  
  125.         assertEquals("Ant in Action", 1, docs.totalHits);  
  126.           
  127.         // 5) Search again  
  128.         t = new Term("subject", "junit");  
  129.         docs = searcher.search(new TermQuery(t), 10);  
  130.           
  131.         // 6) Confirm two hit for 'junit' query.  
  132.         assertEquals("Ant in Action, " + "JUnit in Action, Second Edition",  
  133.                 2, docs.totalHits);  
  134.           
  135.         // 7) Close searcher and directory.       
  136.     }  
  137.       
  138.     /** 
  139.      * BD: List 3.2 - QueryParser, which makes it trivial to translate search text into a Query 
  140.      * @throws Exception 
  141.      */  
  142.     public void testQueryParser() throws Exception {  
  143.         // 1) Create IndexSearcher -> directory is built during setUp()  
  144.         IndexSearcher searcher = getSearcher();  
  145.   
  146.         // 2) Create QueryParser  
  147.         QueryParser parser = new QueryParser(LUCENE_VERSION, "subject",  
  148.                 new SimpleAnalyzer(LUCENE_VERSION));  
  149.           
  150.         // 3) Query subject to have "JUNIT", "ANT" but without "MOCK";  
  151.         Query query = parser.parse("+JUNIT +ANT -MOCK");  
  152.         TopDocs docs = searcher.search(query, 10);  
  153.           
  154.         // 4) Assert to have 1 hit.  
  155.         assertEquals(1, docs.totalHits);  
  156.           
  157.         // 5) Fetch the top1 document from search result.  
  158.         Document d = searcher.doc(docs.scoreDocs[0].doc);  
  159.           
  160.         // 6) Assert its title to be "Ant in Action".  
  161.         assertEquals("Ant in Action", d.get("title"));  
  162.           
  163.         // 7) Query again to have "mock" or "junit"  
  164.         query = parser.parse("mock OR junit");  
  165.         docs = searcher.search(query, 10);  
  166.           
  167.         // 8) Assert to have 2 hit.  
  168.         assertEquals("Ant in Action, " + "JUnit in Action, Second Edition", 2,  
  169.                 docs.totalHits);  
  170.           
  171.         // 9) Close searcher and directory in tearDown()  
  172.     }  
  173. }  
Then we can use below sample code to test it (You may run example from Ch3. Adding search - Implementing a simple search feature to do index in first):
  1. File indexpath = new File("./test");  /*Index folder*/  
  2. String arg_set[] = {indexpath.getAbsolutePath(), "ant"}; /*Query term='ant'*/  
  3. Explainer.main(arg_set);  
The output result will look like:
Query: ant
----------
Ant in Action
0.5 = (MATCH) fieldWeight(subject:ant in 0), product of:
1.0 = tf(termFreq(subject:ant)=1)
1.0 = idf(docFreq=1, maxDocs=2)
0.5 = fieldNorm(field=subject, doc=0)


[ Python 範例代碼 ] 使用 linecache 從文件中讀取指定的行

來源自 這裡 
Preface: 
你想根據給出的行號,從文本文件中讀取一行數據. 而 Python 標準庫 linecache 模塊非常適合這個任務: 
  1. import linecache    
  2. theline  =  linecache .getline(thefilepath, desired_line_number)  
套件說明: 
對這個任務而言,標準的 linecache 模塊是Python能夠提供的最佳解決工具。當你想要對文件中的某些行進行多次讀取時,linecache 特別有用,因為它會緩存一些信息以避免重複一些工作。當你不需要從緩存中獲得行數據時,可以調用模塊的 c​​learcache 函數來釋放被用作緩存的內存。當磁盤上的文件發生了變化時,還可以調用checkcache,以確保緩存中存儲的是最新的信息. 

linecache 讀取並緩存你指定名字的文件中的所有文本,所以,如果文件非常大,而你只需要其中一行,為此使用linecache 則顯得不是那麼必要. 如果這部分可能是你的程序的瓶頸,可以使用顯式的循環,並將其封裝在一個函數中,這樣可以獲得速度上的一些提升,像這樣: 
  1. def getline(thefilepath, desired_line_number):    
  2.       if desired_line_number  <  1:  return ''    
  3.       for current_line_number, line in    
  4. enumerate(open(thefilepath, 'rU')):    
  5.             if  current_line_number  ==    
  6. desired_line_number-1: return line    
  7.       return ''  
唯一需要注意的細節是 enumerate 從0開始計數,因此,既然我們假設desired_line_ number 參數從1開始計算,需要在用 == 比較的時候減去1! 

[ Java 代碼範本 ] Hamming distance

Preface: 
在資訊理論中,兩個等長字元串之間的 漢明距離 是兩個字元串對應位置的不同字元的個數。換句話說,它就是將一個字元串變換成另外一個字元串所需要替換的字元個數. 例如: 
- 1011101 與 1001001 之間的漢明距離是 2。
- 2143896 與 2233796 之間的漢明距離是 3。
- "toned" 與 "roses" 之間的漢明距離是 3。

漢明重量 是字元串相對於同樣長度的零字元串的漢明距離,也就是說,它是字元串中非零的元素個數:對於二進制字元串來說,就是 1 的個數,所以 11101 的漢明重量是 4. 

當你的比較的兩個字串是不同長度時, 漢明距離 便無法使用, 這時你可以考慮另一種方法叫 "編輯距離". 底下的範例代碼提供你計算 "漢明距離", 當兩個字串長度不同時, 就會替換成 "編輯距離". 

Implementation: 
這邊使用類別 HammingUtil 上的靜態方法 getDistance(String str1, String str2) 來計算 "漢明距離"; 靜態方法 getWeight(int i) 用來計算 "漢明重量"; 而靜態方法 LevenshteinDistance(String s, String t) 則是用來計算 "編輯距離". 完整代碼如下: 
  1. package flib.util.coding;  
  2.   
  3. /** 
  4. * BD: Hamming Toolkit 
  5. * Reference: 
  6. *      - Hamming distance 
  7. *        http://en.wikipedia.org/wiki/Hamming_distance 
  8. *      - Levenshtein distance 
  9. *        http://en.wikipedia.org/wiki/Levenshtein_distance 
  10. *      - Java實例9 - 漢明距離Hamming Distance 
  11. *        http://blog.csdn.net/kindterry/article/details/6581344 
  12. *    
  13. * @author John 
  14. * 
  15. */  
  16. public class HammingUtil {  
  17.     /** 
  18.      * BD: Calculate Hamming Distance between str1 and str2. 
  19.      * @param str1: Input string1 
  20.      * @param str2: Input string2 
  21.      * @return Hamming distance 
  22.      */  
  23.     public static int getDistance(String str1, String str2)  
  24.     {  
  25.           
  26.         if(str1.length()==str2.length())  
  27.         {  
  28.             int distance=0;  
  29.             for  ( int  i =  0 ; i < str1.length(); i++) {    
  30.                 if  (str1.charAt(i) != str2.charAt(i)) {    
  31.                     distance++;    
  32.                 }    
  33.             }   
  34.             return distance;  
  35.         }  
  36.         else if(str1.length()>str2.length())  
  37.         {  
  38.             if(str1.indexOf(str2)>=0) return str1.length()-str2.length();  
  39.             else  
  40.             {  
  41.                 return LevenshteinDistance(str1, str2);  
  42.             }  
  43.         }  
  44.         else  
  45.         {  
  46.             if(str2.indexOf(str1)==0) return str2.length()-str1.length();  
  47.             else  
  48.             {  
  49.                 return LevenshteinDistance(str1, str2);  
  50.             }  
  51.         }  
  52.     }  
  53.       
  54.     public static int LevenshteinDistance(String s, String t)  
  55.     {  
  56.         if(s.length()==0) return t.length();  
  57.         else if(t.length()==0) return s.length();  
  58.           
  59.         int cost = 0;  
  60.         if(s.charAt(0) != t.charAt(0)) cost = 1;          
  61.         return Math.min(LevenshteinDistance(s.substring(1), t)+1,   
  62.                         Math.min(LevenshteinDistance(s, t.substring(1))+1,   
  63.                                 LevenshteinDistance(s.substring(1), t.substring(1))+cost));  
  64.         //return LevenshteinDistance(s.substring(1), t.substring(1))+cost;  
  65.     }  
  66.       
  67.     public static int getWeight(int i)  
  68.     {  
  69.         int  n;    
  70.         for  (n =  0 ; i >  0 ; n++) {    
  71.             i &= (i -  1 );    
  72.         }    
  73.         return  n;  
  74.     }  
  75.       
  76.     public  static  void  main(String[] args) {    
  77.         String str1 =  "www.facebook.com" ;    
  78.         String str2 =  "www.faecbok.com" ;     
  79.         int  distance = HammingUtil.getDistance(str1, str2);    
  80.         System.out.println( "distance is "  + distance);    
  81.         int  weight = HammingUtil.getWeight( 255 );    
  82.         System.out.println( "weight is "  + weight);    
  83.     }  
  84. }  
Sample Code: 
接著你可以使用下面範例代碼計算 "漢明距離", "漢明重量": 
  1. public  static  void  main(String[] args) {           
  2.        String str1 =  "facebook" ;    
  3.        String str2 =  "facboak" ;     
  4.        String str3 =  "toned";  
  5.        String str4 =  "roses";  
  6.          
  7.        int  distance1 = HammingUtil.getDistance(str1, str2);    
  8.        int  distance2 = HammingUtil.getDistance(str3, str4);  
  9.        System.out.printf("\t[Info] Hamming distance between '%s' and '%s' is %d...\n",str1, str2, distance1);  
  10.        System.out.printf("\t[Info] Hamming distance between '%s' and '%s' is %d...\n",str3, str4, distance2);  
  11.        int strInBinary = 255;  
  12.        int  weight = HammingUtil.getWeight( strInBinary );    
  13.        System.out.printf("\t[Info] Hamming weight of '%s' is %d...\n", Integer.toBinaryString(strInBinary), weight);    
  14.    }  
執行結果如下: 
[Info] Hamming distance between 'facebook' and 'facboak' is 2...
[Info] Hamming distance between 'toned' and 'roses' is 3...
[Info] Hamming weight of '11111111' is 8...

Supplement: 
* Java example of Hamming distance 
* Java實例9 - 漢明距離Hamming Distance

[Git 常見問題] error: The following untracked working tree files would be overwritten by merge

  Source From  Here 方案1: // x -----删除忽略文件已经对 git 来说不识别的文件 // d -----删除未被添加到 git 的路径中的文件 // f -----强制运行 #   git clean -d -fx 方案2: 今天在服务器上  gi...