// main.go
package main

import (
	"Learn/utils"
	"bufio"
	"encoding/gob"
	"fmt"
	"gorgonia.org/gorgonia"
	tensor "gorgonia.org/tensor"
	"log"
	"os"
	"strconv"
	"strings"
)

const (
	TrainMode         = "t"
	ValidationMode    = "v"
	DialogueMode      = "d"
	ModelFile         = "models/model.gob"
	VocabFile         = "models/vocab.gob"
	TrainingDataDir   = "train"
	ValidationDataDir = "valid" // Создайте эту папку и наполните данными для валидации
	MaxSeqLen         = 1       // Обработка по одному токену
)

// Model представляет параметры модели
type Model struct {
	W []float64
	b []float64
}

func main() {
	reader := bufio.NewReader(os.Stdin)
	fmt.Print("Введите режим работы (t для обучения, v для валидации, d для диалога): ")
	mode, _ := reader.ReadString('\n')
	mode = strings.TrimSpace(mode)

	switch mode {
	case TrainMode:
		train(reader)
	case ValidationMode:
		validate()
	case DialogueMode:
		dialogue(reader)
	default:
		fmt.Println("Некорректный режим работы.")
	}
}

// must обрабатывает ошибку или возвращает узел
func must(n *gorgonia.Node, err error) *gorgonia.Node {
	if err != nil {
		log.Fatalf("Ошибка: %v", err)
	}
	return n
}

// flatten преобразует двумерный срез в одномерный
func flatten(data [][]float64) []float64 {
	var flat []float64
	for _, row := range data {
		flat = append(flat, row...)
	}
	return flat
}

// PreprocessData преобразует сырые данные в пары (input, target)
func PreprocessData(data []string, vocab utils.Vocabulary) ([][]float64, [][]float64) {
	var input [][]float64
	var target [][]float64

	sampleCount := 0
	for _, line := range data {
		tokens := utils.Tokenize(line)
		for j := 0; j < len(tokens)-1; j++ {
			currentToken := tokens[j]
			nextToken := tokens[j+1]

			inputID, exists := vocab.TokenToID[currentToken]
			if !exists {
				inputID = vocab.TokenToID["<UNK>"]
			}

			targetID, exists := vocab.TokenToID[nextToken]
			if !exists {
				targetID = vocab.TokenToID["<UNK>"]
			}

			inputSeq := make([]float64, len(vocab.TokenToID))
			inputSeq[inputID] = 1.0 // One-hot encoding

			targetSeq := make([]float64, len(vocab.TokenToID))
			targetSeq[targetID] = 1.0 // One-hot encoding

			input = append(input, inputSeq)
			target = append(target, targetSeq)
			sampleCount++
		}
	}
	fmt.Printf("Предобработано %d пар (input, target)\n", sampleCount)
	return input, target
}

// readInt читает строку из reader и преобразует её в int
func readInt(reader *bufio.Reader, prompt string) int {
	for {
		fmt.Print(prompt)
		input, err := reader.ReadString('\n')
		if err != nil {
			log.Fatalf("Ошибка при чтении ввода: %v", err)
		}
		input = strings.TrimSpace(input)
		val, err := strconv.Atoi(input)
		if err != nil {
			fmt.Println("Неверный ввод. Пожалуйста, введите целое число.")
			continue
		}
		return val
	}
}

// readString читает строку из reader после вывода prompt
func readString(reader *bufio.Reader, prompt string) string {
	fmt.Print(prompt)
	input, err := reader.ReadString('\n')
	if err != nil {
		log.Fatalf("Ошибка при чтении ввода: %v", err)
	}
	return strings.TrimSpace(input)
}

