天天看點

Lucene基本知識入門Lucene 篇

Lucene 篇

參考位址:《Lucene介紹與使用》

1. Lucene 簡介

Lucene 是一套用于全文檢索和搜尋的開源程式庫,提供了一個簡單卻強大的 API,能夠做全文索引和搜尋。在 Java 開發環境裡,Lucene 是一個成熟的免費開放源代碼工具,它并不是現成的搜尋引擎産品,但可以用來制作搜尋引擎産品。Solr 和 ElasticSearch 都是基于 Lucene 開發的企業級的搜尋引擎産品。 Lucene 的 API 來實作對索引的增(建立索引)、删(删除索引)、改(修改索引)、查(搜尋資料)。

2. 全文檢索

計算機索引程式通過掃描文章中的每一個詞,對每一個詞建立一個索引,指明該詞在文章中出現的次數和位置。當使用者查詢時,檢索程式就根據實作建立的索引進行查找,并将查找的結果回報給使用者的檢索方式。總結起來,就是 Lucene 全文檢索對文檔中全部内容進行分詞,然後對單詞建立反向索引的過程。

3. 建立索引

與關系資料庫 Mysql 對比,Lucene 資料架構主要概念如下:

MySQL Lucene
Database Index
Table Type
Row Document
Column Field
Schema Mapping
Index Everything is indexed
SQL Query DSL
Lucene基本知識入門Lucene 篇

代碼實作流程如下:

// 建立索引
    @Test
    public void testCreate() throws Exception{
        //1 建立文檔對象
        Document document = new Document();
        // 建立并添加字段資訊。
        // 參數:字段的名稱、字段的值、是否存儲;
        // 這裡選Store.YES代表存儲到文檔清單。Store.NO 代表不存儲
        document.add(new StringField("id", "1", Field.Store.YES));
        // 這裡我們 title 字段需要用 TextField,即建立索引又會被分詞;
        // StringField 會建立索引,但是不會被分詞
        document.add(new TextField("title", "谷歌地圖之父跳槽facebook", 
                                   Field.Store.YES));

        //2 索引目錄類,指定索引在硬碟中的位置
        Directory directory = FSDirectory.open(new File("d:\\indexDir"));
        //3 建立分詞器對象
        Analyzer analyzer = new StandardAnalyzer();
        //4 索引寫出工具的配置對象
        IndexWriterConfig conf = new IndexWriterConfig(Version.LATEST, analyzer);
        //5 建立索引的寫出工具類。參數:索引的目錄和配置資訊
        IndexWriter indexWriter = new IndexWriter(directory, conf);

        //6 把文檔交給IndexWriter
        indexWriter.addDocument(document);
        //7 送出
        indexWriter.commit();
        //8 關閉
        indexWriter.close();
    }           

複制

4. 建立索引的 API 詳解

4.1 Document

文檔對象 Document 對應資料庫中的一行,是一條原始的資料;如下圖所示;

Lucene基本知識入門Lucene 篇

4.2 Field

字段類 Field 對應資料庫中的一列,有不同的資料類型。一個 Document 中可以有很多個不同的字段,每一個字段都是一個 Field 類的對象。由于一個 Document 中的字段的類型是不确定的,是以 Field 類就提供了各種不同的子類,來對應這些不同類型的字段。這些子類有一些不同的特性:

  • DoubleField、FloatField、IntField、LongField、StringField、TextField:
    • 這些子類一定會被建立索引,但是不會被分詞,而且不一定會被存儲到文檔清單。
    • 是否存儲要通過構造函數中的參數 Store 來指定:如果Store.YES代表存儲,Store.NO代表不存儲;
  • TextField:既建立索引,又會被分詞;
    • 注:StringField 會建立索引,但不會被分詞;如果不分詞,會造成整個字段作為一個詞條,除非使用者完全比對,否則搜尋不到:
  • StoreField:一定會被存儲,但不一定會建立索引;
    • 注:StoredField 可以建立各種基礎資料類型的字段;
