Deep Learning時代のPose Estimation研究
[1602.00134] Convolutional Pose Machines
[1611.08050] Realtime Multi-Person 2D Pose Estimation using Part Affinity Fields
少し前まではPose estimationは非常に難しい問題だったように思いますが、Convolutional Pose MachinesやRealtime Multi-Person 2D Pose Estimation using Part Affinity Fieldsといった論文において、Fully convolutionalなNNによって人体関節をconfidence map(もしくはbelief mapやheatmapとも呼ばれる)として推定する手法が提案、有効性が検証され、さらにそれを実装したソフトウェアが公開されて応用探索されていくのを見ていると、Pose Estimation自体は既に研究フェーズからは移行したように感じます。
最近、GoogleからWebブラウザで実行可能なPoseNetモデルがリリースされたというニュースがありましたが、OpenPoseのような関節間の接続を表現するPart Affinity Fieldではなく、各ピクセルの関節に対するオフセットを表すOffset Vectorを学習するモデルになっていて、これに限らず手法の工夫ポイントはまだあるようにも思いますが、進歩性としてはどこまで残っているんだろう?とは思います。
Deep Learning時代は、研究から応用までのスピードが速いですね...
KerasでGAN
Mediumの記事を参考に、一番基本のGANについて試してみた。データセットはおなじみのfashion mnist。
GANのアーキテクチャ
ノイズ画像(100次元のランダムなベクトル)からfashion画像を生成するgeneratorは、3層の全結合層から成るネットワーク。各層の出力次元数は28, 29, 210としている。
def get_generator(optimizer, output_dim=784): generator = Sequential() generator.add( Dense( 256, input_dim=random_dim, kernel_initializer=initializers.RandomNormal(stddev=0.02))) generator.add(LeakyReLU(0.2)) generator.add(Dense(512)) generator.add(LeakyReLU(0.2)) generator.add(Dense(1024)) generator.add(LeakyReLU(0.2)) generator.add(Dense(output_dim, activation='tanh')) generator.compile(loss='binary_crossentropy', optimizer=optimizer) return generator
一方のdiscriminatorも3層の全結合層から成るネットワーク。出力ノードはrealかfakeかのバイナリ値。同じように、各層の出力次元数は210, 29, 28としている。
def get_discriminator(optimizer, input_dim=784): discriminator = Sequential() discriminator.add( Dense( 1024, input_dim=input_dim, kernel_initializer=initializers.RandomNormal(stddev=0.02))) discriminator.add(LeakyReLU(0.2)) discriminator.add(Dropout(0.3)) discriminator.add(Dense(512)) discriminator.add(LeakyReLU(0.2)) discriminator.add(Dropout(0.3)) discriminator.add(Dense(256)) discriminator.add(LeakyReLU(0.2)) discriminator.add(Dropout(0.3)) discriminator.add(Dense(1, activation='sigmoid')) discriminator.compile(loss='binary_crossentropy', optimizer=optimizer) return discriminator
GANの学習
最初にdiscriminatorだけを学習してパラメータを更新した後、ノイズ画像(100次元ベクトル)からgeneratorとdiscriminatorを通してrealかfakeかを判定するend-to-endのネットワーク(get_gan_network)に対して、discriminatorは重みを固定してgeneratorだけを学習する、という二段階の処理です。
discriminatorの学習では、x_trainからランダムにピックアップしたreal画像とgeneratorで生成したfake画像をそれぞれbatch_size個用意して、それらを連結したデータ(Xとy_dis)を作成して与えています。また、ノイズ画像から生成した画像をrealと判定するようなgeneratorにするために、ラベルを1としてgeneratorを学習しています。
def train(epochs=1, batch_size=128): # Get the training and testing data (x_train, y_train, x_test, y_test), x_height_width = load_mnist_fashion() # Split the training data into batches of size 128 batch_count = x_train.shape[0] // batch_size # Build our GAN netowrk adam = get_optimizer() generator = get_generator(adam, output_dim=x_train.shape[1]) discriminator = get_discriminator(adam, x_train.shape[1]) gan = get_gan_network(discriminator, random_dim, generator, adam) for e in range(1, epochs + 1): print('-' * 15, 'Epoch %d' % e, '-' * 15) for _ in tqdm(range(batch_count)): # Get a random set of input noise and images noise = np.random.normal(0, 1, size=[batch_size, random_dim]) image_batch = x_train[np.random.randint( 0, x_train.shape[0], size=batch_size)] # Generate fake images generated_images = generator.predict(noise) X = np.concatenate([image_batch, generated_images]) # Labels for generated and real data y_dis = np.zeros(2 * batch_size) # One-sided label smoothing y_dis[:batch_size] = 1.0 # Train discriminator discriminator.trainable = True discriminator.train_on_batch(X, y_dis) # Train generator noise = np.random.normal(0, 1, size=[batch_size, random_dim]) y_gen = np.ones(batch_size) discriminator.trainable = False gan.train_on_batch(noise, y_gen) if e == 1 or e % 20 == 0: plot_generated_images(e, generator, train_shape=x_height_width)
生成された画像
batch_size=128で、300epoch学習を回した結果。
- Epoch1では、まだ単なる矩形画像にしかなっていない

