ユニクロコン 実装編

オプトのユニクロコンペ(https://deepanalytics.jp/compe/36?tab=compedetail)について、もう少し実装のマニアックな話をしてみます。
コードはhttps://github.com/threecourse/opt-uniqlo-publicに上げています。

コードのインターフェイス

データ分析コンペ用のモデル構築用のクラスをどう作るかは長年の課題だったのですが、少しずつ固まってきました。

いろいろ考えた末、ベースとなるクラスはModel, Runnerに2つに分けた。

  • Modelはsklearnの各モデルをwrapするイメージ
  • Runnerは訓練や予測やクロスバリデーションなど一連の計算のもろもろ
  • 結構良い感じで、わりとモデル追加、クラスの拡張の際にストレス無くできた。
  • sklearnのインターフェイスがあまりしっくりこないのと、hyperoptに合わせたいこともあり、sklearnのベースクラスの継承はしていない。
  • データの読み込みをRunnerに持たせているが、微妙かもしれない。Modelに密接に結びついているので、そちらに定義させた方が良いかもしれない。
  • どう頑張っても時にはダーティーにやらなくてはいけないことがあるので、その時は諦める。
  • 型ヒントが効くようにちゃんとdocstring書いた方が良いかもしれない。

Model

  • Modelクラスはmodel.pyに定義。
  • 入力は、モデル名(とfold名)・データ・パラメータなど。
  • 責務は、訓練・予測・モデルの保存・モデルの読み込みなど。
  • 各foldごとに1つモデルのインスタンスが作成される。
  • baggingなどもこの中で処理することができる。

主なメソッドは以下のとおり。

シグネチャ 概要
__init__(run_fold_name) モデル名(とfold名)がないと不便。
train(prms, x_tr, y_tr, x_te, y_te) パラメータとtrainのx, yとvallidationのx, yを入力とし、モデルの訓練を行う。
train_without_validation(prms, x_tr, y_tr) パラメータとtrainのx, yを入力とし、モデルの訓練を行う。trainの全データで訓練したいとき用。
save_model() モデルを保存する。
load_model() モデルを読み込む。
predict(x_te) testやvalidationのxを入力とし、訓練されたモデルから予測値を返す。

Runner

  • Runnerクラスもmodel.pyに定義。
  • 入力は、モデル名・入力データのリスト・パラメータなど。
  • 責務は、データの読み込み、インデックスの読み込み、クロスバリデーションでの訓練(精度の出力も含む)、訓練されたモデルを使ってのテストデータの予測、hyperopt用の精度の出力など。
  • データの読み込みは単にファイル名の指定だけでなく細かく修正することが多いので、Runnerでそこだけ調整できるのは便利。
  • インデックスは複数に対応するようにしておかないと、後で面倒。
  • ログとクロスバリデーションの精度を標準出力とファイルに出力。
  • 動作確認用に少ないデータで流せるよう設計しておけば楽なのだが、対応が面倒なのであまり出来ていない。

主なメソッドは以下のとおり。 各Runnerで定義が必要なのはbuild_model, load_x_train, load_x_test。 ランなどで外部から使用する必要があるのはrun_train, run_test, run_hoptあたり。

シグネチャ 概要
__init__(run_name, flist, prms) モデル名、特徴量名(list)、パラメータ(dict)のセット。
build_model(run_fold_name) 使用するModelの定義。
load_x_train() trainのxの読み込み。
load_x_test() testのxの読み込み。
load_y_train() trainのyの読み込み。
load_w_train() trainのweightの読み込み。weightを変えて訓練したい場合に。
load_index_fold(i_fold) cv用インデックスの読み込み。(trainのidx, validationのidx)を返す。5foldの場合、基本はi_foldに0-4が入る。違うcv用インデックスで計算したい場合、"a0"や"a4"のように指定する。
load_x_fold(i_fold) foldを指定したxの読み込み。(trainのx, validationのx)を返す。
load_y_fold(i_fold) foldを指定したyの読み込み。(trainのy, validationのy)を返す。
load_w_fold(i_fold) foldを指定したweightの読み込み。(trainのweight, validationのweight)を返す。
train_fold(i_fold) 指定したfoldでの訓練を行い、訓練されたモデルを返す。
run_hopt(i_fold = "a0") hyperopt用。指定したfoldでの訓練を行った後に、loss(と訓練されたモデル)を返す。
run_train() クロスバリデーションでの訓練を行う。とりあえず5cv固定。各foldのモデルの保存、精度のログ出力についても行う。
run_test() クロスバリデーションで訓練した各foldのモデルの平均により、testのyの予測を行う。
run_train_alltrain() trainの全データで訓練し、そのモデルを保存する。
run_test_alltrain() trainの全データで訓練したモデルにより、testのyの予測を行う。

パラメータチューニング

パラメータチューニングはhyperoptを用いて、5cvのうちの1foldのみで行い、メインの計算とは別のcv用インデックスで行った。 また、fit_generatorにより1epochを3000枚程度に小さくし、patience=3のearlystoppingで行った。 なお、訓練時はhyperoptの結果を元にepochを固定した。

  • コードはhopt/hopt_resnet.pyなどを参照。
  • 計算時間に余裕があり、精度のばらつきが激しい場合は5cvなどで評価した方が良いが、neural netだと計算時間はさすがにきつい。
  • 同じインデックスだと、hyperoptに使ったfoldだけoverfitすることがあるので、stackingを行う場合にちょっと気になる。気にし過ぎかもしれない。
  • epochをそのままだと、patience=3だとかなり行き過ぎてしまう感じ。
  • epochを固定するのではなく、early_stoppingでsave_best_onlyを使う方法もあるかもしれない。

kerasのジェネレータ、data augmemtation

頑張った。kerasのImageDataGeneratorは、場合によっては少し手を加えないと使いづらい。

  • コードはmodel_keras_util.py, model_keras_util_augmentation.pyなどを参照。
  • 汎用のジェネレータがサポートされていないようだったので、keras.preprocessing.imageのIteratorなどを参考に作成。
  • data augmentationも、ImageDataGeneratorを元に切り貼りして作成。

ログ

  • コードはutil_log.pyを参照。出力はlogフォルダを参照。
  • ログを標準出力とファイルの両方に出力するようにする。
  • 時間の情報を付加することにより、なんとなく時間のかかっている処理がわかる。
  • 一般的な情報とクロスバリデーションの精度の情報の2つに分ける。
  • クロスバリデーションの精度の情報はltsv形式で出力しておく。時々util_result.pyをランすることでモデルごとにまとめる。

ヴィジュアリゼーション、分析

  • コードはanalyzeフォルダを参照
  • 背景領域を0, それ以外を1としてベクトル化し、kmeansクラスタリングを行った。画像が整えられているので、単純だが結構きれいに商品カテゴリごとに分けられる。
  • 巡回サラリーマン問題を解いてカテゴリ間の距離が最小になるように並べた。近いカテゴリが近くにくるので見やすい。
  • カテゴリごとに画像・正解ラベルを並べたpdfを作った。(運営からの許可が出てないのでpdfはアップロードしてません)
  • どうもipython notebookは自分に合わなく、pdfを生成することが多い。