исправь все ошщибки и оптемезируй
# rnn_chatbot.py

import tensorflow as tf
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Input, Embedding, LSTM, Dense
from tensorflow.keras.preprocessing.sequence import pad_sequences
from tensorflow.keras.preprocessing.text import Tokenizer
from sklearn.model_selection import train_test_split
import numpy as np
import json
import pickle
import os
import sys

# Установите репрезентативное количество эпох и размер батча
EPOCHS = 2000
BATCH_SIZE = 2
PATIENCE = 100  # Для ранней остановки

# Пути к файлам
DATA_FILE = 'data.json'
TOKENIZER_FILE = 'tokenizer.pkl'
MODEL_FILE = 'seq2seq_chatbot.h5'


def load_data(json_file):
    """
    Загружает данные из JSON-файла.

    :param json_file: Путь к JSON-файлу с данными.
    :return: Списки вопросов и ответов.
    """
    print(f"Пытаемся загрузить данные из '{json_file}'...")
    if not os.path.exists(json_file):
        print(f"Файл данных '{json_file}' не найден.")
        sys.exit(1)

    try:
        with open(json_file, 'r', encoding='utf-8') as file:
            data = json.load(file)
            print(f"Загружено {len(data)} пар 'вопрос-ответ'.")
    except json.JSONDecodeError as e:
        print(f"Ошибка при разборе JSON-файла '{json_file}': {e}")
        sys.exit(1)

    questions = []
    answers = []

    for index, pair in enumerate(data, start=1):
        question = pair.get("question")
        answer = pair.get("answer")
        if question and answer:
            questions.append(question)
            # Добавляем специальные токены начала и конца ответа
            answers.append("\t " + answer + " \n")
            print(f"Парa {index}: Вопрос - '{question}', Ответ - '{answer}'")
        else:
            print(f"Пропущена пара без вопроса или ответа на позиции {index}: {pair}")

    return questions, answers


def save_tokenizer(tokenizer, filepath):
    """
    Сохраняет объект Tokenizer в файл.

    :param tokenizer: Объект Tokenizer.
    :param filepath: Путь к файлу для сохранения.
    """
    with open(filepath, 'wb') as handle:
        pickle.dump(tokenizer, handle, protocol=pickle.HIGHEST_PROTOCOL)
    print(f"Токенизатор сохранен в '{filepath}'.")


def load_tokenizer(filepath):
    """
    Загружает объект Tokenizer из файла.

    :param filepath: Путь к файлу с токенизатором.
    :return: Объект Tokenizer.
    """
    with open(filepath, 'rb') as handle:
        tokenizer = pickle.load(handle)
    print(f"Токенизатор загружен из '{filepath}'.")
    return tokenizer


def preprocess_data(questions, answers, tokenizer=None):
    """
    Токенизирует и паддит последовательности вопросов и ответов.

    :param questions: Список вопросов.
    :param answers: Список ответов.
    :param tokenizer: Объект Tokenizer или None.
    :return: Паддированные последовательности, токенизатор, максимальные длины.
    """
    if tokenizer is None:
        tokenizer = Tokenizer(filters='', oov_token='<OOV>')
        tokenizer.fit_on_texts(questions + answers)
        save_tokenizer(tokenizer, TOKENIZER_FILE)
    else:
        print("Используется загруженный токенизатор.")

    vocab_size = len(tokenizer.word_index) + 1  # Добавляем 1 для padding

    # Конвертация текстов в последовательности чисел
    question_seqs = tokenizer.texts_to_sequences(questions)
    answer_seqs = tokenizer.texts_to_sequences(answers)

    # Определение максимальной длины для вопросов и ответов
    max_question_len = max(len(seq) for seq in question_seqs)
    max_answer_len = max(len(seq) for seq in answer_seqs)

    # Паддинг последовательностей
    question_padded = pad_sequences(question_seqs, maxlen=max_question_len, padding='post')
    answer_padded = pad_sequences(answer_seqs, maxlen=max_answer_len, padding='post')

    # Разделение ответов на вход (decoder_input) и выход (decoder_output)
    decoder_input = answer_padded[:, :-1]
    decoder_output = answer_padded[:, 1:]
    decoder_output = np.expand_dims(decoder_output, -1)

    return question_padded, decoder_input, decoder_output, tokenizer, vocab_size, max_question_len, max_answer_len


