#
是時候來學一下切片了

大多數情況下，您使用的數據都需根據模型所要求的輸入進行清洗。在本節中，我們將探索 🤗 Datasets 提供的用於數據集清洗的各種功能。

##
切片與切分我們的數據

與 Pandas 類似，🤗 Datasets 提供了幾個函數來操作 Dataset 和 DatasetDict 對象。我們在第三章已經遇到了 Dataset.map() 方法，在本節中，我們將探索我們可以使用的其他功能。

對於這個例子，我們將使用託管在加州大學歐文分校機器學習存儲庫的藥物審查數據集，其中包含患者對各種藥物的評論，以及正在治療的病情和患者滿意度的 10 星評級。

首先我們需要下載並提取數據，這可以通過 wget 和 unzip 命令：

    !wget "https://archive.ics.uci.edu/ml/machine-learning-databases/00462/drugsCom_raw.zip"
    !unzip drugsCom_raw.zip

由於 TSV 只是使用製表符而不是逗號作為分隔符的 CSV 變體，我們可以使用加載csv文件的load_dataset()函數並指定分隔符 示例如下：

    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() 共同來完成抽取：

    drug_sample = drug_dataset["train"].shuffle(seed=42).select(range(1000))
    # Peek at the first few examples
    drug_sample[:3]

    {'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 字符代碼，如'。

讓我們看看我們如何使用 🤗 Datasets 來處理這些問題。為了驗證Unnamed: 0 列存儲的是患者 ID的猜想，我們可以使用 Dataset.unique() 函數來驗證匿名ID 的數量是否與拆分後每部分中的行數匹配：

    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中共有的列：

    drug_dataset = drug_dataset.rename_column(
        original_column_name="Unnamed: 0", new_column_name="patient_id"
    )
    drug_dataset

    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
        })
    })

✏️ 試試看！ 使用 `Dataset.unique()` 函數查找訓練和測試集中滿足某個條件的藥物經過去重之後的數量。

接下來，讓我們使用 Dataset.map()標準化所有 condition 標籤 .正如我們在第三章中所做的那樣，我們可以定義一個簡單的函數，可以將該函數應用於drug_dataset 拆分後每部分的所有行：

    def lowercase_condition(example):
        return {"condition": example["condition"].lower()}

    drug_dataset.map(lowercase_condition)

    AttributeError: 'NoneType' object has no attribute 'lower'

哦不，我們的map功能遇到了問題！從錯誤中我們可以推斷出 condition 列存在 None , 不能轉換為小寫，因為它們不是字符串。讓我們使用 Dataset.filter() 刪除這些行 ，其工作方式類似於 Dataset.map() 。例如：

    def filter_nones(x):
        return x["condition"] is not None

然後運行 drug_dataset.filter(filter_nones) ，我們可以在一行中使用lambda 函數.在 Python 中，lambda 函數是您無需明確命名即可使用的微函數（匿名函數）。它們一般採用如下形式：

    lambda <arguments> : <expression>

其中lambda 是 Python 的特殊關鍵字, arguments 是以逗號進行分隔的函數輸入的列表/集合， expression 代表您希望執行的操作。例如，我們可以定義一個簡單的 lambda 函數來對一個數字進行平方，如下所示：

    lambda x : x * x

我們需要將要輸入給這個函數值括在括號中：

    (lambda x: x * x)(3)

    9

類似地，我們可以通過用逗號分隔多個參數來定義 lambda 函數。例如，我們可以按如下方式計算三角形的面積：

    (lambda base, height: 0.5 * base * height)(4, 8)

    16.0

當您想定義小型、一次性使用的函數時，Lambda 函數非常方便（有關它們的更多信息，我們建議閱讀安德烈·布爾高寫的真正的Python教程）。在🤗 Datasets 中，我們可以使用 lambda 函數來定義簡單的映射和過濾操作，所以讓我們使用這個技巧來消除我們數據集中的 None 條目：

    drug_dataset = drug_dataset.filter(lambda x: x["condition"] is not None)

