import math
import random

# --- 1. ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ (Вместо формул) ---


def activation(x):
    """Сглаживает любое число в диапазон от 0 до 1 (функция Сигмоида)"""
    return 1 / (1 + math.exp(-x))


def activation_derivative(x):
    """Показывает, насколько сильно нужно изменить вес при ошибке"""
    return x * (1 - x)


# --- 2. ДАННЫЕ ДЛЯ ОБУЧЕНИЯ ---
# Задача: выдать 1, только если ОДИН из входов равен 1. Если оба 0 или оба 1 -> выдать 0.
training_inputs = [[0, 0], [0, 1], [1, 0], [1, 1]]
training_outputs = [[0], [1], [1], [0]]

# --- 3. ИНИЦИАЛИЗАЦИЯ СЕТИ (Случайные веса) ---
random.seed(1)  # Задаем фиксацию случайности для повторяемости

# Веса между входным слоем (2 входа) и скрытым слоем (3 нейрона)
weights_input_hidden = [
    [random.uniform(-1, 1) for _ in range(3)] for _ in range(2)
]

# Веса между скрытым слоем (3 нейрона) и выходным слоем (1 выход)
weights_hidden_output = [
    [random.uniform(-1, 1) for _ in range(1)] for _ in range(3)
]

learning_rate = 0.5  # Скорость обучения

# --- 4. ЦИКЛ ОБУЧЕНИЯ (20 000 повторений) ---
for epoch in range(20000):
    for inputs, correct_output in zip(training_inputs, training_outputs):

        # ---- ШАГ А: ПРЯМОЙ ХОД (Сигнал идет вперед) ----

        # Считаем сигналы для 3-х нейронов в скрытом слое
        hidden_layer_outputs = []
        for j in range(3):
            sum_j = inputs[0] * weights_input_hidden[0][j] + inputs[1] * weights_input_hidden[1][j]
            hidden_layer_outputs.append(activation(sum_j))

        # Считаем финальный прогноз на выходе
        final_sum = 0
        for j in range(3):
            final_sum += hidden_layer_outputs[j] * weights_hidden_output[j][0]
        prediction = activation(final_sum)

        # ---- ШАГ Б: ОБРАТНЫЙ ХОД (Исправление ошибок) ----

        # 1. Считаем ошибку на самом выходе
        output_error = correct_output[0] - prediction
        output_delta = output_error * activation_derivative(prediction)

        # 2. Переносим ошибку назад на скрытый слой
        hidden_deltas = []
        for j in range(3):
            hidden_error = output_delta * weights_hidden_output[j][0]
            hidden_deltas.append(
                hidden_error * activation_derivative(hidden_layer_outputs[j])
            )

        # 3. Корректируем веса между скрытым и выходным слоями
        for j in range(3):
            weights_hidden_output[j][0] += (
                learning_rate * output_delta * hidden_layer_outputs[j]
            )

        # 4. Корректируем веса между входным и скрытым слоями
        for i in range(2):
            for j in range(3):
                weights_input_hidden[i][j] += (
                    learning_rate * hidden_deltas[j] * inputs[i]
                )

print("Обучение многослойной сети завершено!\n")

# --- 5. ПРОВЕРКА РЕЗУЛЬТАТОВ ---
print("--- ТЕСТИРОВАНИЕ СЕТИ ---")
for inputs in training_inputs:
    # Повторяем только ПРЯМОЙ ХОД для проверки
    hidden_layer_outputs = []
    for j in range(3):
        sum_j = inputs[0] * weights_input_hidden[0][j] + inputs[1] * weights_input_hidden[1][j]
        hidden_layer_outputs.append(activation(sum_j))

    final_sum = 0
    for j in range(3):
        final_sum += hidden_layer_outputs[j] * weights_hidden_output[j][0]
    prediction = activation(final_sum)

    print(
        f"Входы: {inputs} -> Прогноз сети: {prediction:.4f} (Округляем до {round(prediction)})"
    )
