LLM 課程文件
是時候剖析資料了
並獲得增強的文件體驗
開始使用
是時候剖析資料了
大多數時候,你使用的資料不會完美地為模型訓練做好準備。在本節中,我們將探索 🤗 Datasets 提供的各種功能,以清理資料集。
剖析資料
與 Pandas 類似,🤗 Datasets 提供了多種函式來操作 `Dataset` 和 `DatasetDict` 物件。我們已經在第 3 章中遇到了 `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()` 函式中指定 `delimiter` 引數來載入這些檔案,如下所示:
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")
在進行任何資料分析時,一個好的做法是抽取少量隨機樣本,以便快速瞭解正在處理的資料型別。在 🤗 Datasets 中,我們可以透過將 `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'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)` 來從打亂的資料集中獲取前 1000 個示例。從這個樣本中,我們已經可以看到資料集中存在一些奇怪之處:
- “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` 列重新命名為更易於理解的名稱來稍微清理一下資料集。我們可以使用 `DatasetDict.rename_column()` 函式一次性重新命名兩個拆分中的列。
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` 標籤。正如我們在第 3 章中對分詞所做的那樣,我們可以定義一個簡單的函式,該函式可以應用於 `drug_dataset` 中每個分割的所有行。
def lowercase_condition(example):
return {"condition": example["condition"].lower()}
drug_dataset.map(lowercase_condition)
AttributeError: 'NoneType' object has no attribute 'lower'
哦不,我們的對映函數出了問題!從錯誤中我們可以推斷,`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 函式非常方便(有關它們的更多資訊,我們建議閱讀 Andre Burgaud 撰寫的優秀Real 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']
它奏效了!現在我們已經清理了標籤,接下來讓我們看看如何清理評論本身。
建立新列
每當你處理客戶評論時,一個好的做法是檢查每條評論中的單詞數量。評論可能只有一個詞,比如“太棒了!”,也可能是一篇包含數千詞的完整論文,根據用例,你需要以不同的方式處理這些極端情況。為了計算每條評論中的單詞數量,我們將使用基於按空格分割文字的粗略啟發式方法。
讓我們定義一個簡單的函式來計算每條評論中的單詞數。
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'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 函式執行時間稍長(您可以從進度條中讀取執行時間)。我們可以透過使用列表推導式同時處理多個元素來加快速度。
當您指定 `batched=True` 時,函式會收到一個字典,其中包含資料集的欄位,但每個值現在都是值列表,而不僅僅是單個值。`Dataset.map()` 的返回值也應該相同:一個字典,其中包含我們想要更新或新增到資料集的欄位,以及一個值列表。例如,這是另一種解轉義所有 HTML 字元的方法,但使用了 `batched=True`:
new_drug_dataset = drug_dataset.map(
lambda x: {"review": [html.unescape(o) for o in x["review"]]}, batched=True
)
如果您在筆記本中執行此程式碼,您會發現此命令的執行速度比以前快得多。這並非因為我們的評論已經過 HTML 解轉義——如果您重新執行上一節的指令(不帶 `batched=True`),它將花費與以前相同的時間。這是因為列表推導式通常比在 `for` 迴圈中執行相同的程式碼更快,而且我們還可以透過同時訪問大量元素而不是逐個訪問來獲得一些效能提升。
在 `Dataset.map()` 中使用 `batched=True` 對於釋放我們將在第 6 章中遇到的“快速”分詞器的速度至關重要,這些分詞器可以快速地對大量文字進行分詞。例如,要使用快速分詞器對所有藥物評論進行分詞,我們可以使用如下函式:
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`),這樣你就可以在你的硬體上看到得到的資料。
以下是我們使用和不使用批處理,以及使用快速和慢速分詞器獲得的結果:
選項 | 快速分詞器 | 慢速分詞器 |
---|---|---|
batched=True | 10.8秒 | 4分41秒 |
batched=False | 59.2秒 | 5分3秒 |
這意味著使用 `batched=True` 選項的快速分詞器比沒有批處理的慢速分詞器快 30 倍——這真是令人驚歎!這是 `AutoTokenizer` 預設使用快速分詞器(以及它們被稱為“快速”的原因)的主要原因。它們能夠實現如此快的速度提升,因為在幕後,分詞程式碼是在 Rust 中執行的,Rust 是一種易於並行化程式碼執行的語言。
並行化也是快速分詞器透過批處理實現近 6 倍速度提升的原因:您無法並行化單個分詞操作,但當您想要同時對大量文字進行分詞時,您可以將執行分拆到多個程序中,每個程序負責自己的文字。
`Dataset.map()` 自身也具有一些並行化功能。由於它們沒有 Rust 的支援,因此無法讓慢速分詞器趕上快速分詞器,但它們仍然很有用(特別是當您使用的分詞器沒有快速版本時)。要啟用多處理,請使用 `num_proc` 引數,並在呼叫 `Dataset.map()` 時指定要使用的程序數。
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 個似乎能產生最佳的速度增益。以下是我們使用和不使用多處理獲得的資料:
選項 | 快速分詞器 | 慢速分詞器 |
---|---|---|
batched=True | 10.8秒 | 4分41秒 |
batched=False | 59.2秒 | 5分3秒 |
`batched=True`,`num_proc=8` | 6.52秒 | 41.3秒 |
`batched=False`,`num_proc=8` | 9.49秒 | 45.2秒 |
這些對於慢速分詞器來說是更合理的結果,但快速分詞器的效能也得到了顯著改善。然而,請注意,並非總是如此——對於 `num_proc` 的值不是 8 的情況,我們的測試表明使用 `batched=True` 但不使用該選項更快。通常,我們不建議對使用 `batched=True` 的快速分詞器使用 Python 多處理。
使用 `num_proc` 來加速處理通常是一個很好的主意,只要您使用的函式本身不進行某種多處理。
所有這些功能都濃縮在一個方法中,這已經很了不起了,但還有更多!使用 `Dataset.map()` 和 `batched=True`,您可以更改資料集中的元素數量。這在許多情況下都非常有用,例如您想從一個示例建立多個訓練特徵,並且我們將在第 7 章中進行的幾個 NLP 任務的預處理中需要這樣做。
💡 在機器學習中,示例通常定義為我們輸入到模型的特徵集。在某些上下文中,這些特徵將是 `Dataset` 中的列集,但在其他上下文中(例如這裡和問答),可以從單個示例中提取多個特徵,並且它們屬於單個列。
讓我們看看它是如何工作的!在這裡,我們將對示例進行分詞並將其截斷為最大長度 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。如果您檢視過 `Dataset.map()` 文件,您可能還記得它指的是傳遞給函式的樣本數量;這裡這 1,000 個示例產生了 1,463 個新特徵,導致了形狀錯誤。
問題在於我們試圖混合兩個不同大小的資料集:`drug_dataset` 列將有一定數量的示例(錯誤中的 1,000 個),但我們正在構建的 `tokenized_dataset` 將有更多(錯誤訊息中的 1,463 個;它比 1,000 個多,因為我們透過使用 `return_overflowing_tokens=True` 將長評論分詞成多個示例)。這對於 `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)
我們提到,我們還可以透過使舊列與新列的大小相同來處理長度不匹配的問題。為此,我們需要分詞器在設定 `return_overflowing_tokens=True` 時返回的 `overflow_to_sample_mapping` 欄位。它提供了從新特徵索引到其原始樣本索引的對映。使用這個,我們可以將原始資料集中存在的每個鍵與正確大小的值列表相關聯,方法是將每個示例的值重複多次,因為它們生成了新的特徵。
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 等庫互操作。讓我們看看這是如何工作的。
從 Dataset 到 DataFrame 再返回
為了實現各種第三方庫之間的轉換,🤗 Datasets 提供了 `Dataset.set_format()` 函式。此函式只更改資料集的輸出格式,因此您可以輕鬆切換到其他格式,而不會影響底層資料格式,即 Apache Arrow。格式化是就地完成的。為了演示,讓我們將資料集轉換為 Pandas。
drug_dataset.set_format("pandas")
現在,當我們訪問資料集的元素時,我們得到的是 `pandas.DataFrame` 而不是字典。
drug_dataset["train"][:3]
患者ID | 藥物名稱 | 狀況 | 評論 | 評分 | 日期 | 有用計數 | 評論長度 | |
---|---|---|---|---|---|---|---|---|
0 | 95260 | 胍法辛 | 多動症 | “我的兒子服用 Intuniv 已經四周了……” | 8.0 | 2010年4月27日 | 192 | 141 |
1 | 92703 | 麗波樂 | 節育 | “我以前服用過另一種口服避孕藥,它有 21 天的週期,並且我非常滿意——月經量非常少,最多 5 天,沒有其他副作用……” | 5.0 | 2009年12月14日 | 17 | 134 |
2 | 138000 | 奧索依芙拉 | 節育 | “這是我第一次使用任何形式的避孕措施……” | 8.0 | 2015年11月3日 | 10 | 89 |
讓我們透過選擇 `drug_dataset["train"]` 的所有元素來為整個訓練集建立一個 `pandas.DataFrame`。
train_df = drug_dataset["train"][:]
🚨 在底層,`Dataset.set_format()` 會改變資料集 `__getitem__()` 魔術方法的返回格式。這意味著當我們想從處於 `"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", "count": "frequency"})
)
frequencies.head()
狀況 | 頻率 | |
---|---|---|
0 | 節育 | 27655 |
1 | 抑鬱症 | 8023 |
2 | 痤瘡 | 5209 |
3 | 焦慮症 | 4991 |
4 | 疼痛 | 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 提供了一個 `Dataset.train_test_split()` 函式,該函式基於 `scikit-learn` 中著名的功能。讓我們使用它將訓練集分成 `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
})
})
太棒了,我們現在已經準備好一個數據集,可以用於訓練一些模型了!在第 5 節中,我們將向您展示如何將資料集上傳到 Hugging Face Hub,但現在,讓我們透過檢視幾種將資料集儲存到本地機器的方法來結束我們的分析。
儲存資料集
儘管 🤗 Datasets 將快取每個下載的資料集及其上執行的操作,但有時您會希望將資料集儲存到磁碟(例如,以防快取被刪除)。如下表所示,🤗 Datasets 提供了三個主要函式來以不同格式儲存您的資料集。
資料格式 | 函式 |
---|---|
箭頭 | 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
從中我們可以看到每個拆分都與它自己的 `dataset.arrow` 表以及 `dataset_info.json` 和 `state.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 Lines 格式,其中資料集中的每一行都儲存為單獨的 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}
然後我們可以使用第 2 節中的技術載入 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 進行資料整理的探索就結束了!現在我們有了一個用於訓練模型的清理資料集,以下是您可以嘗試的一些想法:
接下來,我們將看看 🤗 Datasets 如何讓您處理大型資料集而不會讓您的筆記型電腦崩潰!
< > 在 GitHub 上更新