當 None 條目已刪除，我們可以標準化我們的 condition 列：

    drug_dataset = drug_dataset.map(lowercase_condition)
    # Check that lowercasing worked
    drug_dataset["train"]["condition"][:3]

    ['left ventricular dysfunction', 'adhd', 'birth control']

有用！現在我們已經清理了標籤，讓我們來看看清洗後的評論文本。

##
Creating new columns

每當您處理客戶評論時，一個好的做法是檢查每個評論中的字數。評論可能只是一個詞，比如“太棒了！”或包含數千字的完整文章，根據實際的情況，您需要以不同的方式處理這些極端情況。為了計算每條評論中的單詞數，我們將使用基於空格分割每個文本的粗略方法。

讓我們定義一個簡單的函數來計算每條評論中的單詞數：

    def compute_review_length(example):
        return {"review_length": len(example["review"].split())}

與我們的 `lowercase_condition()` 函數不同，`compute_review_length()` 返回一個字典，其鍵與數據集中的列名之一不對應。 在這種情況下，當 `compute_review_length()` 傳遞給 `Dataset.map()` 時，它將應用於數據集中的所有行以創建新的 `review_length` 列：

    drug_dataset = drug_dataset.map(compute_review_length)
    # Inspect the first training example
    drug_dataset["train"][0]

    {'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()對這個新列進行排序，然後查看極端長度的評論的樣子：

    drug_dataset["train"].sort("review_length")[:3]

    {'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]}

正如我們所猜想的那樣，一些評論只包含一個詞，雖然這對於情感分析來說可能沒問題，但如果我們想要預測病情，這些評論可能並不適合。

🙋向數據集添加新列的另一種方法是使用函數Dataset.add_column() 。這允許您輸入Python 列表或 NumPy，在不適合使用Dataset.map()情況下可以很方便。

讓我們使用 Dataset.filter() 功能來刪除包含少於 30 個單詞的評論。與我們對 condition 列的處理相似，我們可以通過選取評論的長度高於此閾值來過濾掉非常短的評論：

    drug_dataset = drug_dataset.filter(lambda x: x["review_length"] > 30)
    print(drug_dataset.num_rows)

    {'train': 138514, 'test': 46108}

如您所見，這已經從我們的原始訓練和測試集中刪除了大約 15% 的評論。

✏️ 試試看！使用 Dataset.sort() 函數查看單詞數最多的評論。請參閱文檔以瞭解您需要使用哪個參數按長度降序對評論進行排序。

我們需要處理的最後一件事是評論中是否存在 HTML 字符代碼。我們可以使用 Python 的html模塊取消這些字符的轉義，如下所示：

    import html

    text = "I&#039;m a transformer called BERT"
    html.unescape(text)

    "I'm a transformer called BERT"

我們將使用 Dataset.map() 對我們語料庫中的所有 HTML 字符進行轉義：

    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 字符進行轉義的方法 ：

    new_drug_dataset = drug_dataset.map(
        lambda x: {"review": [html.unescape(o) for o in x["review"]]}, batched=True
    )

如果您在筆記本中運行此代碼，您會看到此命令的執行速度比前一個命令快得多。這不是因為我們的評論已經是處理過的——如果你重新執行上一節的指令（沒有 batched=True )，它將花費與以前相同的時間。這是因為列表推導式通常比在同一代碼中用 for 循環執行相同的代碼更快，並且我們還通過同時訪問多個元素而不是一個一個來處理來提高處理的速度。

在第六章我們將遇到的“快速”標記器，它可以快速標記大文本列表。使用 Dataset.map() 和 batched=True 是加速的關鍵。例如，要使用快速標記器標記所有藥物評論，我們可以使用這樣的函數：

    from transformers import AutoTokenizer

    tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")

    def tokenize_function(examples):
        return tokenizer(examples["review"], truncation=True)

正如你在第三章所看到的，我們原本就可以將一個或多個示例傳遞給分詞器，因此在batched=True是一個非必須的選項.讓我們藉此機會比較不同選項的性能。在筆記本中，您可以在您要測量的代碼行之前添加 %time來測試改行運行所消耗的時間：

    %time tokenized_dataset = drug_dataset.map(tokenize_function, batched=True)

您還可以通過將整個單元格計時 %%time 在單元格的開頭。在我們執行此操作的硬件上，該指令顯示 10.8 秒（這是寫在“Wall time”之後的數字）。

✏️ 試試看！ 使用和不使用 `batched=True` 執行相同的指令，然後使用慢速標記器嘗試（在 `AutoTokenizer.from_pretrained()` 方法中添加 `use_fast=False`），這樣你就可以看看在你的電腦上它需要多長的時間。

以下是我們在使用和不使用批處理時使用快速和慢速分詞器獲得的結果：

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 參數並指定要在調用中使用的進程數 ：

    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功能的快速標記器 .

使用num_proc以加快處理速度通常是一個好主意，只要您使用的函數還沒有自己帶有的進行某種多進程處理的方法。

將所有這些功能濃縮到一個方法中已經非常了不起，但還有更多！使用 Dataset.map() 和 batched=True 您可以更改數據集中的元素數量。當你想從一個例子中創建幾個訓練特徵時，這是非常有用的。我們將在第七章.中進行的幾個NLP任務的預處理中使用到這個功能，它非常便利。

💡在機器學習中，一個例子通常可以為我們的模型提供一組特徵。在某些情況下，這些特徵會儲存在數據集的幾個列，但在其他情況下（例如此處的例子和用於問答的數據），可以從單個示例的一列中提取多個特徵

讓我們來看看它是如何工作的！在這裡，我們將標記化我們的示例並將最大截斷長度設置128，但我們將要求標記器返回全部文本塊，而不僅僅是第一個。這可以用 return_overflowing_tokens=True ：

    def tokenize_and_split(examples):
        return tokenizer(
            examples["review"],
            truncation=True,
            max_length=128,
            return_overflowing_tokens=True,
        )

在使用Dataset.map() 正式在整個數據集上開始處理之前讓我們先在一個例子上測試一下：

    result = tokenize_and_split(drug_dataset["train"][0])
    [len(inp) for inp in result["input_ids"]]

    [128, 49]

瞧！我們在訓練集中的第一個示例變成了兩個特徵，因為它被標記為超過我們指定的最大截斷長度，因此結果被截成了兩段：第一段長度為 128 ，第二段長度為 49 。現在讓我們對所有元素執行此操作數據集！

    tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True)

    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 參數：

    tokenized_dataset = drug_dataset.map(
        tokenize_and_split, batched=True, remove_columns=drug_dataset["train"].column_names
    )

