0

E資格_深層学習day2

292
0
$$$$

はじめに

本記事はラビットチャレンジ_深層学習day2のレポートである。

勾配消失問題

勾配消失問題とは

 誤差逆伝播の際に層が深いニューラルネットワークにおいて勾配がほぼ0になってしまい、学習が上手くいかなくなる問題のこと。
 中間層の活性化関数において、シグモイド関数が使われなくなりRelu関数が使われるようになったのも、勾配消失問題が関係している。

活性化関数

 ニューラルネットワーク中の1つのニューロンにおいて,複数ノードの和を入力として,その出力を最終決定する関数のこと。
 代表的な活性化関数には、「ステップ関数」、「シグモイド関数」、「ReLu関数」、「ソフトマックス関数」、「恒等関数」がある。中間層ではシグモイド関数は過去に頻繁に使われていたが、勾配消失問題が生じるため、ReLu関数が主流となっていった。

      # ステップ関数
def step_function(x):
    return np.where( x > 0, 1, 0) 

# シグモイド関数
def sigmoid(x):
    return 1/(1 + np.exp(-x))

# ReLU関数
def relu(x):
    return np.maximum(0, x)

# ソフトマックス関数
def softmax(x):
    if x.ndim == 2: # ミニバッチの考慮
        x = x.T
        x = x - np.max(x, axis=0) # オーバーフロー対策
        y = np.exp(x) / np.sum(np.exp(x), axis=0)
        return y.T
    x = x - np.max(x) # オーバーフロー対策
    return np.exp(x) / np.sum(np.exp(x))
    

初期値の設定方法

 重みの初期値設定については、XavierやHeの方法がある。

Xavierの初期値

Xaivierの初期値はsigmoid関数やtanh関数を活性化関数として用いる時に、用いるとよい。
 この初期値はノード数nに対して平均0、標準偏差$ \frac{1}{\sqrt{n}} $である正規分布から重みを設定する。

Heの初期値

 Xaivierの初期値はReLU関数を活性化関数として用いる時に、用いるとよい。
この初期値はノード数nに対して平均0、標準偏差$ \sqrt\frac{2}{{n}} $である正規分布から重みを設定する。

バッチ正規化

 モデルの学習の際にミニバッチを平均0、標準偏差が1となるように正規化を行うことで学習を効率的に行う手法のこと。

      # Batch Normalizationの実装
class BatchNormalization:
    
    # インスタンス変数の定義
    def __init__(self, gamma, beta, momentum=0.9, running_mean=None, running_var=None):
        
        # 再変換用のパラメータ
        self.gamma = gamma # 標準偏差
        self.beta = beta # 平均
        self.momentum = momentum # 減衰率
        
        # テスト時に使用する統計量
        self.running_mean = running_mean # 平均
        self.running_var = running_var # 分散 
        
        # 逆伝播時に使用する統計量
        self.batch_size = None # データ数
        self.xc = None # 偏差
        self.std = None # 標準偏差
        self.dgamma = None # (再変換用の)標準偏差の微分
        self.dbeta = None # (再変換用の)平均の微分
    
    # 順伝播メソッドの定義
    def forward(self, x, train_flg=True):
        # 初期値を与える
        if self.running_mean is None:
            N, D = x.shape
            self.running_mean = np.zeros(D)
            self.running_var = np.zeros(D)
                        
        if train_flg: # 学習時
            # 正規化の計算
            mu = x.mean(axis=0) # 平均
            xc = x - mu # 偏差
            var = np.mean(xc ** 2, axis=0) # 分散
            std = np.sqrt(var + 10e-7) # 標準偏差
            xn = xc / std # 標準化:式(6.7)
            
            # 計算結果を(逆伝播用に)インスタンス変数として保存
            self.batch_size = x.shape[0]
            self.xc = xc # 偏差
            self.xn = xn # 標準化データ
            self.std = std # 標準偏差
            self.running_mean = self.momentum * self.running_mean + (1 - self.momentum) * mu # 過去の平均の情報
            self.running_var = self.momentum * self.running_var + (1 - self.momentum) * var # 過去の分散の情報
        else: # テスト時
            xc = x - self.running_mean
            xn = xc / np.sqrt(self.running_var + 10e-7) # 標準化:式(6.7')
        
        # 再変換
        out = self.gamma * xn + self.beta # 式(6.8)
        return out
    
    # 逆伝播メソッドの定義
    def backward(self, dout):
        
        # 微分の計算
        dbeta = dout.sum(axis=0) # 調整後の平均
        dgamma = np.sum(self.xn * dout, axis=0) # 調整後の標準偏差
        dxn = self.gamma * dout # 正規化後のデータ
        dxc = dxn / self.std # 偏差
        dstd = -np.sum((dxn * self.xc) / (self.std * self.std), axis=0) # 標準偏差
        dvar = 0.5 * dstd / self.std # 分散
        dxc += (2.0 / self.batch_size) * self.xc * dvar # 偏差
        dmu = np.sum(dxc, axis=0) # 平均
        dx = dxc - dmu / self.batch_size # 入力データ
        
        # インスタンス変数に保存
        self.dgamma = dgamma
        self.dbeta = dbeta
        
        return dx
    

