1.6 データセット

Ivoryライブラリでは、学習するデータのセットをイテラブルとして実装します。おもちゃのデータを用意します。

import numpy as np

from ivory.common.dataset import Dataset

x_train = np.arange(0, 201).reshape(-1, 3)
t_train = np.arange(0, 201 // 3).reshape(-1, 1)
data = Dataset((x_train, t_train))
data

[1] 2019-06-12 20:00:28 (187ms) python3 (187ms)

Dataset(batch_size=1, epochs=1, len=67, column=0, size=(67,))

Datasetクラスのreprは、内部状態を表示します。バッチサイズを変えてみます。

data.batch_size = 4
data

[2] 2019-06-12 20:00:28 (7.96ms) python3 (195ms)

Dataset(batch_size=4, epochs=1, len=16, column=0, size=(67,))

データの長さ(=バッチの個数)が16に減りました。実際、

len(data)

[4] 2019-06-12 20:00:29 (8.14ms) python3 (211ms)

16

となります。Datasetクラスのインスタンスは通常のリストのようにインデクシングができます。タプルが返されますので、各々の変数に代入するにはアンパックします。

x, t = data[0]
x

[5] 2019-06-12 20:00:29 (15.6ms) python3 (226ms)

array([[ 0,  1,  2],
       [ 3,  4,  5],
       [ 6,  7,  8],
       [ 9, 10, 11]])
t

[6] 2019-06-12 20:00:29 (7.99ms) python3 (234ms)

array([[0],
       [1],
       [2],
       [3]])

取り出したデータのshapeDatasetshapeと一致します。

data.shape, x.shape, t.shape

[7] 2019-06-12 20:00:29 (7.01ms) python3 (241ms)

(((4, 3), (4, 1)), (4, 3), (4, 1))

先ほどの例では、データは先頭から取り出されていました。random属性をTrueにするとランダムにデータを取り出せます。

data.random = True
x, t = data[0]
x

[8] 2019-06-12 20:00:29 (8.04ms) python3 (250ms)

array([[144, 145, 146],
       [189, 190, 191],
       [129, 130, 131],
       [ 69,  70,  71]])

通常は学習用のデータを「訓練データ」と「検証データ」のサブセットに分けます。(テストデータはまた別に用意するべきです。)split関数を使えばデータをサブセットに分割できます。以下の例では3対1の大きさで2分割しています。

data.split((3, 1))
data

[9] 2019-06-12 20:00:29 (7.04ms) python3 (257ms)

Dataset(batch_size=4, epochs=1, len=12, column=0, size=(50, 17))

size属性の要素数が変化したことが分かります。また、

len(data)

[10] 2019-06-12 20:00:29 (15.7ms) python3 (272ms)

12

となりました。どのサブセットからデータを取得するかは、column属性で指定します。

data.column = 1
data

[11] 2019-06-12 20:00:29 (15.8ms) python3 (288ms)

Dataset(batch_size=4, epochs=1, len=4, column=1, size=(50, 17))

取り出せるデータ数(lenの値)が変わりました。実際に取り出してみます。

x, t = data[0]
x

[12] 2019-06-12 20:00:29 (13.0ms) python3 (301ms)

array([[192, 193, 194],
       [192, 193, 194],
       [195, 196, 197],
       [177, 178, 179]])

多次元配列のように扱うことができます。第1要素がサブセット番号、第2要素がインデックスです。

x, t = data[0, 0]
print(t)
x, t = data[1, 0]
print(t)

[13] 2019-06-12 20:00:29 (12.9ms) python3 (314ms)

[[40]
 [38]
 [23]
 [14]]
[[56]
 [62]
 [58]
 [63]]

columnが1のとき、元データの後半部分からデータを取得していることが分かります。サブセット間でデータを混ぜるには、shuffle関数を使います。

data.shuffle()
data.batch_size = 15
x, t = data[0, 0]
print(t.reshape(-1))
x, t = data[1, 0]
print(t.reshape(-1))

[14] 2019-06-12 20:00:29 (17.1ms) python3 (331ms)

[14 21 29  9  5 31 37  6 56 66 25 30 29  0 30]
[65 10 36 34 39 10 55 55 33 52 65 52 33 33 60]

Datasetはforループで使うことができます。

data.batch_size = 4
for k, (x, t) in enumerate(data):
    print(f"#{k}", t.reshape(-1))

[15] 2019-06-12 20:00:29 (31.3ms) python3 (362ms)

#0 [39 43 34 36]
#1 [55 11 34 39]
#2 [36 39 33 38]
#3 [55 11 65 51]

epochsを指定してイタレーション回数をコントロールできます。epoch属性、index属性、iteration属性がDatasetのイタレーション状態を保持します。epoch属性は、エポックの区切り以外では-1となります。また、state属性はこれらをまとめてタプル値を返します。

data.epochs = 2
for _ in data:
    print(data.epoch, data.index, data.iteration)

[16] 2019-06-12 20:00:29 (8.95ms) python3 (371ms)

0 0 0
-1 1 1
-1 2 2
1 3 3
-1 0 4
-1 1 5
-1 2 6
2 3 7

epochsに-1を入力すると無限にループできます。

data.epochs = -1
for k, _ in enumerate(zip(range(1234), data)):
    pass
print(k, data.state)

[17] 2019-06-12 20:00:29 (39.0ms) python3 (410ms)

1233 (-1, 1, 1233)

イテレータとして使うこともできます。分かりやすくするため、もう一度おもちゃのデータを作成します。

x_train = np.arange(0, 201).reshape(-1, 3)
t_train = np.arange(0, 201 // 3).reshape(-1, 1)
data = Dataset((x_train, t_train))
data.split((2, 3, 4))
data.batch_size = 4
data

[18] 2019-06-12 20:00:29 (12.0ms) python3 (422ms)

Dataset(batch_size=4, epochs=1, len=3, column=0, size=(14, 22, 31))
it = iter(data)
x, t = next(it)
print(t.reshape(-1))
x, t = next(it)
print(t.reshape(-1))
data.column = 2
it = iter(data)
x, t = next(it)
print(t.reshape(-1))

[19] 2019-06-12 20:00:29 (24.0ms) python3 (446ms)

[0 1 2 3]
[4 5 6 7]
[36 37 38 39]

Datasetはスライス表記をサポートしています。

x, t = data[2:4]
print(len(x))
x, t = data[:]
print(len(x))

[20] 2019-06-12 20:00:29 (15.6ms) python3 (462ms)

8
31

データの一部だけを使いたいとき、length属性で大きさを制限できます。

data.length = 20
data

[21] 2019-06-12 20:00:29 (8.01ms) python3 (470ms)

Dataset(batch_size=4, epochs=1, len=2, column=2, size=(4, 6, 10))

スライス表記の結果を見てみます。

x, t = data[:]
len(x)

[22] 2019-06-12 20:00:29 (7.02ms) python3 (477ms)

10

lengthの値を-1にすると、元々の全データを使う状態に戻ります。

data.length = -1
print(data.size)
data

[23] 2019-06-12 20:00:29 (8.02ms) python3 (485ms)

(13, 20, 34)
Dataset(batch_size=4, epochs=1, len=8, column=2, size=(13, 20, 34))
x, t = data[:]
len(x)

[24] 2019-06-12 20:00:29 (9.06ms) python3 (494ms)

34