ぱたへね

はてなダイアリーはrustの色分けができないのでこっちに来た

AutoML Vision Edgeが作るニューラルネットワーク

AutoML Vision Edgeがどのようなニューラルネットワークを作るのかが気になったので調べてみました。 Kaggleの犬猫分類(画像による2クラス分類)をAutoML Vision EdgeでTensoflow Liteのモデルを作り、ネットワーク構造を読み出して分析しました。まだ、完全に理解したわけではないので間違っている所もありそうでうが、雰囲気がつかめたのでまとめてみます。

準備

データを用意する。

ここから、犬と猫の画像をそれぞれ2000枚選びました。
https://www.kaggle.com/c/dogs-vs-cats

データをAutoMLで扱えるようにする。

この2つを見ればだいたいわかります。

ディレクトリに分けた画像をzipにして4000枚アップロードしたところ、ディレクトリ構造は認識してくれず、結局手で4000枚ラベル付けしました。ラベル付けのUIはあまり使いやすくなかったです。まずは500枚くらいアップロードして、作戦を考えた方が良いでしょう。今回であれば、先に犬の画像を2000枚アップロードし全てにdogのラベルをつけた後、猫の画像を2000枚アップロードして未分類の物を全てcatにすれば楽でした。自力でラベル付けした時点で少なくとも10枚は間違っていたのですが、今回は精度はあまり興味が無いのでこのまま進みました。

モデルをトレーニングする。

トレーニングの方法はここの通りに進めればOKです。
https://qiita.com/shinkoizumi0033/items/4ae75e0bebb2848cbd54

上で準備した同じデータセットに対して、Lowest latency、Best Trade-off、Higher accuracyの設定でそれぞれトレーニングしました。トレーニングは1時間半程度(1時間では終わらなかった)で終わり、メールが届くのでそのリンク先を開くと結果がみれます。

f:id:natsutan:20190504101230p:plain

精度が悪いのは、上に書いたようにデータセットがすでに間違っているからだと思います。

モデルをダウンロードする。

訓練が終わった後、、AutoML Visionの画面からモデルを選択し、PREDICTのタブをクリックします。

f:id:natsutan:20190504101340p:plain

この画面でEXPORTをクリックすると、GCP上にデータがエクスポートされます。gsutilを使ってローカルへ持ってきましょう。

TFLiteのモデルをJSONへ変換する。

TFLiteモデルの中身は、flatbufferです。

github.com

flatbuffersをインストールして、flatc コマンドを使えるようにします。

フォーマットの定義ファイルは、wgetで持ってきましょう。

wget https://raw.githubusercontent.com/tensorflow/tensorflow/master/tensorflow/lite/schema/schema.fbs

flatcコマンドが動けばJsonファイルができるはずです。

flatc --json --defaults-json  --strict-json  schema.fbs  -- XXXX.tflite

JSONファイルを読み込んでdotファイルを作る。

ここは自作ツールでやりました。ONNXで同じようなことをしていれば簡単にできると思います。そんなに難しくないです。需要があれば公開します。

dotファイルができれば、pngやPDFに変換して完了です。

オプションによるモデルの比較

モデルの大きさ比較

Lowest latency Best Trade-off Higher accuracy
TFliteファイルのサイズ(Kb) 548 3,179 5,851
推論速度の見積もり(ms) 22 65 105
計算のサイズ UINT8 UINT8 UINT8
層数 65 64 64

TFliteの中身を見てみましたが特に圧縮されている様子も無く、ファイルサイズがほぼパラメータのデータサイズと思って良さそうです。データの持ち方は全モデルでUINT8でした。意外にもレイヤ数はほぼ同じでした。

可視化したニューラルネットワーク

とりあえず3つ並べてみました。

f:id:natsutan:20190504104628p:plain:w200 f:id:natsutan:20190504104625p:plain:w200 f:id:natsutan:20190504104628p:plain:w200

ニューラルネットワークの基本構造

3つのモデルの構造を見てみました。基本的な構造は3つとも同じのようにみえます。

f:id:natsutan:20190504101835p:plain

ResNetに似た構造が、10~11個シーケンシャルにつなげています。これだけで5層あるので5×10で50層、最初、真ん中、最後に少し違う構造が入っていて、合計で64層のニューラルネットワークを構成しています。
Lowest latency が11個、残りの2つが10層で全体のニューラルネットワークを構成しているので、微妙なところではモデルに違いがありそうです。これが、速度、精度との問題なのか、AutoMLで使われているであろう乱数の結果による違いなのかは分かりません。もう少し、いろんなデータで試す必要がありそうです。

