{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.11","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":8900,"databundleVersionId":862232,"sourceType":"competition"}],"dockerImageVersionId":31041,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport pandas as pd\nimport numpy as np\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\nimport torchaudio\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom sklearn.metrics import confusion_matrix, accuracy_score, f1_score\nfrom sklearn.model_selection import train_test_split\nimport time\nimport librosa\nimport warnings\nimport math\nwarnings.filterwarnings('ignore')\n\n# Thiết lập seed để kết quả có thể tái tạo\ntorch.manual_seed(42)\nnp.random.seed(42)\n\n# Đường dẫn đến thư mục dữ liệu\nDATA_DIR = \"/kaggle/input/freesound-audio-tagging/audio_train\"  # Thay đổi thành đường dẫn thư mục của bạn\nCSV_PATH = \"/kaggle/input/freesound-audio-tagging/train.csv\"       # Thay đổi thành đường dẫn file CSV của bạn\n\n# Các tham số\nSAMPLE_RATE = 22050  # Tần số lấy mẫu chuẩn\nDURATION = 3         # Độ dài âm thanh (giây)\nN_MFCC = 40          # Số hệ số MFCC\nN_FFT = 2048         # Kích thước FFT\nHOP_LENGTH = 512     # Bước nhảy\nN_MELS = 128         # Số kênh Mel\nBATCH_SIZE = 1024\nEPOCHS = 20\nLEARNING_RATE = 0.0005\n\n# 1. ĐỌC VÀ XỬ LÝ DỮ LIỆU\n\nclass AudioDataset(Dataset):\n    def __init__(self, annotations, audio_dir, transform=None, target_sr=SAMPLE_RATE, \n                 duration=DURATION, feature_type='mfcc'):\n        \"\"\"\n        Args:\n            annotations (DataFrame): DataFrame chứa thông tin về các file âm thanh và nhãn\n            audio_dir (string): Đường dẫn đến thư mục chứa file âm thanh\n            transform (callable, optional): Optional transform to be applied on a sample\n            target_sr (int): Sample rate mục tiêu\n            duration (float): Độ dài mong muốn (giây)\n            feature_type (str): Loại đặc trưng ('mfcc', 'melspec', 'logmel')\n        \"\"\"\n        self.annotations = annotations\n        self.audio_dir = audio_dir\n        self.transform = transform\n        self.target_sr = target_sr\n        self.duration = duration\n        self.target_samples = int(target_sr * duration)\n        self.feature_type = feature_type\n        \n        # Xác định số classes dựa trên dữ liệu\n        self.num_classes = len(annotations['label'].unique())\n        \n        # Dictionary để map nhãn thành số nguyên\n        unique_labels = sorted(annotations['label'].unique())\n        self.label_to_idx = {label: i for i, label in enumerate(unique_labels)}\n        self.idx_to_label = {i: label for i, label in enumerate(unique_labels)}\n        \n    def __len__(self):\n        return len(self.annotations)\n    \n    def __getitem__(self, idx):\n        audio_path = os.path.join(self.audio_dir, self.annotations.iloc[idx, 0])\n        label = self.annotations.iloc[idx, 1]\n        label_idx = self.label_to_idx[label]\n        \n        # Đọc file âm thanh\n        try:\n            waveform, sr = torchaudio.load(audio_path)\n            \n            # Chuyển đổi thành mono nếu có nhiều kênh\n            if waveform.shape[0] > 1:\n                waveform = torch.mean(waveform, dim=0, keepdim=True)\n            \n            # Resampling nếu cần\n            if sr != self.target_sr:\n                resampler = torchaudio.transforms.Resample(sr, self.target_sr)\n                waveform = resampler(waveform)\n            \n            # Chuẩn hóa độ dài\n            if waveform.shape[1] < self.target_samples:\n                # Zero padding\n                padding = self.target_samples - waveform.shape[1]\n                waveform = torch.nn.functional.pad(waveform, (0, padding))\n            elif waveform.shape[1] > self.target_samples:\n                # Cắt bớt\n                waveform = waveform[:, :self.target_samples]\n            \n            # Chuẩn hóa biên độ\n            waveform = waveform / (torch.max(torch.abs(waveform)) + 1e-8)\n            \n            # Trích xuất đặc trưng dựa trên loại đã chọn\n            if self.feature_type == 'mfcc':\n                # MFCC\n                mfcc_transform = torchaudio.transforms.MFCC(\n                    sample_rate=self.target_sr,\n                    n_mfcc=N_MFCC,\n                    melkwargs={'n_fft': N_FFT, 'hop_length': HOP_LENGTH, 'n_mels': N_MELS}\n                )\n                features = mfcc_transform(waveform)  # Shape: [1, n_mfcc, time]\n                \n            elif self.feature_type == 'melspec':\n                # Mel Spectrogram\n                mel_transform = torchaudio.transforms.MelSpectrogram(\n                    sample_rate=self.target_sr,\n                    n_fft=N_FFT,\n                    hop_length=HOP_LENGTH,\n                    n_mels=N_MELS\n                )\n                features = mel_transform(waveform)  # Shape: [1, n_mels, time]\n                \n            elif self.feature_type == 'logmel':\n                # Log-Mel Spectrogram\n                mel_transform = torchaudio.transforms.MelSpectrogram(\n                    sample_rate=self.target_sr,\n                    n_fft=N_FFT,\n                    hop_length=HOP_LENGTH,\n                    n_mels=N_MELS\n                )\n                features = mel_transform(waveform)\n                features = torch.log(features + 1e-9)  # Log scale\n            \n            # Áp dụng transform nếu được cung cấp\n            if self.transform:\n                features = self.transform(features)\n            \n            return features, label_idx\n        \n        except Exception as e:\n            print(f\"Error loading {audio_path}: {e}\")\n            # Trả về tensor rỗng trong trường hợp lỗi\n            return torch.zeros(1, N_MFCC, int(self.target_samples / HOP_LENGTH) + 1), label_idx\n\n# 2. XÂY DỰNG CÁC MÔ HÌNH\n\n# CNN 1D\nclass CNN1D(nn.Module):\n    def __init__(self, num_classes, input_size=N_MFCC):\n        super(CNN1D, self).__init__()\n        self.conv1 = nn.Conv1d(input_size, 64, kernel_size=3, stride=1, padding=1)\n        self.bn1 = nn.BatchNorm1d(64)\n        self.relu = nn.ReLU()\n        self.pool1 = nn.MaxPool1d(kernel_size=2)\n        \n        self.conv2 = nn.Conv1d(64, 128, kernel_size=3, stride=1, padding=1)\n        self.bn2 = nn.BatchNorm1d(128)\n        self.pool2 = nn.MaxPool1d(kernel_size=2)\n        \n        self.conv3 = nn.Conv1d(128, 256, kernel_size=3, stride=1, padding=1)\n        self.bn3 = nn.BatchNorm1d(256)\n        self.pool3 = nn.MaxPool1d(kernel_size=2)\n        \n        # Tính toán kích thước đầu vào cho lớp fully connected\n        self.fc_input_size = self._get_fc_input_size(input_size)\n        \n        self.fc1 = nn.Linear(self.fc_input_size, 512)\n        self.dropout = nn.Dropout(0.5)\n        self.fc2 = nn.Linear(512, num_classes)\n        \n    def _get_fc_input_size(self, input_size):\n        # Tính toán kích thước đầu vào cho lớp fully connected\n        with torch.no_grad():\n            x = torch.zeros(1, input_size, 216)  # Approximate time dimension\n            x = self.pool3(self.bn3(self.relu(self.conv3(\n                self.pool2(self.bn2(self.relu(self.conv2(\n                    self.pool1(self.bn1(self.relu(self.conv1(x))))))))))))\n            return x.shape[1] * x.shape[2]\n        \n    def forward(self, x):\n        # x shape: [batch, channels, time]\n        x = self.pool1(self.relu(self.bn1(self.conv1(x))))\n        x = self.pool2(self.relu(self.bn2(self.conv2(x))))\n        x = self.pool3(self.relu(self.bn3(self.conv3(x))))\n        \n        # Reshape for fully connected layer\n        x = x.view(x.size(0), -1)\n        \n        x = self.relu(self.fc1(x))\n        x = self.dropout(x)\n        x = self.fc2(x)\n        return x\n\n# CNN 2D\nclass CNN2D(nn.Module):\n    def __init__(self, num_classes, in_channels=1):\n        super(CNN2D, self).__init__()\n        self.conv1 = nn.Conv2d(in_channels, 32, kernel_size=3, stride=1, padding=1)\n        self.bn1 = nn.BatchNorm2d(32)\n        self.relu = nn.ReLU()\n        self.pool1 = nn.MaxPool2d(kernel_size=2)\n        \n        self.conv2 = nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=1)\n        self.bn2 = nn.BatchNorm2d(64)\n        self.pool2 = nn.MaxPool2d(kernel_size=2)\n        \n        self.conv3 = nn.Conv2d(64, 128, kernel_size=3, stride=1, padding=1)\n        self.bn3 = nn.BatchNorm2d(128)\n        self.pool3 = nn.MaxPool2d(kernel_size=2)\n        \n        # Tính toán kích thước đầu vào cho lớp fully connected\n        self.fc_input_size = self._get_fc_input_size(in_channels)\n        \n        self.fc1 = nn.Linear(self.fc_input_size, 512)\n        self.dropout = nn.Dropout(0.5)\n        self.fc2 = nn.Linear(512, num_classes)\n        \n    def _get_fc_input_size(self, in_channels):\n        # Tính toán kích thước đầu vào cho lớp fully connected\n        with torch.no_grad():\n            # Approximate dimensions for MFCC features\n            x = torch.zeros(1, in_channels, N_MFCC, 216)  # [batch, channels, height, width]\n            x = self.pool3(self.bn3(self.relu(self.conv3(\n                self.pool2(self.bn2(self.relu(self.conv2(\n                    self.pool1(self.bn1(self.relu(self.conv1(x))))))))))))\n            return x.numel() // x.shape[0]\n        \n    def forward(self, x):\n        # x shape: [batch, channels, height, width]\n        x = self.pool1(self.relu(self.bn1(self.conv1(x))))\n        x = self.pool2(self.relu(self.bn2(self.conv2(x))))\n        x = self.pool3(self.relu(self.bn3(self.conv3(x))))\n        \n        # Reshape for fully connected layer\n        x = x.view(x.size(0), -1)\n        \n        x = self.relu(self.fc1(x))\n        x = self.dropout(x)\n        x = self.fc2(x)\n        return x\n\n# RNN\nclass RNN(nn.Module):\n    def __init__(self, input_size, hidden_size, num_layers, num_classes):\n        super(RNN, self).__init__()\n        self.hidden_size = hidden_size\n        self.num_layers = num_layers\n        \n        self.rnn = nn.RNN(input_size, hidden_size, num_layers, batch_first=True)\n        self.fc = nn.Linear(hidden_size, num_classes)\n        \n    def forward(self, x):\n        # x shape: [batch, time, features]\n        # Set initial hidden states\n        h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device)\n        \n        # Forward propagate RNN\n        out, _ = self.rnn(x, h0)\n        \n        # Decode the hidden state of the last time step\n        out = self.fc(out[:, -1, :])\n        return out\n\n# LSTM\nclass LSTM(nn.Module):\n    def __init__(self, input_size, hidden_size, num_layers, num_classes):\n        super(LSTM, self).__init__()\n        self.hidden_size = hidden_size\n        self.num_layers = num_layers\n        \n        self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True)\n        self.fc = nn.Linear(hidden_size, num_classes)\n        \n    def forward(self, x):\n        # x shape: [batch, time, features]\n        # Set initial hidden and cell states\n        h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device)\n        c0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device)\n        \n        # Forward propagate LSTM\n        out, _ = self.lstm(x, (h0, c0))\n        \n        # Decode the hidden state of the last time step\n        out = self.fc(out[:, -1, :])\n        return out\n\n# BiLSTM\nclass BiLSTM(nn.Module):\n    def __init__(self, input_size, hidden_size, num_layers, num_classes):\n        super(BiLSTM, self).__init__()\n        self.hidden_size = hidden_size\n        self.num_layers = num_layers\n        \n        self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True, bidirectional=True)\n        self.fc = nn.Linear(hidden_size * 2, num_classes)  # * 2 for bidirectional\n        \n    def forward(self, x):\n        # x shape: [batch, time, features]\n        # Set initial hidden and cell states\n        h0 = torch.zeros(self.num_layers * 2, x.size(0), self.hidden_size).to(x.device)  # * 2 for bidirectional\n        c0 = torch.zeros(self.num_layers * 2, x.size(0), self.hidden_size).to(x.device)\n        \n        # Forward propagate LSTM\n        out, _ = self.lstm(x, (h0, c0))\n        \n        # Decode the hidden state of the last time step\n        out = self.fc(out[:, -1, :])\n        return out\n\n# GRU\nclass GRU(nn.Module):\n    def __init__(self, input_size, hidden_size, num_layers, num_classes):\n        super(GRU, self).__init__()\n        self.hidden_size = hidden_size\n        self.num_layers = num_layers\n        \n        self.gru = nn.GRU(input_size, hidden_size, num_layers, batch_first=True)\n        self.fc = nn.Linear(hidden_size, num_classes)\n        \n    def forward(self, x):\n        # x shape: [batch, time, features]\n        # Set initial hidden state\n        h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device)\n        \n        # Forward propagate GRU\n        out, _ = self.gru(x, h0)\n        \n        # Decode the hidden state of the last time step\n        out = self.fc(out[:, -1, :])\n        return out\n\nclass AudioTransformer(nn.Module):\n    def __init__(self, input_size, num_classes, d_model=216, nhead=8, \n                 num_layers=3, dim_feedforward=512, dropout=0.1):\n        super(AudioTransformer, self).__init__()\n        \n        self.d_model = d_model\n        \n        # Input projection layer\n        self.input_projection = nn.Linear(input_size, d_model)\n        \n        # Positional encoding\n        self.pos_encoder = PositionalEncoding(d_model, dropout)\n        \n        # Transformer encoder\n        encoder_layers = nn.TransformerEncoderLayer(\n            d_model=d_model,\n            nhead=nhead,\n            dim_feedforward=dim_feedforward,\n            dropout=dropout,\n            batch_first=True\n        )\n        self.transformer_encoder = nn.TransformerEncoder(encoder_layers, num_layers)\n        \n        # Output layer\n        self.fc = nn.Linear(d_model, num_classes)\n        \n    def forward(self, x):\n        # x shape: [batch, time, features]\n        \n        # Project input to d_model dimensions\n        x = self.input_projection(x)\n        \n        # Add positional encoding\n        x = self.pos_encoder(x)\n        \n        # Pass through transformer encoder\n        x = self.transformer_encoder(x)\n        \n        # Global average pooling over time dimension\n        x = torch.mean(x, dim=1)\n        \n        # Final classification\n        x = self.fc(x)\n        return x\n\nclass PositionalEncoding(nn.Module):\n    def __init__(self, d_model, dropout=0.1, max_len=5000):\n        super(PositionalEncoding, self).__init__()\n        self.dropout = nn.Dropout(p=dropout)\n\n        position = torch.arange(max_len).unsqueeze(1)\n        div_term = torch.exp(torch.arange(0, d_model, 2) * (-math.log(10000.0) / d_model))\n        pe = torch.zeros(max_len, 1, d_model)\n        pe[:, 0, 0::2] = torch.sin(position * div_term)\n        pe[:, 0, 1::2] = torch.cos(position * div_term)\n        pe = pe.transpose(0, 1)\n        self.register_buffer('pe', pe)\n\n    def forward(self, x):\n        x = x + self.pe[:, :x.size(1)]\n        return self.dropout(x)\n\n# 3. HUẤN LUYỆN VÀ ĐÁNH GIÁ\n\ndef preprocess_data(feature_type='mfcc'):\n    \"\"\"\n    Đọc và tiền xử lý dữ liệu.\n    \n    Args:\n        feature_type (str): Loại đặc trưng ('mfcc', 'melspec', 'logmel')\n    \n    Returns:\n        tuple: (train_loader, val_loader, test_loader, num_classes)\n    \"\"\"\n    # Đọc file CSV\n    df = pd.read_csv(CSV_PATH)\n    \n    # Đổi tên cột nếu cần thiết\n    if 'fname' in df.columns and 'label' in df.columns:\n        pass\n    else:\n        # Đặt tên cột chính xác từ dữ liệu mô tả\n        df.columns = ['fname', 'label', 'manually_verified']\n    \n    # Lấy những dòng có manually_verified = 1 nếu cần\n    # df = df[df['manually_verified'] == 1]\n    \n    # Chia dữ liệu thành train, validation và test\n    train_df, temp_df = train_test_split(df, test_size=0.3, random_state=42, stratify=df['label'])\n    val_df, test_df = train_test_split(temp_df, test_size=0.5, random_state=42, stratify=temp_df['label'])\n    \n    print(f\"Train set size: {len(train_df)}\")\n    print(f\"Validation set size: {len(val_df)}\")\n    print(f\"Test set size: {len(test_df)}\")\n    \n    # Khởi tạo datasets\n    train_dataset = AudioDataset(train_df, DATA_DIR, feature_type=feature_type)\n    val_dataset = AudioDataset(val_df, DATA_DIR, feature_type=feature_type)\n    test_dataset = AudioDataset(test_df, DATA_DIR, feature_type=feature_type)\n    \n    # Tạo data loaders\n    train_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True, num_workers=4)\n    val_loader = DataLoader(val_dataset, batch_size=BATCH_SIZE, shuffle=False, num_workers=4)\n    test_loader = DataLoader(test_dataset, batch_size=BATCH_SIZE, shuffle=False, num_workers=4)\n    \n    num_classes = train_dataset.num_classes\n    \n    return train_loader, val_loader, test_loader, num_classes, train_dataset.idx_to_label\n\ndef train_model(model, train_loader, val_loader, device, model_name, feature_type):\n    \"\"\"\n    Huấn luyện và đánh giá mô hình.\n    \n    Args:\n        model: PyTorch model\n        train_loader: DataLoader cho tập huấn luyện\n        val_loader: DataLoader cho tập validation\n        device: Thiết bị để huấn luyện (CPU hoặc GPU)\n        model_name: Tên của mô hình\n        feature_type: Loại đặc trưng được sử dụng\n    \n    Returns:\n        dict: Lịch sử huấn luyện và thời gian đào tạo\n    \"\"\"\n    criterion = nn.CrossEntropyLoss()\n    optimizer = optim.Adam(model.parameters(), lr=LEARNING_RATE)\n    \n    # Learning rate scheduler\n    scheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='min', factor=0.5, patience=3, verbose=True)\n    \n    # Tracking metrics\n    history = {\n        'train_loss': [],\n        'val_loss': [],\n        'train_acc': [],\n        'val_acc': []\n    }\n    \n    start_time = time.time()\n    best_val_loss = float('inf')\n    \n    # Training loop\n    for epoch in range(EPOCHS):\n        # Training\n        model.train()\n        train_loss = 0.0\n        train_correct = 0\n        train_total = 0\n        \n        for features, labels in train_loader:\n            features, labels = features.to(device), labels.to(device)\n            \n            # Đảm bảo đầu vào phù hợp với kiến trúc mô hình\n            if isinstance(model, CNN1D):\n                # CNN1D nhận đầu vào dạng [batch, features, time]\n                if features.dim() == 4:  # [batch, channels, height, time]\n                    features = features.squeeze(1)  # Remove channel dimension\n            elif isinstance(model, CNN2D):\n                # CNN2D nhận đầu vào dạng [batch, channels, height, width]\n                if features.dim() == 3:  # [batch, height, time]\n                    features = features.unsqueeze(1)  # Add channel dimension\n            elif isinstance(model, (RNN, LSTM, BiLSTM, GRU)):\n                # RNN models nhận đầu vào dạng [batch, time, features]\n                if features.dim() == 3:  # [batch, features, time]\n                    features = features.transpose(1, 2)\n                elif features.dim() == 4:  # [batch, channels, height, time]\n                    b, c, h, w = features.shape\n                    features = features.reshape(b, w, c*h)  # [batch, time, channels*height]\n            \n            optimizer.zero_grad()\n            outputs = model(features)\n            loss = criterion(outputs, labels)\n            loss.backward()\n            optimizer.step()\n            \n            train_loss += loss.item()\n            _, predicted = torch.max(outputs.data, 1)\n            train_total += labels.size(0)\n            train_correct += (predicted == labels).sum().item()\n        \n        train_loss = train_loss / len(train_loader)\n        train_acc = train_correct / train_total\n        \n        # Validation\n        model.eval()\n        val_loss = 0.0\n        val_correct = 0\n        val_total = 0\n        \n        with torch.no_grad():\n            for features, labels in val_loader:\n                features, labels = features.to(device), labels.to(device)\n                \n                # Đảm bảo đầu vào phù hợp với kiến trúc mô hình\n                if isinstance(model, CNN1D):\n                    if features.dim() == 4:\n                        features = features.squeeze(1)\n                elif isinstance(model, CNN2D):\n                    if features.dim() == 3:\n                        features = features.unsqueeze(1)\n                elif isinstance(model, (RNN, LSTM, BiLSTM, GRU)):\n                    if features.dim() == 3:\n                        features = features.transpose(1, 2)\n                    elif features.dim() == 4:\n                        b, c, h, w = features.shape\n                        features = features.reshape(b, w, c*h)\n                \n                outputs = model(features)\n                loss = criterion(outputs, labels)\n                \n                val_loss += loss.item()\n                _, predicted = torch.max(outputs.data, 1)\n                val_total += labels.size(0)\n                val_correct += (predicted == labels).sum().item()\n        \n        val_loss = val_loss / len(val_loader)\n        val_acc = val_correct / val_total\n        \n        # Learning rate scheduler step\n        scheduler.step(val_loss)\n        \n        # Save best model\n        if val_loss < best_val_loss:\n            best_val_loss = val_loss\n            torch.save(model.state_dict(), f\"{model_name}_{feature_type}_best.pth\")\n        \n        # Update history\n        history['train_loss'].append(train_loss)\n        history['val_loss'].append(val_loss)\n        history['train_acc'].append(train_acc)\n        history['val_acc'].append(val_acc)\n        \n        print(f\"Epoch {epoch+1}/{EPOCHS}, \"\n              f\"Train Loss: {train_loss:.4f}, Train Acc: {train_acc:.4f}, \"\n              f\"Val Loss: {val_loss:.4f}, Val Acc: {val_acc:.4f}\")\n    \n    end_time = time.time()\n    train_time = end_time - start_time\n    print(f\"Training time: {train_time:.2f} seconds\")\n    \n    # Plot learning curves\n    plt.figure(figsize=(12, 5))\n    \n    plt.subplot(1, 2, 1)\n    plt.plot(history['train_loss'], label='Train')\n    plt.plot(history['val_loss'], label='Validation')\n    plt.title(f'{model_name} - Loss')\n    plt.xlabel('Epoch')\n    plt.ylabel('Loss')\n    plt.legend()\n    \n    plt.subplot(1, 2, 2)\n    plt.plot(history['train_acc'], label='Train')\n    plt.plot(history['val_acc'], label='Validation')\n    plt.title(f'{model_name} - Accuracy')\n    plt.xlabel('Epoch')\n    plt.ylabel('Accuracy')\n    plt.legend()\n    \n    plt.tight_layout()\n    plt.savefig(f\"{model_name}_{feature_type}_learning_curves.png\")\n    plt.close()\n    \n    return history, train_time\n\ndef evaluate_model(model, test_loader, device, idx_to_label, model_name, feature_type):\n    \"\"\"\n    Đánh giá mô hình trên tập test.\n    \n    Args:\n        model: PyTorch model\n        test_loader: DataLoader cho tập test\n        device: Thiết bị để đánh giá (CPU hoặc GPU)\n        idx_to_label: Dictionary để chuyển đổi index thành nhãn\n        model_name: Tên của mô hình\n        feature_type: Loại đặc trưng được sử dụng\n    \n    Returns:\n        tuple: (accuracy, f1, confusion matrix)\n    \"\"\"\n    model.eval()\n    all_preds = []\n    all_labels = []\n    \n    start_time = time.time()\n    \n    with torch.no_grad():\n        for features, labels in test_loader:\n            features, labels = features.to(device), labels.to(device)\n            \n            # Đảm bảo đầu vào phù hợp với kiến trúc mô hình\n            if isinstance(model, CNN1D):\n                if features.dim() == 4:\n                    features = features.squeeze(1)\n            elif isinstance(model, CNN2D):\n                if features.dim() == 3:\n                    features = features.unsqueeze(1)\n            elif isinstance(model, (RNN, LSTM, BiLSTM, GRU)):\n                if features.dim() == 3:\n                    features = features.transpose(1, 2)\n                elif features.dim() == 4:\n                    b, c, h, w = features.shape\n                    features = features.reshape(b, w, c*h)\n            \n            outputs = model(features)\n            _, preds = torch.max(outputs, 1)\n            \n            all_preds.extend(preds.cpu().numpy())\n            all_labels.extend(labels.cpu().numpy())\n    \n    end_time = time.time()\n    test_time = end_time - start_time\n    \n    # Calculate metrics\n    accuracy = accuracy_score(all_labels, all_preds)\n    f1 = f1_score(all_labels, all_preds, average='weighted')  # Weighted average for multiclass\n    cm = confusion_matrix(all_labels, all_preds)\n    \n    print(f\"\\nTest Results for {model_name}:\")\n    print(f\"Accuracy: {accuracy:.4f}\")\n    print(f\"F1 Score: {f1:.4f}\")\n    print(f\"Test time: {test_time:.2f} seconds\")\n    \n    # Plot confusion matrix\n    plt.figure(figsize=(10, 8))\n    sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',\n               xticklabels=[idx_to_label[i] for i in range(len(idx_to_label))],\n               yticklabels=[idx_to_label[i] for i in range(len(idx_to_label))])\n    plt.xlabel('Predicted')\n    plt.ylabel('True')\n    plt.title(f'Confusion Matrix - {model_name}')\n    plt.tight_layout()\n    plt.savefig(f\"{model_name}_{feature_type}_confusion_matrix.png\")\n    plt.close()\n    \n    return accuracy, f1, cm, test_time\n\ndef main():\n    device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n    print(f\"Using device: {device}\")\n    \n    # Định nghĩa các loại đặc trưng cần thử nghiệm\n    feature_types = ['mfcc']\n    \n    # Lưu trữ kết quả\n    results = {\n        'model_name': [],\n        'feature_type': [],\n        'accuracy': [],\n        'f1_score': [],\n        'train_time': [],\n        'test_time': []\n    }\n\n    # Thử nghiệm với các loại đặc trưng khác nhau\n    for feature_type in feature_types:\n        print(f\"\\nProcessing feature type: {feature_type}\")\n        \n        # Load và tiền xử lý dữ liệu\n        train_loader, val_loader, test_loader, num_classes, idx_to_label = preprocess_data(feature_type)\n        \n        # Định nghĩa các mô hình cần thử nghiệm\n        models = {\n        'CNN1D': CNN1D(num_classes=num_classes),\n        'CNN2D': CNN2D(num_classes=num_classes),\n        'RNN': RNN(input_size=N_MFCC, hidden_size=128, num_layers=2, num_classes=num_classes),\n        'LSTM': LSTM(input_size=N_MFCC, hidden_size=128, num_layers=2, num_classes=num_classes),\n        'BiLSTM': BiLSTM(input_size=N_MFCC, hidden_size=128, num_layers=2, num_classes=num_classes),\n        'GRU': GRU(input_size=N_MFCC, hidden_size=128, num_layers=2, num_classes=num_classes),\n        'Transformer': AudioTransformer(\n            input_size=N_MFCC,\n            num_classes=num_classes,\n            d_model=216,\n            nhead=8,\n            num_layers=3,\n            dim_feedforward=512,\n            dropout=0.1\n        )\n    }\n        \n        # Huấn luyện và đánh giá từng mô hình\n        for model_name, model in models.items():\n            print(f\"\\nTraining {model_name} with {feature_type} features...\")\n            model = model.to(device)\n            \n            # Huấn luyện mô hình\n            history, train_time = train_model(model, train_loader, val_loader, device, model_name, feature_type)\n            \n            # Load best model weights\n            model.load_state_dict(torch.load(f\"{model_name}_{feature_type}_best.pth\"))\n            \n            # Đánh giá mô hình\n            accuracy, f1, cm, test_time = evaluate_model(model, test_loader, device, idx_to_label, model_name, feature_type)\n            \n            # Lưu kết quả\n            results['model_name'].append(model_name)\n            results['feature_type'].append(feature_type)\n            results['accuracy'].append(accuracy)\n            results['f1_score'].append(f1)\n            results['train_time'].append(train_time)\n            results['test_time'].append(test_time)\n    \n    # Tạo DataFrame từ kết quả và lưu vào file CSV\n    results_df = pd.DataFrame(results)\n    results_df.to_csv('model_comparison_results.csv', index=False)\n    \n    # Hiển thị bảng kết quả\n    print(\"\\nFinal Results:\")\n    print(results_df)\n    \n    # Vẽ biểu đồ so sánh\n    plt.figure(figsize=(15, 6))\n    \n    plt.subplot(1, 2, 1)\n    sns.barplot(data=results_df, x='model_name', y='accuracy', hue='feature_type')\n    plt.title('Model Accuracy Comparison')\n    plt.xticks(rotation=45)\n    \n    plt.subplot(1, 2, 2)\n    sns.barplot(data=results_df, x='model_name', y='f1_score', hue='feature_type')\n    plt.title('Model F1 Score Comparison')\n    plt.xticks(rotation=45)\n    \n    plt.tight_layout()\n    plt.savefig('model_comparison.png')\n    plt.close()\n\nif __name__ == \"__main__\":\n    main()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-22T06:29:50.440032Z","iopub.execute_input":"2025-05-22T06:29:50.440543Z","iopub.status.idle":"2025-05-22T06:31:11.565387Z","shell.execute_reply.started":"2025-05-22T06:29:50.440519Z","shell.execute_reply":"2025-05-22T06:31:11.564181Z"}},"outputs":[{"name":"stdout","text":"Using device: cuda\n\nProcessing feature type: mfcc\nTrain set size: 6631\nValidation set size: 1421\nTest set size: 1421\n\nTraining CNN1D with mfcc features...\n","output_type":"stream"},{"traceback":["\u001b[0;31m---------------------------------------------------------------------------\u001b[0m","\u001b[0;31mRuntimeError\u001b[0m                              Traceback (most recent call last)","\u001b[0;32m/tmp/ipykernel_35/237653695.py\u001b[0m in \u001b[0;36m<cell line: 0>\u001b[0;34m()\u001b[0m\n\u001b[1;32m    756\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    757\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0m__name__\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;34m\"__main__\"\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 758\u001b[0;31m     \u001b[0mmain\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m","\u001b[0;32m/tmp/ipykernel_35/237653695.py\u001b[0m in \u001b[0;36mmain\u001b[0;34m()\u001b[0m\n\u001b[1;32m    714\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    715\u001b[0m             \u001b[0;31m# Huấn luyện mô hình\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 716\u001b[0;31m             \u001b[0mhistory\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtrain_time\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mtrain_model\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmodel\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtrain_loader\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mval_loader\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdevice\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mmodel_name\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mfeature_type\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m    717\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    718\u001b[0m             \u001b[0;31m# Load best model weights\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/tmp/ipykernel_35/237653695.py\u001b[0m in \u001b[0;36mtrain_model\u001b[0;34m(model, train_loader, val_loader, device, model_name, feature_type)\u001b[0m\n\u001b[1;32m    498\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    499\u001b[0m             \u001b[0moptimizer\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mzero_grad\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 500\u001b[0;31m             \u001b[0moutputs\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mmodel\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfeatures\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m    501\u001b[0m             \u001b[0mloss\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mcriterion\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0moutputs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mlabels\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    502\u001b[0m             \u001b[0mloss\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mbackward\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.11/dist-packages/torch/nn/modules/module.py\u001b[0m in \u001b[0;36m_wrapped_call_impl\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m   1737\u001b[0m             \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_compiled_call_impl\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m  \u001b[0;31m# type: ignore[misc]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m   1738\u001b[0m         \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1739\u001b[0;31m             \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_call_impl\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m   1740\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m   1741\u001b[0m     \u001b[0;31m# torchrec tests the code consistency with the following code\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.11/dist-packages/torch/nn/modules/module.py\u001b[0m in \u001b[0;36m_call_impl\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m   1748\u001b[0m                 \u001b[0;32mor\u001b[0m \u001b[0m_global_backward_pre_hooks\u001b[0m \u001b[0;32mor\u001b[0m \u001b[0m_global_backward_hooks\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m   1749\u001b[0m                 or _global_forward_hooks or _global_forward_pre_hooks):\n\u001b[0;32m-> 1750\u001b[0;31m             \u001b[0;32mreturn\u001b[0m \u001b[0mforward_call\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m   1751\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m   1752\u001b[0m         \u001b[0mresult\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/tmp/ipykernel_35/237653695.py\u001b[0m in \u001b[0;36mforward\u001b[0;34m(self, x)\u001b[0m\n\u001b[1;32m    185\u001b[0m         \u001b[0mx\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mx\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mview\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msize\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m-\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    186\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 187\u001b[0;31m         \u001b[0mx\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrelu\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfc1\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m    188\u001b[0m         \u001b[0mx\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdropout\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    189\u001b[0m         \u001b[0mx\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfc2\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.11/dist-packages/torch/nn/modules/module.py\u001b[0m in \u001b[0;36m_wrapped_call_impl\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m   1737\u001b[0m             \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_compiled_call_impl\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m  \u001b[0;31m# type: ignore[misc]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m   1738\u001b[0m         \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1739\u001b[0;31m             \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_call_impl\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m   1740\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m   1741\u001b[0m     \u001b[0;31m# torchrec tests the code consistency with the following code\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.11/dist-packages/torch/nn/modules/module.py\u001b[0m in \u001b[0;36m_call_impl\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m   1748\u001b[0m                 \u001b[0;32mor\u001b[0m \u001b[0m_global_backward_pre_hooks\u001b[0m \u001b[0;32mor\u001b[0m \u001b[0m_global_backward_hooks\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m   1749\u001b[0m                 or _global_forward_hooks or _global_forward_pre_hooks):\n\u001b[0;32m-> 1750\u001b[0;31m             \u001b[0;32mreturn\u001b[0m \u001b[0mforward_call\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m   1751\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m   1752\u001b[0m         \u001b[0mresult\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.11/dist-packages/torch/nn/modules/linear.py\u001b[0m in \u001b[0;36mforward\u001b[0;34m(self, input)\u001b[0m\n\u001b[1;32m    123\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    124\u001b[0m     \u001b[0;32mdef\u001b[0m \u001b[0mforward\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0minput\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mTensor\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0mTensor\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 125\u001b[0;31m         \u001b[0;32mreturn\u001b[0m \u001b[0mF\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mlinear\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0minput\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mweight\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mbias\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m    126\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    127\u001b[0m     \u001b[0;32mdef\u001b[0m \u001b[0mextra_repr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0mstr\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;31mRuntimeError\u001b[0m: mat1 and mat2 shapes cannot be multiplied (1024x2560 and 6912x512)"],"ename":"RuntimeError","evalue":"mat1 and mat2 shapes cannot be multiplied (1024x2560 and 6912x512)","output_type":"error"}],"execution_count":1},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}