# your code goes here
import numpy as np

def sigmoid(x):
    """Сигмоидная функция активации"""
    return 1 / (1 + np.exp(-x))

def sigmoid_derivative(x):
    """Производная сигмоидной функции"""
    return x * (1 - x)

class SimpleNeuralNetwork:
    def __init__(self, input_size=2, hidden_size=4, output_size=1):
        """Инициализация нейронной сети"""
        # Веса между входным и скрытым слоями
        self.weights1 = np.random.uniform(-1, 1, (input_size, hidden_size))
        # Веса между скрытым и выходным слоями
        self.weights2 = np.random.uniform(-1, 1, (hidden_size, output_size))
        # Смещения (bias)
        self.bias1 = np.zeros((1, hidden_size))
        self.bias2 = np.zeros((1, output_size))
       
    def forward(self, X):
        """Прямое распространение"""
        # Входной -> скрытый слой
        self.hidden_input = np.dot(X, self.weights1) + self.bias1
        self.hidden_output = sigmoid(self.hidden_input)
       
        # Скрытый -> выходной слой
        self.final_input = np.dot(self.hidden_output, self.weights2) + self.bias2
        self.final_output = sigmoid(self.final_input)
       
        return self.final_output
   
    def backward(self, X, y, output, learning_rate=0.1):
        """Обратное распространение ошибки"""
        # Ошибка на выходном слое
        output_error = y - output
        output_delta = output_error * sigmoid_derivative(output)
       
        # Ошибка на скрытом слое
        hidden_error = np.dot(output_delta, self.weights2.T)
        hidden_delta = hidden_error * sigmoid_derivative(self.hidden_output)
       
        # Обновление весов и смещений
        self.weights2 += learning_rate * np.dot(self.hidden_output.T, output_delta)
        self.bias2 += learning_rate * np.sum(output_delta, axis=0, keepdims=True)
       
        self.weights1 += learning_rate * np.dot(X.T, hidden_delta)
        self.bias1 += learning_rate * np.sum(hidden_delta, axis=0, keepdims=True)
   
    def train(self, X, y, epochs=10000, learning_rate=0.1, verbose=True):
        """Обучение нейронной сети"""
        for epoch in range(epochs):
            # Прямое распространение
            output = self.forward(X)
           
            # Обратное распространение
            self.backward(X, y, output, learning_rate)
           
            # Вывод прогресса
            if verbose and epoch % 1000 == 0:
                loss = np.mean(np.square(y - output))
                print(f"Эпоха {epoch}, Loss: {loss:.6f}")
   
    def predict(self, X):
        """Предсказание для новых данных"""
        output = self.forward(X)
        return (output > 0.5).astype(int), output

# Данные для XNOR
X = np.array([[0, 0],
              [0, 1],
              [1, 0],
              [1, 1]])

y = np.array([[1],
              [0],
              [0],
              [1]])

# Создание и обучение сети
print("Создание нейронной сети для XNOR...")
nn = SimpleNeuralNetwork(input_size=2, hidden_size=4, output_size=1)

print("\nОбучение сети...")
nn.train(X, y, epochs=300, learning_rate=1.57)

# Проверка результатов
print("\nРезультаты:")
predictions, probabilities = nn.predict(X)
for i, (x, pred, prob) in enumerate(zip(X, predictions, probabilities)):
    print(f"Вход: {x} -> Предсказание: {pred[0]} (вероятность: {prob[0]:.4f})")

# Дополнительная проверка точности
accuracy = np.mean(predictions == y) * 100
print(f"\nТочность: {accuracy:.2f}%")