// 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       // Обработка по одному токену
	EmbeddingSize     = 128     // Размер вектора Embedding
)

type Model struct {
	E []float32
	W []float32
	B []float32
}

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
}

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

	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>"]
			}

			input = append(input, inputID)
			target = append(target, targetID)
			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, 5) // Фильтрация токенов, встречающихся менее 5 раз
	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.NewVector(g,
		tensor.Int,
		gorgonia.WithShape(batchSize),
		gorgonia.WithName("x"))

	// Целевые данные
	y := gorgonia.NewVector(g,
		tensor.Int,
		gorgonia.WithShape(batchSize),
		gorgonia.WithName("y"))

	// Embedding слой
	E := gorgonia.NewMatrix(g,
		tensor.Float32,
		gorgonia.WithShape(vocabSize, EmbeddingSize),
		gorgonia.WithName("E"),
		gorgonia.WithInit(gorgonia.GlorotN(1.0)))

	// Получение embedding для входов
	embedded := must(gorgonia.ColwiseLookup(E, x))

	// Линейный слой
	W := gorgonia.NewMatrix(g,
		tensor.Float32,
		gorgonia.WithShape(EmbeddingSize, vocabSize),
		gorgonia.WithName("W"),
		gorgonia.WithInit(gorgonia.GlorotN(1.0)))

	b := gorgonia.NewVector(g,
		tensor.Float32,
		gorgonia.WithShape(vocabSize),
		gorgonia.WithName("b"),
		gorgonia.WithInit(gorgonia.Zeroes()))

	// Прогнозирование: logits = E[x] * W + b
	logits := must(gorgonia.Must(gorgonia.Mul(embedded, W)))
	logits = must(gorgonia.Must(gorgonia.Add(logits, b)))

	// Функция потерь: Categorical Cross-Entropy
	loss := must(gorgonia.Must(gorgonia.CrossEntropy(logits, y)))

	// Добавление потерь к графу
	gorgonia.WithLearnables(W, b, E)

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

	// Определение оптимизатора
	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 := float32(0)
		numBatches := 0
		for i := 0; i < len(input); i += batchSize {
			end := i + batchSize
			if end > len(input) {
				end = len(input)
			}
			currentBatchSize := end - i

			// Если последний батч меньше batchSize, пропускаем его
			if currentBatchSize != batchSize {
				continue
			}

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

			// Создание тензоров для текущего батча
			xVal := tensor.New(tensor.WithShape(batchSize), tensor.Of(tensor.Int), tensor.WithBacking(batchInput))
			yVal := tensor.New(tensor.WithShape(batchSize), tensor.Of(tensor.Int), tensor.WithBacking(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().([]float32)
			if !ok {
				log.Fatalf("Не удалось преобразовать значение потерь")
			}
			currentLoss := lossVal[0]
			totalLoss += currentLoss
			numBatches++

			// Обновление параметров
			if err := solver.Step(gorgonia.NodesToValueGrads([]*gorgonia.Node{W, b, E})); 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 / float32(numBatches)
		fmt.Printf("Epoch %d завершен. Средняя потеря: %.4f\n", epoch, avgLoss)
	}

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

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

// extractValues извлекает значения из узла
func extractValues(node *gorgonia.Node) []float32 {
	value := node.Value()
	if value == nil {
		log.Fatalf("Узел %s не имеет значения", node.Name())
	}
	data, ok := value.Data().([]float32)
	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.NewVector(g,
		tensor.Int,
		gorgonia.WithShape(len(input)),
		gorgonia.WithName("x"))

	// Целевые данные
	y := gorgonia.NewVector(g,
		tensor.Int,
		gorgonia.WithShape(len(target)),
		gorgonia.WithName("y"))

	// Embedding слой
	E := gorgonia.NewMatrix(g,
		tensor.Float32,
		gorgonia.WithShape(vocabSize, EmbeddingSize),
		gorgonia.WithName("E"))

	// Линейный слой
	W := gorgonia.NewMatrix(g,
		tensor.Float32,
		gorgonia.WithShape(EmbeddingSize, vocabSize),
		gorgonia.WithName("W"))

	b := gorgonia.NewVector(g,
		tensor.Float32,
		gorgonia.WithShape(vocabSize),
		gorgonia.WithName("b"))

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

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

	// Получение embedding для входов
	embedded := must(gorgonia.ColwiseLookup(E, x))

	// Прогнозирование: logits = E[x] * W + b
	logits := must(gorgonia.Must(gorgonia.Mul(embedded, W)))
	logits = must(gorgonia.Must(gorgonia.Add(logits, b)))

	// Функция потерь: Categorical Cross-Entropy
	loss := must(gorgonia.Must(gorgonia.CrossEntropy(logits, y)))

	// Добавление потерь к графу
	gorgonia.WithLearnables(W, b, E)

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

	// Создание тензоров для валидации
	xVal := tensor.New(tensor.WithShape(len(input)), tensor.Of(tensor.Int), tensor.WithBacking(input))
	yVal := tensor.New(tensor.WithShape(len(target)), tensor.Of(tensor.Int), tensor.WithBacking(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().([]float32)
	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.NewVector(g,
		tensor.Int,
		gorgonia.WithShape(1),
		gorgonia.WithName("x"))

	// Embedding слой
	E := gorgonia.NewMatrix(g,
		tensor.Float32,
		gorgonia.WithShape(vocabSize, EmbeddingSize),
		gorgonia.WithName("E"))

	// Линейный слой
	W := gorgonia.NewMatrix(g,
		tensor.Float32,
		gorgonia.WithShape(EmbeddingSize, vocabSize),
		gorgonia.WithName("W"))

	b := gorgonia.NewVector(g,
		tensor.Float32,
		gorgonia.WithShape(vocabSize),
		gorgonia.WithName("b"))

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

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

	// Получение embedding для входа
	embedded := must(gorgonia.ColwiseLookup(E, x))

	// Прогнозирование: logits = E[x] * W + b
	logits := must(gorgonia.Must(gorgonia.Mul(embedded, W)))
	logits = must(gorgonia.Must(gorgonia.Add(logits, b)))

	// Softmax для получения вероятностей
	probs := must(gorgonia.SoftMax(logits))

	// Создание машины ленточных вычислений (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>"]
		}

		// Создание тензора
		xVal := tensor.New(tensor.WithShape(1), tensor.Of(tensor.Int), tensor.WithBacking([]int{inputID}))

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

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

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

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

		// Получение слова по ID
		response := "<UNK>"
		if token, exists := 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
}



Unresolved reference 'ColwiseLookup' не существует исправь 

Not enough arguments in call to 'must'
не существует испраь

Not enough arguments in call to 'must'
Unresolved reference 'CrossEntropy'

Not enough arguments in call to 'must'

	// Прогнозирование: logits = E[x] * W + b
	logits := must(gorgonia.Must(gorgonia.Mul(embedded, W)))
	logits = must(gorgonia.Must(gorgonia.Add(logits, b)))

	// Функция потерь: Categorical Cross-Entropy
	loss := must(gorgonia.Must(gorgonia.CrossEntropy(logits, y)))

	// Добавление потерь к графу
	gorgonia.WithLearnables(W, b, E)

Unresolved reference 'WithLearnables'
не существует 

Unresolved reference 'ColwiseLookup' не существует