{ "cells": [ { "cell_type": "code", "execution_count": 41, "metadata": {}, "outputs": [], "source": [ "# 일관된 출력을 위해 유사난수 초기화\n", "def reset_graph(seed=42):\n", " tf.reset_default_graph()\n", " tf.set_random_seed(seed)\n", " np.random.seed(seed)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# CHAPTER 11. 심층 신경망 훈련\n", "---" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "앞 장에서 인공 신경망을 소개했고 두 개의 은닉층만 있는 매우 얕은 심층 신경망을 훈련시켰음
\n", "아주 복잡한 문제를 다뤄야 한다면 아마도 훨씬 더 깊은 심층 신경망을 훈련시켜야 함. 이건 그리 쉬운 일이 아님
\n", "- 첫째, 까다로운 **그래디언트 소실**vanishing gradient(또는 **그래디언트 폭주**exploding gradient) 문제에 직면함\n", "- 둘째, 이런 대규모 신경망에서는 훈련이 극단적으로 느려짐\n", "- 셋째, 수백만 개의 파라미터를 가진 모델은 훈련 세트에 과대적합될 위험이 매우 큼
\n", "\n", "이 장에서는 이 문제들을 차례대로 살펴보고 해결 방법을 제시하겠음" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "## 11.1 그래디언트 소실과 폭주 문제" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- **그래디언트 소실** : 알고리즘이 하위층으로 진행됨에 따라 그래디언트는 점점 작아지는 경우를 말함. 결국 경사 하강법이 하위층의 연결 가중치를 실제 변경되지 않은 채로 둬 훈련이 좋은 솔루션으로 수렴되지 않음
\n", "- **그래디언트 폭주** : 그래디언트 소실과 반대 현상으로 그래디언트가 점점 커져 여러 개의 층이 비정상적으로 큰 가중치로 갱신되어 알고리즘이 발산하는 경우를 말함" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- 로지스틱 활성화 함수를 보면(그림 11-1) 입력의 절댓값이 크면 0이나 1로 수렴해서 기울기가 0에 매우 가까워지는 것을 알 수 있음.
\n", "- 역전파가 될 때 사실상 신경망으로 전파시킬 그래디언트가 거의 없고 조금 있는 그래디언트는 최상위층에서부터 역전파가 진행됨에 따라 점차 약해져서 실제로 아래쪽 층에는 아무것도 도달하지 않게 됨." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "
**그림 11-1 로지스틱 활성화 함수의 수렴**
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 11.1.1 세이비어 초기화와 He 초기화" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "글로럿과 벤지오는 그들의 논문에서 이 문제를 크게 완화시키는 방법을 제안함.
\n", "- 적절한 신호가 흐르기 위해서는 각 층의 출력에 대한 분산이 입력에 대한 분산과 같아야 한다고 주장. 그리고 역방향에서 층을 통과하기 전과 후의 그래디언트 분산이 동일해야 함.
\n", "- 연결 가중치를 [식 11-1]에 기술한 방식대로 무작위로 초기화하는 것. 이 식에서 ninputs와 noutputs는 가중치를 초기화하려는 층의 입력과 출력 연결의 개수임
\n", "(또는 **팬인**fan-in과 **팬아웃**fan-out으로 부름).
\n", "- 이 초기화 전략을 **세이비어 초기화**Xavier initialization 또는 **글로럿 초기화**Glorot initialization라고 함." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "
**식 11-1 세이비어 초기화(로지스틱 활성화 함수를 사용했을 때)**
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- 최근 몇몇 논문은 다른 활성화 함수에 대해 [표 11-1]과 같이 비슷한 전략을 제안함.
\n", "- ReLU 활성화 함수를 위한 초기화 전략을 He 초기화He initialization라고 부르기도 함." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "
**표 11-1 활성화 함수 종류에 따른 초기화 매개변수**
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- tf.layer.dense() 함수는 기본적으로 (균등분포로) 세이비어 초기화를 사용함.
\n", "이를 variance_scaling_initializer() 함수를 사용하여 다음과 같이 He 초기화 방식으로 바꿀 수 있음." ] }, { "cell_type": "code", "execution_count": 42, "metadata": {}, "outputs": [], "source": [ "import tensorflow as tf\n", "\n", "n_inputs = 28 * 28 # MNIST\n", "n_hidden1 = 300\n", "\n", "X = tf.placeholder(tf.float32, shape=(None, n_inputs), name=\"X\")" ] }, { "cell_type": "code", "execution_count": 43, "metadata": {}, "outputs": [], "source": [ "he_init = tf.variance_scaling_initializer()\n", "hidden1 = tf.layers.dense(X, n_hidden1, activation=tf.nn.relu,\n", " kernel_initializer=he_init, name=\"hidden1\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 11.1.2 수렴하지 않는 활성화 함수" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- 글로럿과 벤지오의 2010년 논문에서 얻은 통찰 중 하나는 활성화 함수를 잘못 선택하면 자칫 그래디언트의 소실이나 폭주로 이어질 수 있다는 것.\n", "- 그전에는 대부분 시그모이드 활성화 함수가 최선의 선택일 것이라고 생각했지만 다른 활성화 함수가 심층 신경망에서 훨씬 더 잘 작동한다는 사실이 밝혀짐.\n", "- 특히 ReLU 함수는 특정 양숫값에 수렴하지 않는다는 커다란 장점이 있음(그리고 계산도 빠름)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- 하지만 ReLU 함수는 완벽하지 않음. **죽은 ReLU**dying ReLU로 알려진 문제가 있음.
\n", "훈련하는 동안 일부 뉴런이 0 이외의 값을 출력하지 않는다는 의미로 죽었다고 말함.\n", "- 훈련 도중 뉴런의 가중치가 바뀌어 가중치 합이 음수가 되면 그다음부터 0을 출력하기 시작할 것임. ReLU 함수는 입력이 음수면 그래디언트가 0이 되기 때문에 이런 일이 생기면 뉴런이 다시 살아나기 어려움" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- 이 문제를 해결하기 위해 **LeakyReLU** 같은 ReLU 함수의 변종을 사용.\n", "- 이 함수는 LeakyReLU$\\alpha$(z) = max($\\alpha$z, z)로 정의(그림 11-2).
\n", "하이퍼파라미터 $\\alpha$가 이 함수가 '새는leaky' 정도를 결정. 새는 정도란 z<0일 때 이 함수의 기울기이며, 일반적으로 0.01로 설정.
\n", "이 작은 기울기가 LeakyReLU를 절대 죽지 않게 만들어줌.\n", "- 최근 한 논문에서 여러 ReLU 함수의 변종을 비교해 얻은 결론 하나는 LeakyReLU가 ReLU보다 항상 성능이 높다는 것.\n", "- 사실 $\\alpha$=0.2(만이 통과)로 하는 것이 $\\alpha$=0.01(조금 통과)보다 더 나은 성능을 내는 것으로 보임.\n", "- 이 논문은 훈련하는 동안 주어진 범위에서 $\\alpha$를 무작위로 선택하고 테스트 시에는 평균을 사용하는 **RReLU**randomized leaky ReLU도 평가.
\n", "이 함수도 꽤 잘 작동했으며 (훈련 세트의 과대적합 위험을 줄이는) 규제의 역할을 하는 것처럼 보였음.\n", "- 마지막으로 $\\alpha$가 훈련하는 동안 학습되는 **PReLU**parametric leaky ReLU도 비교함.
\n", "이 함수는 대규모 이미지 데이터셋에서는 ReLU보다 성능이 크게 앞섰지만, 소규모 데이터셋에서는 훈련 세트에 과대적합될 위험이 있음" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "
**그림 11-2 LeakyReLU**
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- 또 하나 중요한 함수는 2015년의 한 논문에서 제안한 **ELU**exponential linear unit라는 새로운 활성화 함수.
\n", "이 함수는 실험에서 다른 모든 ReLU 변종의 성능을 앞질렀음. 훈련 시간이 줄고 신경망의 테스트 세트 성능도 더 높았음.
\n", "이 함수의 정의는 [식 11-2]이고, 그 모습은 [그림 11-3]에 나와 있음." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "
**식 11-2 ELU 활성화 함수**
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "
**그림 11-3 ELU 활성화 함수**
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "이 함수는 몇 가지를 제외하고는 ReLU와 매우 비슷함.\n", "- 첫째, z<0일 때 음숫값이 들어오므로 활성화 함수의 평균 출력이 0에 더 가까워짐. 이는 앞서 이야기한 그래디언트 소실 문제를 완화해줌. 하이퍼파라미터 $\\alpha$는 z가 큰 음숫값일 때 ELU가 수렴할 값을 정의함. 보통 1로 설정하지만 필요하면 다른 하이퍼파라미터처럼 원하는 값으로 변경할 수 있음.\n", "- 둘째, z<0이어도 그래디언트가 0이 아니므로 죽은 뉴런을 만들지 않음.\n", "- 셋째, $\\alpha$=1일 때 이 함수는 z=0에서 급격히 변동하지 않고 z=0을 포함해 모든 구간에서 매끄러워 경사 하강법의 속도를 높여줌.
\n", "\n", "ELU 활성화 함수의 주요 단점은 ReLU나 그 변종들보다 (지수 함수를 사용하기 때문에) 계산이 느리다는 것.
\n", "훈련하는 동안에는 수렴 속도가 빠르기 때문에 상쇄되지만 테스트 시에는 ELU 신경망이 ReLU 신경망보다 느릴 것임." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "텐서플로는 신경망에 사용할 수 있는 elu() 함수를 제공함. 다음처럼 간단하게 dense() 함수를 호출할 때 activation 매개변수에 지정하기만 하면 됨." ] }, { "cell_type": "code", "execution_count": 48, "metadata": {}, "outputs": [], "source": [ "reset_graph()\n", "\n", "X = tf.placeholder(tf.float32, shape=(None, n_inputs), name=\"X\")" ] }, { "cell_type": "code", "execution_count": 49, "metadata": {}, "outputs": [], "source": [ "hidden1 = tf.layers.dense(X, n_hidden1, activation=tf.nn.elu, name=\"hidden1\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "LeakyReLU는 텐서플로가 기본으로 제공하지는 않지만, 다음처럼 간단히 만들어 사용할 수 있음." ] }, { "cell_type": "code", "execution_count": 50, "metadata": {}, "outputs": [], "source": [ "reset_graph()\n", "\n", "X = tf.placeholder(tf.float32, shape=(None, n_inputs), name=\"X\")" ] }, { "cell_type": "code", "execution_count": 51, "metadata": {}, "outputs": [], "source": [ "def leaky_relu(z, name=None):\n", " return tf.maximum(0.01 * z, z, name=name) # 두 개의 매개변수중 큰 값을 반환\n", "\n", "hidden1 = tf.layers.dense(X, n_hidden1, activation=leaky_relu, name=\"hidden1\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 11.1.3 배치 정규화" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- ELU(또는 다른 ReLU 변종)와 함께 He 초기화를 사용하면 훈련 초기 단계에서 그래디언트 소실이나 폭주 문제를 크게 감소시킬 수 있지만,
\n", "훈련하는 동안 다시 발생하지 않으리란 보장은 없음.\n", "- 2015년의 한 논문에서 그래디언트 소실과 폭주 문제를 해결하기 위한 **배치 정규화**Batch Normalization(BN) 기법을 제안함.
\n", "더 일반적으로는, 훈련하는 동안 이전 층의 파라미터가 변함에 따라 각 층에 들어오는 입력의 분포가 변화되는 문제임(**내부 공변량 변화**Internal Covariate Shift 문제라고 부름)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- 이 기법은 각 층에서 활성화 함수를 통과하기 전에 모델에 연산을 하나 추가함.
\n", "단순하게 입력 데이터의 평균을 0으로 만들고 정규화한 다음, 각 층에서 두 개의 새로운 파라미터로 결괏값의 스케일을 조정하고 이동시킴
\n", "(하나는 스케일 조정을 위해, 다른 하나는 이동을 위해 필요)
\n", "다시 말해 이 연산으로 모델이 층마다 입력 데이터의 최적 스케일과 평균을 학습함." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- 입력 데이터의 평균을 0으로 만들고 정규화하려면 알고리즘은 평균과 표준편차를 추정해야 함.
\n", "이를 위해 현재 미니배치에서 입력의 평균과 표준편차를 평가함(그래서 이름이 배치 정규화임)\n", "- [식 11-3]은 전체 알고리즘을 요약한 것." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "
**식 11-3 배치 정규화 알고리즘**
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- $\\mu$B는 미니배치 B에 대해 평가하여 관측한 평균\n", "- $\\sigma$B도 미니배치에 대해 평가하여 관측한 표준편차\n", "- mB는 미니배치에 있는 샘플 수\n", "- $\\hat{X}$(i)은 평균이 0이고 정규화된 입력\n", "- $\\gamma$는 층의 스케일 파라미터\n", "- $\\beta$는 층의 이동 (편향) 파라미터\n", "- ε은 분모가 0이 되는 것을 막기 위한 작은 숫자(전형적으로 10-5)임. 이를 **안전을 위한 항**smoothing term이라고 함.\n", "- z(i)은 BN 연산의 출력. 즉, 입력의 스케일을 조정하고 이동시킨 것." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- 테스트할 때는 평균과 표준편차를 계산할 미니배치가 없으니 전체 훈련 세트의 평균과 표준편차를 대신 사용.
\n", "이 값들은 훈련 과정에서 이동 평균moving average을 사용하여 효율적으로 계산되므로,
\n", "전체적으로 보면 배치 정규화된 층마다 $\\gamma$(스케일), $\\beta$(이동), $\\mu$(평균), $\\sigma$(표준편차) 네 개의 파라미터가 학습됨." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- 이 기법으로 논문에서 실험했던 모든 심층 신경망의 성능이 크게 향상.
\n", "tanh나 심지어 로지스틱 활성화 함수 같이 수렴되는 활성화 함수를 사용하더라도 그래디언트 소실 문제가 크게 감소
\n", "또한 네트워크가 가중치 초기화에 훨씬 덜 민감해짐. 그리고 훨씬 큰 학습률을 사용할 수 있어 학습 속도를 크게 개선해줌.
\n", "마지막으로, 배치 정규화는 규제와 같은 역할을 하여 다른 규제 기법의 필요성을 줄여줌." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- 그러나 배치 정규화는 모델의 복잡도를 키움. 더군다나 실행 시간 면에서도 손해임.
\n", "층마다 추가되는 계산이 신경망의 예측이 느려지게 함. 예측이 빨라야 한다면 배치 정규화를 사용하기 전에 ELU + He 초기화만으로 얼마나 잘 수행되는지 확인해보는 것이 좋음." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**NOTE_** 경사 하강법은 각 층마다 최적의 스케일과 이동을 위한 파라미터를 찾느라 훈련 초기에는 오히려 느려질 수 있음. 하지만 적절한 값을 찾고 나면 점점 빨라짐." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### 텐서플로를 사용해 배치 정규화 구현하기" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "텐서플로는 손쉽게 입력값을 중앙에 정렬하고 정규화해주는 tf.nn.batch_normalization() 함수를 제공하지만, 평균과 표준편차를 직접 계산해 이 함수의 매개변수로 전달해야 함.
\n", "그리고 스케일 조정과 이동을 위한 파라미터를 생성해야 함. 하지만 이는 편리한 방법은 아님.
\n", "대신 다음 코드처럼 이 모든 일을 처리해주는 tf.layers.batch_normalization() 함수를 사용하는 편이 좋음." ] }, { "cell_type": "code", "execution_count": 52, "metadata": {}, "outputs": [], "source": [ "reset_graph()\n", "\n", "# 입력과 출력 크기를 지정하고 은닉층의 뉴런 수를 설정\n", "n_inputs = 28 * 28\n", "n_hidden1 = 300\n", "n_hidden2 = 100\n", "n_outputs = 10\n", "\n", "X = tf.placeholder(tf.float32, shape=(None, n_inputs), name=\"X\")\n", "\n", "training = tf.placeholder_with_default(False, shape=(), name='training')\n", "\n", "hidden1 = tf.layers.dense(X, n_hidden1, name=\"hidden1\")\n", "bn1 = tf.layers.batch_normalization(hidden1, training=training, momentum=0.9)\n", "bn1_act = tf.nn.elu(bn1)\n", "\n", "hidden2 = tf.layers.dense(bn1_act, n_hidden2, name=\"hidden2\")\n", "bn2 = tf.layers.batch_normalization(hidden2, training=training, momentum=0.9)\n", "bn2_act = tf.nn.elu(bn2)\n", "\n", "logits_before_bn = tf.layers.dense(bn2_act, n_outputs, name=\"outputs\")\n", "logits = tf.layers.batch_normalization(logits_before_bn, training=training,\n", " momentum=0.9)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- training은 훈련하는 동안에는 True로 그 외에는 False로 설정. 이를 이용하여 tf.layers.batch_normalization() 함수가 (훈련할 때) 현재 미니배치의 평균과 표준편차를 사용할지 또는 (테스트할 때) 전체 훈련 세트에 대한 평균과 표준편차를 사용할지 지정함.\n", "- 그다음에는 완전 연결 층과 배치 정규화 층이 번갈아 나옴. tf.layers.dense()를 사용해 완전 연결 층을 생성함. 배치 정규화 층 이후에 활성화 함수를 적용하기 때문에
\n", "완전 연결 층에는 어떤 활성화 함수도 지정하지 않음. 그리고 training, momentum 매개변수와 함께 tf.layers.batch_normalization() 함수를 사용해 배치 정규화 층을 만듦\n", "- BN 알고리즘은 **지수 감소**exponential decay를 사용해 이동 평균을 계산함. 그래서 momentum 매개변수가 필요
\n", "새로운 값 v가 주어지면 이동 평균 $\\hat{v}$은 다음 식을 통해 갱신됨.
\n", "\n", " $\\hat{v}$ ← $\\hat{v}$ x momentum + v x (1-momentum)
\n", "\n", "적절한 모멘텀 값은 일반적으로 1에 가까움. 예를 들면 0.9, 0.99, 0.999(데이터셋이 크고 미니배치가 작을 경우 9를 더 넣어 1에 더 가깝게 함)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "이 코드는 같은 배치 정규화 매개변수가 계속 반복되기 때문에 코드 중복이 많음. 이런 중복을 피하기 위해 파이썬의 (표준 라이브러리에 포함된) functools 모듈 안에 있는
\n", "partial() 함수를 사용할 수 있음. 이 함수는 어떤 함수를 감싼 래퍼 함수를 생성하며 매개변수의 기본값을 지정할 수 있도록 도와줌.
\n", "이전 코드에서 신경망의 층을 만드는 코드를 다음과 같이 바꿀 수 있음." ] }, { "cell_type": "code", "execution_count": 53, "metadata": {}, "outputs": [], "source": [ "reset_graph()\n", "\n", "from functools import partial\n", "\n", "X = tf.placeholder(tf.float32, shape=(None, n_inputs), name=\"X\")\n", "\n", "training = tf.placeholder_with_default(False, shape=(), name='training')\n", "\n", "my_batch_norm_layer = partial(tf.layers.batch_normalization,\n", " training=training, momentum=0.9)\n", "\n", "hidden1 = tf.layers.dense(X, n_hidden1, name=\"hidden1\")\n", "bn1 = my_batch_norm_layer(hidden1)\n", "bn1_act = tf.nn.elu(bn1)\n", "hidden2 = tf.layers.dense(bn1_act, n_hidden2, name=\"hidden2\")\n", "bn2 = my_batch_norm_layer(hidden2)\n", "bn2_act = tf.nn.elu(bn2)\n", "logits_before_bn = tf.layers.dense(bn2_act, n_outputs, name=\"outputs\")\n", "logits = my_batch_norm_layer(logits_before_bn)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "데이터 로드" ] }, { "cell_type": "code", "execution_count": 54, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "\n", "(X_train, y_train), (X_test, y_test) = tf.keras.datasets.mnist.load_data()\n", "X_train = X_train.astype(np.float32).reshape(-1, 28*28) / 255.0\n", "X_test = X_test.astype(np.float32).reshape(-1, 28*28) / 255.0\n", "y_train = y_train.astype(np.int32)\n", "y_test = y_test.astype(np.int32)\n", "X_valid, X_train = X_train[:5000], X_train[5000:]\n", "y_valid, y_train = y_train[:5000], y_train[5000:]" ] }, { "cell_type": "code", "execution_count": 55, "metadata": {}, "outputs": [], "source": [ "def shuffle_batch(X, y, batch_size):\n", " rnd_idx = np.random.permutation(len(X))\n", " n_batches = len(X) // batch_size\n", " for batch_idx in np.array_split(rnd_idx, n_batches):\n", " X_batch, y_batch = X[batch_idx], y[batch_idx]\n", " yield X_batch, y_batch" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "구성 단계의 나머지 부분은 비용 함수를 정의하고 옵티마이저를 생성해서 비용 함수를 최소화하도록 함수를 호출한 다음, 평가 연산을 정의하고 Saver 객체를 만드는 것 등임." ] }, { "cell_type": "code", "execution_count": 56, "metadata": {}, "outputs": [], "source": [ "reset_graph()\n", "\n", "batch_norm_momentum = 0.9\n", "\n", "X = tf.placeholder(tf.float32, shape=(None, n_inputs), name=\"X\")\n", "y = tf.placeholder(tf.int32, shape=(None), name=\"y\")\n", "training = tf.placeholder_with_default(False, shape=(), name='training')\n", "\n", "with tf.name_scope(\"dnn\"):\n", " he_init = tf.variance_scaling_initializer()\n", "\n", " my_batch_norm_layer = partial(\n", " tf.layers.batch_normalization,\n", " training=training,\n", " momentum=batch_norm_momentum)\n", "\n", " my_dense_layer = partial(\n", " tf.layers.dense,\n", " kernel_initializer=he_init)\n", "\n", " hidden1 = my_dense_layer(X, n_hidden1, name=\"hidden1\")\n", " bn1 = tf.nn.elu(my_batch_norm_layer(hidden1))\n", " hidden2 = my_dense_layer(bn1, n_hidden2, name=\"hidden2\")\n", " bn2 = tf.nn.elu(my_batch_norm_layer(hidden2))\n", " logits_before_bn = my_dense_layer(bn2, n_outputs, name=\"outputs\")\n", " logits = my_batch_norm_layer(logits_before_bn)\n", "\n", "with tf.name_scope(\"loss\"):\n", " xentropy = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=y, logits=logits)\n", " loss = tf.reduce_mean(xentropy, name=\"loss\")\n", "\n", "learning_rate = 0.01\n", " \n", "with tf.name_scope(\"train\"):\n", " optimizer = tf.train.GradientDescentOptimizer(learning_rate)\n", " training_op = optimizer.minimize(loss)\n", "\n", "with tf.name_scope(\"eval\"):\n", " correct = tf.nn.in_top_k(logits, y, 1)\n", " accuracy = tf.reduce_mean(tf.cast(correct, tf.float32), name=\"accuracy\")\n", " \n", "init = tf.global_variables_initializer()\n", "saver = tf.train.Saver()" ] }, { "cell_type": "code", "execution_count": 57, "metadata": {}, "outputs": [], "source": [ "n_epochs = 20\n", "batch_size = 200" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "실행단계\n", "- 첫째, 훈련하는 동안에는 batch_normalization() 함수에 의존하는 어떤 연산을 수행할 때마다 training 플레이스홀더를 True로 설정\n", "- 둘째, batch_normalization() 함수는 이동 평균을 갱신하기 위해 매 훈련 단계에서 평가할 몇 개의 연산을 만듦
\n", "이 연산은 자동으로 UPDATE_OPS 컬렉션에 추가되므로 우리가 할 일은 컬렉션에서 이 연산들을 뽑아내어 훈련이 반복될 때마다 실행해주면 됨." ] }, { "cell_type": "code", "execution_count": 58, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0 검증 세트 정확도: 0.8952\n", "1 검증 세트 정확도: 0.9202\n", "2 검증 세트 정확도: 0.9318\n", "3 검증 세트 정확도: 0.9422\n", "4 검증 세트 정확도: 0.9468\n", "5 검증 세트 정확도: 0.954\n", "6 검증 세트 정확도: 0.9568\n", "7 검증 세트 정확도: 0.96\n", "8 검증 세트 정확도: 0.962\n", "9 검증 세트 정확도: 0.9638\n", "10 검증 세트 정확도: 0.9662\n", "11 검증 세트 정확도: 0.9682\n", "12 검증 세트 정확도: 0.9672\n", "13 검증 세트 정확도: 0.9696\n", "14 검증 세트 정확도: 0.9706\n", "15 검증 세트 정확도: 0.9704\n", "16 검증 세트 정확도: 0.9718\n", "17 검증 세트 정확도: 0.9726\n", "18 검증 세트 정확도: 0.9738\n", "19 검증 세트 정확도: 0.9742\n" ] } ], "source": [ "extra_update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)\n", "\n", "with tf.Session() as sess:\n", " init.run()\n", " for epoch in range(n_epochs):\n", " for X_batch, y_batch in shuffle_batch(X_train, y_train, batch_size):\n", " sess.run([training_op, extra_update_ops],\n", " feed_dict={training: True, X: X_batch, y: y_batch})\n", " accuracy_val = accuracy.eval(feed_dict={X: X_valid, y: y_valid})\n", " print(epoch, \"검증 세트 정확도:\", accuracy_val)\n", "\n", " save_path = saver.save(sess, \"./my_model_final.ckpt\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 11.1.4 그래디언트 클리핑" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- **그래디언트 클리핑**Gradient Clipping : 그래디언트 폭주 문제를 줄이는 쉬운 방법으로, 역전파될 때 일정 임곗값을 넘어서지 못하게 그래디언트를 그냥 단순히 잘라내는 것
\n", "(순환 신경망에서 일반적으로 널리 사용됨)
\n", "일반적으로 배치 정규화를 선호" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "MNIST를 위한 간단한 신경망을 만들고 그래디언트 클리핑을 적용(시작 부분은 이전과 동일)" ] }, { "cell_type": "code", "execution_count": 59, "metadata": {}, "outputs": [], "source": [ "reset_graph()\n", "\n", "n_inputs = 28 * 28 # MNIST\n", "n_hidden1 = 300\n", "n_hidden2 = 50\n", "n_hidden3 = 50\n", "n_hidden4 = 50\n", "n_hidden5 = 50\n", "n_outputs = 10\n", "\n", "X = tf.placeholder(tf.float32, shape=(None, n_inputs), name=\"X\")\n", "y = tf.placeholder(tf.int32, shape=(None), name=\"y\")\n", "\n", "with tf.name_scope(\"dnn\"):\n", " hidden1 = tf.layers.dense(X, n_hidden1, activation=tf.nn.relu, name=\"hidden1\")\n", " hidden2 = tf.layers.dense(hidden1, n_hidden2, activation=tf.nn.relu, name=\"hidden2\")\n", " hidden3 = tf.layers.dense(hidden2, n_hidden3, activation=tf.nn.relu, name=\"hidden3\")\n", " hidden4 = tf.layers.dense(hidden3, n_hidden4, activation=tf.nn.relu, name=\"hidden4\")\n", " hidden5 = tf.layers.dense(hidden4, n_hidden5, activation=tf.nn.relu, name=\"hidden5\")\n", " logits = tf.layers.dense(hidden5, n_outputs, name=\"outputs\")\n", "\n", "with tf.name_scope(\"loss\"):\n", " xentropy = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=y, logits=logits)\n", " loss = tf.reduce_mean(xentropy, name=\"loss\")\n", " \n", "learning_rate = 0.01" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "이제 그래디언트 클리핑을 적용
\n", "- 옵티마이저의 compute_gradients() 메서드를 먼저 호출\n", "- 그다음에 clip_by_value() 함수를 사용해 그래디언트를 클리핑하는 연산을 생성\n", "- 마지막으로 옵티마이저의 apply_gradients() 메서드를 사용해 클리핑된 그래디언트를 적용하는 연산을 만듦." ] }, { "cell_type": "code", "execution_count": 60, "metadata": {}, "outputs": [], "source": [ "threshold = 1.0\n", "\n", "optimizer = tf.train.GradientDescentOptimizer(learning_rate)\n", "grads_and_vars = optimizer.compute_gradients(loss)\n", "capped_gvs = [(tf.clip_by_value(grad, -threshold, threshold), var)\n", " for grad, var in grads_and_vars]\n", "training_op = optimizer.apply_gradients(capped_gvs)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "그러고 나서 이전과 마찬가지로 매 훈련 단계마다 training_op을 실행.
\n", "이 코드는 그래디언트를 계산하고 그것을 -1.0과 1.0 사이로 클리핑해서 적용함." ] }, { "cell_type": "code", "execution_count": 61, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0 검증 세트 정확도: 0.288\n", "1 검증 세트 정확도: 0.7938\n", "2 검증 세트 정확도: 0.8796\n", "3 검증 세트 정확도: 0.9058\n", "4 검증 세트 정확도: 0.9164\n", "5 검증 세트 정확도: 0.9218\n", "6 검증 세트 정확도: 0.9292\n", "7 검증 세트 정확도: 0.9358\n", "8 검증 세트 정확도: 0.938\n", "9 검증 세트 정확도: 0.9416\n", "10 검증 세트 정확도: 0.9456\n", "11 검증 세트 정확도: 0.9472\n", "12 검증 세트 정확도: 0.9476\n", "13 검증 세트 정확도: 0.953\n", "14 검증 세트 정확도: 0.9566\n", "15 검증 세트 정확도: 0.9566\n", "16 검증 세트 정확도: 0.9578\n", "17 검증 세트 정확도: 0.9586\n", "18 검증 세트 정확도: 0.9624\n", "19 검증 세트 정확도: 0.9614\n" ] } ], "source": [ "with tf.name_scope(\"eval\"):\n", " correct = tf.nn.in_top_k(logits, y, 1)\n", " accuracy = tf.reduce_mean(tf.cast(correct, tf.float32), name=\"accuracy\")\n", "\n", "init = tf.global_variables_initializer()\n", "saver = tf.train.Saver()\n", "\n", "n_epochs = 20\n", "batch_size = 200\n", "\n", "with tf.Session() as sess:\n", " init.run()\n", " for epoch in range(n_epochs):\n", " for X_batch, y_batch in shuffle_batch(X_train, y_train, batch_size):\n", " sess.run(training_op, feed_dict={X: X_batch, y: y_batch})\n", " accuracy_val = accuracy.eval(feed_dict={X: X_valid, y: y_valid})\n", " print(epoch, \"검증 세트 정확도:\", accuracy_val)\n", "\n", " save_path = saver.save(sess, \"./my_model_final.ckpt\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "## 11.2 미리 훈련된 층 재사용하기" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- **전이 학습**transfer learning : 해결하려는 것과 비슷한 유형의 문제를 처리한 신경망이 이미 있는지 찾아보고 그런 다음 그 신경망의 하위층을 재사용하는 것.
\n", "이 방법은 훈련 속도를 크게 높여줄 뿐만 아니라 필요한 훈련 데이터도 훨씬 적음." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "예를 들어 동물, 식물, 자동차, 생활용품을 포함하여 100개의 카테고리로 구분된 이미지를 분류하도록 훈련시킨 DNN을 가지고 있다고 가정.
\n", "그리고 이제 구체적인 자동차의 종류를 분류하는 DNN을 훈련시키려 할 때, 이런 작업들은 비슷한 점이 많으므로 첫 번째 신경망의 일부를 재사용(그림 11-4)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "
**그림 11-4 미리 훈련시킨 층 재사용하기**
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**NOTE_** 만약 원래 문제에서 사용한 것과 크기가 다른 이미지를 입력으로 사용한다면 원본 모델에 맞는 크기로 변경하는 전처리 단계를 추가해야 함.
\n", "일반적으로 전이 학습은 입력이 비슷한 저수준 특성을 가질 때 잘 작동함." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 11.2.1 텐서플로 모델 재사용하기" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- 원본 모델이 텐서플로를 사용해 훈련되었다면 간단하게 바로 복원해서 새로운 작업에 훈련시킬 수 있음.\n", "- import_meta_graph() 함수를 사용하여 기본 그래프에 연산을 적재할 수 있음.
\n", "이 함수는 Saver 객체를 반환하는데, 나중에 저장된 모델 파라미터를 불러올 때 사용함.
\n", "기본적으로 Saver 객체는 .meta 확장자를 가진 파일에 그래프 구조를 저장하므로 이 파일을 로드해야 함." ] }, { "cell_type": "code", "execution_count": 62, "metadata": {}, "outputs": [], "source": [ "reset_graph()" ] }, { "cell_type": "code", "execution_count": 63, "metadata": {}, "outputs": [], "source": [ "saver = tf.train.import_meta_graph(\"./my_model_final.ckpt.meta\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- 그다음에는 훈련 대상인 연산과 텐서를 직접 지정. 이를 위해 그래프의 get_operation_by_name()과 get_tensor_by_name() 메서드를 사용.
\n", "텐서 이름은 연산 이름 뒤에 :0을 붙임(두 번째 출력일 땐 :1, 세 번째일 땐 :2)." ] }, { "cell_type": "code", "execution_count": 66, "metadata": {}, "outputs": [], "source": [ "X = tf.get_default_graph().get_tensor_by_name(\"X:0\")\n", "y = tf.get_default_graph().get_tensor_by_name(\"y:0\")\n", "\n", "accuracy = tf.get_default_graph().get_tensor_by_name(\"eval/accuracy:0\")\n", "\n", "training_op = tf.get_default_graph().get_operation_by_name(\"GradientDescent\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- 미리 훈련된 모델이 잘 문서화되어 있지 않으면 필요한 연산의 이름을 찾기 위해 그래프를 뒤져야 함.
\n", "이런 경우 텐서보드를 사용하여 그래프를 탐색하거나 get_operations() 메서드를 사용해 모든 연산의 리스트를 볼 수 있음." ] }, { "cell_type": "code", "execution_count": 67, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "X\n", "y\n", "hidden1/kernel/Initializer/random_uniform/shape\n", "hidden1/kernel/Initializer/random_uniform/min\n", "hidden1/kernel/Initializer/random_uniform/max\n", "hidden1/kernel/Initializer/random_uniform/RandomUniform\n", "hidden1/kernel/Initializer/random_uniform/sub\n", "hidden1/kernel/Initializer/random_uniform/mul\n", "hidden1/kernel/Initializer/random_uniform\n", "hidden1/kernel\n", "hidden1/kernel/Assign\n", "hidden1/kernel/read\n", "hidden1/bias/Initializer/zeros\n", "hidden1/bias\n", "hidden1/bias/Assign\n", "hidden1/bias/read\n", "dnn/hidden1/MatMul\n", "dnn/hidden1/BiasAdd\n", "dnn/hidden1/Relu\n", "hidden2/kernel/Initializer/random_uniform/shape\n", "hidden2/kernel/Initializer/random_uniform/min\n", "hidden2/kernel/Initializer/random_uniform/max\n", "hidden2/kernel/Initializer/random_uniform/RandomUniform\n", "hidden2/kernel/Initializer/random_uniform/sub\n", "hidden2/kernel/Initializer/random_uniform/mul\n", "hidden2/kernel/Initializer/random_uniform\n", "hidden2/kernel\n", "hidden2/kernel/Assign\n", "hidden2/kernel/read\n", "hidden2/bias/Initializer/zeros\n", "hidden2/bias\n", "hidden2/bias/Assign\n", "hidden2/bias/read\n", "dnn/hidden2/MatMul\n", "dnn/hidden2/BiasAdd\n", "dnn/hidden2/Relu\n", "hidden3/kernel/Initializer/random_uniform/shape\n", "hidden3/kernel/Initializer/random_uniform/min\n", "hidden3/kernel/Initializer/random_uniform/max\n", "hidden3/kernel/Initializer/random_uniform/RandomUniform\n", "hidden3/kernel/Initializer/random_uniform/sub\n", "hidden3/kernel/Initializer/random_uniform/mul\n", "hidden3/kernel/Initializer/random_uniform\n", "hidden3/kernel\n", "hidden3/kernel/Assign\n", "hidden3/kernel/read\n", "hidden3/bias/Initializer/zeros\n", "hidden3/bias\n", "hidden3/bias/Assign\n", "hidden3/bias/read\n", "dnn/hidden3/MatMul\n", "dnn/hidden3/BiasAdd\n", "dnn/hidden3/Relu\n", "hidden4/kernel/Initializer/random_uniform/shape\n", "hidden4/kernel/Initializer/random_uniform/min\n", "hidden4/kernel/Initializer/random_uniform/max\n", "hidden4/kernel/Initializer/random_uniform/RandomUniform\n", "hidden4/kernel/Initializer/random_uniform/sub\n", "hidden4/kernel/Initializer/random_uniform/mul\n", "hidden4/kernel/Initializer/random_uniform\n", "hidden4/kernel\n", "hidden4/kernel/Assign\n", "hidden4/kernel/read\n", "hidden4/bias/Initializer/zeros\n", "hidden4/bias\n", "hidden4/bias/Assign\n", "hidden4/bias/read\n", "dnn/hidden4/MatMul\n", "dnn/hidden4/BiasAdd\n", "dnn/hidden4/Relu\n", "hidden5/kernel/Initializer/random_uniform/shape\n", "hidden5/kernel/Initializer/random_uniform/min\n", "hidden5/kernel/Initializer/random_uniform/max\n", "hidden5/kernel/Initializer/random_uniform/RandomUniform\n", "hidden5/kernel/Initializer/random_uniform/sub\n", "hidden5/kernel/Initializer/random_uniform/mul\n", "hidden5/kernel/Initializer/random_uniform\n", "hidden5/kernel\n", "hidden5/kernel/Assign\n", "hidden5/kernel/read\n", "hidden5/bias/Initializer/zeros\n", "hidden5/bias\n", "hidden5/bias/Assign\n", "hidden5/bias/read\n", "dnn/hidden5/MatMul\n", "dnn/hidden5/BiasAdd\n", "dnn/hidden5/Relu\n", "outputs/kernel/Initializer/random_uniform/shape\n", "outputs/kernel/Initializer/random_uniform/min\n", "outputs/kernel/Initializer/random_uniform/max\n", "outputs/kernel/Initializer/random_uniform/RandomUniform\n", "outputs/kernel/Initializer/random_uniform/sub\n", "outputs/kernel/Initializer/random_uniform/mul\n", "outputs/kernel/Initializer/random_uniform\n", "outputs/kernel\n", "outputs/kernel/Assign\n", "outputs/kernel/read\n", "outputs/bias/Initializer/zeros\n", "outputs/bias\n", "outputs/bias/Assign\n", "outputs/bias/read\n", "dnn/outputs/MatMul\n", "dnn/outputs/BiasAdd\n", "loss/SparseSoftmaxCrossEntropyWithLogits/Shape\n", "loss/SparseSoftmaxCrossEntropyWithLogits/SparseSoftmaxCrossEntropyWithLogits\n", "loss/Const\n", "loss/loss\n", "gradients/Shape\n", "gradients/grad_ys_0\n", "gradients/Fill\n", "gradients/loss/loss_grad/Reshape/shape\n", "gradients/loss/loss_grad/Reshape\n", "gradients/loss/loss_grad/Shape\n", "gradients/loss/loss_grad/Tile\n", "gradients/loss/loss_grad/Shape_1\n", "gradients/loss/loss_grad/Shape_2\n", "gradients/loss/loss_grad/Const\n", "gradients/loss/loss_grad/Prod\n", "gradients/loss/loss_grad/Const_1\n", "gradients/loss/loss_grad/Prod_1\n", "gradients/loss/loss_grad/Maximum/y\n", "gradients/loss/loss_grad/Maximum\n", "gradients/loss/loss_grad/floordiv\n", "gradients/loss/loss_grad/Cast\n", "gradients/loss/loss_grad/truediv\n", "gradients/zeros_like\n", "gradients/loss/SparseSoftmaxCrossEntropyWithLogits/SparseSoftmaxCrossEntropyWithLogits_grad/PreventGradient\n", "gradients/loss/SparseSoftmaxCrossEntropyWithLogits/SparseSoftmaxCrossEntropyWithLogits_grad/ExpandDims/dim\n", "gradients/loss/SparseSoftmaxCrossEntropyWithLogits/SparseSoftmaxCrossEntropyWithLogits_grad/ExpandDims\n", "gradients/loss/SparseSoftmaxCrossEntropyWithLogits/SparseSoftmaxCrossEntropyWithLogits_grad/mul\n", "gradients/dnn/outputs/BiasAdd_grad/BiasAddGrad\n", "gradients/dnn/outputs/BiasAdd_grad/tuple/group_deps\n", "gradients/dnn/outputs/BiasAdd_grad/tuple/control_dependency\n", "gradients/dnn/outputs/BiasAdd_grad/tuple/control_dependency_1\n", "gradients/dnn/outputs/MatMul_grad/MatMul\n", "gradients/dnn/outputs/MatMul_grad/MatMul_1\n", "gradients/dnn/outputs/MatMul_grad/tuple/group_deps\n", "gradients/dnn/outputs/MatMul_grad/tuple/control_dependency\n", "gradients/dnn/outputs/MatMul_grad/tuple/control_dependency_1\n", "gradients/dnn/hidden5/Relu_grad/ReluGrad\n", "gradients/dnn/hidden5/BiasAdd_grad/BiasAddGrad\n", "gradients/dnn/hidden5/BiasAdd_grad/tuple/group_deps\n", "gradients/dnn/hidden5/BiasAdd_grad/tuple/control_dependency\n", "gradients/dnn/hidden5/BiasAdd_grad/tuple/control_dependency_1\n", "gradients/dnn/hidden5/MatMul_grad/MatMul\n", "gradients/dnn/hidden5/MatMul_grad/MatMul_1\n", "gradients/dnn/hidden5/MatMul_grad/tuple/group_deps\n", "gradients/dnn/hidden5/MatMul_grad/tuple/control_dependency\n", "gradients/dnn/hidden5/MatMul_grad/tuple/control_dependency_1\n", "gradients/dnn/hidden4/Relu_grad/ReluGrad\n", "gradients/dnn/hidden4/BiasAdd_grad/BiasAddGrad\n", "gradients/dnn/hidden4/BiasAdd_grad/tuple/group_deps\n", "gradients/dnn/hidden4/BiasAdd_grad/tuple/control_dependency\n", "gradients/dnn/hidden4/BiasAdd_grad/tuple/control_dependency_1\n", "gradients/dnn/hidden4/MatMul_grad/MatMul\n", "gradients/dnn/hidden4/MatMul_grad/MatMul_1\n", "gradients/dnn/hidden4/MatMul_grad/tuple/group_deps\n", "gradients/dnn/hidden4/MatMul_grad/tuple/control_dependency\n", "gradients/dnn/hidden4/MatMul_grad/tuple/control_dependency_1\n", "gradients/dnn/hidden3/Relu_grad/ReluGrad\n", "gradients/dnn/hidden3/BiasAdd_grad/BiasAddGrad\n", "gradients/dnn/hidden3/BiasAdd_grad/tuple/group_deps\n", "gradients/dnn/hidden3/BiasAdd_grad/tuple/control_dependency\n", "gradients/dnn/hidden3/BiasAdd_grad/tuple/control_dependency_1\n", "gradients/dnn/hidden3/MatMul_grad/MatMul\n", "gradients/dnn/hidden3/MatMul_grad/MatMul_1\n", "gradients/dnn/hidden3/MatMul_grad/tuple/group_deps\n", "gradients/dnn/hidden3/MatMul_grad/tuple/control_dependency\n", "gradients/dnn/hidden3/MatMul_grad/tuple/control_dependency_1\n", "gradients/dnn/hidden2/Relu_grad/ReluGrad\n", "gradients/dnn/hidden2/BiasAdd_grad/BiasAddGrad\n", "gradients/dnn/hidden2/BiasAdd_grad/tuple/group_deps\n", "gradients/dnn/hidden2/BiasAdd_grad/tuple/control_dependency\n", "gradients/dnn/hidden2/BiasAdd_grad/tuple/control_dependency_1\n", "gradients/dnn/hidden2/MatMul_grad/MatMul\n", "gradients/dnn/hidden2/MatMul_grad/MatMul_1\n", "gradients/dnn/hidden2/MatMul_grad/tuple/group_deps\n", "gradients/dnn/hidden2/MatMul_grad/tuple/control_dependency\n", "gradients/dnn/hidden2/MatMul_grad/tuple/control_dependency_1\n", "gradients/dnn/hidden1/Relu_grad/ReluGrad\n", "gradients/dnn/hidden1/BiasAdd_grad/BiasAddGrad\n", "gradients/dnn/hidden1/BiasAdd_grad/tuple/group_deps\n", "gradients/dnn/hidden1/BiasAdd_grad/tuple/control_dependency\n", "gradients/dnn/hidden1/BiasAdd_grad/tuple/control_dependency_1\n", "gradients/dnn/hidden1/MatMul_grad/MatMul\n", "gradients/dnn/hidden1/MatMul_grad/MatMul_1\n", "gradients/dnn/hidden1/MatMul_grad/tuple/group_deps\n", "gradients/dnn/hidden1/MatMul_grad/tuple/control_dependency\n", "gradients/dnn/hidden1/MatMul_grad/tuple/control_dependency_1\n", "clip_by_value/Minimum/y\n", "clip_by_value/Minimum\n", "clip_by_value/y\n", "clip_by_value\n", "clip_by_value_1/Minimum/y\n", "clip_by_value_1/Minimum\n", "clip_by_value_1/y\n", "clip_by_value_1\n", "clip_by_value_2/Minimum/y\n", "clip_by_value_2/Minimum\n", "clip_by_value_2/y\n", "clip_by_value_2\n", "clip_by_value_3/Minimum/y\n", "clip_by_value_3/Minimum\n", "clip_by_value_3/y\n", "clip_by_value_3\n", "clip_by_value_4/Minimum/y\n", "clip_by_value_4/Minimum\n", "clip_by_value_4/y\n", "clip_by_value_4\n", "clip_by_value_5/Minimum/y\n", "clip_by_value_5/Minimum\n", "clip_by_value_5/y\n", "clip_by_value_5\n", "clip_by_value_6/Minimum/y\n", "clip_by_value_6/Minimum\n", "clip_by_value_6/y\n", "clip_by_value_6\n", "clip_by_value_7/Minimum/y\n", "clip_by_value_7/Minimum\n", "clip_by_value_7/y\n", "clip_by_value_7\n", "clip_by_value_8/Minimum/y\n", "clip_by_value_8/Minimum\n", "clip_by_value_8/y\n", "clip_by_value_8\n", "clip_by_value_9/Minimum/y\n", "clip_by_value_9/Minimum\n", "clip_by_value_9/y\n", "clip_by_value_9\n", "clip_by_value_10/Minimum/y\n", "clip_by_value_10/Minimum\n", "clip_by_value_10/y\n", "clip_by_value_10\n", "clip_by_value_11/Minimum/y\n", "clip_by_value_11/Minimum\n", "clip_by_value_11/y\n", "clip_by_value_11\n", "GradientDescent/learning_rate\n", "GradientDescent/update_hidden1/kernel/ApplyGradientDescent\n", "GradientDescent/update_hidden1/bias/ApplyGradientDescent\n", "GradientDescent/update_hidden2/kernel/ApplyGradientDescent\n", "GradientDescent/update_hidden2/bias/ApplyGradientDescent\n", "GradientDescent/update_hidden3/kernel/ApplyGradientDescent\n", "GradientDescent/update_hidden3/bias/ApplyGradientDescent\n", "GradientDescent/update_hidden4/kernel/ApplyGradientDescent\n", "GradientDescent/update_hidden4/bias/ApplyGradientDescent\n", "GradientDescent/update_hidden5/kernel/ApplyGradientDescent\n", "GradientDescent/update_hidden5/bias/ApplyGradientDescent\n", "GradientDescent/update_outputs/kernel/ApplyGradientDescent\n", "GradientDescent/update_outputs/bias/ApplyGradientDescent\n", "GradientDescent\n", "eval/in_top_k/InTopKV2/k\n", "eval/in_top_k/InTopKV2\n", "eval/Cast\n", "eval/Const\n", "eval/accuracy\n", "init\n", "save/Const\n", "save/SaveV2/tensor_names\n", "save/SaveV2/shape_and_slices\n", "save/SaveV2\n", "save/control_dependency\n", "save/RestoreV2/tensor_names\n", "save/RestoreV2/shape_and_slices\n", "save/RestoreV2\n", "save/Assign\n", "save/Assign_1\n", "save/Assign_2\n", "save/Assign_3\n", "save/Assign_4\n", "save/Assign_5\n", "save/Assign_6\n", "save/Assign_7\n", "save/Assign_8\n", "save/Assign_9\n", "save/Assign_10\n", "save/Assign_11\n", "save/restore_all\n" ] } ], "source": [ "for op in tf.get_default_graph().get_operations():\n", " print(op.name)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- 원본 모델을 직접 만들었다면 다른 사람들이 모델을 쉽게 재사용할 수 있도록 연산에 명확한 이름을 사용하고 문서화해놓아야 함.
\n", "또 다른 방법은 다른 사람을 위해 중요한 연산들을 모아놓은 컬렉션을 만드는 것." ] }, { "cell_type": "code", "execution_count": 68, "metadata": {}, "outputs": [], "source": [ "for op in (X, y, accuracy, training_op):\n", " tf.add_to_collection(\"my_important_ops\", op)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- 다른 사람들이 모델을 재사용할 때는 다음과 같이 간단히 쓰면 됨." ] }, { "cell_type": "code", "execution_count": 69, "metadata": {}, "outputs": [], "source": [ "X, y, accuracy, training_op = tf.get_collection(\"my_important_ops\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- 이제 세션을 시작하고 Saver 객체를 사용해 모델의 상태를 복원하고 나만의 데이터를 가지고 훈련을 계속할 수 있음." ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "INFO:tensorflow:Restoring parameters from ./my_model_final.ckpt\n", "0 검증 세트 정확도: 0.9726\n", "1 검증 세트 정확도: 0.973\n", "2 검증 세트 정확도: 0.9748\n", "3 검증 세트 정확도: 0.976\n", "4 검증 세트 정확도: 0.9752\n", "5 검증 세트 정확도: 0.9762\n", "6 검증 세트 정확도: 0.975\n", "7 검증 세트 정확도: 0.9772\n", "8 검증 세트 정확도: 0.978\n", "9 검증 세트 정확도: 0.9766\n", "10 검증 세트 정확도: 0.9776\n", "11 검증 세트 정확도: 0.9764\n", "12 검증 세트 정확도: 0.9776\n", "13 검증 세트 정확도: 0.9778\n", "14 검증 세트 정확도: 0.9772\n", "15 검증 세트 정확도: 0.9778\n", "16 검증 세트 정확도: 0.9782\n", "17 검증 세트 정확도: 0.9784\n", "18 검증 세트 정확도: 0.979\n", "19 검증 세트 정확도: 0.9786\n" ] } ], "source": [ "with tf.Session() as sess:\n", " saver.restore(sess, \"./my_model_final.ckpt\")\n", "\n", " # 나만의 데이터로 모델 훈련하기\n", " for epoch in range(n_epochs):\n", " for X_batch, y_batch in shuffle_batch(X_train, y_train, batch_size):\n", " sess.run(training_op, feed_dict={X: X_batch, y: y_batch})\n", " accuracy_val = accuracy.eval(feed_dict={X: X_valid, y: y_valid})\n", " print(epoch, \"검증 세트 정확도:\", accuracy_val)\n", "\n", " save_path = saver.save(sess, \"./my_new_model_final.ckpt\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- 미리 훈련된 그래프의 파이썬 코드에 접근할 수 있다면 필요한 부분만 재사용하고 나머지는 버리면 됨.
\n", "그러나 이경우에는 훈련된 모델을 복원하는 Saver 객체가 필요하고 새로운 모델을 저장하는 또 다른 Saver 객체가 있어야 함.
\n", "\n", "다음 코드는 은닉층 1, 2, 3만 복원" ] }, { "cell_type": "code", "execution_count": 73, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "INFO:tensorflow:Restoring parameters from ./my_model_final.ckpt\n", "0 검증 세트 정확도: 0.911\n", "1 검증 세트 정확도: 0.9358\n", "2 검증 세트 정확도: 0.944\n", "3 검증 세트 정확도: 0.9496\n", "4 검증 세트 정확도: 0.9538\n", "5 검증 세트 정확도: 0.9568\n", "6 검증 세트 정확도: 0.9578\n", "7 검증 세트 정확도: 0.9588\n", "8 검증 세트 정확도: 0.9582\n", "9 검증 세트 정확도: 0.9624\n", "10 검증 세트 정확도: 0.9628\n", "11 검증 세트 정확도: 0.964\n", "12 검증 세트 정확도: 0.9664\n", "13 검증 세트 정확도: 0.9656\n", "14 검증 세트 정확도: 0.967\n", "15 검증 세트 정확도: 0.9654\n", "16 검증 세트 정확도: 0.969\n", "17 검증 세트 정확도: 0.9684\n", "18 검증 세트 정확도: 0.969\n", "19 검증 세트 정확도: 0.9692\n" ] } ], "source": [ "tf.reset_default_graph()\n", "\n", "# 이전과 같은 은닉층 1-3을 가진 새로운 모델을 만듦\n", "n_inputs = 28 * 28 # MNIST\n", "n_hidden1 = 300 # 재사용\n", "n_hidden2 = 50 # 재사용\n", "n_hidden3 = 50 # 재사용\n", "n_hidden4 = 20 # 새로 만듦!\n", "n_outputs = 10 # 새로 만듦!\n", "\n", "X = tf.placeholder(tf.float32, shape=(None, n_inputs), name=\"X\")\n", "y = tf.placeholder(tf.int32, shape=(None), name=\"y\")\n", "\n", "with tf.name_scope(\"dnn\"):\n", " hidden1 = tf.layers.dense(X, n_hidden1, activation=tf.nn.relu, name=\"hidden1\") # 재사용\n", " hidden2 = tf.layers.dense(hidden1, n_hidden2, activation=tf.nn.relu, name=\"hidden2\") # 재사용\n", " hidden3 = tf.layers.dense(hidden2, n_hidden3, activation=tf.nn.relu, name=\"hidden3\") # 재사용\n", " hidden4 = tf.layers.dense(hidden3, n_hidden4, activation=tf.nn.relu, name=\"hidden4\") # 새로 만듦!\n", " logits = tf.layers.dense(hidden4, n_outputs, name=\"outputs\") # 새로 만듦!\n", "\n", "with tf.name_scope(\"loss\"):\n", " xentropy = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=y, logits=logits)\n", " loss = tf.reduce_mean(xentropy, name=\"loss\")\n", "\n", "with tf.name_scope(\"eval\"):\n", " correct = tf.nn.in_top_k(logits, y, 1)\n", " accuracy = tf.reduce_mean(tf.cast(correct, tf.float32), name=\"accuracy\")\n", "\n", "with tf.name_scope(\"train\"):\n", " optimizer = tf.train.GradientDescentOptimizer(learning_rate)\n", " training_op = optimizer.minimize(loss)\n", " \n", "\n", "reuse_vars = tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES,\n", " scope=\"hidden[123]\") # 정규표현식, 은닉층 1~3 모든 변수 목록을 얻음\n", "restore_saver = tf.train.Saver(reuse_vars) # 1-3층 복원시킬 Saver 객체 만듦\n", "\n", "init = tf.global_variables_initializer() # 새 변수, 구 변수 모두 초기화하는 연산을 생성\n", "saver = tf.train.Saver() # 새로운 모델 저장할 두 번째 Saver 객체 생성\n", "\n", "with tf.Session() as sess:\n", " init.run() # 모든 변수 초기화\n", " restore_saver.restore(sess, \"./my_model_final.ckpt\") # 원본 모델의 은닉층 1~3까지의 변수를 복원\n", "\n", " for epoch in range(n_epochs): # 새로운 모델 훈련시키고 저장\n", " for X_batch, y_batch in shuffle_batch(X_train, y_train, batch_size): \n", " sess.run(training_op, feed_dict={X: X_batch, y: y_batch}) \n", " accuracy_val = accuracy.eval(feed_dict={X: X_valid, y: y_valid}) \n", " print(epoch, \"검증 세트 정확도:\", accuracy_val) \n", "\n", " save_path = saver.save(sess, \"./my_new_model_final.ckpt\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 11.2.2 다른 프레임워크의 모델 재사용하기" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "다음 코드는 다른 프레임워크를 사용해 만든 모델의 첫 번째 은닉층으로부터 가중치와 편향을 어떻게 복사하는지 보여줌." ] }, { "cell_type": "code", "execution_count": 77, "metadata": {}, "outputs": [], "source": [ "reset_graph()\n", "\n", "n_inputs = 2\n", "n_hidden1 = 3\n", "\n", "# 재사용할 모델 파라미터를 추출\n", "original_w = [[1., 2., 3.], [4., 5., 6.]] # 다른 프레임워크로부터 가중치를 로드\n", "original_b = [7., 8., 9.] # 다른 프레임워크로부터 편향을 로드\n", "\n", "X = tf.placeholder(tf.float32, shape=(None, n_inputs), name=\"X\")\n", "hidden1 = tf.layers.dense(X, n_hidden1, activation=tf.nn.relu, name=\"hidden1\")\n", "# [...] 모델의 나머지 부분을 구성\n", "\n", "# 텐서플로 변수는 초기화와 연관된 할당 연산(변수 이름에 '/Assign'이 붙은 이름)을 가지고 있음\n", "# hidden1 변수의 할당 노드에 대한 핸들을 구합니다\n", "graph = tf.get_default_graph()\n", "assign_kernel = graph.get_operation_by_name(\"hidden1/kernel/Assign\")\n", "assign_bias = graph.get_operation_by_name(\"hidden1/bias/Assign\")\n", "\n", "# 할당 연산의 두 번째 입력값(변수에 할당될 값)의 핸들을 구함, 여기서는 변수의 초깃값\n", "init_kernel = assign_kernel.inputs[1]\n", "init_bias = assign_bias.inputs[1]\n", "\n", "init = tf.global_variables_initializer()\n", "\n", "with tf.Session() as sess:\n", " sess.run(init, feed_dict={init_kernel: original_w, init_bias: original_b})\n", " # [...] 새 작업에 모델을 훈련시킵니다" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 11.2.3 신경망의 하위층을 학습에서 제외하기" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- 일반적으로 새로운 DNN을 훈련시킬 때 재사용되는 층들의 가중치를 '동결'하는 것이 좋음.
\n", "하위층의 가중치가 고정되면 상위층의 가중치를 훈련시키기 쉬움.
\n", "- 훈련하는 동안 하위층을 고정시키는 방법: 하위층의 변수를 제외하고 훈련시킬 변수 목록을 옵티마이저에 전달하는 것" ] }, { "cell_type": "code", "execution_count": 79, "metadata": {}, "outputs": [], "source": [ "reset_graph()\n", "\n", "n_inputs = 28 * 28 # MNIST\n", "n_hidden1 = 300 # 재사용\n", "n_hidden2 = 50 # 재사용\n", "n_hidden3 = 50 # 재사용\n", "n_hidden4 = 20 # 새로 만듦!\n", "n_outputs = 10 # 새로 만듦!\n", "\n", "X = tf.placeholder(tf.float32, shape=(None, n_inputs), name=\"X\")\n", "y = tf.placeholder(tf.int32, shape=(None), name=\"y\")\n", "\n", "with tf.name_scope(\"dnn\"):\n", " hidden1 = tf.layers.dense(X, n_hidden1, activation=tf.nn.relu, name=\"hidden1\") # 재사용\n", " hidden2 = tf.layers.dense(hidden1, n_hidden2, activation=tf.nn.relu, name=\"hidden2\") # 재사용\n", " hidden3 = tf.layers.dense(hidden2, n_hidden3, activation=tf.nn.relu, name=\"hidden3\") # 재사용\n", " hidden4 = tf.layers.dense(hidden3, n_hidden4, activation=tf.nn.relu, name=\"hidden4\") # 새로 만듦!\n", " logits = tf.layers.dense(hidden4, n_outputs, name=\"outputs\") # 새로 만듦!\n", "\n", "with tf.name_scope(\"loss\"):\n", " xentropy = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=y, logits=logits)\n", " loss = tf.reduce_mean(xentropy, name=\"loss\")\n", "\n", "with tf.name_scope(\"eval\"):\n", " correct = tf.nn.in_top_k(logits, y, 1)\n", " accuracy = tf.reduce_mean(tf.cast(correct, tf.float32), name=\"accuracy\")" ] }, { "cell_type": "code", "execution_count": 80, "metadata": {}, "outputs": [], "source": [ "with tf.name_scope(\"train\"): \n", " optimizer = tf.train.GradientDescentOptimizer(learning_rate)\n", " \n", " # 은닉층 3, 4와 출력층에 있는 학습할 변수 목록을 모두 구함. 은닉층 1, 2에 있는 변수는 남겨둠\n", " train_vars = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES,\n", " scope=\"hidden[34]|outputs\")\n", " training_op = optimizer.minimize(loss, var_list=train_vars) # 학습할 변수 목록을 옵티마이저의 minimize() 함수에 제공" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- 은닉층 1과 2는 훈련하는 동안 동결되었음. 이런 층을 **동결된 층**frozen layer이라고 함." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- 또 다른 방법은 그래프에 stop_gradient() 층을 추가하는 것. 이렇게 하면 이 층 아래의 모든 층이 고정됨." ] }, { "cell_type": "code", "execution_count": 82, "metadata": {}, "outputs": [], "source": [ "reset_graph()\n", "\n", "n_inputs = 28 * 28 # MNIST\n", "n_hidden1 = 300 # 재사용\n", "n_hidden2 = 50 # 재사용\n", "n_hidden3 = 50 # 재사용\n", "n_hidden4 = 20 # 새로 만듦!\n", "n_outputs = 10 # 새로 만듦!\n", "\n", "X = tf.placeholder(tf.float32, shape=(None, n_inputs), name=\"X\")\n", "y = tf.placeholder(tf.int32, shape=(None), name=\"y\")\n", "\n", "with tf.name_scope(\"dnn\"):\n", " hidden1 = tf.layers.dense(X, n_hidden1, activation=tf.nn.relu,\n", " name=\"hidden1\") # 동결층 재사용\n", " hidden2 = tf.layers.dense(hidden1, n_hidden2, activation=tf.nn.relu,\n", " name=\"hidden2\") # 동결층 재사용\n", " hidden2_stop = tf.stop_gradient(hidden2)\n", " hidden3 = tf.layers.dense(hidden2_stop, n_hidden3, activation=tf.nn.relu,\n", " name=\"hidden3\") # 동결하지 않고 재사용\n", " hidden4 = tf.layers.dense(hidden3, n_hidden4, activation=tf.nn.relu,\n", " name=\"hidden4\") # 새로 만듦!\n", " logits = tf.layers.dense(hidden4, n_outputs, name=\"outputs\") # 새로 만듦!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 11.2.4 동결된 층 캐싱하기" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- 동결된 층은 변하지 않기 때문에 각 훈련 샘플에 대해 가장 위쪽의 동결된 층에서 나온 출력을 캐싱하는 것이 가능.\n", "- 전체 데이터셋에 대한 훈련이 여러 번 반복되기 때문에 훈련 샘플마다 동결된 층을 한 번만 거친다면 학습 속도를 크게 높일 수 있음" ] }, { "cell_type": "code", "execution_count": 84, "metadata": {}, "outputs": [], "source": [ "with tf.name_scope(\"loss\"):\n", " xentropy = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=y, logits=logits)\n", " loss = tf.reduce_mean(xentropy, name=\"loss\")\n", "\n", "with tf.name_scope(\"eval\"):\n", " correct = tf.nn.in_top_k(logits, y, 1)\n", " accuracy = tf.reduce_mean(tf.cast(correct, tf.float32), name=\"accuracy\")\n", "\n", "with tf.name_scope(\"train\"):\n", " optimizer = tf.train.GradientDescentOptimizer(learning_rate)\n", " training_op = optimizer.minimize(loss)\n", "\n", "reuse_vars = tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES,\n", " scope=\"hidden[123]\") # 정규 표현식\n", "restore_saver = tf.train.Saver(reuse_vars) # 1-3층 복원\n", "\n", "init = tf.global_variables_initializer()\n", "saver = tf.train.Saver()" ] }, { "cell_type": "code", "execution_count": 85, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "INFO:tensorflow:Restoring parameters from ./my_model_final.ckpt\n", "0 검증 세트 정확도: 0.9506\n", "1 검증 세트 정확도: 0.9508\n", "2 검증 세트 정확도: 0.9598\n", "3 검증 세트 정확도: 0.9576\n", "4 검증 세트 정확도: 0.9588\n", "5 검증 세트 정확도: 0.9458\n", "6 검증 세트 정확도: 0.9616\n", "7 검증 세트 정확도: 0.9624\n", "8 검증 세트 정확도: 0.9536\n", "9 검증 세트 정확도: 0.9598\n", "10 검증 세트 정확도: 0.9582\n", "11 검증 세트 정확도: 0.9634\n", "12 검증 세트 정확도: 0.9562\n", "13 검증 세트 정확도: 0.96\n", "14 검증 세트 정확도: 0.9662\n", "15 검증 세트 정확도: 0.954\n", "16 검증 세트 정확도: 0.9664\n", "17 검증 세트 정확도: 0.9614\n", "18 검증 세트 정확도: 0.9648\n", "19 검증 세트 정확도: 0.9656\n" ] } ], "source": [ "import numpy as np\n", "\n", "n_batches = len(X_train) # batch_size\n", "\n", "with tf.Session() as sess:\n", " init.run()\n", " restore_saver.restore(sess, \"./my_model_final.ckpt\")\n", " \n", " h2_cache = sess.run(hidden2, feed_dict={X: X_train})\n", " h2_cache_valid = sess.run(hidden2, feed_dict={X: X_valid})\n", "\n", " for epoch in range(n_epochs):\n", " shuffled_idx = np.random.permutation(len(X_train))\n", " hidden2_batches = np.array_split(h2_cache[shuffled_idx], n_batches) # 은닉층 2의 출력을 배치로 만듦\n", " y_batches = np.array_split(y_train[shuffled_idx], n_batches) # 타깃\n", " for hidden2_batch, y_batch in zip(hidden2_batches, y_batches):\n", " # 훈련 연산 실행(1,2층 동결), 두 번째 은닉층의 출력을 배치로 만들어 주입\n", " sess.run(training_op, feed_dict={hidden2:hidden2_batch, y:y_batch})\n", "\n", " accuracy_val = accuracy.eval(feed_dict={hidden2: h2_cache_valid, \n", " y: y_valid}) \n", " print(epoch, \"검증 세트 정확도:\", accuracy_val) \n", "\n", " save_path = saver.save(sess, \"./my_new_model_final.ckpt\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 11.2.5 상위층을 변경, 삭제, 대체하기" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- 원본 모델의 출력층은 새로운 작업에는 쓸모가 없고 새 작업을 위한 출력 뉴런 수도 같지 않을 수 있기 때문에 보통 교체됨.\n", "- 비슷하게 원본 모델의 상위층은 하위층보다는 덜 유용. 새로운 작업에서 필요한 고수준 특성은 원본과는 많이 다르기 때문.
\n", "그래서 재사용할 적절한 층의 개수를 알아야 함." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- 1.복사한 모든 층을 동결.
\n", "2.모델을 훈련시키고 얼마나 성능이 나오는지 지켜봄.
\n", "3.가장 위쪽의 은닉층 한 개나 두 개의 동결을 해제해서 역전파로 가중치가 변경되게 하고 성능이 향상되는지 확인
\n", "   훈련 데이터가 많을수록 많은 층을 동결 해제할 수 있음." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- 좋은 성능을 얻을 수 없고 훈련 데이터가 적다면 가장 위쪽의 은닉층(들)을 제거하고 남은 은닉층을 다시 모두 동결.
\n", "재사용에 적절한 층의 개수를 찾을 때까지 반복.\n", "- 훈련 데이터가 충분하다면 최상위 은닉층을 버리는 대신 바꿔볼 수 있으며, 은닉층을 더 추가할 수도 있음." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 11.2.6 모델 저장소" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- 많은 사람이 다양한 문제에 대해 머신러닝 모델을 훈련시키고, 미리 훈련된 모델을 공개하고 있음.\n", "- 텐서플로는 https://github.com/tensorflow/models 에 자체 모델 저장소를 가지고 있음.
\n", "그리고 코드와 미리 학습된 모델, 잘 알려진 이미지 데이터셋을 내려받기 위한 도구도 포함.\n", "- 카페 모델 저장소( http://goo.gl/XI02X3 )
\n", "여러 가지 데이터셋에 훈련시킨 많은 컴퓨터 비전 모델이 있음." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 11.2.7 비지도 사전훈련" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- **비지도 사전훈련**unsupervised pretraining(그림 11-5): 레이블이 없는 훈련 데이터가 많다면 **제한된 볼츠만 머신**Restricted Boltzmann machines(RBM)이나 오토인코더같은 비지도 특성 추출 알고리즘을 사용해 맨 하위층부터 위로 올라가면서 차례로 한 층씩 학습시킴. 각 층은 훈련된 이전 층의 출력으로 훈련됨(훈련 중인 층을 제외하고 다른 층은 모두 동결).
\n", "이런 방식으로 모든 층이 훈련되면 지도 학습으로(즉, 역전파 알고리즘을 사용해) 신경망을 세밀하게 튜닝할 수 있음." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "
**그림 11-5 비지도 사전훈련**
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 11.2.8 보조 작업으로 사전훈련" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- 마지막 선택사항은 레이블된 훈련 데이터를 쉽게 얻거나 생성할 수 있는 보조 작업에 첫 번째 신경망을 훈련시키는 것.
\n", "그리고 이 신경망의 하위층을 실제 작업을 위해 재사용함.
\n", "- 예를 들어, 얼굴을 인식하는 시스템을 만들기위해 인터넷에서 무작위로 많은 인물의 이미지를 수집해서
\n", "두 개의 다른 이미지가 같은 사람의 것인지 감지하는 첫 번째 신경망을 훈련시킬 수 있음.
\n", "
\n", "- 레이블되지 않은 훈련 샘플을 모으는 것은 비용이 저렴하지만 이를 레이블링하려면 비용이 많이 드는 상황에서 일반적인 방법은 훈련 샘플 전체를 '좋은 샘플'로 레이블하고 좋은 샘플을 오염시켜 새로운 훈련 샘플을 생성하여 '나쁜 샘플'로 레이블하는 것. 그런 다음 좋은 샘플과 나쁜 샘플을 분류하는 첫 번째 신경망을 훈련시킬 수 있음.\n", "- 예를 들어, 수백만 개의 문장을 내려받아 '좋은 샘플'이라고 레이블하고, 문장의 단어를 무작위로 바꿔 '나쁜 샘플'이라고 레이블함. 좋은 문장과 나쁜 문장을 구분할 수 있는 신경망을 재사용하면 여러 가지 언어 처리 작업에 도움이 될 것.
\n", "
\n", "- **최대 마진 학습**max margin learning: 첫 번째 신경망이 각 훈련 샘플에 대해 점수를 출력하도록 훈련시키고 좋은 샘플의 점수가 나쁜 샘플의 점수보다 일정 마진 이상 더 크게 만드는 비용함수를 사용하는 것." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "## 11.3 고속 옵티마이저" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- 훈련 속도를 크게 높일 수 있는 방법으로 표준적인 경사 하강법 옵티마이저 대신 더 빠른 옵티마이저를 사용할 수 있음.\n", "- 이 절에서는 가장 인기 있는 옵티마이저인 모멘텀 최적화Momentum optimization, 네스테로프 가속 경사Nesterov Accelerated Gradient,
AdaGrad, RMSProp, Adam 옵티마이저를 소개함." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 11.3.1 모멘텀 최적화" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- **모멘텀 최적화**Momentum optimization의 원리: 볼링공이 매끈한 표면의 완만한 경사를 따라 굴러간다고 하면 처음에는 느리게 출발하지만 종단속도에 도달할 때까지는 빠르게 가속됨.\n", "- 반대로 표준적인 경사 하강법은 경사면을 따라 일정한 크기의 스텝으로 조금씩 내려가 맨 아래에 도착하는 데 시간이 더 오래 걸림.
\n", "경사 하강법은 가중치에 대한 비용 함수 J($\\theta$)의 그래디언트( $\\nabla$$\\theta$J($\\theta$) )에 학습률 $\\eta$를 곱한 것을 바로 차감하여 가중치 $\\theta$를 갱신함. 공식은 $\\theta$ $\\leftarrow$ $\\theta$ - $\\eta$$\\nabla$$\\theta$J($\\theta$)
\n", "이 식은 이전 그래디언트가 얼마였는지 고려하지 않아 국부적으로 그래디언트가 아주 작으면 매우 느려질 것.
\n", "
\n", "- 모멘텀 최적화는 이전 그래디언트가 얼마였는지를 상당히 중요하게 생각함.
\n", "매 반복에서 현재 그래디언트를 (학습률 $\\eta$를 곱한 후) **모멘텀 벡터**momentum vector **m**에 더하고 이 값을 빼는 방식으로 가중치를 갱신함(식 11-4).
\n", "다시 말해 그래디언트를 속도가 아니라 가속도로 사용함. 이 알고리즘에는 **모멘텀**momentum이라는 하이퍼파라미터 $\\beta$가 등장.
\n", "이 값은 0(높은 마찰저항)과 1(마찰저항 없음) 사이로 설정되어야 함. 일반적인 모멘텀 값은 0.9" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "
**식 11-4 모멘텀 알고리즘**
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- 4장에서 보았듯이 입력값의 스케일이 매우 다르면 비용 함수는 한쪽이 길쭉한 그릇처럼 됨(그림 4-7).
\n", "- 경사 하강법이 가파른 경사를 꽤 빠르게 내려가지만 좁고 긴 골짜기에서는 오랜 시간이 걸림. 반면에 모멘텀 최적화는 바닥(최적점)에 도달할 때까지 점점 더 빠르게 내려감.\n", "- 배치 정규화를 사용하지 않는 심층 신경망에서 상위층은 종종 스케일이 매우 다른 입력은 받게 되는데, 모멘텀 최적화를 사용하면 큰 도움이 됨.
\n", "또한 이 기법은 지역 최적점local optima을 건너 뛰도록 하는 데도 도움이 됨." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "
**그림 4-7 특성 스케일에 따른 경사 하강법**
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- 텐서플로에서 모멘텀 최적화는 GradientDescentOptimizer를 MomentumOptimizer로 바꾸면 됨.
\n", "모멘텀 0.9에서 보통 잘 작동하며 경사 하강법보다 거의 항상 더 빠름." ] }, { "cell_type": "code", "execution_count": 86, "metadata": {}, "outputs": [], "source": [ "optimizer = tf.train.MomentumOptimizer(learning_rate=learning_rate,\n", " momentum=0.9)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 11.3.2 네스테로프 가속 경사" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- **네스테로프 모멘텀 최적화**Sesterov Momentum optimization 또는 **네스테로프 가속 경사**Nesterov Accelerated Gradient(NAG): 모멘텀 최적화의 한 변종으로,
\n", "기본 모멘텀 최적화보다 거의 항상 더 빠름. 기본 아이디어는 현재 위치가 아니라 모멘텀의 방향으로 조금 앞서서 비용 함수의 그래디언트를 계산하는 것(식 11-5).
\n", "기본 모멘텀 최적화와 다른 점은 $\\theta$가 아니라 $\\theta$ - $\\beta$m 에서 그래디언트를 측정하는 것뿐." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "
**식 11-4 네스테로프 가속 경사 알고리즘**
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- 일반적으로 모멘텀 벡터가 올바른 방향(즉, 최적점을 향하는 방향)을 가리킬 것이므로 이런 변경이 가능.
\n", "그래서 [그림 11-6]처럼 원래 위치에서의 그래디언트를 사용하는 것보다 그 방향으로 조금 더 나아가서 측정한 그래디언트를 사용하는 것이 약간 더 정확.
\n", "($\\nabla$1은 시작점 $\\theta$에서 측정한 비용 함수의 그래디언트를 나타내고, $\\nabla$2는 $\\theta$ - $\\beta$m에서 측정한 그래디언트를 나타냄)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "
**그림 11-6 기본 모멘텀 최적화와 네스테로프 모멘텀 최적화**
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- NAG가 기본 모멘텀 최적화보다 거의 항상 훈련 속도를 높여줌. 이를 사용하려면 MomentumOptimizer에 use_nesterov=True라고 설정." ] }, { "cell_type": "code", "execution_count": 87, "metadata": {}, "outputs": [], "source": [ "optimizer = tf.train.MomentumOptimizer(learning_rate=learning_rate,\n", " momentum=0.9, use_nesterov=True)" ] } ], "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.5.4" } }, "nbformat": 4, "nbformat_minor": 2 }