// train осуществляет обучение модели
func train(reader *bufio.Reader) {
	// Ввод параметров обучения
	epochs := readInt(reader, "Введите количество эпох: ")
	batchSize := readInt(reader, "Введите размер батча: ")
	maxTrainMessages := readInt(reader, "Введите максимальное количество сообщений для обучения (0 для всех): ")
	optimizerChoice := readString(reader, "Выберите оптимизатор (adam/sgd): ")

	// Загрузка данных
	fmt.Println("Загрузка данных для обучения...")
	data := utils.LoadData(TrainingDataDir, maxTrainMessages)
	totalLoaded := len(data)
	if maxTrainMessages > 0 && totalLoaded > maxTrainMessages {
		data = data[:maxTrainMessages]
		fmt.Printf("Используется %d из %d доступных сообщений.\n", maxTrainMessages, totalLoaded)
	} else {
		fmt.Printf("Используется все %d доступных сообщений.\n", totalLoaded)
	}

	// Построение словаря
	fmt.Println("Построение словаря...")
	vocab := utils.BuildVocabulary(data)
	utils.SaveVocabulary(vocab, VocabFile)
	vocabSize := len(vocab.TokenToID)
	fmt.Printf("Размер словаря: %d\n", vocabSize)

	// Предобработка данных
	fmt.Println("Предобработка данных...")
	input, target := PreprocessData(data, vocab)
	if len(input) == 0 || len(target) == 0 {
		log.Fatalf("Нет данных для обучения после предобработки.")
	}

	// Инициализация графа Gorgonia
	g := gorgonia.NewGraph()

	// Определение узлов ввода и цели
	// Пример для входных и целевых матриц
	x := gorgonia.NewMatrix(g,
		tensor.Float32, // Изменено на Float32
		gorgonia.WithShape(batchSize, vocabSize),
		gorgonia.WithName("x"))

	y := gorgonia.NewMatrix(g,
		tensor.Float32, // Изменено на Float32
		gorgonia.WithShape(batchSize, vocabSize),
		gorgonia.WithName("y"))

	// Параметры модели (W и b)
	W := gorgonia.NewMatrix(g,
		tensor.Float64,
		gorgonia.WithShape(vocabSize, vocabSize),
		gorgonia.WithName("W"),
		gorgonia.WithInit(gorgonia.GlorotN(1.0)))

	b := gorgonia.NewMatrix(g,
		tensor.Float64,
		gorgonia.WithShape(1, vocabSize),
		gorgonia.WithName("b"),
		gorgonia.WithInit(gorgonia.Zeroes()))

	// Прогнозирование: y_pred = x * W + b
	pred := must(gorgonia.Mul(x, W))
	pred = must(gorgonia.BroadcastAdd(pred, b, nil, []byte{0}))

	// Функция потерь: среднеквадратичная ошибка (лучше использовать кросс-энтропию для классификации)
	errorLayer := must(gorgonia.Sub(pred, y))
	squared := must(gorgonia.Square(errorLayer))
	sumSquared, errCreateSum := gorgonia.Sum(squared)
	if errCreateSum != nil {
		log.Fatalf("Ошибка создания узла Sum: %v", errCreateSum)
	}
	elements := float64(batchSize * vocabSize)
	loss := must(gorgonia.Div(sumSquared, gorgonia.NewConstant(elements)))

	// Создание машины ленточных вычислений (Tape Machine)
	machine := gorgonia.NewTapeMachine(g, gorgonia.BindDualValues(W, b))

	// Определение оптимизатора
	var solver gorgonia.Solver
	switch strings.ToLower(optimizerChoice) {
	case "adam":
		solver = gorgonia.NewAdamSolver(gorgonia.WithLearnRate(0.001))
	case "sgd":
		solver = gorgonia.NewVanillaSolver(gorgonia.WithLearnRate(0.01))
	default:
		log.Fatalf("Неизвестный оптимизатор: %s", optimizerChoice)
	}

	// Цикл обучения
	fmt.Println("Начало обучения...")
	for epoch := 1; epoch <= epochs; epoch++ {
		totalLoss := 0.0
		numBatches := 0
		for i := 0; i < len(input); i += batchSize {
			end := i + batchSize
			if end > len(input) {
				end = len(input)
			}
			currentBatchSize := end - i

			batchInput := input[i:end]
			batchTarget := target[i:end]

			// Если последний батч меньше batchSize, заполнить его нулями
			if currentBatchSize < batchSize {
				padding := batchSize - currentBatchSize
				for j := 0; j < padding; j++ {
					batchInput = append(batchInput, make([]float64, vocabSize))
					batchTarget = append(batchTarget, make([]float64, vocabSize))
				}
			}

			// Создание тензоров для текущего батча
			xVal := tensor.New(tensor.WithShape(batchSize, vocabSize), tensor.Of(tensor.Float64), tensor.WithBacking(flatten(batchInput)))
			yVal := tensor.New(tensor.WithShape(batchSize, vocabSize), tensor.Of(tensor.Float64), tensor.WithBacking(flatten(batchTarget)))

			// Ввод значений в граф
			if err := gorgonia.Let(x, xVal); err != nil {
				log.Fatalf("Ошибка установки x: %v", err)
			}
			if err := gorgonia.Let(y, yVal); err != nil {
				log.Fatalf("Ошибка установки y: %v", err)
			}

			// Выполнение форвардного и бэкового прохода
			if err := machine.RunAll(); err != nil {
				log.Fatalf("Ошибка выполнения графа: %v", err)
			}

			// Получение значения потерь
			lossVal, ok := loss.Value().Data().([]float64)
			if !ok {
				log.Fatalf("Не удалось преобразовать значение потерь")
			}
			currentLoss := lossVal[0]
			totalLoss += currentLoss
			numBatches++

			// Обновление параметров
			if err := solver.Step(gorgonia.NodesToValueGrads([]*gorgonia.Node{W, b})); err != nil {
				log.Fatalf("Ошибка обновления параметров: %v", err)
			}

			// Сброс машины для следующего шага
			machine.Reset()

			// Прогресс
			epochProgress := float64(end) / float64(len(input)) * 100
			if epochProgress > 100 {
				epochProgress = 100
			}
			overallProgress := float64(epoch) / float64(epochs) * 100
			if overallProgress > 100 {
				overallProgress = 100
			}
			fmt.Printf("Epoch %d: Processed batch from %d to %d (%.2f%% complete)(%.2f%% all complete)\n",
				epoch, i, end, epochProgress, overallProgress)
		}
		avgLoss := totalLoss / float64(numBatches)
		fmt.Printf("Epoch %d завершен. Средняя потеря: %.4f\n", epoch, avgLoss)
	}

	// Сохранение модели
	fmt.Println("Сохранение модели...")
	// Извлечение значений весов
	WData := extractValues(W)
	bData := extractValues(b)

	model := Model{
		W: WData,
		b: bData,
	}
	saveModel(&model)
	fmt.Println("Обучение завершено и модель сохранена.")
}