入力層直後を除き、2DConvが全て1x1のみで、5x5等はDepthwiseConv2Dでのみ出てくるところが面白いです。

データ数の比較

3つのモデルについて、先頭と最後のデータ数(重みの数)を比べてみました。Best Trade-offとHigher accuracyは構造が同じで、パラメータの数だけの違いに見えます。Lowest latencyはネットワーク構成が違い、パラメータを大きく減らしています。Best Trade-offの半分以下です。

layer Lowest latency Best Trade-off Higher accuracy
0 Conv2D 3x3 16 Conv2D 3x3 32  Conv2D 3x3 48
1 DConv2D 3x3 16 DConv2D 3x3 32  DConv2D 3x3 48 
2 Conv2D 8 Conv2D 16  Conv2D 24 
3 Conv2D 24 Conv2D 48  Conv2D 72
4 DConv2D 3x3 24 DConv2D 3x3 48  DConv2D 3x3 72
5 Conv2D 8 Conv2D 24  Conv2D 24
6 Conv2D 24 Conv2D 72  Conv2D 72 
7 DConv2D 3x3 24 DConv2D 3x3 72 DConv2D 3x3 72
8 Conv2D 8 Conv2D 24  Conv2D 24
9 Add Add   Add
10 Conv2D 24 Conv2D 72  Conv2D 72
 
50 Add Conv2D 1152  Conv2D 1632
51 Conv2D 384 DConv2D 5x5 1152  DConv2D 5x5 1632
52 DConv2D 5x5 384 Conv2D 192  Conv2D 272
53 Conv2D 64 Add  Add
54 Add Conv2D 1152   Conv2D 1632
55 Conv2D 384 DConv2D 5x5 1152  DConv2D 5x5 1632
56 DConv2D 5x5 384 Conv2D 192  Conv2D 272
57 Conv2D 64 Add   Add
58 Add Conv2D 1152  Conv2D 1632
59 Conv2D 384 DConv2D 3x3 1152   DConv2D 3x3 1632
60 DConv2D 3x3 384 Conv2D 320   Conv2D 448
61 Conv2D 112 Conv2D 1280   Conv2D 1280
62 Conv2D 1280 Reducer   Reducer
63 Reducer FC  FC
64 FC SoftMax   SoftMax
65 SoftMax  

こうやって並べてみると、データ数が8の倍数になっているのは分かります。例えば24個のUINT8(8bit)のデータを22個にしても、速度への影響はあまりないのだと思います。ギリギリで削るというよりは、いくつかの組み合わせを試しているように見えます。おそらく、エッジ動作での速度を確保するために、クラウドで使用するAutoMLよりは制約がきつくなっていると感じます。4x4みたいなカーネルの方が精度が高かったとしても、エッジ側のライブラリが3x3と5x5にしか対応していなければ、速度がでないとかの事情はありそうです。

上には書いていないですが、アクティベーション層が全て計算が少ないReLUになっているのもエッジ動作を想定してのことだと思います。

量子化

ちゃんと調査したわけでは無いのであくまで予想ですが、量子化は2種類のフォーマットを組み合わせています。

ここに詳しく説明されていますが、まだ全部みれてません。

heartbeat.fritz.ai

A typical quantized layer

