はじめに
自然言語処理の学習では利用するモデルに応じて文章中の単語のトークン化など様々な前処理を行う必要があります。今回は、自然言語処理で有名なhuggingfaceのライブラリを利用することでモデル依存の工程をなるべく少なく前処理を行う方法を紹介したいと思います。なお、本記事は使用する機械学習のフレームワークとしてPyTorchを想定した記事となります。
利用するデータセット
今回はkaggleのSuperheroes NLP Dataset(ライセンス:CC0: Public Domai)(英語文書データ)を利用して前処理を行う例を主に紹介していきます。なお今回は上記のデータセット全てではなく先頭10件のデータを用います(上記のデータセットの文書のは一部NaNが存在するため)。
データセットの読み込み
まず、最初にhuggingfaceのライブラリを用いたデータセットに読み込みについて説明します。
データセットの読み込みにはload_datasetメソッドを利用することで実現できます。
load_datasetでは
huggingfaceが用意している135種類のnlpタスクのためのデータセットを HuggingFace Hubからダウンロードしてくる方法。
ローカルのデータセットを読み込む方法。
[対応しているファイル形式]
- CSV
- JSON
- text
- pandas pickled dataframe
のいずれかの方法でデータセットを準備することができます。
利用例
インポート文
from datasets import load_dataset
HuggingFace Hubのデータセットを使う場合の例
dataset = load_dataset('glue', 'sst2') # 'sst2'の設定のデータセット'glue'を利用する例。第二引数を設定しないとエラーになる。
print(dataset)
"""
---出力--------------------------------------------------------------------------------
{'train': Dataset(schema: {'sentence': 'string', 'label': 'int64', 'idx': 'int32'}, num_rows: 67349),
'validation': Dataset(schema: {'sentence': 'string', 'label': 'int64', 'idx': 'int32'}, num_rows: 872),
'test': Dataset(schema: {'sentence': 'string', 'label': 'int64', 'idx': 'int32'}, num_rows: 1821)
}
---------------------------------------------------------------------------------------
"""
ローカルのデータセットを使う場合の例
dataset = load_dataset('csv', data_files='tarin.csv')#ローカルのcsvを読み込む場合
print(dataset)
"""
---出力--------------------------------------------------------------------------------
DatasetDict({
train: Dataset({
features: ['Unnamed: 0', 'name', 'real_name', 'full_name', 'overall_score', 'history_text',・・・省略・・・],
num_rows: 10
})
})
---------------------------------------------------------------------------------------
"""
それぞれの場合を比較するとHuggingFace Hubのデータセットを利用する場合、最初からtrain
、validation
、test
の用途に分かれたデータセットが取得されます。一方、ローカルのデータセットをシンプルに読み込む方法では読みこんだcsvはtarin
データとして読み込まれます。もし、ローカルのデータセットからvalidation
、test
データセットを読み込む場合はdata_files
の指定の方法を変更する必要があります。(詳しくは公式ドキュメントを参考)
[公式ドキュメント]
https://huggingface.co/docs/datasets/package_reference/loading_methods.html#datasets.load_dataset
tokenizerの適用
次に、データセット内の文章中の単語をトークン化するtokenizerをデータセットに適用する方法を見ていきます。
ここでは、tokenizerとしてAutoTokenizerを利用する方法を説明します。
AutoTokenizerとは
tokenizerは各モデル毎に存在します。そして、AutoTokenizerはそれらのtokenizerのラッパーに該当するものになります。後述するように引数に利用したいモデルを指定することでそのモデルのtokenizerのインスタンスが取得できます。利用可能なモデルの種類に関しては公式ドキュメントを参照ください。ここでは、BERT
、ROBERTa
、xlnet
、T5
に関して見ていきます。
インポート文
from transformers import AutoTokenizer
[公式ドキュメント]
https://huggingface.co/transformers/model_doc/auto.html
tokenizerの適用方法
ここでは、ローカルのデータセットを読み込んだデータに関してtokenizerを適用していく例を見ていきます。
ここでは、tokenizerによる以下の前処理を行う例を見ていきます。
- 単語のトークン化
- 文章のPadding。(padding長は引数'max_length'で指定したサイズ)
- 文章の切り捨て(truncation)
また、以降の例の紹介でもローカルのデータセットを読み込む場合を想定するものとします。
ここでは、map関数を用いてデータセットにtokenizerを適用していく例を見ていきます。
[参考]
(注)
tokenizerを適用するデータにNaNがある場合
TypeError: TextEncodeInput must be Union[TextInputSequence, Tuple[InputSequence, InputSequence]]
とエラーになってしまいます。
BERTの場合
tokenizer = AutoTokenizer.from_pretrained('bert-base-uncased')
print(tokenizer)
"""
---出力--------------------------------------------------------------------------------
PreTrainedTokenizerFast(・・・省略・・・
---------------------------------------------------------------------------------------
"""
def tokenize_function(examples):
return tokenizer(examples['history_text'],padding='max_length',max_length = 500, truncation=True,return_special_tokens_mask=True)
tokenized_datasets = dataset.map(
tokenize_function,
batched=True,
remove_columns=df.columns.to_list()#今回はCSVの全てのカラムを消す
)
print(tokenized_datasets)
"""
---出力--------------------------------------------------------------------------------
DatasetDict({
train: Dataset({
features: ['attention_mask', 'input_ids', 'special_tokens_mask', 'token_type_ids'],
num_rows: 10
})
})
---------------------------------------------------------------------------------------
"""
# 1つ目のデータのinput_idsを単語に復元
tokenizer.decode(tokenized_datasets["train"][0]['input_ids'])
"""
---出力--------------------------------------------------------------------------------
[CLS] delroy garrett, jr ・・・省略・・・ [PAD]
---------------------------------------------------------------------------------------
xlnetの場合
tokenizer = AutoTokenizer.from_pretrained('xlnet-base-cased')
print(tokenizer)
"""
---出力--------------------------------------------------------------------------------
PreTrainedTokenizerFast(・・・省略・・・
---------------------------------------------------------------------------------------
"""
def tokenize_function(examples):
return tokenizer(examples['history_text'],padding='max_length',add_special_tokens = True,max_length = 500, truncation=True, return_special_tokens_mask=True)
tokenized_datasets = dataset.map(
tokenize_function,
batched=True,
remove_columns=df.columns.to_list()#今回はCSVの全てのカラムを消す
print(tokenized_datasets)
"""
---出力--------------------------------------------------------------------------------
DatasetDict({
train: Dataset({
features: ['attention_mask', 'input_ids', 'special_tokens_mask', 'token_type_ids'],
num_rows: 10
})
})
---------------------------------------------------------------------------------------
"""
# 1つ目のデータのinput_idsを単語に復元
tokenizer.decode(tokenized_datasets["train"][0]['input_ids'])
"""
---出力--------------------------------------------------------------------------------
<pad>・・・省略・・・ Delroy Garrett, Jr. ・・・省略・・・<sep><cls>
---------------------------------------------------------------------------------------
ROBERTaの場合
tokenizer = AutoTokenizer.from_pretrained('roberta-base')
print(tokenizer)
"""
---出力--------------------------------------------------------------------------------
PreTrainedTokenizerFast(・・・省略・・・
---------------------------------------------------------------------------------------
"""
def tokenize_function(examples):
return tokenizer(examples['history_text'],padding='max_length',add_special_tokens = True,max_length = 500, truncation=True, return_special_tokens_mask=True)
tokenized_datasets = dataset.map(
tokenize_function,
batched=True,
remove_columns=df.columns.to_list()#今回はCSVの全てのカラムを消す
print(tokenized_datasets)
"""
---出力--------------------------------------------------------------------------------
DatasetDict({
train: Dataset({
features: ['attention_mask', 'input_ids', 'special_tokens_mask', 'token_type_ids'],
num_rows: 10
})
})
---------------------------------------------------------------------------------------
"""
# 1つ目のデータのinput_idsを単語に復元
tokenizer.decode(tokenized_datasets["train"][0]['input_ids'])
"""
---出力--------------------------------------------------------------------------------
<s>Delroy Garrett, Jr. grew up ・・・省略・・・<pad><pad>
---------------------------------------------------------------------------------------
結果の比較
基本的にAutoTokenizer.from_pretrained
の引数を使用したいモデルにするだけで後はすべて同じ記述でtokenizerを適用できます。また、tokenizerを適用する際不要な列のデータはremove_columnsで指定して消しています。BERT系のモデルでは、inputの系列長が揃っていないとエラーになるため、特に利用するモデル/タスクが必要としないデータはここで削除するとよいでしょう。
また、トークンの単語への復元結果tokenizer.decode~の部分
を比較するとtokenizer(モデル)によって挿入される特殊トークン([cls]など)が異なることがわかります。
Dataloaderの作成
最後に先のdatasetからdataloaderを作成する手順について見ていきます。
Data Collatorの作成
ここまでの前処理で、文章中の単語のトークン化などを行いましたがまだ以下のような前処理が不足していることがわかります。
- 単語のマスク化(マスク予測タスクの場合)
このような更なる前処理(に加えてpaddingやtruncation等も)とdatasetetからミニバッチを作成するためのオブジェクトとしてData Collatorと呼ばれるものがhuggingfaceには備わっているのでその使い方をみていきます。ここでは2つのData Collatorについて紹介します。
[公式ドキュメント]
DataCollatorForLanguageModeling
言語モデリングに使用されるDataCollatorです。入力がすべて同じ長さでない場合は、バッチの最大長までダイナミックにパディングされます。
主に大体のモデルを使った文章のクラス分類等の大半のタスクやマスク予測タスク等に利用できると思われます。
[主なパラメーター]
- tokenizer:エンコードに使用したtokenizer。
mlm:マスク予測タスクをするかどうかのフラグで、Trueの場合は文書中の単語を一定割合で[MASK]に置き換える処理が実行されますデフォルト :True。マスク予測タスク以外で利用する場合はFalseにします。
mlm_probability:マスク予測タスクを実行する場合(mlmがTrueの場合)の文書中の単語を[MASK]で置き換える割合を指定します(defaults :0.15)。
インポート文
from transformers import DataCollatorForLanguageModeling
宣言の例(マスク予測タスクの場合)
data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer,mlm=True,mlm_probability=0.15)
DataCollatorForPermutationLanguageModeling
XLNetのマスク予測タスクに利用するDataCollatorです。XLNetはマスク予測タスクのやり方がBERTなどとは異なるためマスク予測タスクを実施する場合専用のDataCollatorを使用します。その他のタスクを実施する場合前述のDataCollatorForLanguageModelingを利用すればよいと思われます。
[主なパラメーター]
- plm_probability(defaults :0.1666666) :文書中における予測対象とする単語の割合(Permutation Language ModelingにおけるPartial Predictionにて指定するハイパーパラメータのことと思われます)。
インポート文
from transformers import DataCollatorForPermutationLanguageModeling
宣言の例(マスク予測タスクの場合)
data_collator = DataCollatorForPermutationLanguageModeling(tokenizer=tokenizer,plm_probability=0.16666)
dataloaderの作成とバッチの中身の確認
最後にここまでで作成したdatasetとData Collatorからpytorchのdataloaderを作成することで前処理が完了します。
BERTの場合
from torch.utils.data import DataLoader
data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer,mlm=True,mlm_probability=0.15)
train_dataset = tokenized_datasets["train"]
# データローダーの作成
dataloader = DataLoader(
train_dataset,
shuffle=True, # ランダムにデータを取得してバッチ化
batch_size = 2,
collate_fn=data_collator
)
# 以下dataloaderのバッチごとの中身の確認
for i, batch in enumerate(dataloader):
if i == 0:
# batchの中身の確認
print(batch)
# print(tokenizer.decode(batch['input_ids'][1]))の中身
print(tokenizer.decode(batch['input_ids'][1]))
pass
"""
-------------print(batch)の中身-------------
{
'attention_mask':・・・省略・・・
'input_ids':・・・省略・・・
'labels':・・・省略・・・
}
--------------------------
"""
"""
-------------print(tokenizer.decode(batch['input_ids'][1]))の中身-------------
[CLS] originally a history professor on the planet ungara, abin sur was soon appointed green lantern [MASK] space ・・・省略・・・
--------------------------
"""
XLNETの例
from torch.utils.data import DataLoader
data_collator = DataCollatorForPermutationLanguageModeling(tokenizer=tokenizer,plm_probability=0.16666)
train_dataset = tokenized_datasets["train"]
# データローダーの作成
batch_size = 2
# 訓練データローダー
dataloader = DataLoader(
train_dataset,
shuffle=True, # ランダムにデータを取得してバッチ化
batch_size = batch_size,
collate_fn=data_collator
)
# 以下dataloaderのバッチごとの中身の確認
for i, batch in enumerate(dataloader):
if i == 0:
# batchの中身の確認
print(batch)
# input_idsのトークンを単語に復元
print(tokenizer.decode(batch['input_ids'][1]))
pass
"""
-------------print(batch)の出力-------------
{
'perm_mask':・・・省略・・・
'target_mapping'・・・省略・・・
'input_ids':・・・省略・・・
'labels':・・・省略・・・
}
--------------------------
"""
"""
-------------print(tokenizer.decode(batch['input_ids'][1]))の出力-------------
Sapien began life<mask><mask><mask><mask>ul, a Victorian scientist・・・省略・・・
--------------------------
"""
ROBERTaの場合
from torch.utils.data import DataLoader
data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer,mlm=True,mlm_probability=0.15)
train_dataset = tokenized_datasets["train"]
# データローダーの作成
dataloader = DataLoader(
train_dataset,
shuffle=True, # ランダムにデータを取得してバッチ化
batch_size = 2,
collate_fn=data_collator
)
# 以下dataloaderのバッチごとの中身の確認
for i, batch in enumerate(dataloader):
if i == 0:
# batchの中身の確認
print(batch)
# input_idsのトークンを単語に復元
print(tokenizer.decode(batch['input_ids'][1]))
pass
"""
-------------print(batch)の出力-------------
{
'attention_mask':・・・省略・・・
'input_ids':・・・省略・・・
'labels':・・・省略・・・
}
--------------------------
"""
"""
-------------print(tokenizer.decode(batch['input_ids'][1]))の出力-------------
<s>ayla Secura was a Rutian Twi'lek Jedi Knight (and onetime apprentice to Quinlan V<mask>)<mask> served・・・省略・・・
--------------------------
"""
結果の比較
今回はDataCollatorの設定としてマスク予測タスクの設定で前処理を行いました。その結果、ここまでの前処理を実施することで各々のモデルが必要とするマスク予測タスクのための前処理が適切に実施されたことがわかります。特に、BERTとROBERTaのように前処理のコードを利用するモデルの宣言の部分以外を全く同じ方法で書くことができる点はとても使い勝手がいい印象を受けました。また、XLNETなどのモデル特有の前処理が必要となるモデルに関しても利用するオブジェクトを切り替えるだけで同じ流れで前処理が実現できます。
総じて、1度前処理の流れを作ってしまえばモデルを切り替える際かなりの部分が流用できるため簡単に新しいモデルを試すことができる基盤としてhuggingfaceはとても有用であると感じました。