2.2. データ前処理

これまで、私たちはすぐに使えるテンソルとして届く合成データを扱ってきた。
現実世界で深層学習を適用するには、さまざまな形式で保存された雑多なデータを取り込み、必要に応じて前処理を行う必要がある。 幸いなことに、pandas ライブラリ はその大部分を自動化・簡略化してくれる。 この節は pandas の包括的な チュートリアル ではないが、よく使われる基本的なデータ処理についての速習になる。

2.2.1. データセットの読み込み

カンマ区切り値(CSV)ファイルは、表形式(スプレッドシートのような)データを保存するために広く使われている。
CSV では、各行が1つのレコードに対応し、いくつかの(カンマ区切りの)フィールドから構成される。たとえば、“Albert Einstein,March 14 1879,Ulm,Federal polytechnic school,field of gravitational physics” のようなものである。
pandas を使って CSV ファイルを読み込む方法を示すために、ここでは以下で CSV ファイルを作成する ../data/house_tiny.csv を用いる。
このファイルは住宅データセットを表しており、各行が1つの住宅に対応し、列は部屋数(NumRooms)、屋根の種類(RoofType)、価格(Price)を表す。
import os

os.makedirs(os.path.join('..', 'data'), exist_ok=True)
data_file = os.path.join('..', 'data', 'house_tiny.csv')
with open(data_file, 'w') as f:
    f.write('''NumRooms,RoofType,Price
NA,NA,127500
2,NA,106000
4,Slate,178100
NA,NA,140000''')

では、pandas をインポートして read_csv でデータセットを読み込みよう。

import pandas as pd

data = pd.read_csv(data_file)
print(data)
   NumRooms RoofType   Price
0       NaN      NaN  127500
1       2.0      NaN  106000
2       4.0    Slate  178100
3       NaN      NaN  140000

2.2.2. データの準備

教師あり学習では、ある一連の 入力 値が与えられたときに、指定された 目標 値を予測するようにモデルを訓練する。
データセットを処理する最初のステップは、入力値に対応する列と目標値に対応する列を分けることである。
列は名前で選択することも、整数位置に基づくインデックス指定(iloc)で選択することもできる。
お気づきかもしれないが、pandas は CSV の NA の値をすべて、特別な NaNnot a number)値に置き換えた。
これは、たとえば “3,,,270000” のように、項目が空欄の場合にも起こりる。
これらは 欠損値 と呼ばれ、データサイエンスにおける難所の一つである。あなたのキャリアを通じて向き合い続けることになる厄介な問題と言えるだろう。 文脈に応じて、欠損値は 補完 (imputation) か 削除 (deletion) によって処理される。 補完では欠損値を推定値で置き換え、削除では欠損値を含む行または列をデータセットから除外する。
以下に、よく使われる補完のヒューリスティックを示す。
カテゴリ型の入力欄では、NaN を1つのカテゴリとして扱うことができる。
RoofType 列は SlateNaN の値を取るので、pandas はこの列を RoofType_SlateRoofType_nan の2列に変換できる。
屋根の種類が Slate の行では、RoofType_SlateRoofType_nan の値はそれぞれ 1 と 0 になる。
RoofType の値が欠損している行では、その逆になる。
inputs, targets = data.iloc[:, 0:2], data.iloc[:, 2]
inputs = pd.get_dummies(inputs, dummy_na=True)
print(inputs)
   NumRooms  RoofType_Slate  RoofType_nan
0       NaN           False          True
1       2.0           False          True
2       4.0            True         False
3       NaN           False          True

数値の欠損値については、よく使われるヒューリスティックとして、NaN の項目を対応する列の平均値で置き換える 方法がある。

inputs = inputs.fillna(inputs.mean())
print(inputs)
   NumRooms  RoofType_Slate  RoofType_nan
0       3.0           False          True
1       2.0           False          True
2       4.0            True         False
3       3.0           False          True

2.2.3. テンソル形式への変換

これで inputstargets のすべての項目が数値になったので、テンソルに読み込める(2.1 章 を思い出してほしい)。

import torch

