日本熟妇hd丰满老熟妇,中文字幕一区二区三区在线不卡 ,亚洲成片在线观看,免费女同在线一区二区

進階使用

前置知識

BM25簡介

BM25算法(Best Matching 25)是一種廣泛用于信息檢索領域的排名函數,用于在給定查詢(Query)時對一組文檔(Document)進行評分和排序。BM25在計算Query和Document之間的相似度時,本質上是依次計算Query中每個單詞和Document的相關性,然后對每個單詞的相關性進行加權求和。BM25算法一般可以表示為如下形式:

image.svg

上式中,q d 分別表示用來計算相似度的Query和Document, qi 表示 q 的第 i 個單詞,R(qi, d) 表示單詞 qi 和文檔 d 的相關性,Wi 表示單詞 qi 的權重,計算得到的 score(q, d) 表示 q d 的相關性得分,得分越高表示 q d 越相似。Wi R(qi, d) 一般可以表示為如下形式:

image.svg

image.svg

image.svg

其中,N 表示總文檔數,N(qi) 表示包含單詞 qi 的文檔數,tf(qi, d) 表示qi 在文檔 d 中的詞頻,Ld 表示文檔 d 的長度,Lavg 表示平均文檔長度,k1 b 是分別用來控制 tf(qi, d) Ld 對得分影響的超參數。

稀疏向量生成

在檢索場景中,為了讓BM25算法的Score方便進行計算,通常分別對Document和Query進行編碼,然后通過點積的方式計算出兩者的相似度。得益于BM25原理的特性,其原生支持將Score拆分為兩部分Sparse Vector,DashText提供了encode_document以及encode_query兩個接口來分別實現這兩部分向量的生成,其生成鏈路如下圖所示:

image.png

最終生成的稀疏向量可表示為:

image.svg

image.svg

Score/距離計算

生成dq的稀疏向量后,就可以通過簡單的點積進行距離計算,即將相同單詞上的值對應相乘再求和,通過稀疏向量計算距離的方式如下所示:

image.svg

上述計算方式本質上是通過點積來計算的,score 越大表示越相似,如果需要結合Dense Vector一起進行距離度量時,需要對齊距離度量方式。也就是說,在結合Dense Vector+Sparse Vector的場景中,距離計算只支持點積度量方式。

如何自訓練模型

考慮到內置的BM25 Model是基于通用語料(中文Wiki語料)訓練得到,在特定領域下通常不能表現出最佳的效果。因此,在一些特定場景下,通常建議訓練自定義BM25模型。使用DashText來訓練自定義模型時一般需要遵循以下步驟:

Step1:確認使用場景

當準備使用SparseVector來進行信息檢索時,應提前考慮當前場景下的Query以及Document來源,通常需要提前準備好一定數量Document來入庫,這些Document通常需要和特定的業務場景直接相關。

Step2:準備語料

根據BM25原理,語料直接決定了BM25模型的參數。通常應按照以下幾個原則來準備語料:

  • 語料來源應盡可能反映對應場景的特性,盡可能讓 N(qi) 能夠反映對應真實場景的詞頻信息。

  • 調節合理的語料切片長度和切片數量,避免出現語料當中只有少量長文本的情況。

一般情況下,如無特殊要求或限制,可以直接將Step1準備的一系列Document組織為語料即可。

Step3:準備Tokenizer

Tokenizer決定了分詞的結果,分詞的結果則直接影響Sparse Vector的生成,在特定領域下使用自定義Tokenizer會達到更好的效果。DashText提供了兩種擴展Tokenizer的方式:

  • 使用自定義詞表:DashText內置的Jieba Tokenizer支持傳入自定義詞表。(Java SDK暫不支持該功能)

from dashtext import TextTokenizer, SparseVectorEncoder

my_tokenizer = TextTokenizer.from_pretrained(model_name='Jieba', dict='dict.txt')
my_encoder = SparseVectorEncoder(tokenize_function=my_tokenizer.tokenize)
  • 使用自定義Tokenizer:DashText支持任務自定義的Tokenizer,只需提供一個符合Callable[[str], List[str]]簽名的Tokenize函數即可。

