LoginSignup
1
1

More than 5 years have passed since last update.

ディープラーニングを実装から学ぶ(5-2)学習(パラメータ調整2)

Last updated at Posted at 2018-01-21

前回は、パラメータを1つ1つ変更して試してみました。結局、どのパラメータの組み合わせがよいのかよくわかりませんでした。パラメータをランダムに変更して試してみます。データは、MNISTを利用します。

実装変更

パラメータを渡し繰り返し実行できるようにプログラムを変更します。

関数

活性化関数など関数をパラメータで指定可能とします。そのため、関数種別ごとに関数の引数を統一します。

# 正規化関数
def data_norm_func(x, stats, params):
    return nx, stats
# 活性化関数(中間)
def middle_func(u, params):
    return z
def middle_back_func(dz, u, z, params):
    return du
# 活性化関数(出力)
def output_func(u, params):
    return y
# 損失関数
def error_func(y, t):
    return e
# 活性化関数(出力)+損失関数勾配
def output_error_back_func(y, u, t):
    return du
# 重み・バイアスの初期化
def weight_init_func(d_1, d, params):
    return W
def bias_init_func(d, params):
    return b

活性化関数の勾配関数は、活性化関数名+"_back"としています。活性関数がreluの場合、勾配関数は、relu_backです。出力層の活性化関数と損失関数まとめて勾配関数を作成しています。出力層の活性化関数名+勾配関数名+"_back"とします。
関数名の結合にevalを利用しています。

    # 勾配関数
    middle_back_func = eval(middle_func.__name__ + "_back")
    output_error_back_func = eval(output_func.__name__ + "_" + error_func.__name__ + "_back")

以下のように変更しました。

# 正規化関数
def min_max(x, stats, params):
    axis=params.get("axis")
    if "min" not in stats:
        stats["min"] = np.min(x, axis=axis, keepdims=True) # 最小値を求める
    if "max" not in stats:
        stats["max"] = np.max(x, axis=axis, keepdims=True) # 最大値を求める
    return (x-stats["min"])/np.maximum((stats["max"]-stats["min"]),1e-7), stats

def z_score(x, stats, params):
    axis=params.get("axis")
    if "mean" not in stats:
        stats["mean"] = np.mean(x, axis=axis, keepdims=True) # 平均値を求める
    if "std" not in stats:
        stats["std"]  = np.std(x, axis=axis, keepdims=True)  # 標準偏差を求める
    return (x-stats["mean"])/np.maximum(stats["std"],1e-7), stats

# affine変換
def affine(z, W, b):
    return np.dot(z, W) + b

def affine_back(du, z, W, b):
    dz = np.dot(du, W.T)                # zの勾配は、今までの勾配と重みを掛けた値
    dW = np.dot(z.T, du)                # 重みの勾配は、zに今までの勾配を掛けた値
    size = 1
    if z.ndim == 2:
        size = z.shape[0]
    db = np.dot(np.ones(size).T, du)    # バイアスの勾配は、今までの勾配の値
    return dz, dW, db

# 活性化関数(中間)
def sigmoid(u, params):
    return 1/(1 + np.exp(-u))

def sigmoid_back(dz, u, z, params):
    return dz * (z - z**2)

def tanh(u, params):
    return np.tanh(u)

def tanh_back(dz, u, z, params):
    return dz * (1/np.cosh(u)**2)

def relu(u, params):
    return np.maximum(0, u)

def relu_back(dz, u, z, params):
    return dz * np.where(u > 0, 1, 0)

def leaky_relu(u, params):
    return np.maximum(u * params["alpha"], u)

def leaky_relu_back(dz, u, z, params):
    return dz * np.where(u > 0, 1, params["alpha"])

def identity(u, params):
    return u

def identity_back(dz, u, z, params):
    return dz

def softplus(u, params):
    return np.log(1+np.exp(u))

def softplus_back(dz, u, z, params):
    return dz * (1/(1 + np.exp(-u)))

def softsign(u, params):
    return u/(1+np.absolute(u))

def softsign_back(dz, u, z, params):
    return dz * (1/(1+np.absolute(u))**2)

def step(u, params):
    return np.where(u > 0, 1, 0)

def step_back(dz, u, x, params):
    return 0

# 活性化関数(出力)
def softmax(u, params):
    u = u.T
    max_u = np.max(u, axis=0)
    exp_u = np.exp(u - max_u)
    sum_exp_u = np.sum(exp_u, axis=0)
    y = exp_u/sum_exp_u
    return y.T

# 損失関数
def mean_squared_error(y, t):
    size = 1
    if y.ndim == 2:
        size = y.shape[0]
    return 0.5 * np.sum((y-t)**2)/size
def cross_entropy_error(y, t):
    size = 1
    if y.ndim == 2:
        size = y.shape[0]
    return -np.sum(t * np.log(np.maximum(y,1e-7)))/size
    #return -np.sum(t * np.log(y))/size

# 活性化関数(出力)+損失関数勾配
def softmax_cross_entropy_error_back(y, u, t):
    size = 1
    if y.ndim == 2:
        size = y.shape[0]
    return (y - t)/size

def identity_mean_squared_error_back(y, u, t):
    size = 1
    if y.ndim == 2:
        size = y.shape[0]
    return (y - t)/size

# 重み・バイアスの初期化
def lecun_normal(d_1, d, params):
    var = 1/np.sqrt(d_1)
    return np.random.normal(0, var, (d_1, d))
def lecun_uniform(d_1, d, params):
    min = -np.sqrt(3/d_1)
    max = np.sqrt(3/d_1)
    return np.random.uniform(min, max, (d_1, d))