- Epoch100では、各アイテムがかなり分かる程度になっているが、一部単なる矩形画像が残っている

- Epoch300では、各アイテムがはっきりと分かる程度に生成された

レポジトリ
KerasでSemantic segmentation
画像ではなく、ピクセル単位でクラス分類するSegmentationのタスク。 fast.aiにあるtiramisuが実装もあって分かりやすいので試してみた。下記のコードスニペットは、fast.aiのオリジナル実装ではなく、keras2で書き直されたjupyter notebookのコードをベースに、自分で若干の手直しをしたものを使っている。
tiramisu
- 論文は、The One Hundred Layers Tiramisu: Fully Convolutional DenseNets for Semantic Segmentation
- tiramisuはDenseNetのアイデアをSegmentationに適用したアーキテクチャ。FC-DenseNet。
- DenseNetはCVPR2017でBest paper award

tiramisuのネットワーク
- DenseNetはCVPR2017でBest paper award
- 特徴抽出を行うDown-sampling pathと入力画像サイズを復元するUp-sampling path
Down-sampling path
- Down-sampling pathの構成と実装
- DB(Dense Block)とTD(Transition Down)の繰り返しで特徴抽出
- DBの最後の出力特徴マップをUp-sampling pathのためのskip connectionとして保存
def down_path(x, nb_layers, growth_rate, keep_prob, scale): skips = [] for i, nb_layer in enumerate(nb_layers): x, added = dense_block(nb_layer, x, growth_rate, keep_prob, scale) skips.append(x) x = transition_down(x, keep_prob, scale) return skips, added
- DBの構成と実装
def dense_block(nb_layers, x, growth_rate, keep_prob, scale): added = [] for i in range(nb_layers): b = conv_relu_batch_norm( x, growth_rate, keep_prob=keep_prob, scale=scale) x = concat([x, b]) added.append(b) return x, added
- TDは空間解像度の削減、要はpoolingと同等の処理
- 論文では1x1 convolutionと2x2 poolingの組み合わせが提案されているが、fast.aiではstrideが2の1x1 convolutionを提案。
def transition_down(x, keep_prob, scale): return conv_relu_batch_norm( x, x.get_shape().as_list()[-1], ksize=1, scale=scale, keep_prob=keep_prob, stride=2)
Up-sampling path
- Up-sampling pathの構成と実装
- TU(Transition Up)、skip connectionとの連結(concat)、DBの繰り返しで入力画像サイズに復元
def up_path(added, skips, nb_layers, growth_rate, keep_prob, scale): for i, nb_layer in enumerate(nb_layers): x = transition_up(added, scale) x = concat([x, skips[i]]) x, added = dense_block(nb_layer, x, growth_rate, keep_prob, scale) return x
- TUは特徴マップをupsamplingするtransposed convolutionで構成
- 入力と同じch数で、3x3 kernel size、strides 2で2倍の特徴マップを生成
def transition_up(added, scale): x = concat(added) _, row, col, ch = x.get_shape().as_list() return Conv2DTranspose( ch, 3, kernel_initializer='he_uniform', padding='same', strides=(2, 2), kernel_regularizer=l2(scale))(x)
学習
- 56層バージョン。FC-DenseNet56
- OptimizerはRMSProp、学習率の初期値は1e-3、学習率減衰は0.00005
- L2正則化の係数1e-4、Dropout rateは0.2、growth rateは12
- データセットはcamvid、360x480から224x224をクロップして入力画像とする
- batchsizeは10、epochsは30で実行
結果
- 24epochでvalidation accuracyが85%
- 結果画像を見ると、道路と白線は分類出来ているようだが、車と背景との分離はもう一歩。Ground-Truthにはない画像右上の柱が推定出来ている。
- パラメータ数が少ないモデルという特徴があり、実際にGPUの無いMacでも学習を実行出来るのは良い点。

