// main.go
package main

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

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

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("Некорректный режим работы.")
	}
}

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

	for _, line := range data {
		tokens := utils.Tokenize(line)
		var inputSeq []float64
		var targetSeq []float64
		for j, token := range tokens {
			if j >= maxSeqLen {
				break
			}
			id, exists := vocab.TokenToID[token]
			if !exists {
				id = vocab.TokenToID["<UNK>"]
			}
			inputSeq = append(inputSeq, float64(id))
			// Используем следующий токен как цель
			if j < len(tokens)-1 {
				nextToken := tokens[j+1]
				nextID, exists := vocab.TokenToID[nextToken]
				if !exists {
					nextID = vocab.TokenToID["<UNK>"]
				}
				targetSeq = append(targetSeq, float64(nextID))
			} else {
				targetSeq = append(targetSeq, float64(vocab.TokenToID["<UNK>"]))
			}
		}
		// Паддинг до maxSeqLen
		for len(inputSeq) < maxSeqLen {
			inputSeq = append(inputSeq, 0)
			targetSeq = append(targetSeq, 0)
		}
		input = append(input, inputSeq)
		target = append(target, targetSeq)
	}
	return input, target
}

// Обучение модели
func train(reader *bufio.Reader) {
	// Ввод параметров обучения
	fmt.Print("Введите количество эпох: ")
	var epochs int
	_, err := fmt.Scanf("%d", &epochs)
	if err != nil {
		log.Fatalf("Неверный ввод для эпох: %v", err)
	}

	fmt.Print("Введите размер батча: ")
	var batchSize int
	_, err = fmt.Scanf("%d", &batchSize)
	if err != nil {
		log.Fatalf("Неверный ввод для размера батча: %v", err)
	}

	fmt.Print("Введите максимальное количество сообщений для обучения (0 для всех): ")
	var maxTrainMessages int
	_, err = fmt.Scanf("%d", &maxTrainMessages)
	if err != nil {
		log.Fatalf("Неверный ввод для максимального количества сообщений: %v", err)
	}

	// Загрузка данных
	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, MaxSeqLen)

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

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

	y := gorgonia.NewMatrix(g,
		tensor.Float64,
		gorgonia.WithShape(batchSize, MaxSeqLen),
		gorgonia.WithName("y"))

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

	b := gorgonia.NewMatrix(g,
		tensor.Float64,
		gorgonia.WithShape(1, vocabSize), // int, а не float64
		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 * MaxSeqLen)
	loss := must(gorgonia.Div(sumSquared, gorgonia.NewConstant(elements)))

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

	// Определение оптимизатора
	solver := gorgonia.NewAdamSolver(gorgonia.WithLearnRate(0.001))

	// Цикл обучения
	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, MaxSeqLen))
					batchTarget = append(batchTarget, make([]float64, MaxSeqLen))
				}
			}

			// Создание тензоров для текущего батча
			xVal := tensor.New(tensor.WithShape(batchSize, MaxSeqLen), tensor.Of(tensor.Float64), tensor.WithBacking(flatten(batchInput)))
			yVal := tensor.New(tensor.WithShape(batchSize, MaxSeqLen), 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
			overallProgress := float64(epoch) / float64(epochs) * 100
			fmt.Printf("Epoch %d: Processed batch from %d to %d (%.2f%% завершено)(%.2f%% общий прогресс)\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) {
	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, MaxSeqLen)

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

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

	y := gorgonia.NewMatrix(g,
		tensor.Float64,
		gorgonia.WithShape(len(target), MaxSeqLen),
		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) * MaxSeqLen)
	loss := must(gorgonia.Div(sumSquared, gorgonia.NewConstant(elements)))

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

	// Ввод значений
	xVal := tensor.New(tensor.WithShape(len(input), MaxSeqLen), tensor.Of(tensor.Float64), tensor.WithBacking(flatten(input)))
	yVal := tensor.New(tensor.WithShape(len(target), MaxSeqLen), 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()
}

// Диалог с ИИ
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, MaxSeqLen),
		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("Вы: ")
		input, _ := reader.ReadString('\n')
		input = strings.TrimSpace(input)
		if strings.ToLower(input) == "exit" {
			break
		}

		// Токенизация и преобразование в ID
		tokens := utils.Tokenize(input)
		var inputIDs []float64
		for _, token := range tokens {
			if id, exists := vocab.TokenToID[token]; exists {
				inputIDs = append(inputIDs, float64(id))
			} else {
				inputIDs = append(inputIDs, 0) // <UNK>
			}
		}

		// Паддинг до MaxSeqLen
		inputSeq := make([]float64, MaxSeqLen)
		for i, id := range inputIDs {
			if i >= MaxSeqLen {
				break
			}
			inputSeq[i] = id
		}

		// Создание тензора
		xVal := tensor.New(tensor.WithShape(1, MaxSeqLen), 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()
	}
}

// Сохранение модели
func saveModel(m *Model) {
	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)
	}
}

// Загрузка модели
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
}

// utils/loader.go
package utils

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

// LoadData рекурсивно загружает данные из всех .txt файлов в директории и её поддиректориях.
// Если 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() && 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...)
			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
}


// utils/vocabulary.go
package utils

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

// 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) {
	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
}


// 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
}


было задание 

помоги написать языковую модель ИИ на 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

испправь

.\main.go:165:10: undefined: must
.\main.go:166:9: undefined: must
.\main.go:169:16: undefined: must
.\main.go:170:13: undefined: must
.\main.go:176:10: undefined: must
.\main.go:209:109: undefined: flatten
.\main.go:210:109: undefined: flatten
.\main.go:366:10: undefined: must
.\main.go:534:6: saveModel redeclared in this block
	.\main.go:280:6: other declaration of saveModel
.\main.go:549:6: loadModel redeclared in this block
	.\main.go:295:6: other declaration of loadModel
.\main.go:366:10: too many errors