1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

深層学習

Last updated at Posted at 2021-05-06

#Day1

入力層ー中間層

IMG_1833 (1).jpeg

x_i:入力\\
w_i:重み\\
b:バイアス\\
f:活性化関数\\
z:出力\\

配列式

\boldsymbol{W} = \begin{pmatrix} w_{1} \\ w_{2} \\ \vdots \\ w_{n} \end{pmatrix} \qquad \boldsymbol{x} = \begin{pmatrix} x_{1} \\ x_{2} \\ \vdots \\ x_{n} \end{pmatrix}\\
u = w_{1}x_{1} + w_{2}x_{2} +\dots + w_{n}x_{n} + b\\
u = \boldsymbol{W}\boldsymbol{x} + b

コード

W = np.array([[0.1], [0.2]])
b = 0.5
x = np.array([2, 3])
u = np.dot(x, W) + b
z = functions.relu(u)

スクリーンショット 2021-05-06 14.49.47.png

考察

重回帰分析と似ている。
異なるのが、活性化関数の導入

活性化関数

次の層への出力の大きさ・信号ON/OFFを決める非線形関数(関数の図が直線でない)

使い分け

中間層用

  • ReLU関数(主流)
  • Sigmoidシグモイド関数
  • Step ステップ関数

出力層用

  • Softmaxソフトマックス関数(クラス分類2つ以上の場合)
  • Sigmoidシグモイド関数
  • 恒等写像関数(f(x) = x)

Step ステップ関数

スクリーンショット 2021-05-06 15.20.55.png
パーセプトロンで利用される関数
課題:0〜1表現できず、線形分離可能なものしか学習できない

Sigmoidシグモイド関数

スクリーンショット 2021-05-06 15.26.52.png
ON/OFFしかできないステップ関数に対し、信号の強弱を調整できる関数
課題:大きな値では出力が微小のため、勾配消失問題が落ちやすい

ReLU関数

スクリーンショット 2021-05-06 15.31.08.png
勾配消失問題を回避、スパース化(影響が少ない重みを0に)を貢献

Softmaxソフトマックス関数

スクリーンショット 2021-05-07 10.38.58.png

コード

def softmax(x):
    if x.ndim == 2:
        x = x.T
        x = x - np.max(x, axis=0)
        y = np.exp(x) / np.sum(np.exp(x), axis=0)
        return y.T

    x = x - np.max(x) # オーバーフロー対策
    return np.exp(x) / np.sum(np.exp(x))

出力層

誤差関数

出力層のアウトプット(d)とラベル(y)の間、どのぐらい差があるか計算する関数

####二乗誤差
スクリーンショット 2021-05-07 10.27.33.png
※1/2あるのは、微分する際、綺麗な結果になるため
※二乗にするのは、マイナス距離を排除するため

コード
def mean_squared_error(d, y):
    return np.mean(np.square(d - y)) / 2

####交差エントロピー
スクリーンショット 2021-05-07 10.35.48.png

コード
def cross_entropy_error(d, y):
    if y.ndim == 1:
        d = d.reshape(1, d.size)
        y = y.reshape(1, y.size)
        
    # 教師データがone-hot-vectorの場合、正解ラベルのインデックスに変換
    if d.size == y.size:
        d = d.argmax(axis=1)
             
    batch_size = y.shape[0]
    return -np.sum(np.log(y[np.arange(batch_size), d] + 1e-7)) / batch_size

出力層の活性化関数

中間層と出力層の違い

  • 中間層:しきい値の前後で信号の強弱を調整
  • 出力層:信号の大きさをそのままに変更したり、0〜1を限定し、総和を1にしたりする

活性化関数+誤差関数

スクリーンショット 2021-05-07 10.52.34.png

勾配降下法

深層学習の目的

学習を通して、誤差を最小にするネットワックを作る
いわば、誤差関数E(W)を最小化するパラメータWを探すこと

勾配降下法

スクリーンショット 2021-05-07 11.06.28.png

{\epsilon}学習率

スクリーンショット 2021-05-07 11.07.06.png

学習率が大きすぎる

