機械学習
DeepLearning
ディープラーニング
ニューラルネットワーク
深層学習

ニューラルネットワークの各手法を試してきました。今後、CNN、RNNに対応するため実装の見直しを行います。

変更の方針

  • 活性化関数、ドロップアウト、バッチ正規化など各層一律の適用でしたが、任意の階層に任意の活性化関数を適用できるようにします。ドロップアウト、バッチ正規化み1つの層とみなします。
  • 指定回数エポック実行後に、追加で実行できるようにします。途中で状態を保存し、後日、続きから実行することも可能にします。
  • 学習率、オプティマイザーを途中から変更できるようにします。

定義

モデル

定義

任意の階層を定義できるようにします。今までの中間層のノード数が100,50の場合は、以下のように定義します。

model = create_model(28*28)
model = add_layer(model, "affine1", affine, 100)
model = add_layer(model, "relu1", relu)
model = add_layer(model, "affine2", affine, 50)
model = add_layer(model, "relu2", relu)
model = add_layer(model, "affine3", affine, 10)
model = set_output(model, softmax)
model = set_error(model, cross_entropy_error)

create_modelでモデルを作成し、add_layerでレイヤーを追加していきます。出力関数と誤差関数は、set_optput,set_errorで設定します。

# モデル作成関数
model = create_model(input_shape)
# 引数
#  input_shape : 入力データの次元
# 戻り値
#  モデル

# 層追加関数
model = add_layer(model, name, func, d=None, **kwargs)
# 引数
#  model : モデル
#  name  : レイヤの名前
#  func  : 中間層の関数
#  d     : ノード数
#  kwargs: 中間層の関数のパラメータ
# 戻り値
#  モデル

# 出力関数
mode = set_output(model, func, **kwargs)
# 引数
#  model : モデル
#  func  : 出力層の関数
#  kwargs: 出力層の関数のパラメータ
# 戻り値
#  モデル

# 損失関数
mode = set_error(model, func, **kwargs)
# 引数
#  model : モデル
#  func  : 損失関数
#  kwargs: 損失関数のパラメータ
# 戻り値
#  モデル

モデルの定義部分の実装です。

from collections import OrderedDict
import pickle

def create_model(d):
    # modelの生成
    model = {}
    model["input"] = {"d":d}
    model["layer"] = OrderedDict()
    model["weight_decay"] = None
    return model

def add_layer(model, name, func, d=None, **kwargs):
    # layerの設定
    model["layer"][name] = {"func":func, "back_func":eval(func.__name__ + "_back"), "d":d, "params":kwargs, "weights":None}
    return model

def set_output(model, func, **kwargs):
    # layerの設定
    model["output"] = {"func":func, "d":1, "params":kwargs}
    return model

def set_error(model, func, **kwargs):
    # layerの設定
    model["error"] = {"func":func, "d":1, "params":kwargs}
    return model

重み減衰は、レイヤではありませんが、modelに設定します。

# 重み減衰関数
mode = set_weight_decay(model, func, **kwargs):
# 引数
#  model : モデル
#  func  : 重み減衰関数
#  kwargs: 重み減衰関数のパラメータ
# 戻り値
#  モデル
def set_weight_decay(model, func, **kwargs):
    model["weight_decay"] = {"func":func, "back_func":eval(func.__name__ + "_back"), "params":kwargs}
    return model

初期化

init_modelで、affineの重み、バイアスなど、初期化が必要な変数の初期化を行います。重み、バイアスは、modelの中に格納します。

def init_model(model):
    # modelの初期化
    # input
    d_prev = 0
    d_next = model["input"]["d"]
    print("input", "-", d_prev, d_next)
    # layer
    d_prev = d_next
    for k, v in model["layer"].items():
        # 要素数取得
        d = v["d"] if v["d"] != None else d_prev
        # 重み、バイアスの初期化
        d_next = d
        if v["func"].__name__ + "_init_layer" in globals():
            init_layer_func = eval(v["func"].__name__ + "_init_layer")
            d_next, v["weights"] = init_layer_func(d_prev, d, **v["params"])
        # 要素数設定
        v["d_prev"], v["d_next"] = d_prev, d_next
        # モデル表示
        print(k, v["func"].__name__, d_prev, d_next)
        d_prev = d_next
    # output
    model["output"]["d_prev"] = d_prev
    print("output", model["output"]["func"].__name__, d_prev)
    # error
    print("error", model["error"]["func"].__name__)

    return model

