Now Loading...

Now Loading...

本記事では深層モデルの過学習を防止する正則化の手法として、ドロップアウトやドロップコネクトについて解説します。

ドロップアウト

ドロップアウト(Dropout)はHintonらにより2012年に提案された手法です(Hinton etal., 2012, “Improving neural networks by preventing co-adaptation of feature detectors”)。ドロップアウトとは、深層ニューラルネットワークモデルの学習時にモデルの一部を学習させないことによって過学習を防ぐ手段です。ドロップアウトでは具体的に何をしているのかというと、以下の図のように学習時に一部のノードのみ学習させないということを行っています。どのノードを学習させないかはミニバッチ毎にランダムに変化させます。

元のネットワーク
ドロップアウトにより一部のノードが不活性化した状態(1)
ドロップアウトにより一部のノードが不活性化した状態(2)

これは数学的な定式化もできます。まず、以下のように入力vベクトル、バイアスを含む重み行列W、出力hベクトル、非線形の活性化関数aからなる全結合層は以下のように表せます。

ドロップアウトは、この出力hに対してバイナリマスク(0と1の要素で構成される行列)をかける操作に該当します。よって、ベルヌーイ分布に従ってバイナリマスクmが決まるとき、最終的な出力h’は

と表せます。ここで*は要素積です。ちなみにベルヌーイ分布というのは確率pで1を、確率1-Pで0をとるという確率分布のことです。

ドロップアウトの効果は二つあり、

  • 過学習を防ぐことに役立つ
  • 複数モデルの結果を組み合わせることで精度が向上する

です。まず過学習を防げるということについてですが、これはミニバッチ毎に一部のノードが不活性化されることで、パラメータの更新のし過ぎが抑制されるためです。また、複数モデルの結果の組み合わせになるということについてですが、ミニバッチ毎にネットワークのランダムなノードが不活性化されるため、ミニバッチ毎にネットワークは異なるものとなることがわかります。よってドロップアウトを用いた学習は異なるネットワークの結果の組み合わせとなり、汎化性能が向上します。アンサンブル学習に似た効果が得られるというわけです。

なお、ドロップアウト処理は学習時にのみ行う処理であり、推論時には全てのニューロンが有効になります。しかし、そうすると学習の時とは全体のニューロン数が変わってしまうため、推論の際には以下のようにニューロンの出力を1-pでスケーリングしてやる必要があります(もしくは、学習時に各ニューロンの出力を1/(1-p)倍してやることでも同じ効果が得られます)。

ドロップコネクト

次に、ドロップコネクト(DropConnect)についてです。ドロップコネクトはLi Wanらにより2013年に提案されました(Li et al., 2013, “Regularization of Neural Networks Using DropConnect”)。ドロップコネクトではノードを不活性化させるのではなく、代わりにノード間の結合をランダムに切ります。つまり、結合の重みをランダムで0にします。これにより、ノードは前のノードからランダムに入力を受けるわけです。

ドロップコネクトを施したネットワーク

つまりドロップアウトとなにが違うのかというと、ドロップアウトは全結合層の出力をランダムに0にするのに対し、ドロップコネクトは全結合層の重みをランダムに0にするということです。

こちらもドロップアウト同様、過学習の防止及び複数モデルの組み合わせ学習という効果を得ることができます。ただ、通常はノードよりもコネクションの方が数が多いため、ドロップコネクトの方がより多くのパターンを持ちます。

また、ドロップアウトと同様に数学的な定式化をすると、ドロップアウトのときと同様にバイナリマスクMをベルヌーイ分布に従って生成し、

重みWにバイナリマスクをかけます。

あとはドロップアウトの項で示したように普通に全結合層を計算すればいいというわけです。

ドロップアウトの定式化と比較すると、こちらでは重みに対して結合情報をエンコーディングしているということがわかります。

また、ドロップコネクトでもドロップアウトのときと同様に、推論時には学習時に無効化された重みを補うため重みを1-pでスケーリングします(もしくは、学習時に各重みを1/(1-p)倍してやることでも同じ効果が得られます)。