// extractValues извлекает значения из узла
func extractValues(node *gorgonia.Node) []float64 {
	value := node.Value()
	if value == nil {
		log.Fatalf("Узел %s не имеет значения", node.Name())
	}
	data, ok := value.Data().([]float64)
	if !ok {
		log.Fatalf("Невозможно преобразовать данные узла %s", node.Name())
	}
	return data
}

// saveModel сохраняет модель в файл
func saveModel(m *Model) {
	// Проверка и создание директории models, если она не существует
	if _, err := os.Stat("models"); os.IsNotExist(err) {
		err := os.MkdirAll("models", os.ModePerm)
		if err != nil {
			log.Fatalf("Не удалось создать директорию models: %v", err)
		}
	}

	file, err := os.Create(ModelFile)
	if err != nil {
		log.Fatalf("Не удалось создать файл модели: %v", err)
	}
	defer file.Close()

	encoder := gob.NewEncoder(file)
	err = encoder.Encode(m)
	if err != nil {
		log.Fatalf("Не удалось закодировать модель: %v", err)
	}
}

// loadModel загружает модель из файла
func loadModel() *Model {
	file, err := os.Open(ModelFile)
	if err != nil {
		log.Fatalf("Не удалось открыть файл модели: %v", err)
	}
	defer file.Close()

	var m Model
	decoder := gob.NewDecoder(file)
	err = decoder.Decode(&m)
	if err != nil {
		log.Fatalf("Не удалось декодировать модель: %v", err)
	}
	return &m
}

