搜尋
首頁web前端js教程具體介紹JavaScript全文搜尋之相關度評分的範例程式碼

全文搜索,與機器學習領域其他大多數問題不同,是一個 Web 程式設計師在日常工作中經常遇到的問題。客戶可能會要求你在某個地方提供一個搜尋框,然後你會寫一個類似 WHERE title LIKE %:query% 的 SQL 語句實作搜尋功能。一開始,這是沒問題,直到有一天,客戶找到你跟你說,“搜索出錯啦!”

#當然,實際上搜索並沒有“出錯”,只是搜索的結果並不是客戶想要的。一般的用戶並不清楚如何做精確匹配,所以得到的搜尋結果品質很差。為了解決問題,你決定使用全文搜尋。經過一陣枯燥的學習,你開啟了MySQL 的FULLTEXT 索引,並使用了更高級的查詢語法,如「MATCH() … AGAINST() ” 。

好了,問題解決,完結撒花!資料庫規模不大的時候是沒問題了。

但是當你的資料越來越多時,你會發現你的資料庫也越來越慢了。 MySQL 不是一個非常好用的全文搜尋工具。所以你決定使用 ElasticSearch,重構程式碼,並部署 Lucene 驅動的全文搜尋叢集。你會發現它工作的非常好,又快又準確。

這時你不禁會想:為什麼 Lucene 這麼屌呢?

這篇文章(主要介紹 TF-IDF,Okapi BM-25 和普通的相關性評分)和下一篇文章(主要介紹索引)將為你講述全文搜索背後的基本概念

相關性

對每一個搜尋查詢,我們很容易為每個文件定義一個「相關分數」。當使用者進行搜尋時,我們可以使用相關分數進行排序而不是使用文件出現時間來進行排序。這樣,最相關的文件將排在第一個,無論它是多久之前創建的(當然,有的時候和文件的創建時間也是有關的)。

有很多很多種計算文字之間相關性的方法,但是我們要從最簡單的、基於統計的方法說起。這種方法不需要理解語言本身,而是透過統計詞語的使用、匹配和基於文件中特有詞的普及率的權重等情況來決定「相關分數」。

這個演算法不關心字詞是名詞還是動詞,也不關心字詞的意義。它唯一關心的是哪些是常用詞,那些是稀有詞。如果一個搜尋語句中包含常用詞和稀有詞,你最好讓包含稀有詞的文件的評分高一些,同時降低常用詞的權重。

這個演算法稱為 Okapi BM25。它包含兩個基本概念字頻率(term frequency) 簡稱詞頻(“TF”) 和文件頻率倒數(inverse document frequency) 簡稱為(“IDF”) . 把它們放在一起,被稱為“TF-IDF”,這是一種統計測度,用來表示一個詞語(term) 在文件中有多重要。

TF-IDF

字詞頻率( Term Frequency), 簡稱 「TF」, 是一個很簡單的測量標準:一個特定的字詞在文件中出現的次數。你可以把這個值除以該文件中字詞的總數,得到一個分數。例如文件中有 100 個字, ‘the’ 這個字出現了 8 次,那麼 ‘the’ 的 TF 為 8 或 8/100 或 8%(取決於你想怎麼表示它)。

反向檔案頻率Inverse Document Frequency), 簡稱 “IDF”,要複雜一點:一個字越稀有,這個值越高。它由總文件數目除以包含該詞語之文件的數目,再將得到的商取對數得到。越是稀有的詞,越會產生高的 “IDF”。

如果你將這兩個數字乘到一起 (TF*IDF), 你將會得到一個字詞在文件中的權重。 「權重」的定義是:這個詞有多稀有並且在文件中出現的多麼頻繁?

你可以將這個概念用於文件的搜尋查詢。在查詢中的對於查詢中的每個關鍵字,計算他們的 TF-IDF 分數,並將它們加起來。得分最高的就是與查詢語句最符合的文件。

很酷吧!

Okapi BM25

上述演算法是一個可用的演算法,但並不太完美。它給出了一個基於統計的相關分數演算法,我們還可以進一步改進它。