X = torch.tensor(inputs.to_numpy(dtype=float))
y = torch.tensor(targets.to_numpy(dtype=float))
X, y
(tensor([[3., 0., 1.],
         [2., 0., 1.],
         [4., 1., 0.],
         [3., 0., 1.]], dtype=torch.float64),
 tensor([127500., 106000., 178100., 140000.], dtype=torch.float64))
from mxnet import np

X, y = np.array(inputs.to_numpy(dtype=float)), np.array(targets.to_numpy(dtype=float))
X, y
[07:03:52] ../src/storage/storage.cc:196: Using Pooled (Naive) StorageManager for CPU
(array([[3., 0., 1.],
        [2., 0., 1.],
        [4., 1., 0.],
        [3., 0., 1.]], dtype=float64),
 array([127500., 106000., 178100., 140000.], dtype=float64))
from jax import numpy as jnp

X = jnp.array(inputs.to_numpy(dtype=float))
y = jnp.array(targets.to_numpy(dtype=float))
X, y
No GPU/TPU found, falling back to CPU. (Set TF_CPP_MIN_LOG_LEVEL=0 and rerun for more info.)
(Array([[3., 0., 1.],
        [2., 0., 1.],
        [4., 1., 0.],
        [3., 0., 1.]], dtype=float32),
 Array([127500., 106000., 178100., 140000.], dtype=float32))
import tensorflow as tf

X = tf.constant(inputs.to_numpy(dtype=float))
y = tf.constant(targets.to_numpy(dtype=float))
X, y
(<tf.Tensor: shape=(4, 3), dtype=float64, numpy=
 array([[3., 0., 1.],
        [2., 0., 1.],
        [4., 1., 0.],
        [3., 0., 1.]])>,
 <tf.Tensor: shape=(4,), dtype=float64, numpy=array([127500., 106000., 178100., 140000.])>)

2.2.4. 議論

これで、データ列を分割し、欠損変数を補完し、pandas のデータをテンソルに読み込む方法がわかった。
5.7 章 では、さらにいくつかのデータ処理スキルを学ぶ。
この速習では話を単純にしたが、データ処理はかなり複雑になりえる。
たとえば、データセットが1つの CSV ファイルにまとまっているのではなく、リレーショナルデータベースから抽出された複数のファイルに分散していることがある。
たとえば電子商取引アプリケーションでは、顧客住所はあるテーブルに、購買データは別のテーブルにあるかもしれない。
さらに、実務ではカテゴリ型や数値型以外にも、テキスト文字列、画像、音声データ、点群など、さまざまなデータ型に直面する。
しばしば、データ処理が機械学習パイプラインの最大のボトルネックにならないようにするために、高度なツールや効率的なアルゴリズムが必要になる。
これらの問題は、コンピュータビジョンや自然言語処理に進むと現れてきる。
最後に、データ品質にも注意を払わなければならない。
現実世界のデータセットは、外れ値、センサーによる誤測定、記録ミスなどに悩まされることが多く、データをどのモデルに入れる前にも対処が必要である。
seabornBokehmatplotlib などのデータ可視化ツールは、データを手作業で確認し、どのような問題に対処すべきかについて直感を養うのに役立ちる。

2.2.5. 演習

  1. たとえば UCI Machine Learning Repository の Abalone などのデータセットを読み込み、その性質を調べてみよう。欠損値を含む割合はどれくらいだろうか。変数のうち、数値型、カテゴリ型、テキスト型の割合はどれくらいだろうか。

  2. 列番号ではなく列名によってデータ列をインデックス指定し、選択してみよう。pandas の indexing のドキュメントには、その方法の詳細が載っている。

  3. この方法でどれくらい大きなデータセットまで読み込めると思うか。どのような制約があるだろうか。ヒント:データの読み込み時間、表現、処理、メモリ使用量を考えてみよ。自分のノートパソコンで試してみよう。サーバー上で試すとどうなるか。

  4. カテゴリ数が非常に多いデータをどのように扱いますか。カテゴリラベルがすべて一意だったらどうだろうか。後者も含めるべきだろうか。

  5. pandas の代替として何が考えられますか。ファイルから NumPy テンソルを読み込む方法はどうだろうか。Pillow、つまり Python Imaging Library も調べてみよう。