def glorot_normal(d_1, d, params):
    var = np.sqrt(2/(d_1+d))
    return np.random.normal(0, var, (d_1, d))
def glorot_uniform(d_1, d, params):
    min = -np.sqrt(6/(d_1+d))
    max = np.sqrt(6/(d_1+d))
    return np.random.uniform(min, max, (d_1, d))

def he_normal(d_1, d, params):
    var = np.sqrt(2/d_1)
    return np.random.normal(0, var, (d_1, d))
def he_uniform(d_1, d, params):
    min = -np.sqrt(6/d_1)
    max = np.sqrt(6/d_1)
    return np.random.uniform(min, max, (d_1, d))

def normal_w(d_1, d, params):
    mean=0
    var=1
    if "mean" in params:
        mean = params["mean"]
    if "var" in params:
        var = params["var"]
    return np.random.normal(mean, var, (d_1, d))
def normal_b(d, params):
    mean=0
    var=1
    if "mean" in params:
        mean = params["mean"]
    if "var" in params:
        var = params["var"]
    return np.random.normal(mean, var, d)

def uniform_w(d_1, d, params):
    min=0
    max=1
    if "min" in params:
        min = params["min"]
    if "max" in params:
        max = params["max"]
    return np.random.uniform(min, max, (d_1, d))
def uniform_b(d, params):
    min=0
    max=1
    if "min" in params:
        min = params["min"]
    if "max" in params:
        max = params["max"]
    return np.random.uniform(min, max, d)

def zeros_w(d_1, d, params):
    return np.zeros((d_1, d))
def zeros_b(d, params):
    return np.zeros(d)

def ones_w(d_1, d, params):
    return np.ones((d_1, d))
def ones_b(d, params):
    return np.ones(d)

順伝播、逆伝播

各関数は、パラメータにて受け渡しします。

# 順伝播
def propagation(layer, x, W, b, middle_func, middle_params, output_func, output_params):
    u = {}
    z = {}

    # 入力層
    z[0] = x

    # 中間層
    for i in range(1, layer):
        u[i] = affine(z[i-1], W[i], b[i])
        z[i] = middle_func(u[i], middle_params)

    # 出力層
    u[layer] = affine(z[layer-1], W[layer], b[layer])
    y = output_func(u[layer], output_params)

    return u, z, y

# 逆伝播
def back_propagation(layer, u, z, y, t, W, b, middle_back_func, middle_params, output_error_back_func):
    du = {}
    dz = {}
    dW = {}
    db = {}

    # 出力層
    du[layer] = output_error_back_func(y, u[layer], t)
    dz[layer-1], dW[layer], db[layer] = affine_back(du[layer], z[layer-1], W[layer], b[layer])

    # 中間層
    for i in range(layer-1, 0, -1):
        du[i] = middle_back_func(dz[i], u[i], z[i], middle_params)
        dz[i-1], dW[i], db[i] = affine_back(du[i], z[i-1], W[i], b[i])

    return du, dz, dW, db

学習

可変部分についてはパラメータ化しました。
入力層、出力層のノード数は、学習データのサイズから計算するようにしました。中間層のノード数のみ指定します。
後から検証できるように予測結果、重みとバイアスを返却するようにしまいた。また、エポックごとに表示している正解率、誤差も返却しています。

パラメータ一覧

