// 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       // Изменено на 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)
		if len(tokens) < 2 {
			continue // Пропуск строк с менее чем 2 токенами
		}
		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.Float64,
		gorgonia.WithShape(batchSize, vocabSize),
		gorgonia.WithName("x"))

	y := gorgonia.NewMatrix(g,
		tensor.Float64,
		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()

			// Прогресс
			epochBatchProgress := float64(end) / float64(len(input)) * 100
			if epochBatchProgress > 100 {
				epochBatchProgress = 100
			}
			overallProgress := float64(epoch) / float64(epochs) * 100
			fmt.Printf("Epoch %d: Processed batch from %d to %d (%.2f%% complete)(%.2f%% all complete)\n",
				epoch, i, end, epochBatchProgress, 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(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(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 len(record) > 0 {
			trimmed := strings.TrimSpace(record[0])
			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) {
		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 доступных сообщений.
Построение словаря...
Размер словаря: 10001
Предобработка данных...
Предобработано 0 пар (input, target)
2024/11/04 16:06:34 Нет данных для обучения после предобработки.


он их фильтрует неправльно должен получать обсалютнот все соощения

тут также

Введите режим работы (t для обучения, v для валидации, d для диалога): v
Размер словаря: 10001
Загрузка данных для валидации...
Загружено 1094 валидационных записей
Предобработано 0 пар (input, target)
2024/11/04 16:08:08 Нет данных для валидации после предобработки.


и вместо нормального ответа он дает цифры а не ответ созданный на базе обувения

Введите режим работы (t для обучения, v для валидации, d для диалога): d
Начните диалог с ИИ. Введите 'exit' для выхода.
Вы: hi
ИИ: 12818
Вы: hello
ИИ: 12818
Вы: 


помоги написать языковую модель ИИ на golang gorgonia

помоги написать обучение валидацию и диалог с ИИ

в training сделай обучение нейросети и сохранение данных обучения в нужные файлы из который уже ИИ будет брать данные для валидации и для диалога чтобы проверить на сколько неросеть правильно справляеться с заданиями и на сколько правильно она составляет ответ

Enter mode of operation (t for training, v for validation, d for dialogue): t
Loaded 100000 training records
Epoch 1: Processed batch from 0 to 150 (0.15% complete)
Epoch 1: Processed batch from 150 to 300 (0.30% complete)
Epoch 1: Processed batch from 300 to 450 (0.45% complete)
Epoch 1: Processed batch from 450 to 600 (0.60% complete)
Epoch 1: Processed batch from 600 to 750 (0.75% complete)
Epoch 1: Processed batch from 750 to 900 (0.90% complete)
Epoch 1: Processed batch from 900 to 1050 (1.05% complete)
Epoch 1: Processed batch from 1050 to 1200 (1.20% complete)
Epoch 1: Processed batch from 1200 to 1350 (1.35% complete)
Epoch 1: Processed batch from 1350 to 1500 (1.50% complete)(1.50% all complete)

и добавь

Epoch 1: Processed batch from 1350 to 1500 (1.50% complete)(1.50% all complete) где первое в скобках это это на сколько заверешено эпоха а во втророй все в скобках общй прогресс всех эпох

при выборе t в папке проекта выбирается аппка train в ней есть как csv файлы так и txt они там тоже расскиданы по папкам и должны искаться скриптом и на основе сообщений в них ИИ будет обучаться и как я уже писал данные обученяи будут схраняться

при v будут также браться файлы csv или txt и по сохраненным данным обучения будет валидироваться то есть проверятьс на сколько ИИ может отвечать на вопросы

и в диалоге можно будет анпсиать что угодно и ИИ по данным обучения будет составлять ответ на запрос

dataset всех сообщений для ИИ это диалоги людей из твиттера

ТОКЕНИЗИРУЙ ДАННЫЕ ДЛЯ ИИ Tokenize

также оптимизируй скорость обучения ИИ чтобы можно быол выбрать batchSize optimizer := gorgonia.NewAdamSolver(gorgonia.WithLearnRate(0.001)) и такое либо другое

ии дожен выдавать нормальный ответ внятный

чтобы можно было выбирать количество эпох

в train моджет быть много аппок папка/папка/папка/папка/... незнаю сколько и txt файлы такого типа [298954459172700181] [part 1].txt [298954459172700181] [part 2].txt [298954459172700181] [part 3].txt

сам напиши все функции целиком и полностью все загрузки сохранения и обучнеия и все остальное

чтобы можно было выбирать макисмальное количество сообщений для обучения елси это число больше всех найденных то беруться все найденные если меньше то береться ровно столько сколько ты написал maxTrainMessages

golang gorgonia