概要
このチュートリアルでは, ukiyoeデータに対して
- データの読み込み
- データをプロットして確認
- 前処理
- kerasを用いてNNモデルの作成,学習
- 誤識別したデータを確認
を行います.
環境
- python 3.7.4
- tensorflow 1.14.0
- numpy 1.17.2
- matplotlib 3.1.1
データのロード
まずはデータの読み込みをしてみましょう.
import numpy as np
import os
class UkiyoeDataLoader(object):
"""
Example
-------
>>> ukiyoe_dl = UkiyoeDataLoader()
>>> datapath = "./data"
>>> train_imgs, train_lbls, validation_imgs, validation_lbls = ukiyoe_dl.load(datapath)
"""
def __init__(self, validation_size: float):
"""
validation_size : float
[0., 1.]
ratio of validation data
"""
self._basename_list = [
'ukiyoe-train-imgs.npz',\
'ukiyoe-train-labels.npz'
]
self.validation_size = validation_size
def load(self, datapath: str, random_seed: int=13) -> np.ndarray:
filenames_list = self._make_filenames(datapath)
data_list = [np.load(filename)['arr_0'] for filename in filenames_list]
all_imgs, all_lbls = data_list
np.random.seed(random_seed)
perm_idx = np.random.permutation(len(all_imgs))
all_imgs = all_imgs[perm_idx]
all_lbls = all_lbls[perm_idx]
validation_num = int(len(all_lbls)*self.validation_size)
validation_imgs = all_imgs[:validation_num]
validation_lbls = all_lbls[:validation_num]
train_imgs = all_imgs[validation_num:]
train_lbls = all_lbls[validation_num:]
return train_imgs, train_lbls, validation_imgs, validation_lbls
def _make_filenames(self, datapath: str) -> list:
filenames_list = [os.path.join(datapath, basename) for basename in self._basename_list]
return filenames_list
データのフォーマットが.npz
なので,numpy
のnp.load
関数を使って読み込みます.
それ以外のコードは,データを保存した場所(datapath
)を渡すだけで,そこから読み込んでくれるようにするための処理です.
ここで定義したクラスを使うことで,以下のようにしてデータをロードすることができます.
datapath = "./data"
validation_size = 0.2
train_imgs, train_lbls, validation_imgs, validation_lbls = UkiyoeDataLoader(validation_size).load(datapath)
validation_size
ではテストデータの比率を指定しており,ここでは2割のデータをテストデートとして扱っています
プロットしてみよう
データを各クラスごとに,どんな画像データなのか表示してみます.
ここではプロットにmatplotlib
を用います.
import numpy as np
import matplotlib.pyplot as plt
class RandomPlotter(object):
def __init__(self):
self.label_char = ["0", "1", "2", "3",\
"4", "5", "6", "7",\
"8", "9"]
plt.rcParams['font.family'] = 'IPAPGothic'
def _get_unique_labels(self, labels: np.ndarray) -> np.ndarray:
label_unique = np.sort(np.unique(labels))
return label_unique
def _get_random_idx_list(self, labels: np.ndarray) -> list:
label_unique = self._get_unique_labels(labels)
random_idx_list = []
for label in label_unique:
label_indices = np.where(labels == label)[0]
random_idx = np.random.choice(label_indices)
random_idx_list.append(random_idx)
return random_idx_list
def plot(self, images: np.ndarray, labels: np.ndarray) -> None:
"""
Parameters
----------
images : np.ndarray
train_imgs or validation_imgs
labels : np.ndarray
train_lbls or validation_lbls
"""
random_idx_list = self._get_random_idx_list(labels)
fig = plt.figure()
for i, idx in enumerate(random_idx_list):
ax = fig.add_subplot(2, 5, i+1)
ax.tick_params(labelbottom=False, bottom=False)
ax.tick_params(labelleft=False, left=False)
img = images[idx]
ax.imshow(img, cmap='gray')
ax.set_title(self.label_char[i])
fig.show()
このコードでは,各クラスについて一つずつランダムにデータを取り出して,それをプロットしています.
__init__()
のplt.rcParams['font.family'] = 'IPAPGothic'
では,matplotlib
のフォントを日本語に対応したものに変更しています.
もしもこのフォントがないとエラーが出る場合は,このフォントを入れるか,すでにある別の日本語対応フォントに変更してください.
_get_random_idx_list()
では,各クラスごとにランダムにデータのインデックスを抜き出しています.
plot()
内が実際に画像をプロットするコードで,matplotlib
のimshow()
を用いて表示しています.
ここで定義したクラスを用いると,以下のようにしてデータをプロットすることができます.
RandomPlotter().plot(train_imgs, train_lbls)
RandomPlotter().plot(validation_imgs, validation_lbls)
以下のように出力を見ることで, どういった浮世絵データなのか確認できます.
前処理
データの前処理を行います.
ここでは,画像データに対しては,
- 数値データの型を
float32
へ変更
- 値を
[0, 255]
から[0, 1]
に標準化を行います.
ラベルデータに対しては,
0
から9
のint
で表されたラベルを,one-hot表現に変更を行います.
import numpy as np
from tensorflow.keras.utils import to_categorical
class Preprocessor(object):
def transform(self, imgs, lbls=None):
imgs = self._convert_imgs_dtypes(imgs)
imgs = self._normalize(imgs)
if lbls is None:
return imgs
lbls = self._to_categorical_labels(lbls)
return imgs, lbls
def _convert_imgs_dtypes(self, imgs):
_imgs = imgs.astype('float32')
return _imgs
def _normalize(self, imgs):
_imgs = imgs / 255.0
return _imgs
def _to_categorical_labels(self, lbls):
label_num = len(np.unique(lbls))
_lbls = to_categorical(lbls, label_num)
return _lbls
ここで定義したコードを用いると,以下のように前処理を行うことができます.
train_imgs, train_lbls = Preprocessor().transform(train_imgs, train_lbls)
識別してみよう
DNNのフレームワークであるkerasを用いて簡易なNNを作成して識別してみましょう.
kerasはtensorflowに統合され,tensorflowの高レベルAPIとなっているので,tensorflowからインポートします.
(tensorflow.kerasがないというエラーが出た場合は,古いバージョンのtensorflowを使用している可能性があるので,tensorflowをアップデートしてみてください.)
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras.models import Sequential
from tensorflow.keras import layers
from tensorflow.keras import backend as K
from sklearn.metrics import log_loss
datapath = ""
train_imgs, train_lbls, validation_imgs, validation_lbls = UkiyoeDataLoader(validation_size).load(datapath)
train_imgs, train_lbls = Preprocessor().transform(train_imgs, train_lbls)
validation_imgs, validation_lbls = Preprocessor().transform(validation_imgs, validation_lbls)
f = lambda a: np.histogram(a, bins=128)[0]
train_hists = np.apply_along_axis(f, 1, train_imgs.reshape(len(train_imgs), 224*224, 3))
train_hists = train_hists.reshape(len(train_imgs), -1).astype(np.float)
validation_hists = np.apply_along_axis(f, 1, validation_imgs.reshape(len(validation_imgs), 224*224, 3))
validation_hists = validation_hists.reshape(len(validation_imgs), -1).astype(np.float)
batch_size = 128
label_num = 10
epochs = 4
model = Sequential()
model.add(layers.Flatten())
model.add(layers.Dense(128, activation='relu'))
model.add(layers.Dense(512, activation='relu'))
model.add(layers.Dense(128, activation='relu'))
model.add(layers.Dense(label_num, activation='softmax'))
loss = keras.losses.categorical_crossentropy
optimizer = keras.optimizers.Adam(lr=0.001, beta_1=0.9, beta_2=0.999)
model.compile(loss=loss, optimizer=optimizer, metrics=['accuracy'])
model.fit(train_hists, train_lbls,
batch_size=batch_size,
epochs=epochs,
verbose=1,
validation_data=(validation_hists, validation_lbls))
train_score = model.evaluate(train_hists, train_lbls)
y_train = model.predict(train_hists)
train_log_loss = log_loss(np.argmax(train_lbls, axis=1), y_train)
validation_score = model.evaluate(validation_hists, validation_lbls)
y_val = model.predict(validation_hists)
validation_log_loss = log_loss(np.argmax(validation_lbls, axis=1), y_val)
print('Train loss :', train_score[0])
print('Train accuracy :', train_score[1])
print('Train log loss :', train_log_loss)
print('validation loss :', validation_score[0])
print('validation accuracy :', validation_score[1])
print('validation log loss :', validation_log_loss)
画像のRGBごとのヒストグラムを入力とするdenseレイヤーを二段重ねたモデルです.
Train on 1613 samples, validate on 403 samples
Epoch 1/4
1613/1613 [==============================] - 0s 235us/sample - loss: 248.1627 - acc: 0.2728 - val_loss: 96.6572 - val_acc: 0.2953
Epoch 2/4
1613/1613 [==============================] - 0s 139us/sample - loss: 49.8124 - acc: 0.4048 - val_loss: 37.4576 - val_acc: 0.4094
Epoch 3/4
1613/1613 [==============================] - 0s 100us/sample - loss: 21.0337 - acc: 0.4761 - val_loss: 25.8304 - val_acc: 0.3970
Epoch 4/4
1613/1613 [==============================] - 0s 51us/sample - loss: 11.7665 - acc: 0.5189 - val_loss: 20.2862 - val_acc: 0.4293
1613/1613 [==============================] - 0s 53us/sample - loss: 8.3083 - acc: 0.5890
403/403 [==============================] - 0s 87us/sample - loss: 20.2862 - acc: 0.4293
Train loss : 8.308334161683927
Train accuracy : 0.58896464
Train log loss : 7.099104511534913
validation loss : 20.286180082089256
validation accuracy : 0.4292804
validation log loss : 12.11585940360455
出力を見てみると
このシンプルなモデルでは43から47%ほどのaccになるようです.
これをベースラインとして改善してみましょう.
識別結果の出力
学習したモデルにテストデータを入力し提出ファイルを作成します.
test_imgs = np.load('ukiyoe-test-imgs.npz')['arr_0']
test_imgs = Preprocessor().transform(test_imgs)
test_hists = np.apply_along_axis(f, 1, test_imgs.reshape(len(test_imgs), 224*224, 3))
test_hists = test_hists.reshape(len(test_imgs), -1).astype(np.float)
predict_lbls = model.predict(test_hists, batch_size=batch_size)
predict_lbls = np.argmax(predict_lbls, axis=1)
上のコードでは学習データのときと同様にデータの読み込みと前処理を行い, model.predict()
を用いてテストデータに対する出力を得ています.
import pandas as pd
df = pd.DataFrame(predict_lbls, columns=['y'])
df.index.name = 'id'
df.index = df.index + 1
df.to_csv('predict.csv', float_format='%.5f')
最後に提出データのフォーマットに合わせるため, pandas
にnumpy
のデータを渡し, インデックスとカラム名を付加します.
最後に少数表現でcsvファイルに書き出すことで提出ファイルが作成されます.
誤識別したデータの確認
そこで,誤識別した画像に限ってプロットしてみましょう.
class MisclassifiedDataPlotter(object):
"""
このクラスへの入力はpreprocess処理済みのデータを仮定する.
"""
def __init__(self):
self.label_char = ["0", "1", "2", "3",\
"4", "5", "6", "7",\
"8", "9"]
plt.rcParams['font.family'] = 'IPAPGothic'
def _convert_onehot2intvec(self, labels):
labels_int_vec = np.argmax(labels, axis=1)
return labels_int_vec
def _get_mixclassified_idx_list(self, labels_intvec, pred_labels_intvec):
misclassified = labels_intvec != pred_labels_intvec
mis_idxs_list = np.where(misclassified == True)[0]
return mis_idxs_list
def plot(self, images, labels, pred_labels, plot_num: int=5):
"""
Parameters
----------
images : np.ndarray
train_imgs or validation_imgs
labels : np.ndarray
train_lbls or validation_lbls
pred_labels : np.ndarray
predicted labels by trained model
plot_num : int
number of plot images
"""
labels_intvec = self._convert_onehot2intvec(labels)
pred_labels_intvec = self._convert_onehot2intvec(pred_labels)
mis_idxs_list = self._get_mixclassified_idx_list(labels_intvec, pred_labels_intvec)
random_idx_list = list(np.random.choice(mis_idxs_list, size=plot_num, replace=False))
fig = plt.figure()
for i, idx in enumerate(random_idx_list):
ax = fig.add_subplot(1, plot_num, i+1)
ax.tick_params(labelbottom=False, bottom=False)
ax.tick_params(labelleft=False, left=False)
img = images[idx].reshape((224, 224, 3))
ax.imshow(img)
actual_label = self.label_char[labels_intvec[idx]]
pred_label = self.label_char[pred_labels_intvec[idx]]
ax.set_title(f"{pred_label} : actual {actual_label}")
fig.show()
このコードは,前処理をした後のデータを入力すると仮定しています.つまり,画像データは(224, 224, 3)
のfloat
型を持つnp.ndarray
でラベルがone-hot表現になったデータです.
ここで定義したクラスを用いると,以下のようにして誤識別した画像を確認できます.
prediction = model.predict(validation_hists)
mis_plotter = MisclassifiedDataPlotter()
mis_plotter.plot(validation_imgs, validation_lbls, prediction, plot_num=5)
以下が識別ミスをした画像になります.
クラス0に含まれる画像は青っぽい背景を持つものが多いようで, 青成分の多い画像はクラス0として識別されるようです.