保存、読み込み

save_model,load_modelでモデルを保存、読み込みができます。

def save_model(model, file_path):
    f = open(file_path, "wb")
    pickle.dump(model, f)
    f.close
    return

def load_model(file_path):
    f = open(file_path, "rb")
    model = pickle.load(f)
    f.close
    return model

オプティマイザ

定義

オプティマイザを定義します。
Adamの設定例です。

optimizer = create_optimizer(Adam, beta1=0.9, beta2=0.999, lr=0.1)
# オプティマイザ作成関数
optimizer = create_optimizer(func, lr=0.1, **kwargs)
# 引数
#  func  : オプティマイザ関数
#  lr    : 学習率
#  kwargs: オプティマイザ関数のパラメータ
# 戻り値
#  オプティマイザ

オプティマイザ定義部分の実装です。
初期化関数でオプティマイザで利用する変数を初期化します。modelと同様に、保存、読み込み関数を定義します。

def create_optimizer(func, lr=0.1, **kwargs):
    optimizer = {"func":func, "lr":lr, "params":kwargs}
    return optimizer

def init_optimizer(optimizer, model):
    optimizer_stats = {}
    for k, v in model["layer"].items():
        # オプティマイザの初期化
        if v["func"].__name__ + "_init_optimizer" in globals():
            init_optimizer_func = eval(v["func"].__name__ + "_init_optimizer")
            optimizer_stats[k] = init_optimizer_func()
    optimizer["stats"] = optimizer_stats

    return optimizer

def save_optimizer(optimizer, file_path):
    f = open(file_path, "wb")
    pickle.dump(optimizer, f)
    f.close
    return

def load_optimizer(file_path):
    f = open(file_path, "rb")
    optimizer = pickle.load(f)
    f.close
    return optimizer

順伝播、逆伝播、重みの更新

順伝播

モデルの各レイヤを実行します。その後に、出力関数と誤差関数を実行します。

活性化関数には、前層の出力データを渡します。ただし、affineなど重さを利用する場合は、propergation関数を定義し、重さを受け渡しできるようにします。
propergation関数が定義されている場合は、定義されたpropergation関数を呼び出します。

def propagation(model, x, t=None, learn_flag=True):
    us = {}
    u = x
    err = None
    weight_decay_sum = 0
    # layer
    for k, v in model["layer"].items():
        # propagation関数設定
        propagation_func = middle_propagation
        if v["func"].__name__ + "_propagation" in globals():
            propagation_func = eval(v["func"].__name__ + "_propagation")
        # propagation関数実行
        us[k], weight_decay_r = propagation_func(v["func"], u, v["weights"], model["weight_decay"], learn_flag, **v["params"])
        u = us[k]["u_next"]  
        weight_decay_sum = weight_decay_sum + weight_decay_r
    # output
    if "output" in model:
        propagation_func = output_propagation
        # propagation関数実行
        us["output"] = propagation_func(model["output"]["func"], u, learn_flag, **model["output"]["params"])
        u = us["output"]["u_next"]
    # error
    y = u
    if "error" in model:
        if t is not None:
            err = model["error"]["func"](y, t)
    # 重み減衰
    if "weight_decay" is not None:
        if learn_flag:
            err = err + weight_decay_sum

    return y, err, us

逆伝播

逆伝播も順伝播と同様です。
重さを使う場合は、back_propagation関数を定義します。back_propagation関数が定義されている場合は、定義されたback_propagation関数を呼び出します。

