🌊

Deep Learning資格試験 深層学習 畳み込みニューラルネットワーク

2022/01/26に公開

はじめに

日本ディープラーニング協会の Deep Learning 資格試験(E 資格)の受験に向けて、調べた内容をまとめていきます。

レイヤー

畳み込み層

  • 入力データからフィルタを使いタスクを解くために有用な特徴を抽出する。
  • 2次元の入力データIと2次元のフィルターKがある場合の畳み込み(Goodfellow et al. 2018)
(I \times K)(i , k) = \sum_m \sum_n I(i + m , j + n) K(m,n)

ストライド

  • フィルターを適用する位置の間隔をストライド(stride)という。
  • ストライドを2にすると、フィルターを適用する窓の間隔が2要素ごとになる。

パディング

  • 畳み込み層の処理を行う前に、入力データの周囲に固定のデータ(たとえば0など)を埋めること。

ソースコード

コード実装

ソースコードはゼロから作る Deep Learningから引用

python
class Convolution:
    def __init__(self, W, b, stride=1, pad=0):
        self.W = W
        self.b = b
        # ストライド
        self.stride = stride
        # パディング
        self.pad = pad

        # 中間データ(backward時に使用)
        self.x = None
        self.col = None
        self.col_W = None

        # 重み・バイアスパラメータの勾配
        self.dW = None
        self.db = None

    def forward(self, x):
        FN, C, FH, FW = self.W.shape
        N, C, H, W = x.shape

        # 出力の高さ(端数切り捨て)
        out_h = 1 + int((H + 2*self.pad - FH) / self.stride)
        # 出力の幅(端数切り捨て)
        out_w = 1 + int((W + 2*self.pad - FW) / self.stride)

        # 畳み込み演算を効率よく行えるようにするために、入力xを行列colに変換する
        col = im2col(x, FH, FW, self.stride, self.pad)

        # フィルタを2次元配列に変換する
        col_W = self.W.reshape(FN, -1).T

        # 行列の積を計算し、バイパスを足す
        out = np.dot(col, col_W) + self.b

        # 画像形式に戻して、チャンネルの軸を2番目に移動させる
        out = out.reshape(N, out_h, out_w, -1).transpose(0, 3, 1, 2)

        self.x = x
        self.col = col
        self.col_W = col_W

        return out

    def backward(self, dout):
        FN, C, FH, FW = self.W.shape
        dout = dout.transpose(0,2,3,1).reshape(-1, FN)

        self.db = np.sum(dout, axis=0)
        self.dW = np.dot(self.col.T, dout)
        self.dW = self.dW.transpose(1, 0).reshape(FN, C, FH, FW)

        dcol = np.dot(dout, self.col_W.T)
        dx = col2im(dcol, self.x.shape, FH, FW, self.stride, self.pad)

        return dx

プーリング層

  • ある範囲ごとに最大値や平均値を取ることで、入力データの微小な位置変化に対してほぼ不変な表現を出力することができる。

プーリングは、縦・横方向の空間を小さくする演算である。プーリング層には以下の特徴がある。

学習するパラメータがない

  • プーリング層は、畳み込み層と違って学習するパラメータを持たない。プーリングは、対象領域から最大値を得る(もしくは平均を取る)だけの処理なので、学習すべきパラメータは存在しない。

チャンネル数は変化しない

  • プーリングの演算によって、入力データと出力データのチャンネル数は変化しない。

ソースコード

コード実装

ソースコードはゼロから作る Deep Learningから引用

python
class Pooling:
    def __init__(self, pool_h, pool_w, stride=2, pad=0):
        self.pool_h = pool_h
        self.pool_w = pool_w
        self.stride = stride
        self.pad = pad

        self.x = None
        self.arg_max = None

    def forward(self, x):
        N, C, H, W = x.shape
        # 出力の高さ(端数切り捨て)
        out_h = int(1 + (H - self.pool_h) / self.stride)
        # 出力の幅(端数切り捨て)
        out_w = int(1 + (W - self.pool_w) / self.stride)

        # 入力xを2次元配列colに変換する
        col = im2col(x, self.pool_h, self.pool_w, self.stride, self.pad)

        # チャンネル方向のデータが横に並んでいるので、縦に並べ替える
        col = col.reshape(-1, self.pool_h*self.pool_w)

        # 最大値のインデックスを求める(逆伝播時に使用する)
        arg_max = np.argmax(col, axis=1)

        # 最大値を求める
        out = np.max(col, axis=1)

        # 画像形式に戻して、チャンネルの軸を2番目に移動させる
        out = out.reshape(N, out_h, out_w, C).transpose(0, 3, 1, 2)

        self.x = x
        self.arg_max = arg_max

        return out

    def backward(self, dout):
        dout = dout.transpose(0, 2, 3, 1)

        pool_size = self.pool_h * self.pool_w
        dmax = np.zeros((dout.size, pool_size))
        dmax[np.arange(self.arg_max.size), self.arg_max.flatten()] = dout.flatten()
        dmax = dmax.reshape(dout.shape + (pool_size,))

        dcol = dmax.reshape(dmax.shape[0] * dmax.shape[1] * dmax.shape[2], -1)
        dx = col2im(dcol, self.x.shape, self.pool_h, self.pool_w, self.stride, self.pad)

        return dx

ダイレクト畳み込み

  • セマンティックセグメンテーションのように広い領域を参照しなければならないタスクの場合、単にフィルタサイズを大きくするとパラメータ数が増加するため、計算量が増加する。少ないパラメータ数で広い領域を参照できる畳み込み