注:相關問題:
  • 問題1:如何确定一個字段是否需要存儲?
    • 如果一個字段要顯示到最終的結果中,那麼一定要存儲,否則就不存儲。
  • 問題2:如何确定一個字段是否需要建立索引?
    • 如果要根據這個字段進行搜尋,那麼這個字段就必須建立索引。
  • 問題3:如何确定一個字段是否需要分詞?
    • 前提是這個字段首先要建立索引;
    • 然後如果這個字段的值是不可分割的,那麼就不需要分詞。例如:ID;

4.3 Directory

目錄類 Directory 指定索引要存儲的位置。有兩種主要類型:

  • FSDirectory:檔案系統目錄,會把索引庫指向本地磁盤;
    • 特點:速度略慢,但是整體比較安全;
  • RAMDirecotry:記憶體目錄,會把索引庫儲存在記憶體;
    • 特點:速度快,但是不安全;

4.4 Analyzer

分詞器類 Analyzer 提供分詞算法,可以把文檔中的資料按照算法分詞。通常官方的分詞器并沒有合适的中文分詞器,是以一般會用到第三方提供的分詞器。比如 IK 分詞器。

IK 分詞器的詞庫有限,新增加的詞條可以通過配置檔案添加到 IK 的詞庫中(即擴充詞典),同時也可以把一些不用的詞條(停止詞典)去除。

4.5 IndexWriterConfig

索引寫出器配置類 IndexWriterConfig,設定 Lucene 的版本與分詞器類型,用來配置索引寫出器。例如:

//3 建立分詞器對象
Analyzer analyzer = new StandardAnalyzer();
//4 索引寫出工具的配置對象
IndexWriterConfig conf = new IndexWriterConfig(Version.LATEST, analyzer);           

複制

4.6 IndexWriter

IndexWriter 索引寫出器類,用來實作對索引的增删改,即建立索引、删除索引、修改索引。

5. 查詢索引資料

代碼實作如下:

@Test
    public void testSearch() throws Exception {
        // 1. 建立索引目錄對象
        Directory directory = FSDirectory.open(new File("d:\\indexDir"));
        // 2. 建立索引讀取工具
        IndexReader reader = DirectoryReader.open(directory);
        // 3. 建立索引搜尋工具
        IndexSearcher searcher = new IndexSearcher(reader);

        // 4. 建立查詢解析器
        // 兩個參數:預設要查詢的字段的名稱,分詞器
        QueryParser parser = new QueryParser("title", new IKAnalyzer());
        // 5. 建立查詢對象
        Query query = parser.parse("谷歌");

        // 6. 搜尋資料
        // 兩個參數:查詢條件對象,以及要查詢的最大結果條數
        // 傳回的結果按照比對度排名得分前 N 名的文檔資訊(包含查詢到的總條數資訊、所有符合條件的文檔的編号資訊)。
        TopDocs topDocs = searcher.search(query, 10);
        
        // 擷取總條數
        System.out.println("本次搜尋共找到" + topDocs.totalHits + "條資料");
        // 擷取得分文檔對象(ScoreDoc)數組
        // ScoreDoc中包含:文檔的編号、文檔的得分
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
        for (ScoreDoc scoreDoc : scoreDocs) {
            // 取出文檔編号
            int docID = scoreDoc.doc;
            // 根據編号去找文檔
            Document doc = reader.document(docID);
            System.out.println("id: " + doc.get("id"));
            System.out.println("title: " + doc.get("title"));
            // 取出文檔得分
            System.out.println("得分: " + scoreDoc.score);
        }
    }           

複制

5.1 Query

Query 是查詢對象,包含要查詢的關鍵詞資訊;在上面的代碼中,通過 QueryParser 解析關鍵字,得到查詢對象。

5.2 進階查詢