def back_propagation(model, x=None, t=None, y=None, us=None, du=None):
    dus = {}
    if du is None:
        # 出力層+誤差勾配関数
        output_error_back_func = eval(model["output"]["func"].__name__ + "_" + model["error"]["func"].__name__ + "_back")
        du = output_error_back_func(y, us["output"]["u"], t)
        dus["output"] = {"du":du}
    du_next = du
    for k, v in reversed(model["layer"].items()):
        # back propagation関数設定
        back_propagation_func = middle_back_propagation
        if v["func"].__name__ + "_back_propagation" in globals():
            back_propagation_func = eval(v["func"].__name__ + "_back_propagation")
        # back propagation関数実行
        dus[k] = back_propagation_func(v["back_func"], du_next, us[k], v["weights"], model["weight_decay"], **v["params"])
        du_next = dus[k]["du"]

    return du_next, dus

重みの更新

affineなど重みを更新するレイヤでは、update_weight関数を定義し、重みの更新を行います。

def update_weight(model, dus, optimizer):
    for k, v in model["layer"].items():
        # 重み更新
        if v["func"].__name__ + "_update_weight" in globals():
            update_weight_func = eval(v["func"].__name__ + "_update_weight")
            v["weights"], optimizer["stats"][k] = update_weight_func(optimizer["func"], dus[k], v["weights"], optimizer["lr"], optimizer["stats"][k], **optimizer["params"])
    return model, optimizer

誤差

誤差関数を呼び出します。

def error(model, y, t):
    return model["error"]["func"](y, t)

個別実装

それぞれの関数ごとに個別の実装を追加します。
個別に定義する関数名は、以下とします。

  • モデル初期化関数 - 関数名 + "_init_layer"
  • オプティマイザ初期化関数 - 関数名 + "_init_optimizer"
  • 順伝播 - 関数名 + "_propagation"
  • 逆伝播 - 関数名 + "_back_propagation"
  • 重みの更新 - 関数名 + "_update_weight"

必要な関数のみ定義します。

共通

特別の処理が不要な活性化関数は、以下の関数で共通的に対応します。

def middle_propagation(func, u, weights, weight_decay, learn_flag, **params):
    u_next = func(u, **params)
    return {"u":u, "u_next":u_next}, 0

def middle_back_propagation(back_func, du_next, us, weights, weight_decay, **params):
    du = back_func(du_next, us["u"], us["u_next"], **params)
    return {"du":du}

def output_propagation(func, u, learn_flag, **params):
    u_next = func(u, **params)
    return {"u":u, "u_next":u_next}

affine

重みW、バイアスbを利用しますので、各関数を定義します。

def affine_init_layer(d_prev, d, weight_init_func=he_normal, weight_init_params={}, bias_init_func=zeros_b, bias_init_params={}):
    W = weight_init_func(d_prev, d, **weight_init_params)
    b = bias_init_func(d, **bias_init_params)
    return d, {"W":W, "b":b}

def affine_init_optimizer():
    sW = {}
    sb = {}
    return {"sW":sW, "sb":sb}

def affine_propagation(func, u, weights, weight_decay, learn_flag, **params):
    u_next = func(u, weights["W"], weights["b"])
    # 重み減衰対応
    weight_decay_r = 0
    if weight_decay is not None:
        weight_decay_r = weight_decay["func"](weights["W"], **weight_decay["params"])
    return {"u":u, "u_next":u_next}, weight_decay_r

def affine_back_propagation(back_func, du_next, us, weights, weight_decay, **params):
    du, dW, db = back_func(du_next, us["u"], weights["W"], weights["b"])
    # 重み減衰対応
    if weight_decay is not None:
        dW = dW + weight_decay["back_func"](weights["W"], **weight_decay["params"])
    return {"du":du, "dW":dW, "db":db}

def affine_update_weight(func, du, weights, lr, optimizer_stats, **params):
    weights["W"], optimizer_stats["sW"] = func(weights["W"], du["dW"], lr, **params, **optimizer_stats["sW"])
    weights["b"], optimizer_stats["sb"] = func(weights["b"], du["db"], lr, **params, **optimizer_stats["sb"])
    return weights, optimizer_stats

maxout

重みW、バイアスbを利用しますので、各関数を定義します。

def maxout_init_layer(d_prev, d, unit=1, weight_init_func=he_normal, weight_init_params={}, bias_init_func=zeros_b, bias_init_params={}):
    W = weight_init_func(d_prev, d*unit, **weight_init_params)
    b = bias_init_func(d*unit, **bias_init_params)
    return d, {"W":W, "b":b}

