728x90

2023-11-14 49th Class

Backpropagation

#️⃣ chain rule

시그모이드를 4개의 합성함수로 분리하여 순전파, 역전파 하기

  • 1번:
  • 2번:
  • 3번:
  • 4번:
import numpy as np  
  
  
class Function1:  
    def forward(self, z):  
        self.z1 = -z  
        return self.z1  
  
    def backward(self, dy_dz1):  
        dz1_dz = -1  
        dy_dz = dz1_dz * dy_dz1  
        return dy_dz  
  
class Function2:  
    def forward(self, z1):  
        self.z2 = np.exp(z1)  
        return self.z2  
  
    def backward(self, dy_dz2):  
        dz2_dz1 = self.z2 # e^x 를 미분하면 그대로  
        dy_dz1 = dz2_dz1 * dy_dz2  
        return dy_dz1  
  
  
class Function3:  
    def forward(self, z2):  
        self.z3 = 1 + z2  
        return self.z3  
  
    def backward(self, da_dz3):  
        dz3_dz2 = 1  
        dy_dz2 = dz3_dz2 * da_dz3  
        return dy_dz2  
  
  
  
class Function4:  
    def forward(self, z3):  
        self.z3 = z3  
        self.a = 1 / z3  
        return self.a  
  
    def backward(self):  
        da_dz3 = - 1 / (self.z3 **2)  
        return da_dz3  
  
  
class Sigmoid:  
    def __init__(self):  
        self.fn1 = Function1()  
        self.fn2 = Function2()  
        self.fn3 = Function3()  
        self.fn4 = Function4()  
  
    def forward(self, z):  
        z1 = self.fn1.forward(z)  
        z2 = self.fn2.forward(z1)  
        z3 = self.fn3.forward(z2)  
        a = self.fn4.forward(z3)  
        return a  
  
    def backward(self):  
        da_dz3 = self.fn4.backward()  
        dy_dz2 = self.fn3.backward(da_dz3)  
        dy_dz1 = self.fn2.backward(dy_dz2)  
        dy_dz = self.fn1.backward(dy_dz1)  
        return dy_dz  
  
  
sigmoid = Sigmoid()  
a = sigmoid.forward(0)  
print(f"{a=}")  
dy_dz = sigmoid.backward()  
print(f"{dy_dz=}")

'''
a=0.5
dy_dz=0.25
'''

#️⃣ 인공뉴런의 순전파, 역전파 만들기

조건

  1. weight, bias를 random으로 받게끔 하기
  2. learning rate 추가
  3. Sigmoid의 forward, backward
  4. affine & sigmoid class 만들기
  5. 하나의 model class 만들기

각 요소의 미분계수 (참고)

코드 구조 설계

  • class AffineFunction:
    • def init(self): weight vector과 bias를 attributes로 설정 (초기값은 랜덤하게)
    • def forward(self, X): 순전파, input vector와 weight를 내적(원소를 곱해서 summation)한 값 + bias를 계산
    • def backward(self, dJ_dz, lr): 역전파, 시그모이드 단계에서 돌려받은 시그모이드 미분값과, 학습률를 사용해 weight과 bias를 업데이트
  • class Sigmoid:
    • def forward(self, z): 순전파, affine function의 forward를 통과한 값에 sigmoid 함수 적용
    • def backward(self, dJ_dpred):
      • 역전파
      • BinaryCrossEntropy 단계에서 돌려받은 loss 미분값과, 시그모이드 미분값을 곱하는 연산
      • 역전파로 계산한 미분계수간의 곱한 값은 AffineFunction 단계로 돌려줘서 chain rule 역전파 연산에 사용됨
  • class BCELoss:
    • def call(self, pred, y):
      • 순전파 이후 예측값(pred)과 타겟(y)의 차이를 binary crossentropy 방식으로 연산
      • 연산 결과(J)를 예측값(pred)에 대해 미분한 값은 Sigmoid 단계로 돌려줘서 chain rule 역전파 연산에 사용됨
  • class Model:
    • def init(self): AffineFunction과 Sigmoid를 인스턴스화하여 attribute로 설정
    • def forward(self, X):
      • AffineFunction과, Sigmoid를 차례로 통과시키는 순전파 연산 전체
      • 예측값을 반환
    • def backward(self, dJ_dpred, lr):
      • [1] loss의 미분값을 사용해 sigmoid 역전파를 수행하고
      • [2] sigmoid 역전파를 한 값과 학습률을 사용해 affine function의 weight과 bias를 업데이트
  • 메인 루틴:
    • AND 연산을 기준을 기준으로 2개의 input 값과, 이진분류 타겟 y(0 or 1)을 설정
    • 모델과, Loss Function 인스턴스 생성 및 학습률을 설정
    • 30번의 학습(순전파 + 역전파) 진행