パラメータ 既定値  パラメータ値  
中間層ノード数 [100,50]  -
重み初期化関数 he_normal lecun_normal,lecun_uniform,
glorot_normal,glorot_uniform,
he_normal,he_uniform,
normal_w,uniform_w,
zeros_w,ones_w
重み初期化関数パラメータ - normal_wの場合、"mean"、"var"
uniform_wの場合、"min"、"max"
バイアス初期化関数 zeros_b      normal_b,uniform_b,
zeros_b,ones_b
バイアス初期化関数パラメータ -    normal_bの場合、"mean"、"var"
uniform_bの場合、"min"、"max"
学習率 0.1 -
バッチサイズ 100 -
エポック数 50 -
データ正規化関数 min_max min_max,z_score
データ正規化関数パラメータ - "axis":0、"axis":None
中間層活性化関数 relu sigmoid,tanh,relu,leaky_relu,
identity,softplus,softsign,step
中間層活性化関数パラメータ - leaky_reluの場合、"alpha"
出力層活性化関数 softmax softmax,identity
出力層活性化関数パラメータ - -
損失関数 cross_entropy_error mean_squared_error,cross_entropy_error
# 学習
def learn(
    name,                           # 学習識別名
    x_train,                        # 学習データ
    t_train,                        # 学習正解
    x_test,                         # テストデータ
    t_test,                         # テスト正解
    md=[100, 50],                   # 中間層ノード数
    weight_init_func=he_normal,     # 重み初期化関数
    weight_init_params={},          # 重み初期化関数パラメータ
    bias_init_func=zeros_b,         # バイアス初期化関数
    bias_init_params={},            # バイアス初期化関数パラメータ
    eta=0.1,                        # 学習率
    batch_size=100,                 # バッチサイズ
    epoch=50,                       # エポック数
    data_norm_func=min_max,         # データ正規化関数
    data_norm_params={},            # データ正規化関数パラメータ
    middle_func=relu,               # 中間層活性化関数
    middle_params={},               # 中間層活性化関数パラメータ
    output_func=softmax,            # 出力層活性化関数
    output_params={},               # 出力層活性化関数パラメータ
    error_func=cross_entropy_error  # 損失関数
    ):


    # 学習識別名表示
    print(name)
    # ノード数
    d = [x_train.shape[x_train.ndim-1]] + md + [t_train.shape[t_train.ndim-1]]
    # 階層数
    layer = len(d) - 1

    # 重み、バイアスの初期化
    W = {}
    b = {}
    for i in range(layer):
        W[i+1] = weight_init_func(d[i], d[i+1], weight_init_params)
    for i in range(layer):
        b[i+1] = bias_init_func(d[i+1], bias_init_params)

    # 入力データの正規化
    stats = {}
    nx_train, train_stats = data_norm_func(x_train, stats, data_norm_params)
    nx_test,  test_stats  = data_norm_func(x_test, train_stats, data_norm_params)

    # 正解率、誤差初期化
    train_rate = np.zeros(epoch+1)
    test_rate = np.zeros(epoch+1)
    train_err = np.zeros(epoch+1)
    test_err = np.zeros(epoch+1)

    # 勾配関数
    middle_back_func = eval(middle_func.__name__ + "_back")
    output_error_back_func = eval(output_func.__name__ + "_" + error_func.__name__ + "_back")

    # 実行(学習データ)
    u_train, z_train, y_train = propagation(layer, nx_train, W, b, middle_func, middle_params, output_func, output_params)
    train_rate[0] = accuracy_rate(y_train, t_train)
    train_err[0] = error_func(y_train, t_train)
    # 実行(テストデータ)
    u_test, z_test, y_test = propagation(layer, nx_test, W, b, middle_func, middle_params, output_func, output_params)
    test_rate[0] = accuracy_rate(y_test, t_test)
    test_err[0] = error_func(y_test, t_test)
    # 正解率、誤差表示
    print(" 学習データ正解率 = " + str(train_rate[0]) + " テストデータ正解率 = " + str(test_rate[0]) +
          " 学習データ誤差 = " + str(train_err[0]) + " テストデータ誤差 = " + str(test_err[0]))

    # 開始時刻設定
    start_time = time.time()

    for i in range(epoch):
        # データのシャッフル(正解データも同期してシャフルする必要があるため一度、結合し分離)
        nx_t = np.concatenate([nx_train, t_train], axis=1)
        np.random.shuffle(nx_t)
        nx, t = np.split(nx_t, [nx_train.shape[1]], axis=1)

        for j in range(0, nx.shape[0], batch_size):
            # 実行
            u, z, y = propagation(layer, nx[j:j+batch_size], W, b, middle_func, middle_params, output_func, output_params)
            # 勾配を計算
            du, dz, dW, db = back_propagation(layer, u, z, y, t[j:j+batch_size], W, b, middle_back_func, middle_params, output_error_back_func)

            # 重み、バイアスの調整
            for k in range(1, layer+1):
                W[k] = W[k] - eta*dW[k]
                b[k] = b[k] - eta*db[k]

        # 重み、バイアス調整後の実行(学習データ)
        u_train, z_train, y_train = propagation(layer, nx_train, W, b, middle_func, middle_params, output_func, output_params)
        train_rate[i+1] = accuracy_rate(y_train, t_train)
        train_err[i+1] = error_func(y_train, t_train)
        # 重み、バイアス調整後の実行(テストデータ)
        u_test, z_test, y_test = propagation(layer, nx_test, W, b, middle_func, middle_params, output_func, output_params)
        test_rate[i+1] = accuracy_rate(y_test, t_test)
        test_err[i+1] = error_func(y_test, t_test)
        # 正解率、誤差表示
        print(str(i+1) + " 学習データ正解率 = " + str(train_rate[i+1]) + " テストデータ正解率 = " + str(test_rate[i+1]) +
             " 学習データ誤差 = " + str(train_err[i+1]) + " テストデータ誤差 = " + str(test_err[i+1]))

    # 終了時刻設定
    end_time = time.time()
    total_time = end_time - start_time
    print("所要時間 = " +str(int(total_time/60))+" 分 "+str(int(total_time%60)) + " 秒")

    return y_train, y_test, W, b, train_rate, train_err, test_rate, test_err, total_time

# クラスは利用しない方針で対応してきましたが、そろそろクラスの利用を考える必要があるかも
全体のプログラムは、最後に参考として載せています。

ランダムパラメータ実行

ノード数、学習率、バッチサイズをランダムに指定させました。ノード数は、大きくすると正解率が良くなることは分かっていましたが、ノード数を大きくすると計算量が多くなるため、100ノード以下としました。
活性化関数は、leaky_reluを利用します。$ \alpha $をパラメータで指定します。

パラメータ 既定値  ランダム指定  
中間層ノード数 [100,50]      ノード数参照
1階層目のノード数 100 10~99
2階層目のノード数 50 10~99
重み初期化関数 he_normal
バイアス初期化関数 zeros_b     
学習率 0.1 0.005~0.5
バッチサイズ 100 10~90
エポック数 50
データ正規化関数 min_max
中間層活性化関数 relu leaky_relu
中間層活性化関数パラメータ - "alpha":0.0~0.4
出力層活性化関数 softmax
損失関数 cross_entropy_error

実装

学習率は、対数で乱数を発生しました。

def random_log10(min, max):
    min_log10 = np.log10(min)
    max_log10 = np.log10(max)
    rand = np.random.rand() * (max_log10-min_log10) + min_log10
    return 10**rand

メインは、以下のように実装しました。
取得しない戻り値は、"_"を指定すればよいようです。ここでは、y_train, y_test, W, bは、取得しないため、以下のようにしています。

    _, _, _, _, train_rates[i], train_errs[i], test_rates[i], test_errs[i], total_times[i] = learn(name[i], 
        x_train, t_train, x_test, t_test)

全体です。

