重みの初期化

重みの初期化とDeep Learningのランダム性

Deep Learningはランダム性を持つ手法であり、重みの初期値がランダムであるために実行するたびに結果が変わる手法となります。
いくつかの重みの初期化手法が存在し、ReNomに含まれる初期化手法を紹介したいと思います。今回は4つの初期化手法であるUniform Initialization, Gaussian Initialization, Glorot Uniform Initialization, Glorot Normal Initializationで結果を出していきたいと思います。
  • Uniform Initializationは単純に最小-1、最大1の一様分布から初期値をランダムに選んでくる手法となります。
  • Gaussian Initializationは平均0, 分散1の標準正規分布から初期値をランダムに選んでくる手法となります。
  • Glorot Uniform InitializationはUniform Initializationと同様に一様分布から初期値を生成しますが、一様分布の最小値と最大値がレイヤーの入力ユニット数と出力ユニット数によって異なります。
  • Glorot Normal InitializationはGaussian Initializationと同様に正規分布から初期値を生成しますが、分散の値がレイヤーの入力ユニット数と出力ユニット数によって異なります。

一般的に、入力ユニット数と出力ユニット数を考慮して重みの初期値を決めた方が良いと言われています。そこで、以下の図に重みの初期値の決定方法に関する問題点と代表的な手法を示します。

上の図にあるように、各レイヤーの重みを初期化する際に入力ユニット数と出力ユニット数を考慮する必要があります。
そしてGlorot UniformとGlorot Normalという初期化手法はそれを考慮した初期化の手法となっています。
ReNomでどのように初期化手法を適用するのか、そして初期化手法の比較を見ていきましょう。

Required Libraries

  • numpy 1.21.1
  • pandas 0.20.3
  • matplotlib 2.0.2
  • scikit-learn 0.18.1
  • ReNom 2.5.2
In [1]:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.preprocessing import LabelBinarizer
from sklearn.model_selection import train_test_split
import renom as rm
from renom.utility.initializer import Uniform, Gaussian, GlorotUniform, GlorotNormal
from renom.cuda.cuda import set_cuda_active
set_cuda_active(False)

Make label data

