first commit
This commit is contained in:
commit
741acdeff7
15
.gitignore
vendored
Normal file
15
.gitignore
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
|
||||
# abaikan folder
|
||||
botol_plastik_PET_bening2/
|
||||
botol_plastik_PET_bewarna2/
|
||||
dataset_ready/
|
||||
Kaleng/
|
||||
Residu/
|
||||
Dataset3/
|
||||
|
||||
20260204_145009.jpg
|
||||
20260204_145016.jpg
|
||||
confusion_matrix_epoch_20.png
|
||||
training_history.png
|
||||
training_progress_FINAL.png
|
||||
training_progress.png
|
||||
16592
Dataset.ipynb
Normal file
16592
Dataset.ipynb
Normal file
File diff suppressed because it is too large
Load Diff
747
Training_PetTrash.ipynb
Normal file
747
Training_PetTrash.ipynb
Normal file
@ -0,0 +1,747 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "567f48ce",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"🧹 Membersihkan memori...\n",
|
||||
"==================================================\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"import torch\n",
|
||||
"import torch.nn as nn\n",
|
||||
"import torch.optim as optim\n",
|
||||
"from torchvision import datasets, models, transforms\n",
|
||||
"from torch.utils.data import DataLoader\n",
|
||||
"import time, os, copy, gc\n",
|
||||
"import matplotlib\n",
|
||||
"matplotlib.use('Agg') # ✅ FIX: Use non-interactive backend initially\n",
|
||||
"import matplotlib.pyplot as plt\n",
|
||||
"from tqdm.auto import tqdm\n",
|
||||
"import numpy as np\n",
|
||||
"from sklearn.metrics import confusion_matrix\n",
|
||||
"import warnings\n",
|
||||
"warnings.filterwarnings('ignore')\n",
|
||||
"\n",
|
||||
"# 0. CLEAN MEMORY\n",
|
||||
"print(\"🧹 Membersihkan memori...\")\n",
|
||||
"gc.collect()\n",
|
||||
"if torch.cuda.is_available():\n",
|
||||
" torch.cuda.empty_cache()\n",
|
||||
"print(\"=\"*50)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "10035e1e",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"🚀 Device: cuda\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# 1. CONFIG\n",
|
||||
"\n",
|
||||
"DATA_DIR = 'Dataset3'\n",
|
||||
"IMG_SIZE = 256\n",
|
||||
"BATCH_SIZE = 32\n",
|
||||
"NUM_WORKERS = 4\n",
|
||||
"DEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n",
|
||||
"\n",
|
||||
"torch.manual_seed(42)\n",
|
||||
"torch.backends.cudnn.benchmark = True\n",
|
||||
"print(f\"🚀 Device: {DEVICE}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "2e77d25b",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# 2. TRANSFORM (REALISTIC)\n",
|
||||
"# ==========================================\n",
|
||||
"data_transforms = {\n",
|
||||
" 'train': transforms.Compose([\n",
|
||||
" transforms.Resize((IMG_SIZE, IMG_SIZE)),\n",
|
||||
" transforms.RandomAffine(15, translate=(0.1,0.1), scale=(0.9,1.1)),\n",
|
||||
" transforms.RandomHorizontalFlip(),\n",
|
||||
" #transforms.ColorJitter(0.1,0.1),\n",
|
||||
" transforms.ToTensor(),\n",
|
||||
" transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])\n",
|
||||
" ]),\n",
|
||||
" 'val': transforms.Compose([\n",
|
||||
" transforms.Resize((IMG_SIZE, IMG_SIZE)),\n",
|
||||
" transforms.ToTensor(),\n",
|
||||
" transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])\n",
|
||||
" ])\n",
|
||||
"}\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"id": "e7218bec",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"📊 Kelas: ['Kaleng', 'PET_Bening', 'PET_Berwarna', 'Residu']\n",
|
||||
"📊 Train: 2141 | Val: 936\n",
|
||||
"==================================================\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# 3. DATASET\n",
|
||||
"\n",
|
||||
"image_datasets = {\n",
|
||||
" x: datasets.ImageFolder(os.path.join(DATA_DIR, x), data_transforms[x])\n",
|
||||
" for x in ['train','val']\n",
|
||||
"}\n",
|
||||
"dataloaders = {\n",
|
||||
" 'train': DataLoader(\n",
|
||||
" image_datasets['train'],\n",
|
||||
" batch_size=BATCH_SIZE,\n",
|
||||
" shuffle=True,\n",
|
||||
" num_workers=NUM_WORKERS,\n",
|
||||
" pin_memory=True\n",
|
||||
" ),\n",
|
||||
" 'val': DataLoader(\n",
|
||||
" image_datasets['val'],\n",
|
||||
" batch_size=BATCH_SIZE,\n",
|
||||
" shuffle=False,\n",
|
||||
" num_workers=NUM_WORKERS,\n",
|
||||
" pin_memory=True\n",
|
||||
" )\n",
|
||||
"}\n",
|
||||
"\n",
|
||||
"dataset_sizes = {x: len(image_datasets[x]) for x in ['train','val']}\n",
|
||||
"class_names = image_datasets['train'].classes\n",
|
||||
"\n",
|
||||
"print(f\"📊 Kelas: {class_names}\")\n",
|
||||
"print(f\"📊 Train: {dataset_sizes['train']} | Val: {dataset_sizes['val']}\")\n",
|
||||
"print(\"=\"*50)\n",
|
||||
"\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"id": "26793eb7",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# 4. PLOT FUNCTIONS (FIXED VERSION)\n",
|
||||
"\n",
|
||||
"def save_training_plot(history, save_path='training_progress.png'):\n",
|
||||
" \"\"\"Save training history plot\"\"\"\n",
|
||||
" fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5))\n",
|
||||
" \n",
|
||||
" epochs = range(1, len(history['train_acc']) + 1)\n",
|
||||
" \n",
|
||||
" # Plot Accuracy\n",
|
||||
" ax1.plot(epochs, history['train_acc'], 'b-o', label='Train Acc', linewidth=2, markersize=4)\n",
|
||||
" ax1.plot(epochs, history['val_acc'], 'r-s', label='Val Acc', linewidth=2, markersize=4)\n",
|
||||
" ax1.set_xlabel('Epoch', fontsize=12)\n",
|
||||
" ax1.set_ylabel('Accuracy', fontsize=12)\n",
|
||||
" ax1.set_title('Training & Validation Accuracy', fontsize=14, fontweight='bold')\n",
|
||||
" ax1.legend(loc='best', fontsize=10)\n",
|
||||
" ax1.grid(True, alpha=0.3)\n",
|
||||
" \n",
|
||||
" # Plot Loss\n",
|
||||
" ax2.plot(epochs, history['train_loss'], 'b-o', label='Train Loss', linewidth=2, markersize=4)\n",
|
||||
" ax2.plot(epochs, history['val_loss'], 'r-s', label='Val Loss', linewidth=2, markersize=4)\n",
|
||||
" ax2.set_xlabel('Epoch', fontsize=12)\n",
|
||||
" ax2.set_ylabel('Loss', fontsize=12)\n",
|
||||
" ax2.set_title('Training & Validation Loss', fontsize=14, fontweight='bold')\n",
|
||||
" ax2.legend(loc='best', fontsize=10)\n",
|
||||
" ax2.grid(True, alpha=0.3)\n",
|
||||
" \n",
|
||||
" # Add gap annotation on accuracy plot\n",
|
||||
" if len(history['train_acc']) > 0:\n",
|
||||
" latest_gap = history['train_acc'][-1] - history['val_acc'][-1]\n",
|
||||
" gap_color = 'red' if latest_gap > 0.15 else 'green'\n",
|
||||
" ax1.text(0.02, 0.98, f'Latest Gap: {latest_gap:.3f}', \n",
|
||||
" transform=ax1.transAxes, fontsize=10,\n",
|
||||
" verticalalignment='top', bbox=dict(boxstyle='round', \n",
|
||||
" facecolor=gap_color, alpha=0.3))\n",
|
||||
" \n",
|
||||
" plt.tight_layout()\n",
|
||||
" plt.savefig(save_path, dpi=150, bbox_inches='tight')\n",
|
||||
" plt.close()\n",
|
||||
" print(f\"📊 Training plot saved to {save_path}\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def save_confusion_matrix(y_true, y_pred, class_names, epoch, save_path='confusion_matrix.png'):\n",
|
||||
" \"\"\"Save confusion matrix\"\"\"\n",
|
||||
" cm = confusion_matrix(y_true, y_pred)\n",
|
||||
" cm_normalized = cm.astype('float') / cm.sum(axis=1, keepdims=True)\n",
|
||||
" \n",
|
||||
" fig, ax = plt.subplots(figsize=(10, 8))\n",
|
||||
" \n",
|
||||
" # Plot with better colors\n",
|
||||
" im = ax.imshow(cm_normalized, interpolation='nearest', cmap='Blues')\n",
|
||||
" ax.set_title(f'Confusion Matrix - Epoch {epoch}', fontsize=16, fontweight='bold', pad=20)\n",
|
||||
" \n",
|
||||
" # Colorbar\n",
|
||||
" cbar = plt.colorbar(im, ax=ax)\n",
|
||||
" cbar.set_label('Normalized Frequency', rotation=270, labelpad=20)\n",
|
||||
" \n",
|
||||
" # Axis labels\n",
|
||||
" tick_marks = np.arange(len(class_names))\n",
|
||||
" ax.set_xticks(tick_marks)\n",
|
||||
" ax.set_yticks(tick_marks)\n",
|
||||
" ax.set_xticklabels(class_names, rotation=45, ha='right', fontsize=10)\n",
|
||||
" ax.set_yticklabels(class_names, fontsize=10)\n",
|
||||
" \n",
|
||||
" ax.set_ylabel('True Label', fontsize=12, fontweight='bold')\n",
|
||||
" ax.set_xlabel('Predicted Label', fontsize=12, fontweight='bold')\n",
|
||||
" \n",
|
||||
" # Add text annotations\n",
|
||||
" thresh = cm_normalized.max() / 2.\n",
|
||||
" for i in range(len(class_names)):\n",
|
||||
" for j in range(len(class_names)):\n",
|
||||
" # Show both count and percentage\n",
|
||||
" text = f'{cm[i, j]}\\n({cm_normalized[i, j]:.2%})'\n",
|
||||
" ax.text(j, i, text,\n",
|
||||
" ha=\"center\", va=\"center\", fontsize=9,\n",
|
||||
" color=\"white\" if cm_normalized[i, j] > thresh else \"black\")\n",
|
||||
" \n",
|
||||
" plt.tight_layout()\n",
|
||||
" plt.savefig(save_path, dpi=150, bbox_inches='tight')\n",
|
||||
" plt.close()\n",
|
||||
" print(f\"📊 Confusion matrix saved to {save_path}\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def print_classification_report(y_true, y_pred, class_names):\n",
|
||||
" \"\"\"Print per-class accuracy\"\"\"\n",
|
||||
" cm = confusion_matrix(y_true, y_pred)\n",
|
||||
" \n",
|
||||
" print(\"\\n\" + \"=\"*60)\n",
|
||||
" print(\"📊 PER-CLASS PERFORMANCE\")\n",
|
||||
" print(\"=\"*60)\n",
|
||||
" \n",
|
||||
" for i, class_name in enumerate(class_names):\n",
|
||||
" if cm[i].sum() > 0:\n",
|
||||
" accuracy = cm[i, i] / cm[i].sum()\n",
|
||||
" total = cm[i].sum()\n",
|
||||
" correct = cm[i, i]\n",
|
||||
" print(f\"{class_name:15s} | Acc: {accuracy:6.2%} | Correct: {correct:4d}/{total:4d}\")\n",
|
||||
" else:\n",
|
||||
" print(f\"{class_name:15s} | No samples\")\n",
|
||||
" \n",
|
||||
" print(\"=\"*60 + \"\\n\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"id": "daa3d291",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# 5. TRAIN FUNCTION (FIXED)\n",
|
||||
"def train_model(model, criterion, optimizer, scheduler, epochs=20, unfreeze_epoch=12):\n",
|
||||
" history = {'train_loss':[], 'val_loss':[], 'train_acc':[], 'val_acc':[]}\n",
|
||||
" best_wts = copy.deepcopy(model.state_dict())\n",
|
||||
" best_acc = 0.0\n",
|
||||
" start_time = time.time()\n",
|
||||
"\n",
|
||||
" for epoch in range(epochs):\n",
|
||||
" print(f\"\\n{'='*60}\")\n",
|
||||
" print(f\"EPOCH {epoch+1}/{epochs}\")\n",
|
||||
" print('='*60)\n",
|
||||
"\n",
|
||||
" # Progressive unfreezing\n",
|
||||
" if epoch == unfreeze_epoch:\n",
|
||||
" print(\"🔓 Unfreezing deeper layers (features[7:])...\")\n",
|
||||
" for p in model.features[7:].parameters():\n",
|
||||
" p.requires_grad = True\n",
|
||||
" print(\"✅ More layers are now trainable\\n\")\n",
|
||||
"\n",
|
||||
" for phase in ['train','val']:\n",
|
||||
" model.train() if phase=='train' else model.eval()\n",
|
||||
" loss_sum, correct = 0, 0\n",
|
||||
" all_preds, all_labels = [], []\n",
|
||||
"\n",
|
||||
" pbar = tqdm(dataloaders[phase], \n",
|
||||
" desc=f\"{phase.upper():5s}\",\n",
|
||||
" leave=False,\n",
|
||||
" ncols=100)\n",
|
||||
"\n",
|
||||
" for x, y in pbar:\n",
|
||||
" x, y = x.to(DEVICE), y.to(DEVICE)\n",
|
||||
" optimizer.zero_grad()\n",
|
||||
"\n",
|
||||
" with torch.set_grad_enabled(phase=='train'):\n",
|
||||
" out = model(x)\n",
|
||||
" loss = criterion(out, y)\n",
|
||||
" pred = out.argmax(1)\n",
|
||||
" \n",
|
||||
" if phase=='train':\n",
|
||||
" loss.backward()\n",
|
||||
" # Gradient clipping for stability\n",
|
||||
" torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)\n",
|
||||
" optimizer.step()\n",
|
||||
"\n",
|
||||
" loss_sum += loss.item() * x.size(0)\n",
|
||||
" correct += (pred==y).sum().item()\n",
|
||||
"\n",
|
||||
" # Update progress bar\n",
|
||||
" pbar.set_postfix({\n",
|
||||
" 'loss': f'{loss.item():.4f}',\n",
|
||||
" 'acc': f'{(pred==y).float().mean():.4f}'\n",
|
||||
" })\n",
|
||||
"\n",
|
||||
" if phase=='val':\n",
|
||||
" all_preds.extend(pred.cpu().numpy())\n",
|
||||
" all_labels.extend(y.cpu().numpy())\n",
|
||||
"\n",
|
||||
" epoch_loss = loss_sum / dataset_sizes[phase]\n",
|
||||
" epoch_acc = correct / dataset_sizes[phase]\n",
|
||||
"\n",
|
||||
" history[f'{phase}_loss'].append(epoch_loss)\n",
|
||||
" history[f'{phase}_acc'].append(epoch_acc)\n",
|
||||
"\n",
|
||||
" # Print results\n",
|
||||
" print(f\"{phase.upper():5s} | Loss: {epoch_loss:.4f} | Acc: {epoch_acc:.4f} ({epoch_acc*100:.2f}%)\")\n",
|
||||
"\n",
|
||||
" if phase=='val':\n",
|
||||
" # Update scheduler (for CosineAnnealingLR)\n",
|
||||
" scheduler.step()\n",
|
||||
" \n",
|
||||
" # Calculate and display train-val gap\n",
|
||||
" train_val_gap = history['train_acc'][-1] - history['val_acc'][-1]\n",
|
||||
" gap_status = \"⚠️ OVERFITTING!\" if train_val_gap > 0.15 else \"✅ Good\"\n",
|
||||
" print(f\"\\n📊 Train-Val Gap: {train_val_gap:.4f} {gap_status}\")\n",
|
||||
" \n",
|
||||
" # Save confusion matrix\n",
|
||||
" save_confusion_matrix(all_labels, all_preds, class_names, \n",
|
||||
" epoch+1, f'confusion_matrix_epoch_{epoch+1:02d}.png')\n",
|
||||
" \n",
|
||||
" # Print per-class performance\n",
|
||||
" print_classification_report(all_labels, all_preds, class_names)\n",
|
||||
" \n",
|
||||
" # Save best model\n",
|
||||
" if epoch_acc > best_acc:\n",
|
||||
" best_acc = epoch_acc\n",
|
||||
" best_wts = copy.deepcopy(model.state_dict())\n",
|
||||
" print(f\"⭐ NEW BEST MODEL! Val Acc: {best_acc:.4f}\\n\")\n",
|
||||
"\n",
|
||||
" # Save training progress plot after each epoch\n",
|
||||
" save_training_plot(history, 'training_progress.png')\n",
|
||||
" \n",
|
||||
" # Memory cleanup\n",
|
||||
" gc.collect()\n",
|
||||
" if torch.cuda.is_available():\n",
|
||||
" torch.cuda.empty_cache()\n",
|
||||
"\n",
|
||||
" # Training complete\n",
|
||||
" elapsed = time.time() - start_time\n",
|
||||
" print(f\"\\n{'='*60}\")\n",
|
||||
" print(f\"⏱️ Training completed in {elapsed//60:.0f}m {elapsed%60:.0f}s\")\n",
|
||||
" print(f\"🏆 Best Validation Accuracy: {best_acc:.4f} ({best_acc*100:.2f}%)\")\n",
|
||||
" print('='*60 + \"\\n\")\n",
|
||||
"\n",
|
||||
" # Load best weights\n",
|
||||
" model.load_state_dict(best_wts)\n",
|
||||
" \n",
|
||||
" # Save final plots\n",
|
||||
" save_training_plot(history, 'training_progress_FINAL.png')\n",
|
||||
" \n",
|
||||
" return model, history\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 22,
|
||||
"id": "e33a5eb4",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Trainable params : 1,281,000\n",
|
||||
"Total params : 3,504,872\n",
|
||||
"features.0.0.weight | trainable = False\n",
|
||||
"features.0.1.weight | trainable = False\n",
|
||||
"features.0.1.bias | trainable = False\n",
|
||||
"features.1.conv.0.0.weight | trainable = False\n",
|
||||
"features.1.conv.0.1.weight | trainable = False\n",
|
||||
"features.1.conv.0.1.bias | trainable = False\n",
|
||||
"features.1.conv.1.weight | trainable = False\n",
|
||||
"features.1.conv.2.weight | trainable = False\n",
|
||||
"features.1.conv.2.bias | trainable = False\n",
|
||||
"features.2.conv.0.0.weight | trainable = False\n",
|
||||
"features.2.conv.0.1.weight | trainable = False\n",
|
||||
"features.2.conv.0.1.bias | trainable = False\n",
|
||||
"features.2.conv.1.0.weight | trainable = False\n",
|
||||
"features.2.conv.1.1.weight | trainable = False\n",
|
||||
"features.2.conv.1.1.bias | trainable = False\n",
|
||||
"features.2.conv.2.weight | trainable = False\n",
|
||||
"features.2.conv.3.weight | trainable = False\n",
|
||||
"features.2.conv.3.bias | trainable = False\n",
|
||||
"features.3.conv.0.0.weight | trainable = False\n",
|
||||
"features.3.conv.0.1.weight | trainable = False\n",
|
||||
"features.3.conv.0.1.bias | trainable = False\n",
|
||||
"features.3.conv.1.0.weight | trainable = False\n",
|
||||
"features.3.conv.1.1.weight | trainable = False\n",
|
||||
"features.3.conv.1.1.bias | trainable = False\n",
|
||||
"features.3.conv.2.weight | trainable = False\n",
|
||||
"features.3.conv.3.weight | trainable = False\n",
|
||||
"features.3.conv.3.bias | trainable = False\n",
|
||||
"features.4.conv.0.0.weight | trainable = False\n",
|
||||
"features.4.conv.0.1.weight | trainable = False\n",
|
||||
"features.4.conv.0.1.bias | trainable = False\n",
|
||||
"features.4.conv.1.0.weight | trainable = False\n",
|
||||
"features.4.conv.1.1.weight | trainable = False\n",
|
||||
"features.4.conv.1.1.bias | trainable = False\n",
|
||||
"features.4.conv.2.weight | trainable = False\n",
|
||||
"features.4.conv.3.weight | trainable = False\n",
|
||||
"features.4.conv.3.bias | trainable = False\n",
|
||||
"features.5.conv.0.0.weight | trainable = False\n",
|
||||
"features.5.conv.0.1.weight | trainable = False\n",
|
||||
"features.5.conv.0.1.bias | trainable = False\n",
|
||||
"features.5.conv.1.0.weight | trainable = False\n",
|
||||
"features.5.conv.1.1.weight | trainable = False\n",
|
||||
"features.5.conv.1.1.bias | trainable = False\n",
|
||||
"features.5.conv.2.weight | trainable = False\n",
|
||||
"features.5.conv.3.weight | trainable = False\n",
|
||||
"features.5.conv.3.bias | trainable = False\n",
|
||||
"features.6.conv.0.0.weight | trainable = False\n",
|
||||
"features.6.conv.0.1.weight | trainable = False\n",
|
||||
"features.6.conv.0.1.bias | trainable = False\n",
|
||||
"features.6.conv.1.0.weight | trainable = False\n",
|
||||
"features.6.conv.1.1.weight | trainable = False\n",
|
||||
"features.6.conv.1.1.bias | trainable = False\n",
|
||||
"features.6.conv.2.weight | trainable = False\n",
|
||||
"features.6.conv.3.weight | trainable = False\n",
|
||||
"features.6.conv.3.bias | trainable = False\n",
|
||||
"features.7.conv.0.0.weight | trainable = False\n",
|
||||
"features.7.conv.0.1.weight | trainable = False\n",
|
||||
"features.7.conv.0.1.bias | trainable = False\n",
|
||||
"features.7.conv.1.0.weight | trainable = False\n",
|
||||
"features.7.conv.1.1.weight | trainable = False\n",
|
||||
"features.7.conv.1.1.bias | trainable = False\n",
|
||||
"features.7.conv.2.weight | trainable = False\n",
|
||||
"features.7.conv.3.weight | trainable = False\n",
|
||||
"features.7.conv.3.bias | trainable = False\n",
|
||||
"features.8.conv.0.0.weight | trainable = False\n",
|
||||
"features.8.conv.0.1.weight | trainable = False\n",
|
||||
"features.8.conv.0.1.bias | trainable = False\n",
|
||||
"features.8.conv.1.0.weight | trainable = False\n",
|
||||
"features.8.conv.1.1.weight | trainable = False\n",
|
||||
"features.8.conv.1.1.bias | trainable = False\n",
|
||||
"features.8.conv.2.weight | trainable = False\n",
|
||||
"features.8.conv.3.weight | trainable = False\n",
|
||||
"features.8.conv.3.bias | trainable = False\n",
|
||||
"features.9.conv.0.0.weight | trainable = False\n",
|
||||
"features.9.conv.0.1.weight | trainable = False\n",
|
||||
"features.9.conv.0.1.bias | trainable = False\n",
|
||||
"features.9.conv.1.0.weight | trainable = False\n",
|
||||
"features.9.conv.1.1.weight | trainable = False\n",
|
||||
"features.9.conv.1.1.bias | trainable = False\n",
|
||||
"features.9.conv.2.weight | trainable = False\n",
|
||||
"features.9.conv.3.weight | trainable = False\n",
|
||||
"features.9.conv.3.bias | trainable = False\n",
|
||||
"features.10.conv.0.0.weight | trainable = False\n",
|
||||
"features.10.conv.0.1.weight | trainable = False\n",
|
||||
"features.10.conv.0.1.bias | trainable = False\n",
|
||||
"features.10.conv.1.0.weight | trainable = False\n",
|
||||
"features.10.conv.1.1.weight | trainable = False\n",
|
||||
"features.10.conv.1.1.bias | trainable = False\n",
|
||||
"features.10.conv.2.weight | trainable = False\n",
|
||||
"features.10.conv.3.weight | trainable = False\n",
|
||||
"features.10.conv.3.bias | trainable = False\n",
|
||||
"features.11.conv.0.0.weight | trainable = False\n",
|
||||
"features.11.conv.0.1.weight | trainable = False\n",
|
||||
"features.11.conv.0.1.bias | trainable = False\n",
|
||||
"features.11.conv.1.0.weight | trainable = False\n",
|
||||
"features.11.conv.1.1.weight | trainable = False\n",
|
||||
"features.11.conv.1.1.bias | trainable = False\n",
|
||||
"features.11.conv.2.weight | trainable = False\n",
|
||||
"features.11.conv.3.weight | trainable = False\n",
|
||||
"features.11.conv.3.bias | trainable = False\n",
|
||||
"features.12.conv.0.0.weight | trainable = False\n",
|
||||
"features.12.conv.0.1.weight | trainable = False\n",
|
||||
"features.12.conv.0.1.bias | trainable = False\n",
|
||||
"features.12.conv.1.0.weight | trainable = False\n",
|
||||
"features.12.conv.1.1.weight | trainable = False\n",
|
||||
"features.12.conv.1.1.bias | trainable = False\n",
|
||||
"features.12.conv.2.weight | trainable = False\n",
|
||||
"features.12.conv.3.weight | trainable = False\n",
|
||||
"features.12.conv.3.bias | trainable = False\n",
|
||||
"features.13.conv.0.0.weight | trainable = False\n",
|
||||
"features.13.conv.0.1.weight | trainable = False\n",
|
||||
"features.13.conv.0.1.bias | trainable = False\n",
|
||||
"features.13.conv.1.0.weight | trainable = False\n",
|
||||
"features.13.conv.1.1.weight | trainable = False\n",
|
||||
"features.13.conv.1.1.bias | trainable = False\n",
|
||||
"features.13.conv.2.weight | trainable = False\n",
|
||||
"features.13.conv.3.weight | trainable = False\n",
|
||||
"features.13.conv.3.bias | trainable = False\n",
|
||||
"features.14.conv.0.0.weight | trainable = False\n",
|
||||
"features.14.conv.0.1.weight | trainable = False\n",
|
||||
"features.14.conv.0.1.bias | trainable = False\n",
|
||||
"features.14.conv.1.0.weight | trainable = False\n",
|
||||
"features.14.conv.1.1.weight | trainable = False\n",
|
||||
"features.14.conv.1.1.bias | trainable = False\n",
|
||||
"features.14.conv.2.weight | trainable = False\n",
|
||||
"features.14.conv.3.weight | trainable = False\n",
|
||||
"features.14.conv.3.bias | trainable = False\n",
|
||||
"features.15.conv.0.0.weight | trainable = False\n",
|
||||
"features.15.conv.0.1.weight | trainable = False\n",
|
||||
"features.15.conv.0.1.bias | trainable = False\n",
|
||||
"features.15.conv.1.0.weight | trainable = False\n",
|
||||
"features.15.conv.1.1.weight | trainable = False\n",
|
||||
"features.15.conv.1.1.bias | trainable = False\n",
|
||||
"features.15.conv.2.weight | trainable = False\n",
|
||||
"features.15.conv.3.weight | trainable = False\n",
|
||||
"features.15.conv.3.bias | trainable = False\n",
|
||||
"features.16.conv.0.0.weight | trainable = False\n",
|
||||
"features.16.conv.0.1.weight | trainable = False\n",
|
||||
"features.16.conv.0.1.bias | trainable = False\n",
|
||||
"features.16.conv.1.0.weight | trainable = False\n",
|
||||
"features.16.conv.1.1.weight | trainable = False\n",
|
||||
"features.16.conv.1.1.bias | trainable = False\n",
|
||||
"features.16.conv.2.weight | trainable = False\n",
|
||||
"features.16.conv.3.weight | trainable = False\n",
|
||||
"features.16.conv.3.bias | trainable = False\n",
|
||||
"features.17.conv.0.0.weight | trainable = False\n",
|
||||
"features.17.conv.0.1.weight | trainable = False\n",
|
||||
"features.17.conv.0.1.bias | trainable = False\n",
|
||||
"features.17.conv.1.0.weight | trainable = False\n",
|
||||
"features.17.conv.1.1.weight | trainable = False\n",
|
||||
"features.17.conv.1.1.bias | trainable = False\n",
|
||||
"features.17.conv.2.weight | trainable = False\n",
|
||||
"features.17.conv.3.weight | trainable = False\n",
|
||||
"features.17.conv.3.bias | trainable = False\n",
|
||||
"features.18.0.weight | trainable = False\n",
|
||||
"features.18.1.weight | trainable = False\n",
|
||||
"features.18.1.bias | trainable = False\n",
|
||||
"classifier.1.weight | trainable = True\n",
|
||||
"classifier.1.bias | trainable = True\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"model = models.mobilenet_v2(weights='IMAGENET1K_V1')\n",
|
||||
"\n",
|
||||
"#print(model)\n",
|
||||
"\n",
|
||||
"#for name, param in model.named_parameters():\n",
|
||||
"# if param.requires_grad:\n",
|
||||
"# print(name)\n",
|
||||
"for p in model.features.parameters():\n",
|
||||
" p.requires_grad = False\n",
|
||||
"\n",
|
||||
"#for p in model.features[-1:].parameters():\n",
|
||||
"# p.requires_grad = True\n",
|
||||
"\n",
|
||||
"trainable_params = sum(\n",
|
||||
" p.numel() for p in model.parameters() if p.requires_grad\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"total_params = sum(p.numel() for p in model.parameters())\n",
|
||||
"\n",
|
||||
"print(f\"Trainable params : {trainable_params:,}\")\n",
|
||||
"print(f\"Total params : {total_params:,}\")\n",
|
||||
"\n",
|
||||
"for name, param in model.named_parameters():\n",
|
||||
" print(f\"{name:50} | trainable = {param.requires_grad}\")\n",
|
||||
"\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "e2f814a9",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"\n",
|
||||
"🔧 Setting up model...\n",
|
||||
"✅ Model loaded to cuda\n",
|
||||
"✅ Number of classes: 4\n",
|
||||
"✅ Trainable parameters: 891,204 / 2,228,996 (40.0%)\n",
|
||||
"==================================================\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# 6. MODEL SETUP\n",
|
||||
"\n",
|
||||
"print(\"\\n🔧 Setting up model...\")\n",
|
||||
"model = models.mobilenet_v2(weights='IMAGENET1K_V1')\n",
|
||||
"\n",
|
||||
"# Freeze all features initially\n",
|
||||
"for p in model.features.parameters():\n",
|
||||
" p.requires_grad = False\n",
|
||||
"\n",
|
||||
"# Unfreeze only last 2 blocks\n",
|
||||
"for p in model.features[-2:].parameters():\n",
|
||||
" p.requires_grad = True\n",
|
||||
"\n",
|
||||
"# Classifier\n",
|
||||
"num_ftrs = model.classifier[1].in_features\n",
|
||||
"model.classifier = nn.Sequential(\n",
|
||||
" nn.Dropout(0.4),\n",
|
||||
" nn.Linear(num_ftrs, len(class_names))\n",
|
||||
")\n",
|
||||
"model.to(DEVICE)\n",
|
||||
"\n",
|
||||
"print(f\"✅ Model loaded to {DEVICE}\")\n",
|
||||
"print(f\"✅ Number of classes: {len(class_names)}\")\n",
|
||||
"\n",
|
||||
"# Count trainable parameters\n",
|
||||
"trainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad)\n",
|
||||
"total_params = sum(p.numel() for p in model.parameters())\n",
|
||||
"print(f\"✅ Trainable parameters: {trainable_params:,} / {total_params:,} ({trainable_params/total_params*100:.1f}%)\")\n",
|
||||
"print(\"=\"*50)\n",
|
||||
"\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "4a7c01e0",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# 7. LOSS & OPTIMIZER\n",
|
||||
"# ==========================================\n",
|
||||
"class_weights = torch.tensor([1.2, 1.5, 1.2, 0.8]).to(DEVICE)\n",
|
||||
"criterion = nn.CrossEntropyLoss(label_smoothing=0.3)\n",
|
||||
"\n",
|
||||
"optimizer = optim.Adam([\n",
|
||||
" {'params': model.features[-2:].parameters(), 'lr': 1e-5},\n",
|
||||
" {'params': model.classifier.parameters(), 'lr': 1e-4}\n",
|
||||
"], weight_decay=1e-4)\n",
|
||||
"\n",
|
||||
"scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=20)\n",
|
||||
"\n",
|
||||
"print(\"✅ Optimizer: Adam\")\n",
|
||||
"print(\"✅ Scheduler: CosineAnnealingLR\")\n",
|
||||
"print(\"✅ Loss: CrossEntropyLoss (label_smoothing=0.3)\")\n",
|
||||
"print(\"=\"*50)\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "d94e464b",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# ==========================================\n",
|
||||
"# 8. TRAIN\n",
|
||||
"# ==========================================\n",
|
||||
"print(\"\\n🚀 STARTING TRAINING...\\n\")\n",
|
||||
"model, history = train_model(model, criterion, optimizer, scheduler, \n",
|
||||
" epochs=20, unfreeze_epoch=12)\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "619b3f31",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# ==========================================\n",
|
||||
"# 9. SAVE MODEL\n",
|
||||
"# ==========================================\n",
|
||||
"print(\"\\n💾 Saving model...\")\n",
|
||||
"save_dict = {\n",
|
||||
" 'model_state_dict': model.state_dict(),\n",
|
||||
" 'class_names': class_names,\n",
|
||||
" 'history': history,\n",
|
||||
" 'optimizer_state_dict': optimizer.state_dict()\n",
|
||||
"}\n",
|
||||
"\n",
|
||||
"torch.save(save_dict, \"model_sampah_BEST2.pth\")\n",
|
||||
"print(\"✅ Model saved to 'model_sampah_BEST2.pth'\")\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "3ab728ec",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# ==========================================\n",
|
||||
"# 10. FINAL SUMMARY\n",
|
||||
"# ==========================================\n",
|
||||
"print(\"\\n\" + \"=\"*60)\n",
|
||||
"print(\"📊 TRAINING SUMMARY\")\n",
|
||||
"print(\"=\"*60)\n",
|
||||
"print(f\"Final Train Acc: {history['train_acc'][-1]:.4f} ({history['train_acc'][-1]*100:.2f}%)\")\n",
|
||||
"print(f\"Final Val Acc: {history['val_acc'][-1]:.4f} ({history['val_acc'][-1]*100:.2f}%)\")\n",
|
||||
"print(f\"Best Val Acc: {max(history['val_acc']):.4f} ({max(history['val_acc'])*100:.2f}%)\")\n",
|
||||
"print(f\"Train-Val Gap: {history['train_acc'][-1] - history['val_acc'][-1]:.4f}\")\n",
|
||||
"print(\"=\"*60)\n",
|
||||
"\n",
|
||||
"print(\"\\n📁 Generated files:\")\n",
|
||||
"print(\" - model_sampah_BEST2.pth (saved model)\")\n",
|
||||
"print(\" - training_progress_FINAL.png (final training curves)\")\n",
|
||||
"print(f\" - confusion_matrix_epoch_XX.png (confusion matrix for each epoch)\")\n",
|
||||
"print(\"\\n✅ ALL DONE! 🎉\\n\")"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "skripsi",
|
||||
"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.10.19"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
587
coba.ipynb
Normal file
587
coba.ipynb
Normal file
@ -0,0 +1,587 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"id": "ddc93ea2",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Classes: ['Kaleng', 'Residu', 'botol_plastik_PET_bening2', 'botol_plastik_PET_bewarna']\n",
|
||||
"✅ Model loaded\n",
|
||||
"\n",
|
||||
"🧠 Prediction: botol_plastik_PET_bening2\n",
|
||||
"📊 Confidence: 83.07%\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"import torch\n",
|
||||
"import torch.nn as nn\n",
|
||||
"from torchvision import models, transforms\n",
|
||||
"from PIL import Image\n",
|
||||
"\n",
|
||||
"# ==========================================\n",
|
||||
"# CONFIG\n",
|
||||
"# ==========================================\n",
|
||||
"MODEL_PATH = 'model_sampah_final_3.pth'\n",
|
||||
"LABEL_PATH = 'labels.txt'\n",
|
||||
"IMG_PATH = '20260204_145002.jpg'\n",
|
||||
"\n",
|
||||
"DEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n",
|
||||
"\n",
|
||||
"# ==========================================\n",
|
||||
"# LOAD LABELS\n",
|
||||
"# ==========================================\n",
|
||||
"with open(LABEL_PATH, 'r') as f:\n",
|
||||
" class_names = [line.strip() for line in f.readlines()]\n",
|
||||
"\n",
|
||||
"print(\"Classes:\", class_names)\n",
|
||||
"\n",
|
||||
"# ==========================================\n",
|
||||
"# LOAD MODEL\n",
|
||||
"# ==========================================\n",
|
||||
"model = models.mobilenet_v2(weights=None)\n",
|
||||
"num_ftrs = model.classifier[1].in_features\n",
|
||||
"model.classifier[1] = nn.Linear(num_ftrs, len(class_names))\n",
|
||||
"\n",
|
||||
"model.load_state_dict(torch.load(MODEL_PATH, map_location=DEVICE))\n",
|
||||
"model.to(DEVICE)\n",
|
||||
"model.eval()\n",
|
||||
"\n",
|
||||
"print(\"✅ Model loaded\")\n",
|
||||
"\n",
|
||||
"# ==========================================\n",
|
||||
"# PREPROCESS (SAME AS TRAINING)\n",
|
||||
"# ==========================================\n",
|
||||
"transform = transforms.Compose([\n",
|
||||
" transforms.Resize((224, 224)),\n",
|
||||
" transforms.ToTensor(),\n",
|
||||
" transforms.Normalize(\n",
|
||||
" mean=[0.485, 0.456, 0.406],\n",
|
||||
" std=[0.229, 0.224, 0.225]\n",
|
||||
" )\n",
|
||||
"])\n",
|
||||
"\n",
|
||||
"# ==========================================\n",
|
||||
"# LOAD IMAGE\n",
|
||||
"# ==========================================\n",
|
||||
"img = Image.open(IMG_PATH).convert(\"RGB\")\n",
|
||||
"input_tensor = transform(img).unsqueeze(0).to(DEVICE)\n",
|
||||
"\n",
|
||||
"# ==========================================\n",
|
||||
"# INFERENCE\n",
|
||||
"# ==========================================\n",
|
||||
"with torch.no_grad():\n",
|
||||
" outputs = model(input_tensor)\n",
|
||||
" probs = torch.softmax(outputs, dim=1)\n",
|
||||
" confidence, pred = torch.max(probs, 1)\n",
|
||||
"\n",
|
||||
"# ==========================================\n",
|
||||
"# RESULT\n",
|
||||
"# ==========================================\n",
|
||||
"label = class_names[pred.item()]\n",
|
||||
"score = confidence.item()\n",
|
||||
"\n",
|
||||
"print(f\"\\n🧠 Prediction: {label}\")\n",
|
||||
"print(f\"📊 Confidence: {score*100:.2f}%\")\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "17588856",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Folder 'data_gambar' berhasil dibuat!\n",
|
||||
"\n",
|
||||
"--- INSTRUKSI ---\n",
|
||||
"Tekan 'S' untuk menyimpan gambar (Crop)\n",
|
||||
"Tekan 'Q' untuk keluar\n",
|
||||
"Berhasil disimpan: data_gambar/gambar_20260204_142759.jpg\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"import cv2\n",
|
||||
"import os\n",
|
||||
"import time\n",
|
||||
"\n",
|
||||
"# --- KONFIGURASI ---\n",
|
||||
"NAMA_FOLDER = \"data_gambar\" # Nama folder penyimpanan\n",
|
||||
"UKURAN_BOX = 300 # Ukuran kotak ROI (piksel)\n",
|
||||
"\n",
|
||||
"# 1. CEK & BUAT FOLDER JIKA BELUM ADA\n",
|
||||
"if not os.path.exists(NAMA_FOLDER):\n",
|
||||
" os.makedirs(NAMA_FOLDER)\n",
|
||||
" print(f\"Folder '{NAMA_FOLDER}' berhasil dibuat!\")\n",
|
||||
"else:\n",
|
||||
" print(f\"Folder '{NAMA_FOLDER}' sudah ada. Gambar akan disimpan di sana.\")\n",
|
||||
"\n",
|
||||
"# 2. SETUP KAMERA\n",
|
||||
"cap = cv2.VideoCapture(0)\n",
|
||||
"\n",
|
||||
"print(\"\\n--- INSTRUKSI ---\")\n",
|
||||
"print(\"Tekan 'S' untuk menyimpan gambar (Crop)\")\n",
|
||||
"print(\"Tekan 'Q' untuk keluar\")\n",
|
||||
"\n",
|
||||
"while True:\n",
|
||||
" ret, frame = cap.read()\n",
|
||||
" if not ret:\n",
|
||||
" print(\"Gagal membaca kamera\")\n",
|
||||
" break\n",
|
||||
"\n",
|
||||
" # Flip frame (efek cermin)\n",
|
||||
" frame = cv2.flip(frame, 1)\n",
|
||||
"\n",
|
||||
" # 3. HITUNG KOORDINAT ROI (TENGAH)\n",
|
||||
" height, width, _ = frame.shape\n",
|
||||
" x1 = (width - UKURAN_BOX) // 2\n",
|
||||
" y1 = (height - UKURAN_BOX) // 2\n",
|
||||
" x2 = x1 + UKURAN_BOX\n",
|
||||
" y2 = y1 + UKURAN_BOX\n",
|
||||
"\n",
|
||||
" # 4. GAMBAR KOTAK PANDUAN (HIJAU)\n",
|
||||
" # Ini hanya visualisasi, tidak ikut tersimpan saat di-crop nanti\n",
|
||||
" cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2)\n",
|
||||
" \n",
|
||||
" # Tambahkan teks instruksi di layar\n",
|
||||
" cv2.putText(frame, \"Tekan 'S' untuk Simpan\", (x1, y2 + 25), \n",
|
||||
" cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 1)\n",
|
||||
"\n",
|
||||
" # Tampilkan layar\n",
|
||||
" cv2.imshow('Kamera Pengumpul Data', frame)\n",
|
||||
"\n",
|
||||
" # 5. BACA TOMBOL KEYBOARD\n",
|
||||
" key = cv2.waitKey(1) & 0xFF\n",
|
||||
"\n",
|
||||
" # --- LOGIKA SIMPAN (CROP) ---\n",
|
||||
" if key == ord('s') or key == ord('S'):\n",
|
||||
" # Ambil potongan gambar asli (bersih tanpa garis kotak hijau)\n",
|
||||
" # Kita ambil dari koordinat yang sudah dihitung di atas\n",
|
||||
" roi_crop = frame[y1:y2, x1:x2]\n",
|
||||
"\n",
|
||||
" # Buat nama file unik (berdasarkan waktu detik ini)\n",
|
||||
" timestamp = time.strftime(\"%Y%m%d_%H%M%S\")\n",
|
||||
" filename = f\"{NAMA_FOLDER}/gambar_{timestamp}.jpg\"\n",
|
||||
"\n",
|
||||
" # Simpan ke folder\n",
|
||||
" cv2.imwrite(filename, roi_crop)\n",
|
||||
" print(f\"Berhasil disimpan: {filename}\")\n",
|
||||
" \n",
|
||||
" # Efek visual sesaat (layar berkedip putih sedikit tanda tersimpan) - Opsional\n",
|
||||
" cv2.putText(frame, \"TERSIMPAN!\", (x1, y1-10), \n",
|
||||
" cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)\n",
|
||||
" cv2.imshow('Kamera Pengumpul Data', frame)\n",
|
||||
" cv2.waitKey(200) # Jeda sebentar biar tulisan terbaca\n",
|
||||
"\n",
|
||||
" # Keluar program\n",
|
||||
" elif key == ord('q'):\n",
|
||||
" break\n",
|
||||
"\n",
|
||||
"cap.release()\n",
|
||||
"cv2.destroyAllWindows()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 12,
|
||||
"id": "724ac841",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Sedang memuat model... (tunggu sebentar)\n",
|
||||
"Model siap!\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"import cv2\n",
|
||||
"import numpy as np\n",
|
||||
"import tensorflow as tf\n",
|
||||
"from tensorflow.keras.applications.mobilenet_v2 import MobileNetV2, preprocess_input, decode_predictions\n",
|
||||
"from tensorflow.keras.preprocessing import image\n",
|
||||
"\n",
|
||||
"# 1. INISIALISASI MODEL\n",
|
||||
"print(\"Sedang memuat model... (tunggu sebentar)\")\n",
|
||||
"model = MobileNetV2(weights='imagenet', include_top=True)\n",
|
||||
"print(\"Model siap!\")\n",
|
||||
"\n",
|
||||
"# 2. SETUP KAMERA\n",
|
||||
"cap = cv2.VideoCapture(0) \n",
|
||||
"\n",
|
||||
"# Ukuran kotak ROI\n",
|
||||
"BOX_SIZE = 300 \n",
|
||||
"\n",
|
||||
"while True:\n",
|
||||
" ret, frame = cap.read()\n",
|
||||
" if not ret:\n",
|
||||
" break\n",
|
||||
"\n",
|
||||
" # Flip frame (efek cermin)\n",
|
||||
" frame = cv2.flip(frame, 1)\n",
|
||||
"\n",
|
||||
" # Ambil ukuran frame\n",
|
||||
" height, width, _ = frame.shape\n",
|
||||
" \n",
|
||||
" # Hitung koordinat tengah untuk kotak hijau\n",
|
||||
" x1 = (width - BOX_SIZE) // 2\n",
|
||||
" y1 = (height - BOX_SIZE) // 2\n",
|
||||
" x2 = x1 + BOX_SIZE\n",
|
||||
" y2 = y1 + BOX_SIZE\n",
|
||||
"\n",
|
||||
" # --- PROSES AI (Latar Belakang) ---\n",
|
||||
" # Kita tetap perlu crop gambar untuk diserahkan ke AI,\n",
|
||||
" # tapi tidak kita tempel/tampilkan ke layar utama.\n",
|
||||
" roi_frame = frame[y1:y2, x1:x2]\n",
|
||||
"\n",
|
||||
" if roi_frame.size != 0:\n",
|
||||
" # Resize ke 224x224 (Wajib untuk MobileNet)\n",
|
||||
" img_input = cv2.resize(roi_frame, (224, 224))\n",
|
||||
" \n",
|
||||
" # Konversi ke array & preprocess\n",
|
||||
" img_array = image.img_to_array(img_input)\n",
|
||||
" img_array = np.expand_dims(img_array, axis=0)\n",
|
||||
" img_array = preprocess_input(img_array)\n",
|
||||
"\n",
|
||||
" # Prediksi\n",
|
||||
" preds = model.predict(img_array, verbose=0)\n",
|
||||
" results = decode_predictions(preds, top=1)[0]\n",
|
||||
" \n",
|
||||
" # Ambil Label & Akurasi\n",
|
||||
" label = results[0][1]\n",
|
||||
" confidence = results[0][2] * 100\n",
|
||||
"\n",
|
||||
" # Tampilkan Teks Hasil Prediksi di atas kotak\n",
|
||||
" text_display = f\"{label}: {confidence:.1f}%\"\n",
|
||||
" cv2.putText(frame, text_display, (x1, y1 - 10), \n",
|
||||
" cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)\n",
|
||||
"\n",
|
||||
" # --- VISUALISASI UTAMA ---\n",
|
||||
" # Hanya gambar kotak hijau panduan\n",
|
||||
" cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2)\n",
|
||||
" \n",
|
||||
" # Tampilkan layar utama saja\n",
|
||||
" cv2.imshow('MobileNetV2 Clean View', frame)\n",
|
||||
"\n",
|
||||
" if cv2.waitKey(1) & 0xFF == ord('q'):\n",
|
||||
" break\n",
|
||||
"\n",
|
||||
"cap.release()\n",
|
||||
"cv2.destroyAllWindows()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 16,
|
||||
"id": "95017f76",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Memulai proses download ke folder 'dataset_botol_bewarna_bing'...\n",
|
||||
"\n",
|
||||
"--> Sedang mencari gambar: Super O2\n",
|
||||
"[%] Downloading Images to g:\\My Drive\\Smart-Bin\\dataset\\dataset_botol_bewarna_bing\\Super O2\n",
|
||||
"[!] Issue getting: https://img.21food.com/img/product/2012/10/25/indogalung-01510470.jpg\n",
|
||||
"[!] Error:: Remote end closed connection without response\n",
|
||||
"[Error]Invalid image, not saving https://lookaside.fbsbx.com/lookaside/crawler/media/?media_id=4116299515060023\n",
|
||||
"\n",
|
||||
"[!] Issue getting: https://lookaside.fbsbx.com/lookaside/crawler/media/?media_id=4116299515060023\n",
|
||||
"[!] Error:: Invalid image, not saving https://lookaside.fbsbx.com/lookaside/crawler/media/?media_id=4116299515060023\n",
|
||||
"\n",
|
||||
"[Error]Invalid image, not saving https://down-id.img.susercontent.com/file/039e5682e363425a1b87b8a725a4253e\n",
|
||||
"\n",
|
||||
"[!] Issue getting: https://down-id.img.susercontent.com/file/039e5682e363425a1b87b8a725a4253e\n",
|
||||
"[!] Error:: Invalid image, not saving https://down-id.img.susercontent.com/file/039e5682e363425a1b87b8a725a4253e\n",
|
||||
"\n",
|
||||
"[Error]Invalid image, not saving https://cf.shopee.co.id/file/734aeebfee8922e2c5ec88523cb5e6e5_tn\n",
|
||||
"\n",
|
||||
"[!] Issue getting: https://cf.shopee.co.id/file/734aeebfee8922e2c5ec88523cb5e6e5_tn\n",
|
||||
"[!] Error:: Invalid image, not saving https://cf.shopee.co.id/file/734aeebfee8922e2c5ec88523cb5e6e5_tn\n",
|
||||
"\n",
|
||||
"[Error]Invalid image, not saving https://down-id.img.susercontent.com/file/9bd8bd58dc85b83e4944981ce2f97e73\n",
|
||||
"\n",
|
||||
"[!] Issue getting: https://down-id.img.susercontent.com/file/9bd8bd58dc85b83e4944981ce2f97e73\n",
|
||||
"[!] Error:: Invalid image, not saving https://down-id.img.susercontent.com/file/9bd8bd58dc85b83e4944981ce2f97e73\n",
|
||||
"\n",
|
||||
"[!] Issue getting: https://ceklist.id/wp-content/uploads/2023/12/2858-super-o2-air-mineral-sportivo-600ml-1-370x370.jpg\n",
|
||||
"[!] Error:: <urlopen error [WinError 10060] A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond>\n",
|
||||
"[Error]Invalid image, not saving https://down-id.img.susercontent.com/file/sg-11134201-23010-r89m6d1to1lv22\n",
|
||||
"\n",
|
||||
"[!] Issue getting: https://down-id.img.susercontent.com/file/sg-11134201-23010-r89m6d1to1lv22\n",
|
||||
"[!] Error:: Invalid image, not saving https://down-id.img.susercontent.com/file/sg-11134201-23010-r89m6d1to1lv22\n",
|
||||
"\n",
|
||||
"[Error]Invalid image, not saving https://down-id.img.susercontent.com/file/734aeebfee8922e2c5ec88523cb5e6e5\n",
|
||||
"\n",
|
||||
"[!] Issue getting: https://down-id.img.susercontent.com/file/734aeebfee8922e2c5ec88523cb5e6e5\n",
|
||||
"[!] Error:: Invalid image, not saving https://down-id.img.susercontent.com/file/734aeebfee8922e2c5ec88523cb5e6e5\n",
|
||||
"\n",
|
||||
"[Error]Invalid image, not saving https://down-id.img.susercontent.com/file/0859ac0c9a420144d06c20bd2a16d154\n",
|
||||
"\n",
|
||||
"[!] Issue getting: https://down-id.img.susercontent.com/file/0859ac0c9a420144d06c20bd2a16d154\n",
|
||||
"[!] Error:: Invalid image, not saving https://down-id.img.susercontent.com/file/0859ac0c9a420144d06c20bd2a16d154\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"[%] Done. Downloaded 50 images.\n",
|
||||
" Selesai download Super O2!\n",
|
||||
"\n",
|
||||
"========================================\n",
|
||||
"SEMUA SELESAI!\n",
|
||||
"Cek folder 'dataset_botol_bewarna_bing' di komputer Anda.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from bing_image_downloader import downloader\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"# --- KONFIGURASI ---\n",
|
||||
"# 1. Tentukan folder penyimpanan utama\n",
|
||||
"NAMA_FOLDER_UTAMA = \"dataset_botol_bewarna_bing\"\n",
|
||||
"\n",
|
||||
"# 2. Tentukan kata kunci benda yang mau dicari\n",
|
||||
"# Format: \"Kata Kunci\"\n",
|
||||
"daftar_pencarian = [\n",
|
||||
" \"Super O2\"\n",
|
||||
"]\n",
|
||||
"\n",
|
||||
"# 3. Berapa banyak gambar per benda?\n",
|
||||
"JUMLAH_GAMBAR = 50 # Jangan terlalu banyak dulu untuk tes (max 100-200 aman)\n",
|
||||
"\n",
|
||||
"# --- MULAI DOWNLOAD ---\n",
|
||||
"print(f\"Memulai proses download ke folder '{NAMA_FOLDER_UTAMA}'...\\n\")\n",
|
||||
"\n",
|
||||
"for kata_kunci in daftar_pencarian:\n",
|
||||
" print(f\"--> Sedang mencari gambar: {kata_kunci}\")\n",
|
||||
" \n",
|
||||
" downloader.download(\n",
|
||||
" kata_kunci, \n",
|
||||
" limit=JUMLAH_GAMBAR, \n",
|
||||
" output_dir=NAMA_FOLDER_UTAMA, \n",
|
||||
" adult_filter_off=True, # Matikan filter konten dewasa (aman untuk objek umum)\n",
|
||||
" force_replace=False, \n",
|
||||
" timeout=60, \n",
|
||||
" verbose=False\n",
|
||||
" )\n",
|
||||
" print(f\" Selesai download {kata_kunci}!\\n\")\n",
|
||||
"\n",
|
||||
"print(\"=\"*40)\n",
|
||||
"print(\"SEMUA SELESAI!\")\n",
|
||||
"print(f\"Cek folder '{NAMA_FOLDER_UTAMA}' di komputer Anda.\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 23,
|
||||
"id": "643a9a12",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Ditemukan 339 gambar asli.\n",
|
||||
"Memproses dengan Threading...\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
" 27%|██▋ | 90/339 [04:21<04:26, 1.07s/it] c:\\Users\\MSI-PC\\miniconda3\\envs\\skripsi\\lib\\site-packages\\PIL\\Image.py:1034: UserWarning: Palette images with Transparency expressed in bytes should be converted to RGBA images\n",
|
||||
" warnings.warn(\n",
|
||||
"100%|██████████| 339/339 [06:42<00:00, 1.19s/it]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"\n",
|
||||
"SUKSES!\n",
|
||||
"Cek hasil di folder: dataset_augmented/botol_plastik_PET_bewarna\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"import cv2\n",
|
||||
"import numpy as np\n",
|
||||
"from tensorflow.keras.preprocessing.image import ImageDataGenerator, img_to_array, load_img\n",
|
||||
"# GANTI DARI ProcessPoolExecutor KE ThreadPoolExecutor\n",
|
||||
"from concurrent.futures import ThreadPoolExecutor \n",
|
||||
"from tqdm import tqdm \n",
|
||||
"\n",
|
||||
"# --- KONFIGURASI ---\n",
|
||||
"FOLDER_SUMBER = \"botol_plastik_PET_bewarna2\"\n",
|
||||
"FOLDER_HASIL = \"dataset_augmented/botol_plastik_PET_bewarna\"\n",
|
||||
"JUMLAH_COPY = 5\n",
|
||||
"NAMA_BASE = \"botol_pet\"\n",
|
||||
"# Threading lebih ringan, kita bisa set angka aman misal 4 atau 8 worker\n",
|
||||
"MAX_WORKERS = 4 \n",
|
||||
"\n",
|
||||
"if not os.path.exists(FOLDER_HASIL):\n",
|
||||
" os.makedirs(FOLDER_HASIL)\n",
|
||||
"\n",
|
||||
"# Fungsi Worker \n",
|
||||
"def proses_gambar_spesifik(data):\n",
|
||||
" filepath, index_awal = data\n",
|
||||
" \n",
|
||||
" try:\n",
|
||||
" # Load gambar\n",
|
||||
" img = load_img(filepath)\n",
|
||||
" x = img_to_array(img)\n",
|
||||
" x = x.reshape((1,) + x.shape)\n",
|
||||
"\n",
|
||||
" # Definisikan ImageDataGenerator DI DALAM fungsi agar thread-safe\n",
|
||||
" datagen = ImageDataGenerator(\n",
|
||||
" rotation_range=30,\n",
|
||||
" width_shift_range=0.2,\n",
|
||||
" height_shift_range=0.2,\n",
|
||||
" shear_range=0.2,\n",
|
||||
" zoom_range=0.2,\n",
|
||||
" horizontal_flip=True,\n",
|
||||
" fill_mode='nearest'\n",
|
||||
" )\n",
|
||||
" \n",
|
||||
" # Loop generate gambar\n",
|
||||
" i = 0\n",
|
||||
" for batch in datagen.flow(x, batch_size=1):\n",
|
||||
" gambar_aug = batch[0].astype('uint8') \n",
|
||||
" gambar_aug = cv2.cvtColor(gambar_aug, cv2.COLOR_RGB2BGR)\n",
|
||||
" \n",
|
||||
" nomor_urut = index_awal + i\n",
|
||||
" nama_file_baru = f\"{NAMA_BASE}_{nomor_urut:05d}.jpg\"\n",
|
||||
" path_simpan = os.path.join(FOLDER_HASIL, nama_file_baru)\n",
|
||||
" \n",
|
||||
" cv2.imwrite(path_simpan, gambar_aug)\n",
|
||||
" \n",
|
||||
" i += 1\n",
|
||||
" if i >= JUMLAH_COPY:\n",
|
||||
" break\n",
|
||||
" \n",
|
||||
" except Exception as e:\n",
|
||||
" print(f\"Error pada {filepath}: {e}\")\n",
|
||||
"\n",
|
||||
"# --- MAIN PROGRAM ---\n",
|
||||
"if __name__ == '__main__':\n",
|
||||
" semua_file = [f for f in os.listdir(FOLDER_SUMBER) \n",
|
||||
" if f.lower().endswith(('.png', '.jpg', '.jpeg'))]\n",
|
||||
" semua_file.sort()\n",
|
||||
" \n",
|
||||
" print(f\"Ditemukan {len(semua_file)} gambar asli.\")\n",
|
||||
" print(f\"Memproses dengan Threading...\")\n",
|
||||
"\n",
|
||||
" tasks = []\n",
|
||||
" counter = 1\n",
|
||||
" \n",
|
||||
" for nama_file in semua_file:\n",
|
||||
" full_path = os.path.join(FOLDER_SUMBER, nama_file)\n",
|
||||
" tasks.append((full_path, counter))\n",
|
||||
" counter += JUMLAH_COPY\n",
|
||||
"\n",
|
||||
" # GUNAKAN ThreadPoolExecutor\n",
|
||||
" with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:\n",
|
||||
" list(tqdm(executor.map(proses_gambar_spesifik, tasks), total=len(tasks)))\n",
|
||||
"\n",
|
||||
" print(\"\\nSUKSES!\")\n",
|
||||
" print(f\"Cek hasil di folder: {FOLDER_HASIL}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 24,
|
||||
"id": "f865eb1c",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Ditemukan 1695 file. Memulai rename...\n",
|
||||
"Selesai! Semua file sudah direname.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"\n",
|
||||
"# --- KONFIGURASI ---\n",
|
||||
"FOLDER_TARGET = \"dataset_augmented/botol_plastik_PET_bewarna\" # Ganti dengan nama foldermu\n",
|
||||
"NAMA_BARU = \"botol_pet_bewarna\" # Awalan nama file baru\n",
|
||||
"\n",
|
||||
"# 1. Ambil semua file gambar\n",
|
||||
"files = [f for f in os.listdir(FOLDER_TARGET) \n",
|
||||
" if f.lower().endswith(('.png', '.jpg', '.jpeg'))]\n",
|
||||
"\n",
|
||||
"# 2. Urutkan dulu (biar tidak acak)\n",
|
||||
"files.sort()\n",
|
||||
"\n",
|
||||
"print(f\"Ditemukan {len(files)} file. Memulai rename...\")\n",
|
||||
"\n",
|
||||
"# 3. Proses Rename\n",
|
||||
"for index, nama_file_lama in enumerate(files):\n",
|
||||
" # Ambil ekstensi file asli (.jpg atau .png)\n",
|
||||
" ekstensi = os.path.splitext(nama_file_lama)[1]\n",
|
||||
" \n",
|
||||
" # Buat nama baru dengan format 5 digit angka (00001)\n",
|
||||
" # index + 1 agar mulai dari 1, bukan 0\n",
|
||||
" nama_file_baru = f\"{NAMA_BARU}_{index + 1:05d}{ekstensi}\"\n",
|
||||
" \n",
|
||||
" # Gabungkan dengan path folder\n",
|
||||
" path_lama = os.path.join(FOLDER_TARGET, nama_file_lama)\n",
|
||||
" path_baru = os.path.join(FOLDER_TARGET, nama_file_baru)\n",
|
||||
" \n",
|
||||
" # Lakukan rename\n",
|
||||
" try:\n",
|
||||
" os.rename(path_lama, path_baru)\n",
|
||||
" except OSError as e:\n",
|
||||
" print(f\"Error pada {nama_file_lama}: {e}\")\n",
|
||||
"\n",
|
||||
"print(\"Selesai! Semua file sudah direname.\")"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "skripsi",
|
||||
"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.10.19"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
4
labels.txt
Normal file
4
labels.txt
Normal file
@ -0,0 +1,4 @@
|
||||
Botol_PET_Bening
|
||||
Botol_PET_Bewarna
|
||||
Kaleng
|
||||
Residu
|
||||
Loading…
x
Reference in New Issue
Block a user