Lucene5学习之TermVector项向量

简介:

 项向量在Lucene中属于高级话题。利用项向量能实现很多很有意思的功能,比如返回跟当前商品相似的商品。当你需要实现返回与xxxxxxxx类似的东西时,就可以考虑使用项向量,在Lucene中是使用MoreLikeThis来实现。

         项向量其实就是根据Term在文档中出现的频率和文档中包含Term的频率建立的数学模型,计算两个项向量的夹角的方式来判断他们的相似性。而Lucene5中内置的MoreLikeThis的实现方式却是使用打分的方式计算相似度,根据最终得分高低放入优先级队列,评分高的自然在队列最高处。

Java代码   收藏代码
  1. /** 
  2.    * Create a PriorityQueue from a word->tf map. 
  3.    * 
  4.    * @param words a map of words keyed on the word(String) with Int objects as the values. 
  5.    */  
  6.   private PriorityQueue<ScoreTerm> createQueue(Map<String, Int> words) throws IOException {  
  7.     // have collected all words in doc and their freqs  
  8.     int numDocs = ir.numDocs();  
  9.     final int limit = Math.min(maxQueryTerms, words.size());  
  10.     FreqQ queue = new FreqQ(limit); // will order words by score  
  11.   
  12.     for (String word : words.keySet()) { // for every word  
  13.       int tf = words.get(word).x; // term freq in the source doc  
  14.       if (minTermFreq > 0 && tf < minTermFreq) {  
  15.         continue// filter out words that don't occur enough times in the source  
  16.       }  
  17.   
  18.       // go through all the fields and find the largest document frequency  
  19.       String topField = fieldNames[0];  
  20.       int docFreq = 0;  
  21.       for (String fieldName : fieldNames) {  
  22.         int freq = ir.docFreq(new Term(fieldName, word));  
  23.         topField = (freq > docFreq) ? fieldName : topField;  
  24.         docFreq = (freq > docFreq) ? freq : docFreq;  
  25.       }  
  26.   
  27.       if (minDocFreq > 0 && docFreq < minDocFreq) {  
  28.         continue// filter out words that don't occur in enough docs  
  29.       }  
  30.   
  31.       if (docFreq > maxDocFreq) {  
  32.         continue// filter out words that occur in too many docs  
  33.       }  
  34.   
  35.       if (docFreq == 0) {  
  36.         continue// index update problem?  
  37.       }  
  38.   
  39.       float idf = similarity.idf(docFreq, numDocs);  
  40.       float score = tf * idf;  
  41.   
  42.       if (queue.size() < limit) {  
  43.         // there is still space in the queue  
  44.         queue.add(new ScoreTerm(word, topField, score, idf, docFreq, tf));  
  45.       } else {  
  46.         ScoreTerm term = queue.top();  
  47.         if (term.score < score) { // update the smallest in the queue in place and update the queue.  
  48.           term.update(word, topField, score, idf, docFreq, tf);  
  49.           queue.updateTop();  
  50.         }  
  51.       }  
  52.     }  
  53.     return queue;  
  54.   }  

    其实就是通过similarity来计算IDF-TF从而计算得分。

 

    Lucene5中获取项向量的方法:

    1.根据document   id获取

Java代码   收藏代码
  1. reader.getTermVectors(docID);  

    2.根据document id 和 FieldName

Java代码   收藏代码
  1. Terms termFreqVector = reader.getTermVector(i, "subject");  

    

    下面是一个有关Lucene5中TermVector项向量操作的示例代码:

    

