diff --git "a/\353\266\204\354\204\235base_1\354\243\274\354\260\250\354\213\244\354\212\265.ipynb" "b/\353\266\204\354\204\235base_1\354\243\274\354\260\250\354\213\244\354\212\265.ipynb" new file mode 100644 index 0000000..c053df0 --- /dev/null +++ "b/\353\266\204\354\204\235base_1\354\243\274\354\260\250\354\213\244\354\212\265.ipynb" @@ -0,0 +1,1611 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "722686ec", + "metadata": { + "id": "722686ec" + }, + "source": [ + "# DNN → CNN 실습: MNIST 손글씨 숫자 분류\n", + "\n", + "손글씨 숫자 이미지 데이터셋인 **MNIST**를 가지고\n", + "DNN과 CNN을 직접 만들어보는 실습입니다.\n", + "\n", + "\n", + "## 전체 구성\n", + "\n", + "각 파트는 두 단계로 진행됩니다.\n", + "\n", + "1. **기초 실습**: 아주 단순한 구조를 직접 만들어보면서 \"Dense/Conv 층을 어떻게 쌓는지\" 연습해봅니다.\n", + "2. **논문 구조 재현**: 딥러닝 역사에서 실제로 쓰였던 유명한 구조를 그대로 만들어봅니다.\n", + " - DNN에서는 Yann LeCun이 1998년 논문에서 MNIST 벤치마크로 사용한 **784→300→100→10 MLP**\n", + " - CNN에서는 손글씨 인식을 위해 설계된 최초의 성공적인 CNN인 **LeNet-5**" + ] + }, + { + "cell_type": "markdown", + "id": "6928795b", + "metadata": { + "id": "6928795b" + }, + "source": [ + "## 1. 라이브러리 불러오기\n", + "\n", + "딥러닝 모델을 만들고 학습시키는 데 필요한 도구들을 불러옵니다.\n", + "- `tensorflow` / `keras`: 딥러닝 모델을 정의하고 학습시키는 프레임워크\n", + "- `matplotlib`: 이미지와 그래프를 그려서 눈으로 확인하기 위한 도구\n", + "- `numpy`: 숫자 배열(행렬) 연산\n" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "bbd8604a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Collecting tensorflow\n", + " Using cached tensorflow-2.21.0-cp312-cp312-win_amd64.whl.metadata (4.5 kB)\n", + "Collecting matplotlib\n", + " Using cached matplotlib-3.11.0-cp312-cp312-win_amd64.whl.metadata (80 kB)\n", + "Requirement already satisfied: absl-py>=1.0.0 in c:\\Users\\psh03\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages (from tensorflow) (2.5.0)\n", + "Requirement already satisfied: astunparse>=1.6.0 in c:\\Users\\psh03\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages (from tensorflow) (1.6.3)\n", + "Requirement already satisfied: flatbuffers>=25.9.23 in c:\\Users\\psh03\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages (from tensorflow) (25.12.19)\n", + "Requirement already satisfied: gast!=0.5.0,!=0.5.1,!=0.5.2,>=0.2.1 in c:\\Users\\psh03\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages (from tensorflow) (0.7.0)\n", + "Requirement already satisfied: google_pasta>=0.1.1 in c:\\Users\\psh03\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages (from tensorflow) (0.2.0)\n", + "Requirement already satisfied: libclang>=13.0.0 in c:\\Users\\psh03\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages (from tensorflow) (18.1.1)\n", + "Requirement already satisfied: opt_einsum>=2.3.2 in c:\\Users\\psh03\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages (from tensorflow) (3.4.0)\n", + "Requirement already satisfied: packaging in c:\\Users\\psh03\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages (from tensorflow) (24.2)\n", + "Requirement already satisfied: protobuf<8.0.0,>=6.31.1 in c:\\Users\\psh03\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages (from tensorflow) (7.35.1)\n", + "Requirement already satisfied: requests<3,>=2.21.0 in c:\\Users\\psh03\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages (from tensorflow) (2.34.2)\n", + "Requirement already satisfied: setuptools in c:\\Users\\psh03\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages (from tensorflow) (83.0.0)\n", + "Requirement already satisfied: six>=1.12.0 in C:\\Users\\psh03\\AppData\\Roaming\\Python\\Python312\\site-packages (from tensorflow) (1.17.0)\n", + "Requirement already satisfied: termcolor>=1.1.0 in c:\\Users\\psh03\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages (from tensorflow) (3.3.0)\n", + "Requirement already satisfied: typing_extensions>=3.6.6 in C:\\Users\\psh03\\AppData\\Roaming\\Python\\Python312\\site-packages (from tensorflow) (4.16.0)\n", + "Requirement already satisfied: wrapt>=1.11.0 in c:\\Users\\psh03\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages (from tensorflow) (2.2.2)\n", + "Requirement already satisfied: grpcio<2.0,>=1.24.3 in c:\\Users\\psh03\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages (from tensorflow) (1.82.1)\n", + "Requirement already satisfied: keras>=3.12.0 in c:\\Users\\psh03\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages (from tensorflow) (3.15.0)\n", + "Requirement already satisfied: numpy>=1.26.0 in c:\\Users\\psh03\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages (from tensorflow) (2.5.1)\n", + "Requirement already satisfied: h5py<3.15.0,>=3.11.0 in c:\\Users\\psh03\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages (from tensorflow) (3.14.0)\n", + "Requirement already satisfied: ml_dtypes<1.0.0,>=0.5.1 in c:\\Users\\psh03\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages (from tensorflow) (0.5.4)\n", + "Requirement already satisfied: charset_normalizer<4,>=2 in c:\\Users\\psh03\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages (from requests<3,>=2.21.0->tensorflow) (3.4.9)\n", + "Requirement already satisfied: idna<4,>=2.5 in c:\\Users\\psh03\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages (from requests<3,>=2.21.0->tensorflow) (3.18)\n", + "Requirement already satisfied: urllib3<3,>=1.26 in c:\\Users\\psh03\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages (from requests<3,>=2.21.0->tensorflow) (2.7.0)\n", + "Requirement already satisfied: certifi>=2023.5.7 in c:\\Users\\psh03\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages (from requests<3,>=2.21.0->tensorflow) (2026.6.17)\n", + "Collecting contourpy>=1.0.1 (from matplotlib)\n", + " Using cached contourpy-1.3.3-cp312-cp312-win_amd64.whl.metadata (5.5 kB)\n", + "Collecting cycler>=0.10 (from matplotlib)\n", + " Using cached cycler-0.12.1-py3-none-any.whl.metadata (3.8 kB)\n", + "Collecting fonttools>=4.22.0 (from matplotlib)\n", + " Using cached fonttools-4.63.0-cp312-cp312-win_amd64.whl.metadata (121 kB)\n", + "Collecting kiwisolver>=1.3.1 (from matplotlib)\n", + " Using cached kiwisolver-1.5.0-cp312-cp312-win_amd64.whl.metadata (5.2 kB)\n", + "Requirement already satisfied: pillow>=9 in c:\\Users\\psh03\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages (from matplotlib) (12.3.0)\n", + "Collecting pyparsing>=3 (from matplotlib)\n", + " Using cached pyparsing-3.3.2-py3-none-any.whl.metadata (5.8 kB)\n", + "Requirement already satisfied: python-dateutil>=2.7 in C:\\Users\\psh03\\AppData\\Roaming\\Python\\Python312\\site-packages (from matplotlib) (2.9.0.post0)\n", + "Requirement already satisfied: wheel<1.0,>=0.23.0 in c:\\Users\\psh03\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages (from astunparse>=1.6.0->tensorflow) (0.47.0)\n", + "Requirement already satisfied: rich in c:\\Users\\psh03\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages (from keras>=3.12.0->tensorflow) (15.0.0)\n", + "Requirement already satisfied: namex in c:\\Users\\psh03\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages (from keras>=3.12.0->tensorflow) (0.1.0)\n", + "Requirement already satisfied: optree in c:\\Users\\psh03\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages (from keras>=3.12.0->tensorflow) (0.19.1)\n", + "Requirement already satisfied: markdown-it-py>=2.2.0 in c:\\Users\\psh03\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages (from rich->keras>=3.12.0->tensorflow) (4.2.0)\n", + "Requirement already satisfied: pygments<3.0.0,>=2.13.0 in C:\\Users\\psh03\\AppData\\Roaming\\Python\\Python312\\site-packages (from rich->keras>=3.12.0->tensorflow) (2.20.0)\n", + "Requirement already satisfied: mdurl~=0.1 in c:\\Users\\psh03\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages (from markdown-it-py>=2.2.0->rich->keras>=3.12.0->tensorflow) (0.1.2)\n", + "Using cached tensorflow-2.21.0-cp312-cp312-win_amd64.whl (350.9 MB)\n", + "Using cached matplotlib-3.11.0-cp312-cp312-win_amd64.whl (9.3 MB)\n", + "Using cached contourpy-1.3.3-cp312-cp312-win_amd64.whl (226 kB)\n", + "Using cached cycler-0.12.1-py3-none-any.whl (8.3 kB)\n", + "Using cached fonttools-4.63.0-cp312-cp312-win_amd64.whl (2.3 MB)\n", + "Using cached kiwisolver-1.5.0-cp312-cp312-win_amd64.whl (73 kB)\n", + "Using cached pyparsing-3.3.2-py3-none-any.whl (122 kB)\n", + "Installing collected packages: pyparsing, kiwisolver, fonttools, cycler, contourpy, matplotlib, tensorflow\n", + "\n", + " ---------------------------------------- 0/7 [pyparsing]\n", + " ----- ---------------------------------- 1/7 [kiwisolver]\n", + " ----------- ---------------------------- 2/7 [fonttools]\n", + " ----------- ---------------------------- 2/7 [fonttools]\n", + " ----------- ---------------------------- 2/7 [fonttools]\n", + " ----------- ---------------------------- 2/7 [fonttools]\n", + " ----------- ---------------------------- 2/7 [fonttools]\n", + " ----------- ---------------------------- 2/7 [fonttools]\n", + " ----------- ---------------------------- 2/7 [fonttools]\n", + " ----------- ---------------------------- 2/7 [fonttools]\n", + " ----------- ---------------------------- 2/7 [fonttools]\n", + " ----------- ---------------------------- 2/7 [fonttools]\n", + " ----------- ---------------------------- 2/7 [fonttools]\n", + " ----------- ---------------------------- 2/7 [fonttools]\n", + " ----------- ---------------------------- 2/7 [fonttools]\n", + " ----------- ---------------------------- 2/7 [fonttools]\n", + " ----------- ---------------------------- 2/7 [fonttools]\n", + " ----------- ---------------------------- 2/7 [fonttools]\n", + " ----------- ---------------------------- 2/7 [fonttools]\n", + " ----------- ---------------------------- 2/7 [fonttools]\n", + " ----------- ---------------------------- 2/7 [fonttools]\n", + " ----------- ---------------------------- 2/7 [fonttools]\n", + " ----------- ---------------------------- 2/7 [fonttools]\n", + " ----------- ---------------------------- 2/7 [fonttools]\n", + " ----------- ---------------------------- 2/7 [fonttools]\n", + " ----------- ---------------------------- 2/7 [fonttools]\n", + " ----------- ---------------------------- 2/7 [fonttools]\n", + " ----------- ---------------------------- 2/7 [fonttools]\n", + " ----------- ---------------------------- 2/7 [fonttools]\n", + " ----------- ---------------------------- 2/7 [fonttools]\n", + " ----------- ---------------------------- 2/7 [fonttools]\n", + " ----------- ---------------------------- 2/7 [fonttools]\n", + " ----------- ---------------------------- 2/7 [fonttools]\n", + " ----------- ---------------------------- 2/7 [fonttools]\n", + " ----------- ---------------------------- 2/7 [fonttools]\n", + " ---------------------- ----------------- 4/7 [contourpy]\n", + " ---------------------- ----------------- 4/7 [contourpy]\n", + " ---------------------------- ----------- 5/7 [matplotlib]\n", + " ---------------------------- ----------- 5/7 [matplotlib]\n", + " ---------------------------- ----------- 5/7 [matplotlib]\n", + " ---------------------------- ----------- 5/7 [matplotlib]\n", + " ---------------------------- ----------- 5/7 [matplotlib]\n", + " ---------------------------- ----------- 5/7 [matplotlib]\n", + " ---------------------------- ----------- 5/7 [matplotlib]\n", + " ---------------------------- ----------- 5/7 [matplotlib]\n", + " ---------------------------- ----------- 5/7 [matplotlib]\n", + " ---------------------------- ----------- 5/7 [matplotlib]\n", + " ---------------------------- ----------- 5/7 [matplotlib]\n", + " ---------------------------- ----------- 5/7 [matplotlib]\n", + " ---------------------------- ----------- 5/7 [matplotlib]\n", + " ---------------------------- ----------- 5/7 [matplotlib]\n", + " ---------------------------- ----------- 5/7 [matplotlib]\n", + " ---------------------------- ----------- 5/7 [matplotlib]\n", + " ---------------------------- ----------- 5/7 [matplotlib]\n", + " ---------------------------- ----------- 5/7 [matplotlib]\n", + " ---------------------------- ----------- 5/7 [matplotlib]\n", + " ---------------------------- ----------- 5/7 [matplotlib]\n", + " ---------------------------- ----------- 5/7 [matplotlib]\n", + " ---------------------------- ----------- 5/7 [matplotlib]\n", + " ---------------------------- ----------- 5/7 [matplotlib]\n", + " ---------------------------- ----------- 5/7 [matplotlib]\n", + " ---------------------------- ----------- 5/7 [matplotlib]\n", + " ---------------------------- ----------- 5/7 [matplotlib]\n", + " ---------------------------- ----------- 5/7 [matplotlib]\n", + " ---------------------------- ----------- 5/7 [matplotlib]\n", + " ---------------------------- ----------- 5/7 [matplotlib]\n", + " ---------------------------- ----------- 5/7 [matplotlib]\n", + " ---------------------------- ----------- 5/7 [matplotlib]\n", + " ---------------------------- ----------- 5/7 [matplotlib]\n", + " ---------------------------- ----------- 5/7 [matplotlib]\n", + " ---------------------------- ----------- 5/7 [matplotlib]\n", + " ---------------------------- ----------- 5/7 [matplotlib]\n", + " ---------------------------- ----------- 5/7 [matplotlib]\n", + " ---------------------------- ----------- 5/7 [matplotlib]\n", + " ---------------------------- ----------- 5/7 [matplotlib]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------- ----- 6/7 [tensorflow]\n", + " ---------------------------------------- 7/7 [tensorflow]\n", + "\n", + "Successfully installed contourpy-1.3.3 cycler-0.12.1 fonttools-4.63.0 kiwisolver-1.5.0 matplotlib-3.11.0 pyparsing-3.3.2 tensorflow-2.21.0\n", + "Note: you may need to restart the kernel to use updated packages.\n" + ] + } + ], + "source": [ + "%pip install tensorflow matplotlib " + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "7067be71", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "7067be71", + "outputId": "c2e4a2e9-b691-4ef2-ef29-b5362abe1ddb" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "TensorFlow version: 2.21.0\n" + ] + } + ], + "source": [ + "import tensorflow as tf\n", + "from tensorflow.keras import layers, models\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "\n", + "# 그래프에서 한글이 깨지지 않도록 폰트 설정\n", + "plt.rcParams['font.family'] = 'Malgun Gothic'\n", + "plt.rcParams['axes.unicode_minus'] = False\n", + "\n", + "print(\"TensorFlow version:\", tf.__version__)\n" + ] + }, + { + "cell_type": "markdown", + "id": "03edfd84", + "metadata": { + "id": "03edfd84" + }, + "source": [ + "## 2. 데이터 불러오기 및 전처리\n", + "\n", + "**MNIST**는 사람이 손으로 쓴 0~9 숫자 이미지 7만 장으로 이루어진 데이터셋입니다.\n", + "각 이미지는 28x28 픽셀 크기의 흑백 이미지이고, 훈련용 60,000장 / 테스트용 10,000장으로 나뉘어 있습니다.\n", + "케라스에 내장되어 있어서 별도 다운로드 없이 한 줄로 불러올 수 있습니다.\n", + "\n", + "각 픽셀 값은 0(검정)~255(흰색) 사이의 정수입니다. 신경망은 값의 범위가 작고 일정할 때 학습이 더 안정적이고\n", + "빠르게 되는 경향이 있어서, 픽셀 값을 **0~1 사이로 정규화(normalization)** 해줍니다. (255로 나누면 됩니다)\n", + "\n", + "### 🔲 TODO\n" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "2dfd8c11", + "metadata": { + "id": "2dfd8c11" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "훈련 데이터 shape: (60000, 28, 28)\n", + "테스트 데이터 shape: (10000, 28, 28)\n" + ] + } + ], + "source": [ + "# TODO 1: tf.keras.datasets.mnist.load_data() 를 이용해서\n", + "# (x_train, y_train), (x_test, y_test) 를 불러오는 코드를 작성하세요.\n", + "(x_train,y_train),(x_test,y_test)=tf.keras.datasets.mnist.load_data()\n", + "\n", + "# TODO 2: x_train, x_test 를 각각 255로 나눠서 0~1 사이 값으로 정규화하세요.\n", + "x_train=x_train/255.0\n", + "x_test=x_test/255.0\n", + "'''\n", + "x_train/=255.0을 하면 에러가 발생\n", + "-> mnist.load_data()는 dtype이 uint8\n", + "-> x_train=x_train/255.0은 나눗셈 결과로 새 float64 배열을 만들어서 반환\n", + "=> x_train/=255.0은 기존 uint8배열 그 자리에 결과를 넣으려 함.. 에러발생\n", + "'''\n", + "\n", + "\n", + "print(\"훈련 데이터 shape:\", x_train.shape)\n", + "print(\"테스트 데이터 shape:\", x_test.shape)\n" + ] + }, + { + "cell_type": "markdown", + "id": "6afe474b", + "metadata": { + "id": "6afe474b" + }, + "source": [ + "## 3. 데이터 확인해보기\n", + "\n", + "실제로 어떤 이미지들인지 눈으로 확인해봅니다. `imshow`로 이미지를 그리고, 정답 라벨을 제목으로 표시합니다.\n", + "이 셀은 그대로 실행하시면 됩니다.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "fff4a7f1", + "metadata": { + "id": "fff4a7f1" + }, + "outputs": [ + { + "data": { + "image/png": "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IzZixAjfdHld3+tav/nNb5xkQc8GAAAAACtobAAAAACwgsYGAAAAACuSas7G7t27jVjfvn2NmNf4yi+//DLiePTo0YHec/Xq1UasefPmRuzgwYO+G7z07t070Hsi/6lbt64Ra9myZUybknnNqZg7d64Re+GFF3w3D4r+u8huc8hbbrklprQi+MZ1+K9x48bFvEklUlf0RmgTJkyIeT5n9Lj6H3/8MYepQ24qVMi8tb3uuuuM2NixY3032/3kk0+McwYNGmTEPvvsMyNWpEgRIzZjxoyI4xYtWjhBpKenO8mMGg8AAACAFTQ2AAAAAFhBYwMAAACAFTQ2AAAAAFiRVBPEvbz33ntGbNGiRUZs//79Ece1a9c2znnwwQd9J9l6TQb38s033xix7t27+74Oya9OnTpG7MMPPzRiJUqUMGKhUCjieP78+YE2/mvcuLERGzBggO/E2x07dhixNWvWGLFTp075TnD32jRw1apVTqrz2vywQoUKeZKWZBB0Eq/X3xRS1/333x9xfPHFFwd6nddmbJMmTYpbupD7OnbsGPPCE9HlitfGf/v27Qt0La/XtggwIXzLli1G7M0333SSGT0bAAAAAKygsQEAAADAChobAAAAAKygsQEAAADAiqSfIO4lyOSdvXv3BrpWt27djNj06dMDTaBFaqhWrZrvrvZek1537txpxDIyMnwnhR04cMCIvf/++4Fi8VSsWLGI48cff9w4p0OHDk6qu/32230/u1QWPVn+8ssvD/S6rVu3WkoREl3ZsmWN2AMPPOBbJ2dmZhqx5557Ls6pQ26L3tG7f//+vouvyGuvvea7sErQyeBennzyyZhe16tXr0CLuSQTejYAAAAAWEFjAwAAAIAVNDYAAAAAWEFjAwAAAIAV+XKCeBADBw40YnXr1g20M3OzZs2M2MKFC+OYOiSqIkWK+O4y7zUhOHoHe+ncubMRS09PT9qJxJdddlleJyEhVa9ePdB533zzjZOKov9+vHZX37BhQ6C/KeQ/lStXNmIzZ86M6Vovv/yyEVu8eHFM10LeePrpp41Y9ITwY8eOGecsWLDAiPXr18+IHT582DcNRYsWDbQzuFedmJaW5rtAwezZs538hp4NAAAAAFbQ2AAAAABgBY0NAAAAAFak7JyNgwcPBtrAb9WqVUZs7NixvuM+o8fey6uvvhpooxkkrmuuucaI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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "fig, axes = plt.subplots(1, 5, figsize=(10,2))\n", + "for i in range(5):\n", + " axes[i].imshow(x_train[i], cmap='gray')\n", + " axes[i].set_title(f\"label: {y_train[i]}\")\n", + " axes[i].axis('off')\n", + "plt.show()\n" + ] + }, + { + "cell_type": "markdown", + "id": "63ec7226", + "metadata": { + "id": "63ec7226" + }, + "source": [ + "---\n", + "# 📘 DNN 파트\n", + "\n", + "DNN(Deep Neural Network)은 여러 개의 **완전연결층(Dense layer)** 을 쌓아 만든 신경망입니다.\n", + "완전연결층이란 이전 층의 모든 뉴런이 다음 층의 모든 뉴런과 연결되어 있는 구조를 말합니다.\n", + "이미지처럼 2차원으로 생긴 데이터를 Dense layer에 넣으려면, 먼저 1차원으로 펼쳐야(`Flatten`) 합니다.\n", + "단, 이 과정에서 \"어떤 픽셀이 어떤 픽셀 옆에 있었는지\"와 같은 공간 정보는 사라진다는 점을 기억해두세요.\n", + "(뒤에서 CNN과 비교할 때 중요한 포인트가 됩니다)\n", + "\n", + "## 4. [기초 실습] 은닉층 1개짜리 DNN 만들기\n", + "\n", + "가장 단순한 형태의 DNN을 만들어봅니다. 층 구성은 다음과 같습니다.\n", + "- `Flatten(input_shape=(28,28))`: 28x28 이미지를 784개짜리 1차원 벡터로 펼침\n", + "- `Dense(64, relu)`: 은닉층. 64개의 뉴런이 입력으로부터 특징을 조합해서 학습\n", + " - `relu`는 활성화 함수로, 음수는 0으로 만들고 양수는 그대로 통과시켜서 신경망이 비선형적인(복잡한) 패턴을 학습할 수 있게 해줍니다.\n", + "- `Dense(10, softmax)`: 출력층. 0~9 중 하나를 고르는 문제이므로 뉴런 10개\n", + " - `softmax`는 10개의 출력을 \"각 숫자일 확률\"처럼 합이 1이 되게 변환해주는 함수입니다.\n", + "\n", + "모델을 만든 뒤에는 `compile`로 학습 방식을 정하고, `fit`으로 실제 학습을 진행합니다.\n", + "- `optimizer='adam'`: 가중치를 얼마나, 어떤 방향으로 업데이트할지 정하는 알고리즘. 대부분의 경우 무난하게 잘 동작해서 기본값으로 많이 씁니다.\n", + "- `loss='sparse_categorical_crossentropy'`: 정답과 예측이 얼마나 다른지 측정하는 함수. 정답 라벨이 0~9 같은 정수 형태일 때 사용합니다.\n", + "- `metrics=['accuracy']`: 학습 중 정확도를 함께 출력해서 모니터링합니다.\n", + "- `epochs=5`: 전체 훈련 데이터를 5번 반복해서 학습합니다.\n", + "- `validation_split=0.1`: 훈련 데이터의 10%를 학습에 쓰지 않고 따로 떼어놓아, 매 epoch마다 \"본 적 없는 데이터\"에서의 성능(과적합 여부)을 확인합니다.\n", + "\n", + "### 🔲 TODO\n" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "63141575", + "metadata": { + "id": "63141575" + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "c:\\Users\\psh03\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\keras\\src\\layers\\reshaping\\flatten.py:37: UserWarning: Do not pass an `input_shape`/`input_dim` argument to a layer. When using Sequential models, prefer using an `Input(shape)` object as the first layer in the model instead.\n", + " super().__init__(**kwargs)\n" + ] + }, + { + "data": { + "text/html": [ + "
Model: \"sequential\"\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1mModel: \"sequential\"\u001b[0m\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓\n",
+       "┃ Layer (type)                     Output Shape                  Param # ┃\n",
+       "┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩\n",
+       "│ flatten (Flatten)               │ (None, 784)            │             0 │\n",
+       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
+       "│ dense (Dense)                   │ (None, 64)             │        50,240 │\n",
+       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
+       "│ dense_1 (Dense)                 │ (None, 10)             │           650 │\n",
+       "└─────────────────────────────────┴────────────────────────┴───────────────┘\n",
+       "
\n" + ], + "text/plain": [ + "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓\n", + "┃\u001b[1m \u001b[0m\u001b[1mLayer (type) \u001b[0m\u001b[1m \u001b[0m┃\u001b[1m \u001b[0m\u001b[1mOutput Shape \u001b[0m\u001b[1m \u001b[0m┃\u001b[1m \u001b[0m\u001b[1m Param #\u001b[0m\u001b[1m \u001b[0m┃\n", + "┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩\n", + "│ flatten (\u001b[38;5;33mFlatten\u001b[0m) │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m784\u001b[0m) │ \u001b[38;5;34m0\u001b[0m │\n", + "├─────────────────────────────────┼────────────────────────┼───────────────┤\n", + "│ dense (\u001b[38;5;33mDense\u001b[0m) │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m64\u001b[0m) │ \u001b[38;5;34m50,240\u001b[0m │\n", + "├─────────────────────────────────┼────────────────────────┼───────────────┤\n", + "│ dense_1 (\u001b[38;5;33mDense\u001b[0m) │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m10\u001b[0m) │ \u001b[38;5;34m650\u001b[0m │\n", + "└─────────────────────────────────┴────────────────────────┴───────────────┘\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
 Total params: 50,890 (198.79 KB)\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1m Total params: \u001b[0m\u001b[38;5;34m50,890\u001b[0m (198.79 KB)\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
 Trainable params: 50,890 (198.79 KB)\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1m Trainable params: \u001b[0m\u001b[38;5;34m50,890\u001b[0m (198.79 KB)\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
 Non-trainable params: 0 (0.00 B)\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1m Non-trainable params: \u001b[0m\u001b[38;5;34m0\u001b[0m (0.00 B)\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WARNING:tensorflow:TensorFlow GPU support is not available on native Windows for TensorFlow >= 2.11. Even if CUDA/cuDNN are installed, GPU will not be used. Please use WSL2 or the TensorFlow-DirectML plugin.\n", + "Epoch 1/5\n", + "\u001b[1m1688/1688\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m3s\u001b[0m 1ms/step - accuracy: 0.9091 - loss: 0.3229 - val_accuracy: 0.9552 - val_loss: 0.1573\n", + "Epoch 2/5\n", + "\u001b[1m1688/1688\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m2s\u001b[0m 1ms/step - accuracy: 0.9553 - loss: 0.1536 - val_accuracy: 0.9697 - val_loss: 0.1161\n", + "Epoch 3/5\n", + "\u001b[1m1688/1688\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m2s\u001b[0m 1ms/step - accuracy: 0.9669 - loss: 0.1125 - val_accuracy: 0.9690 - val_loss: 0.1060\n", + "Epoch 4/5\n", + "\u001b[1m1688/1688\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m2s\u001b[0m 1ms/step - accuracy: 0.9733 - loss: 0.0894 - val_accuracy: 0.9742 - val_loss: 0.0966\n", + "Epoch 5/5\n", + "\u001b[1m1688/1688\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m2s\u001b[0m 1ms/step - accuracy: 0.9783 - loss: 0.0735 - val_accuracy: 0.9762 - val_loss: 0.0921\n" + ] + } + ], + "source": [ + "# TODO: 아래 4개 층을 순서대로 쌓은 Sequential 모델을 만드세요.\n", + "# 1) Flatten, input_shape=(28,28)\n", + "# 2) Dense, 유닛 64개, activation='relu'\n", + "# 3) Dense, 유닛 10개, activation='softmax' (출력층)\n", + "dnn_basic = models.Sequential([\n", + " layers.Flatten(input_shape=(28,28)),\n", + " layers.Dense(64,activation='relu'),\n", + " layers.Dense(10,activation='softmax')\n", + "])\n", + "\n", + "dnn_basic.summary()\n", + "\n", + "# TODO: optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'] 로 컴파일하세요.\n", + "\n", + "dnn_basic.compile(optimizer='adam',\n", + " loss='sparse_categorical_crossentropy',\n", + " metrics=['accuracy'])\n", + "\n", + "# TODO: x_train, y_train 으로 5 epoch, validation_split=0.1 로 학습시키고\n", + "# 결과를 dnn_basic_history 변수에 저장하세요.\n", + "dnn_basic_history=dnn_basic.fit(x_train,y_train,epochs=5,validation_split=0.1)\n" + ] + }, + { + "cell_type": "markdown", + "id": "4a2c5b8b", + "metadata": { + "id": "4a2c5b8b" + }, + "source": [ + "## 5. [기초 실습] DNN 평가\n", + "\n", + "테스트 데이터(학습에 전혀 쓰이지 않은 데이터)로 최종 성능을 확인합니다." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "b6299dfe", + "metadata": { + "id": "b6299dfe" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[1m313/313\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m0s\u001b[0m 884us/step - accuracy: 0.9721 - loss: 0.0926\n", + "[기초 실습 DNN] 테스트 정확도: 0.9721\n" + ] + } + ], + "source": [ + "dnn_basic_loss, dnn_basic_acc = dnn_basic.evaluate(x_test, y_test)\n", + "print(f\"[기초 실습 DNN] 테스트 정확도: {dnn_basic_acc:.4f}\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "667fdfbd", + "metadata": { + "id": "667fdfbd" + }, + "source": [ + "## 6. [논문 구조 재현] LeCun의 1998년 MNIST 벤치마크 MLP\n", + "\n", + "Yann LeCun 등이 1998년 논문(\"Gradient-Based Learning Applied to Document Recognition\")에서\n", + "MNIST 성능 비교표에 사용했던 구조 중 하나가 **784 → 300 → 100 → 10** 구조의 다층 퍼셉트론(MLP)입니다.\n", + "지금 기준으로 보면 아주 단순한 구조지만, 딥러닝 교재/논문에서 비교 기준(baseline)으로 지금도 자주 인용되는\n", + "역사적으로 의미 있는 구조입니다.\n", + "\n", + "구조는 기초 실습과 똑같은 패턴이고, 은닉층의 뉴런 개수와 층 개수만 다릅니다.\n", + "앞의 코드를 참고해도 좋지만 최대한 본인 힘으로 해보시길 추천드립니다.\n", + "- `Dense(300, relu)`\n", + "- `Dense(100, relu)`\n", + "- `Dense(10, softmax)`\n", + "\n", + "### 🔲 TODO\n" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "f0a91f54", + "metadata": { + "id": "f0a91f54" + }, + "outputs": [ + { + "data": { + "text/html": [ + "
Model: \"sequential_1\"\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1mModel: \"sequential_1\"\u001b[0m\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓\n",
+       "┃ Layer (type)                     Output Shape                  Param # ┃\n",
+       "┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩\n",
+       "│ flatten_1 (Flatten)             │ (None, 784)            │             0 │\n",
+       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
+       "│ dense_2 (Dense)                 │ (None, 300)            │       235,500 │\n",
+       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
+       "│ dense_3 (Dense)                 │ (None, 100)            │        30,100 │\n",
+       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
+       "│ dense_4 (Dense)                 │ (None, 10)             │         1,010 │\n",
+       "└─────────────────────────────────┴────────────────────────┴───────────────┘\n",
+       "
\n" + ], + "text/plain": [ + "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓\n", + "┃\u001b[1m \u001b[0m\u001b[1mLayer (type) \u001b[0m\u001b[1m \u001b[0m┃\u001b[1m \u001b[0m\u001b[1mOutput Shape \u001b[0m\u001b[1m \u001b[0m┃\u001b[1m \u001b[0m\u001b[1m Param #\u001b[0m\u001b[1m \u001b[0m┃\n", + "┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩\n", + "│ flatten_1 (\u001b[38;5;33mFlatten\u001b[0m) │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m784\u001b[0m) │ \u001b[38;5;34m0\u001b[0m │\n", + "├─────────────────────────────────┼────────────────────────┼───────────────┤\n", + "│ dense_2 (\u001b[38;5;33mDense\u001b[0m) │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m300\u001b[0m) │ \u001b[38;5;34m235,500\u001b[0m │\n", + "├─────────────────────────────────┼────────────────────────┼───────────────┤\n", + "│ dense_3 (\u001b[38;5;33mDense\u001b[0m) │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m100\u001b[0m) │ \u001b[38;5;34m30,100\u001b[0m │\n", + "├─────────────────────────────────┼────────────────────────┼───────────────┤\n", + "│ dense_4 (\u001b[38;5;33mDense\u001b[0m) │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m10\u001b[0m) │ \u001b[38;5;34m1,010\u001b[0m │\n", + "└─────────────────────────────────┴────────────────────────┴───────────────┘\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
 Total params: 266,610 (1.02 MB)\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1m Total params: \u001b[0m\u001b[38;5;34m266,610\u001b[0m (1.02 MB)\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
 Trainable params: 266,610 (1.02 MB)\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1m Trainable params: \u001b[0m\u001b[38;5;34m266,610\u001b[0m (1.02 MB)\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
 Non-trainable params: 0 (0.00 B)\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1m Non-trainable params: \u001b[0m\u001b[38;5;34m0\u001b[0m (0.00 B)\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 1/5\n", + "\u001b[1m1688/1688\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m4s\u001b[0m 2ms/step - accuracy: 0.9363 - loss: 0.2166 - val_accuracy: 0.9675 - val_loss: 0.1123\n", + "Epoch 2/5\n", + "\u001b[1m1688/1688\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m4s\u001b[0m 2ms/step - accuracy: 0.9715 - loss: 0.0913 - val_accuracy: 0.9752 - val_loss: 0.0798\n", + "Epoch 3/5\n", + "\u001b[1m1688/1688\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m4s\u001b[0m 2ms/step - accuracy: 0.9817 - loss: 0.0587 - val_accuracy: 0.9802 - val_loss: 0.0717\n", + "Epoch 4/5\n", + "\u001b[1m1688/1688\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m4s\u001b[0m 2ms/step - accuracy: 0.9850 - loss: 0.0454 - val_accuracy: 0.9782 - val_loss: 0.0791\n", + "Epoch 5/5\n", + "\u001b[1m1688/1688\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m5s\u001b[0m 2ms/step - accuracy: 0.9885 - loss: 0.0345 - val_accuracy: 0.9733 - val_loss: 0.1028\n" + ] + } + ], + "source": [ + "# TODO: 아래 4개 층을 순서대로 쌓은 Sequential 모델을 만드세요.\n", + "# 1) Flatten, input_shape=(28,28)\n", + "# 2) Dense, 유닛 300개, activation='relu'\n", + "# 3) Dense, 유닛 100개, activation='relu'\n", + "# 4) Dense, 유닛 10개, activation='softmax' (출력층)\n", + "dnn_paper = models.Sequential([\n", + " layers.Flatten(input_shape=(28,28)),\n", + " layers.Dense(300,activation='relu'),\n", + " layers.Dense(100,activation='relu'),\n", + " layers.Dense(10,activation='softmax')\n", + "])\n", + "\n", + "\n", + "dnn_paper.summary()\n", + "\n", + "# TODO: optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'] 로 컴파일하세요.\n", + "\n", + "dnn_paper.compile(optimizer='adam',loss='sparse_categorical_crossentropy',metrics=['accuracy'])\n", + "# TODO: x_train, y_train 으로 5 epoch, validation_split=0.1 로 학습시키고\n", + "# 결과를 dnn_paper_history 변수에 저장하세요.\n", + "\n", + "dnn_paper_history=dnn_paper.fit(x_train,y_train,epochs=5,validation_split=0.1)" + ] + }, + { + "cell_type": "markdown", + "id": "1c5a8d5f", + "metadata": { + "id": "1c5a8d5f" + }, + "source": [ + "## 7. [논문 구조 재현] DNN 평가" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "ff3de02d", + "metadata": { + "id": "ff3de02d" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[1m313/313\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m0s\u001b[0m 1ms/step - accuracy: 0.9702 - loss: 0.1150\n", + "[LeCun 1998 MLP] 테스트 정확도: 0.9702\n" + ] + } + ], + "source": [ + "dnn_paper_loss, dnn_paper_acc = dnn_paper.evaluate(x_test, y_test)\n", + "print(f\"[LeCun 1998 MLP] 테스트 정확도: {dnn_paper_acc:.4f}\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "26b98bc0", + "metadata": { + "id": "26b98bc0" + }, + "source": [ + "---\n", + "# 📘 CNN 파트\n", + "\n", + "CNN(Convolutional Neural Network)은 이미지를 펼치지 않고, **합성곱(Convolution)** 이라는 연산으로\n", + "이미지의 공간 정보(픽셀 간 위치 관계)를 유지한 채 특징을 추출하는 신경망입니다.\n", + "작은 필터(커널)가 이미지 위를 슬라이딩하면서 엣지, 곡선 같은 지역적인 패턴을 찾아냅니다.\n", + "\n", + "## 8. CNN을 위한 데이터 준비\n", + "\n", + "`Conv2D` 층은 입력으로 **(높이, 너비, 채널)** 형태의 3차원 데이터를 기대합니다.\n", + "지금 데이터는 (28, 28) 형태인데(흑백이라 채널 정보가 따로 없음), 채널 차원 1을 추가해서\n", + "(28, 28, 1) 형태로 바꿔줘야 합니다. `reshape(-1, 28, 28, 1)`에서 `-1`은 \"이미지 개수는 자동으로 맞춰라\"는 뜻입니다.\n", + "\n", + "### 🔲 TODO\n" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "a4cc141d", + "metadata": { + "id": "a4cc141d" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CNN용 훈련 데이터 shape: (60000, 28, 28, 1)\n" + ] + } + ], + "source": [ + "# TODO: x_train, x_test를 각각 (개수, 28, 28, 1) 형태로 reshape 하세요.\n", + "\n", + "x_train_cnn=x_train.reshape(-1,28,28,1)\n", + "x_test_cnn=x_test.reshape(-1,28,28,1)\n", + "\n", + "print(\"CNN용 훈련 데이터 shape:\", x_train_cnn.shape)\n" + ] + }, + { + "cell_type": "markdown", + "id": "0d36af3e", + "metadata": { + "id": "0d36af3e" + }, + "source": [ + "## 9. [기초 실습] Conv층 1개짜리 CNN 만들기\n", + "\n", + "가장 단순한 형태의 CNN을 만들어봅니다.\n", + "- `Conv2D(16, (3,3), relu)`: 3x3 크기의 필터 16개를 이미지 위에 슬라이딩하며 특징(엣지 등)을 추출\n", + "- `MaxPooling2D((2,2))`: 2x2 영역에서 가장 큰 값만 남겨서 feature map 크기를 절반으로 줄임 (연산량 감소 + 중요한 특징 강조)\n", + "- `Flatten` + `Dense(10, softmax)`: 추출된 특징을 1차원으로 펼쳐서 최종 분류\n", + "\n", + "### 🔲 TODO\n" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "0b97fdb9", + "metadata": { + "id": "0b97fdb9" + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "c:\\Users\\psh03\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\keras\\src\\layers\\convolutional\\base_conv.py:113: UserWarning: Do not pass an `input_shape`/`input_dim` argument to a layer. When using Sequential models, prefer using an `Input(shape)` object as the first layer in the model instead.\n", + " super().__init__(activity_regularizer=activity_regularizer, **kwargs)\n" + ] + }, + { + "data": { + "text/html": [ + "
Model: \"sequential_2\"\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1mModel: \"sequential_2\"\u001b[0m\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓\n",
+       "┃ Layer (type)                     Output Shape                  Param # ┃\n",
+       "┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩\n",
+       "│ conv2d (Conv2D)                 │ (None, 26, 26, 16)     │           160 │\n",
+       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
+       "│ max_pooling2d (MaxPooling2D)    │ (None, 13, 13, 16)     │             0 │\n",
+       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
+       "│ flatten_2 (Flatten)             │ (None, 2704)           │             0 │\n",
+       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
+       "│ dense_5 (Dense)                 │ (None, 10)             │        27,050 │\n",
+       "└─────────────────────────────────┴────────────────────────┴───────────────┘\n",
+       "
\n" + ], + "text/plain": [ + "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓\n", + "┃\u001b[1m \u001b[0m\u001b[1mLayer (type) \u001b[0m\u001b[1m \u001b[0m┃\u001b[1m \u001b[0m\u001b[1mOutput Shape \u001b[0m\u001b[1m \u001b[0m┃\u001b[1m \u001b[0m\u001b[1m Param #\u001b[0m\u001b[1m \u001b[0m┃\n", + "┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩\n", + "│ conv2d (\u001b[38;5;33mConv2D\u001b[0m) │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m26\u001b[0m, \u001b[38;5;34m26\u001b[0m, \u001b[38;5;34m16\u001b[0m) │ \u001b[38;5;34m160\u001b[0m │\n", + "├─────────────────────────────────┼────────────────────────┼───────────────┤\n", + "│ max_pooling2d (\u001b[38;5;33mMaxPooling2D\u001b[0m) │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m13\u001b[0m, \u001b[38;5;34m13\u001b[0m, \u001b[38;5;34m16\u001b[0m) │ \u001b[38;5;34m0\u001b[0m │\n", + "├─────────────────────────────────┼────────────────────────┼───────────────┤\n", + "│ flatten_2 (\u001b[38;5;33mFlatten\u001b[0m) │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m2704\u001b[0m) │ \u001b[38;5;34m0\u001b[0m │\n", + "├─────────────────────────────────┼────────────────────────┼───────────────┤\n", + "│ dense_5 (\u001b[38;5;33mDense\u001b[0m) │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m10\u001b[0m) │ \u001b[38;5;34m27,050\u001b[0m │\n", + "└─────────────────────────────────┴────────────────────────┴───────────────┘\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
 Total params: 27,210 (106.29 KB)\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1m Total params: \u001b[0m\u001b[38;5;34m27,210\u001b[0m (106.29 KB)\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
 Trainable params: 27,210 (106.29 KB)\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1m Trainable params: \u001b[0m\u001b[38;5;34m27,210\u001b[0m (106.29 KB)\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
 Non-trainable params: 0 (0.00 B)\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1m Non-trainable params: \u001b[0m\u001b[38;5;34m0\u001b[0m (0.00 B)\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 1/5\n", + "\u001b[1m1688/1688\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m4s\u001b[0m 2ms/step - accuracy: 0.9262 - loss: 0.2647 - val_accuracy: 0.9757 - val_loss: 0.0988\n", + "Epoch 2/5\n", + "\u001b[1m1688/1688\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m4s\u001b[0m 2ms/step - accuracy: 0.9710 - loss: 0.1005 - val_accuracy: 0.9800 - val_loss: 0.0717\n", + "Epoch 3/5\n", + "\u001b[1m1688/1688\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m4s\u001b[0m 2ms/step - accuracy: 0.9787 - loss: 0.0744 - val_accuracy: 0.9828 - val_loss: 0.0614\n", + "Epoch 4/5\n", + "\u001b[1m1688/1688\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m4s\u001b[0m 2ms/step - accuracy: 0.9819 - loss: 0.0615 - val_accuracy: 0.9843 - val_loss: 0.0588\n", + "Epoch 5/5\n", + "\u001b[1m1688/1688\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m5s\u001b[0m 2ms/step - accuracy: 0.9849 - loss: 0.0519 - val_accuracy: 0.9845 - val_loss: 0.0579\n" + ] + } + ], + "source": [ + "# TODO: 아래 4개 층을 순서대로 쌓은 Sequential 모델을 만드세요.\n", + "# 1) Conv2D, 필터 16개, 커널 (3,3), activation='relu', input_shape=(28,28,1)\n", + "# 2) MaxPooling2D, 풀 크기 (2,2)\n", + "# 3) Flatten\n", + "# 4) Dense, 유닛 10개, activation='softmax' (출력층)\n", + "cnn_basic = models.Sequential([\n", + " layers.Conv2D(16,(3,3),activation='relu',input_shape=(28,28,1)),\n", + " layers.MaxPooling2D((2,2)),\n", + " layers.Flatten(),\n", + " layers.Dense(10,activation='softmax')\n", + "])\n", + "\n", + "cnn_basic.summary()\n", + "\n", + "# TODO: optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'] 로 컴파일하세요.\n", + "\n", + "cnn_basic.compile(optimizer='adam',\n", + " loss='sparse_categorical_crossentropy',\n", + " metrics=['accuracy'])\n", + "\n", + "# TODO: x_train_cnn, y_train 으로 5 epoch, validation_split=0.1 로 학습시키고\n", + "# 결과를 cnn_basic_history 변수에 저장하세요.\n", + "\n", + "cnn_basic_history=cnn_basic.fit(x_train_cnn,y_train,epochs=5,validation_split=0.1)\n", + "# 정답\n", + "# cnn_basic_history = cnn_basic.fit(x_train_cnn, y_train, epochs=5, validation_split=0.1)\n" + ] + }, + { + "cell_type": "markdown", + "id": "972f25c0", + "metadata": { + "id": "972f25c0" + }, + "source": [ + "## 10. [기초 실습] CNN 평가" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "260ffac3", + "metadata": { + "id": "260ffac3" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[1m313/313\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m0s\u001b[0m 1ms/step - accuracy: 0.9798 - loss: 0.0610\n", + "[기초 실습 CNN] 테스트 정확도: 0.9798\n" + ] + } + ], + "source": [ + "cnn_basic_loss, cnn_basic_acc = cnn_basic.evaluate(x_test_cnn, y_test)\n", + "print(f\"[기초 실습 CNN] 테스트 정확도: {cnn_basic_acc:.4f}\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "376ece56", + "metadata": { + "id": "376ece56" + }, + "source": [ + "## 11. [논문 구조 재현] LeNet-5 (Yann LeCun, 1998)\n", + "\n", + "**LeNet-5**는 손글씨 숫자 인식을 위해 설계된 최초의 성공적인 CNN 구조로,\n", + "오늘날까지 이어지는 \"Conv → Pool → Conv → Pool → Dense\" CNN 설계의 기본 틀을 확립한 역사적인 모델입니다.\n", + "원 논문은 활성화 함수로 `tanh`, 풀링으로 average pooling을 사용했지만, 여기서는 현대적인 관례에 맞춰\n", + "`relu`와 `MaxPooling`으로 구현합니다.\n", + "\n", + "- `Conv2D(6, (5,5), relu)` → `MaxPooling2D((2,2))`\n", + "- `Conv2D(16, (5,5), relu)` → `MaxPooling2D((2,2))`\n", + "- `Flatten` → `Dense(120, relu)` → `Dense(84, relu)` → `Dense(10, softmax)`\n", + "\n", + "> 참고: 원래 LeNet-5는 32x32 크기 입력을 가정하고 설계됐지만 MNIST는 28x28입니다.\n", + "> 그대로 두면 두 번째 풀링을 거친 후 feature map이 너무 작아져서 구조가 어긋나므로,\n", + "> 첫 Conv2D에 `padding='same'`을 줘서 크기를 보정합니다.\n", + "\n", + "### 🔲 TODO\n" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "2fcb87f6", + "metadata": { + "id": "2fcb87f6" + }, + "outputs": [ + { + "data": { + "text/html": [ + "
Model: \"sequential_3\"\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1mModel: \"sequential_3\"\u001b[0m\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓\n",
+       "┃ Layer (type)                     Output Shape                  Param # ┃\n",
+       "┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩\n",
+       "│ conv2d_1 (Conv2D)               │ (None, 28, 28, 6)      │           156 │\n",
+       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
+       "│ max_pooling2d_1 (MaxPooling2D)  │ (None, 14, 14, 6)      │             0 │\n",
+       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
+       "│ conv2d_2 (Conv2D)               │ (None, 10, 10, 16)     │         2,416 │\n",
+       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
+       "│ max_pooling2d_2 (MaxPooling2D)  │ (None, 5, 5, 16)       │             0 │\n",
+       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
+       "│ flatten_3 (Flatten)             │ (None, 400)            │             0 │\n",
+       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
+       "│ dense_6 (Dense)                 │ (None, 120)            │        48,120 │\n",
+       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
+       "│ dense_7 (Dense)                 │ (None, 84)             │        10,164 │\n",
+       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
+       "│ dense_8 (Dense)                 │ (None, 10)             │           850 │\n",
+       "└─────────────────────────────────┴────────────────────────┴───────────────┘\n",
+       "
\n" + ], + "text/plain": [ + "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓\n", + "┃\u001b[1m \u001b[0m\u001b[1mLayer (type) \u001b[0m\u001b[1m \u001b[0m┃\u001b[1m \u001b[0m\u001b[1mOutput Shape \u001b[0m\u001b[1m \u001b[0m┃\u001b[1m \u001b[0m\u001b[1m Param #\u001b[0m\u001b[1m \u001b[0m┃\n", + "┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩\n", + "│ conv2d_1 (\u001b[38;5;33mConv2D\u001b[0m) │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m28\u001b[0m, \u001b[38;5;34m28\u001b[0m, \u001b[38;5;34m6\u001b[0m) │ \u001b[38;5;34m156\u001b[0m │\n", + "├─────────────────────────────────┼────────────────────────┼───────────────┤\n", + "│ max_pooling2d_1 (\u001b[38;5;33mMaxPooling2D\u001b[0m) │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m14\u001b[0m, \u001b[38;5;34m14\u001b[0m, \u001b[38;5;34m6\u001b[0m) │ \u001b[38;5;34m0\u001b[0m │\n", + "├─────────────────────────────────┼────────────────────────┼───────────────┤\n", + "│ conv2d_2 (\u001b[38;5;33mConv2D\u001b[0m) │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m10\u001b[0m, \u001b[38;5;34m10\u001b[0m, \u001b[38;5;34m16\u001b[0m) │ \u001b[38;5;34m2,416\u001b[0m │\n", + "├─────────────────────────────────┼────────────────────────┼───────────────┤\n", + "│ max_pooling2d_2 (\u001b[38;5;33mMaxPooling2D\u001b[0m) │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m5\u001b[0m, \u001b[38;5;34m5\u001b[0m, \u001b[38;5;34m16\u001b[0m) │ \u001b[38;5;34m0\u001b[0m │\n", + "├─────────────────────────────────┼────────────────────────┼───────────────┤\n", + "│ flatten_3 (\u001b[38;5;33mFlatten\u001b[0m) │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m400\u001b[0m) │ \u001b[38;5;34m0\u001b[0m │\n", + "├─────────────────────────────────┼────────────────────────┼───────────────┤\n", + "│ dense_6 (\u001b[38;5;33mDense\u001b[0m) │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m120\u001b[0m) │ \u001b[38;5;34m48,120\u001b[0m │\n", + "├─────────────────────────────────┼────────────────────────┼───────────────┤\n", + "│ dense_7 (\u001b[38;5;33mDense\u001b[0m) │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m84\u001b[0m) │ \u001b[38;5;34m10,164\u001b[0m │\n", + "├─────────────────────────────────┼────────────────────────┼───────────────┤\n", + "│ dense_8 (\u001b[38;5;33mDense\u001b[0m) │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m10\u001b[0m) │ \u001b[38;5;34m850\u001b[0m │\n", + "└─────────────────────────────────┴────────────────────────┴───────────────┘\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
 Total params: 61,706 (241.04 KB)\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1m Total params: \u001b[0m\u001b[38;5;34m61,706\u001b[0m (241.04 KB)\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
 Trainable params: 61,706 (241.04 KB)\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1m Trainable params: \u001b[0m\u001b[38;5;34m61,706\u001b[0m (241.04 KB)\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
 Non-trainable params: 0 (0.00 B)\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1m Non-trainable params: \u001b[0m\u001b[38;5;34m0\u001b[0m (0.00 B)\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 1/5\n", + "\u001b[1m1688/1688\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m6s\u001b[0m 3ms/step - accuracy: 0.9426 - loss: 0.1828 - val_accuracy: 0.9818 - val_loss: 0.0623\n", + "Epoch 2/5\n", + "\u001b[1m1688/1688\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m5s\u001b[0m 3ms/step - accuracy: 0.9816 - loss: 0.0580 - val_accuracy: 0.9885 - val_loss: 0.0434\n", + "Epoch 3/5\n", + "\u001b[1m1688/1688\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m6s\u001b[0m 3ms/step - accuracy: 0.9866 - loss: 0.0423 - val_accuracy: 0.9915 - val_loss: 0.0323\n", + "Epoch 4/5\n", + "\u001b[1m1688/1688\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m6s\u001b[0m 3ms/step - accuracy: 0.9891 - loss: 0.0342 - val_accuracy: 0.9900 - val_loss: 0.0383\n", + "Epoch 5/5\n", + "\u001b[1m1688/1688\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m6s\u001b[0m 4ms/step - accuracy: 0.9910 - loss: 0.0279 - val_accuracy: 0.9902 - val_loss: 0.0314\n" + ] + } + ], + "source": [ + "# TODO: 아래 8개 층을 순서대로 쌓은 Sequential 모델을 만드세요. (LeNet-5 구조)\n", + "# 1) Conv2D, 필터 6개, 커널 (5,5), activation='relu', padding='same', input_shape=(28,28,1)\n", + "# 2) MaxPooling2D, 풀 크기 (2,2)\n", + "# 3) Conv2D, 필터 16개, 커널 (5,5), activation='relu'\n", + "# 4) MaxPooling2D, 풀 크기 (2,2)\n", + "# 5) Flatten\n", + "# 6) Dense, 유닛 120개, activation='relu'\n", + "# 7) Dense, 유닛 84개, activation='relu'\n", + "# 8) Dense, 유닛 10개, activation='softmax' (출력층)\n", + "lenet5 = models.Sequential([\n", + " layers.Conv2D(6,(5,5),activation='relu',padding='same',input_shape=(28,28,1)),\n", + " layers.MaxPool2D((2,2)),\n", + " layers.Conv2D(16,(5,5),activation='relu'),\n", + " layers.MaxPool2D((2,2)),\n", + " layers.Flatten(),\n", + " layers.Dense(120,activation='relu'),\n", + " layers.Dense(84,activation='relu'),\n", + " layers.Dense(10,activation='softmax')\n", + "])\n", + "\n", + "\n", + "lenet5.summary()\n", + "\n", + "# TODO: optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'] 로 컴파일하세요.\n", + "\n", + "lenet5.compile(optimizer='adam',loss='sparse_categorical_crossentropy',metrics=['accuracy'])\n", + "\n", + "# TODO: x_train_cnn, y_train 으로 5 epoch, validation_split=0.1 로 학습시키고\n", + "# 결과를 lenet5_history 변수에 저장하세요.\n", + "lenet5_history=lenet5.fit(x_train_cnn,y_train,epochs=5,validation_split=0.1)\n" + ] + }, + { + "cell_type": "markdown", + "id": "563bd3f0", + "metadata": { + "id": "563bd3f0" + }, + "source": [ + "## 12. [논문 구조 재현] LeNet-5 평가" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "1d1a91b5", + "metadata": { + "id": "1d1a91b5" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[1m313/313\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m1s\u001b[0m 2ms/step - accuracy: 0.9876 - loss: 0.0365\n", + "[LeNet-5] 테스트 정확도: 0.9876\n" + ] + } + ], + "source": [ + "lenet5_loss, lenet5_acc = lenet5.evaluate(x_test_cnn, y_test)\n", + "print(f\"[LeNet-5] 테스트 정확도: {lenet5_acc:.4f}\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "710e0403", + "metadata": { + "id": "710e0403" + }, + "source": [ + "---\n", + "## 13. 전체 결과 비교\n", + "\n", + "지금까지 만든 4개 모델(기초 실습 DNN / 논문 구조 DNN(LeCun MLP) / 기초 실습 CNN / 논문 구조 CNN(LeNet-5))의\n", + "정확도와 파라미터 수를 한눈에 비교해봅니다.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "5a247cfe", + "metadata": { + "id": "5a247cfe" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "모델 테스트 정확도 파라미터 수 \n", + "DNN (기초 실습) 0.9721 50,890 \n", + "DNN (논문 구조, LeCun MLP) 0.9702 266,610 \n", + "CNN (기초 실습) 0.9798 27,210 \n", + "CNN (논문 구조, LeNet-5) 0.9876 61,706 \n" + ] + }, + { + "data": { + "image/png": "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T8I+98yZM12aptpqSK1atdzMXdasWd11zeh1797dzQ4Gziqqv+Lbb799Xn5fAAAAIKrX5AEbNmyw0qVLW65cufx/DLXR+Pbbb2P8cRSchfZWzJIli+ubGEoVWzUbpwAyLprJ0+t7FAjqEu71AQAAgLSAIA8RtXfvXjejFkjVUlVgJ1SlSpVcYZaePXtax44dXbqlUjcDi6p41HpD6+7iWsOpGbz333/f5s2bl0K/DQAAABB5pGsi4kLXvimFUzN0oVScZ8aMGXby5EmXdvnpp59at27dYhTo8fl8rk9ir169wr6ed78eO3XqVJciCqQGWhN666232mWXXeZawCh9ODY//fST6wWqmWitVd29e7e7/ZFHHnFpzeEuX3zxRYJe6/fff7f//ve/Vq1aNVfoSM+vta8AACBtIMhDRBUqVCjGiayuq2VGOIULF3br5ZYtW2afffaZHT9+3K666qqgbWbPnm1XXnmle+5QarGhCptbt261H3/80SpUqJDCvxGQNGfPnnXrTVWEaP78+a7qcKdOnWJNM27durUbqNDa0zJlyliXLl3cfYMGDbKVK1cGXTRrrWCwcePGCXqtN9980y6//HK33vWbb76xq6++2m2rokUAACD1I10TFuleiap6qcsll1zibpszZ05QI/s9e/a4FE7N5OnkNLDdhtIyx4wZE/Sc48aNsw4dOoR9vbZt27oT48DnB1IDfe41MKFiQqLZZs2+aYZORYYC6XP/4IMP+j/HL730kgvElMKsmenQXpNDhgyxHj16uJ6RCXmtl19+2bWs8fTr188WLFhgP/zwg6tQCwAAUjdm8hBRKqaiGQQFZTqBVDuDVatWuVkKUYqYZvW0Dk++++47d8K6dOlSu//++61UqVJ2zTXXBKV+zpo1y806hNq8ebPrlVexYsWgNgu6DYi0RYsW2Q033OC/nj17dhe46fsQ6vDhw259aiDN1OnzHEqBm4I6Va1N6GsFBniBadRekAgAAFI3gjxE3NChQ93an4ceesilUKqypU46RWlkderUsRo1arjrOjHVSavW5KmZuWY0QgO5Bg0a+B8fSI/TerzQhukDBw6Msa1OZlmrhwtJvR9D05Q1u6biRKG0Fm/06NH+yrJan6e0y3Br+JS+2bt37yS/lug7qe+PihkBAIDUj3RNRJwCssCG5KFr8JQm5smYMaOrqBlX+ueXX34Z9r66deuGnekIR03UdQEuFM1Ch5tBC0fr6TTrVrlyZfcYb81c6ODGzp073ay31tgl5bU0KPL666/bBx98YNOnT7fMmTlkAACQFjCTBwCpgNbRhfaBVKETzWaH8+STT7pWI0o3VhrzkSNHgno+yqhRo6xz585Jei3N6t1yyy22adMmW7hwoavCCQAA0gaGZQEgFdBaUVWy9KjI0IoVK6xq1aqxPkYz22ohcuDAAVuzZo1VqVLFf9+5c+dcOvPy5csT/Vpa83fzzTfb4MGDrUmTJin4WwIAgAuBmTwASAVatGjh1tXpogBt2LBhVqtWLX9lzQkTJriWH17KsVIoFdwdO3bMunfv7qrGBqZTan2rWolo7WpiX0vPrSCPAA8AgLSJIA8AUgG1NFA7EFWWzZUrl3399ddB7UG2bNnimpQrsJNixYq5XnYqmJIvXz6Xvhlo3rx5Vrt27SS91rp169xavNBm6hMnTjxvvz8AAEg5GXxaWZ/OKTVJa1S0vkWpT6nFxFU7I/0WkI7cUbVYpN8C/v/UyaNHj8bodSeatVNQ5jl16pS/FUm4/VqWLFksR44ciX4t7QuPHz8eY3ttF9fz4fw7+tY7/JlxQeR+sGuq/Uvvbtw40m8B6UiRWbMsLcYtrMkDgFQkU6ZMYQM8CQzwYgvuPAkZsIrttXRbbO8BAACkfqRrAgAAAEAUIcgDAAAAgChCkAcAAAAAUYQgDwAAAACiCIVXAKR6tW9+MdJvAenIL98Gt6MAACCtYSYPAAAAAKIIQR4AAAAARBGCPAAAAACIIgR5AAAAABBFCPIAAAAAIIoQ5AEAAABAFCHIAwAAAIAoQpAHAAAAAFGEIA8AAAAAoghBHgAAAABEEYI8AAAAAIgiSQ7yevfubYsXL07ZdwMAAAAAiEyQV69ePXv44Yftuuuus8mTJ9vZs2eT904AAAAAAJEL8tq0aWO//PKLjRgxwiZNmmTlypWzl19+2Q4fPpz8dwUAAAAAiMyavDp16tjEiRNt7ty59uWXX1qJEiVcKufOnTuT+9QAAAAAgAsd5J0+fdo++OADa9asmZ07d87Gjh1r2bJls6pVq9rs2bOT+/QAAAAAgETIbEl06NAhGzNmjI0aNcpq1arlgruaNWu6+9q2bWu33HKLtW/f3rZv324ZMmRI6ssAAAAAAC5EkHfjjTfa9ddfbwsWLLBLL700xv0NGzZ0xVh27dplxYoVS+rLAAAAAAAuRJD3448/Wvbs2ePcRgFgoUKFkvoSAAAAAIALtSbvhx9+sJ49e5rP5/Pf9uabb9q7777rv37VVVdZ5sxJjiMBAAAAABcqyOvbt6917NgxaL1dly5drF+/fnbq1KmkPi0AAAAAIBJBXu7cucP2xDt27JirsgkAAAAASENBXocOHezRRx+1rVu3uuuavVN/vEaNGsW7Vi/QF198YZ07d7Ynn3zS9uzZE+t2XnuGrl272tNPPx122z/++MPNMPbp08f27t2bxN8MAAAAANJhkKdgq127dlalShUrXbq0FSxY0LZs2WLvvPNOgp9j9OjRNmDAANdQXVU4VbFTFTnDUTuGzz77zLVp+Oeff+yGG24I2nbTpk1Wv359y5kzpxUvXtxeeumlpP5qAAAAAJBmJasqSv/+/e2JJ56wbdu2Wf78+S1PnjwJfqxm5gYNGmTfffedXXnllS5oVAD37bffusbqgdauXWvLly+3X3/91RVy0bb33nuvTZ8+3Vq0aOG20XMNGzbM7r77bnc9XCopAAAAAES7jMl+gowZrVSpUokK8GT9+vV28cUXuwDP07RpU/vpp59ibPvXX39ZuXLlgip1qg/fzz//7H4+cuSIff/993bHHXf470/s+wEAAACAdD2Tp7V4SrXULNvJkyeD7ps7d64VKVIkzscrcCtTpkzQbWqqvnDhwhjbVqxY0X755RfbuXOna6yutg2zZs2yHDly+FM1q1at6rb58MMPLV++fPbf//43KIAMpPcb+J6Z9QMAAABg6X0m78EHH7R///3X/ve//7l/H374YStZsqQLrgoUKBDv448fP27ZsmULui1r1qx2+vTpGNtqzZ+KvGj9X/Pmza1y5cp25swZf/uG7du3244dO2zo0KEu5VO3165d2zZv3hz2tYcMGWJ58+b1X/S+AQAAACBdz+RpJm7SpElWoUIFl0qpy+eff+5m57p37x7v4zULd+jQoaDbDh48GGuapfrvtW3b1tatW+dea/78+f7KnpkyZbITJ07YlClT3M+i2T4VgQlXgEVrCVWBM3Amj0APAAAAQLqeyVOrBK2Dk2rVqtnSpUtdG4V9+/a5gCs+ZcuWtd9++y3oNhVWUdAYm8svv9wVWlEa5pw5c6xWrVr+NM8SJUr4Azy56qqr7MCBA2GfRzOICiYDLwAAAACQroO8F1980c2W6XL//ffbK6+84gKrO++8M0FBk1dIZdq0ae662iJ89dVXLh3Tm9Xr0aOHLVq0yF+o5ejRo+5nVdpcsWKFNWnSxL9mb/fu3W4bUSqnZhnr1q2b1F8PAAAAANJXuqb60XlpmZdddplt3LjRBVqaVUuo119/3bU80Do6FXBRM3VV6pTFixfbqFGj3M+asdNavWuuucbNACrImzhxYtCaPgWd119/vdWoUcMVYtH6PTVsBwAAAID0JMlB3tixY13Lgty5c7vr2bNn9wdoCaV+eGvWrLFly5a5WcDy5csHpYPOnDnTn5Kp6pkzZsxws3X16tVzFTQD3XLLLbZq1SoXHOp9KIUUAAAAANKbJAd5I0aMsAYNGtgVV1yRrDeggifhip6oQmbjxo2DblNRl9C2C4HUXqFly5bJej8AAAAAkC7X5A0aNMiGDx/uGpEDAAAAANL4TN6YMWNcNczx48e7dXhZsmRJVDN0AAAAAEAqCvLefPPNsI3LJSHN0AEAAAAAqSjIU6EUAAAAAECUBHk33XST7dq1K+x9pGsCAAAAQBpP19y5c6cNHjzY2rVrR7omAAAAAKT1dM1KlSq5XnZqWN6tW7eUeG8AAAAAgAvVQiGcwoULu5YKBw4cSMmnBQAAAABEIsj75JNPLHPmzKRrAgAAAEBaL7yi2buTJ0/au+++m1LvDQAAAAAQqcIruXLlspIlS7qZPAAAAABAZNAnDwAAAACiSJLX5M2ZM8d69uxpPp8vaHaPdE0AAAAASINBXt++fa1jx46WIUMG/21dunSxfv362alTp1Lq/QEAAAAALkSQlzt3bjt8+HCM248dO2bnzp1L6tMCAAAAACIR5HXo0MEeffRR27p1q7uu2bvevXtbo0aNLHv27Ml5TwAAAACAC114pWvXrrZ3716rUqWK5c+f3/bt22f16tWzcePGJfUpAQAAAADJlKx+B/3797cnnnjCtm3b5gK9PHnyJPf9AAAAAACSIdlN7TJmzGilSpVK7tMAAAAAAFIALRQAAAAAIIrQQgEAAAAAoggtFAAAAAAgitBCAQAAAACiCC0UAAAAACCK0EIBAAAAAKJIxpRqoaAeeatWrbIePXrYP//8kzLvDgAAAABwYYO8f//919577z2rXbu23XfffVaxYkXXGB0AAAAAkIbSNTVr9/bbb9vHH39shw8ftpkzZ1qjRo1S9t0BAAAAAM7fTF7grF2rVq3cjN3ixYutePHiVr58+cS9MgAAAAAgsjN51apVs4IFC9rQoUOtQYMGliFDhpR/RwAAAACACzOTN2zYMMuVK5c98MAD9sILL9hff/2V9FcGAAAAAEQ2yFOK5uzZs2369Ol27Ngxq1OnjjVs2NAOHTqU8u8MAAAAAHBhqmtefvnl9tJLL9nWrVutZ8+ebo1e69at7Y033rB9+/Yl5SkBAAAAAJFuoZAlSxa7/fbb3ezep59+an///bdVr17ddu/enRLvDQAAAABwofvkhc7ubd682QoUKJBSTwsAAAAAuBB98uKa3QMAAAAApPGZPAAAAABA5BHkAQAAAEAUIcgDAAAAgCiS5CDvpptuCltFM7bbAQAAAACpOMj77bff7PTp00G3HT582H7++WfLmjVrSrw3AAAAAMD5rq5533332dKlS91snWbtMmf+f0+xY8cOa9KkieXPnz+xTwsAAAAAiESQ9+KLL9r+/ftdgPfmm29akSJF/PdddNFFVqpUqZR4XwAAAACACxHkFS9e3F2+//571wA9cCYvKf744w+bM2eOFS1a1Jo1a2aZMmWKdds1a9bY4sWLXWDZtGlTy5gxfLbpggUL7NixY3bLLbck670BAAAAQLpZk3fVVVclO8CbO3eu1alTxxYtWmTPPvustW3bNtZthw4darfeeqv99NNP9swzz1ibNm3Cbnf8+HFr3769ffDBB8l6bwAAAACQroK82bNn20MPPWQ+n88OHTpkd9xxhzVs2NDNoiXUI488Yp9++qmNHTvWFWxZsWKF+zfU1q1b7dVXX7Xly5e7bZcsWWIHDx60X375JWww+O+//yb11wIAAACA9Bnk9e3b1zp27GgZMmSw/v37W44cOeyxxx6ze+65xwV+8fnzzz/t6NGjduONN7rr2bNnt9tvv93mzZsXY9vVq1fbtddeawUKFHDXldJ555132owZM4K2W7dunY0ePdrN9AEAAABAepTkfMs8efK4dW+aZZswYYJt2LDBChUqZB06dHCtFPLmzRvn4zdu3Gjly5cPuu2KK65ws3ShSpQo4QK9U6dO+dszbN682Q4cOODfRu9FaZojRoyIN4305MmT7uLR+wUAAACAdD2T9/TTT7s1dBUrVrQBAwa4AG/KlClWu3bteAM8L7BSNc5A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3b9/vxUoUCDWxyBt0OiGgvVt27a5IjxAesV3AeC7AHBMiD6KeRTgFS9ePM7tIhbk5cuXz/bt2xd0mwqlFCpUKOz2ms1bunSp//rw4cOtTJkySXqubNmyuUvo+0H0UIBHkAfwXQA4LgCcH0WbuGbwIl54pXLlyrZixYqgtXE//vijXXvttQl6/KRJk1xxlZR4LgAAAACIFhEL8ooUKWJ16tSxJ554whU+UTrmsmXLrEWLFu7+9evXu23eeOMNd33mzJk2d+5cO378uD3//PNu5u2aa65J0HMBAAAAQHoRsXRNGTt2rOtnp7VwanA+efJk1+vOW2enqpqnT59212vUqGG33nqrm7FTz7xPPvkkwc+F9ENpuM8++2yMdFwgveG7APBdADgmpF8ZfPHV3wQAAAAApBkRS9cEAAAAAKQ8gjwAAAAAiCIEeQAAAAAQRQjyAAAAACCKEOQBqYQqygJIvf7++29r0KCBbdy4MdJvBekAxwQgMr755hu77bbb7NixY2n6v4AgD0gFVOT27rvvtqeeeirSbwVALEqUKGEvvfSSNW7c2LZv387fCeeNTi6vu+46++ijj/grAxfIb7/9Zt9//71r1Va3bl1r2bJlmh5sIcgDLqBdu3bZv//+G+P2DBkyuIP5ypUrbciQIfyfABF09OhRN5L7+OOPW/Pmza19+/a2cOFCd58O/E8//bQblAGSa/PmzWFvz5Url82YMcNefPFF91kEkPL++ecf13e7c+fOVrJkSbv55ptt3rx57j7t/wsWLJimz8nokwdcQBqZveWWW9xJYjgHDx60atWq2ZdffmnVq1fn/waIgMqVK7tm8krXqVSpkpu1e+6552zmzJnue6mZ95o1a9qzzz5rt956K/9HSBJ9jnLmzOn29zq5DGf9+vX2n//8x/2bP39+/tJACqpTp44bUGndurXddNNNduWVVwbdv2PHDrfPX7t2rRUqVCjN/e0J8oAUMH78eLv66qutYsWKsW6zZcsW69Chgy1ZssQFe0oHKFWqlOXJk8caNWrk327cuHE2efJkmz59Ov83wHny559/uhPsIkWKxLivZ8+eblS3b9++/ttGjBhhGzZssHfeecddnzhxovveT5s2jf8jxKAZuIceesjt32OjmboXXnjBHRPatGljN9xwgwvkLr/8cnc88TzyyCOWO3duGzx4MH9pIAU99NBD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idZkav8f+FnSyaHWb2rwQftp7yQzIceEunXrugFEfY7E+3nYsGEJGoTUgCOQVqj4ib4TTzzxhNt/Joey9lStUseGXbt2uWJzKj4XWIlcAyWlSpXyXw/8Tuq4ogGW//73v24A8YEHHvCvr4urkJ7W4qnwESIoiTOASIeefPJJl/6lj03o2h71HQrsa6IGs1on4a2L05qI6tWr+3te6XnUgygu27Ztc89BY2OkZ0p1UzpamzZtXBp0pkyZYqyZCKUm5Dlz5vStW7fO17ZtW1+HDh3cOlY1tI3vsYGU4qa1fEp5C0frobRWKmPGjL7cuXO79a+h63ARHbRGU/v55cuX+29r3Lix25dnzZrVN3369KDtdYzQuuzAdLBbb73Vv2+vUKGCS79Xmr/SjAsUKBDveky9frly5dwaPyCa6LOv/bO+N1rLptTm+vXru3239rGJpXV077zzjlvrXLBgQd+VV17pGpzrOKDvclyU7qm+lEqj1jprNSpv3ry577XXXgtaCqP06UGDBiXp98WFQZAHRwfaRYsW+YYNG+a75557fLfddpvv8ccfd4viPfPmzfPVqlXLnXDqpC6wMa12Clr3oCblnjJlyrhF7qLc7o4dO/pq1KjhO3TokFsnFNc6Co8W6FaqVMmdBADphYpCKGDSGgkdaLWOQgvYFVTdfvvtvvfffz/Wx+o7qAXugd/PNWvW+F544QUX8I0bNy5R70Un5t4aOu0PdKDXAV8nAR6tsaJgSnTSGppnnnnGBfFag6kTzldffdXdp+OFCip069bNly1btqDCKDphrVKliv+6im1pwMBbG6eBA6351EmsPjtqcq/HxEfvRZ+/0HV9QFqlonAawCtfvnyMokD6V9+9wHOr2OgczitwUrhwYTeIohoICu7io2bkKnbnBZj16tXzPffcc67eQmxr8xSQ6vuI1IsgD74FCxa4IikaWdUOZuzYse6EUhXwdFBWwQTRQVVffs3GqbJe9uzZXfEFz0033eT75ptv/NdVkOHll18OOllQ4KgDu16jdevWCfrr6/W1k2LBLtILBWiho6aet99+23f33XeHfZwqYerAroEafV908uwVREoqndBrFFgFUxR0eoVc4quQi+igfbZO+JSd4fFO+lasWOE+G/Lss8/6cuTI4QYDRZ89zSAEDtBdc801voULF/qve1kenTt3jrWicjgaYND3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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "results = {\n", + " \"DNN (기초 실습)\": (dnn_basic_acc, dnn_basic.count_params()),\n", + " \"DNN (논문 구조, LeCun MLP)\": (dnn_paper_acc, dnn_paper.count_params()),\n", + " \"CNN (기초 실습)\": (cnn_basic_acc, cnn_basic.count_params()),\n", + " \"CNN (논문 구조, LeNet-5)\": (lenet5_acc, lenet5.count_params()),\n", + "}\n", + "\n", + "print(f\"{'모델':<26}{'테스트 정확도':<15}{'파라미터 수':<15}\")\n", + "for name, (acc, params) in results.items():\n", + " print(f\"{name:<26}{acc:<15.4f}{params:<15,}\")\n", + "\n", + "names = list(results.keys())\n", + "accs = [v[0] for v in results.values()]\n", + "\n", + "plt.figure(figsize=(9,4))\n", + "plt.bar(names, accs, color=['#a8d0e6','#374785','#f3969a','#e84545'])\n", + "plt.ylim(0.9, 1.0)\n", + "plt.ylabel('Test Accuracy')\n", + "plt.title('모델별 정확도 비교')\n", + "plt.xticks(rotation=15)\n", + "for i, v in enumerate(accs):\n", + " plt.text(i, v+0.002, f\"{v:.4f}\", ha='center')\n", + "plt.tight_layout()\n", + "plt.show()\n" + ] + }, + { + "cell_type": "markdown", + "id": "d8d1a403", + "metadata": { + "id": "d8d1a403" + }, + "source": [ + "## 14. LeNet-5가 학습한 필터 시각화\n", + "\n", + "CNN의 첫 번째 Conv층이 어떤 패턴을 스스로 학습했는지 눈으로 확인해봅니다.\n", + "사람이 정해준 필터가 아니라, 학습 과정에서 데이터로부터 스스로 만들어낸 필터라는 점이 포인트입니다.\n", + "이 셀은 그대로 실행하세요.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "05cc5ecd", + "metadata": { + "id": "05cc5ecd" + }, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAxsAAACsCAYAAAAT+bGZAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjExLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlcelbwAAAAlwSFlzAAAPYQAAD2EBqD+naQAAFlBJREFUeJzt3QuwpvUcB/BntUlSrbKStF3WprJUWElMpiG3WpfSSnSl5G6jG+niOmbcZrOVVETaXLKxwpJbhFRiK7k1USkJUSmSNd9n5t159/Sec97znvOze/T5zJzZ3fe85/c+7/O+p37f5395pyxfvnx5AwAAMMEeMNEFAQAAhA0AAKCMkQ0AAKCEsAEAAJQQNgAAgBLCBgAAUELYAAAASggbAABACWEDmFSuuuqq5uc///mw38/3Rvr+eAxau/vnJur4qp7neOqO9LO5/Wc/+9n/5FhGO46q90e/xzQRx1D5PPI6rc7HB0wuU1f1AQCMxZe//OXm7rvvbh7/+Mf3/P55553X/jnc93vpNEVDf2bo7b1qj9ZU5b7dPzfo8Q19jE6dF7/4xfd5vJFqX3rppc11113XzJw5s9lhhx3u8/1Bjq+fn833/v3vfzfbbbdd3/U++tGPNlOmTGlOOeWUCT2O4b43HsO9D7bffvtm9uzZY3oP/Oc//2kuueSS5ve//30zY8aM5slPfnLzgAc8YFzPo5/mv/Pe+dznPtdMnTq1Z+2R6nS/96rOMzD5CBuwGhuuCR5rjXzNmjWr2XHHHUseox933XVX84UvfGFMjXGvBueKK65o/vWvfzWf/vSnB6rVy3CNUT8N029+85vma1/72krnsfvvD37wgwc6pl6P0W3ZsmXNPffcc5/6+Xev4/373//evPCFL2x+97vftSEj5/HRj350+5qss846Ax3X0Maz8/der81YffOb32w+9alPtU32Pvvs0+yyyy6r5Di6XX/99c1ll13WbL311u3XaK9RzvFhhx3Who1+pc6ee+7ZvsfzGNdcc02z1lprte/FLbfccuBj73V8Qw333umW48hXr/v18/PA/Y+wAauxibg6mBonnHBCM23atOaXv/xl8/CHP3xCHuPee+9t/vCHPzSbbrppX/fPVdrDDz+8edaznjXm5qS7Ubr66qvbP4c2Tquq0cnIQr6OP/74Zvny5e25Pu6449or8rktxjudpPMYHWlA99577+ZBD3pQc+SRRzaPe9zjRq3x6le/utloo42apUuXtletM8rwile8onnd617XnHnmmRPSwHaCT6/XZiwN/Qc+8IHm4x//eLNgwYLmIQ95SDN37tzm0EMPbd70pjc1j3zkI/8nxzHUu971rubkk09uRxnyen74wx9u9thjj2FfozjggAP6/v2If/7zn81zn/vcNqDMnz9/xe0f/OAHm+c973nt4z7wgQ8c6PhzbNtuu207svXMZz6zecQjHtHefuONNzbf/va3myc96Un3CVAj1eq8twFGI2zA/UCuXN92223NEUcc0XziE5+YkJrve9/72ivr/TYdf/3rX9uGZujV5n50N3JprtPI5/GHXtlO7fw5SOjo/PzQ26Jz+0i1//GPf7QBrDOKUOGOO+5ozjjjjPY1PPfcc9vQkMCw7777NgcffPCwzfQNN9zQLF68uJ0+lZ+J/PmRj3yk2WKLLZp3v/vdPZv4sbwuf/nLX9rz86c//amZPn16e5422GCDFfcdKXAlBJ9++untWoEf/OAHzQte8ILm8ssvb7baaqv2+2nw3/a2t7XN8M4779xOxUoj32mOu4/jzjvvbOvcdNNNzcYbb9zet3vkZpDgl0CekJbjTPhJKMioxWh+8pOftCGlX1/60peaTTbZZKWgEfl3vpcphBn1GNTf/va39v2S5/OiF72ove2iiy5qb/vxj388cF2AkQgbcD+Qxuwzn/lMc9ZZZ7VN6dOf/vRx10zQGIuEjYc+9KHjeszMZf/KV77SXpU/8cQTV1zl7b6ynb8PEjZ6TTPJbdFP7dtvv71tuCPBbqznZzi/+tWvms9//vNtk5xG/MADD2wbxE4D/d3vfre9yp6mes6cOe2UnTSkj3nMY1bU+N73vtdezR46qpV/77rrru33X/rSlw78mqQZTiDL9KwEjD//+c/NT3/607aJzVX5znqDK6+8ckVw65721rn6//KXv7w5++yzm4c97GErPUYCUd6/t956a/tapNHvFY4yQnPMMce008Ny5T6BI6/Ze97znuaggw5qBvWOd7yjfQ0SNCLTmnpNSRz6usWjHvWovh/n17/+dRvIe8ntnZqDyjFvttlmzS233LLitrxWuS2BDqCCsAGTXBZLf//732/XRKThzILSoXKl+aSTTmqvgL/mNa9pG8HOFe6x1u2MInRf9e9nvcREhI1M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CBsAAEAJYQMAACghbAAAACWEDQAAoISwAQAAlBA2AACAEsIGAABQQtgAAABKCBsAAEAJYQMAACghbAAAACWEDQAAoISwAQAAlBA2AACAEsIGAABQYmq/d7z++utLDuCAAw4oqbvPPvuU1D388MObKnvuuWczmVxxxRUldWfNmlVS95hjjimpe8opp5TUnTFjRjPZLFmypKTu3LlzS+puueWWk+p3Y4sttmgmmwULFpTU3WmnnUrqTp8+vaTuCSecMKn+OzFnzpySupdccklTZfHixSV1jz766En1Xttqq62ayWTZsmUldffaa6+mSlU/MX/+/JK6++67b7MqGdkAAABKCBsAAEAJYQMAACghbAAAACWEDQAAoISwAQAAlBA2AACAEsIGAABQQtgAAABKCBsAAEAJYQMAACghbAAAACWEDQAAoISwAQAAlBA2AACAEsIGAABQQtgAAABKCBsAAEAJYQMAACghbAAAACWEDQAAoISwAQAAlJja7x2vu+66kgO48847S+peddVVJXUvvvjipsqNN95YUnezzTYrqbvHHnuU1F1rrbVK6l500UUldffff/+SuosXL24mm8svv7yk7qmnnlpSd+bMmSV177777pK6a6yxRknd8847r6my4YYbltTdaKONSuqedNJJJXWvv/76kronnnhiSd2DDz64pO7pp5/eVNl6661L6t51112T6v/5O++8czOZrLvuupPq/RBHHXVUSd1bb721pO5hhx1WUvfSSy/t635GNgAAgBLCBgAAUELYAAAASggbAABACWEDAAAoIWwAAADCBgAAMHkY2QAAAEoIGwAAQAlhAwAAKCFsAAAAJYQNAACghLABAACUEDYAAIASwgYAAFBC2AAAAEoIGwAAQAlhAwAAKCFsAAAAJYQNAACghLABAACUmNrvHbfddtuSA9hoo41K6p522mkldWfMmNFUOfTQQ5vJZOHChSV199tvv0l1fq+99tqSuscff3wz2SxZsqSk7g033FBS9+qrry6pu9lmm5XU3WGHHZrJ5vbbby+pu2jRopK6t912W0ndTTfdtKTu7Nmzm8mk6v0Q06ZNK6l77LHHltTdYIMNSuqee+65JXXnzZtXUnf99dcvqXvJJZc0VS688MKSujvttFNJ3csuu6xZlYxsAAAAJYQNAACghLABAACUEDYAAIASwgYAAFBC2AAAAEoIGwAAQAlhAwAAKCFsAAAAJYQNAACghLABAACUEDYAAIASwgYAAFBC2AAAAEoIGwAAQAlhAwAAKCFsAAAAJYQNAACghLABAACUEDYAAIASwgYAAFBiar93vOeee0oOYOnSpSV1p02bVlL37LPPbqqst956JXXnz59fUnfu3Lkldc8555ySusuXLy+pW/W7MXv27Gay2XXXXUvqzpw5s6TuOuusU1J3+vTpJXWvueaaZrKpeh8vWLCgpO6VV15ZUnfevHkldXfcccdmMpk1a1ZZ7d12262k7sKFC0vqHn300SV1119//WYyqfrvWuV5qPq9u+WWW0rqHnjggc2qZGQDAAAoIWwAAAAlhA0AAKCEsAEAAJQQNgAAgBLCBgAAUELYAAAASggbAABACWEDAAAoIWwAAAAlhA0AAKCEsAEAAJQQNgAAgBLCBgAAUELYAAAASggbAABACWEDAAAoIWwAAAAlhA0AAKCEsAEAAJQQNgAAgBJTli9fvrymNAAAcH9mZAMAACghbAAAACWEDQAAoISwAQAAlBA2AACAEsIGAABQQtgAAABKCBsAAEAJYQMAAGgq/BdRZCO4+IVC1wAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "first_conv_weights = lenet5.layers[0].get_weights()[0] # shape: (5,5,1,6)\n", + "\n", + "fig, axes = plt.subplots(1, 6, figsize=(10,2))\n", + "for i, ax in enumerate(axes.flat):\n", + " ax.imshow(first_conv_weights[:,:,0,i], cmap='gray')\n", + " ax.axis('off')\n", + "plt.suptitle(\"LeNet-5 첫 번째 층이 학습한 6개의 필터\")\n", + "plt.show()\n" + ] + }, + { + "cell_type": "markdown", + "id": "cc5e7918", + "metadata": { + "id": "cc5e7918" + }, + "source": [ + "## 15. 정리\n", + "\n", + "**오늘 배운 것 정리**\n", + "- DNN은 이미지를 1차원으로 펼치면서 공간 정보를 잃는 반면, CNN은 합성곱 연산으로 공간 정보를 유지한 채 특징을 추출합니다.\n", + "- 기초 실습 구조에서 논문 구조로 갈수록 (은닉층/필터 수 증가) 일반적으로 성능이 향상되지만, 그만큼 파라미터 수와 학습 시간도 늘어납니다.\n", + "\n", + "\n", + "**함께 생각해볼 질문**\n", + "1. 은닉층/파라미터를 늘리면 (기초 실습 → 논문 구조) 정확도가 어떻게, 얼마나 바뀌었나요? DNN과 CNN에서 그 차이가 비슷한가요, 다른가요?\n", + "2. 파라미터 수가 늘어난다고 항상 성능이 좋아질까요? 그렇지 않다면 어떤 경우에 그럴까요? (힌트: 과적합)\n", + "3. 오늘 만든 구조들과 비교했을 때, 최근 CNN 구조(ResNet 등)는 어떤 점이 다를까요? (관심 있으면 직접 찾아보기)\n" + ] + } + ], + "metadata": { + "colab": { + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3", + "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.12.4" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}