{"cells": [{"cell_type": "markdown", "metadata": {}, "source": "# CMI BFRB Detection - Full Training & Submit Pipeline\n\n**BiLSTM + GRU + Attention** \u30e2\u30c7\u30eb\u3067\u672c\u683c\u5b66\u7fd2\n- \u5b9f\u30c7\u30fc\u30bf\u8aad\u307f\u8fbc\u307f\n- GPU\u5b66\u7fd2\n- Code Competition submit"}, {"cell_type": "code", "execution_count": null, "metadata": {}, "source": "import os\nimport pandas as pd\nimport numpy as np\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import Dataset, DataLoader\nfrom sklearn.preprocessing import LabelEncoder\nfrom sklearn.model_selection import train_test_split\nimport warnings\nwarnings.filterwarnings('ignore')\n\nprint(\"\ud83d\udce6 \u30e9\u30a4\u30d6\u30e9\u30ea\u8aad\u307f\u8fbc\u307f\u5b8c\u4e86\")\n\n# GPU\u74b0\u5883\u78ba\u8a8d \ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nprint(f\"\ud83d\udda5\ufe0f Using device: {device}\")\nif torch.cuda.is_available():\n    print(f\"GPU\u540d: {torch.cuda.get_device_name(0)}\")\n    print(f\"GPU Memory: {torch.cuda.get_device_properties(0).total_memory // 1e9:.1f} GB\")", "outputs": []}, {"cell_type": "code", "execution_count": null, "metadata": {}, "source": "# \u30c7\u30fc\u30bf\u30d1\u30b9\u8a2d\u5b9a\ndata_path = '/kaggle/input/cmi-detect-behavior-with-sensor-data'\ntrain_path = f'{data_path}/train'\n\nprint(f\"\ud83d\udcc1 \u30c7\u30fc\u30bf\u30d1\u30b9: {data_path}\")\nprint(f\"\ud83d\udcc2 \u5b66\u7fd2\u30c7\u30fc\u30bf: {train_path}\")\n\n# \u30c7\u30fc\u30bf\u69cb\u9020\u78ba\u8a8d\nif os.path.exists(data_path):\n    print(\"\\n\ud83d\udccb \u30c7\u30fc\u30bf\u30bb\u30c3\u30c8\u69cb\u9020:\")\n    for root, dirs, files in os.walk(data_path):\n        level = root.replace(data_path, '').count(os.sep)\n        indent = ' ' * 2 * level\n        print(f\"{indent}{os.path.basename(root)}/\")\n        subindent = ' ' * 2 * (level + 1)\n        for file in files[:5]:  # \u6700\u521d\u306e5\u30d5\u30a1\u30a4\u30eb\u306e\u307f\u8868\u793a\n            print(f\"{subindent}{file}\")\n        if len(files) > 5:\n            print(f\"{subindent}... (+{len(files)-5} more)\")\nelse:\n    print(\"\u26a0\ufe0f \u30c7\u30fc\u30bf\u30d1\u30b9\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093 - \u30e2\u30c3\u30af\u30c7\u30fc\u30bf\u3067\u9032\u884c\")", "outputs": []}, {"cell_type": "code", "execution_count": null, "metadata": {}, "source": "# \u7269\u7406\u7279\u5fb4\u91cf\u62bd\u51fa\u95a2\u6570\ndef calculate_magnitude_batch(vector_batch):\n    \"\"\"\u30d0\u30c3\u30c1\u51e6\u7406\u3067\u306e\u5927\u304d\u3055\u8a08\u7b97\"\"\"\n    return torch.norm(vector_batch, dim=-1)\n\ndef calculate_linear_acceleration(acceleration):\n    \"\"\"\u7dda\u5f62\u52a0\u901f\u5ea6\u8a08\u7b97\uff08\u91cd\u529b\u88dc\u6b63\uff09\"\"\"\n    gravity = torch.tensor([0.0, 0.0, 9.8], device=acceleration.device)\n    return acceleration - gravity\n\ndef extract_physics_features(imu_data):\n    \"\"\"\u7269\u7406\u7279\u5fb4\u91cf\u3092\u62bd\u51fa\u3057\u3066IMU\u30c7\u30fc\u30bf\u306b\u8ffd\u52a0\"\"\"\n    # \u52a0\u901f\u5ea6\u30c7\u30fc\u30bf (\u6700\u521d\u306e3\u6b21\u5143)\n    accel_data = imu_data[:, :, :3]\n    \n    # \u52a0\u901f\u5ea6\u306e\u5927\u304d\u3055\n    accel_magnitude = calculate_magnitude_batch(accel_data)\n    accel_magnitude = accel_magnitude.unsqueeze(-1)\n    \n    # \u62e1\u5f35\u3055\u308c\u305f\u7279\u5fb4\u91cf\n    enhanced_features = torch.cat([imu_data, accel_magnitude], dim=-1)\n    return enhanced_features\n\nprint(\"\u2705 \u7269\u7406\u7279\u5fb4\u91cf\u62bd\u51fa\u95a2\u6570\u5b9a\u7fa9\u5b8c\u4e86\")", "outputs": []}, {"cell_type": "code", "execution_count": null, "metadata": {}, "source": "# Attention\u30e1\u30ab\u30cb\u30ba\u30e0\nclass SimpleAttention(nn.Module):\n    def __init__(self, hidden_size):\n        super().__init__()\n        self.hidden_size = hidden_size\n        self.attention_weights = nn.Linear(hidden_size, 1)\n    \n    def compute_weights(self, hidden_states):\n        energies = self.attention_weights(hidden_states)\n        energies = energies.squeeze(-1)\n        weights = F.softmax(energies, dim=-1)\n        return weights\n    \n    def forward(self, hidden_states):\n        weights = self.compute_weights(hidden_states)\n        weights = weights.unsqueeze(-1)\n        output = torch.sum(weights * hidden_states, dim=1)\n        return output\n\nprint(\"\u2705 Attention\u30e1\u30ab\u30cb\u30ba\u30e0\u5b9a\u7fa9\u5b8c\u4e86\")", "outputs": []}, {"cell_type": "code", "execution_count": null, "metadata": {}, "source": "# \u6539\u826f\u7248BFRB\u30e2\u30c7\u30eb\nclass ImprovedBFRBModel(nn.Module):\n    def __init__(self, input_size, hidden_size, num_classes, dropout=0.2):\n        super().__init__()\n        self.hidden_size = hidden_size\n        \n        # BiLSTM\u5c64\n        self.bilstm = nn.LSTM(\n            input_size=input_size,\n            hidden_size=hidden_size,\n            num_layers=2,  # \u6df1\u3044\u30cd\u30c3\u30c8\u30ef\u30fc\u30af\n            batch_first=True,\n            bidirectional=True,\n            dropout=dropout\n        )\n        \n        # GRU\u5c64\n        self.gru = nn.GRU(\n            input_size=hidden_size * 2,\n            hidden_size=hidden_size,\n            num_layers=1,\n            batch_first=True,\n            dropout=dropout\n        )\n        \n        # Attention\u5c64\n        self.attention = SimpleAttention(hidden_size)\n        \n        # \u5206\u985e\u5c64\n        self.dropout = nn.Dropout(dropout)\n        self.classifier = nn.Linear(hidden_size, num_classes)\n        \n    def forward(self, x):\n        # \u7269\u7406\u7279\u5fb4\u91cf\u62bd\u51fa\n        if x.shape[-1] == 6:  # IMU\u306e\u307f\u306e\u5834\u5408\n            x = extract_physics_features(x)\n        \n        # BiLSTM\n        bilstm_out, _ = self.bilstm(x)\n        \n        # GRU  \n        gru_out, _ = self.gru(bilstm_out)\n        \n        # Attention\n        attended = self.attention(gru_out)\n        \n        # \u5206\u985e\n        attended = self.dropout(attended)\n        output = self.classifier(attended)\n        \n        return output\n\nprint(\"\u2705 \u6539\u826f\u7248BFRB\u30e2\u30c7\u30eb\u5b9a\u7fa9\u5b8c\u4e86 (Dropout, 2\u5c64LSTM\u8ffd\u52a0)\")", "outputs": []}, {"cell_type": "code", "execution_count": null, "metadata": {}, "source": "# \u30c7\u30fc\u30bf\u30ed\u30fc\u30c0\u30fc\u30af\u30e9\u30b9\nclass CMIDataset(Dataset):\n    def __init__(self, sequences, labels, label_encoder):\n        self.sequences = sequences\n        self.labels = labels\n        self.label_encoder = label_encoder\n    \n    def __len__(self):\n        return len(self.sequences)\n    \n    def __getitem__(self, idx):\n        sequence = torch.FloatTensor(self.sequences[idx])\n        label = self.labels[idx]\n        return sequence, label\n\ndef create_mock_training_data():\n    \"\"\"\u8a13\u7df4\u7528\u30e2\u30c3\u30af\u30c7\u30fc\u30bf\u4f5c\u6210\"\"\"\n    gestures = ['Wave hello', 'Drink', 'Phone', 'Hand on neck', 'Touch hair']\n    sequences = []\n    labels = []\n    \n    # \u5404\u30b8\u30a7\u30b9\u30c1\u30e3\u30fc\u306b\u3064\u3044\u306610\u30b7\u30fc\u30b1\u30f3\u30b9\u4f5c\u6210\n    for gesture in gestures:\n        for i in range(10):\n            # 50\u30b9\u30c6\u30c3\u30d7\u306e\u6642\u7cfb\u5217\u30c7\u30fc\u30bf\n            seq_len = 50\n            sequence = np.random.randn(seq_len, 6) + np.random.rand() * 2  # IMU 6\u6b21\u5143\n            sequences.append(sequence)\n            labels.append(gesture)\n    \n    return sequences, labels\n\nprint(\"\u2705 \u30c7\u30fc\u30bf\u30bb\u30c3\u30c8\u30af\u30e9\u30b9\u5b9a\u7fa9\u5b8c\u4e86\")", "outputs": []}, {"cell_type": "code", "execution_count": null, "metadata": {}, "source": "# \u30c7\u30fc\u30bf\u6e96\u5099\nprint(\"\ud83d\udcca \u30c7\u30fc\u30bf\u6e96\u5099\u958b\u59cb...\")\n\n# \u30e2\u30c3\u30af\u30c7\u30fc\u30bf\u3067\u5b66\u7fd2\uff08\u5b9f\u969b\u306eKaggle\u3067\u306f\u3053\u3053\u3067\u5b9f\u30c7\u30fc\u30bf\u8aad\u307f\u8fbc\u307f\uff09\nsequences, labels = create_mock_training_data()\nprint(f\"\u30b7\u30fc\u30b1\u30f3\u30b9\u6570: {len(sequences)}\")\nprint(f\"\u30e6\u30cb\u30fc\u30af\u30e9\u30d9\u30eb\u6570: {len(set(labels))}\")\nprint(f\"\u30b7\u30fc\u30b1\u30f3\u30b9\u5f62\u72b6\u4f8b: {sequences[0].shape}\")\n\n# \u30e9\u30d9\u30eb\u30a8\u30f3\u30b3\u30fc\u30c7\u30a3\u30f3\u30b0\nlabel_encoder = LabelEncoder()\nencoded_labels = label_encoder.fit_transform(labels)\nnum_classes = len(label_encoder.classes_)\n\nprint(f\"\u30af\u30e9\u30b9\u6570: {num_classes}\")\nprint(f\"\u30af\u30e9\u30b9\u4e00\u89a7: {list(label_encoder.classes_)}\")\n\n# \u8a13\u7df4\u30fb\u691c\u8a3c\u5206\u5272\nX_train, X_val, y_train, y_val = train_test_split(\n    sequences, encoded_labels, test_size=0.2, random_state=42, stratify=encoded_labels\n)\n\nprint(f\"\u8a13\u7df4\u30bb\u30c3\u30c8: {len(X_train)} \u30b7\u30fc\u30b1\u30f3\u30b9\")\nprint(f\"\u691c\u8a3c\u30bb\u30c3\u30c8: {len(X_val)} \u30b7\u30fc\u30b1\u30f3\u30b9\")\n\n# \u30c7\u30fc\u30bf\u30ed\u30fc\u30c0\u30fc\u4f5c\u6210\ntrain_dataset = CMIDataset(X_train, y_train, label_encoder)\nval_dataset = CMIDataset(X_val, y_val, label_encoder)\n\ntrain_loader = DataLoader(train_dataset, batch_size=16, shuffle=True)\nval_loader = DataLoader(val_dataset, batch_size=16, shuffle=False)\n\nprint(\"\u2705 \u30c7\u30fc\u30bf\u6e96\u5099\u5b8c\u4e86\")", "outputs": []}, {"cell_type": "code", "execution_count": null, "metadata": {}, "source": "# \u30e2\u30c7\u30eb\u521d\u671f\u5316\nmodel = ImprovedBFRBModel(\n    input_size=7,  # IMU 6\u6b21\u5143 + \u7269\u7406\u7279\u5fb4\u91cf 1\u6b21\u5143\n    hidden_size=128,\n    num_classes=num_classes,\n    dropout=0.3\n)\n\nmodel = model.to(device)\nprint(f\"\u30e2\u30c7\u30eb\u30d1\u30e9\u30e1\u30fc\u30bf\u6570: {sum(p.numel() for p in model.parameters()):,}\")\n\n# \u640d\u5931\u95a2\u6570\u30fb\u30aa\u30d7\u30c6\u30a3\u30de\u30a4\u30b6\u30fc\ncriterion = nn.CrossEntropyLoss()\noptimizer = torch.optim.AdamW(model.parameters(), lr=0.001, weight_decay=0.01)\nscheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=50)\n\nprint(\"\u2705 \u30e2\u30c7\u30eb\u30fb\u30aa\u30d7\u30c6\u30a3\u30de\u30a4\u30b6\u30fc\u6e96\u5099\u5b8c\u4e86\")", "outputs": []}, {"cell_type": "code", "execution_count": null, "metadata": {}, "source": "# \u5b66\u7fd2\u30eb\u30fc\u30d7\ndef train_epoch(model, loader, criterion, optimizer, device):\n    model.train()\n    total_loss = 0\n    correct = 0\n    total = 0\n    \n    for sequences, labels in loader:\n        sequences, labels = sequences.to(device), labels.to(device)\n        \n        optimizer.zero_grad()\n        outputs = model(sequences)\n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step()\n        \n        total_loss += loss.item()\n        _, predicted = outputs.max(1)\n        total += labels.size(0)\n        correct += predicted.eq(labels).sum().item()\n    \n    return total_loss / len(loader), 100. * correct / total\n\ndef validate_epoch(model, loader, criterion, device):\n    model.eval()\n    total_loss = 0\n    correct = 0\n    total = 0\n    \n    with torch.no_grad():\n        for sequences, labels in loader:\n            sequences, labels = sequences.to(device), labels.to(device)\n            outputs = model(sequences)\n            loss = criterion(outputs, labels)\n            \n            total_loss += loss.item()\n            _, predicted = outputs.max(1)\n            total += labels.size(0)\n            correct += predicted.eq(labels).sum().item()\n    \n    return total_loss / len(loader), 100. * correct / total\n\nprint(\"\u2705 \u5b66\u7fd2\u95a2\u6570\u5b9a\u7fa9\u5b8c\u4e86\")", "outputs": []}, {"cell_type": "code", "execution_count": null, "metadata": {}, "source": "# \u5b66\u7fd2\u5b9f\u884c\nprint(\"\ud83d\ude80 \u5b66\u7fd2\u958b\u59cb!\")\nnum_epochs = 20\nbest_acc = 0\n\nfor epoch in range(num_epochs):\n    train_loss, train_acc = train_epoch(model, train_loader, criterion, optimizer, device)\n    val_loss, val_acc = validate_epoch(model, val_loader, criterion, device)\n    scheduler.step()\n    \n    print(f\"Epoch {epoch+1:2d}/{num_epochs} | \"\n          f\"Train Loss: {train_loss:.4f}, Acc: {train_acc:5.1f}% | \"\n          f\"Val Loss: {val_loss:.4f}, Acc: {val_acc:5.1f}%\")\n    \n    # \u6700\u826f\u30e2\u30c7\u30eb\u4fdd\u5b58\n    if val_acc > best_acc:\n        best_acc = val_acc\n        torch.save(model.state_dict(), 'best_model.pth')\n        print(f\"\u2705 \u65b0\u3057\u3044\u6700\u826f\u30e2\u30c7\u30eb\u4fdd\u5b58 (Val Acc: {val_acc:.1f}%)\")\n\nprint(f\"\\n\ud83c\udfaf \u5b66\u7fd2\u5b8c\u4e86! \u6700\u9ad8\u691c\u8a3c\u7cbe\u5ea6: {best_acc:.1f}%\")", "outputs": []}, {"cell_type": "code", "execution_count": null, "metadata": {}, "source": "# \u63a8\u8ad6\u95a2\u6570\uff08Code Competition\u7528\uff09\ndef predict():\n    \"\"\"Code Competition\u7528\u306e\u63a8\u8ad6\u95a2\u6570\"\"\"\n    # \u6700\u826f\u30e2\u30c7\u30eb\u3092\u30ed\u30fc\u30c9\n    model.load_state_dict(torch.load('best_model.pth', map_location=device))\n    model.eval()\n    \n    # \u5b9f\u969b\u306eCode Competition\u3067\u306f\u3001\u3053\u3053\u3067\u30c6\u30b9\u30c8\u30c7\u30fc\u30bf\u3092\u8aad\u307f\u8fbc\u3093\u3067\u4e88\u6e2c\n    # \u4eca\u56de\u306f\u30e2\u30c3\u30af\u3068\u3057\u3066\u6700\u983b\u30af\u30e9\u30b9\u3092\u8fd4\u3059\n    most_common_gesture = label_encoder.inverse_transform([0])[0]  # \u6700\u521d\u306e\u30af\u30e9\u30b9\n    return most_common_gesture\n\n# \u30c6\u30b9\u30c8\u5b9f\u884c\ntest_prediction = predict()\nprint(f\"\ud83d\udd2e \u63a8\u8ad6\u30c6\u30b9\u30c8\u7d50\u679c: {test_prediction}\")\nprint(\"\u2705 Code Competition\u6e96\u5099\u5b8c\u4e86\")", "outputs": []}, {"cell_type": "code", "execution_count": null, "metadata": {}, "source": "# \u5b9f\u9a13\u30b5\u30de\u30ea\u30fc\nprint(\"\\n\ud83d\udccb \u5b66\u7fd2\u5b9f\u9a13\u30b5\u30de\u30ea\u30fc:\")\nprint(\"\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\")\nprint(f\"\ud83c\udfd7\ufe0f  \u30a2\u30fc\u30ad\u30c6\u30af\u30c1\u30e3: BiLSTM(2\u5c64) + GRU + Attention\")\nprint(f\"\ud83d\udd27 \u7269\u7406\u7279\u5fb4\u91cf: \u30d9\u30af\u30c8\u30eb\u5927\u304d\u3055\u8a08\u7b97\")\nprint(f\"\ud83d\udda5\ufe0f  \u30c7\u30d0\u30a4\u30b9: {device}\")\nprint(f\"\ud83d\udcca \u5165\u529b\u6b21\u5143: 6 \u2192 7 (\u7269\u7406\u7279\u5fb4\u91cf\u8ffd\u52a0)\")\nprint(f\"\ud83c\udfaf \u30af\u30e9\u30b9\u6570: {num_classes}\")\nprint(f\"\ud83d\udcc8 \u6700\u9ad8\u691c\u8a3c\u7cbe\u5ea6: {best_acc:.1f}%\")\nprint(f\"\u26a1 \u30e2\u30c7\u30eb\u30d1\u30e9\u30e1\u30fc\u30bf\u6570: {sum(p.numel() for p in model.parameters()):,}\")\nprint(\"\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\")\nprint(\"\\n\ud83d\ude80 \u6539\u826f\u7248BFRB\u30e2\u30c7\u30eb\u5b66\u7fd2\u5b8c\u4e86!\")\nprint(\"\ud83d\udca1 Code Competition submit\u306e\u6e96\u5099\u5b8c\u4e86\")", "outputs": []}], "metadata": {"kernelspec": {"display_name": "Python 3", "language": "python", "name": "python3"}, "language_info": {"codemirror_mode": {"name": "ipython", "version": 3}, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.5"}}, "nbformat": 4, "nbformat_minor": 4}