einops 0.4 : tutorial part 2 : 深層学習

einops 0.4 : tutorial part 2 : 深層学習 (翻訳/解説)

翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 03/26/2022 (0.4.1)

* 本ページは、einops の以下のドキュメントを翻訳した上で適宜、補足説明したものです:

* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

クラスキャット 人工知能 研究開発支援サービス

クラスキャット は人工知能・テレワークに関する各種サービスを提供しています。お気軽にご相談ください :

◆ 人工知能とビジネスをテーマに WEB セミナーを定期的に開催しています。スケジュール
  • お住まいの地域に関係なく Web ブラウザからご参加頂けます。事前登録 が必要ですのでご注意ください。

お問合せ : 本件に関するお問い合わせ先は下記までお願いいたします。

  • 株式会社クラスキャット セールス・マーケティング本部 セールス・インフォメーション
  • sales-info@classcat.com  ;  Web: www.classcat.com  ;   ClassCatJP

 

einops 0.4 : tutorial part 2 : 深層学習

このチュートリアルの内容

  • 深層学習パッケージと共に作業する
  • 深層学習の重要なケース
  • einsops.asnumpy と einops.layers
from einops import rearrange, reduce
import numpy as np
x = np.random.RandomState(42).normal(size=[10, 32, 100, 200])
# utility to hide answers
from utils import guess

 

フレーバーの選択

最も使いやすいフレームワークに切り替えます。

# select one from 'chainer', 'gluon', 'tensorflow', 'pytorch' 
flavour = 'pytorch'
print('selected {} backend'.format(flavour))
if flavour == 'tensorflow':
    import tensorflow as tf
    tape = tf.GradientTape(persistent=True)
    tape.__enter__()
    x = tf.Variable(x) + 0
elif flavour == 'pytorch':
    import torch
    x = torch.from_numpy(x)
    x.requires_grad = True
elif flavour == 'chainer':
    import chainer
    x = chainer.Variable(x)
else:
    assert flavour == 'gluon'
    import mxnet as mx
    mx.autograd.set_recording(True)
    x = mx.nd.array(x, dtype=x.dtype)
    x.attach_grad()
selected pytorch backend
type(x), x.shape
(torch.Tensor, torch.Size([10, 32, 100, 200]))

 

単純な計算

  • bchw を bhwc 形式に変換したり、その逆を行なうことは CV で一般的な操作です。
  • 出力 shape を予測してから貴方の推測を確認してみましょう!
y = rearrange(x, 'b c h w -> b h w c')
guess(y.shape)
Answer is: (10, 100, 200, 32) (hover to see)

 

Worked!

気付いたでしょうか?上記のコードは選択したバックエンドに対して動作しました。einops 関数はそれらがフレームワークに対して native であるかのように任意のテンソルで動作します。

 

バックプロパゲーション

  • 勾配は深層学習の基礎です。
  • einops 演算により (丁度フレームワーク native 演算でのように) バックプロパゲートすることができます。
y0 = x   # torch.Size([10, 32, 100, 200])
y1 = reduce(y0, 'b c h w -> b c', 'max')
y2 = rearrange(y1, 'b c -> c b')
y3 = reduce(y2, 'c b -> ', 'sum')

if flavour == 'tensorflow':
    print(reduce(tape.gradient(y3, x), 'b c h w -> ', 'sum'))
else:
    y3.backward()
    print(reduce(x.grad, 'b c h w -> ', 'sum'))
tensor(320., dtype=torch.float64)

 

Meet einops.asnumpy

単にテンソルを numpy に変換します (そして必要に応じて gpu から引っ張ります)。

from einops import asnumpy
y3_numpy = asnumpy(y3)

print(type(y3_numpy))
<class 'numpy.ndarray'>

 

深層学習の一般的なビルディングブロック

幾つかの馴染みのある演算が einops で書かれる方法を確認しましょう。

Flattening (平坦化) は一般的な演算で、畳込み層と完全結合層の間の境界で頻繁に現れます。

y = rearrange(x, 'b c h w -> b (c h w)')
guess(y.shape)
Answer is: (10, 640000) (hover to see)

 
space-to-depth

