列データ:
-- https://archive.ics.uci.edu/ml/machine-learning-databases/00462/drugsCom_raw.zip
csv
TSV は区切り文字としてカンマの代わりにタブを使用する CSV のバリエーションにすぎないため、ロード スクリプトを使用し、次のように関数delimiter
にパラメータを指定することで、これらのファイルをロードできます。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 ))
# 查看前几个例子
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]}
1. トレーニング データをクリーンアップします。
トレーニング データでは、データをクリーンアップする必要があります。
- この
Unnamed: 0
列は、各患者の匿名 ID によく似ています。 - 列
condition
には大文字と小文字のラベルが混在しています。 \r\n
コメントの長さはさまざまで、Python の行区切り文字 ( ) と HTML 文字コード ( など)が混在して使用されます&\#039;
。
a.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
})
})
2.condition
列には大文字と小文字のラベルが混在しており、小文字に変換されます。
def lowercase_condition(example):
return {"condition": example["condition"].lower()}
drug_dataset.map(lowercase_condition)
次のエラーが報告されます。
AttributeError: 'NoneType' object has no attribute 'lower'
一部のデータでは、condition列为空,需要过滤下空,
ラムダ関数を使用して単純なマッピングおよびフィルタリング操作を定義できます。
drug_dataset = drug_dataset.filter(lambda x: x["condition"] is not None)
エントリを削除した後、列を正規化し、最初の 3 つのエントリを取得してデータを調べることNone
ができます。condition
drug_dataset = drug_dataset.map(lowercase_condition)
# Check that lowercasing worked
drug_dataset["train"]["condition"][:3]
['left ventricular dysfunction', 'adhd', 'birth control']
2. トレーニング データに新しい列を追加します。
例: ユーザーコメントのレビューフィールドの長さをカウントする列を追加し、それをトレーニングセットに追加したい
各レビューの単語数をカウントする簡単な関数を定義します。
def compute_review_length(example):
return {"review_length": len(example["review"].split())}
ompute_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.filter()
機能を使用して 30 単語未満のレビューを削除します。columns で行ったことと同様にcondition
、このしきい値より長いコメントを要求することで、非常に短いコメントを除外できます。
drug_dataset = drug_dataset.filter(lambda x: x["review_length"] > 30)
print(drug_dataset.num_rows)
トレーニング セット データとテスト セット データの数:
{'train': 138514, 'test': 46108}
3. もう一つの問題は、コメント内に HTML の文字コードが含まれていることです。
これらの文字は、次のようにPython モジュールを使用してhtml
エスケープできます。
import html
text = "I'm a transformer called BERT"
html.unescape(text)
"I'm a transformer called BERT"
コーパス内のすべての HTML 文字Dataset.map()和
に対して unescape メソッドを使用します。转义
drug_dataset = drug_dataset.map(lambda x: {"review": html.unescape(x["review"])})
4.map()
メソッドのバッチ処理能力
このDataset.map()
メソッドは 1 つのパラメータを受け取りbatched
、これを設定するとTrue
、サンプルのバッチがマップ関数に即座に送信されます (バッチ サイズは構成可能ですが、デフォルトは 1,000)。たとえば、map 関数は以前はすべての HTML をエスケープしていなかったので、実行に時間がかかります (経過時間は進行状況バーから読み取ることができます)。リスト内包表記を使用して複数の要素を一度に処理することで、処理を高速化できます。
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
)
Dataset.map()
大量のテキストのリストを迅速にトークン化する「高速」トークナイザーを使用する場合は、速度が非常に重要です。たとえば、高速トークナイザーを使用してすべての医薬品レビューをトークン化するには、次のような関数を使用できます。
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")
def tokenize_function(examples):
return tokenizer(examples["review"], truncation=True)
1 つ以上の例をトークナイザーに渡すことができるため、この関数を使用しても使用しなくても使用できますbatched=True
。この機会に、さまざまなオプションのパフォーマンスを比較してみましょう。%time
ノートブックでは、測定したいコード行の前に以下を追加することで、単一行の命令の時間を計測できます。
%time tokenized_dataset = drug_dataset.map(tokenize_function, batched=True)
を使用した場合と使用しない場合で同じ命令を実行し、batched=True
低速トークナイザー (メソッドuse_fast=False
に追加)AutoTokenizer.from_pretrained()
を使用して試してみると、ハードウェアで取得される数値がわかります。
オプション | 高速トークナイザー | トークナイザーが遅い |
---|---|---|
batched=True |
10.8秒 | 4分41秒 |
batched=False |
59.2秒 | 5分3秒 |
これは、batched=True
オプションを使用して高速トークナイザーを使用すると、バッチ処理を行わない低速トークナイザーよりも 30 倍高速であることを意味します。これは非常に驚くべきことです。これが、高速トークナイザーが使用される際のデフォルトである主な理由 (また、高速トークナイザーが「高速」と呼ばれる理由) ですAutoTokenizer
。この高速化を達成できたのは、トークン化されたコードがコード実行を簡単に並列化できる言語である Rust でバックグラウンドで実行されるためです。
並列化は、Fast Tokenizer がバッチ処理を通じて 6 倍近くの高速化を達成する理由でもあります。単一のトークン化操作を並列化することはできませんが、大量のテキストを同時にトークン化したい場合は、実行を複数のプロセスに分割できます。 each それぞれが独自のテキストを担当します。
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秒 |
5. 一連の特徴を入力すると切り捨てられる
機械学習では、通常、サンプルはモデルにフィードする一連の特徴として定義されます。場合によっては、これらの特徴は 内の一連の列になりますDataset
が、他の場合 (ここや質問応答など) では、複数の特徴が 1 つの例から抽出され、1 つの列に属することができます(説明: 1 つの文は切り詰められます)トークナイザーによって設定された最大長のため、複数の段落に分割されます。)
例をトークン化し、最大長 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 に設定されているため、2 つのセグメント (2 つの特徴データ) に分割されます。この入力の長さは (128+49) です。
[128, 49]
データセットのすべての要素に対してこれを実行します。
tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True)
次のエラーが発生します (デフォルト操作の 1,000 行より大きい 1,463 行) 1,000 の例から 1,463 の新機能が得られます。
ArrowInvalid: Column 1 named condition expected length 1463 but got length 1000
- Writer_batch_size (
int
、default1000
) — キャッシュ ファイル ライターの書き込み操作ごとの行数。この値は、処理中のメモリ使用量と処理速度の間の適切な妥協点です。値が大きいほど、処理で実行される検索の回数が減り、値が小さいほど、実行時の一時メモリの消費量が少なくなりますmap
。
古い列を新しい列と同じサイズにすることで、長さの不一致に対処できることも述べました。これを行うには、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
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
})
})
6 検証セットを作成する
データセットは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
})
})
7、データセットを保存します
以下の表に示すように、データセットには、データセットをさまざまな形式で保存するための 3 つの主要な機能が用意されています。
データ形式 | 関数 |
---|---|
矢印 | Dataset.save_to_disk() |
CSV | Dataset.to_csv() |
JSON | Dataset.to_json() |
矢印:
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
データセットを保存した後、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 形式の場合、各分割を別のファイルとして保存する必要があります。1 つの方法は、オブジェクト内のキーと値を反復処理することですDatasetDict
。
for split, dataset in drug_dataset_clean.items():
dataset.to_json(f"drug-reviews-{split}.jsonl")
次のように 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)