TokenViewer
===
###### tags: `LLM / tokenizer`
###### tags: `ML`, `NLP`, `NLU`, `LLM`, `tokenizer`, `Hugging Face`, `AutoTokenizer`, `tokenizer_config.json`, `single_word"`, `lstrip`, `rstrip"`, `normalized`, `special`, `LLaMA`, `LLaVA`, `Vicuna`, `LlamaForCausalLM`
<br>
[TOC]
<br>
## TokenViewer 用途
1. **除錯與錯誤排除**:
當我們訓練或使用自然語言處理模型時,經常會遇到斷詞、tokenize 或 token ID 不符預期的情況。 透過 TokenViewer,開發人員可以快速檢視 tokenize 後的斷詞和對應的 token ID,有助於找出問題並進行偵錯與錯誤排除。
2. **提高模型訓練的效率和效果**:
- **減少無效處理**:
透過TokenViewer,開發者可以快速找出和修正斷詞過程中可能出現的錯誤,例如**過度分割**或**錯誤合併**等問題。正確的斷詞結果將直接影響模型訓練的品質和最終的表現。 例如,“氣象局”若被錯誤分割為“氣”和“象局”(by LLama2),可能導致模型無法正確學習到相關詞彙的語義。
- **最佳化資料輸入**:
合理的斷詞可以減少模型處理的 token 數量,進而降低模型複雜度,從而**加快訓練與推理速度**並提升模型處理時的反應速度。**較少的 token 數量意味著在保持相同資訊量的情況下,模型的處理(訓練與推理)和學習更有效率。**
3. **使用者成本效益**
在使用基於 token 數量計費的自然語言處理服務時,有效管理和優化 token 的使用是非常重要的。 這種計費模式普遍存在於使用 API 進行文字處理的服務中,例如 OpenAI 的 GPT-3、Google Cloud Natural Language API 等。 正確的斷詞可以減少 token 的總數,直接降低使用成本。
**小結**:正確的斷詞不僅減少了 token 數量,還能加快模型的處理速度,同時能減少使用者成本支出(當模型是以 token 數量計費時)。
<br>
### 分割錯誤的例子
- ### 彼はアメリカ人ですか?
- ### LLama2

- '\<s\>', ' ', '�', '�', '�', 'は', 'ア', 'メ', 'リ', 'カ', '人', 'で', 'す', 'か', '?'
- [1, 29871, 232, 192, 191, 30449, 30310, 30604, 30303, 30439, 30313, 30499, 30427, 30412, 30882] **(TOKENS=15)**
- アメリカ 就是 America,如果 America 被斷成 'A', 'm', 'e', 'r', 'i', 'c', 'a',您的想法如何?
- ### LLama3
<
- '彼', 'は', '**アメリカ**', '人', '**ですか**', '?'
- [103031, 15682, 127164, 17792, 112130, 11571] **(TOKENS=6)**
<br>
- ### gpt-4 / gpt-3.5-turbo / text-embedding-ada-002

- '�', '�', 'は', 'ア', 'メ', 'リ', 'カ', '人', 'です', 'か?'
- [14608, 120, 15682, 39880, 39850, 37823, 71493, 17792, 38641, 32149, 11571] **(TOKENS=11)**
<br>
<hr>
<br>
## Playgrounds
### ==Llama Tokenizer==
https://belladoreai.github.io/llama-tokenizer-js/example-demo/build/

<br>
### ==Mistral Tokenizer==
https://lunary.ai/mistral-tokenizer

<br>
### ==GPT-3.5 and GPT-4 Tokenizer==
https://platform.openai.com/tokenizer

<br>
### ==cl100k_base (GPT-3.5-turbo and GPT-4)==
https://gpt-tokenizer.dev/

<br>
### ==huggingface tokenizer playground==
https://huggingface.co/spaces/Xenova/the-tokenizer-playground

