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
'''
#️⃣ 인공뉴런의 순전파, 역전파 만들기
조건
- weight, bias를 random으로 받게끔 하기
- learning rate 추가
- Sigmoid의 forward, backward
- affine & sigmoid class 만들기
- 하나의 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 역전파 연산에 사용됨
- def call(self, pred, y):
- 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()
반응형
'Education > 새싹 TIL' 카테고리의 다른 글
새싹 AI데이터엔지니어 핀테커스 11주차 (목) - MLP Visualization (0) | 2023.11.16 |
---|---|
새싹 AI데이터엔지니어 핀테커스 11주차 (수) - Multi Layer Perceptron (0) | 2023.11.16 |
새싹 AI데이터엔지니어 핀테커스 11주차 (월) - Gradient Based Learning & Backpropagation (0) | 2023.11.13 |
새싹 AI데이터엔지니어 핀테커스 10주차 (금) - Loss Function & Differentiation (0) | 2023.11.10 |
새싹 AI데이터엔지니어 핀테커스 10주차 (목) - Artificial Neuron & MLP (2) | 2023.11.09 |