最小値にいつまでも、たどり着かず、発散してしまう
スクリーンショット 2021-05-07 11.11.26.png

学習率が小さすぎる

学習する時間がかかる
スクリーンショット 2021-05-07 11.13.43.png

学習率の決定、収束性向上のためのアルゴリズム

  • Momentum
  • AdaGrad
  • AdaDelta
  • Adam

確率的勾配降下法

スクリーンショット 2021-05-07 11.21.53.png
データ全体の誤差の代わり、ランダムに抽出したデータの誤差で計算する
以下のメリットがある

  • 計算コストを軽減
  • オンライ学習できる
  • 局所的な最小値に収束するリスクを軽減
    ※オンライ学習:データ全体代わりに、随時、個別のデータを学習する

ミニバッチ勾配降下法

スクリーンショット 2021-05-07 11.29.05.png
ランダムに分割したデータの集合(ミニバッチ)Dtに属するサンプルの平均誤差で計算する
並列計算を活用できるため、計算資源(CPU,GPU)を有効利用できる

数値微分

スクリーンショット 2021-05-07 11.34.14.png
スクリーンショット 2021-05-07 11.34.29.png
微小な数値を生成し擬似的に微分を計算する手法
どんな複雑な関数でも計算できる、
反面、各パラメータをそれぞれE(w+h)とE(w-h)を計算しないといけないため、
計算コストが高いすぎる
しかし、誤差逆伝播の結果を検証するのによく使われている

def numerical_gradient(f, x):
    h = 1e-4
    grad = np.zeros_like(x)

    for idx in range(x.size):
        tmp_val = x[idx]
        # f(x + h)の計算
        x[idx] = tmp_val + h
        fxh1 = f(x)

        # f(x - h)の計算
        x[idx] = tmp_val - h
        fxh2 = f(x)

        grad[idx] = (fxh1 - fxh2) / (2 * h)
        # 値を元に戻す
        x[idx] = tmp_val

    return grad

誤差逆伝播法

スクリーンショット 2021-05-07 11.34.14.png
を計算するため、
算出された誤差を、出力層側から順に微分し、前の層へ、前の層へ伝播する
最小限の計算で各パラメータでの微分値を解析的に計算する手法

スクリーンショット 2021-05-07 15.10.40.png
IMG_1834 (1).jpeg

実装

初期設定

def init_network():
    # print("##### ネットワークの初期化 #####")
    network = {}
    nodesNum = 10
    network['W1'] = np.random.randn(2, nodesNum)
    network['W2'] = np.random.randn(nodesNum)
    network['b1'] = np.random.randn(nodesNum)
    network['b2'] = np.random.randn()

    return network

順伝播

def forward(network, x):
    # print("##### 順伝播開始 #####")
    
    W1, W2 = network['W1'], network['W2']
    b1, b2 = network['b1'], network['b2']
    u1 = np.dot(x, W1) + b1
    z1 = functions.relu(u1)
    
    u2 = np.dot(z1, W2) + b2
    y = u2
    
    return z1, y

逆伝播

# print("\n##### 誤差逆伝播開始 #####")    

    grad = {}
    
    W1, W2 = network['W1'], network['W2']
    b1, b2 = network['b1'], network['b2']

    # 出力層でのデルタ
    delta2 = functions.d_mean_squared_error(d, y)
    # b2の勾配
    grad['b2'] = np.sum(delta2, axis=0)

    # W2の勾配
    grad['W2'] = np.dot(z1.T, delta2)
    # 中間層でのデルタ
    delta1 = np.dot(delta2, W2.T) * functions.d_relu(z1)

    delta1 = delta1[np.newaxis, :]
    # b1の勾配
    grad['b1'] = np.sum(delta1, axis=0)
    x = x[np.newaxis, :]
    # W1の勾配
    grad['W1'] = np.dot(x.T, delta1)

    return grad

b1、b2の計算について

because the network is designed to process examples in (mini-)batches, and you therefore have gradients calculated for more than one example at a time. The sum is squashing the results down to a single update. This would be easier to confirm if you also showed update code for weights.

学習

def f(x):
    y = 3 * x[0] + 2 * x[1]
    return y