y = rearrange(x, 'b c (h h1) (w w1) -> b (h1 w1 c) h w', h1=2, w1=2)
guess(y.shape)
Answer is: (10, 128, 50, 100) (hover to see)

 
depth-to-space (notice that it’s reverse of the previous)

y = rearrange(x, 'b (h1 w1 c) h w -> b c (h h1) (w w1)', h1=2, w1=2)
guess(y.shape)
Answer is: (10, 8, 200, 400) (hover to see)

 

リダクション

単純な 大域平均プーリング

y = reduce(x, 'b c h w -> b c', reduction='mean')
guess(y.shape)
Answer is: (10, 32) (hover to see)

 
最大プーリング with a kernel 2×2

y = reduce(x, 'b c (h h1) (w w1) -> b c h w', reduction='max', h1=2, w1=2)
guess(y.shape)
Answer is: (10, 32, 50, 100) (hover to see)
# you can skip names for reduced axes
y = reduce(x, 'b c (h 2) (w 2) -> b c h w', reduction='max')
guess(y.shape)
Answer is: (10, 32, 50, 100) (hover to see)

 

1d, 2d と 3d プーリングは同じように定義されます

シーケンシャル 1-d モデルに対して、時間に渡るプーリングを多分望むでしょう :

reduce(x, '(t 2) b c -> t b c', reduction='max')

volumetric モデルに対しては、3 次元総てがプーリングされます :

reduce(x, 'b c (x 2) (y 2) (z 2) -> b c x y z', reduction='max')

統一性は einops の長所で、個々の特定のケースのための特定の演算を必要としません。

 

良い課題

  • 1d と 3d のために space-to-depth のバージョンを書く (2d は上で提供されています)。
  • 1d モデルのための平均 / 最大プーリングを書く。

 

Squeeze と unsqueeze (expand_dims)

# models typically work only with batches, 
# so to predict a single image ...
image = rearrange(x[0, :3], 'c h w -> h w c')   # torch.Size([100, 200, 3])
# ... create a dummy 1-element axis ...
y = rearrange(image, 'h w c -> () c h w')
# ... imagine you predicted this with a convolutional network for classification,
# we'll just flatten axes ...
predictions = rearrange(y, 'b c h w -> b (c h w)')
# ... finally, decompose (remove) dummy axis
predictions = rearrange(predictions, '() classes -> classes')

 

リダクションのための keepdims-like な動作

  • empty composition () は長さ 1 の次元を提供します、これはブロードキャスト可能です。
  • 代わりに新しい軸を導入するために単に 1 を使用することもできます、それは () の同義語です。

 
各画像に対する、チャネル毎 mean-正規化 :

y = x - reduce(x, 'b c h w -> b c 1 1', 'mean')
guess(y.shape)
Answer is: (10, 32, 100, 200) (hover to see)

 
バッチ全体に対する、チャネル毎 mean-正規化 :

y = x - reduce(y, 'b c h w -> 1 c 1 1', 'mean')
guess(y.shape)
Answer is: (10, 32, 100, 200) (hover to see)

 

スタッキング

テンソルのリストを取りましょう :

list_of_tensors = list(x)

新しい軸 (テンソルを列挙する軸) は最初に式の左側に現れます。丁度リストをインデックスするように – 最初にインデックスでテンソルを取得します :

tensors = rearrange(list_of_tensors, 'b c h w -> b h w c')
guess(tensors.shape)
Answer is: (10, 100, 200, 32) (hover to see)
# or maybe stack along last dimension?
tensors = rearrange(list_of_tensors, 'b c h w -> h w c b')
guess(tensors.shape)
Answer is: (100, 200, 32, 10) (hover to see)

 

結合 (= Concatenation)

最初の次元に渡り結合しますか?

tensors = rearrange(list_of_tensors, 'b c h w -> (b h) w c')
guess(tensors.shape)
Answer is: (1000, 200, 32) (hover to see)

あるいは多分最後の次元に沿って結合しますか?

tensors = rearrange(list_of_tensors, 'b c h w -> h w (b c)')
guess(tensors.shape)
Answer is: (100, 200, 320) (hover to see)

 

次元内でシャッフルする

