# Course

## Docs

- [End-of-chapter quiz](https://huggingface.co/learn/course/zh-TW/chapter7/9.md)
- [提取文本摘要](https://huggingface.co/learn/course/zh-TW/chapter7/5.md)
- [章節簡介](https://huggingface.co/learn/course/zh-TW/chapter7/1.md)
- [微調掩碼語言模型](https://huggingface.co/learn/course/zh-TW/chapter7/3.md)
- [從頭開始訓練因果語言模型](https://huggingface.co/learn/course/zh-TW/chapter7/6.md)
- [精通自然語言處理](https://huggingface.co/learn/course/zh-TW/chapter7/8.md)
- [翻譯](https://huggingface.co/learn/course/zh-TW/chapter7/4.md)
- [問答](https://huggingface.co/learn/course/zh-TW/chapter7/7.md)
- [Token 分類](https://huggingface.co/learn/course/zh-TW/chapter7/2.md)
- [Part 1 完結!](https://huggingface.co/learn/course/zh-TW/chapter4/5.md)
- [The Hugging Face Hub](https://huggingface.co/learn/course/zh-TW/chapter4/1.md)
- [共享預訓練模型](https://huggingface.co/learn/course/zh-TW/chapter4/3.md)
- [章末小測試](https://huggingface.co/learn/course/zh-TW/chapter4/6.md)
- [構建模型卡片](https://huggingface.co/learn/course/zh-TW/chapter4/4.md)
- [使用預訓練的模型](https://huggingface.co/learn/course/zh-TW/chapter4/2.md)
- [微調，檢查!](https://huggingface.co/learn/course/zh-TW/chapter3/5.md)
- [本章簡介](https://huggingface.co/learn/course/zh-TW/chapter3/1.md)
- [使用 Trainer API 微調模型](https://huggingface.co/learn/course/zh-TW/chapter3/3.md)
- [End-of-chapter quiz](https://huggingface.co/learn/course/zh-TW/chapter3/6.md)
- [使用 Keras 微調一個模型](https://huggingface.co/learn/course/zh-TW/chapter3/3_tf.md)
- [一個完整的訓練](https://huggingface.co/learn/course/zh-TW/chapter3/4.md)
- [處理數據](https://huggingface.co/learn/course/zh-TW/chapter3/2.md)
- [如何寫一個好問題](https://huggingface.co/learn/course/zh-TW/chapter8/5.md)
- [介紹](https://huggingface.co/learn/course/zh-TW/chapter8/1.md)
- [在論壇上尋求幫助](https://huggingface.co/learn/course/zh-TW/chapter8/3.md)
- [第2部分完成！](https://huggingface.co/learn/course/zh-TW/chapter8/6.md)
- [調試訓練管道](https://huggingface.co/learn/course/zh-TW/chapter8/4.md)
- [章末測評](https://huggingface.co/learn/course/zh-TW/chapter8/7.md)
- [Debugging the training pipeline](https://huggingface.co/learn/course/zh-TW/chapter8/4_tf.md)
- [出現錯誤時該怎麼辦](https://huggingface.co/learn/course/zh-TW/chapter8/2.md)
- [章末測驗](https://huggingface.co/learn/course/zh-TW/chapter9/9.md)
- [與 Hugging Face Hub 整合](https://huggingface.co/learn/course/zh-TW/chapter9/5.md)
- [Gradio 簡介](https://huggingface.co/learn/course/zh-TW/chapter9/1.md)
- [瞭解接口類](https://huggingface.co/learn/course/zh-TW/chapter9/3.md)
- [高級接口功能](https://huggingface.co/learn/course/zh-TW/chapter9/6.md)
- [Gradio，回顧！](https://huggingface.co/learn/course/zh-TW/chapter9/8.md)
- [與他人分享演示](https://huggingface.co/learn/course/zh-TW/chapter9/4.md)
- [Gradio 塊簡介](https://huggingface.co/learn/course/zh-TW/chapter9/7.md)
- [構建你的第一個演示](https://huggingface.co/learn/course/zh-TW/chapter9/2.md)
- [Part 2 發佈活動](https://huggingface.co/learn/course/zh-TW/events/2.md)
- [標記器，回顧！](https://huggingface.co/learn/course/zh-TW/chapter6/9.md)
- [字節對編碼標記化](https://huggingface.co/learn/course/zh-TW/chapter6/5.md)
- [本章簡介](https://huggingface.co/learn/course/zh-TW/chapter6/1.md)
- [快速標記器的特殊能力](https://huggingface.co/learn/course/zh-TW/chapter6/3.md)
- [WordPiece 標記化](https://huggingface.co/learn/course/zh-TW/chapter6/6.md)
- [章末小測驗](https://huggingface.co/learn/course/zh-TW/chapter6/10.md)
- [逐塊地構建標記器](https://huggingface.co/learn/course/zh-TW/chapter6/8.md)
- [標準化和預標記化](https://huggingface.co/learn/course/zh-TW/chapter6/4.md)
- [Unigram標記化](https://huggingface.co/learn/course/zh-TW/chapter6/7.md)
- [QA 管道中的快速標記器](https://huggingface.co/learn/course/zh-TW/chapter6/3b.md)
- [根據已有的tokenizer訓練新的tokenizer](https://huggingface.co/learn/course/zh-TW/chapter6/2.md)
- [創建自己的數據集](https://huggingface.co/learn/course/zh-TW/chapter5/5.md)
- [本章簡介](https://huggingface.co/learn/course/zh-TW/chapter5/1.md)
- [是時候來學一下切片了](https://huggingface.co/learn/course/zh-TW/chapter5/3.md)
- [使用 FAISS 進行語義搜索](https://huggingface.co/learn/course/zh-TW/chapter5/6.md)
- [章末小測試](https://huggingface.co/learn/course/zh-TW/chapter5/8.md)
- [大數據? 🤗 Datasets 來救援!](https://huggingface.co/learn/course/zh-TW/chapter5/4.md)
- [🤗 Datasets，回顧！](https://huggingface.co/learn/course/zh-TW/chapter5/7.md)
- [如果我的數據集不在 Hub 上怎麼辦？](https://huggingface.co/learn/course/zh-TW/chapter5/2.md)
- [處理多個序列](https://huggingface.co/learn/course/zh-TW/chapter2/5.md)
- [本章簡介](https://huggingface.co/learn/course/zh-TW/chapter2/1.md)
- [模型](https://huggingface.co/learn/course/zh-TW/chapter2/3.md)
- [把它們放在一起](https://huggingface.co/learn/course/zh-TW/chapter2/6.md)
- [章末小測試](https://huggingface.co/learn/course/zh-TW/chapter2/8.md)
- [標記器（Tokenizer）](https://huggingface.co/learn/course/zh-TW/chapter2/4.md)
- [基本用法完成！](https://huggingface.co/learn/course/zh-TW/chapter2/7.md)
- [管道的內部](https://huggingface.co/learn/course/zh-TW/chapter2/2.md)
- [課程簡介](https://huggingface.co/learn/course/zh-TW/chapter0/1.md)
- [總結](https://huggingface.co/learn/course/zh-TW/chapter1/9.md)
- [“編碼器”模型](https://huggingface.co/learn/course/zh-TW/chapter1/5.md)
- [本章簡介](https://huggingface.co/learn/course/zh-TW/chapter1/1.md)
- [Transformers能做什麼？](https://huggingface.co/learn/course/zh-TW/chapter1/3.md)
- [「解碼器」模型](https://huggingface.co/learn/course/zh-TW/chapter1/6.md)
- [章末小測試](https://huggingface.co/learn/course/zh-TW/chapter1/10.md)
- [Bias and limitations](https://huggingface.co/learn/course/zh-TW/chapter1/8.md)
- [Transformers 是如何工作的？](https://huggingface.co/learn/course/zh-TW/chapter1/4.md)
- [序列到序列模型](https://huggingface.co/learn/course/zh-TW/chapter1/7.md)
- [測驗時間！](https://huggingface.co/learn/course/zh-TW/chapter1/11.md)
- [自然語言處理](https://huggingface.co/learn/course/zh-TW/chapter1/2.md)

### End-of-chapter quiz
https://huggingface.co/learn/course/zh-TW/chapter7/9.md

# End-of-chapter quiz

Let's test what you learned in this chapter!

### 1.下列哪個任務可以被框定為令牌分類問題？
<Question
	choices={[
		{
			text: "找出句子中的語法成分。",
			explain: "正確! 然後我們可以把每個單詞標記為名詞、動詞等。",
			correct: true
		},
		{
			text: "判斷一個句子的語法是否正確。",
			explain: "不，這是一個序列分類問題。"
		},
		{
			text: "找出句子中提到的人物。",
			explain: "不，除非你把你的問題回答定義為一個按順序排列的任務。",
            correct: true
		},
        {
			text: "找到一個句子中能夠回答問題的詞組。",
			explain: "不，那是一個回答問題。"
		}
	]}
/>

### 2.令牌分類預處理的哪一部分與其他預處理管道不同？
<Question
	choices={[
		{
			text: "當我們在數據集上運行一個模型，並獲得該數據集中每個樣本的預測時。",
			explain: "文本的確是作為單獨的單詞給出的，但我們仍然需要應用子詞標記模型。"
		},
		{
			text: "在應用截斷/填充時，我們需要確保將標籤截斷或填充到與輸入相同的大小。",
			explain: "正確！這不同於通常的預處理，我們需要應用完整的標記化管道。你能想到另一個不同點嗎？",
			correct: true
		},
		{
			text: "沒有必要做任何事情，課文已經被標記了。",
			explain: "這並不是特定於令牌分類的——我們總是使用 < code >-100 </code > 作為我們希望在丟失時忽略的令牌的標籤。"
		},
		{
			text: "因為互聯網上有大量的文本",
			explain: "的確如此! 但這並不是唯一的區別。",
			correct: true
		}
	]}
/>

### 3.當我們對標記分類問題中的單詞進行標記並希望標記時，會出現什麼問題？
<Question
	choices={[
		{
			text: "標記器添加了特殊的標記，我們沒有為他們的標籤。",
			explain: "我們將這些 < code >-100 </code > 標記為 < code > ，以便在丟失時忽略它們。"
		},
		{
			text: "每個單詞可以產生幾個標記，所以我們最終得到的標記比標籤多。",
			explain: "這是主要的問題，我們需要將原始標籤與標記對齊。",
			correct: true
		},
		{
			text: "因為目標是按順序排列的文本問題",
			explain: "這是不正確的; 我們需要儘可能多的標籤，否則我們的模型就會出錯。"
		}
	]}
/>

### 4.“領域適應”是什麼意思？
<Question
	choices={[
		{
			text: "因為模型的內部損耗輸出是默認使用的",
			explain: "不，這只是運行推理。"
		},
		{
			text: "當我們在數據集上訓練模型時。",
			explain: "我們確實經常這樣做，但這並不能解釋我們在培訓中如何得到最優化的損失價值。"
		},
		{
			text: "當我們對一個新的數據集微調一個預先訓練好的模型時，它給出的預測更適合這個數據集",
			explain: "正確! 模型使它的知識適應了新的數據集。",
            correct: true
		},
        {
			text: "當我們將錯誤分類的樣本添加到數據集中，使得我們的模型更加健壯。",
			explain: "沒有，除非你把你的問答問題設計成一個按順序排列的任務。"
		}
	]}
/>

### 5.掩碼語言建模問題中的標籤是什麼？
<Question
	choices={[
		{
			text: "輸入句子中的一些標記是隨機屏蔽的，標籤是原始輸入標記。",
			explain: "就是這樣！",
            correct: true
		},
		{
			text: "輸入句子中的一些標記是隨機屏蔽的，標籤是原始的輸入標記，向左移動。",
			explain: "不，將標籤向左移動相當於預測下一個單詞，這就是因果語言模型。"
		},
		{
			text: "輸入句子中的一些標記是隨機屏蔽的，標籤是這個句子是肯定的還是否定的。",
			explain: "這是一個數據增強的序列分類問題，而不是屏蔽語言建模。"
		},
        {
			text: "兩個句子中的一些標記是隨機屏蔽的，標籤是兩個句子是否相似。",
			explain: "這是一個數據增強的序列分類問題，而不是屏蔽語言建模。"
		}
	]}
/>

### 6.這些任務中的哪一個可以看作是一個順序到順序的問題？
<Question
	choices={[
		{
			text: "撰寫長文檔的簡短評論",
			explain: "是的，這是一個總結性問題。試試另一個答案！",
            correct: true
		},
		{
			text: "回答有關文件的問題",
			explain: "正確! 我們可以把每個單詞標記為人或不是人。",
            correct: true
		},
		{
			text: "我們使用 < code >-100 </code > 來標記特殊標記。",
			explain: "這絕對是一個從序列到序列的問題。你能發現另一個嗎？",
            correct: true
		},
        {
			text: "修正我侄子/朋友發來的信息，使它們用正確的英語",
			explain: "這是一種翻譯問題，所以肯定是一個順序到順序的任務。但這不是唯一正確的答案！",
			correct: true
		}
	]}
/>

### 7.對於序列到序列的問題，預處理數據的正確方法是什麼？
<Question
	choices={[
		{
			text: "輸入和目標必須一起發送到標記器，其中包括 < code > input = ... </code > 和 < code > target = ... </code > 。",
			explain: "這可能是我們將來添加的一個 API，但現在不可能。"
		},
		{
			text: "輸入和目標都必須在對標記器的兩個獨立調用中進行預處理。",
			explain: "不，這是在訓練一個模型; 這裡沒有適應性。"
		},
		{
			text: "因為我們在訓練之後計算度量",
			explain: "不是在序列分類問題; 目標也是文本，我們需要轉換成數字！"
		},
        {
			text: "輸入必須發送到標記器，目標也是如此，但需要使用特殊的上下文管理器。",
			explain: "沒錯，標記器需要由上下文管理器放入目標模式。",
			correct: true
		}
	]}
/>

{#if fw === 'pt'}

### 8.為什麼序列到序列問題有一個特定的“培訓者”子類？
<Question
	choices={[
		{
			text: "因為序列到序列問題使用自定義丟失，所以忽略設置為 < code >-100 </code > 的標籤",
			explain: "這根本不是習慣性的損失，而是損失總是通過計算得到的。"
		},
		{
			text: "當您擁有大量可用數據時，即使有一個經過預先訓練的模型可以處理這些數據",
			explain: "沒錯。 Sequence-to-sequence models' predictions are often run using the <code>generate()</code> method.",
			correct: true
		},
		{
			text: "文本是作為單詞給出的，所以我們只需要應用子詞的標記。",
			explain: "< code > Trainer </code > 並不關心這些，因為它們以前已經被預處理過。"
		},
        {
			text: "因為我們在序列到序列問題中使用了兩個模型",
			explain: "我們確實在某種程度上使用了兩種模型，編碼器和解碼器，但是它們被組合在一個模型中。"
		}
	]}
/>

{:else}

### 9.為什麼在 Transformer 模型上調用“ compile ()”時通常不需要指定損失？
<Question
	choices={[
		{
			text: "因為變壓器模型是用非監督式學習來訓練的",
			explain: "不完全是---- 即使是非監督式學習也需要一個損失函數！"
		},
		{
			text: "輸入和目標必須一起發送到 tokenizer，並且使用 < code > input = ... </code > 和 < code > target = ... </code > 。",
			explain: "沒錯！",
			correct: true
		},
		{
			text: "因為我們在訓練之後計算指標",
			explain: "這可以被定義為一個從序列到序列的問題，儘管這不是唯一正確的答案。"
		},
        {
			text: "因為損失是在“ model.fit ()”中指定的",
			explain: "不，損失函數在運行‘ model.com pile ()’時是固定的，不能在‘ model.fit ()’中更改。"
		}
	]}
/>

{/if}

### 10.你應該在什麼時候預先訓練一個新的模型？
<Question
	choices={[
		{
			text: "當您的特定語言沒有經過預先訓練的模型時",
			explain: "沒錯。",
			correct: true
		},
		{
			text: "當您有大量可用的數據時，即使有一個經過訓練的模型可以處理這些數據",
			explain: "在這種情況下，您可能應該使用預先訓練的模型並對數據進行微調，以避免巨大的計算成本。"
		},
		{
			text: "當你擔心你所使用的預先訓練過的模型的偏差時",
			explain: "這是真的，但是你必須確保你用於培訓的數據真的更好。",
			correct: true
		},
        {
			text: "當可用的預先訓練好的模型還不夠好的時候",
			explain: "那麼，你確定你已經正確地調試了你的訓練嗎？"
		}
	]}
/>

### 11.為什麼在大量的文本上預先訓練一個語言模型是很容易的呢？
<Question
	choices={[
		{
			text: "當你擔心你所使用的預先訓練的模型的偏差時",
			explain: "雖然這是真的，但這並不能真正回答這個問題。再試一次！"
		},
		{
			text: "因為預訓練目標不需要人工標記數據",
			explain: "沒錯，語言建模是一個自我監督的問題。",
			correct: true
		},
		{
			text: "因為變形金剛庫只需要幾行代碼就可以開始培訓",
			explain: "正確！這與通常的預處理不同，在預處理中我們需要應用完整的標記化管道。你能想到另一個不同點嗎？"
		}
	]}
/>

### 12.問答任務的預處理數據時，主要的挑戰是什麼？
<Question
	choices={[
		{
			text: "你需要對輸入進行標記。",
			explain: "這是正確的，但這真的是一個主要的挑戰嗎？"
		},
		{
			text: "你需要處理非常長的上下文，這些上下文提供了一些訓練特性，這些特性可能有也可能沒有答案。",
			explain: "這絕對是挑戰之一。",
			correct: true
		},
		{
			text: "您需要將問題的答案以及輸入標記化。",
			explain: "雖然這是真的，但這並不能真正回答問題。試試另一個答案吧！"
		},
       {
			text: "從文本中的答案範圍中，您必須在標記化的輸入中找到開始和結束標記。",
			explain: "這是最難的部分之一，是的！",
			correct: true
		}
	]}
/>

### 13.問題回答中的後處理通常是怎樣進行的？
<Question
	choices={[
		{
			text: "模型給出了答案的開始位置和結束位置，您只需要解碼相應的標記跨度。",
			explain: "這可能是一種方法，但是有點太簡單了。"
		},
		{
			text: "該模型為每個由一個示例創建的特性提供了答案的開始和結束位置，您只需要解碼分數最高的那個特性中相應的記號跨度。",
			explain: "這與我們研究的後處理過程很接近，但並不完全正確。"
		},
		{
			text: "該模型為每個示例創建的特性提供了答案的開始和結束位置，您只需將它們與上下文中得分最高的特性的跨度相匹配。",
			explain: "簡而言之就是這樣！",
			correct: true
		},
        {
			text: "模型生成一個答案，你只需要解碼它。",
			explain: "雖然這是真的，但這並不能真正回答問題。試試另一個答案吧！"
		}
	]}
/>


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter7/9.mdx" />

### 提取文本摘要
https://huggingface.co/learn/course/zh-TW/chapter7/5.md

# 提取文本摘要

{#if fw === 'pt'}

<DocNotebookDropdown
  classNames="absolute z-10 right-0 top-0"
  options={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/chapter7/section5_pt.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/chapter7/section5_pt.ipynb"},
]} />

{:else}

<DocNotebookDropdown
  classNames="absolute z-10 right-0 top-0"
  options={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/chapter7/section5_tf.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/chapter7/section5_tf.ipynb"},
]} />

{/if}


在本節中，我們將看看如何使用 Transformer 模型將長文檔壓縮為摘要，這項任務稱為文本摘要.這是最具挑戰性的 NLP 任務之一，因為它需要一系列能力，例如理解長篇文章和生成能夠捕捉文檔中主要主題的連貫文本。但是，如果做得好，文本摘要是一種強大的工具，可以減輕領域專家詳細閱讀長文檔的負擔，從而加快各種業務流程。

<Youtube id="yHnr5Dk2zCI"/>

儘管在[Hugging Face Hub](https://huggingface.co/models?pipeline_tag=summarization=downloads)上已經存在各種微調模型用於文本摘要，幾乎所有這些都只適用於英文文檔。因此，為了在本節中添加一些變化，我們將為英語和西班牙語訓練一個雙語模型。在本節結束時，您將有一個可以總結客戶評論的[模型](https://huggingface.co/huggingface-course/mt5-small-finetuned-amazon-en-es)。

<iframe src="https://course-demos-mt5-small-finetuned-amazon-en-es.hf.space" frameBorder="0" height="400" title="Gradio app" class="block dark:hidden container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>
<iframe src="https://course-demos-mt5-small-finetuned-amazon-en-es-darkmode.hf.space" frameBorder="0" height="400" title="Gradio app" class="hidden dark:block container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

如下所示：正如我們將看到的，這些摘要很簡潔，因為它們是從客戶在產品評論中提供的標題中學到的。讓我們首先為這項任務準備一個合適的雙語語料庫。

## 準備多語言語料庫

我們將使用[多語言亞馬遜評論語料庫](https://huggingface.co/datasets/amazon_reviews_multi)創建我們的雙語摘要器。該語料庫由六種語言的亞馬遜產品評論組成，通常用於對多語言分類器進行基準測試。然而，由於每條評論都附有一個簡短的標題，我們可以使用標題作為我們模型學習的目標摘要！首先，讓我們從 Hugging Face Hub 下載英語和西班牙語子集：

```python
from datasets import load_dataset

spanish_dataset = load_dataset("amazon_reviews_multi", "es")
english_dataset = load_dataset("amazon_reviews_multi", "en")
english_dataset
```

```python out
DatasetDict({
    train: Dataset({
        features: ['review_id', 'product_id', 'reviewer_id', 'stars', 'review_body', 'review_title', 'language', 'product_category'],
        num_rows: 200000
    })
    validation: Dataset({
        features: ['review_id', 'product_id', 'reviewer_id', 'stars', 'review_body', 'review_title', 'language', 'product_category'],
        num_rows: 5000
    })
    test: Dataset({
        features: ['review_id', 'product_id', 'reviewer_id', 'stars', 'review_body', 'review_title', 'language', 'product_category'],
        num_rows: 5000
    })
})
```

如您所見，對於每種語言，都有 200,000 條評論 **train** 拆分，每個評論有 5,000 條評論 **validation** 和 **test** 分裂。我們感興趣的評論信息包含在 **review_body** 和 **review_title** 列。讓我們通過創建一個簡單的函數來查看一些示例，該函數使用我們在[第五章](/course/chapter5)學到過：

```python
def show_samples(dataset, num_samples=3, seed=42):
    sample = dataset["train"].shuffle(seed=seed).select(range(num_samples))
    for example in sample:
        print(f"\n'>> Title: {example['review_title']}'")
        print(f"'>> Review: {example['review_body']}'")


show_samples(english_dataset)
```

```python out
'>> Title: Worked in front position, not rear'
'>> Review: 3 stars because these are not rear brakes as stated in the item description. At least the mount adapter only worked on the front fork of the bike that I got it for.'

'>> Title: meh'
'>> Review: Does it’s job and it’s gorgeous but mine is falling apart, I had to basically put it together again with hot glue'

'>> Title: Can\'t beat these for the money'
'>> Review: Bought this for handling miscellaneous aircraft parts and hanger "stuff" that I needed to organize; it really fit the bill. The unit arrived quickly, was well packaged and arrived intact (always a good sign). There are five wall mounts-- three on the top and two on the bottom. I wanted to mount it on the wall, so all I had to do was to remove the top two layers of plastic drawers, as well as the bottom corner drawers, place it when I wanted and mark it; I then used some of the new plastic screw in wall anchors (the 50 pound variety) and it easily mounted to the wall. Some have remarked that they wanted dividers for the drawers, and that they made those. Good idea. My application was that I needed something that I can see the contents at about eye level, so I wanted the fuller-sized drawers. I also like that these are the new plastic that doesn\'t get brittle and split like my older plastic drawers did. I like the all-plastic construction. It\'s heavy duty enough to hold metal parts, but being made of plastic it\'s not as heavy as a metal frame, so you can easily mount it to the wall and still load it up with heavy stuff, or light stuff. No problem there. For the money, you can\'t beat it. Best one of these I\'ve bought to date-- and I\'ve been using some version of these for over forty years.'
```

<Tip>

✏️ **試試看！** 更改 `Dataset.shuffle()` 命令中的隨機種子以探索語料庫中的其他評論。 如果您是說西班牙語的人，請查看 `spanish_dataset` 中的一些評論，看看標題是否也像合理的摘要。

</Tip>

此示例顯示了人們通常在網上找到的評論的多樣性，從正面到負面（以及介於兩者之間的所有內容！）。儘管標題為“meh”的示例信息量不大，但其他標題看起來像是對評論本身的體面總結。在單個 GPU 上訓練所有 400,000 條評論的摘要模型將花費太長時間，因此我們將專注於為單個產品領域生成摘要。為了瞭解我們可以選擇哪些域，讓我們將 **english_dataset** 轉換到 **pandas.DataFrame** 並計算每個產品類別的評論數量：

```python
english_dataset.set_format("pandas")
english_df = english_dataset["train"][:]
# Show counts for top 20 products
english_df["product_category"].value_counts()[:20]
```

```python out
home                      17679
apparel                   15951
wireless                  15717
other                     13418
beauty                    12091
drugstore                 11730
kitchen                   10382
toy                        8745
sports                     8277
automotive                 7506
lawn_and_garden            7327
home_improvement           7136
pet_products               7082
digital_ebook_purchase     6749
pc                         6401
electronics                6186
office_product             5521
shoes                      5197
grocery                    4730
book                       3756
Name: product_category, dtype: int64
```

英語數據集中最受歡迎的產品是家居用品、服裝和無線電子產品。不過，為了堅持亞馬遜的主題，讓我們專注於總結書籍的評論——畢竟，這是亞馬遜這家公司成立的基礎！我們可以看到兩個符合要求的產品類別（ **book** 和 **digital_ebook_purchase** )，所以讓我們為這些產品過濾兩種語言的數據集。正如我們在[第五章](/course/chapter5)學到的， 這 **Dataset.filter()** 函數允許我們非常有效地對數據集進行切片，因此我們可以定義一個簡單的函數來執行此操作：

```python
def filter_books(example):
    return (
        example["product_category"] == "book"
        or example["product_category"] == "digital_ebook_purchase"
    )
```

現在，當我們將此函數應用於 **english_dataset** 和 **spanish_dataset** ，結果將只包含涉及書籍類別的那些行。在應用過濾器之前，讓我們將**english_dataset**的格式從 **pandas** 切換回到 **arrow** ：

```python
english_dataset.reset_format()
```

然後我們可以應用過濾器功能，作為健全性檢查，讓我們檢查評論樣本，看看它們是否確實與書籍有關：

```python
spanish_books = spanish_dataset.filter(filter_books)
english_books = english_dataset.filter(filter_books)
show_samples(english_books)
```

```python out
'>> Title: I\'m dissapointed.'
'>> Review: I guess I had higher expectations for this book from the reviews. I really thought I\'d at least like it. The plot idea was great. I loved Ash but, it just didnt go anywhere. Most of the book was about their radio show and talking to callers. I wanted the author to dig deeper so we could really get to know the characters. All we know about Grace is that she is attractive looking, Latino and is kind of a brat. I\'m dissapointed.'

'>> Title: Good art, good price, poor design'
'>> Review: I had gotten the DC Vintage calendar the past two years, but it was on backorder forever this year and I saw they had shrunk the dimensions for no good reason. This one has good art choices but the design has the fold going through the picture, so it\'s less aesthetically pleasing, especially if you want to keep a picture to hang. For the price, a good calendar'

'>> Title: Helpful'
'>> Review: Nearly all the tips useful and. I consider myself an intermediate to advanced user of OneNote. I would highly recommend.'
```

好的，我們可以看到評論並不是嚴格意義上的書籍，可能是指日曆和 OneNote 等電子應用程序等內容。儘管如此，該領域似乎適合訓練摘要模型。在我們查看適合此任務的各種模型之前，我們還有最後一點數據準備要做：將英語和西班牙語評論合併為一個 **DatasetDict** 目的。 🤗 Datasets 提供了一個方便的 **concatenate_datasets()** 函數（顧名思義）合併 **Dataset** 對象。因此，為了創建我們的雙語數據集，我們將遍歷每個拆分，連接該拆分的數據集，並打亂結果以確保我們的模型不會過度擬合單一語言：

```python
from datasets import concatenate_datasets, DatasetDict

books_dataset = DatasetDict()

for split in english_books.keys():
    books_dataset[split] = concatenate_datasets(
        [english_books[split], spanish_books[split]]
    )
    books_dataset[split] = books_dataset[split].shuffle(seed=42)

# Peek at a few examples
show_samples(books_dataset)
```

```python out
'>> Title: Easy to follow!!!!'
'>> Review: I loved The dash diet weight loss Solution. Never hungry. I would recommend this diet. Also the menus are well rounded. Try it. Has lots of the information need thanks.'

'>> Title: PARCIALMENTE DAÑADO'
'>> Review: Me llegó el día que tocaba, junto a otros libros que pedí, pero la caja llegó en mal estado lo cual dañó las esquinas de los libros porque venían sin protección (forro).'

'>> Title: no lo he podido descargar'
'>> Review: igual que el anterior'
```

這當然看起來像是英語和西班牙語評論的混合！現在我們有了一個訓練語料庫，最後要檢查的一件事是評論中單詞的分佈及其標題。這對於摘要任務尤其重要，其中數據中的簡短參考摘要會使模型偏向於僅在生成的摘要中輸出一兩個單詞。下面的圖顯示了單詞分佈，我們可以看到有些標題嚴重偏向於 1-2 個單詞：

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter7/review-lengths.svg" alt="Word count distributions for the review titles and texts."/>
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter7/review-lengths-dark.svg" alt="Word count distributions for the review titles and texts."/>
</div>

為了解決這個問題，我們將過濾掉標題非常短的示例，以便我們的模型可以生成更有趣的摘要。由於我們正在處理英文和西班牙文文本，因此我們可以使用粗略的啟發式方法在空白處拆分標題，然後使用我們可信賴的 **Dataset.filter()** 方法如下：

```python
books_dataset = books_dataset.filter(lambda x: len(x["review_title"].split()) > 2)
```

現在我們已經準備好了我們的語料庫，讓我們來看看一些可以對其進行微調的可能的 Transformer 模型！

## 文本摘要模型

如果你仔細想想，文本摘要是一種類似於機器翻譯的任務：我們有一個像評論這樣的文本正文，我們希望將其“翻譯”成一個較短的版本，以捕捉輸入的顯著特徵。因此，大多數用於文本摘要的 Transformer 模型採用了我們在[第一章](/course/chapter1)遇到的編碼器-解碼器架構。儘管有一些例外，例如 GPT 系列模型，它們在few-shot（少量微調）之後也可以提取摘要。下表列出了一些流行的預訓練模型，可以對其進行微調以進行彙總。

| Transformer 模型 | 描述                                                                                                                                                                                                    | 多種語言? |
| :---------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-----------: |
|    [GPT-2](https://huggingface.co/gpt2-xl)    | 雖然訓練為自迴歸語言模型，但您可以通過在輸入文本末尾附加“TL;DR”來使 GPT-2 生成摘要。                                                                          |      ❌       |
|   [PEGASUS](https://huggingface.co/google/pegasus-large)   | 在預訓練是的目標是來預測多句子文本中的屏蔽句子。 這個預訓練目標比普通語言建模更接近文本摘要，並且在流行的基準測試中得分很高。 |      ❌       |
|     [T5](https://huggingface.co/t5-base)      | 通用的 Transformer 架構，在文本到文本的框架中制定所有任務； 例如，模型文本摘要的輸入格式是`summarize: ARTICLE`。                             |      ❌       |
|     [mT5](https://huggingface.co/google/mt5-base)     | T5 的多語言版本，在多語言 Common Crawl 語料庫 (mC4) 上進行預訓練，涵蓋 101 種語言。                                                       |      ✅       |
|    [BART](https://huggingface.co/facebook/bart-base)     | 一種新穎的 Transformer 架構，其中包含經過訓練的編碼器和解碼器堆棧，以重建被破壞的輸入，結合了 BERT 和 GPT-2 的預訓練方案。                                   |      ❌       |
|  [mBART-50](https://huggingface.co/facebook/mbart-large-50)   | BART 的多語言版本，預訓練了 50 種語言。                                                                                                                                                    |      ✅       |

從此表中可以看出，大多數用於摘要的 Transformer 模型（以及大多數 NLP 任務）都是單語的。如果您的任務是使用“有大量語料庫”的語言（如英語或德語），這很好，但對於世界各地正在使用的數千種其他語言，則不然。幸運的是，有一類多語言 Transformer 模型，如 mT5 和 mBART，可以解決問題。這些模型是使用語言建模進行預訓練的，但有一點不同：它們不是在一種語言的語料庫上訓練，而是同時在 50 多種語言的文本上進行聯合訓練！

我們將使用 mT5，這是一種基於 T5 的有趣架構，在文本到文本框架中進行了預訓練。在 T5 中，每個 NLP 任務都是根據提示前綴來制定的，例如 **summarize:** 這使模型使生成的文本適應提示。如下圖所示，這讓 T5 變得非常通用，因為你可以用一個模型解決很多任務！


<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter7/t5.svg" alt="Different tasks performed by the T5 architecture."/>
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter7/t5-dark.svg" alt="Different tasks performed by the T5 architecture."/>
</div>

mT5 不使用前綴，但具有 T5 的大部分功能，並且具有多語言的優勢。現在我們已經選擇了一個模型，讓我們來看看準備我們的訓練數據。

<Tip>

✏️ **試試看！** 完成本節後，通過使用相同的技術對 mBART 進行微調，看看 mT5 與 mBART 相比有多好。 對於獎勵積分，您還可以嘗試僅在英文評論上微調 T5。 由於 T5 需要一個特殊的前綴提示，因此您需要在下面的預處理步驟中將“summarize:”添加到輸入示例中。

</Tip>

## 預處理數據

<Youtube id="1m7BerpSq8A"/>

我們的下一個任務是對我們的評論及其標題進行標記和編碼。像往常一樣，我們首先加載與預訓練模型檢查點相關的標記器。我們將使用 **mt5-small** 作為我們的檢查點，以便我們可以在合理的時間內微調模型：

```python
from transformers import AutoTokenizer

model_checkpoint = "google/mt5-small"
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)
```

<Tip>

💡在 NLP 項目的早期階段，一個好的做法是在小樣本數據上訓練一類“小”模型。這使您可以更快地調試和迭代端到端工作流。一旦您對結果充滿信心，您始終可以通過簡單地更改模型檢查點來在大規模數據上訓練模型！

</Tip>

讓我們在一個小例子上測試 mT5 標記器：

```python
inputs = tokenizer("I loved reading the Hunger Games!")
inputs
```

```python out
{'input_ids': [336, 259, 28387, 11807, 287, 62893, 295, 12507, 1], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1]}
```

在這裡我們可以看到我們在[第三章](/course/chapter3)第一次微調實驗中遇到的熟悉的 **input_ids** 和 **attention_mask** .讓我們用分詞器解碼這些輸入 ID ，可以**convert_ids_to_tokens()** 函數來查看我們正在處理什麼樣的標記器：

```python
tokenizer.convert_ids_to_tokens(inputs.input_ids)
```

```python out
['▁I', '▁', 'loved', '▁reading', '▁the', '▁Hung', 'er', '▁Games', '</s>']
```

特殊的 Unicode 字符 `▁` 和序列結束標記 `</s>` 表明我們正在處理 SentencePiece 分詞器，它基於在[第六章](/course/chapter6)中討論的Unigram分詞算法. Unigram 對多語言語料庫特別有用，因為它允許 SentencePiece 不知道重音、標點符號以及許多語言（如日語）沒有空格字符。

為了標記我們的語料庫，我們必須處理與摘要相關的細節：因為我們的標籤也是文本，它們可能會超過模型的最大上下文大小。這意味著我們需要對評論及其標題進行截斷，以確保我們不會將過長的輸入傳遞給我們的模型。 🤗 Transformers 中的分詞器提供了一個漂亮的 **as_target_tokenizer()** 函數，它允許您並行分詞並標記標籤的函數。這通常是使用預處理函數內的上下文管理器完成的，該函數首先對輸入進行編碼，然後將標籤編碼為單獨的列。以下是 mT5 的此函數的示例：

```python
max_input_length = 512
max_target_length = 30


def preprocess_function(examples):
    model_inputs = tokenizer(
        examples["review_body"], max_length=max_input_length, truncation=True
    )
    # Set up the tokenizer for targets
    with tokenizer.as_target_tokenizer():
        labels = tokenizer(
            examples["review_title"], max_length=max_target_length, truncation=True
        )

    model_inputs["labels"] = labels["input_ids"]
    return model_inputs
```

讓我們通過這段代碼來了解發生了什麼。我們做的第一件事是定義值 **max_input_length** 和 **max_target_length** ，它為我們的評論和標題的長度設置了上限。由於評論正文通常比標題大得多，我們相應地調整了這些值。然後，在 **preprocess_function()** 我們可以看到評論首先被標記化，然後是標題在 **as_target_tokenizer()** 函數里也做了相同的處理.

有了 `preprocess_function()`，我們在整個課程中廣泛使用的方便的 `Dataset.map()` 函數來標記整個語料庫是一件簡單的事情：

```python
tokenized_datasets = books_dataset.map(preprocess_function, batched=True)
```

既然語料庫已經預處理完畢，我們來看看一些常用的摘要指標。正如我們將看到的，在衡量機器生成的文本的質量方面沒有靈丹妙藥。

<Tip>

💡 你可能已經注意到我們在上面的 `Dataset.map()` 函數中使用了 `batched=True`。 這會以 1,000 個（默認）為單位對示例進行編碼，並允許您利用 🤗 Transformers 中快速標記器的多線程功能。 在可能的情況下，嘗試使用 `batched=True` 來加速您的預處理！

</Tip>


## 文本摘要的指標

<Youtube id="TMshhnrEXlg"/>

與我們在本課程中涵蓋的大多數其他任務相比，衡量文本生成任務（如摘要或翻譯）的性能並不那麼簡單。例如，對於“我喜歡閱讀飢餓遊戲”這樣的評論，有多個有效摘要，例如“我喜歡飢餓遊戲”或“飢餓遊戲是一本好書”。顯然，在生成的摘要和標籤之間應用某種精確匹配並不是一個好的解決方案——即使是人類在這樣的指標下也會表現不佳，因為我們都有自己的寫作風格。

總而言之，最常用的指標之一是[ROUGE 分數](https://en.wikipedia.org/wiki/ROUGE_(metric))（Recall-Oriented Understudy for Gisting Evaluation 的縮寫）。該指標背後的基本思想是將生成的摘要與一組通常由人類創建的參考摘要進行比較。為了更精確，假設我們要比較以下兩個摘要：

```python
generated_summary = "I absolutely loved reading the Hunger Games"
reference_summary = "I loved reading the Hunger Games"
```
比較它們的一種方法是計算重疊單詞的數量，在這種情況下為 6。但是，這有點粗糙，因此 ROUGE 是基於計算計算重疊的 _precision_ 和 _recall_ 分數。。

<Tip>

🙋 如果這是您第一次聽說精確率和召回率，請不要擔心——我們將一起通過一些明確的示例來說明一切。 這些指標通常在分類任務中遇到，因此如果您想了解在該上下文中如何定義精確度和召回率，我們建議查看 scikit-learn [指南](https://scikit-learn.org/stable /auto_examples/model_selection/plot_precision_recall.html）。

</Tip>

對於 ROUGE，recall 衡量生成的參考摘要包含了多少參考摘要。如果我們只是比較單詞，recall可以根據以下公式計算：

$$ \mathrm{Recall} = \frac{\mathrm{Number\,of\,overlapping\, words}}{\mathrm{Total\, number\, of\, words\, in\, reference\, summary}} $$

對於我們上面的簡單例子，這個公式給出了 6/6 = 1 的完美召回率；即，參考摘要中的所有單詞都已由模型生成。這聽起來可能很棒，但想象一下，如果我們生成的摘要是“我真的很喜歡整晚閱讀飢餓遊戲”。這也將有完美的recall，但可以說是一個更糟糕的總結，因為它很冗長。為了處理這些場景，我們還計算了pecision，它在 ROUGE 上下文中衡量生成的摘要中有多少是相關的：

$$ \mathrm{Precision} = \frac{\mathrm{Number\,of\,overlapping\, words}}{\mathrm{Total\, number\, of\, words\, in\, generated\, summary}} $$

將此應用到我們的詳細摘要中會得到 6/10 = 0.6 的精度，這比我們較短的摘要獲得的 6/7 = 0.86 的精度要差得多。在實踐中，通常計算精度和召回率，然後報告 F1-score（精度和召回率的調和平均值）。我們可以在 🤗 Datasets 中通過安裝 **rouge_score** 包來計算他們：

```py
!pip install rouge_score
```

然後按如下方式加載 ROUGE 指標：

```python
from datasets import load_metric

rouge_score = load_metric("rouge")
```

然後我們可以使用 **rouge_score.compute()** 一次性計算所有指標的函數：

```python
scores = rouge_score.compute(
    predictions=[generated_summary], references=[reference_summary]
)
scores
```

```python out
{'rouge1': AggregateScore(low=Score(precision=0.86, recall=1.0, fmeasure=0.92), mid=Score(precision=0.86, recall=1.0, fmeasure=0.92), high=Score(precision=0.86, recall=1.0, fmeasure=0.92)),
 'rouge2': AggregateScore(low=Score(precision=0.67, recall=0.8, fmeasure=0.73), mid=Score(precision=0.67, recall=0.8, fmeasure=0.73), high=Score(precision=0.67, recall=0.8, fmeasure=0.73)),
 'rougeL': AggregateScore(low=Score(precision=0.86, recall=1.0, fmeasure=0.92), mid=Score(precision=0.86, recall=1.0, fmeasure=0.92), high=Score(precision=0.86, recall=1.0, fmeasure=0.92)),
 'rougeLsum': AggregateScore(low=Score(precision=0.86, recall=1.0, fmeasure=0.92), mid=Score(precision=0.86, recall=1.0, fmeasure=0.92), high=Score(precision=0.86, recall=1.0, fmeasure=0.92))}
```

哇，那個輸出中有很多信息——這都是什麼意思？首先，🤗 Datasets實際上計算了精度、召回率和 F1 分數的置信區間；這些是你可以在這裡看到的 **low** , **mid** ， 和 **high** 屬性。此外，🤗 Datasets在比較生成摘要和參考摘要時，會根據不同類型的文本粒度計算各種 ROUGE 分數。這 **rouge1** 變體是一元組的重疊——這只是表達單詞重疊的一種奇特方式，這正是我們上面討論的度量標準。為了驗證這一點，讓我們輸出 **mid** 的數值：

```python
scores["rouge1"].mid
```

```python out
Score(precision=0.86, recall=1.0, fmeasure=0.92)
```
太好了，準確率和召回率匹配了！那麼其他的 ROUGE 分數呢？ **rouge2** 測量二元組之間的重疊（想想單詞對的重疊），而 **rougeL** 和 **rougeLsum** 通過在生成的和參考摘要中查找最長的公共子串來測量最長的單詞匹配序列。中的“總和” **rougeLsum** 指的是這個指標是在整個摘要上計算的，而 **rougeL** 計算為單個句子的平均值。

<Tip>

    ✏️ **試試看！** 創建您自己的生成和參考摘要示例，並查看生成的 ROUGE 分數是否與基於精確度和召回率公式的手動計算一致。 對於附加分，將文本拆分為二元組並比較“rouge2”指標的精度和召回率。

</Tip>

我們將使用這些 ROUGE 分數來跟蹤我們模型的性能，但在此之前，讓我們做每個優秀的 NLP 從業者都應該做的事情：創建一個強大而簡單的baseline！

### 創建強大的baseline

文本摘要的一個常見基線是簡單地取一篇文章的前三個句子，通常稱為 _lead-3_ 基線。 我們可以使用句號(英文使用.)來跟蹤句子邊界，但這在"U.S." or "U.N."之類的首字母縮略詞上會失敗。所以我們將使用 `nltk` 庫，它包含一個更好的算法來處理這些情況。 您可以使用 `pip` 安裝軟件包，如下所示：

```python
!pip install nltk
```

然後下載標點規則：

```python
import nltk

nltk.download("punkt")
```
接下來，我們從 `nltk` 導入句子標記器並創建一個簡單的函數來提取評論中的前三個句子。 文本摘要的約定是用換行符分隔每個摘要，因此我們也將其包含在內並在訓練示例上對其進行測試：

```python
from nltk.tokenize import sent_tokenize


def three_sentence_summary(text):
    return "\n".join(sent_tokenize(text)[:3])


print(three_sentence_summary(books_dataset["train"][1]["review_body"]))
```

```python out
'I grew up reading Koontz, and years ago, I stopped,convinced i had "outgrown" him.'
'Still,when a friend was looking for something suspenseful too read, I suggested Koontz.'
'She found Strangers.'
```

這似乎有效，所以讓我們現在實現一個函數，從數據集中提取這些“摘要”並計算baseline的 ROUGE 分數：

```python
def evaluate_baseline(dataset, metric):
    summaries = [three_sentence_summary(text) for text in dataset["review_body"]]
    return metric.compute(predictions=summaries, references=dataset["review_title"])
```

然後我們可以使用這個函數來計算驗證集上的 ROUGE 分數，並使用 Pandas 對它們進行一些美化：

```python
import pandas as pd

score = evaluate_baseline(books_dataset["validation"], rouge_score)
rouge_names = ["rouge1", "rouge2", "rougeL", "rougeLsum"]
rouge_dict = dict((rn, round(score[rn].mid.fmeasure * 100, 2)) for rn in rouge_names)
rouge_dict
```

```python out
{'rouge1': 16.74, 'rouge2': 8.83, 'rougeL': 15.6, 'rougeLsum': 15.96}
```

我們可以看到`rouge2`的分數明顯低於其他； 這可能反映了這樣一個事實，即評論標題通常很簡潔，因此lead-3 baseline過於冗長。 現在我們有了一個很好的基準，讓我們將注意力轉向微調 mT5！

{#if fw === 'pt'}

## 使用 `Trainer` API微調mT5 

微調模型以進行提取摘要與我們在本章中介紹的其他任務非常相似。 我們需要做的第一件事是從`mt5-small`檢查點加載預訓練模型。 由於摘要提取是一個序列到序列的任務，我們可以使用 AutoModelForSeq2SeqLM 類加載模型，該類會自動下載並緩存權重：

```python
from transformers import AutoModelForSeq2SeqLM

model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint)
```

{:else}

## 使用 `Keras` API微調mT5 

微調模型以進行提取摘要與我們在本章中介紹的其他任務非常相似。 我們需要做的第一件事是從`mt5-small`檢查點加載預訓練模型。 由於摘要提取是一個序列到序列的任務，我們可以使用 AutoModelForSeq2SeqLM 類加載模型，該類會自動下載並緩存權重：

```python
from transformers import TFAutoModelForSeq2SeqLM

model = TFAutoModelForSeq2SeqLM.from_pretrained(model_checkpoint)
```

{/if}

<Tip>

💡 If you're wondering why you don't see any warnings about fine-tuning the model on a downstream task, that's because for sequence-to-sequence tasks we keep all the weights of the network. Compare this to our text classification model in [Chapter 3](/course/chapter3), where the head of the pretrained model was replaced with a randomly initialized network.
💡 如果您想知道為什麼在下游任務中沒有看到任何關於微調模型的警告，那是因為對於序列到序列的任務，我們保留了網絡的所有權重。與我們在[第三章] (/course/chapter3)中的文本分類模型進行比較，文本分類模型預訓練模型的頭部被隨機初始化的網絡替換。

</Tip>

我們需要做的下一件事是登錄 Hugging Face Hub。如果您在notebook中運行此代碼，則可以使用以下實用程序函數執行此操作：

```python
from huggingface_hub import notebook_login

notebook_login()
```

這將顯示一個小部件，您可以在其中輸入您的憑據。或者，您可以在終端中運行此命令並在那裡登錄：

```
huggingface-cli login
```

{#if fw === 'pt'}

我們需要生成摘要以便在訓練期間計算 ROUGE 分數。幸運的是，🤗 Transformers 提供了專用的 `Seq2SeqTrainingArguments` 和 `Seq2SeqTrainer` 類，可以自動為我們完成這項工作！ 為了瞭解它是如何工作的，讓我們首先為我們的實驗定義超參數和其他參數：

```python
from transformers import Seq2SeqTrainingArguments

batch_size = 8
num_train_epochs = 8
# Show the training loss with every epoch
logging_steps = len(tokenized_datasets["train"]) // batch_size
model_name = model_checkpoint.split("/")[-1]

args = Seq2SeqTrainingArguments(
    output_dir=f"{model_name}-finetuned-amazon-en-es",
    evaluation_strategy="epoch",
    learning_rate=5.6e-5,
    per_device_train_batch_size=batch_size,
    per_device_eval_batch_size=batch_size,
    weight_decay=0.01,
    save_total_limit=3,
    num_train_epochs=num_train_epochs,
    predict_with_generate=True,
    logging_steps=logging_steps,
    push_to_hub=True,
)
```

在這裡， **predict_with_generate** 參數已設置為True表明我們應該在評估期間生成摘要，以便我們可以計算每個時期的 ROUGE 分數。正如在[第一章](/course/chapter1)所討論的，解碼器通過逐個預測令牌來執行推理，這是由模型的 **generate()** 方法實現的。設置 **predict_with_generate=True** 告訴 **Seq2SeqTrainer** 使用該方法進行評估。我們還調整了一些默認的超參數，例如學習率、epoch數和權重衰減，並且我們設置了 **save_total_limit** 訓練期間最多隻保存 3 個檢查點的選項——這是因為即使是 mT5 的“small”版本也使用大約 1 GB 的硬盤空間，我們可以通過限制我們保存的副本數量來節省一點空間。

`push_to_hub=True` 參數將允許我們在訓練後將模型推送到 Hub； 您將在`output_dir`定義的位置中的用戶配置文件下找到存儲庫。 請注意，您可以使用 `hub_model_id` 參數指定要推送到的存儲庫的名稱（特別是當您想要推送到組織時，您必須使用此參數）。 例如，當我們將模型推送到 [`huggingface-course` 組織](https://huggingface.co/huggingface-course) 時，我們添加了`hub_model_id="huggingface-course/mt5-finetuned-amazon-en-es"` 到 `Seq2SeqTrainingArguments`。

我們需要做的下一件事是為訓練器提供一個“compute_metrics()”函數，以便我們可以在訓練期間評估我們的模型。 總結起來，這比簡單地在模型的預測上調用 `rouge_score.compute()` 更復雜一些，因為我們需要在計算 ROUGE 分數之前將輸出和標籤解碼為文本。 下面的函數正是這樣做的，並且還利用 `nltk` 中的 `sent_tokenize()` 函數來用換行符分隔摘要語句：

```python
import numpy as np


def compute_metrics(eval_pred):
    predictions, labels = eval_pred
    # Decode generated summaries into text
    decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True)
    # Replace -100 in the labels as we can't decode them
    labels = np.where(labels != -100, labels, tokenizer.pad_token_id)
    # Decode reference summaries into text
    decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True)
    # ROUGE expects a newline after each sentence
    decoded_preds = ["\n".join(sent_tokenize(pred.strip())) for pred in decoded_preds]
    decoded_labels = ["\n".join(sent_tokenize(label.strip())) for label in decoded_labels]
    # Compute ROUGE scores
    result = rouge_score.compute(
        predictions=decoded_preds, references=decoded_labels, use_stemmer=True
    )
    # Extract the median scores
    result = {key: value.mid.fmeasure * 100 for key, value in result.items()}
    return {k: round(v, 4) for k, v in result.items()}
```

{/if}

接下來，我們需要為我們的序列到序列任務定義一個數據整理器。由於 mT5 是一個編碼器-解碼器 Transformer 模型，準備我們的批次的一個微妙之處是，在解碼過程中，我們需要將標籤向右移動一個。 這是為了確保解碼器只看到之前的真實的標籤，而不是當前或未來的標籤，這對於模型來說很容易記憶。 這類似於在 [因果語言建模](/course/chapter7/6) 等任務中如何將掩蔽的自我注意應用於輸入。

幸運的是，🤗 Transformers 提供了一個 `DataCollatorForSeq2Seq` 整理器，它將為我們動態填充輸入和標籤。 要實例化這個收集器，我們只需要提供 `tokenizer` 和 `model`：

{#if fw === 'pt'}

```python
from transformers import DataCollatorForSeq2Seq

data_collator = DataCollatorForSeq2Seq(tokenizer, model=model)
```

{:else}

```python
from transformers import DataCollatorForSeq2Seq

data_collator = DataCollatorForSeq2Seq(tokenizer, model=model, return_tensors="tf")
```

{/if}

讓我們看看這個整理器在輸入一小批示例時會產生什麼。 首先，我們需要刪除帶有字符串的列，因為整理器不知道如何填充這些元素：

```python
tokenized_datasets = tokenized_datasets.remove_columns(
    books_dataset["train"].column_names
)
```

由於 collator 需要一個 `dict` 的列表，其中每個 `dict` 代表數據集中的一個示例，我們還需要在將數據傳遞給 data collator 之前將數據整理成預期的格式：

```python
features = [tokenized_datasets["train"][i] for i in range(2)]
data_collator(features)
```

```python out
{'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, 1,
         1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
         1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]), 'input_ids': tensor([[  1494,    259,   8622,    390,    259,    262,   2316,   3435,    955,
            772,    281,    772,   1617,    263,    305,  14701,    260,   1385,
           3031,    259,  24146,    332,   1037,    259,  43906,    305,    336,
            260,      1,      0,      0,      0,      0,      0,      0],
        [   259,  27531,  13483,    259,   7505,    260, 112240,  15192,    305,
          53198,    276,    259,  74060,    263,    260,    459,  25640,    776,
           2119,    336,    259,   2220,    259,  18896,    288,   4906,    288,
           1037,   3931,    260,   7083, 101476,   1143,    260,      1]]), 'labels': tensor([[ 7483,   259,  2364, 15695,     1,  -100],
        [  259, 27531, 13483,   259,  7505,     1]]), 'decoder_input_ids': tensor([[    0,  7483,   259,  2364, 15695,     1],
        [    0,   259, 27531, 13483,   259,  7505]])}
```

這裡要注意的主要是第一個例子比第二個例子要長，所以第二個例子的 `input_ids` 和 `attention_mask` 已經在右側填充了一個 `[PAD]` 標記（其 ID 是 ` 0`）。 類似地，我們可以看到 `labels` 已用 `-100` 填充，以確保填充標記被損失函數忽略。 最後，我們可以看到一個新的 `decoder_input_ids`，它通過在第一個條目中插入 `[PAD]` 標記將標籤向右移動。

{#if fw === 'pt'}

我們終於擁有了訓練所需的所有的前期準備！我們現在只需要使用標準參數實例化訓練器：

```python
from transformers import Seq2SeqTrainer

trainer = Seq2SeqTrainer(
    model,
    args,
    train_dataset=tokenized_datasets["train"],
    eval_dataset=tokenized_datasets["validation"],
    data_collator=data_collator,
    tokenizer=tokenizer,
    compute_metrics=compute_metrics,
)
```

並啟動我們的訓練：

```python
trainer.train()
```

在訓練期間，您應該會看到訓練損失減少並且 ROUGE 分數隨著每個 epoch 增加。訓練完成後，您可以通過運行**Trainer.evaluate()** 查看最終的 ROUGE 分數 ：

```python
trainer.evaluate()
```

```python out
{'eval_loss': 3.028524398803711,
 'eval_rouge1': 16.9728,
 'eval_rouge2': 8.2969,
 'eval_rougeL': 16.8366,
 'eval_rougeLsum': 16.851,
 'eval_gen_len': 10.1597,
 'eval_runtime': 6.1054,
 'eval_samples_per_second': 38.982,
 'eval_steps_per_second': 4.914}
```

從分數中我們可以看到，我們的模型輕鬆超過了我們的lead-3 baseline——很好！最後要做的是將模型權重推送到 Hub，如下所示：

```
trainer.push_to_hub(commit_message="Training complete", tags="summarization")
```

```python out
'https://huggingface.co/huggingface-course/mt5-finetuned-amazon-en-es/commit/aa0536b829b28e73e1e4b94b8a5aacec420d40e0'
```

這會將檢查點和配置文件保存到 **output_dir** , 在將所有文件上傳到模型中心之前。通過指定 **tags** 參數，我們還確保模型中心上的小部件將是一個用於彙總管道的小部件，而不是與 mT5 架構關聯的默認文本生成小部件（有關模型標籤的更多信息，請參閱[🤗 Hub 文檔](https://huggingface.co/docs/hub/main#how-is-a-models-type-of-inference-api-and-widget-determined)）。輸出來自 **trainer.push_to_hub()** 是 Git 提交哈希的 URL，因此您可以輕鬆查看對模型存儲庫所做的更改！

在結束本節之前，讓我們看一下如何使用 🤗 Accelerate 提供的底層API對 mT5 進行微調。

{:else}

我們幾乎準備好訓練了！ 我們只需要使用我們上面定義的數據整理器將我們的數據集轉換為 tf.data.Dataset ，然後 `compile()` 和 `fit()` 模型。 首先，轉換數據集：
```python
tf_train_dataset = tokenized_datasets["train"].to_tf_dataset(
    columns=["input_ids", "attention_mask", "labels"],
    collate_fn=data_collator,
    shuffle=True,
    batch_size=8,
)
tf_eval_dataset = tokenized_datasets["validation"].to_tf_dataset(
    columns=["input_ids", "attention_mask", "labels"],
    collate_fn=data_collator,
    shuffle=False,
    batch_size=8,
)
```

現在，我們定義訓練超參數並編譯：

```python
from transformers import create_optimizer
import tensorflow as tf

# The number of training steps is the number of samples in the dataset, divided by the batch size then multiplied
# by the total number of epochs. Note that the tf_train_dataset here is a batched tf.data.Dataset,
# not the original Hugging Face Dataset, so its len() is already num_samples // batch_size.
num_train_epochs = 8
num_train_steps = len(tf_train_dataset) * num_train_epochs
model_name = model_checkpoint.split("/")[-1]

optimizer, schedule = create_optimizer(
    init_lr=5.6e-5,
    num_warmup_steps=0,
    num_train_steps=num_train_steps,
    weight_decay_rate=0.01,
)

model.compile(optimizer=optimizer)

# Train in mixed-precision float16
tf.keras.mixed_precision.set_global_policy("mixed_float16")
```

最後，我們擬合模型。 我們在每個 epoch 之後使用`PushToHubCallback`將模型保存到 Hub，這將允許我們稍後使用它進行推理：
```python
from transformers.keras_callbacks import PushToHubCallback

callback = PushToHubCallback(
    output_dir=f"{model_name}-finetuned-amazon-en-es", tokenizer=tokenizer
)

model.fit(
    tf_train_dataset, validation_data=tf_eval_dataset, callbacks=[callback], epochs=8
)
```

我們在訓練期間輸出了一些loss，但實際上我們希望看到我們之前計算的 ROUGE 指標。 要獲得這些指標，我們需要從模型生成輸出並將它們轉換為字符串。 讓我們為 ROUGE 指標構建一些標籤和預測列表以進行比較（請注意，如果您在本節中遇到import的錯誤，您可能需要`!pip install tqdm`）：

```python
from tqdm import tqdm
import numpy as np

all_preds = []
all_labels = []
for batch in tqdm(tf_eval_dataset):
    predictions = model.generate(**batch)
    decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True)
    labels = batch["labels"].numpy()
    labels = np.where(labels != -100, labels, tokenizer.pad_token_id)
    decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True)
    decoded_preds = ["\n".join(sent_tokenize(pred.strip())) for pred in decoded_preds]
    decoded_labels = ["\n".join(sent_tokenize(label.strip())) for label in decoded_labels]
    all_preds.extend(decoded_preds)
    all_labels.extend(decoded_labels)
```

一旦我們有了標籤和預測字符串列表，計算 ROUGE 分數就很容易了：

```python
result = rouge_score.compute(
    predictions=decoded_preds, references=decoded_labels, use_stemmer=True
)
result = {key: value.mid.fmeasure * 100 for key, value in result.items()}
{k: round(v, 4) for k, v in result.items()}
```

```
{'rouge1': 31.4815, 'rouge2': 25.4386, 'rougeL': 31.4815, 'rougeLsum': 31.4815}
```


{/if}

{#if fw === 'pt'}

## 使用 🤗 Accelerate 微調 mT5

使用 🤗 Accelerate 微調我們的模型與我們在 [Chapter 3](/course/chapter3) 中遇到的文本分類示例非常相似。 主要區別在於需要在訓練期間顯式生成摘要並定義我們如何計算 ROUGE 分數（回想一下，`Seq2SeqTrainer` 為我們生成了摘要）。 讓我們看看我們如何在 🤗 Accelerate 中實現這兩個要求！

### 為訓練做好一切準備

The first thing we need to do is create a `DataLoader` for each of our splits. Since the PyTorch dataloaders expect batches of tensors, we need to set the format to `"torch"` in our datasets:
我們需要做的第一件事是為每個數據集的每一個拆分創建一個`DataLoader`。 由於 PyTorch 數據加載器需要成批的張量，我們需要在數據集中將格式設置為`torch`：

```python
tokenized_datasets.set_format("torch")
```

現在我們已經有了僅由張量組成的數據集，接下來要做的是再次實例化`DataCollatorForSeq2Seq`。 為此，我們需要提供模型微調前的版本，所以讓我們從緩存中再次加載它：

```python
model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint)
```

然後我們可以實例化數據整理器並使用它來定義我們的數據加載器：

```python
from torch.utils.data import DataLoader

batch_size = 8
train_dataloader = DataLoader(
    tokenized_datasets["train"],
    shuffle=True,
    collate_fn=data_collator,
    batch_size=batch_size,
)
eval_dataloader = DataLoader(
    tokenized_datasets["validation"], collate_fn=data_collator, batch_size=batch_size
)
```

接下來要做的是定義我們想要使用的優化器。與我們的其他示例一樣，我們將使用 **AdamW** ，這適用於大多數問題：

```python
from torch.optim import AdamW

optimizer = AdamW(model.parameters(), lr=2e-5)
```

最後，我們將模型、優化器和數據加載器提供給 **accelerator.prepare()** 方法：

```python
from accelerate import Accelerator

accelerator = Accelerator()
model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare(
    model, optimizer, train_dataloader, eval_dataloader
)
```

<Tip>

🚨如果您在 TPU 上進行訓練，則需要將上述所有代碼移動到專門的訓練函數中。有關詳細信息，請參閱[第三章](/course/chapter3)。

</Tip>

現在我們已經準備好了我們索要用的對象，還有三件事要做：

* 定義學習率調度計劃。
* 實現一個功能來對摘要進行後續處理以進行評估。
* 在 Hub 上創建一個存儲庫，我們可以將模型推送到該存儲庫。

對於學習率調度，我們將使用前幾節中的標準線性衰減：

```python
from transformers import get_scheduler

num_train_epochs = 10
num_update_steps_per_epoch = len(train_dataloader)
num_training_steps = num_train_epochs * num_update_steps_per_epoch

lr_scheduler = get_scheduler(
    "linear",
    optimizer=optimizer,
    num_warmup_steps=0,
    num_training_steps=num_training_steps,
)
```

對於後續處理，我們需要一個函數，將生成的摘要拆分為由換行符分隔的句子。 這是 ROUGE 指標所期望的格式，我們可以使用以下代碼片段來實現：

```python
def postprocess_text(preds, labels):
    preds = [pred.strip() for pred in preds]
    labels = [label.strip() for label in labels]

    # ROUGE expects a newline after each sentence
    preds = ["\n".join(nltk.sent_tokenize(pred)) for pred in preds]
    labels = ["\n".join(nltk.sent_tokenize(label)) for label in labels]

    return preds, labels
```

如果你還記得我們是如何定義 `Seq2SeqTrainer` 的 `compute_metrics()` 函數的，這對你來說應該很熟悉。

最後，我們需要在 Hugging Face Hub 上創建一個模型存儲庫。 為此，我們可以使用🤗 Hub 庫的get_full_repo_name。 我們只需要為我們的存儲庫定義一個名稱，該庫有一個非常好用的函數可以將存儲庫 ID 與用戶配置文件結合起來：
```python
from huggingface_hub import get_full_repo_name

model_name = "test-bert-finetuned-squad-accelerate"
repo_name = get_full_repo_name(model_name)
repo_name
```

```python out
'lewtun/mt5-finetuned-amazon-en-es-accelerate'
```

現在我們可以使用這個存儲庫名稱將本地版本克隆到我們的結果目錄中，該目錄將存儲訓練的模型：

```python
from huggingface_hub import Repository

output_dir = "results-mt5-finetuned-squad-accelerate"
repo = Repository(output_dir, clone_from=repo_name)
```
這將允許我們在訓練期間通過調用 `repo.push_to_hub()` 方法將模型推送到 Hub！ 現在讓我們通過寫出完整的訓練循環來結束我們的分析。

### 訓練循環

文本摘要的訓練循環與我們遇到的其他 🤗 Accelerate 示例非常相似，大致分為四個主要步驟：這

1. 通過在每個epoch 迭代 `train_dataloader` 中的所有示例來訓練模型。
2. 在每個 epoch 結束時生成模型摘要，首先生成標記，然後將它們（和參考摘要）解碼為文本。
3. 使用我們之前看到的相同技術計算 ROUGE 分數。
4. 保存檢查點並將所有內容推送到 Hub。 在這裡，我們依賴 `Repository` 對象的巧妙的 `blocking=False` 參數，以便我們可以在每個 epoch 異步地上傳檢查點。 這使我們能夠繼續訓練，而不必等待與 GB 大小的模型慢呼呼的上傳！

這些步驟可以在以下代碼塊中看到：

```python
from tqdm.auto import tqdm
import torch
import numpy as np

progress_bar = tqdm(range(num_training_steps))

for epoch in range(num_train_epochs):
    # Training
    model.train()
    for step, batch in enumerate(train_dataloader):
        outputs = model(**batch)
        loss = outputs.loss
        accelerator.backward(loss)

        optimizer.step()
        lr_scheduler.step()
        optimizer.zero_grad()
        progress_bar.update(1)

    # Evaluation
    model.eval()
    for step, batch in enumerate(eval_dataloader):
        with torch.no_grad():
            generated_tokens = accelerator.unwrap_model(model).generate(
                batch["input_ids"],
                attention_mask=batch["attention_mask"],
            )

            generated_tokens = accelerator.pad_across_processes(
                generated_tokens, dim=1, pad_index=tokenizer.pad_token_id
            )
            labels = batch["labels"]

            # If we did not pad to max length, we need to pad the labels too
            labels = accelerator.pad_across_processes(
                batch["labels"], dim=1, pad_index=tokenizer.pad_token_id
            )

            generated_tokens = accelerator.gather(generated_tokens).cpu().numpy()
            labels = accelerator.gather(labels).cpu().numpy()

            # Replace -100 in the labels as we can't decode them
            labels = np.where(labels != -100, labels, tokenizer.pad_token_id)
            if isinstance(generated_tokens, tuple):
                generated_tokens = generated_tokens[0]
            decoded_preds = tokenizer.batch_decode(
                generated_tokens, skip_special_tokens=True
            )
            decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True)

            decoded_preds, decoded_labels = postprocess_text(
                decoded_preds, decoded_labels
            )

            rouge_score.add_batch(predictions=decoded_preds, references=decoded_labels)

    # Compute metrics
    result = rouge_score.compute()
    # Extract the median ROUGE scores
    result = {key: value.mid.fmeasure * 100 for key, value in result.items()}
    result = {k: round(v, 4) for k, v in result.items()}
    print(f"Epoch {epoch}:", result)

    # Save and upload
    accelerator.wait_for_everyone()
    unwrapped_model = accelerator.unwrap_model(model)
    unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save)
    if accelerator.is_main_process:
        tokenizer.save_pretrained(output_dir)
        repo.push_to_hub(
            commit_message=f"Training in progress epoch {epoch}", blocking=False
        )
```

```python out
Epoch 0: {'rouge1': 5.6351, 'rouge2': 1.1625, 'rougeL': 5.4866, 'rougeLsum': 5.5005}
Epoch 1: {'rouge1': 9.8646, 'rouge2': 3.4106, 'rougeL': 9.9439, 'rougeLsum': 9.9306}
Epoch 2: {'rouge1': 11.0872, 'rouge2': 3.3273, 'rougeL': 11.0508, 'rougeLsum': 10.9468}
Epoch 3: {'rouge1': 11.8587, 'rouge2': 4.8167, 'rougeL': 11.7986, 'rougeLsum': 11.7518}
Epoch 4: {'rouge1': 12.9842, 'rouge2': 5.5887, 'rougeL': 12.7546, 'rougeLsum': 12.7029}
Epoch 5: {'rouge1': 13.4628, 'rouge2': 6.4598, 'rougeL': 13.312, 'rougeLsum': 13.2913}
Epoch 6: {'rouge1': 12.9131, 'rouge2': 5.8914, 'rougeL': 12.6896, 'rougeLsum': 12.5701}
Epoch 7: {'rouge1': 13.3079, 'rouge2': 6.2994, 'rougeL': 13.1536, 'rougeLsum': 13.1194}
Epoch 8: {'rouge1': 13.96, 'rouge2': 6.5998, 'rougeL': 13.9123, 'rougeLsum': 13.7744}
Epoch 9: {'rouge1': 14.1192, 'rouge2': 7.0059, 'rougeL': 14.1172, 'rougeLsum': 13.9509}
```

就是這樣！ 運行此程序後，您將獲得與我們使用“Trainer”獲得的模型和結果非常相似的模型和結果。

{/if}

## 使用您微調的模型

將模型推送到 Hub 後，您可以通過推理小部件或“管道”對象來使用它，如下所示：

```python
from transformers import pipeline

hub_model_id = "huggingface-course/mt5-small-finetuned-amazon-en-es"
summarizer = pipeline("summarization", model=hub_model_id)
```

我們可以將測試集中的一些示例（模型還沒有看到）提供給我們的管道，以瞭解生成摘要的質量。 首先讓我們實現一個簡單的函數來一起顯示評論、標題和生成的摘要：

```python
def print_summary(idx):
    review = books_dataset["test"][idx]["review_body"]
    title = books_dataset["test"][idx]["review_title"]
    summary = summarizer(books_dataset["test"][idx]["review_body"])[0]["summary_text"]
    print(f"'>>> Review: {review}'")
    print(f"\n'>>> Title: {title}'")
    print(f"\n'>>> Summary: {summary}'")
```

讓我們看一下我們得到的一個英文例子：

```python
print_summary(100)
```

```python out
'>>> Review: Nothing special at all about this product... the book is too small and stiff and hard to write in. The huge sticker on the back doesn’t come off and looks super tacky. I would not purchase this again. I could have just bought a journal from the dollar store and it would be basically the same thing. It’s also really expensive for what it is.'

'>>> Title: Not impressed at all... buy something else'

'>>> Summary: Nothing special at all about this product'
```

這還不錯！ 我們可以看到，我們的模型實際上已經能夠通過增加部分新詞來執行抽象摘要。 也許我們模型最酷的方面是它是雙語的，所以我們還可以生成西班牙語評論的摘要：

```python
print_summary(0)
```

```python out
'>>> Review: Es una trilogia que se hace muy facil de leer. Me ha gustado, no me esperaba el final para nada'

'>>> Title: Buena literatura para adolescentes'

'>>> Summary: Muy facil de leer'
```

摘要翻譯成了英文的“非常容易閱讀”，在這種情況下，我們可以看到它是直接從評論中提取的。 這顯示了 mT5 模型的多功能性，並讓您體驗了處理多語言語料庫的感覺！

接下來，我們將把注意力轉向稍微複雜的任務：從頭開始訓練語言模型。


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter7/5.mdx" />

### 章節簡介
https://huggingface.co/learn/course/zh-TW/chapter7/1.md

# 章節簡介

在[第三章](/course/chapter3)，您瞭解瞭如何微調文本分類的模型。在本章中，我們將處理以下常見NLP任務：

- 標記(token)分類
- 遮罩語言建模（如BERT）
- 提取文本摘要
- 翻譯
- 因果語言建模預訓練（如GPT-2）
- 問答

{#if fw === 'pt'}

為此，您需要利用[第三章](/course/chapter3)中學到的`Trainer` API 和🤗Accelerate 庫、[第五章](/course/chapter5)中的 🤗 Datasets 庫以及[第六章](/course/chapter6)中的 🤗 Tokenizers 庫的所有知識。我們還會將結果上傳到模型中心，就像我們在[第四章](/course/chapter4)中所做的那樣，所以這確實是將之前所有內容彙集在一起的章節！

每個部分都可以獨立閱讀，並將向您展示如何使用API或按照您自己的訓練循環訓練模型，使用🤗 Accelerate 加速。你可以隨意跳過其中一部分，把注意力集中在你最感興趣的那一部分：API可以優化或訓練您的模型而無需擔心幕後發生了什麼，而訓練循環使用可以讓您更輕鬆地自定義所需的任何結構。

{:else}

為此，您需要利用[第三章](/course/chapter3)中學到的有關Keras API、[第五章](/course/chapter5)中的 🤗 Datasets 庫以及[第六章](/course/chapter6)中的 🤗 Tokenizers 庫的所有知識。我們還會將結果上傳到模型中心，就像我們在[第四章](/course/chapter4)中所做的那樣，所以這確實是將之前所有內容彙集在一起的章節！

每個部分都可以獨立閱讀。

{/if}


<Tip>

如果您按順序閱讀這些部分，您會注意到它們有很多共同的代碼和陳述。 重複是有意為之的，讓您可以深入（或稍後返回）任何您感興趣的任務並找到一個完整的工作示例。

</Tip>


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter7/1.mdx" />

### 微調掩碼語言模型
https://huggingface.co/learn/course/zh-TW/chapter7/3.md

# 微調掩碼語言模型

{#if fw === 'pt'}

<DocNotebookDropdown
  classNames="absolute z-10 right-0 top-0"
  options={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/chapter7/section3_pt.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/chapter7/section3_pt.ipynb"},
]} />

{:else}

<DocNotebookDropdown
  classNames="absolute z-10 right-0 top-0"
  options={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/chapter7/section3_tf.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/chapter7/section3_tf.ipynb"},
]} />

{/if}

對於許多涉及 Transformer 模型的 NLP 程序, 你可以簡單地從 Hugging Face Hub 中獲取一個預訓練的模型, 然後直接在你的數據上對其進行微調, 以完成手頭的任務。只要用於預訓練的語料庫與用於微調的語料庫沒有太大區別, 遷移學習通常會產生很好的結果。 

但是, 在某些情況下, 你需要先微調數據上的語言模型, 然後再訓練特定於任務的head。例如, 如果您的數據集包含法律合同或科學文章, 像 BERT 這樣的普通 Transformer 模型通常會將您語料庫中的特定領域詞視為稀有標記, 結果性能可能不盡如人意。通過在域內數據上微調語言模型, 你可以提高許多下游任務的性能, 這意味著您通常只需執行一次此步驟!

這種在域內數據上微調預訓練語言模型的過程通常稱為 _領域適應_。 它於 2018 年由 [ULMFiT](https://arxiv.org/abs/1801.06146)推廣, 這是使遷移學習真正適用於 NLP 的首批神經架構之一 (基於 LSTM)。 下圖顯示了使用 ULMFiT 進行域自適應的示例; 在本節中, 我們將做類似的事情, 但使用的是 Transformer 而不是 LSTM!

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter7/ulmfit.svg" alt="ULMFiT."/>
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter7/ulmfit-dark.svg" alt="ULMFiT."/>
</div>

在本節結束時, 你將在Hub上擁有一個[掩碼語言模型(masked language model)](https://huggingface.co/huggingface-course/distilbert-base-uncased-finetuned-imdb?text=This+is+a+great+%5BMASK%5D.), 該模型可以自動完成句子, 如下所示:

<iframe src="https://course-demos-distilbert-base-uncased-finetune-7400b54.hf.space" frameBorder="0" height="300" title="Gradio app" class="block dark:hidden container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>
<iframe src="https://course-demos-distilbert-base-uncased-finetuned-imdb-darkmode.hf.space" frameBorder="0" height="300" title="Gradio app" class="hidden dark:block container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

讓我們開始吧!

<Youtube id="mqElG5QJWUg"/>

<Tip>

🙋 如果您對“掩碼語言建模”和“預訓練模型”這兩個術語感到陌生, 請查看[第一章](/course/chapter1), 我們在其中解釋了所有這些核心概念, 並附有視頻!

</Tip>

## 選擇用於掩碼語言建模的預訓練模型

首先, 讓我們為掩碼語言建模選擇一個合適的預訓練模型。如以下屏幕截圖所示, 你可以通過在[Hugging Face Hub](https://huggingface.co/models?pipeline_tag=fill-mask&sort=downloads)上應用"Fill-Mask"過濾器找到:

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter7/mlm-models.png" alt="Hub models." width="80%"/>
</div>

儘管 BERT 和 RoBERTa 系列模型的下載量最大, 但我們將使用名為 [DistilBERT](https://huggingface.co/distilbert-base-uncased)的模型。
可以更快地訓練, 而下游性能幾乎沒有損失。這個模型使用一種稱為[_知識蒸餾_](https://en.wikipedia.org/wiki/Knowledge_distillation)的特殊技術進行訓練, 其中使用像 BERT 這樣的大型“教師模型”來指導參數少得多的“學生模型”的訓練。在本節中對知識蒸餾細節的解釋會使我們離題太遠, 但如果你有興趣, 可以閱讀所有相關內容 [_Natural Language Processing with Transformers_](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/) (俗稱Transformers教科書)。

{#if fw === 'pt'}

讓我們繼續, 使用 `AutoModelForMaskedLM` 類下載 DistilBERT:

```python
from transformers import AutoModelForMaskedLM

model_checkpoint = "distilbert-base-uncased"
model = AutoModelForMaskedLM.from_pretrained(model_checkpoint)
```

我們可以通過調用 `num_parameters()` 方法查看模型有多少參數:

```python
distilbert_num_parameters = model.num_parameters() / 1_000_000
print(f"'>>> DistilBERT number of parameters: {round(distilbert_num_parameters)}M'")
print(f"'>>> BERT number of parameters: 110M'")
```

```python out
'>>> DistilBERT number of parameters: 67M'
'>>> BERT number of parameters: 110M'
```

{:else}

讓我們繼續, 使用 `AutoModelForMaskedLM` 類下載 DistilBERT:

```python
from transformers import TFAutoModelForMaskedLM

model_checkpoint = "distilbert-base-uncased"
model = TFAutoModelForMaskedLM.from_pretrained(model_checkpoint)
```

我們可以通過調用  `summary()` 方法查看模型有多少參數:

```python
model(model.dummy_inputs)  # Build the model
model.summary()
```

```python out
Model: "tf_distil_bert_for_masked_lm"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
distilbert (TFDistilBertMain multiple                  66362880  
_________________________________________________________________
vocab_transform (Dense)      multiple                  590592    
_________________________________________________________________
vocab_layer_norm (LayerNorma multiple                  1536      
_________________________________________________________________
vocab_projector (TFDistilBer multiple                  23866170  
=================================================================
Total params: 66,985,530
Trainable params: 66,985,530
Non-trainable params: 0
_________________________________________________________________
```

{/if}

DistilBERT 大約有 6700 萬個參數, 大約比 BERT 基本模型小兩倍, 這大致意味著訓練的速度提高了兩倍 -- 非常棒! 現在讓我們看看這個模型預測什麼樣的標記最有可能完成一小部分文本:

```python
text = "This is a great [MASK]."
```

作為人類, 我們可以想象 `[MASK]` 標記有很多可能性, 例如 "day"、 "ride" 或者 "painting"。對於預訓練模型, 預測取決於模型所訓練的語料庫, 因為它會學習獲取數據中存在的統計模式。與 BERT 一樣, DistilBERT 在[English Wikipedia](https://huggingface.co/datasets/wikipedia) 和 [BookCorpus](https://huggingface.co/datasets/bookcorpus) 數據集上進行預訓練, 所以我們期望對 `[MASK]` 的預測能夠反映這些領域。為了預測掩碼, 我們需要 DistilBERT 的標記器來生成模型的輸入, 所以讓我們也從 Hub 下載它:

```python
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)
```

使用標記器和模型, 我們現在可以將我們的文本示例傳遞給模型, 提取 logits, 並打印出前 5 個候選:

{#if fw === 'pt'}

```python
import torch

inputs = tokenizer(text, return_tensors="pt")
token_logits = model(**inputs).logits
# Find the location of [MASK] and extract its logits
mask_token_index = torch.where(inputs["input_ids"] == tokenizer.mask_token_id)[1]
mask_token_logits = token_logits[0, mask_token_index, :]
# Pick the [MASK] candidates with the highest logits
top_5_tokens = torch.topk(mask_token_logits, 5, dim=1).indices[0].tolist()

for token in top_5_tokens:
    print(f"'>>> {text.replace(tokenizer.mask_token, tokenizer.decode([token]))}'")
```

{:else}

```python
import numpy as np
import tensorflow as tf

inputs = tokenizer(text, return_tensors="np")
token_logits = model(**inputs).logits
# Find the location of [MASK] and extract its logits
mask_token_index = np.argwhere(inputs["input_ids"] == tokenizer.mask_token_id)[0, 1]
mask_token_logits = token_logits[0, mask_token_index, :]
# Pick the [MASK] candidates with the highest logits
# We negate the array before argsort to get the largest, not the smallest, logits
top_5_tokens = np.argsort(-mask_token_logits)[:5].tolist()

for token in top_5_tokens:
    print(f">>> {text.replace(tokenizer.mask_token, tokenizer.decode([token]))}")
```

{/if}

```python out
'>>> This is a great deal.'
'>>> This is a great success.'
'>>> This is a great adventure.'
'>>> This is a great idea.'
'>>> This is a great feat.'
```

我們可以從輸出中看到模型的預測是指日常用語, 鑑於英語維基百科的基礎, 這也許並不奇怪。讓我們看看我們如何將這個領域改變為更小眾的東西 -- 高度兩極分化的電影評論!


## 數據集

為了展示域適配, 我們將使用著名的[大型電影評論數據集(Large Movie Review Dataset)](https://huggingface.co/datasets/imdb) (或者簡稱為IMDb), 這是一個電影評論語料庫, 通常用於對情感分析模型進行基準測試。通過在這個語料庫上對 DistilBERT 進行微調, 我們預計語言模型將根據維基百科的事實數據調整其詞彙表, 這些數據已經預先訓練到電影評論中更主觀的元素。我們可以使用🤗 Datasets中的`load_dataset()`函數從Hugging Face 中獲取數據:

```python
from datasets import load_dataset

imdb_dataset = load_dataset("imdb")
imdb_dataset
```

```python out
DatasetDict({
    train: Dataset({
        features: ['text', 'label'],
        num_rows: 25000
    })
    test: Dataset({
        features: ['text', 'label'],
        num_rows: 25000
    })
    unsupervised: Dataset({
        features: ['text', 'label'],
        num_rows: 50000
    })
})
```

我們可以看到 `train` 和 `test` 每個拆分包含 25,000 條評論, 而有一個未標記的拆分稱為 `unsupervised` 包含 50,000 條評論。讓我們看一些示例, 以瞭解我們正在處理的文本類型。正如我們在本課程的前幾章中所做的那樣, 我們將鏈接 `Dataset.shuffle()` 和 `Dataset.select()` 函數創建隨機樣本:

```python
sample = imdb_dataset["train"].shuffle(seed=42).select(range(3))

for row in sample:
    print(f"\n'>>> Review: {row['text']}'")
    print(f"'>>> Label: {row['label']}'")
```

```python out

'>>> Review: This is your typical Priyadarshan movie--a bunch of loony characters out on some silly mission. His signature climax has the entire cast of the film coming together and fighting each other in some crazy moshpit over hidden money. Whether it is a winning lottery ticket in Malamaal Weekly, black money in Hera Pheri, "kodokoo" in Phir Hera Pheri, etc., etc., the director is becoming ridiculously predictable. Don\'t get me wrong; as clichéd and preposterous his movies may be, I usually end up enjoying the comedy. However, in most his previous movies there has actually been some good humor, (Hungama and Hera Pheri being noteworthy ones). Now, the hilarity of his films is fading as he is using the same formula over and over again.<br /><br />Songs are good. Tanushree Datta looks awesome. Rajpal Yadav is irritating, and Tusshar is not a whole lot better. Kunal Khemu is OK, and Sharman Joshi is the best.'
'>>> Label: 0'

'>>> Review: Okay, the story makes no sense, the characters lack any dimensionally, the best dialogue is ad-libs about the low quality of movie, the cinematography is dismal, and only editing saves a bit of the muddle, but Sam" Peckinpah directed the film. Somehow, his direction is not enough. For those who appreciate Peckinpah and his great work, this movie is a disappointment. Even a great cast cannot redeem the time the viewer wastes with this minimal effort.<br /><br />The proper response to the movie is the contempt that the director San Peckinpah, James Caan, Robert Duvall, Burt Young, Bo Hopkins, Arthur Hill, and even Gig Young bring to their work. Watch the great Peckinpah films. Skip this mess.'
'>>> Label: 0'

'>>> Review: I saw this movie at the theaters when I was about 6 or 7 years old. I loved it then, and have recently come to own a VHS version. <br /><br />My 4 and 6 year old children love this movie and have been asking again and again to watch it. <br /><br />I have enjoyed watching it again too. Though I have to admit it is not as good on a little TV.<br /><br />I do not have older children so I do not know what they would think of it. <br /><br />The songs are very cute. My daughter keeps singing them over and over.<br /><br />Hope this helps.'
'>>> Label: 1'
```

是的, 這些肯定是電影評論, 如果你年齡足夠,你甚至可能會理解上次評論中關於擁有 VHS 版本的評論😜! 雖然我們不需要語言建模的標籤, 但我們已經可以看到 `0` 表示負面評論, 而 `1` 對應正面。

<Tip>

✏️ **試試看!** 創建 `無監督` 拆分的隨機樣本, 並驗證標籤既不是 `0` 也不是 `1`。在此過程中, 你還可以檢查 `train` 和 `test` 拆分中的標籤是否確實為 `0` 或 `1` -- 這是每個 NLP 從業者在新項目開始時都應該執行的有用的健全性檢查!

</Tip>

現在我們已經快速瀏覽了數據, 讓我們深入研究為掩碼語言建模做準備。正如我們將看到的, 與我們在[第三章](/course/chapter3)中看到的序列分類任務相比, 還需要採取一些額外的步驟。讓我們繼續!

## 預處理數據

<Youtube id="8PmhEIXhBvI"/>

對於自迴歸和掩碼語言建模, 一個常見的預處理步驟是連接所有示例, 然後將整個語料庫拆分為相同大小的塊。 這與我們通常的方法完全不同, 我們只是簡單地標記單個示例。為什麼要將所有內容連接在一起? 原因是單個示例如果太長可能會被截斷, 這將導致丟失可能對語言建模任務有用的信息!

因此, 我們將像往常一樣首先標記我們的語料庫, 但是 _沒有_ 在我們的標記器中設置 `truncation=True` 選項。 我們還將獲取可用的單詞 ID ((如果我們使用快速標記器, 它們是可用的, 如 [第六章](/course/chapter6/3)中所述), 因為我們稍後將需要它們來進行全字屏蔽。我們將把它包裝在一個簡單的函數中, 當我們在做的時候, 我們將刪除 `text` 和 `label` 列, 因為我們不再需要它們:

```python
def tokenize_function(examples):
    result = tokenizer(examples["text"])
    if tokenizer.is_fast:
        result["word_ids"] = [result.word_ids(i) for i in range(len(result["input_ids"]))]
    return result


# Use batched=True to activate fast multithreading!
tokenized_datasets = imdb_dataset.map(
    tokenize_function, batched=True, remove_columns=["text", "label"]
)
tokenized_datasets
```

```python out
DatasetDict({
    train: Dataset({
        features: ['attention_mask', 'input_ids', 'word_ids'],
        num_rows: 25000
    })
    test: Dataset({
        features: ['attention_mask', 'input_ids', 'word_ids'],
        num_rows: 25000
    })
    unsupervised: Dataset({
        features: ['attention_mask', 'input_ids', 'word_ids'],
        num_rows: 50000
    })
})
```

由於 DistilBERT 是一個類似 BERT 的模型, 我們可以看到編碼文本由我們在其他章節中看到的 `input_ids` 和 `attention_mask` 組成, 以及我們添加的 `word_ids`。 

現在我們已經標記了我們的電影評論, 下一步是將它們組合在一起並將結果分成塊。 但是這些塊應該有多大? 這最終將取決於你可用的 GPU 內存量, 但一個好的起點是查看模型的最大上下文大小是多少。這可以通過檢查標記器的 `model_max_length` 屬性來判斷:

```python
tokenizer.model_max_length
```

```python out
512
```

該值來自於與檢查點相關聯的 *tokenizer_config.json* 文件; 在這種情況下, 我們可以看到上下文大小是 512 個標記, 就像 BERT 一樣。

<Tip>

✏️ **試試看!** 一些 Transformer 模型, 例如 [BigBird](https://huggingface.co/google/bigbird-roberta-base) 和 [Longformer](hf.co/allenai/longformer-base-4096), 它們具有比BERT和其他早期Transformer模型更長的上下文長度。為這些檢查點之一實例化標記器, 並驗證 `model_max_length` 是否與模型卡上引用的內容一致。

</Tip>

因此, 以便在像Google Colab 那樣的 GPU 上運行我們的實驗, 我們將選擇可以放入內存的更小一些的東西:

```python
chunk_size = 128
```

<Tip warning={true}>

請注意, 在實際場景中使用較小的塊大小可能是有害的, 因此你應該使用與將應用模型的用例相對應的大小。

</Tip>

有趣的來了。為了展示串聯是如何工作的, 讓我們從我們的標記化訓練集中取一些評論並打印出每個評論的標記數量:

```python
# Slicing produces a list of lists for each feature
tokenized_samples = tokenized_datasets["train"][:3]

for idx, sample in enumerate(tokenized_samples["input_ids"]):
    print(f"'>>> Review {idx} length: {len(sample)}'")
```

```python out
'>>> Review 0 length: 200'
'>>> Review 1 length: 559'
'>>> Review 2 length: 192'
```

然後我們可以用一個簡單的字典理解來連接所有例子, 如下所示:

```python
concatenated_examples = {
    k: sum(tokenized_samples[k], []) for k in tokenized_samples.keys()
}
total_length = len(concatenated_examples["input_ids"])
print(f"'>>> Concatenated reviews length: {total_length}'")
```

```python out
'>>> Concatenated reviews length: 951'
```

很棒, 總長度檢查出來了 -- 現在, 讓我們將連接的評論拆分為大小為 `block_size` 的塊。為此, 我們迭代了 `concatenated_examples` 中的特徵, 並使用列表理解來創建每個特徵的切片。結果是每個特徵的塊字典:

```python
chunks = {
    k: [t[i : i + chunk_size] for i in range(0, total_length, chunk_size)]
    for k, t in concatenated_examples.items()
}

for chunk in chunks["input_ids"]:
    print(f"'>>> Chunk length: {len(chunk)}'")
```

```python out
'>>> Chunk length: 128'
'>>> Chunk length: 128'
'>>> Chunk length: 128'
'>>> Chunk length: 128'
'>>> Chunk length: 128'
'>>> Chunk length: 128'
'>>> Chunk length: 128'
'>>> Chunk length: 55'
```

正如你在這個例子中看到的, 最後一個塊通常會小於最大塊大小。有兩種主要的策略來處理這個問題:

* 如果最後一個塊小於 `chunk_size`, 請刪除它。
* 填充最後一個塊, 直到其長度等於 `chunk_size`。

我們將在這裡採用第一種方法, 因此讓我們將上述所有邏輯包裝在一個函數中, 我們可以將其應用於我們的標記化數據集:

```python
def group_texts(examples):
    # Concatenate all texts
    concatenated_examples = {k: sum(examples[k], []) for k in examples.keys()}
    # Compute length of concatenated texts
    total_length = len(concatenated_examples[list(examples.keys())[0]])
    # We drop the last chunk if it's smaller than chunk_size
    total_length = (total_length // chunk_size) * chunk_size
    # Split by chunks of max_len
    result = {
        k: [t[i : i + chunk_size] for i in range(0, total_length, chunk_size)]
        for k, t in concatenated_examples.items()
    }
    # Create a new labels column
    result["labels"] = result["input_ids"].copy()
    return result
```

注意, 在 `group_texts()` 的最後一步中, 我們創建了一個新的 `labels` 列, 它是 `input_ids` 列的副本。我們很快就會看到, 這是因為在掩碼語言建模中, 目標是預測輸入批次中隨機掩碼的標記, 並通過創建一個 `labels` 列, 們為我們的語言模型提供了基礎事實以供學習。

現在, 讓我們使用我們可信賴的 `Dataset.map()` 函數將 `group_texts()` 應用到我們的標記化數據集:

```python
lm_datasets = tokenized_datasets.map(group_texts, batched=True)
lm_datasets
```

```python out
DatasetDict({
    train: Dataset({
        features: ['attention_mask', 'input_ids', 'labels', 'word_ids'],
        num_rows: 61289
    })
    test: Dataset({
        features: ['attention_mask', 'input_ids', 'labels', 'word_ids'],
        num_rows: 59905
    })
    unsupervised: Dataset({
        features: ['attention_mask', 'input_ids', 'labels', 'word_ids'],
        num_rows: 122963
    })
})
```

你可以看到, 對文本進行分組, 然後對文本進行分塊, 產生的示例比我們最初的 25,000 用於 `train`和 `test` 拆分的示例多得多。那是因為我們現在有了涉及 _連續標記_ 的示例, 這些示例跨越了原始語料庫中的多個示例。你可以通過在其中一個塊中查找特殊的 `[SEP]` 和 `[CLS]` 標記來明確的看到這一點:

```python
tokenizer.decode(lm_datasets["train"][1]["input_ids"])
```

```python out
".... at.......... high. a classic line : inspector : i'm here to sack one of your teachers. student : welcome to bromwell high. i expect that many adults of my age think that bromwell high is far fetched. what a pity that it isn't! [SEP] [CLS] homelessness ( or houselessness as george carlin stated ) has been an issue for years but never a plan to help those on the street that were once considered human who did everything from going to school, work, or vote for the matter. most people think of the homeless"
```

在此示例中, 你可以看到兩篇重疊的電影評論, 一篇關於高中電影, 另一篇關於無家可歸。 讓我們也看看掩碼語言建模的標籤是什麼樣的:

```python out
tokenizer.decode(lm_datasets["train"][1]["labels"])
```

```python out
".... at.......... high. a classic line : inspector : i'm here to sack one of your teachers. student : welcome to bromwell high. i expect that many adults of my age think that bromwell high is far fetched. what a pity that it isn't! [SEP] [CLS] homelessness ( or houselessness as george carlin stated ) has been an issue for years but never a plan to help those on the street that were once considered human who did everything from going to school, work, or vote for the matter. most people think of the homeless"
```

正如前面的 `group_texts()` 函數所期望的那樣, 這看起來與解碼後的 `input_ids` 相同 -- 但是我們的模型怎麼可能學到任何東西呢? 我們錯過了一個關鍵步驟: 在輸入中的隨機位置插入 `[MASK]` 標記! 讓我們看看如何使用特殊的數據整理器在微調期間即時執行此操作。

## 使用 `Trainer` API 微調DistilBERT

微調屏蔽語言模型幾乎與微調序列分類模型相同, 就像我們在 [第三章](/course/chapter3)所作的那樣。 唯一的區別是我們需要一個特殊的數據整理器, 它可以隨機屏蔽每批文本中的一些標記。幸運的是, 🤗 Transformers 為這項任務準備了專用的 `DataCollatorForLanguageModeling` 。我們只需要將它轉遞給標記器和一個 `mlm_probability` 參數, 該參數指定要屏蔽的標記的分數。我們將選擇 15%, 這是 BERT 使用的數量也是文獻中的常見選擇:

```python
from transformers import DataCollatorForLanguageModeling

data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm_probability=0.15)
```

要了解隨機掩碼的工作原理, 讓我們向數據整理器提供一些示例。由於它需要一個 `dict` 的列表, 其中每個 `dict` 表示單個連續文本塊, 我們首先迭代數據集, 然後再將批次提供給整理器。我們刪除了這個數據整理器的 `"word_ids"` 鍵, 因為它不需要它:

```python
samples = [lm_datasets["train"][i] for i in range(2)]
for sample in samples:
    _ = sample.pop("word_ids")

for chunk in data_collator(samples)["input_ids"]:
    print(f"\n'>>> {tokenizer.decode(chunk)}'")
```

```python output
'>>> [CLS] bromwell [MASK] is a cartoon comedy. it ran at the same [MASK] as some other [MASK] about school life, [MASK] as " teachers ". [MASK] [MASK] [MASK] in the teaching [MASK] lead [MASK] to believe that bromwell high\'[MASK] satire is much closer to reality than is " teachers ". the scramble [MASK] [MASK] financially, the [MASK]ful students whogn [MASK] right through [MASK] pathetic teachers\'pomp, the pettiness of the whole situation, distinction remind me of the schools i knew and their students. when i saw [MASK] episode in [MASK] a student repeatedly tried to burn down the school, [MASK] immediately recalled. [MASK]...'

'>>> .... at.. [MASK]... [MASK]... high. a classic line plucked inspector : i\'[MASK] here to [MASK] one of your [MASK]. student : welcome to bromwell [MASK]. i expect that many adults of my age think that [MASK]mwell [MASK] is [MASK] fetched. what a pity that it isn\'t! [SEP] [CLS] [MASK]ness ( or [MASK]lessness as george 宇in stated )公 been an issue for years but never [MASK] plan to help those on the street that were once considered human [MASK] did everything from going to school, [MASK], [MASK] vote for the matter. most people think [MASK] the homeless'
```

很棒, 成功了! 我們可以看到, `[MASK]` 標記已隨機插入我們文本中的不同位置。 這些將是我們的模型在訓練期間必須預測的標記 -- 數據整理器的美妙之處在於, 它將隨機化每個批次的 `[MASK]` 插入! 

<Tip>

✏️ **試試看!** 多次運行上面的代碼片段, 看看隨機屏蔽發生在你眼前! 還要將 `tokenizer.decode()` 方法替換為 `tokenizer.convert_ids_to_tokens()` 以查看有時會屏蔽給定單詞中的單個標記, 而不是其他標記。

</Tip>

{#if fw === 'pt'}

隨機掩碼的一個副作用是, 當使用 `Trainer` 時, 我們的評估指標將不是確定性的, 因為我們對訓練集和測試集使用相同的數據整理器。稍後我們會看到, 當我們使用 🤗 Accelerate 進行微調時, 我們將如何利用自定義評估循環的靈活性來凍結隨機性。

{/if}

在為掩碼語言建模訓練模型時, 可以使用的一種技術是將整個單詞一起屏蔽, 而不僅僅是單個標記。這種方法稱為 _全詞屏蔽_。 如果我們想使用全詞屏蔽, 我們需要自己構建一個數據整理器。數據整理器只是一個函數, 它接受一個樣本列表並將它們轉換為一個批次, 所以現在讓我們這樣做吧! 我們將使用之前計算的單詞 ID 在單詞索引和相應標記之間進行映射, 然後隨機決定要屏蔽哪些單詞並將該屏蔽應用於輸入。請注意, 除了與掩碼對應的標籤外, 所有的標籤均為 `-100`。

{#if fw === 'pt'}

```py
import collections
import numpy as np

from transformers import default_data_collator

wwm_probability = 0.2


def whole_word_masking_data_collator(features):
    for feature in features:
        word_ids = feature.pop("word_ids")

        # Create a map between words and corresponding token indices
        mapping = collections.defaultdict(list)
        current_word_index = -1
        current_word = None
        for idx, word_id in enumerate(word_ids):
            if word_id is not None:
                if word_id != current_word:
                    current_word = word_id
                    current_word_index += 1
                mapping[current_word_index].append(idx)

        # Randomly mask words
        mask = np.random.binomial(1, wwm_probability, (len(mapping),))
        input_ids = feature["input_ids"]
        labels = feature["labels"]
        new_labels = [-100] * len(labels)
        for word_id in np.where(mask)[0]:
            word_id = word_id.item()
            for idx in mapping[word_id]:
                new_labels[idx] = labels[idx]
                input_ids[idx] = tokenizer.mask_token_id
        feature["labels"] = new_labels

    return default_data_collator(features)
```

{:else}

```py
import collections
import numpy as np

from transformers.data import tf_default_data_collator

wwm_probability = 0.2


def whole_word_masking_data_collator(features):
    for feature in features:
        word_ids = feature.pop("word_ids")

        # Create a map between words and corresponding token indices
        mapping = collections.defaultdict(list)
        current_word_index = -1
        current_word = None
        for idx, word_id in enumerate(word_ids):
            if word_id is not None:
                if word_id != current_word:
                    current_word = word_id
                    current_word_index += 1
                mapping[current_word_index].append(idx)

        # Randomly mask words
        mask = np.random.binomial(1, wwm_probability, (len(mapping),))
        input_ids = feature["input_ids"]
        labels = feature["labels"]
        new_labels = [-100] * len(labels)
        for word_id in np.where(mask)[0]:
            word_id = word_id.item()
            for idx in mapping[word_id]:
                new_labels[idx] = labels[idx]
                input_ids[idx] = tokenizer.mask_token_id
        feature["labels"] = new_labels

    return tf_default_data_collator(features)
```

{/if}

Next, we can try it on the same samples as before:

```py
samples = [lm_datasets["train"][i] for i in range(2)]
batch = whole_word_masking_data_collator(samples)

for chunk in batch["input_ids"]:
    print(f"\n'>>> {tokenizer.decode(chunk)}'")
```

```python out
'>>> [CLS] bromwell high is a cartoon comedy [MASK] it ran at the same time as some other programs about school life, such as " teachers ". my 35 years in the teaching profession lead me to believe that bromwell high\'s satire is much closer to reality than is " teachers ". the scramble to survive financially, the insightful students who can see right through their pathetic teachers\'pomp, the pettiness of the whole situation, all remind me of the schools i knew and their students. when i saw the episode in which a student repeatedly tried to burn down the school, i immediately recalled.....'

'>>> .... [MASK] [MASK] [MASK] [MASK]....... high. a classic line : inspector : i\'m here to sack one of your teachers. student : welcome to bromwell high. i expect that many adults of my age think that bromwell high is far fetched. what a pity that it isn\'t! [SEP] [CLS] homelessness ( or houselessness as george carlin stated ) has been an issue for years but never a plan to help those on the street that were once considered human who did everything from going to school, work, or vote for the matter. most people think of the homeless'
```

<Tip>

✏️ **試試看!** 多次運行上面的代碼片段, 看看隨機屏蔽發生在你眼前! 還要將 `tokenizer.decode()` 方法替換為 `tokenizer.convert_ids_to_tokens()` 以查看來自給定單詞的標記始終被屏蔽在一起。

</Tip>

現在我們有兩個數據整理器, 其餘的微調步驟是標準的。如果您沒有足夠幸運地獲得神話般的 P100 GPU 😭, 在 Google Colab 上進行訓練可能需要一段時間, 因此我們將首先將訓練集的大小縮減為幾千個示例。別擔心, 我們仍然會得到一個相當不錯的語言模型! 在 🤗 Datasets 中快速下采樣數據集的方法是通過我們在 [第五章](/course/chapter5) 中看到的 `Dataset.train_test_split()` 函數:

```python
train_size = 10_000
test_size = int(0.1 * train_size)

downsampled_dataset = lm_datasets["train"].train_test_split(
    train_size=train_size, test_size=test_size, seed=42
)
downsampled_dataset
```

```python out
DatasetDict({
    train: Dataset({
        features: ['attention_mask', 'input_ids', 'labels', 'word_ids'],
        num_rows: 10000
    })
    test: Dataset({
        features: ['attention_mask', 'input_ids', 'labels', 'word_ids'],
        num_rows: 1000
    })
})
```

這會自動創建新的 `train` 和 `test` 拆分, 訓練集大小設置為 10,000 個示例, 驗證設置為其中的 10% -- 如果你有一個強大的 GPU, 可以隨意增加它! 我們需要做的下一件事是登錄 Hugging Face Hub。如果你在筆記本中運行此代碼, 則可以使用以下實用程序函數執行此操作:

```python
from huggingface_hub import notebook_login

notebook_login()
```

這將顯示一個小部件, 你可以在其中輸入你的憑據。或者, 你可以運行: 

```
huggingface-cli login
```

在你最喜歡的終端中登錄。 

{#if fw === 'tf'}

登陸後, 我們就可以創建我們的 `tf.data` 數據集。我們將在這裡只使用標準數據整理器, 但你也可以嘗試使用整個單詞掩碼整理器並將結果作為練習進行比較:

```python
tf_train_dataset = downsampled_dataset["train"].to_tf_dataset(
    columns=["input_ids", "attention_mask", "labels"],
    collate_fn=data_collator,
    shuffle=True,
    batch_size=32,
)

tf_eval_dataset = downsampled_dataset["test"].to_tf_dataset(
    columns=["input_ids", "attention_mask", "labels"],
    collate_fn=data_collator,
    shuffle=False,
    batch_size=32,
)
```

接下來, 我們設置訓練超參數並編譯模型。我們使用 🤗 Transformers 庫中的 `create_optimizer()` 函數, 它提供了一個具有線性學習率衰減的 `AdamW` 優化器。我們還使用了模型內置的損失, 當沒有損失被指定為 `compile()` 參數是, 這是默認值, 並且我們將訓練精度設置為 `"mixed_float16"`。請注意,如果你使用的是Colab GPU或其他不支持float16加速的GPU, 你可能應該註釋掉這一行。

此外, 我們還設置了一個 `PushToHubCallback`, 它將在每個epoch後將模型保存到Hub。你可以使用 `hub_model_id` 參數指定你想要推送到的存儲庫的名稱 (特別是你將必須使用該參數來推送到組織)。例如, 為了將模型推送到 [`huggingface-course` organization](https://huggingface.co/huggingface-course), 我們添加了 `hub_model_id="huggingface-course/distilbert-finetuned-imdb"`。默認情況下, 使用的存儲庫將位於你的命名空間中, 並以你設置的輸出目錄命名, 因此在我們的示例中, 它將是 `"lewtun/distilbert-finetuned-imdb"`。

```python
from transformers import create_optimizer
from transformers.keras_callbacks import PushToHubCallback
import tensorflow as tf

num_train_steps = len(tf_train_dataset)
optimizer, schedule = create_optimizer(
    init_lr=2e-5,
    num_warmup_steps=1_000,
    num_train_steps=num_train_steps,
    weight_decay_rate=0.01,
)
model.compile(optimizer=optimizer)

# Train in mixed-precision float16
tf.keras.mixed_precision.set_global_policy("mixed_float16")

callback = PushToHubCallback(
    output_dir=f"{model_name}-finetuned-imdb", tokenizer=tokenizer
)
```

我們現在已經準備好運行 `model.fit()` 了 -- 但在此之前, 讓我們先簡單地看看 _perplexity_, 它是評估語言模型性能的常用指標。

{:else}

登陸後, 我們可以指定 `Trainer` 參數:

```python
from transformers import TrainingArguments

batch_size = 64
# Show the training loss with every epoch
logging_steps = len(downsampled_dataset["train"]) // batch_size
model_name = model_checkpoint.split("/")[-1]

training_args = TrainingArguments(
    output_dir=f"{model_name}-finetuned-imdb",
    overwrite_output_dir=True,
    evaluation_strategy="epoch",
    learning_rate=2e-5,
    weight_decay=0.01,
    per_device_train_batch_size=batch_size,
    per_device_eval_batch_size=batch_size,
    push_to_hub=True,
    fp16=True,
    logging_steps=logging_steps,
)
```

在這裡, 我們調整了一些默認選項, 包括 `logging_steps` , 以確保我們跟蹤每個epoch的訓練損失。我們還使用了 `fp16=True` 來實現混合精度訓練, 這給我們帶來了另一個速度提升。默認情況下, `Trainer` 將刪除不屬於模型的 `forward()` 方法的列。這意味著, 如果你使用整個單詞屏蔽排序器, 你還需要設置 `remove_unused_columns=False`, 以確保我們不會在訓練期間丟失 `word_ids` 列。

請注意, 你可以使用 `hub_model_id` 參數指定要推送到的存儲庫的名稱((特別是你將必須使用該參數向組織推送)。例如, 當我們將模型推送到 [`huggingface-course` organization](https://huggingface.co/huggingface-course) 時, 我們添加了 `hub_model_id="huggingface-course/distilbert-finetuned-imdb"` 到 `TrainingArguments` 中。默認情況下, 使用的存儲庫將在你的命名空間中並以你設置的輸出目錄命名, 因此在我們的示例中, 它將是 `"lewtun/distilbert-finetuned-imdb"`。

我們現在擁有實例化 `Trainer` 的所有成分。這裡我們只使用標準的 `data_collator`, 但你可以嘗試使用整個單詞掩碼整理器並比較結果作為練習: 

```python
from transformers import Trainer

trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=downsampled_dataset["train"],
    eval_dataset=downsampled_dataset["test"],
    data_collator=data_collator,
    tokenizer=tokenizer,
)
```

我們現在準備運行 `trainer.train()` -- 但在此之前讓我們簡要地看一下 _perplexity_, 這是評估語言模型性能的常用指標。

{/if}

### 語言模型的perplexity

<Youtube id="NURcDHhYe98"/>

與文本分類或問答等其他任務不同, 在這些任務中, 我們會得到一個帶標籤的語料庫進行訓練, 而語言建模則沒有任何明確的標籤。那麼我們如何確定什麼是好的語言模型呢? 就像手機中的自動更正功能一樣, 一個好的語言模型是為語法正確的句子分配高概率, 為無意義的句子分配低概率。為了讓你更好地瞭解這是什麼樣子, 您可以在網上找到一整套 "autocorrect fails", 其中一個人手機中的模型產生了一些相當有趣 (而且通常不合適) 的結果! 

{#if fw === 'pt'}

假設我們的測試集主要由語法正確的句子組成, 那麼衡量我們的語言模型質量的一種方法是計算它分配給測試集中所有句子中的下一個單詞的概率。高概率表明模型對看不見的例子並不感到 "驚訝" 或 "疑惑", 並表明它已經學習了語言中的基本語法模式。 perplexity度有多種數學定義, 但我們將使用的定義是交叉熵損失的指數。因此, 我們可以通過 `Trainer.evaluate()` 函數計算測試集上的交叉熵損失, 然後取結果的指數來計算預訓練模型的perplexity度:

```python
import math

eval_results = trainer.evaluate()
print(f">>> Perplexity: {math.exp(eval_results['eval_loss']):.2f}")
```

{:else}

假設我們的測試集主要由語法正確的句子組成, 那麼衡量我們的語言模型質量的一種方法是計算測試集所有句子中它分配給下一個單詞的概率。高概率表明, 該模型表明該模型對未見過的示例不感到 "驚訝" 或 "困惑", 並表明它已經學會了該語言的基本語法模式。關於perplexity度有各種不同的數學定義, 但我們要用的定義是交叉熵損失的指數。因此, 我們可以通過 `model.evaluate()` 方法計算測試集上的交叉熵損失, 然後取結果的指數來計算我們預訓練模型的perplexity度:

```python
import math

eval_loss = model.evaluate(tf_eval_dataset)
print(f"Perplexity: {math.exp(eval_loss):.2f}")
```

{/if}

```python out
>>> Perplexity: 21.75
```

較低的perplexity分數意味著更好的語言模型, 我們可以在這裡看到我們的起始模型有一個較大的值。看看我們能不能通過微調來降低它! 為此, 我們首先運行訓練循環:

{#if fw === 'pt'}

```python
trainer.train()
```

{:else}

```python
model.fit(tf_train_dataset, validation_data=tf_eval_dataset, callbacks=[callback])
```

{/if}

然後像以前一樣計算測試集上的perplexity度:

{#if fw === 'pt'}

```python
eval_results = trainer.evaluate()
print(f">>> Perplexity: {math.exp(eval_results['eval_loss']):.2f}")
```

{:else}

```python
eval_loss = model.evaluate(tf_eval_dataset)
print(f"Perplexity: {math.exp(eval_loss):.2f}")
```

{/if}

```python out
>>> Perplexity: 11.32
```

很好 -- 這大大減少了困惑, 這告訴我們模型已經瞭解了一些關於電影評論領域的知識!

{#if fw === 'pt'}

訓練完成後, 我們可以將帶有訓練信息的模型卡推送到 Hub (檢查點在訓練過程中自行保存):

```python
trainer.push_to_hub()
```

{/if}

<Tip>

✏️ **輪到你了!** 將數據整理器改為全字屏蔽整理器後運行上面的訓練。你有得到更好的結果嗎?

</Tip>

{#if fw === 'pt'} 

在我們的用例中, 我們不需要對訓練循環做任何特別的事情, 但在某些情況下, 你可能需要實現一些自定義邏輯。對於這些應用, 你可以使用 🤗 Accelerate -- 讓我們來看看吧!

## 使用 🤗 Accelerate 微調 DistilBERT

正如我們在 `Trainer` 中看到的, 對掩碼語言模型的微調與 [第三章](/course/chapter3) 中的文本分類示例非常相似。事實上, 唯一的微妙之處是使用特殊的數據整理器, 我們已經在本節的前面介紹過了! 

然而, 我們看到 `DataCollatorForLanguageModeling` 還對每次評估應用隨機掩碼, 因此每次訓練運行時, 我們都會看到perplexity度分數的一些波動。消除這種隨機性來源的一種方法是應用掩碼 _一次_ 在整個測試集上, 然後使用🤗 Transformers 中的默認數據整理器在評估期間收集批次。為了看看它是如何工作的, 讓我們實現一個簡單的函數, 將掩碼應用於批處理, 類似於我們第一次遇到的 `DataCollatorForLanguageModeling`:

```python
def insert_random_mask(batch):
    features = [dict(zip(batch, t)) for t in zip(*batch.values())]
    masked_inputs = data_collator(features)
    # Create a new "masked" column for each column in the dataset
    return {"masked_" + k: v.numpy() for k, v in masked_inputs.items()}
```

接下來, 我們將此函數應用於我們的測試集並刪除未掩碼的列, 以便我們可以用掩碼的列替換它們。你可以通過用適當的替換上面的 `data_collator` 來使整個單詞掩碼, 在這種情況下, 你應該刪除此處的第一行:

```py
downsampled_dataset = downsampled_dataset.remove_columns(["word_ids"])
eval_dataset = downsampled_dataset["test"].map(
    insert_random_mask,
    batched=True,
    remove_columns=downsampled_dataset["test"].column_names,
)
eval_dataset = eval_dataset.rename_columns(
    {
        "masked_input_ids": "input_ids",
        "masked_attention_mask": "attention_mask",
        "masked_labels": "labels",
    }
)
```

然後我們可以像往常一樣設置數據加載器, 但我們將使用🤗 Transformers 中的 `default_data_collator` 作為評估集:

```python
from torch.utils.data import DataLoader
from transformers import default_data_collator

batch_size = 64
train_dataloader = DataLoader(
    downsampled_dataset["train"],
    shuffle=True,
    batch_size=batch_size,
    collate_fn=data_collator,
)
eval_dataloader = DataLoader(
    eval_dataset, batch_size=batch_size, collate_fn=default_data_collator
)
```

從這裡開始, 我們遵循🤗 Accelerate 的標準步驟。第一個任務是加載預訓練模型的新版本:

```
model = AutoModelForMaskedLM.from_pretrained(model_checkpoint)
```

然後我們需要指定優化器; 我們將使用標準的 `AdamW`:

```python
from torch.optim import AdamW

optimizer = AdamW(model.parameters(), lr=5e-5)
```

有了這些對象, 我們現在可以準備使用 `Accelerator` 加速器進行訓練的一切:

```python
from accelerate import Accelerator

accelerator = Accelerator()
model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare(
    model, optimizer, train_dataloader, eval_dataloader
)
```

現在我們的模型、優化器和數據加載器都配置好了, 我們可以指定學習率調度器如下:

```python
from transformers import get_scheduler

num_train_epochs = 3
num_update_steps_per_epoch = len(train_dataloader)
num_training_steps = num_train_epochs * num_update_steps_per_epoch

lr_scheduler = get_scheduler(
    "linear",
    optimizer=optimizer,
    num_warmup_steps=0,
    num_training_steps=num_training_steps,
)
```

在訓練之前要做的最後一件事是: 在 Hugging Face Hub 上創建一個模型庫! 我們可以使用 🤗 Hub 庫來首先生成我們的 repo 的全名:

```python
from huggingface_hub import get_full_repo_name

model_name = "distilbert-base-uncased-finetuned-imdb-accelerate"
repo_name = get_full_repo_name(model_name)
repo_name
```

```python out
'lewtun/distilbert-base-uncased-finetuned-imdb-accelerate'
```

然後使用來自🤗 Hub 的 `Repository` 類:

```python
from huggingface_hub import Repository

output_dir = model_name
repo = Repository(output_dir, clone_from=repo_name)
```

完成後, 只需寫出完整的訓練和評估循環即可:

```python
from tqdm.auto import tqdm
import torch
import math

progress_bar = tqdm(range(num_training_steps))

for epoch in range(num_train_epochs):
    # Training
    model.train()
    for batch in train_dataloader:
        outputs = model(**batch)
        loss = outputs.loss
        accelerator.backward(loss)

        optimizer.step()
        lr_scheduler.step()
        optimizer.zero_grad()
        progress_bar.update(1)

    # Evaluation
    model.eval()
    losses = []
    for step, batch in enumerate(eval_dataloader):
        with torch.no_grad():
            outputs = model(**batch)

        loss = outputs.loss
        losses.append(accelerator.gather(loss.repeat(batch_size)))

    losses = torch.cat(losses)
    losses = losses[: len(eval_dataset)]
    try:
        perplexity = math.exp(torch.mean(losses))
    except OverflowError:
        perplexity = float("inf")

    print(f">>> Epoch {epoch}: Perplexity: {perplexity}")

    # Save and upload
    accelerator.wait_for_everyone()
    unwrapped_model = accelerator.unwrap_model(model)
    unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save)
    if accelerator.is_main_process:
        tokenizer.save_pretrained(output_dir)
        repo.push_to_hub(
            commit_message=f"Training in progress epoch {epoch}", blocking=False
        )
```

```python out
>>> Epoch 0: Perplexity: 11.397545307900472
>>> Epoch 1: Perplexity: 10.904909330983092
>>> Epoch 2: Perplexity: 10.729503505340409
```

很棒, 我們已經能夠評估每個 epoch 的perplexity度, 並確保多次訓練運行是可重複的!

{/if}

## 使用我們微調的模型

你可以通過在Hub上使用其他小部件或在本地使用🤗 Transformers 的`管道`於微調模型進行交互。讓我們使用後者來下載我們的模型, 使用 `fill-mask` 管道:

```python
from transformers import pipeline

mask_filler = pipeline(
    "fill-mask", model="huggingface-course/distilbert-base-uncased-finetuned-imdb"
)
```

然後, 我們可以向管道提供 "This is a great [MASK]" 示例文本, 並查看前 5 個預測是什麼:

```python
preds = mask_filler(text)

for pred in preds:
    print(f">>> {pred['sequence']}")
```

```python out
'>>> this is a great movie.'
'>>> this is a great film.'
'>>> this is a great story.'
'>>> this is a great movies.'
'>>> this is a great character.'
```

好的 -- 我們的模型顯然已經調整了它的權重來預測與電影更密切相關的詞!

<Youtube id="0Oxphw4Q9fo"/>

這結束了我們訓練語言模型的第一個實驗。在 [第六節](/course/chapter7/section6)中你將學習如何從頭開始訓練像 GPT-2 這樣的自迴歸模型; 如果你想了解如何預訓練您自己的 Transformer 模型, 請前往那裡!

<Tip>

✏️ **試試看!** 為了量化域適應的好處, 微調 IMDb 標籤上的分類器和預先訓練和微調的Distil BERT檢查點。如果你需要複習文本分類, 請查看 [第三章](/course/chapter3)。

</Tip>


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter7/3.mdx" />

### 從頭開始訓練因果語言模型
https://huggingface.co/learn/course/zh-TW/chapter7/6.md

# 從頭開始訓練因果語言模型

{#if fw === 'pt'}

<DocNotebookDropdown
  classNames="absolute z-10 right-0 top-0"
  options={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/chapter7/section6_pt.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/chapter7/section6_pt.ipynb"},
]} />

{:else}

<DocNotebookDropdown
  classNames="absolute z-10 right-0 top-0"
  options={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/chapter7/section6_tf.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/chapter7/section6_tf.ipynb"},
]} />

{/if}

到目前為止，我們主要使用預訓練模型，並通過重用預訓練的權重來針對新用例對它們進行微調。正如我們在[第一章](/course/chapter1), 這通常稱為遷移學習，這是將 Transformer 模型應用於大多數標記數據稀疏的現實世界用例的非常成功的策略。在本章中，我們將採用不同的方法並從頭開始訓練一個全新的模型。如果您有大量數據並且它與用於可用模型的預訓練數據有很大不同，那麼這是一個很好的方法。然而，它也需要更多的計算資源來預訓練語言模型，而不僅僅是微調現有的模型。訓練新模型有意義的示例包括由音符、分子序列（如 DNA）或編程語言組成的數據集。後者最近受到關注，這要歸功於 TabNine 和 GitHub 的 Copilot 等工具，它們由 OpenAI 的 Codex 模型提供支持，可以生成長代碼序列。這種文本生成任務最好使用自迴歸或因果語言模型（例如 GPT-2）來解決。

在本節中，我們將構建代碼生成模型的縮小版本：我們將使用 Python 代碼的子集專注於單行完成而不是完整的函數或類。在 Python 中處理數據時，您會經常接觸 Python 數據科學堆棧，包括 `matplotlib` , `seaborn` , `pandas` ， 和 `scikit-learn` 庫。在使用這些框架時，通常需要查找特定的命令，因此如果我們可以使用模型來為我們完成這些調用，那就太好了。

<Youtube id="Vpjb1lu0MDk"/>

在[第六章](/course/chapter6) 我們創建了一個高效的分詞器來處理 Python 源代碼，但我們仍然需要一個大規模數據集來預訓練模型。在這裡，我們將我們的分詞器應用到源自 GitHub 存儲庫的 Python 代碼語料庫。然後我們將使用 `Trainer` API 和 🤗 Accelerate 來訓練模型。讓我們開始吧！

<iframe src="https://course-demos-codeparrot-ds.hf.space" frameBorder="0" height="300" title="Gradio app" class="block dark:hidden container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>
<iframe src="https://course-demos-codeparrot-ds-darkmode.hf.space" frameBorder="0" height="300" title="Gradio app" class="hidden dark:block container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

這實際上展示了使用本節中訓練並上傳到 Hub 的模型。你可以在[這裡](https://huggingface.co/huggingface-course/codeparrot-ds?text=plt.imshow%28)找到。請注意，由於在文本生成過程中發生了一些隨機化，您可能會得到略有不同的結果。
## 收集數據

Python 代碼可以從 GitHub 等代碼存儲庫中獲得，我們可以通過抓取每個 Python 存儲庫來使用它們來創建數據集。這是在[Transformers textbook](https://learning.oreilly.com/library/view/natural-language-processing/9781098103231/)預訓練大型的GPT-2 模型。使用大約 180 GB 的 GitHub 轉儲，其中包含大約 2000 萬個 Python 文件，稱為 `codeparrot` ，作者構建了一個數據集，然後在[Hugging Face Hub](https://huggingface.co/datasets/transformersbook/codeparrot)上分享出來了.

然而，對完整語料庫的訓練既耗時又費力，我們只需要與 Python 數據科學堆棧相關的數據集子集。所以，讓我們開始過濾 `codeparrot` 包含此堆棧中任何庫的所有文件的數據集。由於數據集的太大，我們希望避免下載它；因此反，我們將使用流功能來動態過濾它。為了使用前面提到的庫過濾代碼示例，我們將使用以下函數：

```py
def any_keyword_in_string(string, keywords):
    for keyword in keywords:
        if keyword in string:
            return True
    return False
```

讓我們用兩個例子來測試一下：

```py
filters = ["pandas", "sklearn", "matplotlib", "seaborn"]
example_1 = "import numpy as np"
example_2 = "import pandas as pd"

print(
    any_keyword_in_string(example_1, filters), any_keyword_in_string(example_2, filters)
)
```

```python out
False True
```

我們可以使用它來創建一個函數來流式傳輸數據集並過濾我們想要的元素：

```py
def filter_streaming_dataset(dataset, filters):
    filtered_dict = defaultdict(list)
    total = 0
    for sample in tqdm(iter(dataset)):
        total += 1
        if any_keyword_in_string(sample["content"], filters):
            for k, v in sample.items():
                filtered_dict[k].append(v)
    print(f"{len(filtered_dict['content'])/total:.2%} of data after filtering.")
    return Dataset.from_dict(filtered_dict)
```

然後我們可以簡單地將此函數應用於流數據集：

```py
# This cell will take a very long time to execute, so you should skip it and go to
# the next one!
from datasets import load_dataset

split = "train"  # "valid"
filters = ["pandas", "sklearn", "matplotlib", "seaborn"]

data = load_dataset(f"transformersbook/codeparrot-{split}", split=split, streaming=True)
filtered_data = filter_streaming_dataset(data, filters)
```

```python out
3.26% of data after filtering.
```

這給我們留下了大約 3% 的原始數據集，這個數據集仍然相當可觀——結果數據集有 6 GB，包含 600,000 個 Python 腳本！過濾完整數據集可能需要 2-3 小時，具體取決於您的機器和帶寬。如果您不想自己經歷這個漫長的過程，我們在 Hub 上提供過濾後的數據集供您下載：

```py
from datasets import load_dataset, DatasetDict

ds_train = load_dataset("huggingface-course/codeparrot-ds-train", split="train")
ds_valid = load_dataset("huggingface-course/codeparrot-ds-valid", split="train")

raw_datasets = DatasetDict(
    {
        "train": ds_train,  # .shuffle().select(range(50000)),
        "valid": ds_valid,  # .shuffle().select(range(500))
    }
)

raw_datasets
```

```python out
DatasetDict({
    train: Dataset({
        features: ['repo_name', 'path', 'copies', 'size', 'content', 'license'],
        num_rows: 606720
    })
    valid: Dataset({
        features: ['repo_name', 'path', 'copies', 'size', 'content', 'license'],
        num_rows: 3322
    })
})
```

<Tip>

預訓練語言模型需要一段時間。我們建議您首先通過取消註釋以上兩行的註釋對數據樣本運行訓練循環，並確保訓練成功完成並存儲模型。沒有什麼比最後一步的訓練失敗更令人沮喪的了，因為你忘記創建一個文件夾或者因為保存路徑在訓練循環結束時有一個錯字！

</Tip>

讓我們看一個來自數據集的例子。我們將只顯示每個字段的前 200 個字符：

```py
for key in raw_datasets["train"][0]:
    print(f"{key.upper()}: {raw_datasets['train'][0][key][:200]}")
```

```python out
'REPO_NAME: kmike/scikit-learn'
'PATH: sklearn/utils/__init__.py'
'COPIES: 3'
'SIZE: 10094'
'''CONTENT: """
The :mod:`sklearn.utils` module includes various utilites.
"""

from collections import Sequence

import numpy as np
from scipy.sparse import issparse
import warnings

from .murmurhash import murm
LICENSE: bsd-3-clause'''
```

我們可以看到 `content` 字段包含我們希望我們的模型訓練的代碼。現在我們有了一個數據集，我們需要預處理文本，使其採用適合預訓練的格式。

## 準備數據集

<Youtube id="ma1TrR7gE7I"/>

第一步是對數據進行標記，以便我們可以將其用於訓練。由於我們的目標主要是自動完成短函數調用，因此我們可以保持上下文大小相對較小。這樣做的好處是我們可以更快地訓練模型並且它需要的內存顯著減少。如果您的應用程序擁有更多上下文很重要（例如，如果您希望模型基於具有函數定義的文件編寫單元測試），請確保增加該數量，但請記住，這需要更大的 GPU 內存佔用。現在，讓我們將上下文大小固定為 128 個標記，而不是 GPT-2 或 GPT-3 中分別使用的 1,024 或 2,048 個標記。


大多數文檔包含超過 128 個標記，因此簡單地將輸入截斷到最大長度將消除我們數據集的很大一部分。相反，我們將使用 `return_overflowing_tokens` 標記整個輸入並將其分成幾個塊的選項，就像我們在[第六章](/course/chapter6/4). 我們還將使用 `return_length` 選項自動返回每個創建的塊的長度。通常最後一個塊會小於上下文大小，我們會去掉這些塊以避免填充問題；因為無論如何我們都有大量數據。

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter7/chunking_texts.svg" alt="Chunking a large texts in several pieces."/>
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter7/chunking_texts-dark.svg" alt="Chunking a large texts in several pieces."/>
</div>

讓我們通過查看前兩個示例來確切瞭解這是如何工作的：

```py
from transformers import AutoTokenizer

context_length = 128
tokenizer = AutoTokenizer.from_pretrained("huggingface-course/code-search-net-tokenizer")

outputs = tokenizer(
    raw_datasets["train"][:2]["content"],
    truncation=True,
    max_length=context_length,
    return_overflowing_tokens=True,
    return_length=True,
)

print(f"Input IDs length: {len(outputs['input_ids'])}")
print(f"Input chunk lengths: {(outputs['length'])}")
print(f"Chunk mapping: {outputs['overflow_to_sample_mapping']}")
```

```python out
Input IDs length: 34
Input chunk lengths: [128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 117, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 41]
Chunk mapping: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
```

我們可以看 到，從這兩個示例中我們總共得到了 34 個片段。查看塊長度，我們可以看到兩個文檔末尾的塊都少於 128 個標記（分別為 117 和 41）。這些僅代表我們擁有的數據集的一小部分，因此我們可以安全地將它們扔掉。通過 `overflow_to_sample_mapping` 字段，我們還可以重建哪些塊屬於哪些輸入樣本。

通過這個操作，我們使用了一個方便的🤗 Datasets 中的` Dataset.map()` 函數，就是不需要一對一的映射；正如我們在[第三節](/course/chapter7/3),我們可以創建具有比輸入批次更多或更少元素的批次。這在執行更改元素數量的數據增強或數據過濾等操作時非常有用。在我們的例子中，當將每個元素標記為指定上下文大小的塊時，我們從每個文檔中創建了許多樣本。我們只需要確保刪除現有的列，因為它們的大小存在衝突。如果我們想保留它們，我們可以適當地重複它們，並在`Dataset.map()` 調用中返回它們:

```py
def tokenize(element):
    outputs = tokenizer(
        element["content"],
        truncation=True,
        max_length=context_length,
        return_overflowing_tokens=True,
        return_length=True,
    )
    input_batch = []
    for length, input_ids in zip(outputs["length"], outputs["input_ids"]):
        if length == context_length:
            input_batch.append(input_ids)
    return {"input_ids": input_batch}


tokenized_datasets = raw_datasets.map(
    tokenize, batched=True, remove_columns=raw_datasets["train"].column_names
)
tokenized_datasets
```

```python out
DatasetDict({
    train: Dataset({
        features: ['input_ids'],
        num_rows: 16702061
    })
    valid: Dataset({
        features: ['input_ids'],
        num_rows: 93164
    })
})
```

我們現在有 1670 萬個示例，每個示例有 128 個tokens ，總共相當於大約 21 億個tokens 。作為參考，OpenAI 的 GPT-3 和 Codex 模型分別在 300 和 1000 億個tokens 上訓練，其中 Codex 模型從 GPT-3 檢查點初始化。我們在本節中的目標不是與這些模型競爭，這些模型可以生成長而連貫的文本，而是創建一個縮小版本，為數據科學家提供快速自動完成功能。

現在我們已經準備好了數據集，讓我們設置模型！


<Tip>

✏️ **試試看！** 擺脫所有小於上下文大小的塊在這裡並不是什麼大問題，因為我們使用的是小上下文窗口。隨著上下文大小的增加（或者如果您有一個短文檔語料庫），被丟棄的塊的比例也會增加。準備數據的更有效方法是將所有標記化的樣本加入一個批次中，每個語料之間有一個`eos_token_id` 標記, 然後對連接的序列執行分塊。作為練習，修改 `tokenize()`函數以使用該方法。請注意，您需要設置`truncation=False` 和刪除標記生成器中的其他參數以獲取完整的標記 ID 序列。

</Tip>


## 初始化新模型

我們的第一步是新初始化一個 GPT-2 模型。我們將對我們的模型使用與小型 GPT-2 模型相同的配置，因此我們加載預訓練配置，確保分詞器大小與模型詞彙量大小匹配並設置 `bos` 和 `eos` （序列的開始和結束）令牌 ID：

{#if fw === 'pt'}

```py
from transformers import AutoTokenizer, GPT2LMHeadModel, AutoConfig

config = AutoConfig.from_pretrained(
    "gpt2",
    vocab_size=len(tokenizer),
    n_ctx=context_length,
    bos_token_id=tokenizer.bos_token_id,
    eos_token_id=tokenizer.eos_token_id,
)
```

使用該配置，我們可以加載一個新模型。請注意，這是我們第一次不使用 `from_pretrained()` 函數，因為我們實際上是在自己初始化模型

```py
model = GPT2LMHeadModel(config)
model_size = sum(t.numel() for t in model.parameters())
print(f"GPT-2 size: {model_size/1000**2:.1f}M parameters")
```

```python out
GPT-2 size: 124.2M parameters
```

{:else}

```py
from transformers import AutoTokenizer, TFGPT2LMHeadModel, AutoConfig

config = AutoConfig.from_pretrained(
    "gpt2",
    vocab_size=len(tokenizer),
    n_ctx=context_length,
    bos_token_id=tokenizer.bos_token_id,
    eos_token_id=tokenizer.eos_token_id,
)
```

通過該配置，我們可以加載新模型。請注意，這是我們剛開始不使用`from_pretrained()`函數，因為我們實際上是在自己初始化模型：

```py
model = TFGPT2LMHeadModel(config)
model(model.dummy_inputs)  # Builds the model
model.summary()
```

```python out
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
transformer (TFGPT2MainLayer multiple                  124242432 
=================================================================
Total params: 124,242,432
Trainable params: 124,242,432
Non-trainable params: 0
_________________________________________________________________
```

{/if}

我們的模型有 1.24 億個參數，我們必須對其進行調整。在開始訓練之前，我們需要設置一個負責創建批次的數據整理器。我們可以使用 `DataCollatorForLanguageModeling` ，它是專為語言建模而設計（顧名思義）。除了堆疊和填充批次，它還負責創建語言模型標籤——在因果語言建模中，輸入也用作標籤（只是移動了一個元素），並且這個數據整理器在訓練期間即時創建它們，所以我們不需要複製 `input_ids`。 

注意 `DataCollatorForLanguageModeling` 支持掩碼語言建模 (MLM) 和因果語言建模 (CLM)。默認情況下它為 MLM 準備數據，但我們可以通過設置`mlm=False`參數切換到 CLM ：

{#if fw === 'pt'}

```py
from transformers import DataCollatorForLanguageModeling

tokenizer.pad_token = tokenizer.eos_token
data_collator = DataCollatorForLanguageModeling(tokenizer, mlm=False)
```

{:else}

```py
from transformers import DataCollatorForLanguageModeling

tokenizer.pad_token = tokenizer.eos_token
data_collator = DataCollatorForLanguageModeling(tokenizer, mlm=False, return_tensors="tf")
```

{/if}

讓我們看一個例子：

```py
out = data_collator([tokenized_dataset["train"][i] for i in range(5)])
for key in out:
    print(f"{key} shape: {out[key].shape}")
```

{#if fw === 'pt'}

```python out
input_ids shape: torch.Size([5, 128])
attention_mask shape: torch.Size([5, 128])
labels shape: torch.Size([5, 128])
```

{:else}

```python out
input_ids shape: (5, 128)
attention_mask shape: (5, 128)
labels shape: (5, 128)
```

{/if}

我們可以看到示例已經堆疊在一起，並且所有張量都具有相同的形狀。

{#if fw === 'tf'}

現在，我們可以使用`to_tf_dataset()`方法，使用上面創建的數據整理器將數據集轉換為TensorFlow數據集：

```python
tf_train_dataset = tokenized_dataset["train"].to_tf_dataset(
    columns=["input_ids", "attention_mask", "labels"],
    collate_fn=data_collator,
    shuffle=True,
    batch_size=32,
)
tf_eval_dataset = tokenized_dataset["valid"].to_tf_dataset(
    columns=["input_ids", "attention_mask", "labels"],
    collate_fn=data_collator,
    shuffle=False,
    batch_size=32,
)
```

{/if}

<Tip warning={true}>

⚠️  移動輸入和標籤以對齊它們發生在模型內部，因此數據整理器只需複製輸入以創建標籤。

</Tip>


現在我們已經準備好實際訓練我們的模型的一切了——畢竟這不是那麼多工作！在我們開始訓練之前，我們應該登錄 Hugging Face。如果您在筆記本上工作，則可以使用以下實用程序功能：

```python
from huggingface_hub import notebook_login

notebook_login()
```

這將顯示一個小部件，您可以在其中輸入您的 Hugging Face 登錄憑據。

如果您不是在notebook上工作，只需在終端中輸入以下行：

```bash
huggingface-cli login
```

{#if fw === 'pt'}

剩下要做的就是配置訓練參數並啟動 `Trainer` .我們將使用餘弦學習率，並進行一些Warmup和有效批量大小為 256 ( `per_device_train_batch_size` * `gradient_accumulation_steps`）。當單個批次不適合內存時使用梯度累積，並通過多次向前/向後傳遞逐步建立梯度。當我們使用 🤗 Accelerate 創建訓練循環時，我們將看到這一點。

```py
from transformers import Trainer, TrainingArguments

args = TrainingArguments(
    output_dir="codeparrot-ds",
    per_device_train_batch_size=32,
    per_device_eval_batch_size=32,
    evaluation_strategy="steps",
    eval_steps=5_000,
    logging_steps=5_000,
    gradient_accumulation_steps=8,
    num_train_epochs=1,
    weight_decay=0.1,
    warmup_steps=1_000,
    lr_scheduler_type="cosine",
    learning_rate=5e-4,
    save_steps=5_000,
    fp16=True,
    push_to_hub=True,
)

trainer = Trainer(
    model=model,
    tokenizer=tokenizer,
    args=args,
    data_collator=data_collator,
    train_dataset=tokenized_datasets["train"],
    eval_dataset=tokenized_datasets["valid"],
)
```

現在我們可以開始 `Trainer`並等待訓練完成。根據您是在整個訓練集還是在訓練集的一個子集上運行它，這將分別需要 20 或 2 個小時，因此請喝杯咖啡和一本好書來閱讀！

```py
trainer.train()
```

訓練完成後，我們可以將模型和標記器推送到 Hub：

```py
trainer.push_to_hub()
```

{:else}

剩下要做的就是配置訓練超參數並調用 `compile()` 和 `fit()`。我們將使用帶有一些預熱的學習率調整策略來提高訓練的穩定性：

```py
from transformers import create_optimizer
import tensorflow as tf

num_train_steps = len(tf_train_dataset)
optimizer, schedule = create_optimizer(
    init_lr=5e-5,
    num_warmup_steps=1_000,
    num_train_steps=num_train_steps,
    weight_decay_rate=0.01,
)
model.compile(optimizer=optimizer)

# Train in mixed-precision float16
tf.keras.mixed_precision.set_global_policy("mixed_float16")
```

現在我們可以調用`model.fit()，`並等待訓練完成。你是在完整的訓練集還是他的子集上運行，這將分別需要20和2個小時，所以拿一些咖啡和一本好書來閱讀！訓練完成後，我們可以將模型和分詞器推送到中心：

```py
from transformers.keras_callbacks import PushToHubCallback

callback = PushToHubCallback(output_dir="codeparrot-ds", tokenizer=tokenizer)

model.fit(tf_train_dataset, validation_data=tf_eval_dataset, callbacks=[callback])
```

{/if}

<Tip>

✏️ **試試看!** 除了`TrainingArguments` 之外，我們只需要大約30行代碼就可以從原始文本到訓練GPT-2。 用你自己的數據集試試看，看看你能不能得到好的結果！

</Tip>

<Tip>

{#if fw === 'pt'}

💡 如果您可以訪問具有多個 GPU 的機器，請嘗試在那裡運行代碼。 `Trainer`自動管理多臺機器，這可以極大地加快訓練速度。

{:else}

💡 如果您有權訪問具有多個 GPU 的計算機，則可以嘗試使用 `MirroredStrategy` 上下文來大幅加快訓練速度。您需要創建一個`tf.distribute.MirroredStrategy`對象，並確保 `to_tf_dataset` 命令以及模型創建和對 `fit()`的調用都在其 `scope()` context. 上下文中運行。您可以查看有關此內容的文檔[here](https://www.tensorflow.org/guide/distributed_training#use_tfdistributestrategy_with_keras_modelfit).

{/if}

</Tip>

## 使用管道生成代碼

現在是關鍵的部分：讓我們看看經過訓練的模型的實際效果如何！我們可以在日誌中看到損失穩步下降，但為了讓模型進行測試，讓我們看看它在某些測試上的表現如何。為此，我們將模型包裝在文本生成中的`pipeline` ，如果有可用的，我們會將它放在 GPU 上進行快速生成：

{#if fw === 'pt'}

```py
import torch
from transformers import pipeline

device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
pipe = pipeline(
    "text-generation", model="huggingface-course/codeparrot-ds", device=device
)
```

{:else}

```py
from transformers import pipeline

course_model = TFGPT2LMHeadModel.from_pretrained("huggingface-course/codeparrot-ds")
course_tokenizer = AutoTokenizer.from_pretrained("huggingface-course/codeparrot-ds")
pipe = pipeline(
    "text-generation", model=course_model, tokenizer=course_tokenizer, device=0
)
```

{/if}

讓我們從創建散點圖的簡單任務開始：

```py
txt = """\
# create some data
x = np.random.randn(100)
y = np.random.randn(100)

# create scatter plot with x, y
"""
print(pipe(txt, num_return_sequences=1)[0]["generated_text"])
```

```python out
# create some data
x = np.random.randn(100)
y = np.random.randn(100)

# create scatter plot with x, y
plt.scatter(x, y)

# create scatter
```

結果看起來是正確的。它也適用於 `pandas` 類型？讓我們看看我們是否使用兩個數組可以創建一個 `DataFrame` ：

```py
txt = """\
# create some data
x = np.random.randn(100)
y = np.random.randn(100)

# create dataframe from x and y
"""
print(pipe(txt, num_return_sequences=1)[0]["generated_text"])
```

```python out
# create some data
x = np.random.randn(100)
y = np.random.randn(100)

# create dataframe from x and y
df = pd.DataFrame({'x': x, 'y': y})
df.insert(0,'x', x)
for
```

很好，這是正確的答案——儘管它隨後再次插入了列 `x` 。由於生成的token數量有限，以下 `for` 循環被切斷。讓我們看看我們是否可以做一些更復雜的事情並讓模型幫助我們分組操作：

```py
txt = """\
# dataframe with profession, income and name
df = pd.DataFrame({'profession': x, 'income':y, 'name': z})

# calculate the mean income per profession
"""
print(pipe(txt, num_return_sequences=1)[0]["generated_text"])
```

```python out
# dataframe with profession, income and name
df = pd.DataFrame({'profession': x, 'income':y, 'name': z})

# calculate the mean income per profession
profession = df.groupby(['profession']).mean()

# compute the
```

不錯;這是正確的做法。最後，讓我們看看我們是否也可以將其用於 `scikit-learn` 並建立一個隨機森林模型：

```py
txt = """
# import random forest regressor from scikit-learn
from sklearn.ensemble import RandomForestRegressor

# fit random forest model with 300 estimators on X, y:
"""
print(pipe(txt, num_return_sequences=1)[0]["generated_text"])
```

```python out
# import random forest regressor from scikit-learn
from sklearn.ensemble import RandomForestRegressor

# fit random forest model with 300 estimators on X, y:
rf = RandomForestRegressor(n_estimators=300, random_state=random_state, max_depth=3)
rf.fit(X, y)
rf
```

{#if fw === 'tf'}

看看這幾個例子，似乎模型已經學習了Python數據科學堆棧的一些語法。當然，在將模型部署到現實世界之前，我們需要更徹底地評估模型，但這仍然是一個令人印象深刻的原型。

{:else}

從這幾個例子來看，模型似乎已經學習了 Python 數據科學堆棧的一些語法（當然，在將模型部署到現實世界之前，我們需要對其進行更全面的評估）。然而，有時需要對模型訓練進行更多定製才能實現給定用例的必要性能。例如，如果我們想動態更新批量大小或有一個條件訓練循環來即時跳過壞示例怎麼辦？一種選擇是將 `Trainer` 並添加必要的更改，但有時從頭開始編寫訓練循環會更簡單。這就是🤗 Accelerate 的用武之地。   

{/if}

{#if fw === 'pt'}

## 用 🤗 Accelerate 訓練

我們已經看到了如何使用 `Trainer` ，這可以允許一些自定義。然而，有時我們想要完全控制訓練循環，或者我們想要進行一些奇特的更改。在這種情況下 🤗 Accelerate 是一個不錯的選擇，在本節中，我們將逐步介紹使用它來訓練我們的模型的步驟。為了讓事情變得更有趣，我們還將在訓練循環中添加一些修改。

<Youtube id="Hm8_PgVTFuc"/>

由於我們主要對數據科學庫的合理自動填充感興趣，因此對更多使用這些庫的訓練樣本給予更多權重是有意義的。我們可以通過使用關鍵字輕鬆識別這些示例，例如 `plt`、`pd`、`sk`、`fit`和`predict`等關鍵字，我們可以很容易地識別這些示例，這些關鍵字是matplotlib最常用的導入名稱。`Pyplot`, `pandas`和`sklearn`以及後者的擬合/預測模式。如果這些都表示為單個標記，我們可以輕鬆檢查它們是否出現在輸入序列中。標記可能有一個空格前綴，因此我們還將在標記器詞彙表中檢查這些版本。為了驗證它是否有效，我們將添加一個測試token ，該token 應拆分為多個tokens：

```py
keytoken_ids = []
for keyword in [
    "plt",
    "pd",
    "sk",
    "fit",
    "predict",
    " plt",
    " pd",
    " sk",
    " fit",
    " predict",
    "testtest",
]:
    ids = tokenizer([keyword]).input_ids[0]
    if len(ids) == 1:
        keytoken_ids.append(ids[0])
    else:
        print(f"Keyword has not single token: {keyword}")
```

```python out
'Keyword has not single token: testtest'
```

太好了，這似乎很好用！我們現在可以編寫一個自定義損失函數，它將輸入序列、logits 和我們剛剛選擇的關​​鍵標記作為輸入。首先，我們需要對齊 logits 和輸入：向右移動一個的輸入序列形成標籤，因為下一個標記是當前標記的標籤。我們可以通過從輸入序列的第二個標記開始標記來實現這一點，因為模型無論如何都不會對第一個標記進行預測。然後我們切斷最後一個 logit，因為我們沒有完整輸入序列之後的標記的標籤。有了這個，我們可以計算每個樣本的損失並計算每個樣本中所有關鍵字的出現次數。最後，我們使用出現次數作為權重計算所有樣本的加權平均值。由於我們不想扔掉所有沒有關鍵字的樣本，我們將權重加1：

```py
from torch.nn import CrossEntropyLoss
import torch


def keytoken_weighted_loss(inputs, logits, keytoken_ids, alpha=1.0):
    # Shift so that tokens < n predict n
    shift_labels = inputs[..., 1:].contiguous()
    shift_logits = logits[..., :-1, :].contiguous()
    # Calculate per-token loss
    loss_fct = CrossEntropyLoss(reduce=False)
    loss = loss_fct(shift_logits.view(-1, shift_logits.size(-1)), shift_labels.view(-1))
    # Resize and average loss per sample
    loss_per_sample = loss.view(shift_logits.size(0), shift_logits.size(1)).mean(axis=1)
    # Calculate and scale weighting
    weights = torch.stack([(inputs == kt).float() for kt in keytoken_ids]).sum(
        axis=[0, 2]
    )
    weights = alpha * (1.0 + weights)
    # Calculate weighted average
    weighted_loss = (loss_per_sample * weights).mean()
    return weighted_loss
```

在我們開始使用這個很棒的新損失函數進行訓練之前，我們需要準備一些東西：

- 我們需要數據加載器來批量加載數據。
- 我們需要設置權重衰減參數。
- 有時我們想要求值，因此將求值代碼包裝在一個函數中是有意義的。

讓我們從數據加載器開始。我們只需要將數據集的格式設置為 `"torch"`，然後我們可以將它傳遞給 PyTorch `DataLoader` ,同時設置適當的批量大小：

```py
from torch.utils.data.dataloader import DataLoader

tokenized_dataset.set_format("torch")
train_dataloader = DataLoader(tokenized_dataset["train"], batch_size=32, shuffle=True)
eval_dataloader = DataLoader(tokenized_dataset["valid"], batch_size=32)
```

接下來，我們對參數進行分組，以便優化器知道哪些將獲得額外的權重衰減。通常，所有偏差和 LayerNorm 權重項都不受此限制；以下我們如何做到這一點：

```py
weight_decay = 0.1


def get_grouped_params(model, no_decay=["bias", "LayerNorm.weight"]):
    params_with_wd, params_without_wd = [], []
    for n, p in model.named_parameters():
        if any(nd in n for nd in no_decay):
            params_without_wd.append(p)
        else:
            params_with_wd.append(p)
    return [
        {"params": params_with_wd, "weight_decay": weight_decay},
        {"params": params_without_wd, "weight_decay": 0.0},
    ]
```

由於我們希望在訓練期間定期在驗證集上評估模型，因此我們也為此編寫一個函數。它只是運行評估數據加載器並收集跨進程的所有損失：

```py
def evaluate():
    model.eval()
    losses = []
    for step, batch in enumerate(eval_dataloader):
        with torch.no_grad():
            outputs = model(batch["input_ids"], labels=batch["input_ids"])

        losses.append(accelerator.gather(outputs.loss))
    loss = torch.mean(torch.cat(losses))
    try:
        perplexity = torch.exp(loss)
    except OverflowError:
        perplexity = float("inf")
    return loss.item(), perplexity.item()
```

通過 `evaluate()` 函數我們定期可以獲取損失值和[perplexity](/course/chapter7/3)。接下來，我們重新定義我們的模型以確保我們再次從頭開始訓練：

```py
model = GPT2LMHeadModel(config)
```

然後我們可以定義我們的優化器，使用之前的函數來分割權重衰減的參數：

```py
from torch.optim import AdamW

optimizer = AdamW(get_grouped_params(model), lr=5e-4)
```

現在讓我們準備模型、優化器和數據加載器，以便我們可以開始訓練：

```py
from accelerate import Accelerator

accelerator = Accelerator(fp16=True)

model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare(
    model, optimizer, train_dataloader, eval_dataloader
)
```

<Tip>

🚨 如果您在 TPU 上進行訓練，則需要將從上面的單元格開始的所有代碼移動到專用的訓練函數中。有關詳細信息，請參閱 [第 3 章](/course/chapter3) for more details.

</Tip>

現在我們已經發送了我們的 `train_dataloader`到 `accelerator.prepare()` ，我們可以使用它的長度來計算訓練步驟的數量。請記住，我們應該始終在準備好dataloader後執行此操作，因為該方法會改變其長度。我們使用經典線性學習率調度：

```py
num_train_epochs = 1
num_update_steps_per_epoch = len(train_dataloader)
num_training_steps = num_train_epochs * num_update_steps_per_epoch

lr_scheduler = get_scheduler(
    name="linear",
    optimizer=optimizer,
    num_warmup_steps=1_000,
    num_training_steps=num_training_steps,
)
```

最後，要將我們的模型推送到 Hub，我們需要創建一個 `Repository` 工作文件夾中的對象。如果您尚未登錄，請先登錄 Hugging Face。我們將從我們想要為模型提供的模型 ID 中確定存儲庫名稱（您可以自由地用自己的選擇替換 `repo_name` ；它只需要包含您的用戶名，可以使用`get_full_repo_name()`函數的查看目前的repo_name）：

```py
from huggingface_hub import Repository, get_full_repo_name

model_name = "codeparrot-ds-accelerate"
repo_name = get_full_repo_name(model_name)
repo_name
```

```python out
'sgugger/codeparrot-ds-accelerate'
```

然後我們可以在本地文件夾中克隆該存儲庫。如果它已經存在，這個本地文件夾應該是我們正在使用的存儲庫的克隆：

```py
output_dir = "codeparrot-ds-accelerate"
repo = Repository(output_dir, clone_from=repo_name)
```

我們現在可以上傳我們保存的任何內容 `output_dir` 通過調用 `repo.push_to_hub()` 方法。這將幫助我們在每個 epoch 結束時上傳中間模型。在我們訓練之前，讓我們運行一個快速測試，看看評估函數是否正常工作：

```py
evaluate()
```

```python out
(10.934126853942871, 56057.14453125)
```

這些損失和困惑度的值非常高，但這並不奇怪，因為我們還沒有訓練過模型。有了這個，我們已經準備好編寫訓練腳本的核心部分：訓練循環。在訓練循環中，我們遍歷數據加載器並將批次傳遞給模型。有了 logits，我們就可以評估我們的自定義損失函數。我們通過梯度累積步驟的數量來縮放損失，以便在聚合更多步驟時不會產生更大的損失。在我們優化之前，我們還剪輯了梯度以獲得更好的收斂性。最後，每隔幾步，我們就會使用新的 `evaluate()` 函數評估模型：

```py
from tqdm.notebook import tqdm

gradient_accumulation_steps = 8
eval_steps = 5_000

model.train()
completed_steps = 0
for epoch in range(num_train_epochs):
    for step, batch in tqdm(
        enumerate(train_dataloader, start=1), total=len(train_dataloader)
    ):
        logits = model(batch["input_ids"]).logits
        loss = keytoken_weighted_loss(batch["input_ids"], logits, keytoken_ids)
        if step % 100 == 0:
            accelerator.print(
                {
                    "lr": get_lr(),
                    "samples": step * samples_per_step,
                    "steps": completed_steps,
                    "loss/train": loss.item() * gradient_accumulation_steps,
                }
            )
        loss = loss / gradient_accumulation_steps
        accelerator.backward(loss)
        if step % gradient_accumulation_steps == 0:
            accelerator.clip_grad_norm_(model.parameters(), 1.0)
            optimizer.step()
            lr_scheduler.step()
            optimizer.zero_grad()
            completed_steps += 1
        if (step % (eval_steps * gradient_accumulation_steps)) == 0:
            eval_loss, perplexity = evaluate()
            accelerator.print({"loss/eval": eval_loss, "perplexity": perplexity})
            model.train()
            accelerator.wait_for_everyone()
            unwrapped_model = accelerator.unwrap_model(model)
            unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save)
            if accelerator.is_main_process:
                tokenizer.save_pretrained(output_dir)
                repo.push_to_hub(
                    commit_message=f"Training in progress step {step}", blocking=False
                )
```

就是這樣 - 您現在擁有自己的因果語言模型（例如 GPT-2）的自定義訓練循環，您可以根據自己的需要進一步自定義。

<Tip>

✏️ **試試看!** 創建適合您的用例的自定義損失函數，或在訓練循環中添加另一個自定義步驟。

</Tip>

<Tip>

✏️ **試試看!** 在運行長時間的訓練實驗時，最好使用 TensorBoard 或 Weights Biases 等工具記錄重要指標。向訓練循環添加適當的日誌記錄，以便您始終可以檢查訓練的進行情況。going.

</Tip>

{/if}

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter7/6.mdx" />

### 精通自然語言處理
https://huggingface.co/learn/course/zh-TW/chapter7/8.md

# 精通自然語言處理

如果你在課程中做到了這一步，恭喜你——你現在擁有了用 🤗 Transformers 和 Hugging Face 生態系統解決（幾乎）任何 NLP 任務所需的所有知識和工具！

我們見過很多不同的數據整理器，所以我們製作了這個小視頻來幫助您找到每個任務使用哪一個：

<Youtube id="-RPeakdlHYo"/>

在完成核心 NLP 任務的快速入門後，您應該：

* 瞭解哪種架構（編碼器、解碼器或編碼器-解碼器）最適合每個任務
* 瞭解預訓練和微調語言模型之間的區別
* 瞭解如何使用 `Trainer` API 和 🤗 Accelerate 或 TensorFlow 和 Keras 的分佈式訓練功能來訓練 Transformer 模型，具體選擇那一種方法取決於您所需要完成的任務。
* 瞭解 ROUGE 和 BLEU 等指標在文本生成任務中的意義和侷限性
* 知道如何在 Hub 上和使用 🤗 Transformers 中的“管道”與您的微調模型進行交互

儘管掌握了所有這些知識，但總有一天你會遇到代碼中的困難錯誤，或者對如何解決特定的 NLP 問題有疑問。幸運的是，Hugging Face 社區隨時為您提供幫助！在這部分課程的最後一章中，我們將探討如何調試 Transformer 模型並有效地尋求幫助。

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter7/8.mdx" />

### 翻譯
https://huggingface.co/learn/course/zh-TW/chapter7/4.md

# 翻譯

{#if fw === 'pt'}

<DocNotebookDropdown
  classNames="absolute z-10 right-0 top-0"
  options={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/chapter7/section4_pt.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/chapter7/section4_pt.ipynb"},
]} />

{:else}

<DocNotebookDropdown
  classNames="absolute z-10 right-0 top-0"
  options={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/chapter7/section4_tf.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/chapter7/section4_tf.ipynb"},
]} />

{/if}

現在讓我們深入研究翻譯。這是另一個[sequence-to-sequence 任務](/course/chapter1/7)，這意味著這是一個可以表述為從一個序列到另一個序列的問題。從這個意義上說，這個問題非常類似[文本摘要](/course/chapter7/6)，並且您可以將我們將在此處學習到的一些內容遷移到其他的序列到序列問題，例如：

- **風格遷移** : 創建一個模型將某種風格遷移到一段文本（例如，正式的風格遷移到休閒的風格或莎士比亞英語到現代英語）
- **生成問題的回答** ：創建一個模型，在給定上下文的情況下生成問題的答案

<Youtube id="1JvfrvZgi6c"/>

如果您有足夠大的兩種（或更多）語言的文本語料庫，您可以從頭開始訓練一個新的翻譯模型，就像我們在[因果語言建模](/course/chapter7/6)部分中所做的那樣。然而，微調現有的翻譯模型會更快，無論是從像 mT5 或 mBART 這樣的多語言模型微調到特定的語言對，或者是專門用於從一種語言翻譯成另一種語言的模型。

在本節中，我們將對[KDE4 數據集](https://huggingface.co/datasets/kde4)上的Marian模型進行微調，該模型經過了從英語到法語的翻譯預訓練(因為很多“ Hugging Face”的員工會說這兩種語言)，它是[KDE應用程序](https://apps.kde.org/)本地化文件的數據集。我們將使用的模型已經在從[Opus 數據集](https://opus.nlpl.eu/)(實際上包含KDE4數據集)中提取的法語和英語文本的大型語料庫上進行了預先訓練。但是，即使我們使用的預訓練模型在其預訓練期間使用了這部分數據集，我們也會看到，經過微調後，我們可以得到一個更好的版本。

完成後，我們將擁有一個模型，可以進行這樣的翻譯：

<iframe src="https://course-demos-marian-finetuned-kde4-en-to-fr.hf.space" frameBorder="0" height="350" title="Gradio app" class="block dark:hidden container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>
<iframe src="https://course-demos-marian-finetuned-kde4-en-to-fr-darkmode.hf.space" frameBorder="0" height="350" title="Gradio app" class="hidden dark:block container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

<a class="flex justify-center" href="/huggingface-course/marian-finetuned-kde4-en-to-fr">
<img class="block dark:hidden lg:w-3/5" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter7/modeleval-marian-finetuned-kde4-en-to-fr.png" alt="One-hot encoded labels for question answering."/>
<img class="hidden dark:block lg:w-3/5" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter7/modeleval-marian-finetuned-kde4-en-to-fr-dark.png" alt="One-hot encoded labels for question answering."/>
</a>

與前面的部分一樣，您可以使用以下代碼找到我們將訓練並上傳到 Hub 的實際模型，並[在這裡](https://huggingface.co/huggingface-course/marian-finetuned-kde4-en-to-fr?text=This+plugin+allows+you+to+automatically+translate+web+pages+between+several+languages.)查看模型輸出的結果

## 準備數據

為了從頭開始微調或訓練翻譯模型，我們需要一個適合該任務的數據集。如前所述，我們將使用[KDE4 數據集](https://huggingface.co/datasets/kde4)在本節中，但您可以很容易地調整代碼以使用您自己的數據，只要您有要互譯的兩種語言的句子對。如果您需要複習如何將自定義數據加載到 **Dataset** 可以複習一下[第五章](/course/chapter5).

### KDE4 數據集

像往常一樣，我們使用 **load_dataset()** 函數：

```py
from datasets import load_dataset, load_metric

raw_datasets = load_dataset("kde4", lang1="en", lang2="fr")
```

如果您想使用不同的語言對，您可以使用它們的代碼來指定它們。該數據集共有 92 種語言可用；您可以通過[數據集卡片](https://huggingface.co/datasets/kde4)展開其上的語言標籤來查看它們.

<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter7/language_tags.png" alt="Language available for the KDE4 dataset." width="100%">

我們來看看數據集：

```py
raw_datasets
```

```python out
DatasetDict({
    train: Dataset({
        features: ['id', 'translation'],
        num_rows: 210173
    })
})
```

我們有 210,173 對句子，但在一次訓練過程中，我們需要創建自己的驗證集。正如我們在[第五章](/course/chapter5)學的的那樣, **Dataset** 有一個 **train_test_split()** 方法,可以幫我們拆分數據集。我們將設定固定的隨機數種子：

```py
split_datasets = raw_datasets["train"].train_test_split(train_size=0.9, seed=20)
split_datasets
```

```python out
DatasetDict({
    train: Dataset({
        features: ['id', 'translation'],
        num_rows: 189155
    })
    test: Dataset({
        features: ['id', 'translation'],
        num_rows: 21018
    })
})
```

我們可以將 **test** 的鍵重命名為 **validation** 像這樣：

```py
split_datasets["validation"] = split_datasets.pop("test")
```

現在讓我們看一下數據集的一個元素：

```py
split_datasets["train"][1]["translation"]
```

```python out
{'en': 'Default to expanded threads',
 'fr': 'Par défaut, développer les fils de discussion'}
```

我們得到一個字典，其中包含我們請求的兩種語言的兩個句子。這個充滿技術計算機科學術語的數據集的一個特殊之處在於它們都完全用法語翻譯。然而，法國工程師通常很懶惰，在交談時，大多數計算機科學專用詞彙都用英語表述。例如，“threads”這個詞很可能出現在法語句子中，尤其是在技術對話中；但在這個數據集中，它被翻譯成更正確的“fils de Discussion”。我們使用的預訓練模型已經在一個更大的法語和英語句子語料庫上進行了預訓練，採用了更簡單的選擇，即保留單詞的原樣:

```py
from transformers import pipeline

model_checkpoint = "Helsinki-NLP/opus-mt-en-fr"
translator = pipeline("translation", model=model_checkpoint)
translator("Default to expanded threads")
```

```python out
[{'translation_text': 'Par défaut pour les threads élargis'}]
```

這種情況的另一個例子是“plugin”這個詞，它不是正式的法語詞，但大多數母語人士都能理解，也不會費心去翻譯。
在 KDE4 數據集中，這個詞在法語中被翻譯成更正式的“module d’extension”：
```py
split_datasets["train"][172]["translation"]
```

```python out
{'en': 'Unable to import %1 using the OFX importer plugin. This file is not the correct format.',
 'fr': "Impossible d'importer %1 en utilisant le module d'extension d'importation OFX. Ce fichier n'a pas un format correct."}
```

然而，我們的預訓練模型堅持使用簡練而熟悉的英文單詞：

```py
translator(
    "Unable to import %1 using the OFX importer plugin. This file is not the correct format."
)
```

```python out
[{'translation_text': "Impossible d'importer %1 en utilisant le plugin d'importateur OFX. Ce fichier n'est pas le bon format."}]
```

看看我們的微調模型是否能識別數據集的這些特殊性。（劇透警告：它會）。

<Youtube id="0Oxphw4Q9fo"/>

<Tip>

✏️ **輪到你了！** 另一個在法語中經常使用的英語單詞是“email”。在訓練數據集中找到使用這個詞的第一個樣本。它是如何翻譯的？預訓練模型如何翻譯同一個英文句子？

</Tip>

### 處理數據

<Youtube id="XAR8jnZZuUs"/>

您現在應該知道我們的下一步該做些什麼了：所有文本都需要轉換為token ID，以便模型能夠理解它們。對於這個任務，我們需要同時標記輸入和目標。我們的首要任務是創建我們的 **tokenizer** 對象。如前所述，我們將使用 Marian 英語到法語的預訓練模型。如果您使用另一對語言嘗試此代碼，請確保調整模型Checkpoint。[Helsinki-NLP](https://huggingface.co/Helsinki-NLP)組織提供了多種語言的一千多種模型。

```python
from transformers import AutoTokenizer

model_checkpoint = "Helsinki-NLP/opus-mt-en-fr"
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint, return_tensors="tf")
```

您可以將 **model_checkpoint** 更換為[Hub](https://huggingface.co/models)上你喜歡的任何其他型號，或本地保存的預訓練模型和標記器。

<Tip>

💡 如果正在使用mart、mBART-50或M2 M100等多語言標記器，則需要在tokenizer中設置tokenizer.src_lang和tokenizer.tgt_lang為正確的輸入和目標的語言代碼。

</Tip>

我們的數據準備非常簡單。 只需要記住一件事：您照常處理輸入，但對於這次的輸出目標，您需要將標記器包裝在上下文管理器“as_target_tokenizer()”中。

Python 中的上下文管理器引入了 **with** 語句，當您有兩個相關的操作要成對執行時很有用。最常見的例子是當您寫入或讀取文件時，下面是一個例子：

```
with open(file_path) as f:
    content = f.read()
```

這裡成對執行的兩個相關操作是打開和關閉文件的操作。打開的文件f對應的對象只在with下的縮進塊內有效；在該塊之前打開，在該塊的末尾關閉。

在本例中，上下文管理器 as_target_tokenizer() 將在執行縮進塊之前將標記器設置為輸出語言（此處為法語），然後將其設置回輸入語言（此處為英語）。

因此，預處理一個樣本如下所示：

```python
en_sentence = split_datasets["train"][1]["translation"]["en"]
fr_sentence = split_datasets["train"][1]["translation"]["fr"]

inputs = tokenizer(en_sentence)
with tokenizer.as_target_tokenizer():
    targets = tokenizer(fr_sentence)
```

如果我們忘記在上下文管理器中標記目標，它們將被輸入標記器標記，在Marian模型的情況下，會導致輸出的異常：

```python
wrong_targets = tokenizer(fr_sentence)
print(tokenizer.convert_ids_to_tokens(wrong_targets["input_ids"]))
print(tokenizer.convert_ids_to_tokens(targets["input_ids"]))
```

```python out
['▁Par', '▁dé', 'f', 'aut', ',', '▁dé', 've', 'lop', 'per', '▁les', '▁fil', 's', '▁de', '▁discussion', '</s>']
['▁Par', '▁défaut', ',', '▁développer', '▁les', '▁fils', '▁de', '▁discussion', '</s>']
```

正如我們所看到的，使用英語標記器來預處理法語句子會產生更多的標記，因為標記器不知道任何法語單詞(除了那些也出現在英語語言中的單詞，比如“discussion”)。

`inputs` 和 `targets` 都是帶有我們常用鍵（輸入 ID、注意掩碼等）的字典，所以最後一步是在輸入中設置一個 `"labels"` 鍵。 我們在數據集的預處理函數中執行此操作：

```python
max_input_length = 128
max_target_length = 128


def preprocess_function(examples):
    inputs = [ex["en"] for ex in examples["translation"]]
    targets = [ex["fr"] for ex in examples["translation"]]
    model_inputs = tokenizer(inputs, max_length=max_input_length, truncation=True)

    # Set up the tokenizer for targets
    with tokenizer.as_target_tokenizer():
        labels = tokenizer(targets, max_length=max_target_length, truncation=True)

    model_inputs["labels"] = labels["input_ids"]
    return model_inputs
```

請注意，我們為輸入和輸出設置了相同的最大長度。由於我們處理的文本看起來很短，我們使用 128。

<Tip>

💡如果你使用的是T5模型(更具體地說，是T5 -xxx檢查點之一)，模型將需要文本輸入有一個前綴來表示正在進行的任務，例如從英語到法語的翻譯

</Tip>

<Tip warning={true}>

⚠️ 我們不關注目標的注意力掩碼，因為模型不會需要它。相反，對應於填充標記的標籤應設置為-100，以便在loss計算中忽略它們。這將在稍後由我們的數據整理器完成，因為我們正在應用動態填充，但是如果您在此處使用填充，您應該調整預處理函數以將與填充標記對應的所有標籤設置為 -100。

</Tip>

我們現在可以對數據集的所有數據一次性應用該預處理：

```py
tokenized_datasets = split_datasets.map(
    preprocess_function,
    batched=True,
    remove_columns=split_datasets["train"].column_names,
)
```

現在數據已經過預處理，我們準備好微調我們的預訓練模型！

{#if fw === 'pt'}

## 使用 Trainer API 微調模型

使用 `Trainer` 的實際代碼將與以前相同，只是稍作改動：我們在這裡使用 [`Seq2SeqTrainer`](https://huggingface.co/transformers/main_classes/trainer.html#seq2seqtrainer)， 它是 `Trainer` 的子類，它可以正確處理這種序列到序列的評估，並使用 `generate()` 方法來預測輸入的輸出。 當我們討論評估指標時，我們將更詳細地探討這一點。

首先，我們需要一個實際的模型來進行微調。 我們將使用通常的 `AutoModel` API：

```py
from transformers import AutoModelForSeq2SeqLM

model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint)
```

{:else}

## 使用 Keras 微調模型

首先，我們需要一個實際的模型來進行微調。 我們將使用通常的 `AutoModel` API：

```py
from transformers import TFAutoModelForSeq2SeqLM

model = TFAutoModelForSeq2SeqLM.from_pretrained(model_checkpoint, from_pt=True)
```

<Tip warning={false}>

💡 `Helsinki-NLP/opus-mt-en-fr` checkpoint只有 PyTorch 的權重，所以在使用`from_pretrained()`方法加載模型時不會使用 `from_pt=True` 參數。 當您指定`from_pt=True`，庫會自動下載並轉換PyTorch 為您提供權重。 如您所見，使用🤗transormer 在兩者之間切換非常簡單

</Tip>

{/if}

請注意，這次我們使用的是在翻譯任務上訓練過的模型，並且實際上已經可以使用，因此沒有關於丟失權重或新初始化權重的警告。

### 數據整理

我們需要一個數據整理器來處理動態批處理的填充。在本例中，我們不能像[第3章](/course/chapter3)那樣使用帶填充的**DataCollatorWithPadding**，因為它只填充輸入（輸入ID、注意掩碼和令牌類型ID）。我們的標籤也應該填充到標籤中遇到的最大長度。而且，如前所述，用於填充標籤的填充值應為-100，而不是標記器的填充標記，以確保在損失計算中忽略這些填充值。

這一切都可以由 [`DataCollatorForSeq2Seq`](https://huggingface.co/transformers/main_classes/data_collator.html#datacollatorforseq2seq) 完成。 與 `DataCollatorWithPadding` 一樣，它採用用於預處理輸入的`tokenizer`，但它也採用`model`。 這是因為數據整理器還將負責準備解碼器輸入 ID，它們是標籤偏移之後形成的，開頭帶有特殊標記。 由於不同架構的這種轉變略有不同，因此“DataCollatorForSeq2Seq”需要知道“模型”對象：

{#if fw === 'pt'}

```py
from transformers import DataCollatorForSeq2Seq

data_collator = DataCollatorForSeq2Seq(tokenizer, model=model)
```

{:else}

```py
from transformers import DataCollatorForSeq2Seq

data_collator = DataCollatorForSeq2Seq(tokenizer, model=model, return_tensors="tf")
```

{/if}

為了在幾個樣本上進行測試，我們只需在我們標記化訓練集中的部分數據上調用它：

```py
batch = data_collator([tokenized_datasets["train"][i] for i in range(1, 3)])
batch.keys()
```

```python out
dict_keys(['attention_mask', 'input_ids', 'labels', 'decoder_input_ids'])
```

我們可以檢查我們的標籤是否已使用 **-100** 填充到批次的最大長度：

```py
batch["labels"]
```

```python out
tensor([[  577,  5891,     2,  3184,    16,  2542,     5,  1710,     0,  -100,
          -100,  -100,  -100,  -100,  -100,  -100],
        [ 1211,     3,    49,  9409,  1211,     3, 29140,   817,  3124,   817,
           550,  7032,  5821,  7907, 12649,     0]])
```

我們還可以查看解碼器輸入 ID，看看它們是標籤的偏移形成的版本：

```py
batch["decoder_input_ids"]
```

```python out
tensor([[59513,   577,  5891,     2,  3184,    16,  2542,     5,  1710,     0,
         59513, 59513, 59513, 59513, 59513, 59513],
        [59513,  1211,     3,    49,  9409,  1211,     3, 29140,   817,  3124,
           817,   550,  7032,  5821,  7907, 12649]])
```

以下是我們數據集中第一個和第二個元素的標籤：

```py
for i in range(1, 3):
    print(tokenized_datasets["train"][i]["labels"])
```

```python out
[577, 5891, 2, 3184, 16, 2542, 5, 1710, 0]
[1211, 3, 49, 9409, 1211, 3, 29140, 817, 3124, 817, 550, 7032, 5821, 7907, 12649, 0]
```

{#if fw === 'pt'}

我們將把這個 `data_collator` 傳遞給 `Seq2SeqTrainer`。 接下來，讓我們看一下評估指標。

{:else}

我們現在可以使用 `data_collator` 將我們的每個數據集轉換為 `tf.data.Dataset`，準備好進行訓練：

```python
tf_train_dataset = tokenized_datasets["train"].to_tf_dataset(
    columns=["input_ids", "attention_mask", "labels"],
    collate_fn=data_collator,
    shuffle=True,
    batch_size=32,
)
tf_eval_dataset = tokenized_datasets["validation"].to_tf_dataset(
    columns=["input_ids", "attention_mask", "labels"],
    collate_fn=data_collator,
    shuffle=False,
    batch_size=16,
)
```

{/if}


### 評估指標

<Youtube id="M05L1DhFqcw"/>

{#if fw === 'pt'}

`Seq2SeqTrainer` 添加到其超類 `Trainer` 的功能是在評估或預測期間使用 `generate()` 方法的能力。 在訓練期間，模型將使用帶有注意掩碼的“decoder_input_ids”，以確保它不使用預測的標記之後的標記，以加快訓練速度。 在推理過程中，我們將無法使用預測的標記之後的標記，因為我們沒有標籤，因此使用相同的設置使用帶有注意掩碼的“decoder_input_ids”，評估我們的模型是個好主意。

正如我們在[第一章](/course/chapter1/6)看到的，解碼器通過一個一個地預測標記來執行推理——這是🤗 Transformers 在幕後通過 **generate()** 方法實現的。如果我們設置 predict_with_generate=True，Seq2 Seq Trainer 將允許我們使用該方法進行評估。


{/if}

用於翻譯的傳統指標是[BLEU 分數](https://en.wikipedia.org/wiki/BLEU), 由Kishore Papineni等人在[2002年的一篇文章](https://aclanthology.org/P02-1040.pdf)中引入。BLEU 分數評估翻譯與其標籤的接近程度。它不衡量模型生成輸出的可懂度或語法正確性，而是使用統計規則來確保生成輸出中的所有單詞也出現在目標中。此外，如果相同單詞在目標中沒有重複，則有規則懲罰相同單詞的重複（以避免模型輸出類似 **the the the the the**的句子 ) 並輸出比目標中短的句子（以避免模型輸出像 **the** 這樣的句子）。

BLEU 的一個缺點是它需要文本已經被分詞，這使得比較使用不同標記器的模型之間的分數變得困難。因此，當今用於基準翻譯模型的最常用指標是[SacreBLEU](https://github.com/mjpost/sacrebleu)，它通過標準化標記化步驟解決了這個缺點（和其他的一些缺點）。要使用此指標，我們首先需要安裝 SacreBLEU 庫：

```py
!pip install sacrebleu
```

然後我們可以就像我們在[第三章](/course/chapter3)那樣通過 **load_metric()** 加載它 ：

```py
from datasets import load_metric

metric = load_metric("sacrebleu")
```

該指標將文本作為輸入和目標結果。它旨在接受多個可接受的目標，因為同一個句子通常有多個可接受的翻譯——我們使用的數據集只提供一個，但在 NLP 中找到將多個句子作為標籤的數據集不是一個難題。因此，預測結果應該是一個句子列表，而參考應該是一個句子列表的列表。

讓我們嘗試一個例子：

```py
predictions = [
    "This plugin lets you translate web pages between several languages automatically."
]
references = [
    [
        "This plugin allows you to automatically translate web pages between several languages."
    ]
]
metric.compute(predictions=predictions, references=references)
```

```python out
{'score': 46.750469682990165,
 'counts': [11, 6, 4, 3],
 'totals': [12, 11, 10, 9],
 'precisions': [91.67, 54.54, 40.0, 33.33],
 'bp': 0.9200444146293233,
 'sys_len': 12,
 'ref_len': 13}
```

這得到了 46.75 的 BLEU 分數，這是相當不錯的——作為參考，原始 Transformer 模型在[“Attention Is All You Need” 論文](https://arxiv.org/pdf/1706.03762.pdf)類似的英語和法語翻譯任務中獲得了 41.8 的 BLEU 分數！ （有關各個指標的更多信息，例如 **counts** 和 **bp** ，見[SacreBLEU 倉庫](https://github.com/mjpost/sacrebleu/blob/078c440168c6adc89ba75fe6d63f0d922d42bcfe/sacrebleu/metrics/bleu.py#L74).) 另一方面，如果我們嘗試使用翻譯模型中經常出現的兩種糟糕的預測類型（大量重複或太短），我們將得到相當糟糕的 BLEU 分數：

```py
predictions = ["This This This This"]
references = [
    [
        "This plugin allows you to automatically translate web pages between several languages."
    ]
]
metric.compute(predictions=predictions, references=references)
```

```python out
{'score': 1.683602693167689,
 'counts': [1, 0, 0, 0],
 'totals': [4, 3, 2, 1],
 'precisions': [25.0, 16.67, 12.5, 12.5],
 'bp': 0.10539922456186433,
 'sys_len': 4,
 'ref_len': 13}
```

```py
predictions = ["This plugin"]
references = [
    [
        "This plugin allows you to automatically translate web pages between several languages."
    ]
]
metric.compute(predictions=predictions, references=references)
```

```python out
{'score': 0.0,
 'counts': [2, 1, 0, 0],
 'totals': [2, 1, 0, 0],
 'precisions': [100.0, 100.0, 0.0, 0.0],
 'bp': 0.004086771438464067,
 'sys_len': 2,
 'ref_len': 13}
```

分數可以從 0 到 100，越高越好。

{#if fw === 'tf'}

為了從模型輸出可以被評估基準可以使用的文本，我們將使用 **tokenizer.batch_decode()** 方法。我們只需要清理標籤中所有 **-100** （標記器會自動為填充標記做同樣的事情）：

```py
import numpy as np


def compute_metrics():
    all_preds = []
    all_labels = []
    sampled_dataset = tokenized_datasets["validation"].shuffle().select(range(200))
    tf_generate_dataset = sampled_dataset.to_tf_dataset(
        columns=["input_ids", "attention_mask", "labels"],
        collate_fn=data_collator,
        shuffle=False,
        batch_size=4,
    )
    for batch in tf_generate_dataset:
        predictions = model.generate(
            input_ids=batch["input_ids"], attention_mask=batch["attention_mask"]
        )
        decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True)
        labels = batch["labels"].numpy()
        labels = np.where(labels != -100, labels, tokenizer.pad_token_id)
        decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True)
        decoded_preds = [pred.strip() for pred in decoded_preds]
        decoded_labels = [[label.strip()] for label in decoded_labels]
        all_preds.extend(decoded_preds)
        all_labels.extend(decoded_labels)

    result = metric.compute(predictions=all_preds, references=all_labels)
    return {"bleu": result["score"]}
```

{:else}

為了從模型輸出到度量可以使用的文本，我們將使用 `tokenizer.batch_decode()` 方法。 我們只需要清理標籤中的所有 `-100`（標記器將自動對填充標記執行相同操作）：

```py
import numpy as np


def compute_metrics(eval_preds):
    preds, labels = eval_preds
    # In case the model returns more than the prediction logits
    if isinstance(preds, tuple):
        preds = preds[0]

    decoded_preds = tokenizer.batch_decode(preds, skip_special_tokens=True)

    # Replace -100s in the labels as we can't decode them
    labels = np.where(labels != -100, labels, tokenizer.pad_token_id)
    decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True)

    # Some simple post-processing
    decoded_preds = [pred.strip() for pred in decoded_preds]
    decoded_labels = [[label.strip()] for label in decoded_labels]

    result = metric.compute(predictions=decoded_preds, references=decoded_labels)
    return {"bleu": result["score"]}
```

{/if}

現在這已經完成了，我們已經準備好微調我們的模型了！

### 微調模型

第一步是登錄 Hugging Face，這樣您就可以將結果上傳到模型中心。有一個方便的功能可以幫助您在notebook中完成此操作：

```python
from huggingface_hub import notebook_login

notebook_login()
```

這將顯示一個小部件，您可以在其中輸入您的 Hugging Face 登錄憑據。

如果您不是在notebook上運行代碼，只需在終端中輸入以下行：

```bash
huggingface-cli login
```

{#if fw === 'tf'}

在我們開始之前，讓我們看看我們在沒有任何訓練的情況下從我們的模型中得到了什麼樣的結果：

```py
print(compute_metrics())
```

```
{'bleu': 33.26983701454733}
```

一旦完成，我們就可以準備編譯和訓練模型所需的一切。 注意當使用 `tf.keras.mixed_precision.set_global_policy("mixed_float16")`時——這將告訴 Keras 使用 float16 進行訓練，這可以顯著提高支持它的 GPU（Nvidia 20xx/V100 或更高版本）的速度。

```python
from transformers import create_optimizer
from transformers.keras_callbacks import PushToHubCallback
import tensorflow as tf

# The number of training steps is the number of samples in the dataset, divided by the batch size then multiplied
# by the total number of epochs. Note that the tf_train_dataset here is a batched tf.data.Dataset,
# not the original Hugging Face Dataset, so its len() is already num_samples // batch_size.
num_epochs = 3
num_train_steps = len(tf_train_dataset) * num_epochs

optimizer, schedule = create_optimizer(
    init_lr=5e-5,
    num_warmup_steps=0,
    num_train_steps=num_train_steps,
    weight_decay_rate=0.01,
)
model.compile(optimizer=optimizer)

# Train in mixed-precision float16
tf.keras.mixed_precision.set_global_policy("mixed_float16")
```

接下來，我們定義一個 `PushToHubCallback` 以便在訓練期間將我們的模型上傳到 Hub，正如我們在 [第 2 節]((/course/chapter7/2)) 中看到的，然後我們只需擬合模型時添加該回調函數：

```python
from transformers.keras_callbacks import PushToHubCallback

callback = PushToHubCallback(
    output_dir="marian-finetuned-kde4-en-to-fr", tokenizer=tokenizer
)

model.fit(
    tf_train_dataset,
    validation_data=tf_eval_dataset,
    callbacks=[callback],
    epochs=num_epochs,
)
```

請注意，您可以使用 `hub_model_id` 參數指定要推送到的存儲庫的名稱（當您想把模型推送到指定的組織的時候，您也必須使用此參數）。 例如，當我們將模型推送到 [`huggingface-course` 組織](https://huggingface.co/huggingface-course) 時，我們添加了 `hub_model_id="huggingface-course/marian-finetuned-kde4-en- to-fr"` 到 `Seq2SeqTrainingArguments`。 默認情況下，使用的存儲庫將在您的命名空間中，並以您設置的輸出目錄命名，因此這裡將是 `"sgugger/marian-finetuned-kde4-en-to-fr"`。

<Tip>

💡如果您使用的輸出目錄已經存在，則它需要是您要推送到的存儲庫的本地克隆。如果不是，您將在定義您的名稱時會遇到錯誤，並且需要設置一個新名稱。

</Tip>

最後，讓我們看看訓練結束後我們的指標是什麼樣的：

```py
print(compute_metrics())
```

```
{'bleu': 57.334066271545865}
```

在這個階段，您可以使用模型中心上的推理小部件來測試您的模型並與您的朋友分享。 您已經成功地微調了翻譯任務中的模型——恭喜！

{:else}

一旦完成，我們就可以定義我們的 `Seq2SeqTrainingArguments`。 與 `Trainer` 一樣，我們使用 `TrainingArguments` 的子類，其中包含更多可以設置的字段：

```python
from transformers import Seq2SeqTrainingArguments

args = Seq2SeqTrainingArguments(
    f"marian-finetuned-kde4-en-to-fr",
    evaluation_strategy="no",
    save_strategy="epoch",
    learning_rate=2e-5,
    per_device_train_batch_size=32,
    per_device_eval_batch_size=64,
    weight_decay=0.01,
    save_total_limit=3,
    num_train_epochs=3,
    predict_with_generate=True,
    fp16=True,
    push_to_hub=True,
)
```

除了通常的超參數（如學習率、訓練輪數、批次大小和一些權重衰減）之外，與我們在前幾節中看到的相比，這裡有一些變化：

- 我們沒有設置任何定期評估，因為評估需要耗費一定的時間；我們只會在訓練開始之前和結束之後評估我們的模型一次。
- 我們設置fp16=True，這可以加快支持fp16的 GPU 上的訓練速度。
- 和上面我們討論的那樣，我們設置predict_with_generate=True
- 我們用push_to_hub=True在每個 epoch 結束時將模型上傳到 Hub。

請注意，您可以使用 `hub_model_id` 參數指定要推送到的存儲庫的名稱（當您想把模型推送到指定的組織的時候，您也必須使用此參數）。 例如，當我們將模型推送到 [`huggingface-course` 組織](https://huggingface.co/huggingface-course) 時，我們添加了 `hub_model_id="huggingface-course/marian-finetuned-kde4-en- to-fr"` 到 `Seq2SeqTrainingArguments`。 默認情況下，使用的存儲庫將在您的命名空間中，並以您設置的輸出目錄命名，因此這裡將是 `"sgugger/marian-finetuned-kde4-en-to-fr"`。

<Tip>

💡如果您使用的輸出目錄已經存在，則它需要是您要推送到的存儲庫的本地克隆。如果不是，您將在定義您的名稱時會遇到錯誤，並且需要設置一個新名稱。

</Tip>


最後，我們需要將所有內容傳遞給 **Seq2SeqTrainer** ：

```python
from transformers import Seq2SeqTrainer

trainer = Seq2SeqTrainer(
    model,
    args,
    train_dataset=tokenized_datasets["train"],
    eval_dataset=tokenized_datasets["validation"],
    data_collator=data_collator,
    tokenizer=tokenizer,
    compute_metrics=compute_metrics,
)
```

在訓練之前，我們將首先查看我們的模型獲得的分數，以仔細檢查我們的微調沒有讓事情變得更糟。此命令需要一些時間，因此您可以在執行時喝杯咖啡：

```python
trainer.evaluate(max_length=max_target_length)
```

```python out
{'eval_loss': 1.6964408159255981,
 'eval_bleu': 39.26865061007616,
 'eval_runtime': 965.8884,
 'eval_samples_per_second': 21.76,
 'eval_steps_per_second': 0.341}
```

BLEU的分數還不錯，這反映了我們的模型已經擅長將英語句子翻譯成法語句子。

接下來是訓練，這也需要一些時間：

```python
trainer.train()
```

請注意，當訓練發生時，每次保存模型時（這裡是每個時期），它都會在後臺上傳到 Hub。這樣，如有必要，您將能夠在另一臺機器上繼續您的訓練。

訓練完成後，我們再次評估我們的模型——希望我們會看到 BLEU 分數有所改善！

```py
trainer.evaluate(max_length=max_target_length)
```

```python out
{'eval_loss': 0.8558505773544312,
 'eval_bleu': 52.94161337775576,
 'eval_runtime': 714.2576,
 'eval_samples_per_second': 29.426,
 'eval_steps_per_second': 0.461,
 'epoch': 3.0}
```

這是近 14 點的改進，這很棒。

最後，我們使用 **push_to_hub()** 方法來確保我們上傳模型的最新版本。這 **Trainer** 還創建了一張包含所有評估結果的模型卡並上傳。此模型卡包含可幫助模型中心為推理演示選擇小部件的元數據。通常不需要做額外的更改，因為它可以從模型類中推斷出正確的小部件，但在這種情況下，相同的模型類可以用於所有類型的序列到序列問題，所以我們指定它是一個翻譯模型：

```py
trainer.push_to_hub(tags="translation", commit_message="Training complete")
```

如果您想檢查命令執行的結果，此命令將返回它剛剛執行的提交的 URL，可以打開url進行檢查：

```python out
'https://huggingface.co/sgugger/marian-finetuned-kde4-en-to-fr/commit/3601d621e3baae2bc63d3311452535f8f58f6ef3'
```

在此階段，您可以使用模型中心上的推理小部件來測試您的模型並與您的朋友分享。您已成功微調翻譯任務的模型 - 恭喜！

如果您想更深入地瞭解訓練循環，我們現在將向您展示如何使用 🤗 Accelerate 做同樣的事情。

{/if}

{#if fw === 'pt'}

## 自定義訓練循環

現在讓我們看一下完整的訓練循環，以便您可以輕鬆自定義所需的部分。它看起來很像我們在[本章第二節](/course/chapter7/2)和[第三章第四小節](/course/chapter3/4)所做的。

### 準備訓練所需的一切

您已經多次看到所有這些，因此這一塊會簡略進行。首先我們將構建我們的數據集的**DataLoader** ，在將數據集設置為 **torch** 格式，我們就得到了 PyTorch 張量：

```py
from torch.utils.data import DataLoader

tokenized_datasets.set_format("torch")
train_dataloader = DataLoader(
    tokenized_datasets["train"],
    shuffle=True,
    collate_fn=data_collator,
    batch_size=8,
)
eval_dataloader = DataLoader(
    tokenized_datasets["validation"], collate_fn=data_collator, batch_size=8
)
```

接下來我們重新實例化我們的模型，以確保我們不會繼續上一節的微調，而是再次從預訓練模型開始重新訓練：

```py
model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint)
```

然後我們需要一個優化器：

```py
from torch.optim import AdamW

optimizer = AdamW(model.parameters(), lr=2e-5)
```

一旦我們擁有所有這些對象，我們就可以將它們發送到 `accelerator.prepare()` 方法。 請記住，如果您想在 Colab 筆記本訓練中使用TPU，則需要將所有這些代碼移動到訓練函數中，並且不應執行任何實例化“加速器”的對象。

```py
from accelerate import Accelerator

accelerator = Accelerator()
model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare(
    model, optimizer, train_dataloader, eval_dataloader
)
```

現在我們已經發送了我們的 **train_dataloader** 到 **accelerator.prepare()** ，我們可以使用它的長度來計算訓練步驟的數量。請記住，我們應該始終在準備好數據加載器後執行此操作，因為該方法會更改 **DataLoader** .我們使用從學習率衰減到 0 的經典線性學習率調度：

```py
from transformers import get_scheduler

num_train_epochs = 3
num_update_steps_per_epoch = len(train_dataloader)
num_training_steps = num_train_epochs * num_update_steps_per_epoch

lr_scheduler = get_scheduler(
    "linear",
    optimizer=optimizer,
    num_warmup_steps=0,
    num_training_steps=num_training_steps,
)
```

最後，要將我們的模型推送到 Hub，我們需要創建一個 **Repository** 工作文件夾中的對象。如果您尚未登錄，請先登錄 Hugging Face。我們將從我們想要為模型提供的模型 ID 中確定存儲庫名稱（您可以自由地用自己的選擇替換 **repo_name** ；它需要包含您的用戶名，可以使用**get_full_repo_name()**函數的查看目前的repo_name）：

```py
from huggingface_hub import Repository, get_full_repo_name

model_name = "marian-finetuned-kde4-en-to-fr-accelerate"
repo_name = get_full_repo_name(model_name)
repo_name
```

```python out
'sgugger/marian-finetuned-kde4-en-to-fr-accelerate'
```

然後我們可以在本地文件夾中克隆該存儲庫。如果它已經存在，這個本地文件夾應該是我們正在使用的存儲庫的克隆：

```py
output_dir = "marian-finetuned-kde4-en-to-fr-accelerate"
repo = Repository(output_dir, clone_from=repo_name)
```

我們現在可以通過調用 **repo.push_to_hub()** 方法上傳我們保存的任何內容 **output_dir** 。這將幫助我們在每個 epoch 結束時上傳過程中的模型。

### 訓練循環

我們現在準備編寫完整的訓練循環。為了簡化它的評估部分，我們定義了這個 **postprocess()** 函數接收預測結果和正確標籤並將它們轉換為我們 **metric** 對象所需要的字符串列表：

```py
def postprocess(predictions, labels):
    predictions = predictions.cpu().numpy()
    labels = labels.cpu().numpy()

    decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True)

    # Replace -100 in the labels as we can't decode them.
    labels = np.where(labels != -100, labels, tokenizer.pad_token_id)
    decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True)

    # Some simple post-processing
    decoded_preds = [pred.strip() for pred in decoded_preds]
    decoded_labels = [[label.strip()] for label in decoded_labels]
    return decoded_preds, decoded_labels
```

訓練循環看起來和[本章第二節](/course/chapter7/2)與[第三章](/course/chapter3)很像，在評估部分有一些不同 - 所以讓我們專注於這一點！

首先要注意的是我們使用 `generate()` 方法來計算預測，但這是我們基礎模型上的一個方法，而不是包裝模型🤗 Accelerate 在 `prepare()` 方法中創建。 這就是為什麼我們先解包模型，然後調用這個方法。

第二件事是，就像[token 分類](/course/chapter7/2)，兩個進程可能將輸入和標籤填充為不同的形狀，因此我們在調用 **gather()** 方法之前使用 **accelerator.pad_across_processes()** 使預測和標籤具有相同的形狀。如果我們不這樣做，評估要麼出錯，要麼永遠在阻塞。

```py
from tqdm.auto import tqdm
import torch

progress_bar = tqdm(range(num_training_steps))

for epoch in range(num_train_epochs):
    # Training
    model.train()
    for batch in train_dataloader:
        outputs = model(**batch)
        loss = outputs.loss
        accelerator.backward(loss)

        optimizer.step()
        lr_scheduler.step()
        optimizer.zero_grad()
        progress_bar.update(1)

    # Evaluation
    model.eval()
    for batch in tqdm(eval_dataloader):
        with torch.no_grad():
            generated_tokens = accelerator.unwrap_model(model).generate(
                batch["input_ids"],
                attention_mask=batch["attention_mask"],
                max_length=128,
            )
        labels = batch["labels"]

        # Necessary to pad predictions and labels for being gathered
        generated_tokens = accelerator.pad_across_processes(
            generated_tokens, dim=1, pad_index=tokenizer.pad_token_id
        )
        labels = accelerator.pad_across_processes(labels, dim=1, pad_index=-100)

        predictions_gathered = accelerator.gather(generated_tokens)
        labels_gathered = accelerator.gather(labels)

        decoded_preds, decoded_labels = postprocess(predictions_gathered, labels_gathered)
        metric.add_batch(predictions=decoded_preds, references=decoded_labels)

    results = metric.compute()
    print(f"epoch {epoch}, BLEU score: {results['score']:.2f}")

    # Save and upload
    accelerator.wait_for_everyone()
    unwrapped_model = accelerator.unwrap_model(model)
    unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save)
    if accelerator.is_main_process:
        tokenizer.save_pretrained(output_dir)
        repo.push_to_hub(
            commit_message=f"Training in progress epoch {epoch}", blocking=False
        )
```

```python out
epoch 0, BLEU score: 53.47
epoch 1, BLEU score: 54.24
epoch 2, BLEU score: 54.44
```

完成此操作後，您應該有一個模型，其結果與使用 `Seq2SeqTrainer` 訓練的模型非常相似。 您可以在 [*huggingface-course/marian-finetuned-kde4-en-to-fr-accelerate*](https://huggingface.co/huggingface-course/marian-finetuned-kde4-en-to-fr-accelerate）查看訓練完的結果。 如果您想測試對訓練循環的任何調整，您可以通過編輯上面顯示的代碼直接實現它們！

{/if}

## 使用微調後的模型

我們已經向您展示瞭如何將我們在模型中心微調的模型與推理小部件一起使用。 要在“管道”中本地使用它，我們只需要指定正確的模型標識符：

```py
from transformers import pipeline

# Replace this with your own checkpoint
model_checkpoint = "huggingface-course/marian-finetuned-kde4-en-to-fr"
translator = pipeline("translation", model=model_checkpoint)
translator("Default to expanded threads")
```

```python out
[{'translation_text': 'Par défaut, développer les fils de discussion'}]
```

正如預期的那樣，我們的預訓練模型將其知識適應了我們對其進行微調的語料庫，而不是單獨留下英文單詞“threads”，而是將其翻譯成法語官方版本。 “”的翻譯也是一樣的：

```py
translator(
    "Unable to import %1 using the OFX importer plugin. This file is not the correct format."
)
```

```python out
[{'translation_text': "Impossible d'importer %1 en utilisant le module externe d'importation OFX. Ce fichier n'est pas le bon format."}]
```

風格適應的另一個很好的例子！

<Tip>

✏️ **輪到你了！** “電子郵件”這個詞在模型返回了什麼？

</Tip>


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter7/4.mdx" />

### 問答
https://huggingface.co/learn/course/zh-TW/chapter7/7.md

# 問答

{#if fw === 'pt'}

<DocNotebookDropdown
  classNames="absolute z-10 right-0 top-0"
  options={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/chapter7/section7_pt.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/chapter7/section7_pt.ipynb"},
]} />

{:else}

<DocNotebookDropdown
  classNames="absolute z-10 right-0 top-0"
  options={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/chapter7/section7_tf.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/chapter7/section7_tf.ipynb"},
]} />

{/if}

是時候看問答了! 這項任務有多種形式, 但我們將在本節中關注的一項稱為*提取*的問答。問題的答案就在 _給定的文檔_ 之中。

<Youtube id="ajPx5LwJD-I"/>

我們將使用 [SQuAD 數據集](https://rajpurkar.github.io/SQuAD-explorer/) 微調一個BERT模型, 其中包括群眾工作者對一組維基百科文章提出的問題。以下是一個小的測試樣例:

<iframe src="https://course-demos-bert-finetuned-squad.hf.space" frameBorder="0" height="450" title="Gradio app" class="block dark:hidden container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>
<iframe src="https://course-demos-bert-finetuned-squad-darkmode.hf.space" frameBorder="0" height="450" title="Gradio app" class="hidden dark:block container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

本節使用的代碼已經上傳到了Hub。你可以在 [這裡](https://huggingface.co/huggingface-course/bert-finetuned-squad?context=%F0%9F%A4%97+Transformers+is+backed+by+the+three+most+popular+deep+learning+libraries+%E2%80%94+Jax%2C+PyTorch+and+TensorFlow+%E2%80%94+with+a+seamless+integration+between+them.+It%27s+straightforward+to+train+your+models+with+one+before+loading+them+for+inference+with+the+other.&question=Which+deep+learning+libraries+back+%F0%9F%A4%97+Transformers%3F) 找到它並嘗試用它進行預測。

<Tip>

💡 像 BERT 這樣的純編碼器模型往往很擅長提取諸如 "誰發明了 Transformer 架構?"之類的事實性問題的答案。但在給出諸如 "為什麼天空是藍色的?" 之類的開放式問題時表現不佳。在這些更具挑戰性的情況下, T5 和 BART 等編碼器-解碼器模型通常使用以與 [文本摘要](/course/chapter7/5) 非常相似的方式合成信息。如果你對這種類型的*生成式*問答感興趣, 我們建議您查看我們基於 [ELI5 數據集](https://huggingface.co/datasets/eli5) 的 [演示](https://yjernite.github.io/lfqa.html)。

</Tip>

## 準備數據

最常用作抽取式問答的學術基準的數據集是 [SQuAD](https://rajpurkar.github.io/SQuAD-explorer/), 所以這就是我們將在這裡使用的。還有一個更難的 [SQuAD v2](https://huggingface.co/datasets/squad_v2) 基準, 其中包括沒有答案的問題。只要你自己的數據集包含上下文列、問題列和答案列, 你就應該能夠調整以下步驟。

### SQuAD 數據集

像往常一樣, 我們只需一步就可以下載和緩存數據集, 這要歸功於 `load_dataset()`:

```py
from datasets import load_dataset

raw_datasets = load_dataset("squad")
```

然後我們可以查看這個對象以, 瞭解有關 SQuAD 數據集的更多信息:

```py
raw_datasets
```

```python out
DatasetDict({
    train: Dataset({
        features: ['id', 'title', 'context', 'question', 'answers'],
        num_rows: 87599
    })
    validation: Dataset({
        features: ['id', 'title', 'context', 'question', 'answers'],
        num_rows: 10570
    })
})
```

看起來我們擁有所需的 `context` 、`question` 和 `answers` 字段, 所以讓我們打印訓練集的第一個元素:

```py
print("Context: ", raw_datasets["train"][0]["context"])
print("Question: ", raw_datasets["train"][0]["question"])
print("Answer: ", raw_datasets["train"][0]["answers"])
```

```python out
Context: 'Architecturally, the school has a Catholic character. Atop the Main Building\'s gold dome is a golden statue of the Virgin Mary. Immediately in front of the Main Building and facing it, is a copper statue of Christ with arms upraised with the legend "Venite Ad Me Omnes". Next to the Main Building is the Basilica of the Sacred Heart. Immediately behind the basilica is the Grotto, a Marian place of prayer and reflection. It is a replica of the grotto at Lourdes, France where the Virgin Mary reputedly appeared to Saint Bernadette Soubirous in 1858. At the end of the main drive (and in a direct line that connects through 3 statues and the Gold Dome), is a simple, modern stone statue of Mary.'
Question: 'To whom did the Virgin Mary allegedly appear in 1858 in Lourdes France?'
Answer: {'text': ['Saint Bernadette Soubirous'], 'answer_start': [515]}
```

`context` 和 `question` 字段使用起來非常簡單。但是 `answers` 字段有點棘手, 因為它將字典與兩個都是列表的字段組成。這是在評估過程中 `squad` 指標所期望的格式; 如果你使用的是自己的數據, 則不必擔心將答案採用相同的格式。`text` 字段比較明顯, 而 `answer_start` 字段包含上下文中每個答案的起始字符索引。

在訓練期間, 只有一種可能的答案。我們可以使用 `Dataset.filter()` 方法:

```py
raw_datasets["train"].filter(lambda x: len(x["answers"]["text"]) != 1)
```

```python out
Dataset({
    features: ['id', 'title', 'context', 'question', 'answers'],
    num_rows: 0
})
```

然而, 對於評估, 每個樣本都有幾個可能的答案, 它們可能相同或不同:

```py
print(raw_datasets["validation"][0]["answers"])
print(raw_datasets["validation"][2]["answers"])
```

```python out
{'text': ['Denver Broncos', 'Denver Broncos', 'Denver Broncos'], 'answer_start': [177, 177, 177]}
{'text': ['Santa Clara, California', "Levi's Stadium", "Levi's Stadium in the San Francisco Bay Area at Santa Clara, California."], 'answer_start': [403, 355, 355]}
```

我們不會深入研究評估腳本, 因為它都會被一個 🤗 Datasets 指標包裹起來, 但簡短的版本是一些問題有幾個可能的答案, 這個腳本會將預測的答案與所有​​的可接受的答案並獲得最高分。例如, 我們看一下索引 2 處的樣本e:

```py
print(raw_datasets["validation"][2]["context"])
print(raw_datasets["validation"][2]["question"])
```

```python out
'Super Bowl 50 was an American football game to determine the champion of the National Football League (NFL) for the 2015 season. The American Football Conference (AFC) champion Denver Broncos defeated the National Football Conference (NFC) champion Carolina Panthers 24–10 to earn their third Super Bowl title. The game was played on February 7, 2016, at Levi\'s Stadium in the San Francisco Bay Area at Santa Clara, California. As this was the 50th Super Bowl, the league emphasized the "golden anniversary" with various gold-themed initiatives, as well as temporarily suspending the tradition of naming each Super Bowl game with Roman numerals (under which the game would have been known as "Super Bowl L"), so that the logo could prominently feature the Arabic numerals 50.'
'Where did Super Bowl 50 take place?'
```

我們可以看到, 答案確實可以是我們之前看到的三種可能性之一。

### 處理訓練數據

<Youtube id="qgaM0weJHpA"/>

讓我們從預處理訓練數據開始。困難的部分將是為問題的答案生成標籤, 這將是與上下文中的答案相對應的標記的開始和結束位置。

但是, 我們不要超越自己。首先, 我們需要使用分詞器將輸入中的文本轉換為模型可以理解的 ID:

```py
from transformers import AutoTokenizer

model_checkpoint = "bert-base-cased"
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)
```

如前所述, 我們將對 BERT 模型進行微調, 但你可以使用任何其他模型類型, 只要它實現了快速標記器即可。你可以在 [this big table](https://huggingface.co/transformers/#supported-frameworks) 中看到所有快速版本的架構, 並檢查你正在使用的 `tokenizer` 對象確實由 🤗 Tokenizers 支持, 你可以查看它的 `is_fast` 屬性:

```py
tokenizer.is_fast
```

```python out
True
```

我們可以將問題和上下文一起傳遞給我們的標記器, 它會正確插入特殊標記以形成如下句子:

```
[CLS] question [SEP] context [SEP]
```

讓我們仔細檢查一下:

```py
context = raw_datasets["train"][0]["context"]
question = raw_datasets["train"][0]["question"]

inputs = tokenizer(question, context)
tokenizer.decode(inputs["input_ids"])
```

```python out
'[CLS] To whom did the Virgin Mary allegedly appear in 1858 in Lourdes France? [SEP] Architecturally, '
'the school has a Catholic character. Atop the Main Building\'s gold dome is a golden statue of the Virgin '
'Mary. Immediately in front of the Main Building and facing it, is a copper statue of Christ with arms '
'upraised with the legend " Venite Ad Me Omnes ". Next to the Main Building is the Basilica of the Sacred '
'Heart. Immediately behind the basilica is the Grotto, a Marian place of prayer and reflection. It is a '
'replica of the grotto at Lourdes, France where the Virgin Mary reputedly appeared to Saint Bernadette '
'Soubirous in 1858. At the end of the main drive ( and in a direct line that connects through 3 statues '
'and the Gold Dome ), is a simple, modern stone statue of Mary. [SEP]'
```

然後標籤將成為開始和結束答案的標記的索引, 並且模型的任務是預測輸入中每個標記的開始和結束 logit, 理論標籤如下:

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter7/qa_labels.svg" alt="One-hot encoded labels for question answering."/>
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter7/qa_labels-dark.svg" alt="One-hot encoded labels for question answering."/>
</div>

在這種情況下, 上下文不會太長, 但是數據集中的一些示例的上下文很長, 會超過我們設置的最大長度(在這種情況下為 384)。正如我們在 [第六章](/course/chapter6/4) 中所看到的, 當我們探索 `question-answering` 管道的內部結構時, 我們將通過從我們的數據集的一個樣本中創建幾個訓練特徵來處理長上下文, 它們之間有一個滑動窗口。

要使用當前示例查看其工作原理, 我們可以將長度限制為 100, 並使用 50 個標記的滑動窗口。提醒一下, 我們使用:

- `max_length` 設置最大長度 (此處為 100)
- `truncation="only_second"` 用於當帶有上下文的問題太長時, 截斷上下文t (位於第二個位置) 
- `stride` 設置兩個連續塊之間的重疊標記數 (這裡為 50)
- `return_overflowing_tokens=True` 讓標記器知道我們想要溢出的標記

```py
inputs = tokenizer(
    question,
    context,
    max_length=100,
    truncation="only_second",
    stride=50,
    return_overflowing_tokens=True,
)

for ids in inputs["input_ids"]:
    print(tokenizer.decode(ids))
```

```python out
'[CLS] To whom did the Virgin Mary allegedly appear in 1858 in Lourdes France? [SEP] Architecturally, the school has a Catholic character. Atop the Main Building\'s gold dome is a golden statue of the Virgin Mary. Immediately in front of the Main Building and facing it, is a copper statue of Christ with arms upraised with the legend " Venite Ad Me Omnes ". Next to the Main Building is the Basilica of the Sacred Heart. Immediately behind the basi [SEP]'
'[CLS] To whom did the Virgin Mary allegedly appear in 1858 in Lourdes France? [SEP] the Main Building and facing it, is a copper statue of Christ with arms upraised with the legend " Venite Ad Me Omnes ". Next to the Main Building is the Basilica of the Sacred Heart. Immediately behind the basilica is the Grotto, a Marian place of prayer and reflection. It is a replica of the grotto at Lourdes, France where the Virgin [SEP]'
'[CLS] To whom did the Virgin Mary allegedly appear in 1858 in Lourdes France? [SEP] Next to the Main Building is the Basilica of the Sacred Heart. Immediately behind the basilica is the Grotto, a Marian place of prayer and reflection. It is a replica of the grotto at Lourdes, France where the Virgin Mary reputedly appeared to Saint Bernadette Soubirous in 1858. At the end of the main drive ( and in a direct line that connects through 3 [SEP]'
'[CLS] To whom did the Virgin Mary allegedly appear in 1858 in Lourdes France? [SEP]. It is a replica of the grotto at Lourdes, France where the Virgin Mary reputedly appeared to Saint Bernadette Soubirous in 1858. At the end of the main drive ( and in a direct line that connects through 3 statues and the Gold Dome ), is a simple, modern stone statue of Mary. [SEP]'
```

如我們所見, 我們的示例被分成四個輸入, 每個輸入都包含問題和上下文的一部分。 請注意, 問題的答案 ("Bernadette Soubirous") 僅出現在第三個也是最後一個輸入中, 因此通過以這種方式處理長上下文, 我們將創建一些答案不包含在上下文中的訓練示例。對於這些示例, 標籤將是 `start_position = end_position = 0` (所以我們預測 `[CLS]` 標記)。我們還將在答案被截斷的不幸情況下設置這些標籤, 以便我們只有它的開始(或結束)。對於答案完全在上下文中的示例, 標籤將是答案開始的標記的索引和答案結束的標記的索引。

數據集為我們提供了上下文中答案的開始字符, 通過添加答案的長度, 我們可以找到上下文中的結束字符。要將它們映射到令牌索引, 我們將需要使用我們在 [第六章](/course/chapter6/4) 中研究的偏移映射。我們可以讓標記器通過傳遞 `return_offsets_mapping=True` 來返回這些值:

```py
inputs = tokenizer(
    question,
    context,
    max_length=100,
    truncation="only_second",
    stride=50,
    return_overflowing_tokens=True,
    return_offsets_mapping=True,
)
inputs.keys()
```

```python out
dict_keys(['input_ids', 'token_type_ids', 'attention_mask', 'offset_mapping', 'overflow_to_sample_mapping'])
```

如我們所見, 我們取回了通常的輸入 ID、令牌類型 ID 和注意掩碼, 以及我們需要的偏移映射和一個額外的鍵, `overflow_to_sample_mapping`。當我們同時標記多個文本時, 相應的值將對我們有用(我們應該這樣做以受益於我們的標記器由 Rust 支持的事實)。由於一個樣本可以提供多個特徵, 因此它將每個特徵映射到其來源的示例。因為這裡我們只標記了一個例子, 我們得到一個 `0` 的列表:

```py
inputs["overflow_to_sample_mapping"]
```

```python out
[0, 0, 0, 0]
```

但是, 如果我們標記更多示例, 這將變得更加有用:

```py
inputs = tokenizer(
    raw_datasets["train"][2:6]["question"],
    raw_datasets["train"][2:6]["context"],
    max_length=100,
    truncation="only_second",
    stride=50,
    return_overflowing_tokens=True,
    return_offsets_mapping=True,
)

print(f"The 4 examples gave {len(inputs['input_ids'])} features.")
print(f"Here is where each comes from: {inputs['overflow_to_sample_mapping']}.")
```

```python out
'The 4 examples gave 19 features.'
'Here is where each comes from: [0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3].'
```

正如我們所看到的, 前三個示例 (在訓練集中的索引 2、3 和 4 處) 每個都給出了四個特徵, 最後一個示例(在訓練集中的索引 5 處) 給出了 7 個特徵。

此信息將有助於將我們獲得的每個特徵映射到其相應的標籤。如前所述, 這些標籤是:

- `(0, 0)` 如果答案不在上下文的相應範圍內
- `(start_position, end_position)` 如果答案在上下文的相應範圍內, 則 `start_position` 是答案開頭的標記索引 (在輸入 ID 中), 並且 `end_position` 是答案結束的標記的索引 (在輸入 ID 中)。

為了確定是哪種情況以及標記的位置, 以及(如果相關的話)標記的位置, 我們首先在輸入 ID 中找到開始和結束上下文的索引。我們可以使用標記類型 ID 來執行此操作, 但由於這些 ID 不一定存在於所有模型中 (例如, DistilBERT 不需要它們), 我們將改為使用我們的標記器返回的 `BatchEncoding` 的 `sequence_ids()` 方法。

一旦我們有了這些標記索引, 我們就會查看相應的偏移量, 它們是兩個整數的元組, 表示原始上下文中的字符範圍。因此, 我們可以檢測此特徵中的上下文塊是在答案之後開始還是在答案開始之前結束(在這種情況下, 標籤是 `(0, 0)`)。如果不是這樣, 我們循環查找答案的第一個和最後一個標記:

```py
answers = raw_datasets["train"][2:6]["answers"]
start_positions = []
end_positions = []

for i, offset in enumerate(inputs["offset_mapping"]):
    sample_idx = inputs["overflow_to_sample_mapping"][i]
    answer = answers[sample_idx]
    start_char = answer["answer_start"][0]
    end_char = answer["answer_start"][0] + len(answer["text"][0])
    sequence_ids = inputs.sequence_ids(i)

    # Find the start and end of the context
    idx = 0
    while sequence_ids[idx] != 1:
        idx += 1
    context_start = idx
    while sequence_ids[idx] == 1:
        idx += 1
    context_end = idx - 1

    # If the answer is not fully inside the context, label is (0, 0)
    if offset[context_start][0] > start_char or offset[context_end][1] < end_char:
        start_positions.append(0)
        end_positions.append(0)
    else:
        # Otherwise it's the start and end token positions
        idx = context_start
        while idx <= context_end and offset[idx][0] <= start_char:
            idx += 1
        start_positions.append(idx - 1)

        idx = context_end
        while idx >= context_start and offset[idx][1] >= end_char:
            idx -= 1
        end_positions.append(idx + 1)

start_positions, end_positions
```

```python out
([83, 51, 19, 0, 0, 64, 27, 0, 34, 0, 0, 0, 67, 34, 0, 0, 0, 0, 0],
 [85, 53, 21, 0, 0, 70, 33, 0, 40, 0, 0, 0, 68, 35, 0, 0, 0, 0, 0])
```

讓我們看一些結果來驗證我們的方法是否正確。對於我們發現的第一個特徵, 我們將 `(83, 85)` 作為標籤, 讓我們將理論答案與從 83 到 85 (包括)的標記解碼範圍進行比較:

```py
idx = 0
sample_idx = inputs["overflow_to_sample_mapping"][idx]
answer = answers[sample_idx]["text"][0]

start = start_positions[idx]
end = end_positions[idx]
labeled_answer = tokenizer.decode(inputs["input_ids"][idx][start : end + 1])

print(f"Theoretical answer: {answer}, labels give: {labeled_answer}")
```

```python out
'Theoretical answer: the Main Building, labels give: the Main Building'
```

所以這是一場比賽! 現在讓我們檢查索引 4, 我們將標籤設置為 `(0, 0)`, 這意味著答案不在該功能的上下文塊中

```py
idx = 4
sample_idx = inputs["overflow_to_sample_mapping"][idx]
answer = answers[sample_idx]["text"][0]

decoded_example = tokenizer.decode(inputs["input_ids"][idx])
print(f"Theoretical answer: {answer}, decoded example: {decoded_example}")
```

```python out
'Theoretical answer: a Marian place of prayer and reflection, decoded example: [CLS] What is the Grotto at Notre Dame? [SEP] Architecturally, the school has a Catholic character. Atop the Main Building\'s gold dome is a golden statue of the Virgin Mary. Immediately in front of the Main Building and facing it, is a copper statue of Christ with arms upraised with the legend " Venite Ad Me Omnes ". Next to the Main Building is the Basilica of the Sacred Heart. Immediately behind the basilica is the Grot [SEP]'
```

事實上, 我們在上下文中看不到答案。

<Tip>

✏️ **輪到你了!** 使用 XLNet 架構時, 在左側應用填充, 並切換問題和上下文。將我們剛剛看到的所有代碼改編為 XLNet 架構 (並添加 `padding=True`)。請注意, `[CLS]` 標記可能不在應用填充的 0 位置。

</Tip>

現在我們已經逐步瞭解瞭如何預處理我們的訓練數據, 我們可以將其分組到一個函數中, 我們將應用於整個訓練數據集。我們會將每個特徵填充到我們設置的最大長度, 因為大多數上下文會很長 (並且相應的樣本將被分成幾個特徵), 所以在這裡應用動態填充沒有真正的好處:

```py
max_length = 384
stride = 128


def preprocess_training_examples(examples):
    questions = [q.strip() for q in examples["question"]]
    inputs = tokenizer(
        questions,
        examples["context"],
        max_length=max_length,
        truncation="only_second",
        stride=stride,
        return_overflowing_tokens=True,
        return_offsets_mapping=True,
        padding="max_length",
    )

    offset_mapping = inputs.pop("offset_mapping")
    sample_map = inputs.pop("overflow_to_sample_mapping")
    answers = examples["answers"]
    start_positions = []
    end_positions = []

    for i, offset in enumerate(offset_mapping):
        sample_idx = sample_map[i]
        answer = answers[sample_idx]
        start_char = answer["answer_start"][0]
        end_char = answer["answer_start"][0] + len(answer["text"][0])
        sequence_ids = inputs.sequence_ids(i)

        # Find the start and end of the context
        idx = 0
        while sequence_ids[idx] != 1:
            idx += 1
        context_start = idx
        while sequence_ids[idx] == 1:
            idx += 1
        context_end = idx - 1

        # If the answer is not fully inside the context, label is (0, 0)
        if offset[context_start][0] > start_char or offset[context_end][1] < end_char:
            start_positions.append(0)
            end_positions.append(0)
        else:
            # Otherwise it's the start and end token positions
            idx = context_start
            while idx <= context_end and offset[idx][0] <= start_char:
                idx += 1
            start_positions.append(idx - 1)

            idx = context_end
            while idx >= context_start and offset[idx][1] >= end_char:
                idx -= 1
            end_positions.append(idx + 1)

    inputs["start_positions"] = start_positions
    inputs["end_positions"] = end_positions
    return inputs
```

請注意, 我們定義了兩個常數來確定使用的最大長度以及滑動窗口的長度, 並且我們在標記化之前添加了一點清理: SQuAD 數據集中的一些問題在開頭有額外的空格, 並且不添加任何內容的結尾 (如果你使用像 RoBERTa 這樣的模型, 則在標記化時會佔用空間), 因此我們刪除了那些額外的空格。

為了將此函數應用於整個訓練集, 我們使用 `Dataset.map()` 方法與 `batched=True` 標誌。這是必要的, 因為我們正在更改數據集的長度(因為一個示例可以提供多個訓練特徵):

```py
train_dataset = raw_datasets["train"].map(
    preprocess_training_examples,
    batched=True,
    remove_columns=raw_datasets["train"].column_names,
)
len(raw_datasets["train"]), len(train_dataset)
```

```python out
(87599, 88729)
```

正如我們所見, 預處理增加了大約 1,000 個特徵。我們的訓練集現在可以使用了-- 讓我們深入研究驗證集的預處理!

### 處理驗證數據

預處理驗證數據會稍微容易一些, 因為我們不需要生成標籤(除非我們想計算驗證損失, 但這個數字並不能真正幫助我們理解模型有多好)。真正的樂趣是將模型的預測解釋為原始上下文的跨度。為此, 我們只需要存儲偏移映射和某種方式來將每個創建的特徵與它來自的原始示例相匹配。由於原始數據集中有一個 ID 列, 我們將使用該 ID。

我們將在這裡添加的唯一內容是對偏移映射的一點點清理。它們將包含問題和上下文的偏移量, 但是一旦我們進入後處理階段, 我們將無法知道輸入 ID 的哪一部分對應於上下文以及哪一部分是問題(我們使用的 `sequence_ids()` 方法僅可用於標記器的輸出)。因此, 我們將與問題對應的偏移量設置為 `None`:

```py
def preprocess_validation_examples(examples):
    questions = [q.strip() for q in examples["question"]]
    inputs = tokenizer(
        questions,
        examples["context"],
        max_length=max_length,
        truncation="only_second",
        stride=stride,
        return_overflowing_tokens=True,
        return_offsets_mapping=True,
        padding="max_length",
    )

    sample_map = inputs.pop("overflow_to_sample_mapping")
    example_ids = []

    for i in range(len(inputs["input_ids"])):
        sample_idx = sample_map[i]
        example_ids.append(examples["id"][sample_idx])

        sequence_ids = inputs.sequence_ids(i)
        offset = inputs["offset_mapping"][i]
        inputs["offset_mapping"][i] = [
            o if sequence_ids[k] == 1 else None for k, o in enumerate(offset)
        ]

    inputs["example_id"] = example_ids
    return inputs
```

我們可以像以前一樣將此函數應用於整個驗證數據集:

```py
validation_dataset = raw_datasets["validation"].map(
    preprocess_validation_examples,
    batched=True,
    remove_columns=raw_datasets["validation"].column_names,
)
len(raw_datasets["validation"]), len(validation_dataset)
```

```python out
(10570, 10822)
```

I在這種情況下, 我們只添加了幾百個樣本, 因此驗證數據集中的上下文似乎有點短。

現在我們已經對所有數據進行了預處理, 我們可以開始訓練了。

{#if fw === 'pt'}

## 使用 `Trainer` API 微調模型

這個例子的訓練代碼看起來很像前面幾節中的代碼 -- 最難的是編寫 `compute_metrics()` 函數。由於我們將所有樣本填充到我們設置的最大長度, 因此沒有數據整理器要定義, 所以這個度量計算真的是我們唯一需要擔心的事情。困難的部分是將模型預測後處理為原始示例中的文本範圍; 一旦我們這樣做了, 🤗 Datasets 庫中的指標將為我們完成大部分工作。

{:else}

## 使用 Keras 微調模型

這個示例的訓練代碼看起來很像前幾節中的代碼, 但是計算指標將是唯一的挑戰。因為我們將所有的樣本填充到我們設置的最大長度, 所以不需要定義數據整理器, 所以這個度量計算實際上是我們唯一需要擔心的事情。困難的部分是將模型預測後處理成原始例子中的文本範圍; 一旦我們完成了這些, 🤗 Datasets 庫中的指標將為我們完成大部分工作。

{/if}

### 後處理

{#if fw === 'pt'}

<Youtube id="BNy08iIWVJM"/>

{:else}

<Youtube id="VN67ZpN33Ss"/>

{/if}

該模型將在輸入ID中為答案的開始和結束位置輸出Logit, 正如我們在探索 [`question-answering` pipeline](/course/chapter6/4) 時看到的那樣。後處理步驟將類似於我們在那裡所做的, 所以這裡是我們採取的行動的快速提醒:

- 我們屏蔽了與上下文之外的標記相對應的開始和結束 logits。
- 然後, 我們使用 softmax 將開始和結束 logits 轉換為概率。
- 我們通過取對應的兩個概率的乘積來給每個 `(start_token, end_token)` 組合賦值。
- 我們尋找產生有效答案的最高分數的配對 (例如, `start_token` 低於 `end_token`)。

在這裡, 我們將稍微改變這個過程, 因為我們不需要計算實際分數 (只是預測的答案)。這意味著我們可以跳過 softmax 步驟。為了更快, 我們也不會對所有可能的 `(start_token, end_token)` 對進行評分, 而只會對對應於最高 `n_best` 的那些對進行評分 (使用 `n_best=20`)。由於我們將跳過 softmax, 因此這些分數將是 logit 分數, 並且將通過取 start 和 end logits 的總和來獲得 (而不是乘積, 因為規則 \\(\log(ab) = \log(a) + \log(b)\\))。

為了證明這一切, 我們需要一些預測。由於我們還沒有訓練我們的模型, 我們將使用 QA 管道的默認模型對一小部分驗證集生成一些預測。我們可以使用和之前一樣的處理函數; 因為它依賴於全局常量 `tokenizer`, 我們只需將該對象更改為我們要臨時使用的模型的標記器:

```python
small_eval_set = raw_datasets["validation"].select(range(100))
trained_checkpoint = "distilbert-base-cased-distilled-squad"

tokenizer = AutoTokenizer.from_pretrained(trained_checkpoint)
eval_set = small_eval_set.map(
    preprocess_validation_examples,
    batched=True,
    remove_columns=raw_datasets["validation"].column_names,
)
```

現在預處理已經完成, 我們將分詞器改回我們最初選擇的那個:

```python
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)
```

然後, 我們刪除 `eval_set` 中模型不期待的列, 用所有的小驗證集構建一個批次, 然後通過模型。如果 GPU 可用, 我們會使用它來加快速度:

{#if fw === 'pt'}

```python
import torch
from transformers import AutoModelForQuestionAnswering

eval_set_for_model = eval_set.remove_columns(["example_id", "offset_mapping"])
eval_set_for_model.set_format("torch")

device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
batch = {k: eval_set_for_model[k].to(device) for k in eval_set_for_model.column_names}
trained_model = AutoModelForQuestionAnswering.from_pretrained(trained_checkpoint).to(
    device
)

with torch.no_grad():
    outputs = trained_model(**batch)
```

由於 `Trainer` 將為我們提供 NumPy 數組的預測, 我們獲取開始和結束 logits 並將它們轉換為該格式

```python
start_logits = outputs.start_logits.cpu().numpy()
end_logits = outputs.end_logits.cpu().numpy()
```

{:else}

```python
import tensorflow as tf
from transformers import TFAutoModelForQuestionAnswering

eval_set_for_model = eval_set.remove_columns(["example_id", "offset_mapping"])
eval_set_for_model.set_format("numpy")

batch = {k: eval_set_for_model[k] for k in eval_set_for_model.column_names}
trained_model = TFAutoModelForQuestionAnswering.from_pretrained(trained_checkpoint)

outputs = trained_model(**batch)
```

為了便於實驗, 讓我們將這些輸出轉換為 NumPy 數組:

```python
start_logits = outputs.start_logits.numpy()
end_logits = outputs.end_logits.numpy()
```

{/if}

現在, 我們需要在 `small_eval_set` 中找到每個示例的預測答案。一個示例可能已經在  `eval_set` 中拆分為多個特徵, 因此第一步是將 `small_eval_set` 中的每個示例映射到 `eval_set` 中相應的特徵:

```python
import collections

example_to_features = collections.defaultdict(list)
for idx, feature in enumerate(eval_set):
    example_to_features[feature["example_id"]].append(idx)
```

有了這個, 我們就可以真正開始工作, 循環遍歷所有示例, 併為每個示例遍歷所有相關功能。正如我們之前所說, 我們將查看 `n_best` 開始 logits 和結束 logits 的 logit 分數, 不包括以下的位置:

- 一個不在上下文中的答案
- 長度為負的答案
- 答案太長 (我們將可能性限制在 `max_answer_length=30`)

一旦我們為一個示例獲得了所有可能的答案, 我們只需選擇一個具有最佳 logit 分數的答案:

```python
import numpy as np

n_best = 20
max_answer_length = 30
predicted_answers = []

for example in small_eval_set:
    example_id = example["id"]
    context = example["context"]
    answers = []

    for feature_index in example_to_features[example_id]:
        start_logit = start_logits[feature_index]
        end_logit = end_logits[feature_index]
        offsets = eval_set["offset_mapping"][feature_index]

        start_indexes = np.argsort(start_logit)[-1 : -n_best - 1 : -1].tolist()
        end_indexes = np.argsort(end_logit)[-1 : -n_best - 1 : -1].tolist()
        for start_index in start_indexes:
            for end_index in end_indexes:
                # Skip answers that are not fully in the context
                if offsets[start_index] is None or offsets[end_index] is None:
                    continue
                # Skip answers with a length that is either < 0 or > max_answer_length.
                if (
                    end_index < start_index
                    or end_index - start_index + 1 > max_answer_length
                ):
                    continue

                answers.append(
                    {
                        "text": context[offsets[start_index][0] : offsets[end_index][1]],
                        "logit_score": start_logit[start_index] + end_logit[end_index],
                    }
                )

    best_answer = max(answers, key=lambda x: x["logit_score"])
    predicted_answers.append({"id": example_id, "prediction_text": best_answer["text"]})
```

預測答案的最終格式是我們將使用的度量標準所期望的格式。像往常一樣, 我們可以在 🤗 Datasets 庫的幫助下加載它:

```python
from datasets import load_metric

metric = load_metric("squad")
```

該指標期望我們上面看到的格式的預測答案 (一個字典列表, 其中一個鍵用於示例 ID, 一個鍵用於預測文本) 和以下格式的理論答案 (一個字典列表, 一個鍵示例的 ID 和可能答案的一鍵):

```python
theoretical_answers = [
    {"id": ex["id"], "answers": ex["answers"]} for ex in small_eval_set
]
```

我們現在可以通過查看兩個列表的第一個元素來檢查我們是否得到了合理的結果:

```python
print(predicted_answers[0])
print(theoretical_answers[0])
```

```python out
{'id': '56be4db0acb8001400a502ec', 'prediction_text': 'Denver Broncos'}
{'id': '56be4db0acb8001400a502ec', 'answers': {'text': ['Denver Broncos', 'Denver Broncos', 'Denver Broncos'], 'answer_start': [177, 177, 177]}}
```

還不錯! 現在讓我們看看這個指標給我們的分數:

```python
metric.compute(predictions=predicted_answers, references=theoretical_answers)
```

```python out
{'exact_match': 83.0, 'f1': 88.25}
```

同樣, 考慮到根據 [its paper](https://arxiv.org/abs/1910.01108v2), 在 SQuAD 上微調的 DistilBERT 在整個數據集上的得分分別為 79.1 和 86.9, 這是相當不錯的。

{#if fw === 'pt'}

現在, 讓我們把剛才所做的一切放在 `compute_metrics()` 函數中, 我們將在 `Trainer` 中使用它。通常, `compute_metrics()` 函數只接收一個包含 logits 和 labels 的元組 `eval_preds`。這裡我們需要更多, 因為我們必須在特徵數據集中查找偏移量, 在原始上下文的示例數據集中查找, 因此我們將無法在訓練期間使用此函數獲得常規評估結果。我們只會在訓練結束時使用它來檢查結果。

`compute_metrics()` 函數將與前面相同的步驟分組; 我們只是添加一個小檢查, 以防我們沒有提出任何有效的答案 (在這種情況下, 我們預測一個空字符串)。

{:else}

現在, 讓我們將剛才所做的一切放入 `compute_metrics()` 函數中, 我們將在訓練模型後使用該函數。我們需要傳遞的不僅僅是輸出日誌, 因為我們必須在特徵數據集中尋找偏移量, 在原始上下文的示例數據集中尋找:

{/if}

```python
from tqdm.auto import tqdm


def compute_metrics(start_logits, end_logits, features, examples):
    example_to_features = collections.defaultdict(list)
    for idx, feature in enumerate(features):
        example_to_features[feature["example_id"]].append(idx)

    predicted_answers = []
    for example in tqdm(examples):
        example_id = example["id"]
        context = example["context"]
        answers = []

        # Loop through all features associated with that example
        for feature_index in example_to_features[example_id]:
            start_logit = start_logits[feature_index]
            end_logit = end_logits[feature_index]
            offsets = features[feature_index]["offset_mapping"]

            start_indexes = np.argsort(start_logit)[-1 : -n_best - 1 : -1].tolist()
            end_indexes = np.argsort(end_logit)[-1 : -n_best - 1 : -1].tolist()
            for start_index in start_indexes:
                for end_index in end_indexes:
                    # Skip answers that are not fully in the context
                    if offsets[start_index] is None or offsets[end_index] is None:
                        continue
                    # Skip answers with a length that is either < 0 or > max_answer_length
                    if (
                        end_index < start_index
                        or end_index - start_index + 1 > max_answer_length
                    ):
                        continue

                    answer = {
                        "text": context[offsets[start_index][0] : offsets[end_index][1]],
                        "logit_score": start_logit[start_index] + end_logit[end_index],
                    }
                    answers.append(answer)

        # Select the answer with the best score
        if len(answers) > 0:
            best_answer = max(answers, key=lambda x: x["logit_score"])
            predicted_answers.append(
                {"id": example_id, "prediction_text": best_answer["text"]}
            )
        else:
            predicted_answers.append({"id": example_id, "prediction_text": ""})

    theoretical_answers = [{"id": ex["id"], "answers": ex["answers"]} for ex in examples]
    return metric.compute(predictions=predicted_answers, references=theoretical_answers)
```

我們可以檢查它是否適用於我們的預測:

```python
compute_metrics(start_logits, end_logits, eval_set, small_eval_set)
```

```python out
{'exact_match': 83.0, 'f1': 88.25}
```

看起來不錯! 現在讓我們用它來微調我們的模型。

### 微調模型

{#if fw === 'pt'}

我們現在準備好訓練我們的模型了。讓我們首先創建它, 像以前一樣使用 `AutoModelForQuestionAnswering` 類:

```python
model = AutoModelForQuestionAnswering.from_pretrained(model_checkpoint)
```

{:else}

我們現在準備好訓練我們的模型了。讓我們首先創建它, 像以前一樣使用 `TFAutoModelForQuestionAnswering` 類:

```python
model = TFAutoModelForQuestionAnswering.from_pretrained(model_checkpoint)
```

{/if}

像往常一樣, 我們收到一個警告, 有些權重沒有使用(來自預訓練頭的), 而另一些是隨機初始化的 (用於問答頭的)。你現在應該已經習慣了, 但這意味著這個模型還沒有準備好使用, 需要微調 -- 我們即將這樣做!

為了能夠將我們的模型推送到 Hub, 我們需要登錄 Hugging Face。 如果你在筆記本中運行此代碼, 則可以使用以下實用程序函數執行此操作, 該函數會顯示一個小部件, 你可以在其中輸入登錄憑據:

```python
from huggingface_hub import notebook_login

notebook_login()
```

如果你不在筆記本中工作, 只需在終端中鍵入以下行:

```bash
huggingface-cli login
```

{#if fw === 'pt'}

完成後, 我們就可以定義我們的 `TrainingArguments`。正如我們在定義函數來計算度量時所說的那樣, 由於 `compute_metrics()` 函數的簽名, 我們將不能有常規的求值循環。我們可以編寫 `Trainer` 的子類來完成這一任務(你可以在 [question answering example script](https://github.com/huggingface/transformers/blob/master/examples/pytorch/question-answering/trainer_qa.py)中找到一種方法), 但這對於本節來說有點太長了。相反, 我們只會在訓練結束時評估模型, 並在下面的"自定義訓練循環"向你展示如何進行常規評估。

T這確實時 `Trainer` API 顯示其侷限性和 🤗 Accelerate 庫的亮點所在: 根據特定用例自定義類可能很痛苦, 但調整完全公開的訓練循環很容易。

來看看我們的 `TrainingArguments`:

```python
from transformers import TrainingArguments

args = TrainingArguments(
    "bert-finetuned-squad",
    evaluation_strategy="no",
    save_strategy="epoch",
    learning_rate=2e-5,
    num_train_epochs=3,
    weight_decay=0.01,
    fp16=True,
    push_to_hub=True,
)
```

我們之前已經看到了其中的大部分: 我們設置了一些超參數 (比如學習率、訓練的 epoch 數和一些權重衰減), 並表明我們希望在每個 epoch 結束時保存模型, 跳過評估, 並將我們的結果上傳到模型中心。我們還使用 `fp16=True` 啟用混合精度訓練, 因為它可以在最近的 GPU 上很好地加快訓練速度。

{:else}

現在, 我們可以創建我們的 TF 數據集。這次我們可以使用簡單的默認數據整理器:

```python
from transformers import DefaultDataCollator

data_collator = DefaultDataCollator(return_tensors="tf")
```

現在我們像往常一樣創建數據集。

```python
tf_train_dataset = train_dataset.to_tf_dataset(
    columns=[
        "input_ids",
        "start_positions",
        "end_positions",
        "attention_mask",
        "token_type_ids",
    ],
    collate_fn=data_collator,
    shuffle=True,
    batch_size=16,
)
tf_eval_dataset = validation_dataset.to_tf_dataset(
    columns=["input_ids", "attention_mask", "token_type_ids"],
    collate_fn=data_collator,
    shuffle=False,
    batch_size=16,
)
```

接下來, 我們設置了我們的訓練超參數並編譯了我們的模型:

```python
from transformers import create_optimizer
from transformers.keras_callbacks import PushToHubCallback
import tensorflow as tf

# The number of training steps is the number of samples in the dataset, divided by the batch size then multiplied
# by the total number of epochs. Note that the tf_train_dataset here is a batched tf.data.Dataset,
# not the original Hugging Face Dataset, so its len() is already num_samples // batch_size.
num_train_epochs = 3
num_train_steps = len(tf_train_dataset) * num_train_epochs
optimizer, schedule = create_optimizer(
    init_lr=2e-5,
    num_warmup_steps=0,
    num_train_steps=num_train_steps,
    weight_decay_rate=0.01,
)
model.compile(optimizer=optimizer)

# Train in mixed-precision float16
tf.keras.mixed_precision.set_global_policy("mixed_float16")
```

最後, 我們準備好使用 `model.fit()` 進行訓練了。我們使用 `PushToHubCallback` 在每個時期之後將模型上傳到Hub。

{/if}

默認情況下, 使用的存儲庫將在您的命名空間中, 並以您設置的輸出目錄命名, 因此在我們的例子中, 它將在 `"sgugger/bert-finetuned-squad"` 中。我們可以通過傳遞 `hub_model_id` 來覆蓋它; 例如, 為了將模型推送到 `huggingface_course` 組織, 我們使用了 `hub_model_id="huggingface_course/bert-finetuned-squad"` (這是我們在本節開頭鏈接的模型)。

{#if fw === 'pt'}

<Tip>

💡 如果您使用的輸出目錄存在, 則它需要是您要推送到的存儲庫的本地克隆 (因此, 如果在定義 `Trainer` 時出錯, 請設置新名稱)。

</Tip>

最後, 我們只需將所有內容傳遞給 `Trainer` 類並啟動訓練:

```python
from transformers import Trainer

trainer = Trainer(
    model=model,
    args=args,
    train_dataset=train_dataset,
    eval_dataset=validation_dataset,
    tokenizer=tokenizer,
)
trainer.train()
```

{:else}

```python
from transformers.keras_callbacks import PushToHubCallback

callback = PushToHubCallback(output_dir="bert-finetuned-squad", tokenizer=tokenizer)

# We're going to do validation afterwards, so no validation mid-training
model.fit(tf_train_dataset, callbacks=[callback], epochs=num_train_epochs)
```

{/if}

請注意, 在進行訓練時, 每次保存模型時 (這裡是每個 epoch) 它都會在後臺上傳到 Hub。這樣, 如有必要, 你將能夠在另一臺機器上恢復訓練。整個培訓需要一段時間 (在 Titan RTX 上需要一個多小時), 因此您可以喝杯咖啡或重讀課程中您發現在進行過程中更具挑戰性的部分內容。另請注意, 一旦第一個 epoch 完成, 你將看到一些權重上傳到 Hub, 你可以開始在其頁面上使用你的模型。

{#if fw === 'pt'}

一旦訓練完成, 我們終於可以評估我們的模型(並祈禱我們沒有把所有的計算時間都花在任何事情上)。`Trainer` 的 `predict()` 方法將返回一個元組, 其中第一個元素將是模型的預測 (這裡是帶有開始和結束 logits 的對)。我們將其發送給 `compute_metrics()` 函數:

```python
predictions, _ = trainer.predict(validation_dataset)
start_logits, end_logits = predictions
compute_metrics(start_logits, end_logits, validation_dataset, raw_datasets["validation"])
```

{:else}

一旦訓練完成, 我們終於可以評估我們的模型(並祈禱我們沒有把所有的計算時間都花在任何事情上)。我們的 `model` 的 `predict()` 方法將負責獲取預測, 並且由於我們之前已經完成了定義 `compute_metrics()` 函數的所以艱苦工作, 因此我們可以在一行中獲得結果:

```python
predictions = model.predict(tf_eval_dataset)
compute_metrics(
    predictions["start_logits"],
    predictions["end_logits"],
    validation_dataset,
    raw_datasets["validation"],
)
```

{/if}

```python out
{'exact_match': 81.18259224219489, 'f1': 88.67381321905516}
```

很好! 作為比較, BERT 文章中報告的該模型的基線分數是 80.8 和 88.5, 所以我們應該是正確的。

{#if fw === 'pt'}

最後, 我們使用 `push_to_hub()` 方法確保我們上傳模型的最新版本:

```py
trainer.push_to_hub(commit_message="Training complete")
```

如果你想檢查它, 這將返回它剛剛執行的提交的 URL:

```python out
'https://huggingface.co/sgugger/bert-finetuned-squad/commit/9dcee1fbc25946a6ed4bb32efb1bd71d5fa90b68'
```

`Trainer` 還起草了包含所有評估結果的模型卡並上傳。

{/if}

在這個階段, 你可以使用模型中心上的推理小部件來測試模型並與您的朋友、家人和最喜歡的寵物分享。你已經成功地微調了一個問答任務的模型 -- 恭喜!

<Tip>

✏️ **輪到你了!** 嘗試另一種模型架構, 看看它是否在此任務上表現更好!

</Tip>

{#if fw === 'pt'}

如果你想更深入地瞭解訓練循環, 我們現在將向你展示如何使用 🤗 Accelerate 來做同樣的事情。

## 自定義訓練循環

現在讓我們來看一下完整的訓練循環, 這樣您就可以輕鬆地自定義所需的部分。它看起來很像 [第三章](/course/chapter3/4) 中的訓練循環, 除了評估循環。我們將能夠定期評估模型, 因為我們不再受 `Trainer` 類的限制。

### 為訓練做準備

首先, 我們需要從我們的數據集中構建 `DataLoader`。我們將這些數據集的格式設置為 `"torch"`, 並刪除模型未使用的驗證集中的列。然後, 我們可以使用 Transformers 提供的 `default_data_collator` 作為 `collate_fn`, 並打亂訓練集, 但不打亂驗證集58:

```py
from torch.utils.data import DataLoader
from transformers import default_data_collator

train_dataset.set_format("torch")
validation_set = validation_dataset.remove_columns(["example_id", "offset_mapping"])
validation_set.set_format("torch")

train_dataloader = DataLoader(
    train_dataset,
    shuffle=True,
    collate_fn=default_data_collator,
    batch_size=8,
)
eval_dataloader = DataLoader(
    validation_set, collate_fn=default_data_collator, batch_size=8
)
```

接下來我們重新實例化我們的模型, 以確保我們不會繼續之前的微調, 而是再次從 BERT 預訓練模型開始:

```py
model = AutoModelForQuestionAnswering.from_pretrained(model_checkpoint)
```

然後我們需要一個優化器。像往常一樣, 我們使用經典的 `AdamW`, 它與 Adam 類似, 但對權重衰減的應用方式進行了修復:

```py
from torch.optim import AdamW

optimizer = AdamW(model.parameters(), lr=2e-5)
```

一旦我們擁有所有這些對象, 我們可以將它們發送給 `accelerator.prepare()` 方法。請記住, 如果您想在 Colab 筆記本中的 TPU 上進行訓練, 您需要將所有這些代碼移動到一個訓練函數中, 並且不應該執行任何實例化 `Accelerator` 的單元。我們可以通過傳遞 `fp16=True` 給 `Accelerator` (或者, 如果你將代碼作為腳本執行, 只需確保適當地填寫 🤗 Accelerate `config` )。

```py
from accelerate import Accelerator

accelerator = Accelerator(fp16=True)
model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare(
    model, optimizer, train_dataloader, eval_dataloader
)
```

從前面幾節中你應該知道, 我們只能使用 `train_dataloader` 長度來計算經過 `accelerator.prepare()` 方法後的訓練步驟的數量。我們使用與前幾節相同的線性時間表:

```py
from transformers import get_scheduler

num_train_epochs = 3
num_update_steps_per_epoch = len(train_dataloader)
num_training_steps = num_train_epochs * num_update_steps_per_epoch

lr_scheduler = get_scheduler(
    "linear",
    optimizer=optimizer,
    num_warmup_steps=0,
    num_training_steps=num_training_steps,
)
```

要將我們的模型推送到 Hub, 我們需要在工作文件夾中創建一個 `Repository` 對象。如果你尚未登錄, 請先登錄 Hugging Face Hub。我們將根據我們想要為模型提供的模型 ID 確定存儲庫名稱 (隨意用你自己的選擇替換 `repo_name`; 它只需要包含你的用戶名, 這就是函數 `get_full_repo_name()` 所做的):

```py
from huggingface_hub import Repository, get_full_repo_name

model_name = "bert-finetuned-squad-accelerate"
repo_name = get_full_repo_name(model_name)
repo_name
```

```python out
'sgugger/bert-finetuned-squad-accelerate'
```

然後我們可以將該存儲庫克隆到本地文件夾中。如果它已經存在, 這個本地文件夾應該是我們正在使用的存儲庫的克隆:

```py
output_dir = "bert-finetuned-squad-accelerate"
repo = Repository(output_dir, clone_from=repo_name)
```

我們現在可以通過調用 `repo.push_to_hub()` 方法上傳我們保存在 `output_dir` 中的任何內容。這將幫助我們在每個 epoch 結束時上傳中間模型。

## 訓練循環

我們現在準備編寫完整的訓練循環。在定義了一個進度條來跟蹤訓練進行後, 循環分為三個部分:

- 訓練本身是對 `train_dataloader` 的經典迭代, 前向傳遞模型, 然後反向傳遞和優化器步驟。
- 在計算中, 我們在將 `start_logits` 和 `end_logits` 的所有值轉換為 NumPy 數組之前, 收集它們的所有值。評估循環完成後,我們將連接所有結果。請注意, 我們需要截斷, 因為 `Accelerator` 可能在最後添加了一些示例, 以確保我們在每個過程中擁有相同數量的示例。
- 保存和上傳, 這裡我們先保存模型和分詞器, 然後調用 `repo.push_to_hub()`。正如我們之前所做的那樣, 我們使用參數 `blocking=False` 來告訴 🤗 Hub 庫推入一個異步進程。這樣, 訓練正常繼續, 並且這個 (長) 指令在後臺執行。

這是訓練循環的完整代碼:

```py
from tqdm.auto import tqdm
import torch

progress_bar = tqdm(range(num_training_steps))

for epoch in range(num_train_epochs):
    # Training
    model.train()
    for step, batch in enumerate(train_dataloader):
        outputs = model(**batch)
        loss = outputs.loss
        accelerator.backward(loss)

        optimizer.step()
        lr_scheduler.step()
        optimizer.zero_grad()
        progress_bar.update(1)

    # Evaluation
    model.eval()
    start_logits = []
    end_logits = []
    accelerator.print("Evaluation!")
    for batch in tqdm(eval_dataloader):
        with torch.no_grad():
            outputs = model(**batch)

        start_logits.append(accelerator.gather(outputs.start_logits).cpu().numpy())
        end_logits.append(accelerator.gather(outputs.end_logits).cpu().numpy())

    start_logits = np.concatenate(start_logits)
    end_logits = np.concatenate(end_logits)
    start_logits = start_logits[: len(validation_dataset)]
    end_logits = end_logits[: len(validation_dataset)]

    metrics = compute_metrics(
        start_logits, end_logits, validation_dataset, raw_datasets["validation"]
    )
    print(f"epoch {epoch}:", metrics)

    # Save and upload
    accelerator.wait_for_everyone()
    unwrapped_model = accelerator.unwrap_model(model)
    unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save)
    if accelerator.is_main_process:
        tokenizer.save_pretrained(output_dir)
        repo.push_to_hub(
            commit_message=f"Training in progress epoch {epoch}", blocking=False
        )
```

如果這是您第一次看到使用 🤗 Accelerate 保存的模型, 讓我們花點時間檢查一下它附帶的三行代碼:

```py
accelerator.wait_for_everyone()
unwrapped_model = accelerator.unwrap_model(model)
unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save)
```

第一行是不言自明的: 它告訴所有進程要等到每個人都處於那個階段才能繼續。這是為了確保我們在保存之前在每個過程中都有相同的模型。然後我們獲取 `unwrapped_model`, 這是我們定義的基礎模型。 `accelerator.prepare()` 方法將模型更改為在分佈式訓練中工作, 因此它將不再有 `save_pretrained()` 方法; `accelerator.unwrap_model()` 方法會撤銷該步驟。最後, 我們調用 `save_pretrained()`, 但告訴該方法使用 `accelerator.save()` 而不是 `torch.save()`。

完成後, 你應該擁有一個模型, 該模型產生的結果與使用 `Trainer` 訓練的模型非常相似。你可以在 [*huggingface-course/bert-finetuned-squad-accelerate*](https://huggingface.co/huggingface-course/bert-finetuned-squad-accelerate) 上查看我們使用此代碼訓練的模型。如果你想測試對訓練循環的任何調整, 你可以通過編輯上面顯示的代碼直接實現它們!

{/if}

## 使用微調模型

我們已經向您展示瞭如何將我們在模型中心微調的模型與推理小部件一起使用。要在 `pipeline` 中本地使用它, 你只需要指定模型標識符:

```py
from transformers import pipeline

# Replace this with your own checkpoint
model_checkpoint = "huggingface-course/bert-finetuned-squad"
question_answerer = pipeline("question-answering", model=model_checkpoint)

context = """
🤗 Transformers is backed by the three most popular deep learning libraries — Jax, PyTorch and TensorFlow — with a seamless integration
between them. It's straightforward to train your models with one before loading them for inference with the other.
"""
question = "Which deep learning libraries back 🤗 Transformers?"
question_answerer(question=question, context=context)
```

```python out
{'score': 0.9979003071784973,
 'start': 78,
 'end': 105,
 'answer': 'Jax, PyTorch and TensorFlow'}
```

很棒! 我們的模型與該管道的默認模型一樣有效!


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter7/7.mdx" />

### Token 分類
https://huggingface.co/learn/course/zh-TW/chapter7/2.md

# Token 分類

{#if fw === 'pt'}

<DocNotebookDropdown
  classNames="absolute z-10 right-0 top-0"
  options={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/chapter7/section2_pt.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/chapter7/section2_pt.ipynb"},
]} />

{:else}

<DocNotebookDropdown
  classNames="absolute z-10 right-0 top-0"
  options={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/chapter7/section2_tf.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/chapter7/section2_tf.ipynb"},
]} />

{/if}

我們將探索的第一個應用是Token分類。這個通用任務包括任何可以表述為“為句子中的詞或字分配標籤”的問題，例如：

- **實體命名識別 (NER)**: 找出句子中的實體（如人物、地點或組織）。這可以通過為每個實體或“無實體”指定一個類別的標籤。
- **詞性標註 (POS)**: 將句子中的每個單詞標記為對應於特定的詞性（如名詞、動詞、形容詞等）。
- **分塊（chunking）**: 找到屬於同一實體的Token。這個任務(可結合POS或NER)可以任何將一塊Token作為制定一個標籤(通常是B -),另一個標籤(通常I -)表示Token是否是同一塊,和第三個標籤(通常是O)表示Token不屬於任何塊。也就是標出句子中的短語塊，例如名詞短語（NP），動詞短語（VP）等。

<Youtube id="wVHdVlPScxA"/>

當然，還有很多其他類型的token分類問題；這些只是幾個有代表性的例子。在本節中，我們將在 NER 任務上微調模型 (BERT)，然後該模型將能夠計算如下預測：

<iframe src="https://course-demos-bert-finetuned-ner.hf.space" frameBorder="0" height="350" title="Gradio app" class="block dark:hidden container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>
<iframe src="https://course-demos-bert-finetuned-ner-darkmode.hf.space" frameBorder="0" height="350" title="Gradio app" class="hidden dark:block container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

<a class="flex justify-center" href="/huggingface-course/bert-finetuned-ner">
<img class="block dark:hidden lg:w-3/5" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter7/model-eval-bert-finetuned-ner.png" alt="One-hot encoded labels for question answering."/>
<img class="hidden dark:block lg:w-3/5" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter7/model-eval-bert-finetuned-ner-dark.png" alt="One-hot encoded labels for question answering."/>
</a>

您可以[在這裡](https://huggingface.co/huggingface-course/bert-finetuned-ner?text=My+name+is+Sylvain+and+I+work+at+Hugging+Face+in+Brooklyn).找到我們將訓練並上傳到 Hub的模型，可以嘗試輸入一些句子看看模型的預測結果。

## 準備數據

首先，我們需要一個適合標記分類的數據集。在本節中，我們將使用[CoNLL-2003 數據集](https://huggingface.co/datasets/conll2003), 其中包含來自路透社的新聞報道。

<Tip>

💡 只要您的數據集由帶有相應標籤的分割成單詞並的文本組成，您就能夠將這裡描述的數據處理過程應用到您自己的數據集。如果需要複習如何在.Dataset中加載自定義數據，請參閱[Chapter 5](/course/chapter5)。

</Tip>

### CoNLL-2003 數據集

要加載 CoNLL-2003 數據集，我們使用 來自 🤗 Datasets 庫的**load_dataset()** 方法：

```py
from datasets import load_dataset

raw_datasets = load_dataset("conll2003")
```

這將下載並緩存數據集，就像和我們在[第三章](/course/chapter3) 加載GLUE MRPC 數據集一樣。檢查這個對象可以讓我們看到存在哪些列，以及訓練集、驗證集和測試集之間是如何分割的:

```py
raw_datasets
```

```python out
DatasetDict({
    train: Dataset({
        features: ['chunk_tags', 'id', 'ner_tags', 'pos_tags', 'tokens'],
        num_rows: 14041
    })
    validation: Dataset({
        features: ['chunk_tags', 'id', 'ner_tags', 'pos_tags', 'tokens'],
        num_rows: 3250
    })
    test: Dataset({
        features: ['chunk_tags', 'id', 'ner_tags', 'pos_tags', 'tokens'],
        num_rows: 3453
    })
})
```

特別是，我們可以看到數據集包含我們之前提到的三個任務的標籤：NER、POS 和chunking。與其他數據集的一個很大區別是輸入文本不是作為句子或文檔呈現的，而是單詞列表（最後一列稱為 **tokens** ，但它包含的是這些詞是預先標記化的輸入，仍然需要通過標記器進行子詞標記）。

我們來看看訓練集的第一個元素：

```py
raw_datasets["train"][0]["tokens"]
```

```python out
['EU', 'rejects', 'German', 'call', 'to', 'boycott', 'British', 'lamb', '.']
```

由於我們要執行命名實體識別，我們將查看 NER 標籤：

```py
raw_datasets["train"][0]["ner_tags"]
```

```python out
[3, 0, 7, 0, 0, 0, 7, 0, 0]
```

這一列是類標籤的序列。元素的類型在ner_feature的feature屬性中，我們可以通過查看該特性的names屬性來訪問名稱列表:   

```py
ner_feature = raw_datasets["train"].features["ner_tags"]
ner_feature
```

```python out
Sequence(feature=ClassLabel(num_classes=9, names=['O', 'B-PER', 'I-PER', 'B-ORG', 'I-ORG', 'B-LOC', 'I-LOC', 'B-MISC', 'I-MISC'], names_file=None, id=None), length=-1, id=None)
```

因此，這一列包含的元素是ClassLabels的序列。序列元素的類型在`ner_feature`的`feature`中，我們可以通過查看該`feature`的`names`屬性來訪問名稱列表:

```py
label_names = ner_feature.feature.names
label_names
```

```python out
['O', 'B-PER', 'I-PER', 'B-ORG', 'I-ORG', 'B-LOC', 'I-LOC', 'B-MISC', 'I-MISC']
```

我們在[第六章](/course/chapter6/3), 深入研究**token-classification** 管道時已經看到了這些標籤 ，但為了快速複習：

- `O` 表示這個詞不對應任何實體。
- `B-PER`/`I-PER`意味著這個詞對應於人名實體的開頭/內部。
- `B-ORG`/`I-ORG` 的意思是這個詞對應於組織名稱實體的開頭/內部。
- `B-LOC`/`I-LOC` 指的是是這個詞對應於地名實體的開頭/內部。
- `B-MISC`/`I-MISC` 表示該詞對應於一個雜項實體的開頭/內部。

現在解碼我們之前看到的標籤：

```python
words = raw_datasets["train"][0]["tokens"]
labels = raw_datasets["train"][0]["ner_tags"]
line1 = ""
line2 = ""
for word, label in zip(words, labels):
    full_label = label_names[label]
    max_length = max(len(word), len(full_label))
    line1 += word + " " * (max_length - len(word) + 1)
    line2 += full_label + " " * (max_length - len(full_label) + 1)

print(line1)
print(line2)
```

```python out
'EU    rejects German call to boycott British lamb .'
'B-ORG O       B-MISC O    O  O       B-MISC  O    O'
```

例如混合 **B-** 和 **I-** 標籤，這是相同的代碼在索引 4 的訓練集元素上的預測結果：

```python out
'Germany \'s representative to the European Union \'s veterinary committee Werner Zwingmann said on Wednesday consumers should buy sheepmeat from countries other than Britain until the scientific advice was clearer .'
'B-LOC   O  O              O  O   B-ORG    I-ORG O  O          O         B-PER  I-PER     O    O  O         O         O      O   O         O    O         O     O    B-LOC   O     O   O          O      O   O       O'
```

正如我們所看到的，跨越兩個單詞的實體，如“European Union”和“Werner Zwingmann”，模型為第一個單詞標註了一個B-標籤，為第二個單詞標註了一個I-標籤。

<Tip>

✏️ **輪到你了！** 使用 POS 或chunking標籤識別同一個句子。

</Tip>

### 處理數據

<Youtube id="iY2AZYdZAr0"/>

像往常一樣，我們的文本需要轉換為Token ID，然後模型才能理解它們。正如我們在[第六章](/course/chapter6/)所學的那樣。不過在標記任務中，一個很大的區別是我們有pre-tokenized的輸入。幸運的是，tokenizer API可以很容易地處理這個問題;我們只需要用一個特殊的tokenizer。

首先，讓我們創建`tokenizer`對象。如前所述，我們將使用 BERT 預訓練模型，因此我們將從下載並緩存關聯的分詞器開始：

```python
from transformers import AutoTokenizer

model_checkpoint = "bert-base-cased"
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)
```

你可以更換把 `model_checkpoint` 更換為 [Hub](https://huggingface.co/models),上您喜歡的任何其他型號，或使用您本地保存的預訓練模型和分詞器。唯一的限制是分詞器需要由 🤗 Tokenizers 庫支持，有一個“快速”版本可用。你可以在[這張大表](https://huggingface.co/transformers/#supported-frameworks), 上看到所有帶有快速版本的架構，或者檢查  您可以通過查看它`is_fast` 屬性來檢測正在使用的`tokenizer`對象是否由 🤗 Tokenizers 支持：

```py
tokenizer.is_fast
```

```python out
True
```

要對預先標記的輸入進行標記，我們可以像往常一樣使用我們的`tokenizer` 只需添加 `is_split_into_words=True`:

```py
inputs = tokenizer(raw_datasets["train"][0]["tokens"], is_split_into_words=True)
inputs.tokens()
```

```python out
['[CLS]', 'EU', 'rejects', 'German', 'call', 'to', 'boycott', 'British', 'la', '##mb', '.', '[SEP]']
```

正如我們所見，分詞器添加了模型使用的特殊Token(`[CLS]` 在開始和`[SEP]` 最後) 而大多數單詞未被修改。然而，單詞 `lamb`,被分為兩個子單詞 `la` and `##mb`。這導致了輸入和標籤之間的不匹配:標籤列表只有9個元素，而我們的輸入現在有12個token 。計算特殊Token很容易(我們知道它們在開頭和結尾)，但我們還需要確保所有標籤與適當的單詞對齊。
幸運的是，由於我們使用的是快速分詞器，因此我們可以訪問🤗 Tokenizers超能力，這意味著我們可以輕鬆地將每個令牌映射到其相應的單詞（如[Chapter 6](/course/chapter6/3)):

```py
inputs.word_ids()
```

```python out
[None, 0, 1, 2, 3, 4, 5, 6, 7, 7, 8, None]
```

通過一點點工作，我們可以擴展我們的標籤列表以匹配token 。我們將應用的第一條規則是，特殊token 的標籤為 `-100` 。這是因為默認情況下 `-100` 是一個在我們將使用的損失函數（交叉熵）中被忽略的索引。然後，每個token 都會獲得與其所在單詞的token 相同的標籤，因為它們是同一實體的一部分。對於單詞內部但不在開頭的Token，我們將`B-` 替換為 `I-` (因為token 不以實體開頭):

```python
def align_labels_with_tokens(labels, word_ids):
    new_labels = []
    current_word = None
    for word_id in word_ids:
        if word_id != current_word:
            # Start of a new word!
            current_word = word_id
            label = -100 if word_id is None else labels[word_id]
            new_labels.append(label)
        elif word_id is None:
            # Special token
            new_labels.append(-100)
        else:
            # Same word as previous token
            label = labels[word_id]
            # If the label is B-XXX we change it to I-XXX
            if label % 2 == 1:
                label += 1
            new_labels.append(label)

    return new_labels
```

讓我們在我們的第一句話上試一試：

```py
labels = raw_datasets["train"][0]["ner_tags"]
word_ids = inputs.word_ids()
print(labels)
print(align_labels_with_tokens(labels, word_ids))
```

```python out
[3, 0, 7, 0, 0, 0, 7, 0, 0]
[-100, 3, 0, 7, 0, 0, 0, 7, 0, 0, 0, -100]
```

正如我們所看到的，我們的函數為開頭和結尾的兩個特殊標記添加了  `-100` ，併為分成兩個標記的單詞添加了一個新的`0` 。

<Tip>

✏️ **輪到你了！** 一些研究人員更喜歡每個詞只歸屬一個標籤, 並分配 `-100` 給定詞中的其他子標記。這是為了避免分解成大量子標記的長詞對損失造成嚴重影響。按照此規則更改前一個函數使標籤與輸入id對齊。

</Tip>

為了預處理我們的整個數據集，我們需要標記所有輸入並在所有標籤上應用 `align_labels_with_tokens()` 。為了利用我們的快速分詞器的速度優勢，最好同時對大量文本進行分詞，因此我們將編寫一個處理示例列表的函數並使用帶 `batched=True` 有選項的 `Dataset.map()`方法 .與我們之前的示例唯一不同的是當分詞器的輸入是文本列表（或者像例子中的單詞列表）時  `word_ids()` 函數需要獲取我們想要單詞的索引的ID，所以我們也添加它：

```py
def tokenize_and_align_labels(examples):
    tokenized_inputs = tokenizer(
        examples["tokens"], truncation=True, is_split_into_words=True
    )
    all_labels = examples["ner_tags"]
    new_labels = []
    for i, labels in enumerate(all_labels):
        word_ids = tokenized_inputs.word_ids(i)
        new_labels.append(align_labels_with_tokens(labels, word_ids))

    tokenized_inputs["labels"] = new_labels
    return tokenized_inputs
```

請注意，我們還沒有填充我們的輸入；我們稍後會在使用數據整理器創建batch時這樣做。

我們現在可以一次性將所有預處理應用於數據集的其他部分：

```py
tokenized_datasets = raw_datasets.map(
    tokenize_and_align_labels,
    batched=True,
    remove_columns=raw_datasets["train"].column_names,
)
```

我們已經完成了最難的部分！現在數據已經被預處理了，實際的訓練看起來很像我們[第三章](/course/chapter3)做的.

{#if fw === 'pt'}

## 使用 Trainer API 微調模型

使用 `Trainer` 的實際代碼會和以前一樣；唯一的變化是數據整理成時批處理的方式和度量計算函數。

{:else}

## 使用 Keras 微調模型

使用Keras的實際代碼將與之前非常相似;唯一的變化是將數據整理成批處理的方式和指標計算函數。

{/if}


### 數據排序

我們不能像[第三章](/course/chapter3)那樣只使用一個 `DataCollatorWithPadding `因為這隻會填充輸入（輸入 ID、注意掩碼和標記類型 ID）。在這裡我們的標籤應該以與輸入完全相同的方式填充，以便它們保持長度相同，使用  `-100 ` ，這樣在損失計算中就可以忽略相應的預測。

這一切都是由一個 [`DataCollatorForTokenClassification`](https://huggingface.co/transformers/main_classes/data_collator.html#datacollatorfortokenclassification)完成.它是一個帶有填充的數據整理器它需要  `tokenizer ` 用於預處理輸入：

{#if fw === 'pt'}

```py
from transformers import DataCollatorForTokenClassification

data_collator = DataCollatorForTokenClassification(tokenizer=tokenizer)
```

{:else}

```py
from transformers import DataCollatorForTokenClassification

data_collator = DataCollatorForTokenClassification(
    tokenizer=tokenizer, return_tensors="tf"
)
```

{/if}

為了在幾個樣本上測試這一點，我們可以在訓練集中的示例列表上調用它：

```py
batch = data_collator([tokenized_datasets["train"][i] for i in range(2)])
batch["labels"]
```

```python out
tensor([[-100,    3,    0,    7,    0,    0,    0,    7,    0,    0,    0, -100],
        [-100,    1,    2, -100, -100, -100, -100, -100, -100, -100, -100, -100]])
```

讓我們將其與數據集中第一個和第二個元素的標籤進行比較：

```py
for i in range(2):
    print(tokenized_datasets["train"][i]["labels"])
```

```python out
[-100, 3, 0, 7, 0, 0, 0, 7, 0, 0, 0, -100]
[-100, 1, 2, -100]
```

{#if fw === 'pt'}

正如我們所看到的，第二組標籤的長度已經使用 `-100` 填充到與第一組標籤相同。

{:else}

我們的數據整理器已準備就緒！現在，讓我們用它來製作一個帶有`to_tf_dataset()`方法的`tf.data.Dataset`。

```py
tf_train_dataset = tokenized_datasets["train"].to_tf_dataset(
    columns=["attention_mask", "input_ids", "labels", "token_type_ids"],
    collate_fn=data_collator,
    shuffle=True,
    batch_size=16,
)

tf_eval_dataset = tokenized_datasets["validation"].to_tf_dataset(
    columns=["attention_mask", "input_ids", "labels", "token_type_ids"],
    collate_fn=data_collator,
    shuffle=False,
    batch_size=16,
)
```


 Next stop: the model itself.

{/if}

{#if fw === 'tf'}

### 定義模型

由於我們正在研究Token分類問題，因此我們將使用 `AutoModelForTokenClassification` 類。定義這個模型時要記住的主要事情是傳遞一些關於我們的標籤數量的信息。執行此操作的最簡單方法是將該數字傳遞給 `num_labels` 參數，但是如果我們想要一個很好的推理小部件，就像我們在本節開頭看到的那樣，最好設置正確的標籤對應關係。

它們應該由兩個字典設置， `id2label` 和 `label2id` ，其中包含從 ID 到標籤的映射，反之亦然：

```py
id2label = {i: label for i, label in enumerate(label_names)}
label2id = {v: k for k, v in id2label.items()}
```

現在我們可以將它們傳遞給 `AutoModelForTokenClassification.from_pretrained()` 方法，它們將在模型的配置中設置，然後保存並上傳到Hub：

```py
from transformers import TFAutoModelForTokenClassification

model = TFAutoModelForTokenClassification.from_pretrained(
    model_checkpoint,
    id2label=id2label,
    label2id=label2id,
)
```

就像我們在[第三章](/course/chapter3),定義我們的 `AutoModelForSequenceClassification` ，創建模型會發出警告，提示一些權重未被使用（來自預訓練頭的權重）和一些其他權重被隨機初始化（來自新Token分類頭的權重），我們將要訓練這個模型。我們將在一分鐘內完成，但首先讓我們仔細檢查我們的模型是否具有正確數量的標籤：

```python
model.config.num_labels
```

```python out
9
```

<Tip warning={true}>

⚠️ 如果您的模型標籤數量錯誤，則在稍後調用 `model.fit()` 時將收到一個模糊的錯誤。調試起來可能很煩人，因此請確保執行此檢查以確認您具有預期的標籤數。

</Tip>

### 微調模型

現在，我們已準備好訓練模型了！不過，我們首先要做兩件事：應該登錄到Hugging Face並定義我們的訓練超參數。如果你在notebook上工作，有一個便利功能可以幫助你做到這一點：

```python
from huggingface_hub import notebook_login

notebook_login()
```

這將顯示一個小部件，您可以在其中輸入您的 Hugging Face 賬號和密碼。

如果您不是在notebook上工作，只需在終端中輸入以下行：

```bash
huggingface-cli login
```

登錄後，我們可以準備編譯模型所需的一切。🤗 Transformers提供了一個方便的`create_optimizer()` 函數，該函數將為您提供一個`AdamW`優化器，其中包含適當的權重衰減和學習速率衰減設置，與內置的`Adam`優化器相似，這兩者都將提高模型的性能：

```python
from transformers import create_optimizer
import tensorflow as tf

# Train in mixed-precision float16
# Comment this line out if you're using a GPU that will not benefit from this
tf.keras.mixed_precision.set_global_policy("mixed_float16")

# The number of training steps is the number of samples in the dataset, divided by the batch size then multiplied
# by the total number of epochs. Note that the tf_train_dataset here is a batched tf.data.Dataset,
# not the original Hugging Face Dataset, so its len() is already num_samples // batch_size.
num_epochs = 3
num_train_steps = len(tf_train_dataset) * num_epochs

optimizer, schedule = create_optimizer(
    init_lr=2e-5,
    num_warmup_steps=0,
    num_train_steps=num_train_steps,
    weight_decay_rate=0.01,
)
model.compile(optimizer=optimizer)
```

還要注意，我們不為`compile()`提供`loss`參數。這是因為模型實際上可以在內部計算損失 - 如果您編譯時沒有損失並在輸入字典中提供標籤（就像我們在數據集中所做的那樣），那麼模型將使用該內部損失進行訓練，這將適用於您選擇的任務和模型類型。

接下來，我們定義一個`PushToHubCallback`，以便在訓練期間將模型上傳到 Hub，並使用該回調來擬合模型：

```python
from transformers.keras_callbacks import PushToHubCallback

callback = PushToHubCallback(output_dir="bert-finetuned-ner", tokenizer=tokenizer)

model.fit(
    tf_train_dataset,
    validation_data=tf_eval_dataset,
    callbacks=[callback],
    epochs=num_epochs,
)
```

您之前已經看過其中的大部分內容：我們設置了一些超參數（例如學習率、要訓練的 epoch 數和權重衰減），然後我們指定 `push_to_hub=True` 表明我們想要保存模型並在每個時期結束時對其進行評估，並且我們想要將我們的結果上傳到模型中心。請注意，可以使用hub_model_id參數指定要推送到的存儲庫的名稱(特別是，必須使用這個參數來推送到一個組織)。例如，當我們將模型推送到[`huggingface-course` organization](https://huggingface.co/huggingface-course), 我們添加了 `hub_model_id=huggingface-course/bert-finetuned-ner` 到 `TrainingArguments` .默認情況下，使用的存儲庫將在您的命名空間中並以您設置的輸出目錄命名，因此在我們的例子中它將是 `sgugger/bert-finetuned-ner` .

<Tip>

💡 如果您正在使用的輸出目錄已經存在，那麼輸出目錄必須是從同一個存儲庫clone下來的。如果不是，您將在聲明 `model.fit()` 時遇到錯誤，並且需要設置一個新名稱。

</Tip>

請注意，當訓練發生時，每次保存模型時（這裡是每個epooch），它都會在後臺上傳到 Hub。這樣，如有必要，您將能夠在另一臺機器上繼續您的訓練。

在此階段，您可以使用模型中心上的推理小組件來測試模型並與朋友共享。您已經成功微調了令牌分類任務的模型 - 恭喜！但是，我們的模型到底有多好呢？我們應該評估一些指標來找出答案。

{/if}


### 評估指標

{#if fw === 'pt'}

為了讓 `Trainer` 在每個epoch計算一個度量，我們需要定義一個 `compute_metrics()` 函數，該函數接受預測和標籤數組，並返回一個包含度量名稱和值的字典

用於評估Token分類預測的傳統框架是 [*seqeval*](https://github.com/chakki-works/seqeval). 要使用此指標，我們首先需要安裝seqeval庫：

```py
!pip install seqeval
```

然後我們可以通過加載它 `load_metric()` 函數就像我們在[第三章](/course/chapter3)做的那樣：

{:else}

用於評估Token分類預測的傳統框架是 [*seqeval*](https://github.com/chakki-works/seqeval). 要使用此指標，我們首先需要安裝seqeval庫：

```py
!pip install seqeval
```

然後我們可以通過加載它 `load_metric()` 函數就像我們在[第三章](/course/chapter3)做的那樣：

{/if}

```py
from datasets import load_metric

metric = load_metric("seqeval")
```

這個評估方式與標準精度不同:它實際上將標籤列表作為字符串，而不是整數，因此在將預測和標籤傳遞給它之前，我們需要完全解碼它們。讓我們看看它是如何工作的。首先，我們將獲得第一個訓練示例的標籤:

```py
labels = raw_datasets["train"][0]["ner_tags"]
labels = [label_names[i] for i in labels]
labels
```

```python out
['B-ORG', 'O', 'B-MISC', 'O', 'O', 'O', 'B-MISC', 'O', 'O']
```

然後我們可以通過更改索引 2 處的值來為那些創建假的預測：

```py
predictions = labels.copy()
predictions[2] = "O"
metric.compute(predictions=[predictions], references=[labels])
```

請注意，該指標的輸入是預測列表（不僅僅是一個）和標籤列表。這是輸出：

```python out
{'MISC': {'precision': 1.0, 'recall': 0.5, 'f1': 0.67, 'number': 2},
 'ORG': {'precision': 1.0, 'recall': 1.0, 'f1': 1.0, 'number': 1},
 'overall_precision': 1.0,
 'overall_recall': 0.67,
 'overall_f1': 0.8,
 'overall_accuracy': 0.89}
```

{#if fw === 'pt'}

它返回很多信息！我們獲得每個單獨實體以及整體的準確率、召回率和 F1 分數。對於我們的度量計算，我們將只保留總分，但可以隨意調整 `compute_metrics()` 函數返回您想要查看的所有指標。

這`compute_metrics()` 函數首先採用 logits 的 argmax 將它們轉換為預測（像往常一樣，logits 和概率的順序相同，因此我們不需要應用 softmax）。然後我們必須將標籤和預測從整數轉換為字符串。我們刪除標籤為 `-100` 所有值 ，然後將結果傳遞給 `metric.compute()` 方法：

```py
import numpy as np


def compute_metrics(eval_preds):
    logits, labels = eval_preds
    predictions = np.argmax(logits, axis=-1)

    # Remove ignored index (special tokens) and convert to labels
    true_labels = [[label_names[l] for l in label if l != -100] for label in labels]
    true_predictions = [
        [label_names[p] for (p, l) in zip(prediction, label) if l != -100]
        for prediction, label in zip(predictions, labels)
    ]
    all_metrics = metric.compute(predictions=true_predictions, references=true_labels)
    return {
        "precision": all_metrics["overall_precision"],
        "recall": all_metrics["overall_recall"],
        "f1": all_metrics["overall_f1"],
        "accuracy": all_metrics["overall_accuracy"],
    }
```

現在已經完成了，我們幾乎準備好定義我們的 `Trainer` .我們只需要一個 `model` 微調！

{:else}

它返回很多信息！我們獲得每個單獨實體以及整體的準確率、召回率和 F1 分數。對於我們的度量計算，我們將只保留總分，但可以隨意調整 `compute_metrics()` 函數返回您想要查看的所有指標。

這`compute_metrics()` 函數首先採用 logits 的 argmax 將它們轉換為預測（像往常一樣，logits 和概率的順序相同，因此我們不需要應用 softmax）。然後我們必須將標籤和預測從整數轉換為字符串。我們刪除標籤為 `-100` 所有值 ，然後將結果傳遞給 `metric.compute()` 方法：

```py
import numpy as np

all_predictions = []
all_labels = []
for batch in tf_eval_dataset:
    logits = model.predict(batch)["logits"]
    labels = batch["labels"]
    predictions = np.argmax(logits, axis=-1)
    for prediction, label in zip(predictions, labels):
        for predicted_idx, label_idx in zip(prediction, label):
            if label_idx == -100:
                continue
            all_predictions.append(label_names[predicted_idx])
            all_labels.append(label_names[label_idx])
metric.compute(predictions=[all_predictions], references=[all_labels])
```


```python out
{'LOC': {'precision': 0.91, 'recall': 0.92, 'f1': 0.91, 'number': 1668},
 'MISC': {'precision': 0.70, 'recall': 0.79, 'f1': 0.74, 'number': 702},
 'ORG': {'precision': 0.85, 'recall': 0.90, 'f1': 0.88, 'number': 1661},
 'PER': {'precision': 0.95, 'recall': 0.95, 'f1': 0.95, 'number': 1617},
 'overall_precision': 0.87,
 'overall_recall': 0.91,
 'overall_f1': 0.89,
 'overall_accuracy': 0.97}
```

與我們的模型相比，您的模型做得如何？如果您獲得類似的數字，那麼您的訓練就是成功的！

{/if}

{#if fw === 'pt'}

### 定義模型

由於我們正在研究Token分類問題，因此我們將使用 `AutoModelForTokenClassification` 類。定義這個模型時要記住的主要事情是傳遞一些關於我們的標籤數量的信息。執行此操作的最簡單方法是將該數字傳遞給 `num_labels` 參數，但是如果我們想要一個很好的推理小部件，就像我們在本節開頭看到的那樣，最好設置正確的標籤對應關係。

它們應該由兩個字典設置， `id2label` 和 `label2id` ，其中包含從 ID 到標籤的映射，反之亦然：

```py
id2label = {i: label for i, label in enumerate(label_names)}
label2id = {v: k for k, v in id2label.items()}
```

現在我們可以將它們傳遞給 `AutoModelForTokenClassification.from_pretrained()` 方法，它們將在模型的配置中設置，然後保存並上傳到Hub：

```py
from transformers import AutoModelForTokenClassification

model = AutoModelForTokenClassification.from_pretrained(
    model_checkpoint,
    id2label=id2label,
    label2id=label2id,
)
```

就像我們在[第三章](/course/chapter3),定義我們的 `AutoModelForSequenceClassification` ，創建模型會發出警告，提示一些權重未被使用（來自預訓練頭的權重）和一些其他權重被隨機初始化（來自新Token分類頭的權重），我們將要訓練這個模型。我們將在一分鐘內完成，但首先讓我們仔細檢查我們的模型是否具有正確數量的標籤：

```python
model.config.num_labels
```

```python out
9
```

<Tip warning={true}>

⚠️ 如果模型的標籤數量錯誤，稍後調用Trainer.train()方法時會出現一個模糊的錯誤（類似於“CUDA error: device-side assert triggered”）。這是用戶報告此類錯誤的第一個原因，因此請確保進行這樣的檢查以確認您擁有預期數量的標籤。

</Tip>

### 微調模型

我們現在準備好訓練我們的模型了！在定義我們的 `Trainer`之前，我們只需要做最後兩件事：登錄 Hugging Face 並定義我們的訓練參數。如果您在notebook上工作，有一個方便的功能可以幫助您：

```python
from huggingface_hub import notebook_login

notebook_login()
```
這將顯示一個小部件，您可以在其中輸入您的 Hugging Face 賬號和密碼。如果您不是在notebook上工作，只需在終端中輸入以下行：

```bash
huggingface-cli login
```

Once this is done, we can define our `TrainingArguments`:

```python
from transformers import TrainingArguments

args = TrainingArguments(
    "bert-finetuned-ner",
    evaluation_strategy="epoch",
    save_strategy="epoch",
    learning_rate=2e-5,
    num_train_epochs=3,
    weight_decay=0.01,
    push_to_hub=True,
)
```

您之前已經看過其中的大部分內容：我們設置了一些超參數（例如學習率、要訓練的 epoch 數和權重衰減），然後我們指定 `push_to_hub=True` 表明我們想要保存模型並在每個時期結束時對其進行評估，並且我們想要將我們的結果上傳到模型中心。請注意，可以使用hub_model_id參數指定要推送到的存儲庫的名稱(特別是，必須使用這個參數來推送到一個組織)。例如，當我們將模型推送到[`huggingface-course` organization](https://huggingface.co/huggingface-course), 我們添加了 `hub_model_id=huggingface-course/bert-finetuned-ner` 到 `TrainingArguments` 。默認情況下，使用的存儲庫將在您的命名空間中並以您設置的輸出目錄命名，因此在我們的例子中它將是 `sgugger/bert-finetuned-ner`。

<Tip>

💡 如果您正在使用的輸出目錄已經存在，那麼輸出目錄必須是從同一個存儲庫clone下來的。如果不是，您將在聲明 `Trainer` 時遇到錯誤，並且需要設置一個新名稱。

</Tip>

最後，我們只是將所有內容傳遞給 `Trainer` 並啟動訓練：

```python
from transformers import Trainer

trainer = Trainer(
    model=model,
    args=args,
    train_dataset=tokenized_datasets["train"],
    eval_dataset=tokenized_datasets["validation"],
    data_collator=data_collator,
    compute_metrics=compute_metrics,
    tokenizer=tokenizer,
)
trainer.train()
```

請注意，當訓練發生時，每次保存模型時（這裡是每個epooch），它都會在後臺上傳到 Hub。這樣，如有必要，您將能夠在另一臺機器上繼續您的訓練。

訓練完成後，我們使用 `push_to_hub()` 確保我們上傳模型的最新版本

```py
trainer.push_to_hub(commit_message="Training complete")
```

This command returns the URL of the commit it just did, if you want to inspect it:

```python out
'https://huggingface.co/sgugger/bert-finetuned-ner/commit/26ab21e5b1568f9afeccdaed2d8715f571d786ed'
```

這 `Trainer` 還創建了一張包含所有評估結果的模型卡並上傳。在此階段，您可以使用模型中心上的推理小部件來測試您的模型並與您的朋友分享。您已成功在Token分類任務上微調模型 - 恭喜！

如果您想更深入地瞭解訓練循環，我們現在將向您展示如何使用 🤗 Accelerate 做同樣的事情。

## 自定義訓練循環

現在讓我們看一下完整的訓練循環，這樣您可以輕鬆定義所需的部分。它看起來很像我們在[第三章](/course/chapter3/4), 所做的，對評估進行了一些更改。

### 做好訓練前的準備
首先我們需要為我們的數據集構建 `DataLoader` 。我們將重用我們的 `data_collator` 作為一個 `collate_fn` 並打亂訓練集，但不打亂驗證集：

```py
from torch.utils.data import DataLoader

train_dataloader = DataLoader(
    tokenized_datasets["train"],
    shuffle=True,
    collate_fn=data_collator,
    batch_size=8,
)
eval_dataloader = DataLoader(
    tokenized_datasets["validation"], collate_fn=data_collator, batch_size=8
)
```

接下來，我們重新實例化我們的模型，以確保我們不會從之前的訓練繼續訓練，而是再次從 BERT 預訓練模型開始：

```py
model = AutoModelForTokenClassification.from_pretrained(
    model_checkpoint,
    id2label=id2label,
    label2id=label2id,
)
```

然後我們將需要一個優化器。我們將使用經典 `AdamW` ，這就像 `Adam` ，但在應用權重衰減的方式上進行了改進：
```py
from torch.optim import AdamW

optimizer = AdamW(model.parameters(), lr=2e-5)
```

Once we have all those objects, we can send them to the `accelerator.prepare()` method:

```py
from accelerate import Accelerator

accelerator = Accelerator()
model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare(
    model, optimizer, train_dataloader, eval_dataloader
)
```

<Tip>

🚨 如果您在 TPU 上進行訓練，則需要將以上單元格中的所有代碼移動到專用的訓練函數中。有關詳細信息，請參閱 [第3章](/course/chapter3)。

</Tip>

現在我們已經發送了我們的 `train_dataloader` 到 `accelerator.prepare()` ，我們可以使用它的長度來計算訓練步驟的數量。請記住，我們應該始終在準備好dataloader後執行此操作，因為該方法會改變其長度。我們使用經典線性學習率調度：

```py
from transformers import get_scheduler

num_train_epochs = 3
num_update_steps_per_epoch = len(train_dataloader)
num_training_steps = num_train_epochs * num_update_steps_per_epoch

lr_scheduler = get_scheduler(
    "linear",
    optimizer=optimizer,
    num_warmup_steps=0,
    num_training_steps=num_training_steps,
)
```

最後，要將我們的模型推送到 Hub，我們需要創建一個 `Repository` 工作文件夾中的對象。如果您尚未登錄，請先登錄 Hugging Face。我們將從我們想要為模型提供的模型 ID 中確定存儲庫名稱（您可以自由地用自己的選擇替換 `repo_name` ；它只需要包含您的用戶名，可以使用`get_full_repo_name()`函數的查看目前的repo_name）：

```py
from huggingface_hub import Repository, get_full_repo_name

model_name = "bert-finetuned-ner-accelerate"
repo_name = get_full_repo_name(model_name)
repo_name
```

```python out
'sgugger/bert-finetuned-ner-accelerate'
```

Then we can clone that repository in a local folder. If it already exists, this local folder should be an existing clone of the repository we are working with:

```py
output_dir = "bert-finetuned-ner-accelerate"
repo = Repository(output_dir, clone_from=repo_name)
```

We can now upload anything we save in `output_dir` by calling the `repo.push_to_hub()` method. This will help us upload the intermediate models at the end of each epoch.

### Training loop

### 訓練循環
我們現在準備編寫完整的訓練循環。為了簡化它的評估部分，我們定義了這個 `postprocess()` 接受預測和標籤並將它們轉換為字符串列表的函數，也就是 `metric`對象需要的輸入格式：

```py
def postprocess(predictions, labels):
    predictions = predictions.detach().cpu().clone().numpy()
    labels = labels.detach().cpu().clone().numpy()

    # Remove ignored index (special tokens) and convert to labels
    true_labels = [[label_names[l] for l in label if l != -100] for label in labels]
    true_predictions = [
        [label_names[p] for (p, l) in zip(prediction, label) if l != -100]
        for prediction, label in zip(predictions, labels)
    ]
    return true_labels, true_predictions
```

然後我們可以編寫訓練循環。在定義一個進度條來跟蹤訓練的進行後，循環分為三個部分：

- 訓練本身，這是對`train_dataloader`的經典迭代，向前傳遞模型，然後反向傳遞和優化參數
- 評估,在獲得我們模型的輸出後:因為兩個進程可能將輸入和標籤填充成不同的形狀,在調用`gather()`方法前我們需要使用`accelerator.pad_across_processes()`來讓預測和標籤形狀相同。如果我們不這樣做，評估要麼出錯，要麼永遠不會得到結果。然後，我們將結果發送給`metric.add_batch()`，並在計算循環結束後調用`metric.compute()`。
- 保存和上傳，首先保存模型和標記器，然後調用`repo.push_to_hub()`。注意，我們使用參數`blocking=False`告訴🤗 hub 庫用在異步進程中推送。這樣，訓練將正常繼續，並且該（長）指令將在後臺執行。

這是訓練循環的完整代碼：

```py
from tqdm.auto import tqdm
import torch

progress_bar = tqdm(range(num_training_steps))

for epoch in range(num_train_epochs):
    # Training
    model.train()
    for batch in train_dataloader:
        outputs = model(**batch)
        loss = outputs.loss
        accelerator.backward(loss)

        optimizer.step()
        lr_scheduler.step()
        optimizer.zero_grad()
        progress_bar.update(1)

    # Evaluation
    model.eval()
    for batch in eval_dataloader:
        with torch.no_grad():
            outputs = model(**batch)

        predictions = outputs.logits.argmax(dim=-1)
        labels = batch["labels"]

        # Necessary to pad predictions and labels for being gathered
        predictions = accelerator.pad_across_processes(predictions, dim=1, pad_index=-100)
        labels = accelerator.pad_across_processes(labels, dim=1, pad_index=-100)

        predictions_gathered = accelerator.gather(predictions)
        labels_gathered = accelerator.gather(labels)

        true_predictions, true_labels = postprocess(predictions_gathered, labels_gathered)
        metric.add_batch(predictions=true_predictions, references=true_labels)

    results = metric.compute()
    print(
        f"epoch {epoch}:",
        {
            key: results[f"overall_{key}"]
            for key in ["precision", "recall", "f1", "accuracy"]
        },
    )

    # Save and upload
    accelerator.wait_for_everyone()
    unwrapped_model = accelerator.unwrap_model(model)
    unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save)
    if accelerator.is_main_process:
        tokenizer.save_pretrained(output_dir)
        repo.push_to_hub(
            commit_message=f"Training in progress epoch {epoch}", blocking=False
        )
```

果這是您第一次看到用 🤗 Accelerate 保存的模型，讓我們花點時間檢查一下它附帶的三行代碼：

```py
accelerator.wait_for_everyone()
unwrapped_model = accelerator.unwrap_model(model)
unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save)
```

第一行是不言自明的：它告訴所有進程等到都處於那個階段再繼續(阻塞)。這是為了確保在保存之前，我們在每個過程中都有相同的模型。然後獲取`unwrapped_model`，它是我們定義的基本模型。
`accelerator.prepare()`方法將模型更改為在分佈式訓練中工作，所以它不再有`save_pretraining()`方法;`accelerator.unwrap_model()`方法將撤銷該步驟。最後，我們調用`save_pretraining()`，但告訴該方法使用`accelerator.save()`而不是`torch.save()`。 

當完成之後，你應該有一個模型，它產生的結果與`Trainer`的結果非常相似。你可以在[hugs face-course/bert-fine - tuning -ner-accelerate](https://huggingface.co/huggingface-course/bert-finetuned-ner-accelerate)中查看我們使用這個代碼訓練的模型。如果你想測試訓練循環的任何調整，你可以直接通過編輯上面顯示的代碼來實現它們!

{/if}

## 使用微調模型

我們已經向您展示瞭如何使用我們在模型中心微調的模型和推理小部件。在本地使用它 `pipeline` ，您只需要指定正確的模型標識符：

```py
from transformers import pipeline

# Replace this with your own checkpoint
model_checkpoint = "huggingface-course/bert-finetuned-ner"
token_classifier = pipeline(
    "token-classification", model=model_checkpoint, aggregation_strategy="simple"
)
token_classifier("My name is Sylvain and I work at Hugging Face in Brooklyn.")
```

```python out
[{'entity_group': 'PER', 'score': 0.9988506, 'word': 'Sylvain', 'start': 11, 'end': 18},
 {'entity_group': 'ORG', 'score': 0.9647625, 'word': 'Hugging Face', 'start': 33, 'end': 45},
 {'entity_group': 'LOC', 'score': 0.9986118, 'word': 'Brooklyn', 'start': 49, 'end': 57}]
```

太棒了！我們的模型與此管道的默認模型一樣有效！


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter7/2.mdx" />

### Part 1 完結!
https://huggingface.co/learn/course/zh-TW/chapter4/5.md

# Part 1 完結!

<CourseFloatingBanner
    chapter={4}
    classNames="absolute z-10 right-0 top-0"
/>

這是課程第一部分的結尾！第 2 部分將在 11 月 15 日與大型社區活動一起發佈，[點擊這裡](https://huggingface.co/blog/course-launch-event)查看更多信息.

您現在應該能夠針對文本分類問題（單個或成對句子）對預訓練模型進行微調，並將結果上傳到模型中心。為確保您掌握了第一部分的內容，您應該針對您感興趣的想法進行嘗試（不一定是英語）！一旦你完成，您可以在[Hugging Face 社區](https://discuss.huggingface.co/)的[這個話題](https://discuss.huggingface.co/t/share-your-projects/6803)分享您的項目。

我們迫不及待地想看看您將用它構建什麼！

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter4/5.mdx" />

### The Hugging Face Hub
https://huggingface.co/learn/course/zh-TW/chapter4/1.md

# The Hugging Face Hub

<CourseFloatingBanner
    chapter={4}
    classNames="absolute z-10 right-0 top-0"
/>

[Hugging Face Hub](https://huggingface.co/) -- 我們的主網站，是一個中央平臺，在這個網站上任何人都可以查找、使用和貢獻新的最先進的模型和數據集。它擁有各種各樣的模型，公開可用的模型超過 10,000個。我們在本章去探索Hub中的模型，並在第 5 章中探索Hub中的數據集。

Hub 中的模型不僅限於 🤗 Transformers 甚至 NLP。有用於自然語言處理的[Flair](https://github.com/flairNLP/flair)，[AllenNLP](https://github.com/allenai/allennlp)，[Asteroid](https://github.com/asteroid-team/asteroid)和用於音頻檢測的[pyannote](https://github.com/pyannote/pyannote-audio)，以及對於視覺的[timm](https://github.com/rwightman/pytorch-image-models)，這些例子只是Hub中冰山一角，更多的模型。可以由你去探索。

這些模型中的每一個都作為 Git 存儲庫託管，這允許進行版本控制和重現。在 Hub 上共享模型意味著將其向社區開放，讓任何希望使用它的人都可以輕鬆訪問它，從而使其他人不用為訓練模型而苦惱就可以直接使用模型。

此外，在 Hub 上共享模型會自動為該模型部署託管的推理 API。社區中的任何人都可以直接在模型頁面上自由地測試它，使用自定義輸入和適當的小部件。

最棒的是是在 Hub 上共享和使用任何公共模型是完全免費的！如果您不想公開模型，也存在[付費計劃](https://huggingface.co/pricing)。下面的視頻顯示瞭如何使用 Hub。

<Youtube id="XvSGPZFEjDY"/>

這部分需要有一個 Huggingface.co 帳戶，因為我們將在 Hugging Face Hub 上創建和管理存儲庫：[創建一個賬戶](https://huggingface.co/join)

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter4/1.mdx" />

### 共享預訓練模型
https://huggingface.co/learn/course/zh-TW/chapter4/3.md

# 共享預訓練模型

{#if fw === 'pt'}

<CourseFloatingBanner chapter={4}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/zh-CN/chapter4/section3_pt.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/zh-CN/chapter4/section3_pt.ipynb"},
]} />

{:else}

<CourseFloatingBanner chapter={4}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/zh-CN/chapter4/section3_tf.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/zh-CN/chapter4/section3_tf.ipynb"},
]} />

{/if}

在下面的步驟中，我們將看看將預訓練模型分享到 🤗 Hub 的最簡單方法。有可用的工具和實用程序可以讓直接在 Hub 上共享和更新模型變得簡單，我們將在下面進行探討。

<Youtube id="9yY3RB_GSPM"/>

我們鼓勵所有訓練模型的用戶通過與社區共享來做出貢獻——共享模型，即使是在非常特定的數據集上進行訓練，也將幫助他人，節省他們的時間和計算資源，並提供對有用的訓練工件的訪問。反過來，您可以從其他人所做的工作中受益！

創建新模型存儲庫的方法有以下三種：

- 使用 push_to_hub API 接口
- 使用 huggingface_hub Python 庫
- 使用 web 界面

創建存儲庫後，您可以通過 git 和 git-lfs 將文件上傳到其中。我們將在以下部分引導您創建模型存儲庫並將文件上傳到它們


## 使用 push_to_hub API

{#if fw === 'pt'}

<Youtube id="Zh0FfmVrKX0"/>

{:else}

<Youtube id="pUh5cGmNV8Y"/>

{/if}

將文件上傳到模型中心的最簡單方法是利用 **push_to_hub** API 接口。

在繼續之前，您需要生成一個身份驗證令牌，以便 **huggingface_hub** API 知道您是誰以及您對哪些名稱空間具有寫入權限。確保你在一個環境中 **transformers** 已安裝（見[Setup](/course/chapter0)）。如果您在筆記本中，可以使用以下功能登錄：

```python
from huggingface_hub import notebook_login

notebook_login()
```

在終端中，您可以運行：

```bash
huggingface-cli login
```

在這兩種情況下，系統都會提示您輸入用戶名和密碼，這與您用於登錄 Hub 的用戶名和密碼相同。如果您還沒有 Hub 配置文件，則應該創建一個[here](https://huggingface.co/join)。

好的！您現在已將身份驗證令牌存儲在緩存文件夾中。讓我們創建一些存儲庫！

{#if fw === 'pt'}

如果你玩過 **Trainer** 用於訓練模型的 API，將其上傳到 Hub 的最簡單方法是設置 **push_to_hub=True** 當你定義你的 **TrainingArguments** ：

```py
from transformers import TrainingArguments

training_args = TrainingArguments(
    "bert-finetuned-mrpc", save_strategy="epoch", push_to_hub=True
)
```

你聲明 **trainer.train()** 的時候， 這 **Trainer** 然後每次將您的模型保存到您的命名空間中的存儲庫中時（這裡是每個時代），它將上傳到模型中心。該存儲庫將命名為您選擇的輸出目錄（此處 **bert-finetuned-mrpc** ) 但您可以選擇不同的名稱 **hub_model_id = a_different_name** 。

要將您的模型上傳到您所屬的組織，只需將其傳遞給 **hub_model_id = my_organization/my_repo_name** 。

訓練結束後，你應該做最後的 **trainer.push_to_hub()** 上傳模型的最新版本。它還將生成包含所有相關元數據的模型卡，報告使用的超參數和評估結果！以下是您可能會在此類模型卡中找到的內容示例：

<div class="flex justify-center">
  <img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter4/model_card.png" alt="An example of an auto-generated model card." width="100%"/>
</div>


{:else}


如果您使用Keras來訓練您的模型，則將其上傳到Hub的最簡單方法是在調用 **model.fit()**  時傳遞**PushToHubCallback**：

```py
from transformers import PushToHubCallback

callback = PushToHubCallback(
    "bert-finetuned-mrpc", save_strategy="epoch", tokenizer=tokenizer
)
```

然後，您應該在對**model.fit()**的調用中添加**callbacks=[callback]**。然後，每次將模型保存在命名空間的存儲庫中（此處為每個 epoch）時，回調都會將模型上傳到 Hub。該存儲庫的名稱將類似於您選擇的輸出目錄（此處為**bert-finetuned-mrpc**），但您可以選擇另一個名稱，名稱為**hub_model_id = a_different_name**。

要將您的模型上傳到您所屬的組織，只需將其傳遞給 **hub_model_id = my_organization/my_repo_name** 。

{/if}

在較低級別，可以通過模型、標記器和配置對象直接訪問模型中心 **push_to_hub()** 方法。此方法負責創建存儲庫並將模型和標記器文件直接推送到存儲庫。與我們將在下面看到的 API 不同，不需要手動處理。

為了瞭解它是如何工作的，讓我們首先初始化一個模型和一個標記器：

{#if fw === 'pt'}

```py
from transformers import AutoModelForMaskedLM, AutoTokenizer

checkpoint = "camembert-base"

model = AutoModelForMaskedLM.from_pretrained(checkpoint)
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
```

{:else}

```py
from transformers import TFAutoModelForMaskedLM, AutoTokenizer

checkpoint = "camembert-base"

model = TFAutoModelForMaskedLM.from_pretrained(checkpoint)
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
```

{/if}

你可以自由地用這些做任何你想做的事情——向標記器添加標記，訓練模型，微調它。一旦您對生成的模型、權重和標記器感到滿意，您就可以利用 **push_to_hub()** 方法直接在 **model** 中：

```py
model.push_to_hub("dummy-model")
```

這將創建新的存儲庫 **dummy-model** 在您的個人資料中，並用您的模型文件填充它。
對標記器執行相同的操作，以便所有文件現在都可以在此存儲庫中使用：

```py
tokenizer.push_to_hub("dummy-model")
```

如果您屬於一個組織，只需指定 **organization** 上傳到該組織的命名空間的參數：

```py
tokenizer.push_to_hub("dummy-model", organization="huggingface")
```

如果您希望使用特定的 Hugging Face 令牌，您可以自由地將其指定給 **push_to_hub()** 方法也是：

```py
tokenizer.push_to_hub("dummy-model", organization="huggingface", use_auth_token="<TOKEN>")
```

現在前往模型中心找到您新上傳的模型：*https://huggingface.co/user-or-organization/dummy-model*。

單擊“文件和版本”選項卡，您應該會在以下屏幕截圖中看到可見的文件：

{#if fw === 'pt'}
<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter4/push_to_hub_dummy_model.png" alt="Dummy model containing both the tokenizer and model files." width="80%"/>
</div>
{:else}
<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter4/push_to_hub_dummy_model_tf.png" alt="Dummy model containing both the tokenizer and model files." width="80%"/>
</div>
{/if}

<Tip>

✏️ **試試看**！獲取與檢查點關聯的模型和標記器，並使用該方法將它們上傳到您的命名空間中的存儲庫。在刪除之前，請仔細檢查該存儲庫是否正確顯示在您的頁面上。

</Tip>

如您所見， **push_to_hub()** 方法接受多個參數，從而可以上傳到特定的存儲庫或組織命名空間，或使用不同的 API 令牌。我們建議您查看直接在[🤗 Transformers documentation](https://huggingface.co/transformers/model_sharing.html)瞭解什麼是可能的

這 **push_to_hub()** 方法由[huggingface_hub](https://github.com/huggingface/huggingface_hub)Python 包，為 Hugging Face Hub 提供直接 API。它集成在 🤗 Transformers 和其他幾個機器學習庫中，例如[allenlp](https://github.com/allenai/allennlp).雖然我們在本章中專注於 🤗 Transformers 集成，但將其集成到您自己的代碼或庫中很簡單。

跳到最後一部分，瞭解如何將文件上傳到新創建的存儲庫！

## 使用 huggingface_hub python庫

這 **huggingface_hub** Python 庫是一個包，它為模型和數據集中心提供了一組工具。它為常見任務提供了簡單的方法和類，例如
獲取有關模型中心上存儲庫的信息並對其進行管理。它提供了在 git 之上工作的簡單 API 來管理這些存儲庫的內容並集成 Hub
在您的項目和庫中。

類似於使用 **push_to_hub** API，這將要求您將 API 令牌保存在緩存中。為此，您需要使用 **login** 來自 CLI 的命令，如上一節所述（同樣，確保在這些命令前面加上 **!** 字符（如果在 Google Colab 中運行）：

```bash
huggingface-cli login
```

這 **huggingface_hub** 包提供了幾種對我們有用的方法和類。首先，有幾種方法可以管理存儲庫的創建、刪除等：

```python no-format
from huggingface_hub import (
    # User management
    login,
    logout,
    whoami,

    # Repository creation and management
    create_repo,
    delete_repo,
    update_repo_visibility,

    # And some methods to retrieve/change information about the content
    list_models,
    list_datasets,
    list_metrics,
    list_repo_files,
    upload_file,
    delete_file,
)
```


此外，它還提供了非常強大的 **Repository** 用於管理本地存儲庫的類。我們將在接下來的幾節中探討這些方法和該類，以瞭解如何利用它們。

這 **create_repo** 方法可用於在模型中心上創建新存儲庫：


```py
from huggingface_hub import create_repo

create_repo("dummy-model")
```

這將創建存儲庫 **dummy-model** 在您的命名空間中。如果願意，您可以使用 **organization** 爭論：

```py
from huggingface_hub import create_repo

create_repo("dummy-model", organization="huggingface")
```

這將創建 **dummy-model** 存儲庫中的 **huggingface** 命名空間，假設您屬於該組織。
其他可能有用的參數是：

- private 以指定存儲庫是否應對其他人可見。
- token 如果您想用給定的令牌覆蓋存儲在緩存中的令牌。
-  repo_type 如果你想創建一個或一個替代一個的而不是模型。接受的值和 datasetspace "dataset""space"。

創建存儲庫後，我們應該向其中添加文件！跳到下一部分以查看可以處理此問題的三種方法。


## 使用網絡界面

Web 界面提供了直接在 Hub 中管理存儲庫的工具。使用該界面，您可以輕鬆創建存儲庫、添加文件（甚至是大文件！）、探索模型、可視化差異等等。

要創建新的存儲庫，請訪問[huggingface.co/new](https://huggingface.co/new)：

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter4/new_model.png" alt="Page showcasing the model used for the creation of a new model repository." width="80%"/>
</div>

首先，指定存儲庫的所有者：這可以是您或您所屬的任何組織。如果您選擇一個組織，該模型將出現在該組織的頁面上，並且該組織的每個成員都可以為存儲庫做出貢獻。

接下來，輸入您的模型名稱。這也將是存儲庫的名稱。最後，您可以指定您的模型是公開的還是私有的。私人模特要求您擁有付費 Hugging Face 帳戶，並允許您將模特隱藏在公眾視野之外。

創建模型存儲庫後，您應該看到如下頁面：

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter4/empty_model.png" alt="An empty model page after creating a new repository." width="80%"/>
</div>

這是您的模型將被託管的地方。要開始填充它，您可以直接從 Web 界面添加 README 文件。

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter4/dummy_model.png" alt="The README file showing the Markdown capabilities." width="80%"/>
</div>

README 文件在 Markdown 中 - 隨意使用它！本章的第三部分致力於構建模型卡。這些對於為您的模型帶來價值至關重要，因為它們是您告訴其他人它可以做什麼的地方。

如果您查看“文件和版本”選項卡，您會發現那裡還沒有很多文件——只有自述文件你剛剛創建和.git 屬性跟蹤大文件的文件。

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter4/files.png" alt="The 'Files and versions' tab only shows the .gitattributes and README.md files." width="80%"/>
</div>

接下來我們將看看如何添加一些新文件。

## 上傳模型文件

Hugging Face Hub 上的文件管理系統基於用於常規文件的 git 和 git-lfs（代表[Git Large File Storage](https://git-lfs.github.com/)) 對於較大的文件。

在下一節中，我們將介紹將文件上傳到 Hub 的三種不同方式：通過 **huggingface_hub** 並通過 git 命令。

### The `upload_file` approach

使用 **upload_file** 不需要在您的系統上安裝 git 和 git-lfs。它使用 HTTP POST 請求將文件直接推送到 🤗 Hub。這種方法的一個限制是它不能處理大於 5GB 的文件。
如果您的文件大於 5GB，請按照下面詳述的另外兩種方法進行操作。API 可以按如下方式使用：

```py
from huggingface_hub import upload_file

upload_file(
    "<path_to_file>/config.json",
    path_in_repo="config.json",
    repo_id="<namespace>/dummy-model",
)
```

這將上傳文件 **config.json** 可在 **path_to_file** 到存儲庫的根目錄 **config.json** , 到 **dummy-model** 存儲庫。
其他可能有用的參數是：

- token，如果要通過給定的令牌覆蓋緩存中存儲的令牌。
- repo_type, 如果你想要上傳一個 `dataset` 或一個 `space` 而不是模型。 接受的值為 `"dataset"` 和 `"space"`.


### The `Repository` class

以類似 git 的方式管理本地存儲庫。它抽象了 git 可能遇到的大部分痛點，以提供我們需要的所有功能。

使用這個類需要安裝 git 和 git-lfs，所以確保你已經安裝了 git-lfs（參見[here](https://git-lfs.github.com/)安裝說明）並在開始之前進行設置。

為了開始使用我們剛剛創建的存儲庫，我們可以通過克隆遠程存儲庫將其初始化到本地文件夾開始：

```py
from huggingface_hub import Repository

repo = Repository("<path_to_dummy_folder>", clone_from="<namespace>/dummy-model")
```

這創建了文件夾 **path_to_dummy_folder** 在我們的工作目錄中。該文件夾僅包含 **.gitattributes** 文件，因為這是通過實例化存儲庫時創建的唯一文件 **create_repo**。

從現在開始，我們可以利用幾種傳統的 git 方法：

```py
repo.git_pull()
repo.git_add()
repo.git_commit()
repo.git_push()
repo.git_tag()
```

另外！我們建議您查看 **Repository** 可用文件[here](https://github.com/huggingface/huggingface_hub/tree/main/src/huggingface_hub#advanced-programmatic-repository-management)有關所有可用方法的概述。

目前，我們有一個模型和一個標記器，我們希望將其推送到模型中心。我們已經成功克隆了存儲庫，因此我們可以將文件保存在該存儲庫中。

我們首先通過拉取最新更改來確保我們的本地克隆是最新的：

```py
repo.git_pull()
```

完成後，我們保存模型和標記器文件：

```py
model.save_pretrained("<path_to_dummy_folder>")
tokenizer.save_pretrained("<path_to_dummy_folder>")
```

這 **path_to_dummy_folder** 現在包含所有模型和標記器文件。我們遵循通常的 git 工作流程，將文件添加到暫存區，提交它們並將它們推送到模型中心：

```py
repo.git_add()
repo.git_commit("Add model and tokenizer files")
repo.git_push()
```

恭喜！您剛剛將第一個文件推送到hub上。

### The git-based approach

這是上傳文件的非常簡單的方法：我們將直接使用 git 和 git-lfs 來完成。大多數困難都被以前的方法抽象掉了，但是下面的方法有一些警告，所以我們將遵循一個更復雜的用例。

使用這個類需要安裝 git 和 git-lfs，所以請確保你有[git-lfs](https://git-lfs.github.com/)安裝（請參閱此處瞭解安裝說明）並在開始之前進行設置。

首先從初始化 git-lfs 開始：

```bash
git lfs install
```

```bash
Updated git hooks.
Git LFS initialized.
```

完成後，第一步是克隆您的模型存儲庫：

```bash
git clone https://huggingface.co/<namespace>/<your-model-id>
```

我的用戶名是 **lysandre** 我使用了模型名稱 **dummy** ，所以對我來說，命令最終如下所示：

```
git clone https://huggingface.co/lysandre/dummy
```

我現在有一個名為的文件夾假在我的工作目錄中。我能 **cd** 進入文件夾並查看內容：

```bash
cd dummy && ls
```

```bash
README.md
```

如果您剛剛使用 Hugging Face Hub 創建了您的存儲庫 **create_repo** 方法，這個文件夾應該只包含一個隱藏的 **.gitattributes** 文件。如果您按照上一節中的說明使用 Web 界面創建存儲庫，則該文件夾應包含一個自述文件文件旁邊的隱藏 **.gitattributes** 文件，如圖所示。

添加一個常規大小的文件，例如配置文件、詞彙文件，或者基本上任何幾兆字節以下的文件，就像在任何基於 git 的系統中所做的一樣。但是，更大的文件必須通過 git-lfs 註冊才能將它們推送到Hugging Face。

讓我們回到 Python 來生成我們想要提交到我們的虛擬存儲庫的模型和標記器：

{#if fw === 'pt'}
```py
from transformers import AutoModelForMaskedLM, AutoTokenizer

checkpoint = "camembert-base"

model = AutoModelForMaskedLM.from_pretrained(checkpoint)
tokenizer = AutoTokenizer.from_pretrained(checkpoint)

# Do whatever with the model, train it, fine-tune it...

model.save_pretrained("<path_to_dummy_folder>")
tokenizer.save_pretrained("<path_to_dummy_folder>")
```
{:else}
```py
from transformers import TFAutoModelForMaskedLM, AutoTokenizer

checkpoint = "camembert-base"

model = TFAutoModelForMaskedLM.from_pretrained(checkpoint)
tokenizer = AutoTokenizer.from_pretrained(checkpoint)

# Do whatever with the model, train it, fine-tune it...

model.save_pretrained("<path_to_dummy_folder>")
tokenizer.save_pretrained("<path_to_dummy_folder>")
```
{/if}

現在我們已經保存了一些模型和標記器工件，讓我們再看看假文件夾：

```bash
ls
```

{#if fw === 'pt'}
```bash
config.json  pytorch_model.bin  README.md  sentencepiece.bpe.model  special_tokens_map.json tokenizer_config.json  tokenizer.json
```

If you look at the file sizes (for example, with `ls -lh`), you should see that the model state dict file (*pytorch_model.bin*) is the only outlier, at more than 400 MB.

{:else}
```bash
config.json  README.md  sentencepiece.bpe.model  special_tokens_map.json  tf_model.h5  tokenizer_config.json  tokenizer.json
```

如果您查看文件大小（例如， **ls -lh** )，您應該會看到模型狀態 dict 文件 (pytorch_model.bin) 是唯一的異常值，超過 400 MB。

{/if}

<Tip>
✏️ 從 web 界面創建存儲庫時，*.gitattributes* 文件會自動設置為將具有某些擴展名的文件，例如 *.bin* 和 *.h5* 視為大文件，git-lfs 會對其進行跟蹤您無需進行必要的設置。
</Tip> 

我們現在可以繼續進行，就像我們通常使用傳統 Git 存儲庫一樣。我們可以使用以下命令將所有文件添加到 Git 的暫存環境中 **git add** 命令：

```bash
git add .
```

然後我們可以查看當前暫存的文件：

```bash
git status
```

{#if fw === 'pt'}
```bash
On branch main
Your branch is up to date with 'origin/main'.

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
  modified:   .gitattributes
	new file:   config.json
	new file:   pytorch_model.bin
	new file:   sentencepiece.bpe.model
	new file:   special_tokens_map.json
	new file:   tokenizer.json
	new file:   tokenizer_config.json
```
{:else}
```bash
On branch main
Your branch is up to date with 'origin/main'.

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
  modified:   .gitattributes
  	new file:   config.json
	new file:   sentencepiece.bpe.model
	new file:   special_tokens_map.json
	new file:   tf_model.h5
	new file:   tokenizer.json
	new file:   tokenizer_config.json
```
{/if}

同樣，我們可以確保 git-lfs 使用其跟蹤正確的文件 **status** 命令：

```bash
git lfs status
```

{#if fw === 'pt'}
```bash
On branch main
Objects to be pushed to origin/main:


Objects to be committed:

	config.json (Git: bc20ff2)
	pytorch_model.bin (LFS: 35686c2)
	sentencepiece.bpe.model (LFS: 988bc5a)
	special_tokens_map.json (Git: cb23931)
	tokenizer.json (Git: 851ff3e)
	tokenizer_config.json (Git: f0f7783)

Objects not staged for commit:


```

我們可以看到所有文件都有 **Git** 作為處理程序，除了其中有 **LFS**的*pytorch_model.bin* 和 *sentencepiece.bpe.model*。

{:else}
```bash
On branch main
Objects to be pushed to origin/main:


Objects to be committed:

	config.json (Git: bc20ff2)
	sentencepiece.bpe.model (LFS: 988bc5a)
	special_tokens_map.json (Git: cb23931)
	tf_model.h5 (LFS: 86fce29)
	tokenizer.json (Git: 851ff3e)
	tokenizer_config.json (Git: f0f7783)

Objects not staged for commit:


```

我們可以看到所有文件都有 **Git** 作為處理程序，除了其中有 **LFS**的*t5_model.h5*。

{/if}

讓我們繼續最後的步驟，提交併推送到Hugging Face遠程倉庫：

```bash
git commit -m "First model version"
```

{#if fw === 'pt'}
```bash
[main b08aab1] First model version
 7 files changed, 29027 insertions(+)
  6 files changed, 36 insertions(+)
 create mode 100644 config.json
 create mode 100644 pytorch_model.bin
 create mode 100644 sentencepiece.bpe.model
 create mode 100644 special_tokens_map.json
 create mode 100644 tokenizer.json
 create mode 100644 tokenizer_config.json
```
{:else}
```bash
[main b08aab1] First model version
 6 files changed, 36 insertions(+)
 create mode 100644 config.json
 create mode 100644 sentencepiece.bpe.model
 create mode 100644 special_tokens_map.json
 create mode 100644 tf_model.h5
 create mode 100644 tokenizer.json
 create mode 100644 tokenizer_config.json
```
{/if}

推送可能需要一些時間，具體取決於您的互聯網連接速度和文件大小：

```bash
git push
```

```bash
Uploading LFS objects: 100% (1/1), 433 MB | 1.3 MB/s, done.
Enumerating objects: 11, done.
Counting objects: 100% (11/11), done.
Delta compression using up to 12 threads
Compressing objects: 100% (9/9), done.
Writing objects: 100% (9/9), 288.27 KiB | 6.27 MiB/s, done.
Total 9 (delta 1), reused 0 (delta 0), pack-reused 0
To https://huggingface.co/lysandre/dummy
   891b41d..b08aab1  main -> main
```

{#if fw === 'pt'}
If we take a look at the model repository when this is finished, we can see all the recently added files:

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter4/full_model.png" alt="The 'Files and versions' tab now contains all the recently uploaded files." width="80%"/>
</div>

UI 允許您瀏覽模型文件和提交，並查看每個提交引入的差異：

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter4/diffs.gif" alt="The diff introduced by the recent commit." width="80%"/>
</div>

{:else}

如果我們在完成後查看模型存儲庫，我們可以看到所有最近添加的文件：

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter4/full_model_tf.png" alt="The 'Files and versions' tab now contains all the recently uploaded files." width="80%"/>
</div>

UI 允許您瀏覽模型文件和提交，並查看每個提交引入的差異：

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter4/diffstf.gif" alt="The diff introduced by the recent commit." width="80%"/>
</div>
{/if}


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter4/3.mdx" />

### 章末小測試
https://huggingface.co/learn/course/zh-TW/chapter4/6.md

# 章末小測試

<CourseFloatingBanner
    chapter={4}
    classNames="absolute z-10 right-0 top-0"
/>

讓我們測試一下你在本章所學的知識！
  
### 1. Hub 上的模型有什麼限制？
<Question
	choices={[
		{
			text: "模型來自🤗 Transformers 庫。",
			explain: "雖然 Hugging Face Hub 支持 🤗 Transformers 庫中的模型，但它們並不是唯一的模型！"
		},
		{
			text: "所有型號都有類似於🤗 Transformers 的接口。",
			explain: "將模型上傳到 Hugging Face Hub 時，沒有設置接口要求。"
		},
		{
			text: "沒有限制。",
			explain: "對! 在將模型上傳到Hub時沒有限制。",
            correct: true
		},
        {
			text: "在某種程度上與 NLP 相關的模型。",
			explain: "對應用領域沒有要求！"
		}
	]}
/>

### 2.如何管理Hub上的模型？
<Question
	choices={[
		{
			text: "通過 GCP 帳戶。",
			explain: "不對！"
		},
		{
			text: "通過點對點分佈。",
			explain: "不對！"
		},
		{
			text: "通過 git 和 git-lfs。",
			explain: "正確！ Hub上的模型是簡單的Git存儲庫，利用Git lfs處理大型文件。",
            correct: true
		}
	]}
/>

### 3.你能使用Hugging Face Hub網頁接口做什麼？
<Question
	choices={[
		{
			text: "分叉現有的存儲庫。",
			explain: "在Hugging Face Hub上無法分叉存儲庫。"
		},
		{
			text: "創建一個新的模型庫。",
			explain: "沒錯! 不過，這並不是你能做的全部。",
            correct: true
		},
		{
			text: "管理和編輯文件。",
			explain: "正確！ 不過，這並不是你能做的全部",
            correct: true
		},
        {
			text: "上傳文件。",
			explain: "對! 但這還不是全部。",
            correct: true
		},
        {
			text: "看看不同版本的差異。",
			explain: "沒錯! 不過，這並不是你能做的全部。",
            correct: true
		}
	]}
/>

### 4.模型卡是什麼?
<Question
	choices={[
		{
			text: "模型的粗略描述，因此不如模型和標記器文件重要。",
			explain: "這確實是對模型的描述，但它是一個重要的部分: 如果它不完整或缺乏模型的實用性大幅減少。"
		},
		{
			text: "一種確保再現性、可重用性和公平性的方法。",
			explain: "正確！在模型卡片中共享正確的信息將幫助用戶利用您的模型，並瞭解其侷限性和偏見。",
            correct: true
		},
		{
			text: "一個 Python 文件，可以運行它來檢索有關模型的信息。",
			explain: "模型卡片是簡單的 Markdown 文件。"
		}
	]}
/>

### 5.哪些🤗 Transformers 庫的對象可以直接在 Hub 上通過push _ to _ Hub ()共享？
{#if fw === 'pt'}
<Question
	choices={[
		{
			text: "一個標記器",
			explain: "正確！ 所有標記器都有push_to_hub方法，使用該方法將把所有標記器文件（詞彙表、標記器的架構等）推送到給定的存儲庫。不過，這不是唯一正確的答案！",
            correct: true
		},
		{
			text: "模型配置",
			explain: "對！所有模型配置都有 push _ to _ hub 方法，使用這個方法可以將它們推送到給定的存儲庫。你還有其他能共享的嗎？",
            correct: true
		},
		{
			text: "一個模型",
			explain: "正確！ 所有模型都有 push_to_hub 方法，使用它會將它們及其配置文件推送到給定的存儲庫。不過，這並不是您可以共享的全部內容。",
            correct: true
		},
        {
			text: "Trainer",
			explain: "沒錯————Trainer也實現了push _ to _ hub方法，並且使用它將模型、配置、標記器和模型卡草稿上傳到給定的存儲器。試試其他答案！",
            correct: true
		}
	]}
/>
{:else}
<Question
	choices={[
		{
			text: "一個標記器",
			explain: "正確！ 所有標記器都有push_to_hub方法，使用該方法將把所有標記器文件（詞彙表、標記器的架構等）推送到給定的存儲庫。不過，這不是唯一正確的答案！",
            correct: true
		},
		{
			text: "模型配置",
			explain: "對！所有模型配置都有 push _ to _ hub 方法，使用這個方法可以將它們推送到給定的存儲庫。你還有其他能共享的嗎？",
            correct: true
		},
		{
			text: "一個模型",
			explain: "正確！ 所有模型都有 push_to_hub 方法，使用它會將它們及其配置文件推送到給定的存儲庫。不過，這並不是您可以共享的全部內容。",
            correct: true
		},
		{
			text: "以上都有專用的回調函數",
			explain: "正確————在訓練期間，PushToHubCallback會定期將所有這些對象發送到存儲器。",
            correct: true
		}
	]}
/>
{/if}

### 6.當使用push _ to _ hub ()方法或 CLI 工具時，第一步是什麼？
<Question
	choices={[
		{
			text: "登錄網站。",
			explain: "這對你的本地機器沒有幫助。"
		},
		{
			text: "在終端中運行'huggingface-cli login'。",
			explain: "正確————這將下載並緩存您的個人標記。",
            correct: true
		},
		{
			text: "在筆記本中運行'notebook _ login ()'。",
			explain: "正確————這將顯示一個小部件，讓您進行身份驗證。",
            correct: true
		},
	]}
/>

### 7.您正在使用一個模型和一個標記器————如何將它們上傳到 Hub？
<Question
	choices={[
		{
			text: "通過直接在模型和標記器上調用 push _ to _ hub 方法。",
			explain: "正確！",
            correct: true
		},
		{
			text: "在 Python 運行時中，將它們包裝在huggingface _ hub實用程序中。",
			explain: "模型和標記器已經受益於 < code > huggingface _ hub </code > 實用程序: 不需要額外的包裝！"
		},
		{
			text: "將它們保存到磁盤並調用 < code > transformers-cli upload-model </code >",
			explain: "命令 < code > upload-model </code > 不存在。"
		}
	]}
/>

### 8.您可以使用'Repository'類執行哪些 git 操作？
<Question
	choices={[
		{
			text: "提交。",
			explain: "正確，< code >git _ commit () </ > 方法就是為此而存在的。",
            correct: true
		},
		{
			text: "拉一下",
			explain: "這就是 < code > git _ pull () </code > 方法的目的。",
            correct: true
		},
		{
			text: "推一下",
			explain: "方法 < code > git _ push () </code > 可以做到這一點。",
            correct: true
		},
		{
			text: "合併",
			explain: "不，這個操作在這個 API 中是不可能的。"
		}
	]}
/>


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter4/6.mdx" />

### 構建模型卡片
https://huggingface.co/learn/course/zh-TW/chapter4/4.md

# 構建模型卡片

<CourseFloatingBanner
    chapter={4}
    classNames="absolute z-10 right-0 top-0"
/>

模型卡片是一個配置文件，可以說與模型存儲庫中的模型和 tokenizer 文件一樣重要。它包含了模型的核心定義，確保了社區成員可以復現模型的結果，並提供一個其他成員可以在這個模型基礎上構建他們的組件的平臺。

記錄訓練和評估過程並提供有關使用的數據以及已完成的預處理和後續處理的足夠信息，有助於其他人瞭解對模型的能力——確保模型存在和目前的限制、偏差可以識別和理解。

因此，創建清晰定義模型的模型卡片是非常重要的一步。在這裡，我們提供了一些可以幫助您解決此問題的方法。創建模型卡片是通過您之前看到的 Markdown 文件：README.md 。

「模型卡片」的概念源於谷歌的一個研究方向， Margaret Mitchell 等人在論文[“Model Cards for Model Reporting”](https://arxiv.org/abs/1810.03993)中首次提出，此處包含的許多信息均基於該論文，我們建議您查看這篇論文以瞭解為什麼模型卡片在重視可重複性、可重用性和公平性的時候中如此重要。

模型卡通常以非常簡短的概述開始，說明模型的用途，然後是模型卡片需要的其他信息：

- 模型描述
- 預期用途和限制
- 如何使用
- 侷限性和偏見
- 訓練數據
- 訓練程序
- 評價結果

讓我們來看看每個部分應該包含什麼。

### 模型描述：

提供了有關模型的基本詳細信息。這包括架構、版本、如果它是在論文中介紹的，是否有原始的實現可用？作者以及有關模型的一般信息、任何版權都應歸於此處。這一部分還可以提及有關訓練程序、參數和重要免責聲明的一般信息。

### 預期用途和限制：

在此描述模型可以適用的例子，包括可以應用它的語言、領域。模型卡的這一部分還可以記錄已知超出模型範圍的區域，或者可能表現不佳的區域。

### 使用方法：

此部分應包括一些有關如何使用模型的示例。這可以展示使用 **pipeline()** 函數、模型和標記器類的使用以及其他任何您認為可能有幫助的代碼。

### 訓練數據：

這部分應該指出模型是在哪個數據集上訓練的。也歡迎對數據集進行簡要描述。

### 訓練過程：

此部分中，您應該描述從再現性角度來看有用的訓練的所有相關方面。這包括對數據進行的任何預處理和後處理，以及模型訓練的批量數、批量大小、學習率等細節。

### 變量和指標：

在這裡，您應該描述您用於評估的指標，以及您測量的不同因素。提及使用了哪些指標、在哪個數據集上以及哪個數據集部分，可以輕鬆地將您的模型的性能與其他模型的性能進行比較。

### 評價結果：

這些應該提前在前面的部分告知，例如預期的使用效果和示例。最後，提供模型在評估數據集上的表現的指示。如果模型使用決策閾值，要麼提供評估中使用的決策閾值，要麼提供在不同閾值下針對預期用途進行評估的詳細信息。

## 例子

查看以下幾個精心製作的模型卡的例子：

* [bert-base-cased](https://huggingface.co/bert-base-cased)
* [gpt2](https://huggingface.co/gpt2)
* [distilbert](https://huggingface.co/distilbert-base-uncased)

更多來自於不同組織和公司的示例可以在[這裡](https://github.com/huggingface/model_card/blob/master/examples.md)查閱.

## 提示

發佈模型時不需要模型卡，製作一個模型時不需要包含上述所有部分。但是，模型的文檔會使未來的用戶受益，因此我們建議您儘自己的知識和能力填寫儘可能多的部分。

## 模型卡片元數據

如果您對 Hugging Face Hub 進行了一些探索，您應該已經看到某些模型屬於某些類別：您可以按任務、語言、庫等對其進行過濾。模型所屬的類別來自於您在模型卡片標題中添加的元數據。

例如，如果你看一下[`camembert-base` 模型卡片](https://huggingface.co/camembert-base/blob/main/README.md)，您應該在模型卡標題中看到以下幾行：

```
---
language: fr
license: mit
datasets:
- oscar
---
```

該元數據由 Hugging Face Hub 解析，然後將這個模型識別為法語模型，擁有 MIT 許可證，在 Oscar 數據集上訓練。

允許的指定語言、許可證、標籤、數據集、指標以及模型在訓練時獲得的評估結果在[全部模型卡片的規格](https://raw.githubusercontent.com/huggingface/huggingface_hub/main/modelcard.md)可以查閱。

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter4/4.mdx" />

### 使用預訓練的模型
https://huggingface.co/learn/course/zh-TW/chapter4/2.md

# 使用預訓練的模型

{#if fw === 'pt'}

<CourseFloatingBanner chapter={4}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/zh-CN/chapter4/section2_pt.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/zh-CN/chapter4/section2_pt.ipynb"},
]} />

{:else}

<CourseFloatingBanner chapter={4}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/zh-CN/chapter4/section2_tf.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/zh-CN/chapter4/section2_tf.ipynb"},
]} />

{/if}

模型中心使選擇合適的模型變得簡單，因此只需幾行代碼即可在任何下游庫中使用它。讓我們來看看如何實際使用這些模型之一，以及如何回饋社區。

假設我們正在尋找一種可以執行**mask**填充的French-based模型。

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter4/camembert.gif" alt="Selecting the Camembert model." width="80%"/>
</div>

我們選擇 **camembert-base** 檢查點來嘗試一下。我們需要做的僅僅是輸入 `camembert-base`標識符！正如您在前幾章中看到的，我們可以使用 **pipeline()** 功能：

```py
from transformers import pipeline

camembert_fill_mask = pipeline("fill-mask", model="camembert-base")
results = camembert_fill_mask("Le camembert est <mask> :)")
```

```python out
[
  {'sequence': 'Le camembert est délicieux :)', 'score': 0.49091005325317383, 'token': 7200, 'token_str': 'délicieux'}, 
  {'sequence': 'Le camembert est excellent :)', 'score': 0.1055697426199913, 'token': 2183, 'token_str': 'excellent'}, 
  {'sequence': 'Le camembert est succulent :)', 'score': 0.03453313186764717, 'token': 26202, 'token_str': 'succulent'}, 
  {'sequence': 'Le camembert est meilleur :)', 'score': 0.0330314114689827, 'token': 528, 'token_str': 'meilleur'}, 
  {'sequence': 'Le camembert est parfait :)', 'score': 0.03007650189101696, 'token': 1654, 'token_str': 'parfait'}
]
```

如您所見，在管道中加載模型非常簡單。您唯一需要注意的是所選檢查點是否適合它將用於的任務。例如，這裡我們正在加載 **camembert-base** 檢查點在 **fill-mask** 管道，這完全沒問題。但是如果我們要在 **text-classification** 管道，結果沒有任何意義，因為 **camembert-base** 不適合這個任務！我們建議使用 Hugging Face Hub 界面中的任務選擇器來選擇合適的檢查點：

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter4/tasks.png" alt="The task selector on the web interface." width="80%"/>
</div>

您還可以直接使用模型架構實例化檢查點：

{#if fw === 'pt'}
```py
from transformers import CamembertTokenizer, CamembertForMaskedLM

tokenizer = CamembertTokenizer.from_pretrained("camembert-base")
model = CamembertForMaskedLM.from_pretrained("camembert-base")
```

然而，我們建議使用[Auto* 類](https://huggingface.co/transformers/model_doc/auto.html?highlight=auto#auto-classes)，因為Auto* 類設計與架構無關。前面的代碼示例將只能在 CamemBERT 架構中加載可用的檢查點，但使用 **Auto*** 類使切換檢查點變得簡單：

```py
from transformers import AutoTokenizer, AutoModelForMaskedLM

tokenizer = AutoTokenizer.from_pretrained("camembert-base")
model = AutoModelForMaskedLM.from_pretrained("camembert-base")
```
{:else}
```py
from transformers import CamembertTokenizer, TFCamembertForMaskedLM

tokenizer = CamembertTokenizer.from_pretrained("camembert-base")
model = TFCamembertForMaskedLM.from_pretrained("camembert-base")
```

However, we recommend using the [`TFAuto*` classes](https://huggingface.co/transformers/model_doc/auto.html?highlight=auto#auto-classes) instead, as these are by design architecture-agnostic. While the previous code sample limits users to checkpoints loadable in the CamemBERT architecture, using the `TFAuto*` classes makes switching checkpoints simple:
然而，我們建議使用[`TFAuto*` 類](https://huggingface.co/transformers/model_doc/auto.html?highlight=auto#auto-classes)，因為`TFAuto*`類設計與架構無關。前面的代碼示例將只能在 CamemBERT 架構中加載可用的檢查點，但使用 `TFAuto*`  類使切換檢查點變得簡單：

```py
from transformers import AutoTokenizer, TFAutoModelForMaskedLM

tokenizer = AutoTokenizer.from_pretrained("camembert-base")
model = TFAutoModelForMaskedLM.from_pretrained("camembert-base")
```
{/if}

<Tip>
使用預訓練模型時，一定要檢查它是如何訓練的，在哪些數據集上，它的限制和它的偏差。所有這些信息都應在其模型卡片上註明。
</Tip>


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter4/2.mdx" />

### 微調，檢查!
https://huggingface.co/learn/course/zh-TW/chapter3/5.md

# 微調，檢查!

<CourseFloatingBanner
    chapter={3}
    classNames="absolute z-10 right-0 top-0"
/>

這是非常令人高興的！ 在前兩章中，您瞭解了模型和標記器(tokenizer)，現在您知道如何針對您自己的數據對它們進行微調。回顧一下，在本章中，您：

{#if fw === 'pt'}
* 瞭解了[Hub](https://huggingface.co/datasets)中的數據集
* 學習瞭如何加載和預處理數據集，包括使用動態填充和整理器
* 實現您自己的模型微調和評估
* 實施了一個較為底層的訓練循環
* 使用 🤗 Accelerate 輕鬆調整您的訓練循環，使其適用於多個 GPU 或 TPU

{:else}
* 瞭解了[Hub](https://huggingface.co/datasets)中的數據集
* 學習瞭如何加載和預處理數據集
* 學習瞭如何使用 Keras 微調和評估模型
* 實現了自定義指標

{/if}


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter3/5.mdx" />

### 本章簡介
https://huggingface.co/learn/course/zh-TW/chapter3/1.md

# 本章簡介

<CourseFloatingBanner
    chapter={3}
    classNames="absolute z-10 right-0 top-0"
/>

在 [第二章](/course/chapter2) 我們探索瞭如何使用標記器(Tokenizer)和預訓練模型進行預測。但是，如果您想為自己的數據集微調預訓練模型，該怎麼做呢？這就是本章的主題！你將學到:

{#if fw === 'pt'}
* 如何從模型中心(hub)準備大型數據集
* 如何使用高級`訓練`API微調一個模型
* 如何使用自定義訓練過程
* 如何利用🤗 Accelerate庫在任何分佈式設備上輕鬆運行自定義訓練過程

{:else}
* 如何從模型中心(hub)準備大型數據集
* 如何使用 Keras 微調模型
* 如何使用 Keras 進行預測
* 如何使用自定義指標

{/if}

為了將經過訓練的參數上傳到 Hugging Face Hub，您需要一個 huggingface.co 帳戶: [創建一個賬戶](https://huggingface.co/join)

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter3/1.mdx" />

### 使用 Trainer API 微調模型
https://huggingface.co/learn/course/zh-TW/chapter3/3.md

# 使用 Trainer API 微調模型

<CourseFloatingBanner chapter={3}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/zh-CN/chapter3/section3.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/zh-CN/chapter3/section3.ipynb"},
]} />

<Youtube id="nvBXf7s7vTI"/>

🤗 Transformers提供了一個 **Trainer** 類來幫助您在自己的數據集上微調任何預訓練模型。完成上一節中的所有數據預處理工作後，您只需要執行幾個步驟來創建 **Trainer** .最難的部分可能是為 **Trainer.train()**配置運行環境，因為它在 CPU 上運行速度會非常慢。如果您沒有設置 GPU，您可以訪問免費的 GPU 或 TPU[Google Colab](https://colab.research.google.com/).

下面的示例假設您已經執行了上一節中的示例。下面這段代碼，概括了您需要提前運行的代碼：

```py
from datasets import load_dataset
from transformers import AutoTokenizer, DataCollatorWithPadding

raw_datasets = load_dataset("glue", "mrpc")
checkpoint = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)


def tokenize_function(example):
    return tokenizer(example["sentence1"], example["sentence2"], truncation=True)


tokenized_datasets = raw_datasets.map(tokenize_function, batched=True)
data_collator = DataCollatorWithPadding(tokenizer=tokenizer)
```

### Training

在我們定義我們的 **Trainer** 之前首先要定義一個 **TrainingArguments** 類，它將包含 **Trainer**用於訓練和評估的所有超參數。您唯一必須提供的參數是保存訓練模型的目錄，以及訓練過程中的檢查點。對於其餘的參數，您可以保留默認值，這對於基本微調應該非常有效。

```py
from transformers import TrainingArguments

training_args = TrainingArguments("test-trainer")
```

<Tip>

💡 如果您想在訓練期間自動將模型上傳到 Hub，請將push_to_hub=True添加到TrainingArguments之中. 我們將在[第四章](/course/chapter4/3)中詳細介紹這部分。

</Tip>

第二步是定義我們的模型。正如在[之前的章節](/2_Using Transformers/Introduction)一樣，我們將使用 **AutoModelForSequenceClassification** 類，它有兩個參數：

```py
from transformers import AutoModelForSequenceClassification

model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)
```

你會注意到，和[第二章](/course/chapter2)不一樣的是，在實例化此預訓練模型後會收到警告。這是因為 BERT 沒有在句子對分類方面進行過預訓練，所以預訓練模型的頭部已經被丟棄，而是添加了一個適合句子序列分類的新頭部。警告表明一些權重沒有使用（對應於丟棄的預訓練頭的那些），而其他一些權重被隨機初始化（新頭的那些）。最後鼓勵您訓練模型，這正是我們現在要做的。

一旦我們有了我們的模型，我們就可以定義一個 **Trainer** 通過將之前構造的所有對象傳遞給它——我們的**model** 、**training_args** ，訓練和驗證數據集，**data_collator** ，和 **tokenizer** ：

```py
from transformers import Trainer

trainer = Trainer(
    model,
    training_args,
    train_dataset=tokenized_datasets["train"],
    eval_dataset=tokenized_datasets["validation"],
    data_collator=data_collator,
    tokenizer=tokenizer,
)
```

請注意，當您在這裡完成**tokenizer**後，默認 **Trainer**使用 的**data_collator**會使用之前預定義的 **DataCollatorWithPadding** ，因此您可以在這個例子中跳過 **data_collator=data_collator**。在第 2 節中向您展示這部分處理仍然很重要！

為了讓預訓練模型在在我們的數據集上微調，我們只需要調用**Trainer**的**train()** 方法 ：

```py
trainer.train()
```

這將開始微調（在GPU上應該需要幾分鐘），並每500步報告一次訓練損失。但是，它不會告訴您模型的性能如何（或質量如何）。這是因為:

1. 我們沒有通過將**evaluation_strategy**設置為“**steps**”(在每次更新參數的時候評估)或“**epoch**”(在每個epoch結束時評估)來告訴**Trainer**在訓練期間進行評估。
2. 我們沒有為**Trainer**提供一個**compute_metrics()**函數來直接計算模型的好壞(否則評估將只輸出loss，這不是一個非常直觀的數字)。


### 評估

讓我們看看如何構建一個有用的 **compute_metrics()** 函數並在我們下次訓練時使用它。該函數必須採用 **EvalPrediction** 對象（帶有 **predictions** 和 **label_ids** 字段的參數元組）並將返回一個字符串到浮點數的字典（字符串是返回的指標的名稱，而浮點數是它們的值）。我們可以使用 **Trainer.predict()** 命令來使用我們的模型進行預測：

```py
predictions = trainer.predict(tokenized_datasets["validation"])
print(predictions.predictions.shape, predictions.label_ids.shape)
```

```python out
(408, 2) (408,)
```

 **predict()** 的輸出結果是具有三個字段的命名元組： **predictions** , **label_ids** ， 和 **metrics** .這 **metrics** 字段將只包含傳遞的數據集的loss，以及一些運行時間（預測所需的總時間和平均時間）。如果我們定義了自己的 **compute_metrics()** 函數並將其傳遞給 **Trainer** ，該字段還將包含**compute_metrics()**的結果。

**predict()** 方法是具有三個字段的命名元組： **predictions** , **label_ids** ， 和 **metrics** .這 **metrics** 字段將只包含傳遞的數據集的loss，以及一些運行時間（預測所需的總時間和平均時間）。如果我們定義了自己的 **compute_metrics()** 函數並將其傳遞給 **Trainer** ，該字段還將包含**compute_metrics()** 的結果。如你看到的， **predictions** 是一個形狀為 408 x 2 的二維數組（408 是我們使用的數據集中元素的數量）。這些是我們傳遞給**predict()**的數據集的每個元素的結果(logits)（正如你在[之前的章節](/course/chapter2)看到的情況）。要將我們的預測的可以與真正的標籤進行比較，我們需要在第二個軸上取最大值的索引：

```py
import numpy as np

preds = np.argmax(predictions.predictions, axis=-1)
```

現在建立我們的 **compute_metric()** 函數來較為直觀地評估模型的好壞，我們將使用 🤗 [Evaluate](https://github.com/huggingface/evaluate/) 庫中的指標。我們可以像加載數據集一樣輕鬆加載與 MRPC 數據集關聯的指標，這次使用 **evaluate.load()** 函數。返回的對象有一個 **compute()**方法我們可以用來進行度量計算的方法：

```py
import evaluate

metric = evaluate.load("glue", "mrpc")
metric.compute(predictions=preds, references=predictions.label_ids)
```

```python out
{'accuracy': 0.8578431372549019, 'f1': 0.8996539792387542}
```

您獲得的確切結果可能會有所不同，因為模型頭的隨機初始化可能會影響最終建立的模型。在這裡，我們可以看到我們的模型在驗證集上的準確率為 85.78%，F1 分數為 89.97。這是用於評估 GLUE 基準的 MRPC 數據集結果的兩個指標。而在[BERT 論文](https://arxiv.org/pdf/1810.04805.pdf)中展示的基礎模型的 F1 分數為 88.9。那是 **uncased** 模型，而我們目前正在使用 **cased** 模型，通過改進得到了更好的結果。

最後將所有東西打包在一起，我們得到了我們的 **compute_metrics()** 函數：

```py
def compute_metrics(eval_preds):
    metric = evaluate.load("glue", "mrpc")
    logits, labels = eval_preds
    predictions = np.argmax(logits, axis=-1)
    return metric.compute(predictions=predictions, references=labels)
```

為了查看模型在每個訓練週期結束的好壞，下面是我們如何使用**compute_metrics()**函數定義一個新的 **Trainer** ：

```py
training_args = TrainingArguments("test-trainer", evaluation_strategy="epoch")
model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)

trainer = Trainer(
    model,
    training_args,
    train_dataset=tokenized_datasets["train"],
    eval_dataset=tokenized_datasets["validation"],
    data_collator=data_collator,
    tokenizer=tokenizer,
    compute_metrics=compute_metrics,
)
```

請注意，我們設置了了一個新的 **TrainingArguments** 它的**evaluation_strategy** 設置為 **epoch** 並創建了一個新模型。如果不創建新的模型就直接訓練，就只會繼續訓練之前我們已經訓練過的模型。要啟動新的訓練運行，我們執行：

```
trainer.train()
```

這一次，它將在訓練loss之外，還會輸出每個 epoch 結束時的驗證loss和指標。同樣，由於模型的隨機頭部初始化，您達到的準確率/F1 分數可能與我們發現的略有不同，但它應該在同一範圍內。

這 **Trainer** 將在多個 GPU 或 TPU 上開箱即用，並提供許多選項，例如混合精度訓練（在訓練的參數中使用 **fp16 = True** ）。我們將在第 10 章討論它支持的所有內容。

使用**Trainer** API微調的介紹到此結束。對最常見的 NLP 任務執行此操作的示例將在第 7 章中給出，但現在讓我們看看如何在純 PyTorch 中執行相同的操作。

<Tip>

✏️ **試試看!** 使用您在第 2 節中進行的數據處理，在 GLUE SST-2 數據集上微調模型。

</Tip>



<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter3/3.mdx" />

### End-of-chapter quiz
https://huggingface.co/learn/course/zh-TW/chapter3/6.md

# End-of-chapter quiz

<CourseFloatingBanner
    chapter={3}
    classNames="absolute z-10 right-0 top-0"
/>

Test what you learned in this chapter!

### 1.「情緒」數據集包含標記有情緒的 Twitter 消息。在[模型中心](https://huggingface.co/datasets)中搜索它，然後讀取數據集卡。哪一個不是它的基本情感？
<Question
	choices={[
		{
			text: "歡樂",
			explain: "再試一次ーー這種情緒出現在那個數據集中！"
		},
		{
			text: "愛",
			explain: "再試一次ーー這種情緒出現在那個數據集中！"
		},
		{
			text: "困惑",
			explain: "正確! 困惑不是六種基本情緒之一。",
            correct: true
		},
        {
			text: "驚喜",
			explain: "驚喜! 但不對！"
		}
	]}
/>

### 2.在[模型中心](https://huggingface.co/datasets)中搜索‘ ar _ sarcasm’數據集，它支持哪個任務？
<Question
	choices={[
		{
			text: "情緒分類",
			explain: "沒錯! 你可以感謝這些標籤。",
            correct: true
		},
		{
			text: "機器翻譯",
			explain: "不是這樣的ーー再看看 < a href =’https://huggingface.co/datasets/ar _ sarcasm’> dataset card </a > ！"
		},
		{
			text: "命名實體識別",
			explain: "不是這樣的ーー再看看 < a href =’https://huggingface.co/datasets/ar _ sarcasm’> dataset card </a > ！"
		},
        {
			text: "回答問題",
			explain: "Alas, this question was not answered correctly. 再試一次！"
		}
	]}
/>

### 3.BERT 模型期望如何處理一對句子？
<Question
	choices={[
		{
			text: "表示句子的記號1[ SEP ]表示句子的記號2",
			explain: "在創建批處理時確實意味著填充，但不意味著填充到整個數據集中的最大長度。"
		},
		{
			text: "表示句子的記號表示句子的記號",
			explain: "一個 < code > [ CLS ] </code > 特殊令牌在開始時是必需的，但是這不是唯一的事情！"
		},
		{
			text: "表示句子1[ SEP ]的符號表示句子2[ SEP ]",
			explain: "沒錯！",
            correct: true
		},
        {
			text: "表示句子1[ SEP ]的符號表示句子2",
			explain: "開頭需要一個 < code > [ CLS ] </code > 特殊標記，還需要一個 < code > [ SEP ] </code > 特殊標記來分隔兩個句子，但這還不是全部！"
		}
	]}
/>

{#if fw === 'pt'}
### 4.‘ Dataset.map ()’方法的好處是什麼？
<Question
	choices={[
		{
			text: "函數的結果是緩存的，因此如果我們重新執行代碼，不會花費任何時間。",
			explain: "這確實是這種方法的優點之一! 儘管它不是唯一的一個..。",
            correct: true
		},
		{
			text: "它可以應用多重處理，比在數據集的每個元素上應用函數更快。",
			explain: "這是這個方法的一個優雅的特性，但它不是唯一的一個！",
            correct: true
		},
		{
			text: "它只包含用於訓練的超參數。",
			explain: "這是這種方法的一個優點，儘管還有其他的優點！",
            correct: true
		},
	]}
/>

### 5.什麼是動態填充？
<Question
	choices={[
		{
			text: "就是將每個批處理的輸入填充到整個數據集中的最大長度。",
			explain: "在示例中，我們指定了保存模型及其檢查點的位置。請再試一次！"
		},
		{
			text: "這是當你填補你的輸入時，批處理創建，到最大長度的句子裡面的批處理。",
			explain: "沒錯！“動態”部分來自這樣一個事實，即每個批的大小是在創建時確定的，因此所有批可能具有不同的形狀。",
            correct: true
		},
		{
			text: "就是當您填充輸入時，使每個句子具有與數據集中的前一個相同的標記數量。",
			explain: "這是不正確的，而且由於我們在訓練過程中對數據集進行了洗牌，因此查看數據集中的順序是沒有意義的。"
		},
	]}
/>

### 6.校對函數的用途是什麼？
<Question
	choices={[
		{
			text: "它確保數據集中的所有序列具有相同的長度。",
			explain: "校對函數用於處理單個批處理，而不是整個數據集。此外，我們討論的是通用的校對函數，而不是特定的 < code > > DataCollatorWithPadding </code > 。"
		},
		{
			text: "它把所有的樣品一批一批地放在一起。",
			explain: "正確！ You can pass the collate function as an argument of a <code>DataLoader</code>. We used the <code>DataCollatorWithPadding</code> function, which pads all items in a batch so they have the same length.",
            correct: true
		},
		{
			text: "它預處理整個數據集。",
			explain: "這將是一個預處理函數，而不是校對函數。"
		},
        {
			text: "它截斷數據集中的序列。",
			explain: "校對函數用於處理單個批處理，而不是整個數據集。如果您對截斷感興趣，可以使用 < code > tokenizer </code > 的 < truncate </code > 參數。"
		}
	]}
/>

### 7.當你用一個預先訓練過的語言模型(例如‘ bert-base-uncased’)實例化一個‘ AutoModelForXxx’類，這個類對應於一個不同於它所被訓練的任務時會發生什麼？
<Question
	choices={[
		{
			text: "什麼都沒有，但是你得到了一個警告。",
			explain: "你確實得到了警告，但這還不是全部！"
		},
		{
			text: "丟棄預訓練模型的頭部，並插入一個新的頭部適合的任務。",
			explain: "這就是我們對 < code > Trainer </code > 所做的，而不是 Accelerate 庫。再試一次！",
            correct: true
		},
		{
			text: "丟棄預先訓練好的模型頭部。",
			explain: "Something else needs to happen. 再試一次！"
		},
        {
			text: "沒有，因為模型仍然可以針對不同的任務進行微調。",
			explain: "這個經過訓練的模特的頭沒有經過訓練來解決這個問題，所以我們應該丟掉這個頭！"
		}
	]}
/>

### 8.訓練爭論的目的是什麼？
<Question
	choices={[
		{
			text: "它包含了所有用於訓練和評估的超參數。",
			explain: "正確！",
            correct: true
		},
		{
			text: "它指定模型的大小。",
			explain: "模型大小是由模型配置定義的，而不是類 < code > TrainingArguments </code > 。"
		},
		{
			text: "它只包含用於評估的超參數。",
			explain: "In the example, we specified where the model and its checkpoints will be saved. 再試一次！"
		},
        {
			text: "您可以輕鬆地計算與數據集相關的指標。",
			explain: "In the example, we used an <code>evaluation_strategy</code> as well, so this impacts evaluation. 再試一次！"
		}
	]}
/>

### 9.為什麼要使用 Accelerate 庫？
<Question
	choices={[
		{
			text: "它提供了對更快的模型的訪問。",
			explain: "不，Accelerate 庫不提供任何模型。"
		},
		{
			text: "它提供了一個高級 API，因此我不必實現自己的培訓循環。",
			explain: "This is what we did with <code>Trainer</code>, not the 🤗 Accelerate library. 再試一次！"
		},
		{
			text: "它使我們的訓練循環工作在分佈式策略上",
			explain: "正確! 隨著加速，你的訓練循環將為多個 gpu 和 TPUs 工作。",
            correct: true
		},
        {
			text: "它提供了更多的優化功能。",
			explain: "不，Accelerate 庫不提供任何優化功能。"
		}
	]}
/>

{:else}
### 4.當你用一個預先訓練過的語言模型(例如‘ bert-base-uncased’)實例化一個‘ tfautoodelforxxx’類時，會發生什麼？
<Question
	choices={[
		{
			text: "什麼都沒有，但是你得到了一個警告。",
			explain: "你確實得到了警告，但這還不是全部！"
		},
		{
			text: "丟棄預訓練模型的頭部，並插入一個新的頭部適合的任務。",
			explain: "需要一個特殊的標記來分隔這兩個句子，但這不是唯一的事情！",
            correct: true
		},
		{
			text: "丟棄預先訓練好的模型頭部。",
			explain: "Something else needs to happen. 再試一次！"
		},
        {
			text: "沒有，因為模型仍然可以針對不同的任務進行微調。",
			explain: "這個經過訓練的模特的頭沒有經過訓練來解決這個問題，所以我們應該丟掉這個頭！"
		}
	]}
/>

### 5.來自“變壓器”的 TensorFlow 模型已經是 Keras 模型，這有什麼好處？
<Question
	choices={[
		{
			text: "該模型的工作在一個 TPU 的盒子。",
			explain: "差不多！還需要進行一些小的額外修改。例如，您需要運行 < code > TPUStrategy </code > scope 中的所有內容，包括模型的初始化。"
		},
		{
			text: "您可以利用現有的方法，如 < code > compile () </code > 、 < code > fit () < c/ode > 和 < code > predict () </code > 。",
			explain: "正確! 一旦你有了這些數據，在這些數據上進行培訓只需要很少的工作。",
            correct: true
		},
		{
			text: "你可以學習 Keras 和變形金剛。",
			explain: "沒錯，但我們要找的是別的東西:)",
			correct: true
		},
        {
			text: "困惑",
			explain: "Keras 幫助我們訓練和評估模型，而不是計算與數據集相關的度量。"
		}
	]}
/>

### 6.如何定義自己的定製度量？
<Question
	choices={[
		{
			text: "通過子類化 < code > tfkeras.metrics. Metric </code > 。",
			explain: "太好了！",
			correct: true
		},
		{
			text: "使用 Keras 函數 API。",
			explain: "再試一次！"
		},
		{
			text: "通過使用帶簽名的可調用 < code > metric _ fn (y _ true，y _ pred) </code > 。",
			explain: "正確！",
			correct: true
		},
        {
			text: "通過谷歌搜索。",
			explain: "這不是我們要找的答案，但它應該能幫助你找到答案。",
			correct: true
		}
	]}
/>

{/if}

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter3/6.mdx" />

### 使用 Keras 微調一個模型
https://huggingface.co/learn/course/zh-TW/chapter3/3_tf.md

# 使用 Keras 微調一個模型

<CourseFloatingBanner chapter={3}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/zh-CN/chapter3/section3_tf.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/zh-CN/chapter3/section3_tf.ipynb"},
]} />

完成上一節中的所有數據預處理工作後，您只剩下最後的幾個步驟來訓練模型。 但是請注意，`model.fit()` 命令在 CPU 上運行會非常緩慢。 如果您沒有GPU，則可以在 [Google Colab](https://colab.research.google.com/) 上使用免費的 GPU 或 TPU(需要梯子)。

這一節的代碼示例假設您已經執行了上一節中的代碼示例。 下面一個簡短的摘要，包含了在開始學習這一節之前您需要的執行的代碼：

```py
from datasets import load_dataset
from transformers import AutoTokenizer, DataCollatorWithPadding
import numpy as np

raw_datasets = load_dataset("glue", "mrpc")
checkpoint = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)


def tokenize_function(example):
    return tokenizer(example["sentence1"], example["sentence2"], truncation=True)


tokenized_datasets = raw_datasets.map(tokenize_function, batched=True)

data_collator = DataCollatorWithPadding(tokenizer=tokenizer, return_tensors="tf")

tf_train_dataset = tokenized_datasets["train"].to_tf_dataset(
    columns=["attention_mask", "input_ids", "token_type_ids"],
    label_cols=["labels"],
    shuffle=True,
    collate_fn=data_collator,
    batch_size=8,
)

tf_validation_dataset = tokenized_datasets["validation"].to_tf_dataset(
    columns=["attention_mask", "input_ids", "token_type_ids"],
    label_cols=["labels"],
    shuffle=False,
    collate_fn=data_collator,
    batch_size=8,
)
```

### 訓練模型

從🤗 Transformers 導入的 TensorFlow 模型已經是 Keras 模型。 下面的視頻是對 Keras 的簡短介紹。

<Youtube id="rnTGBy2ax1c"/>

這意味著，一旦我們有了數據，就需要很少的工作就可以開始對其進行訓練。

<Youtube id="AUozVp78dhk"/>

和[第二章](/course/chapter2)使用的方法一樣, 我們將使用二分類的 `TFAutoModelForSequenceClassification`類: 

```py
from transformers import TFAutoModelForSequenceClassification

model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)
```

您會注意到，與 [第二章](/course/chapter2) 不同的是，您在實例化此預訓練模型後會收到警告。 這是因為 BERT 沒有對句子對進行分類進行預訓練，所以預訓練模型的 head 已經被丟棄，而是插入了一個適合序列分類的新 head。 警告表明一些權重沒有使用（對應於丟棄的預訓練頭），而其他一些權重是隨機初始化的（新頭的權重）。 最後鼓勵您訓練模型，這正是我們現在要做的。

要在我們的數據集上微調模型，我們只需要在我們的模型上調用 `compile()` 方法，然後將我們的數據傳遞給 `fit()` 方法。 這將啟動微調過程（在 GPU 上應該需要幾分鐘）並輸出訓練loss，以及每個 epoch 結束時的驗證loss。

<Tip>

請注意🤗 Transformers 模型具有大多數 Keras 模型所沒有的特殊能力——它們可以自動使用內部計算的loss。 如果您沒有在 `compile()` 中設置損失函數，他們將默認使用內部計算的損失。 請注意，要使用內部損失，您需要將標籤作為輸入的一部分傳遞，而不是作為單獨的標籤（這是在 Keras 模型中使用標籤的正常方式）。 您將在課程的第 2 部分中看到這方面的示例，其中定義正確的損失函數可能很棘手。 然而，對於序列分類，標準的 Keras 損失函數可以正常工作，所以我們將在這裡使用它。

</Tip>

```py
from tensorflow.keras.losses import SparseCategoricalCrossentropy

model.compile(
    optimizer="adam",
    loss=SparseCategoricalCrossentropy(from_logits=True),
    metrics=["accuracy"],
)
model.fit(
    tf_train_dataset,
    validation_data=tf_validation_dataset,
)
```

<Tip warning={true}>

請注意這裡有一個非常常見的陷阱——你只是*可以*將損失的名稱作為字符串傳遞給 Keras，但默認情況下，Keras 會假設你已經對輸出應用了 softmax。 然而，許多模型在應用 softmax 之前就輸出，也稱為 *logits*。 我們需要告訴損失函數我們的模型是否經過了softmax，唯一的方法是直接調用它，而不是用字符串的名稱。

</Tip>


### 提升訓練的效果

<Youtube id="cpzq6ESSM5c"/>

如果您嘗試上面的代碼，它肯定會運行，但您會發現loss只是緩慢或零星地下降。 主要原因是*學習率*。 與loss一樣，當我們將優化器的名稱作為字符串傳遞給 Keras 時，Keras 會初始化該優化器具有所有參數的默認值，包括學習率。 但是，根據長期經驗，我們知道Transformer 模型更適合使用比 Adam 的默認值（1e-3）也寫成為 10 的 -3 次方，或 0.001，低得多的學習率。 5e-5 (0.00005) 比默認值大約低 20 倍，是一個更好的起點。

除了降低學習率，我們還有第二個技巧：我們可以慢慢降低學習率。在訓練過程中。 在文獻中，您有時會看到這被稱為 *decaying* 或 *annealing*學習率。 在 Keras 中，最好的方法是使用 *learning rate scheduler*。 一個好用的是`PolynomialDecay`——儘管有這個名字，但在默認設置下，它只是簡單地從初始值線性衰減學習率值在訓練過程中的最終值，這正是我們想要的。但是， 為了正確使用調度程序，我們需要告訴它訓練的次數。 我們將在下面為其計算“num_train_steps”。

```py
from tensorflow.keras.optimizers.schedules import PolynomialDecay

batch_size = 8
num_epochs = 3
# 訓練步數是數據集中的樣本數除以batch size再乘以 epoch。
# 注意這裡的tf_train_dataset是一個轉化為batch後的 tf.data.Dataset，
# 不是原來的 Hugging Face Dataset，所以它的 len() 已經是 num_samples // batch_size。
num_train_steps = len(tf_train_dataset) * num_epochs
lr_scheduler = PolynomialDecay(
    initial_learning_rate=5e-5, end_learning_rate=0.0, decay_steps=num_train_steps
)
from tensorflow.keras.optimizers import Adam

opt = Adam(learning_rate=lr_scheduler)
```

<Tip>

🤗 Transformers 庫還有一個 `create_optimizer()` 函數，它將創建一個具有學習率衰減的 `AdamW` 優化器。 這是一個便捷的方式，您將在本課程的後續部分中詳細瞭解。

</Tip>

現在我們有了全新的優化器，我們可以嘗試使用它進行訓練。 首先，讓我們重新加載模型，以重置我們剛剛進行的訓練運行對權重的更改，然後我們可以使用新的優化器對其進行編譯：

```py
import tensorflow as tf

model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)
loss = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)
model.compile(optimizer=opt, loss=loss, metrics=["accuracy"])
```

現在，我們再次進行fit：

```py
model.fit(tf_train_dataset, validation_data=tf_validation_dataset, epochs=3)
```

<Tip>

💡 如果您想在訓練期間自動將模型上傳到 Hub，您可以在 `model.fit()` 方法中傳遞 `PushToHubCallback`。 我們將在 [第四章](/course/chapter4/3) 中進行介紹

</Tip>

### 模型預測

<Youtube id="nx10eh4CoOs"/>


訓練和觀察的loss下降都非常好，但是如果我們想從訓練後的模型中獲得輸出，或者計算一些指標，或者在生產中使用模型呢？ 為此，我們可以使用`predict()` 方法。 這將返回模型的輸出頭的*logits*數值，每個類一個。

```py
preds = model.predict(tf_validation_dataset)["logits"]
```

我們可以將這些 logit 轉換為模型的類別預測，方法是使用 argmax 找到最高的 logit，它對應於最有可能的類別：

```py
class_preds = np.argmax(preds, axis=1)
print(preds.shape, class_preds.shape)
```

```python out
(408, 2) (408,)
```

現在，讓我們使用這些 `preds` 來計算一些指標！ 我們可以像加載數據集一樣輕鬆地加載與 MRPC 數據集相關的指標，這次使用的是 `evaluate.load()` 函數。 返回的對象有一個 `compute()` 方法，我們可以使用它來進行度量計算：

```py
import evaluate

metric = evaluate.load("glue", "mrpc")
metric.compute(predictions=class_preds, references=raw_datasets["validation"]["label"])
```

```python out
{'accuracy': 0.8578431372549019, 'f1': 0.8996539792387542}
```

您獲得的確切結果可能會有所不同，因為模型頭的隨機初始化可能會改變它獲得的指標。 在這裡，我們可以看到我們的模型在驗證集上的準確率為 85.78%，F1 得分為 89.97。 這些是用於評估 GLUE 基準的 MRPC 數據集結果的兩個指標。 [BERT 論文](https://arxiv.org/pdf/1810.04805.pdf) 中的表格報告了基本模型的 F1 分數為 88.9。 那是 `uncased` 模型，而我們目前使用的是 `cased` 模型，這解釋了為什麼我們會獲得更好的結果。

使用 Keras API 進行微調的介紹到此結束。 第 7 章將給出對大多數常見 NLP 任務執行此操作的示例。如果您想在 Keras API 上磨練自己的技能，請嘗試使第二節所使用的的數據處理在 GLUE SST-2 數據集上微調模型。

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter3/3_tf.mdx" />

### 一個完整的訓練
https://huggingface.co/learn/course/zh-TW/chapter3/4.md

# 一個完整的訓練

<CourseFloatingBanner chapter={3}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/zh-CN/chapter3/section4.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/zh-CN/chapter3/section4.ipynb"},
]} />

<Youtube id="Dh9CL8fyG80"/>

現在，我們將瞭解如何在不使用`Trainer`類的情況下獲得與上一節相同的結果。同樣，我們假設您已經學習了第 2 節中的數據處理。下面是一個簡短的總結，涵蓋了您需要的所有內容:

```py
from datasets import load_dataset
from transformers import AutoTokenizer, DataCollatorWithPadding

raw_datasets = load_dataset("glue", "mrpc")
checkpoint = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)


def tokenize_function(example):
    return tokenizer(example["sentence1"], example["sentence2"], truncation=True)


tokenized_datasets = raw_datasets.map(tokenize_function, batched=True)
data_collator = DataCollatorWithPadding(tokenizer=tokenizer)
```

### 訓練前的準備

在實際編寫我們的訓練循環之前，我們需要定義一些對象。第一個是我們將用於迭代批次的數據加載器。我們需要對我們的`tokenized_datasets`做一些處理，來處理`Trainer`自動為我們做的一些事情。具體來說，我們需要:

- 刪除與模型不期望的值相對應的列（如`sentence1`和`sentence2`列）。
- 將列名`label`重命名為`labels`（因為模型期望參數是`labels`）。
- 設置數據集的格式，使其返回 PyTorch 張量而不是列表。

針對上面的每個步驟，我們的 `tokenized_datasets` 都有一個方法:

```py
tokenized_datasets = tokenized_datasets.remove_columns(["sentence1", "sentence2", "idx"])
tokenized_datasets = tokenized_datasets.rename_column("label", "labels")
tokenized_datasets.set_format("torch")
tokenized_datasets["train"].column_names
```

然後，我們可以檢查結果中是否只有模型能夠接受的列:

```python
["attention_mask", "input_ids", "labels", "token_type_ids"]
```

至此，我們可以輕鬆定義數據加載器:

```py
from torch.utils.data import DataLoader

train_dataloader = DataLoader(
    tokenized_datasets["train"], shuffle=True, batch_size=8, collate_fn=data_collator
)
eval_dataloader = DataLoader(
    tokenized_datasets["validation"], batch_size=8, collate_fn=data_collator
)
```

為了快速檢驗數據處理中沒有錯誤，我們可以這樣檢驗其中的一個批次:

```py
for batch in train_dataloader:
    break
{k: v.shape for k, v in batch.items()}
```

```python out
{'attention_mask': torch.Size([8, 65]),
 'input_ids': torch.Size([8, 65]),
 'labels': torch.Size([8]),
 'token_type_ids': torch.Size([8, 65])}
```

請注意，實際的形狀可能與您略有不同，因為我們為訓練數據加載器設置了`shuffle=True`，並且模型會將句子填充到`batch`中的最大長度。

現在我們已經完全完成了數據預處理（對於任何 ML 從業者來說都是一個令人滿意但難以實現的目標），讓我們將注意力轉向模型。我們完全像在上一節中所做的那樣實例化它:

```py
from transformers import AutoModelForSequenceClassification

model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)
```
為了確保訓練過程中一切順利，我們將`batch`傳遞給這個模型:

```py
outputs = model(**batch)
print(outputs.loss, outputs.logits.shape)
```

```python out
tensor(0.5441, grad_fn=<NllLossBackward>) torch.Size([8, 2])
```

當我們提供 `labels` 時， 🤗 Transformers 模型都將返回這個`batch`的`loss`，我們還得到了 `logits`(`batch`中的每個輸入有兩個，所以張量大小為 8 x 2)。

我們幾乎準備好編寫我們的訓練循環了！我們只是缺少兩件事：優化器和學習率調度器。由於我們試圖自行實現 `Trainer`的功能，我們將使用相同的優化器和學習率調度器。`Trainer` 使用的優化器是 `AdamW` , 與 `Adam` 相同，但在權重衰減正則化方面有所不同(參見[“Decoupled Weight Decay Regularization”](https://arxiv.org/abs/1711.05101)作者:Ilya Loshchilov 和 Frank Hutter):

```py
from torch.optim import AdamW

optimizer = AdamW(model.parameters(), lr=5e-5)
```

最後，默認使用的學習率調度器只是從最大值 (5e-5) 到 0 的線性衰減。 為了定義它，我們需要知道我們訓練的次數，即所有數據訓練的次數(epochs)乘以的數據量（這是我們所有訓練數據的數量）。`Trainer`默認情況下使用三個`epochs`，因此我們定義訓練過程如下:

```py
from transformers import get_scheduler

num_epochs = 3
num_training_steps = num_epochs * len(train_dataloader)
lr_scheduler = get_scheduler(
    "linear",
    optimizer=optimizer,
    num_warmup_steps=0,
    num_training_steps=num_training_steps,
)
print(num_training_steps)
```

```python out
1377
```

### 訓練循環

最後一件事：如果我們可以訪問 GPU,我們將希望使用 GPU(在 CPU 上，訓練可能需要幾個小時而不是幾分鐘)。為此，我們定義了一個 `device`,它在GPU可用的情況下指向GPU 我們將把我們的模型和`batche`放在`device`上:

```py
import torch

device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
model.to(device)
device
```

```python out
device(type='cuda')
```

我們現在準備好訓練了！為了瞭解訓練何時結束，我們使用 `tqdm` 庫,在訓練步驟數上添加了一個進度條:

```py
from tqdm.auto import tqdm

progress_bar = tqdm(range(num_training_steps))

model.train()
for epoch in range(num_epochs):
    for batch in train_dataloader:
        batch = {k: v.to(device) for k, v in batch.items()}
        outputs = model(**batch)
        loss = outputs.loss
        loss.backward()

        optimizer.step()
        lr_scheduler.step()
        optimizer.zero_grad()
        progress_bar.update(1)
```

您可以看到訓練循環的核心與介紹中的非常相似。我們沒有要求任何檢驗，所以這個訓練循環不會告訴我們任何關於模型目前的狀態。我們需要為此添加一個評估循環。


### 評估循環

正如我們之前所做的那樣，我們將使用 🤗 Evaluate 庫提供的指標。我們已經瞭解了 `metric.compute()` 方法，當我們使用 `add_batch()`方法進行預測循環時，實際上該指標可以為我們累積所有 `batch` 的結果。一旦我們累積了所有 `batch` ，我們就可以使用 `metric.compute()` 得到最終結果 .以下是在評估循環中實現所有這些的方法:

```py
import evaluate

metric = evaluate.load("glue", "mrpc")
model.eval()
for batch in eval_dataloader:
    batch = {k: v.to(device) for k, v in batch.items()}
    with torch.no_grad():
        outputs = model(**batch)

    logits = outputs.logits
    predictions = torch.argmax(logits, dim=-1)
    metric.add_batch(predictions=predictions, references=batch["labels"])

metric.compute()
```

```python out
{'accuracy': 0.8431372549019608, 'f1': 0.8907849829351535}
```

同樣，由於模型頭部初始化和數據改組的隨機性，您的結果會略有不同，但它們應該在同一個範圍內。

<Tip>

✏️ **試試看！** 修改之前的訓練循環以在 SST-2 數據集上微調您的模型。

</Tip>

### S使用🤗 Accelerate加速您的訓練循環

<Youtube id="s7dy8QRgjJ0" />

我們之前定義的訓練循環在單個 CPU 或 GPU 上運行良好。但是使用[🤗 Accelerate](https://github.com/huggingface/accelerate)庫，只需進行一些調整，我們就可以在多個 GPU 或 TPU 上啟用分佈式訓練。從創建訓練和驗證數據加載器開始，我們的手動訓練循環如下所示：

```py
from torch.optim import AdamW
from transformers import AutoModelForSequenceClassification, get_scheduler

model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)
optimizer = AdamW(model.parameters(), lr=3e-5)

device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
model.to(device)

num_epochs = 3
num_training_steps = num_epochs * len(train_dataloader)
lr_scheduler = get_scheduler(
    "linear",
    optimizer=optimizer,
    num_warmup_steps=0,
    num_training_steps=num_training_steps,
)

progress_bar = tqdm(range(num_training_steps))

model.train()
for epoch in range(num_epochs):
    for batch in train_dataloader:
        batch = {k: v.to(device) for k, v in batch.items()}
        outputs = model(**batch)
        loss = outputs.loss
        loss.backward()

        optimizer.step()
        lr_scheduler.step()
        optimizer.zero_grad()
        progress_bar.update(1)
```

以下是變化:

```diff
+ from accelerate import Accelerator
  from torch.optim import AdamW
  from transformers import AutoModelForSequenceClassification, get_scheduler

+ accelerator = Accelerator()

  model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)
  optimizer = AdamW(model.parameters(), lr=3e-5)

- device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
- model.to(device)

+ train_dataloader, eval_dataloader, model, optimizer = accelerator.prepare(
+     train_dataloader, eval_dataloader, model, optimizer
+ )

  num_epochs = 3
  num_training_steps = num_epochs * len(train_dataloader)
  lr_scheduler = get_scheduler(
      "linear",
      optimizer=optimizer,
      num_warmup_steps=0,
      num_training_steps=num_training_steps
  )

  progress_bar = tqdm(range(num_training_steps))

  model.train()
  for epoch in range(num_epochs):
      for batch in train_dataloader:
-         batch = {k: v.to(device) for k, v in batch.items()}
          outputs = model(**batch)
          loss = outputs.loss
-         loss.backward()
+         accelerator.backward(loss)

          optimizer.step()
          lr_scheduler.step()
          optimizer.zero_grad()
          progress_bar.update(1)
```

要添加的第一行是導入`Accelerator`。第二行實例化一個 `Accelerator`對象 ，它將查看環境並初始化適當的分佈式設置。 🤗 Accelerate 為您處理數據在設備間的傳遞，因此您可以刪除將模型放在設備上的那行代碼（或者，如果您願意，可使用 `accelerator.device` 代替 `device` ）。

然後大部分工作會在將數據加載器、模型和優化器發送到的`accelerator.prepare()`中完成。這將會把這些對象包裝在適當的容器中，以確保您的分佈式訓練按預期工作。要進行的其餘更改是刪除將`batch`放在 `device` 的那行代碼（同樣，如果您想保留它，您可以將其更改為使用 `accelerator.device` ) 並將 `loss.backward()` 替換為`accelerator.backward(loss)`。

<Tip>
⚠️ 為了使雲端 TPU 提供的加速發揮最大的效益，我們建議使用標記器(tokenizer)的 `padding=max_length` 和 `max_length` 參數將您的樣本填充到固定長度。
</Tip>

如果您想複製並粘貼來直接運行，以下是 🤗 Accelerate 的完整訓練循環:

```py
from accelerate import Accelerator
from torch.optim import AdamW
from transformers import AutoModelForSequenceClassification, get_scheduler

accelerator = Accelerator()

model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)
optimizer = AdamW(model.parameters(), lr=3e-5)

train_dl, eval_dl, model, optimizer = accelerator.prepare(
    train_dataloader, eval_dataloader, model, optimizer
)

num_epochs = 3
num_training_steps = num_epochs * len(train_dl)
lr_scheduler = get_scheduler(
    "linear",
    optimizer=optimizer,
    num_warmup_steps=0,
    num_training_steps=num_training_steps,
)

progress_bar = tqdm(range(num_training_steps))

model.train()
for epoch in range(num_epochs):
    for batch in train_dl:
        outputs = model(**batch)
        loss = outputs.loss
        accelerator.backward(loss)

        optimizer.step()
        lr_scheduler.step()
        optimizer.zero_grad()
        progress_bar.update(1)
```

把這個放在 `train.py` 文件中，可以讓它在任何類型的分佈式設置上運行。要在分佈式設置中試用它，請運行以下命令:

```bash
accelerate config
```

這將詢問您幾個配置的問題並將您的回答轉儲到此命令使用的配置文件中:

```
accelerate launch train.py
```

這將啟動分佈式訓練

這將啟動分佈式訓練。如果您想在 Notebook 中嘗試此操作（例如，在 Colab 上使用 TPU 進行測試），只需將代碼粘貼到 `training_function()` 並使用以下命令運行最後一個單元格:

```python
from accelerate import notebook_launcher

notebook_launcher(training_function)
```

您可以在[🤗 Accelerate repo](https://github.com/huggingface/accelerate/tree/main/examples)找到更多的示例。


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter3/4.mdx" />

### 處理數據
https://huggingface.co/learn/course/zh-TW/chapter3/2.md

# 處理數據

{#if fw === 'pt'}

<CourseFloatingBanner chapter={3}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/zh-CN/chapter3/section2_pt.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/zh-CN/chapter3/section2_pt.ipynb"},
]} />

{:else}

<CourseFloatingBanner chapter={3}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/zh-CN/chapter3/section2_tf.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/zh-CN/chapter3/section2_tf.ipynb"},
]} />

{/if}

{#if fw === 'pt'}
這一小節學習[第一小節](/course/chapter2)中提到的「如何使用模型中心（hub）大型數據集」，下面是我們用模型中心的數據在 PyTorch 上訓練句子分類器的一個例子：

```python
import torch
from torch.optim import AdamW
from transformers import AutoTokenizer, AutoModelForSequenceClassification

# Same as before
checkpoint = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = AutoModelForSequenceClassification.from_pretrained(checkpoint)
sequences = [
    "I've been waiting for a HuggingFace course my whole life.",
    "This course is amazing!",
]
batch = tokenizer(sequences, padding=True, truncation=True, return_tensors="pt")

# This is new
batch["labels"] = torch.tensor([1, 1])

optimizer = AdamW(model.parameters())
loss = model(**batch).loss
loss.backward()
optimizer.step()
```
{:else}
這一小節學習[第一小節](/course/chapter2)中提到的「如何使用模型中心（hub）大型數據集」，下面是我們用模型中心的數據在 TensorFlow 上訓練句子分類器的一個例子：

```python
import tensorflow as tf
import numpy as np
from transformers import AutoTokenizer, TFAutoModelForSequenceClassification

# Same as before
checkpoint = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint)
sequences = [
    "I've been waiting for a HuggingFace course my whole life.",
    "This course is amazing!",
]
batch = dict(tokenizer(sequences, padding=True, truncation=True, return_tensors="tf"))

# This is new
model.compile(optimizer="adam", loss="sparse_categorical_crossentropy")
labels = tf.convert_to_tensor([1, 1])
model.train_on_batch(batch, labels)
```
{/if}

當然，僅僅用兩句話訓練模型不會產生很好的效果。為了獲得更好的結果，您需要準備一個更大的數據集。

在本節中，我們將使用MRPC（微軟研究釋義語料庫）數據集作為示例，該數據集由威廉·多蘭和克里斯·布羅克特在[這篇文章](https://www.aclweb.org/anthology/I05-5002.pdf)發佈。該數據集由5801對句子組成，每個句子對帶有一個標籤，指示它們是否為同義（即，如果兩個句子的意思相同）。我們在本章中選擇了它，因為它是一個小數據集，所以很容易對它進行訓練。

### 從模型中心（Hub）加載數據集

{#if fw === 'pt'}
<Youtube id="_BZearw7f0w"/>
{:else}
<Youtube id="W_gMJF0xomE"/>
{/if}

模型中心（hub）不只是包含模型；它也有許多不同語言的多個數據集。點擊[數據集](https://huggingface.co/datasets)的鏈接即可進行瀏覽。我們建議您在閱讀本節後閱讀一下[加載和處理新的數據集](https://huggingface.co/docs/datasets/loading)這篇文章，這會讓您對huggingface的darasets更加清晰。但現在，讓我們使用MRPC數據集中的[GLUE 基準測試數據集](https://gluebenchmark.com/)，它是構成MRPC數據集的10個數據集之一，這是一個學術基準，用於衡量機器學習模型在10個不同文本分類任務中的性能。

🤗 Datasets庫提供了一個非常便捷的命令，可以在模型中心（hub）上下載和緩存數據集。我們可以通過以下的代碼下載MRPC數據集：

```py
from datasets import load_dataset

raw_datasets = load_dataset("glue", "mrpc")
raw_datasets
```

```python out
DatasetDict({
    train: Dataset({
        features: ['sentence1', 'sentence2', 'label', 'idx'],
        num_rows: 3668
    })
    validation: Dataset({
        features: ['sentence1', 'sentence2', 'label', 'idx'],
        num_rows: 408
    })
    test: Dataset({
        features: ['sentence1', 'sentence2', 'label', 'idx'],
        num_rows: 1725
    })
})
```

正如你所看到的，我們獲得了一個**DatasetDict**對象，其中包含訓練集、驗證集和測試集。每一個集合都包含幾個列(**sentence1**, **sentence2**, **label**, and **idx**)以及一個代表行數的變量，即每個集合中的行的個數（因此，訓練集中有3668對句子，驗證集中有408對，測試集中有1725對）。

默認情況下，此命令在下載數據集並緩存到 **~/.cache/huggingface/dataset**. 回想一下第2章，您可以通過設置**HF_HOME**環境變量來自定義緩存的文件夾。

我們可以訪問我們數據集中的每一個**raw_train_dataset**對象，如使用字典：

```py
raw_train_dataset = raw_datasets["train"]
raw_train_dataset[0]
```

```python out
{'idx': 0,
 'label': 1,
 'sentence1': 'Amrozi accused his brother , whom he called " the witness " , of deliberately distorting his evidence .',
 'sentence2': 'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .'}
```

我們可以看到標籤已經是整數了，所以我們不需要對標籤做任何預處理。要知道哪個數字對應於哪個標籤，我們可以查看**raw_train_dataset**的**features**. 這將告訴我們每列的類型：

```py
raw_train_dataset.features
```

```python out
{'sentence1': Value(dtype='string', id=None),
 'sentence2': Value(dtype='string', id=None),
 'label': ClassLabel(num_classes=2, names=['not_equivalent', 'equivalent'], names_file=None, id=None),
 'idx': Value(dtype='int32', id=None)}
```

在上面的例子之中,**Label（標籤）** 是一種**ClassLabel（分類標籤）**，使用整數建立起到類別標籤的映射關係。**0**對應於**not_equivalent**，**1**對應於**equivalent**。

<Tip>

✏️ **試試看！** 查看訓練集的第15行元素和驗證集的87行元素。他們的標籤是什麼？

</Tip>

### 預處理數據集

{#if fw === 'pt'}
<Youtube id="0u3ioSwev3s"/>
{:else}
<Youtube id="P-rZWqcB6CE"/>
{/if}

為了預處理數據集，我們需要將文本轉換為模型能夠理解的數字。正如你在[第二章](/course/chapter2)上看到的那樣

```py
from transformers import AutoTokenizer

checkpoint = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
tokenized_sentences_1 = tokenizer(raw_datasets["train"]["sentence1"])
tokenized_sentences_2 = tokenizer(raw_datasets["train"]["sentence2"])
```

然而，在兩句話傳遞給模型，預測這兩句話是否是同義之前。我們需要這兩句話依次進行適當的預處理。幸運的是，標記器不僅僅可以輸入單個句子還可以輸入一組句子，並按照我們的BERT模型所期望的輸入進行處理：

```py
inputs = tokenizer("This is the first sentence.", "This is the second one.")
inputs
```

```python out
{ 
  'input_ids': [101, 2023, 2003, 1996, 2034, 6251, 1012, 102, 2023, 2003, 1996, 2117, 2028, 1012, 102],
  'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1],
  'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
}
```

我們在[第二章](/course/chapter2) 討論了**輸入詞id(input_ids)** 和 **注意力遮罩(attention_mask)** ，但我們在那個時候沒有討論**類型標記ID(token_type_ids)**。在這個例子中，**類型標記ID(token_type_ids)**的作用就是告訴模型輸入的哪一部分是第一句，哪一部分是第二句。

<Tip>

✏️ ** 試試看！** 選取訓練集中的第15個元素，將兩句話分別標記為一對。結果和上方的例子有什麼不同？

</Tip>

如果我們將**input_ids**中的id轉換回文字:

```py
tokenizer.convert_ids_to_tokens(inputs["input_ids"])
```

我們將得到：

```python out
['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]']
```

所以我們看到模型需要輸入的形式是 **[CLS] sentence1 [SEP] sentence2 [SEP]**。因此，當有兩句話的時候。**類型標記ID(token_type_ids)** 的值是：

```python out
['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]']
[      0,      0,    0,     0,       0,          0,   0,       0,      1,    1,     1,        1,     1,   1,       1]
```

如您所見，輸入中 **[CLS] sentence1 [SEP]** 它們的類型標記ID均為**0**，而其他部分，對應於**sentence2 [SEP]**，所有的類型標記ID均為**1**.

請注意，如果選擇其他的檢查點，則不一定具有**類型標記ID(token_type_ids)**（例如，如果使用DistilBERT模型，就不會返回它們）。只有當它在預訓練期間使用過這一層，模型在構建時依賴它們，才會返回它們。

用類型標記ID對BERT進行預訓練,並且使用[第一章](/course/chapter1)的遮罩語言模型，還有一個額外的應用類型，叫做下一句預測. 這項任務的目標是建立成對句子之間關係的模型。

在下一個句子預測任務中，會給模型輸入成對的句子（帶有隨機遮罩的標記），並被要求預測第二個句子是否緊跟第一個句子。為了提高模型的泛化能力，數據集中一半的兩個句子在原始文檔中挨在一起，另一半的兩個句子來自兩個不同的文檔。

一般來說，你不需要擔心是否有**類型標記ID(token_type_ids)**。在您的標輸入中：只要您對標記器和模型使用相同的檢查點，一切都會很好，因為標記器知道向其模型提供什麼。

現在我們已經瞭解了標記器如何處理一對句子，我們可以使用它對整個數據集進行處理：如[之前的章節](/course/chapter2)，我們可以給標記器提供一組句子，第一個參數是它第一個句子的列表，第二個參數是第二個句子的列表。這也與我們在[第二章](/course/chapter2)中看到的填充和截斷選項兼容. 因此，預處理訓練數據集的一種方法是：

```py
tokenized_dataset = tokenizer(
    raw_datasets["train"]["sentence1"],
    raw_datasets["train"]["sentence2"],
    padding=True,
    truncation=True,
)
```

這很有效，但它的缺點是返回字典（字典的鍵是**輸入詞id(input_ids)** ， **注意力遮罩(attention_mask)** 和 **類型標記ID(token_type_ids)**，字典的值是鍵所對應值的列表）。而且只有當您在轉換過程中有足夠的內存來存儲整個數據集時才不會出錯（而🤗數據集庫中的數據集是以[Apache Arrow](https://arrow.apache.org/)文件存儲在磁盤上，因此您只需將接下來要用的數據加載在內存中，因此會對內存容量的需求要低一些）。

為了將數據保存為數據集，我們將使用[Dataset.map()](https://huggingface.co/docs/datasets/package_reference/main_classes#datasets.Dataset.map)方法，如果我們需要做更多的預處理而不僅僅是標記化，那麼這也給了我們一些額外的自定義的方法。這個方法的工作原理是在數據集的每個元素上應用一個函數，因此讓我們定義一個標記輸入的函數：

```py
def tokenize_function(example):
    return tokenizer(example["sentence1"], example["sentence2"], truncation=True)
```

此函數的輸入是一個字典（與數據集的項類似），並返回一個包含**輸入詞id(input_ids)** ， **注意力遮罩(attention_mask)** 和 **類型標記ID(token_type_ids)** 鍵的新字典。請注意，如果像上面的**示例**一樣，如果鍵所對應的值包含多個句子（每個鍵作為一個句子列表），那麼它依然可以工作，就像前面的例子一樣標記器可以處理成對的句子列表。這樣的話我們可以在調用**map()**使用該選項 **batched=True** ，這將顯著加快標記與標記的速度。這個**標記器**來自[🤗 Tokenizers](https://github.com/huggingface/tokenizers)庫由Rust編寫而成。當我們一次給它大量的輸入時，這個標記器可以非常快。

請注意，我們現在在標記函數中省略了**padding**參數。這是因為在標記的時候將所有樣本填充到最大長度的效率不高。一個更好的做法：在構建批處理時填充樣本更好，因為這樣我們只需要填充到該批處理中的最大長度，而不是整個數據集的最大長度。當輸入長度變化很大時，這可以節省大量時間和處理能力!

下面是我們如何在所有數據集上同時應用標記函數。我們在調用**map**時使用了**batch =True**，這樣函數就可以同時應用到數據集的多個元素上，而不是分別應用到每個元素上。這將使我們的預處理快許多

```py
tokenized_datasets = raw_datasets.map(tokenize_function, batched=True)
tokenized_datasets
```

🤗Datasets 庫應用這種處理的方式是向數據集添加新的字段，每個字段對應預處理函數返回的字典中的每個鍵:

```python out
DatasetDict({
    train: Dataset({
        features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'],
        num_rows: 3668
    })
    validation: Dataset({
        features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'],
        num_rows: 408
    })
    test: Dataset({
        features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'],
        num_rows: 1725
    })
})
```

在使用預處理函數**map()**時，甚至可以通過傳遞**num_proc**參數使用並行處理。我們在這裡沒有這樣做，因為🤗標記器庫已經使用多個線程來更快地標記我們的樣本，但是如果您沒有使用該庫支持的快速標記器，使用**num_proc**可能會加快預處理。

我們的**標記函數(tokenize_function)**返回包含**輸入詞id(input_ids)** ， **注意力遮罩(attention_mask)** 和 **類型標記ID(token_type_ids)** 鍵的字典,所以這三個字段被添加到數據集的標記的結果中。注意，如果預處理函數**map()**為現有鍵返回一個新值，那將會修改原有鍵的值。

最後一件我們需要做的事情是，當我們一起批處理元素時，將所有示例填充到最長元素的長度——我們稱之為動態填充。

### 動態填充

<Youtube id="7q5NyFT8REg"/>

{#if fw === 'pt'}
負責在批處理中將數據整理為一個batch的函數稱為*collate函數*。它是你可以在構建**DataLoader**時傳遞的一個參數，默認是一個函數，它將把你的數據集轉換為PyTorch張量，並將它們拼接起來(如果你的元素是列表、元組或字典，則會使用遞歸)。這在我們的這個例子中下是不可行的，因為我們的輸入不是都是相同大小的。我們故意在之後每個batch上進行填充，避免有太多填充的過長的輸入。這將大大加快訓練速度，但請注意，如果你在TPU上訓練，這可能會導致問題——TPU喜歡固定的形狀，即使這需要額外的填充。

{:else}

負責在批處理中將數據整理為一個batch的函數稱為*collate函數*。它只會將您的樣本轉換為 tf.Tensor並將它們拼接起來(如果你的元素是列表、元組或字典，則會使用遞歸)。這在我們的這個例子中下是不可行的，因為我們的輸入不是都是相同大小的。我們故意在之後每個batch上進行填充，避免有太多填充的過長的輸入。這將大大加快訓練速度，但請注意，如果你在TPU上訓練，這可能會導致問題——TPU喜歡固定的形狀，即使這需要額外的填充。

{/if}

為了解決句子長度統一的問題，我們必須定義一個collate函數，該函數會將每個batch句子填充到正確的長度。幸運的是，🤗transformer庫通過**DataCollatorWithPadding**為我們提供了這樣一個函數。當你實例化它時，需要一個標記器(用來知道使用哪個詞來填充，以及模型期望填充在左邊還是右邊)，並將做你需要的一切:

{#if fw === 'pt'}
```py
from transformers import DataCollatorWithPadding

data_collator = DataCollatorWithPadding(tokenizer=tokenizer)
```
{:else}
```py
from transformers import DataCollatorWithPadding

data_collator = DataCollatorWithPadding(tokenizer=tokenizer, return_tensors="tf")
```
{/if}

為了測試這個新玩具，讓我們從我們的訓練集中抽取幾個樣本。這裡，我們刪除列**idx**, **sentence1**和**sentence2**，因為不需要它們，並查看一個batch中每個條目的長度:

```py
samples = tokenized_datasets["train"][:8]
samples = {k: v for k, v in samples.items() if k not in ["idx", "sentence1", "sentence2"]}
[len(x) for x in samples["input_ids"]]
```

```python out
[50, 59, 47, 67, 59, 50, 62, 32]
```

毫無疑問，我們得到了不同長度的樣本，從32到67。動態填充意味著該批中的所有樣本都應該填充到長度為67，這是該批中的最大長度。如果沒有動態填充，所有的樣本都必須填充到整個數據集中的最大長度，或者模型可以接受的最大長度。讓我們再次檢查**data_collator**是否正確地動態填充了這批樣本：

```py:

```py
batch = data_collator(samples)
{k: v.shape for k, v in batch.items()}
```

{#if fw === 'tf'}

```python out
{'attention_mask': TensorShape([8, 67]),
 'input_ids': TensorShape([8, 67]),
 'token_type_ids': TensorShape([8, 67]),
 'labels': TensorShape([8])}
```

{:else}

```python out
{'attention_mask': torch.Size([8, 67]),
 'input_ids': torch.Size([8, 67]),
 'token_type_ids': torch.Size([8, 67]),
 'labels': torch.Size([8])}
```

看起來不錯！現在，我們已經將原始文本轉化為了模型可以處理的數據，我們已準備好對其進行微調！

{/if}

<Tip>

✏️ ** 試試看！** 在GLUE SST-2數據集上應用預處理。它有點不同，因為它是由單個句子而不是成對的句子組成的，但是我們所做的其他事情看起來應該是一樣的。另一個更難的挑戰，請嘗試編寫一個可用於任何GLUE任務的預處理函數。

</Tip>

{#if fw === 'tf'}

現在我們有了 dataset 和 data collator，我們需要將 dataset 批次地應用 data collator。 我們可以手動載入批次並整理它們，但這需要大量工作，性能可能也不是很好。 相反，有一個簡單的方法可以為這個問題提供高效的解決方案：`to_tf_dataset()`。 這將在您的數據集上調用一個 `tf.data.Dataset`的方法，這個方法帶有一個可選的 data collator 功能。 `tf.data.Dataset` 是 Keras 可用於 `model.fit()` 的原生 TensorFlow 格式，因此這種方法會立即將🤗 Dataset 轉換為可用於訓練的格式。 讓我們看看它在我們的數據集上是如何使用的！

```py
tf_train_dataset = tokenized_datasets["train"].to_tf_dataset(
    columns=["attention_mask", "input_ids", "token_type_ids"],
    label_cols=["labels"],
    shuffle=True,
    collate_fn=data_collator,
    batch_size=8,
)

tf_validation_dataset = tokenized_datasets["validation"].to_tf_dataset(
    columns=["attention_mask", "input_ids", "token_type_ids"],
    label_cols=["labels"],
    shuffle=False,
    collate_fn=data_collator,
    batch_size=8,
)
```

就是這樣！ 我們可以將這些數據集帶入下一節，在經過所有艱苦的數據預處理工作之後，訓練將變得非常簡單。

{/if}


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter3/2.mdx" />

### 如何寫一個好問題
https://huggingface.co/learn/course/zh-TW/chapter8/5.md

# 如何寫一個好問題

<DocNotebookDropdown
  classNames="absolute z-10 right-0 top-0"
  options={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/chapter8/section5.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/chapter8/section5.ipynb"},
]} />

當您遇到 Hugging Face 庫中的一個看起來不正確的東西時，您一定要告訴我們，以便我們可以修復它（就此而言，任何開源庫也是如此）。如果您不能完全確定錯誤是在您自己的代碼中還是在我們的某個庫中，那麼首先要檢查的是[forums](https://discuss.huggingface.co/).社區會幫助你解決這個問題，Hugging Face 團隊也會密切關注那裡的討論。

<Youtube id="_PAli-V4wj0"/>

當您確定手頭有錯誤時，第一步是構建一個最小的可重現示例。
## 創建一個最小的可重現示例

隔離產生錯誤的代碼段非常重要，因為 Hugging Face 團隊中沒有人是魔術師（目前），他們無法修復他們看不到的東西。顧名思義，最小的可重現示例應該是可重現的。這意味著它不應依賴於您可能擁有的任何外部文件或數據。嘗試用一些看起來像真實值的虛擬值替換您正在使用的數據，但仍然會產生相同的錯誤。

<Tip>

🚨🤗 Transformers 存儲庫中的許多問題都沒有解決，因為用於複製它們的數據不可訪問。

</Tip>

一旦你有一些自包含的東西，你可以嘗試將它減少到更少的代碼行，構建我們所謂的最小的可重複示例.雖然這需要你做更多的工作，但如果你提供一個漂亮的、簡短的錯誤重現器，你幾乎可以保證得到幫助和修復。

如果您覺得足夠舒服，請檢查發生錯誤的源代碼。您可能會找到問題的解決方案（在這種情況下，您甚至可以提出拉取請求來修復它），但更一般地說，這可以幫助維護人員在閱讀您的報告時更好地理解來源。

## 填寫問題模板

當您提交問題時，您會注意到有一個模板需要填寫。我們將按照[🤗 Transformers issues](https://github.com/huggingface/transformers/issues/new/choose)在這裡，但是如果您在另一個存儲庫中報告問題，則需要相同類型的信息。不要將模板留空：花時間填寫它可以最大限度地提高您獲得答案和解決問題的機會。

通常，在提交問題時，請始終保持禮貌。這是一個開源項目，因此您使用的是免費軟件，沒有人有任何義務幫助您。您可能會在您的問題中包含您認為合理的批評，但是維護人員很可能會認為它很糟糕並且不會急於幫助您。確保你閱讀了[code of conduct](https://github.com/huggingface/transformers/blob/master/CODE_OF_CONDUCT.md)項目的。

### 包括您的環境信息

🤗 Transformers 提供了一個實用程序來獲取我們需要的關於您的環境的所有信息。只需在終端中輸入以下內容：

```
transformers-cli env
```

你應該得到這樣的東西：

```out
Copy-and-paste the text below in your GitHub issue and FILL OUT the two last points.

- `transformers` version: 4.12.0.dev0
- Platform: Linux-5.10.61-1-MANJARO-x86_64-with-arch-Manjaro-Linux
- Python version: 3.7.9
- PyTorch version (GPU?): 1.8.1+cu111 (True)
- Tensorflow version (GPU?): 2.5.0 (True)
- Flax version (CPU?/GPU?/TPU?): 0.3.4 (cpu)
- Jax version: 0.2.13
- JaxLib version: 0.1.65
- Using GPU in script?: <fill in>
- Using distributed or parallel set-up in script?: <fill in>
```

您還可以添加一個 **!** 在開始的時候 **transformers-cli env** 命令從筆記本單元執行它，然後在問題的開頭複製並粘貼結果。

### 標記人員

通過輸入標記人員 **@** 其次是他們的 GitHub 句柄將向他們發送通知，以便他們會看到您的問題並可能會更快地回覆。適度使用它，因為如果您標記的人沒有直接鏈接，他們可能不喜歡收到通知。如果您查看了與您的錯誤相關的源文件，您應該在您認為對您的問題負責的行中標記最後一個進行更改的人（您可以通過查看 GitHub 上的所述行找到此信息，選擇它，然後單擊“查看 git blame”）。

否則，模板會提供要標記的人的建議。一般來說，不要標記超過三個人！

### 包含一格可重複的示例

如果您已經設法創建了一個產生錯誤的獨立示例，那麼現在是包含它的時候了！鍵入一行包含三個反引號，後跟 **python** ， 像這樣：

```
```python
```

然後粘貼您的最小可重現示例並鍵入一個帶有三個反引號的新行。這將確保您的代碼格式正確。如果您沒有設法創建可重現的示例，請以清晰的步驟解釋您是如何解決問題的。如果可以，請包含指向錯誤所在的 Google Colab 筆記本的鏈接。你分享的信息越多，維護者就越有能力回覆你。在所有情況下，您都應該複製並粘貼您收到的整個錯誤消息。如果您在 Colab 中工作，請記住，堆棧跟蹤中的某些幀可能會自動摺疊，因此請確保在複製之前展開它們。與代碼示例一樣，將該錯誤消息放在兩行之間，並帶有三個反引號，因此格式正確。

### 描述預期行為

用幾行解釋你期望得到什麼，以便維護人員完全掌握問題。這部分通常很明顯，所以應該用一句話來形容，但在某些情況下，您可能有很多話要說。

## 然後什麼？

提交您的問題後，請確保快速檢查一切是否正常。如果您犯了錯誤，您可以編輯問題，或者如果您發現問題與您最初的想法不同，甚至可以更改其標題。如果你沒有得到答案，就沒有必要對人進行 ping 操作。如果幾天內沒有人幫助您，很可能沒有人能理解您的問題。不要猶豫，回到可重現的例子。你能不能讓它更短更切題？如果您在一週內沒有得到答覆，您可以留言溫和地尋求幫助，特別是如果您已編輯問題以包含有關該問題的更多信息。



<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter8/5.mdx" />

### 介紹
https://huggingface.co/learn/course/zh-TW/chapter8/1.md

# 介紹

既然您知道如何使用🤗 Transformers處理最常見的NLP任務，您應該能夠開始自己的項目了！在本章中，我們將探討遇到問題時該怎麼做。您將學習如何成功調試代碼或訓練，以及如果自己無法解決問題，如何向社區尋求幫助。如果您認為您在其中一個擁抱人臉庫中發現了錯誤，我們將向您展示報告它的最佳方式，以便儘快解決問題。

更準確地說，在本章中，您將學習：

- 出現錯誤時要做的第一件事
- 如何在網上尋求幫助[forums](https://discuss.huggingface.co/)
- 如何調試您的訓練管道
- 如何寫一個好問題

當然，所有這些都與 🤗 Transformers 或Hugging Face 生態無關；本章的經驗教訓適用於大多數開源項目！


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter8/1.mdx" />

### 在論壇上尋求幫助
https://huggingface.co/learn/course/zh-TW/chapter8/3.md

# 在論壇上尋求幫助

<DocNotebookDropdown
  classNames="absolute z-10 right-0 top-0"
  options={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/chapter8/section3.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/chapter8/section3.ipynb"},
]} />

<Youtube id="S2EEG3JIt2A"/>

[Hugging Face 論壇](https://discuss.huggingface.co) 是從開源團隊和更廣泛的 Hugging Face 社區獲得幫助的好地方。以下是論壇某一天的主頁面：

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter8/forums.png" alt="The Hugging Face forums." width="100%"/>
</div>

在左側，您可以看到各種主題分組的所有類別，而右側顯示了最新的主題。主題是包含標題、類別和描述的帖子；它與我們在創建自己的數據集時看到的 GitHub 問題格式非常相似[Chapter 5](/course/chapter5).顧名思義，[Beginners](https://discuss.huggingface.co/c/beginners/5)類別主要面向剛開始使用 Hugging Face 庫和生態系統的人。歡迎對任何庫提出任何問題，無論是調試一些代碼還是尋求有關如何做某事的幫助。 （也就是說，如果您的問題特別涉及某個圖書館，您可能應該前往論壇上的相應圖書館類別。）

同樣，the [Intermediate](https://discuss.huggingface.co/c/intermediate/6)和[Research](https://discuss.huggingface.co/c/research/7)類別用於更高級的問題，例如關於圖書館或您想討論的一些很酷的新 NLP 研究。

當然，我們也應該提到[Course](https://discuss.huggingface.co/c/course/20)類別，您可以在其中提出與 Hugging Face 課程相關的任何問題！

選擇類別後，您就可以編寫第一個主題了。 你可以找一些[guidelines](https://discuss.huggingface.co/t/how-to-request-support/3128) 在有關如何執行此操作的論壇中，在本節中，我們將看看構成一個好的主題的一些功能。

## 寫一篇好的論壇帖子

作為一個運行示例，假設我們試圖從 Wikipedia 文章生成嵌入表示以創建自定義搜索引擎。像往常一樣，我們按如下方式加載分詞器和模型：

```python
from transformers import AutoTokenizer, AutoModel

model_checkpoint = "distilbert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)
model = AutoModel.from_pretrained(model_checkpoint)
```

現在我們嘗試將[變形金剛的維基百科](https://en.wikipedia.org/wiki/Transformers)的一整段進行嵌入表示（熱知識：變形金剛的英文就是 Transformers ，而現在 Transformers 作為一個 🤗 Python 庫也被越來越多人熟知）：

```python
text = """
Generation One is a retroactive term for the Transformers characters that
appeared between 1984 and 1993. The Transformers began with the 1980s Japanese
toy lines Micro Change and Diaclone. They presented robots able to transform
into everyday vehicles, electronic items or weapons. Hasbro bought the Micro
Change and Diaclone toys, and partnered with Takara. Marvel Comics was hired by
Hasbro to create the backstory; editor-in-chief Jim Shooter wrote an overall
story, and gave the task of creating the characthers to writer Dennis O'Neil.
Unhappy with O'Neil's work (although O'Neil created the name "Optimus Prime"),
Shooter chose Bob Budiansky to create the characters.

The Transformers mecha were largely designed by Shōji Kawamori, the creator of
the Japanese mecha anime franchise Macross (which was adapted into the Robotech
franchise in North America). Kawamori came up with the idea of transforming
mechs while working on the Diaclone and Macross franchises in the early 1980s
(such as the VF-1 Valkyrie in Macross and Robotech), with his Diaclone mechs
later providing the basis for Transformers.

The primary concept of Generation One is that the heroic Optimus Prime, the
villainous Megatron, and their finest soldiers crash land on pre-historic Earth
in the Ark and the Nemesis before awakening in 1985, Cybertron hurtling through
the Neutral zone as an effect of the war. The Marvel comic was originally part
of the main Marvel Universe, with appearances from Spider-Man and Nick Fury,
plus some cameos, as well as a visit to the Savage Land.

The Transformers TV series began around the same time. Produced by Sunbow
Productions and Marvel Productions, later Hasbro Productions, from the start it
contradicted Budiansky's backstories. The TV series shows the Autobots looking
for new energy sources, and crash landing as the Decepticons attack. Marvel
interpreted the Autobots as destroying a rogue asteroid approaching Cybertron.
Shockwave is loyal to Megatron in the TV series, keeping Cybertron in a
stalemate during his absence, but in the comic book he attempts to take command
of the Decepticons. The TV series would also differ wildly from the origins
Budiansky had created for the Dinobots, the Decepticon turned Autobot Jetfire
(known as Skyfire on TV), the Constructicons (who combine to form
Devastator),[19][20] and Omega Supreme. The Marvel comic establishes early on
that Prime wields the Creation Matrix, which gives life to machines. In the
second season, the two-part episode The Key to Vector Sigma introduced the
ancient Vector Sigma computer, which served the same original purpose as the
Creation Matrix (giving life to Transformers), and its guardian Alpha Trion.
"""

inputs = tokenizer(text, return_tensors="pt")
logits = model(**inputs).logits
```

```python output
IndexError: index out of range in self
```

呃，我們遇到了一個問題——錯誤信息比我們看到的要神秘得多[section 2](/course/chapter8/section2)!我們無法確定完整回溯的正面或反面，因此我們決定轉向 Hugging Face 論壇尋求幫助。我們如何設計主題？

首先，我們需要點擊右上角的“新建主題”按鈕（注意，要創建主題，我們需要登錄）：

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter8/forums-new-topic.png" alt="Creating a new forum topic." width="100%"/>
</div>

這會出現一個寫作界面，我們可以在其中輸入我們的主題標題，選擇一個類別，並起草內容：

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter8/forum-topic01.png" alt="The interface for creating a forum topic." width="100%"/>
</div>

由於錯誤似乎僅與 🤗 Transformers有關，因此我們將為該類別選擇此錯誤。我們第一次嘗試解釋這個問題可能看起來像這樣：

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter8/forum-topic02.png" alt="Drafting the content for a new forum topic." width="100%"/>
</div>

儘管本主題包含我們需要幫助的錯誤消息，但其編寫方式存在一些問題：

1. 標題描述性不是很強，因此瀏覽論壇的任何人都無法在不閱讀正文的情況下分辨出主題的內容。

2. 正文沒有提供足夠的信息，說明錯誤來自何處以及如何重現錯誤。

3. 這個話題直接用一種有點苛刻的語氣標記了幾個人。

像這樣的主題不太可能很快得到答案（如果他們得到了答案），那麼讓我們看看如何改進它。我們將從選擇一個好標題的第一個問題開始。

### 選擇描述性標題

如果您想就代碼中的錯誤尋求幫助，一個好的經驗法則是在標題中包含足夠的信息，以便其他人可以快速確定他們是否認為他們可以回答您的問題。在我們的運行示例中，我們知道正在引發的異常的名稱，並有一些提示它是在模型的前向傳遞中觸發的，我們調用 **model(**inputs)** .為了傳達這一點，一個可能的標題可能是：

> 自動建模正向傳遞中的索引錯誤的來源？

這個標題告訴讀者在哪裡你認為錯誤來自，如果他們遇到了 **IndexError** 在此之前，他們很有可能知道如何調試它。當然，標題可以是您想要的任何內容，也可以是其他變體，例如：

> 為什麼我的模型會產生索引錯誤？

也可以。現在我們有了一個描述性的標題，讓我們來看看改善主體。

### 設置代碼段的格式

如：也可以。現在我們有了一個描述性的標題，讓我們來看看改善身體。在 IDE 中閱讀源代碼已經夠難的了，但是當將代碼複製粘貼為純文本時就更難了！幸運的是，Hugging Face 論壇支持使用 Markdown，因此您應該始終用三個反引號 (```) 將代碼塊括起來，以便更容易閱讀。讓我們這樣做來美化錯誤消息——在我們這樣做的時候，讓我們讓正文比我們的原始版本更有禮貌：

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter8/forum-topic03.png" alt="Our revised forum topic, with proper code formatting." width="100%"/>
</div>

正如您在屏幕截圖中看到的，將代碼塊括在反引號中會將原始文本轉換為格式化代碼，並帶有顏色樣式！另請注意，單個反引號可用於格式化內聯變量，就像我們所做的那樣 **distilbert-base-uncased** .這個主題看起來好多了，如果幸運的話，我們可能會在社區中找到可以猜測錯誤是什麼的人。然而，與其依靠運氣，不如讓我們在其完整的血腥細節中包含回溯，讓生活更輕鬆！

### 包括完整的回溯

由於回溯的最後一行通常足以調試您自己的代碼，因此很容易在您的主題中提供它以“節省空間”。雖然本意是好的，但這實際上使它更難供其他人調試問題，因為回溯中較高的信息也非常有用。因此，一個好的做法是複製並粘貼所有的回溯，同時確保它的格式很好。由於這些回溯可能會很長，有些人更喜歡在解釋了源代碼之後再展示它們。我們開工吧。現在，我們的論壇主題如下所示：

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter8/forum-topic04.png" alt="Our example forum topic, with the complete traceback." width="100%"/>
</div>

這提供了更多信息，細心的讀者可能會指出問題似乎是由於回溯中的這一行而傳遞了一個長輸入：

> 令牌索引序列長度長於為此模型指定的最大序列長度 （583 > 512）。

但是，通過提供觸發錯誤的實際代碼，我們可以讓他們更輕鬆。我們現在就這樣做。

### 提供可重複的示例

如果您曾經嘗試過調試其他人的代碼，那麼您可能首先嚐試重現他們報告的問題，以便您可以開始通過回溯來查明錯誤。在論壇上獲得（或提供）幫助時沒有什麼不同，所以如果你能提供一個重現錯誤的小例子真的很有幫助。有一半的時間，簡單地完成這個練習將幫助你找出問題所在。在任何情況下，我們的示例缺少的部分是顯示輸入我們提供給模型的。這樣做會為我們提供類似於以下完整示例的內容：

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter8/forum-topic05.png" alt="The final version of our forum topic." width="100%"/>
</div>

該主題現在包含相當多的信息，並且它的編寫方式更可能吸引社區的注意力並獲得有用的答案。有了這些基本指南，您現在可以創建很棒的主題來找到您的 🤗 Transformers問題的答案！



<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter8/3.mdx" />

### 第2部分完成！
https://huggingface.co/learn/course/zh-TW/chapter8/6.md

# 第2部分完成！

恭喜，您已經完成了課程的第二部分！我們正在積極研究第三個，所以訂閱我們的[newsletter](https://huggingface.curated.co/)以確保您不會錯過它的發佈。

。您現在應該能夠處理一系列 NLP 任務，並對它們進行微調或預訓練模型。不要忘記與社區分享您的結果[Model Hub](https://huggingface.co/models).

我們迫不及待地想看看您將利用所獲得的知識構建什麼！


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter8/6.mdx" />

### 調試訓練管道
https://huggingface.co/learn/course/zh-TW/chapter8/4.md

# 調試訓練管道

<DocNotebookDropdown
  classNames="absolute z-10 right-0 top-0"
  options={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/chapter8/section4.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/chapter8/section4.ipynb"},
]} />

你已經編寫了一個漂亮的腳本來訓練或微調給定任務的模型，盡職盡責地遵循 [Chapter 7](/course/chapter7) 中的建議。 但是當你啟動命令 `trainer.train()` 時，可怕的事情發生了：你得到一個錯誤😱！ 或者更糟糕的是，一切似乎都很好，訓練運行沒有錯誤，但生成的模型很糟糕。 在本節中，我們將向您展示如何調試此類問題。

## 調試訓練管道

<Youtube id="L-WSwUWde1U"/>

當您在 `trainer.train()` 中遇到錯誤時，它可能來自多個來源，因為 `Trainer` 通常會將很多東西放在一起組合運行。 它將datasets轉換為dataloaders，因此問題可能出在datasets中，或者在嘗試將datasets的元素一起批處理時出現問題。 然後它需要準備一批數據並將其提供給模型，因此問題可能出在模型代碼中。 之後，它會計算梯度並執行優化器，因此問題也可能出在您的優化器中。 即使訓練一切順利，如果您的評估指標有問題，評估期間仍然可能出現問題。

調試 `trainer.train()` 中出現的錯誤的最佳方法是手動檢查整個管道，看看哪裡出了問題。 錯誤通常很容易解決。

為了證明這一點，我們將使用以下腳本（嘗試）在 [MNLI 數據集](https://huggingface.co/datasets/glue)上微調 DistilBERT 模型：

```py
from datasets import load_dataset, load_metric
from transformers import (
    AutoTokenizer,
    AutoModelForSequenceClassification,
    TrainingArguments,
    Trainer,
)

raw_datasets = load_dataset("glue", "mnli")

model_checkpoint = "distilbert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)


def preprocess_function(examples):
    return tokenizer(examples["premise"], examples["hypothesis"], truncation=True)


tokenized_datasets = raw_datasets.map(preprocess_function, batched=True)
model = AutoModelForSequenceClassification.from_pretrained(model_checkpoint)

args = TrainingArguments(
    f"distilbert-finetuned-mnli",
    evaluation_strategy="epoch",
    save_strategy="epoch",
    learning_rate=2e-5,
    num_train_epochs=3,
    weight_decay=0.01,
)

metric = load_metric("glue", "mnli")


def compute_metrics(eval_pred):
    predictions, labels = eval_pred
    return metric.compute(predictions=predictions, references=labels)


trainer = Trainer(
    model,
    args,
    train_dataset=raw_datasets["train"],
    eval_dataset=raw_datasets["validation_matched"],
    compute_metrics=compute_metrics,
)
trainer.train()
```

如果你嘗試執行它，你會遇到一個相當神秘的錯誤：

```python out
'ValueError: You have to specify either input_ids or inputs_embeds'
```

### 檢查數據

這是不言而喻的，如果你的數據被破壞，“Trainer”將無法形成批次，更不用說訓練你的模型了。 所以首先，你需要看看你的訓練集中有什麼。

為了避免花費無數小時試圖檢查和修復不是錯誤來源的東西，我們建議您使用 `trainer.train_dataset` 進行檢查。 所以讓我們在這裡這樣做：

```py
trainer.train_dataset[0]
```

```python out
{'hypothesis': 'Product and geography are what make cream skimming work. ',
 'idx': 0,
 'label': 1,
 'premise': 'Conceptually cream skimming has two basic dimensions - product and geography.'}
```

你注意到有什麼不對嗎？ 與缺少有關 `input_ids` 的錯誤消息相結合，應該讓您意識到數據集裡是文本，而不是模型可以理解的數字。 在這個例子，輸出的原始錯誤信息非常具有誤導性，因為 `Trainer` 會自動刪除與模型簽名不匹配的列（即模型預期的參數）。 這意味著在這裡，除了標籤之外的所有東西都被丟棄了。 因此，創建批次然後將它們發送到模型沒有問題，而模型又抱怨它沒有收到正確的輸入。

為什麼沒有處理數據生成標記呢？ 我們確實在數據集上使用了“Dataset.map()”方法來對每個樣本應用標記器。 但是如果你仔細看代碼，你會發現我們在將訓練和評估集傳遞給`Trainer`時犯了一個錯誤。 我們在這裡沒有使用 `tokenized_datasets`，而是使用了 `raw_datasets` 🤦。 所以讓我們解決這個問題！

```py
from datasets import load_dataset, load_metric
from transformers import (
    AutoTokenizer,
    AutoModelForSequenceClassification,
    TrainingArguments,
    Trainer,
)

raw_datasets = load_dataset("glue", "mnli")

model_checkpoint = "distilbert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)


def preprocess_function(examples):
    return tokenizer(examples["premise"], examples["hypothesis"], truncation=True)


tokenized_datasets = raw_datasets.map(preprocess_function, batched=True)
model = AutoModelForSequenceClassification.from_pretrained(model_checkpoint)

args = TrainingArguments(
    f"distilbert-finetuned-mnli",
    evaluation_strategy="epoch",
    save_strategy="epoch",
    learning_rate=2e-5,
    num_train_epochs=3,
    weight_decay=0.01,
)

metric = load_metric("glue", "mnli")


def compute_metrics(eval_pred):
    predictions, labels = eval_pred
    return metric.compute(predictions=predictions, references=labels)


trainer = Trainer(
    model,
    args,
    train_dataset=tokenized_datasets["train"],
    eval_dataset=tokenized_datasets["validation_matched"],
    compute_metrics=compute_metrics,
)
trainer.train()
```

這個新代碼現在會給出一個不同的錯誤：

```python out
'ValueError: expected sequence of length 43 at dim 1 (got 37)'
```

查看traceback，我們可以看到錯誤發生在數據整理步驟中：

```python out
~/git/transformers/src/transformers/data/data_collator.py in torch_default_data_collator(features)
    105                 batch[k] = torch.stack([f[k] for f in features])
    106             else:
--> 107                 batch[k] = torch.tensor([f[k] for f in features])
    108 
    109     return batch
```

所以，我們應該去研究一下那個。 然而，在我們這樣做之前，讓我們完成檢查我們的數據， 先確定它100%是正確的。

在調試課程的內容時，您應該始終做的一件事是查看模型的解碼輸入。 我們無法理解直接提供給它的數字，所以我們應該看看這些數字代表什麼。 例如，在計算機視覺中，這意味著查看您傳遞的圖片像素的解碼，在語音中意味著解碼後的音頻樣本，對於我們的 NLP 示例，這意味著使用我們的標記器解碼的輸入：

```py
tokenizer.decode(trainer.train_dataset[0]["input_ids"])
```

```python out
'[CLS] conceptually cream skimming has two basic dimensions - product and geography. [SEP] product and geography are what make cream skimming work. [SEP]'
```

所以這似乎是正確的。 您應該對輸入中的所有鍵執行此操作：

```py
trainer.train_dataset[0].keys()
```

```python out
dict_keys(['attention_mask', 'hypothesis', 'idx', 'input_ids', 'label', 'premise'])
```

請注意，與模型接受的輸入不對應的鍵將被自動丟棄，因此這裡我們將僅保留 `input_ids`、`attention_mask` 和 `label`（將重命名為 `labels`）。 要仔細檢查模型輸入的列，您可以打印模型的類，然後查看其文檔：

```py
type(trainer.model)
```

```python out
transformers.models.distilbert.modeling_distilbert.DistilBertForSequenceClassification
```

所以在我們的例子中，我們在[在這個頁面](https://huggingface.co/transformers/model_doc/distilbert.html#distilbertforsequenceclassification)可以檢查上接受的參數。 `Trainer` 也會記錄它丟棄的列。

我們通過解碼檢查了輸入 ID 是否正確。 接下來是檢查 `attention_mask`：

```py
tokenizer.decode(trainer.train_dataset[0]["attention_mask"])
```

```python out
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
```

由於我們沒有在預處理中應用填充，這看起來非常自然。 為確保該注意掩碼沒有問題，讓我們檢查它與輸入 ID 的長度是否相同：

```py
len(trainer.train_dataset[0]["attention_mask"]) == len(
    trainer.train_dataset[0]["input_ids"]
)
```

```python out
True
```

那挺好的！ 最後，讓我們檢查一下我們的標籤：

```py
trainer.train_dataset[0]["label"]
```

```python out
1
```

與輸入 ID 一樣，這是一個本身並沒有真正意義的數字。 正如我們之前看到的，整數和標籤名稱之間的映射存儲在數據集相應 *feature* 的 `names` 屬性中：

```py
trainer.train_dataset.features["label"].names
```

```python out
['entailment', 'neutral', 'contradiction']
```

所以`1`表示`neutral`，表示我們上面看到的兩句話並不矛盾，也沒有包含關係。 這似乎是正確的！

我們這裡沒有令牌類型 ID，因為 DistilBERT 不需要它們； 如果您的模型中有一些，您還應該確保它們正確匹配輸入中第一句和第二句的位置。

<Tip>

✏️ **輪到你了！** 檢查訓練數據集的第二個元素是否正確。

</Tip>

我們在這裡只對訓練集進行檢查，但您當然應該以同樣的方式仔細檢查驗證集和測試集。

現在我們知道我們的數據集看起來不錯，是時候檢查訓練管道的下一步了。

### 從 datasets 到 dataloaders

訓練管道中可能出錯的下一件事是當“Trainer”嘗試從訓練或驗證集形成批次時。 一旦你確定 `Trainer` 的數據集是正確的，你可以嘗試通過執行以下操作手動形成一個批次（可以將 `train` 替換為 `eval` 用於驗證數據加載器）：

```py
for batch in trainer.get_train_dataloader():
    break
```

此代碼創建訓練數據加載器，然後對其進行迭代，在第一次迭代時停止。 如果代碼執行沒有錯誤，那麼您就有了可以檢查的第一個訓練批次，如果代碼出錯，您可以確定問題出在數據加載器中，如下所示：

```python out
~/git/transformers/src/transformers/data/data_collator.py in torch_default_data_collator(features)
    105                 batch[k] = torch.stack([f[k] for f in features])
    106             else:
--> 107                 batch[k] = torch.tensor([f[k] for f in features])
    108 
    109     return batch

ValueError: expected sequence of length 45 at dim 1 (got 76)
```

檢查trackback的最後一個堆棧的輸出應該足以給你一個線索，但讓我們做更多的挖掘。 批處理創建過程中的大多數問題是由於將示例整理到單個批處理中而出現的，因此在有疑問時首先要檢查的是您的 DataLoader 正在使用什麼 collate_fn：

```py
data_collator = trainer.get_train_dataloader().collate_fn
data_collator
```

```python out
<function transformers.data.data_collator.default_data_collator(features: List[InputDataClass], return_tensors='pt') -> Dict[str, Any]>
```

所以，目前使用的是 `default_data_collator`，但這不是我們在這種情況下想要的。 我們希望將示例填充到批處理中最長的句子，這是由 `DataCollatorWithPadding` 整理器完成的。 而這個數據收集器應該是默認被 `Trainer` 使用的，為什麼這裡沒有使用呢？

答案是因為我們沒有將 `tokenizer` 傳遞給 `Trainer`，所以它無法創建我們想要的 `DataCollatorWithPadding`。 在實踐中，您應該明確地傳遞您想要使用的數據整理器，以確保避免這些類型的錯誤。 讓我們調整我們的代碼來做到這一點：

```py
from datasets import load_dataset, load_metric
from transformers import (
    AutoTokenizer,
    AutoModelForSequenceClassification,
    DataCollatorWithPadding,
    TrainingArguments,
    Trainer,
)

raw_datasets = load_dataset("glue", "mnli")

model_checkpoint = "distilbert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)


def preprocess_function(examples):
    return tokenizer(examples["premise"], examples["hypothesis"], truncation=True)


tokenized_datasets = raw_datasets.map(preprocess_function, batched=True)
model = AutoModelForSequenceClassification.from_pretrained(model_checkpoint)

args = TrainingArguments(
    f"distilbert-finetuned-mnli",
    evaluation_strategy="epoch",
    save_strategy="epoch",
    learning_rate=2e-5,
    num_train_epochs=3,
    weight_decay=0.01,
)

metric = load_metric("glue", "mnli")


def compute_metrics(eval_pred):
    predictions, labels = eval_pred
    return metric.compute(predictions=predictions, references=labels)


data_collator = DataCollatorWithPadding(tokenizer=tokenizer)

trainer = Trainer(
    model,
    args,
    train_dataset=tokenized_datasets["train"],
    eval_dataset=tokenized_datasets["validation_matched"],
    compute_metrics=compute_metrics,
    data_collator=data_collator,
    tokenizer=tokenizer,
)
trainer.train()
```

好消息？ 我們沒有得到與以前相同的錯誤，這絕對是進步。 壞消息？ 我們得到了一個臭名昭著的 CUDA 錯誤：

```python out
RuntimeError: CUDA error: CUBLAS_STATUS_ALLOC_FAILED when calling `cublasCreate(handle)`
```

這很糟糕，因為 CUDA 錯誤通常很難調試。 我們稍後會看到如何解決這個問題，但首先讓我們完成對批處理創建的分析。

如果您確定您的數據整理器是正確的，則應嘗試將其應用於數據集的幾個樣本：

```py
data_collator = trainer.get_train_dataloader().collate_fn
batch = data_collator([trainer.train_dataset[i] for i in range(4)])
```

此代碼將失敗，因為 `train_dataset` 包含字符串列，`Trainer` 通常會刪除這些列。 您可以手動刪除它們，或者如果您想準確地修改 `Trainer` 在幕後所做的事情，您可以調用私有的 `Trainer._remove_unused_columns()` 方法來執行此操作：

```py
data_collator = trainer.get_train_dataloader().collate_fn
actual_train_set = trainer._remove_unused_columns(trainer.train_dataset)
batch = data_collator([actual_train_set[i] for i in range(4)])
```

如果錯誤仍然存在，您應該能夠手動調試數據整理器內部以確定具體的問題。

現在我們已經調試了批處理創建過程，是時候將數據傳遞給模型了！

### 檢查模型

您應該能夠通過執行以下命令來獲得一個批次的數據：

```py
for batch in trainer.get_train_dataloader():
    break
```

如果您在notebook中運行此代碼，您可能會收到與我們之前看到的類似的 CUDA 錯誤，在這種情況下，您需要重新啟動notebook並重新執行最後一個片段，而不運行 `trainer.train()` 行.這是關於 CUDA 錯誤的第二個最煩人的事情：它們會破壞您的Cuda內核，而且無法恢復。它們最煩人的事情是它們很難調試。

這是為什麼？它與 GPU 的工作方式有關。它們在並行執行大量操作方面非常有效，但缺點是當其中一條指令導致錯誤時，您不會立即知道。只有當程序在 GPU 上調用多個進程的同步處理時，它才會意識到出現問題，因此錯誤實際上是在與創建它的原因無關的地方引發的。例如，如果我們查看之前的trackback，錯誤是在向後傳遞期間引發的，但我們會在一分鐘內看到它實際上源於向前傳遞中的某些東西。

那麼我們如何調試這些錯誤呢？答案很簡單：我們沒有。除非您的 CUDA 錯誤是內存不足錯誤（這意味著您的 GPU 中沒有足夠的內存），否則您應該始終返回 CPU 進行調試。

為此，我們只需將模型放回 CPU 上並在我們的一批數據中調用它——“DataLoader”返回的那批數據尚未移動到 GPU：

```python
outputs = trainer.model.cpu()(**batch)
```

```python out
~/.pyenv/versions/3.7.9/envs/base/lib/python3.7/site-packages/torch/nn/functional.py in nll_loss(input, target, weight, size_average, ignore_index, reduce, reduction)
   2386         )
   2387     if dim == 2:
-> 2388         ret = torch._C._nn.nll_loss(input, target, weight, _Reduction.get_enum(reduction), ignore_index)
   2389     elif dim == 4:
   2390         ret = torch._C._nn.nll_loss2d(input, target, weight, _Reduction.get_enum(reduction), ignore_index)

IndexError: Target 2 is out of bounds.
```

所以，思路越來越清晰了。 我們現在在損失計算中沒有出現 CUDA 錯誤，而是有一個“IndexError”（因此與我們之前所說的反向傳播無關）。 更準確地說，我們可以看到是Target 2 造成了錯誤，所以這是檢查模型標籤數量的好時機：

```python
trainer.model.config.num_labels
```

```python out
2
```

有兩個標籤，只允許 0 和 1 作為目標，但是根據錯誤信息我們得到一個 2。得到一個 2 實際上是正常的：如果我們記得我們之前提取的標籤名稱，有三個，所以我們有索引 0 , 1 和 2 在我們的數據集中。 問題是我們沒有告訴我們的模型，它應該創建三個標籤。 所以讓我們解決這個問題！

```py
from datasets import load_dataset, load_metric
from transformers import (
    AutoTokenizer,
    AutoModelForSequenceClassification,
    DataCollatorWithPadding,
    TrainingArguments,
    Trainer,
)

raw_datasets = load_dataset("glue", "mnli")

model_checkpoint = "distilbert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)


def preprocess_function(examples):
    return tokenizer(examples["premise"], examples["hypothesis"], truncation=True)


tokenized_datasets = raw_datasets.map(preprocess_function, batched=True)
model = AutoModelForSequenceClassification.from_pretrained(model_checkpoint, num_labels=3)

args = TrainingArguments(
    f"distilbert-finetuned-mnli",
    evaluation_strategy="epoch",
    save_strategy="epoch",
    learning_rate=2e-5,
    num_train_epochs=3,
    weight_decay=0.01,
)

metric = load_metric("glue", "mnli")


def compute_metrics(eval_pred):
    predictions, labels = eval_pred
    return metric.compute(predictions=predictions, references=labels)


data_collator = DataCollatorWithPadding(tokenizer=tokenizer)

trainer = Trainer(
    model,
    args,
    train_dataset=tokenized_datasets["train"],
    eval_dataset=tokenized_datasets["validation_matched"],
    compute_metrics=compute_metrics,
    data_collator=data_collator,
    tokenizer=tokenizer,
)
```

我們還沒有包含 `trainer.train()` 行，以便花時間檢查一切是否正常。 如果我們請求一個批次的數據並將其傳遞給我們的模型，它現在可以正常工作了！

```py
for batch in trainer.get_train_dataloader():
    break

outputs = trainer.model.cpu()(**batch)
```

下一步是回到 GPU 並檢查一切是否仍然有效：

```py
import torch

device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
batch = {k: v.to(device) for k, v in batch.items()}

outputs = trainer.model.to(device)(**batch)
```

如果仍然出現錯誤，請確保重新啟動notebook並僅執行腳本的最後一個版本。

### 執行一個優化器步驟

現在我們知道我們可以構建實際通過模型檢查的成批次的數據，我們已經為訓練管道的下一步做好準備：計算梯度並執行優化步驟。

第一部分只是在 loss 上調用 `backward()` 方法：

```py
loss = outputs.loss
loss.backward()
```

在這個階段很少出現錯誤，但如果確實出現錯誤，請返回 CPU 以獲取有用的錯誤消息。

要執行優化步驟，我們只需要創建 `optimizer` 並調用它的 `step()` 方法：

```py
trainer.create_optimizer()
trainer.optimizer.step()
```

同樣，如果您在 `Trainer` 中使用默認優化器，則在此階段您不應該收到錯誤，但如果您有自定義優化器，則可能會出現一些問題需要在這裡調試。 如果您在此階段遇到奇怪的 CUDA 錯誤，請不要忘記返回 CPU。 說到 CUDA 錯誤，前面我們提到了一個特殊情況。 現在讓我們來看看。

### 處理 CUDA out-of-memory錯誤

每當您收到以`RuntimeError: CUDA out of memory`開頭的錯誤消息時，這表明您的 GPU 內存不足。 這與您的代碼沒有直接關聯，並且它可能發生在運行良好的代碼中。 此錯誤意味著您試圖在 GPU 的內部存儲器中放入太多東西，這導致了錯誤。 與其他 CUDA 錯誤一樣，您需要重新啟動內核才能再次運行訓練。

要解決這個問題，您只需要使用更少的 GPU 空間——這往往說起來容易做起來難。 首先，確保您沒有同時在 GPU 上運行兩個模型（當然，除非您的問題需要這樣做）。 然後，您可能應該減少batch的大小，因為它直接影響模型的所有中間輸出的大小及其梯度。 如果問題仍然存在，請考慮使用較小版本的模型。

<Tip>

在課程的下一部分中，我們將介紹更先進的技術，這些技術可以幫助您減少內存佔用並讓您微調最大的模型。

</Tip>

### 評估模型

現在我們已經解決了代碼的所有問題，一切都很完美，訓練應該可以順利進行，對吧？ 沒那麼快！ 如果你運行 `trainer.train()` 命令，一開始一切看起來都不錯，但過一會兒你會得到以下信息：

```py
# This will take a long time and error out, so you shouldn't run this cell
trainer.train()
```

```python out
TypeError: only size-1 arrays can be converted to Python scalars
```

您將意識到此錯誤出現在評估階段，因此這是我們需要調試的最後一件事。

您可以像這樣在訓練中獨立運行`Trainer`的評估循環：

```py
trainer.evaluate()
```

```python out
TypeError: only size-1 arrays can be converted to Python scalars
```

<Tip>

💡 您應該始終確保在啟動 `trainer.train()` 之前 `trainer.evaluate()`是可以運行的，以避免在遇到錯誤之前浪費大量計算資源。

</Tip>

在嘗試調試評估循環中的問題之前，您應該首先確保您已經查看了數據，能夠正確地形成批處理，並且可以在其上運行您的模型。 我們已經完成了所有這些步驟，因此可以執行以下代碼而不會出錯：

```py
for batch in trainer.get_eval_dataloader():
    break

batch = {k: v.to(device) for k, v in batch.items()}

with torch.no_grad():
    outputs = trainer.model(**batch)
```

稍等一會兒，錯誤出現，在評估階段結束時，如果我們查看trackback，我們會看到：

```python trace
~/git/datasets/src/datasets/metric.py in add_batch(self, predictions, references)
    431         """
    432         batch = {"predictions": predictions, "references": references}
--> 433         batch = self.info.features.encode_batch(batch)
    434         if self.writer is None:
    435             self._init_writer()
```

這告訴我們錯誤源自 `datasets/metric.py` 模塊——所以這是我們的 `compute_metrics()` 函數的問題。 它需要一個帶有 logits 和標籤的元組作為 NumPy 數組，所以讓我們嘗試輸入它：

```py
predictions = outputs.logits.cpu().numpy()
labels = batch["labels"].cpu().numpy()

compute_metrics((predictions, labels))
```

```python out
TypeError: only size-1 arrays can be converted to Python scalars
```

我們得到同樣的錯誤，所以問題肯定出在那個函數上。 如果我們回顧它的代碼，我們會發現它只是將“預測”和“真實的標籤”轉發到“metric.compute()”。 那麼這種方法有問題嗎？ 並不真地。 讓我們快速瀏覽一下形狀：

```py
predictions.shape, labels.shape
```

```python out
((8, 3), (8,))
```

我們的預測仍然是 logits，而不是實際的預測，這就是metrics返回這個（有點模糊）錯誤的原因。 修復很簡單； 我們只需要在 `compute_metrics()` 函數中添加一個 argmax：

```py
import numpy as np


def compute_metrics(eval_pred):
    predictions, labels = eval_pred
    predictions = np.argmax(predictions, axis=1)
    return metric.compute(predictions=predictions, references=labels)


compute_metrics((predictions, labels))
```

```python out
{'accuracy': 0.625}
```

現在我們的錯誤已修復！ 這是最後一個，所以我們的腳本現在將正確訓練模型。

作為參考，這裡是完全修正好的腳本：

```py
import numpy as np
from datasets import load_dataset, load_metric
from transformers import (
    AutoTokenizer,
    AutoModelForSequenceClassification,
    DataCollatorWithPadding,
    TrainingArguments,
    Trainer,
)

raw_datasets = load_dataset("glue", "mnli")

model_checkpoint = "distilbert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)


def preprocess_function(examples):
    return tokenizer(examples["premise"], examples["hypothesis"], truncation=True)


tokenized_datasets = raw_datasets.map(preprocess_function, batched=True)
model = AutoModelForSequenceClassification.from_pretrained(model_checkpoint, num_labels=3)

args = TrainingArguments(
    f"distilbert-finetuned-mnli",
    evaluation_strategy="epoch",
    save_strategy="epoch",
    learning_rate=2e-5,
    num_train_epochs=3,
    weight_decay=0.01,
)

metric = load_metric("glue", "mnli")


def compute_metrics(eval_pred):
    predictions, labels = eval_pred
    predictions = np.argmax(predictions, axis=1)
    return metric.compute(predictions=predictions, references=labels)


data_collator = DataCollatorWithPadding(tokenizer=tokenizer)

trainer = Trainer(
    model,
    args,
    train_dataset=tokenized_datasets["train"],
    eval_dataset=tokenized_datasets["validation_matched"],
    compute_metrics=compute_metrics,
    data_collator=data_collator,
    tokenizer=tokenizer,
)
trainer.train()
```

在這種情況下，如果沒有更多錯誤，我們的腳本將微調一個應該給出合理結果的模型。 但是，如果訓練沒有任何錯誤，而訓練出來的模型根本表現不佳，我們該怎麼辦？ 這是機器學習中最難的部分，我們將向您展示一些可以提供幫助的技術。

<Tip>

💡 如果您使用手動訓練循環，則相同的步驟也適用於調試訓練管道，而且更容易將它們分開。 但是，請確保您沒有忘記正確位置的 `model.eval()` 或 `model.train()`，或者每個步驟中的 `zero_grad()`！

</Tip>

## 在訓練期間調試靜默（沒有任何錯誤提示）錯誤

我們可以做些什麼來調試一個沒有錯誤地完成但沒有得到好的結果的訓練？ 我們會在這裡給你一些提示，但請注意，這種調試是機器學習中最難的部分，並且沒有神奇的答案。

### 檢查您的數據（再次！）

只有在理論上可以從您的數據中學到任何東西時，您的模型才會學到一些東西。 如果存在損壞數據的錯誤或標籤是隨機屬性的，那麼您很可能不會在數據集上獲得任何知識。 因此，始終首先仔細檢查您的解碼輸入和標籤，然後問自己以下問題：

- 解碼後的數據是否可以理解？
- 你認同這些標籤嗎？
- 有沒有一個標籤比其他標籤更常見？
- 如果模型預測隨機的答案/總是相同的答案，那麼loss/評估指標應該是多少？

<Tip warning={true}>

⚠️ 如果您正在進行分佈式訓練，請在每個過程中打印數據集的樣本，並三次檢查您是否得到相同的結果。 一個常見的錯誤是在數據創建中有一些隨機性來源，這使得每個進程都有不同版本的數據集。

</Tip>

查看您的數據後，查看模型的一些預測並對其進行解碼。 如果模型總是預測同樣的事情，那可能是因為你的數據集偏向一個類別（針對分類問題）； 過採樣稀有類等技術可能會有所幫助。

如果您在初始模型上獲得的loss/評估指標與您期望的隨機預測的loss/評估指標非常不同，請仔細檢查您的loss或評估指標的計算方式，因為那裡可能存在錯誤。 如果您使用最後添加的多個loss，請確保它們具有相同的規模。

當您確定您的數據是完美的時，您可以通過一個簡單的測試來查看模型是否能夠對其進行訓練。

### 在一批上過度擬合你的模型

過度擬合通常是我們在訓練時儘量避免的事情，因為這意味著模型沒有學習識別我們想要的一般特徵，而只是記住了訓練樣本。 在這種情況下，一遍又一遍地嘗試在一個批次上訓練您的模型是一個很好的測試，可以檢查您的問題是否可以通過您嘗試訓練的模型來解決。 它還將幫助您查看您的初始學習率是否太高。

一旦你定義了你的 `Trainer` 之後，這樣做真的很容易； 只需獲取一批訓練數據，然後僅使用該批次運行一個小型手動訓練循環，大約 20 步：

```py
for batch in trainer.get_train_dataloader():
    break

batch = {k: v.to(device) for k, v in batch.items()}
trainer.create_optimizer()

for _ in range(20):
    outputs = trainer.model(**batch)
    loss = outputs.loss
    loss.backward()
    trainer.optimizer.step()
    trainer.optimizer.zero_grad()
```

<Tip>

💡 如果您的訓練數據不平衡，請確保構建一批包含所有標籤的訓練數據。

</Tip>

生成的模型在一個“批次”上應該有接近完美的結果。 讓我們計算結果預測的指標：

```py
with torch.no_grad():
    outputs = trainer.model(**batch)
preds = outputs.logits
labels = batch["labels"]

compute_metrics((preds.cpu().numpy(), labels.cpu().numpy()))
```

```python out
{'accuracy': 1.0}
```

100% 準確率，現在這是一個很好的過擬合示例（這意味著如果你在任何其他句子上嘗試你的模型，它很可能會給你一個錯誤的答案）！

如果你沒有設法讓你的模型獲得這樣的完美結果，這意味著你構建問題或數據的方式有問題，所以你應該修復它。 只有當你可以通過過擬合測試時，你才能確定你的模型實際上可以學到一些東西。

<Tip warning={true}>

⚠️ 在此測試之後，您將不得不重新創建您的模型和“Trainer”，因為獲得的模型可能無法在您的完整數據集上恢復和學習有用的東西。

</Tip>

### 在你有第一個基線之前不要調整任何東西

超參數調優總是被強調為機器學習中最難的部分，但這只是幫助您在指標上有所收穫的最後一步。 大多數情況下，`Trainer` 的默認超參數可以很好地為您提供良好的結果，因此在您獲得超出數據集基線的東西之前，不要開始進行耗時且昂貴的超參數搜索 .

一旦你有一個足夠好的模型，你就可以開始稍微調整一下。 不要嘗試使用不同的超參數啟動一千次運行，而是比較一個超參數的不同值的幾次運行，以瞭解哪個影響最大。

如果您正在調整模型本身，不要嘗試任何您無法合理證明的事情。 始終確保您返回過擬合測試以驗證您的更改沒有產生任何意外後果。

### 請求幫忙

希望您會在本節中找到一些可以幫助您解決問題的建議，但如果不是這樣，請記住您可以隨時在 [論壇](https://discuss.huggingface.co/) 上向社區提問。

以下是一些可能有用的額外資源：

- [“作為工程最佳實踐工具的再現性”](https://docs.google.com/presentation/d/1yHLPvPhUs2KGI5ZWo0sU-PKU3GimAk3iTsI38Z-B5Gw/edit#slide=id.p)，作者：Joel Grus
- [“神經網絡調試清單”](https://towardsdatascience.com/checklist-for-debugging-neural-networks-d8b2a9434f21) 作者：Cecelia Shao
- [“如何對機器學習代碼進行單元測試”](https://medium.com/@keeper6928/how-to-unit-test-machine-learning-code-57cf6fd81765) by Chase Roberts
- [“訓練神經網絡的秘訣”](http://karpathy.github.io/2019/04/25/recipe/)作者：Andrej Karpathy

當然，並不是你在訓練神經網絡時遇到的每一個問題都是你自己的錯！ 如果您在 🤗 Transformers 或 🤗 Datasets 庫中遇到看起來不正確的內容，您可能遇到了錯誤。 你應該告訴我們這一切，在下一節中，我們將準確解釋如何做到這一點。


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter8/4.mdx" />

### 章末測評
https://huggingface.co/learn/course/zh-TW/chapter8/7.md

# 章末測評

讓我們測試一下你在本章學到的東西!

### 1.應該按照什麼順序讀取 Python 回溯？
<Question
	choices={[
		{
			text: "從上到下",
			explain: "再試一次——儘管大多數其他編程語言在頂部打印異常，但 Python 在這方面是特殊的。"
		},
		{
			text: "自下而上",
			explain: "正確！在底部顯示異常的 Python 回溯的一個優點是，在終端中工作時更容易調試，這是你看到的最後一行。",
			correct: true
		}
	]}
/>

### 2.什麼是最小可再生示例？
<Question
	choices={[
		{
			text: "來自一篇研究文章一個簡單的 Transformer 體系結構的實現",
			explain: "雖然從頭開始實現你自己的 Transformer 模型是非常有教育意義的，但這不是我們在這裡討論的內容。"
		},
		{
			text: "一種緊湊且自包含的代碼塊，可以在不依賴於私有文件或數據的情況下運行",
			explain: "正確！查看你的數據幾乎總是你應該做的第一件事，以確保文本被適當地編碼，具有預期的特性，等等。",
			correct: true
		},
		{
			text: "Python 回溯的屏幕截圖",
			explain: "再試一次 -- 儘管在提交問題時, 很容易包含您所面臨錯誤的屏幕截圖, 但這使得其他人很難重現錯誤。"
		},
		{
			text: "一個記錄整個分析的筆記本，包括與錯誤無關的部分",
			explain: "不完全 -- 儘管共享一個顯示錯誤的GoogleColab筆記本會很有幫助, 但要確保它簡短, 並且只包含相關代碼。 "
		}
	]}
/>

### 3.假設你嘗試運行以下代碼，它拋出一個錯誤:
```py
from transformers import GPT3ForSequenceClassification

# ImportError: cannot import name 'GPT3ForSequenceClassification' from 'transformers' (/Users/lewtun/miniconda3/envs/huggingface/lib/python3.8/site-packages/transformers/__init__.py)
# ---------------------------------------------------------------------------
# ImportError                               Traceback (most recent call last)
# /var/folders/28/k4cy5q7s2hs92xq7_h89_vgm0000gn/T/ipykernel_30848/333858878.py in <module>
# ----> 1 from transformers import GPT3ForSequenceClassification

# ImportError: cannot import name 'GPT3ForSequenceClassification' from 'transformers' (/Users/lewtun/miniconda3/envs/huggingface/lib/python3.8/site-packages/transformers/__init__.py)
```

以下哪項可能是論壇主題標題尋求幫助的好選擇?

<Question
	choices={[
		{
			text: "導入錯誤:無法從'transformers'導入名稱'GPT3GPT3ForSequenceClassification'(/Users/lewtun/miniconda3/envs/huggingface/lib/python3.8/site-packages/transformers/_ init _. py) ",
			explain: "包含回溯的最後一行可以是描述性的，但這最好保留給主題的主體部分。再試一次！"
		},
		{
			text: "從transformers導入 gpt3forsequeclassification 時出現問題",
			explain: "再試一次——儘管這提供了有用的信息，但最好還是保留在文本的主體部分。",
		},
		{
			text: "為什麼我不能導入 < code > GPT3ForSequenceClassification </code > ?",
			explain: "不錯的選擇！這個標題是簡潔的，並給讀者一個線索，什麼可能是錯誤的(即,gpt-3不支持在🤗 Transformers)。",
			correct: true
		},
		{
			text: "Gpt-3在🤗 Transformers中支持嗎?",
			explain: "好主意! 用問題作為主題標題是向社區傳達問題的好方法。",
			correct: true
		}
	]}
/>

### 4.假設您試圖運行 'trainer.train ()'，但是遇到了一個神秘的錯誤，這個錯誤不能準確地告訴您錯誤來自哪裡。下列哪一項是您應該首先在您的培訓管道中尋找錯誤的地方？
<Question
	choices={[
		{
			text: "計算梯度並執行反向傳播的優化步驟",
			explain: "儘管優化器中可能存在缺陷, 但這通常是訓練管道中的幾個步驟, 因此首先要檢查其他事項。再試一次!"
		},
		{
			text: "計算度量的評估步驟",
			explain: "評估通常是在整個訓練期結束後進行的, 因此你應該首先在訓練管道的早期檢查某個地方。",
		},
		{
			text: "數據集",
			explain: "正確的!查看數據幾乎總是您應該做的第一件事, 以確保文本進行了適當的編碼, 具有預期的特性, 等等。",
			correct: true
		},
		{
			text: "數據記錄器",
			explain: "再試一次---- 這非常接近你應該檢查的第一件事。你還記得我們交給數據收集者的是什麼東西嗎？"
		}
	]}
/>

### 5.調試 CUDA 錯誤的最好方法是什麼？
<Question
	choices={[
		{
			text: "在論壇或 GitHub 上發佈錯誤消息。",
			explain: "這不會幫助任何人, 因為 CUDA 的錯誤信息通常是非常沒有信息。"
		},
		{
			text: "在 CPU 上執行相同的代碼。",
			explain: "沒錯, 這應該會給你一個更好的錯誤消息!",
			correct: true
		},
		{
			text: "請閱讀回溯以找出錯誤的原因。",
			explain: "對於任何其他錯誤, 你都會這樣做, 但CUDA錯誤通常不會在發生的地方引發, 因為大多數CUDA操作都是異步的。"
		},
		{
			text: "減少批量。",
			explain: "減少批處理大小通常是處理CUDA內存不足錯誤的一個好策略, 但不是為這個特定的問題。再試一次！"
		},
		{
			text: "重新啟動 Jupyter 內核。",
			explain: "再試一次——重新啟動內核不會讓錯誤神奇地消失！",
		}
	]}
/>

### 6.修復 GitHub 上的問題最好的方法是什麼？
<Question
	choices={[
		{
			text: "發佈這個 bug 的完整可重複的例子。",
			explain: "是的，這是幫助維護人員找到 bug 的最好方法。你還應該做什麼？？",
			correct: true
		},
		{
			text: "每天要求更新。",
			explain: "這不太可能給你任何幫助；人們可能會更忽視你。",
		},
		{
			text: "檢查錯誤周圍的源代碼，並試圖找出錯誤發生的原因。在這期雜誌上發佈結果。",
			explain: "這肯定會對維護人員有幫助！如果您確實找到了 bug 的來源和修復程序, 甚至可以打開一個 pull 請求。你還應該做些什麼？",
			correct: true
		}
	]}
/>

### 7.為什麼對一個批處理進行過度調試通常是一種好的調試技術？
<Question
	choices={[
		{
			text: "不是這樣的, 過分合身總是不好的, 應該避免。",
			explain: "當對整個數據集進行訓練時, 過度擬合確實可能是一個信號, 表明你的模型不能很好地推廣到新的示例。但是, 對於調試, 我們通常不會對整個數據集進行訓練。再試一次！"
		},
		{
			text: "這使我們能夠驗證該模型能夠將損耗降低到零。",
			explain: "正確! 只需要兩個樣本的小批量, 我們就可以快速驗證模型是否具有學習能力。",
			correct: true
		},
		{
			text: "它允許我們驗證我們輸入和標籤的張量形狀是正確的。",
			explain: "再試一次 -- 如果你的張量形狀不對齊, 那麼你肯定不能訓練, 即使是一批。",
		}
	]}
/>

### 8.為什麼在 🤗 Transformers 存儲庫中創建新問題時，使用 transformers-cli env 包含有關計算環境的詳細信息是個好主意？
<Question
	choices={[
		{
			text: "它允許維護人員理解你正在使用的庫的哪個版本。",
			explain: "正確! 由於庫的每個主要版本在API中都可能有更改, 因此瞭解您正在使用的特定版本有助於縮小問題範圍。其他好處是什麼?",
			correct: true
		},
		{
			text: "它讓維護人員知道你是在 Windows、 macOS 還是 Linux 上運行代碼。",
			explain: "正確！錯誤有時可能是由你正在使用的特定操作系統引起的, 瞭解這一點有助於維護人員在本地複製這些錯誤。但這並不是唯一的原因。",
			correct: true
		},
		{
			text: "它允許維護人員知道你是在 GPU 還是 CPU 上運行代碼。",
			explain: "正確! 正如我們在本章中看到的, gpu 和 cpu 上的錯誤在風格上可能有很大的不同, 瞭解您正在使用的硬件可以幫助集中維護人員的注意力。但這不是唯一的好處...",
		}
	]}
/>

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter8/7.mdx" />

### Debugging the training pipeline
https://huggingface.co/learn/course/zh-TW/chapter8/4_tf.md

# Debugging the training pipeline

<DocNotebookDropdown
  classNames="absolute z-10 right-0 top-0"
  options={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/chapter8/section4_tf.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/chapter8/section4_tf.ipynb"},
]} />

你已經編寫了一個漂亮的腳本來訓練或微調給定任務的模型，盡職盡責地遵循 [第七章](/course/chapter7) 中的建議。 但是當你啟動命令 `model.fit()` 時，可怕的事情發生了：你得到一個錯誤😱！ 或者更糟糕的是，一切似乎都很好，訓練運行沒有錯誤，但生成的模型很糟糕。 在本節中，我們將向您展示如何調試此類問題。

## Debugging the training pipeline

<Youtube id="N9kO52itd0Q"/>

The problem when you encounter an error in `model.fit()` is that it could come from multiple sources, as training usually brings together a lot of things that you've been working on up until that point. The problem could be something wrong in your dataset, or some issue when trying to batch elements of the datasets together. Or it could be something wrong in the model code, or your loss function or optimizer. And even if everything goes well for training, something could still go wrong during the evaluation if there is a problem with your metric.

The best way to debug an error that arises in `model.fit()` is to manually go through this whole pipeline to see where things went awry. The error is then often very easy to solve.

To demonstrate this, we will use the following script that (tries to) fine-tune a DistilBERT model on the [MNLI dataset](https://huggingface.co/datasets/glue):

當您在 `model.fit()` 中遇到錯誤時，問題在於它可能來自多個來源，因為訓練通常會彙集很多您在此之前一直在做的事情。 問題可能是您的數據集中有問題，或者是在嘗試將數據集的元素批處理在一起時出現問題。 或者模型代碼、損失函數或優化器中可能有問題。 即使訓練一切順利，如果您的指標有問題，評估期間仍然可能出現問題。

調試 `model.fit()` 中出現的錯誤的最佳方法是手動檢查整個管道，看看哪裡出了問題。 錯誤通常很容易解決。

為了證明這一點，我們將使用以下腳本（嘗試）在 [MNLI 數據集](https://huggingface.co/datasets/glue)上微調 DistilBERT 模型：

```py
from datasets import load_dataset, load_metric
from transformers import (
    AutoTokenizer,
    TFAutoModelForSequenceClassification,
)

raw_datasets = load_dataset("glue", "mnli")

model_checkpoint = "distilbert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)


def preprocess_function(examples):
    return tokenizer(examples["premise"], examples["hypothesis"], truncation=True)


tokenized_datasets = raw_datasets.map(preprocess_function, batched=True)

train_dataset = tokenized_datasets["train"].to_tf_dataset(
    columns=["input_ids", "labels"], batch_size=16, shuffle=True
)

validation_dataset = tokenized_datasets["validation_matched"].to_tf_dataset(
    columns=["input_ids", "labels"], batch_size=16, shuffle=True
)

model = TFAutoModelForSequenceClassification.from_pretrained(model_checkpoint)

model.compile(loss="sparse_categorical_crossentropy", optimizer="adam")

model.fit(train_dataset)
```

如果您嘗試執行它，在進行數據集轉換時可能會收到一些“VisibleDeprecationWarning”——這是我們已知的 UX 問題，因此請忽略它。 如果你在 2021 年 11 月之後閱讀這門課程並且它仍在繼續，那麼請在推特上 @carrigmat 上發送憤怒的推文，直到他修復它。

然而，更嚴重的問題是我們得到了一個徹底的錯誤。 它真的非常長：

```python out
ValueError: No gradients provided for any variable: ['tf_distil_bert_for_sequence_classification/distilbert/embeddings/word_embeddings/weight:0', '...']
```

這意味著什麼？ 我們試圖訓練我們的數據，但我們沒有梯度？ 這很令人困惑。 我們甚至不知道該如何開始調試類似的東西？ 當你得到的錯誤並不能立即表明問題出在哪裡時，最好的解決方案通常是按順序檢查所有內容，確保在每個階段一切看起來都是正確的。 當然，開始的地方總是...

### 檢查您的數據

這是不言而喻的，但如果您的數據已損壞，Keras 將無法為您修復它。 所以首先，你需要看看你的訓練集中有什麼。

儘管查看 `raw_datasets` 和 `tokenized_datasets` 很誘人，但我們強烈建議您在數據將要進入模型的地方直接查看數據。 這意味著應該從您使用 `to_tf_dataset()` 函數創建的 `tf.data.Dataset` 讀取輸出！ 那麼我們該怎麼做呢？ `tf.data.Dataset` 對象一次給我們整個批次並且不支持索引，所以我們不能只請求 `train_dataset[0]`。 但是，我們可以禮貌地向它要一批：

```py
for batch in train_dataset:
    break
```

`break` 在一次迭代後結束循環，因此這會抓取來自`train_dataset` 的第一批並將其保存為`batch`。 現在，讓我們看看裡面有什麼：

```python out
{'attention_mask': <tf.Tensor: shape=(16, 76), dtype=int64, numpy=
 array([[1, 1, 1, ..., 0, 0, 0],
        [1, 1, 1, ..., 0, 0, 0],
        [1, 1, 1, ..., 0, 0, 0],
        ...,
        [1, 1, 1, ..., 1, 1, 1],
        [1, 1, 1, ..., 0, 0, 0],
        [1, 1, 1, ..., 0, 0, 0]])>,
 'label': <tf.Tensor: shape=(16,), dtype=int64, numpy=array([0, 2, 1, 2, 1, 1, 2, 0, 0, 0, 1, 0, 1, 2, 2, 1])>,
 'input_ids': <tf.Tensor: shape=(16, 76), dtype=int64, numpy=
 array([[ 101, 2174, 1010, ...,    0,    0,    0],
        [ 101, 3174, 2420, ...,    0,    0,    0],
        [ 101, 2044, 2048, ...,    0,    0,    0],
        ...,
        [ 101, 3398, 3398, ..., 2051, 2894,  102],
        [ 101, 1996, 4124, ...,    0,    0,    0],
        [ 101, 1999, 2070, ...,    0,    0,    0]])>}
```

這看起來不錯，不是嗎？我們將 `labels` 、`attention_mask` 和 `input_ids` 傳遞給模型，這應該是計算輸出和計算損失所需的一切。那麼為什麼我們沒有梯度呢？仔細看：我們將單個字典作為輸入傳遞，但訓練批次通常是輸入張量或字典，加上標籤張量。我們的標籤只是我們輸入字典中的一個鍵。

這是一個問題嗎？實際上，並非總是如此！但這是您在使用 TensorFlow 訓練 Transformer 模型時會遇到的最常見問題之一。我們的模型都可以在內部計算損失，但要做到這一點，需要在輸入字典中傳遞標籤。這是當我們沒有為 `compile()` 指定損失值時使用的損失。另一方面，Keras 通常希望標籤與輸入字典分開傳遞，如果你不這樣做，損失計算通常會失敗。

問題現在變得更清楚了：我們傳遞了一個“損失”參數，這意味著我們要求 Keras 為我們計算損失，但我們將標籤作為模型的輸入傳遞，而不是放在 Keras 期望的地方的！我們需要二選一：要麼使用模型的內部損失並將標籤保留在原處，要麼繼續使用 Keras 損失，但將標籤移動到 Keras 期望的位置。為簡單起見，讓我們採用第一種方法。將對 `compile()` 的調用更改為：

```py
model.compile(optimizer="adam")
```

現在我們將使用模型的內部損失，這個問題應該解決了！

<Tip>

✏️ **輪到你了！** 作為我們解決其他問題後的可選挑戰，你可以嘗試回到這一步，讓模型使用原始 Keras 計算的損失而不是內部損失。 您需要將 `"labels"` 添加到 `to_tf_dataset()` 的 `label_cols` 參數，以確保正確輸出標籤，這將為您提供梯度——但我們指定的損失還有一個問題 . 訓練仍然會遇到這個問題，學習會非常緩慢，並且會在多次訓練損失時達到穩定狀態。 你能弄清楚它是什麼嗎？

一個 ROT13 編碼的提示，如果你卡住了：Vs lbh ybbx ng gur bhgchgf bs FrdhraprPynffvsvpngvba zbqryf va Genafsbezref, gurve svefg bhgchg vf `ybtvgf`。 榮格納 ybtvgf?

第二個提示：Jura lbh fcrpvsl bcgvzvmref, npgvingvbaf 是 ybffrf jvgu fgevatf, Xrenf frgf nyy gur nethzrag inyhrf gb gurve qrsnhygf。 Jung nethzragf qbrf FcnefrPngrtbevpnyPebffragebcl unir, naq jung ner gurve qrsnhygf?

</Tip>

現在，讓我們嘗試訓練。 我們現在應該得到梯度，所以希望（這裡播放不祥的音樂）我們可以調用 `model.fit()` 一切都會正常工作！

```python out
  246/24543 [..............................] - ETA: 15:52 - loss: nan
```

Oh no. 

`nan` 不是一個非常令人開心的損失值。 儘管如此，我們已經檢查了我們的數據，它看起來還不錯。 如果這不是問題，我們下一步該去哪裡？ 顯而易見的下一步是...

### 檢查你的模型

`model.fit()` 是 Keras 中一個非常方便的函數，但它為您做了很多事情，這使得準確找到問題發生的位置變得更加棘手。 如果您正在調試您的模型，一個真正有用的策略是隻將一個批次傳遞給模型，並詳細查看該批次的輸出。 如果模型拋出錯誤，另一個非常有用的提示是使用 `run_eagerly=True` `compile()` 模型。 這會使它變慢很多，但它會使錯誤消息更容易理解，因為它們會準確地指出問題發生在模型代碼的哪個位置。

不過，目前我們還不需要 `run_eagerly`。 讓我們通過模型運行我們之前得到的“批處理”，看看輸出是什麼樣子的：

```py
model(batch)
```

```python out
TFSequenceClassifierOutput(loss=<tf.Tensor: shape=(16,), dtype=float32, numpy=
array([nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan,
       nan, nan, nan], dtype=float32)>, logits=<tf.Tensor: shape=(16, 2), dtype=float32, numpy=
array([[nan, nan],
       [nan, nan],
       [nan, nan],
       [nan, nan],
       [nan, nan],
       [nan, nan],
       [nan, nan],
       [nan, nan],
       [nan, nan],
       [nan, nan],
       [nan, nan],
       [nan, nan],
       [nan, nan],
       [nan, nan],
       [nan, nan],
       [nan, nan]], dtype=float32)>, hidden_states=None, attentions=None)
```

嗯，這很棘手。一切都是`nan`！但這很奇怪，不是嗎？我們所有的 logits 怎麼會變成“nan”？ `nan` 的意思是“不是數字”。 `nan` 值經常出現在您執行禁止操作時，例如除以零。但是，在機器學習中瞭解 `nan` 非常重要的一件事是，該值傾向於*傳播*。如果將一個數字乘以 `nan`，則輸出也是 `nan`。如果你在輸出、損失或梯度的任何地方得到一個“nan”，那麼它會迅速傳播到你的整個模型中——因為當那個“nan”值通過你的網絡傳播回來時，你會得到nan 梯度，當使用這些梯度計算權重更新時，您將獲得 nan 權重，這些權重將計算更多的 nan 輸出！很快，整個網絡將只是“nan”的一大塊。一旦發生這種情況，就很難看出問題是從哪裡開始的。我們如何隔離“nan”第一次出現的地方？

答案是嘗試*重新初始化*我們的模型。一旦我們開始訓練，我們就會在某個地方得到一個“nan”，它很快就會傳播到整個模型中。所以，讓我們從檢查點加載模型而不做任何權重更新，看看我們從哪裡得到一個 `nan` 值：

```py
model = TFAutoModelForSequenceClassification.from_pretrained(model_checkpoint)
model(batch)
```

當我們運行它時，我們得到：

```py out
TFSequenceClassifierOutput(loss=<tf.Tensor: shape=(16,), dtype=float32, numpy=
array([0.6844486 ,        nan,        nan, 0.67127866, 0.7068601 ,
              nan, 0.69309855,        nan, 0.65531296,        nan,
              nan,        nan, 0.675402  ,        nan,        nan,
       0.69831556], dtype=float32)>, logits=<tf.Tensor: shape=(16, 2), dtype=float32, numpy=
array([[-0.04761693, -0.06509043],
       [-0.0481936 , -0.04556257],
       [-0.0040929 , -0.05848458],
       [-0.02417453, -0.0684005 ],
       [-0.02517801, -0.05241832],
       [-0.04514256, -0.0757378 ],
       [-0.02656011, -0.02646275],
       [ 0.00766164, -0.04350497],
       [ 0.02060014, -0.05655622],
       [-0.02615328, -0.0447021 ],
       [-0.05119278, -0.06928903],
       [-0.02859691, -0.04879177],
       [-0.02210129, -0.05791225],
       [-0.02363213, -0.05962167],
       [-0.05352269, -0.0481673 ],
       [-0.08141848, -0.07110836]], dtype=float32)>, hidden_states=None, attentions=None)
```

*現在*我們到了某個地方！ 我們的 logits 中沒有 `nan` 值，這令人放心。 但是我們確實在損失中看到了一些“nan”值！ 這些樣本有什麼特別導致這個問題的嗎？ 讓我們看看它們是哪些（請注意，如果您自己運行此代碼，您可能會得到不同的索引，因為數據集已被隨機打亂）：

```python
import numpy as np

loss = model(batch).loss.numpy()
indices = np.flatnonzero(np.isnan(loss))
indices
```

```python out
array([ 1,  2,  5,  7,  9, 10, 11, 13, 14])
```

讓我們看看這些來自樣本的輸入id：

```python
input_ids = batch["input_ids"].numpy()
input_ids[indices]
```

```python out
array([[  101,  2007,  2032,  2001,  1037, 16480,  3917,  2594,  4135,
        23212,  3070,  2214, 10170,  1010,  2012,  4356,  1997,  3183,
         6838, 12953,  2039,  2000,  1996,  6147,  1997,  2010,  2606,
         1012,   102,  6838,  2001,  3294,  6625,  3773,  1996,  2214,
         2158,  1012,   102,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0],
       [  101,  1998,  6814,  2016,  2234,  2461,  2153,  1998, 13322,
         2009,  1012,   102,  2045,  1005,  1055,  2053,  3382,  2008,
         2016,  1005,  2222,  3046,  8103,  2075,  2009,  2153,  1012,
          102,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0],
       [  101,  1998,  2007,  1996,  3712,  4634,  1010,  2057,  8108,
         2025,  3404,  2028,  1012,  1996,  2616, 18449,  2125,  1999,
         1037,  9666,  1997,  4100,  8663, 11020,  6313,  2791,  1998,
         2431,  1011,  4301,  1012,   102,  2028,  1005,  1055,  5177,
         2110,  1998,  3977,  2000,  2832,  2106,  2025,  2689,  2104,
         2122,  6214,  1012,   102,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0],
       [  101,  1045,  2001,  1999,  1037, 13090,  5948,  2007,  2048,
         2308,  2006,  2026,  5001,  2043,  2026,  2171,  2001,  2170,
         1012,   102,  1045,  2001,  3564,  1999,  2277,  1012,   102,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0],
       [  101,  2195,  4279,  2191,  2039,  1996,  2181,  2124,  2004,
         1996,  2225,  7363,  1012,   102,  2045,  2003,  2069,  2028,
         2451,  1999,  1996,  2225,  7363,  1012,   102,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0],
       [  101,  2061,  2008,  1045,  2123,  1005,  1056,  2113,  2065,
         2009,  2428, 10654,  7347,  2030,  2009,  7126,  2256,  2495,
         2291,   102,  2009,  2003,  5094,  2256,  2495,  2291,  2035,
         2105,  1012,   102,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0],
       [  101,  2051,  1010,  2029,  3216,  2019,  2503,  3444,  1010,
         6732,  1996,  2265,  2038, 19840,  2098,  2125,  9906,  1998,
         2003,  2770,  2041,  1997,  4784,  1012,   102,  2051,  6732,
         1996,  2265,  2003,  9525,  1998,  4569,  1012,   102,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0],
       [  101,  1996, 10556,  2140, 11515,  2058,  1010,  2010,  2162,
         2252,  5689,  2013,  2010,  7223,  1012,   102,  2043,  1996,
        10556,  2140, 11515,  2058,  1010,  2010,  2252,  3062,  2000,
         1996,  2598,  1012,   102,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0],
       [  101, 13543,  1999,  2049,  6143,  2933,  2443,   102,  2025,
        13543,  1999,  6143,  2933,  2003,  2443,   102,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0,     0,     0,     0,     0,     0,
            0,     0,     0,     0]])
```

嗯，這裡有很多東西，但沒有什麼不尋常的。 讓我們看看標籤：

```python out
labels = batch['labels'].numpy()
labels[indices]
```

```python out
array([2, 2, 2, 2, 2, 2, 2, 2, 2])
```

啊! `nan` 樣本都具有相同的標籤，即標籤 2。這是一個非常明顯的提示。 當我們的標籤為 2 時，我們會得到loss為 `nan`，這表明這是檢查模型中標籤數量的好時機：

```python
model.config.num_labels
```

```python out
2
```

現在我們看到了問題：模型認為只有兩個類，但標籤上升到 2，這意味著實際上有三個類（因為 0 也是一個類）。 這就是我們得到“nan”的方式——通過嘗試計算不存在的類的損失！ 讓我們嘗試改變它並再次擬合模型：

```
model = TFAutoModelForSequenceClassification.from_pretrained(model_checkpoint, num_labels=3)
model.compile(optimizer='adam')
model.fit(train_dataset)
```

```python out
  869/24543 [>.............................] - ETA: 15:29 - loss: 1.1032
```

我們在訓練！ 沒有更多的'nan's，我們的損失正在減少......有點。 如果你觀察一段時間，你可能會開始有點不耐煩，因為損失值一直居高不下。 讓我們在這裡停止訓練並嘗試考慮可能導致此問題的原因。 在這一點上，我們很確定數據和模型都沒有問題，但是我們的模型並沒有很好地學習。 還剩下什麼？ 是時候...

### 檢查你的超參數

如果你回頭看上面的代碼，你可能根本看不到任何超參數，除了 `batch_size`，這似乎不是罪魁禍首。不過，不要被迷惑；總是有超參數，如果你看不到它們，那只是意味著你不知道它們的設置是什麼。特別要記住關於 Keras 的一個關鍵點：如果您使用字符串設置損失函數、優化器或激活函數，_它的所有參數都將設置為它們的默認值_。這意味著即使為此使用字符串非常方便，但在這樣做時您應該非常小心，因為它很容易對您隱藏關鍵的事情。 （任何嘗試上述方式的人都應該仔細注意這一事實。）

在這種情況下，我們在哪裡設置了帶有字符串的參數？我們最初使用字符串設置損失，但我們不再這樣做了。但是，我們正在使用字符串設置優化器。難道這對我們隱瞞了什麼？讓我們看看[關於它的一些討論](https://www.tensorflow.org/api_docs/python/tf/keras/optimizers/Adam)。

這裡有什麼需要注意的嗎？沒錯——學習率！當我們只使用字符串“adam”時，我們將獲得默認的學習率，即 0.001，即 1e-3。這對於transormer模型來說太高了！一般來說，我們建議您的模型嘗試 1e-5 和 1e-4 之間的學習率；這比我們在這裡實際使用的值小 10X 到 100X 之間。聽起來這可能是一個主要問題，所以讓我們嘗試減少它。為此，我們需要導入實際的“優化器”對象。當我們這樣做的時候，讓我們從檢查點重新初始化模型，以防高學習率的訓練損壞了它的權重：

```python
from tensorflow.keras.optimizers import Adam

model = TFAutoModelForSequenceClassification.from_pretrained(model_checkpoint)
model.compile(optimizer=Adam(5e-5))
```

<Tip>

💡您還可以從🤗 Transformers 中導入 `create_optimizer()` 函數，這將為您提供具有正確權重衰減以及學習率預熱和學習率衰減的 AdamW 優化器。 此優化器通常會產生比使用默認 Adam 優化器獲得的結果稍好一些的結果。

</Tip>

現在，我們可以嘗試使用新的、改進後的學習率來擬合模型：

```python
model.fit(train_dataset)
```

```python out
319/24543 [..............................] - ETA: 16:07 - loss: 0.9718
```

現在我們的損失真的在某個地方！ 訓練終於看起來奏效了。 這裡有一個教訓：當你的模型正在運行但損失沒有下降，並且你確定你的數據沒問題時，檢查學習率和權重衰減等超參數是個好主意。 將其中任何一個設置得太高很可能導致訓練在高損失值下“停滯”。

## 其他潛在問題

我們已經涵蓋了上面腳本中的問題，但您可能會遇到其他幾個常見錯誤。 讓我們看一個（非常不完整的）列表。

### 處理內存不足錯誤

內存不足的跡象是“分配張量時出現 OOM”之類的錯誤——OOM 是“內存不足”的縮寫。 在處理大型語言模型時，這是一個非常常見的危險。 如果遇到這種情況，一個好的策略是將批量大小減半並重試。 但請記住，有些型號*非常*大。 例如，全尺寸 GPT-2 的參數為 1.5B，這意味著您將需要 6 GB 的內存來存儲模型，另外需要 6 GB 的內存用於梯度下降！ 無論您使用什麼批量大小，訓練完整的 GPT-2 模型通常需要超過 20 GB 的 VRAM，而只有少數 GPU 擁有。 像“distilbert-base-cased”這樣更輕量級的模型更容易運行，訓練也更快。

<Tip>

在課程的下一部分中，我們將介紹更先進的技術，這些技術可以幫助您減少內存佔用並讓您微調最大的模型。

</Tip>

### TensorFlow 🦛餓餓

您應該注意的 TensorFlow 的一個特殊怪癖是，它會在您加載模型或進行任何訓練後立即為自己分配 *所有 * GPU 內存，然後根據需要分配該內存。這與其他框架的行為不同，例如 PyTorch，後者根據 CUDA 的需要分配內存，而不是在內部進行。 TensorFlow 方法的一個優點是，當您耗盡內存時，它通常會給出有用的錯誤，並且它可以從該狀態恢復而不會導致整個 CUDA 內核崩潰。但也有一個重要的缺點：如果你同時運行兩個 TensorFlow 進程，那麼**你將度過一段糟糕的時光**。

如果您在 Colab 上運行，則無需擔心這一點，但如果您在本地運行，這絕對是您應該小心的事情。特別要注意，關閉筆記本選項卡並不一定會關閉該筆記本！您可能需要選擇正在運行的筆記本（帶有綠色圖標的筆記本）並在目錄列表中手動關閉它們。任何使用 TensorFlow 的正在運行的筆記本仍可能佔用大量 GPU 內存，這意味著您啟動的任何新筆記本都可能會遇到一些非常奇怪的問題。

如果您開始運行之前正確的代碼卻收到有關 CUDA、BLAS 或 cuBLAS 的錯誤，這通常是罪魁禍首。您可以使用類似 `nvidia-smi` 的命令來檢查 - 當您關閉或重新啟動當前筆記本時，您的大部分內存是否空閒，或者是否仍在使用中？如果它仍在使用中，則有其他東西在佔用它！

### 檢查您的數據（再次！）

只有在理論上可以從您的數據中學到任何東西時，您的模型才會學到一些東西。 如果存在損壞數據的錯誤或標籤是隨機屬性的，那麼您很可能不會在數據集上獲得任何知識。這裡一個有用的工具是`tokenizer.decode()`。 這會將 `input_ids` 轉換回字符串，因此您可以查看數據並查看您的訓練數據是否正在教授您希望它教授的內容。 例如，像我們上面所做的那樣從 `tf.data.Dataset` 中獲取 `batch` 後，您可以像這樣解碼第一個元素：

```py
input_ids = batch["input_ids"].numpy()
tokenizer.decode(input_ids[0])
```

Then you can compare it with the first label, like so:

```py
labels = batch["labels"].numpy()
label = labels[0]
```
一旦您可以像這樣查看您的數據，您可以問自己以下問題：

- 解碼後的數據是否可以理解？
- 你認同這些標籤嗎？
- 有沒有一個標籤比其他標籤更常見？
- 如果模型預測隨機的答案/總是相同的答案，那麼loss/評估指標應該是多少？

查看您的數據後，查看模型的一些預測並對其進行解碼。 如果模型總是預測同樣的事情，那可能是因為你的數據集偏向一個類別（針對分類問題）； 過採樣稀有類等技術可能會有所幫助。

如果您在初始模型上獲得的loss/評估指標與您期望的隨機預測的loss/評估指標非常不同，請仔細檢查您的loss或評估指標的計算方式，因為那裡可能存在錯誤。 如果您使用最後添加的多個loss，請確保它們具有相同的規模。

當您確定您的數據是完美的時，您可以通過一個簡單的測試來查看模型是否能夠對其進行訓練。

### 在一批上過度擬合你的模型

過度擬合通常是我們在訓練時儘量避免的事情，因為這意味著模型沒有學習識別我們想要的一般特徵，而只是記住了訓練樣本。 但是，一遍又一遍地嘗試在一個批次上訓練您的模型是一個很好的測試，可以檢查您構建的問題是否可以通過您嘗試訓練的模型來解決。 它還將幫助您查看您的初始學習率是否太高。

一旦你定義了你的“模型”，這樣做真的很容易； 只需獲取一批訓練數據，然後將該“批次”視為您的整個數據集，並在其上fit大量epoch：

```py
for batch in train_dataset:
    break

# Make sure you have run model.compile() and set your optimizer,
# and your loss/metrics if you're using them

model.fit(batch, epochs=20)
```

<Tip>

💡 如果您的訓練數據不平衡，請確保構建一批包含所有標籤的訓練數據。

</Tip>

生成的模型在“批次”上應該有接近完美的結果，損失迅速下降到 0（或您正在使用的損失的最小值）。

如果你沒有設法讓你的模型獲得這樣的完美結果，這意味著你構建問題或數據的方式有問題，所以你應該修復它。 只有當你設法通過過擬合測試時，你才能確定你的模型實際上可以學到一些東西。

<Tip warning={true}>

⚠️ 在此測試之後，您將不得不重新創建您的模型和“Trainer”，因為獲得的模型可能無法在您的完整數據集上恢復和學習有用的東西。

</Tip>

### 在你有第一個基線之前不要調整任何東西

超參數調整總是被強調為機器學習中最難的部分，但這只是幫助您在指標上獲得一點點提升的最後一步。 例如將默認的 Adam 學習率 1e-3 與 Transformer 模型一起使用，當然會使學習進行得非常緩慢或完全停止，但大多數時候“合理”的超參數，例如從 1e-5 到 5e-5 的學習率，會很好地給你帶來好的結果。因此，在您獲得超出數據集基線的東西之前，不要開始進行耗時且昂貴的超參數搜索。

一旦你有一個足夠好的模型，你就可以開始稍微調整一下。 不要嘗試使用不同的超參數啟動一千次運行，而是比較一個超參數的不同值的幾次運行，以瞭解哪個影響最大。

如果您正在調整模型本身，不要嘗試任何您無法合理證明的事情。 始終確保您返回過擬合測試以驗證您的更改沒有產生任何意外後果。

### 請求幫忙

希望您會在本節中找到一些可以幫助您解決問題的建議，但如果不是這樣，請記住您可以隨時在 [論壇](https://discuss.huggingface.co/) 上向社區提問。

以下是一些可能有用的額外資源：

- [“作為工程最佳實踐工具的再現性”](https://docs.google.com/presentation/d/1yHLPvPhUs2KGI5ZWo0sU-PKU3GimAk3iTsI38Z-B5Gw/edit#slide=id.p)，作者：Joel Grus
- [“神經網絡調試清單”](https://towardsdatascience.com/checklist-for-debugging-neural-networks-d8b2a9434f21) 作者：Cecelia Shao
- [“如何對機器學習代碼進行單元測試”](https://medium.com/@keeper6928/how-to-unit-test-machine-learning-code-57cf6fd81765) by Chase Roberts
- [“訓練神經網絡的秘訣”](http://karpathy.github.io/2019/04/25/recipe/)作者：Andrej Karpathy

當然，並不是你在訓練神經網絡時遇到的每一個問題都是你自己的錯！ 如果您在 🤗 Transformers 或 🤗 Datasets 庫中遇到看起來不正確的內容，您可能遇到了錯誤。 你應該告訴我們這一切，在下一節中，我們將準確解釋如何做到這一點。


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter8/4_tf.mdx" />

### 出現錯誤時該怎麼辦
https://huggingface.co/learn/course/zh-TW/chapter8/2.md

# 出現錯誤時該怎麼辦

<DocNotebookDropdown
  classNames="absolute z-10 right-0 top-0"
  options={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/chapter8/section2.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/chapter8/section2.ipynb"},
]} />

在本節中, 我們將研究當你嘗試從新調整的 Transformer 模型生成預測時可能發生的一些常見錯誤。這將為 [第四節](/course/chapter8/section4) 做準備, 我們將探索如何調試訓練階段本身。

<Youtube id="DQ-CpJn6Rc4"/>

我們為這一節準備了一個 [模板模型庫](https://huggingface.co/lewtun/distilbert-base-uncased-finetuned-squad-d5716d28), 如果你想運行本章中的代碼, 你首先需要將模型複製到你的 [Hugging Face Hub](https://huggingface.co) 賬號。為此, 首先通過在 Jupyter 筆記本中運行以下任一命令來登錄:

```python
from huggingface_hub import notebook_login

notebook_login()
```

或在你最喜歡的終端中執行以下操作:

```bash
huggingface-cli login
```

這將提示你輸入用戶名和密碼, 並將在下面保存一個令牌 *~/.cache/huggingface/*。登錄後, 你可以使用以下功能複製模板存儲庫:

```python
from distutils.dir_util import copy_tree
from huggingface_hub import Repository, snapshot_download, create_repo, get_full_repo_name


def copy_repository_template():
    # Clone the repo and extract the local path
    template_repo_id = "lewtun/distilbert-base-uncased-finetuned-squad-d5716d28"
    commit_hash = "be3eaffc28669d7932492681cd5f3e8905e358b4"
    template_repo_dir = snapshot_download(template_repo_id, revision=commit_hash)
    # Create an empty repo on the Hub
    model_name = template_repo_id.split("/")[1]
    create_repo(model_name, exist_ok=True)
    # Clone the empty repo
    new_repo_id = get_full_repo_name(model_name)
    new_repo_dir = model_name
    repo = Repository(local_dir=new_repo_dir, clone_from=new_repo_id)
    # Copy files
    copy_tree(template_repo_dir, new_repo_dir)
    # Push to Hub
    repo.push_to_hub()
```

現在, 當你調用 `copy_repository_template()` 時, 它將在你的帳戶下創建模板存儲庫的副本。

## 從 🤗 Transformers 調試管道

要開始我們調試 Transformer 模型的奇妙世界之旅, 請考慮以下場景: 你正在與一位同事合作進行問答項目, 以幫助電子商務網站的客戶找到有關消費品的答案。你的同事給你發了一條消息, 比如:

> 嗨! 我剛剛使用了抱抱臉課程的 [第七章](/course/chapter7/7) 中的技術進行了一個實驗, 並在 SQuAD 上獲得了一些很棒的結果! 我認為我們可以用這個模型作為我們項目的起點。Hub上的模型ID是 "lewtun/distillbert-base-uncased-finetuned-squad-d5716d28"。請隨意測試一下 :)

你首先想到的是使用 🤗 Transformers 中的 `管道`:

```python
from transformers import pipeline

model_checkpoint = get_full_repo_name("distillbert-base-uncased-finetuned-squad-d5716d28")
reader = pipeline("question-answering", model=model_checkpoint)
```

```python out
"""
OSError: Can't load config for 'lewtun/distillbert-base-uncased-finetuned-squad-d5716d28'. Make sure that:

- 'lewtun/distillbert-base-uncased-finetuned-squad-d5716d28' is a correct model identifier listed on 'https://huggingface.co/models'

- or 'lewtun/distillbert-base-uncased-finetuned-squad-d5716d28' is the correct path to a directory containing a config.json file
"""
```

哦不對, 好像出了什麼問題! 如果你是編程新手, 這些類型的錯誤一開始看起來有點神秘 (甚至是一個 `OSError`?!)。這裡顯示的錯誤只是一個更大的錯誤報告的最後一部分, 稱為 _Python traceback_ (又名堆棧跟蹤)。例如, 如果你在 Google Colab 上運行此代碼, 你應該會看到類似於以下屏幕截圖的內容:

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter8/traceback.png" alt="A Python traceback." width="100%"/>
</div>

這些報告中包含很多信息, 所以讓我們一起來看看關鍵部分。首先要注意的是, 應該從 _從底部到頂部_ 讀取回溯。如果你習慣於從上到下閱讀英文文本, 這可能聽起來很奇怪,但它反映了這樣一個事實,即回溯顯示了在下載模型和標記器時 `管道` 進行的函數調用序列。(查看 [第二章](/course/chapter2) 瞭解有關 `pipeline` 如何在後臺工作的更多詳細信息。)

<Tip>

🚨 看到Google Colab 回溯中 "6 幀" 周圍的藍色框了嗎? 這是 Colab 的一個特殊功能, 它將回溯壓縮為"幀"。如果你似乎無法找到錯誤的來源, 請確保通過單擊這兩個小箭頭來展開完整的回溯。

</Tip>

這意味著回溯的最後一行指示最後一條錯誤消息並給出引發的異常的名稱。在這種情況下, 異常類型是`OSError`, 表示與系統相關的錯誤。如果我們閱讀隨附的錯誤消息, 我們可以看到模型的 *config.json* 文件似乎有問題, 我們給出了兩個修復它的建議:

```python out
"""
Make sure that:

- 'lewtun/distillbert-base-uncased-finetuned-squad-d5716d28' is a correct model identifier listed on 'https://huggingface.co/models'

- or 'lewtun/distillbert-base-uncased-finetuned-squad-d5716d28' is the correct path to a directory containing a config.json file
"""
```

<Tip>

💡 如果你遇到難以理解的錯誤消息, 只需將該消息複製並粘貼到 Google 或 [Stack Overflow](https://stackoverflow.com/) 搜索欄中 (是的, 真的!)。你很可能不是第一個遇到錯誤的人, 這是找到社區中其他人發佈的解決方案的好方法。例如, 在 Stack Overflow 上搜索 `OSError: Can't load config for` 給出了幾個[hits](https://stackoverflow.com/search?q=OSError%3A+Can%27t+load+config+for+), 可能是用作解決問題的起點。

</Tip>

第一個建議是要求我們檢查模型ID是否真的正確, 所以首先要做的就是複製標識符並將其粘貼到Hub的搜索欄中:

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter8/wrong-model-id.png" alt="The wrong model name." width="100%"/>
</div>

嗯, 看起來我們同事的模型確實不在 Hub 上... 啊哈, 但是模型名稱中有一個錯字! DistilBERT 的名稱中只有一個 "l", 所以讓我們解決這個問題並尋找 "lewtun/distilbert-base-uncased-finetuned-squad-d5716d28":

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter8/true-model-id.png" alt="The right model name." width="100%"/>
</div>

好的, 這很受歡迎。現在讓我們嘗試使用正確的模型 ID 再次下載模型:

```python
model_checkpoint = get_full_repo_name("distilbert-base-uncased-finetuned-squad-d5716d28")
reader = pipeline("question-answering", model=model_checkpoint)
```

```python out
"""
OSError: Can't load config for 'lewtun/distilbert-base-uncased-finetuned-squad-d5716d28'. Make sure that:

- 'lewtun/distilbert-base-uncased-finetuned-squad-d5716d28' is a correct model identifier listed on 'https://huggingface.co/models'

- or 'lewtun/distilbert-base-uncased-finetuned-squad-d5716d28' is the correct path to a directory containing a config.json file
"""
```

啊, 再次挫敗 -- 歡迎來到機器學習工程師的日常生活! 因為我們已經修復了模型 ID, 所以問題一定出在存儲庫本身。訪問 🤗 Hub 上存儲庫內容的一種快速方法是通過 `huggingface_hub` 庫的 `list_repo_files()` 方法:

```python
from huggingface_hub import list_repo_files

list_repo_files(repo_id=model_checkpoint)
```

```python out
['.gitattributes', 'README.md', 'pytorch_model.bin', 'special_tokens_map.json', 'tokenizer_config.json', 'training_args.bin', 'vocab.txt']
```

有趣 -- 似乎沒有配置文件存儲庫中的 *config.json* 文件! 難怪我們的 `pipeline` 無法加載模型; 我們的同事一定是在微調後忘記將這個文件推送到 Hub。在這種情況下, 問題似乎很容易解決: 我們可以要求他們添加文件, 或者, 因為我們可以從模型 ID 中看到使用的預訓練模型是 [`distilbert-base-uncased`](https://huggingface.co/distilbert-base-uncased), 我們可以下載此模型的配置並將其推送到我們的存儲庫以查看是否可以解決問題。讓我們試試看。使用我們在 [第二章](/course/chapter2) 中學習的技術, 我們使用 `AutoConfig` 類下載模型的配置:

```python
from transformers import AutoConfig

pretrained_checkpoint = "distilbert-base-uncased"
config = AutoConfig.from_pretrained(pretrained_checkpoint)
```

<Tip warning={true}>

🚨 我們在這裡採用的方法並不是萬無一失的, 因為我們的同事可能在微調模型之前已經調整了 `distilbert-base-uncased` 配置。在現實生活中, 我們想首先檢查它們, 但出於本節的目的, 我們假設它們使用默認配置。

</Tip>

然後我們可以使用配置的 `push_to_hub()` 方法將其推送到我們的模型存儲庫:

```python
config.push_to_hub(model_checkpoint, commit_message="Add config.json")
```

現在我們可以通過從最新提交的 `main` 分支中加載模型來測試這是否有效:

```python
reader = pipeline("question-answering", model=model_checkpoint, revision="main")

context = r"""
Extractive Question Answering is the task of extracting an answer from a text
given a question. An example of a question answering dataset is the SQuAD
dataset, which is entirely based on that task. If you would like to fine-tune a
model on a SQuAD task, you may leverage the
examples/pytorch/question-answering/run_squad.py script.

🤗 Transformers is interoperable with the PyTorch, TensorFlow, and JAX
frameworks, so you can use your favourite tools for a wide variety of tasks!
"""

question = "What is extractive question answering?"
reader(question=question, context=context)
```

```python out
{'score': 0.38669535517692566,
 'start': 34,
 'end': 95,
 'answer': 'the task of extracting an answer from a text given a question'}
```

哇哦, 成功了!讓我們回顧一下你剛剛學到的東西:

- Python 中的錯誤消息稱為 _tracebacks_ , 並從下到上閱讀。錯誤消息的最後一行通常包含定位問題根源所需的信息。
- 如果最後一行沒有包含足夠的信息, 請按照您的方式進行回溯, 看看您是否可以確定源代碼中發生錯誤的位置。
- 如果沒有任何錯誤消息可以幫助您調試問題, 請嘗試在線搜索類似問題的解決方案。
- `huggingface_hub` 
// 🤗 Hub?
庫提供了一套工具, 你可以使用這些工具與 Hub 上的存儲庫進行交互和調試。

現在你知道如何調試管道, 讓我們看一下模型本身前向傳遞中的一個更棘手的示例。

## 調試模型的前向傳遞

儘管 `pipeline` 對於大多數需要快速生成預測的應用程序來說非常有用, 有時您需要訪問模型的 logits (例如, 如果您有一些想要應用的自定義後處理)。為了看看在這種情況下會出現什麼問題, 讓我們首先從 `pipeline` 中獲取模型和標記器:

```python
tokenizer = reader.tokenizer
model = reader.model
```

接下來我們需要一個問題, 那麼讓我們看看是否支持我們最喜歡的框架:

```python
question = "Which frameworks can I use?"
```

正如我們在 [第七章](/course/chapter7) 中學習的, 我們需要採取的通常步驟是對輸入進行標記化, 提取開始和結束標記的對數, 然後解碼答案範圍:

```python
import torch

inputs = tokenizer(question, context, add_special_tokens=True)
input_ids = inputs["input_ids"][0]
outputs = model(**inputs)
answer_start_scores = outputs.start_logits
answer_end_scores = outputs.end_logits
# Get the most likely beginning of answer with the argmax of the score
answer_start = torch.argmax(answer_start_scores)
# Get the most likely end of answer with the argmax of the score
answer_end = torch.argmax(answer_end_scores) + 1
answer = tokenizer.convert_tokens_to_string(
    tokenizer.convert_ids_to_tokens(input_ids[answer_start:answer_end])
)
print(f"Question: {question}")
print(f"Answer: {answer}")
```

```python out
"""
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
/var/folders/28/k4cy5q7s2hs92xq7_h89_vgm0000gn/T/ipykernel_75743/2725838073.py in <module>
      1 inputs = tokenizer(question, text, add_special_tokens=True)
      2 input_ids = inputs["input_ids"]
----> 3 outputs = model(**inputs)
      4 answer_start_scores = outputs.start_logits
      5 answer_end_scores = outputs.end_logits

~/miniconda3/envs/huggingface/lib/python3.8/site-packages/torch/nn/modules/module.py in _call_impl(self, *input, **kwargs)
   1049         if not (self._backward_hooks or self._forward_hooks or self._forward_pre_hooks or _global_backward_hooks
   1050                 or _global_forward_hooks or _global_forward_pre_hooks):
-> 1051             return forward_call(*input, **kwargs)
   1052         # Do not call functions when jit is used
   1053         full_backward_hooks, non_full_backward_hooks = [], []

~/miniconda3/envs/huggingface/lib/python3.8/site-packages/transformers/models/distilbert/modeling_distilbert.py in forward(self, input_ids, attention_mask, head_mask, inputs_embeds, start_positions, end_positions, output_attentions, output_hidden_states, return_dict)
    723         return_dict = return_dict if return_dict is not None else self.config.use_return_dict
    724
--> 725         distilbert_output = self.distilbert(
    726             input_ids=input_ids,
    727             attention_mask=attention_mask,

~/miniconda3/envs/huggingface/lib/python3.8/site-packages/torch/nn/modules/module.py in _call_impl(self, *input, **kwargs)
   1049         if not (self._backward_hooks or self._forward_hooks or self._forward_pre_hooks or _global_backward_hooks
   1050                 or _global_forward_hooks or _global_forward_pre_hooks):
-> 1051             return forward_call(*input, **kwargs)
   1052         # Do not call functions when jit is used
   1053         full_backward_hooks, non_full_backward_hooks = [], []

~/miniconda3/envs/huggingface/lib/python3.8/site-packages/transformers/models/distilbert/modeling_distilbert.py in forward(self, input_ids, attention_mask, head_mask, inputs_embeds, output_attentions, output_hidden_states, return_dict)
    471             raise ValueError("You cannot specify both input_ids and inputs_embeds at the same time")
    472         elif input_ids is not None:
--> 473             input_shape = input_ids.size()
    474         elif inputs_embeds is not None:
    475             input_shape = inputs_embeds.size()[:-1]

AttributeError: 'list' object has no attribute 'size'
"""
```

噢, 看起來我們的代碼中有一個錯誤!但我們不怕一點調試。您可以在筆記本中使用 Python 調試器:

<Youtube id="rSPyvPw0p9k"/>

或在終端中:

<Youtube id="5PkZ4rbHL6c"/>

在這裡, 閱讀錯誤消息告訴我們 `'list' object has no attribute 'size'`, 我們可以看到一個 `-->` 箭頭指向 `model(**inputs)` 中出現問題的行。你可以使用 Python 調試器以交互方式調試它, 但現在我們只需打印出一部分 `inputs`, 看看我們有什麼:

```python
inputs["input_ids"][:5]
```

```python out
[101, 2029, 7705, 2015, 2064]
```

這當然看起來像一個普通的 Python `list`, 但讓我們仔細檢查一下類型:

```python
type(inputs["input_ids"])
```

```python out
list
```

是的, 這肯定是一個 Python `list`。那麼出了什麼問題呢? 回憶 [第二章](/course/chapter2) 🤗 Transformers 中的 `AutoModelForXxx` 類在 _tensors_ 上運行(PyTorch或者or TensorFlow), 一個常見的操作是使用 `Tensor.size()` 方法提取張量的維度, 例如, 在 PyTorch 中。讓我們再看看回溯, 看看哪一行觸發了異常:

```
~/miniconda3/envs/huggingface/lib/python3.8/site-packages/transformers/models/distilbert/modeling_distilbert.py in forward(self, input_ids, attention_mask, head_mask, inputs_embeds, output_attentions, output_hidden_states, return_dict)
    471             raise ValueError("You cannot specify both input_ids and inputs_embeds at the same time")
    472         elif input_ids is not None:
--> 473             input_shape = input_ids.size()
    474         elif inputs_embeds is not None:
    475             input_shape = inputs_embeds.size()[:-1]

AttributeError: 'list' object has no attribute 'size'
```

看起來我們的代碼試圖調用 `input_ids.size()`, 但這顯然不適用於 Python `list`, 這只是一個容器。我們如何解決這個問題? 在 Stack Overflow 上搜索錯誤消息給出了很多相關的 [hits](https://stackoverflow.com/search?q=AttributeError%3A+%27list%27+object+has+no+attribute+%27size%27&s=c15ec54c-63cb-481d-a749-408920073e8f)。單擊第一個會顯示與我們類似的問題, 答案如下面的屏幕截圖所示:

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter8/stack-overflow.png" alt="An answer from Stack Overflow." width="100%"/>
</div>

答案建議我們添加 `return_tensors='pt'` 到標記器, 所以讓我們看看這是否適合我們:

```python out
inputs = tokenizer(question, context, add_special_tokens=True, return_tensors="pt")
input_ids = inputs["input_ids"][0]
outputs = model(**inputs)
answer_start_scores = outputs.start_logits
answer_end_scores = outputs.end_logits
# Get the most likely beginning of answer with the argmax of the score
answer_start = torch.argmax(answer_start_scores)
# Get the most likely end of answer with the argmax of the score
answer_end = torch.argmax(answer_end_scores) + 1
answer = tokenizer.convert_tokens_to_string(
    tokenizer.convert_ids_to_tokens(input_ids[answer_start:answer_end])
)
print(f"Question: {question}")
print(f"Answer: {answer}")
```

```python out
"""
Question: Which frameworks can I use?
Answer: pytorch, tensorflow, and jax
"""
```

不錯, 成功了! 這是 Stack Overflow 非常有用的一個很好的例子: 通過識別類似的問題, 我們能夠從社區中其他人的經驗中受益。然而, 像這樣的搜索並不總是會產生相關的答案, 那麼在這種情況下你能做什麼呢? 幸運的是, 有一個受歡迎的開發者社區 [Hugging Face forums](https://discuss.huggingface.co/) 可以幫助你! 在下一節中, 我們將看看如何設計可能得到回答的優秀論壇問題。

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter8/2.mdx" />

### 章末測驗
https://huggingface.co/learn/course/zh-TW/chapter9/9.md

# 章末測驗

<CourseFloatingBanner
    chapter={7}
    classNames="absolute z-10 right-0 top-0"
/>

讓我們測試一下您在本章中學到了什麼！

### 1.你能利用Grado做什麼？
<Question
	choices={[
        {
			text: "為您的機器學習模型創建一個演示",
			explain: "通過幾行 Python 代碼，您可以使用我們的預構建組件庫為 ML 模型生成一個演示。",
			correct: true
		},
		{
			text: "與他人分享你的機器學習模型",
			explain: "在啟動方法中使用 <code> share = True </code> 參數，可以生成一個共享鏈接發送給任何人。",
            correct: true
		},
		{
			text: "調試模型",
			explain: "Grado演示的一個優點是能夠用真實數據測試模型，您可以實時更改並觀察模型的預測變化，從而幫助您調試模型。",
			correct: true
		},
		{
			text: "訓練你的模型",
			explain: "在你的模型被訓練之後，Grado 被設計用來進行模型推理。",
		}
	]}
/>

### 2.Grado只在 PyTorch 模型上工作
<Question
	choices={[
        {
			text: "沒錯",
			explain: "Grdio 可以處理 PyTorch 模型，但除此之外也可以處理任何類型的機器學習模型！"
        },
        {
			text: "不正確",
			explain: "Grdio 是不依賴模型的，這意味著您可以為任何類型的機器學習模型創建演示。",
			correct: true
        }
	]}
/>

### 3.你可以在哪裡發佈一個 GRadio 演示？
<Question
	choices={[
        {
			text: "標準的 python IDEs",
			explain: "對於您最喜歡的 IDE，Grdio 工作得非常好。",
            correct: true
        },
        {
			text: "Google Colab 筆記本",
			explain: "你可以在你的 Google colab 筆記本上創建並啟動一個演示。",
			correct: true
        },
        {
			text: "Jupyter筆記本",
			explain: "很好的選擇 —— 您可以在您的Jupyter notebook創建並啟動一個演示。",
			correct: true
        }
	]}
/>

### 4.Gdio 主要是為 NLP 模型設計的
<Question
	choices={[
        {
			text: "沒錯",
			explain: "GRadio 可以處理幾乎所有的數據類型，而不僅僅是 NLP。"
        },
        {
			text: "假的",
			explain: "Grado 為開發人員提供了一個庫，其中包含幾乎所有數據類型的預構建組件。",
            correct: true
        }
	]}
/>

### 5.下列哪些特性是由 Grado 支持的？
<Question
	choices={[
        {
			text: "多輸入多輸出",
			explain: "多輸入多輸出是可能的。您所需要做的就是將輸入和輸出的列表傳遞給它們相應的參數",
            correct: true
        },
        {
			text: "數據持久性狀態",
			explain: "可以在界面中添加狀態。",
			correct: true
        },
        {
			text: "用戶名和密碼身份驗證",
			explain: "將用戶名/密碼元組的列表傳遞給啟動方法，以添加身份驗證。",
			correct: true
        },
        {
			text: "自動分析誰在使用您的Grado演示",
			explain: "再試一次 —— Grado 不提供給開發人員關於誰使用他們的演示的分析。"
        },
        {
			text: "從“HuggingFace”的模型中心或“HuggingFace” Space加載模型",
			explain: "絕對可以 —— 使用 <code> gr. Interface.load () </code> 方法加載任何 Hugging Face 模型",
			correct: true
        }
	]}
/>

### 6.下列哪一種是從 Hub 或 Spaces 加載 Huggging Face 模型的有效方法？
<Question
	choices={[
        {
			text: "gr.Interface.load('huggingface/{user}/{model_name}')",
			explain: "這是從 Hub 加載 Hugging Face 模型的有效方法",
            correct: true
        },
        {
			text: "gr.Interface.load('model/{user}/{model_name}')",
			explain: "這是從 Hub 加載 Hugging Face 模型的有效方法",
			correct: true
        },
        {
			text: "gr.Interface.load('demos/{user}/{model_name}')",
			explain: "再試一次——你不能使用'demos'前綴來加載模型。"
        },
        {
			text: "gr.Interface.load('spaces/{user}/{model_name}')",
			explain: "這是從 Space 加載 Hugging Face 模型的有效方法",
			correct: true
        }
	]}
/>

### 7.創建您的 Gradio 接口時，您必須添加以下步驟：
<Question
	choices={[
        {
			text: "向預測函數中傳入一個額外的參數，該參數表示接口的狀態。",
			explain: "需要一個額外的參數來存儲接口的歷史記錄或狀態。",
            correct: true
        },
        {
			text: "在預測函數的末尾，將狀態的更新值作為額外的返回值返回。",
			explain: "此歷史記錄或狀態值需要由函數返回。",
            correct: true
        },
        {
			text: "在創建接口時添加狀態輸入和狀態輸出組件",
			explain: "Grado 提供了一個狀態輸入和輸出組件來持久存儲數據。",
            correct: true
        }
	]}
/>

### 8.Gradio 庫包括以下哪些組件？
<Question
	choices={[
        {
			text: "文本框。",
			explain: "是的，可以使用 Textbox 組件創建文本框。",
            correct: true
        },
        {
			text: "圖表。",
			explain: "目前沒有圖形組件。",
        },
        {
			text: "圖像。",
			explain: "是的，您可以使用 Image 組件創建一個圖像上傳小部件。",
            correct: true
        },
        {
			text: "音頻。",
			explain: "是的，您可以使用 Audio 組件創建一個音頻上傳小部件。",
            correct: true
        },
	]}
/>

### 9.Gradio允許你做什麼？
<Question
	choices={[
        {
			text: "將多個演示合併到一個 Web 應用程序中",
			explain: "您可以使用`with gradio.Tabs ():`來為多個演示添加選項卡",
			correct: true
        },
        {
			text: "將單擊/更改/等事件觸發器分配給`Blocks`組件",
			explain: "在分配事件時，傳入三個參數: fn: 應該調用的函數、輸入: 組件的輸入(list)和輸出: 應該調用組件的輸出(list)。",
			correct: true
        },
        {
			text: "自動確定哪些`Blocks`組件應該是交互的還是靜態的",
			explain: "根據您定義的事件觸發器，`Blocks`自動計算出組件是否應該接受用戶輸入。",
			correct: true
        },
		 {
			text: "創建多步演示; 這意味著允許您重用一個組件的輸出作為下一個組件的輸入",
			explain: "您可以使用組件作為一個事件觸發器的輸入，但作為另一個事件觸發器的輸出。",
            correct: true
        },
	]}
/>

### 10.你可以共享一個`Blocks`演示的公共鏈接，並創建一個`Blocks`的演示在HuggingFace空間。
<Question
	choices={[
        {
			text: "沒錯",
			explain: "就像`Interface`一樣，所有的共享和託管功能對於`Blocks`演示都是一樣的！",
			correct: true
        },
        {
			text: "不正確",
			explain: "就像`Interface`一樣，所有的共享和託管功能對於`Blocks`演示都是一樣的！",
			correct: false
        }
	]}
/>

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter9/9.mdx" />

### 與 Hugging Face Hub 整合
https://huggingface.co/learn/course/zh-TW/chapter9/5.md

# 與 Hugging Face Hub 整合

<DocNotebookDropdown
  classNames="absolute z-10 right-0 top-0"
  options={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/chapter9/section5.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/chapter9/section5.ipynb"},
]} />

為了讓你的生活更輕鬆, Gradio 直接與 Hugging Face Hub 和 Hugging Face Spaces 集成。你可以僅使用 *一行代碼* 從中心和空間加載演示。

### 從 Hugging Face Hub 加載模型
首先, 從 Hugging Face 通過 Hub 提供的數千個模型中選擇一個, 如 [第四章](/course/chapter4/2) 中所述。

使用特殊的 `Interface.load()` 方法, 你可以傳遞 `"model/"` (或者, 等效的, `"huggingface/"`) 後面是模型名稱。例如, 這裡是為大型語言模型 [GPT-J](https://huggingface.co/EleutherAI/gpt-j-6B)構建演示的代碼, 添加幾個示例輸入:

```py
import gradio as gr

title = "GPT-J-6B"
description = "Gradio Demo for GPT-J 6B, a transformer model trained using Ben Wang's Mesh Transformer JAX. 'GPT-J' refers to the class of model, while '6B' represents the number of trainable parameters. To use it, simply add your text, or click one of the examples to load them. Read more at the links below."
article = "<p style='text-align: center'><a href='https://github.com/kingoflolz/mesh-transformer-jax' target='_blank'>GPT-J-6B: A 6 Billion Parameter Autoregressive Language Model</a></p>"
examples = [
    ["The tower is 324 metres (1,063 ft) tall,"],
    ["The Moon's orbit around Earth has"],
    ["The smooth Borealis basin in the Northern Hemisphere covers 40%"],
]
gr.Interface.load(
    "huggingface/EleutherAI/gpt-j-6B",
    inputs=gr.Textbox(lines=5, label="Input Text"),
    title=title,
    description=description,
    article=article,
    examples=examples,
    enable_queue=True,
).launch()
```
    
上述代碼將生成以下界面:

<iframe src="https://course-demos-gpt-j-6B.hf.space" frameBorder="0" height="750" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

以這種方式加載模型使用 Hugging Face 的 [Inference API](https://huggingface.co/inference-api),而不是將模型加載到內存中。這對於像 GPT-J 或 T0pp這樣需要大量 RAM 的大型模型是理想的。

### 從 Hugging Face Spaces 空間加載
要從hugs Face Hub加載任何空間並在本地重新創建它, 你可以將 `spaces/` 傳遞給 `Interface`, 再加上空間的名稱。

還記得第 1 節中刪除圖像背景的演示嗎? 讓我們從 Hugging Face Spaces 加載它:

```py
gr.Interface.load("spaces/abidlabs/remove-bg").launch()
```

<iframe src="https://course-demos-remove-bg-original.hf.space" frameBorder="0" height="650" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

從Hub或Spaces加載演示的一個很酷的地方是, 你可以通過覆蓋任何參數來自定義它們。在這裡, 我們添加一個標題並讓它與網絡攝像頭一起使用:

```py
gr.Interface.load(
    "spaces/abidlabs/remove-bg", inputs="webcam", title="Remove your webcam background!"
).launch()
```

<iframe src="https://course-demos-Remove-bg.hf.space" frameBorder="0" height="550" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

現在我們已經探索了幾種將Gradio與hugs Face Hub集成的方法, 讓我們來看看 `Interface` 類的一些高級功能。這就是下一節的主題！

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter9/5.mdx" />

### Gradio 簡介
https://huggingface.co/learn/course/zh-TW/chapter9/1.md

# Gradio 簡介

在本章中，我們將學習如何為您的機器學習構建**交互式演示**模型。

為什麼首先要為您的機器學習模型構建演示或 GUI？演示可以帶來：

- **機器學習開發人員**可以輕鬆地向包括非技術團隊或客戶在內的廣大受眾展示他們的工作
- **研究人員**更輕鬆地重現機器學習模型和行為
- **質量測試人員**或**最終用戶**更容易識別和調試模型的故障點
- **不同的用戶**發現模型中的算法偏差

我們將使用 Gradio 庫為我們的模型構建演示。 Gradio 允許您完全使用 Python 為任何機器學習模型構建、自定義和共享基於 Web 的演示。

以下是一些使用 Gradio 構建的機器學習演示示例：

* 一個**草圖識別**模型，它接收草圖並輸出它認為正在繪製的標籤：

<iframe src="https://course-demos-draw2.hf.space" frameBorder="0" height="450" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

* 一個抽取式**問題回答**模型，它接受上下文段落和一個任務並輸出一個結果和一個概率分數（我們在[第7章](/course/chapter7/7)中討論了這種模型）：

<iframe src="https://course-demos-question-answering-simple.hf.space" frameBorder="0" height="640" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

* 一個**背景去除**模型，它接收圖像並輸出去除背景的圖像：

<iframe src="https://course-demos-remove-bg-original.hf.space" frameBorder="0" height="640" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

本章分為兩個部分，包括_概念_和_應用程序_。在您瞭解每個部分的概念後，您將應用它來構建特定類型的演示，範圍從圖像分類到語音識別。當你讀完本章時，你將能夠用幾行 Python 代碼構建這些演示（以及更多！）。

👀 點擊 <a href="https://huggingface.co/spaces" target="_blank">Hugging Face Spaces</a> 以查看機器學習社區構建的許多機器學習演示的最新示例！

## Gradio 方塊派對🥳

如果你想充分利用本章中的知識，就加入 Gradio 積木派對吧！這是由 Hugging Face 於**5 月 16 日至 31 日**舉辦的社區活動。在此活動中，您將使用 Gradio 構建酷炫的機器學習演示，並參與贏取 Hugging Face 禮物和獎品！

查看 [活動描述](https://github.com/AK391/community-events/blob/main/gradio-blocks/README.md) 可以瞭解如何參與的詳細信息 - 我們迫不及待地想看看你構建的🤗演示！


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter9/1.mdx" />

### 瞭解接口類
https://huggingface.co/learn/course/zh-TW/chapter9/3.md

# 瞭解接口類

<DocNotebookDropdown
  classNames="absolute z-10 right-0 top-0"
  options={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/chapter9/section3.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/chapter9/section3.ipynb"},
]} />

在本節中，我們將仔細研究 `Interface` 類，並瞭解用於創建其的主要參數。

## 如何創建接口

您會注意到 `Interface` 類有 3 個必需參數：

`Interface(fn, inputs, outputs, ...)`

這些參數是：

  - `fn`: 由 Gradio 接口包裝的預測函數。 該函數可以接受一個或多個參數並返回一個或多個值
  - `inputs`: 輸入組件類型。 Gradio 提供了許多預構建的組件，例如`"image"` 或`"mic"`。
  - `outputs`: 輸出組件類型。 同樣，Gradio 提供了許多預構建的組件，例如 `“圖像”`或“標籤”`。

有關組件的完整列表，[請參閱 Gradio 文檔](https://gradio.app/docs)。 每個預構建的組件都可以通過實例化該組件對應的類來定製。

例如，正如我們在 [上一節](/course/chapter9/2) 中看到的，您可以傳入一個 `Textbox(lines=7, label="Prompt")` 組件來創建一個包含 7 行和一個標籤的文本框，而不是將 `"textbox"` 傳遞給 `inputs` 參數。
讓我們看另一個例子，這次是一個 `Audio` 組件。

## 一個帶音頻的簡單示例

如前所述，Gradio 提供了許多不同的輸入和輸出。
因此，讓我們構建一個適用於音頻的“接口”。

在這個例子中，我們將構建一個音頻到音頻的函數，它需要一個音頻文件並簡單地反轉它。

我們將使用 `Audio` 組件作為輸入。 使用 `Audio` 組件時，您可以指定希望音頻的 `source` 是用戶上傳的文件還是用戶錄製聲音的麥克風。 在這種情況下，讓我們將其設置為“麥克風”。 只是為了好玩，我們會在我們的“音頻”中添加一個標籤，上面寫著“在這裡說話……”。

此外，我們希望將音頻作為 numpy 數組接收，以便我們可以輕鬆地“反轉”它。 所以我們將 `"type"` 設置為 `"numpy"`，它會傳遞輸入data 作為 (`sample_rate`, `data`) 的元組進入我們的函數。

我們還將使用 `Audio` 輸出組件，它可以自動將具有采樣率和 numpy 數據數組的元組渲染為可播放的音頻文件。 在這種情況下，我們不需要進行任何自定義，因此我們將使用字符串快捷方式“audio”。


```py
import numpy as np
import gradio as gr


def reverse_audio(audio):
    sr, data = audio
    reversed_audio = (sr, np.flipud(data))
    return reversed_audio


mic = gr.Audio(source="microphone", type="numpy", label="Speak here...")
gr.Interface(reverse_audio, mic, "audio").launch()
```

上面的代碼會產生一個類似下面的界面（如果你的瀏覽器沒有
詢問您的麥克風權限， <a href="https://huggingface.co/spaces/course-demos/audio-reverse" target="_blank">open the demo in  a separate tab</a>.)

<iframe src="https://course-demos-audio-reverse.hf.space" frameBorder="0" height="250" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

您現在應該能夠錄製自己的聲音並聽到自己在反向說話 - 聽起來好怪👻!

## 處理多個輸入和輸出

假設我們有一個更復雜的函數，有多個輸入和輸出。在下面的示例中，我們有一個接受下拉索引、滑塊值和數字的函數，並返回一個音調的音頻樣本。

看看我們如何傳遞輸入和輸出組件列表，看看你能不能跟上正在發生的事情。

這裡的關鍵是當你通過時：
* 輸入組件列表，每個組件依次對應一個參數。
* 輸出組件列表，每個組件對應一個返回值。

下面的代碼片段顯示了三個輸入組件如何與 `generate_tone()` 函數的三個參數對齊：

```py
import numpy as np
import gradio as gr

notes = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]


def generate_tone(note, octave, duration):
    sr = 48000
    a4_freq, tones_from_a4 = 440, 12 * (octave - 4) + (note - 9)
    frequency = a4_freq * 2 ** (tones_from_a4 / 12)
    duration = int(duration)
    audio = np.linspace(0, duration, duration * sr)
    audio = (20000 * np.sin(audio * (2 * np.pi * frequency))).astype(np.int16)
    return (sr, audio)


gr.Interface(
    generate_tone,
    [
        gr.Dropdown(notes, type="index"),
        gr.Slider(minimum=4, maximum=6, step=1),
        gr.Textbox(type="number", value=1, label="Duration in seconds"),
    ],
    "audio",
).launch()
```

<iframe src="https://course-demos-generate-tone.hf.space" frameBorder="0" height="450" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>


### `launch()` 方法

到目前為止，我們已經使用了`launch()`方法來啟動界面，但是我們
還沒有真正討論過它的作用。

默認情況下，`launch()` 方法將在 Web 服務器中啟動演示正在本地運行。 如果您在 Jupyter 或 Colab 筆記本中運行代碼，那麼Gradio 會將演示 GUI 嵌入到筆記本中，以便您輕鬆使用它。

您可以通過不同的參數自定義 `launch()` 的行為：

  - `inline` - whether to display the interface inline on Python notebooks.
  - `inbrowser` - whether to automatically launch the interface in a new tab on the default browser.
  - `share` - whether to create a publicly shareable link from your computer for the interface. Kind of like a Google Drive link!

我們將在下一節中更詳細地介紹 `share` 參數！

## ✏️ 讓我們應用它！

讓我們構建一個界面，讓您演示 **speech-recognition** 模型。
為了讓它變得有趣，我們將接受 *or* 麥克風輸入或上傳的文件。

像往常一樣，我們將使用 🤗 Transformers 中的 `pipeline()` 函數加載我們的語音識別模型。如果您需要快速複習，您可以返回 [第 1 章中的那個部分](/course/chapter1/3)。 接下來，我們將實現一個 `transcribe_audio()` 函數來處理音頻並返回轉錄。 最後，我們將把這個函數包裝在一個 `Interface` 中，其中 `Audio` 組件用於輸入，只有文本用於輸出。 總而言之，此應用程序的代碼如下：

```py
from transformers import pipeline
import gradio as gr

model = pipeline("automatic-speech-recognition")


def transcribe_audio(mic=None, file=None):
    if mic is not None:
        audio = mic
    elif file is not None:
        audio = file
    else:
        return "You must either provide a mic recording or a file"
    transcription = model(audio)["text"]
    return transcription


gr.Interface(
    fn=transcribe_audio,
    inputs=[
        gr.Audio(source="microphone", type="filepath", optional=True),
        gr.Audio(source="upload", type="filepath", optional=True),
    ],
    outputs="text",
).launch()
```

如果您的瀏覽器沒有要求您提供麥克風權限，<a href="https://huggingface.co/spaces/course-demos/audio-reverse" target="_blank">open the demo in a separate tab</a>.

<iframe src="https://course-demos-asr.hf.space" frameBorder="0" height="550" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>


就是這樣！ 您現在可以使用此界面來轉錄音頻。 注意這裡
通過將 `optional` 參數作為 `True` 傳遞，我們允許用戶
提供麥克風或音頻文件（或兩者都不提供，但這會返回錯誤消息）。

繼續看看如何與他人分享您的界面！

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter9/3.mdx" />

### 高級接口功能
https://huggingface.co/learn/course/zh-TW/chapter9/6.md

# 高級接口功能

<DocNotebookDropdown
  classNames="absolute z-10 right-0 top-0"
  options={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/chapter9/section6.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/chapter9/section6.ipynb"},
]} />

現在我們可以構建和共享一個基本接口, 讓我們來探索一些更高級的特性, 如狀態和解釋。

### 使用狀態保存數據

Gradio 支持 *會話狀態*, 其中數據在頁面加載中的多個提交中持續存在。會話狀態對於構建演示很有用, 例如, 你希望在用戶與模型交互時保留數據的聊天機器人。請注意, 會話狀態不會在模型的不同用戶之間共享數據。

要將數據存儲在會話狀態中, 你需要做三件事:

1. 向函數中傳遞一個 *額外的參數* , 該參數表示接口的狀態。
1. 在函數結束時, 將狀態的更新值作為 *額外的返回值* 返回。
1. 在創建`接口`時添加 'state' 輸入和 'state' 輸出組件。

請參閱下面的聊天機器人示例:

```py
import random

import gradio as gr


def chat(message, history):
    history = history or []
    if message.startswith("How many"):
        response = random.randint(1, 10)
    elif message.startswith("How"):
        response = random.choice(["Great", "Good", "Okay", "Bad"])
    elif message.startswith("Where"):
        response = random.choice(["Here", "There", "Somewhere"])
    else:
        response = "I don't know"
    history.append((message, response))
    return history, history


iface = gr.Interface(
    chat,
    ["text", "state"],
    ["chatbot", "state"],
    allow_screenshot=False,
    allow_flagging="never",
)
iface.launch()
```

<iframe src="https://course-demos-Chatbot-Demo.hf.space" frameBorder="0" height="350" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

請注意輸出組件的狀態如何在提交之間保持不變。注意: 可以給 state 參數傳入一個默認值, 作為 state 的初始值。

### 通過解釋來理解預測

大多數機器學習模型都是黑盒子, 函數的內部邏輯對終端用戶是隱藏的。為了提高透明度, 我們通過簡單地將 Interface 類中的解釋關鍵字設置為默認值, 使向模型添加解釋變得非常容易。這允許你的用戶理解輸入的哪些部分負責輸出。看看下面這個簡單的接口, 它顯示了一個還包括解釋的圖像分類器:

```py
import requests
import tensorflow as tf

import gradio as gr

inception_net = tf.keras.applications.MobileNetV2()  # load the model

# Download human-readable labels for ImageNet.
response = requests.get("https://git.io/JJkYN")
labels = response.text.split("\n")


def classify_image(inp):
    inp = inp.reshape((-1, 224, 224, 3))
    inp = tf.keras.applications.mobilenet_v2.preprocess_input(inp)
    prediction = inception_net.predict(inp).flatten()
    return {labels[i]: float(prediction[i]) for i in range(1000)}


image = gr.Image(shape=(224, 224))
label = gr.Label(num_top_classes=3)

title = "Gradio Image Classifiction + Interpretation Example"
gr.Interface(
    fn=classify_image, inputs=image, outputs=label, interpretation="default", title=title
).launch()
```

通過提交一個輸入, 然後單擊輸出組件下的Interpret來測試解釋功能。

<iframe src="https://course-demos-gradio-image-interpretation.hf.space" frameBorder="0" height="570" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

除了Gradio提供的默認解釋方法之外, 你還可以為 `interpretation` 參數指定 `shap`, 並設置 `num_shap` 參數。這使用基於 Shapley 的解釋, 你可以在 [here](https://christophm.github.io/interpretable-ml-book/shap.html) 閱讀更多信息。最後, 還可以將自己的解釋函數傳入 `interpretation` 參數。在Gradio的入門頁面 [here](https://gradio.app/getting_started/) 中可以看到一個例子。

這結束了我們對Gradio的`Interface`類的深入研究。正如我們所看到的, 這個類使用幾行Python代碼創建機器學習演示變得簡單。然而, 有時你會想通過改變佈局或鏈接多個預測函數來定製你的demo。如果我們能以某種方式將 `接口` 分成可定製的 "塊", 那不是很好嗎? 幸運的是, 有! 這是最後一部分的主題。

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter9/6.mdx" />

### Gradio，回顧！
https://huggingface.co/learn/course/zh-TW/chapter9/8.md

# Gradio，回顧！

關於使用 Gradio 構建酷炫的 ML 演示的章節到此結束 - 我們希望您喜歡它！回顧一下，在本章中，我們學習了：

- 如何使用高級 `Interface` API 創建 Gradio 演示，以及如何配置不同的輸入和輸出模式。
- 通過臨時鏈接和託管在 [Hugging Face Spaces](https://huggingface.co/spaces) 上共享 Gradio 演示的不同方式。
- 如何將 Gradio 演示與 Hugging Face Hub 上的Model和Space集成在一起。
- 高級功能，例如在演示中存儲狀態或提供身份驗證。
- 如何使用 Gradio Blocks 完全控制演示的數據流和佈局。

如果您想測試您對本章所涵蓋概念的理解，請查看下一節中的測驗！

## 下一步去哪裡？

如果您想了解有關 Gradio 的更多信息，您可以

- 看看 repo 中的 [Demos](https://github.com/gradio-app/gradio/tree/main/demo)，那裡有很多例子。
- 請參閱 [指南](https://gradio.app/guides/) 頁面，您可以在其中找到有關酷炫和高級功能的指南。
- 查看 [文檔](https://gradio.app/docs/) 頁面瞭解詳情。


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter9/8.mdx" />

### 與他人分享演示
https://huggingface.co/learn/course/zh-TW/chapter9/4.md

# 與他人分享演示

<DocNotebookDropdown
  classNames="absolute z-10 right-0 top-0"
  options={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/chapter9/section4.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/chapter9/section4.ipynb"},
]} />

現在您已經構建了一個演示，您可能希望與其他人分享它。 梯度演示
可以通過兩種方式共享：使用 ***temporary share link*** 或 ***permanent hosting on Spaces***。

我們將很快介紹這兩種方法。 但在分享演示之前，您可能需要完善它 💅.

### 打磨你的 Gradio 演示:

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter9/gradio-demo-overview.png" alt="Overview of a gradio interface">
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter9/gradio-demo-overview-dark.png" alt="Overview of a gradio interface">
</div>

為了給你的演示添加額外的內容，`Interface` 類支持一些可選參數：
    - `title`：你可以給你的演示一個標題，它出現在輸入和輸出組件的上方。
    - `description`：您可以為界面提供描述（文本、Markdown 或 HTML），顯示在輸入和輸出組件的上方和標題下方。
    - `article`：您還可以編寫擴展文章（文本、Markdown 或 HTML）來解釋界面。如果提供，它會出現在輸入和輸出組件的_下方。
    - `theme`：不喜歡默認顏色？將主題設置為使用 `default`、`huggingface`、`grass`、`peach` 之一。您還可以添加 `dark-` 前綴，例如`dark-peach` 用於深色主題（或者只是 `dark` 用於默認的深色主題）。
    - `examples`：為了讓您的演示*更易於使用*，您可以為函數提供一些示例輸入。它們出現在 UI 組件下方，可用於填充界面。這些應該作為嵌套列表提供，其中外部列表​​由樣本組成，每個內部列表對應於每個輸入組件的輸入組成。
    - `live`：如果你想讓你的演示“活”，這意味著你的模型每次輸入更改時都會重新運行，你可以設置 `live=True`。這對使用快速模型很有意義（我們將在本節末尾看到一個示例）
使用上面的選項，我們最終得到了一個更完整的界面。 運行下面的代碼，以便與 Rick and Morty 聊天：

```py
title = "Ask Rick a Question"
description = """
The bot was trained to answer questions based on Rick and Morty dialogues. Ask Rick anything!
<img src="https://huggingface.co/spaces/course-demos/Rick_and_Morty_QA/resolve/main/rick.png" width=200px>
"""

article = "Check out [the original Rick and Morty Bot](https://huggingface.co/spaces/kingabzpro/Rick_and_Morty_Bot) that this demo is based off of."

gr.Interface(
    fn=predict,
    inputs="textbox",
    outputs="text",
    title=title,
    description=description,
    article=article,
    examples=[["What are you doing?"], ["Where should we time travel to?"]],
).launch()
```

使用上面的選項，我們最終得到了一個更完整的界面。 試試下面的界面：

<iframe src="https://course-demos-Rick-and-Morty-QA.hf.space" frameBorder="0" height="800" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

### 使用臨時鏈接分享您的演示
現在我們已經有了機器學習模型的工作演示，讓我們學習如何輕鬆共享指向我們界面的鏈接。
通過在 `launch()` 方法中設置 `share=True` 可以輕鬆地公開共享接口：

```python
gr.Interface(classify_image, "image", "label").launch(share=True)
```

這會生成一個公開的、可共享的鏈接，您可以將其發送給任何人！ 當您發送此鏈接時，另一方的用戶可以在瀏覽器中試用該模型長達 72 小時。 因為處理發生在您的設備上（只要您的設備保持開啟！），您不必擔心打包任何依賴項。 如果您使用 Google Colab 筆記本工作，則始終會自動創建共享鏈接。 它通常看起來像這樣：**XXXXX.gradio.app**。 雖然鏈接是通過 Gradio 鏈接提供的，但我們只是您本地服務器的代理，不會存儲通過接口發送的任何數據。

但是請記住，這些鏈接是可公開訪問的，這意味著任何人都可以使用您的模型進行預測！ 因此，請確保不要通過您編寫的函數公開任何敏感信息，或允許在您的設備上發生任何關鍵更改。 如果設置 `share=False`（默認值），則僅創建本地鏈接。

### 在 Hugging Face Spaces 上託管您的演示

可以傳遞給同事的共享鏈接很酷，但是如何永久託管您的演示並讓它存在於互聯網上自己的“空間”中？

Hugging Face Spaces 提供了在互聯網上永久託管 Gradio 模型的基礎設施，**免費**！ Spaces 允許您創建並推送到（公共或私人）存儲庫，
你的 Gradio 在哪裡
接口代碼將存在於 `app.py` 文件中。 [閱讀分步教程](https://huggingface.co/blog/gradio-spaces) 開始使用，或觀看下面的示例視頻。

<Youtube id="LS9Y2wDVI0k" />

## ✏️ 讓我們應用它!

使用到目前為止我們在各節中學到的知識，讓我們創建我們在[本章第一節](/course/chapter9/1)中看到的草圖識別演示。 讓我們為我們的界面添加一些自定義並設置 `share=True` 以創建一個我們可以傳遞的公共鏈接。

我們可以從 [class_names.txt](https://huggingface.co/spaces/dawood/Sketch-Recognition/blob/main/class_names.txt) 加載標籤，並從 [pytorch_model.bin](https://huggingface.co/spaces/dawood/Sketch-Recognition/blob/main/pytorch_model.bin)加載預訓練的 pytorch 模型 。 通過點擊鏈接並單擊文件預覽左上角的下載來下載這些文件。 讓我們看看下面的代碼，看看我們如何使用這些文件來加載我們的模型並創建一個`predict()`函數：
```py
from pathlib import Path
import torch
import gradio as gr
from torch import nn

LABELS = Path("class_names.txt").read_text().splitlines()

model = nn.Sequential(
    nn.Conv2d(1, 32, 3, padding="same"),
    nn.ReLU(),
    nn.MaxPool2d(2),
    nn.Conv2d(32, 64, 3, padding="same"),
    nn.ReLU(),
    nn.MaxPool2d(2),
    nn.Conv2d(64, 128, 3, padding="same"),
    nn.ReLU(),
    nn.MaxPool2d(2),
    nn.Flatten(),
    nn.Linear(1152, 256),
    nn.ReLU(),
    nn.Linear(256, len(LABELS)),
)
state_dict = torch.load("pytorch_model.bin", map_location="cpu")
model.load_state_dict(state_dict, strict=False)
model.eval()


def predict(im):
    x = torch.tensor(im, dtype=torch.float32).unsqueeze(0).unsqueeze(0) / 255.0
    with torch.no_grad():
        out = model(x)
    probabilities = torch.nn.functional.softmax(out[0], dim=0)
    values, indices = torch.topk(probabilities, 5)
    return {LABELS[i]: v.item() for i, v in zip(indices, values)}
```

現在我們有了一個`predict()`函數。 下一步是定義並啟動我們的漸變界面：

```py
interface = gr.Interface(
    predict,
    inputs="sketchpad",
    outputs="label",
    theme="huggingface",
    title="Sketch Recognition",
    description="Who wants to play Pictionary? Draw a common object like a shovel or a laptop, and the algorithm will guess in real time!",
    article="<p style='text-align: center'>Sketch Recognition | Demo Model</p>",
    live=True,
)
interface.launch(share=True)
```

<iframe src="https://course-demos-Sketch-Recognition.hf.space" frameBorder="0" height="650" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>


注意 `Interface` 中的 `live=True` 參數，這意味著草圖演示使
每次有人在畫板上畫畫時的預測（沒有提交按鈕！）。

此外，我們還在 `launch()` 方法中設置了 `share=True` 參數。
這將創建一個公共鏈接，您可以發送給任何人！ 當您發送此鏈接時，對方的用戶可以嘗試草圖識別模型。 重申一下，您還可以在 Hugging Face Spaces 上託管模型，這就是我們能夠嵌入上面的演示的方式。

接下來，我們將介紹 Gradio 可用於 Hugging Face 生態系統的其他方式！

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter9/4.mdx" />

### Gradio 塊簡介
https://huggingface.co/learn/course/zh-TW/chapter9/7.md

# Gradio 塊簡介

<DocNotebookDropdown
  classNames="absolute z-10 right-0 top-0"
  options={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/chapter9/section7.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/chapter9/section7.ipynb"},
]} />

在前面的部分中, 我們已經使用 `Interface` 類探索並創建了演示。在本節中, 我們將介紹我們 **新開發**的稱為`gradio.Blocks`低級API。

現在, `接口`和`塊`之間有什麼區別?

- ⚡ `接口`: 一個高級 API, 讓你只需提供輸入和輸出列表即可創建完整的機器學習演示。

- 🧱 `塊`: :一個低級的 API, 它允許你完全控制你的應用程序的數據流和佈局。您可以使用`塊`(如 "構建塊")構建非常複雜的多步驟應用程序。


### 為什麼要塊 🧱?

正如我們在前幾節中看到的, `Interface` 類允許你使用幾行代碼輕鬆創建成熟的機器學習demo。`Interface` API 非常易於使用, 但缺乏 `Blocks` API 提供的靈活性。例如, 你可能想要:

- 將相關演示組合為一個web應用程序中的多個選項卡
- 更改demo的佈局, 例如指定輸入和輸出的位置
- 具有多步驟接口, 其中一個模型的輸出成為下一個模型的輸入, 或者通常具有更靈活的數據流
- 根據用戶輸入更改組件的屬性 (例如, 下拉列表中的選項) 或其可見性

我們將在下面探討所有這些概念。

### 使用塊創建簡單demo

安裝 Gradio 後, 將以下代碼作為 Python 腳本、Jupyter 筆記本或 Colab 筆記本運行。

```py
import gradio as gr


def flip_text(x):
    return x[::-1]


demo = gr.Blocks()

with demo:
    gr.Markdown(
        """
    # Flip Text!
    Start typing below to see the output.
    """
    )
    input = gr.Textbox(placeholder="Flip this text")
    output = gr.Textbox()

    input.change(fn=flip_text, inputs=input, outputs=output)

demo.launch()
```

<iframe src="https://course-demos-flip-text.hf.space" frameBorder="0" height="400" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

上述簡單示例介紹了塊的4個基本概念:

1. 塊允許你允許你構建結合markdown、HTML、按鈕和交互組件的web應用程序, 只需在一個帶有gradio的Python中實例化對象。
<Tip>
🙋如果你不熟悉 Python 中的 `with` 語句, 我們建議你查看來自 Real Python 的極好的[教程](https://realpython.com/python-with-statement/)。看完後回到這裡 🤗
</Tip>
實例化組件的順序很重要, 因為每個元素都按照創建的順序呈現到 Web 應用程序中。(更復雜的佈局在下面討論)

2. 你可以在代碼中的任何位置定義常規 Python 函數, 並使用`塊`在用戶輸入的情況下運行它們。在我們的示例中, 們有一個"翻轉"輸入文本的簡單函數, 但你可以編寫任何 Python 函數, 從簡單的計算到處理機器學習模型的預測。

3. 你可以將事件指定給任何`塊`組件。這將在組件被單擊、更改等情況下運行函數。當你分配一個事件時, 你傳入三個參數: `fn`: 應該被調用的函數, `inputs`: 輸入組件的(列表), 以及 `outputs`: 應該被調用的輸出組件的(列表)。

   在上面的示例中, 當名為 `input` 的 `Textbox` 中的值發生變化時, 我們運行 `flip_text()` 函數。該事件讀取`輸入`中的值, 將其作為名稱參數傳遞給 `flip_text()`, 然後它返回一個值, 該值被分配給我們的第二個名為 `output` 的 `Textbox`。

   要查看每個組件支持的事件列表, 請參閱 Gradio [文檔](https://www.gradio.app/docs/)。

4. 塊會根據你定義的事件觸發器自動確定組件是否應該是交互式的 (接受用戶輸入)。在我們的示例中, 第一個文本框是交互式的, 因為它的值由 `flip_text()` 函數使用。第二個文本框不是交互式的, 因為它的值從不用作輸入。在某些情況下, 你可能想要覆蓋它, 你可以通過傳遞一個布爾值給組件的`交互`參數(例如 `gr.Textbox(placeholder="Flip this text", interactive=True)`)。

### 自定義演示的佈局

我們如何使用`塊`來定製我們的演示的佈局? 默認情況下, `塊`在一列中垂直呈現創建的組件。你可以通過使用 `with gradio.Column():` 創建其他列或使用 `with gradio.Row():` 創建其他行並在這些上下文中創建組件來改變這一點。

你應該記住: 在 `列` 下創建的任何組件(這也是默認設置) 都將垂直佈局。在 `Row` 下創建的任何組件都將水平佈局, 類似於 [Web 開發中的 flexbox 模型](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Basic_Concepts_of_Flexbox)。

最後, 你還可以使用 `with gradio.Tabs()` 上下文管理器為您的demo創建選項卡。在此上下文中, 您可以通過使用 `gradio.TabItem(name_of_tab):` 指定來創建多個選項卡。在 `gradio.TabItem(name_of_tab):` 中創建的任何組件都會出現在該選項卡中。

現在讓我們在demo中添加一個 `flip_image()`函數並添加一個翻轉圖像的新選項卡。下面是一個帶有 2 個選項卡的示例, 也使用了一個行:

```py
import numpy as np
import gradio as gr

demo = gr.Blocks()


def flip_text(x):
    return x[::-1]


def flip_image(x):
    return np.fliplr(x)


with demo:
    gr.Markdown("Flip text or image files using this demo.")
    with gr.Tabs():
        with gr.TabItem("Flip Text"):
            with gr.Row():
                text_input = gr.Textbox()
                text_output = gr.Textbox()
            text_button = gr.Button("Flip")
        with gr.TabItem("Flip Image"):
            with gr.Row():
                image_input = gr.Image()
                image_output = gr.Image()
            image_button = gr.Button("Flip")

    text_button.click(flip_text, inputs=text_input, outputs=text_output)
    image_button.click(flip_image, inputs=image_input, outputs=image_output)

demo.launch()
```

<iframe src="https://course-demos-flip-text-image.hf.space" frameBorder="0" height="450" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>


你會注意到, 在這個示例中, 我們還在每個選項卡中創建了一個 `Button` 組件, 並且我們為每個按鈕分配了一個點擊事件,這是實際運行該函數的事件。

### 探索事件和狀態

正如你可以控制佈局一樣, `塊` 可以讓你對觸發函數調用的事件進行細粒度控制。每個組件和許多佈局都有它們支持的特定事件。

例如, `Textbox` 組件有兩個事件: `change()` (當文本框內的值發生變化時), 和 `submit()` (當用戶在關注文本框時按下enter鍵)。更復雜的組件可以有更多的事件: 例如,`Audio`組件也有單獨的事件, 用於播放、清除、暫停音頻文件等。請參閱文檔瞭解每個組件支持的事件。

你可以將事件觸發器附加到這些事件中的一個、一個或多個。你可以通過在組件實例中調用事件名稱作為函數來創建一個事件觸發器 -- 例如 `textbox.change(...)` 或 `btn.click(...)`。如前所述, 該函數接受三個參數:

- `fn`: 要運行的函數
- `inputs`: 組件的(列表), 其值應作為函數的輸入參數提供。每個組件的值按順序映射到相應的函數參數。如果函數不帶任何參數, 則此參數可以為 None。
- `outputs`: 應根據函數返回的值更新其值的組件(列表)。每個返回值按順序設置相應組件的值。如果函數不返回任何內容, 則此參數可以為None。

你甚至可以使輸入和輸出組件成為同一個組件, 就像我們在這個使用 GPT 模型進行文本補全的示例中所做的那樣:

```py
import gradio as gr

api = gr.Interface.load("huggingface/EleutherAI/gpt-j-6B")


def complete_with_gpt(text):
    # Use the last 50 characters of the text as context
    return text[:-50] + api(text[-50:])


with gr.Blocks() as demo:
    textbox = gr.Textbox(placeholder="Type here and press enter...", lines=4)
    btn = gr.Button("Generate")

    btn.click(complete_with_gpt, textbox, textbox)

demo.launch()
```

<iframe src="https://course-demos-blocks-gpt.hf.space" frameBorder="0" height="300" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

### 創建多步demo

在某些情況下, 您可能需要一個 _多步驟的demo_, 其中重用一個函數的輸出作為下一個函數的輸入。使用 `塊` 很容易做到這一點, 因為你可以使用組件作為一個事件觸發器的輸入, 但作為另一個事件觸發器的輸出。看看下面示例中的文本組件, 它的值是語音到文本模型的結果, 但也被傳遞到情感分析模型:

```py
from transformers import pipeline

import gradio as gr

asr = pipeline("automatic-speech-recognition", "facebook/wav2vec2-base-960h")
classifier = pipeline("text-classification")


def speech_to_text(speech):
    text = asr(speech)["text"]
    return text


def text_to_sentiment(text):
    return classifier(text)[0]["label"]


demo = gr.Blocks()

with demo:
    audio_file = gr.Audio(type="filepath")
    text = gr.Textbox()
    label = gr.Label()

    b1 = gr.Button("Recognize Speech")
    b2 = gr.Button("Classify Sentiment")

    b1.click(speech_to_text, inputs=audio_file, outputs=text)
    b2.click(text_to_sentiment, inputs=text, outputs=label)

demo.launch()
```

<iframe src="https://course-demos-blocks-multi-step.hf.space" frameBorder="0" height="600" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

### 更新組件屬性

到目前為止, 我們已經瞭解瞭如何創建事件來更新另一個組件的值。但是, 如果您想更改組件的其他屬性, 例如文本框的可見性或單選按鈕組中的選項, 會發生什麼? 您可以通過返回組件類的 `update()` 方法而不是函數的常規返回值來做到這一點。

這很容易用一個例子來說明:

```py
import gradio as gr


def change_textbox(choice):
    if choice == "short":
        return gr.Textbox.update(lines=2, visible=True)
    elif choice == "long":
        return gr.Textbox.update(lines=8, visible=True)
    else:
        return gr.Textbox.update(visible=False)


with gr.Blocks() as block:
    radio = gr.Radio(
        ["short", "long", "none"], label="What kind of essay would you like to write?"
    )
    text = gr.Textbox(lines=2, interactive=True)

    radio.change(fn=change_textbox, inputs=radio, outputs=text)
    block.launch()
```

<iframe src="https://course-demos-blocks-update-component-properti-833c723.hf.space" frameBorder="0" height="300" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

我們剛剛探索了`塊`的所有核心概念! 就像 `參數一樣`, 你可以創建很酷的demo, 可以通過在`launch()`方法中使用`share=True`來共享, 或者部署在[Hugging Face Spaces](https://huggingface.co/spaces)上。

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter9/7.mdx" />

### 構建你的第一個演示
https://huggingface.co/learn/course/zh-TW/chapter9/2.md

# 構建你的第一個演示

<DocNotebookDropdown
  classNames="absolute z-10 right-0 top-0"
  options={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/chapter9/section2.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/chapter9/section2.ipynb"},
]} />

讓我們從安裝 Gradio 開始吧！ 由於它是一個 Python 包，只需運行：

`$ pip install gradio `

您可以在任何地方運行 Gradio，無論是從您最喜歡的 Python IDE、Jupyter notebook 還是 Google Colab 🤯！
所以無論你在哪裡運行 Python，都可以安裝 Gradio！

讓我們從一個簡單的“Hello World”示例開始，熟悉 Gradio 語法：

```py
import gradio as gr


def greet(name):
    return "Hello " + name


demo = gr.Interface(fn=greet, inputs="text", outputs="text")

demo.launch()
```

讓我們看一下上面的代碼：

-首先，我們定義一個名為 `greet()` 的函數。 在這種情況下，它是一個在您的名字前添加“Hello”的簡單函數，但它通常可以是 *any* Python 函數。 例如，在機器學習應用程序中，此函數將*調用模型以對輸入進行預測*並返回輸出。
- 然後，我們創建一個帶有三個參數的 Gradio `Interface`，`fn`、`inputs` 和 `outputs`。 這些參數定義了預測函數，以及我們想要的輸入和輸出組件的_type_。 在我們的例子中，兩個組件都是簡單的文本框。
- 然後我們在我們創建的 `Interface` 上調用 `launch()` 方法。

如果你運行這段代碼，下面的界面會自動出現在 Jupyter/Colab notebook 中，或者在瀏覽器中彈出 **[http://localhost:7860](http://localhost:7860/)** 如果運行 從一個腳本。

<iframe src="https://course-demos-hello-world.hf.space" frameBorder="0" height="250" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

立即嘗試使用您自己的姓名或其他輸入來使用此 GUI！

您會注意到，在這個 GUI 中，Gradio 自動推斷輸入參數的名稱 (`name`)並將其應用為文本框頂部的標籤。 如果你想改變它怎麼辦？或者，如果您想以其他方式自定義文本框？ 在這種情況下，您可以實例化一個表示輸入組件的類對象。

看看下面的例子：

```py
import gradio as gr


def greet(name):
    return "Hello " + name


# We instantiate the Textbox class
textbox = gr.Textbox(label="Type your name here:", placeholder="John Doe", lines=2)

gr.Interface(fn=greet, inputs=textbox, outputs="text").launch()
```

<iframe src="https://course-demos-hello-world-custom.hf.space" frameBorder="0" height="300" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

在這裡，我們創建了一個帶有標籤、佔位符和一組行數的輸入文本框。您可以對輸出文本框執行相同的操作，但我們現在將其保留。

我們已經看到，只需幾行代碼，Gradio 就可以讓您圍繞任何具有任何類型輸入或輸出的函數創建一個簡單的界面。 在本節中，我們從一個簡單的文本框開始，但在接下來的部分中，我們將介紹其他類型的輸入和輸出。 現在讓我們看看在 Gradio 應用程序中包含一些 NLP。


## 🤖 包括模型預測

現在讓我們構建一個簡單的界面，讓您可以演示像 GPT-2 這樣的**文本生成**模型。

我們將使用 🤗 Transformers 中的 `pipeline()` 函數加載我們的模型。
如果您需要快速複習，您可以返回到 [第 1 章中的那個部分](/course/chapter1/3#text-generation)。

首先，我們定義一個接受文本提示並返回文本完成的預測函數：

```py
from transformers import pipeline

model = pipeline("text-generation")


def predict(prompt):
    completion = model(prompt)[0]["generated_text"]
    return completion
```

此函數完成您提供的提示，您可以使用自己的輸入提示運行它以查看它是如何工作的。 這是一個示例（您可能會得到不同的完成）：

```
predict("My favorite programming language is")
```

```
>> My favorite programming language is Haskell. I really enjoyed the Haskell language, but it doesn't have all the features that can be applied to any other language. For example, all it does is compile to a byte array.
```

現在我們有了一個生成預測的函數，我們可以像之前一樣創建和啟動一個“接口”：

```py
import gradio as gr

gr.Interface(fn=predict, inputs="text", outputs="text").launch()
```


就是這樣！ 您現在可以使用此接口使用 GPT-2 模型生成文本，如下所示 🤯.

<iframe src="https://course-demos-gpt-2.hf.space" frameBorder="0" height="300" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

繼續閱讀以瞭解如何使用 Gradio 構建其他類型的演示！

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter9/2.mdx" />

### Part 2 發佈活動
https://huggingface.co/learn/course/zh-TW/events/2.md

# Part 2 發佈活動

對於課程第 2 部分的發佈，我們在微調 sprint 之前組織了一場現場活動，為期兩天的會談。 如果你錯過了，你可以趕上下面列出的講座！

## Day 1: Transformer 的高級API以及如何訓練它們

**Thomas Wolf：** *遷移學習和Transformers庫的誕生*

<div class="flex justify-center">
<Youtube id="wCYVeahJES0"/>
</div>

<p align="center">
<img src="https://i.imgur.com/9eq8oUi.png" alt="一張圖總結 Thom 的演講" width="80%"/>
</p>

Thomas Wolf 是 Hugging Face 的聯合創始人兼首席科學官。 Thomas Wolf 和 Hugging Face 團隊創建的工具被 5,000 多個研究機構使用，包括 Facebook 人工智能研究、谷歌研究、DeepMind、亞馬遜研究、蘋果、艾倫人工智能研究所以及大多數大學系。 Thomas Wolf 是人工智能領域有史以來最大的研究合作的發起人和高級主席：[“BigScience”](https://bigscience.huggingface.co)，以及一組廣泛使用的 [庫和工具]（https://github.com/huggingface/）。 Thomas Wolf 還是一位多產的教育家、人工智能和自然語言處理領域的思想領袖，並且經常受邀在世界各地的會議上發表演講 [https://thomwolf.io](https://thomwolf.io ）。

**Jay Alammar：** *Transformers模型的圖解*

<div class="flex justify-center">
<Youtube id="VzvG23gmcYU"/>
</div>

<p align="center">
<img src="https://i.imgur.com/rOZAuE9.png" alt="一張圖總結 Jay 的演講" width="80%"/>
</p>

通過他廣受歡迎的 ML 博客，Jay 幫助數百萬研究人員和工程師直觀地理解了機器學習工具和概念，從基礎（最終出現在 NumPy、Pandas 文檔）到前沿（Transformers、BERT、GPT-3）。

**Margaret Mitchell：** *關於機器學習開發中的價值觀*

<div class="flex justify-center">
<Youtube id="8j9HRMjh_s8"/>
</div>

<p align="center">
<img src="https://i.imgur.com/NuIsnY3.png" alt="一張圖總結 Margaret 的演講" width="80%"/>
</p>

Margaret Mitchell 是一名從事人工智能倫理研究的研究員，目前專注於以倫理為依據的人工智能開發。她在自然語言生成、輔助技術、計算機視覺和人工智能倫理方面發表了 50 多篇論文，並在會話生成和情感分類領域擁有多項專利。她之前曾在 Google AI 擔任員工研究科學家，在那裡她創立並共同領導了 Google 的倫理 AI 小組，專注於基礎 AI 倫理研究和在 Google 內部實施 AI 倫理。在加入谷歌之前，她是微軟研究院的一名研究員，專注於計算機視覺到語言的生成；並且是約翰霍普金斯大學的博士後，專注於貝葉斯建模和信息提取。她擁有阿伯丁大學計算機科學博士學位和華盛頓大學計算語言學碩士學位。在獲得學位的同時，她還於 2005 年至 2012 年在俄勒岡健康與科學大學從事機器學習、神經系統疾病和輔助技術方面的工作。她在多樣性、包容性、計算機科學和倫理學的交叉領域領導了許多研討會和倡議。她的工作獲得了國防部長阿什卡特和美國盲人基金會的獎勵，並被多家科技公司實施。她喜歡園藝、狗和貓。

**Matthew Watson 和 Chen Qian：** *使用 Keras 的 NLP 工作流程*

<div class="flex justify-center">
<Youtube id="gZIP-_2XYMM"/>
</div>

<p align="center">
<img src="https://i.imgur.com/1vD2az8.png" alt="一張圖總結 Matt 和 Chen 的演講" width="80%"/>
</p>

Matthew Watson 是 Keras 團隊的機器學習工程師，專注於高級建模 API。 他在本科期間學習計算機圖形學，並在斯坦福大學獲得碩士學位。 作為一名幾乎是英語專業的學生，他轉向計算機科學，熱衷於跨學科工作並使 NLP 為更廣泛的受眾所接受。

Chen Qian 是 Keras 團隊的一名軟件工程師，專注於高級建模 API。 Chen 在斯坦福大學獲得電氣工程碩士學位，他對簡化 ML 任務和大規模 ML 的代碼實現特別感興趣。

**Mark Saroufim：** *如何使用 Pytorch 訓練模型*

<div class="flex justify-center">
<Youtube id="KmvPlW2cbIo"/>
</div>

<p align="center">
<img src="https://i.imgur.com/TPmlkm8.png" alt="一張圖總結 Mark 的演講" width="80%"/>
</p>

Mark Saroufim 是 Pytorch 的合作伙伴工程師，致力於開發 OSS 生產工具，包括 TorchServe 和 Pytorch Enterprise。 Mark 是 Graphcore、[yuri.ai](http://yuri.ai/)、Microsoft 和 NASA 的 JPL 的應用科學家和產品經理。 他熱衷於讓編程更有趣。

**Jakob Uszkoreit:** *它沒有壞所以<del>不要修復</del>讓我們打破它*

<div class="flex justify-center">
<Youtube id="C6jweXYFHSA"/>
</div>

<p align="center">
<img src="https://i.imgur.com/5dWQeNB.png" alt="一張圖總結 Jakob 的演講" width="80%"/>
</p>

Jakob Uszkoreit 是 Inceptive 的聯合創始人。 Inceptive 在緊密循環中使用大規模深度學習和高通量實驗設計用於疫苗和治療的 RNA 分子，目標是使基於 RNA 的藥物更容易獲得、更有效和更廣泛適用。 此前，Jakob 在谷歌工作了十多年，領導谷歌大腦、研究和搜索領域的研發團隊，致力於深度學習基礎、計算機視覺、語言理解和機器翻譯。

## Day 2: 可以使用的工具

**Lewis Tunstall：** *使用 🤗 Transformers Trainer 讓訓練更加簡單*

<div class="flex justify-center">
<Youtube id="u--UVvH-LIQ"/>
</div>

Lewis 是 Hugging Face 的機器學習工程師，專注於開發開源工具並讓更廣泛的社區可以訪問它們。 他還是 O'Reilly 即將出版的有關於Transform的合著者，您可以在 Twitter (@_lewtun) 上關注他，瞭解 NLP 提示和技巧！

**Matthew Carrigan：** *用於 🤗 Transformers 和 🤗 Datasets的新 TensorFlow 特性*

<div class="flex justify-center">
<Youtube id="gQUlXp1691w"/>
</div>

Matt 負責Transformers的TensorFlow維護，並將最終領導一場針對現任PyTorch派系的政變，可能會通過他的推特賬戶@carrigmat進行協調。

**Lysandre Debut:** *使用Hugging Face Hub 作為協作和共享機器學習項目*

<div class="flex justify-center">
<Youtube id="RBw1TmdEZp0"/>
</div>

<p align="center">
<img src="https://i.imgur.com/TarIPCz.png" alt="一張圖總結 Lysandre 的演講" width="80%"/>
</p>

Lysandre 是 Hugging Face 的機器學習工程師，他參與了許多開源項目。 他的目標是通過使用非常簡單的 API 開發強大的工具，讓每個人都可以使用機器學習。

**Lucile Saulnier:** *使用 🤗 Transformers 和 🤗 Tokenizers 獲取您自己的tokenizer*

<div class="flex justify-center">
<Youtube id="UkNmyTFKriI"/>
</div>

Lucile 是 Hugging Face 的機器學習工程師，負責開發和支持開源工具的使用。 她還積極參與了自然語言處理領域的許多研究項目，例如協作訓練模型和 BigScience。

**Sylvain Gugger:** *使用 🤗 Accelerate* 增強您的 PyTorch 訓練循環*

<div class="flex justify-center">
<Youtube id="t8Krzu-nSeY"/>
</div>

Sylvain 是 Hugging Face 的研究工程師，也是🤗 Transformers 的核心維護者之一，也是🤗 Accelerate 的開發者。 他喜歡讓模型訓練變得更容易。

**Merve Noyan:** *使用 🤗 Spaces 展示您的模型演示*

<div class="flex justify-center">
<Youtube id="vbaKOa4UXoM"/>
</div>

Merve 是 Hugging Face 的開發者倡導者，致力於開發工具並圍繞它們構建內容，以使每個人的機器學習民主化。

**Abubakar Abid：** *快速構建機器學習應用程序*

<div class="flex justify-center">
<Youtube id="c7mle2yYpwQ"/>
</div>

<p align="center">
<img src="https://i.imgur.com/qWIFeiF.png" alt="一張圖總結 Abubakar 的演講" width="80%"/>
</p>

Abubakar Abid 是 [Gradio](www.gradio.app) 的首席執行官。 他於 2015 年獲得麻省理工學院電氣工程和計算機科學學士學位，並於 2021 年獲得斯坦福大學應用機器學習博士學位。作為 Gradio 的首席執行官，Abubakar 致力於使機器學習模型更易於演示、調試和部署。

**Mathieu Desvé:** *AWS ML Vision：讓所有客戶都可以使用機器學習*

<div class="flex justify-center">
<Youtube id="O2e3pXO4aRE"/>
</div>

<p align="center">
<img src="https://i.imgur.com/oLdZTKy.png" alt="一張圖總結 Mathieu 的演講" width="80%"/>
</p>

技術愛好者，有空閒時間的創客。 我喜歡挑戰和解決客戶和用戶的問題，每天和有才華的人一起學習。 自 2004 年以來，我在前端、後端、基礎設施、運營和管理等多個職位上工作。 嘗試以敏捷的方式解決公共技術和管理問題。

**Philipp Schmid:** *使用 Amazon SageMaker 和🤗 Transformers 進行託管訓練*

<div class="flex justify-center">
<Youtube id="yG6J2Zfo8iw"/>
</div>

Philipp Schmid 是 Hugging Face 的機器學習工程師和技術主管，負責領導與 Amazon SageMaker 團隊的合作。 他熱衷於使尖端 NLP 模型民主化和生產化，並提高深度學習的易用性。

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/events/2.mdx" />

### 標記器，回顧！
https://huggingface.co/learn/course/zh-TW/chapter6/9.md

# 標記器，回顧！

<CourseFloatingBanner
    chapter={6}
    classNames="absolute z-10 right-0 top-0"
/>

完成這一章，辛苦了！

在深入研究標記器之後，您應該：

- 能夠使用舊的標記器作為模板來訓練新的標記器
- 瞭解如何使用偏移量將標記的位置映射到其原始文本範圍
- 瞭解 BPE、WordPiece 和 Unigram 之間的區別
- 能夠混合和匹配 🤗 Tokenizers 庫提供的塊來構建您自己的標記器
- 能夠在 🤗 Transformers 庫中使用該標記器

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter6/9.mdx" />

### 字節對編碼標記化
https://huggingface.co/learn/course/zh-TW/chapter6/5.md

# 字節對編碼標記化

<CourseFloatingBanner chapter={6}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/zh-CN/chapter6/section5.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/zh-CN/chapter6/section5.ipynb"},
]} />

字節對編碼(BPE)最初被開發為一種壓縮文本的算法,然後在預訓練 GPT 模型時被 OpenAI 用於標記化。許多 Transformer 模型都使用它,包括 GPT、GPT-2、RoBERTa、BART 和 DeBERTa。

<Youtube id="HEikzVL-lZU"/>

<Tip>

💡 本節深入介紹了BPE，甚至展示了一個完整的實現。如果你只想大致瞭解標記化算法，可以跳到最後。

</Tip>

## 訓練算法

BPE 訓練首先計算語料庫中使用的唯一單詞集(在完成標準化和預標記化步驟之後)，然後通過獲取用於編寫這些單詞的所有符號來構建詞彙表。舉一個簡單的例子，假設我們的語料庫使用了這五個詞:

```
"hug", "pug", "pun", "bun", "hugs"
```

基礎詞彙將是 `["b", "g", "h", "n", "p", "s", "u"]`。對於實際情況，基本詞彙表將包含所有 ASCII 字符，至少，可能還包含一些 Unicode 字符。如果您正在標記的示例使用不在訓練語料庫中的字符，則該字符將轉換為未知標記。這就是為什麼許多 NLP 模型在分析帶有表情符號的內容方面非常糟糕的原因之一。

<Tip>

TGPT-2 和 RoBERTa 標記器(非常相似)有一個聰明的方法來處理這個問題: 他們不把單詞看成是用 Unicode 字符寫的，而是用字節寫的。這樣，基本詞彙表的大小很小(256),但你能想到的每個字符仍將被包含在內,而不會最終轉換為未知標記。這個技巧被稱為 *字節級 BPE*。

</Tip>

獲得這個基本詞彙後，我們添加新的標記，直到通過學習*合併*達到所需的詞彙量，這是將現有詞彙表的兩個元素合併為一個新元素的規則。因此在開始時，這些合併將創建具有兩個字符的標記，然後隨著訓練的進行，會創建更長的子詞。

在分詞器訓練期間的任何一步，BPE 算法都會搜索最常見的現有標記對 ("對",這裡我們指的是單詞中的兩個連續標記)。最頻繁的一對將被合併，我們沖洗並重複下一步。

回到我們之前的例子，讓我們假設單詞具有以下頻率：

```
("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5)
```

意味著 `"hug"` 在語料庫中出現了10次, `"pug"` 5次, `"pun"` 12次, `"bun"` 4次, 以及 `"hugs"` 5次。我們通過將每個單詞拆分為字符(形成我們初始詞彙表的字符)來開始訓練,這樣我們就可以將每個單詞視為一個標記列表:

```
("h" "u" "g", 10), ("p" "u" "g", 5), ("p" "u" "n", 12), ("b" "u" "n", 4), ("h" "u" "g" "s", 5)
```

然後我們看成對。這對 `("h", "u")` 出現在單詞 `"hug"` 和 `"hugs"`中,所以語料庫中總共有15次。不過,這並不是最頻繁的一對:這個榮譽屬於 `("u", "g")`,它出現在 `"hug"`, `"pug"`, 以及 `"hugs"`中,在詞彙表中總共 20 次。

因此,標記器學習的第一個合併規則是 `("u", "g") -> "ug"`,意思就是 `"ug"` 將被添加到詞彙表中,並且這對應該合併到語料庫的所有單詞中。在這個階段結束時,詞彙表和語料庫看起來像這樣:

```
Vocabulary: ["b", "g", "h", "n", "p", "s", "u", "ug"]
Corpus: ("h" "ug", 10), ("p" "ug", 5), ("p" "u" "n", 12), ("b" "u" "n", 4), ("h" "ug" "s", 5)
```

現在我們有一些導致標記長於兩個字符的對: 例如 `("h", "ug")`, 在語料庫中出現15次。然而,這個階段最頻繁的對是 `("u", "n")`,在語料庫中出現16次,所以學到的第二個合併規則是 `("u", "n") -> "un"`。將其添加到詞彙表併合並所有現有的這個對,將出現:

```
Vocabulary: ["b", "g", "h", "n", "p", "s", "u", "ug", "un"]
Corpus: ("h" "ug", 10), ("p" "ug", 5), ("p" "un", 12), ("b" "un", 4), ("h" "ug" "s", 5)
```

現在最頻繁的一對是 `("h", "ug")`,所以我們學習了合併規則 `("h", "ug") -> "hug"`,這給了我們第一個三個字母的標記。合併後,語料庫如下所示:

```
Vocabulary: ["b", "g", "h", "n", "p", "s", "u", "ug", "un", "hug"]
Corpus: ("hug", 10), ("p" "ug", 5), ("p" "un", 12), ("b" "un", 4), ("hug" "s", 5)
```

我們繼續這樣合併,直到達到我們所需的詞彙量。

<Tip>

✏️ **現在輪到你了!**你認為下一個合併規則是什麼？

</Tip>

## 標記化算法

標記化緊跟訓練過程,從某種意義上說,通過應用以下步驟對新輸入進行標記:

1. 規範化
2. 預標記化
3. 將單詞拆分為單個字符
4. 將學習的合併規則按順序應用於這些拆分

讓我們以我們在訓練期間使用的示例為例,學習三個合併規則:

```
("u", "g") -> "ug"
("u", "n") -> "un"
("h", "ug") -> "hug"
```

這個單詞 `"bug"` 將被標記為 `["b", "ug"]`。然而 `"mug"`,將被標記為 `["[UNK]", "ug"]`,因為字母 `"m"` 不再基本詞彙表中。同樣,單詞`"thug"` 會被標記為 `["[UNK]", "hug"]`: 字母 `"t"` 不在基本詞彙表中,應用合併規則首先導致 `"u"` 和 `"g"` 被合併,然後是 `"hu"` 和 `"g"` 被合併。

<Tip>

✏️ **現在輪到你了!** 你認為這個詞 `"unhug"` 將如何被標記？

</Tip>

## 實現 BPE

現在讓我們看一下 BPE 算法的實現。這不會是你可以在大型語料庫上實際使用的優化版本;我們只是想向你展示代碼,以便你可以更好地理解算法

首先我們需要一個語料庫,所以讓我們用幾句話創建一個簡單的語料庫:

```python
corpus = [
    "This is the Hugging Face course.",
    "This chapter is about tokenization.",
    "This section shows several tokenizer algorithms.",
    "Hopefully, you will be able to understand how they are trained and generate tokens.",
]
```

接下來,我們需要將該語料庫預先標記為單詞。由於我們正在複製 BPE 標記器(如 GPT-2),我們將使用 `gpt2` 標記器作為預標記化的標記器:

```python
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("gpt2")
```

然後我們在進行預標記化時計算語料庫中每個單詞的頻率:

```python
from collections import defaultdict

word_freqs = defaultdict(int)

for text in corpus:
    words_with_offsets = tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str(text)
    new_words = [word for word, offset in words_with_offsets]
    for word in new_words:
        word_freqs[word] += 1

print(word_freqs)
```

```python out
defaultdict(int, {'This': 3, 'Ġis': 2, 'Ġthe': 1, 'ĠHugging': 1, 'ĠFace': 1, 'ĠCourse': 1, '.': 4, 'Ġchapter': 1,
    'Ġabout': 1, 'Ġtokenization': 1, 'Ġsection': 1, 'Ġshows': 1, 'Ġseveral': 1, 'Ġtokenizer': 1, 'Ġalgorithms': 1,
    'Hopefully': 1, ',': 1, 'Ġyou': 1, 'Ġwill': 1, 'Ġbe': 1, 'Ġable': 1, 'Ġto': 1, 'Ġunderstand': 1, 'Ġhow': 1,
    'Ġthey': 1, 'Ġare': 1, 'Ġtrained': 1, 'Ġand': 1, 'Ġgenerate': 1, 'Ġtokens': 1})
```

下一步是計算基本詞彙,由語料庫中使用的所有字符組成:

```python
alphabet = []

for word in word_freqs.keys():
    for letter in word:
        if letter not in alphabet:
            alphabet.append(letter)
alphabet.sort()

print(alphabet)
```

```python out
[ ',', '.', 'C', 'F', 'H', 'T', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'k', 'l', 'm', 'n', 'o', 'p', 'r', 's',
  't', 'u', 'v', 'w', 'y', 'z', 'Ġ']
```

我們還在該詞彙表的開頭添加了模型使用的特殊標記。對於GPT-2,唯一的特殊標記是 `"<|endoftext|>"`:

```python
vocab = ["<|endoftext|>"] + alphabet.copy()
```

我們現在需要將每個單詞拆分為單獨的字符,以便能夠開始訓練:

```python
splits = {word: [c for c in word] for word in word_freqs.keys()}
```

現在我們已準備好進行訓練,讓我們編寫一個函數來計算每對的頻率。我們需要在訓練的每個步驟中使用它:

```python
def compute_pair_freqs(splits):
    pair_freqs = defaultdict(int)
    for word, freq in word_freqs.items():
        split = splits[word]
        if len(split) == 1:
            continue
        for i in range(len(split) - 1):
            pair = (split[i], split[i + 1])
            pair_freqs[pair] += freq
    return pair_freqs
```

讓我們來看看這個字典在初始拆分後的一部分:

```python
pair_freqs = compute_pair_freqs(splits)

for i, key in enumerate(pair_freqs.keys()):
    print(f"{key}: {pair_freqs[key]}")
    if i >= 5:
        break
```

```python out
('T', 'h'): 3
('h', 'i'): 3
('i', 's'): 5
('Ġ', 'i'): 2
('Ġ', 't'): 7
('t', 'h'): 3
```

現在, 找到最頻繁的對只需要一個快速的循環:

```python
best_pair = ""
max_freq = None

for pair, freq in pair_freqs.items():
    if max_freq is None or max_freq < freq:
        best_pair = pair
        max_freq = freq

print(best_pair, max_freq)
```

```python out
('Ġ', 't') 7
```

所以第一個要學習的合併是 `('Ġ', 't') -> 'Ġt'`, 我們添加 `'Ġt'` 到詞彙表:

```python
merges = {("Ġ", "t"): "Ġt"}
vocab.append("Ġt")
```

要繼續接下來的步驟,我們需要在我們的`分詞`字典中應用該合併。讓我們為此編寫另一個函數:

```python
def merge_pair(a, b, splits):
    for word in word_freqs:
        split = splits[word]
        if len(split) == 1:
            continue

        i = 0
        while i < len(split) - 1:
            if split[i] == a and split[i + 1] == b:
                split = split[:i] + [a + b] + split[i + 2 :]
            else:
                i += 1
        splits[word] = split
    return splits
```

我們可以看看第一次合併的結果:

```py
splits = merge_pair("Ġ", "t", splits)
print(splits["Ġtrained"])
```

```python out
['Ġt', 'r', 'a', 'i', 'n', 'e', 'd']
```

現在我們有了循環所需的一切,直到我們學會了我們想要的所有合併。我們的目標是詞彙量達到50:

```python
vocab_size = 50

while len(vocab) < vocab_size:
    pair_freqs = compute_pair_freqs(splits)
    best_pair = ""
    max_freq = None
    for pair, freq in pair_freqs.items():
        if max_freq is None or max_freq < freq:
            best_pair = pair
            max_freq = freq
    splits = merge_pair(*best_pair, splits)
    merges[best_pair] = best_pair[0] + best_pair[1]
    vocab.append(best_pair[0] + best_pair[1])
```

結果,我們學習了 19 條合併規則(初始詞彙表的大小 31 -- 30 字母字符,加上特殊標記):

```py
print(merges)
```

```python out
{('Ġ', 't'): 'Ġt', ('i', 's'): 'is', ('e', 'r'): 'er', ('Ġ', 'a'): 'Ġa', ('Ġt', 'o'): 'Ġto', ('e', 'n'): 'en',
 ('T', 'h'): 'Th', ('Th', 'is'): 'This', ('o', 'u'): 'ou', ('s', 'e'): 'se', ('Ġto', 'k'): 'Ġtok',
 ('Ġtok', 'en'): 'Ġtoken', ('n', 'd'): 'nd', ('Ġ', 'is'): 'Ġis', ('Ġt', 'h'): 'Ġth', ('Ġth', 'e'): 'Ġthe',
 ('i', 'n'): 'in', ('Ġa', 'b'): 'Ġab', ('Ġtoken', 'i'): 'Ġtokeni'}
```

詞彙表由特殊標記、初始字母和所有合併結果組成:

```py
print(vocab)
```

```python out
['<|endoftext|>', ',', '.', 'C', 'F', 'H', 'T', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'k', 'l', 'm', 'n', 'o',
 'p', 'r', 's', 't', 'u', 'v', 'w', 'y', 'z', 'Ġ', 'Ġt', 'is', 'er', 'Ġa', 'Ġto', 'en', 'Th', 'This', 'ou', 'se',
 'Ġtok', 'Ġtoken', 'nd', 'Ġis', 'Ġth', 'Ġthe', 'in', 'Ġab', 'Ġtokeni']
```

<Tip>

💡 在同一語料庫上使用 `train_new_from_iterator()` 不會產生完全相同的詞彙表。這是因為當有最頻繁對的選擇時,我們選擇遇到的第一個, 而 🤗 Tokenizers 庫根據內部ID選擇第一個。

</Tip>

為了對新文本進行分詞,我們對其進行預分詞、拆分，然後應用學到的所有合併規則:

```python
def tokenize(text):
    pre_tokenize_result = tokenizer._tokenizer.pre_tokenizer.pre_tokenize_str(text)
    pre_tokenized_text = [word for word, offset in pre_tokenize_result]
    splits = [[l for l in word] for word in pre_tokenized_text]
    for pair, merge in merges.items():
        for idx, split in enumerate(splits):
            i = 0
            while i < len(split) - 1:
                if split[i] == pair[0] and split[i + 1] == pair[1]:
                    split = split[:i] + [merge] + split[i + 2 :]
                else:
                    i += 1
            splits[idx] = split

    return sum(splits, [])
```

我們可以在任何由字母表中的字符組成的文本上嘗試這個:

```py
tokenize("This is not a token.")
```

```python out
['This', 'Ġis', 'Ġ', 'n', 'o', 't', 'Ġa', 'Ġtoken', '.']
```

<Tip warning={true}>

⚠️ 如果存在未知字符,我們的實現將拋出錯誤,因為我們沒有做任何處理它們。GPT-2 實際上沒有未知標記(使用字節級 BPE 時不可能得到未知字符),但這可能發生在這裡,因為我們沒有在初始詞彙表中包含所有可能的字節。 BPE 的這方面超出了本節的範圍,因此我們忽略了細節。

</Tip>

這就是 BPE 算法！接下來,我們將看看 WordPiece。

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter6/5.mdx" />

### 本章簡介
https://huggingface.co/learn/course/zh-TW/chapter6/1.md

# 本章簡介

<CourseFloatingBanner
    chapter={6}
    classNames="absolute z-10 right-0 top-0"
/>

在 [第三章] (/course/chapter3) 中，我們研究瞭如何在給定任務上微調模型。 當我們這樣做時，我們需要使用與模型預訓練相同的標記器——但是當我們想從頭開始訓練模型時該怎麼辦？ 不過，使用在來自其他領域或語言的語料庫上預訓練的標記器通常不是最理想的。 例如，在英語語料庫上訓練的標記器在日語文本語料庫上表現不佳，因為兩種語言中空格和標點符號的使用非常不同。

在本章中，您將學習如何在文本語料庫上訓練一個全新的標記器，然後將其用於預訓練語言模型。 這一切都將在 [🤗 Tokenizers](https://github.com/huggingface/tokenizers) 庫的幫助下完成，該庫在 [🤗 Transformers](https://github.com /huggingface/transformers) 庫之內。 我們將仔細研究這個庫提供的功能，並探討快速標記器與“慢”版本的區別。

我們將涵蓋的主題包括：

* 如何訓練一個新的標記器，類似於給定檢查點在新的文本語料庫上使用的標記器
* 快速標記器的特殊功能
* 目前 NLP 中使用的三種主要子詞標記化算法之間的差異
* 如何使用🤗 Tokenizers 庫從頭開始構建標記器並在一些數據上對其進行訓練

本章介紹的技術將使您為 [第 7 章](/course/chapter7/6) 中的部分做好準備，在那部分中，我們著眼於為 Python 源代碼創建語言模型。 讓我們首先看一下什麼是“訓練”標記器？

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter6/1.mdx" />

### 快速標記器的特殊能力
https://huggingface.co/learn/course/zh-TW/chapter6/3.md

# 快速標記器的特殊能力

{#if fw === 'pt'}

<CourseFloatingBanner chapter={6}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/zh-CN/chapter6/section3_pt.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/zh-CN/chapter6/section3_pt.ipynb"},
]} />

{:else}

<CourseFloatingBanner chapter={6}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/zh-CN/chapter6/section3_tf.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/zh-CN/chapter6/section3_tf.ipynb"},
]} />

{/if}

在本節中，我們將仔細研究 🤗 Transformers 中標記器的功能。到目前為止，我們只使用它們來標記輸入或將 ID 解碼迴文本，但是標記器——尤其是那些由 🤗 Tokenizers 庫支持的——可以做更多的事情。為了說明這些附加功能，我們將探索如何重現結果 **token-classification** （我們稱之為 **ner** ） 和 **question-answering** 我們第一次在[Chapter 1](/course/chapter1)中遇到的管道.

<Youtube id="g8quOxoqhHQ"/>

在接下來的討論中，我們會經常區分“慢”和“快”分詞器。慢速分詞器是在 🤗 Transformers 庫中用 Python 編寫的，而快速版本是由 🤗 分詞器提供的，它們是用 Rust 編寫的。如果你還記得在[Chapter 5](/course/chapter5/3)中報告了快速和慢速分詞器對藥物審查數據集進行分詞所需的時間的這張表，您應該知道為什麼我們稱它們為“快”和“慢”：

                | Fast tokenizer | Slow tokenizer
:--------------:|:--------------:|:-------------:
`batched=True`  | 10.8s          | 4min41s
`batched=False` | 59.2s          | 5min3s

<Tip warning={true}>

⚠️ 對單個句子進行分詞時，您不會總是看到相同分詞器的慢速和快速版本之間的速度差異。事實上，快速版本實際上可能更慢！只有同時對大量文本進行標記時，您才能清楚地看到差異。

</Tip>

## 批量編碼

<Youtube id="3umI3tm27Vw"/>

分詞器的輸出不是簡單的 Python 字典；我們得到的實際上是一個特殊的 **BatchEncoding** 目的。它是字典的子類（這就是為什麼我們之前能夠毫無問題地索引到該結果中的原因），但具有主要由快速標記器使用的附加方法。

除了它們的並行化能力之外，快速標記器的關鍵功能是它們始終跟蹤最終標記來自的原始文本範圍——我們稱之為偏移映射.這反過來又解鎖了諸如將每個單詞映射到它生成的標記或將原始文本的每個字符映射到它內部的標記等功能，反之亦然。讓我們看一個例子：

```py
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")
example = "My name is Sylvain and I work at Hugging Face in Brooklyn."
encoding = tokenizer(example)
print(type(encoding))
```

如前所述，我們得到一個 **BatchEncoding** 標記器輸出中的對象：

```python out
<class 'transformers.tokenization_utils_base.BatchEncoding'>
```

由於 **AutoTokenizer** 類默認選擇快速標記器，我們可以使用附加方法 this **BatchEncoding** 對象提供。我們有兩種方法來檢查我們的分詞器是快的還是慢的。我們可以檢查 **is_fast** 的屬性 **tokenizer** ：

```python
tokenizer.is_fast
```

```python out
True
```

或檢查我們的相同屬性 **encoding** ：

```python
encoding.is_fast
```

```python out
True
```

讓我們看看快速標記器使我們能夠做什麼。首先，我們可以訪問令牌而無需將 ID 轉換回令牌：

```py
encoding.tokens()
```

```python out
['[CLS]', 'My', 'name', 'is', 'S', '##yl', '##va', '##in', 'and', 'I', 'work', 'at', 'Hu', '##gging', 'Face', 'in',
 'Brooklyn', '.', '[SEP]']
```

在這種情況下，索引 5 處的令牌是 **##yl** ，它是原始句子中“Sylvain”一詞的一部分。我們也可以使用 **word_ids()** 獲取每個標記來自的單詞索引的方法：

```py
encoding.word_ids()
```

```python out
[None, 0, 1, 2, 3, 3, 3, 3, 4, 5, 6, 7, 8, 8, 9, 10, 11, 12, None]
```

我們可以看到分詞器的特殊標記 **[CLS]** 和 **[SEP]** 被映射到 **None** ，然後每個標記都映射到它起源的單詞。這對於確定一個標記是否在單詞的開頭或兩個標記是否在同一個單詞中特別有用。我們可以依靠 **##** 前綴，但它僅適用於類似 BERT 的分詞器；這種方法適用於任何類型的標記器，只要它是快速的。在下一章中，我們將看到如何使用此功能將每個單詞的標籤正確應用於命名實體識別 (NER) 和詞性 (POS) 標記等任務中的標記。我們還可以使用它來屏蔽來自屏蔽語言建模中來自同一單詞的所有標記（一種稱為全詞掩碼）。

<Tip>

一個詞是什麼的概念很複雜。例如，“I'll”（“I will”的縮寫）算一兩個詞嗎？它實際上取決於分詞器和它應用的預分詞操作。一些標記器只是在空格上拆分，因此他們會將其視為一個詞。其他人在空格頂部使用標點符號，因此將其視為兩個詞。

✏️ 試試看！從bert base cased和roberta base檢查點創建一個標記器，並用它們標記“81s”。你觀察到了什麼？ID這個詞是什麼？

</Tip>

同樣，有一個 **sentence_ids()** 我們可以用來將標記映射到它來自的句子的方法（儘管在這種情況下， **token_type_ids** 分詞器返回的信息可以為我們提供相同的信息）。

最後，我們可以將任何單詞或標記映射到原始文本中的字符，反之亦然，通過 **word_to_chars()** 或者 **token_to_chars()** 和 **char_to_word()** 或者 **char_to_token()** 方法。例如， **word_ids()** 方法告訴我們 **##yl** 是索引 3 處單詞的一部分，但它是句子中的哪個單詞？我們可以這樣發現：

```py
start, end = encoding.word_to_chars(3)
example[start:end]
```

```python out
Sylvain
```

正如我們之前提到的，這一切都是由快速標記器跟蹤每個標記來自列表中的文本跨度這一事實提供支持的抵消.為了說明它們的用途，接下來我們將向您展示如何複製結果 **token-classification** 手動管道。

<Tip>

✏️ 試試看！創建您自己的示例文本，看看您是否能理解哪些標記與單詞 ID 相關聯，以及如何提取單個單詞的字符跨度。對於獎勵積分，請嘗試使用兩個句子作為輸入，看看句子 ID 是否對您有意義。

</Tip>

## 在令牌分類管道內

在[Chapter 1](/course/chapter1)我們第一次嘗試使用 NER——任務是識別文本的哪些部分對應於個人、地點或組織等實體——使用 🤗 Transformers **pipeline()** 功能。然後，在[Chapter 2](/course/chapter2)，我們看到了管道如何將從原始文本中獲取預測所需的三個階段組合在一起：標記化、通過模型傳遞輸入和後處理。前兩步 **token-classification** 管道與任何其他管道相同，但後處理稍微複雜一些 - 讓我們看看如何！

{#if fw === 'pt'}

<Youtube id="0E7ltQB7fM8"/>

{:else}

<Youtube id="PrX4CjrVnNc"/>

{/if}

### 通過管道獲得基本結果

首先，讓我們獲取一個標記分類管道，以便我們可以手動比較一些結果。默認使用的模型是[dbmdz/bert-large-cased-finetuned-conll03-english](https://huggingface.co/dbmdz/bert-large-cased-finetuned-conll03-english);它對句子執行 NER：

```py
from transformers import pipeline

token_classifier = pipeline("token-classification")
token_classifier("My name is Sylvain and I work at Hugging Face in Brooklyn.")
```

```python out
[{'entity': 'I-PER', 'score': 0.9993828, 'index': 4, 'word': 'S', 'start': 11, 'end': 12},
 {'entity': 'I-PER', 'score': 0.99815476, 'index': 5, 'word': '##yl', 'start': 12, 'end': 14},
 {'entity': 'I-PER', 'score': 0.99590725, 'index': 6, 'word': '##va', 'start': 14, 'end': 16},
 {'entity': 'I-PER', 'score': 0.9992327, 'index': 7, 'word': '##in', 'start': 16, 'end': 18},
 {'entity': 'I-ORG', 'score': 0.97389334, 'index': 12, 'word': 'Hu', 'start': 33, 'end': 35},
 {'entity': 'I-ORG', 'score': 0.976115, 'index': 13, 'word': '##gging', 'start': 35, 'end': 40},
 {'entity': 'I-ORG', 'score': 0.98879766, 'index': 14, 'word': 'Face', 'start': 41, 'end': 45},
 {'entity': 'I-LOC', 'score': 0.99321055, 'index': 16, 'word': 'Brooklyn', 'start': 49, 'end': 57}]
```

該模型正確地將“Sylvain”生成的每個標記識別為一個人，將“Hugging Face”生成的每個標記識別為一個組織，將“Brooklyn”生成的標記識別為一個位置。我們還可以要求管道將對應於同一實體的令牌組合在一起：

```py
from transformers import pipeline

token_classifier = pipeline("token-classification", aggregation_strategy="simple")
token_classifier("My name is Sylvain and I work at Hugging Face in Brooklyn.")
```

```python out
[{'entity_group': 'PER', 'score': 0.9981694, 'word': 'Sylvain', 'start': 11, 'end': 18},
 {'entity_group': 'ORG', 'score': 0.97960204, 'word': 'Hugging Face', 'start': 33, 'end': 45},
 {'entity_group': 'LOC', 'score': 0.99321055, 'word': 'Brooklyn', 'start': 49, 'end': 57}]
```

**aggregation_strategy** 選擇將更改為每個分組實體計算的分數。和 **simple** 分數只是給定實體中每個標記的分數的平均值：例如，“Sylvain”的分數是我們在前面的示例中看到的標記分數的平均值 **S** , **##yl** , **##va** ， 和 **##in** .其他可用的策略是：

- `"first"`, 其中每個實體的分數是該實體的第一個標記的分數（因此對於“Sylvain”，它將是 0.993828，標記的分數)

- `"max"`,其中每個實體的分數是該實體中標記的最大分數（因此對於“Hugging Face”，它將是 0.98879766，即“Face”的分數）

- `"average"`, 其中每個實體的分數是組成該實體的單詞分數的平均值（因此對於“Sylvain”，與“simple”策略，但“Hugging Face”的得分為 0.9819，“Hugging”得分的平均值為 0.975，“Face”得分為 0.98879）

現在讓我們看看如何在不使用pipeline（）函數的情況下獲得這些結果！

### 從輸入到預測

{#if fw === 'pt'}

首先，我們需要標記我們的輸入並將其傳遞給模型。這是完全按照[Chapter 2](/course/chapter2);我們使用 **AutoXxx** 類，然後在我們的示例中使用它們：

```py
from transformers import AutoTokenizer, AutoModelForTokenClassification

model_checkpoint = "dbmdz/bert-large-cased-finetuned-conll03-english"
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)
model = AutoModelForTokenClassification.from_pretrained(model_checkpoint)

example = "My name is Sylvain and I work at Hugging Face in Brooklyn."
inputs = tokenizer(example, return_tensors="pt")
outputs = model(**inputs)
```

由於我們正在使用 **AutoModelForTokenClassification** 在這裡，我們為輸入序列中的每個標記獲得一組 logits：

```py
print(inputs["input_ids"].shape)
print(outputs.logits.shape)
```

```python out
torch.Size([1, 19])
torch.Size([1, 19, 9])
```

{:else}

首先，我們需要標記我們的輸入並將其傳遞給模型。這是完全按照[Chapter 2](/course/chapter2);我們使用 **AutoXxx** 類，然後在我們的示例中使用它們：

```py
from transformers import AutoTokenizer, TFAutoModelForTokenClassification

model_checkpoint = "dbmdz/bert-large-cased-finetuned-conll03-english"
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)
model = TFAutoModelForTokenClassification.from_pretrained(model_checkpoint)

example = "My name is Sylvain and I work at Hugging Face in Brooklyn."
inputs = tokenizer(example, return_tensors="tf")
outputs = model(**inputs)
```

於我們正在使用 **AutoModelForTokenClassification** 在這裡，我們為輸入序列中的每個標記獲得一組 logits：

```py
print(inputs["input_ids"].shape)
print(outputs.logits.shape)
```

```python out
(1, 19)
(1, 19, 9)
```

{/if}

我們有一個包含 19 個標記的 1 個序列的批次，模型有 9 個不同的標籤，因此模型的輸出具有 1 x 19 x 9 的形狀。與文本分類管道一樣，我們使用 softmax 函數來轉換這些 logits到概率，我們採用 argmax 來獲得預測（請注意，我們可以在 logits 上採用 argmax，因為 softmax 不會改變順序）：

{#if fw === 'pt'}

```py
import torch

probabilities = torch.nn.functional.softmax(outputs.logits, dim=-1)[0].tolist()
predictions = outputs.logits.argmax(dim=-1)[0].tolist()
print(predictions)
```

{:else}

```py
import tensorflow as tf

probabilities = tf.math.softmax(outputs.logits, axis=-1)[0]
probabilities = probabilities.numpy().tolist()
predictions = tf.math.argmax(outputs.logits, axis=-1)[0]
predictions = predictions.numpy().tolist()
print(predictions)
```

{/if}

```python out
[0, 0, 0, 0, 4, 4, 4, 4, 0, 0, 0, 0, 6, 6, 6, 0, 8, 0, 0]
```

 **model.config.id2label** 屬性包含索引到標籤的映射，我們可以用它來理解預測：

```py
model.config.id2label
```

```python out
{0: 'O',
 1: 'B-MISC',
 2: 'I-MISC',
 3: 'B-PER',
 4: 'I-PER',
 5: 'B-ORG',
 6: 'I-ORG',
 7: 'B-LOC',
 8: 'I-LOC'}
```

正如我們之前看到的，有 9 個標籤： **O** 是不在任何命名實體中的標記的標籤（它代表“外部”），然後我們為每種類型的實體（雜項、人員、組織和位置）提供兩個標籤。標籤 **B-XXX** 表示令牌在實體的開頭 **XXX** 和標籤 **I-XXX** 表示令牌在實體內 **XXX** .例如，在當前示例中，我們希望我們的模型對令牌進行分類 **S** 作為 **B-PER** （一個人實體的開始）和令牌 **##yl** , **##va** 和 **##in** 作為 **I-PER** （在個人實體內）

在這種情況下，您可能認為模型是錯誤的，因為它給出了標籤 **I-PER** 對所有這四個令牌，但這並不完全正確。實際上有兩種格式 **B-** 和 **I-** 標籤：IOB1和IOB2. IOB2 格式（下面粉紅色）是我們介紹的格式，而在 IOB1 格式（藍色）中，標籤以 **B-** 僅用於分隔相同類型的兩個相鄰實體。我們使用的模型在使用該格式的數據集上進行了微調，這就是它分配標籤的原因 **I-PER** 到 **S** 令牌。

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter6/IOB_versions.svg" alt="IOB1 vs IOB2 format"/>
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter6/IOB_versions-dark.svg" alt="IOB1 vs IOB2 format"/>
</div>

了這張地圖，我們已經準備好（幾乎完全）重現第一個管道的結果——我們可以獲取每個未被歸類為的標記的分數和標籤 **O** ：

```py
results = []
tokens = inputs.tokens()

for idx, pred in enumerate(predictions):
    label = model.config.id2label[pred]
    if label != "O":
        results.append(
            {"entity": label, "score": probabilities[idx][pred], "word": tokens[idx]}
        )

print(results)
```

```python out
[{'entity': 'I-PER', 'score': 0.9993828, 'index': 4, 'word': 'S'},
 {'entity': 'I-PER', 'score': 0.99815476, 'index': 5, 'word': '##yl'},
 {'entity': 'I-PER', 'score': 0.99590725, 'index': 6, 'word': '##va'},
 {'entity': 'I-PER', 'score': 0.9992327, 'index': 7, 'word': '##in'},
 {'entity': 'I-ORG', 'score': 0.97389334, 'index': 12, 'word': 'Hu'},
 {'entity': 'I-ORG', 'score': 0.976115, 'index': 13, 'word': '##gging'},
 {'entity': 'I-ORG', 'score': 0.98879766, 'index': 14, 'word': 'Face'},
 {'entity': 'I-LOC', 'score': 0.99321055, 'index': 16, 'word': 'Brooklyn'}]
```

這與我們之前的情況非常相似，只有一個例外：管道還為我們提供了有關 **start** 和 **end** 原始句子中的每個實體。這是我們的偏移映射將發揮作用的地方。要獲得偏移量，我們只需要設置 **return_offsets_mapping=True** 當我們將分詞器應用於我們的輸入時：

```py
inputs_with_offsets = tokenizer(example, return_offsets_mapping=True)
inputs_with_offsets["offset_mapping"]
```

```python out
[(0, 0), (0, 2), (3, 7), (8, 10), (11, 12), (12, 14), (14, 16), (16, 18), (19, 22), (23, 24), (25, 29), (30, 32),
 (33, 35), (35, 40), (41, 45), (46, 48), (49, 57), (57, 58), (0, 0)]
```

每個元組是對應於每個標記的文本跨度，其中 **(0, 0)** 保留用於特殊令牌。我們之前看到索引 5 處的令牌是 **##yl** ， 其中有 **(12, 14)** 作為這裡的抵消。如果我們在示例中抓取相應的切片：


```py
example[12:14]
```

我們得到了正確的文本跨度，而沒有 **##** ：

```python out
yl
```

使用這個，我們現在可以完成之前的結果：

```py
results = []
inputs_with_offsets = tokenizer(example, return_offsets_mapping=True)
tokens = inputs_with_offsets.tokens()
offsets = inputs_with_offsets["offset_mapping"]

for idx, pred in enumerate(predictions):
    label = model.config.id2label[pred]
    if label != "O":
        start, end = offsets[idx]
        results.append(
            {
                "entity": label,
                "score": probabilities[idx][pred],
                "word": tokens[idx],
                "start": start,
                "end": end,
            }
        )

print(results)
```

```python out
[{'entity': 'I-PER', 'score': 0.9993828, 'index': 4, 'word': 'S', 'start': 11, 'end': 12},
 {'entity': 'I-PER', 'score': 0.99815476, 'index': 5, 'word': '##yl', 'start': 12, 'end': 14},
 {'entity': 'I-PER', 'score': 0.99590725, 'index': 6, 'word': '##va', 'start': 14, 'end': 16},
 {'entity': 'I-PER', 'score': 0.9992327, 'index': 7, 'word': '##in', 'start': 16, 'end': 18},
 {'entity': 'I-ORG', 'score': 0.97389334, 'index': 12, 'word': 'Hu', 'start': 33, 'end': 35},
 {'entity': 'I-ORG', 'score': 0.976115, 'index': 13, 'word': '##gging', 'start': 35, 'end': 40},
 {'entity': 'I-ORG', 'score': 0.98879766, 'index': 14, 'word': 'Face', 'start': 41, 'end': 45},
 {'entity': 'I-LOC', 'score': 0.99321055, 'index': 16, 'word': 'Brooklyn', 'start': 49, 'end': 57}]
```

這和我們從第一個管道中得到的一樣！

### 分組實體

使用偏移量來確定每個實體的開始和結束鍵很方便，但該信息並不是絕對必要的。然而，當我們想要將實體組合在一起時，偏移量將為我們節省大量混亂的代碼。例如，如果我們想將令牌組合在一起 **Hu** , **##gging** ， 和 **Face** ，我們可以制定特殊的規則，說前兩個應該附加，同時刪除 **##** ，以及 **Face** 應該添加一個空格，因為它不以 **##** — 但這僅適用於這種特定類型的標記器。我們必須為 SentencePiece 或 Byte-Pair-Encoding 分詞器（本章稍後討論）。

編寫另一組規則。使用偏移量，所有自定義代碼都消失了：我們可以在原始文本中獲取從第一個標記開始到最後一個標記結束的跨度。所以，在令牌的情況下 **Hu** , **##gging** ， 和 **Face** ，我們應該從字符 33（開始 **Hu** ) 並在字符 45 之前結束（結束 **Face** )：

```py
example[33:45]
```

```python out
Hugging Face
```

為了編寫在對實體進行分組的同時對預測進行後處理的代碼，我們將連續並標記為的實體分組在一起 **I-XXX** ，除了第一個，可以標記為 **B-XXX** 或者 **I-XXX** （因此，當我們得到一個實體時，我們停止對實體進行分組 **O** ，一種新型實體，或 **B-XXX** 這告訴我們一個相同類型的實體正在啟動）：

```py
import numpy as np

results = []
inputs_with_offsets = tokenizer(example, return_offsets_mapping=True)
tokens = inputs_with_offsets.tokens()
offsets = inputs_with_offsets["offset_mapping"]

idx = 0
while idx < len(predictions):
    pred = predictions[idx]
    label = model.config.id2label[pred]
    if label != "O":
        # Remove the B- or I-
        label = label[2:]
        start, _ = offsets[idx]

        # Grab all the tokens labeled with I-label
        all_scores = []
        while (
            idx < len(predictions)
            and model.config.id2label[predictions[idx]] == f"I-{label}"
        ):
            all_scores.append(probabilities[idx][pred])
            _, end = offsets[idx]
            idx += 1

        # The score is the mean of all the scores of the tokens in that grouped entity
        score = np.mean(all_scores).item()
        word = example[start:end]
        results.append(
            {
                "entity_group": label,
                "score": score,
                "word": word,
                "start": start,
                "end": end,
            }
        )
    idx += 1

print(results)
```

我們得到了與第二條管道相同的結果！

```python out
[{'entity_group': 'PER', 'score': 0.9981694, 'word': 'Sylvain', 'start': 11, 'end': 18},
 {'entity_group': 'ORG', 'score': 0.97960204, 'word': 'Hugging Face', 'start': 33, 'end': 45},
 {'entity_group': 'LOC', 'score': 0.99321055, 'word': 'Brooklyn', 'start': 49, 'end': 57}]
```

這些偏移量非常有用的另一個任務示例是問答。深入研究這個管道，我們將在下一節中進行，也將使我們能夠了解 🤗 Transformers 庫中標記器的最後一個功能：當我們將輸入截斷為給定長度時處理溢出的標記。


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter6/3.mdx" />

### WordPiece 標記化
https://huggingface.co/learn/course/zh-TW/chapter6/6.md

# WordPiece 標記化  

<CourseFloatingBanner chapter={6}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/zh-CN/chapter6/section6.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/zh-CN/chapter6/section6.ipynb"},
]} />

WordPiece 是 Google 為預訓練 BERT 而開發的標記化算法。此後,它在不少基於 BERT 的 Transformer 模型中得到重用,例如 DistilBERT、MobileBERT、Funnel Transformers 和 MPNET。它在訓練方面與 BPE 非常相似,但實際標記化的方式不同。

<Youtube id="qpv6ms_t_1A"/>

<Tip>

💡 本節深入介紹 WordPiece,甚至展示完整的實現。如果您只想大致瞭解標記化算法,可以跳到最後。

</Tip>

## 訓練算法

<Tip warning={true}>

⚠️ Google 從未開源 WordPiece 訓練算法的實現,因此以下是我們基於已發表文獻的最佳猜測。它可能不是 100% 準確的。

</Tip>

與 BPE 一樣,WordPiece 從一個小詞彙表開始,包括模型使用的特殊標記和初始字母表。因為它通過添加前綴來識別子詞 (如同 `##` 對於 BERT),每個單詞最初是通過將該前綴添加到單詞內的所有字符來拆分的。所以,例如 `"word"` ,像這樣拆分:

```
w ##o ##r ##d
```

因此,初始字母表包含出現在單詞開頭的所有字符以及出現在單詞內部的以 WordPiece 前綴開頭的字符。

然後,再次像 BPE 一樣,WordPiece 學習合併規則。主要區別在於選擇要合併的對的方式。WordPiece 不是選擇最頻繁的對，而是使用以下公式計算每對的分數:

$$\mathrm{score} = (\mathrm{freq\_of\_pair}) / (\mathrm{freq\_of\_first\_element} \times \mathrm{freq\_of\_second\_element})$$

通過將配對的頻率除以其每個部分的頻率的乘積, 該算法優先合併單個部分在詞彙表中頻率較低的對。例如,它不一定會合並 `("un", "##able")` 即使這對在詞彙表中出現的頻率很高,因為 `"un"` 和 `"##able"` 很可能每個詞都出現在很多其他詞中並且出現頻率很高。相比之下,像 `("hu", "##gging")` 可能會更快地合併 (假設 "hugging" 經常出現在詞彙表中),因為 `"hu"` 和 `"##gging"` 這兩個詞單獨出現地頻率可能較低。

讓我們看看我們在 BPE 訓練示例中使用的相同詞彙:

```
("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5)
```

這裡的拆分將是:

```
("h" "##u" "##g", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("h" "##u" "##g" "##s", 5)
```

所以最初的詞彙將是 `["b", "h", "p", "##g", "##n", "##s", "##u"]` (如果我們暫時忘記特殊標記)。最頻繁的一對是 `("##u", "##g")` (目前20次),但 `"##u"` 單獨出現的頻率非常高,所以它的分數不是最高的(它是 1 / 36)。所有帶有 `"##u"` 的對實際上都有相同的分數(1 / 36),所以分數最高的對是 `("##g", "##s")` -- 唯一沒有 `"##u"` 的對-- 1 / 20,所以學習的第一個合併是 `("##g", "##s") -> ("##gs")`。

請注意,當我們合併時,我們刪除了兩個標記之間的 `##`,所以我們添加 `"##gs"` 到詞彙表中,並在語料庫的單詞中應用該合併:

```
Vocabulary: ["b", "h", "p", "##g", "##n", "##s", "##u", "##gs"]
Corpus: ("h" "##u" "##g", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("h" "##u" "##gs", 5)
```

在這一點中, `"##u"` 是在所有可能的對中,因此它們最終都具有相同的分數。假設在這種情況下,第一對被合併, `("h", "##u") -> "hu"`。這使得我們:

```
Vocabulary: ["b", "h", "p", "##g", "##n", "##s", "##u", "##gs", "hu"]
Corpus: ("hu" "##g", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("hu" "##gs", 5)
```

然後下一個最高的分數由 `("hu", "##g")` 和 `("hu", "##gs")` 共享(1/15,與其他所有對的 1/21 相比),因此合併得分最高的第一對:

```
Vocabulary: ["b", "h", "p", "##g", "##n", "##s", "##u", "##gs", "hu", "hug"]
Corpus: ("hug", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("hu" "##gs", 5)
```

我們繼續這樣處理,直到達到我們所需的詞彙量。

<Tip>

✏️ **現在輪到你了!** 下一個合併規則是什麼？
</Tip>

## 標記化算法

WordPiece 和 BPE 中的標記化的不同在於 WordPiece 只保存最終詞彙,而不是學習的合併規則。從要標記的單詞開始,WordPiece 找到詞彙表中最長的子詞,然後對其進行拆分。例如,如果我們使用上面例子中學到的詞彙,對於單詞 `"hugs"`,詞彙表中從頭開始的最長子詞是 `"hug"`,所以我們在那裡拆分並得到 `["hug", "##s"]`。 然後我們繼續使用詞彙表中的 `"##s"`,因此 `"hugs"` 的標記化是 `["hug", "##s"]`.

使用 BPE, 我們將按順序應用學習到的合併並將其標記為 `["hu", "##gs"]`,所以編碼不同。

再舉一個例子,讓我們看看 `"bugs"` 將如何被標記化。 `"b"` 是從詞彙表中單詞開頭開始的最長子詞,所以我們在那裡拆分並得到 `["b", "##ugs"]`。然後 `"##u"` 是詞彙表中從 `"##ugs"` 開始的最長的子詞,所以我們在那裡拆分並得到 `["b", "##u, "##gs"]`。最後, `"##gs"` 在詞彙表中,所以最後一個列表是 `"bugs"` 的標記化。

當分詞達到無法在詞彙表中找到子詞的階段時, 整個詞被標記為未知 -- 例如, `"mug"` 將被標記為 `["[UNK]"]`,就像 `"bum"` (即使我們可以以 `"b"` 和 `"##u"` 開始, `"##m"` 不在詞彙表中,由此產生的標記將只是 `["[UNK]"]`, 不是 `["b", "##u", "[UNK]"]`)。這是與 BPE 的另一個區別,BPE 只會將不在詞彙表中的單個字符分類為未知。

<Tip>

✏️ **現在輪到你了!** `"pugs"` 將被如何標記?

</Tip>

## 實現 WordPiece

現在讓我們看一下 WordPiece 算法的實現。與 BPE 一樣,這只是教學,你將無法在大型語料庫中使用它。

我們將使用與 BPE 示例中相同的語料庫:

```python
corpus = [
    "This is the Hugging Face course.",
    "This chapter is about tokenization.",
    "This section shows several tokenizer algorithms.",
    "Hopefully, you will be able to understand how they are trained and generate tokens.",
]
```

首先,我們需要將語料庫預先標記為單詞。由於我們正在複製 WordPiece 標記器 (如 BERT),因此我們將使用 `bert-base-cased` 標記器用於預標記化:

```python
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")
```

然後我們在進行預標記化時計算語料庫中每個單詞的頻率:

```python
from collections import defaultdict

word_freqs = defaultdict(int)
for text in corpus:
    words_with_offsets = tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str(text)
    new_words = [word for word, offset in words_with_offsets]
    for word in new_words:
        word_freqs[word] += 1

word_freqs
```

```python out
defaultdict(
    int, {'This': 3, 'is': 2, 'the': 1, 'Hugging': 1, 'Face': 1, 'Course': 1, '.': 4, 'chapter': 1, 'about': 1,
    'tokenization': 1, 'section': 1, 'shows': 1, 'several': 1, 'tokenizer': 1, 'algorithms': 1, 'Hopefully': 1,
    ',': 1, 'you': 1, 'will': 1, 'be': 1, 'able': 1, 'to': 1, 'understand': 1, 'how': 1, 'they': 1, 'are': 1,
    'trained': 1, 'and': 1, 'generate': 1, 'tokens': 1})
```

正如我們之前看到的,字母表是由單詞的所有第一個字母組成的唯一集合,以及出現在前綴為 `##` 的其他字母:

```python
alphabet = []
for word in word_freqs.keys():
    if word[0] not in alphabet:
        alphabet.append(word[0])
    for letter in word[1:]:
        if f"##{letter}" not in alphabet:
            alphabet.append(f"##{letter}")

alphabet.sort()
alphabet

print(alphabet)
```

```python out
['##a', '##b', '##c', '##d', '##e', '##f', '##g', '##h', '##i', '##k', '##l', '##m', '##n', '##o', '##p', '##r', '##s',
 '##t', '##u', '##v', '##w', '##y', '##z', ',', '.', 'C', 'F', 'H', 'T', 'a', 'b', 'c', 'g', 'h', 'i', 's', 't', 'u',
 'w', 'y']
```

我們還在該詞彙表的開頭添加了模型使用的特殊標記。在使用 BERT 的情況下,它是列表 `["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"]`:

```python
vocab = ["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"] + alphabet.copy()
```

接下來我們需要拆分每個單詞, 所有不是第一個字母的字母都以 `##` 為前綴:

```python
splits = {
    word: [c if i == 0 else f"##{c}" for i, c in enumerate(word)]
    for word in word_freqs.keys()
}
```

現在我們已經準備好訓練了,讓我們編寫一個函數來計算每對的分數。我們需要在訓練的每個步驟中使用它:

```python
def compute_pair_scores(splits):
    letter_freqs = defaultdict(int)
    pair_freqs = defaultdict(int)
    for word, freq in word_freqs.items():
        split = splits[word]
        if len(split) == 1:
            letter_freqs[split[0]] += freq
            continue
        for i in range(len(split) - 1):
            pair = (split[i], split[i + 1])
            letter_freqs[split[i]] += freq
            pair_freqs[pair] += freq
        letter_freqs[split[-1]] += freq

    scores = {
        pair: freq / (letter_freqs[pair[0]] * letter_freqs[pair[1]])
        for pair, freq in pair_freqs.items()
    }
    return scores
```

讓我們來看看這個字典在初始拆分後的一部分:

```python
pair_scores = compute_pair_scores(splits)
for i, key in enumerate(pair_scores.keys()):
    print(f"{key}: {pair_scores[key]}")
    if i >= 5:
        break
```

```python out
('T', '##h'): 0.125
('##h', '##i'): 0.03409090909090909
('##i', '##s'): 0.02727272727272727
('i', '##s'): 0.1
('t', '##h'): 0.03571428571428571
('##h', '##e'): 0.011904761904761904
```

現在,找到得分最高的對只需要一個快速循環:

```python
best_pair = ""
max_score = None
for pair, score in pair_scores.items():
    if max_score is None or max_score < score:
        best_pair = pair
        max_score = score

print(best_pair, max_score)
```

```python out
('a', '##b') 0.2
```

所以第一個要學習的合併是 `('a', '##b') -> 'ab'`, 並且我們添加 `'ab'` 到詞彙表中:

```python
vocab.append("ab")
```

要繼續接下來的步驟,我們需要在我們的 `拆分` 字典中應用該合併。讓我們為此編寫另一個函數:

```python
def merge_pair(a, b, splits):
    for word in word_freqs:
        split = splits[word]
        if len(split) == 1:
            continue
        i = 0
        while i < len(split) - 1:
            if split[i] == a and split[i + 1] == b:
                merge = a + b[2:] if b.startswith("##") else a + b
                split = split[:i] + [merge] + split[i + 2 :]
            else:
                i += 1
        splits[word] = split
    return splits
```

我們可以看看第一次合併的結果:

```py
splits = merge_pair("a", "##b", splits)
splits["about"]
```

```python out
['ab', '##o', '##u', '##t']
```

現在我們有了循環所需的一切,直到我們學會了我們想要的所有合併。我們的目標詞彙量為70:

```python
vocab_size = 70
while len(vocab) < vocab_size:
    scores = compute_pair_scores(splits)
    best_pair, max_score = "", None
    for pair, score in scores.items():
        if max_score is None or max_score < score:
            best_pair = pair
            max_score = score
    splits = merge_pair(*best_pair, splits)
    new_token = (
        best_pair[0] + best_pair[1][2:]
        if best_pair[1].startswith("##")
        else best_pair[0] + best_pair[1]
    )
    vocab.append(new_token)
```

然後我們可以查看生成的詞彙表:

```py
print(vocab)
```

```python out
['[PAD]', '[UNK]', '[CLS]', '[SEP]', '[MASK]', '##a', '##b', '##c', '##d', '##e', '##f', '##g', '##h', '##i', '##k',
 '##l', '##m', '##n', '##o', '##p', '##r', '##s', '##t', '##u', '##v', '##w', '##y', '##z', ',', '.', 'C', 'F', 'H',
 'T', 'a', 'b', 'c', 'g', 'h', 'i', 's', 't', 'u', 'w', 'y', '##fu', 'Fa', 'Fac', '##ct', '##ful', '##full', '##fully',
 'Th', 'ch', '##hm', 'cha', 'chap', 'chapt', '##thm', 'Hu', 'Hug', 'Hugg', 'sh', 'th', 'is', '##thms', '##za', '##zat',
 '##ut']
```

正如我們所看到的,與 BPE 相比,這個標記器將單詞的一部分作為標記學習得更快一些。

<Tip>

💡 在同一語料庫上使用 `train_new_from_iterator()` 不會產生完全相同的詞彙表。這是因為 🤗 Tokenizers 庫沒有為訓練實現 WordPiece(因為我們不完全確定它的內部結構),而是使用 BPE。

</Tip>

為了對新文本進行分詞,我們對其進行預分詞、拆分,然後對每個單詞應用分詞算法。也就是說,我們從第一個詞的開頭尋找最大的子詞並將其拆分,然後我們在第二部分重複這個過程,對於該詞的其餘部分和文本中的以下詞,依此類推:

```python
def encode_word(word):
    tokens = []
    while len(word) > 0:
        i = len(word)
        while i > 0 and word[:i] not in vocab:
            i -= 1
        if i == 0:
            return ["[UNK]"]
        tokens.append(word[:i])
        word = word[i:]
        if len(word) > 0:
            word = f"##{word}"
    return tokens
```

讓我們用詞彙表中的一個單詞和另一個不在詞彙表中的單詞進行測試:

```python
print(encode_word("Hugging"))
print(encode_word("HOgging"))
```

```python out
['Hugg', '##i', '##n', '##g']
['[UNK]']
```

現在,讓我們編寫一個標記文本的函數:

```python
def tokenize(text):
    pre_tokenize_result = tokenizer._tokenizer.pre_tokenizer.pre_tokenize_str(text)
    pre_tokenized_text = [word for word, offset in pre_tokenize_result]
    encoded_words = [encode_word(word) for word in pre_tokenized_text]
    return sum(encoded_words, [])
```

我們可以在任何文本上嘗試:

```python
tokenize("This is the Hugging Face course!")
```

```python out
['Th', '##i', '##s', 'is', 'th', '##e', 'Hugg', '##i', '##n', '##g', 'Fac', '##e', 'c', '##o', '##u', '##r', '##s',
 '##e', '[UNK]']
```

這就是 WordPiece 算法的全部內容!現在讓我們來看看 Unigram。


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter6/6.mdx" />

### 章末小測驗
https://huggingface.co/learn/course/zh-TW/chapter6/10.md

# 章末小測驗

<CourseFloatingBanner
    chapter={6}
    classNames="absolute z-10 right-0 top-0"
/>

讓我們測試一下您在本章中學到了什麼！

### 1.你應該什麼時候訓練一個新的標記器？
<Question
	choices={[
		{
			text: "當您的數據集與現有的預訓練模型所使用的數據集相似時，並且您希望預訓練一個新模型",
			explain: "在這種情況下，為了節省時間和計算資源，一個更好的選擇是使用與預訓練模型相同的標記器，並對該模型進行微調。"
		},
		{
			text: "當您的數據集與現有的預訓練模型所使用的數據集相似時，並且您希望使用此預訓練模型對新模型進行微調",
			explain: "要根據預先訓練的模型對模型進行微調，您應該始終使用相同的標記器。"
		},
		{
			text: "當您的數據集與現有預訓練模型所使用的數據集不同時，您希望預訓練一個新模型",
			explain: "正確！ 在這種情況下，使用現有預訓練模型標記器將沒有任何優勢。",
            correct: true
		},
        {
			text: "當您的數據集與現有預訓練模型所使用的數據集不同時，但是您希望使用此預訓練模型對新模型進行微調",
			explain: "要根據預先訓練的模型對模型進行微調，您應該始終使用相同的標記器。"
		}
	]}
/>

### 2.當使用“ train_new_from_iterator()”時，使用文本列表生成器與文本列表相比有什麼優點？
<Question
	choices={[
		{
			text: "這是方法 < code > train_new_from_iterator() </code > 接受的唯一類型。",
			explain: "文本列表是一種特殊的文本列表生成器，因此該方法也會接受這種方法。再試一次！"
		},
		{
			text: "您將避免立即將整個數據集載入內存中。",
			explain: "沒錯！每一批文本都會在你迭代的時候從內存中釋放出來，如果你使用數據集存儲文本的話，增益將尤其明顯。",
			correct: true
		},
		{
			text: "這將允許 Tokenizers 庫使用並行處理。",
			explain: "不，無論如何它都將使用並行處理。"
		},
        {
			text: "你訓練的標記器將產生更好的文本。",
			explain: "Tokenizer 不生成文本——您是否將其與語言模型混淆了？"
		}
	]}
/>

### 3.使用“快速”標記器的優點是什麼？
<Question
	choices={[
		{
			text: "當你批處理大量的輸入時，它可以比一個滿速的標記器更快地處理輸入。",
			explain: "正確！由於在 Rust 中實現了並行性，它將在批量處理輸入上更快。你還能想到其他什麼好處嗎？",
			correct: true
		},
		{
			text: "快速的標記法總是比慢速的標記法快。",
			explain: "一個快速的標記器當你只給它一個或很少的文本實際上可能會更慢，因為它不能使用並行。"
		},
		{
			text: "它可以填充和截斷文本。",
			explain: "是的，但是慢速的標記符也會這樣做。"
		},
        {
			text: "它有一些額外的功能，允許你將標記映射到創建它們的文本範圍。",
			explain: "事實上，這些被稱為偏移映射，但這並不是唯一的優點。",
			correct: true
		}
	]}
/>

### 4.“token-classification”管道如何處理跨多個標記的實體？
<Question
	choices={[
		{
			text: "具有相同標籤的實體合併為一個實體。",
			explain: "這有點過於簡單化了，再試一次！"
		},
		{
			text: "實體的開始有一個標籤，實體的持續有一個標籤。",
			explain: "正確！",
			correct: true
		},
		{
			text: "在給定的單詞中，只要第一個標記具有實體的標籤，則認為整個單詞都帶有該實體的標籤。",
			explain: "這是一個處理實體的策略，這裡還有什麼其他的答案嗎？",
			correct: true
		},
        {
			text: "當一個標記具有給定實體的標記時，除非標記為新實體的開始，否則具有相同標記的任何其他後續標記都被視為同一實體的一部分。",
			explain: "這是最常見的將實體組合在一起的方法---- 儘管這不是唯一正確的答案。",
			correct: true
		}
	]}
/>

### 5.“question-answering”流水線如何處理長上下文？
<Question
	choices={[
		{
			text: "實際上並不是這樣，因為它在模型接受的最大長度上截斷了長上下文。",
			explain: "有一個技巧你可以用來處理很長的上下文，你還記得是什麼嗎？"
		},
		{
			text: "它將上下文分成若干部分，並對所得結果進行平均。",
			explain: "不，對結果進行平均是沒有意義的，因為上下文的某些部分不包括答案。"
		},
		{
			text: "它將上下文拆分為若干部分(有重疊部分) ，並在每個部分中查找一個答案的最大得分。",
			explain: "這就是正確答案！",
			correct: true
		},
        {
			text: "它將上下文分成若干部分(不重疊，以提高效率) ，並在每個部分中找到一個答案的最大得分。",
			explain: "不，它包括部分之間的一些重疊，以避免出現答案將分成兩部分的情況。"
		}
	]}
/>

### 6.什麼是標準化？
<Question
	choices={[
		{
			text: "這是 tokenizer 在初始階段對文本執行的任何清理。",
			explain: "這是正確的——例如，它可能涉及刪除重音符號或空格，或縮小輸入的大小寫。",
			correct: true
		},
		{
			text: "這是一種數據增強技術，包括通過刪除稀有單詞使文本更加標準。",
			explain: "不對! 再試一次。"
		},
		{
			text: "在最後的後處理步驟中，tokenizer 添加特殊標記。",
			explain: "這個階段簡單地稱為後期處理。"
		},
        {
			text: "這是當嵌入的平均值為0和標準差為1時，通過減去平均值和除以標準差。",
			explain: "在計算機視覺中，這個過程通常被稱為標準化，但在 NLP 中，這並不是標準化的意思。"
		}
	]}
/>

### 7.什麼是子詞標記化的前標記化？
<Question
	choices={[
		{
			text: "這是標記化之前的步驟，應用數據增強(如隨機遮罩)。",
			explain: "不，這一步是預處理的一部分。"
		},
		{
			text: "這是標記化之前的步驟，在這個步驟中，所需的清理操作應用於文本。",
			explain: "不，這是標準化步驟。"
		},
		{
			text: "這是應用 tokenizer 模型之前的步驟，將輸入拆分為單詞。",
			explain: "這就是正確答案！",
			correct: true
		},
        {
			text: "這是應用 tokenizer 模型之前的步驟，將輸入拆分為標記。",
			explain: "不，分解為標記是 tokenizer 模型的工作。"
		}
	]}
/>

### 8.選擇描述標記化 BPE 模式最準確的句子。
<Question
	choices={[
		{
			text: "BPE 是一個子詞標記算法，從小詞彙表開始，學習合併規則。",
			explain: "的確如此！",
			correct: true
		},
		{
			text: "BPE 是一種子詞標記算法，從大詞彙表開始，逐步從中刪除標記。",
			explain: "不，另一種標記算法所採用的方法。"
		},
		{
			text: "BPE 標記器通過合併最常見的一對標記來學習合併規則。",
			explain: "沒錯！",
			correct: true
		},
		{
			text: "BPE 記號賦予器通過合併一對記號來學習合併規則，該記號最大化了特權頻繁對和較少個別部分的分數。",
			explain: "不，這是另一個標記算法應用的策略。"
		},
		{
			text: "BPE 通過將單詞分割成字符，然後應用合併規則將單詞分解成子單詞。",
			explain: "沒錯！",
			correct: true
		},
		{
			text: "BPE 通過從詞彙表的開頭找到最長的子詞，然後在文本的其餘部分重複這個過程，將單詞轉化為子詞。",
			explain: "不，這是另一種標記化算法的做事方式。"
		},
	]}
/>

### 9.選擇適用於 WordPiece 標記模型的句子。
<Question
	choices={[
		{
			text: "WordPiece 是一個子詞標記算法，它從一個小詞彙表開始，學習合併規則。",
			explain: "的確如此！",
			correct: true
		},
		{
			text: "WordPiece 是一種子詞標記算法，從大詞彙表開始，逐步從中刪除標記。",
			explain: "不，這是一種不同的標記算法所採用的方法。"
		},
		{
			text: "WordPiece 標記器通過合併最常見的兩個標記來學習合併規則。",
			explain: "不，這是另一個標記算法應用的策略。"
		},
		{
			text: "WordPiece 標記器通過合併兩個標記來學習合併規則，這兩個標記最大限度地提高了頻繁出現的標記的分數，而這兩個標記的獨立部分出現頻率較低。",
			explain: "沒錯！",
			correct: true
		},
		{
			text: "根據模型，WordPiece 通過找到最有可能的切分符號，將單詞標記為子單詞。",
			explain: "不，這是另一個標記化算法的工作原理。"
		},
		{
			text: "WordPiece 通過從詞彙表的開頭找到最長的子詞，然後在文本的其餘部分重複這個過程，將單詞轉化為子詞。",
			explain: "是的，這就是 WordPiece 進行編碼的過程。",
			correct: true
		},
	]}
/>

### 10.選擇適用於 Unigram 標記模式的句子。
<Question
	choices={[
		{
			text: "Unigram 是一個子詞標記算法，它從一個很小的詞彙表開始學習合併規則。",
			explain: "不，這是一種不同的標記算法所採用的方法。"
		},
		{
			text: "Unigram 是一種子詞標記算法，它從大詞彙表開始，逐步從中刪除標記。",
			explain: "沒錯！",
			correct: true
		},
		{
			text: "Unigram 通過最小化在整個語料庫中的損失來調整詞彙量。",
			explain: "沒錯！",
			correct: true
		},
		{
			text: "Unigram 通過保留最頻繁的子詞來調整它的詞彙量。",
			explain: "不，這個不正確。"
		},
		{
			text: "根據模型，Unigram 通過找到最可能的分割符號來將詞轉化為子詞。",
			explain: "沒錯！",
			correct: true
		},
		{
			text: "Unigram 通過將單詞分割成字符，然後應用合併規則將其分解成子單詞。",
			explain: "不，這是另一個標記化算法的工作原理。"
		},
	]}
/>


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter6/10.mdx" />

### 逐塊地構建標記器
https://huggingface.co/learn/course/zh-TW/chapter6/8.md

# 逐塊地構建標記器

<CourseFloatingBanner chapter={6}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/zh-CN/chapter6/section8.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/zh-CN/chapter6/section8.ipynb"},
]} />

正如我們在前幾節中看到的，標記化包括幾個步驟：

- 規範化（任何認為必要的文本清理，例如刪除空格或重音符號、Unicode 規範化等） 
- 預標記化（將輸入拆分為單詞） 
- 通過模型處理輸入（使用預先拆分的詞來生成一系列標記） 
- 後處理（添加標記器的特殊標記，生成注意力掩碼和標記類型 ID） 

提醒一下，這裡再看一下整個過程

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter6/tokenization_pipeline.svg" alt="The tokenization pipeline.">
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter6/tokenization_pipeline-dark.svg" alt="The tokenization pipeline.">
</div>

🤗 Tokenizers 庫旨在為每個步驟提供多個選項，您可以將它們混合和匹配在一起。在本節中，我們將看到如何從頭開始構建標記器，而不是像我們[第二節 2](/course/chapter6/2)那樣從舊的標記器訓練新的標記器.然後，您將能夠構建您能想到的任何類型的標記器！

<Youtube id="MR8tZm5ViWU"/>

更準確地說，該庫是圍繞一個中央「Tokenizer」類構建的，構建這個類的每一部分可以在子模塊的列表中重新組合：

- `normalizers` 包含你可以使用的所有可能的Normalizer類型（完整列表[在這裡](https://huggingface.co/docs/tokenizers/api/normalizers)）。 
- `pre_tokenizesr` 包含您可以使用的所有可能的PreTokenizer類型（完整列表[在這裡](https://huggingface.co/docs/tokenizers/api/pre-tokenizers)）。 
- `models` 包含您可以使用的各種類型的Model，如BPE、WordPiece和Unigram（完整列表[在這裡](https://huggingface.co/docs/tokenizers/api/models)）。  
- `trainers` 包含所有不同類型的 trainer，你可以使用一個語料庫訓練你的模型（每種模型一個；完整列表[在這裡](https://huggingface.co/docs/tokenizers/api/trainers)）。 
- `post_processors` 包含你可以使用的各種類型的PostProcessor（完整列表[在這裡](https://huggingface.co/docs/tokenizers/api/post-processors)）。 
- `decoders` 包含各種類型的Decoder，可以用來解碼標記化的輸出（完整列表[在這裡](https://huggingface.co/docs/tokenizers/components#decoders)）。 

您可以[在這裡](https://huggingface.co/docs/tokenizers/components)找到完整的模塊列表。

## 獲取語​​料庫

為了訓練我們的新標記器，我們將使用一個小的文本語料庫（因此示例運行得很快）。獲取語​​料庫的步驟與我們在[在這章的開始]((/course/chapter6/2)那一小節，但這次我們將使用[WikiText-2](https://huggingface.co/datasets/wikitext)數據集：

```python
from datasets import load_dataset

dataset = load_dataset("wikitext", name="wikitext-2-raw-v1", split="train")


def get_training_corpus():
    for i in range(0, len(dataset), 1000):
        yield dataset[i : i + 1000]["text"]
```

**get_training_corpus()** 函數是一個生成器，每次調用的時候將產生 1,000 個文本，我們將用它來訓練標記器。

🤗 Tokenizers 也可以直接在文本文件上進行訓練。以下是我們如何生成一個文本文件，其中包含我們可以在本地使用的來自 WikiText-2 的所有文本/輸入：

```python
with open("wikitext-2.txt", "w", encoding="utf-8") as f:
    for i in range(len(dataset)):
        f.write(dataset[i]["text"] + "\n")
```

接下來，我們將向您展示如何逐塊構建您自己的 BERT、GPT-2 和 XLNet 標記器。這將為我們提供三個主要標記化算法的示例：WordPiece、BPE 和 Unigram。讓我們從 BERT 開始吧！

## 從頭開始構建 WordPiece 標記器

要使用 🤗 Tokenizers 庫構建標記器，我們首先使用 **model** 實例化一個 **Tokenizer** 對象，然後將 **normalizer** , **pre_tokenizer** , **post_processor** ， 和 **decoder** 屬性設置成我們想要的值。

對於這個例子，我們將創建一個 **Tokenizer** 使用 WordPiece 模型：

```python
from tokenizers import (
    decoders,
    models,
    normalizers,
    pre_tokenizers,
    processors,
    trainers,
    Tokenizer,
)

tokenizer = Tokenizer(models.WordPiece(unk_token="[UNK]"))
```

我們必須指定 **unk_token** 這樣模型才知道當它遇到以前沒有見過的字符時要返回什麼。我們可以在此處設置的其他參數包括我們模型的**vocab（字典）**（我們將訓練模型，所以我們不需要設置它）和 **max_input_chars_per_word** 即每個單詞的最大長度（比傳遞的值長的單詞將被拆分）

標記化的第一步是規範化，所以讓我們從它開始。 由於 BERT 被廣泛使用，所以有一個可以使用的 `BertNormalizer`，我們可以為 BERT 設置經典的選項：`lowercase（小寫）` 和 `strip_accents（去除音調）`，不言自明； `clean_text` 刪除所有控制字符並將重複的空格替換為一個； 和 `handle_chinese_chars`，在漢字周圍放置空格。 要實現 `bert-base-uncased` ，我們可以這樣設置這個規範器：

```python
tokenizer.normalizer = normalizers.BertNormalizer(lowercase=True)
```

然而，一般來說，在構建新的標記器時，您可以使用已經在 🤗 Tokenizers庫中實現的非常方便的normalizer——所以讓我們看看如何手動創建 BERT normalizer。 該庫提供了一個“Lowercase（小寫）”的normalizer和一個“StripAccents”的normalizer，您可以使用“序列”組合多個normalizer：

```python
tokenizer.normalizer = normalizers.Sequence(
    [normalizers.NFD(), normalizers.Lowercase(), normalizers.StripAccents()]
)
```

我們也在使用 **NFD** Unicode normalizer，否則 **StripAccents** normalizer 無法正確識別帶重音的字符，因此沒辦法刪除它們。

正如我們之前看到的，我們可以使用 **normalize** 的 **normalize_str()** 方法查看它對給定文本的影響：

```python
print(tokenizer.normalizer.normalize_str("Héllò hôw are ü?"))
```

```python out
hello how are u?
```

<Tip>

**更進一步**如果您在包含 unicode 字符的字符串上測試先前normalizers的兩個版本，您肯定會注意到這兩個normalizers並不完全等效。
為了不過度使用 `normalizers.Sequence` 使版本過於複雜，我們沒有包含當 `clean_text` 參數設置為 `True` 時 `BertNormalizer` 需要的正則表達式替換 - 這是默認行為。 但不要擔心：通過在normalizer序列中添加兩個 `normalizers.Replace` 可以在不使用方便的 `BertNormalizer` 的情況下獲得完全相同的規範化。

</Tip>

接下來是預標記步驟。 同樣，我們可以使用一個預構建的“BertPreTokenizer”：

```python
tokenizer.pre_tokenizer = pre_tokenizers.BertPreTokenizer()
```

或者我們可以從頭開始構建它：

```python
tokenizer.pre_tokenizer = pre_tokenizers.Whitespace()
```

請注意，`Whitespace` 預標記器會在空格和所有非字母、數字或下劃線字符的字符上進行拆分，因此在本次的例子中上會根據空格和標點符號進行拆分：

```python
tokenizer.pre_tokenizer.pre_tokenize_str("Let's test my pre-tokenizer.")
```

```python out
[('Let', (0, 3)), ("'", (3, 4)), ('s', (4, 5)), ('test', (6, 10)), ('my', (11, 13)), ('pre', (14, 17)),
 ('-', (17, 18)), ('tokenizer', (18, 27)), ('.', (27, 28))]
```

如果您只想在空白處進行拆分，則應使用 **WhitespaceSplit** 代替預標記器：

```python
pre_tokenizer = pre_tokenizers.WhitespaceSplit()
pre_tokenizer.pre_tokenize_str("Let's test my pre-tokenizer.")
```

```python out
[("Let's", (0, 5)), ('test', (6, 10)), ('my', (11, 13)), ('pre-tokenizer.', (14, 28))]
```

像normalizers一樣，您可以使用 **Sequence** 組成幾個預標記器：

```python
pre_tokenizer = pre_tokenizers.Sequence(
    [pre_tokenizers.WhitespaceSplit(), pre_tokenizers.Punctuation()]
)
pre_tokenizer.pre_tokenize_str("Let's test my pre-tokenizer.")
```

```python out
[('Let', (0, 3)), ("'", (3, 4)), ('s', (4, 5)), ('test', (6, 10)), ('my', (11, 13)), ('pre', (14, 17)),
 ('-', (17, 18)), ('tokenizer', (18, 27)), ('.', (27, 28))]
```

標記化管道的下一步是輸入給模型。我們已經在初始化中指定了我們的模型，但我們仍然需要訓練它，這將需要一個 **WordPieceTrainer** .在 🤗 Tokenizers 中實例化訓練器時要記住的主要事情是，您需要將您打算使用的所有特殊標記傳遞給它 - 否則它不會將它們添加到詞彙表中，因為它們不在訓練語料庫中：

```python
special_tokens = ["[UNK]", "[PAD]", "[CLS]", "[SEP]", "[MASK]"]
trainer = trainers.WordPieceTrainer(vocab_size=25000, special_tokens=special_tokens)
```

以及指定 **vocab_size（詞典大小）** 和 **special_tokens（特殊的標記）** ，我們可以設置 **min_frequency** （記號必須出現在詞彙表中的次數）或更改 **continuing_subword_prefix** （如果我們想使用與 **##**指代存在與字詞相同的前綴 ）。

要使用我們之前定義的迭代器訓練我們的模型，我們只需要執行以下命令：

```python
tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer)
```

我們還可以使用文本文件來訓練我們的標記器，它看起來像這樣（我們需要先初始化一個空的 **WordPiece** ）：

```python
tokenizer.model = models.WordPiece(unk_token="[UNK]")
tokenizer.train(["wikitext-2.txt"], trainer=trainer)
```

在這兩種情況下，我們都可以通過調用文本來測試標記器 **encode()** 方法：

```python
encoding = tokenizer.encode("Let's test this tokenizer.")
print(encoding.tokens)
```

```python out
['let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '.']
```

這個 **encoding** 獲得的是一個 **Encoding**對象 ，它的屬性中包含標記器的所有必要輸出： **ids** , **type_ids** , **tokens** , **offsets** , **attention_mask** , **special_tokens_mask** ， 和 **overflowing** .

標記化管道的最後一步是後處理。我們需要添加 **[CLS]** 開頭的標記和 **[SEP]** 標記在末尾（或在每個句子之後，如果我們有一對句子）。我們將使用一個 **TemplateProcessor** 為此，但首先我們需要知道 **[CLS]** 和 **[SEP]** 在詞彙表中的ID：

```python
cls_token_id = tokenizer.token_to_id("[CLS]")
sep_token_id = tokenizer.token_to_id("[SEP]")
print(cls_token_id, sep_token_id)
```

```python out
(2, 3)
```

為了給 **TemplateProcessor** 編寫模板，我們必須指定如何處理單個句子和一對句子。對於兩者，我們都編寫了我們想要使用的特殊標記；第一個（或單個）句子表示為 **$A** ，而第二個句子（如果對一對進行編碼）表示為 **$B** .對於這些特殊標記和句子，我們還需要使用在冒號後指定相應的標記類型 ID。

因此經典的 BERT 模板定義如下：

```python
tokenizer.post_processor = processors.TemplateProcessing(
    single=f"[CLS]:0 $A:0 [SEP]:0",
    pair=f"[CLS]:0 $A:0 [SEP]:0 $B:1 [SEP]:1",
    special_tokens=[("[CLS]", cls_token_id), ("[SEP]", sep_token_id)],
)
```

請注意，我們需要傳遞特殊標記的 ID，以便標記器可以正確地將特殊標記轉換為它們的 ID。

添加後，我們之前的示例將輸出出：

```python
encoding = tokenizer.encode("Let's test this tokenizer.")
print(encoding.tokens)
```

```python out
['[CLS]', 'let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '.', '[SEP]']
```

在一對句子中，我們得到了正確的結果：
```python
encoding = tokenizer.encode("Let's test this tokenizer...", "on a pair of sentences.")
print(encoding.tokens)
print(encoding.type_ids)
```

```python out
['[CLS]', 'let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '...', '[SEP]', 'on', 'a', 'pair', 'of', 'sentences', '.', '[SEP]']
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1]
```

我們幾乎從頭開始構建了這個標記器——但是還有最後一步是指定一個解碼器：

```python
tokenizer.decoder = decoders.WordPiece(prefix="##")
```

讓我們測試一下我們之前的 **encoding** ：

```python
tokenizer.decode(encoding.ids)
```

```python out
"let's test this tokenizer... on a pair of sentences."
```

很好！我們可以將標記器保存在一個 JSON 文件中，如下所示：

```python
tokenizer.save("tokenizer.json")
```

然後我們可以使用**from_file()** 方法從該文件裡重新加載 **Tokenizer** 對象：

```python
new_tokenizer = Tokenizer.from_file("tokenizer.json")
```

要在 🤗 Transformers 中使用這個標記器，我們必須將它包裹在一個 **PreTrainedTokenizerFast** 類中。我們可以使用泛型類，或者，如果我們的標記器對應於現有模型，則使用該類（例如這裡的 **BertTokenizerFast** ）。如果您應用本課來構建全新的標記器，則必須使用第一個選項。

要將標記器包裝在 `PreTrainedTokenizerFast` 類中，我們可以將我們構建的標記器作為`tokenizer_object` 傳遞，或者將我們保存為`tokenizer_file` 的標記器文件傳遞。 要記住的關鍵是我們必須手動設置所有特殊標記，因為該類無法從 `tokenizer` 對象推斷出哪個標記是掩碼標記、`[CLS]` 標記等：

```python
from transformers import PreTrainedTokenizerFast

wrapped_tokenizer = PreTrainedTokenizerFast(
    tokenizer_object=tokenizer,
    # tokenizer_file="tokenizer.json", # You can load from the tokenizer file, alternatively
    unk_token="[UNK]",
    pad_token="[PAD]",
    cls_token="[CLS]",
    sep_token="[SEP]",
    mask_token="[MASK]",
)
```

如果您使用特定的標記器類（例如 **BertTokenizerFast** )，您只需要指定與默認標記不同的特殊標記（此處沒有）：

```python
from transformers import BertTokenizerFast

wrapped_tokenizer = BertTokenizerFast(tokenizer_object=tokenizer)
```

然後，您可以像使用任何其他 🤗 Transformers 標記器一樣使用此標記器。你可以用 **save_pretrained()** 方法，或使用 **push_to_hub()** 方法。

現在我們已經瞭解瞭如何構建 WordPiece 標記器，讓我們對 BPE 標記器進行同樣的操作。因為您已經知道了所有步驟，所以我們會進行地更快一點，並且只突出展示兩者不一樣的地方。

## 從頭開始構建 BPE 標記器

現在讓我們構建一個 GPT-2 標記器。與 BERT 標記器一樣，我們首先使用 **Tokenizer** 初始化一個BPE 模型：

```python
tokenizer = Tokenizer(models.BPE())
```

和 BERT 一樣，如果我們有一個詞彙表，我們可以用一個詞彙表來初始化這個模型（在這種情況下，我們需要傳遞 `vocab` 和 `merges`），但是由於我們將從頭開始訓練，所以我們不需要這樣去做。 我們也不需要指定“unk_token”，因為 GPT-2 使用的字節級 BPE，不需要“unk_token”。

GPT-2 不使用歸一化器，因此我們跳過該步驟並直接進入預標記化：

```python
tokenizer.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=False)
```

我們在此處添加到 `ByteLevel` 的選項是不在句子開頭添加空格（默認為ture）。 我們可以看一下使用這個標記器對之前示例文本的預標記：

```python
tokenizer.pre_tokenizer.pre_tokenize_str("Let's test pre-tokenization!")
```

```python out
[('Let', (0, 3)), ("'s", (3, 5)), ('Ġtest', (5, 10)), ('Ġpre', (10, 14)), ('-', (14, 15)),
 ('tokenization', (15, 27)), ('!', (27, 28))]
```

接下來是需要訓練的模型。對於 GPT-2，唯一的特殊標記是文本結束標記：

```python
trainer = trainers.BpeTrainer(vocab_size=25000, special_tokens=["<|endoftext|>"])
tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer)
```

與 `WordPieceTrainer` 以及 `vocab_size` 和 `special_tokens` 一樣，我們可以指定 `min_frequency` 如果我們願意，或者如果我們有一個詞尾後綴（如 `</w>` )，我們可以使用 `end_of_word_suffix` 設置它。

這個標記器也可以在文本文件上訓練：

```python
tokenizer.model = models.BPE()
tokenizer.train(["wikitext-2.txt"], trainer=trainer)
```

讓我們看一下示例文本的標記化後的結果：

```python
encoding = tokenizer.encode("Let's test this tokenizer.")
print(encoding.tokens)
```

```python out
['L', 'et', "'", 's', 'Ġtest', 'Ġthis', 'Ġto', 'ken', 'izer', '.']
```

我們對 GPT-2 標記器添加字節級後處理，如下所示：

```python
tokenizer.post_processor = processors.ByteLevel(trim_offsets=False)
```

`trim_offsets = False` 選項指示我們應該保留以 'Ġ' 開頭的標記的偏移量：這樣偏移量的開頭將指向單詞之前的空格，而不是第一個單詞的字符（因為空格在技術上是標記的一部分）。 讓我們看看我們剛剛編碼的文本的結果，其中 `'Ġtest'` 是索引第 4 處的標記：

```python
sentence = "Let's test this tokenizer."
encoding = tokenizer.encode(sentence)
start, end = encoding.offsets[4]
sentence[start:end]
```

```python out
' test'
```

最後，我們添加一個字節級解碼器：

```python
tokenizer.decoder = decoders.ByteLevel()
```

我們可以仔細檢查它是否正常工作：

```python
tokenizer.decode(encoding.ids)
```

```python out
"Let's test this tokenizer."
```

很好！現在我們完成了，我們可以像以前一樣保存標記器，並將它包裝在一個 **PreTrainedTokenizerFast** 或者 **GPT2TokenizerFast** 如果我們想在 🤗 Transformers中使用它：

```python
from transformers import PreTrainedTokenizerFast

wrapped_tokenizer = PreTrainedTokenizerFast(
    tokenizer_object=tokenizer,
    bos_token="<|endoftext|>",
    eos_token="<|endoftext|>",
)
```

或者：

```python
from transformers import GPT2TokenizerFast

wrapped_tokenizer = GPT2TokenizerFast(tokenizer_object=tokenizer)
```

作為最後一個示例，我們將向您展示如何從頭開始構建 Unigram 標記器。

## 從頭開始構建 Unigram 標記器

現在讓我們構建一個 XLNet 標記器。與之前的標記器一樣，我們首先使用 Unigram 模型初始化一個 **Tokenizer** ：

```python
tokenizer = Tokenizer(models.Unigram())
```

同樣，如果我們有詞彙表，我們可以用詞彙表初始化這個模型。

對於標準化，XLNet 使用了一些替換的方法（來自 SentencePiece）：

```python
from tokenizers import Regex

tokenizer.normalizer = normalizers.Sequence(
    [
        normalizers.Replace("``", '"'),
        normalizers.Replace("''", '"'),
        normalizers.NFKD(),
        normalizers.StripAccents(),
        normalizers.Replace(Regex(" {2,}"), " "),
    ]
)
```

這會取代 **“** 和 **”** 和 **”** 以及任何兩個或多個空格與單個空格的序列，以及刪除文本中的重音以進行標記。

用於任何 SentencePiece 標記器的預標記器是 `Metaspace`：

```python
tokenizer.pre_tokenizer = pre_tokenizers.Metaspace()
```

我們可以像以前一樣查看示例文本的預標記化：

```python
tokenizer.pre_tokenizer.pre_tokenize_str("Let's test the pre-tokenizer!")
```

```python out
[("▁Let's", (0, 5)), ('▁test', (5, 10)), ('▁the', (10, 14)), ('▁pre-tokenizer!', (14, 29))]
```

接下來是需要訓練的模型。 XLNet 有不少特殊的標記：

```python
special_tokens = ["<cls>", "<sep>", "<unk>", "<pad>", "<mask>", "<s>", "</s>"]
trainer = trainers.UnigramTrainer(
    vocab_size=25000, special_tokens=special_tokens, unk_token="<unk>"
)
tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer)
```

不要忘記`UnigramTrainer` 的一個非常重要的參數是`unk_token`。 我們還可以傳遞特定於 Unigram 算法的其他參數，例如刪除標記的每個步驟的“shrinking_factor（收縮因子）”（默認為 0.75）或指定給定標記的最大長度的“max_piece_length”（默認為 16） .

這個標記器也可以在文本文件上訓練：

```python
tokenizer.model = models.Unigram()
tokenizer.train(["wikitext-2.txt"], trainer=trainer)
```

讓我們看一下示例文本的標記化後的結果：

```python
encoding = tokenizer.encode("Let's test this tokenizer.")
print(encoding.tokens)
```

```python out
['▁Let', "'", 's', '▁test', '▁this', '▁to', 'ken', 'izer', '.']
```

A peculiarity of XLNet is that it puts the `<cls>` token at the end of the sentence, with a type ID of 2 (to distinguish it from the other tokens). It's padding on the left, as a result. We can deal with all the special tokens and token type IDs with a template, like for BERT, but first we have to get the IDs of the `<cls>` and `<sep>` tokens:
XLNet 的一個特點是它將`<cls>` 標記放在句子的末尾，類型ID 為2（以將其與其他標記區分開來）。它會將結果填充在左側。 我們可以使用模板處理所有特殊標記和標記類型 ID，例如 BERT，但首先我們必須獲取 `<cls>` 和 `<sep>` 標記的 ID：

```python
cls_token_id = tokenizer.token_to_id("<cls>")
sep_token_id = tokenizer.token_to_id("<sep>")
print(cls_token_id, sep_token_id)
```

```python out
0 1
```

模板如下所示：

```python
tokenizer.post_processor = processors.TemplateProcessing(
    single="$A:0 <sep>:0 <cls>:2",
    pair="$A:0 <sep>:0 $B:1 <sep>:1 <cls>:2",
    special_tokens=[("<sep>", sep_token_id), ("<cls>", cls_token_id)],
)
```

我們可以通過編碼一對句子來測試它的工作原理：

```python
encoding = tokenizer.encode("Let's test this tokenizer...", "on a pair of sentences!")
print(encoding.tokens)
print(encoding.type_ids)
```

```python out
['▁Let', "'", 's', '▁test', '▁this', '▁to', 'ken', 'izer', '.', '.', '.', '<sep>', '▁', 'on', '▁', 'a', '▁pair', 
  '▁of', '▁sentence', 's', '!', '<sep>', '<cls>']
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2]
```

最後，我們添加一個 **Metaspace** 解碼器：

```python
tokenizer.decoder = decoders.Metaspace()
```

我們完成了這個標記器！ 我們可以像以前一樣保存標記器，如果我們想在 🤗 Transformers 中使用它，可以將它包裝在 `PreTrainedTokenizerFast` 或 `XLNetTokenizerFast` 中。 使用 `PreTrainedTokenizerFast` 時要注意的一件事是，我們需要告訴🤗 Transformers 庫應該在左側填充特殊標記：
```python
from transformers import PreTrainedTokenizerFast

wrapped_tokenizer = PreTrainedTokenizerFast(
    tokenizer_object=tokenizer,
    bos_token="<s>",
    eos_token="</s>",
    unk_token="<unk>",
    pad_token="<pad>",
    cls_token="<cls>",
    sep_token="<sep>",
    mask_token="<mask>",
    padding_side="left",
)
```

或者：

```python
from transformers import XLNetTokenizerFast

wrapped_tokenizer = XLNetTokenizerFast(tokenizer_object=tokenizer)
```

現在您已經瞭解瞭如何使用各種構建塊來構建現有的標記器，您應該能夠使用 🤗 tokenizer庫編寫您想要的任何標記器，並能夠在 🤗 Transformers中使用它。

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter6/8.mdx" />

### 標準化和預標記化
https://huggingface.co/learn/course/zh-TW/chapter6/4.md

# 標準化和預標記化

<CourseFloatingBanner chapter={6}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/zh-CN/chapter6/section4.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/zh-CN/chapter6/section4.ipynb"},
]} />

在我們更深入地研究與 Transformer 模型（字節對編碼 [BPE]、WordPiece 和 Unigram）一起使用的三種最常見的子詞標記化算法之前，我們將首先看一下每個標記器應用於文本的預處理。以下是標記化管道中步驟的高級概述：

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter6/tokenization_pipeline.svg" alt="The tokenization pipeline.">
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter6/tokenization_pipeline-dark.svg" alt="The tokenization pipeline.">
</div>

在將文本拆分為子標記之前（根據其模型），分詞器執行兩個步驟： _normalization_ 和 _pre-tokenization_.

## 正常化

<Youtube id="4IIC2jI9CaU"/>

標準化步驟涉及一些常規清理，例如刪除不必要的空格、小寫和/或刪除重音符號。如果你熟悉[Unicode normalization](http://www.unicode.org/reports/tr15/)（例如 NFC 或 NFKC），這也是 tokenizer 可能應用的東西。

🤗Transformers **tokenizer** 有一個屬性叫做 **backend_tokenizer** 它提供了對 🤗 Tokenizers 庫中底層標記器的訪問：

```py
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")
print(type(tokenizer.backend_tokenizer))
```

```python out
<class 'tokenizers.Tokenizer'>
```

**normalizer** 的屬性 **tokenizer** 對象有一個 **normalize_str()** 我們可以用來查看標準化是如何執行的方法：

```py
print(tokenizer.backend_tokenizer.normalizer.normalize_str("Héllò hôw are ü?"))
```

```python out
'hello how are u?'
```

在這個例子中，因為我們選擇了 **bert-base-uncased** 檢查點，標準化應用小寫並刪除重音。

<Tip>

✏️ **試試看!** 從檢查點加載標記器並將相同的示例傳遞給它。您可以看到分詞器的帶殼和無殼版本之間的主要區別是什麼？


</Tip>

## 預標記化

<Youtube id="grlLV8AIXug"/>

正如我們將在下一節中看到的，分詞器不能單獨在原始文本上進行訓練。相反，我們首先需要將文本拆分為小實體，例如單詞。這就是預標記化步驟的用武之地。 正如我們在[Chapter 2](/course/chapter2), 基於單詞的標記器可以簡單地將原始文本拆分為空白和標點符號的單詞。這些詞將是分詞器在訓練期間可以學習的子標記的邊界。

要查看快速分詞器如何執行預分詞，我們可以使用 **pre_tokenize_str()** 的方法 **pre_tokenizer** 的屬性 **tokenizer** 目的：

```py
tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str("Hello, how are  you?")
```

```python out
[('Hello', (0, 5)), (',', (5, 6)), ('how', (7, 10)), ('are', (11, 14)), ('you', (16, 19)), ('?', (19, 20))]
```

請注意分詞器如何已經跟蹤偏移量，這就是它如何為我們提供上一節中使用的偏移量映射。這裡分詞器忽略了這兩個空格，只用一個替換它們，但偏移量在 **are** 和 **you** 考慮到這一點。

由於我們使用的是 BERT 分詞器，預分詞涉及對空格和標點符號進行拆分。對於這一步，其他標記器可以有不同的規則。例如，如果我們使用 GPT-2 標記器：

```py
tokenizer = AutoTokenizer.from_pretrained("gpt2")
tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str("Hello, how are  you?")
```

它也會在空格和標點符號上拆分，但它會保留空格並將它們替換為 **Ġ** 符號，如果我們解碼令牌，則使其能夠恢復原始空格：

```python out
[('Hello', (0, 5)), (',', (5, 6)), ('Ġhow', (6, 10)), ('Ġare', (10, 14)), ('Ġ', (14, 15)), ('Ġyou', (15, 19)),
 ('?', (19, 20))]
```

另請注意，與 BERT 分詞器不同，此分詞器不會忽略雙空格

最後一個例子，讓我們看一下基於 SentencePiece 算法的 T5 分詞器：

```py
tokenizer = AutoTokenizer.from_pretrained("t5-small")
tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str("Hello, how are  you?")
```

```python out
[('▁Hello,', (0, 6)), ('▁how', (7, 10)), ('▁are', (11, 14)), ('▁you?', (16, 20))]
```

與 GPT-2 標記器一樣，這個標記器保留空格並用特定標記替換它們（ **_** )，但 T5 分詞器只在空格上拆分，而不是標點符號。還要注意，它默認在句子的開頭添加了一個空格（之前 **Hello** ) 並忽略了之間的雙空格 **are** 和 **you** .

現在我們已經瞭解了一些不同的標記器如何處理文本，我們可以開始探索底層算法本身。我們首先快速瀏覽一下廣泛適用的 SentencePiece；然後，在接下來的三個部分中，我們將研究用於子詞標記化的三種主要算法是如何工作的。

## 句子

[SentencePiece](https://github.com/google/sentencepiece) 是一種用於文本預處理的標記化算法，您可以將其與我們將在接下來的三個部分中看到的任何模型一起使用。它將文本視為 Unicode 字符序列，並用特殊字符替換空格， **▁** .與 Unigram 算法結合使用（參見[section 7](/course/chapter7/7)), 它甚至不需要預標記化步驟，這對於不使用空格字符的語言（如中文或日語）非常有用。

SentencePiece 的另一個主要特點是可逆標記化：由於沒有對空格進行特殊處理，因此只需通過將它們連接起來並替換 **_** s 帶空格——這會導致標準化的文本。正如我們之前看到的，BERT 分詞器刪除了重複的空格，因此它的分詞是不可逆的。

## 算法概述

在下面的部分中，我們將深入研究三種主要的子詞標記化算法：BPE（由 GPT-2 和其他人使用）、WordPiece（例如由 BERT 使用）和 Unigram（由 T5 和其他人使用）。在我們開始之前，這裡是它們各自工作原理的快速概述。如果您還沒有理解，請在閱讀下一節後立即回到此表。


Model | BPE | WordPiece | Unigram
:----:|:---:|:---------:|:------:
Training | Starts from a small vocabulary and learns rules to merge tokens |  Starts from a small vocabulary and learns rules to merge tokens | Starts from a large vocabulary and learns rules to remove tokens
Training step | Merges the tokens corresponding to the most common pair | Merges the tokens corresponding to the pair with the best score based on the frequency of the pair, privileging pairs where each individual token is less frequent | Removes all the tokens in the vocabulary that will minimize the loss computed on the whole corpus
Learns | Merge rules and a vocabulary | Just a vocabulary | A vocabulary with a score for each token
Encoding | Splits a word into characters and applies the merges learned during training | Finds the longest subword starting from the beginning that is in the vocabulary, then does the same for the rest of the word | Finds the most likely split into tokens, using the scores learned during training

現在讓我們深入瞭解 BPE！

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter6/4.mdx" />

### Unigram標記化
https://huggingface.co/learn/course/zh-TW/chapter6/7.md

# Unigram標記化

<CourseFloatingBanner chapter={6}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/zh-CN/chapter6/section7.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/zh-CN/chapter6/section7.ipynb"},
]} />

在 SentencePiece 中經常使用 Unigram 算法,該算法是 AlBERT、T5、mBART、Big Bird 和 XLNet 等模型使用的標記化算法。

<Youtube id="TGZfZVuF9Yc"/>

<Tip>

💡 本節深入介紹了 Unigram,甚至展示了一個完整的實現。如果你只想大致瞭解標記化算法,可以跳到最後。

</Tip>

## 訓練算法

與 BPE 和 WordPiece 相比，Unigram 在另一個方向上工作：它從一個較大的詞彙表開始，然後從中刪除標記，直到達到所需的詞彙表大小。有多種選項可用於構建基本詞彙表：例如，我們可以採用預標記化單詞中最常見的子串，或者在具有大詞彙量的初始語料庫上應用 BPE。

在訓練的每一步，Unigram 算法都會在給定當前詞彙的情況下計算語料庫的損失。然後，對於詞彙表中的每個符號，算法計算如果刪除該符號，整體損失會增加多少，並尋找增加最少的符號。這些符號對語料庫的整體損失影響較小，因此從某種意義上說，它們「不太需要」並且是移除的最佳候選者。

這是一個非常昂貴的操作，所以我們不只是刪除與最低損失增加相關的單個符號,而且\\(p\\) (\\(p\\)是一個可以控制的超參數,通常是 10 或 20)與最低損失增加相關的符號的百分比。然後重複這個過程，直到詞彙量達到所需的大小。

請注意：我們從不刪除基本字符，以確保可以標記任何單詞。

這或許仍然有點模糊：算法的主要部分是計算語料庫的損失，並查看當我們從詞彙表中刪除一些標記時它會如何變化，但我們還沒有解釋如何做到這一點。這一步依賴於 Unigram 模型的標記化算法，因此我們接下來將深入研究。

我們將重用前面示例中的語料庫：

```
("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5)
```

對於此示例，我們將採用初始詞彙表的所有嚴格子字符串:

```
["h", "u", "g", "hu", "ug", "p", "pu", "n", "un", "b", "bu", "s", "hug", "gs", "ugs"]
```

## 標記化算法

Unigram 模型是一種語言模型,它認為每個標記都獨立於它之前的標記。它是最簡單的語言模型,從某種意義上說, 給定先前上下文的標記 X 的概率就是標記 X 的概率。因此，如果我們使用 Unigram 語言模型生成文本，我們將始終預測最常見的標記。

給定標記的概率是它在原始語料庫中的頻率(我們找到它的次數),除以詞彙表中所有標記的所有頻率的總和(以確保概率總和為 1)。例如, `"ug"` 在 `"hug"` 、 `"pug"` 以及 `"hugs"` 中，所以它在我們的語料庫中的頻率為 20。

以下是詞彙表中所有可能的子詞的出現頻率:

```
("h", 15) ("u", 36) ("g", 20) ("hu", 15) ("ug", 20) ("p", 17) ("pu", 17) ("n", 16)
("un", 16) ("b", 4) ("bu", 4) ("s", 5) ("hug", 15) ("gs", 5) ("ugs", 5)
```

所以,所有頻率之和為210, 並且子詞 `"ug"` 出現的概率是 20/210。

<Tip>

✏️ **現在輪到你了!** 編寫代碼來計算上面的頻率,並仔細檢查顯示的結果以及總和是否正確。

</Tip>

現在，為了對給定的單詞進行標記，我們將所有可能的分割視為標記，並根據 Unigram 模型計算每個分割的概率。由於所有標記都被認為是獨立的，所以這個概率只是每個標記概率的乘積。例如 `"pug"` 的標記化 `["p", "u", "g"]` 的概率為:

$$P([``p", ``u", ``g"]) = P(``p") \times P(``u") \times P(``g") = \frac{5}{210} \times \frac{36}{210} \times \frac{20}{210} = 0.000389$$

相比之下,標記化 `["pu", "g"]` 的概率為:

$$P([``pu", ``g"]) = P(``pu") \times P(``g") = \frac{5}{210} \times \frac{20}{210} = 0.0022676$$

所以一個更有可能。一般來說,具有儘可能少的標記的標記化將具有最高的概率(因為每個標記重複除以 210),這對應於我們直觀想要的:將一個單詞分成儘可能少的標記。

使用 Unigram 模型對單詞進行分詞是概率最高的分詞。在示例 `"pug"` 中,這裡是我們為每個可能的分割獲得的概率:

```
["p", "u", "g"] : 0.000389
["p", "ug"] : 0.0022676
["pu", "g"] : 0.0022676
```

所以 `"pug"` 將被標記為 `["p", "ug"]` 或者 `["pu", "g"]`，取決於首先遇到這些分割中的哪一個（請注意：在更大的語料庫中，這樣的相等的情況很少見）。

在這種情況下，很容易找到所有可能的分割並計算它們的概率,但一般來說會有點困難。有一種用於此的經典算法,稱為 *維特比(Viterbi)算法*。本質上,我們可以構建一個圖來檢測給定單詞的可能分割，如果從_a_到_b_的子詞在詞彙表中，則從字符_a_到字符_b_之間存在一個分支，並將子詞的概率歸因於該分支。

為了在該圖中找到將具有最佳分數的路徑，維特比算法為單詞中的每個位置確定在該位置結束的具有最佳分數的分段。由於我們從開始到結束,可以通過循環遍歷以當前位置結尾的所有子詞，然後使用該子詞開始位置的最佳標記化分數來找到最佳分數。然後，我們只需要展開到達終點所採取的路徑。

讓我們看一個使用我們的詞彙表和單詞 `"unhug"` 的例子。對於每個位置，以最好的分數結尾的子詞如下：

```
Character 0 (u): "u" (score 0.171429)
Character 1 (n): "un" (score 0.076191)
Character 2 (h): "un" "h" (score 0.005442)
Character 3 (u): "un" "hu" (score 0.005442)
Character 4 (g): "un" "hug" (score 0.005442)
```

因此 `"unhug"` 將被標記為 `["un", "hug"]`。

<Tip>

✏️ **現在輪到你了!** 確定單詞 `"huggun"` 的標記化及其分數。

</Tip>

## 回到訓練

現在我們已經瞭解了標記化的工作原理,我們可以更深入地研究訓練期間使用的損失。在任何給定的階段,這個損失是通過對語料庫中的每個單詞進行標記來計算的,使用當前詞彙表和由語料庫中每個標記的頻率確定的 Unigram 模型(如前所述)。

語料庫中的每個詞都有一個分數,損失是這些分數的負對數似然 -- 即所有詞的語料庫中所有詞的總和 `-log(P(word))`。

讓我們用以下語料庫回到我們的例子:

```
("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5)
```

每個單詞的標記化及其各自的分數是:

```
"hug": ["hug"] (score 0.071428)
"pug": ["pu", "g"] (score 0.007710)
"pun": ["pu", "n"] (score 0.006168)
"bun": ["bu", "n"] (score 0.001451)
"hugs": ["hug", "s"] (score 0.001701)
```

所以損失是:

```
10 * (-log(0.071428)) + 5 * (-log(0.007710)) + 12 * (-log(0.006168)) + 4 * (-log(0.001451)) + 5 * (-log(0.001701)) = 169.8
```

現在我們需要計算刪除每個標記如何影響損失。這相當乏味,所以我們在這裡只對兩個標記進行操作,並保存整個過程以備有代碼來幫助我們。在這個(非常)特殊的情況下,我們對所有單詞有兩個等效的標記:正如我們之前看到的,例如, `"pug"` 可以以相同的分數被標記為 `["p", "ug"]`。因此,去除詞彙表中的 `"pu"` 標記將給出完全相同的損失。

另一方面,去除 `"hug"` 損失變得更糟, 因為 `"hug"` 和 `"hugs"` 的標記化會變成:

```
"hug": ["hu", "g"] (score 0.006802)
"hugs": ["hu", "gs"] (score 0.001701)
```

這些變化將導致損失增加:

```
- 10 * (-log(0.071428)) + 10 * (-log(0.006802)) = 23.5
```

因此, 標記 `"pu"`可能會從詞彙表中刪除,但不會刪除 `"hug"`.

## 實現 Unigram

現在讓我們在代碼中實現我們迄今為止看到的所有內容。與 BPE 和 WordPiece 一樣,這不是 Unigram 算法的有效實現(恰恰相反),但它應該可以幫助你更好地理解它。

我們將使用與之前相同的語料庫作為示例:

```python
corpus = [
    "This is the Hugging Face course.",
    "This chapter is about tokenization.",
    "This section shows several tokenizer algorithms.",
    "Hopefully, you will be able to understand how they are trained and generate tokens.",
]
```

這一次,我們將使用 `xlnet-base-cased` 作為我們的模型:

```python
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("xlnet-base-cased")
```

與 BPE 和 WordPiece 一樣,我們首先計算語料庫中每個單詞的出現次數:

```python
from collections import defaultdict

word_freqs = defaultdict(int)
for text in corpus:
    words_with_offsets = tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str(text)
    new_words = [word for word, offset in words_with_offsets]
    for word in new_words:
        word_freqs[word] += 1

word_freqs
```

然後,我們需要將我們的詞彙表初始化為大於我們最終想要的詞彙量。我們必須包含所有基本字符(否則我們將無法標記每個單詞),但對於較大的子字符串,我們將只保留最常見的字符,因此我們按頻率對它們進行排序:

```python
char_freqs = defaultdict(int)
subwords_freqs = defaultdict(int)
for word, freq in word_freqs.items():
    for i in range(len(word)):
        char_freqs[word[i]] += freq
        # Loop through the subwords of length at least 2
        for j in range(i + 2, len(word) + 1):
            subwords_freqs[word[i:j]] += freq

# Sort subwords by frequency
sorted_subwords = sorted(subwords_freqs.items(), key=lambda x: x[1], reverse=True)
sorted_subwords[:10]
```

```python out
[('▁t', 7), ('is', 5), ('er', 5), ('▁a', 5), ('▁to', 4), ('to', 4), ('en', 4), ('▁T', 3), ('▁Th', 3), ('▁Thi', 3)]
```

我們用最優的子詞對字符進行分組,以獲得大小為 300 的初始詞彙表:

```python
token_freqs = list(char_freqs.items()) + sorted_subwords[: 300 - len(char_freqs)]
token_freqs = {token: freq for token, freq in token_freqs}
```

<Tip>

💡 SentencePiece 使用一種稱為增強後綴數組(ESA)的更高效算法來創建初始詞彙表。

</Tip>

接下來,我們計算所有頻率的總和,將頻率轉換為概率。對於我們的模型,我們將存儲概率的對數,因為添加對數比乘以小數在數值上更穩定,這將簡化模型損失的計算:

```python
from math import log

total_sum = sum([freq for token, freq in token_freqs.items()])
model = {token: -log(freq / total_sum) for token, freq in token_freqs.items()}
```

N現在主要功能是使用 Viterbi 算法標記單詞的功能。正如我們之前看到的,該算法計算單詞的每個子串的最佳分段,我們將其存儲在名為 `best_segmentations` 的變量中。我們將在單詞的每個位置(從 0 到其總長度)存儲一個字典,有兩個鍵:最佳分割中最後一個標記的開始索引,以及最佳分割的分數。使用最後一個標記的開始索引,一旦列表完全填充,我們將能夠檢索完整的分段。

填充列表只需兩個循環:主循環遍歷每個起始位置,第二個循環嘗試從該起始位置開始的所有子字符串。如果子串在詞彙表中,我們有一個新的詞分段,直到該結束位置,我們將其與 `best_segmentations` 相比較。

一旦主循環完成,我們就從結尾開始,從一個開始位置跳到下一個,記錄我們前進的標記,直到我們到達單詞的開頭:

```python
def encode_word(word, model):
    best_segmentations = [{"start": 0, "score": 1}] + [
        {"start": None, "score": None} for _ in range(len(word))
    ]
    for start_idx in range(len(word)):
        # This should be properly filled by the previous steps of the loop
        best_score_at_start = best_segmentations[start_idx]["score"]
        for end_idx in range(start_idx + 1, len(word) + 1):
            token = word[start_idx:end_idx]
            if token in model and best_score_at_start is not None:
                score = model[token] + best_score_at_start
                # If we have found a better segmentation ending at end_idx, we update
                if (
                    best_segmentations[end_idx]["score"] is None
                    or best_segmentations[end_idx]["score"] > score
                ):
                    best_segmentations[end_idx] = {"start": start_idx, "score": score}

    segmentation = best_segmentations[-1]
    if segmentation["score"] is None:
        # We did not find a tokenization of the word -> unknown
        return ["<unk>"], None

    score = segmentation["score"]
    start = segmentation["start"]
    end = len(word)
    tokens = []
    while start != 0:
        tokens.insert(0, word[start:end])
        next_start = best_segmentations[start]["start"]
        end = start
        start = next_start
    tokens.insert(0, word[start:end])
    return tokens, score
```

我們已經可以在一些詞上嘗試我們的初始模型:

```python
print(encode_word("Hopefully", model))
print(encode_word("This", model))
```

```python out
(['H', 'o', 'p', 'e', 'f', 'u', 'll', 'y'], 41.5157494601402)
(['This'], 6.288267030694535)
```

現在很容易計算模型在語料庫上的損失!

```python
def compute_loss(model):
    loss = 0
    for word, freq in word_freqs.items():
        _, word_loss = encode_word(word, model)
        loss += freq * word_loss
    return loss
```

我們可以檢查它是否適用於我們擁有的模型:

```python
compute_loss(model)
```

```python out
413.10377642940875
```

計算每個標記的分數也不是很難;我們只需要計算通過刪除每個標記獲得的模型的損失:

```python
import copy


def compute_scores(model):
    scores = {}
    model_loss = compute_loss(model)
    for token, score in model.items():
        # We always keep tokens of length 1
        if len(token) == 1:
            continue
        model_without_token = copy.deepcopy(model)
        _ = model_without_token.pop(token)
        scores[token] = compute_loss(model_without_token) - model_loss
    return scores
```

我們可以在給定的標記上嘗試:

```python
scores = compute_scores(model)
print(scores["ll"])
print(scores["his"])
```

自從 `"ll"` 用於標記化 `"Hopefully"`, 刪除它可能會讓我們使用標記 `"l"` 兩次相反,我們預計它將產生正損失。 `"his"` 僅在單詞`"This"` 內使用,它被標記為自身,所以我們期望它的損失為零。結果如下:

```python out
6.376412403623874
0.0
```

<Tip>

💡 這種方法非常低效,因此 SentencePiece 使用了沒有標記 X 的模型損失的近似值:它不是從頭開始,而是通過其在剩餘詞彙表中的分段替換標記 X。這樣,所有分數可以與模型損失同時計算。

</Tip>

完成所有這些後,我們需要做的最後一件事是將模型使用的特殊標記添加到詞彙表中,然後循環直到我們從詞彙表中修剪了足夠的標記以達到我們想要的大小:

```python
percent_to_remove = 0.1
while len(model) > 100:
    scores = compute_scores(model)
    sorted_scores = sorted(scores.items(), key=lambda x: x[1])
    # Remove percent_to_remove tokens with the lowest scores.
    for i in range(int(len(model) * percent_to_remove)):
        _ = token_freqs.pop(sorted_scores[i][0])

    total_sum = sum([freq for token, freq in token_freqs.items()])
    model = {token: -log(freq / total_sum) for token, freq in token_freqs.items()}
```

然後,為了標記一些文本,我們只需要應用預標記化,然後使用我們的 `encode_word()` 函數:

```python
def tokenize(text, model):
    words_with_offsets = tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str(text)
    pre_tokenized_text = [word for word, offset in words_with_offsets]
    encoded_words = [encode_word(word, model)[0] for word in pre_tokenized_text]
    return sum(encoded_words, [])


tokenize("This is the Hugging Face course.", model)
```

```python out
['▁This', '▁is', '▁the', '▁Hugging', '▁Face', '▁', 'c', 'ou', 'r', 's', 'e', '.']
```

Unigram 就是這樣!希望現在你感覺自己是標記器所有方面的專家。在下一節中,我們將深入研究 🤗 Tokenizers 庫的構建塊,並向您展示如何使用它們來構建您自己的標記器。


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter6/7.mdx" />

### QA 管道中的快速標記器
https://huggingface.co/learn/course/zh-TW/chapter6/3b.md

# QA 管道中的快速標記器

{#if fw === 'pt'}

<CourseFloatingBanner chapter={6}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/zh-CN/chapter6/section3b_pt.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/zh-CN/chapter6/section3b_pt.ipynb"},
]} />

{:else}

<CourseFloatingBanner chapter={6}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/zh-CN/chapter6/section3b_tf.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/zh-CN/chapter6/section3b_tf.ipynb"},
]} />

{/if}

我們現在將深入研究 **question-answering** 管道，看看如何利用偏移量從上下文中獲取手頭問題的答案，有點像我們在上一節中對分組實體所做的。然後我們將看到我們如何處理最終被截斷的非常長的上下文。如果您對問答任務不感興趣，可以跳過此部分。

{#if fw === 'pt'}

<Youtube id="_wxyB3j3mk4"/>

{:else}

<Youtube id="b3u8RzBCX9Y"/>

{/if}

## 使用 `question-answering` 管道

正如我們在[Chapter 1](/course/chapter1)，我們可以使用 **question-answering** 像這樣的管道以獲得問題的答案：

```py
from transformers import pipeline

question_answerer = pipeline("question-answering")
context = """
🤗 Transformers is backed by the three most popular deep learning libraries — Jax, PyTorch, and TensorFlow — with a seamless integration
between them. It's straightforward to train your models with one before loading them for inference with the other.
"""
question = "Which deep learning libraries back 🤗 Transformers?"
question_answerer(question=question, context=context)
```

```python out
{'score': 0.97773,
 'start': 78,
 'end': 105,
 'answer': 'Jax, PyTorch and TensorFlow'}
```

與其他管道不同，它不能截斷和拆分長於模型接受的最大長度的文本（因此可能會丟失文檔末尾的信息），此管道可以處理非常長的上下文，並將返回回答這個問題，即使它在最後：

```py
long_context = """
🤗 Transformers: State of the Art NLP

🤗 Transformers provides thousands of pretrained models to perform tasks on texts such as classification, information extraction,
question answering, summarization, translation, text generation and more in over 100 languages.
Its aim is to make cutting-edge NLP easier to use for everyone.

🤗 Transformers provides APIs to quickly download and use those pretrained models on a given text, fine-tune them on your own datasets and
then share them with the community on our model hub. At the same time, each python module defining an architecture is fully standalone and
can be modified to enable quick research experiments.

Why should I use transformers?

1. Easy-to-use state-of-the-art models:
  - High performance on NLU and NLG tasks.
  - Low barrier to entry for educators and practitioners.
  - Few user-facing abstractions with just three classes to learn.
  - A unified API for using all our pretrained models.
  - Lower compute costs, smaller carbon footprint:

2. Researchers can share trained models instead of always retraining.
  - Practitioners can reduce compute time and production costs.
  - Dozens of architectures with over 10,000 pretrained models, some in more than 100 languages.

3. Choose the right framework for every part of a model's lifetime:
  - Train state-of-the-art models in 3 lines of code.
  - Move a single model between TF2.0/PyTorch frameworks at will.
  - Seamlessly pick the right framework for training, evaluation and production.

4. Easily customize a model or an example to your needs:
  - We provide examples for each architecture to reproduce the results published by its original authors.
  - Model internals are exposed as consistently as possible.
  - Model files can be used independently of the library for quick experiments.

🤗 Transformers is backed by the three most popular deep learning libraries — Jax, PyTorch and TensorFlow — with a seamless integration
between them. It's straightforward to train your models with one before loading them for inference with the other.
"""
question_answerer(question=question, context=long_context)
```

```python out
{'score': 0.97149,
 'start': 1892,
 'end': 1919,
 'answer': 'Jax, PyTorch and TensorFlow'}
```

讓我們看看它是如何做到這一切的！

## 使用模型進行問答

與任何其他管道一樣，我們首先對輸入進行標記化，然後通過模型將其發送。默認情況下用於的檢查點 **question-answering** 管道是[distilbert-base-cased-distilled-squad](https://huggingface.co/distilbert-base-cased-distilled-squad)（名稱中的“squad”來自模型微調的數據集；我們將在[Chapter 7](/course/chapter7/7)):

{#if fw === 'pt'}

```py
from transformers import AutoTokenizer, AutoModelForQuestionAnswering

model_checkpoint = "distilbert-base-cased-distilled-squad"
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)
model = AutoModelForQuestionAnswering.from_pretrained(model_checkpoint)

inputs = tokenizer(question, context, return_tensors="pt")
outputs = model(**inputs)
```

{:else}

```py
from transformers import AutoTokenizer, TFAutoModelForQuestionAnswering

model_checkpoint = "distilbert-base-cased-distilled-squad"
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)
model = TFAutoModelForQuestionAnswering.from_pretrained(model_checkpoint)

inputs = tokenizer(question, context, return_tensors="tf")
outputs = model(**inputs)
```

{/if}

請注意，我們將問題和上下文標記為一對，首先是問題

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter6/question_tokens.svg" alt="An example of tokenization of question and context"/>
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter6/question_tokens-dark.svg" alt="An example of tokenization of question and context"/>
</div>

問答模型的工作方式與我們迄今為止看到的模型略有不同。以上圖為例，該模型已經過訓練，可以預測答案開始的標記的索引（此處為 21）和答案結束處的標記的索引（此處為 24）。這就是為什麼這些模型不返回一個 logits 的張量，而是返回兩個：一個用於對應於答案的開始標記的 logits，另一個用於對應於答案的結束標記的 logits。由於在這種情況下我們只有一個包含 66 個標記的輸入，我們得到：

```py
start_logits = outputs.start_logits
end_logits = outputs.end_logits
print(start_logits.shape, end_logits.shape)
```

{#if fw === 'pt'}

```python out
torch.Size([1, 66]) torch.Size([1, 66])
```

{:else}

```python out
(1, 66) (1, 66)
```

{/if}

為了將這些 logits 轉換為概率，我們將應用一個 softmax 函數——但在此之前，我們需要確保我們屏蔽了不屬於上下文的索引。我們的輸入是 **[CLS] question [SEP] context [SEP]** ，所以我們需要屏蔽問題的標記以及 **[SEP]** 令牌。我們將保留 **[CLS]** 然而，因為某些模型使用它來表示答案不在上下文中。

由於我們將在之後應用 softmax，我們只需要用一個大的負數替換我們想要屏蔽的 logits。在這裡，我們使用 **-10000** ：

{#if fw === 'pt'}

```py
import torch

sequence_ids = inputs.sequence_ids()
# Mask everything apart from the tokens of the context
mask = [i != 1 for i in sequence_ids]
# Unmask the [CLS] token
mask[0] = False
mask = torch.tensor(mask)[None]

start_logits[mask] = -10000
end_logits[mask] = -10000
```

{:else}

```py
import tensorflow as tf

sequence_ids = inputs.sequence_ids()
# Mask everything apart from the tokens of the context
mask = [i != 1 for i in sequence_ids]
# Unmask the [CLS] token
mask[0] = False
mask = tf.constant(mask)[None]

start_logits = tf.where(mask, -10000, start_logits)
end_logits = tf.where(mask, -10000, end_logits)
```

{/if}

現在我們已經正確屏蔽了與我們不想預測的位置相對應的 logits，我們可以應用 softmax：

{#if fw === 'pt'}

```py
start_probabilities = torch.nn.functional.softmax(start_logits, dim=-1)[0]
end_probabilities = torch.nn.functional.softmax(end_logits, dim=-1)[0]
```

{:else}

```py
start_probabilities = tf.math.softmax(start_logits, axis=-1)[0].numpy()
end_probabilities = tf.math.softmax(end_logits, axis=-1)[0].numpy()
```

{/if}

在這個階段，我們可以採用開始和結束概率的 argmax——但我們最終可能會得到一個大於結束索引的開始索引，所以我們需要採取更多的預防措施。我們將計算每個可能的概率 **start_index** 和 **end_index** 在哪裡 **start_index <= end_index** ，然後取元組 **(start_index, end_index)** 以最高的概率。

假設事件“答案開始於 **start_index** ”和“答案結束於 **end_index** ” 要獨立，答案開始於的概率 **start_index** 並結束於 **end_index** 是：

$$\mathrm{start\_probabilities}[\mathrm{start\_index}] \times \mathrm{end\_probabilities}[\mathrm{end\_index}]$$ 

所以，要計算所有的分數，我們只需要計算所有的產品 \\(\mathrm{start\_probabilities}[\mathrm{start\_index}] \times \mathrm{end\_probabilities}[\mathrm{end\_index}]\\) where `start_index <= end_index`.

首先讓我們計算所有可能的產品：
```py
scores = start_probabilities[:, None] * end_probabilities[None, :]
```

{#if fw === 'pt'}

然後我們將屏蔽這些值 **start_index > end_index** 通過將它們設置為 **0** （其他概率都是正數）。這 **torch.triu()** 函數返回作為參數傳遞的 2D 張量的上三角部分，因此它會為我們做屏蔽：

```py
scores = torch.triu(scores)
```

{:else}
然後我們將屏蔽這些值 **start_index > end_index** 通過將它們設置為 **0** （其他概率都是正數）。這 **torch.triu()** 函數返回作為參數傳遞的 2D 張量的上三角部分，因此它會為我們做屏蔽：

```py
scores = np.triu(scores)
```

{/if}

現在我們只需要得到最大值的索引。由於 PyTorch 將返回展平張量中的索引，因此我們需要使用地板除法 **//** 和模數 **%** 操作以獲得 **start_index** 和 **end_index** ：

```py
max_index = scores.argmax().item()
start_index = max_index // scores.shape[1]
end_index = max_index % scores.shape[1]
print(scores[start_index, end_index])
```

我們還沒有完全完成，但至少我們已經有了正確的答案分數（您可以通過將其與上一節中的第一個結果進行比較來檢查這一點）：

```python out
0.97773
```

<Tip>

✏️ **試試看!** 計算五個最可能的答案的開始和結束索引。

</Tip>

我們有 **start_index** 和 **end_index** 就標記而言的答案，所以現在我們只需要轉換為上下文中的字符索引。這是偏移量非常有用的地方。我們可以像在令牌分類任務中一樣抓住它們並使用它們：

```py
inputs_with_offsets = tokenizer(question, context, return_offsets_mapping=True)
offsets = inputs_with_offsets["offset_mapping"]

start_char, _ = offsets[start_index]
_, end_char = offsets[end_index]
answer = context[start_char:end_char]
```

現在我們只需要格式化所有內容以獲得我們的結果：

```py
result = {
    "answer": answer,
    "start": start_char,
    "end": end_char,
    "score": scores[start_index, end_index],
}
print(result)
```

```python out
{'answer': 'Jax, PyTorch and TensorFlow',
 'start': 78,
 'end': 105,
 'score': 0.97773}
```

太棒了！這和我們的第一個例子一樣！

<Tip>

✏️ **試試看!** 使用您之前計算的最佳分數來顯示五個最可能的答案。要檢查您的結果，請返回到第一個管道並在調用它時傳入。

</Tip>

## 處理長上下文

如果我們嘗試對我們之前作為示例使用的問題和長上下文進行標記化，我們將獲得比在 **question-answering** 管道（即 384）：

```py
inputs = tokenizer(question, long_context)
print(len(inputs["input_ids"]))
```

```python out
461
```

因此，我們需要在最大長度處截斷我們的輸入。有幾種方法可以做到這一點，但我們不想截斷問題，只想截斷上下文。由於上下文是第二個句子，我們將使用 **"only_second"** 截斷策略。那麼出現的問題是問題的答案可能不在截斷上下文中。例如，在這裡，我們選擇了一個答案在上下文末尾的問題，當我們截斷它時，答案不存在

```py
inputs = tokenizer(question, long_context, max_length=384, truncation="only_second")
print(tokenizer.decode(inputs["input_ids"]))
```

```python out
"""
[CLS] Which deep learning libraries back [UNK] Transformers? [SEP] [UNK] Transformers : State of the Art NLP

[UNK] Transformers provides thousands of pretrained models to perform tasks on texts such as classification, information extraction,
question answering, summarization, translation, text generation and more in over 100 languages.
Its aim is to make cutting-edge NLP easier to use for everyone.

[UNK] Transformers provides APIs to quickly download and use those pretrained models on a given text, fine-tune them on your own datasets and
then share them with the community on our model hub. At the same time, each python module defining an architecture is fully standalone and
can be modified to enable quick research experiments.

Why should I use transformers?

1. Easy-to-use state-of-the-art models:
  - High performance on NLU and NLG tasks.
  - Low barrier to entry for educators and practitioners.
  - Few user-facing abstractions with just three classes to learn.
  - A unified API for using all our pretrained models.
  - Lower compute costs, smaller carbon footprint:

2. Researchers can share trained models instead of always retraining.
  - Practitioners can reduce compute time and production costs.
  - Dozens of architectures with over 10,000 pretrained models, some in more than 100 languages.

3. Choose the right framework for every part of a model's lifetime:
  - Train state-of-the-art models in 3 lines of code.
  - Move a single model between TF2.0/PyTorch frameworks at will.
  - Seamlessly pick the right framework for training, evaluation and production.

4. Easily customize a model or an example to your needs:
  - We provide examples for each architecture to reproduce the results published by its original authors.
  - Model internal [SEP]
"""
```

這意味著模型將很難選擇正確的答案。為了解決這個問題， **question-answering** 管道允許我們將上下文分成更小的塊，指定最大長度。為確保我們不會在完全錯誤的位置拆分上下文以找到答案，它還包括塊之間的一些重疊。

我們可以讓分詞器（快或慢）通過添加來為我們做這件事 **return_overflowing_tokens=True** ，我們可以指定我們想要的重疊 **stride** 爭論。這是一個使用較小句子的示例：

```py
sentence = "This sentence is not too long but we are going to split it anyway."
inputs = tokenizer(
    sentence, truncation=True, return_overflowing_tokens=True, max_length=6, stride=2
)

for ids in inputs["input_ids"]:
    print(tokenizer.decode(ids))
```

```python out
'[CLS] This sentence is not [SEP]'
'[CLS] is not too long [SEP]'
'[CLS] too long but we [SEP]'
'[CLS] but we are going [SEP]'
'[CLS] are going to split [SEP]'
'[CLS] to split it anyway [SEP]'
'[CLS] it anyway. [SEP]'
```

正如我們所看到的，句子已被分成多個塊，使得每個條目 **inputs["input_ids"]** 最多有 6 個標記（我們需要添加填充以使最後一個條目與其他條目的大小相同）並且每個條目之間有 2 個標記的重疊。

讓我們仔細看看標記化的結果：

```py
print(inputs.keys())
```

```python out
dict_keys(['input_ids', 'attention_mask', 'overflow_to_sample_mapping'])
```

正如預期的那樣，我們得到了輸入 ID 和一個注意力掩碼。最後一個鍵， **overflow_to_sample_mapping** , 是一個映射，它告訴我們每個結果對應哪個句子——這裡我們有 7 個結果，它們都來自我們通過標記器的（唯一）句子：

```py
print(inputs["overflow_to_sample_mapping"])
```

```python out
[0, 0, 0, 0, 0, 0, 0]
```

當我們將幾個句子標記在一起時，這更有用。例如，這個：

```py
sentences = [
    "This sentence is not too long but we are going to split it anyway.",
    "This sentence is shorter but will still get split.",
]
inputs = tokenizer(
    sentences, truncation=True, return_overflowing_tokens=True, max_length=6, stride=2
)

print(inputs["overflow_to_sample_mapping"])
```

讓我們：

```python out
[0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1]
```

這意味著第一個句子像以前一樣分成 7 個塊，接下來的 4 個塊來自第二個句子。


現在讓我們回到我們的長期背景。默認情況下 **question-answering** 管道使用的最大長度為 384，正如我們之前提到的，步長為 128，這對應於模型微調的方式（您可以通過傳遞 **max_seq_len** 和 **stride** 調用管道時的參數）。因此，我們將在標記化時使用這些參數。我們還將添加填充（具有相同長度的樣本，因此我們可以構建張量）以及請求偏移量：

```py
inputs = tokenizer(
    question,
    long_context,
    stride=128,
    max_length=384,
    padding="longest",
    truncation="only_second",
    return_overflowing_tokens=True,
    return_offsets_mapping=True,
)
```

那些 **inputs** 將包含模型期望的輸入 ID 和注意力掩碼，以及偏移量和 **overflow_to_sample_mapping** 我們剛剛談到。由於這兩個不是模型使用的參數，我們將把它們從 **inputs** （我們不會存儲地圖，因為它在這裡沒有用）在將其轉換為張量之前：

{#if fw === 'pt'}

```py
_ = inputs.pop("overflow_to_sample_mapping")
offsets = inputs.pop("offset_mapping")

inputs = inputs.convert_to_tensors("pt")
print(inputs["input_ids"].shape)
```

```python out
torch.Size([2, 384])
```

{:else}

```py
_ = inputs.pop("overflow_to_sample_mapping")
offsets = inputs.pop("offset_mapping")

inputs = inputs.convert_to_tensors("tf")
print(inputs["input_ids"].shape)
```

```python out
(2, 384)
```

{/if}

我們的長上下文被分成兩部分，這意味著在它通過我們的模型後，我們將有兩組開始和結束 logits：

```py
outputs = model(**inputs)

start_logits = outputs.start_logits
end_logits = outputs.end_logits
print(start_logits.shape, end_logits.shape)
```

{#if fw === 'pt'}

```python out
torch.Size([2, 384]) torch.Size([2, 384])
```

{:else}

```python out
(2, 384) (2, 384)
```

{/if}

和以前一樣，我們在採用 softmax 之前首先屏蔽不屬於上下文的標記。我們還屏蔽了所有填充標記（由注意掩碼標記）：

{#if fw === 'pt'}

```py
sequence_ids = inputs.sequence_ids()
# Mask everything apart from the tokens of the context
mask = [i != 1 for i in sequence_ids]
# Unmask the [CLS] token
mask[0] = False
# Mask all the [PAD] tokens
mask = torch.logical_or(torch.tensor(mask)[None], (inputs["attention_mask"] == 0))

start_logits[mask] = -10000
end_logits[mask] = -10000
```

{:else}

```py
sequence_ids = inputs.sequence_ids()
# Mask everything apart from the tokens of the context
mask = [i != 1 for i in sequence_ids]
# Unmask the [CLS] token
mask[0] = False
# Mask all the [PAD] tokens
mask = tf.math.logical_or(tf.constant(mask)[None], inputs["attention_mask"] == 0)

start_logits = tf.where(mask, -10000, start_logits)
end_logits = tf.where(mask, -10000, end_logits)
```

{/if}

然後我們可以使用 softmax 將我們的 logits 轉換為概率：

{#if fw === 'pt'}

```py
start_probabilities = torch.nn.functional.softmax(start_logits, dim=-1)
end_probabilities = torch.nn.functional.softmax(end_logits, dim=-1)
```

{:else}

```py
start_probabilities = tf.math.softmax(start_logits, axis=-1).numpy()
end_probabilities = tf.math.softmax(end_logits, axis=-1).numpy()
```

{/if}

下一步與我們對小上下文所做的類似，但我們對兩個塊中的每一個都重複它。我們將分數歸因於所有可能的答案跨度，然後取得分最高的跨度：

{#if fw === 'pt'}

```py
candidates = []
for start_probs, end_probs in zip(start_probabilities, end_probabilities):
    scores = start_probs[:, None] * end_probs[None, :]
    idx = torch.triu(scores).argmax().item()

    start_idx = idx // scores.shape[1]
    end_idx = idx % scores.shape[1]
    score = scores[start_idx, end_idx].item()
    candidates.append((start_idx, end_idx, score))

print(candidates)
```

{:else}

```py
candidates = []
for start_probs, end_probs in zip(start_probabilities, end_probabilities):
    scores = start_probs[:, None] * end_probs[None, :]
    idx = np.triu(scores).argmax().item()

    start_idx = idx // scores.shape[1]
    end_idx = idx % scores.shape[1]
    score = scores[start_idx, end_idx].item()
    candidates.append((start_idx, end_idx, score))

print(candidates)
```

{/if}

```python out
[(0, 18, 0.33867), (173, 184, 0.97149)]
```

這兩個候選對應於模型能夠在每個塊中找到的最佳答案。該模型對正確答案在第二部分更有信心（這是一個好兆頭！）。現在我們只需要將這兩個標記跨度映射到上下文中的字符跨度（我們只需要映射第二個標記以獲得我們的答案，但看看模型在第一個塊中選擇了什麼很有趣）。

<Tip>

✏️ **試試看!** 修改上面的代碼以返回五個最可能的答案的分數和跨度（總計，而不是每個塊）。

</Tip>

這 **offsets** 我們之前抓取的實際上是一個偏移量列表，每個文本塊有一個列表：

```py
for candidate, offset in zip(candidates, offsets):
    start_token, end_token, score = candidate
    start_char, _ = offset[start_token]
    _, end_char = offset[end_token]
    answer = long_context[start_char:end_char]
    result = {"answer": answer, "start": start_char, "end": end_char, "score": score}
    print(result)
```

```python out
{'answer': '\n🤗 Transformers: State of the Art NLP', 'start': 0, 'end': 37, 'score': 0.33867}
{'answer': 'Jax, PyTorch and TensorFlow', 'start': 1892, 'end': 1919, 'score': 0.97149}
```

如果我們忽略第一個結果，我們會得到與這個長上下文的管道相同的結果——是的！

<Tip>

✏️ **試試看!** 使用您之前計算的最佳分數來顯示五個最可能的答案（對於整個上下文，而不是每個塊）。要檢查您的結果，請返回到第一個管道並在調用它時傳入。

</Tip>

我們對分詞器功能的深入研究到此結束。我們將在下一章再次將所有這些付諸實踐，屆時我們將向您展示如何在一系列常見的 NLP 任務上微調模型。


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter6/3b.mdx" />

### 根據已有的tokenizer訓練新的tokenizer
https://huggingface.co/learn/course/zh-TW/chapter6/2.md

# 根據已有的tokenizer訓練新的tokenizer

<CourseFloatingBanner chapter={6}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/zh-CN/chapter6/section2.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/zh-CN/chapter6/section2.ipynb"},
]} />

如果您感興趣的語言中沒有可用的語言模型，或者如果您的語料庫與您的語言模型所訓練的語料庫有很大不同，您很可能希望從適合您的數據的標記器從頭開始重新訓練模型 . 這將需要在您的數據集上訓練一個新的標記器。 但這究竟是什麼意思？ 當我們在 [第二章](/course/chapter2) 中第一次查看標記器時，我們看到大多數 Transformer 模型使用_子詞分詞算法_。 為了識別哪些子詞是感興趣的並且在手頭的語料庫中最常出現，標記器需要仔細查看語料庫中的所有文本——我們稱之為*training*的過程。 這種訓練的確切規則取決於所使用的標記器的類型，我們將在本章後面討論三種主要算法。

<Youtube id="DJimQynXZsQ"/>

<Tip warning={true}>

⚠️ 訓練標記器與訓練模型不同！模型訓練使用隨機梯度下降使每個batch的loss小一點。它本質上是隨機的（這意味著在進行兩次相同的訓練時，您必須設置一些隨機數種子才能獲得相同的結果）。訓練標記器是一個統計過程，它試圖確定哪些子詞最適合為給定的語料庫選擇，用於選擇它們的確切規則取決於分詞算法。它是確定性的，這意味著在相同的語料庫上使用相同的算法進行訓練時，您總是會得到相同的結果。

</Tip>

## 準備語料庫

🤗 Transformers 中有一個非常簡單的 API，你可以用它來訓練一個新的標記器，使它與現有標記器相同的特徵： **AutoTokenizer.train_new_from_iterator()** .為了復現這一點，假設我們想從頭開始訓練 GPT-2，但使用英語以外的語言。我們的首要任務是在訓練語料庫中收集該語言的大量數據。為了提供每個人都能理解的示例，我們在這裡不會使用俄語或中文之類的語言，而是使用在特定領域的英語語言：Python 代碼。

[🤗 Datasets](https://github.com/huggingface/datasets)庫可以幫助我們組裝一個 Python 源代碼語料庫。我們將使用**load_dataset()**功能下載和緩存[CodeSearchNet](https://huggingface.co/datasets/code_search_net)數據集。該數據集是為[CodeSearchNet 挑戰](https://wandb.ai/github/CodeSearchNet/benchmark)而創建的幷包含來自 GitHub 上開源庫的數百萬種編程語言的函數。在這裡，我們將加載此數據集的 Python 部分：

```py
from datasets import load_dataset

# This can take a few minutes to load, so grab a coffee or tea while you wait!
raw_datasets = load_dataset("code_search_net", "python")
```

我們可以查看訓練集的部分，以查看我們數據集中有哪些列：

```py
raw_datasets["train"]
```

```python out
Dataset({
    features: ['repository_name', 'func_path_in_repository', 'func_name', 'whole_func_string', 'language', 
      'func_code_string', 'func_code_tokens', 'func_documentation_string', 'func_documentation_tokens', 'split_name', 
      'func_code_url'
    ],
    num_rows: 412178
})
```

我們可以看到數據集將文檔字符串與代碼分開，並且有他們各自的標記化後的結果。 這裡。 我們將只使用 `whole_func_string` 列來訓練我們的標記器。 我們可以通過指定到 `train` 中的一部分來查看這些函數的一個示例：

```py
print(raw_datasets["train"][123456]["whole_func_string"])
```

應該打印以下內容：

```out
def handle_simple_responses(
      self, timeout_ms=None, info_cb=DEFAULT_MESSAGE_CALLBACK):
    """Accepts normal responses from the device.

    Args:
      timeout_ms: Timeout in milliseconds to wait for each response.
      info_cb: Optional callback for text sent from the bootloader.

    Returns:
      OKAY packet's message.
    """
    return self._accept_responses('OKAY', info_cb, timeout_ms=timeout_ms)
```

我們需要做的第一件事是將數據集轉換為迭代器文本列表 - 例如，文本列表。使用文本列表將使我們的標記器運行得更快（訓練成批文本而不是一個接一個地處理單個文本），如果我們想避免一次將所有內容都放在內存中，它應該是一個迭代器。如果你的語料庫很大，你會想要利用這樣一個特性：🤗 Datasets 不會將所有內容都加載到 RAM 中，而是將數據集的元素存儲在磁盤上。

執行以下操作將創建一個包含 1,000 個文本的列表的列表，但會將所有內容加載到內存中：

```py
# Don't uncomment the following line unless your dataset is small!
# training_corpus = [raw_datasets["train"][i: i + 1000]["whole_func_string"] for i in range(0, len(raw_datasets["train"]), 1000)]
```

使用 Python 生成器，我們可以避免 Python 將任何內容加載到內存中，直到真正需要為止。要創建這樣的生成器，您只需要將括號替換為圓括號：

```py
training_corpus = (
    raw_datasets["train"][i : i + 1000]["whole_func_string"]
    for i in range(0, len(raw_datasets["train"]), 1000)
)
```

這行代碼不會獲取數據集的任何元素；它只是創建了一個可以在 Python 中使用的對象 **for** 環形。文本只會在您需要時加載（即，當您處於 **for** 需要它們的循環），並且一次只會加載 1,000 個文本。這樣，即使您正在處理龐大的數據集，也不會耗盡所有內存。

生成器對象的問題在於它只能使用一次，每次訪問它將給出下一個值。 下面是一個例子：

```py
gen = (i for i in range(10))
print(list(gen))
print(list(gen))
```

我們第一次得到了這個列表，然後是一個空列表：

```python out
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[]
```

這就是我們定義一個返回生成器的函數的原因：

```py
def get_training_corpus():
    return (
        raw_datasets["train"][i : i + 1000]["whole_func_string"]
        for i in range(0, len(raw_datasets["train"]), 1000)
    )


training_corpus = get_training_corpus()
```

您還可以在一個 **for** 循環內部使用 **yield** 關鍵字定義您的生成器：

```py
def get_training_corpus():
    dataset = raw_datasets["train"]
    for start_idx in range(0, len(dataset), 1000):
        samples = dataset[start_idx : start_idx + 1000]
        yield samples["whole_func_string"]
```

這將產生與以前完全相同的生成器，但允許您使用比列表生成式中更復雜的邏輯。

## 訓練一個新的標記器

現在我們的語料庫是文本批量迭代器的形式，我們準備訓練一個新的標記器。為此，我們首先需要加載要與模型配對的標記器（此處為 GPT-2）：

```py
from transformers import AutoTokenizer

old_tokenizer = AutoTokenizer.from_pretrained("gpt2")
```

即使我們要訓練一個新的標記器，最好還是這樣做以避免完全從頭開始。這樣，我們就不必指定任何關於標記化算法或我們想要使用的特殊標記；我們的新標記器將與 GPT-2 完全相同，唯一會改變的是輸入的數據，這將取決於我們訓練的語料。

首先讓我們看看這個標記器將如何處理示例的數據：

```py
example = '''def add_numbers(a, b):
    """Add the two numbers `a` and `b`."""
    return a + b'''

tokens = old_tokenizer.tokenize(example)
tokens
```

```python out
['def', 'Ġadd', '_', 'n', 'umbers', '(', 'a', ',', 'Ġb', '):', 'Ċ', 'Ġ', 'Ġ', 'Ġ', 'Ġ"""', 'Add', 'Ġthe', 'Ġtwo',
 'Ġnumbers', 'Ġ`', 'a', '`', 'Ġand', 'Ġ`', 'b', '`', '."', '""', 'Ċ', 'Ġ', 'Ġ', 'Ġ', 'Ġreturn', 'Ġa', 'Ġ+', 'Ġb']
```

這個標記器有一些特殊的符號，比如 **Ċ** 和 **Ġ** ，分別表示空格和換行符。正如我們所看到的，這不是太有效：標記器為每個空格返回單獨的標記，當它可以將縮進級別組合在一起時（因為在代碼中具有四個或八個空格的集合將非常普遍）。它也有點奇怪地拆分了函數名稱，而習慣使用**_**的函數命名的方法。

讓我們訓練一個新的標記器，看看它是否能解決這些問題。為此，我們將使用 **train_new_from_iterator()** 方法：

```py
tokenizer = old_tokenizer.train_new_from_iterator(training_corpus, 52000)
```
如果您的語料庫非常大，此命令可能需要一些時間，但對於這個 1.6 GB 文本數據集，它的速度非常快（在具有 12 個內核的 AMD Ryzen 9 3900X CPU 上為 1 分 16 秒）。

注意 **AutoTokenizer.train_new_from_iterator()** 僅當您使用的標記器是“快速（fast）”標記器時才有效。正如您將在下一節中看到的，🤗 Transformers 庫包含兩種類型的標記器：一些完全用 Python 編寫，而另一些（快速的）由 🤗 Tokenizers 庫支持，該庫用[Rust](https://www.rust-lang.org)編程語言編寫。 Python 是最常用於數據科學和深度學習應用程序的語言，但是當需要並行化以提高速度時，必須用另一種語言編寫。例如，模型計算核心的矩陣乘法是用 CUDA 編寫的，CUDA 是一個針對 GPU 的優化 C 庫。

用純 Python 訓練一個全新的標記器會非常緩慢，這就是我們開發 🤗 Tokenizers庫的原因。請注意，正如您無需學習 CUDA 語言即可在 GPU 上執行您的模型一樣，您也無需學習 Rust 即可使用快速標記器。 🤗 Tokenizers 庫為許多內部調用 Rust 代碼的方法提供 Python 綁定；例如，並行化新標記器的訓練，或者，正如我們在[第三章](/course/chapter3)中看到的，對一批輸入進行標記化。

大多數 Transformer 模型都有可用的快速標記器（您可以[在這裡](https://huggingface.co/transformers/#supported-frameworks)檢查一些例外情況)，如果 **AutoTokenizer** 可用，API 總是為您選擇快速標記器。在下一節中，我們將看看快速標記器具有的其他一些特殊功能，這些功能對於標記分類和問答等任務非常有用。然而，在深入研究之前，讓我們在上一個示例中嘗試我們全新的標記器：

```py
tokens = tokenizer.tokenize(example)
tokens
```

```python out
['def', 'Ġadd', '_', 'numbers', '(', 'a', ',', 'Ġb', '):', 'ĊĠĠĠ', 'Ġ"""', 'Add', 'Ġthe', 'Ġtwo', 'Ġnumbers', 'Ġ`',
 'a', '`', 'Ġand', 'Ġ`', 'b', '`."""', 'ĊĠĠĠ', 'Ġreturn', 'Ġa', 'Ġ+', 'Ġb']
```

在這裡我們再次看到特殊符號 **Ċ** 和 **Ġ** 表示空格和換行符，但我們也可以看到我們的標記器學習了一些高度特定於 Python 函數語料庫的標記：例如，有一個 **ĊĠĠĠ** 表示縮進的標記，以及 **Ġ** 表示開始文檔字符串的三個引號的標記。標記器還正確使用**_**命名的規範將函數名稱拆分為  .這是一個非常緊湊的表示；相比之下，在同一個例子中使用簡單的英語標記器會給我們一個更長的句子：

```py
print(len(tokens))
print(len(old_tokenizer.tokenize(example)))
```

```python out
27
36
```

讓我們再看一個例子：

```python
example = """class LinearLayer():
    def __init__(self, input_size, output_size):
        self.weight = torch.randn(input_size, output_size)
        self.bias = torch.zeros(output_size)

    def __call__(self, x):
        return x @ self.weights + self.bias
    """
tokenizer.tokenize(example)
```

```python out
['class', 'ĠLinear', 'Layer', '():', 'ĊĠĠĠ', 'Ġdef', 'Ġ__', 'init', '__(', 'self', ',', 'Ġinput', '_', 'size', ',',
 'Ġoutput', '_', 'size', '):', 'ĊĠĠĠĠĠĠĠ', 'Ġself', '.', 'weight', 'Ġ=', 'Ġtorch', '.', 'randn', '(', 'input', '_',
 'size', ',', 'Ġoutput', '_', 'size', ')', 'ĊĠĠĠĠĠĠĠ', 'Ġself', '.', 'bias', 'Ġ=', 'Ġtorch', '.', 'zeros', '(',
 'output', '_', 'size', ')', 'ĊĊĠĠĠ', 'Ġdef', 'Ġ__', 'call', '__(', 'self', ',', 'Ġx', '):', 'ĊĠĠĠĠĠĠĠ',
 'Ġreturn', 'Ġx', 'Ġ@', 'Ġself', '.', 'weights', 'Ġ+', 'Ġself', '.', 'bias', 'ĊĠĠĠĠ']
```

除了一個縮進對應的token，這裡我們還可以看到一個雙縮進的token： **ĊĠĠĠĠĠĠĠ** .特殊的 Python 詞如 **class** , **init** , **call** , **self** ， 和 **return** 每個都被標記為一個標記，我們可以看到，以及分裂 **_** 和 **.** 標記器甚至可以正確拆分駝峰式名稱： **LinearLayer** 被標記為 **[ĠLinear, Layer]** .

## 保存標記器

為了確保我們以後可以使用它，我們需要保存我們的新標記器。就像模型一樣，是通過 **save_pretrained()** 方法：

```py
tokenizer.save_pretrained("code-search-net-tokenizer")
```

這將創建一個名為的*code-search-net-tokenizer*的新文件夾，它將包含重新加載標記器所需要的所有文件。如果您想與您的同事和朋友分享這個標記器，您可以通過登錄您的帳戶將其上傳到 Hub。如果您在notebook上工作，有一個方便的功能可以幫助您：

```python
from huggingface_hub import notebook_login

notebook_login()
```

這將顯示一個小部件，您可以在其中輸入您的 Hugging Face 登錄憑據。如果您不是在notebook上工作，只需在終端中輸入以下行：

```bash
huggingface-cli login
```

登錄後，您可以通過執行以下命令來推送您的標記器：

```py
tokenizer.push_to_hub("code-search-net-tokenizer")
```

這將在您的命名空間中創建一個名為**code-search-net-tokenizer**的新存儲庫  ，包含標記器文件。然後，您可以使用以下命令從任何地方加載標記器的 **from_pretrained()** 方法：

```py
# Replace "huggingface-course" below with your actual namespace to use your own tokenizer
tokenizer = AutoTokenizer.from_pretrained("huggingface-course/code-search-net-tokenizer")
```

您現在已準備好從頭開始訓練語言模型並根據您手頭的任務對其進行微調！我們將在[第七章](/course/chapter7)進行這部分。但首先，在本章的其餘部分，我們將仔細研究快速標記器，並詳細探討調用 **train_new_from_iterator()** 方法時實際發生的情況 .


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter6/2.mdx" />

### 創建自己的數據集
https://huggingface.co/learn/course/zh-TW/chapter5/5.md

# 創建自己的數據集

<CourseFloatingBanner chapter={5}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/zh-CN/chapter5/section5.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/zh-CN/chapter5/section5.ipynb"},
]} />

有時，不存在合適的數據集適用於您構建 NLP 應用，因此您需要自己創建。在本節中，我們將向您展示如何創建一個[GitHub issues](https://github.com/features/issues/)的語料庫，GitHub issues通常用於跟蹤 GitHub 存儲庫中的錯誤或功能。該語料庫可用於各種目的，包括：
* 探索關閉未解決的issue或拉取請求需要多長時間
* 訓練一個*多標籤分類器*可以根據issue的描述（例如，“錯誤”、“增強”或“issue”）用元數據標記issue
* 創建語義搜索引擎以查找與用戶查詢匹配的issue

在這裡，我們將專注於創建語料庫，在下一節中，我們將探索語義搜索。我們將使用與流行的開源項目相關的 GitHub issue：🤗 Datasets！接下來讓我們看看如何獲取數據並探索這些issue中包含的信息。

## 獲取數據

您可以瀏覽 🤗 Datasets 中的所有issue[Issues tab](https://github.com/huggingface/datasets/issues).如以下屏幕截圖所示，在撰寫本文時，有 331 個未解決的issue和 668 個已關閉的issue。

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter5/datasets-issues.png" alt="The GitHub issues associated with 🤗 Datasets." width="80%"/>
</div>

如果您單擊其中一個issue，您會發現它包含一個標題、一個描述和一組表徵該issue的標籤。下面的屏幕截圖顯示了一個示例.

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter5/datasets-issues-single.png" alt="A typical GitHub issue in the 🤗 Datasets repository." width="80%"/>
</div>

要下載所有存儲庫的issue，我們將使用[GitHub REST API](https://docs.github.com/en/rest)投票[Issues endpoint](https://docs.github.com/en/rest/reference/issues#list-repository-issues).此節點返回一個 JSON 對象列表，每個對象包含大量字段，其中包括標題和描述以及有關issue狀態的元數據等。

下載issue的一種便捷方式是通過 **requests** 庫，這是用 Python 中發出 HTTP 請求的標準方式。您可以通過運行以下的代碼來安裝庫：

```python
!pip install requests
```

安裝庫後，您通過調用 **requests.get()** 功能來獲取**Issues**節點。例如，您可以運行以下命令來獲取第一頁上的第一個Issues：

```py
import requests

url = "https://api.github.com/repos/huggingface/datasets/issues?page=1&per_page=1"
response = requests.get(url)
```

這 **response** 對象包含很多關於請求的有用信息，包括 HTTP 狀態碼：

```py
response.status_code
```

```python out
200
```

其中一個狀態碼 **200** 表示請求成功（您可以[在這裡](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes)找到可能的 HTTP 狀態代碼列表）。然而，我們真正感興趣的是有效的信息，由於我們知道我們的issues是 JSON 格式，讓我們按如下方式查看所有的信息：

```py
response.json()
```

```python out
[{'url': 'https://api.github.com/repos/huggingface/datasets/issues/2792',
  'repository_url': 'https://api.github.com/repos/huggingface/datasets',
  'labels_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792/labels{/name}',
  'comments_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792/comments',
  'events_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792/events',
  'html_url': 'https://github.com/huggingface/datasets/pull/2792',
  'id': 968650274,
  'node_id': 'MDExOlB1bGxSZXF1ZXN0NzEwNzUyMjc0',
  'number': 2792,
  'title': 'Update GooAQ',
  'user': {'login': 'bhavitvyamalik',
   'id': 19718818,
   'node_id': 'MDQ6VXNlcjE5NzE4ODE4',
   'avatar_url': 'https://avatars.githubusercontent.com/u/19718818?v=4',
   'gravatar_id': '',
   'url': 'https://api.github.com/users/bhavitvyamalik',
   'html_url': 'https://github.com/bhavitvyamalik',
   'followers_url': 'https://api.github.com/users/bhavitvyamalik/followers',
   'following_url': 'https://api.github.com/users/bhavitvyamalik/following{/other_user}',
   'gists_url': 'https://api.github.com/users/bhavitvyamalik/gists{/gist_id}',
   'starred_url': 'https://api.github.com/users/bhavitvyamalik/starred{/owner}{/repo}',
   'subscriptions_url': 'https://api.github.com/users/bhavitvyamalik/subscriptions',
   'organizations_url': 'https://api.github.com/users/bhavitvyamalik/orgs',
   'repos_url': 'https://api.github.com/users/bhavitvyamalik/repos',
   'events_url': 'https://api.github.com/users/bhavitvyamalik/events{/privacy}',
   'received_events_url': 'https://api.github.com/users/bhavitvyamalik/received_events',
   'type': 'User',
   'site_admin': False},
  'labels': [],
  'state': 'open',
  'locked': False,
  'assignee': None,
  'assignees': [],
  'milestone': None,
  'comments': 1,
  'created_at': '2021-08-12T11:40:18Z',
  'updated_at': '2021-08-12T12:31:17Z',
  'closed_at': None,
  'author_association': 'CONTRIBUTOR',
  'active_lock_reason': None,
  'pull_request': {'url': 'https://api.github.com/repos/huggingface/datasets/pulls/2792',
   'html_url': 'https://github.com/huggingface/datasets/pull/2792',
   'diff_url': 'https://github.com/huggingface/datasets/pull/2792.diff',
   'patch_url': 'https://github.com/huggingface/datasets/pull/2792.patch'},
  'body': '[GooAQ](https://github.com/allenai/gooaq) dataset was recently updated after splits were added for the same. This PR contains new updated GooAQ with train/val/test splits and updated README as well.',
  'performed_via_github_app': None}]
```

哇，這是很多信息！我們可以看到有用的字段，例如 **標題** , **內容** ，  **參與的成員**， **issue的描述信息**，以及打開issue的GitHub 用戶的信息。

<Tip>

✏️ 試試看！單擊上面 JSON 中的幾個 URL，以瞭解每個 GitHub issue中我url鏈接到的實際的地址。
</Tip>

如 GitHub[文檔](https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting) 中所述，未經身份驗證的請求限制為每小時 60 個請求。雖然你可以增加 **per_page** 查詢參數以減少您發出的請求數量，您仍然會遭到任何超過幾千個issue的存儲庫的速率限制。因此，您應該關注 GitHub 的[創建個人身份令牌](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token)，創建一個個人訪問令牌這樣您就可以將速率限制提高到每小時 5,000 個請求。獲得令牌後，您可以將其包含在請求標頭中：

```py
GITHUB_TOKEN = xxx  # Copy your GitHub token here
headers = {"Authorization": f"token {GITHUB_TOKEN}"}
```

<Tip warning={true}>

⚠️ 不要與陌生人共享存在GITHUB令牌的筆記本。我們建議您在使用完後將GITHUB令牌刪除，以避免意外洩漏此信息。一個更好的做法是，將令牌存儲在.env文件中，並使用 [`python-dotenv` library](https://github.com/theskumar/python-dotenv) 為您自動將其作為環境變量加載。

</Tip>

現在我們有了訪問令牌，讓我們創建一個可以從 GitHub 存儲庫下載所有issue的函數：

```py
import time
import math
from pathlib import Path
import pandas as pd
from tqdm.notebook import tqdm


def fetch_issues(
    owner="huggingface",
    repo="datasets",
    num_issues=10_000,
    rate_limit=5_000,
    issues_path=Path("."),
):
    if not issues_path.is_dir():
        issues_path.mkdir(exist_ok=True)

    batch = []
    all_issues = []
    per_page = 100  # Number of issues to return per page
    num_pages = math.ceil(num_issues / per_page)
    base_url = "https://api.github.com/repos"

    for page in tqdm(range(num_pages)):
        # Query with state=all to get both open and closed issues
        query = f"issues?page={page}&per_page={per_page}&state=all"
        issues = requests.get(f"{base_url}/{owner}/{repo}/{query}", headers=headers)
        batch.extend(issues.json())

        if len(batch) > rate_limit and len(all_issues) < num_issues:
            all_issues.extend(batch)
            batch = []  # Flush batch for next time period
            print(f"Reached GitHub rate limit. Sleeping for one hour ...")
            time.sleep(60 * 60 + 1)

    all_issues.extend(batch)
    df = pd.DataFrame.from_records(all_issues)
    df.to_json(f"{issues_path}/{repo}-issues.jsonl", orient="records", lines=True)
    print(
        f"Downloaded all the issues for {repo}! Dataset stored at {issues_path}/{repo}-issues.jsonl"
    )
```

現在我們可以調用 **fetch_issues()** 批量下載所有issue，避免超過GitHub每小時的請求數限制；結果將存儲在repository_name-issues.jsonl文件，其中每一行都是一個 JSON 對象，代表一個issue。讓我們使用這個函數從 🤗 Datasets中抓取所有issue：

```py
# Depending on your internet connection, this can take several minutes to run...
fetch_issues()
```

下載issue後，我們可以使用我們 [section 2](/course/chapter5/2)新學會的方法在本地加載它們:

```py
issues_dataset = load_dataset("json", data_files="datasets-issues.jsonl", split="train")
issues_dataset
```

```python out
Dataset({
    features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'timeline_url', 'performed_via_github_app'],
    num_rows: 3019
})
```

太好了，我們已經從頭開始創建了我們的第一個數據集！但是為什麼會有幾千個issue，而🤗 Datasets存儲庫中的[Issues 選項卡](https://github.com/huggingface/datasets/issues)總共卻只顯示了大約 1,000 個issue🤔？如 GitHub [文檔](https://docs.github.com/en/rest/reference/issues#list-issues-assigned-to-the-authenticated-user)中所述，那是因為我們也下載了所有的拉取請求：

>Git Hub的REST API v3認為每個pull請求都是一個issue，但並不是每個issue都是一個pull請求。因此，“Issues”節點可能在響應中同時返回issue和拉取請求。你可以通過pull_request 的 key來辨別pull請求。請注意，從“Issues”節點返回的pull請求的id將是一個issue id。

由於issue和pull request的內容有很大的不同，我們先做一些小的預處理，讓我們能夠區分它們。

## 清理數據

上面來自 GitHub 文檔的片段告訴我們， **pull_request** 列可用於區分issue和拉取請求。讓我們隨機挑選一些樣本，看看有什麼不同。我們將使用在[第三節](/course/chapter5/3), 學習的方法，使用 **Dataset.shuffle()** 和 **Dataset.select()** 抽取一個隨機樣本，然後將 **html_url** 和 **pull_request** 列使用zip函數打包，以便我們可以比較各種 URL：

```py
sample = issues_dataset.shuffle(seed=666).select(range(3))

# Print out the URL and pull request entries
for url, pr in zip(sample["html_url"], sample["pull_request"]):
    print(f">> URL: {url}")
    print(f">> Pull request: {pr}\n")
```

```python out
>> URL: https://github.com/huggingface/datasets/pull/850
>> Pull request: {'url': 'https://api.github.com/repos/huggingface/datasets/pulls/850', 'html_url': 'https://github.com/huggingface/datasets/pull/850', 'diff_url': 'https://github.com/huggingface/datasets/pull/850.diff', 'patch_url': 'https://github.com/huggingface/datasets/pull/850.patch'}

>> URL: https://github.com/huggingface/datasets/issues/2773
>> Pull request: None

>> URL: https://github.com/huggingface/datasets/pull/783
>> Pull request: {'url': 'https://api.github.com/repos/huggingface/datasets/pulls/783', 'html_url': 'https://github.com/huggingface/datasets/pull/783', 'diff_url': 'https://github.com/huggingface/datasets/pull/783.diff', 'patch_url': 'https://github.com/huggingface/datasets/pull/783.patch'}
```

這裡我們可以看到，每個pull請求都與各種url相關聯，而普通issue只有一個None條目。我們可以使用這一點不同來創建一個新的is_pull_request列通過檢查pull_request字段是否為None來區分它們:

```py
issues_dataset = issues_dataset.map(
    lambda x: {"is_pull_request": False if x["pull_request"] is None else True}
)
```

<Tip>

✏️ 試試看！計算在 🤗 Datasets中解決issue所需的平均時間。您可能會發現 Dataset.filter()函數對於過濾拉取請求和未解決的issue很有用，並且您可以使用Dataset.set_format()函數將數據集轉換為DataFrame，以便您可以輕鬆地按照需求修改創建和關閉的時間的格式（以時間戳格式）。

</Tip>

儘管我們可以通過刪除或重命名某些列來進一步清理數據集，但在此階段儘可能保持數據集“原始”狀態通常是一個很好的做法，以便它可以在多個應用程序中輕鬆使用。在我們將數據集推送到 Hugging Face Hub 之前，讓我們再添加一些缺少的數據：與每個issue和拉取請求相關的評論。我們接下來將添加它們——你猜對了——我們將依然使用GitHub REST API！

## 擴充數據集

如以下屏幕截圖所示，與issue或拉取請求相關的評論提供了豐富的信息，特別是如果我們有興趣構建搜索引擎來回答用戶對這個項目的疑問。

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter5/datasets-issues-comment.png" alt="Comments associated with an issue about 🤗 Datasets." width="80%"/>
</div>

GitHub REST API 提供了一個 [評論節點](https://docs.github.com/en/rest/reference/issues#list-issue-comments) 返回與issue編號相關的所有評論。讓我們測試節點以查看它返回的內容：

```py
issue_number = 2792
url = f"https://api.github.com/repos/huggingface/datasets/issues/{issue_number}/comments"
response = requests.get(url, headers=headers)
response.json()
```

```python out
[{'url': 'https://api.github.com/repos/huggingface/datasets/issues/comments/897594128',
  'html_url': 'https://github.com/huggingface/datasets/pull/2792#issuecomment-897594128',
  'issue_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792',
  'id': 897594128,
  'node_id': 'IC_kwDODunzps41gDMQ',
  'user': {'login': 'bhavitvyamalik',
   'id': 19718818,
   'node_id': 'MDQ6VXNlcjE5NzE4ODE4',
   'avatar_url': 'https://avatars.githubusercontent.com/u/19718818?v=4',
   'gravatar_id': '',
   'url': 'https://api.github.com/users/bhavitvyamalik',
   'html_url': 'https://github.com/bhavitvyamalik',
   'followers_url': 'https://api.github.com/users/bhavitvyamalik/followers',
   'following_url': 'https://api.github.com/users/bhavitvyamalik/following{/other_user}',
   'gists_url': 'https://api.github.com/users/bhavitvyamalik/gists{/gist_id}',
   'starred_url': 'https://api.github.com/users/bhavitvyamalik/starred{/owner}{/repo}',
   'subscriptions_url': 'https://api.github.com/users/bhavitvyamalik/subscriptions',
   'organizations_url': 'https://api.github.com/users/bhavitvyamalik/orgs',
   'repos_url': 'https://api.github.com/users/bhavitvyamalik/repos',
   'events_url': 'https://api.github.com/users/bhavitvyamalik/events{/privacy}',
   'received_events_url': 'https://api.github.com/users/bhavitvyamalik/received_events',
   'type': 'User',
   'site_admin': False},
  'created_at': '2021-08-12T12:21:52Z',
  'updated_at': '2021-08-12T12:31:17Z',
  'author_association': 'CONTRIBUTOR',
  'body': "@albertvillanova my tests are failing here:\r\n```\r\ndataset_name = 'gooaq'\r\n\r\n    def test_load_dataset(self, dataset_name):\r\n        configs = self.dataset_tester.load_all_configs(dataset_name, is_local=True)[:1]\r\n>       self.dataset_tester.check_load_dataset(dataset_name, configs, is_local=True, use_local_dummy_data=True)\r\n\r\ntests/test_dataset_common.py:234: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\ntests/test_dataset_common.py:187: in check_load_dataset\r\n    self.parent.assertTrue(len(dataset[split]) > 0)\r\nE   AssertionError: False is not true\r\n```\r\nWhen I try loading dataset on local machine it works fine. Any suggestions on how can I avoid this error?",
  'performed_via_github_app': None}]
```

我們可以看到註釋存儲在body字段中，所以讓我們編寫一個簡單的函數，通過在response.json()中為每個元素挑選body內容來返回與某個issue相關的所有評論：

```py
def get_comments(issue_number):
    url = f"https://api.github.com/repos/huggingface/datasets/issues/{issue_number}/comments"
    response = requests.get(url, headers=headers)
    return [r["body"] for r in response.json()]


# Test our function works as expected
get_comments(2792)
```

```python out
["@albertvillanova my tests are failing here:\r\n```\r\ndataset_name = 'gooaq'\r\n\r\n    def test_load_dataset(self, dataset_name):\r\n        configs = self.dataset_tester.load_all_configs(dataset_name, is_local=True)[:1]\r\n>       self.dataset_tester.check_load_dataset(dataset_name, configs, is_local=True, use_local_dummy_data=True)\r\n\r\ntests/test_dataset_common.py:234: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\ntests/test_dataset_common.py:187: in check_load_dataset\r\n    self.parent.assertTrue(len(dataset[split]) > 0)\r\nE   AssertionError: False is not true\r\n```\r\nWhen I try loading dataset on local machine it works fine. Any suggestions on how can I avoid this error?"]
```

這看起來不錯，所以讓我們使用 **Dataset.map()** 方法在我們數據集中每個issue的添加一個**comments**列：

```py
# Depending on your internet connection, this can take a few minutes...
issues_with_comments_dataset = issues_dataset.map(
    lambda x: {"comments": get_comments(x["number"])}
)
```

最後一步是將增強數據集與原始數據保存在一起，以便我們可以將它們都推送到 Hub：

```py
issues_with_comments_dataset.to_json("issues-datasets-with-comments.jsonl")
```

## 將數據集上傳到 Hugging Face Hub

<Youtube id="HaN6qCr_Afc"/>

現在我們有了我們的增強數據集，是時候將它推送到 Hub 並且與社區共享它！要上傳數據集，我們將使用[🤗 Hub 庫](https://github.com/huggingface/huggingface_hub)，它允許我們通過 Python API 與 Hugging Face Hub 進行交互。 🤗 Hub 預裝了🤗 Transformers，所以我們可以直接使用它。例如，我們可以使用 **list_datasets()** 獲取有關當前託管在 Hub 上的所有公共數據集的信息的函數：

```py
from huggingface_hub import list_datasets

all_datasets = list_datasets()
print(f"Number of datasets on Hub: {len(all_datasets)}")
print(all_datasets[0])
```

```python out
Number of datasets on Hub: 1487
Dataset Name: acronym_identification, Tags: ['annotations_creators:expert-generated', 'language_creators:found', 'languages:en', 'licenses:mit', 'multilinguality:monolingual', 'size_categories:10K<n<100K', 'source_datasets:original', 'task_categories:structure-prediction', 'task_ids:structure-prediction-other-acronym-identification']
```

我們可以看到 Hub 上目前有近 1,500 個數據集， **list_datasets()** 函數還提供了有關每個數據集存儲庫的一些基本元數據。

為了上傳我們的數據集，我們需要做的第一件事是在 Hub 上創建一個新的數據集存儲庫。為此，我們需要一個身份驗證令牌，可以通過首先使用**notebook_login()** 函數登錄 Hugging Face Hub 獲得：

```py
from huggingface_hub import notebook_login

notebook_login()
```
這將創建一個小部件，您可以在其中輸入您的用戶名和密碼， API 令牌將保存在~/.huggingface/令牌.如果您在終端中運行代碼，則可以改為通過 CLI 登錄：

```bash
huggingface-cli login
```

完成此操作後，我們可以創建一個新的數據集存儲庫 **create_repo()** 功能：

```py
from huggingface_hub import create_repo

repo_url = create_repo(name="github-issues", repo_type="dataset")
repo_url
```

```python out
'https://huggingface.co/datasets/lewtun/github-issues'
```

在此示例中，我們創建了一個名為的空數據集存儲庫 **github-issues** 在下面 **lewtun** 用戶名（當您運行此代碼時，用戶名應該是您的 Hub 用戶名！）。

<Tip>

✏️ 試試看！使用您的 Hugging Face Hub 用戶名和密碼獲取令牌並創建一個名為 github-issues.請記住永遠不要將您的憑據保存在 Colab 或任何其他存儲庫中，因為這些信息可能會被不法分子利用。

</Tip>

接下來，讓我們將存儲庫從 Hub 克隆到我們的本地機器，並將我們的數據集文件複製到其中。 🤗 Hub 提供了一個方便的 **Repository** 類，它包含許多常見 Git 命令的類，因此要克隆遠程存儲庫，我們只需要提供我們要克隆的 URL 和本地路徑：

```py
from huggingface_hub import Repository

repo = Repository(local_dir="github-issues", clone_from=repo_url)
!cp datasets-issues-with-comments.jsonl github-issues/
```

默認情況下，使用Git LFS跟蹤各種文件擴展名（如.bin、.gz和.zip），以便在同一Git工作流中對大型文件進行版本控制。您可以在存儲庫的.gitattributes文件找到跟蹤文件擴展名的列表。要在列表中包含JSON行格式，我們可以運行以下命令：

```py
repo.lfs_track("*.jsonl")
```

然後我們可以使用 **Repository.push_to_hub()** 將數據集推送到 Hub：

```py
repo.push_to_hub()
```

如果我們導航到包含在 **repo_url** ，我們現在應該看到我們的數據集文件已經上傳。

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter5/hub-repo.png" alt="Our dataset repository on the Hugging Face Hub." width="80%"/>
</div>

從這裡，任何人都可以通過簡單地提供來下載數據集 **load_dataset()** 以存儲庫 ID 作為 **path** 爭論：

```py
remote_dataset = load_dataset("lewtun/github-issues", split="train")
remote_dataset
```

```python out
Dataset({
    features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'performed_via_github_app', 'is_pull_request'],
    num_rows: 2855
})
```

很酷，我們已經將我們的數據集推送到 Hub，其他人可以使用它！只剩下一件重要的事情要做：添加一個數據卡這解釋了語料庫是如何創建的，併為使用數據集的其他提供一些其他有用的信息。

<Tip>

💡 您還可以使用一些 Git 魔法直接從終端將數據集上傳到 Hugging Face Hub。有關如何執行此操作的詳細信息，請參閱 [🤗 Datasets guide](https://huggingface.co/docs/datasets/share#share-a-dataset-using-the-cli) 指南。

</Tip>

## 創建數據集卡片

有據可查的數據集更有可能對其他人（包括你未來的自己！）有用，因為它們提供了上下文，使用戶能夠決定數據集是否與他們的任務相關，並評估任何潛在的偏見或與使用相關的風險。在 Hugging Face Hub 上，此信息存儲在每個數據集存儲庫的自述文件文件。在創建此文件之前，您應該執行兩個主要步驟：

1. 使用[數據集標籤應用程序](https://huggingface.co/datasets/tagging/) 創建YAML格式的元數據標籤。這些標籤用於各種各樣的搜索功能，並確保您的數據集可以很容易地被社區成員找到。因為我們已經在這裡創建了一個自定義數據集，所以您需要克隆數據集標籤存儲庫並在本地運行應用程序。它的界面是這樣的:

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter5/datasets-tagger.png" alt="The 'datasets-tagging' interface." width="80%"/>
</div>

2.閱讀[🤗 Datasets guide](https://github.com/huggingface/datasets/blob/master/templates/README_guide.md) 關於創建信息性數據集卡片的指南，並將其作為模板使用。

您可以創建自述文件文件直接在Hub上，你可以在裡面找到模板數據集卡片 **lewtun/github-issues** 數據集存儲庫。填寫好的數據集卡片的屏幕截圖如下所示。!

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter5/dataset-card.png" alt="A dataset card." width="80%"/>
</div>

<Tip>

✏️試試看！使用應用程序和 [🤗 Datasets guide](https://github.com/huggingface/datasets/blob/master/templates/README_guide.md) 指南來完成 GitHub issue數據集的 README.md 文件。

</Tip>

很好! 我們在本節中看到，創建一個好的數據集可能非常複雜，但幸運的是，將其上傳並與社區共享會很容易實現。在下一節中，我們將使用我們的新數據集創建一個帶有 🤗 Datasets的語義搜索引擎，該數據集可以將issue與最相關的issue和評論進行匹配。

<Tip>

✏️ 試試看！按照我們在本節中採取的步驟為您最喜歡的開源庫創建一個 GitHub issue數據集（當然，選擇 🤗 Datasets以外的其他東西！）。對於獎勵積分，微調多標籤分類器以預測該領域中存在的標籤。
</Tip>




<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter5/5.mdx" />

### 本章簡介
https://huggingface.co/learn/course/zh-TW/chapter5/1.md

# 本章簡介

<CourseFloatingBanner
    chapter={5}
    classNames="absolute z-10 right-0 top-0"
/>

在[第三章](/course/chapter3)第一次體驗了 🤗Datasets 庫，並發現在微調模型時有三個主要步驟：

1. 從 Hugging Face Hub 加載一個數據集。
2. 使用 Dataset.map() 對數據進行預處理。
3. 載入和計算指標(特徵)。

但這只是🤗 Datasets的表面功能而已！在本章中，我們將深入瞭解這個庫。在此過程中，我們將找到以下問題的答案：

* 當數據集不在 hub 上時，您該怎麼做？
* 如何對數據集進行切片？（如果你真正的特別需要使用pandas的時候該怎麼辦？）
* 當你的數據集很大，會撐爆你筆記本電腦的RAM時，你會怎麼做？
* 「內存映射」和 Apache Arrow 到底是什麼？
* 如何創建自己的數據集並將其推送到中心？

您在這裡學到的技術將為您在[第6章](/course/chapter6)和[第7章](/course/chapter7)中的高級標記化和微調任務做好準備——所以，喝杯咖啡，讓我們開始吧!

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter5/1.mdx" />

### 是時候來學一下切片了
https://huggingface.co/learn/course/zh-TW/chapter5/3.md

# 是時候來學一下切片了

<CourseFloatingBanner chapter={5}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/zh-CN/chapter5/section3.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/zh-CN/chapter5/section3.ipynb"},
]} />

大多數情況下，您使用的數據都需根據模型所要求的輸入進行清洗。在本節中，我們將探索 🤗 Datasets 提供的用於數據集清洗的各種功能。

<Youtube id="tqfSFcPMgOI"/>

## 切片與切分我們的數據

與 Pandas 類似，🤗 Datasets 提供了幾個函數來操作 **Dataset** 和 **DatasetDict** 對象。我們在[第三章](/course/chapter3)已經遇到了 **Dataset.map()** 方法，在本節中，我們將探索我們可以使用的其他功能。

對於這個例子，我們將使用託管在[加州大學歐文分校機器學習存儲庫](https://archive.ics.uci.edu/ml/index.php)的[藥物審查數據集](https://archive.ics.uci.edu/ml/datasets/Drug+Review+Dataset+%28Drugs.com%29)，其中包含患者對各種藥物的評論，以及正在治療的病情和患者滿意度的 10 星評級。

首先我們需要下載並提取數據，這可以通過 **wget** 和 **unzip** 命令：

```py
!wget "https://archive.ics.uci.edu/ml/machine-learning-databases/00462/drugsCom_raw.zip"
!unzip drugsCom_raw.zip
```

由於 TSV 只是使用製表符而不是逗號作為分隔符的 CSV 變體，我們可以使用加載**csv**文件的**load_dataset()**函數並指定分隔符 示例如下：

```py
from datasets import load_dataset

data_files = {"train": "drugsComTrain_raw.tsv", "test": "drugsComTest_raw.tsv"}
# \t is the tab character in Python
drug_dataset = load_dataset("csv", data_files=data_files, delimiter="\t")
```

在進行任何類型的數據分析時，一個好的做法是抽取一個小的隨機樣本，以快速瞭解您正在處理的數據類型。在🤗數據集中，我們可以通過鏈接 **Dataset.shuffle()** 和 **Dataset.select()** 共同來完成抽取：

```py
drug_sample = drug_dataset["train"].shuffle(seed=42).select(range(1000))
# Peek at the first few examples
drug_sample[:3]
```

```python out
{'Unnamed: 0': [87571, 178045, 80482],
 'drugName': ['Naproxen', 'Duloxetine', 'Mobic'],
 'condition': ['Gout, Acute', 'ibromyalgia', 'Inflammatory Conditions'],
 'review': ['"like the previous person mention, I&#039;m a strong believer of aleve, it works faster for my gout than the prescription meds I take. No more going to the doctor for refills.....Aleve works!"',
  '"I have taken Cymbalta for about a year and a half for fibromyalgia pain. It is great\r\nas a pain reducer and an anti-depressant, however, the side effects outweighed \r\nany benefit I got from it. I had trouble with restlessness, being tired constantly,\r\ndizziness, dry mouth, numbness and tingling in my feet, and horrible sweating. I am\r\nbeing weaned off of it now. Went from 60 mg to 30mg and now to 15 mg. I will be\r\noff completely in about a week. The fibro pain is coming back, but I would rather deal with it than the side effects."',
  '"I have been taking Mobic for over a year with no side effects other than an elevated blood pressure.  I had severe knee and ankle pain which completely went away after taking Mobic.  I attempted to stop the medication however pain returned after a few days."'],
 'rating': [9.0, 3.0, 10.0],
 'date': ['September 2, 2015', 'November 7, 2011', 'June 5, 2013'],
 'usefulCount': [36, 13, 128]}
```

請注意，出於可以復現的目的，我們已將在**Dataset.shuffle()**選取了固定的隨機數種子。 **Dataset.select()** 需要一個可迭代的索引，所以我們已經通過了 **range(1000)** 從隨機打亂的數據集中選取前 1,000 個示例。從抽取的數據中，我們已經可以看到我們數據集的一些特點：

* **Unnamed: 0**這列看起來很像每個患者的匿名 ID。
* **condition** 這列包含有描述健康狀況的標籤。
* 評論長短不一，混合有 Python 行分隔符 (**\r\n**) 以及 HTML 字符代碼，如** &\#039;**。
  
讓我們看看我們如何使用 🤗 Datasets 來處理這些問題。為了驗證**Unnamed: 0** 列存儲的是患者 ID的猜想，我們可以使用 **Dataset.unique()** 函數來驗證匿名ID 的數量是否與拆分後每部分中的行數匹配：

```py
for split in drug_dataset.keys():
    assert len(drug_dataset[split]) == len(drug_dataset[split].unique("Unnamed: 0"))
```

這似乎證實了我們的假設，所以讓我們把 **Unnamed: 0** 列重命名為患者的id。我們可以使用 **DatasetDict.rename_column()**函數一次性重命名DatasetDict中共有的列：

```py
drug_dataset = drug_dataset.rename_column(
    original_column_name="Unnamed: 0", new_column_name="patient_id"
)
drug_dataset
```

```python out
DatasetDict({
    train: Dataset({
        features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount'],
        num_rows: 161297
    })
    test: Dataset({
        features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount'],
        num_rows: 53766
    })
})
```

<Tip>

✏️ **試試看！** 使用 `Dataset.unique()` 函數查找訓練和測試集中滿足某個條件的藥物經過去重之後的數量。

</Tip>

接下來，讓我們使用 **Dataset.map()**標準化所有 **condition** 標籤 .正如我們在[第三章](/course/chapter3)中所做的那樣，我們可以定義一個簡單的函數，可以將該函數應用於**drug_dataset** 拆分後每部分的所有行：

```py
def lowercase_condition(example):
    return {"condition": example["condition"].lower()}


drug_dataset.map(lowercase_condition)
```

```python out
AttributeError: 'NoneType' object has no attribute 'lower'
```

哦不，我們的map功能遇到了問題！從錯誤中我們可以推斷出 **condition** 列存在 **None** , 不能轉換為小寫，因為它們不是字符串。讓我們使用 **Dataset.filter()** 刪除這些行 ，其工作方式類似於 **Dataset.map()** 。例如：

```py
def filter_nones(x):
    return x["condition"] is not None
```

然後運行 **drug_dataset.filter(filter_nones)** ，我們可以在一行中使用lambda 函數.在 Python 中，lambda 函數是您無需明確命名即可使用的微函數（匿名函數）。它們一般採用如下形式：

```
lambda <arguments> : <expression>
```

其中**lambda** 是 Python 的特殊[關鍵字](https://docs.python.org/3/reference/lexical_analysis.html#keywords), **arguments** 是以逗號進行分隔的函數輸入的列表/集合， **expression** 代表您希望執行的操作。例如，我們可以定義一個簡單的 lambda 函數來對一個數字進行平方，如下所示：

```
lambda x : x * x
```

我們需要將要輸入給這個函數值括在括號中：

```py
(lambda x: x * x)(3)
```

```python out
9
```

類似地，我們可以通過用逗號分隔多個參數來定義 lambda 函數。例如，我們可以按如下方式計算三角形的面積：

```py
(lambda base, height: 0.5 * base * height)(4, 8)
```

```python out
16.0
```

當您想定義小型、一次性使用的函數時，Lambda 函數非常方便（有關它們的更多信息，我們建議閱讀安德烈·布爾高寫的[真正的Python教程](https://realpython.com/python-lambda/)）。在🤗 Datasets 中，我們可以使用 lambda 函數來定義簡單的映射和過濾操作，所以讓我們使用這個技巧來消除我們數據集中的 **None** 條目：

```py
drug_dataset = drug_dataset.filter(lambda x: x["condition"] is not None)
```

當 **None** 條目已刪除，我們可以標準化我們的 **condition** 列：

```py
drug_dataset = drug_dataset.map(lowercase_condition)
# Check that lowercasing worked
drug_dataset["train"]["condition"][:3]
```

```python out
['left ventricular dysfunction', 'adhd', 'birth control']
```

有用！現在我們已經清理了標籤，讓我們來看看清洗後的評論文本。

## Creating new columns

每當您處理客戶評論時，一個好的做法是檢查每個評論中的字數。評論可能只是一個詞，比如“太棒了！”或包含數千字的完整文章，根據實際的情況，您需要以不同的方式處理這些極端情況。為了計算每條評論中的單詞數，我們將使用基於空格分割每個文本的粗略方法。

讓我們定義一個簡單的函數來計算每條評論中的單詞數：

```py
def compute_review_length(example):
    return {"review_length": len(example["review"].split())}
```

與我們的 `lowercase_condition()` 函數不同，`compute_review_length()` 返回一個字典，其鍵與數據集中的列名之一不對應。 在這種情況下，當 `compute_review_length()` 傳遞給 `Dataset.map()` 時，它將應用於數據集中的所有行以創建新的 `review_length` 列：

```py
drug_dataset = drug_dataset.map(compute_review_length)
# Inspect the first training example
drug_dataset["train"][0]
```

```python out
{'patient_id': 206461,
 'drugName': 'Valsartan',
 'condition': 'left ventricular dysfunction',
 'review': '"It has no side effect, I take it in combination of Bystolic 5 Mg and Fish Oil"',
 'rating': 9.0,
 'date': 'May 20, 2012',
 'usefulCount': 27,
 'review_length': 17}
```

正如預期的那樣，我們可以看到一個 **review_length** 列已添加到我們的訓練集中。我們可以使用 **Dataset.sort()**對這個新列進行排序，然後查看極端長度的評論的樣子：

```py
drug_dataset["train"].sort("review_length")[:3]
```

```python out
{'patient_id': [103488, 23627, 20558],
 'drugName': ['Loestrin 21 1 / 20', 'Chlorzoxazone', 'Nucynta'],
 'condition': ['birth control', 'muscle spasm', 'pain'],
 'review': ['"Excellent."', '"useless"', '"ok"'],
 'rating': [10.0, 1.0, 6.0],
 'date': ['November 4, 2008', 'March 24, 2017', 'August 20, 2016'],
 'usefulCount': [5, 2, 10],
 'review_length': [1, 1, 1]}
```

正如我們所猜想的那樣，一些評論只包含一個詞，雖然這對於情感分析來說可能沒問題，但如果我們想要預測病情，這些評論可能並不適合。

<Tip>

🙋向數據集添加新列的另一種方法是使用函數Dataset.add_column() 。這允許您輸入Python 列表或 NumPy，在不適合使用Dataset.map()情況下可以很方便。

</Tip>

讓我們使用 **Dataset.filter()** 功能來刪除包含少於 30 個單詞的評論。與我們對 **condition** 列的處理相似，我們可以通過選取評論的長度高於此閾值來過濾掉非常短的評論：

```py
drug_dataset = drug_dataset.filter(lambda x: x["review_length"] > 30)
print(drug_dataset.num_rows)
```

```python out
{'train': 138514, 'test': 46108}
```

如您所見，這已經從我們的原始訓練和測試集中刪除了大約 15% 的評論。

<Tip>

✏️ 試試看！使用 Dataset.sort() 函數查看單詞數最多的評論。請參閱文檔以瞭解您需要使用哪個參數按長度降序對評論進行排序。

</Tip>

我們需要處理的最後一件事是評論中是否存在 HTML 字符代碼。我們可以使用 Python 的**html**模塊取消這些字符的轉義，如下所示：

```py
import html

text = "I&#039;m a transformer called BERT"
html.unescape(text)
```

```python out
"I'm a transformer called BERT"
```

我們將使用 **Dataset.map()** 對我們語料庫中的所有 HTML 字符進行轉義：

```python
drug_dataset = drug_dataset.map(lambda x: {"review": html.unescape(x["review"])})
```

如您所見， **Dataset.map()** 方法對於處理數據非常有用——在示例中僅僅是淺嘗輒止就有很大的收穫！

## map() 方法的超級加速

**Dataset.map()** 方法有一個 **batched** 參數，如果設置為 **True** , map 函數將會分批執行所需要進行的操作（批量大小是可配置的，但默認為 1,000）。例如，之前對所有 HTML 進行轉義的 map 函數運行需要一些時間（您可以從進度條中讀取所用時間）。我們可以通過使用列表推導同時處理多個元素來加快速度。

當您在使用 **Dataset.map()**函數時指定 **batched=True**。該函數會接收一個包含數據集字段的字典，每個值都是一個列表，而不僅僅是單個值。**Dataset.map()** 的返回值應該是相同的：一個包含我們想要更新或添加到數據集中的字段的字典，字典的鍵是要添加的字段，字典的值是結果的列表。例如，這是使用 **batched=True**對所有 HTML 字符進行轉義的方法 ：

```python
new_drug_dataset = drug_dataset.map(
    lambda x: {"review": [html.unescape(o) for o in x["review"]]}, batched=True
)
```

如果您在筆記本中運行此代碼，您會看到此命令的執行速度比前一個命令快得多。這不是因為我們的評論已經是處理過的——如果你重新執行上一節的指令（沒有 **batched=True** )，它將花費與以前相同的時間。這是因為列表推導式通常比在同一代碼中用 **for** 循環執行相同的代碼更快，並且我們還通過同時訪問多個元素而不是一個一個來處理來提高處理的速度。

在[第六章](/course/chapter6)我們將遇到的“快速”標記器，它可以快速標記大文本列表。使用 **Dataset.map()** 和 **batched=True** 是加速的關鍵。例如，要使用快速標記器標記所有藥物評論，我們可以使用這樣的函數：

```python
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")


def tokenize_function(examples):
    return tokenizer(examples["review"], truncation=True)
```

正如你在[第三章](/course/chapter3)所看到的，我們原本就可以將一個或多個示例傳遞給分詞器，因此在**batched=True**是一個非必須的選項.讓我們藉此機會比較不同選項的性能。在筆記本中，您可以在您要測量的代碼行之前添加 **%time**來測試改行運行所消耗的時間：

```python no-format
%time tokenized_dataset = drug_dataset.map(tokenize_function, batched=True)
```

您還可以通過將整個單元格計時 **%%time** 在單元格的開頭。在我們執行此操作的硬件上，該指令顯示 10.8 秒（這是寫在“Wall time”之後的數字）。

<Tip>

✏️ **試試看！** 使用和不使用 `batched=True` 執行相同的指令，然後使用慢速標記器嘗試（在 `AutoTokenizer.from_pretrained()` 方法中添加 `use_fast=False`），這樣你就可以看看在你的電腦上它需要多長的時間。

</Tip>

以下是我們在使用和不使用批處理時使用快速和慢速分詞器獲得的結果：

Options         | Fast tokenizer | Slow tokenizer
:--------------:|:--------------:|:-------------:
`batched=True`  | 10.8s          | 4min41s
`batched=False` | 59.2s          | 5min3s

這意味著使用帶有 **batched=True** 選項比沒有批處理的慢選項快 30 倍——這真是太棒了！這就是為什麼**AutoTokenizer** 的默認設置是**use_fast=True**的主要原因 （以及為什麼它們被稱為“快速”）。他們能夠實現這樣的加速，因為在底層的標記化代碼是在 Rust 中執行的，Rust 是一種可以輕鬆並行化執行的語言。

並行化也是快速標記器通過批處理實現近 6 倍加速的原因：單個標記化操作是不能並行的，但是當您想同時標記大量文本時，您可以將執行拆分為多個進程，每個進程都對自己的文本負責。

**Dataset.map()** 也有一些自己的並行化能力。由於它們不受 Rust 的支持，因此慢速分詞器的速度趕不上快速分詞器，但它們仍然會更快一些（尤其是當您使用沒有快速版本的分詞器時）。要啟用多處理，請在**Dataset.map()**時使用 **num_proc** 參數並指定要在調用中使用的進程數 ：

```py
slow_tokenizer = AutoTokenizer.from_pretrained("bert-base-cased", use_fast=False)


def slow_tokenize_function(examples):
    return slow_tokenizer(examples["review"], truncation=True)


tokenized_dataset = drug_dataset.map(slow_tokenize_function, batched=True, num_proc=8)
```

您可以對處理的時間進行一些試驗，以確定要使用的最佳進程數；在我們的例子中，8 似乎產生了最好的速度增益。以下是我們在使用和不使用多處理時所需要的時間：

Options         | Fast tokenizer | Slow tokenizer
:--------------:|:--------------:|:-------------:
`batched=True`  | 10.8s          | 4min41s
`batched=False` | 59.2s          | 5min3s
`batched=True`, `num_proc=8`  | 6.52s          | 41.3s
`batched=False`, `num_proc=8` | 9.49s          | 45.2s

對於慢速分詞器來說，這些結果要合理得多，但快速分詞器的性能也得到了顯著提高。但是請注意，情況並非總是如此——除了 **num_proc=8**，我們的測試表明，使用**batched=True**而不帶有**num_proc**參數的選項處理起來更快。通常，我們不建議將 Python 多線程處理用於具有**batched=True**功能的快速標記器  .

<Tip>

使用num_proc以加快處理速度通常是一個好主意，只要您使用的函數還沒有自己帶有的進行某種多進程處理的方法。

</Tip>

將所有這些功能濃縮到一個方法中已經非常了不起，但還有更多！使用 **Dataset.map()** 和 **batched=True** 您可以更改數據集中的元素數量。當你想從一個例子中創建幾個訓練特徵時，這是非常有用的。我們將在[第七章](/course/chapter7).中進行的幾個NLP任務的預處理中使用到這個功能，它非常便利。

<Tip>

💡在機器學習中，一個例子通常可以為我們的模型提供一組特徵。在某些情況下，這些特徵會儲存在數據集的幾個列，但在其他情況下（例如此處的例子和用於問答的數據），可以從單個示例的一列中提取多個特徵

</Tip>

讓我們來看看它是如何工作的！在這裡，我們將標記化我們的示例並將最大截斷長度設置128，但我們將要求標記器返回全部文本塊，而不僅僅是第一個。這可以用 **return_overflowing_tokens=True** ：

```py
def tokenize_and_split(examples):
    return tokenizer(
        examples["review"],
        truncation=True,
        max_length=128,
        return_overflowing_tokens=True,
    )
```

在使用**Dataset.map()** 正式在整個數據集上開始處理之前讓我們先在一個例子上測試一下：

```py
result = tokenize_and_split(drug_dataset["train"][0])
[len(inp) for inp in result["input_ids"]]
```

```python out
[128, 49]
```

瞧！我們在訓練集中的第一個示例變成了兩個特徵，因為它被標記為超過我們指定的最大截斷長度，因此結果被截成了兩段：第一段長度為 128 ，第二段長度為 49 。現在讓我們對所有元素執行此操作數據集！

```py
tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True)
```

```python out
ArrowInvalid: Column 1 named condition expected length 1463 but got length 1000
```

不好了！它沒有起作用！為什麼呢？查看錯誤消息會給我們一個線索：列的長度不匹配，一列長度為 1,463，另一列長度為 1,000。1,000行的"review"給出了 1,463 行的新特徵，導致和原本的1000行數據不匹配。

問題出在我們試圖混合兩個不同大小的不同數據集： **drug_dataset** 列將有一定數量的元素（我們錯誤中的 1,000），但是我們正在構建**tokenized_dataset** 將有更多的元素（錯誤消息中的 1,463）。這不適用於 **Dataset** ，因此我們需要從舊數據集中刪除列或使它們的大小與新數據集中的大小相同。我們可以用 **remove_columns** 參數：

```py
tokenized_dataset = drug_dataset.map(
    tokenize_and_split, batched=True, remove_columns=drug_dataset["train"].column_names
)
```

現在這個過程沒有錯誤。我們可以通過比較長度來檢查新數據集的元素是否比原始數據集多得多：

```py
len(tokenized_dataset["train"]), len(drug_dataset["train"])
```

```python out
(206772, 138514)
```

我們提到我們還可以通過使舊列與新列的大小相同來處理長度不匹配的問題。為此，我們可以使用 **overflow_to_sample_mapping** 字段，當我們設置**return_overflowing_tokens=True** .它為我們提供了特徵到它所產生的樣本的映射。使用這個，我們可以將原始數據集中的每個鍵關聯到一個合適大小的值列表中，通過遍歷所有的數據來生成新特性:

```py
def tokenize_and_split(examples):
    result = tokenizer(
        examples["review"],
        truncation=True,
        max_length=128,
        return_overflowing_tokens=True,
    )
    # Extract mapping between new and old indices
    sample_map = result.pop("overflow_to_sample_mapping")
    for key, values in examples.items():
        result[key] = [values[i] for i in sample_map]
    return result
```

我們可以使用**Dataset.map()**來進行批處理，這樣無需我們刪除舊列：

```py
tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True)
tokenized_dataset
```

```python out
DatasetDict({
    train: Dataset({
        features: ['attention_mask', 'condition', 'date', 'drugName', 'input_ids', 'patient_id', 'rating', 'review', 'review_length', 'token_type_ids', 'usefulCount'],
        num_rows: 206772
    })
    test: Dataset({
        features: ['attention_mask', 'condition', 'date', 'drugName', 'input_ids', 'patient_id', 'rating', 'review', 'review_length', 'token_type_ids', 'usefulCount'],
        num_rows: 68876
    })
})
```

我們獲得了與以前相同數量的訓練特徵，但在這裡我們保留了所有舊字段。如果您在使用模型計算之後需要它們進行一些後處理，您可能需要使用這種方法。

您現在已經瞭解了 🤗 Datasets如何以各種方式用於預處理數據集。雖然🤗 Datasets 的處理功能會覆蓋你大部分的模型訓練需求，有時您可能需要切換到 Pandas 以使用更強大的功能，例如 **DataFrame.groupby()** 或用於可視化的高級 API。幸運的是，🤗 Datasets旨在與 Pandas、NumPy、PyTorch、TensorFlow 和 JAX 等庫可以相互轉換。讓我們來看看這是如何工作的。

## `🤗 Datasets 和 DataFrames 的相互轉換

<Youtube id="tfcY1067A5Q"/>

為了實現各種第三方庫之間的轉換，🤗 Datasets 提供了一個 **Dataset.set_format()** 功能。此功能可以通過僅更改輸出格式的，輕鬆切換到另一種格式，而不會影響底層數據格式，即 Apache Arrow。格式化會在數據本身上進行。為了演示，讓我們將數據集轉換為 Pandas：

```py
drug_dataset.set_format("pandas")
```

現在，當我們訪問數據集的元素時，我們會得到一個 **pandas.DataFrame** 而不是字典：

```py
drug_dataset["train"][:3]
```

<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>patient_id</th>
      <th>drugName</th>
      <th>condition</th>
      <th>review</th>
      <th>rating</th>
      <th>date</th>
      <th>usefulCount</th>
      <th>review_length</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>95260</td>
      <td>Guanfacine</td>
      <td>adhd</td>
      <td>"My son is halfway through his fourth week of Intuniv..."</td>
      <td>8.0</td>
      <td>April 27, 2010</td>
      <td>192</td>
      <td>141</td>
    </tr>
    <tr>
      <th>1</th>
      <td>92703</td>
      <td>Lybrel</td>
      <td>birth control</td>
      <td>"I used to take another oral contraceptive, which had 21 pill cycle, and was very happy- very light periods, max 5 days, no other side effects..."</td>
      <td>5.0</td>
      <td>December 14, 2009</td>
      <td>17</td>
      <td>134</td>
    </tr>
    <tr>
      <th>2</th>
      <td>138000</td>
      <td>Ortho Evra</td>
      <td>birth control</td>
      <td>"This is my first time using any form of birth control..."</td>
      <td>8.0</td>
      <td>November 3, 2015</td>
      <td>10</td>
      <td>89</td>
    </tr>
  </tbody>
</table>

讓我們創建一個 **pandas.DataFrame** 來選擇 **drug_dataset[train]** 的所有元素：

```py
train_df = drug_dataset["train"][:]
```

<Tip>

🚨 在底層，`Dataset.set_format()` 改變了數據集的 `__getitem__()` dunder 方法的返回格式。 這意味著當我們想從 `"pandas"` 格式的 `Dataset` 中創建像 `train_df` 這樣的新對象時，我們需要對整個數據集進行切片以獲得 `pandas.DataFrame`。 無論輸出格式如何，您都可以自己驗證 `drug_dataset["train"]` 的類型依然還是 `Dataset`。

</Tip>


從這裡我們可以使用我們想要的所有 Pandas 功能。例如，我們可以通過花式鏈接來計算 **condition**類之間的分佈 ：

```py
frequencies = (
    train_df["condition"]
    .value_counts()
    .to_frame()
    .reset_index()
    .rename(columns={"index": "condition", "count": "frequency"})
)
frequencies.head()
```

<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>condition</th>
      <th>frequency</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>birth control</td>
      <td>27655</td>
    </tr>
    <tr>
      <th>1</th>
      <td>depression</td>
      <td>8023</td>
    </tr>
    <tr>
      <th>2</th>
      <td>acne</td>
      <td>5209</td>
    </tr>
    <tr>
      <th>3</th>
      <td>anxiety</td>
      <td>4991</td>
    </tr>
    <tr>
      <th>4</th>
      <td>pain</td>
      <td>4744</td>
    </tr>
  </tbody>
</table>


一旦我們完成了 Pandas 分析，我們總是通過使用對象 **Dataset.from_pandas()**方法可以創建一個新的 **Dataset** 如下：


```py
from datasets import Dataset

freq_dataset = Dataset.from_pandas(frequencies)
freq_dataset
```

```python out
Dataset({
    features: ['condition', 'frequency'],
    num_rows: 819
})
```

<Tip>

✏️ **試試看！** 計算每種藥物的平均評級並將結果存儲在一個新的Dataset.

</Tip>

我們對 🤗 Datasets中可用的各種預處理技術的介紹到此結束。在最後一部分，讓我們創建一個驗證集來準備用於訓練分類器的數據集。在此之前，我們將輸出格式 **drug_dataset** 從 **pandas**重置到 **arrow** ：

```python
drug_dataset.reset_format()
```

## 創建驗證集

儘管我們有一個可以用於評估的測試集，但在開發過程中保持測試集不變並創建一個單獨的驗證集是一個很好的做法。一旦您對模型在測試集上的表現感到滿意，您就可以對驗證集進行最終的檢查。此過程有助於降低您過擬合測試集並部署在現實世界數據上失敗的模型的風險。

🤗 Datasets提供了一個基於**scikit-learn**的經典方法**Dataset.train_test_split()** .讓我們用它把我們的訓練集分成 **train** 和 **validation** （為了可以復現，我們將設置**seed**的值為一個常量）：

```py
drug_dataset_clean = drug_dataset["train"].train_test_split(train_size=0.8, seed=42)
# Rename the default "test" split to "validation"
drug_dataset_clean["validation"] = drug_dataset_clean.pop("test")
# Add the "test" set to our `DatasetDict`
drug_dataset_clean["test"] = drug_dataset["test"]
drug_dataset_clean
```

```python out
DatasetDict({
    train: Dataset({
        features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length', 'review_clean'],
        num_rows: 110811
    })
    validation: Dataset({
        features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length', 'review_clean'],
        num_rows: 27703
    })
    test: Dataset({
        features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length', 'review_clean'],
        num_rows: 46108
    })
})
```

太好了，我們現在已經準備好了一個數據集，可以用來訓練一些模型了！在[第五節]](/course/chapter5/5)我們將向您展示如何將數據集上傳到 Hugging Face Hub，但現在讓我們查看在本地計算機上保存數據集的幾種方法。

## 保存數據集

<Youtube id="blF9uxYcKHo"/>

雖然 🤗 Datasets 會緩存每個下載的數據集和對它執行的操作，但有時你會想要將數據集保存到磁盤（例如，以防緩存被刪除）。如下表所示，🤗 Datasets 提供了三個主要功能來以不同的格式保存您的數據集：

| 數據格式    |        對應的方法        |
| :---------: | :--------------------: |
|    Arrow    | `Dataset.save_to_disk()` |
|     CSV     |    `Dataset.to_csv()`    |
|    JSON     |   `Dataset.to_json()`    |

例如，讓我們以 Arrow 格式保存我們清洗過的數據集：

```py
drug_dataset_clean.save_to_disk("drug-reviews")
```

這將創建一個具有以下結構的目錄：

```
drug-reviews/
├── dataset_dict.json
├── test
│   ├── dataset.arrow
│   ├── dataset_info.json
│   └── state.json
├── train
│   ├── dataset.arrow
│   ├── dataset_info.json
│   ├── indices.arrow
│   └── state.json
└── validation
    ├── dataset.arrow
    ├── dataset_info.json
    ├── indices.arrow
    └── state.json
```

在那裡我們可以看到每個部分.arrow表，以及一些元數據數據集信息.json和狀態文件保存在一起.您可以將 Arrow 格式視為一個精美的列和行的表格，它針對構建處理和傳輸大型數據集的高性能應用程序進行了優化。

保存數據集後，我們可以使用 **load_from_disk()** 功能從磁盤讀取數據如下：

```py
from datasets import load_from_disk

drug_dataset_reloaded = load_from_disk("drug-reviews")
drug_dataset_reloaded
```

```python out
DatasetDict({
    train: Dataset({
        features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length'],
        num_rows: 110811
    })
    validation: Dataset({
        features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length'],
        num_rows: 27703
    })
    test: Dataset({
        features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length'],
        num_rows: 46108
    })
})
```

對於 CSV 和 JSON 格式，我們必須將每個部分存儲為單獨的文件。一種方法是迭代**DatasetDict**中的鍵和值 ：

```py
for split, dataset in drug_dataset_clean.items():
    dataset.to_json(f"drug-reviews-{split}.jsonl")
```

這將保存每個拆分都是[JSON的標準格式](https://jsonlines.org)，其中數據集中的每一行都存儲為一行 JSON。這是第一個示例：

```py
!head -n 1 drug-reviews-train.jsonl
```

```python out
{"patient_id":141780,"drugName":"Escitalopram","condition":"depression","review":"\"I seemed to experience the regular side effects of LEXAPRO, insomnia, low sex drive, sleepiness during the day. I am taking it at night because my doctor said if it made me tired to take it at night. I assumed it would and started out taking it at night. Strange dreams, some pleasant. I was diagnosed with fibromyalgia. Seems to be helping with the pain. Have had anxiety and depression in my family, and have tried quite a few other medications that haven't worked. Only have been on it for two weeks but feel more positive in my mind, want to accomplish more in my life. Hopefully the side effects will dwindle away, worth it to stick with it from hearing others responses. Great medication.\"","rating":9.0,"date":"May 29, 2011","usefulCount":10,"review_length":125}
```

然後我們可以使用[第二節](/course/chapter5/2)學過的技術加載 JSON 文件如下：

```py
data_files = {
    "train": "drug-reviews-train.jsonl",
    "validation": "drug-reviews-validation.jsonl",
    "test": "drug-reviews-test.jsonl",
}
drug_dataset_reloaded = load_dataset("json", data_files=data_files)
```

這就是我們探索 🤗 Datasets 的旅程！現在我們有了一個清洗過的數據集，以下是您可以嘗試的一些想法：

1. 使用[第3章](/course/chapter3)的技術來訓練一個分類器，它可以根據藥物評論預測病人的情況。
2. 使用 [Chapter 1](/course/chapter1) 中的“summarization”管道生成評論摘要。

接下來，我們將看看 🤗 Datasets如何使您能夠在不撐爆筆記本電腦內存的情況下處理龐大的數據集！

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter5/3.mdx" />

### 使用 FAISS 進行語義搜索
https://huggingface.co/learn/course/zh-TW/chapter5/6.md

# 使用 FAISS 進行語義搜索

{#if fw === 'pt'}

<CourseFloatingBanner chapter={5}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/zh-CN/chapter5/section6_pt.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/zh-CN/chapter5/section6_pt.ipynb"},
]} />

{:else}

<CourseFloatingBanner chapter={5}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/zh-CN/chapter5/section6_tf.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/zh-CN/chapter5/section6_tf.ipynb"},
]} />

{/if}

在[第五小節](/course/chapter5/5), 我們從 🤗 Datasets 存儲庫創建了一個包含 GitHub 問題和評論的數據集。在本節中，我們將使用這些信息來構建一個搜索引擎，它可以幫助我們找到這個庫最緊迫問題的答案！

<Youtube id="OATCgQtNX2o"/>

## 使用嵌入進行語義搜索

正如我們在[第一章](/course/chapter1),學習的, 基於 Transformer 的語言模型會將文本中的每個標記轉換為嵌入向量.事實證明，可以“彙集”各個嵌入向量來創建整個句子、段落或文檔（在某些情況下）的向量表示。然後，通過計算每個嵌入之間的點積相似度（或其他一些相似度度量）並返回相似度最大的文檔，這些嵌入可用於在語料庫中找到相似的文檔。在本節中，我們將使用嵌入來開發語義搜索引擎。與基於將查詢中的關鍵字的傳統方法相比，這些搜索引擎具有多種優勢。

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter5/semantic-search.svg" alt="Semantic search."/>
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter5/semantic-search-dark.svg" alt="Semantic search."/>
</div>

## ## 加載和準備數據集

我們需要做的第一件事是下載我們的 GitHub 問題數據集，所以讓我們使用 🤗 Hub 庫來解析我們的文件在 Hugging Face Hub 上存儲的數據：

```py
from huggingface_hub import hf_hub_url

data_files = hf_hub_url(
    repo_id="lewtun/github-issues",
    filename="datasets-issues-with-comments.jsonl",
    repo_type="dataset",
)
```

將 URL 存儲在 **data_files** ，然後我們可以使用[第二小節](/course/chapter5/2)介紹的方法加載遠程數據集:

```py
from datasets import load_dataset

issues_dataset = load_dataset("json", data_files=data_files, split="train")
issues_dataset
```

```python out
Dataset({
    features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'performed_via_github_app', 'is_pull_request'],
    num_rows: 2855
})
```

這裡我們在load_dataset()中使用了默認的訓練集分割，所以它返回一個數據集而不是數據集字典。第一項任務是過濾掉pull請求，因為這些請求很少用於回答用戶提出的問題，而且會給我們的搜索引擎帶來噪聲。現在應該很熟悉了，我們可以使用dataset.filter()函數來排除數據集中的這些行。同時，讓我們也過濾掉沒有註釋的行，因為這些行不會是用戶提問的答案:

```py
issues_dataset = issues_dataset.filter(
    lambda x: (x["is_pull_request"] == False and len(x["comments"]) > 0)
)
issues_dataset
```

```python out
Dataset({
    features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'performed_via_github_app', 'is_pull_request'],
    num_rows: 771
})
```

我們可以看到我們的數據集中有很多列，其中大部分我們不需要構建我們的搜索引擎。從搜索的角度來看，信息量最大的列是 **title** , **body** ， 和 **comments** ，而 **html_url** 為我們提供了一個回到源問題的鏈接。讓我們使用 **Dataset.remove_columns()** 刪除其餘部分的功能：

```py
columns = issues_dataset.column_names
columns_to_keep = ["title", "body", "html_url", "comments"]
columns_to_remove = set(columns_to_keep).symmetric_difference(columns)
issues_dataset = issues_dataset.remove_columns(columns_to_remove)
issues_dataset
```

```python out
Dataset({
    features: ['html_url', 'title', 'comments', 'body'],
    num_rows: 771
})
```

為了創建我們的嵌入，我們將用問題的標題和正文來擴充每條評論，因為這些字段通常包含有用的上下文信息。因為我們的 **comments** 列當前是每個問題的評論列表，我們需要“重新組合”列，以便每一條評論都包含一個 **(html_url, title, body, comment)** 元組。在 Pandas 中，我們可以使用 [DataFrame.explode() 函數](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.explode.html), 它為類似列表的列中的每個元素創建一個新行，同時複製所有其他列值。為了看到它的實際效果，讓我們首先切換到 Pandas的**DataFrame** 格式：

```py
issues_dataset.set_format("pandas")
df = issues_dataset[:]
```

如果我們檢查這裡的第一行 **DataFrame** 我們可以看到有四個評論與這個問題相關：

```py
df["comments"][0].tolist()
```

```python out
['the bug code locate in ：\r\n    if data_args.task_name is not None:\r\n        # Downloading and loading a dataset from the hub.\r\n        datasets = load_dataset("glue", data_args.task_name, cache_dir=model_args.cache_dir)',
 'Hi @jinec,\r\n\r\nFrom time to time we get this kind of `ConnectionError` coming from the github.com website: https://raw.githubusercontent.com\r\n\r\nNormally, it should work if you wait a little and then retry.\r\n\r\nCould you please confirm if the problem persists?',
 'cannot connect，even by Web browser，please check that  there is some  problems。',
 'I can access https://raw.githubusercontent.com/huggingface/datasets/1.7.0/datasets/glue/glue.py without problem...']
```

我們希望這些評論中的每一條都得到一行。讓我們檢查是否是這種情況：

```py
comments_df = df.explode("comments", ignore_index=True)
comments_df.head(4)
```

<table border="1" class="dataframe" style="table-layout: fixed; word-wrap:break-word; width: 100%;">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>html_url</th>
      <th>title</th>
      <th>comments</th>
      <th>body</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>https://github.com/huggingface/datasets/issues/2787</td>
      <td>ConnectionError: Couldn't reach https://raw.githubusercontent.com</td>
      <td>the bug code locate in ：\r\n    if data_args.task_name is not None...</td>
      <td>Hello,\r\nI am trying to run run_glue.py and it gives me this error...</td>
    </tr>
    <tr>
      <th>1</th>
      <td>https://github.com/huggingface/datasets/issues/2787</td>
      <td>ConnectionError: Couldn't reach https://raw.githubusercontent.com</td>
      <td>Hi @jinec,\r\n\r\nFrom time to time we get this kind of `ConnectionError` coming from the github.com website: https://raw.githubusercontent.com...</td>
      <td>Hello,\r\nI am trying to run run_glue.py and it gives me this error...</td>
    </tr>
    <tr>
      <th>2</th>
      <td>https://github.com/huggingface/datasets/issues/2787</td>
      <td>ConnectionError: Couldn't reach https://raw.githubusercontent.com</td>
      <td>cannot connect，even by Web browser，please check that  there is some  problems。</td>
      <td>Hello,\r\nI am trying to run run_glue.py and it gives me this error...</td>
    </tr>
    <tr>
      <th>3</th>
      <td>https://github.com/huggingface/datasets/issues/2787</td>
      <td>ConnectionError: Couldn't reach https://raw.githubusercontent.com</td>
      <td>I can access https://raw.githubusercontent.com/huggingface/datasets/1.7.0/datasets/glue/glue.py without problem...</td>
      <td>Hello,\r\nI am trying to run run_glue.py and it gives me this error...</td>
    </tr>
  </tbody>
</table>

太好了，我們可以看到評論成功被擴充， **comments** 是包含個人評論的列！現在我們已經完成了 Pandas要完成的部分功能，我們可以快速切換回 **Dataset** 通過加載 **DataFrame** 在內存中：

```py
from datasets import Dataset

comments_dataset = Dataset.from_pandas(comments_df)
comments_dataset
```

```python out
Dataset({
    features: ['html_url', 'title', 'comments', 'body'],
    num_rows: 2842
})
```

太好了，我們獲取到了幾千條的評論！


<Tip>

✏️ **試試看！** 看看能不能不用pandas就可以完成列的擴充； 這有點棘手； 你可能會發現 🤗 Datasets 文檔的 ["Batch mapping"](https://huggingface.co/docs/datasets/about_map_batch#batch-mapping) 對這個任務很有用。

</Tip>

現在我們每行有一個評論，讓我們創建一個新的 **comments_length** 列來存放每條評論的字數：

```py
comments_dataset = comments_dataset.map(
    lambda x: {"comment_length": len(x["comments"].split())}
)
```

我們可以使用這個新列來過濾掉簡短的評論，其中通常包括“cc @lewtun”或“謝謝！”之類與我們的搜索引擎無關的內容。雖然無法為過濾器選擇的精確數字，但大約大於15 個單詞似乎是一個不錯的選擇：

```py
comments_dataset = comments_dataset.filter(lambda x: x["comment_length"] > 15)
comments_dataset
```

```python out
Dataset({
    features: ['html_url', 'title', 'comments', 'body', 'comment_length'],
    num_rows: 2098
})
```

稍微清理了我們的數據集後，讓我們將問題標題、描述和評論連接到一個新的 **text** 列。像往常一樣，我們可以編寫一個簡單的函數，並將其傳遞給 **Dataset.map()**來做到這些 ：

```py
def concatenate_text(examples):
    return {
        "text": examples["title"]
        + " \n "
        + examples["body"]
        + " \n "
        + examples["comments"]
    }


comments_dataset = comments_dataset.map(concatenate_text)
```

我們終於準備好創建一些嵌入了！讓我們來看看。

## 創建文本嵌入

我們在[第二章](/course/chapter2) 學過，我們可以通過使用 **AutoModel** 類來完成詞嵌入。我們需要做的就是選擇一個合適的檢查點來加載模型。幸運的是，有一個名為 **sentence-transformers** 專門用於創建詞嵌入。如庫中[文檔](https://www.sbert.net/examples/applications/semantic-search/README.html#symmetric-vs-asymmetric-semantic-search), 所述的，我們這次要實現的是非對稱語義搜索，因為我們有一個簡短的查詢，我們希望在比如問題評論等更長的文檔中找到其答案。通過查看[模型概述表](https://www.sbert.net/docs/pretrained_models.html#model-overview) 我們可以發現 **multi-qa-mpnet-base-dot-v1** 檢查點在語義搜索方面具有最佳性能，因此我們將在我們的應用程序中使用它。我們還將使用相同的檢查點加載標記器：

{#if fw === 'pt'}

```py
from transformers import AutoTokenizer, AutoModel

model_ckpt = "sentence-transformers/multi-qa-mpnet-base-dot-v1"
tokenizer = AutoTokenizer.from_pretrained(model_ckpt)
model = AutoModel.from_pretrained(model_ckpt)
```

為了加快嵌入過程，將模型和輸入放在 GPU 設備上，所以現在讓我們這樣做：

```py
import torch

device = torch.device("cuda")
model.to(device)
```

{:else}

```py
from transformers import AutoTokenizer, TFAutoModel

model_ckpt = "sentence-transformers/multi-qa-mpnet-base-dot-v1"
tokenizer = AutoTokenizer.from_pretrained(model_ckpt)
model = TFAutoModel.from_pretrained(model_ckpt, from_pt=True)
```

請注意，我們已將 from_pt=True 設置為  from_pretrained（） 方法的參數。這是因為 multi-qa-mpnet-base-dot-v1 檢查點只有PyTorch權重，因此設置 from_pt=True 會自動將它們轉換為TensorFlow格式。如您所見，在Transformers中的🤗框架之間切換非常簡單！

{/if}

正如我們之前提到的，我們希望將 GitHub 問題語料庫中的每個條目表示為單個向量，因此我們需要以某種方式“池化”或平均化我們的標記嵌入。一種流行的方法是在我們模型的輸出上執行CLS 池化，我們只獲取**[CLS]** 令牌的最後一個隱藏狀態。以下函數為我們提供了這樣的方法：

```py
def cls_pooling(model_output):
    return model_output.last_hidden_state[:, 0]
```

接下來，我們將創建一個輔助函數，該函數將標記文檔列表，將tensor放在 GPU 上，然後提供給模型，最後對輸出使用CLS 池化：

{#if fw === 'pt'}

```py
def get_embeddings(text_list):
    encoded_input = tokenizer(
        text_list, padding=True, truncation=True, return_tensors="pt"
    )
    encoded_input = {k: v.to(device) for k, v in encoded_input.items()}
    model_output = model(**encoded_input)
    return cls_pooling(model_output)
```

我們可以通過在我們的語料庫中輸入第一個文本條目並檢查輸出維度來測試該函數是否有效：

```py
embedding = get_embeddings(comments_dataset["text"][0])
embedding.shape
```

```python out
torch.Size([1, 768])
```

太好了，我們已經將語料庫中的第一個條目轉換為 768 維向量！我們可以用 **Dataset.map()** 應用我們的 **get_embeddings()** 函數到我們語料庫中的每一行，所以讓我們創建一個新的 **embeddings** 列如下：

```py
embeddings_dataset = comments_dataset.map(
    lambda x: {"embeddings": get_embeddings(x["text"]).detach().cpu().numpy()[0]}
)
```

{:else}

```py
def get_embeddings(text_list):
    encoded_input = tokenizer(
        text_list, padding=True, truncation=True, return_tensors="tf"
    )
    encoded_input = {k: v for k, v in encoded_input.items()}
    model_output = model(**encoded_input)
    return cls_pooling(model_output)
```

我們可以通過在我們的語料庫中輸入第一個文本條目並檢查輸出維度來測試該函數是否有效：

```py
embedding = get_embeddings(comments_dataset["text"][0])
embedding.shape
```

```python out
TensorShape([1, 768])
```

太好了，我們已經將語料庫中的第一個條目轉換為 768 維向量！我們可以用 **Dataset.map()** 應用我們的 **get_embeddings()** 函數到我們語料庫中的每一行，所以讓我們創建一個新的 **embeddings** 列如下：

```py
embeddings_dataset = comments_dataset.map(
    lambda x: {"embeddings": get_embeddings(x["text"]).numpy()[0]}
)
```

{/if}

請注意，我們已經將嵌入轉換為 NumPy 數組——這是因為當我們嘗試使用 FAISS 索引它們時，🤗 Datasets需要這種格式，我們接下來會這樣做。


## 使用 FAISS 進行高效的相似性搜索

現在我們有了一個詞嵌入數據集，我們需要一些方法來搜索它們。為此，我們將在 🤗 Datasets中使用一種特殊的數據結構，稱為 FAISS指數.[FAISS](https://faiss.ai/) (short for Facebook AI Similarity Search) （Facebook AI Similarity Search 的縮寫）是一個提供高效算法來快速搜索和聚類嵌入向量的庫。FAISS 背後的基本思想是創建一個特殊的數據結構，稱為指數。這允許人們找到哪些嵌入詞與輸入的詞嵌入相似。在 🤗 Datasets中創建一個 FAISS 索引很簡單——我們使用 **Dataset.add_faiss_index()** 函數並指定我們要索引的數據集的哪一列：

```py
embeddings_dataset.add_faiss_index(column="embeddings")
```

現在，我們可以使用**Dataset.get_nearest_examples()**函數進行最近鄰居查找。讓我們通過首先嵌入一個問題來測試這一點，如下所示：

{#if fw === 'pt'}

```py
question = "How can I load a dataset offline?"
question_embedding = get_embeddings([question]).cpu().detach().numpy()
question_embedding.shape
```

```python out
torch.Size([1, 768])
```

{:else}

```py
question = "How can I load a dataset offline?"
question_embedding = get_embeddings([question]).numpy()
question_embedding.shape
```

```python out
(1, 768)
```

{/if}

就像文檔一樣，我們現在有一個 768 維向量表示查詢，我們可以將其與整個語料庫進行比較以找到最相似的嵌入：

```py
scores, samples = embeddings_dataset.get_nearest_examples(
    "embeddings", question_embedding, k=5
)
```

 **Dataset.get_nearest_examples()** 函數返回一個分數元組，對查詢和文檔之間的相似度進行排序，以及一組最佳匹配的結果（這裡是 5 個）。讓我們把這些收集到一個 **pandas.DataFrame** 以便我們可以輕鬆地對它們進行排序：

```py
import pandas as pd

samples_df = pd.DataFrame.from_dict(samples)
samples_df["scores"] = scores
samples_df.sort_values("scores", ascending=False, inplace=True)
```

現在我們可以遍歷前幾行來查看我們的查詢與評論的匹配程度：

```py
for _, row in samples_df.iterrows():
    print(f"COMMENT: {row.comments}")
    print(f"SCORE: {row.scores}")
    print(f"TITLE: {row.title}")
    print(f"URL: {row.html_url}")
    print("=" * 50)
    print()
```

```python out
"""
COMMENT: Requiring online connection is a deal breaker in some cases unfortunately so it'd be great if offline mode is added similar to how `transformers` loads models offline fine.

@mandubian's second bullet point suggests that there's a workaround allowing you to use your offline (custom?) dataset with `datasets`. Could you please elaborate on how that should look like?
SCORE: 25.505046844482422
TITLE: Discussion using datasets in offline mode
URL: https://github.com/huggingface/datasets/issues/824
==================================================

COMMENT: The local dataset builders (csv, text , json and pandas) are now part of the `datasets` package since #1726 :)
You can now use them offline
\`\`\`python
datasets = load_dataset("text", data_files=data_files)
\`\`\`

We'll do a new release soon
SCORE: 24.555509567260742
TITLE: Discussion using datasets in offline mode
URL: https://github.com/huggingface/datasets/issues/824
==================================================

COMMENT: I opened a PR that allows to reload modules that have already been loaded once even if there's no internet.

Let me know if you know other ways that can make the offline mode experience better. I'd be happy to add them :)

I already note the "freeze" modules option, to prevent local modules updates. It would be a cool feature.

----------

> @mandubian's second bullet point suggests that there's a workaround allowing you to use your offline (custom?) dataset with `datasets`. Could you please elaborate on how that should look like?

Indeed `load_dataset` allows to load remote dataset script (squad, glue, etc.) but also you own local ones.
For example if you have a dataset script at `./my_dataset/my_dataset.py` then you can do
\`\`\`python
load_dataset("./my_dataset")
\`\`\`
and the dataset script will generate your dataset once and for all.

----------

About I'm looking into having `csv`, `json`, `text`, `pandas` dataset builders already included in the `datasets` package, so that they are available offline by default, as opposed to the other datasets that require the script to be downloaded.
cf #1724
SCORE: 24.14896583557129
TITLE: Discussion using datasets in offline mode
URL: https://github.com/huggingface/datasets/issues/824
==================================================

COMMENT: > here is my way to load a dataset offline, but it **requires** an online machine
>
> 1. (online machine)
>
> ```
>
> import datasets
>
> data = datasets.load_dataset(...)
>
> data.save_to_disk(/YOUR/DATASET/DIR)
>
> ```
>
> 2. copy the dir from online to the offline machine
>
> 3. (offline machine)
>
> ```
>
> import datasets
>
> data = datasets.load_from_disk(/SAVED/DATA/DIR)
>
> ```
>
>
>
> HTH.


SCORE: 22.893993377685547
TITLE: Discussion using datasets in offline mode
URL: https://github.com/huggingface/datasets/issues/824
==================================================

COMMENT: here is my way to load a dataset offline, but it **requires** an online machine
1. (online machine)
\`\`\`
import datasets
data = datasets.load_dataset(...)
data.save_to_disk(/YOUR/DATASET/DIR)
\`\`\`
2. copy the dir from online to the offline machine
3. (offline machine)
\`\`\`
import datasets
data = datasets.load_from_disk(/SAVED/DATA/DIR)
\`\`\`

HTH.
SCORE: 22.406635284423828
TITLE: Discussion using datasets in offline mode
URL: https://github.com/huggingface/datasets/issues/824
==================================================
"""
```

我們的第二個搜索結果似乎與查詢相符。

<Tip>

✏️  試試看！創建您自己的查詢並查看您是否可以在檢索到的文檔中找到答案。您可能需要增加參數k以擴大搜索範圍。

</Tip>

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter5/6.mdx" />

### 章末小測試
https://huggingface.co/learn/course/zh-TW/chapter5/8.md

# 章末小測試

<CourseFloatingBanner
    chapter={5}
    classNames="absolute z-10 right-0 top-0"
/>

本章涵蓋了很多方面! 如果你沒有掌握所有細節, 不用擔心; 在下一章將幫助你瞭解內部的事情是如何工作的。

不過, 在繼續下一章之前, 讓我們測試一下你在本章學到的內容。

### 1.🤗 Datasets中的 `load_dataset ()` 函數允許你從下列哪個位置加載數據集？
<Question
	choices={[
		{
			text: "在本地，例如在你的筆記本電腦上",
			explain: "正確！ 你可以將本地文件的路徑傳遞給 <code>load_dataset()</code> 函數的 <code>data_files</code> 參數來加載本地數據集。",
			correct: true
		},
		{
			text: "Hugging Face Hub",
			explain: "正確! 你可以通過提供數據集 ID 在 Hub 上加載數據集, 例如 < code > load _ dataset ('em otion') </code > 。",
			correct: true
		},
		{
			text: "遠程服務器",
			explain: "正確！ 你可以將URL傳遞給 <code>load_dataset()</code> 函數的 <code>data_files</code> 參數來加載遠程文件。",
			correct: true
		},
	]}
/>

### 2.假設您加載了 GLUE 任務，如下所示:
```py
from datasets import load_dataset

dataset = load_dataset("glue", "mrpc", split="train")
```

以下哪個命令將從 `dataset` 中生成50個元素的隨機樣本?

<Question
	choices={[
		{
			text: "< 代碼 > dataset.sample (50) </code >",
			explain: "這是不正確的——沒有 < code > Dataset.sample () </code > 方法。"
		},
		{
			text: "<code>dataset.shuffle().select(range(50))</code>",
			explain: "正確！ 正如你在本章中看待的, 你首先打亂了數據集, 然後從中選擇樣本。",
			correct: true
		},
		{
			text: "<code>dataset.select(range(50)).shuffle()</code>",
			explain: "這是不正確的——儘管代碼會運行, 但它只會隨機處理數據集中的前50個元素。"
		}
	]}
/>

### 3.假設你有一個叫做寵物數據集的家庭寵物數據集，它有一個名字列表示每個寵物的名字。下列哪種方法可以讓你過濾所有名字以字母"L"開頭的寵物的數據？
<Question
	choices={[
		{
			text: "< code > pets _ dataset. filter (lambda x: x ['name'] . startswith ('L')) </code >",
			explain: "正確! 為這些快速過濾使用 Python lambda 函數是一個好主意。你還能想到其他解決方案嗎？",
			correct: true
		},
		{
			text: "< code > pets _ dataset. filter (lambda x ['name'] . startswith ('L') </code >",
			explain: "這是不正確的—— lambda 函數採用通用格式 < code > lambda * arguments * : * expression * </code >, 因此在這種情況下需要提供參數。"
		},
		{
			text: "創建一個類似於 < code > def filter _ names (x) : return x ['name'] . startswith ('L') </code > 的函數並運行 < code > pets _ dataset. filter (filter _ names) </code > 。",
			explain: "正確！就像使用 < code > Dataset.map () </code > 一樣，你可以將顯式函數傳遞給 < code > Dataset.filter () </code > 。當你有一些不適合於簡短 lambda 函數的複雜邏輯時，這是非常有用的。其他解決方案中還有哪一個可行？",
			correct: true
		}
	]}
/>

### 4.什麼是內存映射？
<Question
	choices={[
		{
			text: "CPU 與 GPU RAM 之間的映射",
			explain: "不是這樣的---- 再試一次！",
		},
		{
			text: "RAM 和文件系統存儲之間的映射",
			explain: "正確！🤗 Datasets 將每個數據集視為一個內存映射文件。這允許庫訪問和操作數據集的元素，而無需將其完全加載到內存中。",
			correct: true
		},
		{
			text: "🤗 Datasets緩存中兩個文件之間的映射",
			explain: "這是不正確的-再試一次！"
		}
	]}
/>

### 5.下列哪一項是內存映射的主要好處？
<Question
	choices={[
		{
			text: "訪問內存映射文件比從磁盤讀取或寫入磁盤更快。",
			explain: "正確! 這使得🤗 Datasets 可以快速傳輸，但這並不是唯一的好處。",
			correct: true
		},
		{
			text: "應用程序可以訪問一個非常大的文件中的數據段，而不必首先將整個文件讀入 RAM。",
			explain: "正確! ——這允許🤗Datasets在你的筆記本電腦上加載多個GB的數據集, 而不消耗你的CPU。內存映射還提供了什麼其他優勢?",
			correct: true
		},
		{
			text: "它消耗更少的能量，所以你的電池持續時間更長。",
			explain: "這是不正確的——再試一次！"
		}
	]}
/>

### 6.為什麼下面的代碼是錯誤的？
```py
from datasets import load_dataset

dataset = load_dataset("allocine", streaming=True, split="train")
dataset[0]
```

<Question
	choices={[
		{
			text: "它試圖對一個太大而無法放入 RAM 的數據集進行流式處理。",
			explain: "這是不正確的---- 流數據集是動態解壓的, 你可以用非常小的RAM處理TB大小的數據集!",
		},
		{
			text: "它嘗試訪問 < code > IterableDataset </code > 。",
			explain: "正確! < code > IterableDataset </code > 是一個生成器, 而不是一個容器, 因此你應該使用 < code > next (iter (dataset)) </code > 來訪問它的元素。",
			correct: true
		},
		{
			text: "數據集 < code > allocine </code > 沒有分割< code >訓練集</code >。",
			explain: "這是不正確的---- 查看 Hub 上的[ < code > allocine </code > dataset card ]( https://huggingface.co/datasets/allocine ), 看看它包含哪些拆分。"
		}
	]}
/>

### 7.創建數據集卡的主要好處是什麼？
<Question
	choices={[
		{
			text: "它提供了關於數據集的預期用途和支持的任務的信息，以便社區中的其他人可以對使用它做出明智的決定。",
			explain: "正確！ 未記錄的數據集可用於訓練可能無法反映數據集創建者意圖的模型, 或者如果對違反隱私或許可限制的數據進行訓練, 則可能會產生法律狀態模糊的模型。不過, 這並不是唯一的好處!",
			correct : true
		},
		{
			text: "它有助於引起人們對語料庫中存在的偏見的注意。",
			explain: "正確！ 幾乎所有數據集都存在某種形式的偏差, 這可能會產生負面的後果。瞭解它們有助於模型構建著瞭解如何解決固有的偏見。數據集卡還有什麼幫助？",
			correct : true
		},
		{
			text: "它提高了社區中其他人使用我的數據集的機會。",
			explain: "正確！一個編寫良好的數據集卡片會帶來更高的寶貴的數據集使用率。它還提供什麼其他好處？",
			correct: true
		},
	]}
/>


### 8.什麼是語義搜索？
<Question
	choices={[
		{
			text: "一種在查詢中的單詞和語料庫中的文檔之間搜索精確匹配的方法",
			explain: "這是不正確的---這種類型的搜索被稱為“詞彙搜索”，這是你通常在傳統搜索引擎中可以看到的。"
		},
		{
			text: "一種通過理解查詢的上下文含義來搜索匹配文檔的方法",
			explain: "正確！ 語義搜索使用嵌入向量來表示查詢和文檔，並使用相似度度量來衡量它們之間的重疊量。你還能怎麼形容它？",
			correct: true
		},
		{
			text: "一種提高搜索準確性的方法",
			explain: "正確！語義搜索引擎能夠比關鍵字匹配更好地捕獲查詢意圖，並且通常能夠以更高的精度檢索文檔。但這不是唯一正確的答案——語義搜索還能提供什麼？",
			correct: true
		}
	]}
/>

### 9.對於非對稱語義搜索，通常有:
<Question
	choices={[
		{
			text: "一個簡短的查詢和一個較長的段落回答查詢",
			explain: "正確！",
			correct : true
		},
		{
			text: "相同長度的查詢和段落",
			explain: "這實際上是一個對稱語義搜索的例子——再試一次！"
		},
		{
			text: "一個較長的查詢和一個較短的段落來回答查詢",
			explain: "這是錯誤的---- 再試一次！"
		}
	]}
/>

### 10.我可以使用數據集加載數據用於其他領域，如語音處理？
<Question
	choices={[
		{
			text: "沒有",
			explain: "正確！🤗 Datasets目前支持表格數據、音頻和計算機視覺。查看[MNIST]( https://huggingface.co/datasets/allocine )數據集以獲取計算機視覺示例。"
		},
		{
			text: "是的",
			explain: "正確！查看令人興奮的發展與語音和視覺在🤗 Transformers庫，看看🤗 Datasets是如何使用這些領域。",
			correct : true
		},
	]}
/>


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter5/8.mdx" />

### 大數據? 🤗 Datasets 來救援!
https://huggingface.co/learn/course/zh-TW/chapter5/4.md

# 大數據? 🤗 Datasets 來救援!

<CourseFloatingBanner chapter={5}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/zh-CN/chapter5/section4.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/zh-CN/chapter5/section4.ipynb"},
]} />


如今,不難發現我們經常使用數GB的數據集, 特別是如果你打算從頭開始預訓練像 BERT 或者 GPT-2 這樣的轉換器。 在這種情況下, _加載_ 數據集就是一個挑戰。例如, 用於預訓練 GPT-2 的 WebText 語料庫包含超過 800 萬個文檔和 40 GB 的文本 -- 將其加載到筆記本電腦的 RAM 中可能會讓它抓狂！

幸運的是, 🤗 Datasets 旨在克服這些限制。它通過將數據集作為內存映射文件來處理，並通過在語料庫中流化條目來擺脫硬盤限制, 從而使你避免內存管理問題。

<Youtube id="JwISwTCPPWo"/>

在本節中, 我們將探索🤗 Datasets 的特性。它有一個稱為 [the Pile](https://pile.eleuther.ai)的825 GB的語料庫。 讓我們開始吧!

## 什麼是Pile?

The Pile 是由[EleutherAI](https://www.eleuther.ai)創建的一個英語文本語料庫, 用於訓練大規模語言模型。它包含各種各樣的數據集, 涵蓋科學文章, GitHub 代碼庫以及過濾的Web文本。訓練語料庫在[14 GB chunks](https://the-eye.eu/public/AI/pile/), 並且你也可以下載幾個[單獨的組件](https://the-eye.eu/public/AI/pile_preliminary_components/)。 讓我們先來看看 PubMed Abstracts 數據集, 它是[PubMed](https://pubmed.ncbi.nlm.nih.gov/)上的1500萬篇生物醫學出版物的摘要的語料庫。 數據集採用[JSON行格式](https://jsonlines.org) 並使用`zstandard`庫進行壓縮, 所以我們首先需要先安裝`zstandard`庫:

```py
!pip install zstandard
```

接下來, 我們可以使用[第二節](/course/chapter5/2)中所學的加載遠程數據集的方法加載數據集:

```py
from datasets import load_dataset

# This takes a few minutes to run, so go grab a tea or coffee while you wait :)
data_files = "https://the-eye.eu/public/AI/pile_preliminary_components/PUBMED_title_abstracts_2019_baseline.jsonl.zst"
pubmed_dataset = load_dataset("json", data_files=data_files, split="train")
pubmed_dataset
```

```python out
Dataset({
    features: ['meta', 'text'],
    num_rows: 15518009
})
```

我們可以看到我們的數據集中有 15,518,009 行和 2 列 -- 這是非常多的!

<Tip>

✎ 默認情況下, 🤗 Datasets 會自動解壓加載數據集所需的文件。 如果你想保留硬盤空間, 你可以傳遞 `DownloadConfig(delete_extracted=True)` 到 `download_config` 的 `load_dataset()`參數. 有關更多詳細信息, 請參閱文檔](https://huggingface.co/docs/datasets/package_reference/builder_classes#datasets.DownloadConfig)。

</Tip>

讓我們看看數據集的第一個元素的內容:

```py
pubmed_dataset[0]
```

```python out
{'meta': {'pmid': 11409574, 'language': 'eng'},
 'text': 'Epidemiology of hypoxaemia in children with acute lower respiratory infection.\nTo determine the prevalence of hypoxaemia in children aged under 5 years suffering acute lower respiratory infections (ALRI), the risk factors for hypoxaemia in children under 5 years of age with ALRI, and the association of hypoxaemia with an increased risk of dying in children of the same age ...'}
```

可以看到, 這看起來像是醫學文章的摘要。 現在讓我們看看我們使用了RAM的多少存儲空間來加載數據集!

## 內存映射的魔力

在 Python 中測量內存使用情況的一個簡單的方法是使用[`psutil`](https://psutil.readthedocs.io/en/latest/)庫,它可以使用 `pip`安裝, 如下所示:

```python
!pip install psutil
```

它提供了一個 `Process` 類,這個類允許我們檢查當前進程的內存使用情況, 如下所示:

```py
import psutil

# Process.memory_info is expressed in bytes, so convert to megabytes
print(f"RAM used: {psutil.Process().memory_info().rss / (1024 * 1024):.2f} MB")
```

```python out
RAM used: 5678.33 MB
```

這裡的`rss`屬性是指 _常駐集_ 的大小, 它是進程在RAM中佔用的內存比例。 這個測量結果也包括了 Python 編譯器和我們加載的庫所使用的內存, 所以實際上用於加載數據集的內存會更小一些。為了比較, 讓我們使用 `dataset_size` 屬性看看數據集在磁盤上有多大。 由於結果像之前一樣用字節表示, 我們需要手動將其轉換為GB:

```py
print(f"Number of files in dataset : {pubmed_dataset.dataset_size}")
size_gb = pubmed_dataset.dataset_size / (1024**3)
print(f"Dataset size (cache file) : {size_gb:.2f} GB")
```

```python out
Number of files in dataset : 20979437051
Dataset size (cache file) : 19.54 GB
```

非常棒 -- 儘管它將近20GB, 但我們能夠佔用很少的RAM空間加載和訪問數據集!

<Tip>

✏️ **試試看!** 從[subsets](https://the-eye.eu/public/AI/pile_preliminary_components/)中選擇一個大於你的筆記本或者臺式機的RAM大小的子集, 用 🤗 Datasets加載這個數據集, 並且測量RAM的使用量。 請注意, 要獲得準確的測量結果, 你需要在另一個進程中執行這個操作。你可以在 [the Pile paper](https://arxiv.org/abs/2101.00027)的表一中找到每個子集解壓後的大小。

</Tip>

如果你熟悉 Pandas, 這個結果可能會讓人感到很意外。因為 Wes Kinney 的著名的[經驗法則](https://wesmckinney.com/blog/apache-arrow-pandas-internals/) 是你需要的RAM應該是數據集的大小的5倍到10倍。 那麼 🤗 Datasets 是如何解決這個內存管理問題的呢? 🤗 Datasets 將每一個數據集看作一個[內存映射文件](https://en.wikipedia.org/wiki/Memory-mapped_file), 它提供了RAM和文件系統存儲之間的映射, 該映射允許庫訪問和操作數據集的元素, 而且無需將其完全加載到內存中。

內存映射文件也一個在多個進程之間共享, 這使得像 `Dataset.map()`之類的方法可以並行化, 並且無需移動或者賦值數據集。在底層, 這些功能都是由[Apache Arrow](https://arrow.apache.org)內存格式和[`pyarrow`](https://arrow.apache.org/docs/python/index.html)庫提供的支持, 使得數據加載和處理速度快如閃電。 (更多有關Apache Arrow的詳細信息以及與Pandas的比較, 請查看[Dejan Simic's blog post](https://towardsdatascience.com/apache-arrow-read-dataframe-with-zero-memory-69634092b1a).) 為了更清晰地看到這個過程, 讓我們通過迭代PubMed Abstracts數據集中的所有元素來運行一個速度測試小程序:

```py
import timeit

code_snippet = """batch_size = 1000

for idx in range(0, len(pubmed_dataset), batch_size):
    _ = pubmed_dataset[idx:idx + batch_size]
"""

time = timeit.timeit(stmt=code_snippet, number=1, globals=globals())
print(
    f"Iterated over {len(pubmed_dataset)} examples (about {size_gb:.1f} GB) in "
    f"{time:.1f}s, i.e. {size_gb/time:.3f} GB/s"
)
```

```python out
'Iterated over 15518009 examples (about 19.5 GB) in 64.2s, i.e. 0.304 GB/s'
```

這裡我們使用了 Python的 `timeit` 模塊來測量執行 `code_snippet`所耗的時間。 你通常能以十分之幾GB/s到幾GB/s的速度迭代數據集。通過上述的方法就已經能夠解決大多數大數據集加載的限制, 但是有時候你不得不使用一個很大的數據集, 它甚至都不能存儲在筆記本電腦的硬盤上。例如, 如果我們嘗試下載整個 Pile, 我們需要825GB的可用磁盤空間! 為了處理這種情況, 🤗 Datasets 提供了一個流式功能, 這個功能允許我們動態下載和訪問元素, 並且不需要下載整個數據集。讓我們來看看這個功能是如何工作的。

<Tip>

💡在 Jupyter 筆記中你還可以使用[`%%timeit` magic function](https://ipython.readthedocs.io/en/stable/interactive/magics.html#magic-timeit)為單元格計時。

</Tip>

## 流式數據集

要使用數據集流, 你只需要將 `streaming=True` 參數傳遞給 `load_dataset()` 函數。接下來, 讓我們再次加載 PubMed Abstracts 數據集, 但是採用流模式:

```py
pubmed_dataset_streamed = load_dataset(
    "json", data_files=data_files, split="train", streaming=True
)
```

與我們在本章其他地方遇到的熟悉的 `Dataset` 不同,  `streaming=True` 返回的對象是一個 `IterableDataset`。 顧名思義, 要訪問 `IterableDataset` , 我們需要迭代它。我們可以按照如下方式訪問流式數據集的第一個元素:


```py
next(iter(pubmed_dataset_streamed))
```

```python out
{'meta': {'pmid': 11409574, 'language': 'eng'},
 'text': 'Epidemiology of hypoxaemia in children with acute lower respiratory infection.\nTo determine the prevalence of hypoxaemia in children aged under 5 years suffering acute lower respiratory infections (ALRI), the risk factors for hypoxaemia in children under 5 years of age with ALRI, and the association of hypoxaemia with an increased risk of dying in children of the same age ...'}
```

如果您需要在訓練期間標記流式數據集中的元素可以使用 `IterableDataset.map()`進行動態處理。該過程與我們在[第三章](/course/chapter3)中標記數據集的過程完全相同, 唯一的區別是輸出是逐個返回的:

```py
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("distilbert-base-uncased")
tokenized_dataset = pubmed_dataset_streamed.map(lambda x: tokenizer(x["text"]))
next(iter(tokenized_dataset))
```

```python out
{'input_ids': [101, 4958, 5178, 4328, 6779, ...], 'attention_mask': [1, 1, 1, 1, 1, ...]}
```

<Tip>

💡 你可以傳遞 `batched=True` 來通過流式加速標記化, 如同我們在上一節看到的那樣。它將逐批處理示例; 默認的批量大小為 1,000, 可以使用 `batch_size` 參數指定批量大小。

</Tip>

你還可以使用 `IterableDataset.shuffle()` 打亂流式數據集, 但與 `Dataset.shuffle()` 不同的是這隻會打亂預定義 `buffer_size` 中的元素:

```py
shuffled_dataset = pubmed_dataset_streamed.shuffle(buffer_size=10_000, seed=42)
next(iter(shuffled_dataset))
```

```python out
{'meta': {'pmid': 11410799, 'language': 'eng'},
 'text': 'Randomized study of dose or schedule modification of granulocyte colony-stimulating factor in platinum-based chemotherapy for elderly patients with lung cancer ...'}
```

在這個示例中, 我們從緩衝區的前 10,000 個示例中隨機選擇了一個示例。一旦訪問了一個示例, 它在緩衝區中的位置就會被語料庫中的下一個示例填充 (即, 上述案例中的第 10,001個示例)。你還可以使用 `IterableDataset.take()` 和 `IterableDataset.skip()` 函數從流式數據集中選擇元素, 它的作用類似於 `Dataset.select()`。例如, 要選擇 PubMed Abstracts 數據集的前5個示例, 我們可以執行以下操作:

```py
dataset_head = pubmed_dataset_streamed.take(5)
list(dataset_head)
```

```python out
[{'meta': {'pmid': 11409574, 'language': 'eng'},
  'text': 'Epidemiology of hypoxaemia in children with acute lower respiratory infection ...'},
 {'meta': {'pmid': 11409575, 'language': 'eng'},
  'text': 'Clinical signs of hypoxaemia in children with acute lower respiratory infection: indicators of oxygen therapy ...'},
 {'meta': {'pmid': 11409576, 'language': 'eng'},
  'text': "Hypoxaemia in children with severe pneumonia in Papua New Guinea ..."},
 {'meta': {'pmid': 11409577, 'language': 'eng'},
  'text': 'Oxygen concentrators and cylinders ...'},
 {'meta': {'pmid': 11409578, 'language': 'eng'},
  'text': 'Oxygen supply in rural africa: a personal experience ...'}]
```

同樣, 你可以使用 `IterableDataset.skip()` 函數將打亂的數據集拆分為訓練集和驗證集, 如下所示:

```py
# Skip the first 1,000 examples and include the rest in the training set
train_dataset = shuffled_dataset.skip(1000)
# Take the first 1,000 examples for the validation set
validation_dataset = shuffled_dataset.take(1000)
```

讓我們用一個常見的任務來進行我們對數據集流的最後探索: 將多個數據集組合在一起創建一個心得語料庫。 🤗 Datasets 提供了一個 `interleave_datasets()` 函數, 它將一個 `IterableDataset` 對象列表組合為單個的 `IterableDataset`, 其中新數據集的元素是通過在列表中的對象交替獲得的。當你試圖組合大型數據集時, 這個函數特別有用, 讓我們通過下面這個例子來試著組合 Pile的自由法律數據集,它是來自美國法院的51 GB的法律意見數據集:

```py
law_dataset_streamed = load_dataset(
    "json",
    data_files="https://the-eye.eu/public/AI/pile_preliminary_components/FreeLaw_Opinions.jsonl.zst",
    split="train",
    streaming=True,
)
next(iter(law_dataset_streamed))
```

```python out
{'meta': {'case_ID': '110921.json',
  'case_jurisdiction': 'scotus.tar.gz',
  'date_created': '2010-04-28T17:12:49Z'},
 'text': '\n461 U.S. 238 (1983)\nOLIM ET AL.\nv.\nWAKINEKONA\nNo. 81-1581.\nSupreme Court of United States.\nArgued January 19, 1983.\nDecided April 26, 1983.\nCERTIORARI TO THE UNITED STATES COURT OF APPEALS FOR THE NINTH CIRCUIT\n*239 Michael A. Lilly, First Deputy Attorney General of Hawaii, argued the cause for petitioners. With him on the brief was James H. Dannenberg, Deputy Attorney General...'}
```

這個數據集足夠大, 可以對大多數筆記本電腦的RAM有足夠的壓力, 但是我們已經能夠毫不費力地加載和訪問它! 現在我們使用 `interleave_datasets()` 函數加載來自 FreeLaw 和 PubMed Abstracts 的數據集:

```py
from itertools import islice
from datasets import interleave_datasets

combined_dataset = interleave_datasets([pubmed_dataset_streamed, law_dataset_streamed])
list(islice(combined_dataset, 2))
```

```python out
[{'meta': {'pmid': 11409574, 'language': 'eng'},
  'text': 'Epidemiology of hypoxaemia in children with acute lower respiratory infection ...'},
 {'meta': {'case_ID': '110921.json',
   'case_jurisdiction': 'scotus.tar.gz',
   'date_created': '2010-04-28T17:12:49Z'},
  'text': '\n461 U.S. 238 (1983)\nOLIM ET AL.\nv.\nWAKINEKONA\nNo. 81-1581.\nSupreme Court of United States.\nArgued January 19, 1983.\nDecided April 26, 1983.\nCERTIORARI TO THE UNITED STATES COURT OF APPEALS FOR THE NINTH CIRCUIT\n*239 Michael A. Lilly, First Deputy Attorney General of Hawaii, argued the cause for petitioners. With him on the brief was James H. Dannenberg, Deputy Attorney General...'}]
```

這裡我們使用了來自Python的 `itertools` 模塊的 `islice()` 函數從合併的數據集中選擇前兩個示例, 並且我們可以看到它們實際上就是兩個源數據集中的前兩個示例拼在一起形成的:

最後, 如果你想流式傳輸整個825GB的 Pile, 你可以按照如下方式獲取所有準備好的文件:

```py
base_url = "https://the-eye.eu/public/AI/pile/"
data_files = {
    "train": [base_url + "train/" + f"{idx:02d}.jsonl.zst" for idx in range(30)],
    "validation": base_url + "val.jsonl.zst",
    "test": base_url + "test.jsonl.zst",
}
pile_dataset = load_dataset("json", data_files=data_files, streaming=True)
next(iter(pile_dataset["train"]))
```

```python out
{'meta': {'pile_set_name': 'Pile-CC'},
 'text': 'It is done, and submitted. You can play “Survival of the Tastiest” on Android, and on the web...'}
```

<Tip>

✏️ **試試看!** 使用像[`mc4`](https://huggingface.co/datasets/mc4) 或者 [`oscar`](https://huggingface.co/datasets/oscar)這樣的大型 Common Crawl 語料庫來創建一個流式多語言數據集, 該數據集代表你選擇的國家/地區語言的口語比例。例如, 瑞士的四種民族語言分別是德語、法語、意大利語和羅曼什語, 因此你可以嘗試根據根據口語比例對Oscar子集進行採用來創建瑞士語料庫。

</Tip>

你現在擁有加載和處理各種類型和大小的數據集的所需的所有工具 -- 但是除非你非常幸運, 否則在你的NLP之旅中會有一個難題, 你將不得不創建一個數據集來解決手頭的問題。這就是下一節的主題!


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter5/4.mdx" />

### 🤗 Datasets，回顧！
https://huggingface.co/learn/course/zh-TW/chapter5/7.md

# 🤗 Datasets，回顧！

<CourseFloatingBanner
    chapter={5}
    classNames="absolute z-10 right-0 top-0"
/>

這是對 🤗 Datasets 庫的一次完整遊覽——祝賀你走到這一步！憑藉從本章中獲得的知識，您應該能夠：

- 從任何地方加載數據集，無論是 Hugging Face Hub、您的筆記本電腦還是您公司的遠程服務器。
- 混合使用Dataset.map()和Dataset.filter()函數來整理數據。
- 使用`Dataset.set_format()`在 Pandas 和 NumPy 等數據格式之間快速切換.
- 創建您自己的數據集並將其推送到 Hugging Face Hub。.
- 使用 Transformer 模型為您的文檔創建詞嵌入，並使用 FAISS 構建語義搜索引擎。.

在[第七章](/course/chapter7)，當我們深入研究 Transformer 模型非常適合的核心 NLP 任務時，我們將充分利用所有這些。

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter5/7.mdx" />

### 如果我的數據集不在 Hub 上怎麼辦？
https://huggingface.co/learn/course/zh-TW/chapter5/2.md

# 如果我的數據集不在 Hub 上怎麼辦？

<CourseFloatingBanner chapter={5}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/zh-CN/chapter5/section2.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/zh-CN/chapter5/section2.ipynb"},
]} />

你知道如何使用[Hugging Face Hub](https://huggingface.co/datasets)下載數據集, 但你經常會發現自己正在處理存儲在筆記本電腦或遠程服務器上的數據。在本節中，我們將向您展示如何使用 🤗 Datasets來加載 Hugging Face Hub 上不可用的數據集。

<Youtube id="HyQgpJTkRdE"/>

## 使用本地和遠程數據集

🤗 Datasets 提供了加載腳本來加載本地和遠程數據集。它支持幾種常見的數據格式，例如：

|    Data format     | Loading script |                         Example                         |
| :----------------: | :------------: | :-----------------------------------------------------: |
|     CSV & TSV      |     `csv`      |     `load_dataset("csv", data_files="my_file.csv")`     |
|     Text files     |     `text`     |    `load_dataset("text", data_files="my_file.txt")`     |
| JSON & JSON Lines  |     `json`     |   `load_dataset("json", data_files="my_file.jsonl")`    |
| Pickled DataFrames |    `pandas`    | `load_dataset("pandas", data_files="my_dataframe.pkl")` |

如表所示, 對於每種數據格式, 我們只需要使用 `load_dataset()` 函數, 使用 `data_files` 指定一個或多個文件的路徑的參數。 讓我們從本地文件加載數據集開始；稍後我們將看到如何對遠程文件執行相同的操作。

## 加載本地數據集

對於這個例子，我們將使用 [SQuAD-it dataset](https://github.com/crux82/squad-it/), 這是一個大規模的意大利語問答數據集。

訓練和測試都託管在 GitHub 上, 因此我們可以通過`wget`命令非常簡單地下載它們:

```python
!wget https://github.com/crux82/squad-it/raw/master/SQuAD_it-train.json.gz
!wget https://github.com/crux82/squad-it/raw/master/SQuAD_it-test.json.gz
```

這將下載兩個名為*SQuAD_it-train.json.gz* 和 *SQuAD_it-test.json.gz*的壓縮文件, 我們可以用Linux的解壓命令 `gzip`:

```python
!gzip -dkv SQuAD_it-*.json.gz
```

```bash
SQuAD_it-test.json.gz:	   87.4% -- replaced with SQuAD_it-test.json
SQuAD_it-train.json.gz:	   82.2% -- replaced with SQuAD_it-train.json
```

我們可以看到壓縮文件已經被替換為SQuAD_it-train.json和SQuAD_it-text.json,並且數據以 JSON 格式存儲。

<Tip>

✎ 如果你想知道為什麼上面的shell命令中喲與一個字符`!`,那是因為我們是在 Jupyter notebook 中運行它們。如果您想在終端中下載和解壓縮數據集，只需刪除前綴!即可。

</Tip>

使用`load_dataset()`函數來加載JSON文件, 我們只需要知道我們是在處理普通的 JSON(類似於嵌套字典)還是 JSON 行(行分隔的 JSON)。像許多問答數據集一樣, SQuAD-it 使用嵌套格式,所有文本都存儲在 `data`文件中。這意味著我們可以通過指定參數`field`來加載數據集,如下所示:

```py
from datasets import load_dataset

squad_it_dataset = load_dataset("json", data_files="SQuAD_it-train.json", field="data")
```

默認情況下, 加載本地文件會創建一個帶有`train`的`DatasetDict` 對象。 我們可以通過 `squad_it_dataset`查看:

```py
squad_it_dataset
```

```python out
DatasetDict({
    train: Dataset({
        features: ['title', 'paragraphs'],
        num_rows: 442
    })
})
```

這向我們顯示了與訓練集相關聯的行數和列名。我們可以通過索引到 `train` 查看示例，如下所示:

```py
squad_it_dataset["train"][0]
```

```python out
{
    "title": "Terremoto del Sichuan del 2008",
    "paragraphs": [
        {
            "context": "Il terremoto del Sichuan del 2008 o il terremoto...",
            "qas": [
                {
                    "answers": [{"answer_start": 29, "text": "2008"}],
                    "id": "56cdca7862d2951400fa6826",
                    "question": "In quale anno si è verificato il terremoto nel Sichuan?",
                },
                ...
            ],
        },
        ...
    ],
}
```

很好, 我們已經加載了我們的第一個本地數據集! 但是, 雖然這對訓練集有效, 但是我們真正想要的是包括 `train` 和 `test` 的 `DatasetDict` 對象。這樣的話就可以使用 `Dataset.map()` 函數同時處理訓練集和測試集。 為此, 我們提供參數`data_files`的字典,將每個分割名稱映射到與該分割相關聯的文件：

```py
data_files = {"train": "SQuAD_it-train.json", "test": "SQuAD_it-test.json"}
squad_it_dataset = load_dataset("json", data_files=data_files, field="data")
squad_it_dataset
```

```python out
DatasetDict({
    train: Dataset({
        features: ['title', 'paragraphs'],
        num_rows: 442
    })
    test: Dataset({
        features: ['title', 'paragraphs'],
        num_rows: 48
    })
})
```

這正是我們想要的。現在, 現在，我們可以應用各種預處理技術來清理數據、標記評論等。

<Tip>

`load_dataset()`函數的`data_files`參數非常靈活並且可以是單個文件路徑、文件路徑列表或將分割後的名稱映射到文件路徑的字典。您還可以根據Unix shell使用的規則對與指定模式匹配的文件進行全局定位（例如，您可以通過設置'data_files=“*.JSON”'將目錄中的所有JSON文件作為單個拆分進行全局定位）。有關更多詳細信息，請參閱🤗Datasets 文檔。

</Tip>

🤗 Datasets實際上支持輸入文件的自動解壓,所以我們可以跳過使用`gzip`,直接設置 `data_files`參數傳遞壓縮文件:

```py
data_files = {"train": "SQuAD_it-train.json.gz", "test": "SQuAD_it-test.json.gz"}
squad_it_dataset = load_dataset("json", data_files=data_files, field="data")
```

如果您不想手動解壓縮許多 GZIP 文件，這會很有用。自動解壓也適用於其他常見格式,如 ZIP 和 TAR,因此您只需將 `data_files` 設置為壓縮文件所在的路徑,你就可以開始了!

現在你知道如何在筆記本電腦或臺式機上加載本地文件，讓我們來看看加載遠程文件。

## 加載遠程數據集

如果你在公司擔任數據研究員或編碼員,那麼你要分析的數據集很有可能存儲在某個遠程服務器上。幸運的是,加載遠程文件就像加載本地文件一樣簡單!我們沒有提供本地文件的路徑, 而是將`load_dataset()`的`data_files`參數指向存儲遠程文件的一個或多個URL。例如, 對於託管在 GitHub 上的 SQuAD-it 數據集, 我們可以將 `data_files` 指向 _SQuAD_it-*.json.gz_ 的網址,如下所示:

```py
url = "https://github.com/crux82/squad-it/raw/master/"
data_files = {
    "train": url + "SQuAD_it-train.json.gz",
    "test": url + "SQuAD_it-test.json.gz",
}
squad_it_dataset = load_dataset("json", data_files=data_files, field="data")
```

這將返回和上面的本地例子相同的 `DatasetDict` 對象, 但省去了我們手動下載和解壓 _SQuAD_it-*.json.gz_ 文件的步驟。這是我們對加載未託管在Hugging Face Hub的數據集的各種方法的總結。既然我們已經有了一個可以使用的數據集,讓我們開始大展身手吧！

<Tip>

✏️ **試試看!** 選擇託管在GitHub或[UCI Machine Learning Repository](https://archive.ics.uci.edu/ml/index.php)上的另一個數據集並嘗試使用上述技術在本地和遠程加載它。另外,可以嘗試加載CSV或者文本格式存儲的數據集(有關這些格式的更多信息,請參閱[文檔](https://huggingface.co/docs/datasets/loading#local-and-remote-files))。

</Tip>




<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter5/2.mdx" />

### 處理多個序列
https://huggingface.co/learn/course/zh-TW/chapter2/5.md

# 處理多個序列

{#if fw === 'pt'}

<CourseFloatingBanner chapter={2}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/zh-CN/chapter2/section5_pt.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/zh-CN/chapter2/section5_pt.ipynb"},
]} />

{:else}

<CourseFloatingBanner chapter={2}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/zh-CN/chapter2/section5_tf.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/zh-CN/chapter2/section5_tf.ipynb"},
]} />

{/if}

{#if fw === 'pt'}
<Youtube id="M6adb1j2jPI"/>
{:else}
<Youtube id="ROxrFOEbsQE"/>
{/if}

在上一節中，我們探討了最簡單的用例：對一個小長度的序列進行推理。然而，一些問題已經出現：

* 我們如何處理多個序列？


* 我們如何處理多個序列不同長度?


* 詞彙索引是讓模型正常工作的唯一輸入嗎？


* 是否存在序列太長的問題？

讓我們看看這些問題會帶來什麼樣的問題，以及如何使用🤗 Transformers API解決它們

## 模型需要一批輸入

在上一個練習中，您看到了序列如何轉換為數字列表。讓我們將此數字列表轉換為張量，並將其發送到模型：

{#if fw === 'pt'}
```py
import torch
from transformers import AutoTokenizer, AutoModelForSequenceClassification

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = AutoModelForSequenceClassification.from_pretrained(checkpoint)

sequence = "I've been waiting for a HuggingFace course my whole life."

tokens = tokenizer.tokenize(sequence)
ids = tokenizer.convert_tokens_to_ids(tokens)
input_ids = torch.tensor(ids)
# This line will fail.
model(input_ids)
```

```python out
IndexError: Dimension out of range (expected to be in range of [-1, 0], but got 1)
```
{:else}
```py
import tensorflow as tf
from transformers import AutoTokenizer, TFAutoModelForSequenceClassification

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint)

sequence = "I've been waiting for a HuggingFace course my whole life."

tokens = tokenizer.tokenize(sequence)
ids = tokenizer.convert_tokens_to_ids(tokens)
input_ids = tf.constant(ids)
# This line will fail.
model(input_ids)
```

```py out
InvalidArgumentError: Input to reshape is a tensor with 14 values, but the requested shape has 196 [Op:Reshape]
```
{/if}

哦不！為什麼失敗了？我們遵循了第2節中管道的步驟。

問題是我們向模型發送了一個序列，而 🤗Transformers 模型默認情況下需要多個句子。在這裡，當我們將分詞器應用於一個應用程序時，我們嘗試在幕後完成分詞器所做的一切，但如果仔細觀察，您會發現它不僅將輸入ID列表轉換為張量，還在其頂部添加了一個維度：

{#if fw === 'pt'}
```py
tokenized_inputs = tokenizer(sequence, return_tensors="pt")
print(tokenized_inputs["input_ids"])
```

```python out
tensor([[  101,  1045,  1005,  2310,  2042,  3403,  2005,  1037, 17662, 12172,
          2607,  2026,  2878,  2166,  1012,   102]])
```
{:else}
```py
tokenized_inputs = tokenizer(sequence, return_tensors="tf")
print(tokenized_inputs["input_ids"])
```

```py out
tf.Tensor: shape=(1, 16), dtype=int32, numpy=
array([[  101,  1045,  1005,  2310,  2042,  3403,  2005,  1037, 17662,
        12172,  2607,  2026,  2878,  2166,  1012,   102]], dtype=int32)>
```
{/if}

讓我們重試並添加一個新維度：

{#if fw === 'pt'}
```py
import torch
from transformers import AutoTokenizer, AutoModelForSequenceClassification

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = AutoModelForSequenceClassification.from_pretrained(checkpoint)

sequence = "I've been waiting for a HuggingFace course my whole life."

tokens = tokenizer.tokenize(sequence)
ids = tokenizer.convert_tokens_to_ids(tokens)

input_ids = torch.tensor([ids])
print("Input IDs:", input_ids)

output = model(input_ids)
print("Logits:", output.logits)
```
{:else}
```py
import tensorflow as tf
from transformers import AutoTokenizer, TFAutoModelForSequenceClassification

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint)

sequence = "I've been waiting for a HuggingFace course my whole life."

tokens = tokenizer.tokenize(sequence)
ids = tokenizer.convert_tokens_to_ids(tokens)

input_ids = tf.constant([ids])
print("Input IDs:", input_ids)

output = model(input_ids)
print("Logits:", output.logits)
```
{/if}

我們打印輸入ID以及生成的logits-以下是輸出：

{#if fw === 'pt'}
```python out
Input IDs: [[ 1045,  1005,  2310,  2042,  3403,  2005,  1037, 17662, 12172,  2607, 2026,  2878,  2166,  1012]]
Logits: [[-2.7276,  2.8789]]
```
{:else}
```py out
Input IDs: tf.Tensor(
[[ 1045  1005  2310  2042  3403  2005  1037 17662 12172  2607  2026  2878
   2166  1012]], shape=(1, 14), dtype=int32)
Logits: tf.Tensor([[-2.7276208  2.8789377]], shape=(1, 2), dtype=float32)
```
{/if}

*Batching* 是一次通過模型發送多個句子的行為。如果你只有一句話，你可以用一個序列構建一個批次：


```
batched_ids = [ids, ids]
```

這是一批兩個相同的序列！

<Tip>

✏️ **試試看！** 將此列表轉換為張量並通過模型傳遞。檢查您是否獲得與之前相同的登錄（但是隻有兩次）
</Tip>

批處理允許模型在輸入多個句子時工作。使用多個序列就像使用單個序列構建批一樣簡單。不過，還有第二個問題。當你試圖將兩個（或更多）句子組合在一起時，它們的長度可能不同。如果您以前使用過張量，那麼您知道它們必須是矩形，因此無法將輸入ID列表直接轉換為張量。為了解決這個問題，我們通常填充輸入。

## 填充輸入

以下列表不能轉換為張量：

```py no-format
batched_ids = [
    [200, 200, 200],
    [200, 200]
]
```

為了解決這個問題，我們將使用填充使張量具有矩形。Padding通過在值較少的句子中添加一個名為Padding token的特殊單詞來確保我們所有的句子長度相同。例如，如果你有10個包含10個單詞的句子和1個包含20個單詞的句子，填充將確保所有句子都包含20個單詞。在我們的示例中，生成的張量如下所示：

```py no-format
padding_id = 100

batched_ids = [
    [200, 200, 200],
    [200, 200, padding_id],
]
```

可以在tokenizer.pad_token_id中找到填充令牌ID. 讓我們使用它，將我們的兩句話分別發送到模型中，並分批發送到一起：


{#if fw === 'pt'}
```py no-format
model = AutoModelForSequenceClassification.from_pretrained(checkpoint)

sequence1_ids = [[200, 200, 200]]
sequence2_ids = [[200, 200]]
batched_ids = [
    [200, 200, 200],
    [200, 200, tokenizer.pad_token_id],
]

print(model(torch.tensor(sequence1_ids)).logits)
print(model(torch.tensor(sequence2_ids)).logits)
print(model(torch.tensor(batched_ids)).logits)
```

```python out
tensor([[ 1.5694, -1.3895]], grad_fn=<AddmmBackward>)
tensor([[ 0.5803, -0.4125]], grad_fn=<AddmmBackward>)
tensor([[ 1.5694, -1.3895],
        [ 1.3373, -1.2163]], grad_fn=<AddmmBackward>)
```
{:else}
```py no-format
model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint)

sequence1_ids = [[200, 200, 200]]
sequence2_ids = [[200, 200]]
batched_ids = [
    [200, 200, 200],
    [200, 200, tokenizer.pad_token_id],
]

print(model(tf.constant(sequence1_ids)).logits)
print(model(tf.constant(sequence2_ids)).logits)
print(model(tf.constant(batched_ids)).logits)
```

```py out
tf.Tensor([[ 1.5693678 -1.3894581]], shape=(1, 2), dtype=float32)
tf.Tensor([[ 0.5803005  -0.41252428]], shape=(1, 2), dtype=float32)
tf.Tensor(
[[ 1.5693681 -1.3894582]
 [ 1.3373486 -1.2163193]], shape=(2, 2), dtype=float32)
```
{/if}

我們批處理預測中的logits有點問題：第二行應該與第二句的logits相同，但我們得到了完全不同的值！


這是因為Transformer模型的關鍵特性是關注層，它將每個標記上下文化。這些將考慮填充標記，因為它們涉及序列中的所有標記。為了在通過模型傳遞不同長度的單個句子時，或者在傳遞一批應用了相同句子和填充的句子時獲得相同的結果，我們需要告訴這些注意層忽略填充標記。這是通過使用 attention mask來實現的。

## 注意力遮罩（Attention masks）

*Attention masks* 是與輸入 ID 張量形狀完全相同的張量，用0和1填充：1s表示應注意相應的標記，0s表示不應注意相應的標記（即，模型的注意力層應忽略它們）。

讓我們用 attention mask 完成上一個示例：

{#if fw === 'pt'}
```py no-format
batched_ids = [
    [200, 200, 200],
    [200, 200, tokenizer.pad_token_id],
]

attention_mask = [
    [1, 1, 1],
    [1, 1, 0],
]

outputs = model(torch.tensor(batched_ids), attention_mask=torch.tensor(attention_mask))
print(outputs.logits)
```

```python out
tensor([[ 1.5694, -1.3895],
        [ 0.5803, -0.4125]], grad_fn=<AddmmBackward>)
```
{:else}
```py no-format
batched_ids = [
    [200, 200, 200],
    [200, 200, tokenizer.pad_token_id],
]

attention_mask = [
    [1, 1, 1],
    [1, 1, 0],
]

outputs = model(tf.constant(batched_ids), attention_mask=tf.constant(attention_mask))
print(outputs.logits)
```

```py out
tf.Tensor(
[[ 1.5693681  -1.3894582 ]
 [ 0.5803021  -0.41252586]], shape=(2, 2), dtype=float32)
```
{/if}

現在我們得到了該批中第二個句子的相同登錄。

請注意，第二個序列的最後一個值是一個填充ID，它在attention mask中是一個0值。

<Tip>

✏️ 試試看！在第2節中使用的兩個句子上手動應用標記化（“我一生都在等待Hugging Face課程。”和“我非常討厭這個！”）。通過模型傳遞它們，並檢查您是否獲得與第2節中相同的登錄。現在使用填充標記將它們批處理在一起，然後創建適當的注意掩碼。檢查通過模型時是否獲得相同的結果！

</Tip>

## 長序列

對於Transformers模型，我們可以通過模型的序列長度是有限的。大多數模型處理多達512或1024個令牌的序列，當要求處理更長的序列時，會崩潰。此問題有兩種解決方案：



* 使用支持的序列長度較長的模型。


* 截斷序列。


模型有不同的支持序列長度，有些模型專門處理很長的序列。
[Longformer](https://huggingface.co/transformers/model_doc/longformer.html)
這是一個例子，另一個是
[LED](https://huggingface.co/transformers/model_doc/led.html)
. 如果您正在處理一項需要很長序列的任務，我們建議您查看這些模型。

否則，我們建議您通過指定max_sequence_length參數：

```py
sequence = sequence[:max_sequence_length]
```


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter2/5.mdx" />

### 本章簡介
https://huggingface.co/learn/course/zh-TW/chapter2/1.md

# 本章簡介

<CourseFloatingBanner
    chapter={2}
    classNames="absolute z-10 right-0 top-0"
/>

正如你在 [Chapter 1](/course/chapter1),中看到的那樣，Transformers 模型通常非常大。對於數以百萬計到數千萬計數十億的參數，訓練和部署這些模型是一項複雜的任務。此外，由於幾乎每天都在發佈新模型，而且每種模型都有自己的實現，因此嘗試它們絕非易事。

創建 🤗Transformers 庫就是為了解決這個問題。它的目標是提供一個API，通過它可以加載、訓練和保存任何Transformer模型。這個庫的主要特點是：
-  **易於使用**：下載、加載和使用最先進的NLP模型進行推理只需兩行代碼即可完成。
- **靈活**：所有模型的核心都是簡單的 PyTorch **nn.Module** 或者 TensorFlow **tf.kears.Model**，可以像它們各自的機器學習（ML）框架中的任何其他模型一樣進行處理。
- **簡單**：當前位置整個庫幾乎沒有任何摘要。“都在一個文件中”是一個核心概念：模型的正向傳遞完全定義在一個文件中，因此代碼本身是可以理解的，並且是可以破解的。

最後一個特性使🤗 Transformers與其他ML庫截然不同。這些模型不是基於通過文件共享的模塊構建的；相反，每一個模型都有自己的網絡結構（layers）。除了使模型更加容易接受和更容易理解，這還允許你輕鬆地在一個模型上實驗，而且不影響其他模型。

本章將從一個端到端的示例開始，在該示例中，我們一起使用模型和tokenizer分詞器來複制[Chapter 1](/course/chapter1)中引入的函數 pipeline(). 接下來，我們將討論模型API：我們將深入研究模型和配置類，並向您展示如何加載模型以及如何將數值輸入處理為輸出預測。

然後我們來看看標記器API，它是 pipeline() 函數的另一個主要組件。它是作用分詞器負責第一個和最後一個處理步驟，處理從文本到神經網絡數字輸入的轉換，以及在需要時轉換回文本。最後，我們將向您展示如何處理在一個準備好的批處理中通過一個模型發送多個句子的問題，然後詳細介紹 pipeline() 函數。

<Tip>
⚠️ 為了從模型中心和 🤗Transformers 的所有可用功能中獲益，我們建議<a href="https://huggingface.co/join">creating an account</a>.
</Tip>

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter2/1.mdx" />

### 模型
https://huggingface.co/learn/course/zh-TW/chapter2/3.md

# 模型

{#if fw === 'pt'}

<CourseFloatingBanner chapter={2}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/zh-CN/chapter2/section3_pt.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/zh-CN/chapter2/section3_pt.ipynb"},
]} />

{:else}

<CourseFloatingBanner chapter={2}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/zh-CN/chapter2/section3_tf.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/zh-CN/chapter2/section3_tf.ipynb"},
]} />

{/if}

{#if fw === 'pt'}
<Youtube id="AhChOFRegn4"/>
{:else}
<Youtube id="d3JVgghSOew"/>
{/if}

{#if fw === 'pt'}
在本節中，我們將更詳細地瞭解如何創建和使用模型。我們將使用
AutoModel類，當您希望從檢查點實例化任何模型時，這非常方便。

這個AutoModel類及其所有相關項實際上是對庫中各種可用模型的簡單包裝。它是一個聰明的包裝器，因為它可以自動猜測檢查點的適當模型體系結構，然後用該體系結構實例化模型。

{:else}
在本節中，我們將更詳細地瞭解如何創建和使用模型。我們將使用
AutoModel類，當您希望從檢查點實例化任何模型時，這非常方便。

這個AutoModel類及其所有相關項實際上是對庫中各種可用模型的簡單包裝。它是一個聰明的包裝器，因為它可以自動猜測檢查點的適當模型體系結構，然後用該體系結構實例化模型。

{/if}

但是，如果您知道要使用的模型類型，則可以使用直接定義其體系結構的類。讓我們看看這是如何與BERT模型一起工作的。

## 創建轉換器

初始化BERT模型需要做的第一件事是加載配置對象：

{#if fw === 'pt'}
```py
from transformers import BertConfig, BertModel

# Building the config
config = BertConfig()

# Building the model from the config
model = BertModel(config)
```
{:else}
```py
from transformers import BertConfig, TFBertModel

# Building the config
config = BertConfig()

# Building the model from the config
model = TFBertModel(config)
```
{/if}

配置包含許多用於構建模型的屬性：

```py
print(config)
```

```python out
BertConfig {
  [...]
  "hidden_size": 768,
  "intermediate_size": 3072,
  "max_position_embeddings": 512,
  "num_attention_heads": 12,
  "num_hidden_layers": 12,
  [...]
}
```

雖然您還沒有看到所有這些屬性都做了什麼，但您應該認識到其中的一些屬性：hidden_size屬性定義了hidden_狀態向量的大小，num_hidden_layers定義了Transformer模型的層數。

### 不同的加載方式

從默認配置創建模型會使用隨機值對其進行初始化：


{#if fw === 'pt'}
```py
from transformers import BertConfig, BertModel

config = BertConfig()
model = BertModel(config)

# Model is randomly initialized!
```
{:else}
```py
from transformers import BertConfig, TFBertModel

config = BertConfig()
model = TFBertModel(config)

# Model is randomly initialized!
```
{/if}


該模型可以在這種狀態下使用，但會輸出胡言亂語；首先需要對其進行訓練。我們可以根據手頭的任務從頭開始訓練模型，但正如您在
[Chapter 1](/course/chapter1)
，這將需要很長的時間和大量的數據，並將產生不可忽視的環境影響。為了避免不必要的重複工作，必須能夠共享和重用已經訓練過的模型。


加載已經訓練過的Transformers模型很簡單-我們可以使用from_pretrained()
方法：

{#if fw === 'pt'}
```py
from transformers import BertModel

model = BertModel.from_pretrained("bert-base-cased")
```

正如您之前看到的，我們可以用等效的AutoModel類替換Bert模型。從現在開始，我們將這樣做，因為這會產生檢查點不可知的代碼；如果您的代碼適用於一個檢查點，那麼它應該與另一個檢查點無縫地工作。即使體系結構不同，這也適用，只要檢查點是針對類似任務（例如，情緒分析任務）訓練的。

{:else}
```py
from transformers import TFBertModel

model = TFBertModel.from_pretrained("bert-base-cased")
```

正如您之前看到的，我們可以用等效的AutoModel類替換Bert模型。從現在開始，我們將這樣做，因為這會產生檢查點不可知的代碼；如果您的代碼適用於一個檢查點，那麼它應該與另一個檢查點無縫地工作。即使體系結構不同，這也適用，只要檢查點是針對類似任務（例如，情緒分析任務）訓練的。

{/if}

在上面的代碼示例中，我們沒有使用BertConfig

，而是通過Bert base cased標識符加載了一個預訓練模型。這是一個模型檢查點，由BERT的作者自己訓練；您可以在
[model card](https://huggingface.co/bert-base-cased)中找到更多細節.



該模型現在使用檢查點的所有權重進行初始化。它可以直接用於對訓練過的任務進行推理，也可以對新任務進行微調。通過預先訓練重量而不是從頭開始的訓練，我們可以很快取得好的效果。



權重已下載並緩存在緩存文件夾中（因此將來對from_pretrained()方法的調用將不會重新下載它們）默認為
~/.cache/huggingface/transformers
. 您可以通過設置
HF_HOME
環境變量來自定義緩存文件夾。



用於加載模型的標識符可以是模型中心Hub上任何模型的標識符，只要它與BERT體系結構兼容。可以找到可用的BERT檢查點的完整列表
[here](https://huggingface.co/models?filter=bert)
.
### 保存模型

保存模型和加載模型一樣簡單--我們使用
save_pretrained()
方法，類似於
from_pretrained()
方法：

```py
model.save_pretrained("directory_on_my_computer")
```

這會將兩個文件保存到磁盤：

{#if fw === 'pt'}
```
ls directory_on_my_computer

config.json model.safetensors
```
{:else}
```
ls directory_on_my_computer

config.json tf_model.h5
```
{/if}

如果你看一下
config.json
文件，您將識別構建模型體系結構所需的屬性。該文件還包含一些元數據，例如檢查點的來源以及上次保存檢查點時使用的🤗 Transformers版本。

{#if fw === 'pt'}
這個 *model.safetensors* 文件就是眾所周知的*state dictionary*; 它包含模型的所有權重。這兩個文件齊頭並進；配置是瞭解模型體系結構所必需的，而模型權重是模型的參數。

{:else}
這個 *model.safetensors* 文件就是眾所周知的*state dictionary*; 它包含模型的所有權重。這兩個文件齊頭並進；配置是瞭解模型體系結構所必需的，而模型權重是模型的參數。

{/if}

### 使用Transformers模型進行推理

既然您知道了如何加載和保存模型，那麼讓我們嘗試使用它進行一些預測。Transformer模型只能處理數字——分詞器生成的數字。但在我們討論標記化器之前，讓我們先探討模型接受哪些輸入。

標記化器可以將輸入轉換為適當的框架張量，但為了幫助您瞭解發生了什麼，我們將快速瞭解在將輸入發送到模型之前必須做什麼。

假設我們有幾個序列：

```py
sequences = ["Hello!", "Cool.", "Nice!"]
```

分詞器將這些轉換為詞彙表索引，通常稱為
input IDs
. 每個序列現在都是一個數字列表！結果是：

```py no-format
encoded_sequences = [
    [101, 7592, 999, 102],
    [101, 4658, 1012, 102],
    [101, 3835, 999, 102],
]
```

這是一個編碼序列列表：一個列表列表。張量只接受矩形（想想矩陣）。此“數組”已為矩形，因此將其轉換為張量很容易：

{#if fw === 'pt'}
```py
import torch

model_inputs = torch.tensor(encoded_sequences)
```
{:else}
```py
import tensorflow as tf

model_inputs = tf.constant(encoded_sequences)
```
{/if}

### 使用張量作為模型的輸入



在模型中使用張量非常簡單-我們只需將輸入稱為模型：


```python
output = model(model_inputs)
```



雖然模型接受許多不同的參數，但只需要
input IDs。我們稍後將解釋其他參數的作用以及何時需要它們，但首先我們需要更仔細地瞭解
Transformer模型可以理解的輸入的標記


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter2/3.mdx" />

### 把它們放在一起
https://huggingface.co/learn/course/zh-TW/chapter2/6.md

# 把它們放在一起

{#if fw === 'pt'}

<CourseFloatingBanner chapter={2}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/zh-CN/chapter2/section6_pt.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/zh-CN/chapter2/section6_pt.ipynb"},
]} />

{:else}

<CourseFloatingBanner chapter={2}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/zh-CN/chapter2/section6_tf.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/zh-CN/chapter2/section6_tf.ipynb"},
]} />

{/if}

在最後幾節中，我們一直在盡最大努力手工完成大部分工作。我們探討了標記化器的工作原理，並研究了標記化、到輸入ID的轉換、填充、截斷和注意掩碼。

然而，正如我們在第2節中所看到的，🤗 Transformers API可以通過一個高級函數為我們處理所有這些，我們將在這裡深入討論。當你直接在句子上調用標記器時，你會得到準備通過模型傳遞的輸入

```py
from transformers import AutoTokenizer

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)

sequence = "I've been waiting for a HuggingFace course my whole life."

model_inputs = tokenizer(sequence)
```

這裡，`model_inputs`
變量包含模型良好運行所需的一切。對於DistilBERT，它包括輸入 ID和注意力掩碼(attention mask)。其他接受額外輸入的模型也會有標記器對象的輸出。

正如我們將在下面的一些示例中看到的，這種方法非常強大。首先，它可以標記單個序列：

```py
sequence = "I've been waiting for a HuggingFace course my whole life."

model_inputs = tokenizer(sequence)
```

它還一次處理多個序列，並且API沒有任何變化：

```py
sequences = ["I've been waiting for a HuggingFace course my whole life.", "So have I!"]

model_inputs = tokenizer(sequences)
```

它可以根據幾個目標進行填充：

```py
# Will pad the sequences up to the maximum sequence length
model_inputs = tokenizer(sequences, padding="longest")

# Will pad the sequences up to the model max length
# (512 for BERT or DistilBERT)
model_inputs = tokenizer(sequences, padding="max_length")

# Will pad the sequences up to the specified max length
model_inputs = tokenizer(sequences, padding="max_length", max_length=8)
```

它還可以截斷序列:

```py
sequences = ["I've been waiting for a HuggingFace course my whole life.", "So have I!"]

# Will truncate the sequences that are longer than the model max length
# (512 for BERT or DistilBERT)
model_inputs = tokenizer(sequences, truncation=True)

# Will truncate the sequences that are longer than the specified max length
model_inputs = tokenizer(sequences, max_length=8, truncation=True)
```

標記器對象可以處理到特定框架張量的轉換，然後可以直接發送到模型。例如，在下面的代碼示例中，我們提示標記器從不同的框架返回張量——`"pt"`返回Py Torch張量，`"tf"`返回TensorFlow張量，`"np"`返回NumPy數組：

```py
sequences = ["I've been waiting for a HuggingFace course my whole life.", "So have I!"]

# Returns PyTorch tensors
model_inputs = tokenizer(sequences, padding=True, return_tensors="pt")

# Returns TensorFlow tensors
model_inputs = tokenizer(sequences, padding=True, return_tensors="tf")

# Returns NumPy arrays
model_inputs = tokenizer(sequences, padding=True, return_tensors="np")
```

## 特殊詞符(token)

如果我們看一下標記器返回的輸入 ID，我們會發現它們與之前的略有不同：

```py
sequence = "I've been waiting for a HuggingFace course my whole life."

model_inputs = tokenizer(sequence)
print(model_inputs["input_ids"])

tokens = tokenizer.tokenize(sequence)
ids = tokenizer.convert_tokens_to_ids(tokens)
print(ids)
```

```python out
[101, 1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172, 2607, 2026, 2878, 2166, 1012, 102]
[1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172, 2607, 2026, 2878, 2166, 1012]
```

一個在開始時添加了一個標記(token) ID，一個在結束時添加了一個標記(token) ID。讓我們解碼上面的兩個ID序列，看看這是怎麼回事：

```py
print(tokenizer.decode(model_inputs["input_ids"]))
print(tokenizer.decode(ids))
```

```python out
"[CLS] i've been waiting for a huggingface course my whole life. [SEP]"
"i've been waiting for a huggingface course my whole life."
```

標記器在開頭添加了特殊單詞`[CLS]`，在結尾添加了特殊單詞`[SEP]`。這是因為模型是用這些數據預訓練的，所以為了得到相同的推理結果，我們還需要添加它們。請注意，有些模型不添加特殊單詞，或者添加不同的單詞；模型也可能只在開頭或結尾添加這些特殊單詞。在任何情況下，標記器都知道需要哪些詞符，並將為您處理這些詞符。

## 結束：從標記器到模型

現在我們已經看到了標記器對象在應用於文本時使用的所有單獨步驟，讓我們最後一次看看它如何處理多個序列（填充！），非常長的序列（截斷！），以及多種類型的張量及其主要API：

{#if fw === 'pt'}
```py
import torch
from transformers import AutoTokenizer, AutoModelForSequenceClassification

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = AutoModelForSequenceClassification.from_pretrained(checkpoint)
sequences = ["I've been waiting for a HuggingFace course my whole life.", "So have I!"]

tokens = tokenizer(sequences, padding=True, truncation=True, return_tensors="pt")
output = model(**tokens)
```
{:else}
```py
import tensorflow as tf
from transformers import AutoTokenizer, TFAutoModelForSequenceClassification

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint)
sequences = ["I've been waiting for a HuggingFace course my whole life.", "So have I!"]

tokens = tokenizer(sequences, padding=True, truncation=True, return_tensors="tf")
output = model(**tokens)
```
{/if}


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter2/6.mdx" />

### 章末小測試
https://huggingface.co/learn/course/zh-TW/chapter2/8.md

# 章末小測試

<CourseFloatingBanner
    chapter={2}
    classNames="absolute z-10 right-0 top-0"
/>

### 1. 語言建模 Pipeline 的順序是什麼？
<Question
	choices={[
		{
			text: "首先是模型它處理文本並返回原始預測。然後，分詞器會對這些預測進行解釋，並在需要時將它們轉換回文本",
			explain: "模型不能理解文本！必須首先使用標記器(Tokenizer)將文本轉換為id，之後才可以輸入給模型。"
		},
		{
			text: "首先是 標記器(Tokenizer)，它處理文本並返回id。模型根據這些 id 並輸出預測，可以是一些文本。",
			explain: "該模型的預測結果是id而不是文本。為了將預測轉換回文本，必須使用標記器(Tokenizer)！"
		},
		{
			text: "標記器(Tokenizer) 處理文本並返回 id。模型處理這些 id 並輸出一個預測。然後可以再次使用 標記器(Tokenizer) 將這些預測轉換回某些文本。",
			explain: "正確! 標記器(Tokenizer)可以用於id與文本的相互轉換。",
            correct: true
		}
	]}
/>

### 2. Transformer模型的輸出有多少個維度，每個維度分別是什麼？
<Question
	choices={[
		{
			text: "兩個維度，分別是: 序列長度(Sequence Length)和批次大小(Batch Size)",
			explain: "錯! 該模型的張量輸出還具有第三個維度: 隱藏層大小(Hidden Size)。"
		},
		{
			text: "兩個維度，分別是: 序列長度(Sequence Length)和隱藏層大小(Hidden Size)",
			explain: "錯! 所有Transformer模型都批量進行計算，即使是單個序列; 那麼批次大小(Batch Size)為1！"
		},
		{
			text: "三個維度，分別是: 序列長度(Sequence Length)、批次大小(Batch Size)和隱藏層大小(Hidden Size)",
			explain: "正確！",
            correct: true
		}
	]}
/>

### 3.下列哪一個是Subword標記(Tokenization)的例子（從分詞的顆粒度來劃分）？
<Question
	choices={[
		{
			text: "WordPiece",
			explain: "是的，這是一個Subword 標記化(Tokenization)的例子！",
            correct: true
		},
		{
			text: "基於單個字符的標記(Tokenization)",
			explain: "基於字符的標記(Tokenization)不是一種Subword 標記化(Tokenization)。"
		},
		{
			text: "基於空格和標點符號的分割",
			explain: "這是一種基於單詞標記化(Tokenization)！"
		},
		{
			text: "BPE(Byte Pair Encoding)",
			explain: "是的，這是一個Subword 標記化(Tokenization)的例子！",
            correct: true
        },
		{
			text: "Unigram",
			explain: "是的，這是一個Subword 標記化(Tokenization)的例子！",
            correct: true
        },
		{
			text: "以上都不是",
			explain: "不對！"
        }
	]}
/>

### 4.什麼是模型的 Head 層？
<Question
	choices={[
		{
			text: "原始Transformer網絡的一種組件，將張量(Tensors)重定向到它們的正確層",
			explain: "不對! 沒有這樣的組件。"
		},
		{
			text: "也稱為自注意力(self-attention)機制，它根據序列中的其他標記(Token)來調整一個標記(Token)",
			explain: "不對！ 自注意力層確實包含注意力“Head”層，但這些不是模型的head層。"
		},
		{
			text: "一個附加組件，通常由一個或幾個層組成，用於將Transformer的預測轉換為特定於任務的輸出",
			explain: "沒錯。它的全名是Adaptation Heads，也被簡單地稱為模型的頭部，在不同的任務上有不同的形式: 語言模型Heads，問題回答Heads，序列分類Heads..。",
			correct: true
		} 
	]}
/>

{#if fw === 'pt'}
### 5.什麼是AutoModel？
<Question
	choices={[
		{
			text: "根據您的數據自動進行訓練的模型",
			explain: "錯誤。您可能把AutoModel與我們的<a href='https://huggingface.co/autonlp'>AutoNLP</a> 產品相混淆了？"
		},
		{
			text: "一個根據Checkpoint(檢查點)返回模型體系結構的對象",
			explain: "確切地說: <code>AutoModel</code>只需要知道初始化的Checkpoint(檢查點)就可以返回正確的體系結構。",
			correct: true
		},
		{
			text: "一種可以自動檢測輸入語言來加載正確權重的模型",
			explain: "不正確; 雖然有些Checkpoint(檢查點)和模型能夠處理多種語言，但是沒有內置的工具可以根據語言自動選擇Checkpoint(檢查點)。您應該前往 <a href='https://huggingface.co/models'>Model Hub</a> 尋找完成所需任務的最佳Checkpoint(檢查點)！"
		} 
	]}
/>

{:else}
### 5.什麼是 TFAutoModel？
<Question
	choices={[
		{
			text: "根據您的數據自動進行訓練的模型",
			explain: "錯誤。您可能把TFAutoModel與我們的<a href='https://huggingface.co/autonlp'>AutoNLP</a> 產品相混淆了？"
		},
		{
			text: "一個根據Checkpoint(檢查點)返回模型體系結構的對象",
			explain: "確切地說: <code>TFAutoModel</code>只需要知道初始化的Checkpoint(檢查點)就可以返回正確的體系結構。",
			correct: true
		},
		{
			text: "一種可以自動檢測輸入語言來加載正確權重的模型",
			explain: "不正確; 雖然有些Checkpoint(檢查點)和模型能夠處理多種語言，但是沒有內置的工具可以根據語言自動選擇Checkpoint(檢查點)。您應該前往 <a href='https://huggingface.co/models'>Model Hub</a> 尋找完成所需任務的最佳Checkpoint(檢查點)！"
		} 
	]}
/>

{/if}

### 6.當將不同長度的序列批處理在一起時，需要進行哪些處理？
<Question
	choices={[
		{
			text: "截短",
			explain: "是的，截斷是一個正確的方式，可以將他們轉化為一個固定長度的矩形序列。這是唯一的一個答案嗎？",
			correct: true
		},
		{
			text: "返回Tensors",
			explain: "雖然其他技術允許返回矩形張量，但在批處理序列時不可以直接返回長度不一致的Tensors。(序列必須是固定長度)"
		},
		{
			text: "填充",
			explain: "是的，填充是一個正確的方式，可以將他們轉化為一個固定長度的矩形序列。這是唯一的一個答案嗎？",
			correct: true
		}, 
		{
			text: "注意力遮蔽(Attention masking)",
			explain: "當然！當處理不同長度的序列時，注意力遮蔽是最重要的。然而，這並不是唯一需要進行的處理。",
			correct: true
		} 
	]}
/>

### 7.將 SoftMax激活函數應用於序列分類(Sequence Classification)模型的 logits 輸出有什麼意義？
<Question
	choices={[
		{
			text: "它軟化了logits輸出，使結果更可靠。",
			explain: "不， SoftMax激活函數不會影響結果的可靠性。"
		},
		{
			text: "它限定了上下界，使模型的輸出結果可以被解釋。",
			explain: "正確！結果值被壓縮到了0和1之間。不過，這並不是我們使用SoftMax激活函數的唯一原因。",
            correct: true
		},
		{
			text: "輸出的和是1，從而產生一個可能的概率解釋。",
			explain: "沒錯，但這並不是我們使用SoftMax激活函數的唯一原因。",
            correct: true
		}
	]}
/>

### 8.大多數標記器(Tokenizer)的API以什麼方法為核心？
<Question
	choices={[
		{
			text: "<code>編碼</code> ，因為它可以將文本編碼為id，將預測的id解碼為文本",
			explain: "錯! 雖然 <code>編碼</code> 方法確實存在於標記器中，但是它不存在於模型中。"
		},
		{
			text: "直接調用標記器(Tokenizer)對象。",
			explain: "完全正確！標記化器(Tokenizer) 的 <code>__call__</code>方法是一個非常強大的方法，可以處理幾乎任何事情。它也是從模型中獲取預測的方法。",
			correct: true
		},
		{
			text: "<code>pad</code>(填充)",
			explain: "錯! <code>pad</code>(填充)非常有用，但它只是標記器(Tokenizer) API的一部分。"
		},
		{
			text: "<code>tokenize</code>(標記)",
			explain: "可以說，<code>tokenize</code>(標記)方法是最有用的方法之一，但它不是標記器(Tokenizer) API的核心方法。"
		}
	]}
/>

### 9.這個代碼示例中的`result`變量包含什麼？
```py
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")
result = tokenizer.tokenize("Hello!")
```

<Question
	choices={[
		{
			text: "字符串列表，每個字符串都是一個標記(Token)",
			explain: "正確! 把這個轉換成id後，就可以傳輸給模型了！",
            correct: true
		},
		{
			text: "一個ID的列表",
			explain: "不正確; 這就是 <code>__call__</code> 或 <code>convert_tokens_to_ids</code>方法的作用！"
		},
		{
			text: "包含所有標記(Token)的字符串",
			explain: "這將是次優的，因為Tokenizer會將字符串拆分為多個標記的列表。"
		}
	]}
/>

{#if fw === 'pt'}
### 10.下面的代碼有什麼錯誤嗎？
```py
from transformers import AutoTokenizer, AutoModel

tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")
model = AutoModel.from_pretrained("gpt2")

encoded = tokenizer("Hey!", return_tensors="pt")
result = model(**encoded)
```

<Question
	choices={[
		{
			text: "不，看起來是對的。",
			explain: "不幸的是，將一個模型與一個用不同Checkpoint(檢查點)訓練的Tokenizer(標記器)耦合在並不是一個好主意。模型沒有在這個這個Tokenizer(標記器)上訓練來理解Tokenizer(標記器)的輸出，因此模型輸出(如果它可以運行的話)不會有任何意義。"
		},
		{
			text: "Tokenizer(標記器)和模型應該來自相同的Checkpoint(檢查點)。",
			explain: "對！",
            correct: true
		},
		{
			text: "由於每個輸入都是一個Batch，因此可以使用標記器(Tokenizer)對其進行平移和截斷來改善這段代碼。",
			explain: "的確，每個模型都需要Batch類型的輸入。然而，截斷或填充這個序列並不一定有意義，這裡只有一句話，而這些技術是用來批處理一個句子列表的。"
		}
	]}
/>

{:else}
### 10.下面的代碼有什麼錯誤嗎？
```py
from transformers import AutoTokenizer, TFAutoModel

tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")
model = TFAutoModel.from_pretrained("gpt2")

encoded = tokenizer("Hey!", return_tensors="pt")
result = model(**encoded)
```

<Question
	choices={[
		{
			text: "不，看起來是對的。",
			explain: "不幸的是，將一個模型與一個用不同Checkpoint(檢查點)訓練的Tokenizer(標記器)耦合在並不是一個好主意。模型沒有在這個這個Tokenizer(標記器)上訓練來理解Tokenizer(標記器)的輸出，因此模型輸出(如果它可以運行的話)不會有任何意義。"
		},
		{
			text: "Tokenizer(標記器)和模型應該來自相同的Checkpoint(檢查點)。",
			explain: "對！",
            correct: true
		},
		{
			text: "由於每個輸入都是一個Batch，因此可以使用標記器(Tokenizer)對其進行平移和截斷來改善這段代碼。",
			explain: "的確，每個模型都需要Batch類型的輸入。然而，截斷或填充這個序列並不一定有意義，這裡只有一句話，而這些技術是用來批處理一個句子列表的。"
		}
	]}
/>

{/if}


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter2/8.mdx" />

### 標記器（Tokenizer）
https://huggingface.co/learn/course/zh-TW/chapter2/4.md

# 標記器（Tokenizer）

{#if fw === 'pt'}

<CourseFloatingBanner chapter={2}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/zh-CN/chapter2/section4_pt.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/zh-CN/chapter2/section4_pt.ipynb"},
]} />

{:else}

<CourseFloatingBanner chapter={2}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/zh-CN/chapter2/section4_tf.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/zh-CN/chapter2/section4_tf.ipynb"},
]} />

{/if}

<Youtube id="VFp38yj8h3A"/>

標記器(Tokenizer)是 NLP 管道的核心組件之一。它們有一個目的：將文本轉換為模型可以處理的數據。模型只能處理數字，因此標記器(Tokenizer)需要將我們的文本輸入轉換為數字數據。在本節中，我們將確切地探討標記化管道中發生的事情。

在 NLP 任務中，通常處理的數據是原始文本。這是此類文本的示例

```
Jim Henson was a puppeteer
```

但是，模型只能處理數字，因此我們需要找到一種將原始文本轉換為數字的方法。這就是標記器（tokenizer）所做的，並且有很多方法可以解決這個問題。目標是找到最有意義的表示——即對模型最有意義的表示——並且如果可能的話，找到最小的表示。

讓我們看一下標記化算法的一些示例，並嘗試回答您可能對標記化提出的一些問題。

## 基於詞的(Word-based)

<Youtube id="nhJxYji1aho"/>

想到的第一種標記器是基於詞的(_word-based_).它通常很容易設置和使用，只需幾條規則，並且通常會產生不錯的結果。例如，在下圖中，目標是將原始文本拆分為單詞併為每個單詞找到一個數字表示：

<div class="flex justify-center">
  <img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/word_based_tokenization.svg" alt="An example of word-based tokenization."/>
  <img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/word_based_tokenization-dark.svg" alt="An example of word-based tokenization."/>
</div>

有多種方法可以拆分文本。例如，我們可以通過應用Python的`split()`函數，使用空格將文本標記為單詞：

```py
tokenized_text = "Jim Henson was a puppeteer".split()
print(tokenized_text)
```

```python out
['Jim', 'Henson', 'was', 'a', 'puppeteer']
```

還有一些單詞標記器的變體，它們具有額外的標點符號規則。使用這種標記器，我們最終可以得到一些非常大的“詞彙表”，其中詞彙表由我們在語料庫中擁有的獨立標記的總數定義。

每個單詞都分配了一個 ID，從 0 開始一直到詞彙表的大小。該模型使用這些 ID 來識別每個單詞。

如果我們想用基於單詞的標記器(tokenizer)完全覆蓋一種語言，我們需要為語言中的每個單詞都有一個標識符，這將生成大量的標記。例如，英語中有超過 500,000 個單詞，因此要構建從每個單詞到輸入 ID 的映射，我們需要跟蹤這麼多 ID。此外，像“dog”這樣的詞與“dogs”這樣的詞的表示方式不同，模型最初無法知道“dog”和“dogs”是相似的：它會將這兩個詞識別為不相關。這同樣適用於其他相似的詞，例如“run”和“running”，模型最初不會認為它們是相似的。

最後，我們需要一個自定義標記(token)來表示不在我們詞彙表中的單詞。這被稱為“未知”標記(token)，通常表示為“[UNK]”或"&lt;unk&gt;"。如果你看到標記器產生了很多這樣的標記，這通常是一個不好的跡象，因為它無法檢索到一個詞的合理表示，並且你會在這個過程中丟失信息。製作詞彙表時的目標是以這樣一種方式進行，即標記器將盡可能少的單詞標記為未知標記。

減少未知標記數量的一種方法是使用更深一層的標記器(tokenizer)，即基於字符的(_character-based_)標記器(tokenizer)。

## 基於字符(Character-based)

<Youtube id="ssLq_EK2jLE"/>

基於字符的標記器(tokenizer)將文本拆分為字符，而不是單詞。這有兩個主要好處：

- 詞彙量要小得多。
- 詞彙外（未知）標記(token)要少得多，因為每個單詞都可以從字符構建。

但是這裡也出現了一些關於空格和標點符號的問題：

<div class="flex justify-center">
  <img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/character_based_tokenization.svg" alt="An example of character-based tokenization."/>
  <img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/character_based_tokenization-dark.svg" alt="An example of character-based tokenization."/>
</div>

這種方法也不是完美的。由於現在表示是基於字符而不是單詞，因此人們可能會爭辯說，從直覺上講，它的意義不大：每個字符本身並沒有多大意義，而單詞就是這種情況。然而，這又因語言而異；例如，在中文中，每個字符比拉丁語言中的字符包含更多的信息。

另一件要考慮的事情是，我們的模型最終會處理大量的詞符(token)：雖然使用基於單詞的標記器(tokenizer)，單詞只會是單個標記，但當轉換為字符時，它很容易變成 10 個或更多的詞符(token)。

為了兩全其美，我們可以使用結合這兩種方法的第三種技術：*子詞標記化(subword tokenization)*。

## 子詞標記化

<Youtube id="zHvTiHr506c"/>

子詞分詞算法依賴於這樣一個原則，即不應將常用詞拆分為更小的子詞，而應將稀有詞分解為有意義的子詞。

例如，“annoyingly”可能被認為是一個罕見的詞，可以分解為“annoying”和“ly”。這兩者都可能作為獨立的子詞出現得更頻繁，同時“annoyingly”的含義由“annoying”和“ly”的複合含義保持。

這是一個示例，展示了子詞標記化算法如何標記序列“Let's do tokenization!”：

<div class="flex justify-center">
  <img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/bpe_subword.svg" alt="A subword tokenization algorithm."/>
  <img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/bpe_subword-dark.svg" alt="A subword tokenization algorithm."/>
</div>

這些子詞最終提供了很多語義含義：例如，在上面的示例中，“tokenization”被拆分為“token”和“ization”，這兩個具有語義意義同時節省空間的詞符(token)（只需要兩個標記(token)代表一個長詞）。這使我們能夠對較小的詞彙表進行相對較好的覆蓋，並且幾乎沒有未知的標記

這種方法在土耳其語等粘著型語言(agglutinative languages)中特別有用，您可以通過將子詞串在一起來形成（幾乎）任意長的複雜詞。

### 還有更多！

不出所料，還有更多的技術。僅舉幾例：

- Byte-level BPE, 用於 GPT-2
- WordPiece, 用於 BERT
- SentencePiece or Unigram, 用於多個多語言模型

您現在應該對標記器(tokenizers)的工作原理有足夠的瞭解，以便開始使用 API。

## 加載和保存

加載和保存標記器(tokenizer)就像使用模型一樣簡單。實際上，它基於相同的兩種方法： `from_pretrained()` 和 `save_pretrained()` 。這些方法將加載或保存標記器(tokenizer)使用的算法（有點像*建築學(architecture)*的模型）以及它的詞彙（有點像*權重(weights)*模型）。

加載使用與 BERT 相同的檢查點訓練的 BERT 標記器(tokenizer)與加載模型的方式相同，除了我們使用  `BertTokenizer` 類：

```py
from transformers import BertTokenizer

tokenizer = BertTokenizer.from_pretrained("bert-base-cased")
```

{#if fw === 'pt'}
如同 `AutoModel`，`AutoTokenizer` 類將根據檢查點名稱在庫中獲取正確的標記器(tokenizer)類，並且可以直接與任何檢查點一起使用：

{:else}
如同 `TFAutoModel`，  `AutoTokenizer` 類將根據檢查點名稱在庫中獲取正確的標記器(tokenizer)類，並且可以直接與任何檢查點一起使用：

{/if}

```py
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")
```

我們現在可以使用標記器(tokenizer)，如上一節所示：

```python
tokenizer("Using a Transformer network is simple")
```

```python out
{'input_ids': [101, 7993, 170, 11303, 1200, 2443, 1110, 3014, 102],
 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0],
 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1]}
```

保存標記器(tokenizer)與保存模型相同:

```py
tokenizer.save_pretrained("directory_on_my_computer")
```

我們在[Chapter 3](/Couse/chapter3)中將更多地談論`token_type_ids`，稍後我們將解釋 `attention_mask` 鍵。首先，讓我們看看 `input_ids` 如何生成。為此，我們需要查看標記器(tokenizer)的中間方法。

## 編碼

<Youtube id="Yffk5aydLzg"/>

將文本翻譯成數字被稱為編碼(_encoding_).編碼分兩步完成：標記化，然後轉換為輸入 ID。

正如我們所見，第一步是將文本拆分為單詞（或單詞的一部分、標點符號等），通常稱為*標記(token)*。有多個規則可以管理該過程，這就是為什麼我們需要使用模型名稱來實例化標記器(tokenizer)，以確保我們使用模型預訓練時使用的相同規則。

第二步是將這些標記轉換為數字，這樣我們就可以用它們構建一個張量並將它們提供給模型。為此，標記器(tokenizer)有一個*詞彙(vocabulary)*，這是我們在實例化它時下載的部分 `from_pretrained()` 方法。同樣，我們需要使用模型預訓練時使用的相同詞彙。

為了更好地理解這兩個步驟，我們將分別探討它們。請注意，我們將使用一些單獨執行部分標記化管道的方法來向您展示這些步驟的中間結果，但實際上，您應該直接在您的輸入上調用標記器(tokenizer)（如第 2 部分所示）。

### 標記化

標記化過程由標記器(tokenizer)的`tokenize()` 方法實現：

```py
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")

sequence = "Using a Transformer network is simple"
tokens = tokenizer.tokenize(sequence)

print(tokens)
```

此方法的輸出是一個字符串列表或標記(token)：

```python out
['Using', 'a', 'transform', '##er', 'network', 'is', 'simple']
```

這個標記器(tokenizer)是一個子詞標記器(tokenizer)：它對詞進行拆分，直到獲得可以用其詞彙表表示的標記(token)。`transformer` 就是這種情況，它分為兩個標記：`transform` 和 `##er`。

### 從詞符(token)到輸入 ID
輸入 ID 的轉換由標記器(tokenizer)的`convert_tokens_to_ids()`方法實現：

```py
ids = tokenizer.convert_tokens_to_ids(tokens)

print(ids)
```

```python out
[7993, 170, 11303, 1200, 2443, 1110, 3014]
```

這些輸出一旦轉換為適當的框架張量，就可以用作模型的輸入，如本章前面所見。

<Tip>

✏️ **試試看！** 在我們在第 2 節中使用的輸入句子（“I've been waiting for a HuggingFace course my whole life.”和“I hate this so much!”）複製最後兩個步驟（標記化和轉換為輸入 ID）。檢查您獲得的輸入 ID 是否與我們之前獲得的相同！

</Tip>

## 解碼

*解碼(Decoding)* 正好相反：從詞彙索引中，我們想要得到一個字符串。這可以通過  `decode()` 方法實現，如下：

```py
decoded_string = tokenizer.decode([7993, 170, 11303, 1200, 2443, 1110, 3014])
print(decoded_string)
```

```python out
'Using a Transformer network is simple'
```

請注意， `decode` 方法不僅將索引轉換回標記(token)，還將屬於相同單詞的標記(token)組合在一起以生成可讀的句子。當我們使用預測新文本的模型（根據提示生成的文本，或序列到序列問題（如翻譯或摘要））時，這種行為將非常有用。

到現在為止，您應該瞭解標記器(tokenizer)可以處理的原子操作：標記化、轉換為 ID 以及將 ID 轉換回字符串。然而，我們只是刮到了冰山一角。在下一節中，我們將採用我們的方法來克服它的限制，並看看如何克服它們。


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter2/4.mdx" />

### 基本用法完成！
https://huggingface.co/learn/course/zh-TW/chapter2/7.md

# 基本用法完成！

<CourseFloatingBanner
    chapter={2}
    classNames="absolute z-10 right-0 top-0"
/>

很好地完成了到這裡的課程！總而言之，在本章中，您可以：

- 學習了Transformers模型的基本構造塊。


- 瞭解了標記化管道的組成。


- 瞭解瞭如何在實踐中使用Transformers模型。


- 學習瞭如何利用分詞器將文本轉換為模型可以理解的張量。


- 將分詞器和模型一起設置，以從文本到預測。


- 瞭解了inputs IDs的侷限性，並瞭解了attention mask。


- 使用多功能和可配置的分詞器方法。



從現在起，您應該能夠自由瀏覽🤗 Transformers文檔：詞彙聽起來很熟悉，並且您已經看到了大部分時間將使用的方法。


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter2/7.mdx" />

### 管道的內部
https://huggingface.co/learn/course/zh-TW/chapter2/2.md

# 管道的內部

{#if fw === 'pt'}

<CourseFloatingBanner chapter={2}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/zh-CN/chapter2/section2_pt.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/zh-CN/chapter2/section2_pt.ipynb"},
]} />

{:else}

<CourseFloatingBanner chapter={2}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/zh-CN/chapter2/section2_tf.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/zh-CN/chapter2/section2_tf.ipynb"},
]} />

{/if}

<Tip>
這是第一部分，根據您使用 PyTorch 或者 TensorFlow，內容略有不同。點擊標題上方的平臺，選一個您喜歡的吧！
</Tip>

{#if fw === 'pt'}
<Youtube id="1pedAIvTWXk"/>
{:else}
<Youtube id="wVN12smEvqg"/>
{/if}

讓我們從一個完整的示例開始，看看在[Chapter 1](/course/chapter1)中執行以下代碼時在幕後發生了什麼

```python
from transformers import pipeline

classifier = pipeline("sentiment-analysis")
classifier(
    [
        "I've been waiting for a HuggingFace course my whole life.",
        "I hate this so much!",
    ]
)
```

獲得:

```python out
[{'label': 'POSITIVE', 'score': 0.9598047137260437},
 {'label': 'NEGATIVE', 'score': 0.9994558095932007}]
```

正如我們在[Chapter 1](/course/chapter1)中看到的，此管道將三個步驟組合在一起：預處理、通過模型傳遞輸入和後處理：

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/full_nlp_pipeline.svg" alt="The full NLP pipeline: tokenization of text, conversion to IDs, and inference through the Transformer model and the model head."/>
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/full_nlp_pipeline-dark.svg" alt="The full NLP pipeline: tokenization of text, conversion to IDs, and inference through the Transformer model and the model head."/>
</div>

讓我們快速瀏覽一下這些內容。

## 使用分詞器進行預處理

與其他神經網絡一樣，Transformer 模型無法直接處理原始文本， 因此我們管道的第一步是將文本輸入轉換為模型能夠理解的數字。 為此，我們使用*tokenizer*(標記器)，負責：

- 將輸入拆分為單詞、子單詞或符號（如標點符號），稱為標記(*token*)
- 將每個標記(token)映射到一個整數
- 添加可能對模型有用的其他輸入

所有這些預處理都需要以與模型預訓練時完全相同的方式完成，因此我們首先需要從[Model Hub](https://huggingface.co/models)中下載這些信息。為此，我們使用`AutoTokenizer`類及其`from_pretrained()`方法。使用我們模型的檢查點名稱，它將自動獲取與模型的標記器相關聯的數據，並對其進行緩存（因此只有在您第一次運行下面的代碼時才會下載）。

因為`sentiment-analysis`（情緒分析）管道的默認檢查點是`distilbert-base-uncased-finetuned-sst-2-english`（你可以看到它的模型卡[here](https://huggingface.co/distilbert-base-uncased-finetuned-sst-2-english))，我們運行以下程序：

```python
from transformers import AutoTokenizer

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
```

一旦我們有了標記器，我們就可以直接將我們的句子傳遞給它，然後我們就會得到一本字典，它可以提供給我們的模型！剩下要做的唯一一件事就是將輸入ID列表轉換為張量。

您可以使用🤗 Transformers，而不必擔心哪個 ML 框架被用作後端；它可能是 PyTorch 或 TensorFlow，或 Flax。但是，Transformers型號只接受*張量*作為輸入。如果這是你第一次聽說張量，你可以把它們想象成NumPy數組。NumPy數組可以是標量（0D）、向量（1D）、矩陣（2D）或具有更多維度。它實際上是張量；其他 ML 框架的張量行為類似，通常與 NumPy 數組一樣易於實例化。

要指定要返回的張量類型（PyTorch、TensorFlow 或 plain NumPy），我們使用`return_tensors`參數：

{#if fw === 'pt'}
```python
raw_inputs = [
    "I've been waiting for a HuggingFace course my whole life.",
    "I hate this so much!",
]
inputs = tokenizer(raw_inputs, padding=True, truncation=True, return_tensors="pt")
print(inputs)
```
{:else}
```python
raw_inputs = [
    "I've been waiting for a HuggingFace course my whole life.",
    "I hate this so much!",
]
inputs = tokenizer(raw_inputs, padding=True, truncation=True, return_tensors="tf")
print(inputs)
```
{/if}

現在不要擔心填充和截斷；我們稍後會解釋這些。這裡要記住的主要事情是，您可以傳遞一個句子或一組句子，還可以指定要返回的張量類型（如果沒有傳遞類型，您將得到一組列表）。

{#if fw === 'pt'}

以下是PyTorch張量的結果：

```python out
{
    'input_ids': tensor([
        [  101,  1045,  1005,  2310,  2042,  3403,  2005,  1037, 17662, 12172, 2607,  2026,  2878,  2166,  1012,   102],
        [  101,  1045,  5223,  2023,  2061,  2172,   999,   102,     0,     0,     0,     0,     0,     0,     0,     0]
    ]), 
    '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, 0, 0, 0, 0, 0, 0, 0, 0, 0]
    ])
}
```
{:else}

以下是 TensorFlow 張量的結果：

```python out
{
    'input_ids': <tf.Tensor: shape=(2, 16), dtype=int32, numpy=
        array([
            [  101,  1045,  1005,  2310,  2042,  3403,  2005,  1037, 17662, 12172,  2607,  2026,  2878,  2166,  1012,   102],
            [  101,  1045,  5223,  2023,  2061,  2172,   999,   102,     0,     0,     0,     0,     0,     0,     0,     0]
        ], dtype=int32)>, 
    'attention_mask': <tf.Tensor: shape=(2, 16), dtype=int32, numpy=
        array([
            [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
            [1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]
        ], dtype=int32)>
}
```
{/if}

輸出本身是一個包含兩個鍵的字典，`input_ids`和`attention_mask`。`input_ids`包含兩行整數（每個句子一行），它們是每個句子中標記的唯一標記（token）。我們將在本章後面解釋什麼是`attention_mask`。

## 瀏覽模型

{#if fw === 'pt'}
我們可以像使用標記器一樣下載預訓練模型。🤗 Transformers提供了一個`AutoModel`類，該類還具有`from_pretrained()`方法：

```python
from transformers import AutoModel

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
model = AutoModel.from_pretrained(checkpoint)
```
{:else}
我們可以像使用標記器一樣下載預訓練模型。🤗 Transformers提供了一個`TFAutoModel`類，該類還具有`from_pretrained()`方法：

```python
from transformers import TFAutoModel

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
model = TFAutoModel.from_pretrained(checkpoint)
```
{/if}

在這個代碼片段中，我們下載了之前在管道中使用的相同檢查點（它實際上應該已經被緩存），並用它實例化了一個模型。

這個架構只包含基本轉換器模塊：給定一些輸入，它輸出我們將調用的內容*隱藏狀態（hidden states）*，亦稱*特徵（features）*。對於每個模型輸入，我們將檢索一個高維向量，表示**Transformer模型對該輸入的上下文理解**。 

如果這不合理，不要擔心。我們以後再解釋。

雖然這些隱藏狀態本身可能很有用，但它們通常是模型另一部分（稱為*頭部(head)*）的輸入。 在[Chapter 1](/course/chapter1)中，可以使用相同的體系結構執行不同的任務，但這些任務中的每個任務都有一個與之關聯的不同頭。

### 高維向量？

Transformers 模塊的向量輸出通常較大。它通常有三個維度：

- **Batch size**: 一次處理的序列數（在我們的示例中為2）。
- **Sequence length**: 序列的數值表示的長度（在我們的示例中為16）。
- **Hidden size**: 每個模型輸入的向量維度。

由於最後一個值，它被稱為「高維」。隱藏的大小可能非常大（768通常用於較小的型號，而在較大的型號中，這可能達到3072或更大）。

如果我們將預處理的輸入輸入到模型中，我們可以看到這一點：

{#if fw === 'pt'}
```python
outputs = model(**inputs)
print(outputs.last_hidden_state.shape)
```

```python out
torch.Size([2, 16, 768])
```
{:else}
```py
outputs = model(inputs)
print(outputs.last_hidden_state.shape)
```

```python out
(2, 16, 768)
```
{/if}

注意🤗 Transformers 模型的輸出與`namedtuple`或詞典相似。您可以通過屬性（就像我們所做的那樣）或鍵（`輸出["last_hidden_state"]`）訪問元素，甚至可以通過索引訪問元素，前提是您確切知道要查找的內容在哪裡（`outputs[0]`）。

### 模型頭：數字的意義

模型頭將隱藏狀態的高維向量作為輸入，並將其投影到不同的維度。它們通常由一個或幾個線性層組成：


<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/transformer_and_head.svg" alt="A Transformer network alongside its head."/>
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/transformer_and_head-dark.svg" alt="A Transformer network alongside its head."/>
</div>

Transformers 模型的輸出直接發送到模型頭進行處理。

在此圖中，模型由其嵌入層和後續層表示。嵌入層將標記化輸入中的每個輸入ID轉換為表示關聯標記(token)的向量。後續層使用注意機制操縱這些向量，以生成句子的最終表示。


🤗 Transformers中有許多不同的體系結構，每種體系結構都是圍繞處理特定任務而設計的。以下是一個非詳盡的列表：

- `*Model` (retrieve the hidden states)
- `*ForCausalLM`
- `*ForMaskedLM`
- `*ForMultipleChoice`
- `*ForQuestionAnswering`
- `*ForSequenceClassification`
- `*ForTokenClassification`
- 以及其他 🤗

{#if fw === 'pt'}
對於我們的示例，我們需要一個帶有序列分類頭的模型（能夠將句子分類為肯定或否定）。因此，我們實際上不會使用`AutoModel`類，而是使用`AutoModelForSequenceClassification`：

```python
from transformers import AutoModelForSequenceClassification

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
model = AutoModelForSequenceClassification.from_pretrained(checkpoint)
outputs = model(**inputs)
```
{:else}
For our example, we will need a model with a sequence classification head (to be able to classify the sentences as positive or negative). So, we won't actually use the `TFAutoModel` class, but `TFAutoModelForSequenceClassification`:

```python
from transformers import TFAutoModelForSequenceClassification

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint)
outputs = model(inputs)
```
{/if}

現在，如果我們觀察輸入的形狀，維度將低得多：模型頭將我們之前看到的高維向量作為輸入，並輸出包含兩個值的向量（每個標籤一個）：

```python
print(outputs.logits.shape)
```

{#if fw === 'pt'}

```python out
torch.Size([2, 2])
```

{:else}

```python out
(2, 2)
```

{/if}

因為我們只有兩個句子和兩個標籤，所以我們從模型中得到的結果是2 x 2的形狀。

## 對輸出進行後處理

我們從模型中得到的輸出值本身並不一定有意義。我們來看看,

```python
print(outputs.logits)
```

{#if fw === 'pt'}
```python out
tensor([[-1.5607,  1.6123],
        [ 4.1692, -3.3464]], grad_fn=<AddmmBackward>)
```
{:else}
```python out
<tf.Tensor: shape=(2, 2), dtype=float32, numpy=
    array([[-1.5606991,  1.6122842],
           [ 4.169231 , -3.3464472]], dtype=float32)>
```
{/if}

我們的模型預測第一句為`[-1.5607, 1.6123]`，第二句為`[ 4.1692, -3.3464]`。這些不是概率，而是*logits*，即模型最後一層輸出的原始非標準化分數。要轉換為概率，它們需要經過[SoftMax](https://en.wikipedia.org/wiki/Softmax_function)層（所有🤗Transformers模型輸出logits，因為用於訓練的損耗函數通常會將最後的激活函數（如SoftMax）與實際損耗函數（如交叉熵）融合）：

{#if fw === 'pt'}
```py
import torch

predictions = torch.nn.functional.softmax(outputs.logits, dim=-1)
print(predictions)
```
{:else}
```py
import tensorflow as tf

predictions = tf.math.softmax(outputs.logits, axis=-1)
print(predictions)
```
{/if}

{#if fw === 'pt'}
```python out
tensor([[4.0195e-02, 9.5980e-01],
        [9.9946e-01, 5.4418e-04]], grad_fn=<SoftmaxBackward>)
```
{:else}
```python out
tf.Tensor(
[[4.01951671e-02 9.59804833e-01]
 [9.9945587e-01 5.4418424e-04]], shape=(2, 2), dtype=float32)
```
{/if}

現在我們可以看到，模型預測第一句為`[0.0402, 0.9598]`，第二句為`[0.9995,  0.0005]`。這些是可識別的概率分數。

為了獲得每個位置對應的標籤，我們可以檢查模型配置的`id2label`屬性（下一節將對此進行詳細介紹）：

```python
model.config.id2label
```

```python out
{0: 'NEGATIVE', 1: 'POSITIVE'}
```

現在我們可以得出結論，該模型預測了以下幾點：
 
- 第一句：否定：0.0402，肯定：0.9598
- 第二句：否定：0.9995，肯定：0.0005

我們已經成功地複製了管道的三個步驟：使用標記化器進行預處理、通過模型傳遞輸入以及後處理！現在，讓我們花一些時間深入瞭解這些步驟中的每一步。

<Tip>

✏️ **試試看！** 選擇兩個（或更多）你自己的文本並在管道中運行它們。然後自己複製在這裡看到的步驟，並檢查是否獲得相同的結果！

</Tip>


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter2/2.mdx" />

### 課程簡介
https://huggingface.co/learn/course/zh-TW/chapter0/1.md

# 課程簡介

歡迎來到 Hugging Face 的教學！本篇介紹將會帶著你設置運行環境。如果你正開始學的話，不妨先看看[第一章](/course/chapter1)再回來，這樣就能直接開始試著執行裡面的程式碼了。

我們會用到的所有函式庫都將會以 Python 資源包的方式被取得，所以這邊我們會教你如何設置 Python 環境並安裝你所需要的函式庫。

本篇將會涵蓋兩種設置環境的方法 - 使用 Colab notebook 或是 Python 虛擬環境。選你自己覺得合適的方式就好，但是對於初學者我們強烈推薦先從使用 Colab notebook 開始。

我們不會提到 Windows 系統，如果你是 Windows 的使用者，我們建議使用 Colab notebook。如果你用的是 Linux 或是 macOS，你可以任意選擇上述的兩種方法。

大部分的教學都會需要一個Hugging Face的帳號。我們建議現在就[創一個](https://huggingface.co/join)。

## 使用Google Colab notebook

用 Colab notebook 是最簡單容易的方法；在瀏覽器開一頁 Colab notebook 就能直接開始寫程式了！

如果你對 Colab notebook 不熟悉的話，我們建議你從[這篇介紹](https://colab.research.google.com/notebooks/intro.ipynb)開始。在 Colab 上你可以使用一些加速硬體，像是 GPU 或 TPU，而且工作量不大的話也不收費。

當你開始熟悉 Colab 後，建立新的筆記本然後開始進行設置：

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter0/new_colab.png" alt="An empty colab notebook" width="80%"/>
</div>

接下來就是安裝我們將會用到的函式庫。我們會使用 `pip` 這個Python的資源管理工具來安裝。在Colab notebook裡，你可以用 `!` 來執行系統指令，所以你可以用以下的指令來安裝 🤗 Transformers 函式庫：

```
!pip install transformers
```

把函式庫導入到 Python runtime 可以確認你的資源包有被正確地安裝：

```
import transformers
```

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter0/install.gif" alt="A gif showing the result of the two commands above: installation and import" width="80%"/>
</div>

這會安裝一個非常輕量的 🤗 Transformers。裡面沒有安裝任何像是 PyTorch 或 TensorFlow 等的機器學習框架。因為我們會用到很多函式庫裡的不同功能，所以我們建議安裝包含了大部分使用情境所需資源的開發用版本：


```
!pip install transformers[sentencepiece]
```

這會花一點時間，不過裝完你就已經完全準備好面對剩下的教學了！


## 使用Python虛擬環境

如果你比較想用 Python 虛擬環境的話，第一步就是安裝 Python。我們建議跟著[這篇教學](https://realpython.com/installing-python/)做為起手式。


當你安裝好 Python 後，你應該就能從終端機執行 Python 指令了。在進行下一步之前你可以先執行以下指令來確認 Python 有沒有安裝好：`python --version` 這條指令會讓終端機顯示你所安裝的 Python 版本。


在終端機執行像是`python --version`的 Python 指令時，你應該把你的指令想成是用你系統上主要的 Python 版本來執行。我們建議不要在這個版本上安裝任何資源包，讓每個專案在各自獨立的環境裡運行就可以了。這樣每個專案都可以有各自的相依性跟資源包，你也不用擔心不同專案之間使用同一個環境時潛在的相容性問題。


在 Python 我們可以用[*虛擬環境*](https://docs.python.org/3/tutorial/venv.html)來做這件事。虛擬環境是一個獨立包裝的樹狀目錄，每一個目錄下都有安裝特定版本的Python跟它需要的所有資源包。創建這樣的虛擬環境可以用很多不同的工具，不過我們會用一個叫做[`venv`](https://docs.python.org/3/library/venv.html#module-venv)的Python官方資源包。

首先，創建你希望你的程式執行時所在的目錄 - 舉例來說，你可能想要在你的家目錄下新增一個叫*transformers-course*的目錄：

```
mkdir ~/transformers-course
cd ~/transformers-course
```
從這個目錄裡面，你可以用Python的`venv`模組建立一個虛擬環境：

```
python -m venv .env
```
你現在應該在你的空資料夾裡找到一個叫做*.env*的目錄，這個目錄就是你的虛擬環境。
You should now have a directory called *.env* in your otherwise empty folder:

```
ls -a
```

```out
.      ..    .env
```
你可用 `activate` 和 `deactivate` 這兩個腳本來啟用或關閉你的虛擬環境：

```
# Activate the virtual environment
source .env/bin/activate

# Deactivate the virtual environment
deactivate
```
你可以執行 `which python` 指令來確認你的虛擬環境是否有被啟用：如果它指向虛擬環境的目錄，那表示你的虛擬環境已經啟用了！

```
which python
```

```out
/home/<user>/transformers-course/.env/bin/python
```

### 安裝相依性資源包

在之前的段落中提到的使用 Google Colab 的情況裡，你會需要安裝相依性資源包才能繼續。你可以用 `pip` 這個資源管理工具來安裝開發版的🤗 Transformers：

```
pip install "transformers[sentencepiece]"
```
你現在已經準備就緒，可以開始了！


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter0/1.mdx" />

### 總結
https://huggingface.co/learn/course/zh-TW/chapter1/9.md

# 總結

<CourseFloatingBanner
    chapter={1}
    classNames="absolute z-10 right-0 top-0"
/>

在本章中，您瞭解瞭如何使用來自🤗Transformers 的函數 pipeline() 處理不同的 NLP 任務。您還了解了如何在模型中心（hub）中搜索和使用模型，以及如何使用推理API直接在瀏覽器中測試模型。

我們討論了Transformer模型如何在應用層上工作，並討論了遷移學習和微調的重要性。您可以使用完整的體系結構，也可以僅使用編碼器或解碼器，具體取決於您要解決的任務類型。下表總結了這一點：

|  模型   | 示例  | 任務|
|  ----  | ----  |----|
| 編碼器  | ALBERT, BERT, DistilBERT, ELECTRA, RoBERTa	|句子分類、命名實體識別、從文本中提取答案|
| 解碼器  | CTRL, GPT, GPT-2, Transformer XL	 |文本生成|
| 編碼器-解碼器  | BART, T5, Marian, mBART	 |文本摘要、翻譯、生成問題的回答|

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter1/9.mdx" />

### “編碼器”模型
https://huggingface.co/learn/course/zh-TW/chapter1/5.md

# “編碼器”模型

<CourseFloatingBanner
    chapter={1}
    classNames="absolute z-10 right-0 top-0"
/>

<Youtube id="MUqNwgPjJvQ" />

“編碼器”模型指僅使用編碼器的Transformer模型。在每個階段，注意力層都可以獲取初始句子中的所有單詞。這些模型通常具有“雙向”注意力，被稱爲自編碼模型。

這些模型的預訓練通常圍繞着以某種方式破壞給定的句子（例如：通過隨機遮蓋其中的單詞），並讓模型尋找或重建給定的句子。

“編碼器”模型最適合於需要理解完整句子的任務，例如：句子分類、命名實體識別（以及更普遍的單詞分類）和閱讀理解後回答問題。

該系列模型的典型代表有：

- [ALBERT](https://huggingface.co/transformers/model_doc/albert.html)
- [BERT](https://huggingface.co/transformers/model_doc/bert.html)
- [DistilBERT](https://huggingface.co/transformers/model_doc/distilbert.html)
- [ELECTRA](https://huggingface.co/transformers/model_doc/electra.html)
- [RoBERTa](https://huggingface.co/transformers/model_doc/roberta.html)


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter1/5.mdx" />

### 本章簡介
https://huggingface.co/learn/course/zh-TW/chapter1/1.md

# 本章簡介

<CourseFloatingBanner
    chapter={1}
    classNames="absolute z-10 right-0 top-0"
/>

## 歡迎來到🤗教學

<Youtube id="00GKzGyWFEs" />

本教學將使用 Hugging Face 生態系統中的庫——🤗 Transformers、🤗 Datasets、🤗 Tokenizers 和 🤗 Accelerate——以及 Hugging Face Hub 教你自然語言處理 (NLP)。它是完全免費的，並且沒有廣告。


## 有什麼是值得期待的？

以下是課程的簡要概述：

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/summary.svg" alt="Brief overview of the chapters of the course."/>
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/summary-dark.svg" alt="Brief overview of the chapters of the course."/>
</div>

- 第 1 章到第 4 章介紹了 🤗 Transformers 庫的主要概念。在本課程的這一部分結束時，您將熟悉 Transformer 模型的工作原理，並將瞭解如何使用 [Hugging Face Hub](https://huggingface.co/models) 中的模型，在數據集上對其進行微調，並在 Hub 上分享您的結果。
- 第 5 章到第 8 章在深入研究經典 NLP 任務之前，教授 🤗 Datasets和 🤗 Tokenizers的基礎知識。在本部分結束時，您將能夠自己解決最常見的 NLP 問題。
- 第 9 章到第 12 章更加深入，探討瞭如何使用 Transformer 模型處理語音處理和計算機視覺中的任務。在此過程中，您將學習如何構建和分享模型，並針對生產環境對其進行優化。在這部分結束時，您將準備好將🤗 Transformers 應用於（幾乎）任何機器學習問題！

這個課程：

* 需要良好的 Python 知識 
* 最好先學習深度學習入門課程，例如[DeepLearning.AI](https://www.deeplearning.ai/) 提供的 [fast.ai實用深度學習教程](https://course.fast.ai/) 
* 不需要事先具備 [PyTorch](https://pytorch.org/) 或 [TensorFlow](https://www.tensorflow.org/) 知識，雖然熟悉其中任何一個都會對huggingface的學習有所幫助

完成本課程後，我們建議您查看 [DeepLearning.AI的自然語言處理系列課程](https://www.coursera.org/specializations/natural-language-processing?utm_source=deeplearning-ai&utm_medium=institutions&utm_campaign=20211011-nlp-2-hugging_face-page-nlp-refresh)，其中涵蓋了廣泛的傳統 NLP 模型，如樸素貝葉斯和 LSTM，這些模型非常值得瞭解！

## 我們是誰？

關於作者：

**Matthew Carrigan** 是 Hugging Face 的機器學習工程師。他住在愛爾蘭都柏林，之前在 Parse.ly 擔任機器學習工程師，在此之前，他在Trinity College Dublin擔任博士後研究員。他不相信我們會通過擴展現有架構來實現 AGI，但無論如何都對機器人充滿希望。

**Lysandre Debut** 是 Hugging Face 的機器學習工程師，從早期的開發階段就一直致力於 🤗 Transformers 庫。他的目標是通過使用非常簡單的 API 開發工具，讓每個人都可以使用 NLP。

**Sylvain Gugger** 是 Hugging Face 的一名研究工程師，也是 🤗Transformers庫的核心維護者之一。此前，他是 fast.ai 的一名研究科學家，他與Jeremy Howard 共同編寫了[Deep Learning for Coders with fastai and Py Torch](https://learning.oreilly.com/library/view/deep-learning-for/9781492045519/)。他的主要研究重點是通過設計和改進允許模型在有限資源上快速訓練的技術，使深度學習更容易普及。

**Merve Noyan** 是 Hugging Face 的開發者倡導者，致力於開發工具並圍繞它們構建內容，以使每個人的機器學習平民化。

**Lucile Saulnier** 是 Hugging Face 的機器學習工程師，負責開發和支持開源工具的使用。她還積極參與了自然語言處理領域的許多研究項目，例如協作訓練和 BigScience。

**Lewis Tunstall**  是 Hugging Face 的機器學習工程師，專注於開發開源工具並使更廣泛的社區可以使用它們。他也是即將出版的一本書[O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/)的作者之一。

**Leandro von Werra**  是 Hugging Face 開源團隊的機器學習工程師，也是即將出版的一本書[O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/)的作者之一。他擁有多年的行業經驗，通過在整個機器學習堆棧中工作，將 NLP 項目投入生產。

你準備好了嗎？在本章中，您將學習：
* 如何使用 `pipeline()` 函數解決文本生成、分類等NLP任務
* 關於 Transformer 架構
* 如何區分編碼器、解碼器和編碼器-解碼器架構和用例 


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter1/1.mdx" />

### Transformers能做什麼？
https://huggingface.co/learn/course/zh-TW/chapter1/3.md

# Transformers能做什麼？

<CourseFloatingBanner chapter={1}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/zh-CN/chapter1/section3.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/zh-CN/chapter1/section3.ipynb"},
]} />

在本節中，我們將看看 Transformer 模型可以做什麼，並使用 🤗 Transformers 庫中的第一個工具：pipeline() 函數。
<Tip>
👀 看到那個右上角的 <em>在Colab中打開</em>的按鈕了嗎? 單擊它就可以打開一個包含本節所有代碼示例的 Google Colab 筆記本。 每一個有實例代碼的小節都會有它。

如果您想在本地運行示例，我們建議您查看<a href="/course/chapter0">準備</a>.
</Tip>

## Transformer被應用於各個方面！
Transformer 模型用於解決各種 NLP 任務，就像上一節中提到的那樣。以下是一些使用 Hugging Face 和 Transformer 模型的公司和組織，他們也通過分享他們的模型回饋社區：

<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/companies.PNG" alt="使用 Hugging Face 的公司" width="100%">

[🤗 Transformers 庫](https://github.com/huggingface/transformers)提供了創建和使用這些共享模型的功能。[模型中心（hub）](https://huggingface.co/models)包含數千個任何人都可以下載和使用的預訓練模型。您還可以將自己的模型上傳到 Hub！

<Tip>
⚠️ Hugging Face Hub 不限於 Transformer 模型。任何人都可以分享他們想要的任何類型的模型或數據集！創建一個 Huggingface.co 帳戶(https://huggingface.co/join)以使用所有可用功能！
</Tip>

在深入研究 Transformer 模型的底層工作原理之前，讓我們先看幾個示例，看看它們如何用於解決一些有趣的 NLP 問題。

## 使用pipelines

<iframe width="605" height="320" src="https://www.youtube.com/embed/tiZFewofSLM" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
(這裡有一個視頻，但是國內可能打不開,譯者注)


🤗 Transformers 庫中最基本的對象是 **pipeline()** 函數。它將模型與其必要的預處理和後處理步驟連接起來，使我們能夠通過直接輸入任何文本並獲得最終的答案：

```python
from transformers import pipeline

classifier = pipeline("sentiment-analysis")
classifier("I've been waiting for a HuggingFace course my whole life.")
```
```python out
[{'label': 'POSITIVE', 'score': 0.9598047137260437}]
```


我們也可以多傳幾句！
```python
classifier(
    ["I've been waiting for a HuggingFace course my whole life.", "I hate this so much!"]
)
``` 
```python out
[{'label': 'POSITIVE', 'score': 0.9598047137260437},
 {'label': 'NEGATIVE', 'score': 0.9994558095932007}]
```
默認情況下，此pipeline選擇一個特定的預訓練模型，該模型已針對英語情感分析進行了微調。創建**分類器**對象時，將下載並緩存模型。如果您重新運行該命令，則將使用緩存的模型，無需再次下載模型。

將一些文本傳遞到pipeline時涉及三個主要步驟：

1. 文本被預處理為模型可以理解的格式。
2. 預處理的輸入被傳遞給模型。
3. 模型處理後輸出最終人類可以理解的結果。

目前[可用的一些pipeline](https://huggingface.co/transformers/main_classes/pipelines.html)是：

* `feature-extraction`（獲取文本的向量表示）
* `fill-mask`
* `ner`（命名實體識別）
* `question-answering`
* `sentiment-analysis`
* `summarization`
* `text-generation`
* `translation`
* `zero-shot-classification`

讓我們來看看其中的一些吧！

## 零樣本分類
我們將首先處理一項非常具挑戰性的任務，我們需要對尚未標記的文本進行分類。這是實際項目中的常見場景，因為注釋文本通常很耗時並且需要領域專業知識。對於這項任務**zero-shot-classification**pipeline非常強大：它允許您直接指定用於分類的標籤，因此您不必依賴預訓練模型的標籤。下面的模型展示瞭如何使用這兩個標籤將句子分類為正面或負面——但也可以使用您喜歡的任何其他標籤集對文本進行分類。

```python
from transformers import pipeline

classifier = pipeline("zero-shot-classification")
classifier(
    "This is a course about the Transformers library",
    candidate_labels=["education", "politics", "business"],
)
```
```python out
{'sequence': 'This is a course about the Transformers library',
 'labels': ['education', 'business', 'politics'],
 'scores': [0.8445963859558105, 0.111976258456707, 0.043427448719739914]}
```

此pipeline稱為zero-shot，因為您不需要對數據上的模型進行微調即可使用它。它可以直接返回您想要的任何標籤列表的概率分數！
<Tip>
✏️**快來試試吧！** 使用您自己的序列和標籤，看看模型的行為。
</Tip>

## 文本生成
現在讓我們看看如何使用pipeline來生成一些文本。這裡的主要使用方法是您提供一個提示，模型將通過生成剩餘的文本來自動完成整段話。這類似於許多手機上的預測文本功能。文本生成涉及隨機性，因此如果您沒有得到相同的如下所示的結果，這是正常的。

```python
from transformers import pipeline

generator = pipeline("text-generation")
generator("In this course, we will teach you how to")
```
```python out
[{'generated_text': 'In this course, we will teach you how to understand and use '
                    'data flow and data interchange when handling user data. We '
                    'will be working with one or more of the most commonly used '
                    'data flows — data flows of various types, as seen by the '
                    'HTTP'}]
```
您可以使用參數 **num_return_sequences** 控制生成多少個不同的序列，並使用參數 **max_length** 控制輸出文本的總長度。

<Tip>
✏️**快來試試吧！** 使用 num_return_sequences 和 max_length 參數生成兩個句子，每個句子 15 個單詞。
</Tip>

## 在pipeline中使用 Hub 中的其他模型
前面的示例使用了默認模型，但您也可以從 Hub 中選擇特定模型以在特定任務的pipeline中使用 - 例如，文本生成。轉到[模型中心（hub）](https://huggingface.co/models)並單擊左側的相應標籤將會只顯示該任務支持的模型。[例如這樣](https://huggingface.co/models?pipeline_tag=text-generation)。

讓我們試試 [**distilgpt2**](https://huggingface.co/distilgpt2) 模型吧！以下是如何在與以前相同的pipeline中加載它：

```python
from transformers import pipeline

generator = pipeline("text-generation", model="distilgpt2")
generator(
    "In this course, we will teach you how to",
    max_length=30,
    num_return_sequences=2,
)
```
```python out
[{'generated_text': 'In this course, we will teach you how to manipulate the world and '
                    'move your mental and physical capabilities to your advantage.'},
 {'generated_text': 'In this course, we will teach you how to become an expert and '
                    'practice realtime, and with a hands on experience on both real '
                    'time and real'}]
```
您可以通過單擊語言標籤來篩選搜索結果，然後選擇另一種文本生成模型的模型。模型中心（hub）甚至包含支持多種語言的多語言模型。

通過單擊選擇模型後，您會看到有一個小組件，可讓您直接在線試用。通過這種方式，您可以在下載之前快速測試模型的功能。
<Tip>
✏️**快來試試吧！** 使用標籤篩選查找另一種語言的文本生成模型。使用小組件測試並在pipeline中使用它！
</Tip>

## 推理 API
所有模型都可以使用 Inference API 直接通過瀏覽器進行測試，該 API 可在 [Hugging Face 網站](https://huggingface.co/)上找到。通過輸入自定義文本並觀察模型的輸出，您可以直接在此頁面上使用模型。

小組件形式的推理 API 也可作為付費產品使用，如果您的工作流程需要它，它會派上用場。有關更多詳細信息，請參閱[定價頁面](https://huggingface.co/pricing)。

## Mask filling
您將嘗試的下一個pipeline是 **fill-mask**。此任務的想法是填充給定文本中的空白：
```python
from transformers import pipeline

unmasker = pipeline("fill-mask")
unmasker("This course will teach you all about <mask> models.", top_k=2)
```
```python out
[{'sequence': 'This course will teach you all about mathematical models.',
  'score': 0.19619831442832947,
  'token': 30412,
  'token_str': ' mathematical'},
 {'sequence': 'This course will teach you all about computational models.',
  'score': 0.04052725434303284,
  'token': 38163,
  'token_str': ' computational'}]
```
**top_k** 參數控制要顯示的結果有多少種。請注意，這裡模型填充了特殊的< **mask** >詞，它通常被稱為掩碼標記。其他掩碼填充模型可能有不同的掩碼標記，因此在探索其他模型時要驗證正確的掩碼字是什麼。檢查它的一種方法是查看小組件中使用的掩碼。

<Tip>
✏️**快來試試吧！** 在 Hub 上搜索基於 bert 的模型並在推理 API 小組件中找到它的掩碼。這個模型對上面pipeline示例中的句子預測了什麼？
</Tip>

## 命名實體識別
命名實體識別 (NER) 是一項任務，其中模型必須找到輸入文本的哪些部分對應於諸如人員、位置或組織之類的實體。讓我們看一個例子：
```python
from transformers import pipeline

ner = pipeline("ner", grouped_entities=True)
ner("My name is Sylvain and I work at Hugging Face in Brooklyn.")
```
```python out
[{'entity_group': 'PER', 'score': 0.99816, 'word': 'Sylvain', 'start': 11, 'end': 18}, 
 {'entity_group': 'ORG', 'score': 0.97960, 'word': 'Hugging Face', 'start': 33, 'end': 45}, 
 {'entity_group': 'LOC', 'score': 0.99321, 'word': 'Brooklyn', 'start': 49, 'end': 57}
]
```
在這裡，模型正確地識別出 Sylvain 是一個人 (PER)，Hugging Face 是一個組織 (ORG)，而布魯克林是一個位置 (LOC)。

我們在pipeline創建函數中傳遞選項 **grouped_entities=True** 以告訴pipeline將對應於同一實體的句子部分重新組合在一起：這裡模型正確地將「Hugging」和「Face」分組為一個組織，即使名稱由多個詞組成。事實上，正如我們即將在下一章看到的，預處理甚至會將一些單詞分成更小的部分。例如，**Sylvain** 分割為了四部分：**S、##yl、##va** 和 **##in**。在後處理步驟中，pipeline成功地重新組合了這些部分。

<Tip>
✏️**快來試試吧！** 在模型中心（hub）搜索能夠用英語進行詞性標注（通常縮寫為 POS）的模型。這個模型對上面例子中的句子預測了什麼？
</Tip>

## 問答系統
問答pipeline使用來自給定上下文的信息回答問題：
```python
from transformers import pipeline

question_answerer = pipeline("question-answering")
question_answerer(
    question="Where do I work?",
    context="My name is Sylvain and I work at Hugging Face in Brooklyn",
)
```
```python out
{'score': 0.6385916471481323, 'start': 33, 'end': 45, 'answer': 'Hugging Face'}
klyn",
)

```
請注意，此pipeline通過從提供的上下文中提取信息來工作；它不會憑空生成答案。

## 文本摘要
文本摘要是將文本縮減為較短文本的任務，同時保留文本中的主要（重要）信息。下面是一個例子：

```python
from transformers import pipeline

summarizer = pipeline("summarization")
summarizer(
    """
    America has changed dramatically during recent years. Not only has the number of 
    graduates in traditional engineering disciplines such as mechanical, civil, 
    electrical, chemical, and aeronautical engineering declined, but in most of 
    the premier American universities engineering curricula now concentrate on 
    and encourage largely the study of engineering science. As a result, there 
    are declining offerings in engineering subjects dealing with infrastructure, 
    the environment, and related issues, and greater concentration on high 
    technology subjects, largely supporting increasingly complex scientific 
    developments. While the latter is important, it should not be at the expense 
    of more traditional engineering.

    Rapidly developing economies such as China and India, as well as other 
    industrial countries in Europe and Asia, continue to encourage and advance 
    the teaching of engineering. Both China and India, respectively, graduate 
    six and eight times as many traditional engineers as does the United States. 
    Other industrial countries at minimum maintain their output, while America 
    suffers an increasingly serious decline in the number of engineering graduates 
    and a lack of well-educated engineers.
"""
)
```
```python out
[{'summary_text': ' America has changed dramatically during recent years . The '
                  'number of engineering graduates in the U.S. has declined in '
                  'traditional engineering disciplines such as mechanical, civil '
                  ', electrical, chemical, and aeronautical engineering . Rapidly '
                  'developing economies such as China and India, as well as other '
                  'industrial countries in Europe and Asia, continue to encourage '
                  'and advance engineering .'}]
```
與文本生成一樣，您指定結果的 **max_length** 或 **min_length**。

## 翻譯
對於翻譯，如果您在任務名稱中提供語言對（例如「**translation_en_to_fr**」），則可以使用默認模型，但最簡單的方法是在[模型中心（hub）](https://huggingface.co/models)選擇要使用的模型。在這裡，我們將嘗試從法語翻譯成英語：

```python
from transformers import pipeline

translator = pipeline("translation", model="Helsinki-NLP/opus-mt-fr-en")
translator("Ce cours est produit par Hugging Face.")
```
```python out
[{'translation_text': 'This course is produced by Hugging Face.'}]

```

與文本生成和摘要一樣，您可以指定結果的 **max_length** 或 **min_length**。

<Tip>

✏️**快來試試吧！** 搜索其他語言的翻譯模型，嘗試將前一句翻譯成幾種不同的語言。

</Tip>

到目前為止顯示的pipeline主要用於演示目的。它們是為特定任務而編程的，不能對他們進行自定義的修改。在下一章中，您將瞭解 **pipeline()** 函數內部的內容以及如何進行自定義的修改。

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter1/3.mdx" />

### 「解碼器」模型
https://huggingface.co/learn/course/zh-TW/chapter1/6.md

# 「解碼器」模型

<CourseFloatingBanner
    chapter={1}
    classNames="absolute z-10 right-0 top-0"
/>

<Youtube id="d_ixlCubqQw" />

「解碼器」模型通常指僅使用解碼器的 Transformer 模型。在每個階段，對於給定的單詞，注意力層只能獲取到句子中位於將要預測單詞前面的單詞。這些模型通常被稱爲自迴歸模型。

「解碼器」模型的預訓練通常圍繞預測句子中的下一個單詞進行。

這些模型最適合於涉及文本生成的任務。

該系列模型的典型代表有：


- [CTRL](https://huggingface.co/transformers/model_doc/ctrl.html)
- [GPT](https://huggingface.co/docs/transformers/model_doc/openai-gpt)
- [GPT-2](https://huggingface.co/transformers/model_doc/gpt2.html)
- [Transformer XL](https://huggingface.co/transformers/model_doc/transformerxl.html)


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter1/6.mdx" />

### 章末小測試
https://huggingface.co/learn/course/zh-TW/chapter1/10.md

# 章末小測試

<CourseFloatingBanner
    chapter={1}
    classNames="absolute z-10 right-0 top-0"
/>

這一章涵蓋了很多內容！ 如果有一些不太明白的地方，請不要擔心； 下一章將幫助你瞭解這些模塊在底層是如何運作的。

讓我們來測試一下你在這一章學到了什麼！

### 1. 探索 Hub 並尋找 `roberta-large-mnli` checkpoint。 它可以完成什麼類型的任務？


<Question
	choices={[
		{
			text: "文本摘要提取",
			explain: "點擊前往<a href=\"https://huggingface.co/roberta-large-mnli\">roberta-large-mnli 頁面</a>回顧一下."
		},
		{
			text: "文本分類",
			explain: "更準確地說，它對兩個句子在三個標籤（矛盾、無關、相近）之間的邏輯鏈接進行分類——這項任務也稱爲<em>自然語言推理</em>.",
			correct: true
		},
		{
			text: "文本生成",
			explain: "點擊前往<a href=\"https://huggingface.co/roberta-large-mnli\">roberta-large-mnli 頁面</a>回顧一下."
		}
	]}
/>

### 2. 下面的代碼將會返回什麼結果？

```py
from transformers import pipeline

ner = pipeline("ner", grouped_entities=True)
ner("My name is Sylvain and I work at Hugging Face in Brooklyn.")
```

<Question
	choices={[
		{
			text: "它將返回這個句子的分類的分數, 帶有 \"positive\" 或者 \"negative\"的標籤.",
			explain: "這個選項是不對的 — <code>sentiment-analysis</code> pipeline將會返回這些."
		},
		{
			text: "它將返回一個生成的文本來完成這句話。",
			explain: "這個選項是不對的 — <code>text-generation</code> pipeline將會返回這些.",
		},
		{
			text: "它將返回代表人員、組織或位置的單詞。",
			explain: "此外，使用 <code>grouped_entities=True</code>，它會將屬於同一實體的單詞組合在一起，例如“Hugging Face”。",
			correct: true
		}
	]}
/>

### 3. 在此代碼示例中...的地方應該填寫什麼？

```py
from transformers import pipeline

filler = pipeline("fill-mask", model="bert-base-cased")
result = filler("...")
```

<Question
	choices={[
		{
			text: "This <mask> has been waiting for you.",
			explain: "這個選項是不對的。 請查看 <code>bert-base-cased</code> 模型卡片,然後再嘗試找找錯在哪裏。"
		},
		{
			text: "This [MASK] has been waiting for you.",
			explain: "正解! 這個模型的mask的掩碼是[MASK].",
			correct: true
		},
		{
			text: "This man has been waiting for you.",
			explain: "這個選項是不對的。 這個pipeline的作用是填充經過mask的文字，因此它需要在輸入的文本中存在mask的token。"
		}
	]}
/>

### 4. 爲什麼這段代碼會無法運行？

```py
from transformers import pipeline

classifier = pipeline("zero-shot-classification")
result = classifier("This is a course about the Transformers library")
```

<Question
	choices={[
		{
			text: "這個 pipeline 要求提供用來分類此文本的標籤。",
			explain: "正解 — 正確的代碼需要包括：<code>candidate_labels=[...]</code>.",
			correct: true
		},
		{
			text: "這個pipeline需要多個句子，而不僅僅是一個。",
			explain: "這個選項是不對的。儘管正確使用時，此pipeline可以同時處理多個句子（與所有其他pipeline一樣）。"
		},
		{
			text: "像往常一樣，🤗 Transformers 庫出故障了。",
			explain: "對此，我們不予置評！"
		},
		{
			text: "該 pipeline 需要更長的輸入； 這個句子太短了。",
			explain: "這個選項是不對的。 不過請注意，在這個 pipeline 處理時，太長的文本將被截斷。"
		}
	]}
/>

### 5. “遷移學習”是什麼意思？

<Question
	choices={[
		{
			text: "通過在同一數據集上訓練模型，將預訓練模型的知識遷移到新模型。",
			explain: "不，那將是同一模型的兩個版本。"
		},
		{
			text: "通過使用第一個模型的權重初始化第二個模型，將預訓練模型的知識遷移到新模型。",
			explain: "正確：當第二個模型接受新任務訓練時，它*遷移*第一個模型的知識。",
			correct: true
		},
		{
			text: "構建與第一個模型具有相同架構的第二個模型，將預訓練模型的知識遷移到新模型。",
			explain: "架構只是模型的構建方式； 在這種情況下，沒有知識共享或遷移。"
		}
	]}
/>

### 6. 語言模型在預訓練時通常不需要標籤，這樣的說法是否正確。


<Question
	choices={[
		{
			text: "正確",
			explain: "預訓練通常是<em>自監督</em>，這意味着標籤是根據輸入自動創建的（例如：預測下一個單詞或填充一些[MARSK]單詞）。",
			correct: true
		},
		{
			text: "錯誤",
			explain: "這不是一個正確的答案。"
		}
	]}
/>


### 7. 選擇最能描述「模型(model)」、「架構(architecture)」和「權重(weights)」的句子。
<Question
	choices={[
		{
			text: "如果模型是一座建築物，那麼它的架構就是藍圖，而權重就是住在裏面的人。",
			explain: "按照這個比喻，權重將是用於建造建築物的磚塊和其他材料。"
		},
		{
			text: "架構是構建模型的地圖，其權重是地圖上表示的城市。",
			explain: "這個比喻的問題在於，一張地圖通常代表只有一個確定的事實（法國只有一個城市叫巴黎）。 對於給定的體系結構，多個權重是可能的。"
		},
		{
			text: "架構是用於構建模型的一系列數學函數，其權重是這些函數參數。",
			explain: "同一組數學函數（架構）可以通過使用不同的參數（權重）來構建不同的模型。",
			correct: true
		}
	]}
/>


### 8. 你將使用以下哪種類型的模型來根據輸入的提示生成文本？

<Question
	choices={[
		{
			text: "一個“編碼器”模型",
			explain: "“編碼器”模型生成整個句子的表示，這種表示更適合於分類之類的任務。"
		},
		{
			text: "一個“解碼器”模型",
			explain: "“解碼器”模型非常適合根據提示生成文本。",
			correct: true
		},
		{
			text: "一個“序列到序列”模型",
			explain: "“序列到序列”模型更適合於你想要根據輸入的句子而不是給定的提示生成句子的任務。"
		}
	]}
/>

### 9. 你會使用哪些類型的模型來生成文本的摘要？

<Question
	choices={[
		{
			text: "一個「編碼器」模型",
			explain: "「編碼器」模型生成整個句子的表示，這種表示更適合於分類之類的任務。"
		},
		{
			text: "一個「解碼器」模型",
			explain: "「解碼器」模型對於生成輸出文本(如摘要)很好，但它們不具備利用上下文(如整個文本)進行總結的能力。"
		},
		{
			text: "一個「序列到序列」模型",
			explain: "「序列到序列」模型非常適合摘要任務。",
			correct: true
		}
	]}
/>

### 10. 你會使用哪一種類型的模型來根據特定的標籤對文本輸入進行分類？

<Question
	choices={[
		{
			text: "一個“編碼器”模型",
			explain: "“編碼器”模型可以生成整個句子的表示，非常適合分類這樣的任務。",
			correct: true
		},
		{
			text: "一個“解碼器”模型",
			explain: "“解碼器”模型適合於生成輸出文本，而不是從句子中提取標籤。"
		},
		{
			text: "一個“序列到序列”模型",
			explain: "“序列到序列”模型更適合於輸入句子而不是標籤生成文本的任務。",
		}
	]}
/>

### 11. 模型中觀察到的偏見有哪些可能的來源？

<Question
	choices={[
		{
			text: "這個模型是一個預訓練模型的微調版本，它從中繼承了預訓練模型的偏見。",
			explain: "當應用遷移學習時，在預訓練模型中產生的偏見在微調模型中不能完全去除。",
			correct: true
		},
		{
			text: "用於訓練模型的數據是有偏見的。",
			explain: "這是最明顯的偏見來源，但不是唯一的來源。",
			correct: true
		},
		{
			text: "模型優化的指標是有偏見的。",
			explain: "一個不太明顯的偏見來源是模型的訓練方式。你的模型將盲目地只針對你選擇的指標進行優化，而不會思考是否會帶有偏見。",
			correct: true
		}
	]}
/>


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter1/10.mdx" />

### Bias and limitations
https://huggingface.co/learn/course/zh-TW/chapter1/8.md

# Bias and limitations

<CourseFloatingBanner chapter={1}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/zh-CN/chapter1/section8.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/zh-CN/chapter1/section8.ipynb"},
]} />

如果您打算在正式的項目中使用經過預訓練或經過微調的模型。請注意：雖然這些模型是很強大，但它們也有侷限性。其中最大的一個問題是，爲了對大量數據進行預訓練，研究人員通常會蒐集所有他們能找到的內容，中間可能夾帶一些意識形態或者價值觀的刻板印象。

爲了快速解釋清楚這個問題，讓我們回到一個使用 BERT 模型的 pipeline 的例子：

```python
from transformers import pipeline

unmasker = pipeline("fill-mask", model="bert-base-uncased")
result = unmasker("This man works as a [MASK].")
print([r["token_str"] for r in result])

result = unmasker("This woman works as a [MASK].")
print([r["token_str"] for r in result])
```

```python out
['lawyer', 'carpenter', 'doctor', 'waiter', 'mechanic']
['nurse', 'waitress', 'teacher', 'maid', 'prostitute']
```
當要求模型填寫這兩句話中缺少的單詞時，模型給出的答案中，只有一個與性別無關（服務生/女服務生）。其他職業通常與某一特定性別相關，妓女最終進入了模型中與「女人」和「工作」相關的前五位。儘管 BERT 是使用經過篩選和清洗後，明顯中立的數據集上建立的的 Transformer 模型，而不是通過從互聯網上搜集數據（它是在[Wikipedia 英文](https://huggingface.co/datasets/wikipedia)和[BookCorpus](https://huggingface.co/datasets/bookcorpus)數據集）。

因此，當您使用這些工具時，您需要記住，使用的原始模型的時候，很容易生成性別歧視、種族主義或恐同內容。這種固有偏見不會隨着微調模型而使消失。

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter1/8.mdx" />

### Transformers 是如何工作的？
https://huggingface.co/learn/course/zh-TW/chapter1/4.md

# Transformers 是如何工作的？

<CourseFloatingBanner
    chapter={1}
    classNames="absolute z-10 right-0 top-0"
/>

在本節中，我們將深入瞭解 Transformer 模型的架構。

## 一點Transformers的發展歷史

以下是 Transformer 模型（簡短）歷史中的一些關鍵節點：

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/transformers_chrono.svg" alt="A brief chronology of Transformers models."/>
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/transformers_chrono-dark.svg" alt="A brief chronology of Transformers models."/>
</div>

[Transformer 架構](https://arxiv.org/abs/1706.03762) 於 2017 年 6 月推出。原本研究的重點是翻譯任務。隨後推出了幾個有影響力的模型，包括

- **2018 年 6 月**: [GPT](https://cdn.openai.com/research-covers/language-unsupervised/language_understanding_paper.pdf), 第一個預訓練的 Transformer 模型，用於各種 NLP 任務並獲得極好的結果

- **2018 年 10 月**: [BERT](https://arxiv.org/abs/1810.04805), 另一個大型預訓練模型，該模型旨在生成更好的句子摘要（下一章將詳細介紹！）

- **2019 年 2 月**: [GPT-2](https://cdn.openai.com/better-language-models/language_models_are_unsupervised_multitask_learners.pdf), GPT 的改進（並且更大）版本，由於道德問題沒有立即公開發布

- **2019 年 10 月**: [DistilBERT](https://arxiv.org/abs/1910.01108), BERT 的提煉版本，速度提高 60%，內存減輕 40%，但仍保留 BERT 97% 的性能

- **2019 年 10 月**: [BART](https://arxiv.org/abs/1910.13461) 和 [T5](https://arxiv.org/abs/1910.10683), 兩個使用與原始 Transformer 模型相同架構的大型預訓練模型（第一個這樣做）

- **2020 年 5 月**, [GPT-3](https://arxiv.org/abs/2005.14165), GPT-2 的更大版本，無需微調即可在各種任務上表現良好（稱爲零樣本學習）

這個列表並不全面，只是爲了突出一些不同類型的 Transformer 模型。大體上，它們可以分爲三類：

- GPT-like (也被稱作自迴歸Transformer模型)
- BERT-like (也被稱作自動編碼Transformer模型) 
- BART/T5-like (也被稱作序列到序列的 Transformer模型)

稍後我們將更深入地探討這些分類。

## Transformers是語言模型

上面提到的所有 Transformer 模型（GPT、BERT、BART、T5 等）都被訓練爲語言模型。這意味着他們已經以無監督學習的方式接受了大量原始文本的訓練。無監督學習是一種訓練類型，其中目標是根據模型的輸入自動計算的。這意味着不需要人工來標記數據！

這種類型的模型可以對其訓練過的語言進行統計理解，但對於特定的實際任務並不是很有用。因此，一般的預訓練模型會經歷一個稱爲*遷移學習*的過程。在此過程中，模型在給定任務上以監督方式（即使用人工註釋標籤）進行微調。

任務的一個例子是閱讀 *n* 個單詞的句子，預測下一個單詞。這被稱爲因果語言建模，因爲輸出取決於過去和現在的輸入。

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/causal_modeling.svg" alt="Example of causal language modeling in which the next word from a sentence is predicted."/>
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/causal_modeling-dark.svg" alt="Example of causal language modeling in which the next word from a sentence is predicted."/>
</div>

另一個例子是*遮罩語言建模*，該模型預測句子中的遮住的詞。

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/masked_modeling.svg" alt="Example of masked language modeling in which a masked word from a sentence is predicted."/>
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/masked_modeling-dark.svg" alt="Example of masked language modeling in which a masked word from a sentence is predicted."/>
</div>

## Transformer是大模型

除了一些特例（如 DistilBERT）外，實現更好性能的一般策略是增加模型的大小以及預訓練的數據量。

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/model_parameters.png" alt="Number of parameters of recent Transformers models" width="90%"/>
</div>

不幸的是，訓練模型，尤其是大型模型，需要大量的數據，時間和計算資源。它甚至會對環境產生影響，如下圖所示。

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/carbon_footprint.svg" alt="The carbon footprint of a large language model."/>
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/carbon_footprint-dark.svg" alt="The carbon footprint of a large language model."/>
</div>

<Youtube id="ftWlj4FBHTg"/>

Transformers是由一個團隊領導的（非常大的）模型項目，該團隊試圖減少預訓練對環境的影響，通過運行大量試驗以獲得最佳超參數。

想象一下，如果每次一個研究團隊、一個學生組織或一家公司想要訓練一個模型，都從頭開始訓練的。這將導致巨大的、不必要的浪費！

這就是爲什麼共享語言模型至關重要：共享經過訓練的權重，當遇見新的需求時在預訓練的權重之上進行微調，可以降低訓練模型訓練的算力和時間消耗，降低全球的總體計算成本和碳排放。


## 遷移學習

<Youtube id="BqqfQnyjmgg" />

*預訓練是*訓練模型前的一個操作：隨機初始化權重，在沒有任何先驗知識的情況下開始訓練。

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/pretraining.svg" alt="The pretraining of a language model is costly in both time and money."/>
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/pretraining-dark.svg" alt="The pretraining of a language model is costly in both time and money."/>
</div>

這種預訓練通常是在非常大量的數據上進行的。因此，它需要大量的數據，而且訓練可能需要幾周的時間。

另一方面，*微調*是在模型經過預訓練後完成的訓練。要執行微調，首先需要獲取一個經過預訓練的語言模型，然後使用特定於任務的數據集執行額外的訓練。等等，爲什麼不直接爲最後的任務而訓練呢？有幾個原因：

*  預訓練模型已經在與微調數據集有一些相似之處的數據集上進行了訓練。因此，微調過程能夠利用模型在預訓練期間獲得的知識（例如，對於NLP問題，預訓練模型將對您在任務中使用的語言有某種統計規律上的理解）。
*  由於預訓練模型已經在大量數據上進行了訓練，因此微調需要更少的數據來獲得不錯的結果。
*  出於同樣的原因，獲得好結果所需的時間和資源要少得多

例如，可以利用英語的預訓練過的模型，然後在arXiv語料庫上對其進行微調，從而形成一個基於科學/研究的模型。微調只需要有限的數據量：預訓練模型獲得的知識可以“遷移”到目標任務上，因此被稱爲*遷移學習*。

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/finetuning.svg" alt="The fine-tuning of a language model is cheaper than pretraining in both time and money."/>
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/finetuning-dark.svg" alt="The fine-tuning of a language model is cheaper than pretraining in both time and money."/>
</div>

因此，微調模型具有較低的時間、數據、財務和環境成本。迭代不同的微調方案也更快、更容易，因爲與完整的預訓練相比，訓練的約束更少。

這個過程也會比從頭開始的訓練（除非你有很多數據）取得更好的效果，這就是爲什麼你應該總是嘗試利用一個預訓練的模型--一個儘可能接近你手頭的任務的模型--並對其進行微調。

## 一般的體系結構
在這一部分，我們將介紹Transformer模型的一般架構。如果你不理解其中的一些概念，不要擔心；下文將詳細介紹每個組件。

<Youtube  id="H39Z_720T5s" />

## 介紹

該模型主要由兩個塊組成：

* **Encoder (左側)**: 編碼器接收輸入並構建其表示（其特徵）。這意味着對模型進行了優化，以從輸入中獲得理解。
* **Decoder (右側)**: 解碼器使用編碼器的表示（特徵）以及其他輸入來生成目標序列。這意味着該模型已針對生成輸出進行了優化。

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/transformers_blocks.svg" alt="Architecture of a Transformers models"/>
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/transformers_blocks-dark.svg" alt="Architecture of a Transformers models"/>
</div>

這些部件中的每一個都可以獨立使用，具體取決於任務：

* **Encoder-only models**: 適用於需要理解輸入的任務，如句子分類和命名實體識別。
* **Decoder-only models**: 適用於生成任務，如文本生成。
* **Encoder-decoder models** 或者 **sequence-to-sequence models**: 適用於需要根據輸入進行生成的任務，如翻譯或摘要。

在後面的部分中，我們將單獨地深入研究這些體系結構。

## 注意力層

Transformer模型的一個關鍵特性是*注意力層*。事實上，介紹Transformer架構的文章的標題是[“注意力就是你所需要的”](https://arxiv.org/abs/1706.03762)! 我們將在課程的後面更加深入地探索注意力層；現在，您需要知道的是，這一層將告訴模型在處理每個單詞的表示時，要特別重視您傳遞給它的句子中的某些單詞（並且或多或少地忽略其他單詞）。

把它放在語境中，考慮將文本從英語翻譯成法語的任務。在輸入“You like this course”的情況下，翻譯模型還需要注意相鄰的單詞“You”，以獲得單詞“like”的正確翻譯，因爲在法語中，動詞“like”的變化取決於主題。然而，句子的其餘部分對於該詞的翻譯沒有用處。同樣，在翻譯“this”時，模型也需要注意“course”一詞，因爲“this”的翻譯不同，取決於相關名詞是單數還是複數。同樣，句子中的其他單詞對於“this”的翻譯也不重要。對於更復雜的句子（以及更復雜的語法規則），模型需要特別注意可能出現在句子中更遠位置的單詞，以便正確地翻譯每個單詞。

同樣的概念也適用於與自然語言相關的任何任務：一個詞本身有一個含義，但這個含義受語境的影響很大，語境可以是研究該詞之前或之後的任何其他詞（或多個詞）。

現在您已經瞭解了注意力層的含義，讓我們更仔細地瞭解Transformer架構。

## 原始的結構

Transformer架構最初是爲翻譯而設計的。在訓練期間，編碼器接收特定語言的輸入（句子），而解碼器需要輸出對應語言的翻譯。在編碼器中，注意力層可以使用一個句子中的所有單詞（正如我們剛纔看到的，給定單詞的翻譯可以取決於它在句子中的其他單詞）。然而，解碼器是按順序工作的，並且只能注意它已經翻譯過的句子中的單詞。例如，當我們預測了翻譯目標的前三個單詞時，我們將它們提供給解碼器，然後解碼器使用編碼器的所有輸入來嘗試預測第四個單詞。

爲了在訓練過程中加快速度（當模型可以訪問目標句子時），解碼器會被輸入整個目標，但不允許獲取到要翻譯的單詞（如果它在嘗試預測位置2的單詞時可以訪問位置2的單詞，解碼器就會偷懶，直接輸出那個單詞，從而無法學習到正確的語言關係！）。例如，當試圖預測第4個單詞時，注意力層只能獲取位置1到3的單詞。

最初的Transformer架構如下所示，編碼器位於左側，解碼器位於右側：

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/transformers.svg" alt="Architecture of a Transformers models"/>
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/transformers-dark.svg" alt="Architecture of a Transformers models"/>
</div>

注意，解碼器塊中的第一個注意力層關聯到解碼器的所有（過去的）輸入，但是第二注意力層使用編碼器的輸出。因此，它可以訪問整個輸入句子，以最好地預測當前單詞。這是非常有用的，因爲不同的語言可以有語法規則將單詞按不同的順序排列，或者句子後面提供的一些上下文可能有助於確定給定單詞的最佳翻譯。

也可以在編碼器/解碼器中使用*注意力遮罩層*，以防止模型注意某些特殊單詞。例如，在批處理句子時，填充特殊詞使所有句子的長度一致。

##  架構與參數

在本課程中，當我們深入探討Transformers模型時，您將看到
架構、參數和模型
。 這些術語的含義略有不同：

* **架構**: 這是模型的骨架 -- 每個層的定義以及模型中發生的每個操作。
* **Checkpoints**: 這些是將在給架構中結構中加載的權重。
* **模型**: 這是一個籠統的術語，沒有“架構”或“參數”那麼精確：它可以指兩者。爲了避免歧義，本課程使用將使用架構和參數。

例如，BERT是一個架構，而 `bert-base-cased`， 這是谷歌團隊爲BERT的第一個版本訓練的一組權重參數，是一個參數。我們可以說“BERT模型”和"`bert-base-cased`模型."


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter1/4.mdx" />

### 序列到序列模型
https://huggingface.co/learn/course/zh-TW/chapter1/7.md

# 序列到序列模型

<CourseFloatingBanner
    chapter={1}
    classNames="absolute z-10 right-0 top-0"
/>

<Youtube id="0_4KEb08xrE" />

編碼器-解碼器模型（也稱爲序列到序列模型)同時使用 Transformer 架構的編碼器和解碼器兩個部分。在每個階段，編碼器的注意力層可以訪問初始句子中的所有單詞，而解碼器的注意力層只能訪問位於輸入中將要預測單詞前面的單詞。

這些模型的預訓練可以使用訓練編碼器或解碼器模型的方式來完成，但通常涉及更復雜的內容。例如，[T5](https://huggingface.co/t5-base)通過將文本的隨機跨度（可以包含多個單詞）替換爲單個特殊單詞來進行預訓練，然後目標是預測該掩碼單詞替換的文本。

序列到序列模型最適合於圍繞根據給定輸入生成新句子的任務，如摘要、翻譯或生成性問答。

該系列模型的典型代表有：

- [BART](https://huggingface.co/transformers/model_doc/bart.html)
- [mBART](https://huggingface.co/transformers/model_doc/mbart.html)
- [Marian](https://huggingface.co/transformers/model_doc/marian.html)
- [T5](https://huggingface.co/transformers/model_doc/t5.html)


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter1/7.mdx" />

### 測驗時間！
https://huggingface.co/learn/course/zh-TW/chapter1/11.md

# 測驗時間！

是時候測試你的知識了！我們為你準備了一個簡短的測驗，來測試你對本章節所涵蓋概念的理解程度。

要參加測驗，你需要按照以下步驟進行：

1. 登入你的 Hugging Face 帳戶
2. 回答測驗中的問題
3. 提交你的答案


## 多選題測驗

在這個測驗中，你需要從選項列表中選擇正確答案。我們將測試你對監督式微調基礎知識的掌握程度。

<iframe
	src="https://huggingface-course-chapter-1-exam.hf.space"
	frameborder="0"
	width="850"
	height="450"
></iframe>

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter1/11.mdx" />

### 自然語言處理
https://huggingface.co/learn/course/zh-TW/chapter1/2.md

# 自然語言處理

<CourseFloatingBanner
    chapter={1}
    classNames="absolute z-10 right-0 top-0"
/>

在進入 Transformer 模型之前，讓我們快速概述一下自然語言處理是什麼以及我們為什麼這麼重視它。

## 什麼是自然語言處理？

NLP 是語言學和機器學習交叉領域，專注於理解與人類語言相關的一切。 NLP 任務的目標不僅是單獨理解單個單詞，而且是能夠理解這些單詞的上下文。

以下是常見 NLP 任務的列表，每個任務都有一些示例：

- **對整個句子進行分類**: 獲取評論的情緒，檢測電子郵件是否為垃圾郵件，確定句子在語法上是否正確或兩個句子在邏輯上是否相關
- **對句子中的每個詞進行分類**: 識別句子的語法成分（名詞、動詞、形容詞）或命名實體（人、地點、組織）
- **生成文本內容**: 用自動生成的文本完成提示，用屏蔽詞填充文本中的空白
- **從文本中提取答案**: 給定問題和上下文，根據上下文中提供的信息提取問題的答案
- **從輸入文本生成新句子**: 將文本翻譯成另一種語言，總結文本

NLP 不僅限於書面文本。它還解決了語音識別和計算機視覺中的複雜挑戰，例如生成音頻樣本的轉錄或圖像描述。
## 為什麼具有挑戰性？

計算機處理信息的方式與人類不同。例如，當我們讀到「我餓了」這句話時，我們很容易理解它的意思。同樣，給定兩個句子，例如「我很餓」和「我很傷心」，我們可以輕鬆確定它們的相似程度。對於機器學習 (ML) 模型，此類任務更加困難。文本需要以一種使模型能夠從中學習的方式進行處理。而且由於語言很複雜，我們需要仔細考慮必須如何進行這種處理。關於如何表示文本已經做了很多研究，我們將在下一章中介紹一些方法。

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/zh-TW/chapter1/2.mdx" />