# サンプルデータを作成
data_sets_size = 100000
data_sets = [0 for i in range(data_sets_size)]

for i in range(data_sets_size):
    data_sets[i] = {}
    # ランダムな値を設定
    data_sets[i]['x'] = np.random.rand(2)
    
    ## 試してみよう_入力値の設定
    # data_sets[i]['x'] = np.random.rand(2) * 10 -5 # -5〜5のランダム数値
    
    # 目標出力を設定
    data_sets[i]['d'] = f(data_sets[i]['x'])
    
losses = []
# 学習率
learning_rate = 0.07

# 抽出数
epoch = 1000

# パラメータの初期化
network = init_network()
# データのランダム抽出
random_datasets = np.random.choice(data_sets, epoch)

# 勾配降下の繰り返し
for dataset in random_datasets:
    x, d = dataset['x'], dataset['d']
    z1, y = forward(network, x)
    grad = backward(x, d, z1, y)
    # パラメータに勾配適用
    for key in ('W1', 'W2', 'b1', 'b2'):
        network[key]  -= learning_rate * grad[key]

    # 誤差
    loss = functions.mean_squared_error(d, y)
    losses.append(loss)

print("##### 結果表示 #####")    
lists = range(epoch)


plt.plot(lists, losses, '.')
# グラフの表示
plt.show()

スクリーンショット 2021-05-07 16.34.47.png

#Day2

勾配消失問題

層が重ねれば重ねるほど、勾配降下法を用いると、
下位層のパラメータを更新するための勾配が小さくなりすぎ、
やがて、下位層のパラメータが更新できなくる問題
スクリーンショット 2021-05-14 16.45.52.png

シグモイド関数の問題

スクリーンショット 2021-05-14 16.49.01.png
シグモイド関数を微分した最大値がたった0.25
スクリーンショット 2021-05-14 16.51.54.png
上記の式で勾配を計算すると何回も0.25を掛けたら、段々小さくなり、勾配消失が発生してしまう

勾配消失を解決する方法

活性化関数の選択

スクリーンショット 2021-05-14 17.00.32.png
ReLU活性化関数の最大値が1で、最小値が0により、
勾配消失問題を抑止でき、スパース化に貢献できる

重みの初期値設定

y = w1x1 + w2x2 + ... + wNxN + b
重みw1,w2,...は普段、正規分布で初期化されているが、
yに対する活性関数の微分を大きすぎない(勾配爆発しない)、小さすぎない(勾配消失しない)ように、yの分散を重みの分散と近くように保つ手法。

Xavier
\frac{1}{\sqrt{n}}
network['W1'] = np.random.randn(input_layer_size, hidden_layer_1_size) / (np.sqrt(input_layer_size))

######利用する活性化関数

  • ReLU
  • Sigmoid
  • Tanch(双曲線正接)

He
\frac{2}{\sqrt{n}}
network['W1'] = np.random.randn(input_layer_size, hidden_layer_1_size) / (np.sqrt(input_layer_size)) * np.sqrt(2)

######利用する活性化関数

  • ReLU
考察

重みの初期値が0にすると、勾配消失が発生
1にすると、勾配爆発が発生
どの場合でも、うまく学習できない

バッチの正規化

正規分布のように、入力値の偏りを調整する手法

数式

スクリーンショット 2021-05-17 13.59.32.png

スクリーンショット 2021-05-17 13.59.49.png

IMG_1848 (1).jpeg

コード
Forward
def batchnorm_forward(x, gamma, beta, eps):

  N, D = x.shape

  #step1: calculate mean
  mu = 1./N * np.sum(x, axis = 0)

  #step2: subtract mean vector of every trainings example
  xmu = x - mu

  #step3: following the lower branch - calculation denominator
  sq = xmu ** 2

  #step4: calculate variance
  var = 1./N * np.sum(sq, axis = 0)

  #step5: add eps for numerical stability, then sqrt
  sqrtvar = np.sqrt(var + eps)

  #step6: invert sqrtwar
  ivar = 1./sqrtvar

  #step7: execute normalization
  xhat = xmu * ivar

  #step8: Nor the two transformation steps
  gammax = gamma * xhat

  #step9
  out = gammax + beta

  #store intermediate
  cache = (xhat,gamma,xmu,ivar,sqrtvar,var,eps)

  return out, cache
