{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceType":"competition","sourceId":3649,"databundleVersionId":46718}],"dockerImageVersionId":31329,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nfrom sklearn.model_selection import train_test_split\nimport tensorflow as tf\nfrom tensorflow import keras\nfrom tensorflow.keras import layers, datasets\nimport warnings\n\nwarnings.filterwarnings('ignore')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-17T07:47:28.553563Z","iopub.execute_input":"2026-04-17T07:47:28.554738Z","iopub.status.idle":"2026-04-17T07:47:28.560925Z","shell.execute_reply.started":"2026-04-17T07:47:28.554696Z","shell.execute_reply":"2026-04-17T07:47:28.559835Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 단계 1: 데이터 로드 및 탐색","metadata":{}},{"cell_type":"markdown","source":"## 1. Keras에서 CIFAR-10 데이터 직접 로드","metadata":{}},{"cell_type":"code","source":"(X_train_full, y_train_full), (X_test, y_test) = datasets.cifar10.load_data()\nX_train_full = X_train_full.astype('float32')\nX_test = X_test.astype('float32')\ny_train_full = y_train_full.flatten()\ny_test = y_test.flatten()\n\nprint(f\"훈련 데이터: {X_train_full.shape}\")  # (50000, 32, 32, 3)\nprint(f\"테스트 데이터: {X_test.shape}\")      # (10000, 32, 32, 3)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-17T07:47:28.566580Z","iopub.execute_input":"2026-04-17T07:47:28.567463Z","iopub.status.idle":"2026-04-17T07:47:31.381944Z","shell.execute_reply.started":"2026-04-17T07:47:28.567430Z","shell.execute_reply":"2026-04-17T07:47:31.381070Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 2. 정규화 (0 ~ 255 → 0 ~ 1)","metadata":{}},{"cell_type":"code","source":"X_train_full = X_train_full / 255.0\n\nX_test = X_test / 255.0","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-17T07:47:31.383503Z","iopub.execute_input":"2026-04-17T07:47:31.383872Z","iopub.status.idle":"2026-04-17T07:47:31.621128Z","shell.execute_reply.started":"2026-04-17T07:47:31.383846Z","shell.execute_reply":"2026-04-17T07:47:31.620353Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 3. 훈련/검증 분할 (8:2)","metadata":{}},{"cell_type":"code","source":"# ✅ TODO: train_test_split 사용하여 분할\n\nX_train, X_val, y_train, y_val = train_test_split(\n    X_train_full, y_train_full,\n    test_size=0.2,\n    random_state=42,\n    stratify=y_train_full  # 클래스 비율 유지\n)\n\nprint(f\"훈련: {X_train.shape}, 검증: {X_val.shape}\")\nprint(f\"훈련 라벨 분포: {np.bincount(y_train)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-17T07:47:31.622141Z","iopub.execute_input":"2026-04-17T07:47:31.622446Z","iopub.status.idle":"2026-04-17T07:47:32.269947Z","shell.execute_reply.started":"2026-04-17T07:47:31.622420Z","shell.execute_reply":"2026-04-17T07:47:32.269126Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 4. 클래스 이름 정의","metadata":{}},{"cell_type":"code","source":"class_names = ['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-17T07:47:32.271931Z","iopub.execute_input":"2026-04-17T07:47:32.272307Z","iopub.status.idle":"2026-04-17T07:47:32.276850Z","shell.execute_reply.started":"2026-04-17T07:47:32.272280Z","shell.execute_reply":"2026-04-17T07:47:32.276083Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 5. 시각화: 각 클래스별 샘플 이미지","metadata":{}},{"cell_type":"code","source":"fig, axes = plt.subplots(5, 2, figsize=(8, 10))\n\nfor class_idx in range(10):\n    \n    # ✅ TODO: 각 클래스의 이미지 인덱스 찾기\n\n    indices = np.where(y_train == class_idx)[0]\n    idx = indices[0] if len(indices) > 0 else 0\n\n    row, col = class_idx // 2, class_idx % 2\n    axes[row, col].imshow(X_train[idx])\n    axes[row, col].set_title(class_names[class_idx])\n    axes[row, col].axis('off')  # 힌트: axis\n\nplt.tight_layout()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-17T07:47:32.278133Z","iopub.execute_input":"2026-04-17T07:47:32.278505Z","iopub.status.idle":"2026-04-17T07:47:32.733482Z","shell.execute_reply.started":"2026-04-17T07:47:32.278466Z","shell.execute_reply":"2026-04-17T07:47:32.732652Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 6. 이미지 샘플 확인 (데이터 형태, 픽셀값 범위)","metadata":{}},{"cell_type":"code","source":"print(f\"이미지 최소값: {X_train.min():.3f}, 최대값: {X_train.max():.3f}\")\nprint(f\"평균: {X_train.mean():.3f}, 표준편차: {X_train.std():.3f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-17T07:47:32.734609Z","iopub.execute_input":"2026-04-17T07:47:32.734911Z","iopub.status.idle":"2026-04-17T07:47:33.181684Z","shell.execute_reply.started":"2026-04-17T07:47:32.734885Z","shell.execute_reply":"2026-04-17T07:47:33.180764Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 단계 2: CNN 모델 구축","metadata":{}},{"cell_type":"markdown","source":"## 1. CNN 모델 구축","metadata":{}},{"cell_type":"code","source":"model = keras.Sequential([\n\n    # 합성곱층 1: 32개 필터, 3×3 크기\n    layers.Conv2D(32, (3, 3), activation='relu', padding='same', input_shape=(32, 32, 3)),\n\n    # ✅ TODO: 풀링층 추가\n    # 2×2 Max Pooling으로 크기 절반으로 축소\n    layers.MaxPooling2D(pool_size=(2, 2)),\n    \n\n    # 합성곱층 2: 64개 필터\n    layers.Conv2D(64, (3, 3), activation='relu', padding='same'),\n\n    # ✅ TODO: 풀링층 추가\n    layers.MaxPooling2D(pool_size=(2, 2)),\n    \n\n    # 합성곱층 3: 128개 필터\n    layers.Conv2D(128, (3, 3), activation='relu', padding='same'),\n\n    # ✅ TODO: 풀링층 추가\n    layers.MaxPooling2D(pool_size=(2, 2)),\n   \n\n    # Flatten: 2D 특징맵을 1D 벡터로 변환\n    layers.Flatten(),\n\n    # Dense층: 분류\n    layers.Dense(256, activation='relu'),\n\n    # ✅ TODO: Dropout 추가 (과적합 방지)\n    # Dropout(0.5) = 50% 뉴런 무작위 비활성화\n\n    layers.Dropout(0.5),\n\n    # 출력층: 10개 클래스\n    # ✅ TODO: 활성화함수 선택 (다중분류)\n\n    layers.Dense(10, activation='softmax')\n])\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-17T07:47:33.182941Z","iopub.execute_input":"2026-04-17T07:47:33.183496Z","iopub.status.idle":"2026-04-17T07:47:33.247810Z","shell.execute_reply.started":"2026-04-17T07:47:33.183454Z","shell.execute_reply":"2026-04-17T07:47:33.246815Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 2. 모델 컴파일","metadata":{}},{"cell_type":"code","source":"model.compile(\n\n    optimizer=keras.optimizers.Adam(learning_rate=0.001),\n\n    # ✅ TODO: 손실함수 선택\n    # 옵션: 'categorical_crossentropy', 'sparse_categorical_crossentropy'\n\n    loss='sparse_categorical_crossentropy',\n\n    metrics=['accuracy']\n\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-17T07:47:33.249142Z","iopub.execute_input":"2026-04-17T07:47:33.250058Z","iopub.status.idle":"2026-04-17T07:47:33.259851Z","shell.execute_reply.started":"2026-04-17T07:47:33.250017Z","shell.execute_reply":"2026-04-17T07:47:33.258860Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 3. 모델 요약\n","metadata":{}},{"cell_type":"code","source":"model.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-17T07:47:33.261003Z","iopub.execute_input":"2026-04-17T07:47:33.261374Z","iopub.status.idle":"2026-04-17T07:47:33.289073Z","shell.execute_reply.started":"2026-04-17T07:47:33.261336Z","shell.execute_reply":"2026-04-17T07:47:33.288220Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 단계 3: 모델 훈련","metadata":{}},{"cell_type":"markdown","source":"## 1. 조기 종료 (과적합 방지)","metadata":{}},{"cell_type":"code","source":"early_stop = keras.callbacks.EarlyStopping(\n\n    monitor='val_loss',\n    patience=10,\n    restore_best_weights=True\n\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-17T07:47:33.292180Z","iopub.execute_input":"2026-04-17T07:47:33.292815Z","iopub.status.idle":"2026-04-17T07:47:33.297861Z","shell.execute_reply.started":"2026-04-17T07:47:33.292788Z","shell.execute_reply":"2026-04-17T07:47:33.297102Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 2.훈련","metadata":{}},{"cell_type":"code","source":"history = model.fit(\n\n    X_train, y_train,\n    validation_data=(X_val, y_val),\n    epochs=100,\n    batch_size=128,  # CIFAR-10은 작은 이미지라 배치 크기 증가 가능\n\n    # ✅ TODO: 콜백 전달\n\n    callbacks=[early_stop],\n    verbose=1\n\n)\n\nprint(f\"훈련 완료! 총 {len(history.history['loss'])}개 에포크\")\n\npd.DataFrame(history.history).tail(10)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-17T07:47:33.299747Z","iopub.execute_input":"2026-04-17T07:47:33.300102Z","iopub.status.idle":"2026-04-17T07:48:43.835218Z","shell.execute_reply.started":"2026-04-17T07:47:33.300076Z","shell.execute_reply":"2026-04-17T07:48:43.834007Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 단계 4: 성능 평가","metadata":{}},{"cell_type":"markdown","source":"## 1. 훈련/검증 손실 & 정확도 시각화","metadata":{}},{"cell_type":"code","source":"fig, axes = plt.subplots(1, 2, figsize=(12, 4))\n\n# 손실\naxes[0].plot(history.history['loss'], label='Train Loss')\naxes[0].plot(history.history['val_loss'], label='Val Loss')\naxes[0].set_xlabel('Epoch')\naxes[0].set_ylabel('Loss')\naxes[0].set_title('Model Loss')\naxes[0].legend()\naxes[0].grid(True)\n\n# 정확도\naxes[1].plot(history.history['accuracy'], label='Train Acc')\naxes[1].plot(history.history['val_accuracy'], label='Val Acc')\naxes[1].set_xlabel('Epoch')\naxes[1].set_ylabel('Accuracy')\naxes[1].set_title('Model Accuracy')\naxes[1].legend()\naxes[1].grid(True)\n\nplt.tight_layout()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-17T07:48:43.836811Z","iopub.execute_input":"2026-04-17T07:48:43.837553Z","iopub.status.idle":"2026-04-17T07:48:44.206693Z","shell.execute_reply.started":"2026-04-17T07:48:43.837508Z","shell.execute_reply":"2026-04-17T07:48:44.205809Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 2. 최종 성능 평가","metadata":{}},{"cell_type":"code","source":"val_loss, val_acc = model.evaluate(X_val, y_val)\n\nprint(f\"\\n=== 검증 성능 ===\")\nprint(f\"Loss: {val_loss:.4f}\")\nprint(f\"정확도: {val_acc:.4f} ({val_acc*100:.2f}%)\")\n\n# 테스트 성능 (최종 평가)\ntest_loss, test_acc = model.evaluate(X_test, y_test)\n\nprint(f\"\\n=== 테스트 성능 (최종) ===\")\nprint(f\"Loss: {test_loss:.4f}\")\nprint(f\"정확도: {test_acc:.4f} ({test_acc*100:.2f}%)\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-17T07:48:44.207781Z","iopub.execute_input":"2026-04-17T07:48:44.208132Z","iopub.status.idle":"2026-04-17T07:48:47.466185Z","shell.execute_reply.started":"2026-04-17T07:48:44.208106Z","shell.execute_reply":"2026-04-17T07:48:47.465383Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 3. 예측 확률 분석","metadata":{}},{"cell_type":"code","source":"y_val_pred_proba = model.predict(X_val)\n\n# ✅ TODO: 확률을 클래스 인덱스로 변환\ny_val_pred = np.argmax(y_val_pred_proba, axis=1)\n\n# 신뢰도 확인\nconfidence = np.max(y_val_pred_proba, axis=1)\n\nprint(f\"\\n예측 신뢰도:\")\nprint(f\"  평균: {confidence.mean():.4f}\")\nprint(f\"  최소: {confidence.min():.4f}\")\nprint(f\"  최대: {confidence.max():.4f}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-17T07:48:47.467212Z","iopub.execute_input":"2026-04-17T07:48:47.467589Z","iopub.status.idle":"2026-04-17T07:48:49.472498Z","shell.execute_reply.started":"2026-04-17T07:48:47.467562Z","shell.execute_reply":"2026-04-17T07:48:49.471657Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 단계 5: 혼동행렬 & 클래스별 분석","metadata":{}},{"cell_type":"code","source":"from sklearn.metrics import confusion_matrix, classification_report\nimport seaborn as sns","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-17T07:48:49.473567Z","iopub.execute_input":"2026-04-17T07:48:49.474100Z","iopub.status.idle":"2026-04-17T07:48:49.478884Z","shell.execute_reply.started":"2026-04-17T07:48:49.474071Z","shell.execute_reply":"2026-04-17T07:48:49.477832Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 1. 예측 (10-class)","metadata":{}},{"cell_type":"code","source":"y_val_pred_proba = model.predict(X_val)\n\n# ✅ TODO: 확률을 클래스 인덱스로 변환\ny_val_pred = np.argmax(y_val_pred_proba, axis=1)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-17T07:48:49.480125Z","iopub.execute_input":"2026-04-17T07:48:49.480493Z","iopub.status.idle":"2026-04-17T07:48:50.671412Z","shell.execute_reply.started":"2026-04-17T07:48:49.480466Z","shell.execute_reply":"2026-04-17T07:48:50.670715Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 2. 혼동행렬 (10×10) 계산","metadata":{}},{"cell_type":"code","source":"# ✅ TODO: confusion_matrix 사용\n\ncm = confusion_matrix(y_val, y_val_pred, labels=range(10))\n\nplt.figure(figsize=(10, 8))\n\nsns.heatmap(cm, annot=True, fmt='d', cmap='Blues', cbar=True,\n            xticklabels=class_names,\n            yticklabels=class_names)\n\nplt.title('Confusion Matrix (Validation Data)')\nplt.ylabel('Actual')\nplt.xlabel('Predicted')\nplt.xticks(rotation=45, ha='right')\nplt.yticks(rotation=0)\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-17T07:48:50.672684Z","iopub.execute_input":"2026-04-17T07:48:50.673152Z","iopub.status.idle":"2026-04-17T07:48:51.142915Z","shell.execute_reply.started":"2026-04-17T07:48:50.673122Z","shell.execute_reply":"2026-04-17T07:48:51.141818Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 3. 분류 리포트","metadata":{}},{"cell_type":"code","source":"print(\"\\n분류 리포트:\")\n\n# ✅ TODO: classification_report 사용\nprint(classification_report(y_val, y_val_pred, target_names=class_names))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-17T07:48:51.144067Z","iopub.execute_input":"2026-04-17T07:48:51.144796Z","iopub.status.idle":"2026-04-17T07:48:51.167820Z","shell.execute_reply.started":"2026-04-17T07:48:51.144764Z","shell.execute_reply":"2026-04-17T07:48:51.166604Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 4. 클래스별 정확도","metadata":{}},{"cell_type":"code","source":"print(f\"\\n클래스별 정확도:\")\n\nfor i, name in enumerate(class_names):\n\n    class_mask = y_val == i\n\n    if class_mask.sum() > 0:\n        \n        # ✅ TODO: 각 클래스 정확도 계산\n        acc = np.sum(y_val_pred[class_mask] == i) / class_mask.sum()\n        print(f\"  {name}: {acc:.4f} ({acc*100:.2f}%)\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-17T07:48:51.169305Z","iopub.execute_input":"2026-04-17T07:48:51.169775Z","iopub.status.idle":"2026-04-17T07:48:51.178475Z","shell.execute_reply.started":"2026-04-17T07:48:51.169746Z","shell.execute_reply":"2026-04-17T07:48:51.177460Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 5. 가장 잘못 분류된 샘플 시각화","metadata":{}},{"cell_type":"code","source":"# 신뢰도가 낮은 샘플 찾기\n\nconfidence_scores = np.max(y_val_pred_proba, axis=1)\n\nlow_confidence_idx = np.argsort(confidence_scores)[:9]\n\nfig, axes = plt.subplots(3, 3, figsize=(10, 10))\n\nfor i, idx in enumerate(low_confidence_idx):\n    row, col = i // 3, i % 3\n    axes[row, col].imshow(X_val[idx])\n    actual = class_names[y_val[idx]]\n    predicted = class_names[y_val_pred[idx]]\n    conf = confidence_scores[idx]\n    axes[row, col].set_title(f\"실제: {actual}\\n예측: {predicted}\\n신뢰도: {conf:.2f}\")\n    axes[row, col].axis('off')\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-17T07:48:51.179690Z","iopub.execute_input":"2026-04-17T07:48:51.180047Z","iopub.status.idle":"2026-04-17T07:48:52.170503Z","shell.execute_reply.started":"2026-04-17T07:48:51.180021Z","shell.execute_reply":"2026-04-17T07:48:52.169692Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 단계 6: Day 3 (Dense Network) vs Day 4 (CNN) 성능 비교","metadata":{}},{"cell_type":"markdown","source":"## 성능 비교 표","metadata":{}},{"cell_type":"code","source":"comparison = pd.DataFrame({\n\n    'Dense Network (Day 3)': {\n        '입력': '224×224×3 → 150,528 벡터',\n        '파라미터': '약 77,246,660개',\n        '예상 정확도': '70~78%',\n        '훈련 시간': '2~5분',\n        '메모리': '~2GB'\n    },\n\n    'CNN (Day 4)': {\n        '입력': '32×32×3 (이미지 그대로)',\n        '파라미터': f'{model.count_params():,}개',\n        '검증 정확도': f'{val_acc*100:.2f}%',\n        '훈련 시간': '3~5분',\n        '메모리': '~500MB'\n    }\n\n})\n\nprint(\"\\n=== Dense Network vs CNN 비교 ===\")\nprint(comparison.to_string())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-17T07:48:52.171920Z","iopub.execute_input":"2026-04-17T07:48:52.172302Z","iopub.status.idle":"2026-04-17T07:48:52.181560Z","shell.execute_reply.started":"2026-04-17T07:48:52.172271Z","shell.execute_reply":"2026-04-17T07:48:52.180553Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 핵심 차이점","metadata":{}},{"cell_type":"code","source":"print(\"\\n핵심 차이점:\")\nprint(\"1. 파라미터 수:\")\nprint(f\"   - Dense: 77,246,660개 (약 77M)\")\nprint(f\"   - CNN: {model.count_params():,}개 (약 {model.count_params()/1e6:.1f}M)\")\nprint(f\"   - CNN이 약 {77246660/model.count_params():.0f}배 더 효율적!\")\nprint(\"\\n2. 공간 정보 보존:\")\nprint(\"   - Dense: 이미지를 1D 벡터로 변환 → 공간 정보 손실\")\nprint(\"   - CNN: 2D 구조 유지 → 인접 픽셀 관계 학습\")\nprint(\"\\n3. 특징 학습:\")\nprint(\"   - Dense: 전체 픽셀을 함께 처리\")\nprint(\"   - CNN: 지역 패턴부터 시작 (엣지→텍스처→객체)\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-17T07:48:58.510732Z","iopub.execute_input":"2026-04-17T07:48:58.511708Z","iopub.status.idle":"2026-04-17T07:48:58.518336Z","shell.execute_reply.started":"2026-04-17T07:48:58.511657Z","shell.execute_reply":"2026-04-17T07:48:58.517518Z"}},"outputs":[],"execution_count":null}]}