現在這個過程沒有錯誤。我們可以通過比較長度來檢查新數據集的元素是否比原始數據集多得多：

    len(tokenized_dataset["train"]), len(drug_dataset["train"])

    (206772, 138514)

我們提到我們還可以通過使舊列與新列的大小相同來處理長度不匹配的問題。為此，我們可以使用 overflow_to_sample_mapping 字段，當我們設置return_overflowing_tokens=True .它為我們提供了特徵到它所產生的樣本的映射。使用這個，我們可以將原始數據集中的每個鍵關聯到一個合適大小的值列表中，通過遍歷所有的數據來生成新特性:

    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()來進行批處理，這樣無需我們刪除舊列：

    tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True)
    tokenized_dataset

    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 的相互轉換

為了實現各種第三方庫之間的轉換，🤗 Datasets 提供了一個 Dataset.set_format() 功能。此功能可以通過僅更改輸出格式的，輕鬆切換到另一種格式，而不會影響底層數據格式，即 Apache Arrow。格式化會在數據本身上進行。為了演示，讓我們將數據集轉換為 Pandas：

    drug_dataset.set_format("pandas")

現在，當我們訪問數據集的元素時，我們會得到一個 pandas.DataFrame 而不是字典：

    drug_dataset["train"][:3]

| patient_id | drugName | condition | review | rating | date | usefulCount | review_length
--- | --- | --- | --- | --- | --- | --- | --- | ---
0 | 95260 | Guanfacine | adhd | "My son is halfway through his fourth week of Intuniv..." | 8.0 | April 27, 2010 | 192 | 141
1 | 92703 | Lybrel | birth control | "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..." | 5.0 | December 14, 2009 | 17 | 134
2 | 138000 | Ortho Evra | birth control | "This is my first time using any form of birth control..." | 8.0 | November 3, 2015 | 10 | 89