# MNISTデータ読み込み
x_train, t_train, x_test, t_test = load_mnist('c:\\mnist\\')

# 結果格納変数定義
train_rates = {}
test_rates = {}
train_errs = {}
test_errs = {}
total_times = {}

# パラメータ格納得変数定義
name = {}
d1 = {}
d2 = {}
eta = {}
batch_size = {}
alpha = {}

# 100回実行
for i in range(100):
    d1[i] = np.random.randint(10,100)
    d2[i] = np.random.randint(10,100)
    eta[i] = random_log10(0.005, 0.5)
    batch_size[i] = np.random.randint(1,10)*10
    alpha[i] = np.random.uniform(0.0, 0.4)
    # 識別名
    name[i]=str(i) + " d=[784," + str(d1[i]) + "," + str(d2[i]) + ",10] eta=" + str(eta[i]) + " batch_size=" + str(batch_size[i]) + " alpha=" + str(alpha[i])
    # 学習
    _, _, _, _, train_rates[i], train_errs[i], test_rates[i], test_errs[i], total_times[i] = learn(name[i], 
        x_train, t_train, x_test, t_test,
        md=[d1[i], d2[i]],          # ノード数
        eta=eta[i],                         # 学習率
        batch_size=batch_size[i],           # バッチサイズ
        middle_func=leaky_relu,             # 中間層活性化関数
        middle_params={"alpha":alpha[i]}    # 中間層活性化関数パラメータ
)

結果

ベスト10を表にしました。
50エポック実行しましたが、今回は、各エポックの中で最高のテスト正解率を示します。また、その時のエポック数を示します。

順位 中間層ノード数 学習率 バッチサイズ leaky_relu 最高テスト正解 エポック数
1 [93,42] 0.302433213955 60 0.12512107 0.9829 28
2 [79,50] 0.45138265927 70 0.00961201 0.9822 35
3 [98,73] 0.336036375354 80 0.22205088 0.9812 23
4 [86,73] 0.0610066365149 20 0.09067058 0.9810 47
5 [98,68] 0.176085698986 80 0.29227329 0.9807 33
6 [98,17] 0.266984069679 80 0.21617011 0.9801 30
7 [94,76] 0.0988126904573 60 0.31976136 0.9799 41
8 [65,61] 0.0396461304998 30 0.00062834 0.9796 25
9 [93,88] 0.013555748156 20 0.06251875 0.9790 44
10 [86,49] 0.138111484307 90 0.05841015 0.9789 27

やはり、1階層目のノード数が多い場合が良い結果となりました。学習率は何とも言えません。

次に、ワースト10です。ただし、100回実行した内の13回は途中でオーバフローが発生し、最後まで学習できませんでした。オーバフローを除いたワーストです。

順位 中間層ノード数 学習率 バッチサイズ leaky_relu 最高テスト正解 エポック数
1 [11,38] 0.00799377305169 80 0.10308717 0.9419 47
2 [10,33] 0.360876335467 80 0.00512821 0.9436 49
3 [10,46] 0.0204809268382 90 0.37675387 0.9470 46
4 [12,13] 0.154481655964 60 0.3670397 0.9528 37
5 [10,94] 0.225053347204 80 0.0332154 0.953 37
6 [15,14] 0.174654717994 30 0.04042519 0.956 18
7 [18,26] 0.427977079413 90 0.05340576 0.9563 45
8 [10,91] 0.106041722932 40 0.35032192 0.9574 36
9 [19,25] 0.0925265723877 10 0.01006869 0.9603 31
10 [13,61] 0.0194410259578 40 0.28872271 0.9609 37

1階層目のノード数がすべて10台です。他の要素に比べてノード数の要因が大きいか、

ちなみに、オーバフローが発生したデータです。

中間層ノード数 学習率 バッチサイズ leaky_relu
[70,47] 0.455120565919 40 0.390406
[38,60] 0.364510678024 20 0.3752583
[11,15] 0.410981255062 10 0.22906989
[30,16] 0.47312762974 10 0.16016474
[49,72] 0.153288885731 10 0.33473031
[85,30] 0.336504492399 10 0.0048215
[24,55] 0.364312295363 10 0.06023217
[81,71] 0.463112906631 40 0.23853512
[49,73] 0.481576312862 20 0.12823488
[40,42] 0.404546974262 70 0.32874793
[60,27] 0.413759964447 30 0.2589165
[47,15] 0.499725707964 70 0.32093298
[54,98] 0.326704994757 40 0.26140594

共通していることは、学習率が大きいことです。ただし、2位の学習率は0.45でした。何をきっかけにオーバーフローが発生しているのわかりません。

ランダムパラメータ実行2

前の実行では、ノード数の影響が大きかったため、ノード数は固定して実行します。
その代わりデータの正規化関数をmin_max,z_scoreで変更してみます。

パラメータ 既定値  ランダム指定  
中間層ノード数 [100,50]  
重み初期化関数 he_normal
バイアス初期化関数 zeros_b     
学習率 0.1 0.005~0.5
バッチサイズ 100 20~100
エポック数 50
データ正規化関数 min_max min_max,z_score
データ正規化関数パラメータ - "axis":None,0
中間層活性化関数 relu leaky_relu
中間層活性化関数パラメータ - "alpha":0.0~0.3
出力層活性化関数 softmax
損失関数 cross_entropy_error

実装

以下のように実装しました。

# MNISTデータ読み込み
x_train, t_train, x_test, t_test = load_mnist('c:\\mnist\\')

# 結果格納変数定義
train_rates = {}
test_rates = {}
train_errs = {}
test_errs = {}
total_times = {}

