ファッション画像を分類するニューラルネットワークの解説

 ニューラルネットワーク(NN)を用いて画像からラベルの予測値を出す方法は、計算過程が少し複雑なので、ここで整理したいと思います。
 私のように、大学で微積や線形代数を習得していない人にもわかるレベルで説明できたらと思います。

データ取得と分析方法

 今回NNの分析対象データとして、Fashion MNISTを利用します。6万件のファッション関連画像データに、それぞれBagやDress、Coatなど10種類のラベルが付いたデータです。(ラベルを付けてくれた人、お疲れ様です…)

データ取得

 データ取得はTensorflowを使うと便利でした。train_imagesとして6万件、test_imagesとして1万件を取得できます。一つのイメージサイズは、28x28ピクセルで、1ピクセル当り0~255(8バイト)の数値が入っています。

import tensorflow as tf
from tensorflow import keras
fashion_mnist = keras.datasets.fashion_mnist
    (train_images, train_labels), (test_images, test_labels) = fashion_mnist.load_data()

分析方法

  • Loss Function (Cross Entropy)
    最小化したいLoss Functionには、Cross Entropy(CE)($${-\sum_{k=1}^K y_k^{(i)}\log(\hat{y}_k^{(i)})}$$)を使います。ここで、Kは識別クラス(0~9)。yはLabelで、$${\hat{y}}$$はNNの予測値です。

  • Layer (300x10)
    Hidden Layerは1層のみで300ノード。Output Layerは10ノード(Labelの数と同じ)とします。

  • 変数($${W^{[1]}, b^{[1]}, W^{[2]} , b^{[2]}}$$)
    変数(ウェイト:Wとバイアス:b)は、以下の通りになります:

    • $${W^{[1]} \in \mathbb{R^{(28\times28=784)\times300}}}$$

    • $${b^{[1]} \in \mathbb{R^{300}}}$$

    • $${W^{[2]} \in \mathbb{R^{300\times10}}}$$

    • $${b^{[2]} \in \mathbb{R^{10}}}$$

  • 最適値の求め方(Gradient descent法)
    CEが最小になるような変数を、Gradient descent法を使って見つけていきます:

    • $${W_{ij}^{[1]} = W_{ij}^{[1]} -\alpha \frac{\partial CE}{\partial W_{ij}^{[1]}}}$$

    • $${b_{i}^{[1]} = b_{i}^{[1]} -\alpha \frac{\partial CE}{\partial b_{i}^{[1]}}}$$

    • $${W_{ij}^{[2]} = W_{ij}^{[2]} -\alpha \frac{\partial CE}{\partial W_{ij}^{[2]}}}$$

    • $${b_{i}^{[2]} = b_{i}^{[2]} -\alpha \frac{\partial CE}{\partial b_{i}^{[2]}}}$$
      (実際には処理を高速化するため、上記のようにデータごとのCEを1つ1つ計算して変数を更新するのではなく、バッチデータ(1000件ごと)にベクトル単位で更新していきます。)

  • Activation Function (SigmoidとSoftmax)
    Hidden LayerのActivation Functionには、Sigmoid($${\frac{1}{1+e^{-x}}}$$)、Output Layerには、Softmax($${\frac{e^{x_j}}{\sum_{i=1}^K +e^{x_i}}}$$)を使います。


では、早速CEを最小化する変数の最適値を求めていきましょう!

ニューラルネットワークのアルゴリズムとコード

 変数の最適値を求めるには、まず、変数($${W^{[1]}, b^{[1]}, W^{[2]} , b^{[2]}}$$)からForward propagationによりActivation Function($${A^{[1]},A^{[2]} = \hat{y}}$$)を算出します。それらを用いBackward Propagationで($${W^{[1]}, b^{[1]}, W^{[2]} , b^{[2]}}$$)を更新します。このForward/Backawrd Propagationを、CEが収束するまでひたすら繰り返します。

Forward Propagation

  • 変数初期化
    まず、$${W^{[1]}, b^{[1]}, W^{[2]} , b^{[2]}}$$を初期化します。初期値は、Shapeに注意しながら、Wには0~1のランダム変数、bにはZeroをいれておきます。変数はparamsというDictionary形式にしておきます。

'W1': np.random.normal(size=(input_size, num_hidden)),
'b1': np.zeros(num_hidden),
'W2': np.random.normal (size=(num_hidden, num_output)),
'b2': np.zeros(num_output)
  • アルゴリズムとコード
    まずHidden LayerのActicatino Functionを導出しましょう。
    $${A^{[1](i)} =sigmoid(W^{[1]}\cdot x^{(i)} +b^{[1]}) \in \mathbb{R^{300}}}$$
    $${A^{[2](i)} (=\hat{y}^{(i)}) =softmax(W^{[2]}\cdot A^{[1](i)} +b^{[2]}) \in \mathbb{R^{10}}}$$
    $${CE= -\sum_{k=1}^K y_k^{(i)}\log(\hat{y}_k^{(i)}) \in \mathbb{R}}$$
    実際のコードでは、サンプル単位ではなく、ベクトルを使い計算します。そのため、Loss FunctionもCEの平均値であるJCEを使います。
    $${JCE= -\frac{1}{n}\sum_{i=1}^n\sum_{k=1}^K y_k^{(i)}\log(\hat{y}_k^{(i)}) \in \mathbb{R}}$$

    z1 = x.dot(params['W1']) + params['b1']
    A1 = sigmoid(z1)
    z2 = A1.dot(params['W2']) + params['b2']
    A2 = softmax(z2)
    JCE = np.sum(-labels*np.log(A2))/x.shape[0]

