# your code goes here
import numpy as np

class TrainableXNOR:
    def __init__(self, learning_rate=0.5):
        # Увеличиваем начальные веса для лучшей сходимости
        np.random.seed(42)
        self.W1 = np.random.randn(2, 3) * 0.5  # 3 нейрона в скрытом слое
        self.b1 = np.zeros((1, 3))
        self.W2 = np.random.randn(3, 1) * 0.5
        self.b2 = np.zeros((1, 1))
        self.lr = learning_rate
    
    def sigmoid(self, x):
        # Защита от переполнения
        x = np.clip(x, -500, 500)
        return 1 / (1 + np.exp(-x))
    
    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]
        
        # Ошибка выходного слоя
        error_output = output - y
        
        # Градиент выходного слоя
        delta2 = error_output * self.sigmoid_derivative(output)
        dW2 = np.dot(self.a1.T, delta2) / m
        db2 = np.sum(delta2, axis=0, keepdims=True) / m
        
        # Ошибка скрытого слоя
        error_hidden = np.dot(delta2, self.W2.T)
        
        # Градиент скрытого слоя
        delta1 = error_hidden * self.sigmoid_derivative(self.a1)
        dW1 = np.dot(X.T, delta1) / m
        db1 = np.sum(delta1, axis=0, keepdims=True) / m
        
        # Обновление весов
        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 % 2000 == 0:
                predictions = output.flatten()
                print(f"\nEpoch {epoch}, Loss: {loss:.6f}")
                print("Predictions:", predictions)
                print("Binary:", [1 if p > 0.5 else 0 for p in predictions])
        
        return losses

# Данные
X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]], dtype=np.float64)
y = np.array([[1], [0], [0], [1]], dtype=np.float64)  # XNOR

# Создание и обучение сети
print("Обучение XNOR нейросети...")
network = TrainableXNOR(learning_rate=0.8)
losses = network.train(X, y, epochs=10000)

# Финальное тестирование
print("\n" + "="*50)
print("Финальные результаты:")
print("Input  Target  Output     Predicted")
print("-"*50)

for i in range(len(X)):
    output = network.forward(X[i:i+1])
    predicted = 1 if output[0][0] > 0.5 else 0
    status = "✓" if predicted == y[i][0] else "✗"
    print(f"{X[i]}   {int(y[i][0])}      {output[0][0]:.6f}   {predicted} {status}")

# Проверка точности
correct = 0
for i in range(len(X)):
    output = network.forward(X[i:i+1])
    predicted = 1 if output[0][0] > 0.5 else 0
    if predicted == y[i][0]:
        correct += 1

print(f"\nТочность: {correct}/{len(X)} ({(correct/len(X))*100:.1f}%)")