確認テスト

Q. シグモイド関数を微分したとき、入力値が0のときに最大値をとる。その値はいくらか。
A. 0.25

Q. 重みの初期値に0を設定すると、どのような問題が発生するか。簡潔に説明せよ。
A. すべての重みが均一に更新されてしまい、重みをもつ意味がなくなってしまう。

Q. 一般的に考えられるバッチ正規化のメリットを2つあげよ
A. ①正則化の効果がある ②学習率を高く設定できる

学習率最適化手法

モメンタム

モメンタムはSGDに慣性項を付与した学習率最適化手法である。
更新式: モメンタム モメンタム

      class Momentum:
    def __init__(self, learning_rate=0.01, momentum=0.9):
        self.learning_rate = learning_rate
        self.momentum = momentum
        self.v = None
        
    def update(self, params, grad):
        if self.v is None:
            self.v = {}
            for key, val in params.items():                                
                self.v[key] = np.zeros_like(val)
                
        for key in params.keys():
            self.v[key] = self.momentum * self.v[key] - self.learning_rate * grad[key] 
            params[key] += self.v[key]

    

AdaGrad

AdaGradは過去の勾配の二乗和を計算する

      class AdaGrad:
    def __init__(self, learning_rate=0.01):
        self.learning_rate = learning_rate
        self.h = None
        
    def update(self, params, grad):
        if self.h is None:
            self.h = {}
            for key, val in params.items():
                self.h[key] = np.zeros_like(val)
            
        for key in params.keys():
            self.h[key] += grad[key] * grad[key]
            params[key] -= self.learning_rate * grad[key] / (np.sqrt(self.h[key]) + 1e-7)
    

RMSProp

モメンタムが勾配を調整している手法に対し、RMSPropは学習率を調整している手法である。
RMSProp RMSProp

      class RMSprop:
    def __init__(self, learning_rate=0.01, decay_rate = 0.99):
        self.learning_rate = learning_rate
        self.decay_rate = decay_rate
        self.h = None
        
    def update(self, params, grad):
        if self.h is None:
            self.h = {}
            for key, val in params.items():
                self.h[key] = np.zeros_like(val)
            
        for key in params.keys():
            self.h[key] *= self.decay_rate
            self.h[key] += (1 - self.decay_rate) * grad[key] * grad[key]
            params[key] -= self.learning_rate * grad[key] / (np.sqrt(self.h[key]) + 1e-7)
    

Adam

Adamの移動平均で振動を抑制するモーメンタムと学習率を調整して振動を抑制するRMSProp を組み合わせている手法である。
Adam Adam

      class Adam:
    def __init__(self, learning_rate=0.001, beta1=0.9, beta2=0.999):
        self.learning_rate = learning_rate
        self.beta1 = beta1
        self.beta2 = beta2
        self.iter = 0
        self.m = None
        self.v = None
        
    def update(self, params, grad):
        if self.m is None:
            self.m, self.v = {}, {}
            for key, val in params.items():
                self.m[key] = np.zeros_like(val)
                self.v[key] = np.zeros_like(val)
        
        self.iter += 1
        lr_t  = self.learning_rate * np.sqrt(1.0 - self.beta2 ** self.iter) / (1.0 - self.beta1 ** self.iter)         
        
        for key in params.keys():
            self.m[key] += (1 - self.beta1) * (grad[key] - self.m[key])
            self.v[key] += (1 - self.beta2) * (grad[key] ** 2 - self.v[key])
            
            params[key] -= lr_t * self.m[key] / (np.sqrt(self.v[key]) + 1e-7)
    

確認テスト