Backward
def batchnorm_backward(dout, cache):

  #unfold the variables stored in cache
  xhat,gamma,xmu,ivar,sqrtvar,var,eps = cache

  #get the dimensions of the input/output
  N,D = dout.shape

  #step9
  dbeta = np.sum(dout, axis=0)
  dgammax = dout #not necessary, but more understandable

  #step8
  dgamma = np.sum(dgammax*xhat, axis=0)
  dxhat = dgammax * gamma

  #step7
  divar = np.sum(dxhat*xmu, axis=0)
  dxmu1 = dxhat * ivar

  #step6
  dsqrtvar = -1. /(sqrtvar**2) * divar

  #step5
  dvar = 0.5 * 1. /np.sqrt(var+eps) * dsqrtvar

  #step4
  dsq = 1. /N * np.ones((N,D)) * dvar

  #step3
  dxmu2 = 2 * xmu * dsq

  #step2
  dx1 = (dxmu1 + dxmu2)
  dmu = -1 * np.sum(dxmu1+dxmu2, axis=0)

  #step1
  dx2 = 1. /N * np.ones((N,D)) * dmu

  #step0
  dx = dx1 + dx2

  return dx, dgamma, dbeta

効果
  • 多少ノイズがInputに追加されたため、過学習の抑止を期待できる。
  • 学習率を大きく設定しても、Inputが正規化されたため、勾配消失・爆発を抑えられる。

学習率最適化手法

勾配降下法

w^{(t+1)} = w^{(t)} - \epsilon\Delta E

モメンタム

V_{t} = \mu V_{t-1}  - \epsilon\Delta E\\
w^{(t+1)} = w^{(t)} + V_{t}\\
\mu :慣性
class Momentum:
    def __init__(self, learning_rate=0.01, momentum=0.9):
        self.learning_rate = learning_rate
        self.momentum = momentum
        self.v = None
        
    def update(self, params, grad):
        if self.v is None:
            self.v = {}
            for key, val in params.items():                                
                self.v[key] = np.zeros_like(val)
                
        for key in params.keys():
            self.v[key] = self.momentum * self.v[key] - self.learning_rate * grad[key] 
            params[key] += self.v[key]

特徴

  • 局所的最適解にならず、大局的最適解となる
  • 最適値に行くまでの時間が早い

AdaGrad

h_{t} = h_{t-1}  + (\Delta E)^2\\
w^{(t+1)} = w^{(t)}  - \epsilon \frac{1}{\sqrt{h_{t}}} \Delta E\\
class AdaGrad:
    def __init__(self, learning_rate=0.01):
        self.learning_rate = learning_rate
        self.h = None
        
    def update(self, params, grad):
        if self.h is None:
            self.h = {}
            for key, val in params.items():
                self.h[key] = np.zeros_like(val)
            
        for key in params.keys():
            self.h[key] += grad[key] * grad[key]
            params[key] -= self.learning_rate * grad[key] / (np.sqrt(self.h[key]) + 1e-7)

特徴

  • 勾配の穏やかな斜面に対して、最適値に近いづける
  • 学習率が徐々に小さくなるため、鞍点問題が起こることもある

RMSProp

h_{t} = \alpha h_{t-1}  + (1 -\alpha)(\Delta E)^2\\
w^{(t+1)} = w^{(t)}  - \epsilon \frac{1}{\sqrt{h_{t}} + \theta} \Delta E\\
class RMSprop:
    def __init__(self, learning_rate=0.01, decay_rate = 0.99):
        self.learning_rate = learning_rate
        self.decay_rate = decay_rate
        self.h = None
        
    def update(self, params, grad):
        if self.h is None:
            self.h = {}
            for key, val in params.items():
                self.h[key] = np.zeros_like(val)
            
        for key in params.keys():
            self.h[key] *= self.decay_rate
            self.h[key] += (1 - self.decay_rate) * grad[key] * grad[key]
            params[key] -= self.learning_rate * grad[key] / (np.sqrt(self.h[key]) + 1e-7)