from dashtext import SparseVectorEncoder
from transformers import BertTokenizer

my_tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
my_encoder = SparseVectorEncoder(tokenize_function=my_tokenizer.tokenize)
import com.aliyun.dashtext.common.DashTextException;
import com.aliyun.dashtext.common.ErrorCode;
import com.aliyun.dashtext.encoder.SparseVectorEncoder;
import com.aliyun.dashtext.tokenizer.BaseTokenizer;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class Main {
    public static class MyTokenizer implements BaseTokenizer {
        @Override
        public List<String> tokenize(String s) throws DashTextException {
            if (s == null) {
                throw new DashTextException(ErrorCode.INVALID_ARGUMENT);
            }

            // 使用正則表達式將文本按空白符和標點符號分割,并轉換為小寫
            return Arrays.stream(s.split("\\s+|(?<!\\d)[.,](?!\\d)"))
                    .map(String::toLowerCase)
                    .filter(token -> !token.isEmpty())   // 過濾掉空字符串
                    .collect(Collectors.toList());
        }
    }

    public static void main(String[] args) {
        SparseVectorEncoder encoder = new SparseVectorEncoder(new MyTokenizer());
    }
}

Step4:訓練模型

實際上,這里的“訓練”本質上是一個“統計”參數的過程。由于訓練自定義模型的過程中包含著大量Tokenizing/Hashing過程,所以可能會耗費一定的時間。DashText提供了SparseVectorEncoder.train接口可以用來訓練模型。

Step5:調參優化(可選)

模型訓練完成后,可以準備部分驗證數據集以及通過微調 k1 b 來達到最佳的召回效果。調節k1和b一般需要遵循以下原則:

  • 調節k1 (1.2 < k1 < 2)可控制Document詞頻對Score的影響,k1 越大Document的詞頻對Score的貢獻越小。

  • 調節b (0 < b < 1)可控制文檔長度對Score的影響,b 越大表示文檔長度對權重的影響越大

一般情況下,如無特殊要求或限制,不需要調整 k1 b

Step6:Finetune模型(可選)

實際場景下,可能會存在需要補充訓練語料來增量式地更新BM25模型參數的情況。DashText的SparseVectorEncoder.train接口原生支持模型的增量更新。需要注意的是,模型更改之后,使用舊模型進行編碼并已入庫的向量就失去了時效性,一般需要重新入庫。

示例代碼

以下是一個簡單完整的自訓練模型示例。

from dashtext import SparseVectorEncoder
from pydantic import BaseModel
from typing import Dict, List


class Result(BaseModel):
    doc: str
    score: float


def calculate_score(query_vector: Dict[int, float], document_vector: Dict[int, float]) -> float:
    score = 0.0
    for key, value in query_vector.items():
        if key in document_vector:
            score += value * document_vector[key]
    return score


# 創建空SparseVectorEncoder(可以設置自定義Tokenizer)
encoder = SparseVectorEncoder()


# step1: 準備語料以及Documents
corpus_document: List[str] = [
    "The quick brown fox rapidly and agilely leaps over the lazy dog that lies idly by the roadside.",
    "Never jump over the lazy dog quickly",
    "A fox is quick and jumps over dogs",
    "The quick brown fox",
    "Dogs are domestic animals",
    "Some dog breeds are quick and jump high",
    "Foxes are wild animals and often have a brown coat",
]


# step2: 訓練BM25 Model
encoder.train(corpus_document)


# step3: 調參優化BM25 Model
query: str = "quick brown fox"
print(f"query: {query}")
k1s = [1.0, 1.5]
bs = [0.5, 0.75]
for k1, b in zip(k1s, bs):
    print(f"current k1: {k1}, b: {b}")
    encoder.b = b
    encoder.k1 = k1
    query_vector = encoder.encode_queries(query)
    results: List[Result] = []
    for idx, doc in enumerate(corpus_document):
        doc_vector = encoder.encode_documents(doc)
        score = calculate_score(query_vector, doc_vector)
        results.append(Result(doc=doc, score=score))
    results.sort(key=lambda r: r.score, reverse=True)

    for result in results:
        print(result)