Okapi BM25 是目前被認為最先進的排名演算法之一(所以稱為 ElasticSearch )。 Okapi BM25 在 TF-IDF 的基礎上增加了兩個可調參數,k1 和 b,, 分別代表 “詞語頻率飽和度(term frequency saturation)” 和 “字段長度規則”。這是什麼鬼?

為了能直觀的理解“詞語頻率飽和度”,請想像兩篇差不多長度的討論棒球的文章。另外,我們假設所有文件(除去這兩篇)並沒有太多與棒球相關的內容,因此 “棒球” 這個詞將具有很高的 IDF – 它極稀少而且很重要。 這兩篇文章都是討論棒球的,而且都花了大量的篇幅討論它,但是其中一篇比另一篇更多的使用了“棒球”這個詞。那麼在這種情況,是否一篇文章真的比另一篇文章相差很多的分數呢?既然兩份兩份文件都是大篇幅討論棒球的,那麼「棒球」這個詞出現 40 次還是 80 次都是一樣的。事實上,30 次就該封頂囉!

這就是「字詞頻率飽和度。原生的TF-IDF 演算法沒有飽和的概念,所以出現80 次「棒球」的文檔要比出現40 次的得分高一倍。有些時候,此時我們所希望的,但有些時候我們並不希望這樣。之間。 Field-length normalization)將文件的長度歸約化到全部文件的平均長度上。上。和1 之間,1 意味著全部歸約化,0 則不進行歸約化。知道了式子中的每一項是什麼,這肯定是很容易就理解了。

方法Tokenize(),目的是為了解析

字串

到tokens的

陣列

中。演算法來減少熵的量同時也提高匹配程度(“walking”和”walk”匹配是相同的)。寫的概念深入之前,如果我過於解釋這一節就請多擔待。資料結構:this.documents.和this.terms。著文檔中的所有詞語和詞語的數量與出現頻率。回答以下問題:在文件#3 中,'walk' 這個字詞出現了多少次?

我們在也使用了另一個資料結構,this.terms。它表示語料庫中的所有字詞。透過這個資料結構,我們可以在O(1)時間內回答以下問題:’walk’ 這個字在多少個文件中出現過?他們的 id 是什麼?

最後,我們記錄了每個文件的長度,並記錄了整個語料庫中文件的平均長度。

注意,上面的程式碼中, idf 被初始化 0,而且 updateidf() 方法被註解掉了。這是因為這個方法運行的非常慢,並且只需在建立索引之後運行一次就可以了。既然運行一次就能滿足需求,就沒有必要運行5000次。先把它註解掉,然後在大批量的索引作業之後執行,就可以節省很多時間。下面是這個函數的程式碼:

BM25.prototype.updateIdf = function() {
    var keys = Object.keys(this.terms);
    for (var i = 0, len = keys.length; i < len; i++) {
        var term = keys[i];
        var num = (this.totalDocuments - this.terms[term].n + 0.5);
        var denom = (this.terms[term].n + 0.5);
        this.terms[term].idf = Math.max(Math.log10(num / denom), 0.01);
    }
};

这是一个非常简单的函数,但是由于它需要遍历整个语料库中的所有词语,并更新所有词语的值,这就导致它工作的就有点慢。这个方法的实现采用了逆向文档频率 (inverse document frequency) 的标准公式(你可以在 Wikipedia 上找到这个公式)—  由总文件数目除以包含该词语之文件的数目,再将得到的商取对数得到。我做了一些修改,让返回值一直大于0。

