ニューラルネットワークの重みとバイアス

ニューラルネットワークで扱われる多くのパラメータと、特に重みとバイアス・その行列表現について

ニューラルネットワーク

機械学習の目的となる問題の多くは分類問題であったり、回帰の問題といった構造をしていることが多くあります。
Random ForestやSVMなどは非常に有名な手法となっており、ニューラルネットワークもそれらの手法の仲間の一つとして認識することができます。しかし他の手法と少し異なる点としては拡張として、生成モデルなどの他の単純なモデルでは出来ない機能がニューラルネットワークの定義によっては可能となる点です。
ニューラルネットワークは多くのコンポーネントからなっていますが、基本的にはレイヤーやユニット、活性化関数や損失関数などから構成されています。
基本的には入力のデータと出力データを元に入力とラベルのパターンを学習し、分類問題や回帰問題を解くことができるようにネットワークのパラメータが学習されます。
ニューラルネットワークの基本的な構造は以下のようになっています。
上に見られるように、入力のユニット数は入力データの説明変数の数を表しており、我々は入力のデータに対して重みを掛けることでニューラルネットワークの計算が始まります。
ニューラルネットワークにはその重みの他にバイアスも存在し、入力データとラベルから主にはその重みとバイアスを学習する作業となります。
実際にこれらのshapeがどうなっているのかを見ていきます。

Required Libraries

  • matplotlib 2.0.2
  • numpy 1.12.1
  • scikit-learn 0.18.2
  • glob2 0.6
In [1]:
import numpy as np
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report, confusion_matrix
import renom as rm
from renom.optimizer import Sgd, Adam

Sequentialモデルを用いたネットワーク定義と入力データと教師データからの学習

In [2]:
class Model(rm.Model):
    def __init__(self):
        self.layer1 = rm.Dense(10)
        self.layer2 = rm.Dense(3)
    def forward(self, x, epoch, batch):
        t1 = rm.relu(self.layer1(x))
        out = self.layer2(t1)
        if epoch is not None and epoch < 2 and batch < 3:
            print("epoch:{}  batch:{} weight shape:{} bias shape:{}".format(epoch, batch, self.layer1.params.w.shape, self.layer1.params.b.shape))
            print("weight:{}".format(self.layer1.params.w))
            print("bias:{}".format(self.layer1.params.b))
            print()
        return out

iris = load_iris()
data = iris.data
label = iris.target

model = Model()