# step4: 選擇最優參數并保存模型
encoder.b = 0.75
encoder.k1 = 1.5
encoder.dump("./model.json")


# step5: 后續使用時可以加載模型
new_encoder = SparseVectorEncoder()
bm25_model_path = "./model.json"
new_encoder.load(bm25_model_path)


# step6: 對模型進行finetune并保存
extra_corpus: List[str] = [
    "The fast fox jumps over the lazy, chubby dog",
    "A swift fox hops over a napping old dog",
    "The quick fox leaps over the sleepy, plump dog",
    "The agile fox jumps over the dozing, heavy-set dog",
    "A speedy fox vaults over a lazy, old dog lying in the sun"
]

new_encoder.train(extra_corpus)
new_bm25_model_path = "new_model.json"
new_encoder.dump(new_bm25_model_path)
import com.aliyun.dashtext.encoder.SparseVectorEncoder;

import java.io.*;
import java.util.*;

public class Main {

    public static class Result {
        public String doc;
        public float score;

        public Result(String doc, float score) {
            this.doc = doc;
            this.score = score;
        }

        @Override
        public String toString() {
            return String.format("Result(doc=%s, score=%f)", doc, score);
        }
    }

    public static float calculateScore(Map<Long, Float> queryVector, Map<Long, Float> documentVector) {
        float score = 0.0f;
        for (Map.Entry<Long, Float> entry : queryVector.entrySet()) {
            if (documentVector.containsKey(entry.getKey())) {
                score += entry.getValue() * documentVector.get(entry.getKey());
            }
        }
        return score;
    }

    public static void main(String[] args) throws IOException {
        // 創建空SparseVectorEncoder(可以設置自定義Tokenizer)
        SparseVectorEncoder encoder = new SparseVectorEncoder();

        // step1: 準備語料以及Documents
        List<String> corpusDocument = Arrays.asList(
                "The quick brown fox rapidly and agilely leaps over the lazy dog that lies idly by the roadside.",
                "Never jump over the lazy dog quickly",
                "A fox is quick and jumps over dogs",
                "The quick brown fox",
                "Dogs are domestic animals",
                "Some dog breeds are quick and jump high",
                "Foxes are wild animals and often have a brown coat"
        );

        // step2: 訓練BM25 Model
        encoder.train(corpusDocument);

        // step3: 調參優化BM25 Model
        String query = "quick brown fox";
        System.out.println("query: " + query);
        float[] k1s = {1.0f, 1.5f};
        float[] bs = {0.5f, 0.75f};
        for (int i = 0; i < k1s.length; i++) {
            float k1 = k1s[i];
            float b = bs[i];
            System.out.println("current k1: " + k1 + ", b: " + b);
            encoder.setB(b);
            encoder.setK1(k1);

            Map<Long, Float> queryVector = encoder.encodeQueries(query);
            List<Result> results = new ArrayList<>();
            for (String doc : corpusDocument) {
                Map<Long, Float> docVector = encoder.encodeDocuments(doc);
                float score = calculateScore(queryVector, docVector);
                results.add(new Result(doc, score));
            }

            results.sort((r1, r2) -> Float.compare(r2.score, r1.score));

            for (Result result : results) {
                System.out.println(result);
            }
        }

        // step4: 選擇最優參數并保存模型
        encoder.setB(0.75f);
        encoder.setK1(1.5f);
        encoder.dump("./model.json");

        // step5: 后續使用時可以加載模型
        SparseVectorEncoder newEncoder = new SparseVectorEncoder();
        newEncoder.load("./model.json");

        // step6: 對模型進行finetune并保存
        List<String> extraCorpus = Arrays.asList(
                "The fast fox jumps over the lazy, chubby dog",
                "A swift fox hops over a napping old dog",
                "The quick fox leaps over the sleepy, plump dog",
                "The agile fox jumps over the dozing, heavy-set dog",
                "A speedy fox vaults over a lazy, old dog lying in the sun"
        );
        newEncoder.train(extraCorpus);
        newEncoder.dump("./new_model.json");
    }
}

API參考

DashText API詳情可參考:https://pypi.org/project/dashtext/