import numpy as np

# Neural network class를 기능을 추가하여 다시 정의하는 코드입니다. 
# "def feed_forward(self, X):"부분을 추가로 정의한 것을 볼 수 있습니다. 

class NeuralNetwork:
    
    def __init__(self):
       # 신경망의 구조를 결정합니다. 입력층 2 노드, 은닉층 3 노드, 출력층 1 노드
        self.inputs = 2
        self.hiddenNodes = 3
        self.outputNodes = 1
        
        # 가중치를 초기화 합니다.
        # layer 1 가중치 (2x3)
        self.w1 = np.random.randn(self.inputs,self.hiddenNodes)
        
        # layer 2 가중치 (3x1)
        self.w2 = np.random.randn(self.hiddenNodes, self.outputNodes)
        
    def sigmoid(self, s):
        return 1 / (1+np.exp(-s))
    
    def feed_forward(self, X):        
        # 가중합 계산
        self.hidden_sum = np.dot(X, self.w1)
        
        # 활성화함수
        self.activated_hidden = self.sigmoid(self.hidden_sum)
        
        # 출력층에서 사용할 은닉층의 각 노드의 출력값을 가중합 합니다.
        self.output_sum = np.dot(self.activated_hidden, self.w2)
        
        # 출력층 활성화, 예측값을 출력합니다.
        self.activated_output = self.sigmoid(self.output_sum)
        
        return self.activated_output
# 예측을 수행해 봅시다.
nn = NeuralNetwork()
print(X[0])
output = nn.feed_forward(X[0])
print("예측값: ", output)
error = y[0] - output

# 모든 데이터를 예측해보고 에러값을 계산해 보겠습니다.
# print(X)
output_all = nn.feed_forward(X)
error_all = y - output_all
print(output_all)
print(error_all)

역전파

import numpy as np

# 음수 가중치를 가지는 활성화는 낮추고, 양수 가중치를 가지는 활성화는 높이고 싶습니다.
class NeuralNetwork:
    
    def __init__(self):
       # 신경망의 구조를 결정합니다. 입력층 2 노드, 은닉층 3 노드, 출력층 1 노드 / Bias 없음.
        self.inputs = 2
        self.hiddenNodes = 3
        self.outputNodes = 1
        
        # 가중치를 초기화 합니다.
        # layer 1 가중치 (2x3)
        self.w1 = np.random.randn(self.inputs,self.hiddenNodes)
        
        # layer 2 가중치 (3x1)
        self.w2 = np.random.randn(self.hiddenNodes, self.outputNodes)
        
    def sigmoid(self, s):
        return 1 / (1+np.exp(-s))
    
    def sigmoidPrime(self, s):
        sx = self.sigmoid(s)
        return sx * (1-sx)
    
    def feed_forward(self, X):        
        # 가중합 계산
        self.hidden_sum = np.dot(X, self.w1)
        
        # 활성화함수
        self.activated_hidden = self.sigmoid(self.hidden_sum)
        
        # 출력층에서 사용할 은닉층의 각 노드의 출력값을 가중합 합니다.
        self.output_sum = np.dot(self.activated_hidden, self.w2)
        
        # 출력층 활성화, 예측값을 출력합니다.
        self.activated_output = self.sigmoid(self.output_sum)
        
        return self.activated_output
    
    def backward(self, X, y, o):
        # 역전파 알고리즘
        
        # 출력층의 손실값 (Error) 입니다.
        self.o_error = y - o 
        
        # 출력층 활성화함수인 시그모이드 함수의 도함수를 사용합니다. (dE / dY * dY / dy) - Latter A
        self.o_delta = self.o_error * self.sigmoidPrime(o)
        
        # z2 error: 출력층의 가중치가 얼마나 에러에 기여했는지 
        self.z2_error = self.o_delta.dot(self.w2.T)
        
        # z2 delta: 시그모이드 도함수를 z2 error에 적용합니다. (d_HiddenE / dY) * (dY / dy) - Latter 
        self.z2_delta = self.z2_error*self.sigmoidPrime(self.output_sum)

        # w1를 업데이트 합니다.
        self.w1 += X.T.dot(self.z2_delta) # X * dE/dY * dY/dy(=Y(1-Y))
        # w2를 업데이트 합니다.
        self.w2 += self.activated_hidden.T.dot(self.o_delta) # H1 * Y(1-Y) * (Y - o) # (Latter A)
        
    def train(self, X,y):
        o = self.feed_forward(X)
        self.backward(X,y,o)