Java代码   收藏代码
  1. package com.yida.framework.lucene5.termvector;  
  2.   
  3. import java.io.IOException;  
  4. import java.nio.file.Paths;  
  5.   
  6. import org.apache.lucene.document.Document;  
  7. import org.apache.lucene.index.DirectoryReader;  
  8. import org.apache.lucene.index.IndexReader;  
  9. import org.apache.lucene.index.Term;  
  10. import org.apache.lucene.index.Terms;  
  11. import org.apache.lucene.index.TermsEnum;  
  12. import org.apache.lucene.search.BooleanClause;  
  13. import org.apache.lucene.search.BooleanClause.Occur;  
  14. import org.apache.lucene.search.BooleanQuery;  
  15. import org.apache.lucene.search.IndexSearcher;  
  16. import org.apache.lucene.search.TermQuery;  
  17. import org.apache.lucene.search.TopDocs;  
  18. import org.apache.lucene.store.Directory;  
  19. import org.apache.lucene.store.FSDirectory;  
  20. import org.apache.lucene.util.BytesRef;  
  21. import org.apache.lucene.util.CharsRefBuilder;  
  22. /** 
  23.  * 查找类似书籍-测试 
  24.  * @author Lanxiaowei 
  25.  * 
  26.  */  
  27. public class BookLikeThis {  
  28.     public static void main(String[] args) throws IOException {  
  29.         String indexDir = "C:/lucenedir";  
  30.         Directory directory = FSDirectory.open(Paths.get(indexDir));  
  31.         IndexReader reader = DirectoryReader.open(directory);  
  32.         IndexSearcher searcher = new IndexSearcher(reader);  
  33.         // 最大的索引文档ID  
  34.         int numDocs = reader.maxDoc();  
  35.   
  36.         BookLikeThis blt = new BookLikeThis();  
  37.         for (int i = 0; i < numDocs; i++) {  
  38.             System.out.println();  
  39.             Document doc = reader.document(i);  
  40.             System.out.println(doc.get("title"));  
  41.   
  42.             Document[] docs = blt.docsLike(reader, searcher, i, 10);  
  43.             if (docs.length == 0) {  
  44.                 System.out.println("  -> Sorry,None like this");  
  45.             }  
  46.             for (Document likeThisDoc : docs) {  
  47.                 System.out.println("  -> " + likeThisDoc.get("title"));  
  48.             }  
  49.         }  
  50.         reader.close();  
  51.         directory.close();  
  52.     }  
  53.   
  54.     public Document[] docsLike(IndexReader reader, IndexSearcher searcher,  
  55.             int id, int max) throws IOException {  
  56.         //根据文档id加载文档对象  
  57.         Document doc = reader.document(id);  
  58.         //获取所有的作者  
  59.         String[] authors = doc.getValues("author");  
  60.         BooleanQuery authorQuery = new BooleanQuery();  
  61.         //遍历所有的作者  
  62.         for (String author : authors) {  
  63.             //包含所有作者的书籍  
  64.             authorQuery.add(new TermQuery(new Term("author", author)),Occur.SHOULD);  
  65.         }  
  66.         //authorQuery权重乘以2  
  67.         authorQuery.setBoost(2.0f);  
  68.   
  69.         //获取subject域的项向量  
  70.         Terms vector = reader.getTermVector(id, "subject");  
  71.         TermsEnum termsEnum = vector.iterator(null);  
  72.         CharsRefBuilder spare = new CharsRefBuilder();  
  73.         BytesRef text = null;  
  74.         BooleanQuery subjectQuery = new BooleanQuery();  
  75.         while ((text = termsEnum.next()) != null) {  
  76.             spare.copyUTF8Bytes(text);  
  77.             String term = spare.toString();  
  78.             //System.out.println("term:" + term);  
  79.             // if isNoiseWord  
  80.             TermQuery tq = new TermQuery(new Term("subject", term));  
  81.             //使用subject域中的项向量构建BooleanQuery  
  82.             subjectQuery.add(tq, Occur.SHOULD);  
  83.         }  
  84.   
  85.         BooleanQuery likeThisQuery = new BooleanQuery();  
  86.         likeThisQuery.add(authorQuery, BooleanClause.Occur.SHOULD);  
  87.         likeThisQuery.add(subjectQuery, BooleanClause.Occur.SHOULD);  
  88.   
  89.         //排除自身  
  90.         likeThisQuery.add(new TermQuery(new Term("isbn", doc.get("isbn"))),  
  91.                 BooleanClause.Occur.MUST_NOT);  
  92.   
  93.         TopDocs hits = searcher.search(likeThisQuery, 10);  
  94.         int size = max;  
  95.         if (max > hits.scoreDocs.length) {  
  96.             size = hits.scoreDocs.length;  
  97.         }  
  98.   
  99.         Document[] docs = new Document[size];  
  100.         for (int i = 0; i < size; i++) {  
  101.             docs[i] = reader.document(hits.scoreDocs[i].doc);  
  102.         }  
  103.         return docs;  
  104.     }  
  105. }  

    通过计算项向量夹角的方式判定相似度的代码示例:

    