Pytorchで実装してみる

まず、ドロップアウトやドロップコネクトの効果を確認するために普通のモデルを用いてMNISTデータを10エポック学習させてみます。その学習結果は以下のようになります。学習過程での損失、正解率もプロットします。わざと過学習を発生させるため、少ないデータで学習させています。

なお、以降のコードはモデルの乱数を固定していないため実行ごとに結果が異なり、正式に比較はできませんが、固定すればより正確に比較できます。

 

少しですが過学習が発生していることがわかります。もう少しデータを偏らせたりしたらより顕著になるかもしれませんが、ドロップアウトやドロップコネクトの効果を見るには十分です。

では、ドロップアウトとドロップコネクトを実装してみましょう。ドロップアウトはpytorchではすでに実装されており、モデル中でtorch.nn.Droputを用いるだけで簡単に使えます。以下はSubclassing APIとしてモデルを定義したとき、ドロップアウトを用いる例です。

class Net(nn.Module):
    def __init__(self):
      super(Net,self).__init__()
      self.conv1 = nn.Conv2d(in_channels=1, out_channels=16, kernel_size=5, padding=2)
      self.conv2 = nn.Conv2d(in_channels=16, out_channels=32, kernel_size=5, padding=2)

      self.dropout_1 = nn.Dropout(0.5)

      self.dropout_2 = nn.Dropout(0.5)

      self.fc1 = nn.Linear(32 * 7 * 7, 128)
      self.fc2 = nn.Linear(128,10)

    def forward(self,x):
      x = torch.relu(F.max_pool2d(self.conv1(x), 2))
      x = torch.relu(F.max_pool2d(self.conv2(x), 2))

      x = x.view(-1, x.size(1) * x.size(2) * x.size(3)) #テンソルを平らに(1次元に)する処理

      x = self.dropout_1(x) #ドロップアウトを適用
      x = torch.relu(self.fc1(x))
      x = self.dropout_2(x) #ドロップアウトを適用
      x = self.fc2(x)
      return x

これを学習させ、その学習過程をプロットすると以下のようになります。

 

あんまり効果がないように見えますね。

一方、ドロップコネクトはpytorchにデフォルトの機能がないので、カスタムレイヤとして自分で実装してやる必要があります。以下は実装の一例です。重みを確率的に消す処理を行います。

def _weight_drop(module, weights, dropout):
    for name_w in weights:
        w = getattr(module, name_w)
        del module._parameters[name_w]
        module.register_parameter(name_w + '_raw', Parameter(w))

    original_module_forward = module.forward

    def forward(*args, **kwargs):
        for name_w in weights:
            raw_w = getattr(module, name_w + '_raw')
            w = torch.nn.functional.dropout(raw_w, p=dropout, training=module.training)
            setattr(module, name_w, w)

        return original_module_forward(*args, **kwargs)

    setattr(module, 'forward', forward)


class WeightDropLinear(torch.nn.Linear):
    def __init__(self, *args, weight_dropout=0.0, **kwargs):
        super().__init__(*args, **kwargs)
        weights = ['weight']
        _weight_drop(self, weights, weight_dropout)

このクラスを用いてドロップコネクトを実装したモデルで学習させてみたものは以下になります。

 

正直微妙な結果ですね。今回はドロップアウトやドロップコネクトにより過学習が改善するところを見たかったのですが、もっと過学習が顕著に出てるデータを使えば、はっきり効果がわかるかもしれません。

TensorFlowで実装してみる

同じことをTensorFlowでもやってみます。まず普通のモデルで過学習を起こします。タイムアウトになる場合は何回か実行してみてください。

 

こちらはかなり綺麗に(?)過学習が出ています。

TensorFlowでもドロップアウトの機能は容易に用いることができ、以下のようにモデル中でtf.keras.layers.Dropoutを用いるだけです。

 

結構改善が見られますね(全体的なLossは上がってますが)。

一方、TensorFlowでドロップコネクトを用いるにはdropconnect-tensorflowを用いれば簡単に実装することができます。

