2019-06-25こしあんTweetPocketPyTorchではmatmulの挙動が特殊なので、思った通りにテンソル積が取れないことがあります。この記事では、基本的な畳み込み演算である「Conv2D」を使い、Numpyのドット積相当の演算を行うという方法を解説します。目次はじめに具体例:色空間変換Numpyで色空間変換Kerasの場合1×1畳み込みPyTorchでの1×1畳み込みによるテンソル計算まとめはじめにPyTorchの変態コーディング技術です。画像のテンソルのチャンネル成分に対して処理をかけたいというのはときどき遭遇します。Keras(TensorFlowバックエンド)だとチャンネルが最後にあるので、4階テンソルに対して2×2行列をK.dotで掛けて終わりですが、PyTorchだとチャンネルが最後ではない(NCHWなので2番目)ので、かなり面倒です。先に結論を言うと、この手のテンソル積を取りたいときは、torch.matmulなのですが、PyTorchのmatmulの挙動が相当特殊なので、自分では再現できませんでした。Torchのテンソルをpermute関数使って軸を逐一入れ替えて積を取るとかは一応できるのですが、それだとパフォーマンス面でちょっとどうなのかな?と思うわけです。PyTorchの場合はいい方法見つけました。1×1畳み込みの計算が同じなのでConv2Dを使ってテンソル積を計算してしまうという方法です。Numpy、Keras、PyTorchの順に具体例で説明していきます。画像の色空間を考えます。普段ピクセルのRGB値というときはRGB色空間ですが、輝度や色差といった情報を取り出したい場合は別の色空間に変換するのが便利です。ピクセル値は24ビットカラーでは0~255の整数ですが、RGB色空間では0~1の小数で考えます。一方で、YCrCb色空間は、Y(輝度)は0~1、CrCb(色差)は-0.5~0.5の小数で定義されます。RGB色空間→YCrCb色空間への変換、あるいはその逆変換は行列積で計算できます。1.772\end{bmatrix}\begin{bmatrix}Y\\Cr\\Cb\end{bmatrix}$$で表されます。本当にこんなんで正しいの?ということで、RGB→YCrCb→RGB、YCrCb→RGB→YCrCbの変換をしても元に戻る(行列積が単位行列になる)のを確認します。RGB2YCrCb))結果は以下のようになります。1.0000000e+00]]どちらも単位行列となりました(若干誤差はある)。Numpyで色空間変換さて、色空間変換を画像に適用してみましょう。こちらの記事からテスト用のカラーグラデーションを持ってきて使います。これを「color_bar.png」とします。numpy_version()先に全体のコードを貼ります。やっているのことは、画像を読み込む→RGB色空間のテンソルとして保存→YCrCb色空間に変換→RGB色空間に戻すということです。各処理の終わりにcheck_rangeという値域の確認用の関数を差し込んいます。これでRGB色空間は全て0~1、YCrCb色空間はYが0~1/CrCbが-0.5~0.5の範囲にあるということを確認します。結果は次の通りになります。1.0000002]うまく行っています。最大が0.5になるのに対して、RGBに再度戻すと全て最小が0、最大が1に統一されていますね。ここからがポイントなのですが、全ての変換計算はnp.dotというドット積の関数で計算しています。3)を掛けるという計算ですね。4)という出力になりますね。変換式の定義がピクセル値を右からかけるのに対して、np.dotではピクセル値を左からかけているので、変換行列の転置操作が必要になります。Kerasの場合Kerasの場合はNumpyとほとんど変わりません。実際の画像処理では画像を複数まとめてミニバッチとして扱うことが多いので、np.expand_dimsで4階テンソルとして計算します。バッチサイズが1のミニバッチですね。keras_version()np.dotをK.dotに置き換えればいいだけです。掛けられる側が3階テンソルだろうが、4階テンソルだろうが大丈夫です。以下のようになります。1.0000002]それぞれの色空間の定義に則っているのが確認できます。1×1畳み込みNumpyやKerasの場合、チャンネルの軸が最後にあったのでそのままどんとテンソル積をとってあげれば大丈夫でした。PyTorchの場合はchannels_firstなので、そのまま積を取るというわけにはいかなくなります(einsumの関数を使えば書けるだろうけど自分はやりたくない)。ここで畳み込みの計算定義(Conv2D)を思い出しましょう。自分が書いた記事からの図ですが、これは3×3カーネルの場合です。行列単位で見ればドット積と変わらなくなりますよね。1×1畳み込みはテンソル積の計算関数として使えるということなのです。全体のコードを貼ります。weight_tensor)」の部分です。引数の順番はnp.dotやK.dotと同じです。畳み込みのカーネル部分にかける側のテンソルを入れればよいということです。weight_tensorは4階テンソルにします。カーネルサイズ2)なので、今回の場合は「(3,3,1,1)」とするのが正しいです。np.dotやK.dotと異なるのは、変換行列の転置が不要ということです。畳み込みの内部実装でやっているのでしょう。結果は次のようになります。1.0000002]確かにうまくいきました。これは使えそうですね。まとめConv2Dは1×1カーネルにすれば普通のテンソル積の計算もできる。PyTorchの場合、チャンネルが一番外の軸ではないのでこの方法が有効。Commentコメントをキャンセルメールアドレスが公開されることはありません。