def build_model(vocab_size, embedding_dim, latent_dim, max_question_len, max_answer_len):
    """
    Строит Seq2Seq модель с использованием LSTM.

    :param vocab_size: Размер словаря.
    :param embedding_dim: Размер эмбеддингов.
    :param latent_dim: Размер LSTM слоя.
    :param max_question_len: Максимальная длина вопроса.
    :param max_answer_len: Максимальная длина ответа.
    :return: Обучающая модель, модель энкодера, модель декодера.
    """
    # Энкодер
    encoder_inputs = Input(shape=(max_question_len,), name='encoder_inputs')
    encoder_embedding = Embedding(vocab_size, embedding_dim, mask_zero=True, name='encoder_embedding')(encoder_inputs)
    encoder_lstm = LSTM(latent_dim, return_state=True, name='encoder_lstm')
    _, state_h, state_c = encoder_lstm(encoder_embedding)
    encoder_states = [state_h, state_c]

    # Декодер (для обучения)
    decoder_inputs_train = Input(shape=(max_answer_len - 1,), name='decoder_inputs_train')
    decoder_embedding = Embedding(vocab_size, embedding_dim, mask_zero=True, name='decoder_embedding')(
        decoder_inputs_train)
    decoder_lstm = LSTM(latent_dim, return_sequences=True, return_state=True, name='decoder_lstm')
    decoder_outputs, _, _ = decoder_lstm(decoder_embedding, initial_state=encoder_states)
    decoder_dense = Dense(vocab_size, activation='softmax', name='decoder_dense')
    decoder_outputs = decoder_dense(decoder_outputs)

    # Обучающая модель
    model = Model([encoder_inputs, decoder_inputs_train], decoder_outputs)

    # Компиляция модели
    model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])

    # Вывод структуры модели
    model.summary()

    # Модель энкодера (для инференса)
    encoder_model = Model(encoder_inputs, encoder_states)

    # Модель декодера (для инференса)
    decoder_state_input_h = Input(shape=(latent_dim,), name='decoder_state_input_h')
    decoder_state_input_c = Input(shape=(latent_dim,), name='decoder_state_input_c')
    decoder_states_inputs = [decoder_state_input_h, decoder_state_input_c]

    decoder_inputs_single = Input(shape=(1,), name='decoder_inputs_single')
    decoder_emb2 = decoder_embedding(decoder_inputs_single)

    decoder_outputs2, state_h2, state_c2 = decoder_lstm(decoder_emb2, initial_state=decoder_states_inputs)
    decoder_outputs2 = decoder_dense(decoder_outputs2)
    decoder_states2 = [state_h2, state_c2]

    decoder_model = Model(
        [decoder_inputs_single] + decoder_states_inputs,
        [decoder_outputs2] + decoder_states2
    )

    return model, encoder_model, decoder_model


def train_model(model, encoder_input_train, decoder_input_train, decoder_output_train, encoder_input_val,
                decoder_input_val, decoder_output_val):
    """
    Обучает модель с использованием ранней остановки и логированием.

    :param model: Обучающая модель.
    :param encoder_input_train: Тренировочные входные данные для энкодера.
    :param decoder_input_train: Тренировочные входные данные для декодера.
    :param decoder_output_train: Тренировочные выходные данные для декодера.
    :param encoder_input_val: Валидационные входные данные для энкодера.
    :param decoder_input_val: Валидационные входные данные для декодера.
    :param decoder_output_val: Валидационные выходные данные для декодера.
    :return: История обучения.
    """
    early_stop = tf.keras.callbacks.EarlyStopping(monitor='val_loss', patience=PATIENCE, restore_best_weights=True)
    log_callback = tf.keras.callbacks.LambdaCallback(
        on_epoch_end=lambda epoch, logs: print(
            f"Epoch {epoch + 1}: loss={logs['loss']:.4f}, accuracy={logs['accuracy'] * 100:.2f}%, "
            f"val_loss={logs.get('val_loss', 0):.4f}, val_accuracy={logs.get('val_accuracy', 0) * 100:.2f}%"
        ) if (epoch + 1) % 100 == 0 else None
    )

    history = model.fit(
        [encoder_input_train, decoder_input_train],
        decoder_output_train,
        validation_data=([encoder_input_val, decoder_input_val], decoder_output_val),
        epochs=EPOCHS,
        batch_size=BATCH_SIZE,
        callbacks=[early_stop, log_callback],
        verbose=0  # Логирование осуществляется через callback
    )
    return history


def decode_sequence(input_seq, encoder_model, decoder_model, tokenizer, max_answer_len):
    """
    Генерирует ответ на основе входной последовательности.

    :param input_seq: Входная последовательность.
    :param encoder_model: Модель энкодера для инференса.
    :param decoder_model: Модель декодера для инференса.
    :param tokenizer: Объект Tokenizer.
    :param max_answer_len: Максимальная длина ответа.
    :return: Сгенерированный ответ.
    """
    # Получаем состояния энкодера
    states_value = encoder_model.predict(input_seq)

    # Инициализируем целевую последовательность со стартовым токеном
    target_seq = np.array([[tokenizer.word_index['\t']]])

    decoded_sentence = ''
    stop_condition = False

    while not stop_condition:
        # Предсказываем следующий токен
        output_tokens, h, c = decoder_model.predict([target_seq] + states_value)

        # Выбираем наиболее вероятный токен
        sampled_token_index = np.argmax(output_tokens[0, -1, :])
        sampled_word = tokenizer.index_word.get(sampled_token_index, '<OOV>')

        # Проверяем условие остановки
        if sampled_word == '\n' or len(decoded_sentence.split()) >= max_answer_len:
            stop_condition = True
        else:
            decoded_sentence += ' ' + sampled_word

            # Обновляем целевую последовательность
            target_seq = np.array([[sampled_token_index]])

            # Обновляем состояния
            states_value = [h, c]

    return decoded_sentence.strip()