Q. Momentum, AdaGrad, RMSPropの特徴をそれぞれ簡潔に説明せよ。
A.
モメンタム:谷間についてから最も低い位置(最適値)にいくまでの時間が早い
AdaGrad:勾配の緩やかな斜面に対して、最適値に近づける
RMSProp:ハイパーパラメータの調整が必要な場合が少ない

過学習

L1正則化とL2正則化

 正則化は、機械学習のモルの複雑さを制限する手法の1つ。

L1正則化

L1正則化は、学習アルゴリズムによって推定された各特徴量の係数に対して、係数の絶対値に基づく罰則を加える。
$L1(w) = Loss(w) + λ * ||w||1$

L2正則化

$L2(w) = Loss(w) + λ/2 * w^2$

確認テスト

Q. 機械学習で使われる線形モデルの正則化は、モデルの重みを制限することで可能となる。前述の線形モデルの正則化手法の中にリッジ回帰という手法があり、その特徴として正しいものを選択しなさい。
A. (a)ハイパーパラメータを大きな値に設定すると、すべての重みが限りなく0に近づく。

Q. L1正則化を表しているグラフはどちらか答えよ。
A. 右側のLasso推定量のグラフ

ドロップアウト

 ドロップアウトとはランダムにノードを削除して学習させる手法である。
ドロップアウトのイメージ図 ドロップアウトのイメージ図

      class Dropout:
    def __init__(self, rate=0.5):
        self.rate = rate
        self.mask = None

    def forward(self, x, train_flg=True):
        if train_flg:
            # ドロップアウトを適用する場合は、マスクを作成する
            self.mask = np.random.rand(*x.shape) > self.rate
            return x * self.mask
        else:
            # テスト時は、出力をスケーリングする
            return x * (1.0 - self.rate)

    def backward(self, dout):
        # 前の層に対する勾配を計算する
        return dout * self.mask
    

畳み込みニューラルネットワーク(CNN)の概念

畳み込みニューラルネットワーク(CNN)とは

 畳み込みニューラルネットワークとは、主に画像認識や画像分類などのタスクで用いられるニューラルネットワークのこと。畳み込み層とプーリング層、全結合層という層を持つのが特徴。

畳み込み層は、入力データに対してフィルターを適用しすることで入力データの特徴を抽出し、プーリング層は、畳み込み層の出力データを縮小することで畳み込み層の出力データの位置感度を低下させ、モデルの汎化性能を向上させる役割を持つ。

畳み込み層

畳み込み層は、入力画像に対してカーネル(フィルター)と呼ばれる小さな行列を適用して、特徴マップを出力する層のこと。畳み込み層により、画像内の特徴の検出や、画像認識に必要な情報の抽出が行われる。

パディング

 入力画像の周囲に、特定の値を埋め込んで画像のサイズを拡張する処理のこと。畳み込み演算を適用すると、出力特徴マップのサイズが入力特徴マップよりも小さくなるが、パディングを使って出力特徴マップのサイズを調整することができる。

ストライド

 フィルターを移動する際のピクセルの数。ストライドを大きくすると、出力特徴マップのサイズが小さくなり、畳み込み演算の計算量が減少する。一方、ストライドを小さくすると、出力特徴マップのサイズが大きくなり、特徴量の詳細さを増やすことができる。

チャンネル

 入力画像やフィルターの色の深さを表す数値。通常、RGB画像は3つのチャンネル(Red、Green、Blue)を持つ。また、チャンネル数を増やすことで、畳み込み層が学習することができる特徴の種類を増やすことができる。

実装

      class Convolution:
    # W: フィルター, b: バイアス
    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: filter_number, C: channel, FH: filter_height, FW: filter_width
        FN, C, FH, FW = self.W.shape
        N, C, H, W = x.shape
        # 出力値のheight, width
        out_h = 1 + int((H + 2 * self.pad - FH) / self.stride)
        out_w = 1 + int((W + 2 * self.pad - FW) / self.stride)
        
        # xを行列に変換
        col = im2col(x, FH, FW, self.stride, self.pad)
        # フィルターをxに合わせた行列に変換
        col_W = self.W.reshape(FN, -1).T

        out = np.dot(col, col_W) + self.b
        # 計算のために変えた形式を戻す
        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)
        # dcolを画像データに変換
        dx = col2im(dcol, self.x.shape, FH, FW, self.stride, self.pad)

        return dx
    

プーリング層

プーリング層は、畳み込みニューラルネットワークにおいて、特徴マップのダウンサンプリングを行う層のこと。