// validate выполняет валидацию модели
func validate() {
	// Загрузка модели
	model := loadModel()

	// Загрузка словаря
	vocab := utils.LoadVocabulary(VocabFile)
	vocabSize := len(vocab.TokenToID)
	fmt.Printf("Размер словаря: %d\n", vocabSize)

	// Загрузка валидационных данных
	fmt.Println("Загрузка данных для валидации...")
	data := utils.LoadData(ValidationDataDir, 10000) // Измените лимит при необходимости
	fmt.Printf("Загружено %d валидационных записей\n", len(data))

	// Предобработка данных
	input, target := PreprocessData(data, vocab)
	if len(input) == 0 || len(target) == 0 {
		log.Fatalf("Нет данных для валидации после предобработки.")
	}

	// Инициализация графа
	g := gorgonia.NewGraph()

	// Определение узлов ввода и цели
	x := gorgonia.NewMatrix(g,
		tensor.Float64,
		gorgonia.WithShape(len(input), vocabSize),
		gorgonia.WithName("x"))

	y := gorgonia.NewMatrix(g,
		tensor.Float64,
		gorgonia.WithShape(len(target), vocabSize),
		gorgonia.WithName("y"))

	// Параметры модели
	WNode := gorgonia.NewMatrix(g,
		tensor.Float64,
		gorgonia.WithShape(vocabSize, vocabSize),
		gorgonia.WithName("W"))

	bNode := gorgonia.NewMatrix(g,
		tensor.Float64,
		gorgonia.WithShape(1, vocabSize),
		gorgonia.WithName("b"))

	// Присвоение загруженных значений
	WVal := tensor.New(tensor.WithShape(vocabSize, vocabSize), tensor.Of(tensor.Float64), tensor.WithBacking(model.W))
	bVal := tensor.New(tensor.WithShape(1, vocabSize), tensor.Of(tensor.Float64), tensor.WithBacking(model.b))

	if err := gorgonia.Let(WNode, WVal); err != nil {
		log.Fatalf("Ошибка при установке W: %v", err)
	}
	if err := gorgonia.Let(bNode, bVal); err != nil {
		log.Fatalf("Ошибка при установке b: %v", err)
	}

	// Прогнозирование: y_pred = x * W + b
	pred := must(gorgonia.Mul(x, WNode))
	pred = must(gorgonia.BroadcastAdd(pred, bNode, nil, []byte{0}))

	// Функция потерь: среднеквадратичная ошибка
	errorLayer := must(gorgonia.Sub(pred, y))
	squared := must(gorgonia.Square(errorLayer))
	sumSquared, errCreateSum := gorgonia.Sum(squared)
	if errCreateSum != nil {
		log.Fatalf("Ошибка создания узла Sum: %v", errCreateSum)
	}
	elements := float64(len(input) * vocabSize)
	loss := must(gorgonia.Div(sumSquared, gorgonia.NewConstant(elements)))

	// Создание машины ленточных вычислений (Tape Machine)
	machine := gorgonia.NewTapeMachine(g)

	// Создание тензоров для валидации
	xVal := tensor.New(tensor.WithShape(len(input), vocabSize), tensor.Of(tensor.Float64), tensor.WithBacking(flatten(input)))
	yVal := tensor.New(tensor.WithShape(len(target), vocabSize), tensor.Of(tensor.Float64), tensor.WithBacking(flatten(target)))

	// Ввод значений в граф
	if err := gorgonia.Let(x, xVal); err != nil {
		log.Fatalf("Ошибка при установке x: %v", err)
	}
	if err := gorgonia.Let(y, yVal); err != nil {
		log.Fatalf("Ошибка при установке y: %v", err)
	}

	// Выполнение графа
	fmt.Println("Выполнение валидации...")
	if err := machine.RunAll(); err != nil {
		log.Fatalf("Ошибка выполнения графа: %v", err)
	}

	// Получение значения потерь
	lossVal, ok := loss.Value().Data().([]float64)
	if !ok {
		log.Fatalf("Не удалось преобразовать значение потерь")
	}
	fmt.Printf("Validation Loss: %.4f\n", lossVal[0])

	// Сброс машины
	machine.Reset()
}