# パラメータ格納得変数定義
name = {}
d1 = {}
d2 = {}
eta = {}
batch_size = {}
alpha = {}

# 100回実行
for i in range(100):
    data_norm = np.random.randint(1,5)
    if data_norm == 1:
        data_norm_func = min_max
        data_norm_params = {"axis":None}
    elif data_norm == 2:
        data_norm_func = min_max
        data_norm_params = {"axis":0}
    elif data_norm == 3:
        data_norm_func = z_score
        data_norm_params = {"axis":None}
    elif data_norm == 4:
        data_norm_func = z_score
        data_norm_params = {"axis":0}
    eta[i] = random_log10(0.005, 0.5)
    batch_size[i] = np.random.randint(2,11)*10
    alpha[i] = np.random.uniform(0.0, 0.3)
    # 識別名
    name[i]=str(i) + " data_norm=" + str(data_norm) + " eta=" + str(eta[i]) + " batch_size=" + str(batch_size[i]) + " alpha=" + str(alpha[i])
    # 学習
    _, _, _, _, train_rates[i], train_errs[i], test_rates[i], test_errs[i], total_times[i] = learn(name[i], 
        x_train, t_train, x_test, t_test,
        data_norm_func=data_norm_func,      # データ正規化関数
        data_norm_params=data_norm_params,  # データ正規化関数パラメータ
        eta=eta[i],                         # 学習率
        batch_size=batch_size[i],           # バッチサイズ
        middle_func=leaky_relu,             # 中間層活性化関数
        middle_params={"alpha":alpha[i]}    # 中間層活性化関数パラメータ
)

結果

テスト正解率が98%を超えたデータです。

順位 正規化関数 学習率 バッチサイズ leaky_relu 最高テスト正解 エポック数
1 min_max(axis=None) 0.0927278701741 20 0.09471167614945931 0.9838 37
2 min_max(axis=None) 0.214239714583 30 0.09976643519311072 0.9835 49
3 min_max(axis=None) 0.399621312108 70 0.16896605478656143 0.9826 47
4 min_max(axis=0) 0.378970024408 80 0.1391841049698662 0.9821 31
5 min_max(axis=0) 0.217384147336 30 0.0604149834408775 0.9820 47
6 z_score(axis=None) 0.0414183778921 30 0.03558674916729219 0.9816 47
7 min_max(axis=None) 0.268258601853 50 0.1438740580208833 0.9814 37
8 z_score(axis=None) 0.265386314508 100 0.14360062241900276 0.9810 25
9 z_score(axis=None) 0.0987836039477 40 0.03587081823860711 0.9806 49
10 min_max(axis=None) 0.186050959515 80 0.12623575288912603 0.9805 26
11 min_max(axis=0) 0.111506240912 60 0.05412096930493985 0.9804 39
11 z_score(axis=None) 0.202900552044 70 0.021710006625834655 0.9804 33
13 z_score(axis=None) 0.042169178054 20 0.04035340299593023 0.9803 28
14 z_score(axis=None) 0.183082207233 70 0.06608108085632532 0.9801 19
15 min_max(axis=None) 0.040402223936 30 0.27864566494599446 0.9801 49
16 min_max(axis=None) 0.0784329078478 40 0.23942583642707663 0.9800 32
16 min_max(axis=None) 0.0768888266807 50 0.0900024215022027 0.9800 49

学習率は、ある程度大きい方が良さそうです。今回も、学習率が大きいと、100回中14回オーバフローが発生しました。
学習率の大きい順に15個並べてみます。順位が×は、オーバフローが発生した回です。

順位 正規化関数 学習率 バッチサイズ leaky_relu 最高テスト正解 エポック数
× z_score(axis=0) 0.493526468126 50 0.16302193208387158 - -
× z_score(axis=None) 0.493025438687 60 0.15257860986164548 - -
× z_score(axis=0) 0.457465751436 100 0.1833198975977736 - -
× min_max(axis=0) 0.438151737948 20 0.2226416317106006 - -
× z_score(axis=0) 0.427182859114 50 0.1775239627560666 - -
3 min_max(axis=None) 0.399621312108 70 0.16896605478656143 0.9826 47
4 min_max(axis=0) 0.378970024408 80 0.1391841049698662 0.9821 31
× z_score(axis=None) 0.327766842604 20 0.12409631412746923 - -
× z_score(axis=0) 0.327609746863 30 0.25360283198659694 - -
× min_max(axis=0) 0.311587718674 40 0.2771468518188593 - -
× z_score(axis=0) 0.302442036526 20 0.10774988623712077 - -
× z_score(axis=0) 0.292413067448 70 0.07117326944102596 - -
7 min_max(axis=None) 0.268258601853 50 0.1438740580208833 0.9814 37
8 z_score(axis=None) 0.265386314508 100 0.14360062241900276 0.9810 25

オーバフローが発生する場合と発生しない場合の傾向がつかめません。

テスト正解率のワースト5です。

順位 正規化関数 学習率 バッチサイズ leaky_relu 最高テスト正解 エポック数
1 min_max(axis=0) 0.00553520035274 90 0.2681946146915237 0.9600 50
2 min_max(axis=0) 0.0050595537481 90 0.12487558139465842 0.9604 50
3 min_max(axis=0) 0.00696012106581 90 0.2483865324925018 0.9626 49
4 min_max(axis=0) 0.00666249586536 100 0.0020656761874577323 0.9649 49
5 min_max(axis=0) 0.00700526924433 90 0.2400293442668525 0.9654 49

これから言えることは、学習率が小さい&バッチサイズが大きいです。学習率が小さいとなかなか学習が進まない、ただし、バッチサイズを小さくすると学習率が小さくてっもある程度学習が進みます。