WIndowsでTensorflow C++ APIを使う準備
TL;DR
WindowsでTensorflowのC++ APIを使わなければいけないケースというのはそれほど多くないと思うし、どうしてもという理由がなければやらない方が良いと思う!
ステップ
基本は、下記のブログを参照すれば出来る。
- cmakeを使って、VS2015のソリューションを生成
- Tensorflowのソースディレクトリは
tensorflow/tensorflow/contrib/cmake - ソリューションファイルの生成先は、例えば
tensorflo/cmake_buildを作って指定する - CUDAは9.0で、CuDNNは7.0
- Tensorflowのソースディレクトリは
- configureして、swigが見つからないというエラーが出たらswig.exeのパスを指定
- swigwin-3.0.12をダウンロード
swigwin-3.0.12/swig.exeを指定するだけ
- 再度configure、configure doneになったらgenerate
- generate doneになったらOpen projectでソリューションファイルを開く
- x64でReleaseビルド
注意点
- ビルドが終了するまで数時間はかかる
- メモリ16GBのマシンでビルドしたら、いくつかのプロジェクトでリソース不足的なエラーでビルド出来てなかった。結構なハイスペックが要求されそう。
あなたは世界を変えない
Ruby on Railsの作者DHHさんのmediumエントリー。最近読んだ中でも一番良いなと思った。
TwitterやFacebookも世界を変えよう❗という明確なミッションを持って始まったわけではないし、そういうミッションを持つことは大変な重荷。壮大な幻想(ミッション)を持つことで、毎日毎日会議を繰り返す日々に無理やり意味をもたせようとしているのでは?みたいな指摘も。
Set out to do good work. Set out to be fair in your dealings with customers, employees, and reality. Leave a lasting impressions on the people you touch, and worry less (or not at all!) about changing the world. Chances are you won’t, and if you do, it’s not going to be because you said you would.
良い仕事をしよう、顧客や従業員、現実に対してフェアでいよう、関わる人に対して良い印象を残そう。そういうことに集中することの方がより大事だよというアドバイスでした。
I have no illusions that my time in this world is going to be remembered through the ages. When it’s over, I’ll be so fortunate as to have left an impression on my friends, my family, and a few colleagues in the industry. And those impressions will fade quickly, so they aren’t even worth elevating much.
私の人生は世代を通じて思い出されるという幻想は持ってない。終わったときに、友達や家族、何人かの同僚に思い出してもらえれば相当幸せだろう。それらの印象もいずれは薄れていくし、もてはやすようなものではない。
相当なオッサンになってきたので、こういう考えもそうだよなぁと思うようになった。
P.S. いくつかmediumでエントリーを見たけど、DHHさんの英語って難しくないですか❓独特の言い方というか、自分が知らないだけなのか…
CourseraのDeep Learning Specializationコースのまとめノートを見つけた
先日全てのレクチャーをやり終えたCourseraのDeep Learning Specializationコース。そのまとめノートをSlideShareで公開している人がいた❗
My notes from @AndrewYNg excellent deep learning specialization on Coursera (https://t.co/SdzM1S9gA2)
— Tess (@TessFerrandez) 2018年3月5日
Yay! finally finished it
Full notes on slideshare:https://t.co/XuuRGgq39Z pic.twitter.com/k210NaRci0
図解が上手い❗今まで見てきたDeep Learningの解説資料の中でも、分かりやすいランキングのかなり上の方に位置すると個人的に思う。
PASCAL-Part Datasetのmatファイルフォーマットがようやくわかった

FCNでSemantic segmentationをやってみようと思いたち、物体のパーツ単位で正解マスクが提供されているPASCAL-Part Datasetを使うことにした。のだが、正解マスクが含まれているmatファイルのフォーマットがよくわからない!データセットにはオリジナル画像 やマスク画像をタイル表示するデモ用のmatlabスクリプトがあるけれど、matlabはあまり慣れてない... こういうのは慣れてるものを使えばいいやということで、ipythonでインタラクティブに解析することにしたよ。
ルート > オブジェクト > パーツ
結局、データのルート > オブジェクト > オブジェクト毎パーツというような階層になってて、書いてみれば直感的にわかるけど、多量にネストされていて理解するまでに時間がかかった...
例えば、下の画像であれば

In [1]: import scipy.io as sio In [2]: data = sio.loadmat('Annotations_Part/2008_003228.mat') In [3]: data.keys() Out[3]: dict_keys(['__header__', '__version__', '__globals__', 'anno']) In [4]: data['anno'][0][0][0] Out[4]: array(['2008_003228'], dtype='<U11') # ここがルートで、この画像に含まれるオブジェクト数は4 In [5]: data['anno'][0][0][1].shape Out[5]: (1, 4) # オブジェクトの情報 # オブジェクトはpersonが3個、catが1個(画像左の黒い領域) In [6]: data['anno'][0][0][1][:, 0][0][0] Out[6]: array(['person'], dtype='<U6') In [7]: data['anno'][0][0][1][:, 1][0][0] Out[7]: array(['person'], dtype='<U6') In [8]: data['anno'][0][0][1][:, 2][0][0] Out[8]: array(['person'], dtype='<U6') In [9]: data['anno'][0][0][1][:, 3][0][0] Out[9]: array(['cat'], dtype='<U3') # パーツの情報 # 1番目のオブジェクトのパーツ数, personはmaxで24パーツまで In [11]: data['anno'][0][0][1][:, 1][0][3].shape Out[11]: (1, 16) # 0番目のパーツの名前 => head In [16]: data['anno'][0][0][1][:, 1][0][3][:, 0][0][0] Out[16]: array(['head'], dtype='<U4') # 1番目のパーツの名前 => left ear In [21]: data['anno'][0][0][1][:, 1][0][3][:, 1][0][0] Out[21]: array(['lear'], dtype='<U4') # 正解マスク In [22]: data['anno'][0][0][1][:, 1][0][3][:, 1][0][1] Out[22]: array([[0, 0, 0, ..., 0, 0, 0], [0, 0, 0, ..., 0, 0, 0], [0, 0, 0, ..., 0, 0, 0], ..., [0, 0, 0, ..., 0, 0, 0], [0, 0, 0, ..., 0, 0, 0], [0, 0, 0, ..., 0, 0, 0]], dtype=uint8) # 正解マスクのサイズ = 画像サイズ In [23]: data['anno'][0][0][1][:, 1][0][3][:, 1][0][1].shape Out[23]: (375, 500)
後は、これを取り出して画像とペアにすれば、FCN用のPart datasetが準備出来ることになる!(ほんとはまだ)
おまけで、matlabのデモコードを実行したときのマスク画像(右下)はこんなの。
