その他

E資格 学習内容まとめ・実装演習(深層学習 Day2後半)

はじめに

今回の記事は、前回に引き続き、私が受講しているE資格 JDLA認定プログラムの「ラビットチャレンジ」のレポート記事です。

今回のテーマは深層学習です。
講座ではDay1〜Day4まで分かれており、今回はDay2(前半)に取り組みます。
Day2は主に、畳み込みニューラルネットワーク(CNN)について扱います。
確認テスト、実装演習も併せて載せていきます。

過学習

過学習の原因

過学習の原因は以下のようなものが挙げられる。

  • パラメータの数が多い
  • パラメータの値が不適切
  • ノードの数が多い

実行したいタスクに対して、ニューラルネットワークが複雑すぎる(自由度が高い)ということです。
学習が進むと重みの値にばらつきが発生し、重みが大きいほど重要な値であることを示します。
しかし、それが大きいことによって過学習の原因となります。

正則化

L1・L2正則化

誤差関数に加えることで重みを抑制します。
$$
\begin{eqnarray}
L_p=\|x \|_p =\left(|x_1|^p+| x_2 |^p+| x_3 |^p+\cdots +| x_n|^p\right)^{\frac{1}{p}}=\left(\sum_{i=1}^n|x_i|^p\right)^{\frac{1}{p}}
\end{eqnarray}
$$
\(P\)が1であればL1正則化、2であればL2正則化です。
\(\lambda\)は減衰させるスケーリングのための値です。
$$
\begin{eqnarray}
E_n(w)+\frac{1}{p}\lambda \|x\|_p
\end{eqnarray}
$$
L2正則化はL1正則化より計算量があるためリソースを消費しますが、性能向上には良いです。

確認テスト

  • 機械学習で使われる線形モデル(線形回帰、PCA、…etc)の正則化は、モデルの重みを制限することで可能となる。
    線形モデルの正則化手法の中にリッジ回帰という手法があり、その特徴として正しいものを選択しなさい。

(a)ハイパーパラメータを大きな値に設定すると、すべての重みが限りなく0に近づく
(b)ハイパーパラメータを0に設定すると、非線形回帰となる
(c)バイアス項についても、正則化される
(d)リッジ回帰の場合、隠れ層に対して正則化項を加える

  • 下図について、L1正則化を表しているグラフはどちらか答えよ。

⇨右のLasso推定量

ドロップアウト

ドロップアウトは、ランダムにノードを削除して学習させることです。
擬似的なアンサンブル学習ができます。
TensorflowやKeras等のライブラリでも提供されていますが、
“与えるパラメータがライブラリによってどれだけのノードを残すか”だったり、”どれだけのノードを消すか”だったり逆の意味になっていることがあるため、注意が必要です。

実装演習

過学習

import numpy as np
from collections import OrderedDict
from common import layers
from data.mnist import load_mnist
import matplotlib.pyplot as plt
from multi_layer_net import MultiLayerNet
from common import optimizer


(x_train, d_train), (x_test, d_test) = load_mnist(normalize=True)

print("データ読み込み完了")

# 過学習を再現するために、学習データを削減
x_train = x_train[:300]
d_train = d_train[:300]

network = MultiLayerNet(input_size=784, hidden_size_list=[100, 100, 100, 100, 100, 100], output_size=10)
optimizer = optimizer.SGD(learning_rate=0.01)

iters_num = 1000
train_size = x_train.shape[0]
batch_size = 100

train_loss_list = []
accuracies_train = []
accuracies_test = []

plot_interval=10


for i in range(iters_num):
    batch_mask = np.random.choice(train_size, batch_size)
    x_batch = x_train[batch_mask]
    d_batch = d_train[batch_mask]

    grad = network.gradient(x_batch, d_batch)
    optimizer.update(network.params, grad)

    loss = network.loss(x_batch, d_batch)
    train_loss_list.append(loss)

    if (i+1) % plot_interval == 0:
        accr_train = network.accuracy(x_train, d_train)
        accr_test = network.accuracy(x_test, d_test)
        accuracies_train.append(accr_train)
        accuracies_test.append(accr_test)

        print('Generation: ' + str(i+1) + '. 正答率(トレーニング) = ' + str(accr_train))
        print('                : ' + str(i+1) + '. 正答率(テスト) = ' + str(accr_test))        

lists = range(0, iters_num, plot_interval)
plt.plot(lists, accuracies_train, label="training set")
plt.plot(lists, accuracies_test,  label="test set")
plt.legend(loc="lower right")
plt.title("accuracy")
plt.xlabel("count")
plt.ylabel("accuracy")
plt.ylim(0, 1.0)
# グラフの表示
plt.show()
# 途中省略
Generation: 1000. 正答率(トレーニング) = 1.0
                : 1000. 正答率(テスト) = 0.7629


過学習が起きていることがわかります。

L1正則化

(x_train, d_train), (x_test, d_test) = load_mnist(normalize=True)

print("データ読み込み完了")

# 過学習を再現するために、学習データを削減
x_train = x_train[:300]
d_train = d_train[:300]

network = MultiLayerNet(input_size=784, hidden_size_list=[100, 100, 100, 100, 100, 100], output_size=10)