def get_answer(question, tokenizer, max_question_len, max_answer_len, encoder_model, decoder_model):
    """
    Получает ответ от модели на заданный вопрос.

    :param question: Вопрос пользователя.
    :param tokenizer: Объект Tokenizer.
    :param max_question_len: Максимальная длина вопроса.
    :param max_answer_len: Максимальная длина ответа.
    :param encoder_model: Модель энкодера для инференса.
    :param decoder_model: Модель декодера для инференса.
    :return: Ответ модели.
    """
    # Преобразуем вопрос в последовательность
    sequence = tokenizer.texts_to_sequences([question])
    padded_sequence = pad_sequences(sequence, maxlen=max_question_len, padding='post')

    # Генерируем ответ
    decoded = decode_sequence(padded_sequence, encoder_model, decoder_model, tokenizer, max_answer_len)

    # Обработка пустых ответов
    if decoded:
        return decoded
    else:
        return 'Извините, я не понял ваш вопрос.'


def main():
    # Проверка наличия токенизатора и модели
    if os.path.exists(MODEL_FILE) and os.path.exists(TOKENIZER_FILE):
        print("Загрузка существующей модели и токенизатора...")
        model = tf.keras.models.load_model(MODEL_FILE)
        tokenizer = load_tokenizer(TOKENIZER_FILE)

        try:
            # Восстановление модели энкодера
            encoder_inputs = model.input[0]  # encoder_inputs
            encoder_outputs = model.get_layer('encoder_lstm').output[1:]
            encoder_model = Model(encoder_inputs, encoder_outputs)

            # Восстановление модели декодера
            latent_dim = model.get_layer('encoder_lstm').units
            decoder_state_input_h = Input(shape=(latent_dim,), name='decoder_state_input_h')
            decoder_state_input_c = Input(shape=(latent_dim,), name='decoder_state_input_c')
            decoder_states_inputs = [decoder_state_input_h, decoder_state_input_c]

            decoder_inputs_single = Input(shape=(1,), name='decoder_inputs_single')
            decoder_embedding = model.get_layer('decoder_embedding')(decoder_inputs_single)
            decoder_lstm = model.get_layer('decoder_lstm')
            decoder_outputs2, state_h2, state_c2 = decoder_lstm(decoder_embedding, initial_state=decoder_states_inputs)
            decoder_dense = model.get_layer('decoder_dense')
            decoder_outputs2 = decoder_dense(decoder_outputs2)
            decoder_states2 = [state_h2, state_c2]

            decoder_model = Model(
                [decoder_inputs_single] + decoder_states_inputs,
                [decoder_outputs2] + decoder_states2
            )

        except Exception as e:
            print(f"Ошибка при восстановлении моделей энкодера и декодера: {e}")
            sys.exit(1)

    else:
        print("Загрузка и подготовка данных...")
        # Загрузка данных из JSON
        questions, answers = load_data(DATA_FILE)

        print("Токенизация и подготовка данных...")
        # Токенизация и подготовка данных
        question_padded, decoder_input, decoder_output, tokenizer, vocab_size, max_question_len, max_answer_len = preprocess_data(
            questions, answers)

        # Разделение на тренировочные и валидационные наборы
        print("Разделение данных на обучающие и валидационные наборы...")
        encoder_input_train, encoder_input_val, decoder_input_train, decoder_input_val, decoder_output_train, decoder_output_val = train_test_split(
            question_padded,
            decoder_input,
            decoder_output,
            test_size=0.2,
            random_state=42
        )

        print("Построение модели...")
        # Построение модели
        embedding_dim = 64
        latent_dim = 128
        model, encoder_model, decoder_model = build_model(vocab_size, embedding_dim, latent_dim, max_question_len,
                                                          max_answer_len)

        print("Обучение модели...")
        # Обучение модели
        history = train_model(
            model,
            encoder_input_train,
            decoder_input_train,
            decoder_output_train,
            encoder_input_val,
            decoder_input_val,
            decoder_output_val
        )

        # Сохранение модели и токенизатора
        print("Сохранение модели и токенизатора...")
        model.save(MODEL_FILE)
        save_tokenizer(tokenizer, TOKENIZER_FILE)
        print(f"Модель сохранена в '{MODEL_FILE}'.")

    print("Чатбот готов к использованию!")

    # Взаимодействие с пользователем через консоль
    while True:
        try:
            user_question = input('Ваш вопрос (или "выход" для завершения): ')
            if user_question.lower() == 'выход':
                print('До свидания!')
                break
            response = get_answer(
                user_question,
                tokenizer,
                max_question_len,
                max_answer_len,
                encoder_model,
                decoder_model
            )
            print('Ответ:', response)
        except KeyboardInterrupt:
            print('\nДо свидания!')
            sys.exit()
        except Exception as e:
            print(f"Произошла ошибка: {e}")
            continue


if __name__ == "__main__":
    main()