Java代码   收藏代码
  1. package com.yida.framework.lucene5.termvector;  
  2.   
  3. import java.io.IOException;  
  4. import java.nio.file.Paths;  
  5. import java.util.Iterator;  
  6. import java.util.Map;  
  7. import java.util.TreeMap;  
  8.   
  9. import org.apache.lucene.document.Document;  
  10. import org.apache.lucene.index.DirectoryReader;  
  11. import org.apache.lucene.index.IndexReader;  
  12. import org.apache.lucene.index.Terms;  
  13. import org.apache.lucene.index.TermsEnum;  
  14. import org.apache.lucene.search.IndexSearcher;  
  15. import org.apache.lucene.store.Directory;  
  16. import org.apache.lucene.store.FSDirectory;  
  17. import org.apache.lucene.util.BytesRef;  
  18. import org.apache.lucene.util.CharsRefBuilder;  
  19.   
  20. /** 
  21.  * 利用项向量自动书籍分类[项向量夹角越小相似度越高] 
  22.  *  
  23.  * @author Lanxiaowei 
  24.  *  
  25.  */  
  26. public class CategoryTest {  
  27.     public static void main(String[] args) throws IOException {  
  28.         String indexDir = "C:/lucenedir";  
  29.         Directory directory = FSDirectory.open(Paths.get(indexDir));  
  30.         IndexReader reader = DirectoryReader.open(directory);  
  31.         //IndexSearcher searcher = new IndexSearcher(reader);  
  32.         Map<String, Map<String, Integer>> categoryMap = new TreeMap<String, Map<String,Integer>>();  
  33.         //构建分类的项向量  
  34.         buildCategoryVectors(categoryMap, reader);  
  35.           
  36.         getCategory("extreme agile methodology",categoryMap);  
  37.           
  38.         getCategory("montessori education philosophy",categoryMap);  
  39.           
  40.     }  
  41.   
  42.     /** 
  43.      * 根据项向量自动判断分类[返回项向量夹角最小的即相似度最高的] 
  44.      *  
  45.      * @param subject 
  46.      * @return 
  47.      */  
  48.     private static String getCategory(String subject,  
  49.             Map<String, Map<String, Integer>> categoryMap) {  
  50.         //将subject按空格分割  
  51.         String[] words = subject.split(" ");  
  52.   
  53.         Iterator<String> categoryIterator = categoryMap.keySet().iterator();  
  54.         double bestAngle = Double.MAX_VALUE;  
  55.         String bestCategory = null;  
  56.   
  57.         while (categoryIterator.hasNext()) {  
  58.             String category = categoryIterator.next();  
  59.   
  60.             double angle = computeAngle(categoryMap, words, category);  
  61.             // System.out.println(" -> angle = " + angle + " (" +  
  62.             // Math.toDegrees(angle) + ")");  
  63.             if (angle < bestAngle) {  
  64.                 bestAngle = angle;  
  65.                 bestCategory = category;  
  66.             }  
  67.         }  
  68.         System.out.println("The best like:" + bestCategory + "-->" + subject);  
  69.         return bestCategory;  
  70.     }  
  71.   
  72.     public static void buildCategoryVectors(  
  73.             Map<String, Map<String, Integer>> categoryMap, IndexReader reader)  
  74.             throws IOException {  
  75.         int maxDoc = reader.maxDoc();  
  76.         // 遍历所有索引文档  
  77.         for (int i = 0; i < maxDoc; i++) {  
  78.             Document doc = reader.document(i);  
  79.             // 获取category域的值  
  80.             String category = doc.get("category");  
  81.   
  82.             Map<String, Integer> vectorMap = categoryMap.get(category);  
  83.             if (vectorMap == null) {  
  84.                 vectorMap = new TreeMap<String, Integer>();  
  85.                 categoryMap.put(category, vectorMap);  
  86.             }  
  87.               
  88.             Terms termFreqVector = reader.getTermVector(i, "subject");  
  89.             TermsEnum termsEnum = termFreqVector.iterator(null);  
  90.             addTermFreqToMap(vectorMap, termsEnum);  
  91.         }  
  92.     }  
  93.   
  94.     /** 
  95.      * 统计项向量中每个Term出现的document个数,key为Term的值,value为document总个数 
  96.      *  
  97.      * @param vectorMap 
  98.      * @param termsEnum 
  99.      * @throws IOException 
  100.      */  
  101.     private static void addTermFreqToMap(Map<String, Integer> vectorMap,  
  102.             TermsEnum termsEnum) throws IOException {  
  103.         CharsRefBuilder spare = new CharsRefBuilder();  
  104.         BytesRef text = null;  
  105.         while ((text = termsEnum.next()) != null) {  
  106.             spare.copyUTF8Bytes(text);  
  107.             String term = spare.toString();  
  108.             int docFreq = termsEnum.docFreq();  
  109.             System.out.println("term:" + term + "-->docFreq:" + docFreq);  
  110.             // 包含该term就累加document出现频率  
  111.             if (vectorMap.containsKey(term)) {  
  112.                 Integer value = (Integer) vectorMap.get(term);  
  113.                 vectorMap.put(term, new Integer(value.intValue() + docFreq));  
  114.             } else {  
  115.                 vectorMap.put(term, new Integer(docFreq));  
  116.             }  
  117.         }  
  118.     }  
  119.   
  120.     /** 
  121.      * 计算两个Term项向量的夹角[夹角越小则相似度越大] 
  122.      *  
  123.      * @param categoryMap 
  124.      * @param words 
  125.      * @param category 
  126.      * @return 
  127.      */  
  128.     private static double computeAngle(Map<String, Map<String, Integer>> categoryMap,  
  129.             String[] words, String category) {  
  130.         Map<String, Integer> vectorMap = categoryMap.get(category);  
  131.   
  132.         int dotProduct = 0;  
  133.         int sumOfSquares = 0;  
  134.         for (String word : words) {  
  135.             int categoryWordFreq = 0;  
  136.   
  137.             if (vectorMap.containsKey(word)) {  
  138.                 categoryWordFreq = vectorMap.get(word).intValue();  
  139.             }  
  140.   
  141.             dotProduct += categoryWordFreq;  
  142.             sumOfSquares += categoryWordFreq * categoryWordFreq;  
  143.         }  
  144.   
  145.         double denominator = 0.0d;  
  146.         if (sumOfSquares == words.length) {  
  147.             denominator = sumOfSquares;  
  148.         } else {  
  149.             denominator = Math.sqrt(sumOfSquares) * Math.sqrt(words.length);  
  150.         }  
  151.   
  152.         double ratio = dotProduct / denominator;  
  153.   
  154.         return Math.acos(ratio);  
  155.     }  
  156. }  

    

 

    MoreLikeThis使用示例:

