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ù)生成的詞匯表將輸入文本分割成最小的可處理單位,稱(chēng)為 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)文章
Java 進(jìn)階使用 Lambda 表達(dá)式實(shí)現(xiàn)超強(qiáng)的排序功能
今天要說(shuō)的是第二種排序方式,在內(nèi)存中實(shí)現(xiàn)數(shù)據(jù)排序。這篇文章主要介紹了Java 進(jìn)階使用 Lambda 表達(dá)式實(shí)現(xiàn)超強(qiáng)的排序功能,需要的朋友可以參考下2021-11-11
Java?通過(guò)手寫(xiě)分布式雪花SnowFlake生成ID方法詳解
SnowFlake是twitter公司內(nèi)部分布式項(xiàng)目采用的ID生成算法,開(kāi)源后廣受?chē)?guó)內(nèi)大廠的好評(píng)。由這種算法生成的ID,我們就叫做SnowFlakeID,下面我們來(lái)詳細(xì)看看2022-04-04
SpringCloud?微服務(wù)數(shù)據(jù)權(quán)限控制的實(shí)現(xiàn)
這篇文章主要介紹的是權(quán)限控制的數(shù)據(jù)權(quán)限層面,意思是控制可訪(fǎng)問(wèn)數(shù)據(jù)資源的數(shù)量,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2021-11-11
Spring Boot 2.5.0 重新設(shè)計(jì)的spring.sql.init 配置有啥用
前幾天Spring Boot 2.5.0發(fā)布了,其中提到了關(guān)于Datasource初始化機(jī)制的調(diào)整,有讀者私信想了解這方面做了什么調(diào)整。那么今天就要詳細(xì)說(shuō)說(shuō)這個(gè)重新設(shè)計(jì)的配置內(nèi)容,并結(jié)合實(shí)際情況說(shuō)說(shuō)我的理解和實(shí)踐建議2021-05-05
Java中float類(lèi)型的范圍及其與十六進(jìn)制的轉(zhuǎn)換例子
這篇文章主要介紹了Java中float類(lèi)型的范圍及其與十六進(jìn)制的轉(zhuǎn)換例子,是Java入門(mén)學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-10-10
詳解spring cloud整合Swagger2構(gòu)建RESTful服務(wù)的APIs
這篇文章主要介紹了詳解spring cloud整合Swagger2構(gòu)建RESTful服務(wù)的APIs,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-01-01
解決springmvc整合Mybatis的Log4j日志輸出問(wèn)題
這篇文章主要介紹了解決springmvc整合Mybatis的Log4j日志輸出問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07
如何用idea數(shù)據(jù)庫(kù)編寫(xiě)快遞e站
這篇文章主要介紹了如何用idea數(shù)據(jù)庫(kù)編寫(xiě)快遞e站,本文通過(guò)圖文實(shí)例相結(jié)合給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01
java實(shí)現(xiàn)在pdf模板的指定位置插入圖片
這篇文章主要為大家詳細(xì)介紹了java如何實(shí)現(xiàn)在pdf模板的指定位置插入圖片,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-10-10