讓我們創建一個 pandas.DataFrame 來選擇 drug_dataset[train] 的所有元素：

    train_df = drug_dataset["train"][:]

🚨 在底層，`Dataset.set_format()` 改變了數據集的 `__getitem__()` dunder 方法的返回格式。 這意味著當我們想從 `"pandas"` 格式的 `Dataset` 中創建像 `train_df` 這樣的新對象時，我們需要對整個數據集進行切片以獲得 `pandas.DataFrame`。 無論輸出格式如何，您都可以自己驗證 `drug_dataset["train"]` 的類型依然還是 `Dataset`。

從這裡我們可以使用我們想要的所有 Pandas 功能。例如，我們可以通過花式鏈接來計算 condition類之間的分佈 ：

    frequencies = (
        train_df["condition"]
        .value_counts()
        .to_frame()
        .reset_index()
        .rename(columns={"index": "condition", "condition": "frequency"})
    )
    frequencies.head()

| condition | frequency
--- | --- | ---
0 | birth control | 27655
1 | depression | 8023
2 | acne | 5209
3 | anxiety | 4991
4 | pain | 4744

一旦我們完成了 Pandas 分析，我們總是通過使用對象 Dataset.from_pandas()方法可以創建一個新的 Dataset 如下：

    from datasets import Dataset

    freq_dataset = Dataset.from_pandas(frequencies)
    freq_dataset

    Dataset({
        features: ['condition', 'frequency'],
        num_rows: 819
    })

✏️ 試試看！ 計算每種藥物的平均評級並將結果存儲在一個新的Dataset.

我們對 🤗 Datasets中可用的各種預處理技術的介紹到此結束。在最後一部分，讓我們創建一個驗證集來準備用於訓練分類器的數據集。在此之前，我們將輸出格式 drug_dataset 從 pandas重置到 arrow ：

    drug_dataset.reset_format()

##
創建驗證集

儘管我們有一個可以用於評估的測試集，但在開發過程中保持測試集不變並創建一個單獨的驗證集是一個很好的做法。一旦您對模型在測試集上的表現感到滿意，您就可以對驗證集進行最終的檢查。此過程有助於降低您過擬合測試集並部署在現實世界數據上失敗的模型的風險。

🤗 Datasets提供了一個基於scikit-learn的經典方法Dataset.train_test_split() .讓我們用它把我們的訓練集分成 train 和 validation （為了可以復現，我們將設置seed的值為一個常量）：

    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

    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，但現在讓我們查看在本地計算機上保存數據集的幾種方法。

##
保存數據集

雖然 🤗 Datasets 會緩存每個下載的數據集和對它執行的操作，但有時你會想要將數據集保存到磁盤（例如，以防緩存被刪除）。如下表所示，🤗 Datasets 提供了三個主要功能來以不同的格式保存您的數據集：

數據格式 | 對應的方法
--- | ---
Arrow | `Dataset.save_to_disk()`
CSV | `Dataset.to_csv()`
JSON | `Dataset.to_json()`

例如，讓我們以 Arrow 格式保存我們清洗過的數據集：

    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() 功能從磁盤讀取數據如下：

    from datasets import load_from_disk

    drug_dataset_reloaded = load_from_disk("drug-reviews")
    drug_dataset_reloaded

    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中的鍵和值 ：

    for split, dataset in drug_dataset_clean.items():
        dataset.to_json(f"drug-reviews-{split}.jsonl")

這將保存每個拆分都是JSON的標準格式，其中數據集中的每一行都存儲為一行 JSON。這是第一個示例：

    !head -n 1 drug-reviews-train.jsonl

    {"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}

然後我們可以使用第二節學過的技術加載 JSON 文件如下：

    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章的技術來訓練一個分類器，它可以根據藥物評論預測病人的情況。
2. 使用 Chapter 1 中的“summarization”管道生成評論摘要。

接下來，我們將看看 🤗 Datasets如何使您能夠在不撐爆筆記本電腦內存的情況下處理龐大的數據集！