除了使用 QueryParser 解析之外,也可以通過自定義查詢對象(進階查詢),即通過 Query 的子類,直接建立查詢對象,實作進階查詢。實作進階查詢的測試代碼如下:

// 傳入 Query 對象,實作進階查詢
    public void search(Query query) throws Exception {
        // 1. 建立索引目錄對象
        Directory directory = FSDirectory.open(new File("indexDir"));
        // 2. 建立索引讀取工具
        IndexReader reader = DirectoryReader.open(directory);
        // 3. 建立索引搜尋工具
        IndexSearcher searcher = new IndexSearcher(reader);

        // 4. 搜尋資料
        // 兩個參數:查詢條件對象,以及要查詢的最大結果條數
        // 傳回的結果是按照比對度排名得分前 N 名的文檔資訊(包含查詢到的總條數資訊、所有符合條件的文檔的編号資訊)。
        TopDocs topDocs = searcher.search(query, 10);
        // 5. 擷取總條數
        System.out.println("本次搜尋共找到" + topDocs.totalHits + "條資料");
        // 擷取得分文檔對象(ScoreDoc)數組.SocreDoc中包含:文檔的編号、文檔的得分
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;

        for (ScoreDoc scoreDoc : scoreDocs) {
            // 取出文檔編号
            int docID = scoreDoc.doc;
            // 根據編号去找文檔
            Document doc = reader.document(docID);
            System.out.println("id: " + doc.get("id"));
            System.out.println("title: " + doc.get("title"));
            // 取出文檔得分
            System.out.println("得分: " + scoreDoc.score);
        }
    }           

複制

5.2.1 TermQuery

TermQuery 詞條查詢,詞條 Term 是搜尋的最小機關,不可以再被分詞,而且值必須是字元串。

@Test
    public void testTermQuery() throws Exception {
        // 建立詞條查詢對象
        Query query = new TermQuery(new Term("title", "谷歌地圖"));
        search(query);
    }           

複制

5.2.2 WildcardQuery

WildcardQuery 通配符查詢,類似于用資料庫中

like ‘%谷歌%’

的通配符用法。

  • ? 字元可以代表任意一個字元;
  • * 字元可以代表任意多個任意字元;
@Test
    public void testWildCardQuery() throws Exception {
        // 建立查詢對象
        Query query = new WildcardQuery(new Term("title", "*歌*"));
        search(query);
    }           

複制

5.2.3 FuzzyQuery

FuzzyQuery 模糊查詢, 允許使用者輸錯,但是要求錯誤的最大編輯距離不能超過 2。編輯距離就是一個單詞到另一個單詞最少要修改的次數,比如 facebool --> facebook 需要編輯1次,編輯距離就是1。

@Test
    public void testFuzzyQuery() throws Exception {
        // 建立模糊查詢對象:允許使用者輸錯。但是要求錯誤的最大編輯距離不能超過2
        // 編輯距離:一個單詞到另一個單詞最少要修改的次數 facebool --> facebook 需要編輯1次,編輯距離就是1
//    Query query = new FuzzyQuery(new Term("title","fscevool"));
        // 可以手動指定編輯距離,但是參數必須在0~2之間
        Query query = new FuzzyQuery(new Term("title","facevool"),1);
        search(query);
    }           

複制

5.2.4 NumericRangeQuery

數值範圍查詢 NumericRangeQuery 可以對非 String 類型的 ID 進行精确查找。

@Test
	public void testNumericRangeQuery() throws Exception{
		// 數值範圍查詢對象
		// 參數:字段名稱,最小值、最大值、是否包含最小值、是否包含最大值
		Query query = NumericRangeQuery.newLongRange("id", 2L, 2L, true, true);
		search(query);
	}           

複制

6. 全文檢索

參考位址:《全文檢索原理及實作方式》

6.1 全文檢索簡介

我們生活中的資料總體分為兩種:結構化資料和非結構化資料。其中結構化資料指具有固定格式或有限長度的資料,如資料庫,中繼資料等。非結構化資料指不定長或無固定格式的資料,如郵件,word 文檔等。