BM25.prototype.search = function(query) {

    var queryTerms = BM25.Tokenize(query);
    var results = [];

    // Look at each document in turn. There are better ways to do this with inverted indices.
    var keys = Object.keys(this.documents);
    for (var j = 0, nDocs = keys.length; j < nDocs; j++) {
        var id = keys[j];
        // The relevance score for a document is the sum of a tf-idf-like
        // calculation for each query term.
        this.documents[id]._score = 0;

        // Calculate the score for each query term
        for (var i = 0, len = queryTerms.length; i < len; i++) {
            var queryTerm = queryTerms[i];

            // We&#39;ve never seen this term before so IDF will be 0.
            // Means we can skip the whole term, it adds nothing to the score
            // and isn&#39;t in any document.
            if (typeof this.terms[queryTerm] === &#39;undefined&#39;) {
                continue;
            }

            // This term isn&#39;t in the document, so the TF portion is 0 and this
            // term contributes nothing to the search score.
            if (typeof this.documents[id].terms[queryTerm] === &#39;undefined&#39;) {
                continue;
            }

            // The term is in the document, let&#39;s go.
            // The whole term is :
            // IDF * (TF * (k1 + 1)) / (TF + k1 * (1 - b + b * docLength / avgDocLength))

            // IDF is pre-calculated for the whole docset.
            var idf = this.terms[queryTerm].idf;
            // Numerator of the TF portion.
            var num = this.documents[id].terms[queryTerm].count * (this.k1 + 1);
            // Denomerator of the TF portion.
            var denom = this.documents[id].terms[queryTerm].count 
                + (this.k1 * (1 - this.b + (this.b * this.documents[id].termCount / this.averageDocumentLength)));

            // Add this query term to the score
            this.documents[id]._score += idf * num / denom;
        }

        if (!isNaN(this.documents[id]._score) && this.documents[id]._score > 0) {
            results.push(this.documents[id]);
        }
    }

    results.sort(function(a, b) { return b._score - a._score; });
    return results.slice(0, 10);
};

最后,search() 方法遍历所有的文档,并给出每个文档的 BM25 分数,然后按照由大到小的顺序进行排序。当然了,在搜索过程中遍历语料库中的每个文档实是不明智。这个问题在 Part Two (反向索引和性能)中得到解决。

上面的代码已经做了很好的注释,其要点如下:为每个文档和每个词语计算 BM25 分数。词语的 idf 分数已经预先计算好了,使用的时候只需要查询即可。词语频率作为文档属性的一部分也已经预先计算好了。之后只需要简单的四则运算即可。最后给每个文档增加一个临时变量 _score,然后根据 score 做降序排列并返回前 10 个结果。

示例,源代码,注意事项和下一步计划

上面的示例有很多方法进行优化,我们将在 “全文搜索”的第二部分中介绍它们,欢迎继续收看。我希望我能在几个星期之后完成它。下面列了下次将要提到的内容:

  • 反向索引和快速搜索

  • 快速索引

  • 更好的搜索结果

为了这个演示,我编了一个小的维基百科爬虫,爬到相当多(85000)维基百科文章的第一段。由于索引到所有85K文件需要90秒左右,在我的电脑我已经削减了一半。不想让你们仅仅为了一个简单的全文本演示浪费你的笔记本电脑电量。

因为索引是一个繁重的、模块化的CPU操作,我把它当成一个网络工作者。索引运行在一个后台线程上–在这里你可以找到完整的源代码。你也会发现到词干算法和我的停用词列表中的源代码参考。至于代码许可,还是一如既往为教育目的而免费,而不用于任何商业目的。

最后是演示。一旦索引完成,尝试寻找随机的东西和短语,维基百科会知道的。注意,只有40000段的索引,所以你可能要尝试一些新的话题。


以上是具體介紹JavaScript全文搜尋之相關度評分的範例程式碼的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
JavaScript和Web:核心功能和用例JavaScript和Web:核心功能和用例Apr 18, 2025 am 12:19 AM

JavaScript在Web開發中的主要用途包括客戶端交互、表單驗證和異步通信。 1)通過DOM操作實現動態內容更新和用戶交互;2)在用戶提交數據前進行客戶端驗證,提高用戶體驗;3)通過AJAX技術實現與服務器的無刷新通信。

了解JavaScript引擎:實施詳細信息了解JavaScript引擎:實施詳細信息Apr 17, 2025 am 12:05 AM