Java代码   收藏代码
  1. package com.yida.framework.lucene5.termvector;  
  2.   
  3. import java.io.IOException;  
  4. import java.nio.file.Paths;  
  5.   
  6. import org.apache.lucene.analysis.standard.StandardAnalyzer;  
  7. import org.apache.lucene.document.Document;  
  8. import org.apache.lucene.index.DirectoryReader;  
  9. import org.apache.lucene.index.IndexReader;  
  10. import org.apache.lucene.queries.mlt.MoreLikeThis;  
  11. import org.apache.lucene.search.IndexSearcher;  
  12. import org.apache.lucene.search.Query;  
  13. import org.apache.lucene.search.ScoreDoc;  
  14. import org.apache.lucene.search.TopDocs;  
  15. import org.apache.lucene.store.Directory;  
  16. import org.apache.lucene.store.FSDirectory;  
  17.   
  18. /** 
  19.  * MoreLikeThis[更多与此相似] 
  20.  *  
  21.  * @author Lanxiaowei 
  22.  *  
  23.  */  
  24. public class MoreLikeThisTest {  
  25.     public static void main(String[] args) throws IOException {  
  26.         String indexDir = "C:/lucenedir";  
  27.         Directory directory = FSDirectory.open(Paths.get(indexDir));  
  28.         IndexReader reader = DirectoryReader.open(directory);  
  29.         IndexSearcher searcher = new IndexSearcher(reader);  
  30.         MoreLikeThis moreLikeThis = new MoreLikeThis(reader);  
  31.         moreLikeThis.setAnalyzer(new StandardAnalyzer());  
  32.         moreLikeThis.setFieldNames(new String[] { "title","author","subject" });  
  33.         moreLikeThis.setMinTermFreq(1);  
  34.         moreLikeThis.setMinDocFreq(1);  
  35.         int docNum = 1;  
  36.         Query query = moreLikeThis.like(docNum);  
  37.         //System.out.println(query.toString());  
  38.         TopDocs topDocs = searcher.search(query, Integer.MAX_VALUE);  
  39.         ScoreDoc[] scoreDocs = topDocs.scoreDocs;  
  40.         //文档id为1的书  
  41.         System.out.println(reader.document(docNum).get("title") + "-->");  
  42.         for (ScoreDoc sdoc : scoreDocs) {  
  43.             Document doc = reader.document(sdoc.doc);  
  44.               
  45.             //找到与文档id为1的书相似的书  
  46.             System.out.println("    more like this:  " + doc.get("title"));  
  47.         }  
  48.     }  
  49. }  

 

    MoreLikeThisQuery使用示例:

    