転置畳み込み(transposed convolution)、逆畳み込み(Deconvolution)

  • 生成モデルのようにアップサンプリングを行う場合に使う畳み込み
  • 特徴マップを拡大してから畳み込む処理。
    1. 特徴マップの各 pixel を stride で指定した pixel 数ずつ空けて配置し
    2. kernel size-1 だけ特徴マップの周囲に余白を取り
    3. padding で指定された pixel 数だけ余白を削り
    4. 畳み込み処理を行う

畳み込み層の出力画像サイズの計算

  • 入力サイズの高さ:H

  • 入力サイズの幅:W

  • フィルタの高さ:FH

  • フィルタの幅:FW

  • パディング:P

  • ストライド:S

  • 出力画像の高さ:

\frac{H + 2 \times P - FH}{S} + 1
  • 出力画像の幅:
\frac{W + 2 \times P - FW}{S} + 1

計算量

  • 入力マップ:H \times W \times C
  • カーネル:K \times K \times C
  • 出力マップ:H \times W \times M

ストライド1でパディングありの場合の1つの点での計算量は、K \times K \times C \times M
出力マップ全体すると、H \times W \times K \times K \times C \times M

パラメータ数

  • 入力のチャンネル数:C_i
  • 出力のチャンネル数:C_o
C_i \cdot C_o \cdot k^2 + C_o

物体検出

  • 物体検出とは、ある画像における物体の位置と、その物体に対応するクラスを予測するタスクである。
  • 教師データは、対象となる物体が含まれる矩形領域を示すバウンディングボックスとそれに対応するクラスを用意する。
  • 同一物体に対して複数のバウンディングボックスが検出されることがある。これを防ぐために信頼度スコアが最も高いバウンディングボックスを採用する。(非最大値抑制(Non-Maximum Suppression)という)
  • 教師データと検出されたバウンディングボックスの評価指標として IoU がある。

セマンティックセグメンテーション

  • ピクセルごとにクラス分類問題を解くこと。
  • 1枚の画像の中に含まれる複数の物体に対して、それぞれが存在する領域に対応するクラスを出力するタスクである。
  • ある正解ラベルの領域内に、いくつかのピクセルが独立して異なるクラスに割り当てられる問題がある。条件付き確率場による後処理を行うことで精度向上が期待できる。

転移学習

  • 対象データを用いて学習したモデルを用いて、サンプルが少量である他のデータ集合の汎化性能を向上させる方法

データ拡張(Data Augmentation)

  • 手元にあるデータに何らかの変換を施し、それをデータ集合に追加することで疑似的にデータ量を増加させる。
  • 例えば、
    • 画像の回転
    • 左右平行移動、上下平行移動
    • 拡大と縮小
    • 画像のせん断
    • 左右反転、上下反転
    • 明るさの調整
    • チャンネルシフト
    • 画素値のリスケーリング
    • データセット全体の平均を 0 にする
    • データセット全体の標準偏差を 1 にする
    • 各サンプルの平均を 0 にする
    • 各サンプルの標準偏差を 1 にする

データセット

名称 クラス Train+Val BOX/画像 画像サイズ Ground-Truth BB
VOC12 20 11,540 2.4 470 \times 380 左上と右下
ILSVRC17 200 476,668 1.1 500 \times 400
MS COCO18 80 123,287 7.3 640 \times 480
OICOD18 500 1,743,042 7.0 一様でない
  • VOC12:Visual Object Classes
  • ILSVRC17:ImageNet Scale Visual Recognition Challenge
  • MS COCO18:MS Common Object in Context
  • OICOD18:Open Images Challenge Object Detection

im2col

  • フィルタにとって都合の良いように入力データを展開する。
  • 具体的には、入力データに対してフィルタを適用する場所の領域(3次元のブロック)を横方向に1列に展開する。
  • 畳み込み演算の実装は、for 文を幾重にも重ねた実装になるが、NumPy では、for 文を使うと処理が遅くなってしまう。

ソースコード

ソースコードはゼロから作る Deep Learningから引用

pytyhon
def im2col(input_data, filter_h, filter_w, stride=1, pad=0, constant_value=0):
    """
    Parameters
    ----------
      input_data : (データ数, チャンネル, 高さ, 幅)の4次元配列からなる入力データ
      filter_h : フィルターの高さ
      filter_w : フィルターの幅
      stride : ストライド
      pad : パディングサイズ
      constant_value : パディング処理で埋める際の値
    Returns
    -------
      col : 2次元配列
    """
    N, C, H, W = input_data.shape

    # 出力サイズの計算
    out_h = (H + 2*pad - filter_h)//stride + 1
    out_w = (W + 2*pad - filter_w)//stride + 1

    # 入力画像のパディング
    ## 第1引数:パディングの対象となる入力データ
    ## 第2引数:次元毎に先頭と末尾に何個ずつ値を追加するか指定する。
    ##          高さと幅に対してパディングを行うため、第1、2次元は0、第3、4次元はパラメータのpad
    ## 第3引数:パディングの種類(constantを指定した場合は、第4引数の値でパディングを行う。)
    ## 第4引数:パディング処理で埋める際の値
    img = np.pad(input_data, [(0,0), (0,0), (pad, pad), (pad, pad)], 'constant', constant_value=constant_value)

    col = np.zeros((N, C, filter_h, filter_w, out_h, out_w))

    # フィルタに対する画像をスライス
    for y in range(filter_h):
        y_max = y + stride*out_h
        for x in range(filter_w):
            x_max = x + stride*out_w
            col[:, :, y, x, :, :] = img[:, :, y:y_max:stride, x:x_max:stride]

    # (画像枚数 * 畳み込み回数 * フィルタの要素数)の行列に変換
    col = col.transpose(0, 4, 5, 1, 2, 3).reshape(N * out_h * out_w, -1)
    return col

Discussion