一般的なTF Liteで使われる量子化フォーマット

          "quantization": {
            "min": [
              -22.376556
            ],
            "max": [
              23.63975
            ],
            "scale": [
              0.180456
            ],
            "zero_point": [
              124
            ],

Fake Quantization

おそらく、単純にかけ算をするだけのフォーマット。

          "quantization": {
            "scale": [
              0.011161
            ],
            "zero_point": [
              0
            ],

他に気になったこと

定番のMaxpoolingやBatchNormalizationが入っていないのも気になりました。Maxpoolingに関してはResNetの構造を上手く使っているので必要なさそうですが、BatchNormalizationはあまり効果がないのかConvの計算に組み込まれて(Fuse)いるのかは良く分かりません。

まとめ

  • AutoML Vision Edgeで作ったモデルは、JSONに変換して構成を見ることができる。
  • 今回のケースでは、ResNetっぽいNNを生成している。いくつかの組み合わせから選んでいる様子。
  • エッジでの計算早くなりそうなレイヤーを組み合わせている。
  • Lowest latencyは層数こそ変わらないが、かなり重みの数を減らしている。

次は物体検出が、どのようなネットワークで構成されているかをやってみたいです。

Rustでnomを使ってみた

nomはrust用のパーサコンビネータです。 https://github.com/Geal/nom/

Qiitaの記事を見て使ってみましたが、小一時間ハマったので結果をメモ

元ネタ https://qiita.com/k5n/items/e95842744fc5db931d03

このサンプルが最新のnomでは動きませんでした。

Cargo.toml

[dependencies]
nom = "^4.2"

とりあえずchain!が影も形もなくて、do_parse!で置き換え、結果の受け取り方も少し変わっています。

#[macro_use]
extern crate nom;

use nom::{space, alpha};

named!(name_parser<&str>,
    do_parse!(
        tag!("Hello,") >>
        space >>
        name: map_res!(alpha, std::str::from_utf8) >>
        (name)
    )
);

fn main() {
    match name_parser("Hello, world!".as_bytes()) {
        Ok(name)  => println!("name = {:?}", name),
        _ => println!("Error")
    }
}

これを実行すると、ちゃんと動いているようです。

name = ([33], "world")

機械学習の説明

今日来たPiqcyから Attention is not Explanation の紹介に反応。 https://arxiv.org/abs/1902.10186

「自然言語処理においてAttentionはよくモデルの判断根拠として用いられるが、本当に説明になっているのかを検証した研究。結果として、GradientベースのスコアとAttentionは乖離があり、またAttentionの分布が異なるよう変更しても予測結果を維持することができることを確認」

意味づけとか解釈の話は、それで一つの研究ジャンルって所まで分かってきた。GRAD-CAMはいろんなアルゴリズムの中でも、意味づけするための仕組みが不要、計算量が少ない事がメリットで、正確さ(これもいろいろ方向がある)はそれなりのアルゴリズムっぽい。

この本が分かりやすく勉強中。 https://christophm.github.io/interpretable-ml-book/

TPUがサポートしていないOPをTensorboardで調べる

TPUを使うと良く謎のエラーに悩まされます。

RuntimeError: Compilation failed: Compilation failure: Detected unsupported operations when trying to compile graph cluster_666512355060360340[] on XLA_TPU_JIT: Placeholder

どのoperationをTPUがサポートしていて、そうでないのかが良く分からず悩んでました。 Tensorboardを使うと、どのoperationがTPUをサポートしているのかを教えてくれます。

やり方は簡単で、tensorboardのgraphを表示させた状態で、TPU compatibilityをクリックするだけです。

f:id:natsutan:20190119100635p:plain

この例ですと6のoperationがサポート対象外です。サポート対象外であってもCPUで動かせる時もあるので全部TPU compatibilityにする必要は無いのですが、エラーの解決やパフォーマンス向上には役に立ちますね。

GCP, TPU, Tensorflow, Tensorboard

ローカルで動いたTensorflow環境をTPUに持って行くとすんなり動かない。動いた物を集めながら、差分を少しずつでも調べていかないと前に進まない感じがする。少しずつ環境をつくっています。やりたいことがいっぱいあるがもどかしい。

TPUを使う

Kerasを使わずにTPUを使ったサンプル。これはあっさりと動きました。

import os
import tensorflow as tf
from tensorflow.contrib import tpu
from tensorflow.examples.tutorials.mnist import input_data

MAX_EPOCH = 50

mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
num_input = 28 * 28 * 1
num_classes = 10

x_ = tf.placeholder("float", shape=[None, num_input], name='X')
y_ = tf.placeholder("float", shape=[None, num_classes], name='Y')

is_training = tf.placeholder(tf.bool)
x_image = tf.reshape(x_, [-1, 28, 28, 1])

conv1 = tf.layers.conv2d(inputs=x_image, filters=32, kernel_size=(5, 5), padding='same', activation=tf.nn.relu)
pool1 = tf.layers.max_pooling2d(inputs=conv1, pool_size=(2, 2), strides=2)
conv2 = tf.layers.conv2d(inputs=pool1, filters=64, kernel_size=(5, 5), padding='same', activation=tf.nn.relu)
pool2 = tf.layers.max_pooling2d(inputs=conv2, pool_size=(2, 2), strides=2)

pool2_flat = tf.reshape(pool2, shape=[-1, 7 * 7 * 64])
dense = tf.layers.dense(inputs=pool2_flat, units=1024, activation=tf.nn.relu)
dropout = tf.layers.dropout(inputs=dense, rate=0.4, training=is_training)
logits = tf.layers.dense(inputs=dropout, units=10)

# 誤差関数
cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits_v2(logits=logits, labels=y_))
train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)