def maxout_init_optimizer():
    sW = {}
    sb = {}
    return {"sW":sW, "sb":sb}

def maxout_propagation(func, u, weights, weight_decay, learn_flag, unit=1, **params):
    u_affine = affine(u, weights["W"], weights["b"])
    u_next   = func(u_affine, unit)
    # 重み減衰対応(未対応)
    weight_decay_r = 0
    #if weight_decay is not None:
    #    weight_decay_r = weight_decay["func"](weights["W"], **weight_decay["params"])
    return {"u":u, "u_next":u_next, "u_affine":u_affine}, weight_decay_r

def maxout_back_propagation(back_func, du_next, us, weights, weight_decay, unit=1, **params):
    du_affine = back_func(du_next, us["u_affine"], us["u_next"], unit)
    du, dW, db = affine_back(du_affine, us["u"], weights["W"], weights["b"])
    # 重み減衰対応(未対応)
    #if weight_decay is not None:
    #    dW = dW + weight_decay["back_func"](weights["W"], **weight_decay["params"])
    return {"du":du, "dW":dW, "db":db}

def maxout_update_weight(func, du, weights, lr, optimizer_stats, **params):
    weights["W"], optimizer_stats["sW"] = func(weights["W"], du["dW"], lr, **params, **optimizer_stats["sW"])
    weights["b"], optimizer_stats["sb"] = func(weights["b"], du["db"], lr, **params, **optimizer_stats["sb"])
    return weights, optimizer_stats

prelu

alphaを学習するため、各関数を定義します。

def prelu_init_layer(d_prev, d):
    alpha = np.zeros(d)
    return d, {"alpha":alpha}

def prelu_init_optimizer():
    salpha = {}
    return {"salpha":salpha}

def prelu_propagation(func, u, weights, weight_decay, learn_flag, **params):
    u_next = func(u, weights["alpha"])
    return {"u":u, "u_next":u_next}, 0

def prelu_back_propagation(back_func, du_next, us, weights, weight_decay, **params):
    du, dalpha = back_func(du_next, us["u"], us["u_next"], weights["alpha"])
    return {"du":du, "dalpha":dalpha}

def prelu_update_weight(func, du, weights, lr, optimizer_stats, **params):
    weights["alpha"], optimizer_stats["salpha"] = func(weights["alpha"], du["dalpha"], lr, **params, **optimizer_stats["salpha"])
    return weights, optimizer_stats

rrelu

実行時に乱数を利用し、alphaを決定するため、各関数を定義します。

def rrelu_init_layer(d_prev, d, min=0.0, max=0.1):
    alpha = np.random.uniform(min, max, d)
    return d, {"alpha":alpha}

def rrelu_propagation(func, u, weights, weight_decay, learn_flag, min=0.0, max=0.1):
    u_next = func(u, weights["alpha"])
    return {"u":u, "u_next":u_next}, 0

def rrelu_back_propagation(back_func, du_next, us, weights, weight_decay, min=0.0, max=0.1):
    du = back_func(du_next, us["u"], us["u_next"], weights["alpha"])
    return {"du":du}

batch_normalization

gamma、betaを学習するため、各関数を定義します。

def batch_normalization_init_layer(d_prev, d, batch_norm_node=False, nouse_gamma_beta=False):
    # バッチ正規化
    if batch_norm_node:
        gamma = np.ones((1, d))
        beta  = np.zeros((1, d))
    else:
        gamma = np.ones(1)
        beta  = np.zeros(1)
    return d, {"gamma":gamma, "beta":beta, "nouse_gamma_beta":nouse_gamma_beta}

def batch_normalization_init_optimizer():
    sgamma = {}
    sbeta = {}
    return {"sgamma":sgamma, "sbeta":sbeta}

def batch_normalization_propagation(func, u, weights, weight_decay, learn_flag, **params):
    u_next = func(u, weights["gamma"], weights["beta"])
    return {"u":u, "u_next":u_next}, 0

def batch_normalization_back_propagation(back_func, du_next, us, weights, weight_decay, **params):
    du, dgamma, dbeta = back_func(du_next, us["u"], weights["gamma"], weights["beta"])
    return {"du":du, "dgamma":dgamma, "dbeta":dbeta}