理解JavaScript引擎內部工作原理對開發者重要,因為它能幫助編寫更高效的代碼並理解性能瓶頸和優化策略。 1)引擎的工作流程包括解析、編譯和執行三個階段;2)執行過程中,引擎會進行動態優化,如內聯緩存和隱藏類;3)最佳實踐包括避免全局變量、優化循環、使用const和let,以及避免過度使用閉包。

Python vs. JavaScript:學習曲線和易用性Python vs. JavaScript:學習曲線和易用性Apr 16, 2025 am 12:12 AM

Python更適合初學者,學習曲線平緩,語法簡潔;JavaScript適合前端開發,學習曲線較陡,語法靈活。 1.Python語法直觀,適用於數據科學和後端開發。 2.JavaScript靈活,廣泛用於前端和服務器端編程。

Python vs. JavaScript:社區,圖書館和資源Python vs. JavaScript:社區,圖書館和資源Apr 15, 2025 am 12:16 AM

Python和JavaScript在社區、庫和資源方面的對比各有優劣。 1)Python社區友好,適合初學者,但前端開發資源不如JavaScript豐富。 2)Python在數據科學和機器學習庫方面強大,JavaScript則在前端開發庫和框架上更勝一籌。 3)兩者的學習資源都豐富,但Python適合從官方文檔開始,JavaScript則以MDNWebDocs為佳。選擇應基於項目需求和個人興趣。

從C/C到JavaScript:所有工作方式從C/C到JavaScript:所有工作方式Apr 14, 2025 am 12:05 AM

從C/C 轉向JavaScript需要適應動態類型、垃圾回收和異步編程等特點。 1)C/C 是靜態類型語言,需手動管理內存,而JavaScript是動態類型,垃圾回收自動處理。 2)C/C 需編譯成機器碼,JavaScript則為解釋型語言。 3)JavaScript引入閉包、原型鍊和Promise等概念,增強了靈活性和異步編程能力。

JavaScript引擎:比較實施JavaScript引擎:比較實施Apr 13, 2025 am 12:05 AM

不同JavaScript引擎在解析和執行JavaScript代碼時,效果會有所不同,因為每個引擎的實現原理和優化策略各有差異。 1.詞法分析:將源碼轉換為詞法單元。 2.語法分析:生成抽象語法樹。 3.優化和編譯:通過JIT編譯器生成機器碼。 4.執行:運行機器碼。 V8引擎通過即時編譯和隱藏類優化,SpiderMonkey使用類型推斷系統,導致在相同代碼上的性能表現不同。

超越瀏覽器:現實世界中的JavaScript超越瀏覽器:現實世界中的JavaScriptApr 12, 2025 am 12:06 AM

JavaScript在現實世界中的應用包括服務器端編程、移動應用開發和物聯網控制:1.通過Node.js實現服務器端編程,適用於高並發請求處理。 2.通過ReactNative進行移動應用開發,支持跨平台部署。 3.通過Johnny-Five庫用於物聯網設備控制,適用於硬件交互。

使用Next.js(後端集成)構建多租戶SaaS應用程序使用Next.js(後端集成)構建多租戶SaaS應用程序Apr 11, 2025 am 08:23 AM

我使用您的日常技術工具構建了功能性的多租戶SaaS應用程序(一個Edtech應用程序),您可以做同樣的事情。 首先,什麼是多租戶SaaS應用程序? 多租戶SaaS應用程序可讓您從唱歌中為多個客戶提供服務

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
1 個月前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
1 個月前By尊渡假赌尊渡假赌尊渡假赌
威爾R.E.P.O.有交叉遊戲嗎?
1 個月前By尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

SecLists

SecLists

SecLists是最終安全測試人員的伙伴。它是一個包含各種類型清單的集合,這些清單在安全評估過程中經常使用,而且都在一個地方。 SecLists透過方便地提供安全測試人員可能需要的所有列表,幫助提高安全測試的效率和生產力。清單類型包括使用者名稱、密碼、URL、模糊測試有效載荷、敏感資料模式、Web shell等等。測試人員只需將此儲存庫拉到新的測試機上,他就可以存取所需的每種類型的清單。

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

強大的PHP整合開發環境