特徴

  • 局所的最適解にならず、大局的最適解となる
  • AdaGradの進化系
  • AdaGradに比べたら、ハイパーパラメーターの調整が少ない

Adam

モメンタムの、過去の勾配の指数関数的減衰平均と
RMSPropの、過去の勾配の2乗の指数関数的減衰平均を
孕んだ最適化アルゴリズム

m_{t} = \beta_{1} m_{t-1}  + (1 -\beta_{1})\Delta E\\
v_{t} = \beta_{2} v_{t-1}  + (1 -\beta_{2})(\Delta E)^2\\
w^{(t+1)} = w^{(t)}  - \epsilon \frac{m_{t}}{\sqrt{v_{t}} + \theta} \\
class Adam:
    def __init__(self, learning_rate=0.001, beta1=0.9, beta2=0.999):
        self.learning_rate = learning_rate
        self.beta1 = beta1
        self.beta2 = beta2
        self.iter = 0
        self.m = None
        self.v = None
        
    def update(self, params, grad):
        if self.m is None:
            self.m, self.v = {}, {}
            for key, val in params.items():
                self.m[key] = np.zeros_like(val)
                self.v[key] = np.zeros_like(val)
        
        self.iter += 1
        lr_t  = self.learning_rate * np.sqrt(1.0 - self.beta2 ** self.iter) / (1.0 - self.beta1 ** self.iter)         
        
        for key in params.keys():
            self.m[key] += (1 - self.beta1) * (grad[key] - self.m[key])
            self.v[key] += (1 - self.beta2) * (grad[key] ** 2 - self.v[key])
            
            params[key] -= lr_t * self.m[key] / (np.sqrt(self.v[key]) + 1e-7)

過学習

原因

  • 学習データに対して、パラメーター数が圧倒的に多い
  • パラメーターの値が適切ではない
  • ノード(層)が多い

正則化

過学習を抑止するため、ネットワークの(パラメーター、層など)を制約する

L1正則化-Lasso

\|W\|_1 = (| w_1| + | w_2| +...+ | w_n|)\\
E_n(W) + \lambda \|W\|_1\\

λ:weight_decay_lambda
スクリーンショット 2021-05-19 11.15.40.png

ソース
# 正則化強度設定 ======================================
weight_decay_lambda = 0.005
# =================================================

for i in range(iters_num):
    batch_mask = np.random.choice(train_size, batch_size)
    x_batch = x_train[batch_mask]
    d_batch = d_train[batch_mask]

    grad = network.gradient(x_batch, d_batch)
    weight_decay = 0
    
    for idx in range(1, hidden_layer_num+1):
        grad['W' + str(idx)] = network.layers['Affine' + str(idx)].dW + weight_decay_lambda * np.sign(network.params['W' + str(idx)])
        grad['b' + str(idx)] = network.layers['Affine' + str(idx)].db
        network.params['W' + str(idx)] -= learning_rate * grad['W' + str(idx)]
        network.params['b' + str(idx)] -= learning_rate * grad['b' + str(idx)]        
        weight_decay += weight_decay_lambda * np.sum(np.abs(network.params['W' + str(idx)]))

    loss = network.loss(x_batch, d_batch) + weight_decay
    train_loss_list.append(loss)  
解説

正則化付き勾配

grad[W] = grad[w] + λ *np.sign(W)

grad['W' + str(idx)] = network.layers['Affine' + str(idx)].dW + weight_decay_lambda * np.sign(network.params['W' + str(idx)])

損失計算

weight_decay = 0    
for idx in range(1, hidden_layer_num+1):
    ...       
    weight_decay += weight_decay_lambda * np.sum(np.abs(network.params['W' + str(idx)]))
loss = network.loss(x_batch, d_batch) + weight_decay

L2正則化-Ridge

\|W\|_2 = \sqrt{(| w_1|^2 + | w_2|^2 +...+ | w_n|^2)}\\
E_n(W) + \frac{1}{2} \lambda \|W\|_2\\

