モデルの重みをどのように初期化するかは、Deep Learningにおいて重要なトピックです。
初期重みは、勾配、出力部分空間など、多くの要素に影響を与えます。
この記事では、最も重要で広く使われている重みの初期化手法と、それをPyTorchを使って実装する方法について学びます。
この記事では、ユーザーが初級レベルのPyTorchの知識を持っていることを想定しています。
なぜモデルの重みを初期化することが重要なのでしょうか?
深層学習モデルをトレーニングする目的は、望ましい結果を得るために最適なモデルの重みのセットを見つけることです。
Deep Learningで使用されるトレーニング方法は一般的に反復的な性質を持っており、時間の経過とともに更新される必要のある重みの初期セットを提供する必要があります。
初期の重みは、訓練の最終的な結果を決定する上で大きな役割を果たします。
重みの初期化を誤ると、勾配が消失したり爆発したりする可能性があり、これは明らかに望ましくないことです。
そのため、我々は層の初期化にはいくつかの標準的な方法を用いています。
一般的な経験則
経験則では、「初期モデルの重みは0に近づける必要があるが、0ではない」ということです。
素朴な考えとしては、任意に0に近い分布からサンプリングすることでしょう。
例えば、U(-0.01, 0.01) や N(0, 0.01) からサンプリングした値で重みを埋めるように選択することができます。
標準的な手法のほとんどは、一様分布や正規分布からのサンプリングに基づいています。
しかし,本当のトリックは,これらの分布の境界条件を設定することにあります。
一般的に使用される境界条件の1つは1/sqrt(n)で、nは層への入力の数です。
PyTorchでは、uniform_
関数とnormal_
関数を使用して、レイヤーの重みを一様分布または正規分布からサンプリングするように設定することができます。
以下は、 uniform_()
と normal_()
が動作する簡単な例です。
# Linear Dense Layer layer_1 = nn.Linear( 5 , 2 )
print ( "Initial Weight of layer 1:" )
print (layer_1.weight)
# Initialization with uniform distribution nn.init.uniform_(layer_1.weight, - 1 / sqrt( 5 ), 1 / sqrt( 5 ))
print ( " )
print (layer_1.weight)
# Initialization with normal distribution nn.init.normal_(layer_1.weight, 0 , 1 / sqrt( 5 ))
print ( " )
print (layer_1.weight)
|
結果は以下の通りです。
Initial Weight of layer 1 :
Parameter containing: tensor([[ - 0.0871 , - 0.0804 , 0.2327 , - 0.1453 , - 0.1019 ],
[ - 0.1338 , - 0.2465 , 0.3257 , - 0.2669 , - 0.1537 ]], requires_grad = True )
Weight after sampling from Uniform Distribution:
Parameter containing: tensor([[ 0.4370 , - 0.4110 , 0.2631 , - 0.3564 , 0.0707 ],
[ - 0.0009 , 0.3716 , - 0.3596 , 0.3667 , 0.2465 ]], requires_grad = True )
Weight after sampling from Normal Distribution:
Parameter containing: tensor([[ - 0.2148 , 0.1156 , 0.7121 , 0.2840 , - 0.4302 ],
[ - 0.2647 , 0.2148 , - 0.0852 , - 0.3813 , 0.6983 ]], requires_grad = True )
|
しかし、この方法にはいくつかの限界もあります。
これらの方法は少し一般化されすぎており、 Sigmoid
や Tanh
、 ReLU
などの非線形活性化関数を持つ層では、勾配が消失したり爆発したりする可能性が高く、少し問題がある傾向があります。
そこで、次のセクションでは、この問題に対処するために提案された先進的な手法をいくつか紹介します。
非線形活性化レイヤーの初期化
Xavier(Glorot)初期化法とKaiming初期化法の2つがある。
ここでは、数式や証明には触れず、どこをどのように使うかに焦点を当てます。
数学的な背景をスキップしてくださいということでは全くありません。
1. Xavier 初期化
Xavier 初期化は、活性化関数が Sigmoid
と Tanh
であるレイヤに用いられる。
Xavier初期化には2つの異なるバージョンがある。
その違いは、データのサンプリング元となる分布にあり、一様分布と正規分布があります。
ここでは、この2つのバージョンについて簡単に説明します。
2. ザビエル一様分布
ウエイトテンソルは一様分布 U(-a, a) からサンプリングされた値で満たされる。
# The convolution layer conv_layer = nn.Conv2d( 1 , 4 , ( 2 , 2 ))
# Initiliazing with Xavier Uniform nn.init.xavier_uniform_(conv_layer.weight) |
input_dimと
output_dimは出力と入力の次元、より明確には前の層と前の層の次元、そして
gain` は単純なスケーリングファクターです。
例えば、以下の様になります。
# The convolution layer conv_layer = nn.Conv2d( 1 , 4 , ( 2 , 2 ))
# Initiliazing with Xavier Normal nn.init.xavier_normal_(conv_layer.weight) |
3. ザビエル正規分布
この方法は、値が正規分布からサンプリングされることを除けば、前の方法と似ている ! [Normal](データ: image/svg+xml; base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NyIgaGVpZ2h0PSIzMSIgdmlld0JveD0iMCAwIDk3IDMxIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIBmaWxsPSIjY2ZkNGRiIi 8+PC9zdmc+)ここにある通りです。
conv_layer = nn.Conv2d( 1 , 4 , ( 2 , 2 ))
nn.init.kaiming_uniform_(conv_layer.weight, mode = 'fan_in' , nonlinearity = 'relu' )
|
と input_dim
と output_dim
は出力と入力の次元、より明示的には前と前の層の次元を表しています。
例えば、以下の様になります。
conv_layer = nn.Conv2d( 1 , 4 , ( 2 , 2 ))
nn.init.kaiming_normal_(conv_layer.weight, mode = 'fan_in' , nonlinearity = 'relu' )
|
Kaiming初期化
これまで、活性化関数が sigmoid
と Tanh
の場合の重みの初期化方法について説明してきました。
しかし、まだ ReLU
については議論していません。
ReLU活性化関数を持つ層は、Kaiming が
ReLU` 活性化関数を持つ層の初期化方法を提案するまで、Xavier 法で初期化されていました。
KaimingはXavierの初期化とは境界条件の数式が少し違うだけです。
KamingのPyTorch実装は、ReLUだけでなくLeakyReLUも扱います。
PyTorchでは、kaimingの初期化にfan_inモードとfan_outモードという2種類のモードを用意しています。
fan_in モードを使用すると、爆発や内破からデータを確実に保護することができます。
同様に、fan_outモードはback-propogationで勾配を保存しようとします。
この記事もチェック:pythonで活性化関数ReLuを実装する方法を分かりやすく解説する
1. カイミング一様分布
ウエイトテンソルは一様分布U(-a, a)からサンプリングされた値で満たされる。
import torch.nn as nn
import torch.nn.functional as F
class Net(nn.Module):
def __init__( self ):
# Layer definitions
super ().__init__()
self .conv1 = nn.Conv2d( 3 , 6 , 5 )
self .pool = nn.MaxPool2d( 2 , 2 )
self .conv2 = nn.Conv2d( 6 , 16 , 5 )
self .fc1 = nn.Linear( 16 * 5 * 5 , 120 )
self .fc2 = nn.Linear( 120 , 84 )
self .fc3 = nn.Linear( 84 , 10 )
# Initialization
nn.init.kaiming_normal_( self .fc1.weight, mode = 'fan_in' ,
nonlinearity = 'relu' )
nn.init.kaiming_normal_( self .fc2.weight, mode = 'fan_in' ,
nonlinearity = 'relu' )
nn.init.xavier_normal_( self .fc3.weight)
def forward( self , x):
x = self .pool(F.relu( self .conv1(x)))
x = self .pool(F.relu( self .conv2(x)))
x = x.view( - 1 , 16 * 5 * 5 )
x = F.relu( self .fc1(x))
x = F.relu( self .fc2(x))
x = self .fc3(x)
x = nn.sigmoid(x)
return x
# Every time you create a new mode, it will have a weight initialized model net = Net()
|
fan_inモードでは入力次元を、fan_outモードでは出力次元を使用します。
ReLUのゲインは√2、LeakyReLuのゲインは√(1/a^2 +1)です。
ゲインは通常 kaiming_uniform_()
と kaiming_normal_()
関数で処理され、扱う非線形の種類だけを指定する必要がある。
例えば、以下の様になります。
# Defining a method for initialization of linear weights # The initialization will be applied to all linear layers # irrespective of their activation function def init_weights(m):
if type (m) = = nn.Linear:
torch.nn.init.xavier_uniform(m.weight)
# Applying it to our net net. apply (init_weights)
|
2. カイミング正規分布
層の重みは正規分布からサンプリングされる ! [正規分布](data: image/svg+xml; base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NyIgaGVpZ2h0PSIzMSIgdmlld0JveD0iMCAwIDk3IDMxIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIBmaWxsPSIjY2ZkNGRiIi 8+PC9zdmc+)ここにある。
# Create the model net = Net()
# Apply the Xavier normal method to the last layer nn.init.xavier_normal_( self .fc3.weight)
|
は出力寸法、input_dim、output_dim は入力寸法で、動作モードの選択により決定される。
例えば、以下の様になります。
初期化ルールをPyTorchモデルに統合する
PyTorchを使用して単一のレイヤーを初期化する方法に慣れたので、実際のPyTorchモデルのレイヤーを初期化してみましょう。
この初期化は、モデル定義の中で行うこともできますし、モデルが定義された後にこれらのメソッドを適用することもできます。
1. モデル定義時の初期化
2. モデル作成後の初期化
特定のレイヤーのルールを定義してモデル全体に適用するか、あるいは単一のレイヤーを初期化することで、いつでもモデルを作成した後に重みを変更することができます。
まとめ
これで、重みの初期化に関する今回の記事は終わりです。
今後もディープラーニングやPyTorchに関するこのような記事をお届けしていきますので、ご期待ください。