對于非結構化資料(即對全文資料)進行搜尋主要有兩種方法。一是順序掃描,比如要找内容包含某一個字元串的檔案,就是一個文檔一個文檔的看,對于每一個文檔,從頭看到尾,如果此文檔包含此字元串,則此文檔為我們要找的檔案,接着看下一個檔案,直到掃描完所有的檔案。當然這是一種特别慢的搜尋方法。

另外一種方法就是全文檢索。全文檢索的思路類似于資料庫的索引,它将非結構化資料中的一部分資訊提取出來,重新組織,使其變得有一定結構,然後對此有一定結構的資料進行搜尋,進而達到搜尋相對較快的目的。這部分從非結構化資料中提取出的,然後重新組織的資訊,我們稱之索引。

比如字典,字典的拼音表和部首檢字表就相當于字典的索引,對每一個字的解釋是非結構化的,如果字典沒有音節表和部首檢字表,在茫茫辭海中找一個字隻能順序掃描。然而字的某些資訊可以提取出來進行結構化處理,比如讀音,就比較結構化,分聲母和韻母,分别隻有幾種可以一一列舉,于是将讀音拿出來按一定的順序排列,每一項讀音都指向此字的詳細解釋的頁數。我們搜尋時按結構化的拼音搜到讀音,然後按其指向的頁數,便可找到我們的非結構化資料——也即對字的解釋。這種先建立索引,再對索引進行搜尋的過程就叫**全文檢索 (Full-text Search) **。

全文檢索大體分兩個過程,索引建立 (Indexing) 和搜尋索引 (Search)。

  • 索引建立:将現實世界中所有的結構化和非結構化資料提取資訊,建立索引的過程。
    1. 有一系列待索引檔案;
    2. 被索引檔案經過文法分析和語言處理形成一系列詞 (Term) 。
    3. 經過索引建立形成詞典和反向索引表。
    4. 通過索引存儲,将索引寫入硬碟。
  • 搜尋索引:得到使用者的查詢請求,搜尋建立的索引,然後傳回結果的過程。
    1. 使用者輸入查詢語句。
    2. 對查詢語句經過文法分析和語言分析,得到一系列詞(Term) 。
    3. 通過文法分析,得到一個查詢樹;
    4. 通過索引存儲,将索引讀入到記憶體。
    5. 利用查詢樹搜尋索引,進而得到每個詞 (Term) 的文檔連結清單;根據查詢樹邏輯運算,對文檔連結清單進行交集、差集、非運算,并得到結果文檔。
    6. 将搜尋到的結果文檔進行查詢的相關性排序。
    7. 傳回查詢結果給使用者。

6.2 索引建立

非結構化資料中所存儲的資訊是每個檔案包含哪些字元串,也即已知檔案,欲求字元串相對容易,也即是從檔案到字元串的映射。而我們想搜尋的資訊是哪些檔案包含此字元串,即已知字元串,欲求檔案,也就是從字元串到檔案的映射。兩者恰恰相反。于是如果索引總能夠儲存從字元串到檔案的映射,則會大大提高搜尋速度。

6.2.1 索引建立簡述

假設我的檔案集合裡面有100篇文檔,為了友善表示,我們為文檔編号從1到100

左邊儲存的是一系列字元串,稱為詞典;右面表示每個字元串都指向包含此字元串的文檔 (Document) 連結清單,此文檔連結清單稱為倒排表 (Posting List)。

有了索引,便使儲存的資訊和要搜尋的資訊一緻,可以大大加快搜尋的速度。

注:比如說,我們要尋找既包含字元串“lucene”又包含字元串“solr”的文檔,我們隻需要以下幾步:
  1. 取出包含字元串“lucene”的文檔連結清單。
  2. 取出包含字元串“solr”的文檔連結清單。
  3. 通過合并連結清單,找出既包含“lucene”又包含“solr”的檔案。

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-oPMFyyvK-1594915230287)(./pic/全文檢索_并集.jpg)]