<br>
<hr>
<br>
## [討論] tokenizer_config.json
### 欄位用途
- **Question**
tokenizer_config.json
```json
"added_tokens_decoder": {
"128000": {
"content": "<|begin_of_text|>",
"lstrip": false,
"normalized": false,
"rstrip": false,
"single_word": false,
"special": true
},
```
這些欄位代表什麼意思?
- **ChatGPT4**
在 `tokenizer_config.json` 檔案中的 `added_tokens_decoder` 部分用於定義特殊的令牌(token)及其屬性。 這些令牌通常是在基礎詞彙表之外添加到分詞器中的,可能是為了處理特定的標記需求。 這裡的每個欄位都有特定的用途:
- **"content"**:
**內容**:這個欄位定義了令牌的文字內容。 在你的範例中,內容為空字串,這意味著令牌可能代表一個不可見的或是控制性的字元。
- **"lstrip"**:
**左剝離**:如果設定為 `true`,表示在符合該令牌時,將自動從配對開始處刪除空白符號。 如果為 `false`,則保留左側空白。
- **"normalized"**:
**規範化**:此欄位指定該令牌在使用前是否應進行規範化處理,如小寫化、移除變音符號等。 在你的範例中,由於設定為 `false`,不進行任何規範化處理。
- **"rstrip"**:
**右剝離**:與 `lstrip` 相似,如果設為 `true`,則在符合該令牌時將自動從匹配結束處刪除空白符號。 如果為 `false`,則保留右側空白。
- **"single_word"**:
**單字**:如果設定為 `true`,則令牌必須作為完整單字出現才會被辨識。 這有助於防止令牌被錯誤地識別為其他單字的一部分。 你的範例中此值為 `false`,表示該令牌可以出現在更大字串的任何位置。
- **"special"**:
**特殊令牌**:標記該令牌是否為特殊令牌。 特殊令牌通常用於特定的模型操作,如句子的開始或結束。 `true` 表示這是一個特殊令牌。
這些設定有助於客製化和優化分詞器的行為,特別是在處理複雜文字或需要適應特定語言模型要求時。 每個欄位的具體設定可以根據不同的應用場景進行調整,以適應特定的文字處理需求。
<br>
### "lstrip": true/false 的效果
- **Question**
`"lstrip": true` 可以舉具體的例子嗎?
- **ChatGPT4**
當分詞器配置中的 `"lstrip": true` 時,表示在識別並處理該特定令牌之前,將自動從該令牌左側移除任何空白字元。 這個屬性通常用於處理格式化或語法結構中的特定情況,確保令牌的正確解析和使用。
### 具體例子:
假設我們有一個特殊的令牌 `<start>` 用來標識句子的開始,而我們想確保無論輸入文字如何格式化,這個標記都應緊接在句子的第一個單字之前,不留空白。 這在自然語言處理的某些應用中尤其重要,例如在自動文字產生中標識句子的起始位置。
#### 範例文字:
```
複製程式碼```
" <start>這是一個例子。"
```
#### 處理過程:
- 在沒有 `"lstrip": true` 的情況下,如果文字前面有空格,`<start>` 令牌前的空格會被保留,這可能會影響後續的處理邏輯,例如在產生文字或進行模式匹配時。
- 如果 `"lstrip": true`,分詞器在處理時會自動移除 `<start>` 前的所有空格,確保 `<start>` 緊鄰句子的實際開始位置。 處理後的文字會變成:
```
"<start>這是一個例子。"
```
#### 效果:
透過使用 `"lstrip": true`,無論輸入資料如何格式化,特殊令牌 `<start>` 都能正確地放置在文字的起始位置,而不會有任何前導空白。 這對於保持資料的整潔性和一致性至關重要,尤其是在需要精確控製文字結構的場景中。
### 總結:
`"lstrip": true` 是一種有用的設置,它有助於確保在分詞過程中自動調整令牌前的空白,適用於需要精確控制令牌位置和上下文清晰的應用。 這種功能使得令牌的使用更加靈活和準確,有助於提高整體的文字處理品質和效能。
<br>
### "normalized": true/false 的效果
- **Question**
`"normalized": true` 可以舉具體的例子嗎?
- **ChatGPT4**
當分詞器配置中的 `"normalized": true` 時,這表示在將文字轉換為令牌之前,分詞器會自動套用一些標準化處理。 這種處理通常包括將文字轉換為統一的格式,例如將所有字元轉換為小寫、去除變音符號、或其他標準化規則,以確保文字處理的一致性和準確性。
### 具體例子:
假設我們要處理多語言文本,特別是涉及使用拉丁字母的語言,其中包括英語、法語、德語等。 不同的語言使用的字元可能包括特殊符號或變音符號,這可能影響分詞器的一致性和性能。
#### 範例文字:
```
"Café Müller"
```
在這個例子中,單字 "Café" 包含一個帶有變音符號的字元 'é'。
#### 處理過程:
- 如果 `"normalized": false` 或此屬性未指定,分詞器會將"Café" 和"Müller" 當作含有特殊字元的單字處理,這可能導致與不帶變音符號的相同單字"Cafe"、"Muller " 處理不一致。
- 如果 `"normalized": true`,分詞器會自動將 "Café" 中的 'é' 轉換為 'e',並將 "Müller" 中的 'ü' 轉換為 'u'。 處理後的文字會變成:
```
"Cafe Muller"
```
#### 效果:
透過 `"normalized": true"` 的設置,分詞器確保所有輸入文字都遵循相同的字元標準,使得"Café" 和"Cafe" 能夠被視為相同的單詞,進而提高模型訓練和文字處理的一致性。 這種標準化處理特別有助於跨語言的資料處理和模型訓練,因為它簡化了語言的複雜性,使得分詞器更容易處理和識別文字。
### 總結:
`"normalized": true` 是一種重要的設置,尤其是在處理多語言文字時。 它透過自動標準化輸入文字來增強資料的一致性,減少語言差異的影響,從而提高了處理效率和準確性。 這在全球化應用和服務中尤其重要,可以顯著提高系統的普遍性和使用者體驗。
<br>
### "single_word": true/false 的效果
- **Question**
"single_word": true 可以舉具體的例子嗎?
- **ChatGPT4**
在分詞器配置中,`"single_word": true` 是一個重要的屬性,它指定了令牌必須作為完整的單字出現才會被識別。 這個屬性幫助確保特定的令牌不會被錯誤地識別為更長單字的一部分,從而提高了分詞的精確性。
### 具體例子:
假設我們有一個特殊的令牌 `<user>`,用於標記文字中使用者提到的名字或使用者名稱。 在分析社群媒體或對話系統的文字時,我們希望只在單字獨立出現時識別這個特殊令牌。
#### 範例文字:
```
"I spoke with <user> and then went to the supermarket."
```
在這個例子中,`<user>` 應該被辨識為一個獨立的令牌。
#### 另一個範例文字:
```
"The username is <user>name123."
```
在這個例子中,`<user>` 緊鄰其它字元組成更長的字串 `<user>name123`。
#### 處理過程:
- 如果 `"single_word": false` 或此屬性未被指定,分詞器可能無法區分 `<user>` 和 `<user>name123`,並且會在兩種情況下都將 `<user>` 作為單獨的令牌處理。
- 如果 `"single_word": true`,則分詞器會要求 `<user>` 必須以獨立的單字出現。 因此,在第一個範例中,`<user>` 會被正確識別和處理,而在第二個範例中,`<user>name123` 不會被拆分,因為它不是獨立的單字。
#### 效果:
透過使用 `"single_word": true"` 的設置,可以確保只有當 `<user>` 完全獨立時才被識別為特殊令牌。這樣的配置減少了錯誤識別的可能性,特別是在處理需要精確分詞的應用場景 (如語法分析、實體識別等)中非常有用。
### 總結:
`"single_word": true` 是一種有助於提高分詞精確度的設置,特別是當令牌具有特定的應用或格式要求時。 這種設定有助於確保令牌被正確識別和處理,避免了將其錯誤地識別為更長字串的一部分的問題,從而提高了資料處理的準確性和可靠性。
<br>
### "special": true/false 的效果
- **Question**
`"special": true` 可以舉具體的例子嗎?
- **ChatGPT4**
在分詞器配置中,`"special": true` 屬性用於識別一個令牌為特殊令牌。 特殊令牌通常具有一些非常規用途或重要的語義角色,在模型處理中需要特別注意。 這種標記有助於在文字預處理、模型訓練或文字生成中對這些令牌進行特殊處理。
### 具體例子:
假設我們在處理自然語言生成或機器翻譯任務時,經常使用特殊令牌來表示輸入文字的開始和結束,這有助於模型確定句子的邊界。 這些特殊令牌可以是 `<start>` 和 `<end>`。
#### 範例文字:
在一個自動文字產生的模型訓練中,你可能會看到如下的訓練資料:
```
<start> Hello, how are you today? <end>
```
在這個例子中,`<start>` 和 `<end>` 是用來指示句子的開始和結束的。
#### 處理過程:
- `"special": true` 屬性會設定在 `<start>` 和 `<end>` 這樣的令牌上,讓得分詞器和模型知道這些令牌有特定的用途。
- 在模型訓練或文字產生時,這些令牌可以用來觸發特定的行為,例如初始化狀態或清理產生的輸出。
#### 效果:
- **初始化和結尾處理**:在生成文字時,`<start>` 令牌可以觸發生成過程的開始,而 `<end>` 令牌可以告訴模型停止進一步的文字生成,因為句子已經完成。
- **上下文管理**:在序列到序列的模型應用中,這些特殊令牌幫助模型維護和管理不同部分的上下文狀態,例如,在回答問題或進行多輪對話時明確對話的起止。
### 總結:
透過使用 `"special": true"` 的設置,可以確保模型正確理解和處理這些具有控制功能的令牌。這對於確保文字產生的準確性和流暢性至關重要,特別是在需要精確控制生成過程的 在應用中,如機器翻譯、自動文字摘要或互動式對話系統。
<br>
<hr>
<br>
## 測試程式碼
### API: AutoTokenizer
- ### `AutoTokenizer.save_pretrained('.')` 輸出底下檔案:
- `tokenizer_config.json`
- `special_tokens_map.json`
- `tokenizer.model`
- `added_tokens.json` <-- 但目錄下無此檔案
- `tokenizer.json`
<br>
### demo code
```python
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained('.')
tokenizer.save_pretrained('output') # export tokenizer.json
# ('output/tokenizer_config.json',
# 'output/special_tokens_map.json',
# 'output/tokenizer.model',
# 'output/added_tokens.json',
# 'output/tokenizer.json')
```
- `trust_remote_code=True`
- [internlm/internlm2-chat-7b](https://huggingface.co/internlm/internlm2-chat-7b)
```
tokenizer = AutoTokenizer.from_pretrained("internlm/internlm2-chat-7b", trust_remote_code=True)
```
- error
```
The repository for .../internlm/internlm2-chat-7b contains custom code which must be executed to correctly load the model. You can inspect the repository content at https://hf.co/.../internlm/internlm2-chat-7b.
You can avoid this prompt in future by passing the argument `trust_remote_code=True`.
Do you wish to run the custom code? [y/N] y
Traceback (most recent call last):
...
...
ModuleNotFoundError: No module named 'transformers_modules.'
```
- solution
- [抛出异常 No module named 'transformers_modules.' #110](https://github.com/mymusise/ChatGLM-Tuning/issues/110#issuecomment-1495541448)
transoformer版本为4.27.1时,把模型路径更改为 ==**绝对路径**==,一切正常
- ### `AutoTokenizer.save_vocabulary('.')` 輸出底下檔案:
- `tokenizer.model`
<br>
### 來源: [LlamaForCausalLM](https://huggingface.co/docs/transformers/v4.32.1/en/model_doc/llama2#transformers.LlamaForCausalLM.forward.example)
```python=
from transformers import AutoTokenizer, LlamaForCausalLM
model = LlamaForCausalLM.from_pretrained(PATH_TO_CONVERTED_WEIGHTS)
tokenizer = AutoTokenizer.from_pretrained(PATH_TO_CONVERTED_TOKENIZER)
prompt = "Hey, are you conscious? Can you talk to me?"
inputs = tokenizer(prompt, return_tensors="pt")
# Generate
generate_ids = model.generate(inputs.input_ids, max_length=30)
tokenizer.batch_decode(generate_ids, skip_special_tokens=True, clean_up_tokenization_spaces=False)[0]
# "Hey, are you conscious? Can you talk to me?\nI'm not conscious, but I can talk to you."
```
- `PATH_TO_CONVERTED_WEIGHTS` 填入 `'.'` (當前模型資料夾)
- `PATH_TO_CONVERTED_TOKENIZER` 填入 `'.'` (當前模型資料夾)
- `return_tensors`
- `'tf'`: Return TensorFlow `tf.constant` objects.
- `'pt'`: Return PyTorch `torch.Tensor` objects.
- `'np'`: Return Numpy `np.ndarray` objects.
(資料來源: `help(tokenizer.encode)`)
- `input`
`{'input_ids': tensor([[ 1, 18637, 29892, 526, 366, 19861, 29973, 1815, 366, 5193,
304, 592, 29973]]), 'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]])}`
- `generate_ids`
`tensor([[ 1, 18637, 29892, 526, 366, 19861, 29973, 1815, 366, 5193,
304, 592, 29973, 2]])`
- ### 執行結果
"Hey, are you conscious?"
[](https://hackmd.io/_uploads/rkztY51WA.png)
- 無法產生:"Hey, are you conscious? Can you talk to me?"
<br>
### tokenizer 所需檔案為何?
- [lmsys/vicuna-7b-v1.5](https://huggingface.co/lmsys/vicuna-7b-v1.5/tree/main)
- tokenizer.model
- tokenizer_config.json
- 載入 tokenizer 測試
```python
from transformers import AutoTokenizer, LlamaForCausalLM
AutoTokenizer.from_pretrained('.')
```
執行結果:
```
LlamaTokenizerFast(name_or_path='.', vocab_size=32000, model_max_length=4096, is_fast=True, padding_side='right', truncation_side='right', special_tokens={'bos_token': '<s>', 'eos_token': '</s>', 'unk_token': '<unk>'}, clean_up_tokenization_spaces=False), added_tokens_decoder={
0: AddedToken("<unk>", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True),
1: AddedToken("<s>", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True),
2: AddedToken("</s>", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True),
}
```
PASS
<br>
## Llama3
### Llama3 / tokenizer.json
- tokenizer.json
```
"模åŀĭ": 123123,
```
- decode
- "模åŀĭ" = "模型"
```python=
s = "模åŀĭ" # 模型
b = []
for ss in s:
num = ord(ss)
b.append(num if num <= 255 else num - 162)
bytearray(b).decode('utf8') # '模型'
```
- "ĠÑĢеволÑİ" = "~револю" (en: "~revolution", cht: "〜革命")
```python=
s = "ĠÑĢеволÑİ"
b = []
for ss in s:
num = ord(ss)
b.append(num if num <= 255 else num - 162)
bytearray(b).decode('utf8') # '~револю'
```
- 參考資料
- [[huggingface][docs] Llama3](https://huggingface.co/docs/transformers/main/model_doc/llama3)
- The tokenizer is a BPE model based on [tiktoken](https://github.com/openai/tiktoken) (vs the one based on sentencepiece implementation for Llama2).
- [[github] openai / tiktoken](https://github.com/openai/tiktoken)
<br>
### [llama3 / llama / tokenizer.py](https://github.com/meta-llama/llama3/blob/main/llama/tokenizer.py)
```
$ pip install blobfile
```
```python=
>>> from tokenizer import *
>>> t = Tokenizer('./tokenizer.model') # error: ModuleNotFoundError: No module named 'blobfile'
>>> t.encode("模型", bos=False, eos=False)
[123123]
>>> t.decode([123123])
'模型'
>>> t.encode("語言", bos=False, eos=False)
[102158, 78244]
>>> t.decode([102158, 78244])
'語言'
```
<br>
<hr>
<br>
## 總結 API
### 取得 token 清單
> `tokenizer.tokenize(text)`
- ### DEMO
```python=
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained('.')
text = "氣象局是專門研究天氣的機構"
tokens = tokenizer.tokenize(text)
print(tokens)
```
執行結果:
`['▁', '<0xE6>', '<0xB0>', '<0xA3>', '象', '局', '是', '<0xE5>', '<0xB0>', '<0x88>', '門', '研', '究', '天', '<0xE6>', '<0xB0>', '<0xA3>', '的', '機', '<0xE6>', '<0xA7>', '<0x8B>']`
<br>
### 取得 token-ID 清單
> - 方法一:
> ```
> input_ids = tokenizer.encode(text)
> ```
> - 方法二:
> ```
> encoded_input = tokenizer.encode_plus(text)
> input_ids = encoded_input['input_ids']
> ```
> 等同:
> ```
> encoded_input = tokenizer(text)
> input_ids = encoded_input['input_ids']
> ```
- ### DEMO 方法一
```python=
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained('.')
text = "氣象局是專門研究天氣的機構"
input_ids = tokenizer.encode(text)
print(input_ids)
```
執行結果:
`[1, 29871, 233, 179, 166, 31133, 31655, 30392, 232, 179, 139, 31075, 31367, 31455, 30408, 233, 179, 166, 30210, 31540, 233, 170, 142]`
- ### DEMO 方法二
```python=
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained('.')
text = "氣象局是專門研究天氣的機構"
encoded_input = tokenizer.encode_plus(text)
input_ids = encoded_input['input_ids']
print(input_ids)
```
執行結果:
`[1, 29871, 233, 179, 166, 31133, 31655, 30392, 232, 179, 139, 31075, 31367, 31455, 30408, 233, 179, 166, 30210, 31540, 233, 170, 142]`
<br>
### token-ID 轉 token
> `encoded_input = tokenizer(text)`
> `tokens = tokenizer.convert_ids_to_tokens(encoded_input['input_ids'])`
```python=
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained('.')
text = "氣象局是專門研究天氣的機構"
encoded_input = tokenizer(text)
tokenizer.convert_ids_to_tokens(encoded_input['input_ids'])
```
執行結果:
`['<s>', '▁', '<0xE6>', '<0xB0>', '<0xA3>', '象', '局', '是', '<0xE5>', '<0xB0>', '<0x88>', '門', '研', '究', '天', '<0xE6>', '<0xB0>', '<0xA3>', '的', '機', '<0xE6>', '<0xA7>', '<0x8B>']`
- 與 `tokens = tokenizer.tokenize(text)` 差異在於:多了起始 token `'<s>'`
<br>
### 取得 token/token-ID 清單與字元的對應關係
- ### DEMO 方法一
```python=
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained('.')
text = "氣象局是專門研究天氣的機構"
# token list
tokens = tokenizer.tokenize(text)
# token-ID list
encoded_input = tokenizer.encode_plus(text, return_offsets_mapping=True)
input_ids = encoded_input['input_ids']
offset_mapping = encoded_input['offset_mapping']
# 忽略 input_ids[0]=1 和 offset_mapping[0]=(0,0)
metadata = list(zip(tokens, input_ids[1:], offset_mapping[1:]))
for token, token_id, offset in metadata:
start, end = offset
print(f'{token:6s}\t(={token_id:<5d}) --> {text[start: end]}')
```
執行結果:
```
▁ (=29871) --> 氣
<0xE6> (=233 ) --> 氣
<0xB0> (=179 ) --> 氣
<0xA3> (=166 ) --> 氣
象 (=31133) --> 象
局 (=31655) --> 局
是 (=30392) --> 是
<0xE5> (=232 ) --> 專
<0xB0> (=179 ) --> 專
<0x88> (=139 ) --> 專
門 (=31075) --> 門
研 (=31367) --> 研
究 (=31455) --> 究
天 (=30408) --> 天
<0xE6> (=233 ) --> 氣
<0xB0> (=179 ) --> 氣
<0xA3> (=166 ) --> 氣
的 (=30210) --> 的
機 (=31540) --> 機
<0xE6> (=233 ) --> 構
<0xA7> (=170 ) --> 構
<0x8B> (=142 ) --> 構
```

- ### DEMO 方法二
```python=
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained('.')
text = "氣象局是專門研究天氣的機構"
# token-ID list
encoded_input = tokenizer.encode_plus(text, return_offsets_mapping=True)
input_ids = encoded_input['input_ids']
offset_mapping = encoded_input['offset_mapping']
tokens = tokenizer.convert_ids_to_tokens(input_ids)
metadata = list(zip(tokens, input_ids, offset_mapping))
for token, token_id, offset in metadata:
start, end = offset
print(f'{token:6s}\t(={token_id:<5d}) --> {text[start: end]}')
```
執行結果:
```
<s> (=1 ) --> <--- 多出的起始 token
▁ (=29871) --> 氣
<0xE6> (=233 ) --> 氣
<0xB0> (=179 ) --> 氣
<0xA3> (=166 ) --> 氣
象 (=31133) --> 象
局 (=31655) --> 局
是 (=30392) --> 是
<0xE5> (=232 ) --> 專
<0xB0> (=179 ) --> 專
<0x88> (=139 ) --> 專
門 (=31075) --> 門
研 (=31367) --> 研
究 (=31455) --> 究
天 (=30408) --> 天
<0xE6> (=233 ) --> 氣
<0xB0> (=179 ) --> 氣
<0xA3> (=166 ) --> 氣
的 (=30210) --> 的
機 (=31540) --> 機
<0xE6> (=233 ) --> 構
<0xA7> (=170 ) --> 構
<0x8B> (=142 ) --> 構
```
<br>
### 參數:return_tensors='pt'
- ### DEMO (效果皆相同)
```python
>>> text = "氣象局是專門研究天氣的機構"
>>> tokenizer(text, return_tensors='pt')
{'input_ids': tensor([[ 1, 29871, 233, 179, 166, 31133, 31655, 30392, 232, 179, 139, 31075, 31367, 31455, 30408, 233, 179, 166, 30210, 31540, 233, 170, 142]]),
'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]])}
>>> tokenizer(text)
{'input_ids': [1, 29871, 233, 179, 166, 31133, 31655, 30392, 232, 179, 139, 31075, 31367, 31455, 30408, 233, 179, 166, 30210, 31540, 233, 170, 142], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}
```
```python
>>> tokenizer.encode(text, return_tensors='pt')
tensor([[ 1, 29871, 233, 179, 166, 31133, 31655, 30392, 232, 179, 139, 31075, 31367, 31455, 30408, 233, 179, 166, 30210, 31540, 233, 170, 142]])
>>> tokenizer.encode(text)
[1, 29871, 233, 179, 166, 31133, 31655, 30392, 232, 179, 139, 31075, 31367, 31455, 30408, 233, 179, 166, 30210, 31540, 233, 170, 142]
```
```python
>>> tokenizer.encode_plus(text, return_tensors='pt')
{'input_ids': tensor([[ 1, 29871, 233, 179, 166, 31133, 31655, 30392, 232, 179, 139, 31075, 31367, 31455, 30408, 233, 179, 166, 30210, 31540, 233, 170, 142]]), 'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]])}
>>> tokenizer.encode_plus(text)
{'input_ids': [1, 29871, 233, 179, 166, 31133, 31655, 30392, 232, 179, 139, 31075, 31367, 31455, 30408, 233, 179, 166, 30210, 31540, 233, 170, 142], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}
```
<br>
再將 `encoded_input` 帶到 `tokenizer.convert_ids_to_tokens()`
```python
>>> encoded_input = tokenizer.encode_plus(text, return_tensors='pt')
>>> encoded_input['input_ids']
tensor([[ 1, 29871, 233, 179, 166, 31133, 31655, 30392, 232, 179,
139, 31075, 31367, 31455, 30408, 233, 179, 166, 30210, 31540,
233, 170, 142]])
>>> tokenizer.convert_ids_to_tokens(encoded_input['input_ids'][0])
['<s>', '▁', '<0xE6>', '<0xB0>', '<0xA3>', '象', '局', '是', '<0xE5>', '<0xB0>', '<0x88>', '門', '研', '究', '天', '<0xE6>', '<0xB0>', '<0xA3>', '的', '機', '<0xE6>', '<0xA7>', '<0x8B>']
>>> encoded_input = tokenizer.encode_plus(text)
>>> encoded_input['input_ids']
[1, 29871, 233, 179, 166, 31133, 31655, 30392, 232, 179, 139, 31075, 31367, 31455, 30408, 233, 179, 166, 30210, 31540, 233, 170, 142]
>>> tokenizer.convert_ids_to_tokens(encoded_input['input_ids'])
['<s>', '▁', '<0xE6>', '<0xB0>', '<0xA3>', '象', '局', '是', '<0xE5>', '<0xB0>', '<0x88>', '門', '研', '究', '天', '<0xE6>', '<0xB0>', '<0xA3>', '的', '機', '<0xE6>', '<0xA7>', '<0x8B>']
```
- 有無 tensor type,`tokenizer.convert_ids_to_tokens()` 皆能處理。
<br>
<hr>
<br>
### 整合性函數:產生 ruby 文字
```python=
from transformers import AutoTokenizer
from pathlib import Path
def tokenize(tokenizer, text, grouped=False) -> list:
# token-ID list
encoded_input = tokenizer.encode_plus(text, return_offsets_mapping=True)
input_ids = encoded_input['input_ids']
offset_mapping = encoded_input['offset_mapping']
tokens = tokenizer.convert_ids_to_tokens(input_ids)
# triple info
token_info_list = list(zip(tokens, input_ids, offset_mapping))
#print(f'token_info_list: {token_info_list}')
if not grouped:
return token_info_list
else:
new_token_info_list = [[ token_info_list[0] ]]
new_idx = 0
for idx in range(1, len(token_info_list)):
pre = token_info_list[idx - 1]
next = token_info_list[idx]
# (from, to) and '<...>' are the same?
if next[2] == pre[2] and \
next[0][0] == pre[0][0] and \
next[0][-1] == pre[0][-1]:
# put 'next' in the same group
new_token_info_list[new_idx].append(next)
else:
# put 'next' in a new group
new_token_info_list.append([next])
new_idx += 1
#print(f'new_token_info_list: {new_token_info_list}')
return new_token_info_list
text = "氣象局是專門研究天氣的機構"
tokenizer1 = AutoTokenizer.from_pretrained('.')
token_info_list = tokenize(tokenizer1, text, grouped=True)
print(token_info_list)
tokenizer2 = AutoTokenizer.from_pretrained('bert-base-chinese')
token_info_list = tokenize(tokenizer2, text, grouped=False)
print(token_info_list)
```
執行結果
`[[('<s>', 1, (0, 0))], [('▁', 29871, (0, 1))], [('<0xE6>', 233, (0, 1)), ('<0xB0>', 179, (0, 1)), ('<0xA3>', 166, (0, 1))], [('象', 31133, (1, 2))], [('局', 31655, (2, 3))], [('是', 30392, (3, 4))], [('<0xE5>', 232, (4, 5)), ('<0xB0>', 179, (4, 5)), ('<0x88>', 139, (4, 5))], [('門', 31075, (5, 6))], [('研', 31367, (6, 7))], [('究', 31455, (7, 8))], [('天', 30408, (8, 9))], [('<0xE6>', 233, (9, 10)), ('<0xB0>', 179, (9, 10)), ('<0xA3>', 166, (9, 10))], [('的', 30210, (10, 11))], [('機', 31540, (11, 12))], [('<0xE6>', 233, (12, 13)), ('<0xA7>', 170, (12, 13)), ('<0x8B>', 142, (12, 13))]]`
`[('[CLS]', 101, (0, 0)), ('氣', 3706, (0, 1)), ('象', 6496, (1, 2)), ('局', 2229, (2, 3)), ('是', 3221, (3, 4)), ('專', 2201, (4, 5)), ('門', 7271, (5, 6)), ('研', 4777, (6, 7)), ('究', 4955, (7, 8)), ('天', 1921, (8, 9)), ('氣', 3706, (9, 10)), ('的', 4638, (10, 11)), ('機', 3582, (11, 12)), ('構', 3539, (12, 13)), ('[SEP]', 102, (0, 0))]`
<br>
<hr>
<br>
<br>
<hr>
<br>
## 建構 UI
### gradio
- [Quickstart](https://www.gradio.app/guides/quickstart)
<br>
<hr>
<br>
## 參考資料
- ### [github] huggingface
- [transformers/src/transformers/models/auto/tokenization_auto.py](https://github.com/huggingface/transformers/blob/main/src/transformers/models/auto/tokenization_auto.py)
```
"mistral",
(
"LlamaTokenizer" if is_sentencepiece_available() else None,
"LlamaTokenizerFast" if is_tokenizers_available() else None,
),
```
```
("idefics", (None, "LlamaTokenizerFast" if is_tokenizers_available() else None)),
("idefics2", ("LlamaTokenizer", "LlamaTokenizerFast" if is_tokenizers_available() else None)),
```
```
("phi3", ("LlamaTokenizer", "LlamaTokenizerFast" if is_tokenizers_available() else None)),`
```
- [transformers/src/transformers/models/llama/tokenization_llama.py](https://github.com/huggingface/transformers/blob/main/src/transformers/models/llama/tokenization_llama.py)
- [[huggingface] model_doc/llama#transformers.LlamaTokenizer](https://huggingface.co/docs/transformers/main/en/model_doc/llama#transformers.LlamaTokenizer)
- ### [HuggingFace] docs / tokenizers
- [The tokenization pipeline](https://huggingface.co/docs/tokenizers/pipeline)
- 有些 hugginface 模型會有自己的 tokenizer code, 像是這個:
https://huggingface.co/microsoft/Phi-3-mini-4k-instruct/tree/main
- ### [Python / transformers](/ILXMFP5CRsiHffBy6B8org)
- ### [bert模型中的[CLS]、[UNK]、[SEP]是什么意思?](https://blog.csdn.net/weixin_43797818/article/details/107352565)
- ### [【自然语言处理NLP】Bert中的特殊词元表示](https://blog.csdn.net/weixin_44624036/article/details/131146059)
- ### [大模型对中文切分的 token 长什么样?](https://zhuanlan.zhihu.com/p/637275462)
- [[github] playground/llm_token/token_llama.py](https://github.com/magican-z/playground/blob/main/llm_token/token_llama.py)
- token_llama.py
- ### [huggingface使用(一):AutoTokenizer(通用)、BertTokenizer(基于Bert)](https://blog.csdn.net/u013250861/article/details/124535020)
- AutoTokenizer、BertTokenizer的区别
- ### [Unicode?UTF-8?GBK?……聊聊字符集和字符编码格式](https://blog.hackerpie.com/posts/text-processing/character-sets-and-encoding-formats/)
- ### [Let's build the GPT Tokenizer](https://www.youtube.com/watch?v=zduSFxRajkE) (#ChatGPT, #token, #tokenization)
- ### [[medium] 一個字等於一個token嗎?](https://medium.com/seaniap/2d35c46dcb4c)
- 原始的文字和轉換後的結果之間,不存在一個簡單的一對一對應關係。換句話說,每一個字元,都不是簡單的代表一個token,而是背後隱藏著更多的複雜性。
<br>
<hr>
<hr>
<br>
## 討論
### HuggingFace 所提供的 AutoTokenizer,假設給予一串中文字,例如 "氣象局是專門介紹天氣的機構",在產生一串的 token id 後,怎麼知道一個 token 是對應到"氣象"(二個字) 還是"氣象局" (三個字)?
- ### ChatGPT3
AutoTokenizer 會將輸入的文字進行斷詞(tokenization),將文字分割成不同的 tokens。在 HuggingFace 的 Transformers 库中,大多數的 tokenizer 都是基於 subword tokenization 的方法,例如 **Byte-Pair Encoding (BPE)** 或 **WordPiece**。
當給定一串中文字,如"氣象局是一個專門介紹天氣的機構",tokenizer 會將其轉換為對應的 token id 序列。在這個序列中,每個 token 都有其自己的 token id。然而,tokenizer 會將輸入的文字根據其所使用的 subword tokenization 方法進行分割,生成對應的 tokens。例如,"氣象局" 可能被分割成 "氣象" 和 "局" 這兩個 tokens。因此,每個 token 對應的是 subword,而不是原始文字,也就是說,"氣象" 和 "氣象局" 在 token id 序列中會被表示成不同的 tokens,每個 token 都有自己獨立的 token id。
- ### 能寫個範例將 中文句子轉成 token id ?
當然,我們可以使用 HuggingFace Transformers 中的 AutoTokenizer 來將中文句子轉換成 token id。以下是一個示例程式碼:
```python=
from transformers import AutoTokenizer
# 選擇一個中文 tokenizer,例如中文 BERT 模型
tokenizer = AutoTokenizer.from_pretrained("bert-base-chinese")
# 要轉換的中文句子
sentence = "氣象局是一個專門介紹天氣的機構"
# 使用 tokenizer 將句子轉換成 token id
tokenized_sentence = tokenizer(sentence, return_tensors="pt")
# 取出 token id
input_ids = tokenized_sentence["input_ids"]
print("原始句子:", sentence)
print("Token ID:", input_ids)
```
這段程式碼首先使用 `AutoTokenizer.from_pretrained` 方法選擇一個中文 tokenizer(這裡以中文 BERT 為例),然後將要轉換的中文句子使用這個 tokenizer 轉換成 token id,最後打印出轉換後的 token id。
- ### ChatGPT4
在使用 Hugging Face 的 AutoTokenizer 時,分詞器如何將文字分割為 token 取決於它的訓練資料和分詞演算法。 例如,有些分詞器可能使用基於字元的分割,而其他的可能使用基於字或子字單元(如BPE、WordPiece)的分割。 對於中文文本,常見的處理方法是按字元分割,因為中文不像英文有明顯的空格分隔符號。
要理解並檢視 AutoTokenizer 是如何將特定的中文句子分割為 tokens 的,你可以使用以下步驟:
1. **匯入和載入 AutoTokenizer**
首先,你需要從 Hugging Face 的 transformers 庫中匯入並載入適合的 AutoTokenizer。
```python=
from transformers import AutoTokenizer
# 以中文BERT模型為例
tokenizer = AutoTokenizer.from_pretrained('bert-base-chinese')
```
2. **對文本進行分詞並查看 token**
使用分詞器處理你的中文文本,並查看每個 token 對應的文本。
```python=
text = "氣象局是專門介紹天氣的機構"
tokens = tokenizer.tokenize(text)
print(tokens)
```
3. **看 token 對應的原文**
為了理解每個 token 是如何對應到原始文本的,你可以使用 `tokenizer` 的 `encode_plus` 方法,它返回 token 的額外信息,包括 tokens 在原文中的位置。
```Python=
encoded_input = tokenizer.encode_plus(text, return_offsets_mapping=True)
offset_mapping = encoded_input['offset_mapping']
for token, offset in zip(tokens, offset_mapping):
start, end = offset
print(f"Token: {token}, Corresponding text: {text[start:end]}")
```
在上面的程式碼中,`offset_mapping` 是一個列表,列表中的每個元素是一個元組,表示 token 在原始文字中的起始和結束位置。 這樣你就可以明確知道每個 token 對應的原文是什麼。
### 注意
- 根據所使用的特定模型,`AutoTokenizer` 的行為可能會有所不同。 特別是在不同的語言和模型架構中,分詞的方法和粒度可能會有明顯差異。
- 如果使用的模型不是專門針對中文優化的,其分詞效果可能不理想。 因此,選擇正確的模型很重要,例如 `bert-base-chinese` 是專門為中文優化的 BERT 模型。
透過這種方式,你可以詳細了解和驗證 `AutoTokenizer` 是如何處理和理解你的中文文字的。
---
### 實際測試
```python=
from transformers import AutoTokenizer
# 以中文BERT模型為例
tokenizer = AutoTokenizer.from_pretrained('bert-base-chinese')
text = "氣象局是專門介紹天氣的機構"
tokens = tokenizer.tokenize(text)
print(tokens)
encoded_input = tokenizer.encode_plus(text, return_offsets_mapping=True)
offset_mapping = encoded_input['offset_mapping']
for token, offset in zip(tokens, offset_mapping):
start, end = offset
print(f"Token: {token}, Corresponding text: {text[start:end]}")
```
執行結果:
```
tokenizer_config.json: 100%|██████████| 49.0/49.0 [00:00<00:00, 160kB/s]
config.json: 100%|██████████| 624/624 [00:00<00:00, 2.39MB/s]
vocab.txt: 100%|██████████| 110k/110k [00:00<00:00, 277kB/s]
tokenizer.json: 100%|██████████| 269k/269k [00:00<00:00, 676kB/s]
Token: 氣, Corresponding text:
Token: 象, Corresponding text: 氣
Token: 局, Corresponding text: 象
Token: 是, Corresponding text: 局
Token: 一, Corresponding text: 是
Token: 個, Corresponding text: 一
Token: 專, Corresponding text: 個
Token: 門, Corresponding text: 專
Token: 介, Corresponding text: 門
Token: 紹, Corresponding text: 介
Token: 天, Corresponding text: 紹
Token: 氣, Corresponding text: 天
Token: 的, Corresponding text: 氣
Token: 機, Corresponding text: 的
Token: 構, Corresponding text: 機
```
變數內容:
- **`tokenizer`**
```
BertTokenizerFast(name_or_path='bert-base-chinese', vocab_size=21128, model_max_length=512, is_fast=True, padding_side='right', truncation_side='right', special_tokens={'unk_token': '[UNK]', 'sep_token': '[SEP]', 'pad_token': '[PAD]', 'cls_token': '[CLS]', 'mask_token': '[MASK]'}, clean_up_tokenization_spaces=True), added_tokens_decoder={
0: AddedToken("[PAD]", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True),
100: AddedToken("[UNK]", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True),
101: AddedToken("[CLS]", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True),
102: AddedToken("[SEP]", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True),
103: AddedToken("[MASK]", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True),
```
- **`tokens`**
`['氣', '象', '局', '是', '一', '個', '專', '門', '介', '紹', '天', '氣', '的', '機', '構']`
- **`encoded_input`**
`{'input_ids': [101, 3706, 6496, 2229, 3221, 671, 943, 2201, 7271, 792, 5171, 1921, 3706, 4638, 3582, 3539, 102], 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 'offset_mapping': [(0, 0), (0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (7, 8), (8, 9), (9, 10), (10, 11), (11, 12), (12, 13), (13, 14), (14, 15), (0, 0)]}`
- **`offset_mapping`**
`[(0, 0), (0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (7, 8), (8, 9), (9, 10), (10, 11), (11, 12), (12, 13), (13, 14), (14, 15), (0, 0)]`
---
### 實際測試 -- 改寫版本 --- model: bert-base-chinese
```python=
from transformers import AutoTokenizer
# 以中文BERT模型為例
tokenizer = AutoTokenizer.from_pretrained('bert-base-chinese')
text = "氣象局是專門介紹天氣的機構"
tokens = tokenizer.tokenize(text)
print(tokens, f'(len={len(tokens)})')
encoded_input = tokenizer.encode_plus(text, return_offsets_mapping=True)
input_ids = encoded_input['input_ids']
offset_mapping = encoded_input['offset_mapping']
print(input_ids, f'(len={len(input_ids)})')
print(offset_mapping, f'(len={len(offset_mapping)})')
for token, input_id, offset in zip(tokens, input_ids[1:], offset_mapping[1:]):
start, end = offset
print(f"Token: {token} (={input_id}), 文字: {text[start:end]}")
```
執行結果:
```
Token: 氣 (=3706), 文字: 氣
Token: 象 (=6496), 文字: 象
Token: 局 (=2229), 文字: 局
Token: 是 (=3221), 文字: 是
Token: 專 (=2201), 文字: 專
Token: 門 (=7271), 文字: 門
Token: 介 (=792), 文字: 介
Token: 紹 (=5171), 文字: 紹
Token: 天 (=1921), 文字: 天
Token: 氣 (=3706), 文字: 氣
Token: 的 (=4638), 文字: 的
Token: 機 (=3582), 文字: 機
Token: 構 (=3539), 文字: 構
```
---
### 實際測試 -- 改寫版本 -- model: vicuna-7b-v1.5
```
Token: ▁ (=29871), 文字: 氣
Token: <0xE6> (=233), 文字: 氣
Token: <0xB0> (=179), 文字: 氣
Token: <0xA3> (=166), 文字: 氣
Token: 象 (=31133), 文字: 象
Token: 局 (=31655), 文字: 局
Token: 是 (=30392), 文字: 是
Token: <0xE5> (=232), 文字: 專
Token: <0xB0> (=179), 文字: 專
Token: <0x88> (=139), 文字: 專
Token: 門 (=31075), 文字: 門
Token: 介 (=31633), 文字: 介
Token: <0xE7> (=234), 文字: 紹
Token: <0xB4> (=183), 文字: 紹
Token: <0xB9> (=188), 文字: 紹
Token: 天 (=30408), 文字: 天
Token: <0xE6> (=233), 文字: 氣
Token: <0xB0> (=179), 文字: 氣
Token: <0xA3> (=166), 文字: 氣
Token: 的 (=30210), 文字: 的
Token: 機 (=31540), 文字: 機
Token: <0xE6> (=233), 文字: 構
Token: <0xA7> (=170), 文字: 構
Token: <0x8B> (=142), 文字: 構
```
- **比對線上結果**
https://huggingface.co/spaces/Xenova/the-tokenizer-playground


- **變數內容**
- **`tokens` (len=24)**
`['▁', '<0xE6>', '<0xB0>', '<0xA3>', '象', '局', '是', '<0xE5>', '<0xB0>', '<0x88>', '門', '介', '<0xE7>', '<0xB4>', '<0xB9>', '天', '<0xE6>', '<0xB0>', '<0xA3>', '的', '機', '<0xE6>', '<0xA7>', '<0x8B>']`
- **`input_ids` (len=25)**
`[1, 29871, 233, 179, 166, 31133, 31655, 30392, 232, 179, 139, 31075, 31633, 234, 183, 188, 30408, 233, 179, 166, 30210, 31540, 233, 170, 142]`
- **`offset_mapping` (len=25)**
`[(0, 0), (0, 1), (0, 1), (0, 1), (0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (4, 5), (4, 5), (5, 6), (6, 7), (7, 8), (7, 8), (7, 8), (8, 9), (9, 10), (9, 10), (9, 10), (10, 11), (11, 12), (12, 13), (12, 13), (12, 13)]`
- **漢字 UTF-8 編碼**
- 氣:<0xE6><0xB0><0xA3>
`''.join(f'<0x{c:02X}>' for c in '氣'.encode('utf-8'))`
`'<0xE6><0xB0><0xA3>'`
- 專:<0xE5><0xB0><0x88>
- 紹:<0xE7><0xB4><0xB9>
- 構:<0xE6><0xA7><0x8B>
<br>
### token 和文字對不起來
- ### Question
上面程式碼的執行結果:
```
Token: ▁, Corresponding text:
Token: <0xE6>, Corresponding text: 氣
Token: <0xB0>, Corresponding text: 氣
Token: <0xA3>, Corresponding text: 氣
Token: 象, Corresponding text: 氣
Token: 局, Corresponding text: 象
Token: 是, Corresponding text: 局
Token: 一, Corresponding text: 是
Token: <0xE5>, Corresponding text: 一
Token: <0x80>, Corresponding text: 個
Token: <0x8B>, Corresponding text: 個
Token: <0xE5>, Corresponding text: 個
Token: <0xB0>, Corresponding text: 專
Token: <0x88>, Corresponding text: 專
Token: 門, Corresponding text: 專
Token: 介, Corresponding text: 門
Token: <0xE7>, Corresponding text: 介
Token: <0xB4>, Corresponding text: 紹
Token: <0xB9>, Corresponding text: 紹
Token: 天, Corresponding text: 紹
Token: <0xE6>, Corresponding text: 天
Token: <0xB0>, Corresponding text: 氣
Token: <0xA3>, Corresponding text: 氣
Token: 的, Corresponding text: 氣
Token: 機, Corresponding text: 的
Token: <0xE6>, Corresponding text: 機
Token: <0xA7>, Corresponding text: 構
Token: <0x8B>, Corresponding text:
```
看來文字對不起來,程式碼哪邊出錯?
- ### ChatGPT4
從你提供的輸出結果來看,出現了兩個主要的問題:
1. **錯誤的字元編碼顯示**:你的輸出中顯示如 `<0xE6>`(�) 這樣的字元編碼,而不是期望中的中文字元。 這通常是由於字元編碼處理不當導致的。
2. **錯誤的偏移映射**:`offset_mapping` 的輸出似乎沒有正確地對應到原始文字的字元。
這些問題可能是因為使用的 `AutoTokenizer` 類型不適合中文文字處理,或是在處理中文字元時有編碼問題。
### 解決方法
針對上述問題,你可以嘗試以下步驟來調整和改進程式碼:
1. **確保使用適合中文的分詞器:**
確保使用的是專門針對中文優化的模型和分詞器,如 `bert-base-chinese`。 這可以透過正確選擇預訓練模型來實現。
2. **修正編碼問題:**
如果在處理過程中遇到編碼問題,確保所有的字串操作都使用統一的字元編碼(如UTF-8)。
3. **重新檢視 `offset_mapping` 的使用:**
需要確保正確地使用 `offset_mapping` 來取得每個 token 對應的原文。
### 修正後的程式碼範例
假設你使用的是針對中文最佳化的 BERT 模型(如 `bert-base-chinese`),可以試試以下程式碼:
```python=
from transformers import BertTokenizer
# 確保使用適合中文的分詞器
tokenizer = BertTokenizer.from_pretrained('bert-base-chinese')
text = "氣象局是專門介紹天氣的機構"
encoded_input = tokenizer(text, return_offsets_mapping=True, return_tensors='pt')
tokens = tokenizer.convert_ids_to_tokens(encoded_input['input_ids'][0])
offset_mapping = encoded_input['offset_mapping'][0]
for token, offset in zip(tokens, offset_mapping):
start, end = offset
print(f"Token: {token}, Corresponding text: {text[start:end]}")
```
這段程式碼將:
- 使用適合中文的 `BertTokenizer`。
- 透過 `return_offsets_mapping=True` 來取得每個 token 在原文中的位置。
- 輸出每個 token 和其對應的原文部分。
如果你仍然遇到問題,請檢查你的環境設定是否正確支援中文處理,確保所有相關的庫和環境都是最新的,並且正確處理了中文字元編碼。
---
### 實際測試 -- error
> NotImplementedError: return_offset_mapping is not available when using Python tokenizers. To use this feature, change your tokenizer to one deriving from transformers.PreTrainedTokenizerFast. More information on available tokenizers at https://github.com/huggingface/transformers/pull/2674
```
encoded_input = tokenizer(text, return_offsets_mapping=True, return_tensors='pt')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File ".../site-packages/transformers/tokenization_utils_base.py", line 2803, in __call__
encodings = self._call_one(text=text, text_pair=text_pair, **all_kwargs)
File ".../site-packages/transformers/tokenization_utils_base.py", line 2909, in _call_one
return self.encode_plus(
File ".../site-packages/transformers/tokenization_utils_base.py", line 2982, in encode_plus
return self._encode_plus(
File ".../site-packages/transformers/tokenization_utils.py", line 711, in _encode_plus
raise NotImplementedError(
NotImplementedError: return_offset_mapping is not available when using Python tokenizers. To use this feature, change your tokenizer to one deriving from transformers.PreTrainedTokenizerFast. More information on available tokenizers at https://github.com/huggingface/transformers/pull/2674
```
---
### 實際測試 -- 改寫版本
> 將 `BertTokenizer` 換成 `AutoTokenizer`
```python=
from transformers import AutoTokenizer
# 確保使用適合中文的分詞器
tokenizer = AutoTokenizer.from_pretrained('bert-base-chinese')
text = "氣象局是專門介紹天氣的機構"
encoded_input = tokenizer(text, return_offsets_mapping=True, return_tensors='pt')
tokens = tokenizer.convert_ids_to_tokens(encoded_input['input_ids'][0])
offset_mapping = encoded_input['offset_mapping'][0]
for token, offset in zip(tokens, offset_mapping):
start, end = offset
print(f"Token: {token}, Corresponding text: {text[start:end]}")
```
執行結果:
```
Token: [CLS], Corresponding text:
Token: 氣, Corresponding text: 氣
Token: 象, Corresponding text: 象
Token: 局, Corresponding text: 局
Token: 是, Corresponding text: 是
Token: 專, Corresponding text: 專
Token: 門, Corresponding text: 門
Token: 介, Corresponding text: 介
Token: 紹, Corresponding text: 紹
Token: 天, Corresponding text: 天
Token: 氣, Corresponding text: 氣
Token: 的, Corresponding text: 的
Token: 機, Corresponding text: 機
Token: 構, Corresponding text: 構
Token: [SEP], Corresponding text:
```
- 變數內容
- `tokens`
`['[CLS]', '氣', '象', '局', '是', '專', '門', '介', '紹', '天', '氣', '的', '機', '構', '[SEP]']`
- `offset_mapping`
```
tensor([[ 0, 0],
[ 0, 1],
[ 1, 2],
[ 2, 3],
[ 3, 4],
[ 4, 5],
[ 5, 6],
[ 6, 7],
[ 7, 8],
[ 8, 9],
[ 9, 10],
[10, 11],
[11, 12],
[12, 13],
[ 0, 0]])
```
<br>
### BPE tokenise 是怎樣的編碼原理?
[ChatGPT4]
**Byte Pair Encoding (BPE)** 是一種主要用於自然語言處理領域的數據壓縮技術,後來被廣泛應用於子詞單元的分割(即 tokenization)。BPE 通過找出語料庫中最常見的字節對或字符對並將它們合併為單一的單元,逐步建立一個更高效的編碼方式。這種方法的關鍵在於減少整個文檔或語料庫的編碼大小,同時保持文本的重要語義單元。
BPE 的 tokenization 過程可以分為以下幾個步驟:
1. **初始化詞彙表**:初始詞彙表包括文本中所有唯一字符(或字節),以及每個詞的結束符號,通常使用特殊字符如 "`</w>`" 表示。
2. **統計頻率**:計算文本中所有相鄰字符對的出現頻率。
3. **合併最頻繁對**:從文本中選擇出現頻率最高的字符對,將其合併為一個新單元,並更新詞彙表以及文本中的字符對頻率統計。合併後,這對字符會被視為一個單獨的單元。
4. **重複合併過程**:重複步驟 3 直到達到預定的詞彙大小或合併次數。
5. **生成最終的詞彙表**:過程結束後,生成的詞彙表將包括所有單字、常見的字符組合,以及常見的單詞或字根等。
使用 BPE 的優點之一是它可以有效地降低未知字(out-of-vocabulary, OOV)的發生率,因為即使一個特定的詞不在詞彙表中,該詞也可以被分解成詞彙表中存在的子單元。這使得 BPE 非常適合於大規模且多樣化的語料庫,如機器翻譯、文本生成等自然語言處理任務。
在實際應用中,如 GPT 和 BERT 等現代深度學習模型,BPE 或其變體(如 SentencePiece 或 WordPiece)經常被用作前處理步驟,以優化模型的學習效率和性能。
<br>
### LLM 所使用的 tokenizer.json 是在訓練時不斷調整所產生?還是一開始就產生好,再依 token 分割取得 token ID?
[ChatGPT4]
在大型語言模型(LLM)如ChatGPT這類模型中,使用的 `tokenizer.json` 通常是在模型訓練之前就產生好的,並且不會在訓練過程中不斷調整。 這個檔案包含了用於將文字轉換為模型可以理解的 token ID 的必要資訊。 以下是有關 `tokenizer.json` 和分詞器(tokenizer)在模型訓練中使用的更詳細的解釋:
#### Tokenizer的作用與生成過程
- **分詞器的決定:**
- 分詞器是在模型訓練之前確定的。 它是基於一種預先定義的詞彙表(vocabulary)來運作,這個詞彙表定義了哪些字串(單字或字元)被認為是有效的 token。
- 詞彙表的產生通常是基於大量文本資料。 透過統計這些文本中的詞頻等信息,確定哪些詞或字符組合應被包括在內。
- **tokenizer.json的作用:**
- `tokenizer.json`檔案包含了用於將文字字串轉換為 token ID 序列的所有規則和映射關係。 這可能包括單字到 token ID 的映射、分詞規則、特殊字元處理規則等。
- 這個檔案通常在訓練之前生成,作為模型輸入處理的一部分,並在整個訓練過程和模型使用期間保持不變。
- **文字預處理:**
- 在模型訓練和推理(inference)過程中,所有輸入文字首先透過分詞器進行處理。 分詞器讀取 `tokenizer.json`,將輸入文字轉換為一系列的token ID。
- 然後將這些token ID輸入到模型中,用於訓練或產生預測。
#### 訓練和分詞器不變性
- **分詞器的設計和實作需要在模型訓練前就完成**,因為它直接影響模型如何理解和處理輸入資料。 一旦訓練開始,分詞器通常不會更改,因為這會影響模型的學習和穩定性。
- **如果改變了分詞器,已經訓練的模型可能無法正確理解新的token ID**,或者可能需要對模型進行重新訓練以適應新的分詞規則。
因此,`tokenizer.json`是在訓練前就準備好的關鍵組件,其內容和結構在模型的生命週期中保持一致性,以確保輸入資料的處理方式始終與訓練時相同。 這種做法保證了模型的輸出品質和預測的一致性。
<br>
### 1美元可以購買大約 16667 個token
每1000個token六美分,相當於幾美金?
[ChatGPT4]
從您的問題來看,您想知道每個token的費用和一美元可以買多少token。
基於每1000個token的成本是六美分($0.06):
- 每個token的成本是 $\frac{0.06}{1000}$ 美元。
- 因此,1美元可以購買 $\frac{1}{\frac{0.06}{1000}}$ 個token。
我將立即手動計算這個結果:
每個token的成本是 $\frac{0.06}{1000} = 0.00006$ 美元,所以1美元可以購買 $\frac{1}{0.00006}$ 個token。 這個計算結果為 $\frac{1}{0.00006} = 16666.67$ 個token。
因此,1美元可以購買大約 16667 個token。
備註:
- 2024/05/22 - 台幣1元可以購買 516.3 token