ReLU6 とは何者なのか?


TL;DR

  • MobileNet で使われていることで有名な ReLU6 を復習してみた
  • 初出の論文(と思われるもの)では sparse feature を得るために使うという導入だった
  • MobileNet の文脈では固定小数点数の演算に適している(上限を与えない場合と比べて resolution が高い)
  • とはいえ 6 は実験によって決めるべきパラメタでしかなく、それ以上に深い意味はない(と思う)

最近勉強会で MobileNetV2について発表 する機会があり、改めて ReLU6 について考え直してみた。 ちなみに ReLU6 とは何かというと、単純に上限を 6 に設定した ReLU である。

xmin(max(0,x),6).x \rightarrow \min ( \max (0,x), 6 ).

この ReLU6 は MobileNet で使われていることで有名だが、これについて復習してみようというエントリーである。

最初の導入

最初の導入は自分が調べたところ この論文 である。 正確に言うと TensorFlow source code で refer しているのを見つけてそれ以上は調べていないというものである。 この論文から導入の意味を引用すると

In our tests, this encourages the model to learn sparse features earlier. In the formulation of [8], this is equivalent to imagining that each ReLU unit consists of only 6 replicated bias-shifted Bernoulli units, rather than an infinite amount. We will refer to ReLU units capped at n as ReLU-n units

ということで、sparse feature を得るのに有効だったとのことである。 これはまあ言いたいことは分かるがスパース性が効いているという具体的な証拠は見たことがないので、この主張がどれくらい正しいのかというのは微妙なところかもしれない。

固定小数点数での取り扱い

TensorFlow や TensorFlow Lite (そして PyTorch など他のライブラリでも)では軽量なモデルの実現のために固定小数点数による取り扱いがサポートされている。 例えば TensorFlow では 8 bit での量子化 が使える。 その他にも TOCO という TensorFlow から TensorFlow Lite のモデルの converter においても量子化オプションがサポートされている。

そもそも 8 bit で扱うというのは省メモリなモデルを実現するためである。 単純に 32 bit の場合から比べて 1/4 になり、経験上そこまでモデルの性能が落ちないので、お手軽にできて効果の高い手法である。

8 bit 量子化で整数として重みや活性化関数を扱うことを考えれば、浮動小数点数から変換する必要がある。 straightforward にやるならば学習時に遭遇した最小値と最大値の値を記録しておいてそれらを用いて 8 bit との対応を付けるわけだが、この方法では低頻度でも大きい値に遭遇すればそれに引きずられてしまう。 活性化関数に話を限れば ReLU であれば最小値は 0 なので、扱える bit 数が固定である以上最大値が小さい方が元の浮動小数点数をより細かく扱うことができる。

ということで最大値を手で決めてしまうというのは一つの有効な戦略である。 「なぜそれが 6 なのか」というのは実験によって決められた単なるパラメタであるという理解だが、詳細にそのあたりを調べた論文などは見たことがない。 まあただのパラメタサーチだし劇的に結果が変わるというものではないと思われるので、そこまでモチベーションはなさそう。 ちなみに この reddit のスレッド では ReLU2 にしても性能低下は見られなかったと言っている。

Keras における ReU6 の取り扱い

TensorFlow では relu6 関数 が定義されている。 c++ のコードは こんな感じ になっていて、まあ細かいことは置いといてとにかく relu6 が明示的に準備されている。

これは Keras においても同様であって、例えば model をロードするときは

model = load_model('mobilenet.h5', custom_objects={'relu6': mobilenet.relu6})

みたいに custom_objects を指定しなければならないというやや面倒臭い事情があった。

しかし、Keras には ReLU layer が導入されて、ごく最近になって relu6 関数を使わなくなっている。 例えば この PR のように。 これで relu6 関数を使わずに ReLU layer で統一的に扱えるのだが、現状では変更が新しすぎて pip install では不整合が起きているように見える。 具体的には pip install keras で 2.2.0 が入るが、実際に MobileNet を定義している keras-applications の方がちゃんと追随できてなさそう。 現状では keras-applications の方を source から build することで relu6 を使わない状況が実現できる。

しばらくすればちゃんと整備されると思うが、使ってみようとしてちょっとハマったので記録を残しておいた。 source から build した keras-applications を使うことで、以下のように custom_objects を使わずに model をロードできる。

model = load_model('mobilenet.h5')

まとめ

ReLU6 について改めて調べ直してみた。 理論的には特別新しい知識が身についたわけではないが、最新版の Keras での取り扱いに関して relu6 関数がなくなっていて、整合的に使うには keras-applications を source から build する必要があることが分かった。