pip install dropconnect-tensorflow
import tensorflow as tf
from tensorflow.keras import layers,models
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
from dropconnect_tensorflow import DropConnectDense

#データを読み込む
mnist = tf.keras.datasets.mnist
(X_train, y_train),(X_test, y_test) = mnist.load_data(path='/mnt/lib/ztodataset/mnist.npz')

#データを少なく取る
df = pd.DataFrame(columns=["label"])
df["label"] = y_train.reshape([-1])

list_0 = df.loc[df.label==0].sample(n=10)#n=10でsampling
list_1 = df.loc[df.label==1].sample(n=10)
list_2 = df.loc[df.label==2].sample(n=10)
list_3 = df.loc[df.label==3].sample(n=10)
list_4 = df.loc[df.label==4].sample(n=10)
list_5 = df.loc[df.label==5].sample(n=10)
list_6 = df.loc[df.label==6].sample(n=10)
list_7 = df.loc[df.label==7].sample(n=10)
list_8 = df.loc[df.label==8].sample(n=10)
list_9 = df.loc[df.label==9].sample(n=10)

label_list = pd.concat([list_0,list_1,list_2,list_3,list_4,list_5,list_6,list_7,list_8,
                       list_9])
label_list = label_list.sort_index()
label_idx = label_list.index.values

train_label = label_list.label.values

"""
x_trainからlabel用のdataframe.indexを取り出すことでlabelに対応したデータを取り出す。
"""
X_train = X_train[label_idx]
y_train= train_label

#正規化
X_train, X_test = X_train/255.0, X_test/255.0


model = tf.keras.models.Sequential([
    tf.keras.layers.Reshape((28, 28, 1), input_shape=(28, 28)),

    tf.keras.layers.Conv2D(16, (5, 5), activation='relu'),
    tf.keras.layers.MaxPooling2D((2, 2)),

    tf.keras.layers.Conv2D(32, (5, 5), activation='relu'),
    tf.keras.layers.MaxPooling2D((2, 2)),

    tf.keras.layers.Flatten(),

    DropConnectDense(128, prob=0.5, activation="relu"), #線形層にドロップコネクトを適用
    DropConnectDense(10, prob=0.5, activation="softmax")

])

model.compile(optimizer='adam',
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])

history = model.fit(X_train, y_train,
                    batch_size=64,
                    epochs=50,
                    verbose=0,
                    validation_data=(X_test, y_test))

plt.plot(history.history['loss'], label='train')
plt.plot(history.history['val_loss'], label = 'val')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.legend(loc='lower right')

plt.show()

こちらはなぜかあまり改善が見られませんでした。

まとめ

今回は、ドロップアウトとドロップコネクトについて簡単に解説しました。いずれも非常に重要なところなのでわからないところがある場合はよく復習しておきましょう。

\ シェア /

E資格スピードパッケージ2023#2修了者合格率100%達成

zero to one E資格 jdla

zero to oneの「E資格」向け認定プログラム

日本ディープラーニング協会の実施するE資格の受験ならzero to oneの「E資格」向け認定プログラム (税込165,000円) をおすすめします。当講座は、東京大学大学院工学系研究科の松尾豊教授と東北大学大学院情報科学研究科の岡谷貴之教授が監修する実践的なプログラムとなっています。
厚生労働省の教育訓練給付制度対象のE資格認定プログラムの中では最安値※となり、実質負担額49,500円~(支給割合70%の場合)で受講可能です。※2023年弊社調べ zero to one E資格 jdla

人工知能基礎講座を提供中

人工知能の第一人者である東京大学の松尾豊教授が監修した人工知能基礎講座を受講してみませんか? 人工知能の歴史から自然言語処理、機械学習、深層学習といった最先端のトピックやAIに関わる法律問題まで網羅しているので全てのビジネスパーソン・AIの初学者におすすめです。

サンプル動画

人工知能基礎講座はこちら↓ zero to one G検定 人工知能基礎 jdla

AI初学者・ビジネスパーソン向けのG検定対策講座

G検定受験前にトレーニングしたい方向けの問題集「G検定実践トレーニング」も提供中です。 zero to one E資格 jdla