{"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":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-04-17T06:59:07.561213Z","iopub.execute_input":"2026-04-17T06:59:07.561890Z","iopub.status.idle":"2026-04-17T06:59:07.849767Z","shell.execute_reply.started":"2026-04-17T06:59:07.561849Z","shell.execute_reply":"2026-04-17T06:59:07.849013Z"}},"outputs":[],"execution_count":null},{"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\nwarnings.filterwarnings('ignore')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-17T06:59:07.851275Z","iopub.execute_input":"2026-04-17T06:59:07.851743Z","iopub.status.idle":"2026-04-17T06:59:33.535422Z","shell.execute_reply.started":"2026-04-17T06:59:07.851712Z","shell.execute_reply":"2026-04-17T06:59:33.534802Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 1. Keras에서 CIFAR-10 데이터 직접 로드\n(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()\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-17T06:59:33.536350Z","iopub.execute_input":"2026-04-17T06:59:33.536988Z","iopub.status.idle":"2026-04-17T06:59:39.183166Z","shell.execute_reply.started":"2026-04-17T06:59:33.536961Z","shell.execute_reply":"2026-04-17T06:59:39.182378Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 2. 정규화 (0~255 → 0~1)\nX_train_full = X_train_full / 255.0\nX_test = X_test / 255.0","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-17T06:59:39.184956Z","iopub.execute_input":"2026-04-17T06:59:39.185285Z","iopub.status.idle":"2026-04-17T06:59:39.416256Z","shell.execute_reply.started":"2026-04-17T06:59:39.185262Z","shell.execute_reply":"2026-04-17T06:59:39.415565Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 3. 훈련/검증 분할 (8:2)\n# ✅ TODO: train_test_split 사용하여 분할\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)\nprint(f\"훈련: {X_train.shape}, 검증: {X_val.shape}\")\nprint(f\"훈련 라벨 분포: {np.bincount(y_train)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-17T06:59:39.417295Z","iopub.execute_input":"2026-04-17T06:59:39.417613Z","iopub.status.idle":"2026-04-17T06:59:39.630599Z","shell.execute_reply.started":"2026-04-17T06:59:39.417581Z","shell.execute_reply":"2026-04-17T06:59:39.629628Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 4. 클래스 이름 정의\nclass_names = ['airplane', 'automobile', 'bird', 'cat', 'deer',\n               'dog', 'frog', 'horse', 'ship', 'truck']","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-17T06:59:39.631772Z","iopub.execute_input":"2026-04-17T06:59:39.632088Z","iopub.status.idle":"2026-04-17T06:59:39.636295Z","shell.execute_reply.started":"2026-04-17T06:59:39.632066Z","shell.execute_reply":"2026-04-17T06:59:39.635279Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 5. 시각화: 각 클래스별 샘플 이미지\nfig, axes = plt.subplots(5, 2, figsize=(8, 10))\nfor class_idx in range(10):\n    # ✅ TODO: 각 클래스의 이미지 인덱스 찾기\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\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-17T06:59:39.637454Z","iopub.execute_input":"2026-04-17T06:59:39.638342Z","iopub.status.idle":"2026-04-17T06:59:40.103963Z","shell.execute_reply.started":"2026-04-17T06:59:39.638301Z","shell.execute_reply":"2026-04-17T06:59:40.103206Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 6. 이미지 샘플 확인 (데이터 형태, 픽셀값 범위)\nprint(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-17T06:59:40.104943Z","iopub.execute_input":"2026-04-17T06:59:40.105239Z","iopub.status.idle":"2026-04-17T06:59:40.545630Z","shell.execute_reply.started":"2026-04-17T06:59:40.105206Z","shell.execute_reply":"2026-04-17T06:59:40.544740Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 1. CNN 모델 구축\nmodel = keras.Sequential([\n    # 합성곱층 1: 32개 필터, 3×3 크기\n    layers.Conv2D(32, (3, 3), activation='relu', padding='same', input_shape=(32, 32, 3)),\n    # ✅ TODO: 풀링층 추가\n    # 2×2 Max Pooling으로 크기 절반으로 축소\n    layers.MaxPool2D(pool_size=(2, 2)),\n    \n    # 합성곱층 2: 64개 필터\n    layers.Conv2D(64, (3, 3), activation='relu', padding='same'),\n    # ✅ TODO: 풀링층 추가\n    layers.MaxPool2D(pool_size=(2, 2)),\n    \n    # 합성곱층 3: 128개 필터\n    layers.Conv2D(128, (3, 3), activation='relu', padding='same'),\n    # ✅ TODO: 풀링층 추가\n    layers.MaxPool2D(pool_size=(2, 2)),\n    \n    # Flatten: 2D 특징맵을 1D 벡터로 변환\n    layers.Flatten(),\n    \n    # Dense층: 분류\n    layers.Dense(256, activation='relu'),\n    # ✅ TODO: Dropout 추가 (과적합 방지)\n    # Dropout(0.5) = 50% 뉴런 무작위 비활성화\n    layers.Dropout(0.5),\n    \n    # 출력층: 10개 클래스\n    # ✅ TODO: 활성화함수 선택 (다중분류)\n    layers.Dense(10, activation='softmax')\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-17T06:59:40.546798Z","iopub.execute_input":"2026-04-17T06:59:40.547499Z","iopub.status.idle":"2026-04-17T06:59:43.323362Z","shell.execute_reply.started":"2026-04-17T06:59:40.547467Z","shell.execute_reply":"2026-04-17T06:59:43.322711Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 2. 모델 컴파일\nmodel.compile(\n    optimizer=keras.optimizers.Adam(learning_rate=0.001),\n    # ✅ TODO: 손실함수 선택\n    # 옵션: 'categorical_crossentropy', 'sparse_categorical_crossentropy'\n    loss='sparse_categorical_crossentropy',\n    metrics=['accuracy']\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-17T06:59:43.325096Z","iopub.execute_input":"2026-04-17T06:59:43.325442Z","iopub.status.idle":"2026-04-17T06:59:43.338577Z","shell.execute_reply.started":"2026-04-17T06:59:43.325418Z","shell.execute_reply":"2026-04-17T06:59:43.337901Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 3. 모델 요약\nmodel.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-17T06:59:43.339457Z","iopub.execute_input":"2026-04-17T06:59:43.339845Z","iopub.status.idle":"2026-04-17T06:59:43.610029Z","shell.execute_reply.started":"2026-04-17T06:59:43.339821Z","shell.execute_reply":"2026-04-17T06:59:43.609337Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 조기 종료 (과적합 방지)\nearly_stop = keras.callbacks.EarlyStopping(\n    monitor='val_loss',\n    patience=10,\n    restore_best_weights=True\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-17T07:06:22.723282Z","iopub.execute_input":"2026-04-17T07:06:22.724068Z","iopub.status.idle":"2026-04-17T07:06:22.728216Z","shell.execute_reply.started":"2026-04-17T07:06:22.724034Z","shell.execute_reply":"2026-04-17T07:06:22.727298Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 훈련\nhistory = model.fit(\n    X_train, y_train,\n    validation_data=(X_val, y_val),\n    epochs=100,\n    batch_size=128,  # CIFAR-10은 작은 이미지라 배치 크기 증가 가능\n    # ✅ TODO: 콜백 전달\n    callbacks=[early_stop],\n    verbose=1\n)\nprint(f\"훈련 완료! 총 {len(history.history['loss'])}개 에포크\")\npd.DataFrame(history.history).tail(10)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-17T07:06:23.617024Z","iopub.execute_input":"2026-04-17T07:06:23.617807Z","iopub.status.idle":"2026-04-17T07:06:52.286874Z","shell.execute_reply.started":"2026-04-17T07:06:23.617776Z","shell.execute_reply":"2026-04-17T07:06:52.286026Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 1. 훈련/검증 손실 & 정확도 시각화\nfig, axes = plt.subplots(1, 2, figsize=(12, 4))\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# 정확도\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)\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-17T07:06:56.700642Z","iopub.execute_input":"2026-04-17T07:06:56.701628Z","iopub.status.idle":"2026-04-17T07:06:57.050830Z","shell.execute_reply.started":"2026-04-17T07:06:56.701593Z","shell.execute_reply":"2026-04-17T07:06:57.050115Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_loss, train_acc = model.evaluate(X_train, y_train)\nprint(f\"\\n=== 훈련 성능 ===\")\nprint(f\"Loss: {train_loss:.4f}\")\nprint(f\"정확도: {train_acc:.4f} ({train_acc*100:.2f}%)\")\nval_loss, val_acc = model.evaluate(X_val, y_val)\nprint(f\"\\n=== 검증 성능 ===\")\nprint(f\"Loss: {val_loss:.4f}\")\nprint(f\"정확도: {val_acc:.4f} ({val_acc*100:.2f}%)\")\n# 테스트 성능 (최종 평가)\ntest_loss, test_acc = model.evaluate(X_test, y_test)\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-17T08:02:52.154539Z","iopub.execute_input":"2026-04-17T08:02:52.155278Z","iopub.status.idle":"2026-04-17T08:02:58.683576Z","shell.execute_reply.started":"2026-04-17T08:02:52.155247Z","shell.execute_reply":"2026-04-17T08:02:58.682922Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 3. 예측 확률 분석\ny_val_pred_proba = model.predict(X_val)\n# ✅ TODO: 확률을 클래스 인덱스로 변환\ny_val_pred = np.argmax(y_val_pred_proba, axis=1)\n# 신뢰도 확인\nconfidence = np.max(y_val_pred_proba, axis=1)\nprint(f\"\\n예측 신뢰도:\")\nprint(f\"  평균: {confidence.mean():.4f}\")\nprint(f\"  최소: {confidence.min():.4f}\")\nprint(f\"  최대: {confidence.max():.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-17T07:08:02.117015Z","iopub.execute_input":"2026-04-17T07:08:02.117458Z","iopub.status.idle":"2026-04-17T07:08:03.208760Z","shell.execute_reply.started":"2026-04-17T07:08:02.117423Z","shell.execute_reply":"2026-04-17T07:08:03.208100Z"}},"outputs":[],"execution_count":null},{"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:08:17.586934Z","iopub.execute_input":"2026-04-17T07:08:17.587854Z","iopub.status.idle":"2026-04-17T07:08:17.990309Z","shell.execute_reply.started":"2026-04-17T07:08:17.587821Z","shell.execute_reply":"2026-04-17T07:08:17.989629Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 1. 예측 (10-class)\ny_val_pred_proba = model.predict(X_val)\n# ✅ TODO: 확률을 클래스 인덱스로 변환\ny_val_pred = np.argmax(y_val_pred_proba, axis=1)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-17T07:08:35.970433Z","iopub.execute_input":"2026-04-17T07:08:35.970798Z","iopub.status.idle":"2026-04-17T07:08:37.037195Z","shell.execute_reply.started":"2026-04-17T07:08:35.970770Z","shell.execute_reply":"2026-04-17T07:08:37.036257Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 2. 혼동행렬 (10×10) 계산\n# ✅ TODO: confusion_matrix 사용\ncm = confusion_matrix(y_val, y_val_pred, labels=range(10))\nplt.figure(figsize=(10, 8))\nsns.heatmap(cm, annot=True, fmt='d', cmap='Blues', cbar=True,\n            xticklabels=class_names,\n            yticklabels=class_names)\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:09:07.137098Z","iopub.execute_input":"2026-04-17T07:09:07.137846Z","iopub.status.idle":"2026-04-17T07:09:07.558698Z","shell.execute_reply.started":"2026-04-17T07:09:07.137814Z","shell.execute_reply":"2026-04-17T07:09:07.557847Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 3. 분류 리포트\nprint(\"\\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:09:23.569285Z","iopub.execute_input":"2026-04-17T07:09:23.569557Z","iopub.status.idle":"2026-04-17T07:09:23.590629Z","shell.execute_reply.started":"2026-04-17T07:09:23.569535Z","shell.execute_reply":"2026-04-17T07:09:23.589716Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 4. 클래스별 정확도\nprint(f\"\\n클래스별 정확도:\")\nfor i, name in enumerate(class_names):\n    class_mask = y_val == i\n    if class_mask.sum() > 0:\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:49:05.217755Z","iopub.execute_input":"2026-04-17T07:49:05.218166Z","iopub.status.idle":"2026-04-17T07:49:05.227427Z","shell.execute_reply.started":"2026-04-17T07:49:05.218126Z","shell.execute_reply":"2026-04-17T07:49:05.226511Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 5. 가장 잘못 분류된 샘플 시각화\n# 신뢰도가 낮은 샘플 찾기\nconfidence_scores = np.max(y_val_pred_proba, axis=1)\nlow_confidence_idx = np.argsort(confidence_scores)[:9]\nfig, axes = plt.subplots(3, 3, figsize=(10, 10))\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')\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-17T07:49:07.454858Z","iopub.execute_input":"2026-04-17T07:49:07.455495Z","iopub.status.idle":"2026-04-17T07:49:07.982804Z","shell.execute_reply.started":"2026-04-17T07:49:07.455465Z","shell.execute_reply":"2026-04-17T07:49:07.981747Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 성능 비교 표\ncomparison = pd.DataFrame({\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    '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})\nprint(\"\\n=== Dense Network vs CNN 비교 ===\")\nprint(comparison.to_string())\n# 핵심 차이점\nprint(\"\\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: 지역 패턴부터 시작 (엣지→텍스처→객체)\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-17T07:57:49.283742Z","iopub.execute_input":"2026-04-17T07:57:49.284631Z","iopub.status.idle":"2026-04-17T07:57:49.294344Z","shell.execute_reply.started":"2026-04-17T07:57:49.284599Z","shell.execute_reply":"2026-04-17T07:57:49.293586Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}