# your code goes here
import numpy as np

class TrainableXNOR:
    def __init__(self, learning_rate=0.1):
        # Инициализация весов случайными значениями
        np.random.seed(42)
        self.W1 = np.random.randn(2, 2) * 0.01
        self.b1 = np.zeros((1, 2))
        self.W2 = np.random.randn(2, 1) * 0.01
        self.b2 = np.zeros((1, 1))
        self.lr = learning_rate
    
    def sigmoid(self, x):
        return 1 / (1 + np.exp(-np.clip(x, -700, 700)))
    
    def sigmoid_derivative(self, x):
        return x * (1 - x)
    
    def forward(self, X):
        self.z1 = np.dot(X, self.W1) + self.b1
        self.a1 = self.sigmoid(self.z1)
        self.z2 = np.dot(self.a1, self.W2) + self.b2
        self.a2 = self.sigmoid(self.z2)
        return self.a2
    
    def backward(self, X, y, output):
        m = X.shape[0]
        
        # Градиент выходного слоя
        dz2 = output - y
        dW2 = (1/m) * np.dot(self.a1.T, dz2)
        db2 = (1/m) * np.sum(dz2, axis=0, keepdims=True)
        
        # Градиент скрытого слоя
        da1 = np.dot(dz2, self.W2.T)
        dz1 = da1 * self.sigmoid_derivative(self.a1)
        dW1 = (1/m) * np.dot(X.T, dz1)
        db1 = (1/m) * np.sum(dz1, axis=0, keepdims=True)
        
        # Обновление параметров
        self.W2 -= self.lr * dW2
        self.b2 -= self.lr * db2
        self.W1 -= self.lr * dW1
        self.b1 -= self.lr * db1
    
    def train(self, X, y, epochs=10000):
        losses = []
        for epoch in range(epochs):
            output = self.forward(X)
            loss = np.mean((output - y) ** 2)
            losses.append(loss)
            self.backward(X, y, output)
            
            if epoch % 1000 == 0:
                print(f"Epoch {epoch}, Loss: {loss:.6f}")
        return losses

# Данные для обучения
X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
y = np.array([[1], [0], [0], [1]])  # XNOR

# Обучение и тестирование
network = TrainableXNOR(learning_rate=0.5)
losses = network.train(X, y, epochs=10000)

print("\nРезультаты после обучения:")
print("Input  Target  Output    Predicted")
for i in range(len(X)):
    output = network.forward(X[i].reshape(1, -1))
    predicted = 1 if output > 0.5 else 0
    print(f"{X[i]}   {y[i][0]}      {output[0][0]:.4f}    {predicted}")