前回の正弦波フィットでは、フィット具合がいまいちでした。そこで、どういうパラメータにすると、どの程度まで正弦波にフィットできるのかを調べてみました。
前回と同様、\(y=\sin(2x)+n\) (\(n\)がノイズ)からサンプルしたデータにフィッティングをして、\(y=\sin(2x)\)を学習できるか試してみました。\(x\)の値の生成範囲は[-10, 10)です。モデルのパラメータは下表の値の組み合わせで、合計72パターン試しました。
Parameter name | Values |
Activation type | tanh, relu |
# of units for each layer | 1, 2, 4, 8, 16, 32 |
# of hidden layers | 1, 2, 3, 4, 5, 6 |
活性化関数が tanh の場合
まずは、Activationをtanhにした場合です。u1は各レイヤーのユニット数が1で、f2は元の関数が\(\sin(2x)\)であることを表します。図中の各系列名のlayerの数値は隠れ層-1を表します。背景の薄い青色の正弦波が学習データの元のになった正弦波です。これに近いほど、うまく学習できていることになります。
ユニット数が1なので、layerが1だと、
\[y=w_2 \tanh(w_1 x + b_1) + b_2\]
になるため、単に\(\tanh\)の平行移動と拡大縮小ができるだけです。
ユニット数が2つになると、次の図のように、原点の前後の正弦波の山に近づくようにフィットできるようになります。
隠れ層が1(図中ではlayer=0)の場合、\(\tanh\)を2つ、拡大縮小移動して重ね合わせて作ることができます。
隠れ層が2なら、\(\tanh\)を4つ重ね合わせていることになります。隠れ層が1増えるたびに使える\(\tanh\)は2倍になるので、フィットしやすくなります。しかし、この数では原点の前後の正弦波の山にフィットできるのみでした。
ユニット数が4つになると、原点から2つ目までの山と谷にフィットできそうになります(下図)。
ユニット数が8つになると、隠れ層が6層なら、原点の前後3個の山と谷にフィットでき、4つめはもう少しでフィットできそうです(下図)。
ユニット数が16個になると、隠れ層が6層なら、原点の前後5個の山と谷にフィットできます。
ユニット数が32個になると、隠れ層が4層以上なら原点の前後7個の山と谷にフィットできます。これは、学習データの存在範囲の正弦波にフィットできていることを表します。
活性化関数が relu の場合
次に、reluの場合です。ユニット数が1の場合は、\(\tanh\)の場合と同じで、ReLUそのものの形を拡大縮小と平行移動させたグラフになります。
ユニット数が2に増えると、隠れ層の数によっては折れ曲がる箇所が増えます。
ユニット数が4に増えると、山が大きめになります。
ユニット数が8に増えると、隠れ層の数によっては、原点の前後の1つ目の山または谷に近い形になります。
ユニット数が16に増えると、隠れ層の数が6の場合、学習データの\(x\)の範囲である[-10, 10)の範囲では、不完全ながらも正弦波に追従しています。
ユニット数が32の場合、隠れ層の数が5または6なら、正弦波をかなり近似できているように見えます。
ユニット数と隠れ層の数がそこそこ大きくなれば、活性化関数が\(\tanh\)でもReLUでも今回の条件の正弦波からサンプリングしたデータから元の正弦波に近いモデルを学習できることが分かりました。
ソースコード
学習時のソースコードは以下の通りです。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53 | # This script trains a neural network model using MNIST.
import keras, os, math
import numpy as np
from keras.models import Sequential
from keras.layers import Dense, Activation
TRAIN_EPOCH = 100
# Model for fitting
# nunit : # of units for each dense layer
# nlayer: # of hidden layers - 1
# act : activation type
def approx_model(nunit, nlayer, act):
model = Sequential()
model.add(Dense(nunit, input_shape=(1,)))
model.add(Activation(act))
for v in range(nlayer):
model.add(Dense(nunit))
model.add(Activation(act))
model.add(Dense(1))
print(model.summary())
return model
# Make samples on sine curve with noise
def sine_dist(freq):
xd = np.random.rand(10000,)*20-10
yd = [math.sin(x*freq)+np.random.normal(0, 1) for x in xd]
return xd, yd
if __name__ == '__main__':
if not os.path.exists("models"):
os.mkdir("models")
for act in ['tanh', 'relu']:
for nunit in [1, 2, 4, 8, 16, 32]:
for nlayer in [0, 1, 2, 3, 4, 5]:
# Make a model
model = approx_model(nunit, nlayer, act)
opt = keras.optimizers.Adamax(lr=0.002, beta_1=0.9, beta_2=0.999,
epsilon=1e-08, decay=1e-4)
model.compile(optimizer=opt, loss='mean_squared_error')
model.save_weights("initial-weights.hdf5")
for freq in [2]:
# Make data for training and validation
x_train, y_train = sine_dist(freq)
x_val, y_val = sine_dist(freq)
# Train and save a model
model.load_weights("initial-weights.hdf5")
model.fit(x_train, y_train, validation_data=(x_val, y_val),
epochs=TRAIN_EPOCH, batch_size=32)
model.save("models/sine-{0}-u{1}-l{2}-f{3}.hdf5".format(act, nunit, nlayer, freq))
|
また、Jupyter notebookでグラフを表示させるコードは次の通りです。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | %matplotlib inline
import keras, math
import matplotlib.pyplot as plt
import numpy as np
x = np.arange(-20, 20, 0.001)
for act in ['tanh', 'relu']:
for u in [1, 2, 4, 8, 16, 32]:
plt.figure(figsize=(15, 10))
plt.title('{0}-u{1}-f2'.format(act, u))
plt.grid()
yc = np.array([math.sin(xi*2) for xi in x])
plt.plot(x, yc, color='lightblue')
for l in range(6):
model = keras.models.load_model(
'models/sine-{0}-u{1}-l{2}-f2.hdf5'.format(act, u, l))
y = model.predict(x)
plt.plot(x, y, label='layer={0}'.format(l))
plt.legend(loc='upper left', frameon=False)
plt.show()
|