λ:weight_decay_lambda
スクリーンショット 2021-05-19 11.16.18.png

ソース
# 正則化強度設定 ======================================
weight_decay_lambda = 0.1
# =================================================

for i in range(iters_num):
    batch_mask = np.random.choice(train_size, batch_size)
    x_batch = x_train[batch_mask]
    d_batch = d_train[batch_mask]

    grad = network.gradient(x_batch, d_batch)
    weight_decay = 0
    
    for idx in range(1, hidden_layer_num+1):
        grad['W' + str(idx)] = network.layers['Affine' + str(idx)].dW + weight_decay_lambda * network.params['W' + str(idx)]
        grad['b' + str(idx)] = network.layers['Affine' + str(idx)].db
        network.params['W' + str(idx)] -= learning_rate * grad['W' + str(idx)]
        network.params['b' + str(idx)] -= learning_rate * grad['b' + str(idx)]        
        weight_decay += 0.5 * weight_decay_lambda * np.sqrt(np.sum(network.params['W' + str(idx)] ** 2))

    loss = network.loss(x_batch, d_batch) + weight_decay
    train_loss_list.append(loss) 
解説

正則化付き勾配

grad[W] = grad[w] + λ *W

grad['W' + str(idx)] = network.layers['Affine' + str(idx)].dW + weight_decay_lambda * network.params['W' + str(idx)]

損失計算

weight_decay = 0
for idx in range(1, hidden_layer_num+1):
   ...       
   weight_decay += 0.5 * weight_decay_lambda * np.sqrt(np.sum(network.params['W' + str(idx)] ** 2))
loss = network.loss(x_batch, d_batch) + weight_decay

ドロップアウト

ランダムにノードを削除し、学習する
スクリーンショット 2021-05-19 13.22.53.png

ソース
class Dropout:
    def __init__(self, dropout_ratio=0.5):
        self.dropout_ratio = dropout_ratio
        self.mask = None

    def forward(self, x, train_flg=True):
        if train_flg:
            self.mask = np.random.rand(*x.shape) > self.dropout_ratio
            return x * self.mask
        else:
            return x * (1.0 - self.dropout_ratio)

    def backward(self, dout):
        return dout * self.mask

学習結果

過学習

学習データに対する正解率が1.0に達しているが、
テストデータに対する正解率が0.65前後に止まっている。
スクリーンショット 2021-05-19 14.19.07.png

L1正則化

スパース化の影響により、正解率の変動幅が激しくなり、
過学習が多少改善できている
スクリーンショット 2021-05-19 14.21.47.png

L2正則化

学習データに対する正解率が1.0に達していないが、
テストデータに対する正解率も0.65前後に止まっている。
スクリーンショット 2021-05-19 14.25.02.png

ドロップアウト

滑らかに学習できている
スクリーンショット 2021-05-19 14.28.55.png

ドロップアウト+L1正則化

スクリーンショット 2021-05-19 14.31.19.png

ドロップアウト+L2正則化

スクリーンショット 2021-05-19 14.45.07.png

畳み込みニューラルネットワークの概念

畳み込み層

写真のデータだと、縦・横・チャネルが存在しているため、
1次元の配列に変化すると、左、上、右、下のピクセルの関係がなくなる。
畳み込み層を利用すると、そういう関係を保ちつつ、学習できる。

演算概念

スクリーンショット 2021-05-22 14.03.50.png

バイアス

スクリーンショット 2021-05-22 14.04.33.png

パディング

スクリーンショット 2021-05-22 14.05.10.png

ストライド

スクリーンショット 2021-05-22 14.05.44.png

チャンネル

スクリーンショット 2021-05-22 14.06.22.png

出力サイズの計算

出力値のheight, width

#入力
input_h
input_w

#フィルタ
filter_h
filter_w

#パディング
padding

#ストライド
stride

#出力
output_h
output_w

#算出
output_h = 1 + int((input_h + 2 * padding - filter_h) / stride)
output_w = 1 + int((input_w + 2 * padding - filter_w) / stride)

ソース

im2col

