Hugging Faceで独自データセットを使う

本記事は、__Hugging Face (Transformers) で独自データセットを使用する__ 方法の備忘録的記事です。CSVファイルやpandas.DataFrameのデータを、Hugging Faceで使用するための方法を紹介します。

ちなみに、[Hugging Face (Transformers)](https://huggingface.co/docs/transformers/index)は、主に自然言語処理にて広く活用されている、ディープラーニングのライブラリです。Hugging Faceで公開されている訓練済みモデルを簡単に使用できたり、Keras/Pytorchの両方で使用できたり、便利なライブラリです。

# 概要
Hugging Faceには、[Datasets](https://huggingface.co/docs/datasets/index)というパッケージがあります。
そして、その中に`datasets.Dataset`クラスが存在します。
Hugging Face (Transformers) では、データセットを、この`datasets.Dataset`クラスとして読み込んで使用します。

本記事は、独自データセット（csvファイルやpandas.DataFrame形式）を、`datasets.Dataset`クラスとして読み込む方法を紹介していく、という内容です。

主な内容：
1. datasets.Datasetとして読み込む方法
    - csvファイルから
    - pandas.DataFrameから
2. datasets.Featuresによるクラスラベルの指定
3. train_test_split() によるサンプル分割

文章分類タスクを想定しています。
他のタスクでの方法は、今後追記できたらとは思います。

また、本記事の内容は、以下の、Hugging Faceのドキュメントをベースにしています。
より詳細な内容を知りたい方は、こちらをご参照ください。

[Hugging Face - Datasets > Load](https://huggingface.co/docs/datasets/v2.4.0/en/loading)


# 準備
まず、読み込むダミーデータを用意します。
```:フォルダ構成
./my_dataset/
    train1.csv
    train2.csv
    test.csv
```
```text: train1.csv
id,text,label
0,ようやく目標が達成できそうだ,喜
1,どうしてこんな目に遭うんだ,怒
2,全然上手くいかない……,哀
3,明日から長期休暇だ,楽
... （略）
```
（train2.csv, test.csvは省略します。ファイル形式はtrain1.csvと同じものとします。）

# csvファイルから読み込む
読み込み方法は２パターンあります。

```python
import datasets
# 方法1
dataset_dict = datasets.load_dataset('csv', data_files=['./my_dataset/train1.csv'])
# 方法2
dataset_dict = datasets.load_dataset('./my_dataset', data_files=['train1.csv'])
```

また、複数ファイルを指定したり、訓練・評価ファイルを指定したりすることも可能です。
```python
# 方法2, 複数ファイル指定
dataset_dict = datasets.load_dataset('./my_dataset', data_files=['train1.csv', 'train2.csv'])
# 方法2, 訓練・評価サンプルをそれぞれ指定
dataset_dict = datasets.load_dataset('./my_dataset', data_files={'train': 'train1.csv', 'test': 'test.csv'})
```
なお、これらの返値は、`datasets.DatasetDict`クラスとなっています。
`train`や`test`といったキーに対して、値として`datasets.Dataset`が格納されています。
```python:print(dataset_dict)  ※方法2, 訓練・評価サンプルをそれぞれ指定
DatasetDict({
    train: Dataset({
        features: ['id', 'text', 'label'],
        num_rows: 50
    }),
    test: Dataset({
        features: ['id', 'text', 'label'],
        num_rows: 20
    })
})
```

`datasets.Dataset`として直接読み込むことも可能です。
```python
# datasets.Datasetとして読み込む
dataset = datasets.Dataset.from_csv(['./my_dataset/train1.csv'])
```

他にも、csvファイル内の、必要な列だけ読み込むことも可能です。
```python
# 方法2, 読み込む列名を指定
dataset_dict = datasets.load_dataset('./my_dataset', data_files=['train1.csv'], usecols=['text', 'label'])
print(dataset_dict)
```
```python:print(dataset_dict)
DatasetDict({
    train: Dataset({
        features: ['text', 'label'],  # 'id'列が除外されている
        num_rows: 50
    })
})
```
# pandas.DataFrameから読み込む
続いて、`pandas.DataFrame`からの読み込み方法です。

データセットに前処理を加えたり一部を除外したりしたい場合など、csvファイルから直接ではなく、DataFrameで処理をしてから取り込みたい、という場合があるかと思います。
```python
df_train = pd.read_csv('./my_dataset/train1.csv')
dataset = datasets.Dataset.from_pandas(df_train[['text', 'label']])
```

# datasets.Features を指定する
データセットの各値の「意味」を指定することで、データセットを使いやすくします。
本記事の文脈においては、`label`が文章分類のクラスラベルであることを指定します。

具体的には、引数`features`に、`datasets.Features`を指定します。
```python
# datsets.Featuresを定義
my_features = datasets.Features({
    'text': datasets.Value('string'),
    'label': datasets.ClassLabel(names=['喜', '怒', '哀', '楽'])})

# Dataset生成時に、featuresを指定
dataset_w_feat = datasets.Dataset.from_pandas(
    df_train[['text', 'label']], features=my_features)
```
上記では、`Dataset.from_pandas()`で使用していますが、`load_dataset()`や`Dataset.from_csv()`でも同様に使用できます。

`features`の指定の有無で、出力を確認してみます。
`label`の要素が、クラス名（文字列）から、クラス番号に変換されているのがわかります。
```python
print(dataset.features)
# {'text': Value(dtype='string', id=None),
#  'label': Value(dtype='string', id=None),  <--- 文字列として読み込まれている

print(dataset_w_feat.features)
# {'text': Value(dtype='string', id=None),
#  'label': ClassLabel(num_classes=4, names=['喜', '怒', '哀', '楽'], id=None)}  <--- クラスラベルとして読み込まれている

print(dataset[0])
# {'text': 'ようやく目標が達成できそうだ', 'label': '喜'}

print(dataset_w_feat[0])
# {'text': 'ようやく目標が達成できそうだ', 'label': 0}  <--- クラス番号に変換されている
```

なお、クラス名とクラス番号は、以下の方法で相互に変換することも可能です。
```python
dataset_w_feat.features['label'].int2str(0)    # 喜
dataset_w_feat.features['label'].str2int('怒')  # 1
```

# train_test_split()によるサンプル分割
最後に、`datasets.Dataset`を、訓練・評価用にサンプルを分割する方法です。
`train_test_split()`という関数を使用します
```python
dataset_dict = dataset_w_feat.train_test_split(test_size=0.2, shuffle=True)
print(dataset_dict)
```
```python:print(dataset_dict)
DatasetDict({
    train: Dataset({
        features: ['text', 'label'],
        num_rows: 40
    })
    test: Dataset({
        features: ['text', 'label'],
        num_rows: 10
    })
})
```
csvファイルの時点でサンプルが分割されている場合は、前述の方法（「方法2, 訓練・評価サンプルをそれぞれ指定」）を使用します。

# おわりに
Hugging Face (Transformers) にて、独自データセットを使用する方法を、一部まとめてみました。

何かしら参考になりましたら嬉しいです。
また、誤った記載やより良い方法がありましたら、ご指摘いただけますと幸いです。

最後までお読みいただきありがとうございました。
それでは！(*ˊᗜˋ)ﾉｼ