CNN による画像分類で使われる前処理・テスト時処理まとめ
とりあえず ImageNet 系の論文で、目に入ったものから順々にまとめていきます。情報・ツッコミ歓迎。
前処理・Data Augmentation
Mean Subtraction
入力画像から平均を引く。[103.939, 116.779, 123.68] を各ピクセルから引く。VGG はこれ。
Per-pixel Mean Subtraction
入力画像から平均を引く。ピクセル・チャンネルごとに計算された平均を引く。即ち、224x224x3 個の値について個別に平均を計算し用いる。AlexNet 論文から使われており、ResNet もこれ。
Random Crop
256x256 ピクセルに画像をリサイズし、そこから 224x224 のパッチをランダムに取り出す。AlexNet 論文で使われていた。ちなみに Chainer の ImageNet サンプルはこれと Horizontal Flip をやっている(これしかやっていないので相応の精度しか出ない)。
Horizontal Flip
画像をランダムにフリップする。ImageNet では縦方向はなく水平方向の flip のみが使われるようであり、AlexNet 論文以降必ず使われているようだ。
Scale Augmentation
まず画像をリサイズする。[256, 480] からランダムに短辺の長さを選ぶ。次に、224x224 のパッチをそこからランダムサンプルする。
VGG 論文から使われており、ResNet 本家論文でも使われる。
Aspect Ratio Augmentation
上の Scale Augmentation に加え画像のアスペクト比を [¾, 4/3] で変換する・・・ぽい?(論文の記述が短くわかりにくい)
GoogLeNet の論文で使われている。FAIR の ResNet 再現記事でも使われており、本家から 1.2% の精度向上に寄与したらしい。
Color Augmentation
AlexNet 論文で行われた方法は以下である。各ピクセルの RGB を 3 次元のベクトルの集合だと考え PCA をかける。ガウス分布でノイズを生成し、固有ベクトル方向にノイズを加える。乱数は各ピクセルではなくパッチ全体に対して共通。論文によると AlexNet の精度への寄与は 1% 程度。ResNet 本家論文でも使われている。PCA した結果はここにあったり。
“Some Improvements on Deep Convolutional Neural Network Based Image Classification” 論文で行われた方法は以下である。contrast, brightness, color を、[0.5, 1.5] の間でランダムに変更する。変更の順番もランダムに選択する。(ご丁寧に、Python image library (PIL) を使ったと書いてある。)その後で、AlexNet 論文と同様の操作を行う。FAIR の ResNet 再現記事ではこれを利用したと書いてあったが、精度への寄与はとても小さかったらしい。
テスト時
Ensemble
複数のモデルを独立に学習し、それらの予測を平均する。
GoogLeNet 論文では、ネットワークは同じだが入力の処理法を変えた 7 つのモデルをアンサンブルしているらしい。GoogLeNet の論文は全体的に詳細が濁されているのでよくわからない。single crop だと top-5 error が 2% 弱程度向上。
ResNet 本家論文では、34B, 34C, 50, 101, 152×2 の 6 モデルをアンサンブルしている。fully-convolutional-form で top-5 error に 1% 弱程度向上。
10-crop Testing
テスト画像 1 つから data augmentation と類似した手順で 10 個のパッチを切り出し、それぞれに対する予測を平均して答える。
AlexNet 論文では、(4 スミ+中央)×(反転の有無)で 10 個のパッチを切り出している。GoogLeNet は 144 パッチも試してる。
GoogLeNet 論文では、crop 数の精度への影響が載っている。top-5 error で、10 crops で約 1% 弱向上、144 crops で 2% 強向上(下表は GoogLeNet 論文より引用)。
Fully-convolutional-form Testing
まず、全結合層たちを畳み込み層とみなす。例えば、直前の画像サイズが s×s であれば、最初の全結合層は s×s → 1×1 の畳み込み層であるとみなす。すると、ネットワークは fully convolutional (全レイヤーが畳み込み)となる。fully convolutional なネットワークの利点は、入力の画像サイズが変化しても適用することができることである(ただし、出力サイズも変化する)。
テスト画像をリサイズする。この時の画像サイズは、学習で使っている画像サイズと一致しているとは限らないし、正方形とも限らない。例えば、短辺を 480 にする。ネットワークをこの画像に適用する。出力を average pooling してそれを予測とする。
さらに、テスト画像のサイズを複数試し、その結果の平均を用いる。例えば、ResNet 本家論文では短辺を 224, 256, 384, 480, 640 になるようにリサイズしている。また、horizontal flip も試して平均を取る。
VGG から使われている。ResNet 本家論文を見るところ、10-crop Testing と比べて、大体 2% 程度精度に寄与している。
Chainer で全結合層を畳み込み層にするコードの例を mitmul さんから貰ったので貼っておきます。
def linear2conv(model, name, feature_size=1): """Convert Linear layer to 1x1 Convolution Layer args: model (chainer.Chain): The input model name (str): Name of a Linear link that will be converted to 1x1 conv ex. '/predictor/trunk/fc6' feature_size (int): The feature map size of convolution layer """ target = model lnames = [n for n in name.split('/') if len(n) > 0] for l in lnames[:-1]: target = target.__dict__[l] t = target.__dict__[lnames[-1]] assert ('Linear' in str(t.__class__)) out_dim, in_dim = t.W.data.shape in_dim = in_dim // (feature_size ** 2) conv = L.Convolution2D(in_dim, out_dim, feature_size) W = t.W.data.reshape((out_dim, in_dim, feature_size, feature_size)) conv.W.data, conv.b.data = W, t.b.data target.__dict__[lnames[-1]] = conv return model
参考文献
- A. Krizhevsky, I. Sutskever, and G. Hinton. Imagenet classification with deep convolutional neural networks. In NIPS, 2012. (AlexNet)
- A. G. Howard. Some Improvements on Deep Convolutional Neural Network Based Image Classification. CoRR abs/1312.5402, 2013.
- C. Szegedy, W. Liu, Y. Jia, P. Sermanet, S. Reed, D. Anguelov, D. Erhan, V. Vanhoucke, and A. Rabinovich. Going deeper with convolutions. In CVPR, 2015. (GoogLeNet)
- K. Simonyan and A. Zisserman. Very deep convolutional networks for large-scale image recognition. In ICLR, 2015. (VGG)
- K. He, X. Zhang, S. Ren, J. Sun. Deep Residual Learning for Image Recognition. In CVPR, 2016. (ResNet)
- Torch | Training and investigating Residual Nets
謝辞
- 宮戸さん
- mitmul さん
スヌープモードを設定して QPI を爆速で超える
スヌープモードの影響
マルチ CPU のマシンでメモリ帯域を測ると、別ソケット側のメモリとの帯域が死ぬほど遅いという現象がある。これは、CPU のスヌープモードがデフォルトの「Early Snoop」になっているからである。BIOS からこれを「Home Snoop」に変更することによって、帯域は大きく向上する。
この記事によると、Early Snoop では 6GB/s 程度であったのが、Home Snoop では 25GB/s 程度になっている。
ただし、Home Snoop にすると、代償として同一 NUMA ノード内でのレイテンシが 10% 程度悪くなってしまう。
スヌープとは
キャッシュコヒーレンシを保つ方法の 1 つがスヌープ方式(スヌープキャッシュ)である。各キャッシュメモリが、自身とよそのキャッシュラインの状況を管理し、メッセージにより情報交換を行う方式である。この情報交換の方式に、MESI プロトコル等が含まれる。
スヌープ方式以外のアプローチは、ディレクトリ方式、共有キャッシュなどである。
スヌープモードとは
技術的詳細は分からないが、スヌープモードを変更すると、このスヌープのアルゴリズムが切り替わると思われる。ちなみに、上述の Early Snoop, Home Snoop の他に Cluster on Die (COD) モードというのがある。これは、他の NUMA ノードへのアクセスがかなり少ない仮定を置いたモードのようで、仮想マシンを扱う場合などを想定しているようだ。
数列の和を計算するアルゴリズム
Cython の落とし穴
ポインタを使ってあれこれしたいときの落とし穴を会社で教えてもらった。
numpy の ndarray.data の挙動が型を指定するかによって変わる
tutorials NumpyPointerToC · cython/cython Wiki · GitHub
ここに書かれた方法2でポインタを取り出したい時、引数の型の指定を取り除くと、挙動が変わってしまう。コンパイルエラーも起きず、実行もできるが、ndarray の中が書き換わらないというかなり不思議な状態になる。
これは Cython (cimport) 側の numpy と Python (import) 側の numpy の両方で data が定義されており、Cython 側の定義が優先され上書きされるからである。
Regularized Greedy Forest (RGF) のロス関数をカスタマイズする
Regularized greedy forest (RGF) in C++
RGF の公式実装のロス関数をカスタマイズするには、C++ のコードを直接書き換えることになる。とはいえそんなに難しくない。以下の方法がお手軽。
- src/comm/AzLoss.cpp を開く
- AzLoss::getLosses 関数に loss_type == AzLoss_Xtemp という場合分けを追加
- o._loss1 にロス関数の 1 階微分を代入する
- o.loss2 にロス関数の 2 階微分を代入する
- 使用時には loss に 'Xtemp' を指定する
getLoss の方には追加せずとも動いた。getLoss と getLosses とか、_loss1 と loss2 とか、命名が滅茶苦茶すぎてヤバい……。
malloc にわざと失敗させる
#define _GNU_SOURCE #include <dlfcn.h> #include <stddef.h> #include <stdlib.h> #include <stdio.h> #include <assert.h> typedef void* (*malloc_t)(size_t); static malloc_t libc_malloc = NULL; static unsigned long malloc_max_called = 1000 * 1000; void initialize() { libc_malloc = (malloc_t)dlsym(RTLD_NEXT, "malloc"); if (libc_malloc == NULL) { perror("dlsym"); exit(1); } char const* const s = getenv("MALLOC_MAX_CALLED"); if (s == NULL) return; sscanf(s, "%lu", &malloc_max_called); } unsigned long count = 0; void* malloc(size_t n) { if (libc_malloc == NULL) initialize(); if (count >= malloc_max_called) return NULL; count++; return libc_malloc(n); }
% gcc -Wall -g -fPIC -shared failing_malloc.c -o failing_malloc.so -ldl % MALLOC_MAX_CALLED=1000 LD_PRELOAD=./failing_malloc.so ./a.out
機械学習アルゴリズムの直感を養えるデモ・記事
発見次第更新予定
デモ
Neural Network
Gradient Boosting
t-SNE
リンク集
記事
- How to Use t-SNE Effectively — Distill ・・・ t-SNE の出力はしばしば誤解を招くという話
- Neural Networks, Manifolds, and Topology -- colah's blog ・・・ NN の非線形な変換による中間表現の直感
NIPS のヤバいプロモーションビデオ
音を出して観るべき。
Python の処理系
- Pysco
- Pyjion
- Nuitka
- Skulpt
Chainerを明示的にCUDA無しを指定してインストールする
python setup.py --cupy-no-cuda install
https://github.com/pfnet/cupy/blob/master/cupy_setup_build.py#L211github.com
nvvp とか使いたさに中途半端に手元のマシンに CUDA を入れているとこういう指定が必要になる。
Deep Learning のデバッグ
ミニバッチ化時にミスることが多いので、以下のようなことをすると良いらしい。
- データごとの計算を書いてみて、1 つずつ計算したものと照合する
- ミニバッチサイズを 1 にして計算したものの和or平均とミニバッチで計算したものを比較する
numpy の行列乗算:matmul, dot, @
dot と matmul
2 次元では完全に同一。3 次元以上では異なる挙動をする。
- dot は a の最後の軸と b の最後から 2 番目の軸を掛け合わせる
- matmul は行列の配列だとみなして行列積を計算する
t-SNE の実装はどれを使うべきなのか?
scikit-learn の問題点
scikit-learn 信者としてはとりあえず scikit-learn の実装を使いたくなるが、scikit-learn の実装はおすすめできないらしい。
- -https://www.red dit.com/r/MachineLearning/comments/47kf7w/scikitlearn_tsne_implementation/ (はてなブログはred ditのURLを貼るとbad requestになり投稿できない謎仕様)
Besides being slower, sklearn's t-SNE implementation is fine once you realize the default learning rate is way too high for most applications. The definitive bh_tsne implementation by the author sets the learning rate to 200, and the original 2008 paper describes setting it to 100, while sklearn is set to 1000.
- 遅い
- デフォルト値の learning rate が大きすぎる
とのこと。それに加えて、自分の経験としては、Barnes Hut 木を指定してもメモリをもりもり確保して(即 Θ(n^2) のメモリを確保してる気がする)メモリ不足で死ぬ。だめ。
公式実装に基づくものたち
自分の結論
$ pip install bhtsne
からの
import sklearn.base import bhtsne import numpy as np class BHTSNE(sklearn.base.BaseEstimator, sklearn.base.TransformerMixin): def __init__(self, dimensions=2, perplexity=30.0, theta=0.5, rand_seed=-1): self.dimensions = dimensions self.perplexity = perplexity self.theta = theta self.rand_seed = rand_seed def fit_transform(self, x): return bhtsne.tsne( x.astype(np.float64), dimensions=self.dimensions, perplexity=self.perplexity, theta=self.theta, rand_seed=self.rand_seed)
バイナリ探偵をする時に使うコマンド
共有ライブラリ編
- env | grep LD
- md5sum
- 一致してるものを調べる
- ls -l
- シンボリックリンクを調べる
- readelf -a hogehoge.so
- readelf -a hogehoge.so | grep SONAME
- SONAME を調べる
- find build/ -name '*so' | xargs ldd | less
- 依存関係を調べる
- ldd `which hoge` とかも
- LD_DEBUG=all <コマンドを実行>
- 共有ライブラリの読み込みに関して大量のデバッグログが吐かれる
- objdump -d hogehoge.so | less
- アセンブラを読む
- 特に、<関数名> で検索することが多い
- man ld-linux.so
- readeof -d hoge.so
- nm -D hoge.so