本文へスキップ
ひもとくAI

畳み込み:フィルターを滑らせる

レッスン 2/7

小さな窓を、画像の上で滑らせる

前のレッスンでは、画像を1列に並べると、隣り合うマスの関係が見えにくくなることを確かめました。このレッスンでは、隣り合うマスをまとめて見る 計算、畳み込み(たたみこみ)を学びます。CNNの「C」(Convolution)は、この畳み込みのことです。

直感:虫めがねで、少しずつ見ていく

大きな絵を、3×3マスだけが見える小さな窓(虫めがねのようなもの)を通して見る場面を想像してください。窓を左上に置いて中をよく見たら、右に1マスずらして、また見る。右端まで来たら、1マス下の左端に戻って、また見る。これを繰り返すと、絵の全体を、小さな範囲ずつ見て回れます。

窓の中で「何を探すか」を決めるのが、フィルター です。フィルターは、窓と同じ3×3の数の表です。

窓の中で行う計算は、第4章の内積と同じ

窓を1か所に置いたら、次の計算をします。

  1. 窓の中の9マスと、フィルターの9マスを、同じ位置同士で掛ける
  2. 9個の積を、全部足す

「同じ位置同士を掛けて、全部足す」は、第4章で学んだ 内積 そのものです。9個の数の並び(窓の中)と、9個の数の並び(フィルター)の内積を計算していると考えられます。

足した結果の1つの数を、出力の表の、窓の位置に対応するマスに書き込みます。窓を滑らせながらこれを繰り返すと、出力の表ができあがります。この「窓を滑らせながら、フィルターとの内積を並べていく」計算が、畳み込みです。

現実のどこで使われているか

  • スマートフォンの顔認識や、写真の中の人や物を見つける機能
  • 自動運転の車が、カメラの映像から歩行者や標識を見つける仕組み
  • 画像を「ぼかす」「くっきりさせる」写真の加工アプリ(これは、決まったフィルターで畳み込みをしています)

どれも、画像の上でフィルターを滑らせて、小さな範囲の模様を調べることから始まります。

1か所ずつ計算してみる

5×5マスの画像に、3×3のフィルターで畳み込みをします。フィルターは、四すみと真ん中が1、残りが0の「×」の形です。

下の図の「次の位置」ボタンで、窓(点線の枠)を1マスずつ動かせます。各位置で、窓の中の9マスとフィルターを同じ位置同士で掛けて足した結果が、出力の太い枠のマスに入ります。

位置 1 / 9

出力の0行目・0列目: 1×1 + 2×0 + 0×1 + 0×0 + 1×1 + 3×0 + 2×1 + 0×0 + 1×1 = 5

画像の値
0行目12010
1行目01312
2行目20101
3行目13020
4行目01211
フィルターの値
0行目101
1行目010
2行目101
5×5の画像の上を、3×3のフィルターが1マスずつ動く。窓が左上にあるとき、出力の0行目・0列目は 1×1 + 2×0 + 0×1 + 0×0 + 1×1 + 3×0 + 2×1 + 0×0 + 1×1 = 5。全部の位置を計算すると、出力は上の行から 5, 6, 3 / 4, 8, 5 / 8, 2, 7 になる

左上の位置を、手で確かめる

窓が左上にあるとき、窓の中は次の9マスです。

0列目1列目2列目
0行目120
1行目013
2行目201

フィルターが1のマス(四すみと真ん中)だけが、足し算に残ります。1+0+1+2+1=51 + 0 + 1 + 2 + 1 = 5 で、出力の左上は5になります。

式で書くと

出力の ii 行目・jj 列目の値は、次のように書けます。

出力[i,j]=∑a=02∑b=02画像[i+a, j+b]×フィルター[a,b]\text{出力}[i, j] = \sum_{a=0}^{2} \sum_{b=0}^{2} \text{画像}[i+a,\ j+b] \times \text{フィルター}[a, b]

∑\sum が2つ並んでいるのは、「aa を0から2まで、その中で bb を0から2まで変えながら、全部足す」という意味です。aa がフィルターの中の行、bb が列を表すので、3×3の9マス分を足していることになります。ii と jj は窓の左上の位置で、窓を滑らせると変わります。

出力は、画像より小さくなる

3×3の窓は、5×5の画像の中で、横に3通り(左端が0列目・1列目・2列目)、縦に3通りの位置に置けます。そのため、出力は3×3です。

一般に、一辺が NN マスの画像に、一辺が kk マスのフィルターを使うと、出力の一辺は N−k+1N - k + 1 マスになります。5×5の画像なら 5−3+1=35 - 3 + 1 = 3、手書き数字の8×8の画像なら 8−3+1=68 - 3 + 1 = 6 です。

パディングとストライド