// dialogue осуществляет диалог с ИИ
func dialogue(reader *bufio.Reader) {
	// Загрузка модели
	model := loadModel()

	// Загрузка словаря
	vocab := utils.LoadVocabulary(VocabFile)
	vocabSize := len(vocab.TokenToID)
	idToToken := make(map[int]string)
	for id, token := range vocab.IDToToken {
		idToToken[id] = token
	}

	// Инициализация графа
	g := gorgonia.NewGraph()

	// Определение узлов ввода
	x := gorgonia.NewMatrix(g,
		tensor.Float64,
		gorgonia.WithShape(1, vocabSize),
		gorgonia.WithName("x"))

	// Параметры модели
	WNode := gorgonia.NewMatrix(g,
		tensor.Float64,
		gorgonia.WithShape(vocabSize, vocabSize),
		gorgonia.WithName("W"))

	bNode := gorgonia.NewMatrix(g,
		tensor.Float64,
		gorgonia.WithShape(1, vocabSize),
		gorgonia.WithName("b"))

	// Присвоение загруженных значений
	WVal := tensor.New(tensor.WithShape(vocabSize, vocabSize), tensor.Of(tensor.Float64), tensor.WithBacking(model.W))
	bVal := tensor.New(tensor.WithShape(1, vocabSize), tensor.Of(tensor.Float64), tensor.WithBacking(model.b))

	if err := gorgonia.Let(WNode, WVal); err != nil {
		log.Fatalf("Ошибка при установке W: %v", err)
	}
	if err := gorgonia.Let(bNode, bVal); err != nil {
		log.Fatalf("Ошибка при установке b: %v", err)
	}

	// Прогнозирование: y_pred = x * W + b
	pred := must(gorgonia.Mul(x, WNode))
	pred = must(gorgonia.BroadcastAdd(pred, bNode, nil, []byte{0}))

	// Создание машины ленточных вычислений (Tape Machine)
	machine := gorgonia.NewTapeMachine(g)

	fmt.Println("Начните диалог с ИИ. Введите 'exit' для выхода.")

	for {
		fmt.Print("Вы: ")
		inputStr, err := reader.ReadString('\n')
		if err != nil {
			log.Fatalf("Ошибка при чтении ввода: %v", err)
		}
		inputStr = strings.TrimSpace(inputStr)
		if strings.ToLower(inputStr) == "exit" {
			break
		}

		// Токенизация и преобразование в ID
		tokens := utils.Tokenize(inputStr)
		if len(tokens) == 0 {
			fmt.Println("ИИ: <Нет ответа>")
			continue
		}

		// Берём последний токен для предсказания следующего
		lastToken := tokens[len(tokens)-1]
		inputID, exists := vocab.TokenToID[lastToken]
		if !exists {
			inputID = vocab.TokenToID["<UNK>"]
		}

		// One-hot кодирование
		inputSeq := make([]float64, vocabSize)
		inputSeq[inputID] = 1.0

		// Создание тензора
		xVal := tensor.New(tensor.WithShape(1, vocabSize), tensor.Of(tensor.Float64), tensor.WithBacking(inputSeq))

		// Ввод значения в граф
		if err := gorgonia.Let(x, xVal); err != nil {
			log.Fatalf("Ошибка установки x: %v", err)
		}

		// Выполнение графа
		if err := machine.RunAll(); err != nil {
			log.Fatalf("Ошибка выполнения графа: %v", err)
		}

		// Получение предсказания
		predVal, ok := pred.Value().Data().([]float64)
		if !ok {
			log.Fatalf("Не удалось преобразовать предсказание")
		}

		// Поиск индекса с наибольшим значением
		maxIdx := 0
		maxProb := predVal[0]
		for i, prob := range predVal {
			if prob > maxProb {
				maxProb = prob
				maxIdx = i
			}
		}

		// Получение слова по ID
		response := "<UNK>"
		if token, exists := vocab.IDToToken[maxIdx]; exists {
			response = token
		}

		fmt.Printf("ИИ: %s\n", response)

		// Сброс машины для следующего шага
		machine.Reset()
	}
}


