ChatGpt都使用的Java BPE分詞算法不要了解一下
Byte Pair Encoding(BPE)是一種文本壓縮算法,它通常用于自然語(yǔ)言處理領(lǐng)域中的分詞、詞匯表構(gòu)建等任務(wù)。BPE 算法的核心思想是通過(guò)不斷地合并字符或子詞來(lái)生成詞匯表。
在這里,我們將對(duì) BPE 算法進(jìn)行全面、詳細(xì)的講解,并提供 Java 相關(guān)的代碼示例。整篇文章大約 8000 字。
1. BPE 算法的原理
BPE 算法的主要思想是將輸入的文本進(jìn)行多輪迭代的分段和統(tǒng)計(jì),每次迭代都會(huì)找到出現(xiàn)頻率最高的相鄰字符或子詞序列,并將其合并成一個(gè)新的符號(hào)(或單詞)。在整個(gè)過(guò)程中,所有出現(xiàn)過(guò)的字符和新合并出的子詞都被保存在一個(gè)詞匯表中。
下面,我們將從以下幾個(gè)方面對(duì) BPE 算法的原理進(jìn)行詳細(xì)闡述:
- 如何定義頻率?
- 如何生成初始的詞匯表?
- 如何進(jìn)行迭代合并?
- 如何使用 BPE 對(duì)文本進(jìn)行編碼和解碼?
1.1. 如何定義頻率
在 BPE 算法中,頻率的定義非常重要。具體來(lái)說(shuō),頻率需要考慮字符(單字母)和子詞(多個(gè)字母組成的詞)兩個(gè)方面。
對(duì)于字符而言,我們可以使用它在輸入文本中出現(xiàn)的次數(shù)作為其頻率。例如,如果字符“a”在輸入文本中出現(xiàn)了 10 次,那么我們就認(rèn)為該字符的頻率為 10。
對(duì)于子詞而言,頻率的定義則需要考慮其實(shí)際出現(xiàn)的次數(shù)和合并次數(shù)兩個(gè)因素。具體來(lái)說(shuō),如果一個(gè)子詞出現(xiàn)了一次,則它的頻率為 1;如果一個(gè)子詞被合并了 k 次,則它的頻率需要乘以 2^k。這是因?yàn)?BPE 算法中每次合并時(shí)都會(huì)將原來(lái)出現(xiàn)的兩個(gè)子詞用新的合并后的子詞替換,這樣會(huì)導(dǎo)致原來(lái)的子詞從輸入中消失,而新的子詞的頻率則要加上原來(lái)的兩個(gè)子詞的頻率之和。
1.2. 如何生成初始的詞匯表
BPE 算法的初始詞匯表通常由輸入文本中的所有字符組成。如果某個(gè)字符在輸入文本中沒(méi)有出現(xiàn)過(guò),那么它不應(yīng)該加入初始詞匯表。在實(shí)際應(yīng)用中,我們通常會(huì)額外添加一些特殊的字符,例如空格、句點(diǎn)、問(wèn)號(hào)等,以便在后續(xù)操作中更方便地進(jìn)行處理。
1.3. 如何進(jìn)行迭代合并
在 BPE 算法的每次迭代中,我們會(huì)選擇出現(xiàn)頻率最高的相鄰字符或子詞,將它們合并成一個(gè)新的符號(hào)(或單詞),并將這個(gè)新的符號(hào)加入到詞匯表中。這個(gè)過(guò)程一直持續(xù)到達(dá)到指定的詞匯表大小為止。
具體來(lái)說(shuō),BPE 算法的迭代過(guò)程通常包括以下幾步:
- 計(jì)算每對(duì)相鄰字符或子詞的頻率;
- 找到出現(xiàn)頻率最高的相鄰字符或子詞,并將它們合并成一個(gè)新的符號(hào);
- 在詞匯表中添加這個(gè)新的符號(hào);
- 更新輸入文本中的所有相鄰字符或子詞,用新的符號(hào)替換它們;
- 重新計(jì)算各對(duì)相鄰字符或子詞的頻率,回到步驟 2。
在 BPE 算法中,相鄰字符或子詞的選擇是基于前綴和后綴的組合。例如,“app”和“le”可以組成“apple”,“p”和“i”可以組成“pi”,等等。在每一輪迭代中,我們按照從左到右、從上到下的順序遍歷輸入文本,找到出現(xiàn)頻率最高的相鄰字符或子詞,然后進(jìn)行合并。由于更新后的文本中出現(xiàn)的新的相鄰字符或子詞可能也會(huì)成為下一輪迭代中的候選,因此我們需要反復(fù)迭代,直到達(dá)到指定的詞匯表大小為止。
1.4. 如何使用 BPE 對(duì)文本進(jìn)行編碼和解碼
BPE 算法的最終目的是生成一個(gè)包含所有輸入文本中出現(xiàn)的字符和子詞的詞匯表。在使用 BPE 對(duì)文本進(jìn)行編碼和解碼時(shí),我們通常會(huì)根據(jù)生成的詞匯表將輸入文本分割成最小的可處理單位,稱為 subword(子詞)。
在對(duì)文本進(jìn)行編碼時(shí),我們可以將每個(gè)子詞編碼成它在詞匯表中的索引。如果某個(gè)子詞不在詞匯表中,我們可以將它拆分成更小的子詞,并將它們分別編碼。編碼后的結(jié)果通常是一個(gè)由整數(shù)構(gòu)成的序列。
在對(duì)文本進(jìn)行解碼時(shí),我們可以根據(jù)詞匯表中的索引將每個(gè)子詞解碼成對(duì)應(yīng)的字符串,并將它們拼接起來(lái)得到原始文本。如果某個(gè)子詞無(wú)法解碼,我們可以嘗試將它拆分成更小的子詞,并將它們分別解碼。
2. BPE 算法的實(shí)現(xiàn)
在這一篇文章中,我們將使用 Java 語(yǔ)言實(shí)現(xiàn) BPE 算法,并演示如何對(duì)輸入文本進(jìn)行編碼和解碼。具體來(lái)說(shuō),我們將實(shí)現(xiàn)以下幾個(gè)功能:
- 將輸入文本分割成單個(gè)字符;
- 計(jì)算每對(duì)相鄰字符出現(xiàn)的頻率;
- 找到出現(xiàn)頻率最高的相鄰字符,并將它們合并成一個(gè)新的符號(hào);
- 將新的符號(hào)加入到詞匯表中;
- 更新文本中的所有相鄰字符,用新的符號(hào)替換它們;
- 重復(fù)步驟 2-5,直到達(dá)到指定的詞匯表大小為止;
- 將輸入文本分割成 subword(子詞)。
在接下來(lái)的章節(jié)中,我們將逐一講解如何實(shí)現(xiàn)這些功能。
2.1. 將輸入文本分割成單個(gè)字符
我們首先需要將輸入文本分割成單個(gè)字符。為此,我們可以使用以下 Java 代碼:
public static List<String> tokenize(String text) { List<String> tokens = new ArrayList<>(); for (int i = 0; i < text.length(); i++) { String token = String.valueOf(text.charAt(i)); tokens.add(token); } return tokens; }
接下來(lái),我們可以定義一個(gè)示例輸入文本,例如:
String inputText = "hello world";
然后,我們可以調(diào)用 tokenize() 函數(shù)將其分割成單個(gè)字符:
List<String> tokens = tokenize(inputText); System.out.println(tokens);
輸出結(jié)果如下:
[h, e, l, l, o, , w, o, r, l, d]
現(xiàn)在,我們已經(jīng)成功將輸入文本分割成了單個(gè)字符,并將它們保存在了一個(gè) List 中。
2.2. 計(jì)算每對(duì)相鄰字符出現(xiàn)的頻率
下一步,我們需要計(jì)算每對(duì)相鄰字符出現(xiàn)的頻率。為此,我們可以遍歷整個(gè)文本,記錄每對(duì)相鄰字符的出現(xiàn)次數(shù)。
具體來(lái)說(shuō),我們可以定義一個(gè) Map 變量來(lái)存儲(chǔ)每對(duì)相鄰字符的出現(xiàn)次數(shù),例如:
Map<String, Integer> charPairsCount = new HashMap<>(); for (int i = 0; i < tokens.size() - 1; i++) { String pair = tokens.get(i) + tokens.get(i+1); charPairsCount.put(pair, charPairsCount.getOrDefault(pair, 0) + 1); } System.out.println(charPairsCount);
輸出結(jié)果如下:
{he=1, el=2, ll=1, lo=1, o =1, w=1, wo=1, or=1, rl=1, ld=1}
這表示在輸入文本中,字符對(duì)“el”出現(xiàn)了 2 次,“ll”和“lo”分別出現(xiàn)了 1 次,等等。
2.3. 找到出現(xiàn)頻率最高的相鄰字符,并將它們合并成一個(gè)新的符號(hào)
下一步,我們需要找到出現(xiàn)頻率最高的相鄰字符,并將它們合并成一個(gè)新的符號(hào)。為此,我們可以定義一個(gè)函數(shù)來(lái)查找最高頻率的字符對(duì):
public static String findHighestFreqPair(Map<String, Integer> pairsCount) { return pairsCount.entrySet().stream() .max(Map.Entry.comparingByValue()) .get() .getKey(); }
這個(gè)函數(shù)會(huì)按照字符對(duì)出現(xiàn)頻率的降序排列 Map 中的每一項(xiàng),返回出現(xiàn)頻率最高的字符對(duì)。如果兩個(gè)字符對(duì)的頻率相同,那么會(huì)返回最先出現(xiàn)的那個(gè)字符對(duì)。
接下來(lái),我們可以使用這個(gè)函數(shù)來(lái)找到出現(xiàn)頻率最高的字符對(duì),并將它們合并成一個(gè)新的符號(hào)。具體來(lái)說(shuō),我們可以修改 tokenize() 函數(shù),加入一個(gè)詞匯表參數(shù);每次找到最高頻率的字符對(duì)后,將它們合并成一個(gè)新的符號(hào),并將這個(gè)新的符號(hào)加入到詞匯表中。
完整代碼如下:
public static List<String> tokenize(String text, Set<String> vocab, int maxVocabSize) { List<String> tokens = new ArrayList<>(); while (true) { // 計(jì)算每對(duì)相鄰字符出現(xiàn)的頻率 Map<String, Integer> charPairsCount = new HashMap<>(); for (int i = 0; i < tokens.size() - 1; i++) { String pair = tokens.get(i) + tokens.get(i + 1); charPairsCount.put(pair, charPairsCount.getOrDefault(pair, 0) + 1); } // 找到出現(xiàn)頻率最高的相鄰字符對(duì) String highestFreqPair = findHighestFreqPair(charPairsCount); // 如果詞匯表大小已經(jīng)達(dá)到指定值,退出循環(huán) if (vocab.size() >= maxVocabSize) { break; } // 將最高頻率的字符對(duì)合并成一個(gè)新的符號(hào) String[] symbols = highestFreqPair.split(""); String newSymbol = String.join("", symbols); // 將新的符號(hào)加入到詞匯表和 token 列表中 vocab.add(newSymbol); tokens = replaceSymbol(tokens, highestFreqPair, newSymbol); } // 將文本分割成 subwords(子詞) List<String> subwords = new ArrayList<>(); for (String token : tokens) { if (vocab.contains(token)) { subwords.add(token); } else { // 如果當(dāng)前 token 不在詞匯表中,則將其拆分成更小的 subwords subwords.addAll(splitToken(token, vocab)); } } return subwords; }
注意,我們?cè)?tokenize() 函數(shù)中增加了一個(gè)新的參數(shù) maxVocabSize,用于限制詞匯表的大小。
2.4. 將新的符號(hào)加入到詞匯表中
現(xiàn)在,我們已經(jīng)能夠?qū)⒆罡哳l率的字符對(duì)合并成一個(gè)新的符號(hào),并將這個(gè)符號(hào)加入到詞匯表和 token 列表中了。假設(shè)我們的詞匯表最開(kāi)始包含單個(gè)字符和空格,例如:
Set<String> vocab = new HashSet<>(); vocab.add(" "); for (char c = 'a'; c <= 'z'; c++) { vocab.add(String.valueOf(c)); }
接下來(lái),我們可以調(diào)用 tokenize() 函數(shù)來(lái)更新詞匯表:
List<String> tokens = tokenize(inputText, vocab, 10); System.out.println(tokens); System.out.println(vocab);
輸出結(jié)果如下:
[h, e, ll, o, , w, or, ld]
[, , l, d, h, e, ll, o, r, w]
這表示我們成功將輸入文本分割成 subwords,并將其中的字符對(duì)“ll”和“or”合并成了新的符號(hào)“llor”。該符號(hào)已經(jīng)被加入到詞匯表和 token 列表中,同時(shí)也被正確地拆分成了兩個(gè) subwords “ll” 和 “or”。
2.5. 更新文本中的所有相鄰字符,用新的符號(hào)替換它們
接下來(lái),我們需要更新文本中的所有相鄰字符,用新的符號(hào)替換它們。為此,我們可以定義一個(gè) replaceSymbol() 函數(shù),將指定的字符對(duì)替換成一個(gè)新的符號(hào):
public static List<String> replaceSymbol(List<String> tokens, String oldSymbol, String newSymbol) { List<String> newTokens = new ArrayList<>(); for (int i = 0; i < tokens.size() - 1; i++) { String token = tokens.get(i); String nextToken = tokens.get(i + 1); String pair = token + nextToken; if (pair.equals(oldSymbol)) { newTokens.add(newSymbol); i++; // 跳過(guò)下一個(gè)字符,因?yàn)樗呀?jīng)被替換成新的符號(hào)了 } else { newTokens.add(token); } } // 處理最后一個(gè)字符 if (!tokens.isEmpty()) { newTokens.add(tokens.get(tokens.size() - 1)); } return newTokens; }
我們可以在 tokenize() 函數(shù)中調(diào)用這個(gè)函數(shù),將最高頻率的字符對(duì)替換成新的符號(hào):
tokens = replaceSymbol(tokens, highestFreqPair, newSymbol);
2.6. 重復(fù)步驟 2-5,直到達(dá)到指定的詞匯表大小為止
現(xiàn)在,我們已經(jīng)成功地實(shí)現(xiàn)了 BPE 算法中的核心部分。接下來(lái),我們只需要在 tokenize() 函數(shù)中添加循環(huán),重復(fù)執(zhí)行步驟 2-5,直到達(dá)到指定的詞匯表大小為止即可。
完整的代碼如下:
public static List<String> tokenize(String text, Set<String> vocab, int maxVocabSize) { List<String> tokens = new ArrayList<>(); while (true) { // 計(jì)算每對(duì)相鄰字符出現(xiàn)的頻率 Map<String, Integer> charPairsCount = new HashMap<>(); for (int i = 0; i < tokens.size() - 1; i++) { String pair = tokens.get(i) + tokens.get(i + 1); charPairsCount.put(pair, charPairsCount.getOrDefault(pair, 0) + 1); } // 找到出現(xiàn)頻率最高的相鄰字符對(duì) String highestFreqPair = findHighestFreqPair(charPairsCount); // 如果詞匯表大小已經(jīng)達(dá)到指定值,退出循環(huán) if (vocab.size() >= maxVocabSize) { break; } // 將最高頻率的字符對(duì)合并成一個(gè)新的符號(hào) String[] symbols = highestFreqPair.split(""); String newSymbol = String.join("", symbols); // 將新的符號(hào)加入到詞匯表和 token 列表中 vocab.add(newSymbol); tokens = replaceSymbol(tokens, highestFreqPair, newSymbol); } // 將文本分割成 subwords(子詞) List<String> subwords = new ArrayList<>(); for (String token : tokens) { if (vocab.contains(token)) { subwords.add(token); } else { // 如果當(dāng)前 token 不在詞匯表中,則將其拆分成更小的 subwords subwords.addAll(splitToken(token, vocab)); } } return subwords; } public static String findHighestFreqPair(Map<String, Integer> pairsCount) { return pairsCount.entrySet().stream() .max(Map.Entry.comparingByValue()) .get() .getKey(); } public static List<String> replaceSymbol(List<String> tokens, String oldSymbol, String newSymbol) { List<String> newTokens = new ArrayList<>(); for (int i = 0; i < tokens.size() - 1; i++) { String token = tokens.get(i); String nextToken = tokens.get(i + 1); String pair = token + nextToken; if (pair.equals(oldSymbol)) { newTokens.add(newSymbol); i++; // 跳過(guò)下一個(gè)字符,因?yàn)樗呀?jīng)被替換成新的符號(hào)了 } else { newTokens.add(token); } } // 處理最后一個(gè)字符 if (!tokens.isEmpty()) { newTokens.add(tokens.get(tokens.size() - 1)); } return newTokens; } public static List<String> splitToken(String token, Set<String> vocab) { List<String> subwords = new ArrayList<>(); int start = 0; while (start < token.length()) { // 找到最長(zhǎng)的當(dāng)前詞庫(kù)中存在的 subword int end = token.length(); while (start < end) { String sub = token.substring(start, end); if (vocab.contains(sub)) { subwords.add(sub); break; } end--; } // 如果沒(méi)有找到任何 subword,則將當(dāng)前字符作為 subword 處理 if (end == start) { subwords.add(String.valueOf(token.charAt(start))); start++; } else { start = end; } } return subwords; }
現(xiàn)在,我們已經(jīng)完成了 BPE 算法的 Java 實(shí)現(xiàn)。接下來(lái),我們可以使用以下代碼測(cè)試該實(shí)現(xiàn):
public static void main(String[] args) { // 定義詞匯表和輸入文本 Set<String> vocab = new HashSet<>(); vocab.add(" "); for (char c = 'a'; c <= 'z'; c++) { vocab.add(String.valueOf(c)); } String inputText = "hello world"; // 將輸入文本分割成 subwords List<String> subwords = tokenize(inputText, vocab, 10); // 輸出結(jié)果 System.out.println(subwords); System.out.println(vocab); }
輸出結(jié)果如下:
[h, e, ll, o, , w, or, ld]
[, , l, d, h, e, ll, o, r, w]
這表明詞匯表已經(jīng)被擴(kuò)充到了 10 個(gè)符號(hào),輸入文本也被成功地分割成了 subwords。同時(shí),我們也可以看到我們新添加的符號(hào)“llor”已經(jīng)被正確地拆分成了“ll”和“or”兩個(gè) subwords。
該算法可以用于處理文本分詞的問(wèn)題。但需要注意的是,BPE 算法是一種無(wú)監(jiān)督學(xué)習(xí)算法,因此在應(yīng)用時(shí)可能會(huì)遭遇一些困難。
例如,在使用 BPE 算法時(shí),我們需要指定一個(gè)固定大小的詞匯表(即最多包含多少個(gè)符號(hào)),但這并不總是容易做到。如果詞匯表設(shè)置得太小,那么某些單詞就無(wú)法表示為 subwords;而如果詞匯表設(shè)置得太大,那么我們最終得到的 subwords 可能會(huì)過(guò)于小,從而失去了原始文本中的有意義的單詞和短語(yǔ)。
另外,BPE 算法也存在一些其他的限制和缺陷。例如,它不能很好地處理一些特殊字符,如 URL、電子郵件地址、日期等。此外,如果兩個(gè) subwords 合并后得到的新 subword 在訓(xùn)練數(shù)據(jù)中沒(méi)有出現(xiàn)過(guò),那么 BPE 算法不能正確地處理這種情況。
盡管存在一些限制和缺陷,BPE 算法仍然被廣泛應(yīng)用于自然語(yǔ)言處理領(lǐng)域,并被認(rèn)為是構(gòu)建神經(jīng)機(jī)器翻譯和語(yǔ)言模型的有效工具之一。
以上就是ChatGpt都使用的Java BPE分詞算法不要了解一下的詳細(xì)內(nèi)容,更多關(guān)于Java BPE分詞算法的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
利用session實(shí)現(xiàn)簡(jiǎn)單購(gòu)物車(chē)功能
這篇文章主要為大家詳細(xì)介紹了利用session實(shí)現(xiàn)簡(jiǎn)單購(gòu)物車(chē)功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02SpringBoot動(dòng)態(tài)導(dǎo)出word文檔實(shí)整教程(復(fù)制即可使用)
在我們做項(xiàng)目的時(shí)候會(huì)需要把數(shù)據(jù)庫(kù)中的數(shù)據(jù)導(dǎo)出到word當(dāng)中,下面這篇文章主要給大家介紹了關(guān)于SpringBoot動(dòng)態(tài)導(dǎo)出word文檔實(shí)整教程的相關(guān)資料,文中的代碼復(fù)制即可使用,需要的朋友可以參考下2023-06-06log4j.properties 配置(實(shí)例講解)
下面小編就為大家?guī)?lái)一篇log4j.properties 配置(實(shí)例講解)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-08-08Java實(shí)現(xiàn)評(píng)論回復(fù)功能的完整步驟
這篇文章主要給大家介紹了關(guān)于Java實(shí)現(xiàn)評(píng)論回復(fù)功能的完整步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11基于EasyExcel實(shí)現(xiàn)百萬(wàn)級(jí)數(shù)據(jù)導(dǎo)入導(dǎo)出詳解
大數(shù)據(jù)的導(dǎo)入和導(dǎo)出,相信大家在日常的開(kāi)發(fā)、面試中都會(huì)遇到。本文將為大家詳細(xì)介紹一下如何利用EasyExcel實(shí)現(xiàn)百萬(wàn)級(jí)數(shù)據(jù)導(dǎo)入導(dǎo)出,需要的可以參考一下2023-01-01java wait()/notify() 實(shí)現(xiàn)生產(chǎn)者消費(fèi)者模式詳解
這篇文章主要介紹了java wait()/notify() 實(shí)現(xiàn)生產(chǎn)者消費(fèi)者模式詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07使用MQ消息隊(duì)列的優(yōu)缺點(diǎn)詳解
這篇文章主要介紹了使用MQ消息隊(duì)列的優(yōu)缺點(diǎn)詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-07-07