{ "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "Using TensorFlow backend.\n" ] }, { "data": { "text/plain": [ "'2.2.4'" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import keras\n", "keras.__version__" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "# 5.1 - 합성곱 신경망 소개\n", "\n", "이 노트북은 [케라스 창시자에게 배우는 딥러닝](https://tensorflow.blog/케라스-창시자에게-배우는-딥러닝/) 책의 5장 1절의 코드 예제입니다. 책에는 더 많은 내용과 그림이 있습니다. 이 노트북에는 소스 코드에 관련된 설명만 포함합니다. 이 노트북의 설명은 케라스 버전 2.2.2에 맞추어져 있습니다. 케라스 최신 버전이 릴리스되면 노트북을 다시 테스트하기 때문에 설명과 코드의 결과가 조금 다를 수 있습니다.\n", "\n", "----\n", "\n", "컨브넷의 정의와 컨브넷이 컴퓨터 비전 관련 작업에 잘 맞는 이유에 대해 이론적 배경을 알아보겠습니다. 하지만 먼저 간단한 컨브넷 예제를 둘러 보죠. 2장에서 완전 연결 네트워크로 풀었던(이 방식의 테스트 정확도는 97.8%였습니다) MNIST 숫자 이미지 분류에 컨브넷을 사용해 보겠습니다. 기본적인 컨브넷이더라도 2장의 완전 연결된 모델의 성능을 훨씬 앞지를 것입니다.\n", "\n", "다음 코드는 기본적인 컨브넷의 모습입니다. `Conv2D`와 `MaxPooling2D` 층을 쌓아 올렸습니다. 잠시 후에 이들이 무엇인지 배우겠습니다.\n", "\n", "컨브넷이 `(image_height, image_width, image_channels)` 크기의 입력 텐서를 사용한다는 점이 중요합니다(배치 차원은 포함하지 않습니다). 이 예제에서는 MNIST 이미지 포맷인 `(28, 28, 1)` 크기의 입력을 처리하도록 컨브넷을 설정해야 합니다. 이 때문에 첫 번째 층의 매개변수로 `input_shape=(28, 28, 1)`을 전달합니다." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "from keras import layers\n", "from keras import models\n", "\n", "model = models.Sequential()\n", "model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1)))\n", "model.add(layers.MaxPooling2D((2, 2)))\n", "model.add(layers.Conv2D(64, (3, 3), activation='relu'))\n", "model.add(layers.MaxPooling2D((2, 2)))\n", "model.add(layers.Conv2D(64, (3, 3), activation='relu'))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "지금까지 컨브넷 구조를 출력해 보죠:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "_________________________________________________________________\n", "Layer (type) Output Shape Param # \n", "=================================================================\n", "conv2d_1 (Conv2D) (None, 26, 26, 32) 320 \n", "_________________________________________________________________\n", "max_pooling2d_1 (MaxPooling2 (None, 13, 13, 32) 0 \n", "_________________________________________________________________\n", "conv2d_2 (Conv2D) (None, 11, 11, 64) 18496 \n", "_________________________________________________________________\n", "max_pooling2d_2 (MaxPooling2 (None, 5, 5, 64) 0 \n", "_________________________________________________________________\n", "conv2d_3 (Conv2D) (None, 3, 3, 64) 36928 \n", "=================================================================\n", "Total params: 55,744\n", "Trainable params: 55,744\n", "Non-trainable params: 0\n", "_________________________________________________________________\n" ] } ], "source": [ "model.summary()" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "`Conv2D`와 `MaxPooling2D` 층의 출력은 `(height, width, channels)` 크기의 3D 텐서입니다. 높이와 넓이 차원은 네트워크가 깊어질수록 작아지는 경향이 있습니다. 채널의 수는 `Conv2D` 층에 전달된 첫 번째 매개변수에 의해 조절됩니다(32개 또는 64개).\n", "\n", "다음 단계에서 마지막 층의 (`(3, 3, 64)` 크기인) 출력 텐서를 완전 연결 네트워크에 주입합니다. 이 네트워크는 이미 익숙하게 보았던 `Dense` 층을 쌓은 분류기입니다. 이 분류기는 1D 벡터를 처리하는데 이전 층의 출력이 3D 텐서입니다. 그래서 먼저 3D 출력을 1D 텐서로 펼쳐야 합니다. 그다음 몇 개의 `Dense` 층을 추가합니다:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "model.add(layers.Flatten())\n", "model.add(layers.Dense(64, activation='relu'))\n", "model.add(layers.Dense(10, activation='softmax'))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "10개의 클래스를 분류하기 위해 마지막 층의 출력 크기를 10으로 하고 소프트맥스 활성화 함수를 사용합니다. 이제 전체 네트워크는 다음과 같습니다:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "_________________________________________________________________\n", "Layer (type) Output Shape Param # \n", "=================================================================\n", "conv2d_1 (Conv2D) (None, 26, 26, 32) 320 \n", "_________________________________________________________________\n", "max_pooling2d_1 (MaxPooling2 (None, 13, 13, 32) 0 \n", "_________________________________________________________________\n", "conv2d_2 (Conv2D) (None, 11, 11, 64) 18496 \n", "_________________________________________________________________\n", "max_pooling2d_2 (MaxPooling2 (None, 5, 5, 64) 0 \n", "_________________________________________________________________\n", "conv2d_3 (Conv2D) (None, 3, 3, 64) 36928 \n", "_________________________________________________________________\n", "flatten_1 (Flatten) (None, 576) 0 \n", "_________________________________________________________________\n", "dense_1 (Dense) (None, 64) 36928 \n", "_________________________________________________________________\n", "dense_2 (Dense) (None, 10) 650 \n", "=================================================================\n", "Total params: 93,322\n", "Trainable params: 93,322\n", "Non-trainable params: 0\n", "_________________________________________________________________\n" ] } ], "source": [ "model.summary()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "여기에서 볼 수 있듯이 `(3, 3, 64)` 출력이 `(576,)` 크기의 벡터로 펼쳐진 후 `Dense` 층으로 주입되었습니다.\n", "\n", "이제 MNIST 숫자 이미지에 이 컨브넷을 훈련합니다. 2장의 MNIST 예제 코드를 많이 재사용하겠습니다." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "from keras.datasets import mnist\n", "from keras.utils import to_categorical\n", "\n", "(train_images, train_labels), (test_images, test_labels) = mnist.load_data()\n", "\n", "train_images = train_images.reshape((60000, 28, 28, 1))\n", "train_images = train_images.astype('float32') / 255\n", "\n", "test_images = test_images.reshape((10000, 28, 28, 1))\n", "test_images = test_images.astype('float32') / 255\n", "\n", "train_labels = to_categorical(train_labels)\n", "test_labels = to_categorical(test_labels)" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Epoch 1/5\n", "60000/60000 [==============================] - 4s 71us/step - loss: 0.1786 - acc: 0.9441\n", "Epoch 2/5\n", "60000/60000 [==============================] - 3s 51us/step - loss: 0.0487 - acc: 0.9853\n", "Epoch 3/5\n", "60000/60000 [==============================] - 3s 51us/step - loss: 0.0339 - acc: 0.9892\n", "Epoch 4/5\n", "60000/60000 [==============================] - 3s 51us/step - loss: 0.0253 - acc: 0.9921\n", "Epoch 5/5\n", "60000/60000 [==============================] - 3s 51us/step - loss: 0.0191 - acc: 0.9943\n" ] }, { "data": { "text/plain": [ "" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "model.compile(optimizer='rmsprop',\n", " loss='categorical_crossentropy',\n", " metrics=['accuracy'])\n", "model.fit(train_images, train_labels, epochs=5, batch_size=64)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "테스트 데이터에서 모델을 평가해 보죠:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "10000/10000 [==============================] - 0s 32us/step\n" ] } ], "source": [ "test_loss, test_acc = model.evaluate(test_images, test_labels)" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0.9898" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "test_acc" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "2장의 완전 연결 네트워크는 97.8%의 테스트 정확도를 얻은 반면, 기본적인 컨브넷은 99.2%의 테스트 정확도를 얻었습니다. 에러율이 (상대적으로) 64%나 줄었습니다. 나쁘지 않군요!" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.6" } }, "nbformat": 4, "nbformat_minor": 2 }