// utils/loader.go
package utils

import (
	"encoding/csv"
	"log"
	"os"
	"path/filepath"
	"strings"
)

// LoadData рекурсивно загружает данные из всех .txt и .csv файлов в директории и её поддиректориях.
// Если maxMessages <= 0, загружает все доступные сообщения.
func LoadData(directory string, maxMessages int) []string {
	var data []string
	err := filepath.Walk(directory, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			log.Printf("Не удалось получить доступ к пути %s: %v", path, err)
			return nil
		}
		if !info.IsDir() {
			if strings.HasSuffix(strings.ToLower(info.Name()), ".txt") {
				content, err := LoadTXT(path)
				if err != nil {
					log.Printf("Ошибка загрузки TXT файла %s: %v", path, err)
					return nil
				}
				data = append(data, content...)
			} else if strings.HasSuffix(strings.ToLower(info.Name()), ".csv") {
				content, err := LoadCSV(path)
				if err != nil {
					log.Printf("Ошибка загрузки CSV файла %s: %v", path, err)
					return nil
				}
				data = append(data, content...)
			}
			if maxMessages > 0 && len(data) >= maxMessages {
				return filepath.SkipDir
			}
		}
		return nil
	})
	if err != nil && err != filepath.SkipDir {
		log.Fatalf("Ошибка при обходе директорий: %v", err)
	}
	if maxMessages > 0 && len(data) > maxMessages {
		data = data[:maxMessages]
	}
	return data
}

// LoadTXT загружает данные из TXT файла.
func LoadTXT(filepath string) ([]string, error) {
	content, err := os.ReadFile(filepath)
	if err != nil {
		return nil, err
	}
	lines := strings.Split(string(content), "\n")
	var records []string
	for _, line := range lines {
		trimmed := strings.TrimSpace(line)
		if trimmed != "" {
			records = append(records, trimmed)
		}
	}
	return records, nil
}

// LoadCSV загружает данные из CSV файла.
// Предполагается, что сообщения находятся в первом столбце.
func LoadCSV(filepath string) ([]string, error) {
	file, err := os.Open(filepath)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	reader := csv.NewReader(file)
	records, err := reader.ReadAll()
	if err != nil {
		return nil, err
	}

	var messages []string
	for _, record := range records {
		// Пропускаем заголовок, если он существует
		if strings.HasPrefix(strings.ToLower(record[0]), "tweet_id") {
			continue
		}

		if len(record) > 4 { // Проверяем, что есть пятый столбец
			trimmed := strings.TrimSpace(record[4])
			if trimmed != "" {
				messages = append(messages, trimmed)
			}
		}
	}
	return messages, nil
}

// utils/tokenizer.go
package utils

import (
	"strings"
	"unicode"
)

// Tokenize разбивает текст на токены.
func Tokenize(text string) []string {
	// Приведение к нижнему регистру
	text = strings.ToLower(text)
	// Удаление пунктуации
	var sb strings.Builder
	for _, r := range text {
		if unicode.IsLetter(r) || unicode.IsNumber(r) || unicode.IsSpace(r) {
			sb.WriteRune(r)
		}
	}
	cleanText := sb.String()
	// Разбиение по пробелам
	tokens := strings.Fields(cleanText)
	return tokens
}

// utils/vocabulary.go
package utils

import (
	"encoding/gob"
	"log"
	"os"
	"strings"
)

// Vocabulary хранит соответствие между токенами и их уникальными идентификаторами.
type Vocabulary struct {
	TokenToID map[string]int
	IDToToken map[int]string
}

