浮世絵の画像から、その画像の作者を識別しよう。
浮世絵のクラスは10種類あるため、多クラス分類モデルを作成します。
※なお、本コンペティションでは、学生賞を用意しております。
学生の方は、リーダーボード→LBチーム→リーダーボードチームリストより、「学生限定」のリーダーボードにご参加ください。
1位 100,000円
学生賞 50,000円
※ 対象者には、コンペティション終了後 メールにてご連絡いたします。
2020年1月14日 0:00
データをダウンロードするにはログインまたはユーザー登録して下さい
このデータセットは浮世絵のデータです。
米・メトロポリタン美術館及びニューヨーク公共図書館がCC0ライセンスで公開した両館のコレクションから浮世絵の画像を集めました。
各浮世絵の画像をデータは224×224の大きさでRGBの画像データです。
データ数は、訓練データが3158枚、テストデータが397枚です。
このコンペティションは、accuracy(正解数/全サンプル数)によって評価されます。
ukiyoe-test-imgs.npz
に対して、作成したモデルで予測を行い、 その結果を次のフォーマットのcsvファイルで提出してください。
以下のフォーマットで提出します
id,y
1,0
2,5
3,2
...
本コンペでは、開催期間終了後 賞金対象者のコードを公開し、ユーザーの皆様にチーティング有無をレビューしていただき順位確定させる、オープンレビュー方式のコンペティションを行います。
賞金対象ユーザー
コンペ終了後1週間以内:
トピックにて、学習過程の分かるコードの公開をお願いいたします。
(簡易解説までつけていただけると助かります。)
1.5か月以内:
レビュアー(ユーザー)より、チーティングの疑いに関するコメントがある場合は、ご回答をお願いいたします。
※チーティングとは無関係のコメント(ノウハウに関する質疑 等)についてもご回答いただけると幸いですが、順位確定の判断材料とは致しません。
レビュアー(ユーザーの皆様)
コンペ終了後1か月以内:
公開コードを確認いただき、チーティングが疑われる場合は、トピックを通して質疑の投稿をお願いいたします。
レビュアーからの質疑と、回答状況をふまえて、最終的に運営側で順位確定を判断します。
不正発覚の場合は失格とし、リーダーボードの順位を繰上げます。
順位繰上げにより賞金対象者となられた場合は、繰上げ日より一週間以内に、トピックにてコードを公開いただき、「Open Review Competition」と同様のフローにて順位を確定させていただきます。
開始日 2019/10/2 0:00 JST
終了日 2020/1/14 0:00 JST
エントリー締め切り なし
・コンペ終了までに、LBチームより「学生限定」リーダーボードにご参加ください。コンペ終了後の参加の場合は賞金対象外となります、ご注意ください。
・賞金のお支払いにあたり、本人確認のため学生証をご提示いただきます。
・参加者ごとに1つのアカウントでご参加ください
・チーム参加にの場合は、最大5名での参加が可能です
・1日あたり、最大5回までの提出が可能です
・ユーザー間での情報共有
コンペティションに関連するコード・データを、チーム外のユーザーと共有することはできません。全参加者が利用できる場合に限り、共有可能です。
・外部データの使用
本コンペティションで公開されているデータのみを用いてチャレンジして下さい。コンペ外データを用いて学習されたモデルの使用も禁止とします。
※コンペ期間中、不正の疑われる結果が提出されている場合は、コード提出依頼や、データ削除対応させていただくことがございます。
公平性の担保、チーティング等の不正防止のため、予告なくルールの追加・変更を行う場合がございます。
ご不便をおかけすることもあるかと思いますが、サービス向上のためご了承ください。
はい。最も精度の高い学習モデルを作成した優勝者には、賞金10万円を贈呈します。
順位確定までのプロセスについては、ルール「Open Review Competition」を参照ください。
可能です。チームページから作成いただけます。
こちらから作成いただけます。
コンペティション参加にはアカウント登録が必要となりますのでご注意ください。
本コンペティションで公開されているデータのみを用いてチャレンジして下さい。コンペ外データを用いて学習されたモデルの使用も禁止とします。
Seed を固定することが推奨です.ただし,Seed を固定しなくても提出用コードとしては認めていく方針です。
全く同じスコアの場合、提出時間の早いユーザーが上位となります。
このチュートリアルでは, ukiyoeデータに対して
を行います.
まずはデータの読み込みをしてみましょう.
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
# shuffle data
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]
# split train and validation
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
# dataの準備
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)
# modelの設定
batch_size = 128
label_num = 10
epochs = 4
# model作成
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を学習する
model.fit(train_hists, train_lbls,
batch_size=batch_size,
epochs=epochs,
verbose=1,
validation_data=(validation_hists, validation_lbls))
# modelを評価する
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) # このmodelは上で作成したkerasのNNです.
mis_plotter = MisclassifiedDataPlotter()
mis_plotter.plot(validation_imgs, validation_lbls, prediction, plot_num=5)
以下が識別ミスをした画像になります.
クラス0に含まれる画像は青っぽい背景を持つものが多いようで, 青成分の多い画像はクラス0として識別されるようです.