# 画像データを2次元配列に変換
'''
input_data: 入力値
filter_h: フィルターの高さ
filter_w: フィルターの横幅
stride: ストライド
pad: パディング
'''
def im2col(input_data, filter_h, filter_w, stride=1, pad=0):
    # N: number, C: channel, H: height, W: width
    N, C, H, W = input_data.shape
    out_h = (H + 2 * pad - filter_h)//stride + 1
    out_w = (W + 2 * pad - filter_w)//stride + 1

    img = np.pad(input_data, [(0,0), (0,0), (pad, pad), (pad, pad)], 'constant')
    col = np.zeros((N, C, filter_h, filter_w, out_h, out_w))
    for y in range(filter_h):
        y_start = y 
        y_max = y_start + stride * out_h
        for x in range(filter_w):
            x_start = x 
            x_max = x_start + stride * out_w
            col[:, :, y, x, :, :] = img[:, :, y_start:y_max:stride, x_start:x_max:stride]
    col = col.transpose(0, 4, 5, 1, 2, 3) # (N, C, filter_h, filter_w, out_h, out_w) -> (N, filter_w, out_h, out_w, C, filter_h)   
    col = col.reshape(N * out_h * out_w, -1)
    return col
確認
input_data = np.random.rand(2, 1, 4, 4)*100//1 # number, channel, height, widthを表す
print('========== input_data ===========\n', input_data)
print('==============================')
filter_h = 2
filter_w = 2
stride = 2
pad = 0
col = im2col(input_data, filter_h=filter_h, filter_w=filter_w, stride=stride, pad=pad)
print('============= col ==============\n', col)
print('==============================')

スクリーンショット 2021-05-24 9.14.21.png

col2im

# 2次元配列を画像データに変換
def col2im(col, input_shape, filter_h, filter_w, stride=1, pad=0):
    # N: number, C: channel, H: height, W: width
    N, C, H, W = input_shape
    # 切り捨て除算    
    out_h = (H + 2 * pad - filter_h)//stride + 1
    out_w = (W + 2 * pad - filter_w)//stride + 1
    col = col.reshape(N, out_h, out_w, C, filter_h, filter_w).transpose(0, 3, 4, 5, 1, 2) # (N, filter_h, filter_w, out_h, out_w, C)
    img = np.zeros((N, C, H + 2 * pad + stride - 1, W + 2 * pad + stride - 1))
    for y in range(filter_h):
        y_max = y + stride * out_h
        for x in range(filter_w):
            x_max = x + stride * out_w
            img[:, :, y:y_max:stride, x:x_max:stride] = col[:, :, y, x, :, :]

    return img[:, :, pad:H + pad, pad:W + pad]
確認
img = col2im(col, input_shape=(2, 1, 4, 4),filter_h=filter_h, filter_w=filter_w, stride=stride, pad=pad)
print(img)

スクリーンショット 2021-05-24 9.15.21.png

Convolution

class Convolution:
    # W: フィルター, b: バイアス
    def __init__(self, W, b, stride=1, pad=0):
        self.W = W
        self.b = b
        self.stride = stride
        self.pad = pad
        
        # 中間データ(backward時に使用)
        self.x = None   
        self.col = None
        self.col_W = None
        
        # フィルター・バイアスパラメータの勾配
        self.dW = None
        self.db = None

    def forward(self, x):
        # FN: filter_number, C: channel, FH: filter_height, FW: filter_width
        FN, C, FH, FW = self.W.shape
        N, C, H, W = x.shape
        # 出力値のheight, width
        out_h = 1 + int((H + 2 * self.pad - FH) / self.stride)
        out_w = 1 + int((W + 2 * self.pad - FW) / self.stride)
        
        # xを行列に変換
        col = im2col(x, FH, FW, self.stride, self.pad)
        # フィルターをxに合わせた行列に変換
        col_W = self.W.reshape(FN, -1).T
        print('col',col.shape)
        print('col_W',col_W.shape)
        out = np.dot(col, col_W) + self.b
        # 計算のために変えた形式を戻す
        out = out.reshape(N, out_h, out_w, -1).transpose(0, 3, 1, 2)
        print('out',out.shape)
        self.x = x
        self.col = col
        self.col_W = col_W

        return out

    def backward(self, dout):
        FN, C, FH, FW = self.W.shape
        dout = dout.transpose(0, 2, 3, 1).reshape(-1, FN)

        self.db = np.sum(dout, axis=0)
        self.dW = np.dot(self.col.T, dout)
        self.dW = self.dW.transpose(1, 0).reshape(FN, C, FH, FW)

        dcol = np.dot(dout, self.col_W.T)
        # dcolを画像データに変換
        dx = col2im(dcol, self.x.shape, FH, FW, self.stride, self.pad)

        return dx