ランダムパラメータ実行3

学習率変更

学習率を0.1~1.0にランダムに変更して実行してみました。活性化関数は、reluです。時間がかかるためエポック数は5にしました。
5エポック目のテスト正解率をグラフ化します。

eta_random.png

学習率が0.2~0,4あたりが良い結果となりました。

leaky_reluのalpha変更

学習率を0.1として、leaky_reluの$ \alpha $を0.0~0.3の間でランダムに変更してみます。エポック数は5です。

alpha_random.png

学習率が0.1の時は、$ \alpha $が0の方がよさそうです。

学習率を0.5に変更して実行してみました。

alpha05_random.png

学習率を大きくしたため序盤の正解率は向上しました。
なお、ここまでの実行ではオーバフローは発生しませんでした。

オーバフロー

オーバフローを強制的に発生させるため、学習率を1.0、leaky_reluの$ \alpha $を0.1として実行しました。1エポック目のバッチ実行ごとに、u[3]、W[3]、dW[3]、dz[2]の最大値を表にします。

No. u[3] W[3] dW[3] dz[2]
1 1.02414694045 0.71142370528 0.103146812254 0.0556380289438
2 3.50086120607 0.717012777871 0.304204124963 0.0616197182729
3 42.0303510583 1.01637660155 9.19282549189 0.122515322953
4 187.486171612 2.5055810304 18.8419152026 1.06820299205
5 20979.4768888 6.79860594896 315.422922008 0.330265278138
6 1313582.06845 326.681196395 36853.335196 4.23711824052
7 807654175770.0 13940.1241538 19612923.7657 1680.74798761
8 2.09712899853e+22 7001210.14566 3.20687900685e+15 700478.542985
9 3.05664299541e+45 1.15197613201e+15 1.54566291385e+30 1.15197613094e+14
10 3.56936712336e+90 6.28215508137e+29 3.32250265958e+60 2.17387842199e+29
11 6.50199793932e+180 8.60638368111e+59 3.7355633174e+120 8.60638368111e+58

爆発的に大きな値になっています。
参考までに、学習率を1.0、reluの場合です。大きな値にはなりません。最終的にも大きな値とはなりません。

No. u[3] W[3] dW[3] dz[2]
1 1.13917149054 0.71142370528 0.115069014906 0.0562912992378
2 2.85198881448 0.710679074987 0.411644293618 0.0567578104152
3 43.2878534582 1.04498258635 10.9761589965 0.133511676772
4 2.97599151397 3.045657225 0.3944940876 1.1832955403
5 3.55269416732 3.045657225 0.697594957774 1.06049038113
6 3.46893146088 3.045657225 0.54347919221 0.613186155551
7 1.87653864759 3.045657225 0.188400250272 0.181941475396
8 6.61003329221 3.045657225 2.21060981208 0.100531181658
9 6.28065023901 3.045657225 0.472896096975 0.13147556295
10 1.8999663514 3.045657225 0.0873401782609 0.0868772615172

負の値に対するleaky_reluの勾配($ \alpha $=0.1)がここまで影響したのでしょうか、

今回は、ほとんど内容がありませんでした。次回は、過学習の対応を考えます。

参考

今回実行したプログラムの全体です。

import gzip
import numpy as np
# MNIST読み込み
def load_mnist( mnist_path ) :
    return _load_image(mnist_path + 'train-images-idx3-ubyte.gz'), \
           _load_label(mnist_path + 'train-labels-idx1-ubyte.gz'), \
           _load_image(mnist_path + 't10k-images-idx3-ubyte.gz'), \
           _load_label(mnist_path + 't10k-labels-idx1-ubyte.gz')
def _load_image( image_path ) :
    # 画像データの読み込み
    with gzip.open(image_path, 'rb') as f:
        buffer = f.read()
    size = np.frombuffer(buffer, np.dtype('>i4'), 1, offset=4)
    rows = np.frombuffer(buffer, np.dtype('>i4'), 1, offset=8)
    columns = np.frombuffer(buffer, np.dtype('>i4'), 1, offset=12)
    data = np.frombuffer(buffer, np.uint8, offset=16)
    image = np.reshape(data, (size[0], rows[0]*columns[0]))
    image = image.astype(np.float32)
    return image
def _load_label( label_path ) :
    # 正解データ読み込み
    with gzip.open(label_path, 'rb') as f:
        buffer = f.read()
    size = np.frombuffer(buffer, np.dtype('>i4'), 1, offset=4)
    data = np.frombuffer(buffer, np.uint8, offset=8)
    label = np.zeros((size[0], 10))
    for i in range(size[0]):
        label[i, data[i]] = 1
    return label

# 正規化関数
def min_max(x, stats, params):
    axis=params.get("axis")
    if "min" not in stats:
        stats["min"] = np.min(x, axis=axis, keepdims=True) # 最小値を求める
    if "max" not in stats:
        stats["max"] = np.max(x, axis=axis, keepdims=True) # 最大値を求める
    return (x-stats["min"])/np.maximum((stats["max"]-stats["min"]),1e-7), stats

def z_score(x, stats, params):
    axis=params.get("axis")
    if "mean" not in stats:
        stats["mean"] = np.mean(x, axis=axis, keepdims=True) # 平均値を求める
    if "std" not in stats:
        stats["std"]  = np.std(x, axis=axis, keepdims=True)  # 標準偏差を求める
    return (x-stats["mean"])/np.maximum(stats["std"],1e-7), stats

# affine変換
def affine(z, W, b):
    return np.dot(z, W) + b