code

import numpy as np  
  
# weight, bias를 random으로 설정하여 순전파 역전파  
class AffineFunction:  
    def __init__(self):  
        self.w = np.random.randn(2)  
        self.b = np.random.randn()  
  
    def forward(self, X):  
        self.X = X  
        self.z = np.dot(X, self.w) + self.b  
        return self.z  
  
    def backward(self, dJ_dz, lr):  
        # 0. 기본 식:  
        # w1 := w1 - LR * [w1에 대한 미분값(dz_dw1) * 시그모이드에서 넘어온 미분값(dJ_dz)]  
        # w2 := w2 - LR * [w2에 대한 미분값(dz_dw2) * 시그모이드에서 넘어온 미분값(dJ_dz)]  
        # b := b - LR * [b에 대한 미분값(dz_db) * 시그모이드에서 넘어온 미분값(dJ_dz)]  
  
        # 1. 파라미터 별 미분 값 구하기  
        dz_dw = self.X  
        dz_db = 1  
  
        # 2. 파라미터 업데이트  
        self.w -= lr * dz_dw * dJ_dz  
        self.b -= lr * dz_db * dJ_dz  
  
class Sigmoid:  
    def forward(self, z):  
        self.z = z  
        self.a = 1 / (1 + np.exp(-z))  
        return self.a  
  
    def backward(self, dJ_dpred):  
        # 0. 기본 식: 시그모이드 미분값(da_dz) * 로스 미분값(dJ_dpred)  
  
        # 1. 시그모이드 미분값 da_dz = a(1-a)        
        da_dz = self.a * (1 - self.a)  
        
        # 2. 최종 식 da_dz * dJ_pred        
        dJ_dz = da_dz * dJ_dpred  
        return dJ_dz  
  
  
class BCELoss:  
    def __call__(self, pred, y):  
        # 1. loss 계산  
        J = -(y * np.log(pred) + (1 - y) * np.log(1 - pred))  
        # 2. loss 미분 값계산:  dJ /d yhat  
        dJ_dpred = (pred - y) / (pred + 1e-7 * (1 - pred + 1e-7))  
        # 분모의 + 1e-7는 ZeroDivision Error 막기 위해 추가함 (0으로 나누면 에러나서)  
        return J, dJ_dpred  
  
  
class Model:  
    def __init__(self):  
        self.affine = AffineFunction()  
        self.sigmoid = Sigmoid()  
  
    def forward(self, X):  
        z = self.affine.forward(X)  
        pred = self.sigmoid.forward(z)  
        return pred  
  
    def backward(self, dJ_dpred, lr):  
        dJ_dz = self.sigmoid.backward(dJ_dpred)  
        w, b = self.affine.backward(dJ_dz, lr)  
        return w, b
  
  
# AND  
X = np.array([1, 0])  
y = 0  
model = Model()  
loss_fn = BCELoss()  
lr = 0.1  
  
print(f"AND GATE Train with {X=} {y=}")  
  
for i in range(30):  
    pred = model.forward(X)  
    loss, dJ_dpred = loss_fn(pred, y)  
    print(f"{loss=:.4f}")  
    model.backward(dJ_dpred, lr)

result

AND GATE Train with X=array([1, 0]) y=0
loss=0.4690
loss=0.4517
loss=0.4351
loss=0.4193
loss=0.4041
loss=0.3895
loss=0.3756
loss=0.3624
loss=0.3497
loss=0.3376
loss=0.3260
loss=0.3150
loss=0.3045
loss=0.2945
loss=0.2849
loss=0.2758
loss=0.2671
loss=0.2588
loss=0.2509
loss=0.2433
loss=0.2361
loss=0.2292
loss=0.2226
loss=0.2163
loss=0.2103
loss=0.2046
loss=0.1991
loss=0.1938
loss=0.1887
loss=0.1839

AND GATE weight, bias 변화 확인 테스트

  • AND 연산의 4가지 case [0, 0], [0, 1], [1, 0], [1, 1] 와 target 0, 0, 0, 1을 각 500번씩 학습시킴

code

def train_andgate():  
    # AND  
    X_data = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])  
    y_data = [0, 0, 0, 1]  
    model = Model()  
    loss_fn = BCELoss()  
    lr = 0.1  
  
    for i in range(2000):  
        X = X_data[i % 4]  
        y = y_data[i % 4]  
        pred = model.forward(X)  
        loss, dJ_dpred = loss_fn(pred, y)  
        w, b= model.backward(dJ_dpred, lr)  
        if i in [0, 201, 402, 603, 804, 1005, 1206, 1407, 1608, 1809, 2000]:  
            print(f"{w=}, {b=:.4f}", end=" / ")  
            print(f"{X=}, {y=}, {pred=:.4f}, {loss=:.4f}")