def batch_normalization_update_weight(func, du, weights, lr, optimizer_stats, **params):
    if not weights["nouse_gamma_beta"]:
        weights["gamma"], optimizer_stats["sgamma"] = func(weights["gamma"], du["dgamma"], lr, **params, **optimizer_stats["sgamma"])
        weights["beta"],  optimizer_stats["sbeta"]  = func(weights["beta"] , du["dbeta"],  lr, **params, **optimizer_stats["sbeta"])
    return weights, optimizer_stats

dropout

実行時に乱数を利用し、ドロップアウトマスクを決定するため、各関数を定義します。

def dropout_propagation(func, u, weights, weight_decay, learn_flag, dropout_ratio=0.9):
    dropout_mask = None
    if learn_flag:
        dropout_mask = set_dropout_mask(u[-1].shape, dropout_ratio)
        u_next = func(u, dropout_mask)
    else:
        u_next = u * dropout_ratio
    return {"u":u, "u_next":u_next, "dropout_mask":dropout_mask}, 0

def dropout_back_propagation(back_func, du_next, us, weights, weight_decay, dropout_ratio=0.9):
    du = back_func(du_next, us["dropout_mask"])
    return {"du":du}

関数群

モデル、オプティマイザで定義する関数群です。
基本的には、今までと同じですが、以下の点を変更しています。

  • 引数名の統一
  • オプションパラメータを辞書を利用したparamsから個別のパラメータに変更
  • 層に対応するため、dropoutの実装見直し
# affine変換
def affine(u, W, b):
    return np.dot(u, W) + b

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

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

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

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

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

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

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

def leaky_relu(u, alpha):
    return np.maximum(u * alpha, u)

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

def prelu(u, alpha):
    return np.where(u > 0, u, u * alpha )

def prelu_back(dz, u, z, alpha):
    return dz * np.where(u > 0, 1, alpha), np.sum(dz * np.where(u > 0, 0, u), axis=0)/u.shape[0]

def rrelu(u, alpha):
    return np.where(u > 0, u, u * alpha )

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

def relun(u, n):
    return np.minimum(np.maximum(0, u), n)

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

def srelu(u, alpha):
    return np.where(u > alpha, u, alpha)

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

def elu(u, alpha):
    return np.where(u > 0, u,  alpha * (np.exp(u) - 1))

def elu_back(dz, u, z, alpha):
    return dz * np.where(u > 0, 1,  alpha * np.exp(u))

def maxout(u, unit):
    u = u.reshape((u.shape[0],int(u.shape[1]/unit),unit))
    return np.max(u, axis=2)

def maxout_back(dz, u, z, unit):
    u = u.reshape((u.shape[0],int(u.shape[1]/unit),unit))
    u_max = np.argmax(u,axis=2)
    du = np.zeros_like(u)
    for i in range(du.shape[0]):
        for j in range(du.shape[1]):
            du[i,j,u_max[i,j]] = dz[i,j]
    return du.reshape((du.shape[0],int(du.shape[1]*unit)))

def identity(u):
    return u

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

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

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

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

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

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

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

# 活性化関数(出力)
def softmax(u):
    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]
    if t.shape[t.ndim-1] == 1:
        # 2値分類
        return -np.sum(t * np.log(np.maximum(y,1e-7)) + (1 - t) * np.log(np.maximum(1 - y,1e-7)))/size
    else:
        # 多クラス分類
        return -np.sum(t * np.log(np.maximum(y,1e-7)))/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 sigmoid_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):
    var = 1/np.sqrt(d_1)
    return np.random.normal(0, var, (d_1, d))
def lecun_uniform(d_1, d):
    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):
    var = np.sqrt(2/(d_1+d))
    return np.random.normal(0, var, (d_1, d))
def glorot_uniform(d_1, d):
    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):
    var = np.sqrt(2/d_1)
    return np.random.normal(0, var, (d_1, d))
def he_uniform(d_1, d):
    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, mean=0, var=1):
    return np.random.normal(mean, var, (d_1, d))
def normal_b(d, params, mean=0, var=1):
    return np.random.