Backward Propagation

 続いて、Forward Propagationで得られた変数($${A^{[1]},A^{[2]} = \hat{y} }$$)を使い、$${\frac{\partial JCE}{\partial W^{[2]}}, \frac{\partial JCE}{\partial b^{[2]}}, \frac{\partial JCE}{\partial W^{[1]}}, \frac{\partial JCE}{\partial b^{[1]}}}$$を導出していきます。

  • $${W^{[2]}}$$と$${b^{[2]}}$$の更新
    $${\frac{\partial JCE}{\partial W^{[2]}}}$$, $${\frac{\partial JCE}{\partial b^{[2]}}}$$の計算には、合成関数の微分法を使って、計算可能な形に変えます。
    $${\frac{\partial JCE}{\partial W^{[2]}}= \frac{\partial JCE}{\partial Z^{[2]}}\cdot\frac{\partial Z^{[2]}}{\partial W^{[2]}}=\frac{1}{n}(A^{[2]}-\hat{y})\cdot A^{[1]}\in \mathbb{R^{300\times 10}}}$$
    $${\frac{\partial JCE}{\partial b^{[2]}}= \frac{\partial JCE}{\partial Z^{[2]}}\cdot\frac{\partial Z^{[2]}}{\partial b^{[2]}}=\frac{1}{n}(A^{[2]}-\hat{y})\in \mathbb{R^{10}}}$$

  • Softmaxの微分はこちらのサイトが分かりやすかったです。
    The Softmax function and its derivative - Eli Bendersky's website (thegreenplace.net)

dW2 = (A1.T@(A2 - labels))/n
db2 = np.sum(A2 - labels, axis=0)/n
  • $${W^{[1]}}$$と$${b^{[1]}}$$の更新
    こちらも合成関数の微分法で計算を進めていきます。
    $${\frac{\partial JCE}{\partial W^{[1]}}= \frac{\partial JCE}{\partial Z^{[2]}}\cdot\frac{\partial Z^{[2]}}{\partial A^{[1]}}\cdot\frac{\partial A^{[1]}}{\partial Z^{[1]}}\cdot\frac{\partial Z^{[1]}}{\partial W^{[1]}}=\frac{1}{n}(A^{[2]}-\hat{y})\cdot W^{[2]}\circ A^{[1]}\circ(1-A^{[1]})\cdot x\in \mathbb{R^{784\times 300}}}$$
    $${\frac{\partial JCE}{\partial b^{[1]}}= \frac{\partial JCE}{\partial Z^{[2]}}\cdot\frac{\partial Z^{[2]}}{\partial A^{[1]}}\cdot\frac{\partial A^{[1]}}{\partial Z^{[1]}}\cdot\frac{\partial Z^{[1]}}{\partial b^{[1]}}=\frac{1}{n}(A^{[2]}-\hat{y})\cdot W^{[2]}\circ A^{[1]}\circ(1-A^{[1]})\in \mathbb{R^{300}}}$$

($${\cdot}$$はDot product、$${\circ}$$はHadamard productです)

dW1 = data.T@((A2 - labels)@params["W2"].T*A1*(1-A1))/n
db1 = np.sum((A2 - labels)@params["W2"].T*A1*(1-A1), axis=0)/n

Gradient descent

Backward Propagationで得られた$${\frac{\partial JCE}{\partial W^{[2]}}, \frac{\partial JCE}{\partial b^{[2]}}, \frac{\partial JCE}{\partial W^{[1]}}, \frac{\partial JCE}{\partial b^{[1]}}}$$を使い、以下の式で変数を更新していきます。

  • $${W_{ij}^{[1]} = W_{ij}^{[1]} -\alpha \frac{\partial CE}{\partial W_{ij}^{[1]}}}$$

  • $${b_{i}^{[1]} = b_{i}^{[1]} -\alpha \frac{\partial CE}{\partial b_{i}^{[1]}}}$$

  • $${W_{ij}^{[2]} = W_{ij}^{[2]} -\alpha \frac{\partial CE}{\partial W_{ij}^{[2]}}}$$

  • $${b_{i}^{[2]} = b_{i}^{[2]} -\alpha \frac{\partial CE}{\partial b_{i}^{[2]}}}$$

JCEをチェックし、収束する変数($${W^{[1]}, b^{[1]}, W^{[2]} , b^{[2]}}$$)を得られたところで、Gradient descentは完了です。($${\alpha}$$はラーニングレートです。)


予測してみよう

 NNを使った画像のラベル予測は、Foward Propagationで算出出来ます。
 ラーニングレート、Hidden Layerの数、Gradient descentのバッチサイズ、Reglarizationの有無、epoch(繰り返し)の数などのパラメターが調整可能であり、頑張れば96%の精度で予測できる優秀な変数($${W^{[1]}, b^{[1]}, W^{[2]} , b^{[2]}}$$)が見つかるそうです。
 ぜひトライしてみてください!

参考:Lecture 11 - Introduction to Neural Networks | Stanford CS229: Machine Learning (Autumn 2018)


この記事が気に入ったらサポートをしてみませんか?