Java代码   收藏代码
  1. package com.yida.framework.lucene5.termvector;  
  2.   
  3. import java.io.IOException;  
  4. import java.nio.file.Paths;  
  5.   
  6. import org.apache.lucene.analysis.Analyzer;  
  7. import org.apache.lucene.analysis.standard.StandardAnalyzer;  
  8. import org.apache.lucene.document.Document;  
  9. import org.apache.lucene.index.DirectoryReader;  
  10. import org.apache.lucene.index.IndexReader;  
  11. import org.apache.lucene.queries.mlt.MoreLikeThis;  
  12. import org.apache.lucene.queries.mlt.MoreLikeThisQuery;  
  13. import org.apache.lucene.search.IndexSearcher;  
  14. import org.apache.lucene.search.Query;  
  15. import org.apache.lucene.search.ScoreDoc;  
  16. import org.apache.lucene.search.TopDocs;  
  17. import org.apache.lucene.store.Directory;  
  18. import org.apache.lucene.store.FSDirectory;  
  19.   
  20. /** 
  21.  * MoreLikeThisQuery测试 
  22.  * @author Lanxiaowei 
  23.  * 
  24.  */  
  25. public class MoreLikeThisQueryTest {  
  26.     public static void main(String[] args) throws IOException {  
  27.         String indexDir = "C:/lucenedir";  
  28.         Directory directory = FSDirectory.open(Paths.get(indexDir));  
  29.         IndexReader reader = DirectoryReader.open(directory);  
  30.         IndexSearcher searcher = new IndexSearcher(reader);  
  31.         String[] moreLikeFields = new String[] {"title","author"};  
  32.         MoreLikeThisQuery query = new MoreLikeThisQuery("lucene in action",   
  33.             moreLikeFields, new StandardAnalyzer(), "author");  
  34.         query.setMinDocFreq(1);  
  35.         query.setMinTermFrequency(1);  
  36.         //System.out.println(query.toString());  
  37.         TopDocs topDocs = searcher.search(query, Integer.MAX_VALUE);  
  38.         ScoreDoc[] scoreDocs = topDocs.scoreDocs;  
  39.         //文档id为1的书  
  40.         //System.out.println(reader.document(docNum).get("title") + "-->");  
  41.         for (ScoreDoc sdoc : scoreDocs) {  
  42.             Document doc = reader.document(sdoc.doc);  
  43.               
  44.             //找到与文档id为1的书相似的书  
  45.             System.out.println("    more like this:  " + doc.get("title"));  
  46.         }  
  47.     }  
  48. }  

    注意MoreLikeThisQuery需要指定分词器,因为你需要指定likeText(即相似参照物),并对likeText进行分词得到多个Term,然后计算每个Term的IDF-TF最终计算得分。涉及到分词那就需要知道域的类型,比如你还必须指定一个fieldName即域名称,按照这个域的类型来进行分词,其他的参数moreLikeFields表示从哪些域里提取Term计算相似度。你还可以通过setStopWords去除likeText中的停用词。
     最后说一点小技巧,reader.document(docId)根据docid可以加载索引文档对象,这个你们都知道,但它还有一个重载方法得引起你们的重视:

