Murayama blog.

プログラミング教育なブログ

Xavierの初期値 Heの初期値の考察

ゼロから作るDeep Learning 第6章を参考に、ニューラルネットワークの隠れ層のアクティベーション(活性化関数の出力)の分布を確認してみます。

次のプログラムは1000件のサンプル(1つのサンプルは100次元のベクトル)を、5層の隠れ層(ノードはすべて100)に流すものです。

import numpy as np
import matplotlib.pyplot as plt

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

def tanh(x):
    return np.tanh(x)

def relu(x):
    return np.maximum(0, x)

def show_activation(activation, weights, ylim=(0, 50000)):

    x = np.random.randn(1000, 100)
    node = 100
    hidden = 5
    activations = {}
    for i in range(hidden):
        if i != 0:
            x = activations[i - 1]
      
        w = weights(node)
        z = np.dot(x, w)
        a = activation(z)
        activations[i] = a
      
    plt.figure(figsize=(18, 4))
    for i, a in activations.items():
        plt.subplot(1, len(activations), i + 1)
        plt.title(str(i + 1) + "-layer")
        plt.hist(a.flatten(), 30, range=(0,1))
        plt.ylim(ylim)
    plt.show()

show_activation(sigmoid, lambda n: np.random.randn(n, n) * 1)
# show_activation(sigmoid, lambda n: np.random.randn(n, n) * 0.01)
# show_activation(sigmoid, lambda n: np.random.randn(n, n) *  np.sqrt(1.0 / n), (0, 10000)) # Xavier

# show_activation(relu, lambda n: np.random.randn(n, n) * 0.01, (0, 7000))
# show_activation(relu, lambda n: np.random.randn(n, n) * np.sqrt(1.0 / n), (0, 7000)) # Xavier
# show_activation(relu, lambda n: np.random.randn(n, n) * np.sqrt(2.0 / n), (0, 7000)) # He

このプログラムでは重みのスケールを標準偏差1のガウス分布としています。

show_activation(sigmoid, lambda n: np.random.randn(n, n) * 1)

以降、重みを変更するとアクティベーションにどのような変化を及ぼすか確認していきます。

標準偏差1の場合

標準偏差1の実行結果は次のようになります。

f:id:yamasahi:20171125231255p:plain

アクティベーションが0と1に偏っているのがわかります。sigmoid関数の出力が0に近くにつれて(あるいは1に近くづにつれて)、その微分の値は0に近づきます。そのため、0と1に偏った分布では逆伝搬での勾配の値が小さくなってしまいます。このような現象は「勾配消失問題(gradient vanishing)」と呼ばれます。

標準偏差0.01の場合

次は重みのスケールを標準偏差0.01のガウス分布としています。

show_activation(sigmoid, lambda n: np.random.randn(n, n) * 0.01)

結果は次のようになります。

f:id:yamasahi:20171125231322p:plain

0.5付近に集中するようになりました。勾配消失問題は解消できましたが、アクティベーションに偏りがあるということは表現力が乏しいということです。複数のニューロンが同じような出力をするのであれば、ニューロンが複数存在する意味が失われてしまいます。「表現力の制限」が問題になってしまいます。

Xavierの初期値の場合

次にXavier Glorotの初期値を試してみます。これは前層のノード数が n の場合 1/sqrt(n) を標準偏差とした分布を使うというものです。

Kerasの場合はglorot_uniform、florot_normalのような初期値が定義されています。

show_activation(sigmoid, lambda n: np.random.randn(n, n) *  np.sqrt(1.0 / n), (0, 10000)) # Xavier

結果は次のようになります。

f:id:yamasahi:20171125231336p:plain

これまでの結果に比べてばらつきのある結果を得ることができました。「勾配消失問題」や「表現力の制限」といった問題を上手く回避できているのがわかります。

ReLU関数の場合

ここまでsigmoid関数のアクティベーションを見てきました。ReLU関数の場合はどうでしょうか。

標準偏差0.01の場合

show_activation(relu, lambda n: np.random.randn(n, n) * 0.01, (0, 7000))

f:id:yamasahi:20171125231354p:plain

Xavierの初期値の場合

show_activation(relu, lambda n: np.random.randn(n, n) * np.sqrt(1.0 / n), (0, 7000)) # Xavier

f:id:yamasahi:20171125231403p:plain

ReLU関数の場合、Xavierの初期値を使ったとしても、層が深くなるにつれて偏りが大きくなります。そこでReLU関数の場合はHeの初期値を使うことでこのようなケースに対処できます。Heの初期値は sqrt(2.0 / n)を標準偏差とするものです。ReLU関数の場合、負の領域がすべて0になるため、ばらつきにより広がりを持たせるために、2倍にすると考えます。

Heの初期値の場合

show_activation(relu, lambda n: np.random.randn(n, n) * np.sqrt(2.0 / n), (0, 7000)) # He

実行結果を見ていましょう。

f:id:yamasahi:20171125231413p:plain

Heの初期値を使えば偏りのないアクティベーションを確認することができました。