def affine_back(du, z, W, b):
    dz = np.dot(du, W.T)                # zの勾配は、今までの勾配と重みを掛けた値
    dW = np.dot(z.T, du)                # 重みの勾配は、zに今までの勾配を掛けた値
    size = 1
    if z.ndim == 2:
        size = z.shape[0]
    db = np.dot(np.ones(size).T, du)    # バイアスの勾配は、今までの勾配の値
    return dz, dW, db

# 活性化関数(中間)
def sigmoid(u, params):
    return 1/(1 + np.exp(-u))

def sigmoid_back(dz, u, z, params):
    return dz * (z - z**2)

def tanh(u, params):
    return np.tanh(u)

def tanh_back(dz, u, z, params):
    return dz * (1/np.cosh(u)**2)

def relu(u, params):
    return np.maximum(0, u)

def relu_back(dz, u, z, params):
    return dz * np.where(u > 0, 1, 0)

def leaky_relu(u, params):
    return np.maximum(u * params["alpha"], u)

def leaky_relu_back(dz, u, z, params):
    return dz * np.where(u > 0, 1, params["alpha"])

def identity(u, params):
    return u

def identity_back(dz, u, z, params):
    return dz

def softplus(u, params):
    return np.log(1+np.exp(u))

def softplus_back(dz, u, z, params):
    return dz * (1/(1 + np.exp(-u)))

def softsign(u, params):
    return u/(1+np.absolute(u))

def softsign_back(dz, u, z, params):
    return dz * (1/(1+np.absolute(u))**2)

def step(u, params):
    return np.where(u > 0, 1, 0)

def step_back(dz, u, x, params):
    return 0

# 活性化関数(出力)
def softmax(u, params):
    u = u.T
    max_u = np.max(u, axis=0)
    exp_u = np.exp(u - max_u)
    sum_exp_u = np.sum(exp_u, axis=0)
    y = exp_u/sum_exp_u
    return y.T

# 損失関数
def mean_squared_error(y, t):
    size = 1
    if y.ndim == 2:
        size = y.shape[0]
    return 0.5 * np.sum((y-t)**2)/size
def cross_entropy_error(y, t):
    size = 1
    if y.ndim == 2:
        size = y.shape[0]
    return -np.sum(t * np.log(np.maximum(y,1e-7)))/size
    #return -np.sum(t * np.log(y))/size

# 活性化関数(出力)+損失関数勾配
def softmax_cross_entropy_error_back(y, u, t):
    size = 1
    if y.ndim == 2:
        size = y.shape[0]
    return (y - t)/size

def identity_mean_squared_error_back(y, u, t):
    size = 1
    if y.ndim == 2:
        size = y.shape[0]
    return (y - t)/size

# 重み・バイアスの初期化
def lecun_normal(d_1, d, params):
    var = 1/np.sqrt(d_1)
    return np.random.normal(0, var, (d_1, d))
def lecun_uniform(d_1, d, params):
    min = -np.sqrt(3/d_1)
    max = np.sqrt(3/d_1)
    return np.random.uniform(min, max, (d_1, d))

def glorot_normal(d_1, d, params):
    var = np.sqrt(2/(d_1+d))
    return np.random.normal(0, var, (d_1, d))
def glorot_uniform(d_1, d, params):
    min = -np.sqrt(6/(d_1+d))
    max = np.sqrt(6/(d_1+d))
    return np.random.uniform(min, max, (d_1, d))

def he_normal(d_1, d, params):
    var = np.sqrt(2/d_1)
    return np.random.normal(0, var, (d_1, d))
def he_uniform(d_1, d, params):
    min = -np.sqrt(6/d_1)
    max = np.sqrt(6/d_1)
    return np.random.uniform(min, max, (d_1, d))

def normal_w(d_1, d, params):
    mean=0
    var=1
    if "mean" in params:
        mean = params["mean"]
    if "var" in params:
        var = params["var"]
    return np.random.normal(mean, var, (d_1, d))
def normal_b(d, params):
    mean=0
    var=1
    if "mean" in params:
        mean = params["mean"]
    if "var" in params:
        var = params["var"]
    return np.random.normal(mean, var, d)

def uniform_w(d_1, d, params):
    min=0
    max=1
    if "min" in params:
        min = params["min"]
    if "max" in params:
        max = params["max"]
    return np.random.uniform(min, max, (d_1, d))
def uniform_b(d, params):
    min=0
    max=1
    if "min" in params:
        min = params["min"]
    if "max" in params:
        max = params["max"]
    return np.random.uniform(min, max, d)

def zeros_w(d_1, d, params):
    return np.zeros((d_1, d))
def zeros_b(d, params):
    return np.zeros(d)

def ones_w(d_1, d, params):
    return np.ones((d_1, d))
def ones_b(d, params):
    return np.ones(d)

# 正解率
def accuracy_rate(y, t):
    max_y = np.argmax(y, axis=1)
    max_t = np.argmax(t, axis=1)
    return np.sum(max_y == max_t)/y.shape[0]
import numpy as np
import time

# 順伝播
def propagation(layer, x, W, b, middle_func, middle_params, output_func, output_params):
    u = {}
    z = {}

    # 入力層
    z[0] = x

    # 中間層
    for i in range(1, layer):
        u[i] = affine(z[i-1], W[i], b[i])
        z[i] = middle_func(u[i], middle_params)

    # 出力層
    u[layer] = affine(z[layer-1], W[layer], b[layer])
    y = output_func(u[layer], output_params)

    return u, z, y