実装

      class Pooling:
    def __init__(self, pool_h, pool_w, stride=1, 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を行列に変換
        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)
        # 整形
        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
    

確認テスト

Q. サイズ6×6の入力画像を、サイズ2×2のフィルタで畳み込んだときの出力画像のサイズを答えよ。なお、ストライドとパディングは1とする。
A. 7×7

最新のCNN

Alexnet

 AlexNetは、ILSVRC2012で優勝した、深層学習の畳み込みニューラルネットワーク(CNN)のモデル。
 畳み込み層とプーリング層から構成される5つの畳み込み層と3つの全結合層で構成される。従来の手法よりも大幅に深いモデルで、当時の最先端技術を使用して高速かつ効率的に訓練することができた。

実装

      import os, sys, cv2, random
import numpy as np
import pandas as pd
from keras.layers import Input, Dropout, Flatten, Conv2D, MaxPooling2D, Dense, Activation, BatchNormalization
from keras.models import Sequential
from keras.optimizers import SGD


def AlexNet():
    model = Sequential()

    # 第1畳み込み層
    model.add(conv2d(96, 11, strides=(4,4), bias_init=0, input_shape=(ROWS, COLS, 3)))
    model.add(MaxPooling2D(pool_size=(3, 3), strides=(2, 2)))
    model.add(BatchNormalization())

    # 第2畳み込み層
    model.add(conv2d(256, 5, bias_init=1))
    model.add(MaxPooling2D(pool_size=(3, 3), strides=(2, 2)))
    model.add(BatchNormalization())

    # 第3~5畳み込み層
    model.add(conv2d(384, 3, bias_init=0))
    model.add(conv2d(384, 3, bias_init=1))
    model.add(conv2d(256, 3, bias_init=1))
    model.add(MaxPooling2D(pool_size=(3, 3), strides=(2, 2)))
    model.add(BatchNormalization())

    # 密結合層
    model.add(Flatten())
    model.add(dense(4096))
    model.add(Dropout(0.5))
    model.add(dense(4096))
    model.add(Dropout(0.5))

    # 読み出し層
    model.add(Dense(2, activation='softmax'))

    model.compile(optimizer=SGD(lr=0.01), loss='categorical_crossentropy', metrics=['accuracy'])
    return model
    

参考:KerasによるAlexNetを用いた犬猫分類モデル
https://qiita.com/ornew/items/8ca914d222ce068158c4)

その他の代表的なCNN

 Alexnet以外の代表的なCNNには、VGG16, GoogLeNet, ResNet等がある。
 これらのモデルも、ILSVRCで優秀な成績を収めている。

[フレームワーク演習]正則化/最適化

過学習を抑制する方法

過学習を抑制する方法には主にパラメータ正則化、正規化レイヤー、正規化レイヤーがある。
パラメータ正則化:L1正則化、L2正則化、Elastic Net
正則化レイヤー:Dropout
正規化レイヤー:バッチ正規化、レイヤー正規化、インスタンス正規化

バッチ正規化の利点

 ・学習を速く進行させることができる
 ・初期値にそれほど依存しない
 ・過学習を抑制する
参考:ゼロから作るDeepLearning 6章3節 Batch Normalization

過学習が起きる理由

 訓練データにだけ適合する学習を行うと過学習になる。

参考文献/参考にしたサイト

ゼロから作るDeep Learning, 斎藤 康毅, オライリー・ジャパン
深層学習教科書 ディープラーニング G検定(ジェネラリスト)公式テキスト 第2版, 翔泳社
徹底攻略ディープラーニングG検定ジェネラリスト問題集 第2版 (徹底攻略シリーズ), インプレス
統計学実践ガイドブック, 日本統計学会

【決定版】スーパーわかりやすい最適化アルゴリズム -損失関数からAdamとニュートン法-
https://qiita.com/omiita/items/1735c1d048fe5f611f80

KerasによるAlexNetを用いた犬猫分類モデル
https://qiita.com/ornew/items/8ca914d222ce068158c4

画像分類の6つの代表的なアーキテクチャの特徴まとめ
https://ai-kenkyujo.com/artificial-intelligence/ai-architecture-02/

投稿日:2023326

この記事を高評価した人

高評価したユーザはいません

この記事に送られたバッジ

バッジはありません。

投稿者

コメント

他の人のコメント

コメントはありません。
読み込み中...
読み込み中