后面的Set集合表示你需要返回那些域值,不指定默认是返回所有Store.YES的域,这样做的好处就是减少内存占用,比如你确定某些域的值在你本次查询中你不需要返回给用户展示,那你可以在Set中不包含该域,就好比SQL里的select * from table 和 select id,name from table。

      上面介绍了通过计算向量夹角和IDF-TF打分两种方式来计算相似度,你也可以实现自己的相似度算法,这个就超出了Lucene范畴了,那是算法设计问题了,好的算法决定了匹配的精准度。

      

      如果你还有什么问题请加我Q-Q:7-3-6-0-3-1-3-0-5,

或者加裙
一起交流学习!

转载:http://iamyida.iteye.com/blog/2201196

目录
相关文章
|
15天前
|
机器学习/深度学习 搜索推荐 机器人
为什么应用里需要向量检索?
向量检索在推荐系统、图片搜索等领域广泛应用,通过神经网络提取非结构化数据的语义信息,实现高效检索,提升非结构化数据处理能力。
|
15天前
|
存储 机器学习/深度学习 API
高维向量搜索:在 Elasticsearch 8.X 中利用 dense_vector 的实战探索
高维向量搜索:在 Elasticsearch 8.X 中利用 dense_vector 的实战探索
39 0
高维向量搜索:在 Elasticsearch 8.X 中利用 dense_vector 的实战探索
|
15天前
|
存储 机器学习/深度学习 搜索推荐
Elasticsearch 8.X 向量检索和普通检索能否实现组合检索?如何实现?
Elasticsearch 8.X 向量检索和普通检索能否实现组合检索?如何实现?
29 3
|
15天前
|
人工智能 自然语言处理 算法
向量检索基础方法总结
介绍大规模向量检索算法,包括基于空间划分和基于图的方法。文章提到了LSH、PQ和HNSW等技术,并详细解释了PQ的乘积量化和倒排乘积量化(IVFPQ)原理,以及NSW和HNSW(Hierarchical Navigable Small World)的图构造和查询优化策略。
|
15天前
|
机器学习/深度学习 人工智能 算法
浅谈向量检索
本文介绍了向量检索的背景、概念和方法,旨在一个给定的向量数据集中,快速找到与查询向量最接近的向量。
|
15天前
|
人工智能 Java API
向量检索的3种方式
本文介绍向量检索服务如何通过控制台、SDK、API三种不同的方式检索向量。
向量检索的3种方式
|
15天前
|
存储 JSON 搜索推荐
基于向量检索服务与灵积实现语义搜索
本教程演示如何使用向量检索服务(DashVector),结合灵积模型服务上的Embedding API,来从0到1构建基于文本索引的构建+向量检索基础上的语义搜索能力。具体来说,我们将基于QQ 浏览器搜索标题语料库(QBQTC:QQ Browser Query Title Corpus)进行实时的文本语义搜索,查询最相似的相关标题。
基于向量检索服务与灵积实现语义搜索
|
PyTorch 算法框架/工具 索引
pytorch使用None索引进行维度扩展
pytorch使用None索引进行维度扩展
149 0
向量检索/向量相似性计算方法(持续更新ing...)
本文介绍各种用于向量检索的向量相似性计算方法,将会简单介绍各种方法的优缺点等信息,并用toy example给出代码示例。
向量检索/向量相似性计算方法(持续更新ing...)
|
Java API Apache
lucene 相关性参考
假期梳理了之前在新浪博客的文档,将一些有用的内容搬到这里。本文是lucene序列原理分享之一:相关性原理。
64 0