Вот мой код:
```py

# train.py

import torch
import torch.nn as nn
from torch.utils.data import Dataset, DataLoader
import pandas as pd
from model import BitcoinPricePredictor

# Определение датасета с ленивой генерацией последовательностей
class BitcoinDataset(Dataset):
    def __init__(self, data, sequence_length=200):
        self.sequence_length = sequence_length
        self.data = data.reset_index(drop=True)
        self.features = self.data.columns.difference(['Symbol']).tolist()
        self.symbols = self.data['Symbol'].unique()

        # Сохраняем стартовые и конечные индексы для каждой валюты
        self.symbol_indices = []
        for symbol in self.symbols:
            symbol_data = self.data[self.data['Symbol'] == symbol]
            start_idx = symbol_data.index[0]
            end_idx = symbol_data.index[-1]
            self.symbol_indices.append((start_idx, end_idx))

        # Подсчитываем общее количество доступных последовательностей
        self.total_sequences = sum(
            max(0, end - start - self.sequence_length + 1)
            for start, end in self.symbol_indices
        )

    def __len__(self):
        return self.total_sequences

    def __getitem__(self, idx):
        # Поиск соответствующего символа и смещения
        cumulative_length = 0
        for (start, end) in self.symbol_indices:
            symbol_length = end - start - self.sequence_length + 1
            if idx < cumulative_length + symbol_length:
                seq_start = start + idx - cumulative_length
                seq_end = seq_start + self.sequence_length
                sequence = self.data.iloc[seq_start:seq_end][self.features].values
                target = self.data.iloc[seq_end]['Close']
                return torch.tensor(sequence, dtype=torch.float32), torch.tensor(target, dtype=torch.float32)
            cumulative_length += symbol_length
        raise IndexError("Index out of range")

def train():
    print(f"CUDA: {torch.cuda.is_available()}")
    # Загружаем данные
    train_df = pd.read_csv('data/train_data.csv')
    val_df = pd.read_csv('data/test_data.csv')

    # Параметры
    sequence_length = 200
    batch_size = 64  # Увеличьте, если позволяет память GPU
    num_epochs = 8#50
    learning_rate = 0.001
    input_size = train_df.shape[1] - 1  # Исключаем столбец 'Symbol'

    print(f"-- datasets loading")
    # Создаём датасеты
    train_dataset = BitcoinDataset(train_df, sequence_length)
    val_dataset = BitcoinDataset(val_df, sequence_length)

    print(f"-- data opt")
    # Загрузчики данных с оптимизациями
    train_loader = DataLoader(
        train_dataset, batch_size=batch_size, shuffle=True,
        num_workers=4, pin_memory=True
    )
    val_loader = DataLoader(
        val_dataset, batch_size=batch_size, shuffle=False,
        num_workers=4, pin_memory=True
    )

    print(f"-- model init")
    # Инициализируем модель, функцию потерь и оптимизатор
    model = BitcoinPricePredictor(input_size=input_size, hidden_size=128, dropout=0.2)
    criterion = nn.MSELoss()
    optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)

    print(f"-- cuda model load")
    # Переносим модель на устройство (CPU или GPU)
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    model.to(device)

    print(f"-- train start")
    # Обучение модели с валидацией
    best_val_loss = float('inf')
    for epoch in range(num_epochs):
        model.train()
        epoch_loss = 0
        for sequences, targets in train_loader:
            sequences = sequences.to(device, non_blocking=True)
            targets = targets.to(device, non_blocking=True)

            # Прямой проход
            outputs = model(sequences)
            loss = criterion(outputs.squeeze(), targets)

            # Обратный проход и оптимизация
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()

            epoch_loss += loss.item()
        avg_loss = epoch_loss / len(train_loader)

        # Валидация
        model.eval()
        val_loss = 0
        with torch.no_grad():
            for sequences, targets in val_loader:
                sequences = sequences.to(device, non_blocking=True)
                targets = targets.to(device, non_blocking=True)
                outputs = model(sequences)
                loss = criterion(outputs.squeeze(), targets)
                val_loss += loss.item()
        avg_val_loss = val_loss / len(val_loader)

        print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {avg_loss:.6f}, Val Loss: {avg_val_loss:.6f}')

        # Сохраняем лучшую модель
        if avg_val_loss < best_val_loss:
            best_val_loss = avg_val_loss
            torch.save(model.state_dict(), 'models/best_model.pth')

    print("Лучшие веса модели сохранены в models/best_model.pth")

if __name__ == "__main__":
    train()

```
Сделай так, чтобы на каждой эпохе был прогресс-бар, показывающий прогресс. Приведи обновленный код.