stMind

about Tech, Computer vision and Machine learning

アーセンベンゲル監督のラストホームゲーム関連グッズ

ベンゲル監督の最後のエミレーツ、バーンリー戦で配られたグッズをebayで手に入れることができました。総額は、全部で2万円強。

送料が高かった(約3000円)のが痛かったけど、ベンゲル監督信者としては、これくらいなら十分出せますね!

今も、ebayではいくつか出品されていますし、現地に行けなくて悔しい思いをした人がいれば、見てみるといいかと思います。(merci arseneなどで検索)

  • Merci Arsene Tシャツ

  • キーリング

  • 22 GUN SALUT

これはマッチデープログラムではないのかな?22年間のベンゲル監督のスタッツが載ってました。

  • チームシート(メンバー表)

これはまあ、セットで購入したのでおまけみたいなもんですね(笑)

  • 20周年の記念キーリングとバッジ

これは20周年のときの限定。裏にはちゃんとシリアル番号が振ってあります。

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を学習するモデルになっていて、これに限らず手法の工夫ポイントはまだあるようにも思いますが、進歩性としてはどこまで残っているんだろう?とは思います。

medium.com

Deep Learning時代は、研究から応用までのスピードが速いですね...

KerasでGAN

towardsdatascience.com

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では、まだ単なる矩形画像にしかなっていない

f:id:satojkovic:20180521235550p:plain

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

f:id:satojkovic:20180521235705p:plain

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

f:id:satojkovic:20180521235922p:plain

レポジトリ

github.com

KerasでSemantic segmentation

画像ではなく、ピクセル単位でクラス分類するSegmentationのタスク。 fast.aiにあるtiramisuが実装もあって分かりやすいので試してみた。下記のコードスニペットは、fast.aiのオリジナル実装ではなく、keras2で書き直されたjupyter notebookのコードをベースに、自分で若干の手直しをしたものを使っている。

github.com

tiramisu

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の構成と実装
    • BN(Batch Normalization)、ReLU、3x3 same convolution(とdropout)を組み合わせたLayerで特徴マップを生成
      • growth rateが特徴マップ数を表す
    • 特徴マップを入力に連結(concat)
    • 次の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でも学習を実行出来るのは良い点。

f:id:satojkovic:20180501234738p:plain

WIndowsでTensorflow C++ APIを使う準備

TL;DR

WindowsでTensorflowのC++ APIを使わなければいけないケースというのはそれほど多くないと思うし、どうしてもという理由がなければやらない方が良いと思う!

ステップ

基本は、下記のブログを参照すれば出来る。

tadaoyamaoka.hatenablog.com

  1. cmakeを使って、VS2015のソリューションを生成
    • Tensorflowのソースディレクトリはtensorflow/tensorflow/contrib/cmake
    • ソリューションファイルの生成先は、例えばtensorflo/cmake_buildを作って指定する
    • CUDAは9.0で、CuDNNは7.0
  2. configureして、swigが見つからないというエラーが出たらswig.exeのパスを指定
    • swigwin-3.0.12をダウンロード
    • swigwin-3.0.12/swig.exeを指定するだけ
  3. 再度configure、configure doneになったらgenerate
  4. generate doneになったらOpen projectでソリューションファイルを開く
  5. x64でReleaseビルド

注意点

  • ビルドが終了するまで数時間はかかる
  • メモリ16GBのマシンでビルドしたら、いくつかのプロジェクトでリソース不足的なエラーでビルド出来てなかった。結構なハイスペックが要求されそう。

あなたは世界を変えない

Ruby on Railsの作者DHHさんのmediumエントリー。最近読んだ中でも一番良いなと思った。

TwitterFacebookも世界を変えよう❗という明確なミッションを持って始まったわけではないし、そういうミッションを持つことは大変な重荷。壮大な幻想(ミッション)を持つことで、毎日毎日会議を繰り返す日々に無理やり意味をもたせようとしているのでは?みたいな指摘も。

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で公開している人がいた❗

図解が上手い❗今まで見てきたDeep Learningの解説資料の中でも、分かりやすいランキングのかなり上の方に位置すると個人的に思う。