исправь сентитические ошибки import os
import csv
import torch
import torch.nn as nn
import torch.optim as optim
from collections import Counter
from torch.utils.data import Dataset, DataLoader
from torch.nn.utils.rnn import pad_sequence

try:
from datasets import load_dataset
except ImportError:
raise ImportError("Установите библиотеку datasets командой: pip install datasets")


def download_and_save_dataset():
data_dir = './data'
os.makedirs(data_dir, exist_ok=True)

text
# Скачиваем датасет AG News (train и test)
dataset = load_dataset("ag_news")

# Сохраняем обучающий датасет (train.csv)
train_csv = os.path.join(data_dir, "train.csv")
with open(train_csv, "w", encoding="utf-8", newline="") as csvfile:
    writer = csv.writer(csvfile)
    # Записываем заголовок
    writer.writerow(["text", "label"])
    for example in dataset["train"]:
        # В AG News поле "text" может отсутствовать, текст составляем из title и description
        text = example.get("title", "") + " " + example.get("description", "")
        label = example["label"]
        writer.writerow([text.strip(), label])

# Сохраняем тестовый датасет в виде валидационного набора (val.csv)
val_csv = os.path.join(data_dir, "val.csv")
with open(val_csv, "w", encoding="utf-8", newline="") as csvfile:
    writer = csv.writer(csvfile)
    writer.writerow(["text", "label"])
    for example in dataset["test"]:
        text = example.get("title", "") + " " + example.get("description", "")
        label = example["label"]
        writer.writerow([text.strip(), label])

print("Датасет скачан и сохранён в папке ./data (train.csv и val.csv).")
1. Определение кастомного датасета для текстовых данных
class CustomTextDataset(Dataset):
def init(self, file_path, vocab=None, build_vocab=False):
"""
file_path - путь до CSV-файла с данными.
Структура файла: первая строка - заголовки ("text", "label"),
далее: текст и метка класса.

text
    Если build_vocab=True, то на основе данного датасета будет построен словарь.
    Если vocab передаётся, он используется для преобразования токенов в индексы.
    """
    self.samples = []
    self.labels = []
    self.max_length = 100  # максимальная длина последовательности (при необходимости можно изменить)
    
    with open(file_path, 'r', encoding='utf-8') as f:
        reader = csv.reader(f)
        header = next(reader)  # пропускаем заголовок
        for row in reader:
            if len(row) < 2:
                continue
            text, label = row[0], row[1]
            # Простая токенизация по пробелам, можно заменить на более сложную
            tokens = text.strip().split()
            self.samples.append(tokens)
            self.labels.append(int(label))
    
    if build_vocab:
        # Строим словарь: берем все токены из датасета
        counter = Counter()
        for tokens in self.samples:
            counter.update(tokens)
        # Добавляем специальные токены
        self.vocab = {'<PAD>': 0, '<UNK>': 1}
        for token in counter:
            self.vocab[token] = len(self.vocab)
    else:
        # Используем переданный словарь
        if vocab is None:
            raise ValueError("Если build_vocab=False, необходимо передать словарь vocab")
        self.vocab = vocab

def __len__(self):
    return len(self.samples)

def __getitem__(self, idx):
    tokens = self.samples[idx]
    label = self.labels[idx]
    # Преобразуем токены в индексы
    indices = [self.vocab.get(token, self.vocab['<UNK>']) for token in tokens]
    # Возвращаем последовательность в виде тензора и метку
    return torch.tensor(indices, dtype=torch.long), torch.tensor(label, dtype=torch.long)
Функция для формирования батчей с padding (выравнивание последовательностей до максимальной длины в батче)
def collate_fn(batch):
texts, labels = zip(*batch)
# pad_sequence автоматически дополняет последовательности до максимальной длины в батче значением padding_value (0)
texts = pad_sequence(texts, batch_first=True, padding_value=0)
labels = torch.stack(labels)
return texts, labels

2. Определение модели TextLSTM для классификации
class TextLSTM(nn.Module):
def init(self, vocab_size, embed_size, hidden_size, num_classes, num_layers=1, bidirectional=False):
super(TextLSTM, self).init()
self.embedding = nn.Embedding(vocab_size, embed_size, padding_idx=0)
self.lstm = nn.LSTM(embed_size, hidden_size, num_layers=num_layers,
batch_first=True, bidirectional=bidirectional)
# Если bidirectional True, то размер скрытого состояния умножается на 2
lstm_output_size = hidden_size * 2 if bidirectional else hidden_size
self.fc = nn.Linear(lstm_output_size, num_classes)

text
def forward(self, x):
    # x: [batch_size, seq_len]
    embedded = self.embedding(x)  # [batch_size, seq_len, embed_size]
    lstm_out, (h_n, c_n) = self.lstm(embedded)
    # Используем последнее скрытое состояние
    if self.lstm.bidirectional:
        # Конкатенация финальных скрытых состояний обоих направлений
        h_n = torch.cat((h_n[-2], h_n[-1]), dim=1)
    else:
        h_n = h_n[-1]
    out = self.fc(h_n)
    return out

def main():
# Шаг 0: Скачивание датасета с Hugging Face, если CSV файлы не существуют
train_file = './data/train.csv'
val_file = './data/val.csv'
if not os.path.exists(train_file) or not os.path.exists(val_file):
print("CSV файлов с данными не обнаружено. Скачиваю датасет с Hugging Face...")
download_and_save_dataset()

text
# Создаем тренировочный датасет и строим словарь
train_dataset = CustomTextDataset(train_file, build_vocab=True)
vocab = train_dataset.vocab
vocab_size = len(vocab)

# Создаем валидационный датасет с использованием того же словаря
val_dataset = CustomTextDataset(val_file, vocab=vocab, build_vocab=False)

batch_size = 32
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True, collate_fn=collate_fn)
val_loader = DataLoader(val_dataset, batch_size=batch_size, shuffle=False, collate_fn=collate_fn)

# Параметры модели
embed_size = 100
hidden_size = 128
# Определяем число классов на основе уникальных меток
num_classes = len(set(train_dataset.labels))
num_epochs = 10
learning_rate = 0.001

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = TextLSTM(vocab_size, embed_size, hidden_size, num_classes, bidirectional=True)
model.to(device)

criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=learning_rate)

for epoch in range(num_epochs):
    model.train()
    total_loss = 0.0
    for texts, labels in train_loader:
        texts = texts.to(device)
        labels = labels.to(device)
        
        optimizer.zero_grad()
        outputs = model(texts)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()
        
        total_loss += loss.item()
    
    avg_loss = total_loss / len(train_loader)
    print(f'Epoch {epoch+1}/{num_epochs}, Training Loss: {avg_loss:.4f}')
    
    # Валидация
    model.eval()
    correct = 0
    total = 0
    with torch.no_grad():
        for texts, labels in val_loader:
            texts = texts.to(device)
            labels = labels.to(device)
            outputs = model(texts)
            _, predicted = torch.max(outputs, dim=1)
            total += labels.size(0)
            correct += (predicted == labels).sum().item()
    val_acc = correct / total if total > 0 else 0
    print(f'Validation Accuracy: {val_acc:.4f}')

# Сохранение обученной модели
torch.save(model.state_dict(), "text_lstm_model.pth")
print("Модель сохранена.")
if name == "main":
main()