iters_num = 1000
train_size = x_train.shape[0]
batch_size = 100
learning_rate=0.1

train_loss_list = []
accuracies_train = []
accuracies_test = []

plot_interval=10
hidden_layer_num = network.hidden_layer_num

# 正則化強度設定 ======================================
weight_decay_lambda = 0.005
# =================================================

for i in range(iters_num):
    batch_mask = np.random.choice(train_size, batch_size)
    x_batch = x_train[batch_mask]
    d_batch = d_train[batch_mask]

    grad = network.gradient(x_batch, d_batch)
    weight_decay = 0

    for idx in range(1, hidden_layer_num+1):
        grad['W' + str(idx)] = network.layers['Affine' + str(idx)].dW + weight_decay_lambda * np.sign(network.params['W' + str(idx)])
        grad['b' + str(idx)] = network.layers['Affine' + str(idx)].db
        network.params['W' + str(idx)] -= learning_rate * grad['W' + str(idx)]
        network.params['b' + str(idx)] -= learning_rate * grad['b' + str(idx)]        
        weight_decay += weight_decay_lambda * np.sum(np.abs(network.params['W' + str(idx)]))

    loss = network.loss(x_batch, d_batch) + weight_decay
    train_loss_list.append(loss)        

    if (i+1) % plot_interval == 0:
        accr_train = network.accuracy(x_train, d_train)
        accr_test = network.accuracy(x_test, d_test)
        accuracies_train.append(accr_train)
        accuracies_test.append(accr_test)

        print('Generation: ' + str(i+1) + '. 正答率(トレーニング) = ' + str(accr_train))
        print('                : ' + str(i+1) + '. 正答率(テスト) = ' + str(accr_test))               

lists = range(0, iters_num, plot_interval)
plt.plot(lists, accuracies_train, label="training set")
plt.plot(lists, accuracies_test,  label="test set")
plt.legend(loc="lower right")
plt.title("accuracy")
plt.xlabel("count")
plt.ylabel("accuracy")
plt.ylim(0, 1.0)
# グラフの表示
plt.show()
Generation: 1000. 正答率(トレーニング) = 0.9666666666666667
                : 1000. 正答率(テスト) = 0.7158

L2正則化

# 過学習を再現するために、学習データを削減
x_train = x_train[:300]
d_train = d_train[:300]

network = MultiLayerNet(input_size=784, hidden_size_list=[100, 100, 100, 100, 100, 100], output_size=10)

iters_num = 1000
train_size = x_train.shape[0]
batch_size = 100
learning_rate=0.01

train_loss_list = []
accuracies_train = []
accuracies_test = []

plot_interval=10
hidden_layer_num = network.hidden_layer_num

# 正則化強度設定 ======================================
weight_decay_lambda = 0.1
# =================================================

for i in range(iters_num):
    batch_mask = np.random.choice(train_size, batch_size)
    x_batch = x_train[batch_mask]
    d_batch = d_train[batch_mask]

    grad = network.gradient(x_batch, d_batch)
    weight_decay = 0

    for idx in range(1, hidden_layer_num+1):
        grad['W' + str(idx)] = network.layers['Affine' + str(idx)].dW + weight_decay_lambda * network.params['W' + str(idx)]
        grad['b' + str(idx)] = network.layers['Affine' + str(idx)].db
        network.params['W' + str(idx)] -= learning_rate * grad['W' + str(idx)]
        network.params['b' + str(idx)] -= learning_rate * grad['b' + str(idx)]        
        weight_decay += 0.5 * weight_decay_lambda * np.sqrt(np.sum(network.params['W' + str(idx)] ** 2))

    loss = network.loss(x_batch, d_batch) + weight_decay
    train_loss_list.append(loss)        

    if (i+1) % plot_interval == 0:
        accr_train = network.accuracy(x_train, d_train)
        accr_test = network.accuracy(x_test, d_test)
        accuracies_train.append(accr_train)
        accuracies_test.append(accr_test)

        print('Generation: ' + str(i+1) + '. 正答率(トレーニング) = ' + str(accr_train))
        print('                : ' + str(i+1) + '. 正答率(テスト) = ' + str(accr_test))               


lists = range(0, iters_num, plot_interval)
plt.plot(lists, accuracies_train, label="training set")
plt.plot(lists, accuracies_test,  label="test set")
plt.legend(loc="lower right")
plt.title("accuracy")
plt.xlabel("count")
plt.ylabel("accuracy")
plt.ylim(0, 1.0)
# グラフの表示
plt.show()
Generation: 1000. 正答率(トレーニング) = 0.9133333333333333
                : 1000. 正答率(テスト) = 0.7025

ドロップアウト

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

    def forward(self, x, train_flg=True):
        if train_flg:
            self.mask = np.random.rand(*x.shape) > self.dropout_ratio
            return x * self.mask
        else:
            return x * (1.0 - self.dropout_ratio)

    def backward(self, dout):
        return dout * self.mask
from common import optimizer
(x_train, d_train), (x_test, d_test) = load_mnist(normalize=True)

print("データ読み込み完了")