// BuildVocabulary строит словарь токенов из данных.
func BuildVocabulary(data []string) Vocabulary {
	vocab := Vocabulary{
		TokenToID: make(map[string]int),
		IDToToken: make(map[int]string),
	}
	vocab.TokenToID["<UNK>"] = 0
	vocab.IDToToken[0] = "<UNK>"
	idx := 1
	for _, line := range data {
		tokens := Tokenize(line)
		for _, token := range tokens {
			if _, exists := vocab.TokenToID[token]; !exists {
				vocab.TokenToID[token] = idx
				vocab.IDToToken[idx] = token
				idx++
			}
		}
	}
	return vocab
}

// SaveVocabulary сохраняет словарь в файл.
func SaveVocabulary(vocab Vocabulary, filepath string) {
	// Проверка и создание директории models, если она не существует
	dir := filepath[:strings.LastIndex(filepath, "/")]
	if _, err := os.Stat(dir); os.IsNotExist(err) && dir != "" {
		err := os.MkdirAll(dir, os.ModePerm)
		if err != nil {
			log.Fatalf("Не удалось создать директорию %s: %v", dir, err)
		}
	}

	file, err := os.Create(filepath)
	if err != nil {
		log.Fatalf("Не удалось создать файл словаря: %v", err)
	}
	defer file.Close()

	encoder := gob.NewEncoder(file)
	err = encoder.Encode(vocab)
	if err != nil {
		log.Fatalf("Не удалось сохранить словарь: %v", err)
	}
}

// LoadVocabulary загружает словарь из файла.
func LoadVocabulary(filepath string) Vocabulary {
	file, err := os.Open(filepath)
	if err != nil {
		log.Fatalf("Не удалось открыть файл словаря: %v", err)
	}
	defer file.Close()

	var vocab Vocabulary
	decoder := gob.NewDecoder(file)
	err = decoder.Decode(&vocab)
	if err != nil {
		log.Fatalf("Не удалось декодировать словарь: %v", err)
	}
	return vocab
}





Введите режим работы (t для обучения, v для валидации, d для диалога): t
Введите количество эпох: 10
Введите размер батча: 150
Введите максимальное количество сообщений для обучения (0 для всех): 10000
Выберите оптимизатор (adam/sgd): adam
Загрузка данных для обучения...
Используется все 10000 доступных сообщений.
Построение словаря...
Размер словаря: 16528
Предобработка данных...
runtime: VirtualAlloc of 139264 bytes failed with errno=1455
fatal error: out of memory

runtime stack:
runtime.throw({0x8a1ab0?, 0xca0fe99000?})
        C:/Users/79685/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.22.6.windows-amd64/src/runtime/panic.go:1023 +0x65 fp=0xa536fffa28 sp=0xa536fff9f8 pc=0xdd205
runtime.sysUsedOS(0xca0fe94000, 0x22000)
        C:/Users/79685/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.22.6.windows-amd64/src/runtime/mem_windows.go:83 +0x1bb fp=0xa536fffa88 sp=0xa536fffa28 pc=0xbbe9b
runtime.sysUsed(...)
        C:/Users/79685/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.22.6.windows-amd64/src/runtime/mem.go:77
runtime.(*mheap).allocSpan(0xded1a0, 0x11, 0x0, 0x1)
        C:/Users/79685/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.22.6.windows-amd64/src/runtime/mheap.go:1347 +0x487 fp=0xa536fffb28 sp=0xa536fffa88 pc=0xce327
runtime.(*mheap).alloc.func1()
        C:/Users/79685/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.22.6.windows-amd64/src/runtime/mheap.go:964 +0x5c fp=0xa536fffb70 sp=0xa536fffb28 pc=0xcdadc
runtime.systemstack(0xc0002241c0)
        C:/Users/79685/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.22.6.windows-amd64/src/runtime/asm_amd64.s:509 +0x49 fp=0xa536fffb80 sp=0xa536fffb70 pc=0x10d929