チャネル・シャッフル (shufflnet 論文で描かれているように)

y = rearrange(x, 'b (g1 g2 c) h w-> b (g2 g1 c) h w', g1=4, g2=4)
guess(y.shape)
Answer is: (10, 32, 100, 200) (hover to see)

 
チャネルシャッフルの単純バージョン

y = rearrange(x, 'b (g c) h w-> b (c g) h w', g=4)
guess(y.shape)
Answer is: (10, 32, 100, 200) (hover to see)

 

次元の分割

ここに非常に便利なトリックがあります。

Example : ネットワークが各位置に対して幾つかの bbox を予測する場合

それぞれが 4 座標を持つ 8 bbox を得たと仮定します。
4 つの個別の変数に座標をわけるため、対応する次元をフロントに移動し、タプルをアンパックします。

bbox_x, bbox_y, bbox_w, bbox_h = \
    rearrange(x, 'b (coord bbox) h w -> coord b bbox h w', coord=4, bbox=8)
# now you can operate on individual variables
max_bbox_area = reduce(bbox_w * bbox_h, 'b bbox h w -> b h w', 'max')
guess(bbox_x.shape)
guess(max_bbox_area.shape)
Answer is: (10, 8, 100, 200) (hover to see)
Answer is: (10, 100, 200) (hover to see)

 

Shape のパース

単に便利なユティリティです :

from einops import parse_shape
def convolve_2d(x):
    # imagine we have a simple 2d convolution with padding,
    # so output has same shape as input.
    # Sorry for laziness, use imagination!
    return x
# imagine we are working with 3d data
x_5d = rearrange(x, 'b c x (y z) -> b c x y z', z=20)
# but we have only 2d convolutions. 
# That's not a problem, since we can apply
y = rearrange(x_5d, 'b c x y z -> (b z) c x y')
y = convolve_2d(y)
# not just specifies additional information, but verifies that all dimensions match
y = rearrange(y, '(b z) c x y -> b c x y z', **parse_shape(x_5d, 'b c x y z'))
parse_shape(x_5d, 'b c x y z')
{'b': 10, 'c': 32, 'x': 100, 'y': 10, 'z': 20}
# we can skip some dimensions by writing underscore
parse_shape(x_5d, 'batch c _ _ _')
{'batch': 10, 'c': 32}

 

Striding anything

最後に、任意の演算をどのようにストライドされた演算に変換するのでしょう?
(ストライドを持つ畳込みのように、別名 dilated/atrous 畳込み)

# each image is split into subgrids, each is now a separate "image"
y = rearrange(x, 'b c (h hs) (w ws) -> (hs ws b) c h w', hs=2, ws=2)
y = convolve_2d(y)
# pack subgrids back to an image
y = rearrange(y, '(hs ws b) c h w -> b c (h hs) (w ws)', hs=2, ws=2)

assert y.shape == x.shape

 

層で演算することを好むフレームワークのために、層が利用可能です。

貴方のバックエンドに依存して適切なものをインポートする必要があります :

from einops.layers.chainer import Rearrange, Reduce
from einops.layers.gluon import Rearrange, Reduce
from einops.layers.keras import Rearrange, Reduce
from einops.layers.torch import Rearrange, Reduce

einops 層は演算と同じで、同じパラメータを持ちます。
(最初の引数は例外で、これは呼び出し時に渡される必要があります)

layer = Rearrange(pattern, **axes_lengths)
layer = Reduce(pattern, reduction, **axes_lengths)

# apply layer to tensor
x = layer(x)

モデルを構築するためには通常は、演算ではなく、層を使用するのが便利です。

# example given for pytorch, but code in other frameworks is almost identical
from torch.nn import Sequential, Conv2d, MaxPool2d, Linear, ReLU
from einops.layers.torch import Reduce

model = Sequential(
    Conv2d(3, 6, kernel_size=5),
    MaxPool2d(kernel_size=2),
    Conv2d(6, 16, kernel_size=5),
    # combined pooling and flattening in a single step
    Reduce('b c (h 2) (w 2) -> b (c h w)', 'max'), 
    Linear(16*5*5, 120), 
    ReLU(),
    Linear(120, 10), 
)
 

以上