result

w=array([-0.67118365,  0.33768056]), b=-1.3686 / X=array([0, 0]), y=0, pred=0.2055, loss=0.2300
w=array([0.59800725, 1.2191753 ]), b=-1.9940 / X=array([0, 1]), y=0, pred=0.3250, loss=0.3930
w=array([1.20148858, 1.59111998]), b=-2.7858 / X=array([1, 0]), y=0, pred=0.1743, loss=0.1915
w=array([1.67891003, 1.91474772]), b=-3.3624 / X=array([1, 1]), y=1, pred=0.5420, loss=0.6125
w=array([2.03064026, 2.17479361]), b=-3.8558 / X=array([0, 0]), y=0, pred=0.0208, loss=0.0210
w=array([2.31699053, 2.39473414]), b=-4.2840 / X=array([0, 1]), y=0, pred=0.1340, loss=0.1439
w=array([2.54887926, 2.60437286]), b=-4.6555 / X=array([1, 0]), y=0, pred=0.1104, loss=0.1170
w=array([2.772214  , 2.80717186]), b=-4.9626 / X=array([1, 1]), y=1, pred=0.6406, loss=0.4453
w=array([2.95720033, 2.97910143]), b=-5.2521 / X=array([0, 0]), y=0, pred=0.0052, loss=0.0052
w=array([3.12336679, 3.1290271 ]), b=-5.5222 / X=array([0, 1]), y=0, pred=0.0849, loss=0.0887
  • 초기의 weight과 bias는 random으로 설정되었음
  • 학습이 누적될 수록 weight과 bias가 업데이트됨
  • AND GATE의 기본 파라미터값인 w1=0.5, w2=0.5, b=-0.7의 배수와 비슷한 값으로 파라미터가 업데이트 되고있음
  • and gate에서 input x값이 0인 경우 affine function의 해당 weight의 미분값이 0이 되어 해당 데이터 포인트의 weight 파라미터가 업데이트 되지 않음
  • 즉 x1=0, x2=1이라면 input x값 중 0이 아닌것(w2)과 bias만 업데이트 됨 (w1은 업데이트가 되지 않음)

#️⃣ 랜덤 데이터셋으로 딥러닝 학습 (Layer 1개) 및 시각화

from dl_09_full_nn import Model  
from dl_09_full_nn import BCELoss  
  
from sklearn.datasets import make_blobs  
import matplotlib.pyplot as plt  
import numpy as np  
  
  
def calculate_accuracy(preds, targets):  
    rounded_preds = np.round(preds)  
    correct = (rounded_preds == targets).sum().item()  
    accuracy = correct / len(targets)  
    return accuracy  
  
  
def main_routine():  
    N_SAMPLES = 100  
    LR = 0.001  
    EPOCHS = 30  
  
    X, y = make_blobs(n_samples=N_SAMPLES, centers=2, n_features=2,  
                      cluster_std=0.5, random_state=0)  
  
    '''Instantiation'''  
    model = Model()  
    loss_fn = BCELoss()  
  
    epoch_accuracy = list()  
    epoch_loss = list()  
  
    for epoch in range(EPOCHS):  
        pred_list = list()  
        loss_list = list()  
  
        for X_, y_ in zip(X, y):  
            '''Training'''  
            pred = model.forward(X_)  
            loss, dJ_dpred = loss_fn(pred, y_)  
            w, b = model.backward(dJ_dpred, LR)  
  
            '''Metric(loss, accuracy) Calculations'''  
            pred_list.append(pred)  
            loss_list.append(loss)  
  
        epoch_accuracy.append(calculate_accuracy(pred_list, y))  
        epoch_loss.append(sum(loss_list)/len(loss_list))  
  
    '''Result Visualization'''  
    fig, axes = plt.subplots(2, 1, figsize=(10, 5))  
    axes[0].plot(epoch_loss, linestyle='-')  
    axes[0].set_ylabel("BCELoss", fontsize=15)  
    axes[1].plot(epoch_accuracy, linestyle='-')  
    axes[1].set_ylabel("Accuracy", fontsize=15)  
    axes[1].set_xlabel("Epoch", fontsize=15)  
    fig.tight_layout()  
    plt.show()  
  
  
if __name__ == '__main__':  
    main_routine()

training model 231114.png

반응형