X_train, X_test, y_train, y_test = train_test_split(data, label, test_size=0.3)
y_train = y_train.reshape(len(X_train), -1)
y_test = y_test.reshape(len(X_test), -1)
batch_size = 8
epoch = 10
N = len(X_train)
optimizer = Sgd(lr=0.001)

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 = y_train[perm[j*batch_size : (j+1)*batch_size]]

        with model.train():
            l = rm.softmax_cross_entropy(model(train_batch, i, j), response_batch)
        grad = l.grad()
        grad.update(optimizer)
        loss += l.as_ndarray()
    train_loss = loss / (N // batch_size)

    test_loss = rm.softmax_cross_entropy(model(X_test, None, None), y_test).as_ndarray()
epoch:0  batch:0 weight shape:(4, 10) bias shape:(1, 10)
weight:[[-0.22148813 -0.06455304  0.24886461 -0.22360604 -0.2212799   0.14054264
   0.06318101  0.12782612 -0.37893802 -0.69344664]
 [ 0.13048458 -0.30900329 -0.13451999 -0.00094005  0.21173206  0.07846587
   0.15815271  0.16371909 -0.03767572 -0.8459813 ]
 [-0.55866587  0.02095983  0.25410637  0.04105124 -0.63527399  0.10226102
   0.30327985  0.01344239 -0.34383473  0.20845887]
 [ 0.00146433  0.14428516 -0.37800437 -0.66030794 -0.1535928  -0.41640413
   0.05833655 -0.28710318 -0.11259635  0.58286715]]
bias:[[ 0.  0.  0.  0.  0.  0.  0.  0.  0.  0.]]

epoch:0  batch:1 weight shape:(4, 10) bias shape:(1, 10)
weight:[[-0.22148813 -0.06455304  0.25000107 -0.22360604 -0.2212799   0.14019379
   0.0611538   0.12805027 -0.37893802 -0.69344664]
 [ 0.13048458 -0.30900329 -0.13419022 -0.00094005  0.21173206  0.0782304
   0.1572374   0.16374263 -0.03767572 -0.8459813 ]
 [-0.55866587  0.02095983  0.25530201  0.04105124 -0.63527399  0.10216144
   0.30181283  0.01376484 -0.34383473  0.20845887]
 [ 0.00146433  0.14428516 -0.37757117 -0.66030794 -0.1535928  -0.41641617
   0.05786775 -0.28697777 -0.11259635  0.58286715]]
bias:[[  0.00000000e+00   0.00000000e+00   1.79188093e-04   0.00000000e+00
    0.00000000e+00  -6.85928389e-05  -3.49776412e-04   3.19690043e-05
    0.00000000e+00   0.00000000e+00]]

epoch:0  batch:2 weight shape:(4, 10) bias shape:(1, 10)
weight:[[-0.22148813 -0.06455304  0.25033292 -0.22360604 -0.2212799   0.13935776
   0.05878816  0.12789027 -0.37893802 -0.69344664]
 [ 0.13048458 -0.30900329 -0.13454546 -0.00094005  0.21173206  0.07756997
   0.15600587  0.16343664 -0.03767572 -0.8459813 ]
 [-0.55866587  0.02095983  0.25636706  0.04105124 -0.63527399  0.10199867
   0.30032635  0.01402663 -0.34383473  0.20845887]
 [ 0.00146433  0.14428516 -0.37709758 -0.66030794 -0.1535928  -0.41641265
   0.05739774 -0.28683606 -0.11259635  0.58286715]]
bias:[[  0.00000000e+00   0.00000000e+00   1.40944816e-04   0.00000000e+00
    0.00000000e+00  -2.51324120e-04  -7.51175801e-04  -3.48089925e-05
    0.00000000e+00   0.00000000e+00]]

epoch:1  batch:0 weight shape:(4, 10) bias shape:(1, 10)
weight:[[-0.22148813 -0.06455304  0.27561915 -0.22360604 -0.2212799   0.13690807
   0.02486142  0.13276328 -0.37893802 -0.69344664]
 [ 0.13048458 -0.30900329 -0.12640031 -0.00094005  0.21173206  0.07457455
   0.13912132  0.16403335 -0.03767572 -0.8459813 ]
 [-0.55866587  0.02095983  0.28480351  0.04105124 -0.63527399  0.10428211
   0.2761648   0.02186429 -0.34383473  0.20845887]
 [ 0.00146433  0.14428516 -0.36625826 -0.66030794 -0.1535928  -0.41518098
   0.04929309 -0.28363782 -0.11259635  0.58286715]]
bias:[[ 0.          0.          0.00338294  0.          0.         -0.00102422
  -0.0063385   0.00038907  0.          0.        ]]

epoch:1  batch:1 weight shape:(4, 10) bias shape:(1, 10)
weight:[[-0.22148813 -0.06455304  0.2782279  -0.22360604 -0.2212799   0.13704985
   0.02220462  0.13325311 -0.37893802 -0.69344664]
 [ 0.13048458 -0.30900329 -0.12551248 -0.00094005  0.21173206  0.07450633
   0.13782531  0.16411592 -0.03767572 -0.8459813 ]
 [-0.55866587  0.02095983  0.28760439  0.04105124 -0.63527399  0.1047022
   0.27423552  0.02259091 -0.34383473  0.20845887]
 [ 0.00146433  0.14428516 -0.36530989 -0.66030794 -0.1535928  -0.41502473
   0.0486936  -0.28337926 -0.11259635  0.58286715]]
bias:[[ 0.          0.          0.00377256  0.          0.         -0.00102172
  -0.00679465  0.00044973  0.          0.        ]]

epoch:1  batch:2 weight shape:(4, 10) bias shape:(1, 10)
weight:[[-0.22148813 -0.06455304  0.28150427 -0.22360604 -0.2212799   0.1374025
   0.01957459  0.13400918 -0.37893802 -0.69344664]
 [ 0.13048458 -0.30900329 -0.12427821 -0.00094005  0.21173206  0.07453582
   0.13649586  0.16432714 -0.03767572 -0.8459813 ]
 [-0.55866587  0.02095983  0.29110238  0.04105124 -0.63527399  0.10535019
   0.27233651  0.02359707 -0.34383473  0.20845887]
 [ 0.00146433  0.14428516 -0.36394855 -0.66030794 -0.1535928  -0.41474748
   0.04804279 -0.28296828 -0.11259635  0.58286715]]
bias:[[ 0.          0.          0.00423871  0.          0.         -0.00099465
  -0.00724287  0.00054148  0.          0.        ]]

重みとバイアスは上記の出力のように学習され、更新していきます。
Irisデータセットを用いて150サンプルで4次元のデータセットを学習しています。
Batch sizeとepochはこちらで設定しなければいけないパラメータとなっています。
Epochは何回SGDによる最適化が行われるのかを表すiteration回数であり、結果はepochに依存して大きく変わることが多いです。そのため、epochはhyper parameter searchなどによって良い値を探すことが多くなると思います。もしくは学習曲線を見る方法もあります。
Batch sizeはメモリの効率的な仕様や処理速度のために重要になることが多い設定値となっています。
画像データのような大きくて一度にとても計算できないようなデータの場合にはミニバッチと呼ばれる単位にデータを分ける必要があります。例えばサンプル数が1600あって、ミニバッチが16だとすると、サンプルを16個ずつ分けて1つの学習セットとして学習が行われ、1epochの計算で160回のSGDの計算が行われることになります。学習時のデータのshapeは16x4(一度に学習するサンプル数x特徴量数)となります。ネットワークの重みは図の通り特徴量数x隠れ層のユニット数(基本的には前のLayerのユニット数x次のLayerのユニット数)となっており、重みのshapeは4x10、バイアスは1x10となっています。

基本的に重みはどの入力データにそれぞれ掛けられ、特に多クラスの分類問題においてはそのユニットへ向かうエッヂの重みが大きく学習されたり、重みはどういった作用をしているのか比較的理解がしやすいと考えられますが、バイアスについてはどのような役割を持っているのかがわかりづらい時があります。

バイアスを用いないとき、問題によっては分類直線を引くことが出来なかったり、回帰問題においては上の図のように原点を通らない回帰直線を描きたい場合にはその回帰直線がそれほど値を近似してくれないケースも起こり得ます。
次にバイアスを使ったケースと使わなかったケースを回帰問題においていていくことで、ニューラルネットワークにおいてバイアスがどのような貢献をしているのか見ていきたいと思います。バイアスを使う・使わないといったことについてはSequentialモデルでは記述が難しいので、Functionalモデルを用いてモデルを定義していきます。

Regression Problem

make_regression functionはscikit-learnに含まれる関数で、回帰問題のためのデータセットを生成することができます。この関数を用いてどのようなデータが生成されるのかを簡単に見たいと思います。

In [3]:
from sklearn.datasets import make_regression
import matplotlib.pyplot as plt

X, y = make_regression(n_samples=500, n_features=1, random_state=0, noise=4.0, bias=100.0)
X = - X
plt.scatter(X, y)
plt.show()
../../../_images/notebooks_basic_algorithm_neural_network_general_notebook_10_0.png

上記のケースは1次元のケースですが、これだけでは非常に簡単な予測となるために実際にモデルを定義して、5次元のデータに対する回帰問題を設定したいと思います。

Functionalモデルを用いてバイアスを使わなかったケース

In [4]:
class Model(rm.Model):
    def __init__(self, input_size, hidden_size, output_size):
        self.w1 = rm.Variable(np.random.randn(input_size, hidden_size)*0.01)
        self.w2 = rm.Variable(np.random.randn(hidden_size, output_size)*0.01)

    def forward(self, x):
        t1 = rm.dot(x, self.w1)
        t2 = rm.relu(t1)
        out = rm.dot(t2, self.w2)
        return out

data, label = make_regression(n_samples=500, n_features=5, random_state=0, noise=4.0, bias=100.0)
data = - data

model = Model(input_size=data.shape[1], hidden_size=10, output_size=1)

X_train, X_test, y_train, y_test = train_test_split(data, label, test_size=0.3)
y_train = y_train.reshape(len(X_train), -1)
y_test = y_test.reshape(len(X_test), -1)
batch_size = 8
epoch = 10
N = len(X_train)
optimizer = Sgd(lr=0.001)

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 = y_train[perm[j*batch_size : (j+1)*batch_size]]

        with model.train():
            l = rm.mean_squared_error(model(train_batch), response_batch)
        grad = l.grad()
        grad.update(optimizer)
        loss += l.as_ndarray()
    train_loss = loss / (N // batch_size)

    test_loss = rm.mean_squared_error(model(X_test), y_test).as_ndarray()
    print("epoch:{:03d}, train_loss:{:.4f}, test_loss:{:.4f}".format(i, float(train_loss), float(test_loss)))
epoch:000, train_loss:9117.4320, test_loss:10460.8447
epoch:001, train_loss:4329.0102, test_loss:1750.3320
epoch:002, train_loss:1493.0594, test_loss:1058.3417
epoch:003, train_loss:1275.2536, test_loss:844.1409
epoch:004, train_loss:1120.4195, test_loss:873.4360
epoch:005, train_loss:893.6425, test_loss:749.1420
epoch:006, train_loss:659.9649, test_loss:720.2554
epoch:007, train_loss:599.5877, test_loss:631.9391
epoch:008, train_loss:547.1918, test_loss:715.0874
epoch:009, train_loss:574.4669, test_loss:630.0540

Functionalモデルを用いてバイアスを使ったケース

In [5]:
class Model(rm.Model):
    def __init__(self, input_size, hidden_size, output_size):
        self.w1 = rm.Variable(np.random.randn(input_size, hidden_size)*0.01)
        self.b1 = rm.Variable(np.zeros((1, hidden_size)))
        self.w2 = rm.Variable(np.random.randn(hidden_size, output_size)*0.01)
        self.b2 = rm.Variable(np.zeros((1, output_size)))

    def forward(self, x):
        t1 = rm.dot(x, self.w1) + self.b1
        t2 = rm.relu(t1)
        out = rm.dot(t2, self.w2) + self.b2
        return out

data, label = make_regression(n_samples=500, n_features=5, random_state=0, noise=4.0, bias=100.0)
data = - data

model = Model(input_size=data.shape[1], hidden_size=10, output_size=1)

X_train, X_test, y_train, y_test = train_test_split(data, label, test_size=0.3)
y_train = y_train.reshape(len(X_train), -1)
y_test = y_test.reshape(len(X_test), -1)
batch_size = 8
epoch = 10
N = len(X_train)
optimizer = Sgd(lr=0.001)

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 = y_train[perm[j*batch_size : (j+1)*batch_size]]

        with model.train():
            l = rm.mean_squared_error(model(train_batch), response_batch)
        grad = l.grad()
        grad.update(optimizer)
        loss += l.as_ndarray()
    train_loss = loss / (N // batch_size)

    test_loss = rm.mean_squared_error(model(X_test), y_test).as_ndarray()
    print("epoch:{:03d}, train_loss:{:.4f}, test_loss:{:.4f}".format(i, float(train_loss), float(test_loss)))
epoch:000, train_loss:7894.1248, test_loss:1777.0327
epoch:001, train_loss:575.9983, test_loss:482.7759
epoch:002, train_loss:502.4154, test_loss:473.9365
epoch:003, train_loss:487.0127, test_loss:449.0021
epoch:004, train_loss:408.5537, test_loss:294.7806
epoch:005, train_loss:202.7415, test_loss:117.5818
epoch:006, train_loss:100.6396, test_loss:98.6491
epoch:007, train_loss:81.2815, test_loss:99.0892
epoch:008, train_loss:68.5562, test_loss:110.5399
epoch:009, train_loss:57.0712, test_loss:95.6728
上記の結果に見られるようにバイアスを使った結果と使わなかった結果は大きく異なります。
バイアスを使わなかったケースではLossが突然上がり始めてしまったり、高い値のままうまく回帰ができなかったケースが何度がありました。ニューラルネットワークは基本的にはこの重みとバイアスを学習していき、うまく分類をするパターンであったり、うまく回帰ができるパターンを探すことになります。
これらのニューラルネットワークが学習するパラメータを念頭に置いた上でbatch_sizeやepochについて、更には過学習が起きてしまったとき、DropoutやBatch Normalizationについて考えることで理解が深まると考えています。