順序掃描是每次都要掃描,而建立索引的過程僅僅需要一次,以後便是一勞永逸的了,每次搜尋,建立索引的過程不必經過,僅僅搜尋建立好的索引就可以了。這也是全文搜尋相對于順序掃描的優勢之一:一次索引,多次使用。

6.2.2 索引建立原理

建立原理在文章《全文檢索原理及實作方式》有詳細的說明,這裡隻進行總結。

  1. 準備一些要索引的原文檔 (Document);例如有文檔:
    • 文檔 1:Students should be allowed to go out with their friends, but not allowed to drink beer;
    • 文檔 2:My friend Jerry went to school to see his students but found them drunk which is not allowed.
  2. 文檔分詞:做分詞、去除标點符号、去除無效詞 (a, the, this) 等,獲得詞元;
  3. 詞元處理:如變為小寫、去除複數、轉為一般現在時等操作;
  4. 建構索引:将處理後的詞元傳給索引元件,建立得到一個字典。按照字母順序排序後,可以得到每個詞元在每個文檔中出現的頻率。将每個詞資訊合并,并按照頻率倒序排序,可以得到倒排連結清單。
    • [外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-IUIDhhfy-1594915230290)(./pic/全文檢索_倒排表.jpg)]
    • 文檔頻次 (Document Frequency):總共有多少檔案包含此詞 (Term)。
    • 詞頻率 (Frequency):檔案中包含了幾個此詞 (Term)。

6.3 搜尋索引

問題:如何像 Google 一樣在成千上萬的搜尋結果中,找到和查詢語句最相關的呢?如何判斷搜尋出的文檔和查詢語句的相關性呢?

6.3.1 輸入查詢語句

查詢語句也是有一定文法的,比如最基本的 AND, OR, NOT 等。

6.3.2 查詢語句建構文法樹

  1. 詞法分析:識别單詞和關鍵字;比如提取查詢語句的 AND, NOT 等;
  2. 文法分析:形成文法樹;
  3. 語言處理:同詞元處理;

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-Ss8iDcGY-1594915230292)(./pic/全文檢索_構成文法樹.jpg)]

6.3.3 搜尋索引

按照文法樹,對索引進行搜尋。類似于 6.2.1 的過程。

6.3.4 計算相關性排序

計算文檔和查詢語句的相關性,我們可以把查詢語句看作一片短小的文檔,對文檔與文檔之間的相關性 (relevance) 進行打分 (scoring),分數高的相關性好,排在前面。

文檔由很多詞組成,找出詞對文檔重要性的過程,又稱為計算詞的權重 (Term weight)。影響一個詞在一片文檔中重要性的關鍵因素:

  1. Term Frequency (tf):某個詞在某篇文檔中出現的次數;TF 值越大,說明該詞越重要;
    • 可以了解為:一個詞在某篇文檔中出現的次數很多,說明該文檔就是講這方面的問題的;是以說明這個詞在這篇文章很重要。
  2. Document Frequency (df):所有文檔中,某個詞在多少文檔中出現過;DF 值越大,說明該詞越不重要;
    • 例如:this 在很多文檔中出現,但它并不重要。

判斷 Term 之間關系進而得到文檔相關性的過程,就是向量空間模型算法。該算法把文檔看作一系列詞 (Term),每一個詞 (Term) 都有一個權重 (Term weight)。不同的詞 (Term) 根據自己在文檔中的權重來影響文檔相關性的打分計算。計算方法在前面所述的文檔中可以計算。

比如計算一個共有 11 個詞的查詢語句,共有三篇文檔搜尋出來,首先計算所有詞的權重,然後根據打分公式分别計算查詢語句與三篇文檔的相關性。最後按照相關性進行排序,即可得到最想要的文檔。