# 逆伝播
def back_propagation(layer, u, z, y, t, W, b, middle_back_func, middle_params, output_error_back_func):
    du = {}
    dz = {}
    dW = {}
    db = {}

    # 出力層
    du[layer] = output_error_back_func(y, u[layer], t)
    dz[layer-1], dW[layer], db[layer] = affine_back(du[layer], z[layer-1], W[layer], b[layer])

    # 中間層
    for i in range(layer-1, 0, -1):
        du[i] = middle_back_func(dz[i], u[i], z[i], middle_params)
        dz[i-1], dW[i], db[i] = affine_back(du[i], z[i-1], W[i], b[i])

    return du, dz, dW, db

# 学習
def learn(
    name,                           # 学習識別名
    x_train,                        # 学習データ
    t_train,                        # 学習正解
    x_test,                         # テストデータ
    t_test,                         # テスト正解
    md=[100, 50],                   # 中間層ノード数
    weight_init_func=he_normal,     # 重み初期化関数
    weight_init_params={},          # 重み初期化関数パラメータ
    bias_init_func=zeros_b,         # バイアス初期化関数
    bias_init_params={},            # バイアス初期化関数パラメータ
    eta=0.1,                        # 学習率
    batch_size=100,                 # バッチサイズ
    epoch=50,                       # エポック数
    data_norm_func=min_max,         # データ正規化関数
    data_norm_params={},            # データ正規化関数パラメータ
    middle_func=relu,               # 中間層活性化関数
    middle_params={},               # 中間層活性化関数パラメータ
    output_func=softmax,            # 出力層活性化関数
    output_params={},               # 出力層活性化関数パラメータ
    error_func=cross_entropy_error  # 損失関数
    ):


    # 学習識別名表示
    print(name)
    # ノード数
    d = [x_train.shape[x_train.ndim-1]] + md + [t_train.shape[t_train.ndim-1]]
    # 階層数
    layer = len(d) - 1

    # 重み、バイアスの初期化
    W = {}
    b = {}
    for i in range(layer):
        W[i+1] = weight_init_func(d[i], d[i+1], weight_init_params)
    for i in range(layer):
        b[i+1] = bias_init_func(d[i+1], bias_init_params)

    # 入力データの正規化
    stats = {}
    nx_train, train_stats = data_norm_func(x_train, stats, data_norm_params)
    nx_test,  test_stats  = data_norm_func(x_test, train_stats, data_norm_params)

    # 正解率、誤差初期化
    train_rate = np.zeros(epoch+1)
    test_rate = np.zeros(epoch+1)
    train_err = np.zeros(epoch+1)
    test_err = np.zeros(epoch+1)

    # 勾配関数
    middle_back_func = eval(middle_func.__name__ + "_back")
    output_error_back_func = eval(output_func.__name__ + "_" + error_func.__name__ + "_back")

    # 実行(学習データ)
    u_train, z_train, y_train = propagation(layer, nx_train, W, b, middle_func, middle_params, output_func, output_params)
    train_rate[0] = accuracy_rate(y_train, t_train)
    train_err[0] = error_func(y_train, t_train)
    # 実行(テストデータ)
    u_test, z_test, y_test = propagation(layer, nx_test, W, b, middle_func, middle_params, output_func, output_params)
    test_rate[0] = accuracy_rate(y_test, t_test)
    test_err[0] = error_func(y_test, t_test)
    # 正解率、誤差表示
    print(" 学習データ正解率 = " + str(train_rate[0]) + " テストデータ正解率 = " + str(test_rate[0]) +
          " 学習データ誤差 = " + str(train_err[0]) + " テストデータ誤差 = " + str(test_err[0]))

    # 開始時刻設定
    start_time = time.time()

    for i in range(epoch):
        # データのシャッフル(正解データも同期してシャフルする必要があるため一度、結合し分離)
        nx_t = np.concatenate([nx_train, t_train], axis=1)
        np.random.shuffle(nx_t)
        nx, t = np.split(nx_t, [nx_train.shape[1]], axis=1)

        for j in range(0, nx.shape[0], batch_size):
            # 実行
            u, z, y = propagation(layer, nx[j:j+batch_size], W, b, middle_func, middle_params, output_func, output_params)
            # 勾配を計算
            du, dz, dW, db = back_propagation(layer, u, z, y, t[j:j+batch_size], W, b, middle_back_func, middle_params, output_error_back_func)

            # 重み、バイアスの調整
            for k in range(1, layer+1):
                W[k] = W[k] - eta*dW[k]
                b[k] = b[k] - eta*db[k]

        # 重み、バイアス調整後の実行(学習データ)
        u_train, z_train, y_train = propagation(layer, nx_train, W, b, middle_func, middle_params, output_func, output_params)
        train_rate[i+1] = accuracy_rate(y_train, t_train)
        train_err[i+1] = error_func(y_train, t_train)
        # 重み、バイアス調整後の実行(テストデータ)
        u_test, z_test, y_test = propagation(layer, nx_test, W, b, middle_func, middle_params, output_func, output_params)
        test_rate[i+1] = accuracy_rate(y_test, t_test)
        test_err[i+1] = error_func(y_test, t_test)
        # 正解率、誤差表示
        print(str(i+1) + " 学習データ正解率 = " + str(train_rate[i+1]) + " テストデータ正解率 = " + str(test_rate[i+1]) +
             " 学習データ誤差 = " + str(train_err[i+1]) + " テストデータ誤差 = " + str(test_err[i+1]))

    # 終了時刻設定
    end_time = time.time()
    total_time = end_time - start_time
    print("所要時間 = " +str(int(total_time/60))+" 分 "+str(int(total_time%60)) + " 秒")

    return y_train, y_test, W, b, train_rate, train_err, test_rate, test_err, total_time
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1