# 過学習を再現するために、学習データを削減
x_train = x_train[:300]
d_train = d_train[:300]

# ドロップアウト設定 ======================================
use_dropout = True
dropout_ratio = 0.15
# ====================================================

network = MultiLayerNet(input_size=784, hidden_size_list=[100, 100, 100, 100, 100, 100], output_size=10,
                        weight_decay_lambda=weight_decay_lambda, use_dropout = use_dropout, dropout_ratio = dropout_ratio)
optimizer = optimizer.SGD(learning_rate=0.01)
# optimizer = optimizer.Momentum(learning_rate=0.01, momentum=0.9)
# optimizer = optimizer.AdaGrad(learning_rate=0.01)
# optimizer = optimizer.Adam()

iters_num = 1000
train_size = x_train.shape[0]
batch_size = 100

train_loss_list = []
accuracies_train = []
accuracies_test = []

plot_interval=10


for i in range(iters_num):
    batch_mask = np.random.choice(train_size, batch_size)
    x_batch = x_train[batch_mask]
    d_batch = d_train[batch_mask]

    grad = network.gradient(x_batch, d_batch)
    optimizer.update(network.params, grad)

    loss = network.loss(x_batch, d_batch)
    train_loss_list.append(loss)    

    if (i+1) % plot_interval == 0:
        accr_train = network.accuracy(x_train, d_train)
        accr_test = network.accuracy(x_test, d_test)
        accuracies_train.append(accr_train)
        accuracies_test.append(accr_test)

        print('Generation: ' + str(i+1) + '. 正答率(トレーニング) = ' + str(accr_train))
        print('                : ' + str(i+1) + '. 正答率(テスト) = ' + str(accr_test))        

lists = range(0, iters_num, plot_interval)
plt.plot(lists, accuracies_train, label="training set")
plt.plot(lists, accuracies_test,  label="test set")
plt.legend(loc="lower right")
plt.title("accuracy")
plt.xlabel("count")
plt.ylabel("accuracy")
plt.ylim(0, 1.0)
# グラフの表示
plt.show()
Generation: 1000. 正答率(トレーニング) = 0.13
                : 1000. 正答率(テスト) = 0.1135

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

畳み込みニューラルネットワーク(Convolutional Neural Network:CNN)は、よく画像認識等で使用されているニューラルネットワークです。
しかし、画像だけにしか使えないわけではなく、1次元CNNを使うことで時系列データを扱うこともできます。

例えば画像の場合、縦、横、チャンネル(RGBやグレースケール)について学習できます。

畳み込み層

画像とフィルタ(カーネル)の積の総和にバイアスを加え、算出することで特徴を抽出します。
例えば、横線を抽出するフィルタに対する畳み込みの結果が大きければ、横線の特徴を抽出(横線が画像に存在する)し、小さければ横線がないということになる。

活性化関数に負数を0にできるReLU関数を使うことで、あきらかにそのフィルタに対する特徴を持っていない箇所をないものとして扱うことができ、学習に悪影響を与えることを防ぎます。

パディング

パディングとは、画像の周囲に固定値(0を使うことが多い)を埋め込むことです。
パディングなしで畳み込みをすると、元の画像より小さくなりますが、パディングをすることでサイズを維持することができます。

また、パディングなしの場合、画像の端の方は他の部分と比べて畳み込みに使われる回数が少なくなり、特徴として抽出されにくいですが、パディングをすることによって、より端の方も特徴を抽出できるようになります。

パディングに使う値は0でなくとも良いですが、学習に影響を与える可能性があるため、0とすることが一般的です。

ストライド

ストライドとは、フィルタをどれだけずらして畳み込みを行うかということです。
画像に対してフィルタを少しずつずらしながら畳み込みは行っていきます。
仮にストライドを1とした場合、次の畳み込みは1ピクセルずらして行うことになります。
ストライドが2なら2ピクセルずらすことになります。

チャンネル

チャンネルとは、空間的な奥行きのことです。
チャンネルが1つだと、例えばカラー画像でもRGBの違いを区別せずに畳み込みすることになり、正確な結果となりません。
カラー画像の場合は、RGBの3チャンネルに分けて畳み込みを行います。

プーリング層

プーリング層は、対象領域の中から1つの値を取得する層です。
畳み込んだ後に行うことで、それらしい特徴を持った値のみを抽出できます。

最大値プーリング

3×3等の対象領域の中で最大値を取得します。

平均値プーリング

3×3等の対象領域の中で平均値を取得します。

確認テスト

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

⇨高さ(または幅)+2パディング(左右または上下)-フィルタの高さ(または幅)をストライドで割った値に1を足します。
\((6+2*1-2/1)+1=7\)
⇨高さ、幅ともに同じため7×7となります。

最新のCNN

AlexNet

2012年にImageNetコンペティションで優勝したモデルです。
5層の畳み込み層およびプーリング層、それに続いて2層の全結合層という構造です。
ドロップアウトを使用して過学習を抑えています。

その他のモデル

  • ZFNet
  • GoogLeNet
  • VGGNet
  • ResNet
  • ensembled networks
  • Squeeze and Excitation Network(SENet)