Chapter01 Computer Vision & Nueral Network


Chapter 01 Computer Vision & Nueral Network


ニューラルネットワークの開始


人工ニューラルネットワーク(NNまたはANN)は、情報の処理、従来のモードの識別、または新しいモードの検出、および複雑なプロセスに近い強力な機械学習ツールである.

レプリケーションニューロン


初期の人工神経ネットワークは人間の神経ネットワークを形成しようとした.下図に示すように、人工ニューラルネットワークは、所与の入力に基づいて、所与の重み付け値を用いて結果値を導出する.

上記の一連の手順を次のように表すことができます.
H(x) = WX + b
x = (x0, x1) w(transpose) = (w0, w1)
重み付け後試験および入力後試験では、結果値H(X)を得ることができます.最後の実行時の出力は、関数をアクティブにするだけで取得できます.
アクティブ化関数のタイプは次のとおりです.
  • Step function
  • Sigmoid function
  • Hyperbolic tangent
  • ReLU
  • ELU
  • インプリメンテーション


    現在、人工ニューロンの値が得られている場合は、他の人工ニューロンに伝達しなければならない.このプロセスを「前進」(Forward)と呼びます.numpyを用いて以下に示すように簡単に実施することができる.
    import numpy as np
    
    class Neuron(object):
      '''simple forward Neuron
      
      Args(입력): 
          num_inputs(int) : 입력 벡터 크기 / 입력 값 개수
          activation_fn : 사용할 활성화 함수
      
      Attributes(속성):
          W (ndarray) : 각 입력에 대한 가중치
          b (float) : Bias. 가중합에 더해진다
          activation_fn (callable) : 활성화 함수
      '''
      
      # 랜덤값으로 가중치 벡터와 bias를 초기화 함
      def __init__(self, num_inputs, activation_fn):
        super().__init__()
        self.W = np.random.rand(num_inputs) # 주어진 특징 벡터 차원과 맞게 가중치를 0~1사이로 random하게 초기화한다
        self.b = np.random.rand(1) # bias 초기화
        self.activation_fn = activation_fn
      
      # 뉴런을 통해 입력 신호를 전달
      def forward(self, x):
        z = np.dot(x, self.W) + self.b # Z = WX + b
        return self.activation_fn(z) # 활성화 함수를 통과시킴
        # 결과가 같게 나오기 위해서 random seed를 고정한다
    
    np.random.seed(42)
    # 3개의 random 입력을 행으로 받을 수 있는 배열 (shape = (1,3))
    x = np.random.rand(3).reshape(1,3)
    print(x) # 임의로 만든 입력 벡터 출력
    
    # Step function 정의
    step_fn = lambda y: 0 if y<=0 else 1 # 0을 기준으로 크면 1 작으면 0의 값을 가진다
    
    # Perceptron을 instance화 시킨다
    perceptron = Neuron(num_inputs=x.size, activation_fn=step_fn) # 계단함수를 활성화 함수로 사용
    
    out = perceptron.forward(x) # 뉴련의 출력 값
    print(out) 

    ニューロンを階層化する


    通常、Neural Networkは、入力が同じで、演算が同じニューロンのセットから構成されます.
    次の図では、ネットワークには入力レイヤと出力レイヤがあります.3つの隠し層(hidden layer)もあります.
    入力レイヤが最初の非表示レイヤに関連付けられ、新しい実行プログラムが出力レイヤに接続されていることがわかります.特に下の非表示レイヤでは、各ニューロンが全接続レイヤまたは密集レイヤと呼ばれる前のレイヤからのすべての値に接続されていることがわかります.

    多層実装


    単一ニューロンのように多層化することができます
    import numpy as np
    
    class FullyConnectedLayer(object):
      '''simple forward Neuron
      
      Args(입력): 
          num_inputs(int) : 입력 벡터 크기 / 입력 값 갯수
          layer size(int) : 출력 벡터의 크기 / 뉴런의 갯수
          activation_fn : 사용할 활성화 함수
      
      Attributes(속성):
          W (ndarray) : 각 입력에 대한 가중치
          b (float) : Bias. 가중합에 더해진다
          activation_fn (callable) : 활성화 함수
      '''
      
      # 랜덤값으로 가중치 벡터와 bias를 초기화 함
      def __init__(self, num_inputs, layer_size, activation_fn):
        super().__init__()
        self.W = np.random.standard_normal(num_inputs) # 주어진 특징 벡터 차원과 맞게 가중치를 정규 분포를 이용하여 초기화한다
        self.b = np.random.standard_normal(layer_size) # bias 초기화
        self.size = layer_size # network속 뉴런의 갯수
        self.activation_fn = activation_fn
      
      # 뉴런을 통해 입력 신호를 전달
      def forward(self, x):
        z = np.dot(x, self.W) + self.b # Z = WX + b
        return self.activation_fn(z) # 활성화 함수를 통과시킴
    FullyConnectLayerは、クラスNeuronの変数のレベルを調整します.
    次はFCRayerのインスタンス化プロセスです
    # 결과가 같게 나오기 위해서 random seed를 고정한다
    np.random.seed(42)
    # 2개의 random 입력을 행으로 받을 수 있는 배열 (shape = (1,2))
    # -1~1 사이의 임의의 실수값으로 초기화한다
    x1 = np.random.uniform(-1, 1, 2).reshape(1,2)
    x2 = np.random.uniform(-1, 1, 2).reshape(1,2)
    # 임의로 만든 입력 벡터 출력
    print('x1:', x1) 
    print('x2:', x2)
    
    # ReLU함수 정의
    relu_fn = lambda y: np.maximum(y, 0) # 0보다 작은 값은 0, 큰 경우는 y = x이다
    
    # instance화
    layer = FullyConnectedLayer(2, 3, relu_fn)
    
    # 각 input(x1, x2)에 대한 출력값 출력
    out1 = layer.forward(x1)
    print('out1:',out1)
    out2 = layer.forward(x2)
    print('out2:',out2)
    FCRayerのx 1,x 2(2 D)出力値(3 D)は、次の手順で取得できます.

    トレーニングニューラルネットワーク


    ニューラルネットワークを訓練する方法は大体3種類ある.

    1)指導的学習


    :最も普遍的で、最も理解しやすい.予測ラベル(結果)を実際のラベル(答え)と比較することで、(=損失)のエラーの程度を評価できます.この損失を減らすには、ネットワークパラメータ(ウェイト、バイアス)を調整し、ネットワーク精度がしきい値に達するまで上記の手順を繰り返します.

    2)指導なし学習


    :指導的な学習とは異なり、label(実際の正解)は提供されません.つまり、ニューラルネットワークが自分で分類した結果を得ることができます.非アドバイザラーニングは、主にクラスタリング(クラスタ)または次元ダウン(dimension reduction)に使用されます.

    3)学習強化


    :強化学習はインタラクティブな学習に基づいている.つまり、私たちの核心思想はやりながら改善することです.彼らにもっと多くの奨励を与えることで、彼らは自分が賢くなる機械を作ることができます.詳細は機械学習を参照してください.

    ネットワークトレーニング


    Cost関数(原価関数)


    :コスト関数の目標は、現在の重みでパフォーマンスを測定することです.我々は主に費用関数を用いて最適解を探した.Costの最低点が最適です.
    我々がよく用いるcost関数はMSE,バイナリクロスエントロピーなどである.
    損失関数の最も重要な点は、常に凸(下へ凸)でなければならないことです.
    この理由は後でもう一度説明します.

    Back propagation


    ネットワークパラメータを更新して損失を最小限に抑えるにはどうすればいいですか?
    一般的に用いられる方法は勾配降下法である.すなわち、コスト関数の勾配(微分値)を用いる.cost関数の傾きに沿って下がると,最適解を見つけることができる.これが勾配降下!
    次の図に示すように、現在の(1)点が斜角に沿って下に続くと、最低点に達します.この場所が一番適当でしょうか?
    以前はcost関数の注意点は常に凸でなければならなかった.このため、下図には2つの極小値が存在し、下図と異なり、2番目の極小値が1番目の極小値よりも小さい場合、ニューラルネットワークは1番目の点が最適(実際には2番目が最適)であると判断し、学習を終了する問題を生じる.すなわち,「グローバルミニマム」(Global Minimum)では学習は終了しないが,「ローカルミニマム」(Local Minimum)では学習は終了し,これはよく悩む問題である.

    では、ネットワークのパラメータ値を勾配で調整するにはどうすればいいのでしょうか.
    重み付け値から(学習率)X(コスト関数の勾配)を減算するだけです.
    学習率(learning rate)はユーザーが決定する必要があります.この値は経験によって決定する必要があります.学習と同時に学習速度を調整し、モデルのパフォーマンスを向上させることができます.学習率が高すぎて、ネット学習速度が速いが、調整幅が大きすぎて、最適なタイミングを逃す可能性がある.逆に、小さすぎると、勉強が遅くなります.したがって、モデルで適切な値を検索します.通常は0.01,0.1,1,...など0.001...などなど.
    Networkの学習過程をまとめてみましょう.
    1.ネットワークにn個のトレーニングデータを提供する
    2.chain-ruleを用いてコストを計算し、逆伝搬により勾配値を得る
    3.対応する微分値でパラメータを更新する
    4.トレーニングセット全体について1~3ステップ繰り返す
    5.条件が満たされるまで手順1~4を繰り返す
    トレーニングセット全体を繰り返してepochと呼ぶ.
    トレーニングセット全体を別々に学習し、mini-batchと呼ばれ、分治の概念と考えられる!

    ニューラルネットワークの分類方法を教える


    前には転送機能のみが実装されていた.今backPropagationと最適化方法を追加しましょう!
    class FullyConnectedLayer(object):
    
      # 기존의 코드와 거의 유사
      def __init__(self, num_inputs, layer_size, activation_fn, d_activation_fn):
        super().__init__()
        self.W = np.random.standard_normal((num_inputs, layer_size)) 
        self.b = np.random.standard_normal(layer_size) 
        self.size = layer_size 
        self.activation = activation_fn
        self.d_activation_fn = d_activation_fn # 활성화 함수의 도함수
        self.x, self.y, self.dL_dW, self.dL_db = 0, 0, 0, 0 # 스토리지 속성
      
      # 뉴런을 통해 입력 신호를 전달
      def forward(self, x):
        z = np.dot(x, self.W) + self.b
        self.y = self.activation(z) 
        self.x = x # 역전파를 위해 값을 저장
        return self.y
    
      # 손실을 역전파 -> chain rule을 활용한다
      def backward(self, dL_dy):
          # chain rule
          dy_dz = self.d_activation_fn(self.y) # f'
          dL_dz = (dL_dy * dy_dz) # dL/dz = dL/dy * dy/dz = l'_{k+1} * f'
          dz_dw = self.x.T
          dz_dx = self.W.T
          dz_db = np.ones(dL_dy.shape[0]) # dz/db = d(W.x + b)/db = 0 + db/db = "ones"-vector 
    
          # 계층 매개변수 dL, w, r, t를 계산하고 저장한다
          self.dL_dW = np.dot(dz_dw, dL_dz)
          self.dL_db = np.dot(dz_db, dL_dz)
    
          # 미분값 w,r,t와 이전 계층의 x를 계산:
          dL_dx = np.dot(dL_dz, dz_dx)
          return dL_dx
    
      # 계층의 매개변수 w,r,t 미분값을 최적화 
      def optimize(self, lr): # lr: learning rate
        self.W -= lr * self.dL_dW
        self.b -= lr * self.dL_db
    # 시그모이드 도함수
    def derivated_sigmoid(y):
      return y * (1-y)
    
    # L2 loss 함수
    def loss_L2(pred, target):
      return np.sum(np.square(pred-target)) / pred.shape[0]
    
    # L2 loss  도함수
    def derivated_loss_L2(pred, target):
      return 2 * (pred-target)  
    class SimpleNetwork(object):
      def __init__(self, num_inputs, num_outputs, hidden_layers_size = (64,32), activation_function=sigmoid, derivated_activation_function=derivated_sigmoid,
                   loss_fn=loss_L2, d_loss_fn = derivated_loss_L2):
        super().__init__()
        sizes = [num_inputs, *hidden_layers_size, num_outputs]
        self.layers = [
          FullyConnectedLayer(sizes[i], sizes[i+1], activation_function, derivated_activation_function) for i in range(len(sizes) - 1)                 
        ]
        self.loss_fn, self.d_loss_fn = loss_fn, d_loss_fn
    
      def forward(self, x):
        for layer in self.layers:
          x = layer.forward(x)
        return x
    
      # 마지막 계층에서 처음 계층까지 손실 미분값을 역전파
      def backward(self, dL_dy):
        for layer in reversed(self.layers):
          dL_dy = layer.backward(dL_dy)
        return dL_dy
    
      # 저장된 gradient값에 따라 매개변수 최적화
      def optimize(self, lr):
        for layer in self.layers:
          layer.optimize(lr)
    
      def predict(self, x):
        estimations = self.forward(x)
        best_class = np.argmax(estimations)
        return best_class
    
      def evaluate(self, X_test, y_test):
        num_corrects = 0
        for i in range(len(X_test)):
          if self.predict(X_test[i]) == y_test[i]: 
            num_corrects += 1
        return num_corrects / len(X_test)
    
      def train(self, X_train, y_train, X_val = None, y_val = None, batch_size=32, num_epochs=5, learning_rate=1e-3 ):
        
        # 제공된 데이터셋에서 네트워크를 훈련하고 평가
        num_batches_per_epoch = len(X_train) ## // Batch size
        loss = []
        for i in range(num_epochs): # 각 epoch마다
          epoch_loss = 0
          for b in range(num_batches_per_epoch):
            # 배치 가져오기
            b_idx = b * batch_size # batch start index
            b_idx_e = b_idx + batch_size # batch end index
            x, y_true = X_train[b_idx:b_idx_e], y_train[b_idx:b_idx_e]
            # 배치에 최적화
            y = self.forward(x)
            epoch_loss += self.loss_fn(y, y_true) # 예측값과 실제값 비교
            dL_dy = self.d_loss_fn(y, y_true)
            self.backward(dL_dy) # = 역전파
            self.optimize(learning_rate) # 최적화
    
          loss.append(epoch_loss / num_batches_per_epoch)
          # epoch마다 정확도를 측정
          accuracy = self.evaluate(X_val,y_val)
          print("Epoch {:4d}: training loss = {:.6f} | val accuracy = {:.2f}%".format(i, loss[i], accuracy * 100))

    Overfitting & underfitting


    上記のフレームワークを使用すると、異なるパラメータ(階層サイズ、学習レート、バッチサイズ)を使用して複数のモデルを作成できます.
    ネットワークが複雑すぎる場合やtrainデータセットが少なすぎる場合
    このような場合、train dataを学ぶのは適切ですが、新しいtest dataに対しては、オーバーフィットと呼ばれる大きな誤差が発生します.ネットワークがトレーニングセットを覚えていると考えるだけです.すなわち,汎用化は新しい例に適用できる程度には発展しなかった.この問題を解決する方法には、データセットの追加やコンプライアンス・テクノロジーの適用が含まれます.詳細は後章でご紹介します!
    フィッティングが足りないということは、ネットワークに十分なパラメータがないことを意味し、モデルが簡単すぎて誤差が大きいと思えば、簡単です.
    下の画像を見るとunderfittingとoverfittingがよくわかります!

    「小さすぎる」はモデルが単純すぎることを示し、「小さすぎる」はモデルが複雑すぎることを示します.
    いいですよ.

    Reference

  • [Tensorflo 2を用いた深い学習コンピュータビジョン]第01章