簡単な全結合ニューラルネットワークを構築して建物のエネルギー効率を解析します。暖房/冷房負荷は空調が室内の気温を一定に保つためにどの程度のエネルギーを必要とするかを表す指標(単位はkWh)で、室内気温を保ちにくいほど負荷は大きくなります。例えば、部屋の容積が大きかったり、壁が熱を通しやすい(つまり外と熱を交換しやすい)ほど空調の負荷は大きくなります。ここでは建物の壁や窓の面積などから、暖房の負荷を回帰予測します。データはUCIで無料で公開されているデータセット( https://archive.ics.uci.edu/ml/datasets/Energy+efficiency )を用います。

In [2]:
columns = ["RelativeCompactness", "SurfaceArea", "WallArea", "RoofArea", "OverallArea",
           "Orientation", "GlazingArea", "GlazingAreaDistribution", "HeatingLoad", "CoolingLoad"]
df = pd.read_excel("./ENB2012_data.xlsx", names=columns)
df.head()

df_s = df.copy()

for col in df.columns:
    v_std = df[col].std()
    v_mean = df[col].mean()
    df_s[col] = (df_s[col] - v_mean) / v_std

df_s.head()
Out[2]:
RelativeCompactness SurfaceArea WallArea RoofArea OverallArea Orientation GlazingArea GlazingAreaDistribution HeatingLoad CoolingLoad
0 2.040447 -1.784712 -0.561586 -1.469119 0.999349 -1.340767 -1.7593 -1.813393 -0.669679 -0.342443
1 2.040447 -1.784712 -0.561586 -1.469119 0.999349 -0.446922 -1.7593 -1.813393 -0.669679 -0.342443
2 2.040447 -1.784712 -0.561586 -1.469119 0.999349 0.446922 -1.7593 -1.813393 -0.669679 -0.342443
3 2.040447 -1.784712 -0.561586 -1.469119 0.999349 1.340767 -1.7593 -1.813393 -0.669679 -0.342443
4 1.284142 -1.228438 0.000000 -1.197897 0.999349 -1.340767 -1.7593 -1.813393 -0.145408 0.388113
In [3]:
X, y = np.array(df_s.iloc[:, :8]), np.array(df_s.iloc[:, 8:])
X_train, X_test, labels_train, labels_test = train_test_split(X, y, test_size=0.1, random_state=42)

Training Loop

学習ループ中では過学習を起こしていないか、もしくは今の手法で正しく学習できているかを確認するために、Lossの下がり方を見ることをお勧めします。
Lossは正解と予測の差ですので、小さくなっていればなっているほど手法の評価としては良いという解釈ができます。
今回は手法の比較の意味でLossを比較していきたいと思います。
In [4]:
def train_loop(epoch, N, batch_size, sequential, X_train, labels_train, X_test, labels_test, optimizer):
    learning_curve = []
    test_learning_curve = []

    for i in range(epoch):
        perm = np.random.permutation(N)
        loss = 0
        for j in range(0, N//batch_size):
            train_batch = X_train[perm[j*batch_size : (j+1)*batch_size]]
            response_batch = labels_train[perm[j*batch_size : (j+1)*batch_size]]
            with sequential.train():
                l = rm.mse(sequential(train_batch), response_batch)
            grad = l.grad()
            grad.update(optimizer)
            loss += l.as_ndarray()
        train_loss = loss / (N//batch_size)
        test_loss = rm.mse(sequential(X_test), labels_test).as_ndarray()
        test_learning_curve.append(test_loss)
        learning_curve.append(train_loss)
    return learning_curve, test_learning_curve

Network definition and initialize parameters

In [5]:
output_size = 1
epoch = 500
batch_size = 128
N = len(X_train)
optimizer = rm.Adam()

network1 = rm.Sequential([
    rm.Dense(8, initializer=Uniform()),
    rm.Relu(),
    rm.Dense(8, initializer=Uniform()),
    rm.Relu(),
    rm.Dense(6, initializer=Uniform()),
    rm.Relu(),
    rm.Dense(1, initializer=Uniform())
])

network2 = rm.Sequential([
    rm.Dense(8, initializer=Gaussian()),
    rm.Relu(),
    rm.Dense(8, initializer=Gaussian()),
    rm.Relu(),
    rm.Dense(6, initializer=Gaussian()),
    rm.Relu(),
    rm.Dense(1, initializer=Gaussian())
])

network3 = rm.Sequential([
    rm.Dense(8, initializer=GlorotUniform()),
    rm.Relu(),
    rm.Dense(8, initializer=GlorotUniform()),
    rm.Relu(),
    rm.Dense(6, initializer=GlorotUniform()),
    rm.Relu(),
    rm.Dense(1, initializer=GlorotUniform())
])

network4 = rm.Sequential([
    rm.Dense(8, initializer=GlorotNormal()),
    rm.Relu(),
    rm.Dense(8, initializer=GlorotNormal()),
    rm.Relu(),
    rm.Dense(6, initializer=GlorotNormal()),
    rm.Relu(),
    rm.Dense(1, initializer=GlorotNormal())
])

learning_curve, test_learning_curve_Gaussian = train_loop(epoch=epoch, N=N, batch_size=batch_size, sequential=network1, X_train=X_train, labels_train=labels_train, X_test=X_test, labels_test=labels_test, optimizer=optimizer)
learning_curve, test_learning_curve_Uniform = train_loop(epoch=epoch, N=N, batch_size=batch_size, sequential=network2, X_train=X_train, labels_train=labels_train, X_test=X_test, labels_test=labels_test, optimizer=optimizer)
learning_curve, test_learning_curve_GlorotUniform = train_loop(epoch=epoch, N=N, batch_size=batch_size, sequential=network3, X_train=X_train, labels_train=labels_train, X_test=X_test, labels_test=labels_test, optimizer=optimizer)
learning_curve, test_learning_curve_GlorotNormal = train_loop(epoch=epoch, N=N, batch_size=batch_size, sequential=network4, X_train=X_train, labels_train=labels_train, X_test=X_test, labels_test=labels_test, optimizer=optimizer)


plt.clf()
plt.plot(test_learning_curve_Gaussian, linewidth=1, label="Gaussian")
plt.plot(test_learning_curve_Uniform, linewidth=1, label="Uniform")
plt.plot(test_learning_curve_GlorotUniform, linewidth=1, label="GlorotUniform")
plt.plot(test_learning_curve_GlorotNormal, linewidth=1, label="GlorotNormal")
plt.title("learning_curve")
plt.ylabel("error")
plt.xlabel("epoch")
plt.ylim(0,0.5)
plt.legend()
plt.grid()
plt.show()
../../../_images/notebooks_basic_algorithm_weight_initialization_notebook_9_0.png
上記の結果から、Glorot Normal Initializationが最も良い結果となりました。
InitializationとGlorot Uniform Initializationが悪い結果となりました。
この初期値のランダム性によってDeep Learningの結果は実行する度に異なる結果となります。
どの初期化手法がベストかということを言うのは難しく、分析するデータやネットワークに合わせてどの初期化手法がベストなのかを考える必要があります。