correct_prediction = tf.equal(tf.argmax(logits, 1), tf.argmax(y_, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

# TPU
tpu_grpc_url = tf.contrib.cluster_resolver.TPUClusterResolver(tpu=[os.environ['TPU_NAME']])

strategy = tf.contrib.tpu.TPUDistributionStrategy(tpu_grpc_url)

init = tf.global_variables_initializer()

tf.summary.scalar('softmax_cross_entropy', cross_entropy)
tf.summary.scalar('accuracy', accuracy)

merged = tf.summary.merge_all()

with tf.Session() as sess:
    with tf.name_scope('summary'):
        writer = tf.summary.FileWriter('./logs_tpu', sess.graph)

        sess.run(init)

        for i in range(MAX_EPOCH):
            batch = mnist.train.next_batch(50)

            train_accuracy = accuracy.eval(session=sess, feed_dict={x_: batch[0], y_: batch[1], is_training: True})
            print("step = %d, training accuracy = %g" % (i, train_accuracy))

            s = sess.run(merged,  feed_dict={x_: batch[0], y_: batch[1], is_training: False})
            writer.add_summary(s, i)

            train_step.run(session=sess, feed_dict={x_: batch[0], y_: batch[1], is_training: False})
            print("Test Accuracy:", sess.run(accuracy, feed_dict={x_: mnist.test.images, y_: mnist.test.labels, is_training: False}))


tpu.shutdown_system()

Tensorboardを使う

ここを参考に6006のfirewall ruleを追加することで、GCPでの学習結果をtensorboardで確認できます。 https://qiita.com/tk_01/items/307716a680460f8dbe17

f:id:natsutan:20190118205846p:plain f:id:natsutan:20190118205849p:plain

どうやら上手く学習できているようです。

PythonとKerasによるディープラーニング

PythonとKerasによるディープラーニング

PythonとKerasによるディープラーニングを読みました。Kerasの作者が書いた本だけあって、非常に分かりやすく書かれています。Kerasの楽できる関数群をフルに使って、短い記述で定番のニューラルネットワークを動かすことができます。単にニューラルネットワークの説明だけでなく、データの内容を確認したり、学習結果をグラフにプロットしたり、もう半歩先まではコード付きで解説があります。

本のレベルとしては、最初の一歩の次に進める本です。CNNを使った識別や、LSTMを使った未来予測など、やりたいことがはっきりしているのであれば、ショートカットとしてとても役に立ちます。Pythonこれからという人には、ちょっと厳しいと思います。

この本を読んで手を動かしていけば、例えばニューラルネットワークがどこを見て猫と判断しているのかをわかりやすくするヒートマップも、Kerasを使って簡単にできるようになります。

f:id:natsutan:20190105225346j:plain
heatmap

こんな感じですね。識別の部分も含めて、これくらいのコードで実装できます。

model = VGG16(weights='imagenet')

preds = model.predict(x)
print('Predicted:', decode_predictions(preds, top=3))

last_conv_layer = model.get_layer('block5_conv3')
cat_output = model.output[:,386]
grads = K.gradients(cat_output, last_conv_layer.output)[0]

pooled_grads = K.mean(grads, axis=(0,1,2))
grads = K.function([model.input], [pooled_grads, last_conv_layer.output[0]])

iterate = K.function([model.input], [pooled_grads, last_conv_layer.output[0]])

pooled_grads_value, conv_layer_value = iterate([x])

for i in range(512):
    conv_layer_value[:, :, 1] *= pooled_grads_value[i]

heatmap = np.mean(conv_layer_value, axis=-1)
heetmap = np.maximum(heatmap, 0)
heatmap /= np.max(heatmap)

一方、Kerasの弱点として、型から外れると使いにくいというのもあり、ちょっと難しいことをするとこの一冊だけではきついです。

ちょうど昨年末にQiitaに闇のkerasに対する防衛術という記事が上がってました。Kerasが想定していることと違う事をしようとすると、まだまだ情報が足りなかったり、見つけた情報も更新が速すぎて古かったりします。何か新しいことをするというレベルではなく、まず手持ちのデータで識別から始めたいという人におすすめします。

TensorFlowのAPI Level

TensorFlowが良くわからないので勉強しています。 Hands-On Convolutional Neural Networks with TensorFlow を読んでいたら、TensorFlow API levelsというセクションで、High-Level TensorFlow API、Mid-level TensorFlow API, Low-Level TensorFlow APIという表現が出てきました。気になったので、公式ドキュメントを調べてみました。

TensorFlow GuideにAPIの説明がありました。

High Level APIs

これは明確に記載があります。

  • Keras
  • Eager Execution
  • Importting Data
  • Estimators

の4系統です。

Low Level APIs

TensorFlowで使う VariableとかSessionとかがこの辺ですね。

それ以外

High-Level API以外は全部Low-Level APIかとも思ったのですが、TensorBoardはどちらでもなさそうです。また、Mid Level APIは、この本の作者の用語っぽいですね。あと、tf.contribなんかもどちらでもないです。