プーリング層

スクリーンショット 2021-05-22 14.22.50.png

ソース

class Pooling:
    def __init__(self, pool_h, pool_w, stride=1, pad=0):
        self.pool_h = pool_h
        self.pool_w = pool_w
        self.stride = stride
        self.pad = pad
        
        self.x = None
        self.arg_max = None

    def forward(self, x):
        N, C, H, W = x.shape
        out_h = int(1 + (H - self.pool_h) / self.stride)
        out_w = int(1 + (W - self.pool_w) / self.stride)
        
        # xを行列に変換
        col = im2col(x, self.pool_h, self.pool_w, self.stride, self.pad)
        # プーリングのサイズに合わせてリサイズ
        col = col.reshape(-1, self.pool_h*self.pool_w)
        
        # 行ごとに最大値を求める
        arg_max = np.argmax(col, axis=1)
        out = np.max(col, axis=1)
        # 整形
        out = out.reshape(N, out_h, out_w, C).transpose(0, 3, 1, 2)

        self.x = x
        self.arg_max = arg_max

        return out

    def backward(self, dout):
        dout = dout.transpose(0, 2, 3, 1)
        
        pool_size = self.pool_h * self.pool_w
        dmax = np.zeros((dout.size, pool_size))
        dmax[np.arange(self.arg_max.size), self.arg_max.flatten()] = dout.flatten()
        dmax = dmax.reshape(dout.shape + (pool_size,)) 
        
        dcol = dmax.reshape(dmax.shape[0] * dmax.shape[1] * dmax.shape[2], -1)
        dx = col2im(dcol, self.x.shape, self.pool_h, self.pool_w, self.stride, self.pad)
        
        return dx

スクリーンショット 2021-05-22 16.35.49.png

最新CNN

AlexNet

#### 概要
2012年、ヒントン 教授チームに発表される物体認識のモデル
初の深層学習概念を取り組むモデル
#### 構造
input:224×224×3の写真
output:1000個の要素を持つ一次元の配列

畳み込み層:5
プーリング層:3
全結合層:3
スクリーンショット 2021-05-22 16.41.46.png

#### ソース・Keras

#Alex net
model = Sequential()

# Layer 1
model.add(Conv2D(48, kernel_size=(11, 11), strides=(4,4), input_shape=(224, 224, 3),activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2), strides=(2,2), padding='same'))
model.add(BatchNormalization())

# Layer 2
model.add(Conv2D(128, kernel_size=(5, 5), activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2), strides=(2,2), padding='same'))
model.add(BatchNormalization())

# Layer 3
model.add(Conv2D(192, kernel_size=(3, 3), activation='relu'))
model.add(BatchNormalization())

# Layer 4
model.add(Conv2D(192, kernel_size=(3, 3), activation='relu'))
model.add(BatchNormalization())

# Layer 5
model.add(Conv2D(128, kernel_size=(3, 3), activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2), strides=(2,2), padding='same'))
model.add(BatchNormalization())

model.add(Flatten())

# Layer 6
model.add(Dense(2048, activation='relu'))
model.add(BatchNormalization())
model.add(Dropout(0.01))

# Layer 7
model.add(Dense(2048, activation='relu'))
model.add(BatchNormalization())
model.add(Dropout(0.01))

# Layer 8
model.add(Dense(1000, activation="softmax"))

model.summary()
model.compile(optimizer=keras.optimizers.Adam(lr=0.001), 
                loss='categorical_crossentropy',
                metrics = ['accuracy'])

1
0
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
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?