実際のCNNでは、窓の動かし方を、次の2つの設定で変えることがあります。

  • パディング: 画像のまわりを、0のマスで囲んでから畳み込みをします。まわりを1マスずつ囲むと、5×5の画像は7×7になり、3×3のフィルターでの出力は 7−3+1=57 - 3 + 1 = 5 で、元の画像と同じ5×5になります。画像の端のマスも、窓の真ん中で見られるようになります
  • ストライド: 窓を1マスずつではなく、何マスかおきに動かします。ストライドが2なら、窓を2マスずつ動かすので、出力は縦も横もおよそ半分になります

まわりを囲むマスの数を PP、窓を動かすマスの数を SS とすると、出力の一辺は次の式で求められます。

⌊N+2P−kS⌋+1\left\lfloor \frac{N + 2P - k}{S} \right\rfloor + 1

⌊ ⌋\lfloor \ \rfloor は、小数点以下を切り捨てるという記号です。この章では、ずっと P=0P = 0(囲まない)、S=1S = 1(1マスずつ)なので、この式は N−k+1N - k + 1 と同じになります。

なお、フィルターは カーネル とも呼ばれます。どちらも同じもの(窓に掛ける数の表)を指します。

NumPyで畳み込みを書く

窓を滑らせる動きは、第2章の for 文を2重にすると書けます。for 文の中に、もう1つ for 文を書く形です。外側の for 文で窓の行 i を、内側の for 文で窓の列 j を動かします。

2重の for 文では、外側が1回進むごとに、内側が最初から最後まで回ります。5×5の画像なら、i = 0 のときに j = 0, 1, 2、次に i = 1 のときに j = 0, 1, 2……と進み、全部で 3×3=93 \times 3 = 9 回、窓の中を計算します。窓を「右へ、右端まで来たら次の行の左端へ」と動かす順番と同じです。

import numpy as np

def convolve(image, filt):
    h, w = image.shape            # 画像の行の数・列の数
    k = filt.shape[0]             # フィルターの一辺
    out = np.zeros((h - k + 1, w - k + 1))   # 出力の表(最初は全部0)
    for i in range(h - k + 1):
        for j in range(w - k + 1):
            window = image[i:i+k, j:j+k]     # 窓の中(k行×k列)を切り出す
            out[i, j] = np.sum(window * filt)  # 同じ位置同士を掛けて、全部足す
    return out

image = np.array([[1, 2, 0, 1, 0],
                  [0, 1, 3, 1, 2],
                  [2, 0, 1, 0, 1],
                  [1, 3, 0, 2, 0],
                  [0, 1, 2, 1, 1]])
filt = np.array([[1, 0, 1],
                 [0, 1, 0],
                 [1, 0, 1]])
convolve(image, filt)   # → [[5., 6., 3.], [4., 8., 5.], [8., 2., 7.]](np.zeros で作ったので小数で表示される)
  • image[i:i+k, j:j+k] は、i 行目から k 行分、j 列目から k 列分を切り出します。第3章の scores[:3](先頭から3つ)は、始まりを省略した書き方でした。[始まり:終わり] と書くと、「始まり」の番号から「終わり」の 1つ手前 までを取り出します。例えば i = 1、k = 3 なら 1:4 で、1・2・3行目の3行分です。カンマの前が行、後ろが列の範囲です
  • window * filt は、同じ形の配列同士の * なので、同じ位置同士の掛け算 になります。np.sum で、その9個を全部足します

機械学習でどう使われるか

このレッスンでは、フィルターの数を人間が決めました。CNNでは、フィルターの9個の数が、学習で決まる重み です。第6章のニューロンの重みと同じように、正解との差(損失)が小さくなる向きに、誤差逆伝播で少しずつ調整されます。人間が「どんな模様を探すか」を決めなくても、データから、見分けるのに役立つフィルターが見つかります。

よくある誤解

  • 「畳み込みは、画像を小さくするための処理」: 出力が小さくなるのは、窓を画像の外に出さないことによる副作用です。目的は、小さな範囲の模様を調べることです(実際のCNNでは、画像のまわりを0で囲んで、出力を同じ大きさにすることもよくあります)
  • 「数学の授業で習う畳み込みと、まったく同じ計算」: 数学で「畳み込み」と呼ぶ計算では、フィルターを上下左右に反転させてから重ねます。CNNでは、反転させずにそのまま重ねるのが一般的です。フィルターの数は学習で決まるので、反転させる計算で学習しても、反転した形のフィルターが見つかるだけで、見分ける力は変わりません

振り返り

  • 畳み込みは、画像の上で窓を滑らせながら、窓の中とフィルターの内積(同じ位置同士を掛けて足す)を並べていく計算
  • 一辺 NN の画像に一辺 kk のフィルターを使うと、出力の一辺は N−k+1N - k + 1
  • CNNでは、フィルターの数は学習で決まる重み

演習

演習を読み込んでいます…