├── BP
├── 01.png
├── 02.png
├── 03.png
├── 04.png
├── 05.png
├── 06.png
├── 07-1.png
├── 07.png
├── 08.png
├── 09.png
├── 09_.png
├── 10.png
├── 11.png
├── 12.png
├── 13.png
├── 14.png
├── 15.png
├── 16.png
├── 17.png
├── 18.png
├── 19.png
├── 20.png
├── bp.ipynb
├── dWL.png
├── dWL2.png
├── dW_1sample.png
├── dW_samples.png
├── entire_network.png
├── flatten.png
├── function-net.png
├── generaljacobian.png
├── network.png
├── terminal.png
├── terminal_scalar.png
├── terminal_vector.png
└── transpose_gj.png
├── CNN
├── imgs
│ ├── backward_conv.png
│ ├── base.png
│ ├── change_delta.png
│ ├── compare_method.png
│ ├── conv_matrix.png
│ ├── convbackward_transconv.png
│ ├── deconv1-cs231n.png
│ ├── deconv2-cs231n.png
│ ├── deconv3-cs231n.png
│ ├── delta_input.gif
│ ├── no_padding_strides_transposed.gif
│ ├── padding1.png
│ ├── padding2.png
│ ├── padding_stride.gif
│ ├── padding_stride_1.png
│ ├── padding_stride_I00.png
│ ├── padding_stride_I03.png
│ ├── padding_stride_diff.gif
│ ├── padding_stride_part.gif
│ ├── stride2_1.png
│ ├── stride2_2.png
│ ├── transconv_matrix.png
│ ├── transconv_method1_1.png
│ └── transconv_method1_2.png
└── transconv_fullconv.ipynb
├── EM
├── Kmeans.ipynb
├── em_algorithm.ipynb
└── spiderman.jpg
├── GAN
├── 1.png
├── 10.png
├── 11.png
├── 12.png
├── 13.png
├── 14.png
├── 15.png
├── 2.png
├── 3.png
├── 4.png
├── 5.png
├── 6.png
├── 7.png
├── 8.png
├── 9.png
├── GANs.ipynb
├── change_of_variable.ipynb
└── h.png
├── PRML
├── Figure2.7.png
├── calculus_of_variations.ipynb
├── eq2.51.png
└── prml-chap2.ipynb
├── README.md
├── diffusion
├── ddpm_part1.ipynb
├── ddpm_part2-1.ipynb
└── ddpm_part2-2.ipynb
├── fitting
├── fig2.png
├── fig3.png
├── matrix-derivative.ipynb
└── product-of-gaussian.ipynb
├── gradientboost
└── gradient_boosting.ipynb
├── mnistattn
└── mnist_attn.ipynb
├── naive
├── 3.png
├── 4.png
├── 5.png
├── 6.png
├── 7.png
├── 8.png
└── naive.ipynb
├── pca
└── pca.ipynb
├── perceptron
├── fig1.png
├── perceptron.ipynb
└── perceptron.png
├── sampling
├── change-of-variable.png
├── det.png
├── double-integral.ipynb
├── jacobian.png
└── test_js.ipynb
├── simplenet
├── mnist.pkl.gz
└── simplenet.ipynb
├── svd
├── svd.ipynb
├── svd_portrait1.png
├── svd_portrait2.png
├── svd_portrait3.png
├── svd_randscape1.png
├── svd_randscape2.png
├── svd_randscape3.png
└── svd_summary.png
├── svm
├── dual1-anno.png
├── dual2-anno.png
├── dual3-anno.png
├── duality.ipynb
├── duality_example.ipynb
├── implicit-diff.png
├── ineq_hess1.png
├── ineq_hess2.png
├── lagrangian.png
└── local-dual.png
├── t5
└── gentle_t5_trans.ipynb
└── transformer
└── annotated_transformer.ipynb
/BP/01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/BP/01.png
--------------------------------------------------------------------------------
/BP/02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/BP/02.png
--------------------------------------------------------------------------------
/BP/03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/BP/03.png
--------------------------------------------------------------------------------
/BP/04.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/BP/04.png
--------------------------------------------------------------------------------
/BP/05.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/BP/05.png
--------------------------------------------------------------------------------
/BP/06.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/BP/06.png
--------------------------------------------------------------------------------
/BP/07-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/BP/07-1.png
--------------------------------------------------------------------------------
/BP/07.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/BP/07.png
--------------------------------------------------------------------------------
/BP/08.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/BP/08.png
--------------------------------------------------------------------------------
/BP/09.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/BP/09.png
--------------------------------------------------------------------------------
/BP/09_.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/BP/09_.png
--------------------------------------------------------------------------------
/BP/10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/BP/10.png
--------------------------------------------------------------------------------
/BP/11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/BP/11.png
--------------------------------------------------------------------------------
/BP/12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/BP/12.png
--------------------------------------------------------------------------------
/BP/13.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/BP/13.png
--------------------------------------------------------------------------------
/BP/14.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/BP/14.png
--------------------------------------------------------------------------------
/BP/15.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/BP/15.png
--------------------------------------------------------------------------------
/BP/16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/BP/16.png
--------------------------------------------------------------------------------
/BP/17.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/BP/17.png
--------------------------------------------------------------------------------
/BP/18.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/BP/18.png
--------------------------------------------------------------------------------
/BP/19.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/BP/19.png
--------------------------------------------------------------------------------
/BP/20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/BP/20.png
--------------------------------------------------------------------------------
/BP/dWL.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/BP/dWL.png
--------------------------------------------------------------------------------
/BP/dWL2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/BP/dWL2.png
--------------------------------------------------------------------------------
/BP/dW_1sample.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/BP/dW_1sample.png
--------------------------------------------------------------------------------
/BP/dW_samples.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/BP/dW_samples.png
--------------------------------------------------------------------------------
/BP/entire_network.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/BP/entire_network.png
--------------------------------------------------------------------------------
/BP/flatten.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/BP/flatten.png
--------------------------------------------------------------------------------
/BP/function-net.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/BP/function-net.png
--------------------------------------------------------------------------------
/BP/generaljacobian.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/BP/generaljacobian.png
--------------------------------------------------------------------------------
/BP/network.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/BP/network.png
--------------------------------------------------------------------------------
/BP/terminal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/BP/terminal.png
--------------------------------------------------------------------------------
/BP/terminal_scalar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/BP/terminal_scalar.png
--------------------------------------------------------------------------------
/BP/terminal_vector.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/BP/terminal_vector.png
--------------------------------------------------------------------------------
/BP/transpose_gj.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/BP/transpose_gj.png
--------------------------------------------------------------------------------
/CNN/imgs/backward_conv.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/CNN/imgs/backward_conv.png
--------------------------------------------------------------------------------
/CNN/imgs/base.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/CNN/imgs/base.png
--------------------------------------------------------------------------------
/CNN/imgs/change_delta.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/CNN/imgs/change_delta.png
--------------------------------------------------------------------------------
/CNN/imgs/compare_method.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/CNN/imgs/compare_method.png
--------------------------------------------------------------------------------
/CNN/imgs/conv_matrix.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/CNN/imgs/conv_matrix.png
--------------------------------------------------------------------------------
/CNN/imgs/convbackward_transconv.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/CNN/imgs/convbackward_transconv.png
--------------------------------------------------------------------------------
/CNN/imgs/deconv1-cs231n.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/CNN/imgs/deconv1-cs231n.png
--------------------------------------------------------------------------------
/CNN/imgs/deconv2-cs231n.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/CNN/imgs/deconv2-cs231n.png
--------------------------------------------------------------------------------
/CNN/imgs/deconv3-cs231n.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/CNN/imgs/deconv3-cs231n.png
--------------------------------------------------------------------------------
/CNN/imgs/delta_input.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/CNN/imgs/delta_input.gif
--------------------------------------------------------------------------------
/CNN/imgs/no_padding_strides_transposed.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/CNN/imgs/no_padding_strides_transposed.gif
--------------------------------------------------------------------------------
/CNN/imgs/padding1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/CNN/imgs/padding1.png
--------------------------------------------------------------------------------
/CNN/imgs/padding2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/CNN/imgs/padding2.png
--------------------------------------------------------------------------------
/CNN/imgs/padding_stride.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/CNN/imgs/padding_stride.gif
--------------------------------------------------------------------------------
/CNN/imgs/padding_stride_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/CNN/imgs/padding_stride_1.png
--------------------------------------------------------------------------------
/CNN/imgs/padding_stride_I00.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/CNN/imgs/padding_stride_I00.png
--------------------------------------------------------------------------------
/CNN/imgs/padding_stride_I03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/CNN/imgs/padding_stride_I03.png
--------------------------------------------------------------------------------
/CNN/imgs/padding_stride_diff.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/CNN/imgs/padding_stride_diff.gif
--------------------------------------------------------------------------------
/CNN/imgs/padding_stride_part.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/CNN/imgs/padding_stride_part.gif
--------------------------------------------------------------------------------
/CNN/imgs/stride2_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/CNN/imgs/stride2_1.png
--------------------------------------------------------------------------------
/CNN/imgs/stride2_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/CNN/imgs/stride2_2.png
--------------------------------------------------------------------------------
/CNN/imgs/transconv_matrix.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/CNN/imgs/transconv_matrix.png
--------------------------------------------------------------------------------
/CNN/imgs/transconv_method1_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/CNN/imgs/transconv_method1_1.png
--------------------------------------------------------------------------------
/CNN/imgs/transconv_method1_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/CNN/imgs/transconv_method1_2.png
--------------------------------------------------------------------------------
/CNN/transconv_fullconv.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": 1,
6 | "metadata": {},
7 | "outputs": [],
8 | "source": [
9 | "import torch\n",
10 | "import torch.nn.functional as F"
11 | ]
12 | },
13 | {
14 | "cell_type": "code",
15 | "execution_count": 2,
16 | "metadata": {},
17 | "outputs": [
18 | {
19 | "data": {
20 | "text/plain": [
21 | "'1.0.0'"
22 | ]
23 | },
24 | "execution_count": 2,
25 | "metadata": {},
26 | "output_type": "execute_result"
27 | }
28 | ],
29 | "source": [
30 | "torch.__version__"
31 | ]
32 | },
33 | {
34 | "cell_type": "markdown",
35 | "metadata": {},
36 | "source": [
37 | "# 합성곱 신경망에서 컨벌루션과 트랜스포즈드 컨벌루션의 관계 Relationship between Convolution and Transposed Convolution in CNN \n",
38 | "\n",
39 | "
\n",
40 | " 2020.02.29 수정
\n",
41 | " 2019.05.09 작성
\n",
42 | " 조준우 metamath@gmail.com\n",
43 | "
\n",
44 | "\n",
45 | "
\n",
46 | "\n",
47 | "이 문서의 목적은 CNN에서 CONV층의 포워드 패스 컨벌루션의 백워드 연산이 상류층 그래디언트를 필터로 하는 컨벌루션이며 이 백워드 패스 컨벌루션이 결국 포워드 패스의 컨벌루션에 대한 트랜스포즈드 컨벌루션transposed convolution이라는 것을 알아보고자 하는 것이다.\n",
48 | "\n",
49 | "CNN의 CONV층에서 일어나는 포워드 패스 연산을 컨벌루션convolution이라고 이야기하는데 정확하게 표현하면 이 연산을 코릴레이션correlation, [1]이라고 해야한다. 수학적으로 정의된 컨벌루션에 맞게 연산을 하려면 필터를 180도 돌리고 패딩을 줘서 연산을 해야한다. 그런데 신기하게도 코릴레이션의 백워드 패스 연산을 구해보면 정확하게 컨벌루션이 되는 것을 확인할 수 있다. 본 문서에서는 코릴레이션과 컨벌루션을 관행처럼 모두 컨벌루션이라고 이야기하고 구분이 필요한 경우 필터를 돌리지 않는 경우를 포워드 패스 컨벌루션, 필터를 돌려서 풀 컨벌루션하는 경우를 백워드 패스 컨벌루션으로 구분하도록 했다.\n",
50 | "\n",
51 | "CONV층의 기본적인 미분에 대해서는 [jo]를 참고하여 관련 내용을 먼저 읽고 이 문서를 읽으면 이해가 더 쉽다."
52 | ]
53 | },
54 | {
55 | "cell_type": "markdown",
56 | "metadata": {},
57 | "source": [
58 | "## 기본 모델, no padding, stride=1\n",
59 | "\n",
60 | "### CONV층의 미분을 나타내는 컨벌루션\n",
61 | "\n",
62 | "아래 그림은 4x4 입력 $I$가 3x3 필터 $w$를 통해 2x2 출력 $\\mathbf{z}$가 되고 이후 어떤 연산 $f(\\mathbf{z})$에 의해 스칼라 $C$가 출력되는 전체 모델을 나타낸다. 하단에 있는 $\\delta_{kl}$은 $C$가 $z_{kl}$까지 미분된 그래디언트gradient이다. 상류층 그래디언트upstream gradient $\\delta_{kl}$에 도형을 그려 놓은 것은 내용중에 이 텐서가 180도 회전을 하게 되는데 이때 이 현상을 좀 더 알아보기 쉽게 보여주기 위한 것이다.\n",
63 | "\n",
64 | "
\n",
65 | "\n",
66 | "$z_{kl}$은 다음 식처럼 입력과 필터의 컨벌루션으로 계산 되어진다. 앞서 이야기한것처럼 필터를 회전시키지 않고 그대로 입력에 겹쳐서 연산한다.\n",
67 | "\n",
68 | "$$\n",
69 | "z_{kl} = \\sum_{q=0}^{Q-1} \\sum_{r=0}^{R-1} w_{qr} I_{(k+q),(l+r)} \\tag{1}\n",
70 | "$$\n",
71 | "\n",
72 | "그리고 $\\mathbf{z}$로부터 스칼라 $C$를 다음처럼 정의하자.\n",
73 | "\n",
74 | "$$\n",
75 | "C = f(\\mathbf{z})\n",
76 | "$$\n",
77 | "\n",
78 | "그러면 $C$의 $I$에 대한 미분은 다음과 같다.\n",
79 | "\n",
80 | "$$\n",
81 | "\\frac{\\partial \\, C}{\\partial \\, I_{ij}} = \\sum_{k} \\sum_{l} \\frac{\\partial \\, z_{kl}}{\\partial \\, I_{ij}} \\frac{\\partial \\, C}{\\partial \\, z_{kl}}\n",
82 | "$$\n",
83 | "\n",
84 | "표기를 간단히 하기 위해 상류층 그래디언트를 다음처럼 적기로 하자.\n",
85 | "\n",
86 | "$$\n",
87 | "\\delta_{kl} = \\frac{\\partial \\, C}{\\partial \\, z_{kl}}\n",
88 | "$$\n",
89 | "\n",
90 | "그럼 다음과 같다.\n",
91 | "\n",
92 | "$$\n",
93 | "\\frac{\\partial \\, C}{\\partial \\, I_{ij}} = \\sum_{k} \\sum_{l} \\frac{\\partial \\, z_{kl}}{\\partial \\, I_{ij}} \\delta_{kl} \\tag{2}\n",
94 | "$$\n",
95 | "\n",
96 | "이제 $\\dfrac{\\partial \\, z_{kl}}{\\partial \\, I_{ij}}$ 부분을 생각해보자. 다시쓰면\n",
97 | "\n",
98 | "$$\n",
99 | "\\frac{\\partial \\, z_{kl}}{\\partial \\, I_{ij}} = \\frac{\\partial}{\\partial \\, I_{ij}} \\left( \\sum_{q=0}^{Q-1} \\sum_{r=0}^{R-1} w_{qr} I_{(k+q),(l+r)} \\right)\n",
100 | "$$\n",
101 | "\n",
102 | "이므로 $I$에 대한 인덱스가 $k+q=i$, $l+r=j$인 경우만 값이 남고 나머지는 모두 0이다. 이 두 관계에 의해\n",
103 | "\n",
104 | "$$\n",
105 | "q = i-k \\\\\n",
106 | "r = j-l\n",
107 | "$$\n",
108 | "\n",
109 | "이므로 이 인덱스를 미분식에 대입하면\n",
110 | "\n",
111 | "$$\n",
112 | "\\frac{\\partial \\, z_{kl}}{\\partial \\, I_{ij}} = w_{(i-k),(j-l)}\n",
113 | "$$\n",
114 | "\n",
115 | "이고, 이를 식(2)에 대입하면 $I$에 대한 미분은 다음 식(3)처럼 된다.\n"
116 | ]
117 | },
118 | {
119 | "cell_type": "markdown",
120 | "metadata": {},
121 | "source": [
122 | "$$\n",
123 | "\\frac{\\partial \\, C}{\\partial \\, I_{ij}} = \\sum_{k} \\sum_{l} w_{(i-k),(j-l)} \\delta_{kl} \\tag{3}\n",
124 | "$$\n",
125 | "\n",
126 | "식(3)은 $\\delta$를 180도 회전시켜서 필터 $w$에 패딩 1을 주고 컨벌루션 연산을 수행하는 것이다. 물론 반대로 필터를 180도 회전시켜서 $\\delta$에 컨벌루션하는것도 결과는 완전히 동일하다. 여기서는 계산 결과로 인해 $w$의 인덱스에 음수가 나오기 때문에 편의상 필터 $w$를 바로 그리고 도형으로 표시된 $\\delta$를 뒤집어 그렸을 뿐이다. 앞으로도 컨벌루션의 백워드 패스 즉 미분에 대해서는 $\\delta$를 돌려서 그리도록 한다.\n",
127 | "\n",
128 | "이렇게 패딩을 주고 컨벌루션하는 방식을 풀 컨벌루션full convolution이라고 한다. 왜 그런지 다음 그림과 그림에 해당하는 상황을 식(3)으로 풀어 적어 비교해보자."
129 | ]
130 | },
131 | {
132 | "cell_type": "markdown",
133 | "metadata": {},
134 | "source": [
135 | "$$\n",
136 | "\\frac{\\partial \\, C}{\\partial \\, I_{00}} = w_{00} \\delta_{00} + w_{0,-1} \\delta_{01} + w_{-1,0} \\delta_{10} + w_{-1,-1} \\delta_{11}\n",
137 | "$$\n",
138 | "\n",
139 | "
\n",
140 | "\n",
141 | "앞선 그림에 나타난 $\\delta$와 지금 그림에서 $\\delta$가 어떻게 다른지 주의깊게 살펴보자. 식(3)처럼 컨벌루션하면 $\\delta$가 180도 돌아간다는 것을 알 수 있다. 그림과 같은 상태로 시작하여 오른쪽으로 한칸씩 이동하면서 컨벌루션 연산을 수행한다. 연산 결과는 4x4가 되며 이것이 $C$를 입력 $I$로 미분한 그래디언트가 된다.\n",
142 | "\n",
143 | "지금까지 결과를 `pytorch`로 확인해보자. 임의의 텐서에 대해 위 상황과 같게 만들고 식(3)을 통한 결과와 `pytorch`의 `autograd`를 이용해서 만든 결과를 비교할 것이다."
144 | ]
145 | },
146 | {
147 | "cell_type": "code",
148 | "execution_count": 3,
149 | "metadata": {},
150 | "outputs": [
151 | {
152 | "name": "stdout",
153 | "output_type": "stream",
154 | "text": [
155 | "z\n",
156 | "tensor([[[[-0.1649, -0.4734],\n",
157 | " [ 1.4067, -3.9543]]]], grad_fn=)\n",
158 | "C\n",
159 | "tensor(1.6647, grad_fn=)\n",
160 | "dI by torch\n",
161 | "tensor([[[[-0.1396, -0.2800, -0.2398, -0.0950],\n",
162 | " [-0.0218, -0.2158, -0.9501, -0.6822],\n",
163 | " [ 0.1053, 0.2052, -0.1437, 0.0235],\n",
164 | " [ 0.0399, 0.1652, 0.0697, 0.0059]]]])\n"
165 | ]
166 | }
167 | ],
168 | "source": [
169 | "# 임의의 인풋과 필터에 대해서 포워드 패스를 수행한다.\n",
170 | "I = torch.randn(1 ,1, 4, 4, requires_grad=True)\n",
171 | "w = torch.randn(1, 1, 3, 3)\n",
172 | "z = F.conv2d(I, w, stride=1, padding=0)\n",
173 | "C = (torch.sigmoid(z)).sum() # f(z) = sum (sigmoid(z)) 로 정의\n",
174 | "\n",
175 | "print('z')\n",
176 | "print(z)\n",
177 | "\n",
178 | "print('C')\n",
179 | "print(C)\n",
180 | "\n",
181 | "###########################################################\n",
182 | "# 파이토치의 autograd를 이용해 일단 dI를 구한다.\n",
183 | "###########################################################\n",
184 | "dI_torch = torch.autograd.grad(C, I, torch.Tensor([1]), retain_graph=True)[0]\n",
185 | "print('dI by torch')\n",
186 | "print(dI_torch)"
187 | ]
188 | },
189 | {
190 | "cell_type": "markdown",
191 | "metadata": {},
192 | "source": [
193 | "`pytorch`의 `autograd.grad`함수를 이용하여 그래디언트를 구했다. 이제 식(3)으로 구한 4x4행렬이 이것과 일치해야 할 것이다."
194 | ]
195 | },
196 | {
197 | "cell_type": "code",
198 | "execution_count": 4,
199 | "metadata": {},
200 | "outputs": [
201 | {
202 | "name": "stdout",
203 | "output_type": "stream",
204 | "text": [
205 | "delta\n",
206 | "tensor([[[[0.2483, 0.2365],\n",
207 | " [0.1580, 0.0185]]]])\n",
208 | "delta flip\n",
209 | "tensor([[[[0.0185, 0.1580],\n",
210 | " [0.2365, 0.2483]]]])\n",
211 | "dI\n",
212 | "tensor([[[[-0.1396, -0.2800, -0.2398, -0.0950],\n",
213 | " [-0.0218, -0.2158, -0.9501, -0.6822],\n",
214 | " [ 0.1053, 0.2052, -0.1437, 0.0235],\n",
215 | " [ 0.0399, 0.1652, 0.0697, 0.0059]]]])\n"
216 | ]
217 | }
218 | ],
219 | "source": [
220 | "##########################################################\n",
221 | "# eq(3)으로 dI를 구한다.\n",
222 | "##########################################################\n",
223 | "# delta = dC/dz를 구한다. \n",
224 | "delta = torch.autograd.grad(C, z, torch.Tensor([1]), retain_graph=True)[0]\n",
225 | "print('delta')\n",
226 | "print(delta)\n",
227 | "\n",
228 | "# delta를 180도 돌리고\n",
229 | "delta_flip = torch.flip(delta, [2, 3])\n",
230 | "print('delta flip')\n",
231 | "print(delta_flip)\n",
232 | "\n",
233 | "# w에 패딩을 주고 컨벌루션한다.\n",
234 | "print('dI')\n",
235 | "dI = F.conv2d(w, delta_flip, padding=1)\n",
236 | "print(dI)\n",
237 | "\n",
238 | "# dI_torch와 dI는 정확히 일치한다."
239 | ]
240 | },
241 | {
242 | "cell_type": "markdown",
243 | "metadata": {},
244 | "source": [
245 | "결과를 보면 `pytorch`의 `autograd`를 이용한 `dI_torch`와 식(3)을 통해 계산한 `dI`가 정확히 일치하는 것을 확인할 수 있다. \n",
246 | "\n",
247 | "지금까지 간단한 예제를 통해 **CONV층의 백워드 연산은 상류층 그래디언트를 180도 돌리고 패딩을 주고 진행하는 또다른 컨벌루션 연산**이라는 것을 확인했다. 이제 이것과 똑같은 결과를 주는 또다른 연산방식을 알아보자."
248 | ]
249 | },
250 | {
251 | "cell_type": "markdown",
252 | "metadata": {},
253 | "source": [
254 | "### 필터의 적층으로 계산하는 방식[cs231n]\n",
255 | "\n",
256 | "다음 그림과 같은 연산을 생각해보자.\n",
257 | "\n",
258 | "
\n",
259 | "
\n",
260 | "\n",
261 | "그래디언트의 값 $\\delta_{00}$ (그림에서 빨간색 다이아몬드)를 필터의 모든 값에 곱한다. 이런 곱을 브로드캐스팅 곱이라 하는데 이렇게해서 나온 결과를 4x4 배열의 왼쪽 상단에 위치시킨다. 이제 $\\delta_{01}$을 다시 필터에 브로드캐스팅 곱한 결과를 4x4 배열의 오른쪽 상단에 위치시킨다. 이때 앞서 계산한 결과와 겹쳐지는 부분은 값을 누적해서 더해준다. 이렇게 계산한 결과는 앞서 이야기한 백워드 패스 컨벌루션과 정확히 일치하게 된다. 이것을 확인하기 위해 연산 결과로 나오는 4x4행렬에서 0행 1열에 해당하는 값을 계산하는 경우에 대해 두 방법을 비교해보면 다음 그림과 같다.\n",
262 | "\n",
263 | "
\n",
264 | "\n",
265 | "두 방법 모두 결과가 $\\delta_{00}w_{01} + \\delta_{01}w_{00}$으로 동일한 것을 알 수 있다. 나머지 위치에서도 결과는 동일하게 된다.\n",
266 | "\n",
267 | "아래 코드가 위에서 설명한 방식을 구현한 코드이다. `stride`, `padding`을 모두 고려한 코드라 설명보다 다소 복잡해보이지만 가운데 중복된 `for`문 만 신경써서 보면 된다."
268 | ]
269 | },
270 | {
271 | "cell_type": "code",
272 | "execution_count": 5,
273 | "metadata": {},
274 | "outputs": [],
275 | "source": [
276 | "def conv_transpose_cs231n(o, w, stride=1, padding=0, output_padding=0):\n",
277 | " fw, fh = w.size()[2:]\n",
278 | " ow, oh = o.size()[2:]\n",
279 | " \n",
280 | " rw = (fw + (ow-1)*stride)\n",
281 | " rh = (fh + (oh-1)*stride)\n",
282 | " result = torch.zeros((1,1,rw,rh))\n",
283 | " \n",
284 | " for i in range(o.size()[2]):\n",
285 | " for j in range(o.size()[3]):\n",
286 | " # 필터 w에 o의 값 하나를 브로드캐스팅 곱하여 \n",
287 | " # result에 적당한 위치에 적층시킨다.\n",
288 | " result[:,:,\n",
289 | " i*stride:i*stride+fw,\n",
290 | " j*stride:j*stride+fh] += o[:,:,i,j]*w \n",
291 | " \n",
292 | " # padding 만큼 깍아낸다.\n",
293 | " if padding > 0 :\n",
294 | " if -padding+output_padding == 0 :\n",
295 | " return result[:,:,padding:,padding:]\n",
296 | " else :\n",
297 | " return result[:,:,\n",
298 | " padding:-padding+output_padding,\n",
299 | " padding:-padding+output_padding]\n",
300 | " else :\n",
301 | " return result"
302 | ]
303 | },
304 | {
305 | "cell_type": "markdown",
306 | "metadata": {},
307 | "source": [
308 | "위 함수를 사용하여 계산을 반복하면 결과는 다음과 같다."
309 | ]
310 | },
311 | {
312 | "cell_type": "code",
313 | "execution_count": 6,
314 | "metadata": {},
315 | "outputs": [
316 | {
317 | "data": {
318 | "text/plain": [
319 | "tensor([[[[-0.1396, -0.2800, -0.2398, -0.0950],\n",
320 | " [-0.0218, -0.2158, -0.9501, -0.6822],\n",
321 | " [ 0.1053, 0.2052, -0.1437, 0.0235],\n",
322 | " [ 0.0399, 0.1652, 0.0697, 0.0059]]]])"
323 | ]
324 | },
325 | "execution_count": 6,
326 | "metadata": {},
327 | "output_type": "execute_result"
328 | }
329 | ],
330 | "source": [
331 | "conv_transpose_cs231n(delta, w)"
332 | ]
333 | },
334 | {
335 | "cell_type": "markdown",
336 | "metadata": {},
337 | "source": [
338 | "예상처럼 필터를 적층시키는 방법도 동일한 결과를 얻을 수 있다. 즉 이런 방식으로 계산을 해도 CONV층의 미분을 계산할 수 있다."
339 | ]
340 | },
341 | {
342 | "cell_type": "markdown",
343 | "metadata": {},
344 | "source": [
345 | "### 필터로 만든 행렬을 전치시키는 방식[cs231n],[Shibuya]\n",
346 | "\n",
347 | "앞서 필터와 입력을 적당히 적층시키는 방식으로 포워드 패스 컨벌루션의 미분과 동일한 결과를 만들어 봤다. 이런 일련의 동일한 연산을 트랜스포즈드 컨벌루션이라고도 이야기하는데 여기서는 이런 이름이 붙은 이유에 대해 알아보기로 하자.\n",
348 | "\n",
349 | "포워드 패스 컨벌루션은 필터 요소를 적당히 위치시킨 행렬과 입력의 행렬곱 한번으로 수행할 수 있다. 우리 예제의 경우 그림으로 나타내면 다음과 같다.\n",
350 | "\n",
351 | "
\n"
352 | ]
353 | },
354 | {
355 | "cell_type": "markdown",
356 | "metadata": {},
357 | "source": [
358 | "위 그림처럼 필터 요소를 적당히 배치해서 행렬을 만든다. 그리고 입력 $I$를 열벡터로 만들어 행렬곱을 하면 결과는 $\\mathbf{z}$임을 쉽게 알 수 있다. 아래 코드는 이 과정을 간단하게 실험해본 것이다."
359 | ]
360 | },
361 | {
362 | "cell_type": "code",
363 | "execution_count": 7,
364 | "metadata": {},
365 | "outputs": [
366 | {
367 | "name": "stdout",
368 | "output_type": "stream",
369 | "text": [
370 | "z\n",
371 | "tensor([[-0.1649],\n",
372 | " [-0.4734],\n",
373 | " [ 1.4067],\n",
374 | " [-3.9543]], grad_fn=)\n"
375 | ]
376 | }
377 | ],
378 | "source": [
379 | "output_dim = 2\n",
380 | "input_dim = 4\n",
381 | "\n",
382 | "row = torch.flatten(torch.cat((w, torch.zeros(1,1,3,1)), 3))[:-1]\n",
383 | "m = torch.zeros((output_dim**2, input_dim**2)) \n",
384 | "\n",
385 | "# 필터의 요소를 적당히 배열한 행렬 m을 만든다.\n",
386 | "m[0,:row.shape[0]] = row\n",
387 | "m[1,1:1+row.shape[0]] = row\n",
388 | "m[2,4:4+row.shape[0]] = row\n",
389 | "m[3,5:5+row.shape[0]] = row\n",
390 | "\n",
391 | "# 필터로 만든 행렬 m과 입력을 열벡터로 만들어 행렬곱하면 포워드 패스 컨벌루션이 완성된다. \n",
392 | "print('z')\n",
393 | "print(torch.mm(m, I.reshape(input_dim**2,1)))\n"
394 | ]
395 | },
396 | {
397 | "cell_type": "markdown",
398 | "metadata": {},
399 | "source": [
400 | "우리의 첫 실험코드에서 출력된 $\\mathbf{z}$와 비교해보면 포워드 패스가 성공적으로 수행되는 것을 확인할 수 있다. \n",
401 | "\n",
402 | "이제 이렇게 구성된 필터의 행렬을 전치시킨 행렬에 상류층 그래디언트 $\\delta$를 열벡터로 만들어 행렬곱 해보자. 다음 그림과 같다.\n",
403 | "\n",
404 | "
\n",
405 | "\n",
406 | "전치된 행렬과 $\\delta$의 곱을 생각해보면 앞서 논의한 두가지 연산 방식과 이번에도 일치하는 것을 알 수 있다. 따라서 이 연산의 결과는 $\\dfrac{\\partial \\, C}{\\partial \\, I}$이 되는 것을 알 수 있다. 정리하면 필터를 적당히 조작한 행렬의 전치행렬을 상류층 그래디언트에 행렬곱하면 CONV층에 대한 미분을 수행할 수 있는 것이다. 다음 코드로 실제 실험을 해보자."
407 | ]
408 | },
409 | {
410 | "cell_type": "code",
411 | "execution_count": 8,
412 | "metadata": {},
413 | "outputs": [
414 | {
415 | "name": "stdout",
416 | "output_type": "stream",
417 | "text": [
418 | "dI\n"
419 | ]
420 | },
421 | {
422 | "data": {
423 | "text/plain": [
424 | "tensor([[-0.1396, -0.2800, -0.2398, -0.0950],\n",
425 | " [-0.0218, -0.2158, -0.9501, -0.6822],\n",
426 | " [ 0.1053, 0.2052, -0.1437, 0.0235],\n",
427 | " [ 0.0399, 0.1652, 0.0697, 0.0059]])"
428 | ]
429 | },
430 | "execution_count": 8,
431 | "metadata": {},
432 | "output_type": "execute_result"
433 | }
434 | ],
435 | "source": [
436 | "# 필터로 만든 행렬을 전치시키고 상위 그래디언트를 열벡터로 만들어 행렬곱\n",
437 | "print('dI')\n",
438 | "torch.mm(torch.t(m), delta.reshape(4,1)).reshape(input_dim,input_dim)"
439 | ]
440 | },
441 | {
442 | "cell_type": "markdown",
443 | "metadata": {},
444 | "source": [
445 | "결과로 구해진 `dI` 역시 이전과 동일하다. 이런 이유로 CONV층의 포워드 패스 컨벌루션의 미분 또는 백워드 연산이 트랜스포즈드 컨벌루션이라고 불리기도 한다."
446 | ]
447 | },
448 | {
449 | "cell_type": "markdown",
450 | "metadata": {},
451 | "source": [
452 | "이로써 패딩이 없고 스트라이드가 1인 경우 포워드 패스 컨벌루션과 그에 대한 미분이 트랜스포즈드 컨벌루션과 어떤 관계를 가지는지 알아보았다."
453 | ]
454 | },
455 | {
456 | "cell_type": "markdown",
457 | "metadata": {},
458 | "source": [
459 | "## padding이 있고, stride가 2이상인 경우\n",
460 | "\n",
461 | "이제 패딩과 스트라이드가 있는 일반적인 경우에 대해 CONV층을 미분해보고 이 미분 결과와 앞서 논의한 백워드 패스 컨벌루션, 필터를 적층시키는 방식 그리고 트랜스포즈드 컨벌루션이 모두 같은 결과를 주는지 확인해보기로 하자. 트랜스포즈드 컨벌루션을 위해 필터로 구성되는 행렬을 직접 만들기는 번거로우므로 `pytorch`에서 트랜스포즈드 컨벌루션을 수행하는 `conv_transpose2d`함수를 사용하기로 한다."
462 | ]
463 | },
464 | {
465 | "cell_type": "markdown",
466 | "metadata": {},
467 | "source": [
468 | "### padding=1, stride=1\n",
469 | "\n",
470 | "이전과 같은 입력과 필터가 있는 상황에서 입력에 패딩 1을 주는 상황으로 시작한다. \n",
471 | "\n",
472 | "
\n",
473 | "\n",
474 | "위 그림과 같은 상황으로 패딩이 영향을 미쳐 $\\mathbf{z}$는 4x4가 된다. $z_{kl}$은 식(1)과 거의 동일하나 패딩으로 인해 입력의 인덱스에 $-p$가 추가 된다. $p$는 주어진 패딩 숫자이다.\n",
475 | "\n",
476 | "$$\n",
477 | "z_{kl}= \\sum_{q}^{Q-1}\\sum_{r}^{R-1} w_{qr} I_{(k-p+q),(l-p+r)} \\tag{4}\n",
478 | "$$\n",
479 | "\n",
480 | "이전과 마찬가지로 $\\dfrac{\\partial C}{\\partial I_{ij}}$를 계산하기 위해 $\\dfrac{\\partial \\, z_{kl}}{\\partial \\, I_{ij}}$를 계산해야 한다.\n",
481 | "\n",
482 | "$$\n",
483 | "\\frac{\\partial \\, z_{kl}}{\\partial \\, I_{ij}} = \\frac{\\partial}{\\partial \\, I_{ij}} \\left( \\sum_{q=0}^{Q-1} \\sum_{r=0}^{R-1} w_{qr} I_{(k-p+q),(l-p+r)} \\right)\n",
484 | "$$"
485 | ]
486 | },
487 | {
488 | "cell_type": "markdown",
489 | "metadata": {},
490 | "source": [
491 | "위 미분식은 인덱스 $i$, $j$가 다음과 같은 경우를 제외하고는 모두 0이다.\n",
492 | "\n",
493 | "$$\n",
494 | "i=k-p+q \\\\\n",
495 | "j=l-p+r\n",
496 | "$$\n",
497 | "\n",
498 | "위 관계에 의해 $q$, $r$은 다음과 같다.\n",
499 | "\n",
500 | "$$\n",
501 | "q = i-k+p \\\\\n",
502 | "r = j-l+p\n",
503 | "$$\n",
504 | "\n",
505 | "$w$의 $q$,$r$ 인덱스를 위 식으로 바꾸면 $\\dfrac{\\partial \\, z_{kl}}{\\partial \\, I_{ij}}$는 다음처럼 된다.\n",
506 | "\n",
507 | "$$\n",
508 | "\\frac{\\partial \\, z_{kl}}{\\partial \\, I_{ij}} = w_{(i-k+p),(j-l+p)}\n",
509 | "$$\n",
510 | "\n",
511 | "최종적으로 다음과 같은 결과를 얻게 된다.\n",
512 | "\n",
513 | "$$\n",
514 | "\\frac{\\partial \\, C}{\\partial \\, I_{ij}} = \\sum_{k} \\sum_{l} w_{(i-k+p),(j-l+p)} \\delta_{kl} \\tag{5}\n",
515 | "$$\n",
516 | "\n",
517 | "식(5)를 식(3)과 비교하면 패딩이 있는 경우 백워드 패스 컨벌루션은 $\\delta$를 필터 $w$에 컨벌루션할 때 패딩을 하나 덜 주는 것이라는 것을 알 수 있다. 이런 상황은 $w$ 인덱스에 $+p$ 때문에 생긴 현상이다. 식(5)를 그림으로 나타내면 다음과 같다. $\\dfrac{\\partial \\, C}{\\partial \\, I_{00}}$을 계산하고 있는 상황이다. 필터를 오른쪽으로 이동하면서 컨벌루션을 진행하면 4x4인 결과를 얻을 수 있다.\n",
518 | "\n",
519 | "
\n"
520 | ]
521 | },
522 | {
523 | "cell_type": "markdown",
524 | "metadata": {},
525 | "source": [
526 | "그림을 보면 패딩 3을 주고 풀 컨벌루션하는 상태에서 필터가 오른쪽 아래 대각선으로 하나 더 내려와서 패딩 2만 주고 컨벌루션하는 상태임을 알 수 있다.\n",
527 | "\n",
528 | "이제 실험을 해보자. 이전 실험과 마찬가지로 포워드 패스를 진행한 후 `pytorch`에서 제공하는 `autograd.grad`를 이용하여 미분한다."
529 | ]
530 | },
531 | {
532 | "cell_type": "code",
533 | "execution_count": 9,
534 | "metadata": {},
535 | "outputs": [
536 | {
537 | "name": "stdout",
538 | "output_type": "stream",
539 | "text": [
540 | "z\n",
541 | "tensor([[[[-0.6838, 2.2917, -1.7890, -0.7573],\n",
542 | " [ 0.7184, -2.7135, 3.0137, -1.2195],\n",
543 | " [ 3.6879, -1.9462, -1.2523, 0.5662],\n",
544 | " [-1.4461, -0.1571, 0.5570, 0.6655]]]],\n",
545 | " grad_fn=)\n",
546 | "C\n",
547 | "tensor(7.5301, grad_fn=)\n",
548 | "dI by torch\n",
549 | "tensor([[[[-0.1527, -0.3709, -0.3277, -0.0539],\n",
550 | " [-0.3383, -0.0456, -0.4488, -0.4934],\n",
551 | " [-0.2962, -0.5721, -0.7154, -0.4871],\n",
552 | " [-0.2728, -0.2301, -0.1836, -0.0912]]]])\n"
553 | ]
554 | }
555 | ],
556 | "source": [
557 | "# 임의의 인풋과 필터에 대해서 포워드 패스를 수행한다.\n",
558 | "I = torch.randn(1 ,1, 4, 4, requires_grad=True)\n",
559 | "w = torch.randn(1, 1, 3, 3)\n",
560 | "z = F.conv2d(I, w, stride=1, padding=1) # 이번에는 패딩 1을 준다.\n",
561 | "C = (torch.sigmoid(z)).sum() # f(z) = sum (sigmoid(z)) 로 정의\n",
562 | "\n",
563 | "print('z')\n",
564 | "print(z)\n",
565 | "\n",
566 | "print('C')\n",
567 | "print(C)\n",
568 | "\n",
569 | "###########################################################\n",
570 | "# pytorch의 autograd를 이용해 일단 dI를 구한다.\n",
571 | "###########################################################\n",
572 | "dI_torch = torch.autograd.grad(C, I, torch.Tensor([1]), retain_graph=True)[0]\n",
573 | "print('dI by torch')\n",
574 | "print(dI_torch)"
575 | ]
576 | },
577 | {
578 | "cell_type": "markdown",
579 | "metadata": {},
580 | "source": [
581 | "두번째로 백워드 컨벌루션을 실행해서 결과를 `dI_torch`하고 비교해보자. 백워드 컨벌루션할때 전술한것 처럼 패딩을 2로 준다."
582 | ]
583 | },
584 | {
585 | "cell_type": "code",
586 | "execution_count": 10,
587 | "metadata": {},
588 | "outputs": [
589 | {
590 | "name": "stdout",
591 | "output_type": "stream",
592 | "text": [
593 | "delta\n",
594 | "tensor([[[[0.2229, 0.0834, 0.1227, 0.2173],\n",
595 | " [0.2203, 0.0583, 0.0446, 0.1760],\n",
596 | " [0.0238, 0.1094, 0.1729, 0.2310],\n",
597 | " [0.1543, 0.2485, 0.2316, 0.2242]]]])\n",
598 | "delta flip\n",
599 | "tensor([[[[0.2242, 0.2316, 0.2485, 0.1543],\n",
600 | " [0.2310, 0.1729, 0.1094, 0.0238],\n",
601 | " [0.1760, 0.0446, 0.0583, 0.2203],\n",
602 | " [0.2173, 0.1227, 0.0834, 0.2229]]]])\n",
603 | "dI\n",
604 | "tensor([[[[-0.1527, -0.3709, -0.3277, -0.0539],\n",
605 | " [-0.3383, -0.0456, -0.4488, -0.4934],\n",
606 | " [-0.2962, -0.5721, -0.7154, -0.4871],\n",
607 | " [-0.2728, -0.2301, -0.1836, -0.0912]]]])\n"
608 | ]
609 | }
610 | ],
611 | "source": [
612 | "##########################################################\n",
613 | "# eq(5)로 dI를 구한다.\n",
614 | "##########################################################\n",
615 | "# delta = dC/dz를 구한다. \n",
616 | "delta = torch.autograd.grad(C, z, torch.Tensor([1]), retain_graph=True)[0]\n",
617 | "print('delta')\n",
618 | "print(delta)\n",
619 | "\n",
620 | "# delta를 180도 돌리고\n",
621 | "delta_flip = torch.flip(delta, [2, 3])\n",
622 | "print('delta flip')\n",
623 | "print(delta_flip)\n",
624 | "\n",
625 | "# w에 패딩을 주고 컨벌루션한다.\n",
626 | "# full conv를 위해 패딩 3을 주어야하나 \n",
627 | "# 포워드 패스 컨벌루션에서 패딩이 있었기 때문에\n",
628 | "# 백워드 패스에서는 패딩을 2만 준다.\n",
629 | "# 즉 delta_flip이 오른쪽 아래 대각선 방향으로 내려온다.\n",
630 | "print('dI')\n",
631 | "dI = F.conv2d(w, delta_flip, padding=2)\n",
632 | "print(dI)\n",
633 | "\n",
634 | "# dI_torch와 dI는 정확히 일치한다."
635 | ]
636 | },
637 | {
638 | "cell_type": "markdown",
639 | "metadata": {},
640 | "source": [
641 | "세번째로 필터를 적층시키는 방식으로 계산한 결과를 살펴보자. 포워드 패스 컨벌루션에 패딩 1이 있으므로 함수를 호출할 때 `padding=1`을 지정한다. 여기서 패딩의 의미에 대해서는 마지막에 생각해보도록 하자."
642 | ]
643 | },
644 | {
645 | "cell_type": "code",
646 | "execution_count": 11,
647 | "metadata": {},
648 | "outputs": [
649 | {
650 | "data": {
651 | "text/plain": [
652 | "tensor([[[[-0.1527, -0.3709, -0.3277, -0.0539],\n",
653 | " [-0.3383, -0.0456, -0.4488, -0.4934],\n",
654 | " [-0.2962, -0.5721, -0.7154, -0.4871],\n",
655 | " [-0.2728, -0.2301, -0.1836, -0.0912]]]])"
656 | ]
657 | },
658 | "execution_count": 11,
659 | "metadata": {},
660 | "output_type": "execute_result"
661 | }
662 | ],
663 | "source": [
664 | "conv_transpose_cs231n(delta, w, padding=1)"
665 | ]
666 | },
667 | {
668 | "cell_type": "markdown",
669 | "metadata": {},
670 | "source": [
671 | "마지막으로 `pytorch`에서 제공하는 `conv_transpose2d`함수를 사용해보자. `conv_transpose_cs231n`함수를 사용할 때 처럼 포워드 패스 컨벌루션에서 패딩 1이 있었으므로 여기서도 똑같은 조건 패딩 1을 주고 함수를 실행한다. "
672 | ]
673 | },
674 | {
675 | "cell_type": "code",
676 | "execution_count": 12,
677 | "metadata": {},
678 | "outputs": [
679 | {
680 | "data": {
681 | "text/plain": [
682 | "tensor([[[[-0.1527, -0.3709, -0.3277, -0.0539],\n",
683 | " [-0.3383, -0.0456, -0.4488, -0.4934],\n",
684 | " [-0.2962, -0.5721, -0.7154, -0.4871],\n",
685 | " [-0.2728, -0.2301, -0.1836, -0.0912]]]])"
686 | ]
687 | },
688 | "execution_count": 12,
689 | "metadata": {},
690 | "output_type": "execute_result"
691 | }
692 | ],
693 | "source": [
694 | "F.conv_transpose2d(delta, w, padding=1)"
695 | ]
696 | },
697 | {
698 | "cell_type": "markdown",
699 | "metadata": {},
700 | "source": [
701 | "우리가 알아본것 처럼 `pytorch`를 통한 미분, 백워드 패스 컨벌루션, 필터를 적층시키는 방법 그리고 트랜스포즈드 컨벌루션의 결과가 모두 동일하게 나온다. \n",
702 | "\n",
703 | "트랜스포즈드 컨벌루션에서 패딩 1을 주는 의미는 백워드 패스 컨벌루션 과정을 보면 알 수 있다. 백워드 패스 컨벌루션 과정을 유도하면서 포워드 패스 컨벌루션에서 주어진 패딩 만큼 필터를 오른쪽 아래 대각선 방향으로 이동시킨 것을 확인했다. 즉, 트랜스포즈드 컨벌루션에서 패딩은 포워드 패스 컨벌루션에서 패딩의 의미와는 정반대로 필터가 패딩만큼 오른쪽 아래 방향으로 내려오는 방식으로 동작하게 된다."
704 | ]
705 | },
706 | {
707 | "cell_type": "markdown",
708 | "metadata": {},
709 | "source": [
710 | "### padding=0, stride=2\n",
711 | "\n",
712 | "이번에는 스트라이드가 2이상인 경우를 알아보자. 입력 5x5에 필터 3x3을 스트라이드 2로 포워드 패스 컨벌루션하면 출력은 2x2가 된다.\n",
713 | "\n",
714 | "
\n"
715 | ]
716 | },
717 | {
718 | "cell_type": "markdown",
719 | "metadata": {},
720 | "source": [
721 | "스트라이드가 2이기 때문에 $z_{kl}$은 다음 식처럼 된다.\n",
722 | "\n",
723 | "$$\n",
724 | "z_{kl} = \\sum_{q=0}^{Q-1} \\sum_{r=0}^{R-1} w_{qr} I_{(k \\times s + q),(l \\times s + r)}\n",
725 | "$$\n",
726 | "\n",
727 | "위 식에서 $s$는 스타라이드 수이다. $z_{kl}$이 정의되었으므로 이전과 동일한 과정을 통해 CONV층을 미분해보자. 이전과 마찬가지로 $\\dfrac{\\partial C}{\\partial I_{ij}}$를 계산하기 위해 $\\dfrac{\\partial \\, z_{kl}}{\\partial \\, I_{ij}}$를 계산해야 한다.\n",
728 | "\n",
729 | "$$\n",
730 | "\\frac{\\partial \\, z_{kl}}{\\partial \\, I_{ij}} = \\frac{\\partial}{\\partial \\, I_{ij}} \\left( \\sum_{q=0}^{Q-1} \\sum_{r=0}^{R-1} w_{qr} I_{(k \\times s + q),(l \\times s + r)} \\right)\n",
731 | "$$\n"
732 | ]
733 | },
734 | {
735 | "cell_type": "markdown",
736 | "metadata": {},
737 | "source": [
738 | "위 미분식은 인덱스 $i$, $j$가 다음과 같은 경우를 제외하고는 모두 0이다.\n",
739 | "\n",
740 | "$$\n",
741 | "i=k \\times s+q \\\\\n",
742 | "j=l \\times s+r\n",
743 | "$$\n",
744 | "\n",
745 | "위 관계에 의해 $q$, $r$은 다음과 같다.\n",
746 | "\n",
747 | "$$\n",
748 | "q = i-k \\times s \\\\\n",
749 | "r = j-l \\times s\n",
750 | "$$\n",
751 | "\n",
752 | "$w$의 $q$,$r$ 인덱스를 위 식으로 바꾸면 $\\dfrac{\\partial \\, z_{kl}}{\\partial \\, I_{ij}}$는 다음처럼 된다.\n",
753 | "\n",
754 | "$$\n",
755 | "\\frac{\\partial \\, z_{kl}}{\\partial \\, I_{ij}} = w_{(i-k \\times s),(j-l \\times s)}\n",
756 | "$$\n",
757 | "\n",
758 | "최종적으로 다음과 같은 결과를 얻게 된다.\n",
759 | "\n",
760 | "$$\n",
761 | "\\frac{\\partial \\, C}{\\partial \\, I_{ij}} = \\sum_{k} \\sum_{l} w_{(i-k \\times s),(j-l \\times s)} \\delta_{kl} \\tag{6}\n",
762 | "$$\n",
763 | "\n",
764 | "식(6)이 어떤 형태로 컨벌루션되는지 확인하기 위해 $\\dfrac{\\partial \\, C}{\\partial \\, I_{00}}$을 직접 적어보자. $k$, $l$에 대한 시그마를 직접 모두 풀어 적어보면 다음과 같다.\n",
765 | "\n",
766 | "$$\n",
767 | "\\frac{\\partial \\, C}{\\partial \\, I_{00}} = w_{00} \\delta_{00} + w_{0,-2} \\delta_{01}+ w_{-2,0} \\delta_{10} + w_{-2,-2} \\delta_{11}\n",
768 | "$$\n",
769 | "\n",
770 | "위 연산을 그림으로 나타내면 아래와 같다.\n",
771 | "\n",
772 | "
\n",
773 | "\n",
774 | "상류층 그래디언트를 180도 돌려서 필터에 풀 컨벌루션하는 것은 이전과 동일하다. 하지만 상류층 그래디언트 사이에 빈 행과 열이 하나씩 들어가게 된 아주 묘한 상황이 발생한 것을 확인할 수 있다. 이런 현상은 결국 포워드 패스 컨벌루션에 스트라이드가 1에서 2로 증가한 것 때문에 발생한 것이다.\n",
775 | "\n",
776 | "많은 인터넷 문서에서 인용하고 있는 컨벌루션에 대한 [Dumoulin]의 애니메이션이 있다. 그 중 패딩이 없고, 스타라이드가 2인 경우 트랜스포즈드 컨벌루션을 나타낸 것이 아래 그림이다. 이제 그림에서 파란색 셀들이 서로 떨어져서 그려지는 것을 명확하게 이해할 수 있게 되었다. 아래 애니메이션은 지금 우리 문서와 완전히 동일한 상황을 묘사한 것이다. 이동하는 음영으로 표시된 3x3 행렬이 필터, 셀이 분리된 파란색 부분이 상류층 그래디언트이고 연산 결과는 5x5이다. \n",
777 | "\n",
778 | "\n",
779 | "
\n"
780 | ]
781 | },
782 | {
783 | "cell_type": "markdown",
784 | "metadata": {},
785 | "source": [
786 | "지금까지 내용을 실험으로 확인해보자."
787 | ]
788 | },
789 | {
790 | "cell_type": "code",
791 | "execution_count": 13,
792 | "metadata": {},
793 | "outputs": [
794 | {
795 | "name": "stdout",
796 | "output_type": "stream",
797 | "text": [
798 | "z\n",
799 | "tensor([[[[-0.9776, -2.0720],\n",
800 | " [ 3.6869, 0.3097]]]], grad_fn=)\n",
801 | "C\n",
802 | "tensor(1.9376, grad_fn=)\n",
803 | "dI by torch\n",
804 | "tensor([[[[-0.0213, 0.0978, 0.2229, 0.0489, 0.1168],\n",
805 | " [-0.0450, -0.1255, -0.1753, -0.0628, -0.0764],\n",
806 | " [ 0.0244, 0.1345, -0.2847, 0.1816, 0.1369],\n",
807 | " [-0.0054, -0.0151, -0.0737, -0.1543, -0.1878],\n",
808 | " [ 0.0032, 0.0147, -0.0029, 0.1509, -0.3688]]]])\n"
809 | ]
810 | }
811 | ],
812 | "source": [
813 | "# 임의의 인풋과 필터에 대해서 포워드 패스를 수행한다.\n",
814 | "I = torch.randn(1 ,1, 5, 5, requires_grad=True)\n",
815 | "w = torch.randn(1, 1, 3, 3)\n",
816 | "z = F.conv2d(I, w, stride=2, padding=0) # 이번에는 패딩없이 스트라이드 2만 준다.\n",
817 | "C = (torch.sigmoid(z)).sum() # f(z) = sum (sigmoid(z)) 로 정의\n",
818 | "\n",
819 | "print('z')\n",
820 | "print(z)\n",
821 | "\n",
822 | "print('C')\n",
823 | "print(C)\n",
824 | "\n",
825 | "###########################################################\n",
826 | "# pytorch의 autograd를 이용해 일단 dI를 구한다.\n",
827 | "###########################################################\n",
828 | "dI_torch = torch.autograd.grad(C, I, torch.Tensor([1]), retain_graph=True)[0]\n",
829 | "print('dI by torch')\n",
830 | "print(dI_torch)"
831 | ]
832 | },
833 | {
834 | "cell_type": "code",
835 | "execution_count": 14,
836 | "metadata": {},
837 | "outputs": [
838 | {
839 | "name": "stdout",
840 | "output_type": "stream",
841 | "text": [
842 | "delta\n",
843 | "tensor([[[[0.1986, 0.0993],\n",
844 | " [0.0238, 0.2441]]]])\n",
845 | "delta stride\n",
846 | "tensor([[[[0.1986, 0.0000, 0.0993],\n",
847 | " [0.0000, 0.0000, 0.0000],\n",
848 | " [0.0238, 0.0000, 0.2441]]]])\n",
849 | "delta stride flip\n",
850 | "tensor([[[[0.2441, 0.0000, 0.0238],\n",
851 | " [0.0000, 0.0000, 0.0000],\n",
852 | " [0.0993, 0.0000, 0.1986]]]])\n",
853 | "dI\n",
854 | "tensor([[[[-0.0213, 0.0978, 0.2229, 0.0489, 0.1168],\n",
855 | " [-0.0450, -0.1255, -0.1753, -0.0628, -0.0764],\n",
856 | " [ 0.0244, 0.1345, -0.2847, 0.1816, 0.1369],\n",
857 | " [-0.0054, -0.0151, -0.0737, -0.1543, -0.1878],\n",
858 | " [ 0.0032, 0.0147, -0.0029, 0.1509, -0.3688]]]])\n"
859 | ]
860 | }
861 | ],
862 | "source": [
863 | "##########################################################\n",
864 | "# eq(6)로 dI를 구한다.\n",
865 | "##########################################################\n",
866 | "# delta = dC/dz를 구한다. \n",
867 | "delta = torch.autograd.grad(C, z, torch.Tensor([1]), retain_graph=True)[0]\n",
868 | "print('delta')\n",
869 | "print(delta)\n",
870 | "\n",
871 | "# delta에 포워드 패스에 있던 stride를 반영한다.\n",
872 | "delta_stride = torch.zeros((1, 1, 3, 3))\n",
873 | "delta_stride[0,0,0,0] = delta[0,0,0,0]\n",
874 | "delta_stride[0,0,0,2] = delta[0,0,0,1]\n",
875 | "delta_stride[0,0,2,0] = delta[0,0,1,0]\n",
876 | "delta_stride[0,0,2,2] = delta[0,0,1,1]\n",
877 | "print('delta stride')\n",
878 | "print(delta_stride)\n",
879 | "\n",
880 | "# delta를 180도 돌리고\n",
881 | "delta_stride_flip = torch.flip(delta_stride, [2, 3])\n",
882 | "print('delta stride flip')\n",
883 | "print(delta_stride_flip)\n",
884 | "\n",
885 | "# w에 패딩을 주고 컨벌루션한다. \n",
886 | "# full convolution하기 위해 padding=2로 준다.\n",
887 | "print('dI')\n",
888 | "dI = F.conv2d(w, delta_stride_flip, padding=2)\n",
889 | "print(dI)\n",
890 | "\n",
891 | "# dI_torch와 dI는 정확히 일치한다."
892 | ]
893 | },
894 | {
895 | "cell_type": "markdown",
896 | "metadata": {},
897 | "source": [
898 | "필터를 적층시키는 방법으로도 계산해보자. 특별한것은 없고 출력 텐서에서 필터를 적층시킬 위치를 결정할 때 스트라이드를 반영하도록 `stride=2`를 지정하면 된다."
899 | ]
900 | },
901 | {
902 | "cell_type": "code",
903 | "execution_count": 15,
904 | "metadata": {},
905 | "outputs": [
906 | {
907 | "data": {
908 | "text/plain": [
909 | "tensor([[[[-0.0213, 0.0978, 0.2229, 0.0489, 0.1168],\n",
910 | " [-0.0450, -0.1255, -0.1753, -0.0628, -0.0764],\n",
911 | " [ 0.0244, 0.1345, -0.2847, 0.1816, 0.1369],\n",
912 | " [-0.0054, -0.0151, -0.0737, -0.1543, -0.1878],\n",
913 | " [ 0.0032, 0.0147, -0.0029, 0.1509, -0.3688]]]])"
914 | ]
915 | },
916 | "execution_count": 15,
917 | "metadata": {},
918 | "output_type": "execute_result"
919 | }
920 | ],
921 | "source": [
922 | "conv_transpose_cs231n(delta, w, stride=2)"
923 | ]
924 | },
925 | {
926 | "cell_type": "markdown",
927 | "metadata": {},
928 | "source": [
929 | "계산 결과 동일한 텐서를 얻을 수 있다. 이제 마지막으로 `pytorch`에서 지원하는 트랜스포즈드 컨벌루션을 실행해서 결과가 동일한지 확인해보자."
930 | ]
931 | },
932 | {
933 | "cell_type": "code",
934 | "execution_count": 16,
935 | "metadata": {},
936 | "outputs": [
937 | {
938 | "data": {
939 | "text/plain": [
940 | "tensor([[[[-0.0213, 0.0978, 0.2229, 0.0489, 0.1168],\n",
941 | " [-0.0450, -0.1255, -0.1753, -0.0628, -0.0764],\n",
942 | " [ 0.0244, 0.1345, -0.2847, 0.1816, 0.1369],\n",
943 | " [-0.0054, -0.0151, -0.0737, -0.1543, -0.1878],\n",
944 | " [ 0.0032, 0.0147, -0.0029, 0.1509, -0.3688]]]])"
945 | ]
946 | },
947 | "execution_count": 16,
948 | "metadata": {},
949 | "output_type": "execute_result"
950 | }
951 | ],
952 | "source": [
953 | "F.conv_transpose2d(delta, w, stride=2)"
954 | ]
955 | },
956 | {
957 | "cell_type": "markdown",
958 | "metadata": {},
959 | "source": [
960 | "예상대로 결과는 동일하다."
961 | ]
962 | },
963 | {
964 | "cell_type": "markdown",
965 | "metadata": {},
966 | "source": [
967 | "### padding=1, stride=2\n",
968 | "\n",
969 | "마지막으로 패딩 1이상, 스트라이드 2이상인 경우에 대해서 알아보자. 입력 4x4에 패딩 1을 주고 3x3 필터를 사용하여 스트라이드 2로 포워드 패스 컨벌루션하면 출력은 2x2가 된다. 아래 그림이 이런 상황을 보여주고 있다."
970 | ]
971 | },
972 | {
973 | "cell_type": "markdown",
974 | "metadata": {},
975 | "source": [
976 | "\n",
977 | "
\n"
978 | ]
979 | },
980 | {
981 | "cell_type": "markdown",
982 | "metadata": {},
983 | "source": [
984 | "여기서 눈여겨 봐야할 것은 패딩된 입력에서 마지막 열과 마지막 행(그림에서 회색 표시)은 연산에 참여하지 않는다는 점이다. 이제 이전 경우들과 마찬가지로 논리를 적용하자.\n",
985 | "\n",
986 | "패딩과 스트라이드가 동시에 있기 때문에 이전 과정에서 유도한 $z_{kl}$ 식의 인덱스를 함께 쓰면 $z_{kl}$은 다음 식처럼 된다.\n",
987 | "\n",
988 | "$$\n",
989 | "z_{kl} = \\sum_{q=0}^{Q-1} \\sum_{r=0}^{R-1} w_{qr} I_{(k \\times s - p + q),(l \\times s -p + r)}\n",
990 | "$$\n",
991 | "\n",
992 | "위 식에서 $p$는 패딩수, $s$는 스타라이드 수이다. $z_{kl}$이 정의되었으므로 이전과 동일한 과정을 통해 CONV층을 미분해보자. 이전과 마찬가지로 $\\dfrac{\\partial C}{\\partial I_{ij}}$를 계산하기 위해 $\\dfrac{\\partial \\, z_{kl}}{\\partial \\, I_{ij}}$를 계산해야 한다.\n",
993 | "\n",
994 | "$$\n",
995 | "\\frac{\\partial \\, z_{kl}}{\\partial \\, I_{ij}} = \\frac{\\partial}{\\partial \\, I_{ij}} \\left( \\sum_{q=0}^{Q-1} \\sum_{r=0}^{R-1} w_{qr} I_{(k \\times s - p + q),(l \\times s -p + r)} \\right)\n",
996 | "$$\n"
997 | ]
998 | },
999 | {
1000 | "cell_type": "markdown",
1001 | "metadata": {},
1002 | "source": [
1003 | "위 미분식은 인덱스 $i$, $j$가 다음과 같은 경우를 제외하고는 모두 0이다.\n",
1004 | "\n",
1005 | "$$\n",
1006 | "i=k \\times s-p+q \\\\\n",
1007 | "j=l \\times s-p+r\n",
1008 | "$$\n",
1009 | "\n",
1010 | "위 관계에 의해 $q$, $r$은 다음과 같다.\n",
1011 | "\n",
1012 | "$$\n",
1013 | "q = i-k \\times s + p \\\\\n",
1014 | "r = j-l \\times s + p\n",
1015 | "$$\n",
1016 | "\n",
1017 | "$w$의 $q$,$r$ 인덱스를 위 식으로 바꾸면 $\\dfrac{\\partial \\, z_{kl}}{\\partial \\, I_{ij}}$는 다음처럼 된다.\n",
1018 | "\n",
1019 | "$$\n",
1020 | "\\frac{\\partial \\, z_{kl}}{\\partial \\, I_{ij}} = w_{(i-k \\times s +p),(j-l \\times s+p)}\n",
1021 | "$$\n",
1022 | "\n",
1023 | "최종적으로 다음과 같은 결과를 얻게 된다.\n",
1024 | "\n",
1025 | "$$\n",
1026 | "\\frac{\\partial \\, C}{\\partial \\, I_{ij}} = \\sum_{k} \\sum_{l} w_{(i-k \\times s+p),(j-l \\times s+p)} \\delta_{kl} \\tag{7}\n",
1027 | "$$\n",
1028 | "\n",
1029 | "식(7)이 구체적으로 어떻게 작동하는지 알아보기 위해 $\\dfrac{\\partial \\, C}{\\partial \\, I_{00}}$, $\\dfrac{\\partial \\, C}{\\partial \\, I_{03}}$ 두 경우에 대해 직접 인덱스를 풀어 써보자.\n",
1030 | "\n",
1031 | "$\\dfrac{\\partial \\, C}{\\partial \\, I_{00}}$에 해당하는 식과 그림은 다음과 같다.\n",
1032 | "\n",
1033 | "$$\n",
1034 | "\\frac{\\partial \\, C}{\\partial \\, I_{00}} = w_{11}\\delta_{00} + w_{1,-1}\\delta_{01} + w_{-1,1}\\delta_{10} + w_{-1,-1}\\delta_{11}\n",
1035 | "$$\n",
1036 | "\n",
1037 | "
\n",
1038 | "\n",
1039 | "$\\dfrac{\\partial \\, C}{\\partial \\, I_{03}}$에 해당하는 식과 그림은 다음과 같다.\n",
1040 | "\n",
1041 | "$$\n",
1042 | "\\frac{\\partial \\, C}{\\partial \\, I_{03}} = w_{14}\\delta_{00} + w_{1,2}\\delta_{01} + w_{-1,4}\\delta_{10} + w_{-1,2}\\delta_{11}\n",
1043 | "$$\n",
1044 | "\n",
1045 | "
\n",
1046 | "\n",
1047 | "그림으로 부터 $\\delta$가 $w$에 컨벌루션될 때 좌우 패딩이 비대칭이라는 것을 알 수 있다. 앞서 주목했듯이 입력의 제일 오른쪽 열과 아래 행이 포워드 패스 컨벌루션 연산에 참여하지 않으면서 발생한 비대칭이 백워드 패스 컨벌루션에서도 반영된 것이다. 전체적인 백워드 패스 컨벌루션은 다음 애니메이션 처럼 동작한다.\n",
1048 | "\n",
1049 | "
\n"
1050 | ]
1051 | },
1052 | {
1053 | "cell_type": "markdown",
1054 | "metadata": {},
1055 | "source": [
1056 | "미분을 통해 얻어진 수식에 의해 비대칭적으로 동작하는 컨벌루션을 좌우 패딩이 대칭이 되게 하려면 $\\delta$에 모든 요소가 0인 행과 열을 하나씩 추가하고 $w$에 제로 패딩 2를 주고 컨벌루션하면 된다. 즉 아래 그림처럼 주황색 점선으로 제로패딩을 $\\delta$에 추가하고 백워드 패스 컨벌루션하면 결과는 이전과 똑같으면서 대칭적으로 패딩을 처리할 수 있게 된다.\n",
1057 | "\n",
1058 | "
\n"
1059 | ]
1060 | },
1061 | {
1062 | "cell_type": "markdown",
1063 | "metadata": {},
1064 | "source": [
1065 | "지금까지 논의를 실험해보기로 하자.\n",
1066 | "\n"
1067 | ]
1068 | },
1069 | {
1070 | "cell_type": "code",
1071 | "execution_count": 17,
1072 | "metadata": {},
1073 | "outputs": [
1074 | {
1075 | "name": "stdout",
1076 | "output_type": "stream",
1077 | "text": [
1078 | "z\n",
1079 | "tensor([[[[ 0.7538, -1.3294],\n",
1080 | " [ 2.6747, 0.2749]]]], grad_fn=)\n",
1081 | "C\n",
1082 | "tensor(2.3931, grad_fn=)\n",
1083 | "dI by torch\n",
1084 | "tensor([[[[-0.2310, -0.1451, -0.1756, 0.0134],\n",
1085 | " [ 0.2352, -0.7029, 0.3610, 0.0670],\n",
1086 | " [-0.0640, -0.2363, -0.2604, 0.0198],\n",
1087 | " [ 0.0500, -0.6766, 0.2031, -0.4016]]]])\n"
1088 | ]
1089 | }
1090 | ],
1091 | "source": [
1092 | "# 임의의 인풋과 필터에 대해서 포워드 패스를 수행한다.\n",
1093 | "I = torch.randn(1 ,1, 4, 4, requires_grad=True)\n",
1094 | "w = torch.randn(1, 1, 3, 3)\n",
1095 | "z = F.conv2d(I, w, stride=2, padding=1)\n",
1096 | "C = (torch.sigmoid(z)).sum() # f(z) = sum (sigmoid(z)) 로 정의\n",
1097 | "\n",
1098 | "print('z')\n",
1099 | "print(z)\n",
1100 | "\n",
1101 | "print('C')\n",
1102 | "print(C)\n",
1103 | "\n",
1104 | "###########################################################\n",
1105 | "# pytorch의 autograd를 이용해 일단 dI를 구한다.\n",
1106 | "###########################################################\n",
1107 | "dI_torch = torch.autograd.grad(C, I, torch.Tensor([1]), retain_graph=True)[0]\n",
1108 | "print('dI by torch')\n",
1109 | "print(dI_torch)"
1110 | ]
1111 | },
1112 | {
1113 | "cell_type": "markdown",
1114 | "metadata": {},
1115 | "source": [
1116 | "중간 단계 미분 $\\dfrac{\\partial \\, C}{\\partial \\, \\mathbf{z}}$를 구한다."
1117 | ]
1118 | },
1119 | {
1120 | "cell_type": "code",
1121 | "execution_count": 18,
1122 | "metadata": {},
1123 | "outputs": [
1124 | {
1125 | "name": "stdout",
1126 | "output_type": "stream",
1127 | "text": [
1128 | "delta\n",
1129 | "tensor([[[[0.2176, 0.1655],\n",
1130 | " [0.0603, 0.2453]]]])\n"
1131 | ]
1132 | }
1133 | ],
1134 | "source": [
1135 | "# delta = dC/dz를 구한다. \n",
1136 | "delta = torch.autograd.grad(C, z, torch.Tensor([1]), retain_graph=True)[0]\n",
1137 | "print('delta')\n",
1138 | "print(delta)"
1139 | ]
1140 | },
1141 | {
1142 | "cell_type": "markdown",
1143 | "metadata": {},
1144 | "source": [
1145 | "이제 백워드 패스 컨벌루션을 한다. 위 그림처럼 $\\delta$에 `stride`를 표현하고 추가로 제로 패딩을 넣고, 180도 돌린 다음 패딩 2로 컨벌루션 한다."
1146 | ]
1147 | },
1148 | {
1149 | "cell_type": "code",
1150 | "execution_count": 19,
1151 | "metadata": {},
1152 | "outputs": [
1153 | {
1154 | "name": "stdout",
1155 | "output_type": "stream",
1156 | "text": [
1157 | "delta stride\n",
1158 | "tensor([[[[0.2176, 0.0000, 0.1655, 0.0000],\n",
1159 | " [0.0000, 0.0000, 0.0000, 0.0000],\n",
1160 | " [0.0603, 0.0000, 0.2453, 0.0000],\n",
1161 | " [0.0000, 0.0000, 0.0000, 0.0000]]]])\n",
1162 | "delta stride flip\n",
1163 | "tensor([[[[0.0000, 0.0000, 0.0000, 0.0000],\n",
1164 | " [0.0000, 0.2453, 0.0000, 0.0603],\n",
1165 | " [0.0000, 0.0000, 0.0000, 0.0000],\n",
1166 | " [0.0000, 0.1655, 0.0000, 0.2176]]]])\n",
1167 | "dI\n",
1168 | "tensor([[[[-0.2310, -0.1451, -0.1756, 0.0134],\n",
1169 | " [ 0.2352, -0.7029, 0.3610, 0.0670],\n",
1170 | " [-0.0640, -0.2363, -0.2604, 0.0198],\n",
1171 | " [ 0.0500, -0.6766, 0.2031, -0.4016]]]])\n"
1172 | ]
1173 | }
1174 | ],
1175 | "source": [
1176 | "delta_stride = torch.zeros((1, 1, 4, 4))\n",
1177 | "delta_stride[0,0,0,0] = delta[0,0,0,0]\n",
1178 | "delta_stride[0,0,0,2] = delta[0,0,0,1]\n",
1179 | "delta_stride[0,0,2,0] = delta[0,0,1,0]\n",
1180 | "delta_stride[0,0,2,2] = delta[0,0,1,1]\n",
1181 | "print('delta stride')\n",
1182 | "print(delta_stride)\n",
1183 | "\n",
1184 | "delta_stride_flip = torch.flip(delta_stride, [2, 3])\n",
1185 | "print('delta stride flip')\n",
1186 | "print(delta_stride_flip)\n",
1187 | "\n",
1188 | "print('dI')\n",
1189 | "dI = F.conv2d(w, delta_stride_flip, padding=2)\n",
1190 | "print(dI)"
1191 | ]
1192 | },
1193 | {
1194 | "cell_type": "markdown",
1195 | "metadata": {},
1196 | "source": [
1197 | "예상대로 결과가 미분한것과 동일하다.\n",
1198 | "\n",
1199 | "수식에서 필터 $w$의 인덱스가 음수도 되기 때문에 수식과 그림을 일치시키기 위해 $w$를 고정하고 $\\delta$를 슬라이딩 시켰는데 우리가 조금 더 익숙한 형태인 $w$가 슬라이딩 하는 형태로 그림을 다시 그려보면 아래와 같다.\n",
1200 | "\n",
1201 | "
"
1202 | ]
1203 | },
1204 | {
1205 | "cell_type": "markdown",
1206 | "metadata": {},
1207 | "source": [
1208 | "위 그림도 완전히 동일한 결과를 주게되는데 포워드 패스시 패딩과 스트라이드가 백워드 패스 다시말해 트랜스포즈 컨벌루션에서 어떻게 작용하는지 조금 더 이해하기 쉽게 보여주고 있다.\n",
1209 | "\n",
1210 | "`stride=1`은 입력을 한칸씩 벌리고(도형 사아에 흰색 셀) `padding=1`은 의미 그대로 입력 주변부로 한칸씩 패딩(회색 셀)을 하는 것이 된다. 역시 이번에도 오른쪽과 아래쪽에 추가 패딩이 있어서 비대칭으로 패딩이 됨을 확인할 수 있는데 이 상태를 만들기 위해 추가 패딩을 `pytorch`에서 어떻게 하는지 곧 알아보기로 하겠다."
1211 | ]
1212 | },
1213 | {
1214 | "cell_type": "markdown",
1215 | "metadata": {},
1216 | "source": [
1217 | "제로 패딩을 하지 않고 스트라이드 2라는 조건만으로 컨벌루션하면 상류층 그래디언트 $\\delta$가 다음 그림처럼 바뀌는 것을 앞서 확인했었다.\n",
1218 | "\n",
1219 | "
\n",
1220 | "\n",
1221 | "이 상태에서 패딩1을 반영해서 컨벌루션하면 다음과 같은 결과가 나온다."
1222 | ]
1223 | },
1224 | {
1225 | "cell_type": "code",
1226 | "execution_count": 20,
1227 | "metadata": {},
1228 | "outputs": [
1229 | {
1230 | "name": "stdout",
1231 | "output_type": "stream",
1232 | "text": [
1233 | "delta stride\n",
1234 | "tensor([[[[0.2176, 0.0000, 0.1655],\n",
1235 | " [0.0000, 0.0000, 0.0000],\n",
1236 | " [0.0603, 0.0000, 0.2453]]]])\n",
1237 | "delta stride flip\n",
1238 | "tensor([[[[0.2453, 0.0000, 0.0603],\n",
1239 | " [0.0000, 0.0000, 0.0000],\n",
1240 | " [0.1655, 0.0000, 0.2176]]]])\n",
1241 | "dI\n",
1242 | "tensor([[[[-0.2310, -0.1451, -0.1756],\n",
1243 | " [ 0.2352, -0.7029, 0.3610],\n",
1244 | " [-0.0640, -0.2363, -0.2604]]]])\n"
1245 | ]
1246 | }
1247 | ],
1248 | "source": [
1249 | "delta_stride = torch.zeros((1, 1, 3, 3))\n",
1250 | "delta_stride[0,0,0,0] = delta[0,0,0,0]\n",
1251 | "delta_stride[0,0,0,2] = delta[0,0,0,1]\n",
1252 | "delta_stride[0,0,2,0] = delta[0,0,1,0]\n",
1253 | "delta_stride[0,0,2,2] = delta[0,0,1,1]\n",
1254 | "print('delta stride')\n",
1255 | "print(delta_stride)\n",
1256 | "\n",
1257 | "delta_stride_flip = torch.flip(delta_stride, [2, 3])\n",
1258 | "print('delta stride flip')\n",
1259 | "print(delta_stride_flip)\n",
1260 | "\n",
1261 | "print('dI')\n",
1262 | "dI = F.conv2d(w, delta_stride_flip, padding=1)\n",
1263 | "print(dI)"
1264 | ]
1265 | },
1266 | {
1267 | "cell_type": "markdown",
1268 | "metadata": {},
1269 | "source": [
1270 | "이전 실험 결과에서 마지막 행과 열이 없어지게 된다. `pytorch`에서 제공하는 `conv2d`함수에 패딩을 `padding=1`로 지정하였기 때문에 상하좌우에 대칭적으로 패딩이 들어가서 다음 그림처럼 컨벌루션되므로 이것은 당연한 결과이다. \n",
1271 | "\n",
1272 | "
\n",
1273 | "\n",
1274 | "이미 확인한것처럼 $\\dfrac{\\partial \\, C}{\\partial \\, I}$를 제대로 구하기 위해서는 $\\delta$에 마지막 열과 행을 추가로 제로 패딩 해주면 된다. 그래서 첫번째 실험에서 `delta_stride`를 4x4 제로 텐서로 초기화 했던 것이다. \n",
1275 | "\n",
1276 | "이제 스트라이드 2, 패딩 1이라는 조건으로 함수 `conv_transpose_cs231n`, `conv_transpose2d`들을 호출하면 어떤식으로 결과를 내놓는지 확인해보자."
1277 | ]
1278 | },
1279 | {
1280 | "cell_type": "code",
1281 | "execution_count": 21,
1282 | "metadata": {},
1283 | "outputs": [
1284 | {
1285 | "name": "stdout",
1286 | "output_type": "stream",
1287 | "text": [
1288 | "tensor([[[[-0.2310, -0.1451, -0.1756],\n",
1289 | " [ 0.2352, -0.7029, 0.3610],\n",
1290 | " [-0.0640, -0.2363, -0.2604]]]])\n",
1291 | "\n",
1292 | "\n",
1293 | "tensor([[[[-0.2310, -0.1451, -0.1756],\n",
1294 | " [ 0.2352, -0.7029, 0.3610],\n",
1295 | " [-0.0640, -0.2363, -0.2604]]]])\n"
1296 | ]
1297 | }
1298 | ],
1299 | "source": [
1300 | "print(conv_transpose_cs231n(delta, w, padding=1, stride=2))\n",
1301 | "print('\\n')\n",
1302 | "print(F.conv_transpose2d(delta, w, padding=1, stride=2))"
1303 | ]
1304 | },
1305 | {
1306 | "cell_type": "markdown",
1307 | "metadata": {},
1308 | "source": [
1309 | "바로 직전 실험에서 얻은 행과 열이 하나 작은 백워드 패스 컨벌루션 결과와 똑같다. \n",
1310 | "\n",
1311 | "$delta$를 슬라이딩 시키나 $w$를 슬라이딩 시키나 결국 추가로 오른쪽 열과 아래 행을 제로 패딩해야 하는데 `pytorch`에 이렇게 하는 옵션이 따로 마련되어 있다. `output_padding`이 그 역할을 하는 인자이다. `output_padding=1`을 주고 두 함수를 다시 호출해보자."
1312 | ]
1313 | },
1314 | {
1315 | "cell_type": "code",
1316 | "execution_count": 22,
1317 | "metadata": {},
1318 | "outputs": [
1319 | {
1320 | "name": "stdout",
1321 | "output_type": "stream",
1322 | "text": [
1323 | "tensor([[[[-0.2310, -0.1451, -0.1756, 0.0134],\n",
1324 | " [ 0.2352, -0.7029, 0.3610, 0.0670],\n",
1325 | " [-0.0640, -0.2363, -0.2604, 0.0198],\n",
1326 | " [ 0.0500, -0.6766, 0.2031, -0.4016]]]])\n",
1327 | "\n",
1328 | "\n",
1329 | "tensor([[[[-0.2310, -0.1451, -0.1756, 0.0134],\n",
1330 | " [ 0.2352, -0.7029, 0.3610, 0.0670],\n",
1331 | " [-0.0640, -0.2363, -0.2604, 0.0198],\n",
1332 | " [ 0.0500, -0.6766, 0.2031, -0.4016]]]])\n"
1333 | ]
1334 | }
1335 | ],
1336 | "source": [
1337 | "print(conv_transpose_cs231n(delta, w, padding=1, stride=2, output_padding=1))\n",
1338 | "print('\\n')\n",
1339 | "print(F.conv_transpose2d(delta, w, padding=1, stride=2, output_padding=1))"
1340 | ]
1341 | },
1342 | {
1343 | "cell_type": "markdown",
1344 | "metadata": {},
1345 | "source": [
1346 | "결과는 예상처럼 $\\delta$에 제로 패딩을 준 백워드 패스 컨벌루션과 일치하게 된다. 이것으로 포워드 패스 컨벌루션과 트랜스포즈드 컨벌루션의 관계를 모두 알아보았다."
1347 | ]
1348 | },
1349 | {
1350 | "cell_type": "markdown",
1351 | "metadata": {},
1352 | "source": [
1353 | "## 참고문헌\n",
1354 | "\n",
1355 | "1. [jo] CNN 역전파를 이해하는 가장 쉬운 방법The easiest way to understand CNN backpropagation, 조준우, https://metamath1.github.io/2017/01/23/CNN-backpropagation.html\n",
1356 | "\n",
1357 | "2. [cs231n] CS231n: Convolutional Neural Networks for Visual Recognition, http://cs231n.github.io\n",
1358 | "\n",
1359 | "3. [Shibuya] Up-sampling with Transposed Convolution, Naoki Shibuya, https://towardsdatascience.com/up-sampling-with-transposed-convolution-9ae4f2df52d0 , 번역글: 변성윤, https://zzsza.github.io/data/2018/06/25/upsampling-with-transposed-convolution/\n",
1360 | "\n",
1361 | "4. [Dumoulin] A guide to convolution arithmetic for deep learning Convolution arithmetic, Vincent Dumoulin, Francesco Visin\n",
1362 | ", https://github.com/vdumoulin/conv_arithmetic"
1363 | ]
1364 | },
1365 | {
1366 | "cell_type": "code",
1367 | "execution_count": 23,
1368 | "metadata": {},
1369 | "outputs": [
1370 | {
1371 | "data": {
1372 | "text/html": [
1373 | "\n",
1374 | "\n",
1375 | "\n",
1376 | "\n",
1377 | "\n",
1378 | "\n"
1404 | ],
1405 | "text/plain": [
1406 | ""
1407 | ]
1408 | },
1409 | "metadata": {},
1410 | "output_type": "display_data"
1411 | }
1412 | ],
1413 | "source": [
1414 | "%%html\n",
1415 | "\n",
1416 | "\n",
1417 | "\n",
1418 | "\n",
1419 | "\n",
1420 | ""
1446 | ]
1447 | },
1448 | {
1449 | "cell_type": "code",
1450 | "execution_count": null,
1451 | "metadata": {},
1452 | "outputs": [],
1453 | "source": []
1454 | }
1455 | ],
1456 | "metadata": {
1457 | "kernelspec": {
1458 | "display_name": "Python 3",
1459 | "language": "python",
1460 | "name": "python3"
1461 | },
1462 | "language_info": {
1463 | "codemirror_mode": {
1464 | "name": "ipython",
1465 | "version": 3
1466 | },
1467 | "file_extension": ".py",
1468 | "mimetype": "text/x-python",
1469 | "name": "python",
1470 | "nbconvert_exporter": "python",
1471 | "pygments_lexer": "ipython3",
1472 | "version": "3.7.3"
1473 | }
1474 | },
1475 | "nbformat": 4,
1476 | "nbformat_minor": 4
1477 | }
1478 |
--------------------------------------------------------------------------------
/EM/spiderman.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/EM/spiderman.jpg
--------------------------------------------------------------------------------
/GAN/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/GAN/1.png
--------------------------------------------------------------------------------
/GAN/10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/GAN/10.png
--------------------------------------------------------------------------------
/GAN/11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/GAN/11.png
--------------------------------------------------------------------------------
/GAN/12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/GAN/12.png
--------------------------------------------------------------------------------
/GAN/13.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/GAN/13.png
--------------------------------------------------------------------------------
/GAN/14.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/GAN/14.png
--------------------------------------------------------------------------------
/GAN/15.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/GAN/15.png
--------------------------------------------------------------------------------
/GAN/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/GAN/2.png
--------------------------------------------------------------------------------
/GAN/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/GAN/3.png
--------------------------------------------------------------------------------
/GAN/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/GAN/4.png
--------------------------------------------------------------------------------
/GAN/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/GAN/5.png
--------------------------------------------------------------------------------
/GAN/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/GAN/6.png
--------------------------------------------------------------------------------
/GAN/7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/GAN/7.png
--------------------------------------------------------------------------------
/GAN/8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/GAN/8.png
--------------------------------------------------------------------------------
/GAN/9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/GAN/9.png
--------------------------------------------------------------------------------
/GAN/h.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/GAN/h.png
--------------------------------------------------------------------------------
/PRML/Figure2.7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/PRML/Figure2.7.png
--------------------------------------------------------------------------------
/PRML/eq2.51.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/PRML/eq2.51.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ml-simple-works
2 |
3 | 머신 러닝 관련 작은 주제들을 선정해서 jupyter notebook으로 실습을 겸하여 내용을 정리하고 있습니다.
4 |
5 | **한국어로 찾아보기 힘든 내용**을 선택하여 가능한 자세하게 풀어적고 있으며
6 |
7 | 누구나 쉽게 읽을 수 있도록 작성하고 있습니다.
8 |
9 | ipynb 파일은 github에서 직접 보면 수식이 깨지고 예쁘게 나오지 않기 때문에
10 |
11 | nbviewer를 통해 보거나 구블로그 https://metamath1.github.io/, 신블로그 https://metamath1.github.io/blog 에서 확인하는 것을 추천합니다.
12 |
13 | 그리고 PC에서만 보시길 권해드립니다. 모바일에서는 수식이 보기 좋지 않아서......
14 |
15 |
16 | ## 지금까지 정리된 내용
17 |
18 | - 친절한 디퓨전 모델 2-2편: DDPM 실습 파이토치편
19 |
20 | - 친절한 디퓨전 모델 2-1편: DDPM 실습 텐서플로편
21 |
22 | - 천철한 디퓨전 모델 1편: DDPM 이론편
23 |
24 | - 친절한 영어-한국어 번역기 만들기A Gentle Introduction to Creating an English-to-Korean translator with Transformers
25 |
26 | - 친절한 그래디언트 부스팅A Gentle Introduction to Gradient Boosting
27 |
28 | - 진짜로(?) 주석 달린 트랜스포머 Really annotated transformers
29 |
30 | - PCA와 공분산 행렬의 고유벡터PCA and the Eigen Vectors of Covariance Matrix
31 |
32 | - 어텐션 쉽게 이해하기Attention is easy to understand.
33 |
34 | - PRML 부록 D 변분법 내용 정리 PRML/calculus-of-variations.ipynb
35 |
36 | - PRML 9장 EM 알고리즘 완전분석 Expectation-Maximization, EM/em_algorithm.ipynb
37 |
38 | - K 평균 군집화 K-means Clustering, EM/Kmeans.ipynb
39 |
40 | - 역전파 알고리즘 완전정복 A Step by Step Backpropagation, BP/bp.ipynb
41 |
42 | - 합성곱 신경망에서 컨벌루션과 트랜스포즈드 컨벌루션의 관계 Relationship between Convolution and Transposed Convolution in CNN, CNN/transconv_fullconv.ipynb
43 |
44 | - 서포트벡터머신을 위한 비선형 계획 문제의 쌍대정리Duality in Non-Linear Programming for Support Vector Machine, svm/duality.ipynb
45 |
46 | - 대칭행렬의 대각화와 특잇값 분해Symmetric matrix Diagonalization and Singular Value Decomposition, svd/svd.ipynb
47 |
48 | - 벡터, 행렬에 대한 미분Derivatives for vectors and matrices, fitting/matrix-derivative.ipynb
49 |
50 | - 다변수 가우시안 확률분포multi-variable normal에서 사전확률분포prior, 사후확률분포posterior, 조건부확률분포conditional와 주변확률분포marginal : Pattern Recognition and Machine Learning - Chap. 2-1 , PRML/prml-chap2.ipynb
51 |
52 | - 야코비안Jacobian과 치환적분, sampling/double-integral.ipynb
53 |
54 | - 베이즈정리와 정규분포의 곱, fitting/product-of-gaussian.ipynb
55 |
56 | - 150줄로 된 간단한 네트워크Simple network with 150 lines, simplenet.ipynb/simplenet.ipynb
57 |
58 | - Change of continuous random variable - PRML EX-1.4 보충, GAN/change_of_variable.ipynb
59 |
60 | - 나이브 베이즈Naive Bayse - 밑바닥부터 시작하는 데이터 과학Data Science from Scratch 보충 설명, naive/naive.ipynb
61 |
62 | - 퍼셉트론 테스트와 수렴정리Perceptron test and its convergence theorem, perceptron/perceptron.ipynb
63 |
64 | - GANs 기초 - 엔트로피, Keras로 구현한 1D GANs 그리고 Goodfellow 논문 겉핥기, GAN/GANs.ipynb
65 |
66 | - CNN 역전파를 이해하는 가장 쉬운 방법The easiest way to understand CNN backpropagation, https://metamath1.github.io/cnn/index.html
67 |
--------------------------------------------------------------------------------
/fitting/fig2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/fitting/fig2.png
--------------------------------------------------------------------------------
/fitting/fig3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/fitting/fig3.png
--------------------------------------------------------------------------------
/naive/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/naive/3.png
--------------------------------------------------------------------------------
/naive/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/naive/4.png
--------------------------------------------------------------------------------
/naive/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/naive/5.png
--------------------------------------------------------------------------------
/naive/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/naive/6.png
--------------------------------------------------------------------------------
/naive/7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/naive/7.png
--------------------------------------------------------------------------------
/naive/8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/naive/8.png
--------------------------------------------------------------------------------
/naive/naive.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "# 나이브 베이즈Naive Bayse - 베르누이Bernoulli와 다항분포Multinomial 나이브 베이즈 비교 \n",
8 | "\n",
9 | "\n",
10 | "2017.05.19 조준우 metamath@gmail.com
\n",
11 | "수정: 2019.07.14
\n",
12 | "
\n",
13 | "\n",
14 | "\n",
15 | "이 문서는 밑바닥부터 시작하는 데이터 과학[1]의 나이브 베이즈 단원에 대한 보충 설명이다. 책의 설명이 좀 부족하다 생각되어 보충 문서를 작성하게 되었다. 책에서 일반적인 나이브 베이즈 가정을 간결한 문장과 내용으로 잘 설명하고 있으나 베르누이 나이브 베이즈Bernoulli Naive Bayes에 대한 설명은 전혀 없는데 예제 코드는 이것을 구현하고 있다는데 있다. 다항분포 나이브 베이즈 모델과 베르누이 나이브 베이즈 모델을 잘 이해하기 위해서는 책에서 설명하는 나이브 베이즈 가정만으로는 부족하고 확률변수와 확률분포에 대한 이해가 조금 더 필요한데 여기에서 기본적으로 관련 내용을 간략하게 정리한 후 코드와 함께 살펴 보도록 한다. 책에서도 스팸메일 분류기를 예로 들어 설명하므로 여기서도 기본적으로 스펨 메일 분류를 예로 설명하도록 한다. \n"
16 | ]
17 | },
18 | {
19 | "cell_type": "markdown",
20 | "metadata": {},
21 | "source": [
22 | "## 1. 문제 정의\n",
23 | "\n",
24 | "어떤 문서(또는 메세지) $\\mathcal{D}$가 있을 때 이 문서가 스팸($S$)클래스에 속하는지 일반 문서($ \\neg S$)클래스에 속하는지 분류하는 문제가 있다고 하자. 그리고 우리에게 꽤 많은 단어들에 대해서 그 단어가 스팸메일에 나타날 확률과 일반메일에 나타날 확률에 대한 데이터를 가지고 있다고 하자. 예를 들면 \"Sale은 일반메일에서 3%확률로 나타나고, 스팸메일에서 60%확률로 나타난다\" 이런 데이터를 수만개 단어에 대해서 가지고 있다는 것이다. 기호로 적으면 $P(\\text{'Naive'}|S)$와 $P(\\text{'Naive'}|\\neg S)$를 데이터로 가지고 있는 것이다. 우리가 풀고 싶은 문제는 $P(S\\,|\\,\\mathcal{D})$ 와 $P(\\neg S\\,|\\,\\mathcal{D})$중 어느쪽이 더 큰가 하는 문제이다. $P(S\\,|\\,\\mathcal{D})$ 는 문서 $\\mathcal{D}$가 주어졌다는 가정하에서 그 문서가 스팸일 확률 즉 조건부 확률을 나타낸다. $P(\\neg S\\,|\\,\\mathcal{D})$는 문서 $\\mathcal{D}$가 주어졌다는 가정하에서 그 문서가 스팸이 아닐 확률을 나타낸다.\n",
25 | "해당 문제를 해결하기 위해 주어진 도큐먼트로 부터 판단에 사용할 특징feature를 추출해야 하는데 이때 어떤 확률분포를 사용하는지에 따라 특징벡터가 달라지게 된다. 일단 그 내용은 잠시뒤로 미루고 지금은 $\\mathcal{D}$로 부터 단어를 추출하여 특징 벡터 $\\mathbf{x}=(x_{1}, x_{2}, ... , x_{n})$가 준비되었다하고 이야기를 계속 하자. 그럼 특징벡터 $\\mathbf{x}$가 관찰되었을 때 그 벡터가 $S$에 속할 확률은 베이즈정리를 사용해서 아래와 같이 쓸 수 있다.\n",
26 | "\n",
27 | "$$ P(S\\,|\\,x_{1}, x_{2}, ... , x_{n}) = \\frac{P(x_{1}, x_{2}, ... , x_{n}\\,|\\,S)\\,P(S)}{P(x_{1}, x_{2}, ... , x_{n})}$$\n",
28 | "\n",
29 | "이때 특징벡터의 각 요소가 조건부 독립이라는 가정을 하면, 다시 말해 문서 $d$가 스팸이라는 가정하에 'love'이라는 단어가 나올 확률은 'partner'라는 단어가 나올 확률과 아무 상관이 없다고 가정하면 (실제로는 상관이 있을것이다. 그래서 이런 가정을 책[1]에서 '말도 안되는 가정'이라고 이야기한다.) 다음과 같이 다시 쓸 수 있다.\n",
30 | "\n",
31 | "$$ P(S\\,|\\,x_{1}, x_{2}, ... , x_{n}) = \\frac{P(x_{1}\\,|\\,S)\\,P(x_{2}\\,|\\,S) \\cdots P(x_{n}\\,|\\,S)\\,P(S)}{P(x_{1})\\,P(x_{2}) \\cdots P(x_{n})}$$\n",
32 | "\n",
33 | "조건부 독립가정때문에 이제 $P(S\\,|\\, \\mathbf{x})$와 $P( \\neg S\\,|\\, \\mathbf{x})$ 를 구하기 위해서는 $P(x_{1}\\,|\\,S)$, $P(x_{2}\\,|\\,S)$같은 것들을 알면되게 되었다. 그런데 이미 우리는 이런 데이터를 엄청 많이 가지고 있다고 했다. 분모는 확률들의 곱이라 양수 인데 우리는 $P(S\\,|\\, \\mathbf{x})$와 $P( \\neg S\\,|\\, \\mathbf{x})$의 크기 비교를 할것이므로 계산하지 않아도 무방하다. 즉 다음과 같다.\n",
34 | "\n",
35 | "$$ P(S\\,|\\,x_{1}, x_{2}, ... , x_{n}) \\propto P(x_{1}\\,|\\,S)\\,P(x_{2}\\,|\\,S) \\cdots P(x_{n}\\,|\\,S)\\,P(S)$$\n",
36 | "\n",
37 | "따라서 문서로부터 적절히 특징벡터를 추출하는 방법, 기존 데이터로 부터 $P(S\\,|\\, \\mathbf{x})$와 $P( \\neg S\\,|\\, \\mathbf{x})$를 계산하는 방법, 전체 문서에서 스팸과 스팸이 아닌 문서가 차지하는 비율인 사전확률 $P(S)$, $P(\\neg S)$을 구하는 방법을 알면 문제를 해결 할 수 있게 된다.\n"
38 | ]
39 | },
40 | {
41 | "cell_type": "markdown",
42 | "metadata": {},
43 | "source": [
44 | "## 2. 실습\n"
45 | ]
46 | },
47 | {
48 | "cell_type": "code",
49 | "execution_count": 1,
50 | "metadata": {
51 | "collapsed": true
52 | },
53 | "outputs": [],
54 | "source": [
55 | "import os, math, random, re, glob\n",
56 | "from collections import Counter, defaultdict"
57 | ]
58 | },
59 | {
60 | "cell_type": "markdown",
61 | "metadata": {},
62 | "source": [
63 | "## 2.1.보조 함수 정의 \n",
64 | "\n",
65 | "우선 실습에 필요한 보조함수를 정의한다. tokenize 함수는 하나의 도큐먼트(메세지)를 단어별로 쪼갠 다음 중복을 제거하여 되돌리고, tokenize2는 단어별로 쪼갠 다음 중복 단어가 있다면 그대로 허용한채 되돌린다. split_data는 주어진 기존 데이터(학습데이터)를 학습용과 테스트용으로 분리하여 되돌린다.\n",
66 | "(여기 사용된 코드들은 \"밑바닥부터 시작하는 데이터 과학\"의 코드를 대부분 이용하고 다항분포 나이브 베이즈 모델과 베르누이 나이브 베이즈 모델 구분하여 구현하기 위해 약간의 코드를 수정 추가했다.)\n"
67 | ]
68 | },
69 | {
70 | "cell_type": "code",
71 | "execution_count": 2,
72 | "metadata": {
73 | "collapsed": true
74 | },
75 | "outputs": [],
76 | "source": [
77 | "def split_data(data, prob):\n",
78 | " \"\"\"\n",
79 | " split data into fractions [prob, 1 - prob]\n",
80 | " \"\"\"\n",
81 | " results = [], []\n",
82 | " for row in data:\n",
83 | " results[0 if random.random() < prob else 1].append(row)\n",
84 | " return results\n",
85 | "\n",
86 | "def tokenize(message):\n",
87 | " \"\"\"\n",
88 | " 주어진 메세지를 단어 단위로 자른 집합을 되돌림(단어의 중복제거)\n",
89 | " \"\"\"\n",
90 | " message = message.lower() # convert to lowercase\n",
91 | " all_words = re.findall(\"[a-z0-9']+\", message) # extract the words\n",
92 | " return set(all_words) # remove duplicates\n",
93 | "\n",
94 | "def tokenize2(message):\n",
95 | " \"\"\"\n",
96 | " 주어진 메세지를 단어 단위로 자른 집합을 되돌림(중복 단어 허용)\n",
97 | " \"\"\"\n",
98 | " message = message.lower() # convert to lowercase\n",
99 | " all_words = re.findall(\"[a-z0-9']+\", message) # extract the words\n",
100 | " return all_words # duplicates"
101 | ]
102 | },
103 | {
104 | "cell_type": "markdown",
105 | "metadata": {},
106 | "source": [
107 | "## 2.2. 데이터 로딩\n",
108 | "\n",
109 | "\"밑바닥부터 시작하는 데이터 과학\"의 예제 소스 저장소에서 학습에 사용할 데이터 폴더 3개를 내려 받는다. 우선 3개 폴더에 있는 메일 데이터로 부터 제목을 추출하면 제목이 약 3423개가 추출이 된다. 이 3000여개의 도큐먼트에 대해 어떤 것이 스팸메일 제목인지 아닌지를 이미 다 알고 있다. 학습과 그 결과를 확인하기위해 이 3000여개의 데이터를 약 2500여개의 학습데이터와 800여개의 테스트 데이터로 나눈다.\n"
110 | ]
111 | },
112 | {
113 | "cell_type": "code",
114 | "execution_count": 3,
115 | "metadata": {},
116 | "outputs": [
117 | {
118 | "name": "stdout",
119 | "output_type": "stream",
120 | "text": [
121 | "전체 데이터 개수 : 3423 \n",
122 | "\n",
123 | "\n",
124 | "학습 데이터 개수 : 2547 \n",
125 | "학습데이터 중 스팸 메일 개수 : 364 \n",
126 | "학습데이터 중 일반 메일 개수 : 2183 \n",
127 | "\n",
128 | "\n",
129 | "테스트 데이터 개수 : 876 \n",
130 | "테스트데이터 중 스팸 메일 개수 : 139 \n",
131 | "테스트데이터 중 일반 메일 개수 : 737 \n"
132 | ]
133 | }
134 | ],
135 | "source": [
136 | "#############################################################\n",
137 | "# 데이터 만들기\n",
138 | "# data = [ (메일의 제목, 스팸인가?아닌가? True or False), (), (), ... ]\n",
139 | "#############################################################\n",
140 | "data = []\n",
141 | "\n",
142 | "#학습데이터는 스팸메일과 일반메일의 제목으로 한다.\n",
143 | "subject_regex = re.compile(r\"^Subject:\\s+\")\n",
144 | "\n",
145 | "# 데이터가 들어있는 폴더 3개를 돌면서...\n",
146 | "for fn in ['easy_ham' , 'hard_ham', 'spam']:\n",
147 | " is_spam = \"ham\" not in fn\n",
148 | "\n",
149 | " #디렉토리 내의 파일 리스트...\n",
150 | " for fn2 in glob.iglob(fn+\"/*\") :\n",
151 | " with open(fn2,'r',encoding='ISO-8859-1') as file:\n",
152 | " for line in file:\n",
153 | " if line.startswith(\"Subject:\"):\n",
154 | " subject = subject_regex.sub(\"\", line).strip()\n",
155 | " data.append((subject, is_spam))\n",
156 | "\n",
157 | "print(\"전체 데이터 개수 : {} \".format(len(data)))\n",
158 | "print(\"\\n\")\n",
159 | "\n",
160 | "#############################################################\n",
161 | "# data를 train_data, test_data로 나눔\n",
162 | "#############################################################\n",
163 | "random.seed(0)\n",
164 | "train_data, test_data = split_data(data, 0.75)\n",
165 | "\n",
166 | "print(\"학습 데이터 개수 : {} \".format(len(train_data)))\n",
167 | "\n",
168 | "#############################################################\n",
169 | "# 학습데이터 중에 스팸메일 개수와 일반메일 개수를 카운팅\n",
170 | "#############################################################\n",
171 | "num_spams = len([is_spam\n",
172 | " for message, is_spam in train_data\n",
173 | " if is_spam])\n",
174 | "num_non_spams = len(train_data) - num_spams\n",
175 | "\n",
176 | "print(\"학습데이터 중 스팸 메일 개수 : {} \".format(num_spams))\n",
177 | "print(\"학습데이터 중 일반 메일 개수 : {} \".format(num_non_spams))\n",
178 | "print(\"\\n\")\n",
179 | "\n",
180 | "print(\"테스트 데이터 개수 : {} \".format(len(test_data)))\n",
181 | "\n",
182 | "t_num_spams = len([is_spam\n",
183 | " for message, is_spam in test_data\n",
184 | " if is_spam])\n",
185 | "t_num_non_spams = len(test_data) - t_num_spams\n",
186 | "print(\"테스트데이터 중 스팸 메일 개수 : {} \".format(t_num_spams))\n",
187 | "print(\"테스트데이터 중 일반 메일 개수 : {} \".format(t_num_non_spams))"
188 | ]
189 | },
190 | {
191 | "cell_type": "markdown",
192 | "metadata": {},
193 | "source": [
194 | "## 2.3. 학습 \n",
195 | "\n",
196 | "### 2.3.1 사전확률 $P(S)$, $P(\\neg S)$ 구하기\n",
197 | "\n",
198 | " 앞서 적절히 특징벡터를 추출하는 방법, 기존 데이터로 부터 $P(S\\,|\\, \\mathbf{x})$와 $P( \\neg S\\,|\\, \\mathbf{x})$를 계산하는 방법, 사전확률 $P(S)$, $P(\\neg S)$을 구하는 방법을 알면 분류기를 만들 수 있다고 했다. 사전확률 $P(S)$, $P(\\neg S)$을 구하는 방법은 간단한다. 전체 도큐먼트 3000여개에 대해 이미 스팸, 햄(스펨이 아닌 경우를 가르킴)을 알고 았으므로 사전확률은 아래처럼 구할 수 았다.\n",
199 | "\n",
200 | "$$ P(S) = \\frac{\\text{#spam}}{\\text{#Document}} $$\n",
201 | "\n",
202 | "$$ P(\\neg S) = \\frac{\\text{#ham}}{\\text{#Document}} $$"
203 | ]
204 | },
205 | {
206 | "cell_type": "markdown",
207 | "metadata": {},
208 | "source": [
209 | "### 2.3.2 가능도Likelihood 구하기\n",
210 | "\n",
211 | "나이브베이즈 분류기의 학습은 주어진 데이터를 바탕으로 가능도 $P(w_{i}\\,|\\,S)$, $P(w_{i}\\,|\\, \\neg S)$를 계산하는 과정이다. 가능도는 학습데이터에 존재하는 모든 데이터에 대해서 구하게 된다. 문서의 앞에서 엄청 많은 단어에 대해 스펨클래스에서 그 단어가 나타날 확률을 이미 가지고 있다고 했는데 그 데이터를 만드는 작업을 하는 것이다. 일단 학습데이터에 존재하는 단어들을 사전에 담고 각 단어에 대해 스팸에서 출현한 횟수, 일반메일에서 출현한 횟수를 저장한다. 이때 베르누이 분포 모델을 쓸 경우 출현한 횟수가 많다 하더라도 1만 카운팅하고 다항분포 모델에서는 출현 수 만큼 그대로 카운팅한다. 이 자료를 단어장 $V$라 한다. 아래 코드가 있다. \n",
212 | "\n",
213 | "\n",
214 | "```python\n",
215 | "for message, is_spam in train_data:\n",
216 | " \"\"\"\n",
217 | " 베르누이 분포 모델의 경우 워드카운팅 \n",
218 | " 한 문서(message)에서 단어를 추출하고 중복을 제거한 후 그 유일 단어들에 대해서\n",
219 | " 한번씩만 +1 을 함 결국 word가 해당 문서에 나왔다, 안나왔다만 표시\n",
220 | " \"\"\"\n",
221 | " for word in tokenize(message): #중복을 제거한 단어들\n",
222 | " brn_counts[word][0 if is_spam else 1] += 1\n",
223 | " \n",
224 | " \"\"\"\n",
225 | " 다항 분포 모델의 경우 워드카운팅\n",
226 | " 이번에는 중복을 제거하지 않고 카운팅을 함 그래서 한 문서에 word가 10번나오면\n",
227 | " 10번 +1이 되서 결과적으로 해당 문서에 word가 몇번 나왔는지 저장하게 됨\n",
228 | " \"\"\"\n",
229 | " for word in tokenize2(message): #중복을 제거하지 않은 단어들\n",
230 | " mtnm_counts[word][0 if is_spam else 1] += 1 \n",
231 | "```\n",
232 | "\n",
233 | "`brn_counts`는 특정 단어가 스팸 클래스에 나타난 문서수와 햄 클래스에 나타난 문서수를 저장하게 되고 `mtnm_counts`는 특정 단어가 스팸 클래스에 나타난 횟수, 햄 클래스에 나타난 횟수를 저장하게 된다. 구체적으로 아래처럼 저장된다.\n",
234 | "\n",
235 | "```python\n",
236 | "brn_counts['naive'][0] # 스팸 클래스에서 naive란 단어가 출현하는 문서 수를 저장\n",
237 | "brn_counts['naive'][1] # 햄 클래스에서 naive란 단어가 출현하는 문서 수를 저장\n",
238 | "\n",
239 | "mtnm_counts['naive'][0] # 스팸 클래스에 있는 문서에서 naive 단어가 출현한 총 횟수\n",
240 | "mtnm_counts['naive'][1] # 햄 클래스에 있는 문서에서 naive 단어가 출현한 총 횟수\n",
241 | "\n",
242 | "# 이런 식으로 단어장의 모든 단어에 대해서 계산한다.\n",
243 | "```"
244 | ]
245 | },
246 | {
247 | "cell_type": "markdown",
248 | "metadata": {},
249 | "source": [
250 | "#### 2.3.2.1 베르누이 나이브 베이즈\n",
251 | "\n",
252 | "이제 문서를 특징벡터로 나타내는 방식을 알아볼 차례이다. $V$는 나이브베이즈 모델에서 사용하는 단어 $|V|$개가 들어 있는 단어장이라고 하자. 즉 $V$는 다음과 같은 배열이다.\n",
253 | "\n",
254 | "$$\n",
255 | "V = (w_1, w_2, \\dots , w_{|V|})\n",
256 | "$$\n",
257 | "\n",
258 | "여기서 $w_i$는 단어를 나타낸다. 이제 어떤 샘플 문서를 $\\mathcal{D}$라고 하자. 이 문서를 벡터화 해서 표시한 것을 특징 벡터라고 하는데 길이가 $|V|$인 벡터를 만들어 각 자리를 확률변수 $X_i$로 쓰기로 한다.\n",
259 | "\n",
260 | "$$\n",
261 | "\\mathcal{D} = (X_1, X_2, \\dots, X_{|V|})^{\\text{T}}\n",
262 | "$$\n",
263 | "\n",
264 | "$X_i$는 베르누이 확률변수로 0 또는 1을 가진다. 따라서 특징벡터화 된 문서 $\\mathcal{D}$의 각 성분은 다음과 같은 의미를 가진다.\n",
265 | "\n",
266 | "$$\n",
267 | "\\mathcal{D} = (D\\text{에 } w_1 \\text{이 있으면 1 없으면0}, D\\text{에 }w_2 \\text{가 있으면 1 없으면0}, \\dots, D\\text{에 }w_{|V|} \\text{가 있으면 1 없으면0})^{\\text{T}}\n",
268 | "$$\n",
269 | "\n",
270 | "예를들어 $\\mathcal{D} = (1, 0, 0, 1, 1, 0, 0, 1, 0, 0)^{\\text{T}}$라면 단어장 $V$이 단어중 1번, 4번, 5번, 8번 단어가 이 문서에 출현한 것이다. 이처럼 단어의 출현 여부를 따져서 문서를 벡터로 변환한다. 당연하게도 모든 문서는 길이 $|V|$짜리 벡터로 변환되게 된다. 정리하면 문서를 길이 $|V|$짜리 벡터로 만드는데 각 자리를 베르누이 확률변수로 만드는 것이다. 이런 모델을 베르누이 나이브 베이즈라 한다.\n",
271 | "\n",
272 | "이 특징벡터의 각 자리는 베르누이 확률변수이므로 각 변수에 대한 확률질량함수를 정의해야 한다. 베르누이 확률변수에 대한 확률질량함수는 다음처럼 정의된다.\n",
273 | "\n",
274 | "\n",
275 | "$$\n",
276 | "P(X=x) = p^x (1-p)^{1-x}\n",
277 | "$$\n",
278 | "\n",
279 | "위 정의를 사용해서 문서의 클래스가 $C_k$로 가정했을 때 우리가 정의한 확률변수 $X_i$의 확률 질량함수를 써보면 다음처럼 될 것이다. 즉 각 확률변수 $X_i$의 분포에 대한 파라미터 $p_i$를 결정해야 한다. \n",
280 | "\n",
281 | "\n",
282 | "$$ \n",
283 | "P(X_{i}=x_i \\,|\\, C_k) =p_i^{x_i}(1-p_i)^{1-x_i} \n",
284 | "$$\n",
285 | "\n",
286 | "식에서 $P(X_{i}=x_i \\,|\\, C_k)$의 의미는 문서의 클래스가 $C_k$라 했을 때 단어 $w_i$가 출현할 확률($x_i=1$인 경우) 또는 출현하지 않을 확률($x_i=0$인 경우)이 된다. 따라서 파리미터 $p_i$를 $P(w_{i}\\,|\\,C_{k})$로 고쳐쓰면\n",
287 | "\n",
288 | "$$ \n",
289 | "P(X_{i}=x_i \\,|\\, C_k) = P(w_{i}\\,|\\,C_{k})^{x_i}(1-P(w_{i}\\,|\\,C_{k}))^{1-x_i} \n",
290 | "$$\n",
291 | "\n",
292 | "그러면 문서 전체의 가능도는 나이브 베이즈 가정에 따라 다음처럼 된다.\n",
293 | "\n",
294 | "$$\n",
295 | "P(\\mathcal{D} \\,|\\, C_k) = \\prod_{i=1}^{|V|} P(w_{i}\\,|\\,C_{k})^{x_i}(1-P(w_{i}\\,|\\,C_{k}))^{1-x_i} \n",
296 | "$$\n",
297 | "\n",
298 | " 이제 $P(w_{i}\\,|\\,C_{k})$를 어떻게 구할 것인가 생각해봐야 한다. (우리 문제에서는 $k=2$인 경우로 $C_{1}=S$, $C_{2}= \\neg S $ 로 생각하면 됨) 특정 단어 $w_{i}$가 문서에 나타날 확률은 자연계에 분명 존재할 것이다. 우리가 그것을 정확히 모를 뿐이다. (이 세상에 존재하는 문서란 문서는 모두 가지고 와서 단어 $w_i$가 나타난 문서의 수를 세면 된다.) $w_{i}$가 제약이 없이 그냥 문서에 나타날 확률(엄밀히 말하면 문서라는것도 일종의 제약이다)과 특정 제약이 있는 경우, 예를 들어 스팸문서에 나타날 확률은 엄연히 다를 것이다. 우리는 메일중에서 스팸메일에 나타날 확률, 햄메일에 나타날 확률을 알면 되므로 $P(w_{i}\\,|\\,S)$, $P(w_{i}\\,|\\,\\neg S)$를 알고 싶은 것이다. 자연상태에 존재하는 이 확률분포를 정확히 알 방법은 없다. (세상에 존재하는 모든 이메일을 다 모아서 스팸과 햄으로 분류하고 그 중 $w_i$가 나타나는 문서수를 세면 된다.) 따라서 우리가 가지고 있는 데이터를 바탕으로 근사된 $\\hat{P}(w_{i}\\,|\\,S)$, $\\hat{P}(w_{i}\\,|\\,\\neg S)$ 구하여야 한다.\n",
299 | "\n",
300 | "베르누이 모델에서 단어장 $V$ 중 특정 단어 $w_i$가 특정 문서 클래스 $C_k$에 나타날 확률 $P(w_i \\,|\\, C_k)$는\n",
301 | "\n",
302 | "
\n",
303 | "\n",
304 | "로 계산한다.\n",
305 | "\n",
306 | "예를 들어 총 10개의 문서가 있는 C라는 클래스에서 naive라는 단어가 1번 문서에 3번, 2번 문서에 5번 나오고 3번~10번 문서에는 나오지 않은 상황이라면 \n",
307 | "베르누이 모델의 경우 $\\hat{P}(\\text{'naive'}|C) = \\frac{2}{10}$이 된다."
308 | ]
309 | },
310 | {
311 | "cell_type": "markdown",
312 | "metadata": {},
313 | "source": [
314 | "#### 2.3.2.2 다항분포 나이브 베이즈\n",
315 | "\n",
316 | "이번에는 다항분포 나이브 베이즈를 알아보자. 먼저 베르누이 확률분포를 일반화한 멀티누이multinoulli 확률분포에 대해 간략히 알아보기로 하자. 이 분포는 카테고리categorical 확률분포라고도 한다.\n",
317 | "\n",
318 | "베르누이 확률변수는 0 또는 1을 가지는 이진 변수였다. 즉 선택 가능한 항목이 2개인 셈이다. 이를 일반화 하여 선택가능한 항목이 여러개가 되면 어떻게 될까? 예를 들어 항목이 6개라면 확률변수는 1~6까지 값을 가지게 될 것이다. 이 때 확률변수가 가지는 값을 정수로 쓰지 않고 많은 경우 원 핫 인코딩 벡터로 표시하게 된다. $X=4$이면 다음처럼 표시하는 것이다.\n",
319 | "\n",
320 | "$$X = (0,0,0,1,0,0)^{\\text{T}}$$\n",
321 | "\n",
322 | "베르누이 확률변수의 예로 동전던지기를 들 수 있듯이 멀티누이 확률변수의 예는 주사위 던지기를 들 수 있겠다. 주사위를 던져서 나오는 눈 수를 값으로 가지는 확률변수 $X$는 멀티누이 확률변수인 것이다. 멀티누이 확률 변수는 어느 한 자리만 1이고 나머지는 0인 벡터로 표시할 수 있다. 그래서 각 자리가 1이 될 확률 $p_i$를 정해서 다음처럼 멀티누이 확률변수의 확률질량함수를 나타낼 수 있다.\n",
323 | "\n",
324 | "$$\n",
325 | "P(X = x) = \\prod_{i=1}^K p_i^{x_i}\n",
326 | "$$\n",
327 | "\n",
328 | "여기서 $K$는 멀티누이 확률변수 벡터의 길이, 즉 항목 수이다. 이 때 $p_i$는 각 자리수가 1이 될 확률이므로 $\\sum_{i=1}^K p_i = 1$이 되어야 한다. 주사위 각 눈이 나올 확률을 모두 더하면 1이 되는 것과 같은 이야기다.\n",
329 | "\n",
330 | "\n",
331 | "이제 주사위를 10번 던진다고 해보자. 그래서 1이 3번, 2가 2번, 3이 1번, 4가 2번, 5가 2번, 6이 0번 나왔다고 해보자. 이를 벡터로 나타내면 $(3, 2, 1, 2, 2, 0)^{\\text{T}}$가 될 것이다. 다시 한번 10번을 던지면 결과는 $(2, 2, 1, 2, 1, 2)^{\\text{T}}$일 수도 있다. 이런 벡터변수를 확률변수로 봤을 때 이 변수들이 따르는 분포가 있는데 이를 다항분포라 한다. 멀티누이 확률변수의 각 시행은 서로 독립이므로 다음처럼 다항분포의 확률질량함수는 멀티누이 확률질량함수를 곱하고 여기에 정규화 상수를 추가로 곱하는 형태가 된다.\n",
332 | "\n",
333 | "$$\n",
334 | "\\text{Mult}(x_1, x_2, \\dots, x_K) = \\frac{N!}{x_1! x_2! \\cdots x_K!} \\prod_{i=1}^K p_i^{x_i} \n",
335 | "$$\n",
336 | "\n",
337 | "이 경우는 $\\sum_{i=1}^K x_i = N$이라는 제약조건을 가지게 된다. 이 정도로 다항분포를 따르는 확률변수의 정의를 이야기하고 다시 원래 문제로 돌아가보자."
338 | ]
339 | },
340 | {
341 | "cell_type": "markdown",
342 | "metadata": {},
343 | "source": [
344 | "베르누이 나이브 베이즈에서 문서 $\\mathcal{D}$는 각 자리가 베르누이 확률변수인 길이 $|V|$인 특징벡터로 변환되었다. 이제 약간 시각을 다르게 하여 문서 $\\mathcal{D}$를 다항분포를 따르는 확률변수로 변환시켜보자. 벡터의 길이는 $|V|$로 동일하지만 각 요소 자리에 단어의 출현 여부를 0, 1로 기록하는 것이 아니라 출현 횟수를 기록하는 것이다. \n",
345 | "\n",
346 | "예를 들어 전체 단어 수가 10개인 경우 어떤 문서 $\\mathcal{D}$는 각 모델에 대해서 다음처럼 변환 될 수 있다.\n",
347 | "\n",
348 | "- 베르누이 나이브 베이즈: $\\mathcal{D}=(1, 0, 0, 1, 1, 0, 0, 1, 0, 0)^{\\text{T}}$\n",
349 | "\n",
350 | "- 다항분포 나이브 베이즈: $\\mathcal{D}=(2, 0, 0, 4, 1, 0, 0, 3, 0, 0)^{\\text{T}}$\n",
351 | "\n",
352 | "두 모델의 차이는 문서 $\\mathcal{D}$에 '1번, 4번, 5번, 8번 단어가 나왔다'로 해석하느냐 '1번 단어가 2번, 4번 단어가 4번, 5번 단어가 1번, 8번 단어가 3번 나왔다'로 해석하느냐의 차이이다."
353 | ]
354 | },
355 | {
356 | "cell_type": "markdown",
357 | "metadata": {},
358 | "source": [
359 | "베르누이 나이브 베이즈처럼 다항분포 나이브 베이즈의 경우도 각 자리에 부여될 파라미터 $P(w_{i}\\,|\\,C_{k})$를 모두 구해야 하는데 구체적인 계산법은 잠시 뒤로 미루고 일단 구해졌다고 가정하자. 그러면 문서 전체의 가능도는 다음과 같이 된다.\n",
360 | "\n",
361 | "$$\n",
362 | "P(\\mathcal{D} \\,|\\, C_k) = \\frac{N!}{ \\prod_{i=1}^{|V|} x_i !} \\prod_{i=1}^{|V|} P(w_{i}\\,|\\,C_{k})^{x_i}\n",
363 | "$$\n",
364 | "\n",
365 | "여기서 $N$은 $V$에 있는 모든 단어가 $D$에 나타난 총 횟수인데 각 샘플마다 모두 다르게 된다. \n",
366 | "\n",
367 | "지금까지 상황을 좀 쉬운 예로 다시한번 정리하고 이로부터 다항분포의 파라미터를 유도해보자. 우리가 주사위를 여러번 던져서 각 자리수가 나오는 횟수를 기록하면 이것이 다항분포를 따른다고 했었다. 이렇게 한 실험을 다음과 같은 행렬로 정리했다고 가정하자. 단 이 주사위는 각 눈이 나올 확률이 공평하게 $\\frac1 6$아닌 주사위다. \n",
368 | "\n",
369 | "$$\n",
370 | "\\mathbf{M} = \\begin{bmatrix}\n",
371 | "2 & 1 & 3 & 3 & 2 & 0 \\\\\n",
372 | "3 & 1 & 4 & 2 & 1 & 1 \\\\\n",
373 | "4 & 2 & 5 & 2 & 2 & 2 \n",
374 | "\\end{bmatrix}\n",
375 | "$$"
376 | ]
377 | },
378 | {
379 | "cell_type": "markdown",
380 | "metadata": {},
381 | "source": [
382 | "위 행렬에서 한 행은 주사위 여러번 던지는 실험의 결과를 정리한 것이다. 첫번째 실험은 총 11번 주사위를 던진 실험으로 그 결과가 1행에 표시되었다. 두번째 실험은 12번 주사위를 던졌고 그 결과를 2행에 표시했으며, 세번째 행은 17번 실험한 것을 나타낸다. 각 눈의 수는 행렬에 기록된 것처럼 나왔다 하자. 그러면 각 행은 다항분포 확률변수가 되며 이에 대한 각각의 확률값은 다음처럼 계산될 수 있다.\n",
383 | "\n",
384 | "$$\n",
385 | "\\begin{aligned}\n",
386 | "\\text{Mult}(x_1=2, x_2=1, x_3=3, x_4=3, x_5=2, x_6=0) = \\frac{11!}{2! \\times 1! \\times 3! \\times 3! \\times 2! \\times 0!} \\prod_{i=1}^6 p_i^{x_i} \\\\[5pt]\n",
387 | "\\text{Mult}(x_1=3, x_2=1, x_3=4, x_4=2, x_5=1, x_6=1) = \\frac{12!}{3! \\times 1! \\times 4! \\times 2! \\times 1! \\times 1!} \\prod_{i=1}^6 p_i^{x_i} \\\\[5pt]\n",
388 | "\\text{Mult}(x_1=4, x_2=2, x_3=5, x_4=2, x_5=2, x_6=2) = \\frac{20!}{4! \\times 2! \\times 5! \\times 2! \\times 2! \\times 2!} \\prod_{i=1}^6 p_i^{x_i}\n",
389 | "\\end{aligned}\n",
390 | "$$\n"
391 | ]
392 | },
393 | {
394 | "cell_type": "markdown",
395 | "metadata": {},
396 | "source": [
397 | "이제 각 눈이 나올 확률 $p_i$를 생각해보자. 상식적으로 생각해봐도 $p_1$은 전체 시도 횟수에 대해서 1이 나온 수가 차지하는 비율일 것이다. 실험을 총 40번했고 1은 9번 나왔으므로 $p_1=\\frac{9}{40}$이다. 나머지 파라미터도 모두 그렇게 계산된다. 다항분포 나이브 베이즈도 이것과 정확하게 동일한 상황을 문서에 적용하고 있다. \n",
398 | "\n",
399 | "위 $\\mathbf{M}$행렬을 그대로 문서에 적용해보자. 훈련 샘플로 사용되는 특정 문서 클래스에 대한 문서는 총 3개가 있는 경우가 될 것이다. 우리 문제에서 생각해보면 스팸 메일이 총 3개 있다고 생각해도 좋다. 단어장 $V$에 있는 전체 단어는 6개가 된다. 그리고 1번 문서는 1번에서 6번까지 단어가 각각 2번, 1번, 3번, 3번, 2번, 0번 등장한 것이다. \n",
400 | "\n",
401 | "이 상황에서 1번 단어 $w_1$이 현재 문서 클래스 $C_k$에 나타날 확률 $P(w_i \\,|\\, C_k)$을 주사위의 경우처럼 똑같이 생각해서 다음과 같이 구할 수 있을것이다."
402 | ]
403 | },
404 | {
405 | "cell_type": "markdown",
406 | "metadata": {},
407 | "source": [
408 | "
\n",
409 | "\n",
410 | "만약 1번 단어가 'naive'라는 단어였으면 다항분포 모델의 경우는 $\\hat{P}(\\text{'naive'}|C_k) = \\frac{9}{40} $이 된다.\n",
411 | "\n",
412 | "우리는 문서의 클래스에 대한 가능도 $P(\\mathbf{D} \\,|\\, C_k)$의 상대적 크기차만 비교할 것이므로 정규화 상수는 중요치 않고 다음 관계를 이용하게 된다.\n",
413 | "\n",
414 | "$$\n",
415 | "P(\\mathcal{D} \\,|\\, C_k) \\propto \\prod_{i=1}^{|V|} P(w_{i}\\,|\\,C_{k})^{x_i}\n",
416 | "$$\n",
417 | "\n",
418 | "이것으로 두가지 나이브 베이즈 모델에서 문서의 가능도를 구하기 위한 각 단어의 가능도를 구하는 방법을 상세히 알아보았다."
419 | ]
420 | },
421 | {
422 | "cell_type": "markdown",
423 | "metadata": {},
424 | "source": [
425 | "### 2.3.3 스무딩 적용\n",
426 | "\n",
427 | "훈련 세트에 있는 3888개의 단어에 대해 베르누이 모델을 위해 특정 단어가 나타나는 스팸메일의 수, 특정 단어가 나타나는 일반메일의 수, 다항분포 모델을 위해 그 단어가 스팸메일에 나타나는 출현 수와 일반메일에 나타나는 출현 수를 계산한다. 이 후 그 정보를 이용하여 방금 설명한것 처럼 $P(w_{i}\\,|\\,S)$, $P(w_{i}\\,|\\, \\neg S)$ 계산해서 저장을 하는데 이 때 라플라스 스무딩laplace smoothing을 적용해야 한다. 스무딩 적용 이유는 책[1]에 잘 나와있기 때문에 별도로 언급하지 않고 어떤 원리로 스무딩을 적용하는지 두가지 경우에 대해 나눠서 설명한다.\n",
428 | "\n",
429 | "#### 2.3.3.1 베르누이 나이브 베이즈\n",
430 | "\n",
431 | "스무딩의 목적이 가능도가 0이 되는 것을 방지하기 위함이라 분자에 적당한 수 k를 더해서 0이되는 것을 막는 것인데 그냥 분자에만 k를 더하면 안된다. 다시 한번 아래 그림을 보고 베르누이 분포 모델에서 가능도를 구하는 방법을 생각해보자.\n",
432 | "\n",
433 | "
\n",
434 | "\n",
435 | "전체 스팸메일 수가 분모에 가고 분자에 특정 단어를 포함하는 스팸메일 수가 온다. 문제는 어떤 특정 단어가 일반메일에만 나타나고 스팸메일에는 한번도 안나타나는 경우 가능도는 0이 되고 그 단어가 포함된 스팸메일은 절대로 스팸으로 분류되지 않는다는 것이다. 이런 현상을 막기위해 분자에 임의의 수 k를 더해준다. 그리고, 당연하게도 분모는 그 단어가 나타나는 스팸메일과 일반메일의 합이므로 일반메일 그룹에도 k를 더해준다. 따라서 베르누이 분포 모델의 경우 가능도는 아래와 같이 구한다. (결국 분모에는 2k가 더해짐)\n",
436 | "\n",
437 | "
\n"
438 | ]
439 | },
440 | {
441 | "cell_type": "markdown",
442 | "metadata": {},
443 | "source": [
444 | "#### 2.3.3.2 다항분포 나이브 베이즈\n",
445 | "\n",
446 | "반면 다항분포 모델에서는 가능도가 아래 그림처럼 단어의 출현수로 계산이 된다. 그래서 특정 단어가 스팸메일에 0번 출현했다면 (출현하지 않았다면) 같은 문제가 생기게 된다.\n",
447 | "\n",
448 | "
\n",
449 | "\n",
450 | "이때도 역시 분자에 k를 더해 k번 출현한 것으로 가정한다. 이는 우리가 모은 스팸메일 데이터 이전에 우리가 모으지 못한 어떤 스팸메일 군에서 k번 출현했을 것이다 라고 가정하는 것이다. 그렇다면 당연히 다른 단어들에도 k번 이미 출현했다고 가정해야 하므로 분모의 모든 단어의 출현수에 k를 더해 준다. 이 경우 분모에는 (k $\\times$ '단어장 단어 개수') 가 더해지게 된다.\n",
451 | "\n",
452 | "
\n",
453 | "\n",
454 | "아래 코드는 설명한 스무딩을 적용해서 가능도를 구하는 과정이다.\n",
455 | "\n",
456 | "```python\n",
457 | "\"\"\"\n",
458 | "베르누이 분포를 사용하는 모델의 경우\n",
459 | "spam : w가 포함된 스팸문서 수\n",
460 | "num_spam : 스팸문서 수\n",
461 | "분자에 k, 분모에 2k 더함\n",
462 | "결과 : [(단어1, P(단어1|S), P(단어1|ㄱS), ... (단어n, P(단어n|S), P(단어n|ㄱS)]\n",
463 | "\"\"\"\n",
464 | "k = 0.5 #smoothing factor\n",
465 | "brn_word_probs =[(w,\n",
466 | " (spam + k) / (num_spams + 2*k), \n",
467 | " (non_spam + k) / (num_non_spams + 2*k))\n",
468 | " for w, (spam, non_spam) in brn_counts.items()]\n",
469 | "\n",
470 | "\"\"\"\n",
471 | "다항 분포를 사용하는 모델의 경우\n",
472 | "spam : w의 스팸메일 출현 수\n",
473 | "Vfs : V에 있는 단어들이 스팸메일에 나타난 총 출현 수\n",
474 | "Vs : |V| 즉, 단어장에 유일단어 수\n",
475 | "분자에 k, 분모에 k*|V| 더함\n",
476 | "결과 : [(단어1, P(단어1|S), P(단어1|ㄱS), ... (단어n, P(단어n|S), P(단어n|ㄱS)]\n",
477 | "\"\"\"\n",
478 | "k = 0.1\n",
479 | "mtnm_word_probs =[(w,\n",
480 | " (spam + k) / (Vfs + k*Vs),\n",
481 | " (non_spam + k) / (Vfh + k*Vh))\n",
482 | " for w, (spam, non_spam) in mtnm_counts.items()]\n",
483 | "```\n",
484 | "\n",
485 | "코드 결과에서 확인할 수 있듯이 3888개의 단어에 대해 위 작업을 수행하였다."
486 | ]
487 | },
488 | {
489 | "cell_type": "code",
490 | "execution_count": 4,
491 | "metadata": {
492 | "scrolled": false
493 | },
494 | "outputs": [
495 | {
496 | "name": "stdout",
497 | "output_type": "stream",
498 | "text": [
499 | "클래스 사전확률\n",
500 | "P(S) : 0.142913 \n",
501 | "P(¬S) : 0.857087 \n",
502 | "\n",
503 | "\n",
504 | "스팸메일에 있는 유일 단어 총수 : 1023\n",
505 | "그 단어들의 총 출연수(중복포함) : 2288\n",
506 | "\n",
507 | "\n",
508 | "일반메일에 있는 유일 단어 총수 : 3289\n",
509 | "그 단어들의 총 출연수(중복포함) : 13294\n",
510 | "\n",
511 | "\n",
512 | "학습 단어장의 크기:3888\n"
513 | ]
514 | }
515 | ],
516 | "source": [
517 | "\"\"\"\n",
518 | "학습 목표 : P(w|S), P(w|¬S) 를 만든다. \n",
519 | "먼저 단어를 각 메일에서 나온 횟수를 카운트한다. \n",
520 | "counts := dict('word1':[스팸메일에 나온 수, 일반메일에 나온 수], 'word2':[스팸메일에 나온 수, 일반메일에 나온 수], ... )\n",
521 | "\"\"\"\n",
522 | "#############################################################\n",
523 | "# 사전확률 P(S), P(¬S) 를 계산 \n",
524 | "#############################################################\n",
525 | "prior_S = num_spams / (num_spams+num_non_spams)\n",
526 | "prior_non_S = num_non_spams / (num_spams+num_non_spams)\n",
527 | "\n",
528 | "print(\"클래스 사전확률\")\n",
529 | "print(\"P(S) : {:f} \".format(prior_S))\n",
530 | "print(\"P(¬S) : {:f} \".format(prior_non_S))\n",
531 | "print(\"\\n\")\n",
532 | "\n",
533 | "all_words = {'spam':[], 'ham':[]}\n",
534 | "brn_counts = defaultdict(lambda: [0, 0])\n",
535 | "mtnm_counts = defaultdict(lambda: [0, 0])\n",
536 | "for message, is_spam in train_data:\n",
537 | " for word in tokenize(message):\n",
538 | " brn_counts[word][0 if is_spam else 1] += 1\n",
539 | " \n",
540 | " for word in tokenize2(message):\n",
541 | " mtnm_counts[word][0 if is_spam else 1] += 1 \n",
542 | " if is_spam :\n",
543 | " all_words['spam'].append(word)\n",
544 | " else:\n",
545 | " all_words['ham'].append(word)\n",
546 | "\n",
547 | "Vfs = len(all_words['spam'])\n",
548 | "Vfh = len(all_words['ham'])\n",
549 | " \n",
550 | "Vs = len(set(all_words['spam']))\n",
551 | "Vh = len(set(all_words['ham']))\n",
552 | "\n",
553 | "print(\"스팸메일에 있는 유일 단어 총수 : {}\".format(Vs))\n",
554 | "print(\"그 단어들의 총 출연수(중복포함) : {}\".format(Vfs))\n",
555 | "print(\"\\n\")\n",
556 | "print(\"일반메일에 있는 유일 단어 총수 : {}\".format(Vh)) \n",
557 | "print(\"그 단어들의 총 출연수(중복포함) : {}\".format(Vfh))\n",
558 | "print(\"\\n\")\n",
559 | "\n",
560 | "#############################################################\n",
561 | "# 가능도 P(w|S), P(w|¬S) 를 계산 \n",
562 | "#############################################################\n",
563 | "\"\"\"\n",
564 | "turn the word_counts into a dictionary of triplets \n",
565 | "word_probs = {word1:(p(word1|S), p(word1|¬S)) , word2:(p(word2|S), p(word2|¬S)), ... }\n",
566 | "\"\"\"\n",
567 | "k = 0.1 #smoothing factor\n",
568 | "brn_word_probs =[(w,\n",
569 | " (spam + k) / (num_spams + 2 * k),\n",
570 | " (non_spam + k) / (num_non_spams + 2 * k))\n",
571 | " for w, (spam, non_spam) in brn_counts.items()]\n",
572 | "\n",
573 | "k = 0.1\n",
574 | "mtnm_word_probs =[(w,\n",
575 | " (spam + k) / (Vfs + k*Vs),\n",
576 | " (non_spam + k) / (Vfh + k*Vh))\n",
577 | " for w, (spam, non_spam) in mtnm_counts.items()]\n",
578 | "\n",
579 | "\n",
580 | "print(\"학습 단어장의 크기:{}\".format(len(brn_counts)))"
581 | ]
582 | },
583 | {
584 | "cell_type": "markdown",
585 | "metadata": {},
586 | "source": [
587 | "## 2.4. 분류\n",
588 | "\n",
589 | "주어진 메시지에 대한 스팸 여부를 판별한다. 학습데이터에 있는 모든 단어들에 대해 $P(w_{i}\\,|\\,S)$, $P(w_{i}\\,|\\, \\neg S)$를 계산해두었으므로 주어진 메세지를 단어로 분리한 후 특징 벡터로 만들고 모든 단어의 가능도 값을 곱한다. \n",
590 | "\n",
591 | "\n",
592 | "### 2.4.1 베르누이 나이브 베이즈\n",
593 | "\n",
594 | "#### 2.4.1.1 특징 벡터 만들기\n",
595 | "\n",
596 | "\n",
597 | "베르누이 분포 모델에서는 $V$의 단어가 주어진 $d$에 나왔는지 안나왔는지만 판단하므로 다음의 예와 같이 특징벡터를 만든다.\n",
598 | "\n",
599 | "```python\n",
600 | "V = [w1, w2, w3, w4, w5, w6, w7, w8] #학습데이터로 부터 추출된 단어장\n",
601 | "d = \"w2 w5 w2 w7 w5 w5\"\n",
602 | "x = [0, 1, 0, 0, 1, 0, 1, 0] \n",
603 | "```\n",
604 | "\n",
605 | "V의 각 단어가 d에 나왔는지 판단 확률변수 $X_1=0$, $X_2=1$, $X_3=0$, $X_4=0$, $X_5=1$, $X_6=0$, $X_7=1$, $X_8=0$ 처럼 된다.\n",
606 | "\n",
607 | "#### 2.4.1.2 가능도 계산\n",
608 | "\n",
609 | "이 후 아래 수식을 이용하여 가능도를 곱해 나간다.\n",
610 | "\n",
611 | "$$ \n",
612 | "\\begin{align}\n",
613 | "P(C_{k} \\,|\\, \\mathbf{x}) &\\propto P(\\mathbf{x} \\,|\\, C_{k})P(C_{k}) \\\\[5pt]\n",
614 | "&\\propto P(C_{k}) \\prod_{i=1}^{|V|} p(w_{i}\\,|\\,C_{k})^{x_i} ( 1-p(w_{i}\\,|\\,C_{k}))^{1-x_{i}} \n",
615 | "\\end{align}\n",
616 | "$$\n",
617 | "\n",
618 | "여기서, $\\mathbf{x}$는 입력된 메세지가 변환된 특징벡터, $|V|$은 학습된 전체단어의 개수이고, $x_{i}$는 $\\mathbf{x}$의 요소이며 학습된 단어장의 i번째 단어인 $w_{i}$가 입력된 메세지에 포함되면 1 없으면 0을 가지는 바이너리 변수이다. 수식에 모든 요소들은 이미 다 앞에서 계산해 둔 것들이다. 그리고 책에서는 사전확률 $P(S)$, $P(\\neg S)$를 곱하지 않는데 여기서는 명시적으로 곱해주었다. 구체적인 코드는 아래와 같다.\n",
619 | "\n",
620 | "```python\n",
621 | "#입력된 메세지를 단어별로 자른다. 여기선 잘린 단어의 중복 허용안함\n",
622 | "message_words = tokenize(message)\n",
623 | "\n",
624 | "# 클래스 사전확률을 곱해주고 P(S)*∏P(w|S) , P(¬S)*∏P(w|¬S)\n",
625 | "log_prob_if_spam ,log_prob_if_not_spam = math.log(prior_S),math.log(prior_non_S)\n",
626 | "\n",
627 | "for word, prob_if_spam, prob_if_not_spam in brn_word_probs:\n",
628 | " # for each word in the message,\n",
629 | " # add the log probability of seeing it\n",
630 | " if word in message_words:\n",
631 | " log_prob_if_spam += math.log(prob_if_spam)\n",
632 | " log_prob_if_not_spam += math.log(prob_if_not_spam)\n",
633 | "\n",
634 | " # for each word that's not in the message\n",
635 | " # add the log probability of _not_ seeing it\n",
636 | " else:\n",
637 | " log_prob_if_spam += math.log(1.0 - prob_if_spam)\n",
638 | " log_prob_if_not_spam += math.log(1.0 - prob_if_not_spam)\n",
639 | "```\n",
640 | "\n",
641 | "### 2.4.2 다항분포 나이브 베이즈\n",
642 | "\n",
643 | "#### 2.4.2.1 특징 벡터 만들기\n",
644 | "\n",
645 | "다항분포 모델에서는 $V$의 단어가 주어진 $d$에 몇번 나왔는지 판단하므로 다음의 예와 같이 특징벡터를 만든다.\n",
646 | "\n",
647 | "```python\n",
648 | "V = [w1, w2, w3, w4, w5, w6, w7, w8] #학습데이터로 부터 추출된 단어장\n",
649 | "d = \"w2 w5 w2 w7 w5 w5\"\n",
650 | "x = [0, 2, 0, 0, 3, 0, 1, 0]\n",
651 | "```\n",
652 | "\n",
653 | "V의 각 단어가 d에 몇번 나왔는지 판단 하므로 확률변수는 $X_1=0$, $X_2=2$, $X_3=0$, $X_4=0$, $X_5=3$, $X_6=0$, $X_7=1$, $X_8=0$ 처럼 된다.\n",
654 | "\n",
655 | "#### 2.4.2.2 가능도 계산\n",
656 | "\n",
657 | "이 후 아래 수식을 이용하여 가능도를 곱해 나간다.\n",
658 | "\n",
659 | "$$\n",
660 | "\\begin{align}\n",
661 | "P(C_{k}\\,|\\,\\mathbf{x} ) &\\propto P(\\mathbf{x} \\,|\\, C_{k})P(C_{k}) \\\\[5pt]\n",
662 | "&\\propto P(C_{k}) \\prod_{i=1}^{|V|} P(w_{i}\\,|\\,C_{k})^{x_i} \n",
663 | "\\end{align}\n",
664 | "$$\n",
665 | "\n",
666 | "여기서, $\\mathbf{x}$는 입력된 메세지가 변환된 특징벡터, $|V|$은 학습된 전체단어의 개수이고, $x_{i}$는 $\\mathbf{x}$의 요소이며 학습된 단어장의 i번째 단어인 $w_{i}$가 입력된 메세지에 나타난 출현수이다. 역시 수식에 모든 요소들은 이미 다 앞에서 계산해 둔 것들이다. 구현에 있어서 한가지 팁이 있는데 어차피 단어장 $V$의 단어중 $d$에 한번도 나타나지 않은 단어의 가능도는 $P(w_{i}\\,|\\,C_{k})^{0}=1$이 되므로 구현에서는 $d$에 속하는 단어의 가능도만 곱하면 된다. 구체적인 코드는 아래와 같다. \n",
667 | "\n",
668 | "```python\n",
669 | "#입력된 메세지를 단어별로 자른다. 여기선 잘린 단어의 중복 허용\n",
670 | "message_words = tokenize2(message)\n",
671 | "\n",
672 | "# 클래스 사전확률을 곱해주고 P(S)*∏P(w|S) , P(¬S)*∏P(w|¬S)\n",
673 | "log_prob_if_spam ,log_prob_if_not_spam = math.log(prior_S),math.log(prior_non_S)\n",
674 | " \n",
675 | "for word, prob_if_spam, prob_if_not_spam in mtnm_word_probs:\n",
676 | " # for each word in the message,\n",
677 | " if word in message_words:\n",
678 | " # ∏p(w|S) , ∏p(w|¬S) -> ∑ log(p(w|S)) , ∑ log(p(w|¬S)) \n",
679 | " log_prob_if_spam += math.log(prob_if_spam)\n",
680 | " log_prob_if_not_spam += math.log(prob_if_not_spam) \n",
681 | "```\n"
682 | ]
683 | },
684 | {
685 | "cell_type": "code",
686 | "execution_count": 5,
687 | "metadata": {},
688 | "outputs": [
689 | {
690 | "name": "stdout",
691 | "output_type": "stream",
692 | "text": [
693 | "베르누이 나이브 베이즈\n",
694 | "일반 메일을 일반 메일이라고 분류한 경우 : 708\n",
695 | "일반 메일을 스팸 메일이라고 분류한 경우 : 29\n",
696 | "스팸 메일을 스팸 메일이라고 분류한 경우 : 108\n",
697 | "스팸 메일을 일반 메일이라고 분류한 경우 : 31\n",
698 | "정밀도 : 0.788321\n",
699 | "재현율 : 0.776978\n",
700 | "\n",
701 | "\n",
702 | "다항분포 나이브 베이즈\n",
703 | "일반 메일을 일반 메일이라고 분류한 경우 : 698\n",
704 | "일반 메일을 스팸 메일이라고 분류한 경우 : 39\n",
705 | "스팸 메일을 스팸 메일이라고 분류한 경우 : 114\n",
706 | "스팸 메일을 일반 메일이라고 분류한 경우 : 25\n",
707 | "정밀도 : 0.745098\n",
708 | "재현율 : 0.820144\n"
709 | ]
710 | }
711 | ],
712 | "source": [
713 | "###############################################################################\n",
714 | "def brn_spam_probability(message): \n",
715 | " \"\"\"\n",
716 | " 책에서 설명은 기본적인 다항분포 나이브베이즈를 설명하고 코드는\n",
717 | " 베르누이 나이브 베이즈로 작성 되어 있다.\n",
718 | " https://nlp.stanford.edu/IR-book/html/htmledition/the-bernoulli-model-1.html\n",
719 | " https://www.datascienceschool.net/view-notebook/c19b48e3c7b048668f2bb0a113bd25f7/\n",
720 | " 그러면서 아무런 언급이 없음.\n",
721 | " \"\"\"\n",
722 | " global prior_S, prior_non_S\n",
723 | " \n",
724 | " message_words = tokenize(message)\n",
725 | " log_prob_if_spam ,log_prob_if_not_spam = math.log(prior_S),math.log(prior_non_S)\n",
726 | " #log_prob_if_spam = log_prob_if_not_spam = 0.0\n",
727 | " \n",
728 | " for word, prob_if_spam, prob_if_not_spam in brn_word_probs:\n",
729 | " # for each word in the message,\n",
730 | " # add the log probability of seeing it\n",
731 | " if word in message_words:\n",
732 | " log_prob_if_spam += math.log(prob_if_spam)\n",
733 | " log_prob_if_not_spam += math.log(prob_if_not_spam)\n",
734 | "\n",
735 | " # for each word that's not in the message\n",
736 | " # add the log probability of _not_ seeing it\n",
737 | " else:\n",
738 | " log_prob_if_spam += math.log(1.0 - prob_if_spam)\n",
739 | " log_prob_if_not_spam += math.log(1.0 - prob_if_not_spam)\n",
740 | "\n",
741 | " prob_if_spam = math.exp(log_prob_if_spam)\n",
742 | " prob_if_not_spam = math.exp(log_prob_if_not_spam)\n",
743 | " return prob_if_spam / (prob_if_spam + prob_if_not_spam)\n",
744 | "\n",
745 | "def mtnm_spam_probability(message):\n",
746 | " \"\"\"\n",
747 | " 주어진 메세지가 스팸인지 아닌지 학습된 likelihood 로부터 계산한다.\n",
748 | " 일반적인 multinomial 나이브베이즈로 작성.\n",
749 | " \"\"\"\n",
750 | " global prior_S, prior_non_S\n",
751 | " \n",
752 | " message_words = tokenize2(message)\n",
753 | " \n",
754 | " # 클래스 사전확률을 곱해주고 P(S)*∏P(w|S) , P(¬S)*∏P(w|¬S)\n",
755 | " log_prob_if_spam ,log_prob_if_not_spam = math.log(prior_S),math.log(prior_non_S)\n",
756 | " \n",
757 | " for word, prob_if_spam, prob_if_not_spam in mtnm_word_probs:\n",
758 | " # for each word in the message,\n",
759 | " if word in message_words:\n",
760 | " # ∏p(w|S) , ∏p(w|¬S) -> ∑ log(p(w|S)) , ∑ log(p(w|¬S)) \n",
761 | " log_prob_if_spam += math.log(prob_if_spam)\n",
762 | " log_prob_if_not_spam += math.log(prob_if_not_spam) \n",
763 | " \n",
764 | " prob_if_spam = math.exp(log_prob_if_spam)\n",
765 | " prob_if_not_spam = math.exp(log_prob_if_not_spam)\n",
766 | "\n",
767 | " return prob_if_spam / (prob_if_spam + prob_if_not_spam) \n",
768 | "###############################################################################\n",
769 | "\n",
770 | "#테스트 데이터를 분류한다.\n",
771 | "brn_classified = [(subject, is_spam, brn_spam_probability(subject))\n",
772 | " for subject, is_spam in test_data]\n",
773 | "\n",
774 | "mtnm_classified = [(subject, is_spam, mtnm_spam_probability(subject))\n",
775 | " for subject, is_spam in test_data]\n",
776 | "\n",
777 | "#분류된 결과를 성공 실패로 나눠서 카운팅한다.\n",
778 | "counts = Counter((is_spam, spam_prob > 0.5) # (actual, predicted)\n",
779 | " for _, is_spam, spam_prob in brn_classified)\n",
780 | "\n",
781 | "print(\"베르누이 나이브 베이즈\")\n",
782 | "print(\"일반 메일을 일반 메일이라고 분류한 경우 : {}\".format(counts[(False,False)]))\n",
783 | "print(\"일반 메일을 스팸 메일이라고 분류한 경우 : {}\".format(counts[(False,True)]))\n",
784 | "print(\"스팸 메일을 스팸 메일이라고 분류한 경우 : {}\".format(counts[(True,True)]))\n",
785 | "print(\"스팸 메일을 일반 메일이라고 분류한 경우 : {}\".format(counts[(True,False)]))\n",
786 | "print(\"정밀도 : {:f}\".format( counts[(True,True)] / (counts[(True,True)]+counts[(False,True)]) ) )\n",
787 | "print(\"재현율 : {:f}\".format( counts[(True,True)] / (counts[(True,True)]+counts[(True,False)]) ) )\n",
788 | "print(\"\\n\")\n",
789 | "\n",
790 | "counts = Counter((is_spam, spam_prob > 0.5) # (actual, predicted)\n",
791 | " for _, is_spam, spam_prob in mtnm_classified)\n",
792 | "\n",
793 | "print(\"다항분포 나이브 베이즈\")\n",
794 | "print(\"일반 메일을 일반 메일이라고 분류한 경우 : {}\".format(counts[(False,False)]))\n",
795 | "print(\"일반 메일을 스팸 메일이라고 분류한 경우 : {}\".format(counts[(False,True)]))\n",
796 | "print(\"스팸 메일을 스팸 메일이라고 분류한 경우 : {}\".format(counts[(True,True)]))\n",
797 | "print(\"스팸 메일을 일반 메일이라고 분류한 경우 : {}\".format(counts[(True,False)]))\n",
798 | "print(\"정밀도 : {:f}\".format( counts[(True,True)] / (counts[(True,True)]+counts[(False,True)]) ) )\n",
799 | "print(\"재현율 : {:f}\".format( counts[(True,True)] / (counts[(True,True)]+counts[(True,False)]) ) )\n"
800 | ]
801 | },
802 | {
803 | "cell_type": "markdown",
804 | "metadata": {},
805 | "source": [
806 | "두 경우 모두에서 코드 몇줄로는 썩 나쁘지 않은 결과를 나타내었음을 확인할 수 있다.\n"
807 | ]
808 | },
809 | {
810 | "cell_type": "markdown",
811 | "metadata": {},
812 | "source": [
813 | "## 3. 참고문헌\n",
814 | "\n",
815 | "\n",
816 | "1. 밑바닥부터 시작하는 데이터 과학Data Science from Scratch, 한빛미디어, Orelly, 조엘 그루스\n",
817 | "\n",
818 | "2. Introduction to Information Retrieval, Cambridge University Press. 2008. (Chapter 13 Text classification and Naive Bayes) https://nlp.stanford.edu/IR-book/\n",
819 | "\n",
820 | "3. Lecture Note for Informatics, Univ. of Edinburgh http://www.inf.ed.ac.uk/teaching/courses/inf2b/lectureSchedule.html\n"
821 | ]
822 | },
823 | {
824 | "cell_type": "code",
825 | "execution_count": 6,
826 | "metadata": {},
827 | "outputs": [
828 | {
829 | "data": {
830 | "text/html": [
831 | "\n",
832 | "\n",
833 | "\n",
834 | "\n",
835 | "\n",
836 | "\n"
862 | ],
863 | "text/plain": [
864 | ""
865 | ]
866 | },
867 | "metadata": {},
868 | "output_type": "display_data"
869 | }
870 | ],
871 | "source": [
872 | "%%html\n",
873 | "\n",
874 | "\n",
875 | "\n",
876 | "\n",
877 | "\n",
878 | ""
904 | ]
905 | }
906 | ],
907 | "metadata": {
908 | "anaconda-cloud": {},
909 | "kernelspec": {
910 | "display_name": "Python 3",
911 | "language": "python",
912 | "name": "python3"
913 | },
914 | "language_info": {
915 | "codemirror_mode": {
916 | "name": "ipython",
917 | "version": 3
918 | },
919 | "file_extension": ".py",
920 | "mimetype": "text/x-python",
921 | "name": "python",
922 | "nbconvert_exporter": "python",
923 | "pygments_lexer": "ipython3",
924 | "version": "3.5.2"
925 | }
926 | },
927 | "nbformat": 4,
928 | "nbformat_minor": 2
929 | }
930 |
--------------------------------------------------------------------------------
/perceptron/fig1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/perceptron/fig1.png
--------------------------------------------------------------------------------
/perceptron/perceptron.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/perceptron/perceptron.png
--------------------------------------------------------------------------------
/sampling/change-of-variable.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/sampling/change-of-variable.png
--------------------------------------------------------------------------------
/sampling/det.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/sampling/det.png
--------------------------------------------------------------------------------
/sampling/jacobian.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/sampling/jacobian.png
--------------------------------------------------------------------------------
/sampling/test_js.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": 1,
6 | "metadata": {},
7 | "outputs": [
8 | {
9 | "data": {
10 | "application/javascript": [
11 | "//https://stackoverflow.com/questions/30367591/how-to-add-external-javascript-file-in-ipython-notebook\n",
12 | "\n",
13 | "window.get_element = function(el){\n",
14 | " if(el){ $(el).html('') }\n",
15 | " return (el !== undefined) ? el[0] : $('script').last().parent()[0];\n",
16 | "};\n",
17 | "\n",
18 | "element = undefined;"
19 | ],
20 | "text/plain": [
21 | ""
22 | ]
23 | },
24 | "metadata": {},
25 | "output_type": "display_data"
26 | }
27 | ],
28 | "source": [
29 | "%%javascript\n",
30 | "//https://stackoverflow.com/questions/30367591/how-to-add-external-javascript-file-in-ipython-notebook\n",
31 | "\n",
32 | "window.get_element = function(el){\n",
33 | " if(el){ $(el).html('') }\n",
34 | " return (el !== undefined) ? el[0] : $('script').last().parent()[0];\n",
35 | "};\n",
36 | "\n",
37 | "element = undefined;"
38 | ]
39 | },
40 | {
41 | "cell_type": "code",
42 | "execution_count": 25,
43 | "metadata": {},
44 | "outputs": [
45 | {
46 | "data": {
47 | "application/javascript": [
48 | "//https://stackoverflow.com/questions/30367591/how-to-add-external-javascript-file-in-ipython-notebook\n",
49 | "\n",
50 | "requirejs.config({\n",
51 | " paths: { \n",
52 | " 'd3': ['//cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3'], \n",
53 | " // strip .js ^, require adds it back\n",
54 | " },\n",
55 | "});\n",
56 | "\n",
57 | "(function(){\n",
58 | " var targetElement = get_element(element);\n",
59 | " \n",
60 | " require(['d3'], function(d3) {\n",
61 | " //console.log(\"Loaded :)\"); \n",
62 | " //\n",
63 | " \n",
64 | " var data = [1, 2, 3, 4, 5, 6, 10]\n",
65 | " var svg = d3.select(targetElement).append('svg')\n",
66 | " .attr('width', '350px')\n",
67 | " .attr('height', '100px')\n",
68 | " .style('border', '1px solid lightgray');\n",
69 | "\n",
70 | " svg.selectAll('circle')\n",
71 | " .data(data)\n",
72 | " .enter()\n",
73 | " .append('circle')\n",
74 | " .style('fill', 'skyblue')\n",
75 | " .attr('cx', function(d, i){ return i * (350/data.length) + 15})\n",
76 | " .attr('cy', '50px')\n",
77 | " .attr('r', function(d){ return d * 3})\n",
78 | " \n",
79 | " $(targetElement).append(\n",
80 | " $(\"\")\n",
81 | " );\n",
82 | " $(targetElement).append($(\"0
\"));\n",
83 | " $(targetElement).append($(\"\"));\n",
84 | " \n",
85 | " return {};\n",
86 | " });\n",
87 | "})()"
88 | ],
89 | "text/plain": [
90 | ""
91 | ]
92 | },
93 | "metadata": {},
94 | "output_type": "display_data"
95 | }
96 | ],
97 | "source": [
98 | "%%javascript\n",
99 | "//https://stackoverflow.com/questions/30367591/how-to-add-external-javascript-file-in-ipython-notebook\n",
100 | "\n",
101 | "requirejs.config({\n",
102 | " paths: { \n",
103 | " 'd3': ['//cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3'], \n",
104 | " // strip .js ^, require adds it back\n",
105 | " },\n",
106 | "});\n",
107 | "\n",
108 | "(function(){\n",
109 | " var targetElement = get_element(element);\n",
110 | " \n",
111 | " require(['d3'], function(d3) {\n",
112 | " //console.log(\"Loaded :)\"); \n",
113 | " //\n",
114 | " \n",
115 | " var data = [1, 2, 3, 4, 5, 6, 10]\n",
116 | " var svg = d3.select(targetElement).append('svg')\n",
117 | " .attr('width', '350px')\n",
118 | " .attr('height', '100px')\n",
119 | " .style('border', '1px solid lightgray');\n",
120 | "\n",
121 | " svg.selectAll('circle')\n",
122 | " .data(data)\n",
123 | " .enter()\n",
124 | " .append('circle')\n",
125 | " .style('fill', 'skyblue')\n",
126 | " .attr('cx', function(d, i){ return i * (350/data.length) + 15})\n",
127 | " .attr('cy', '50px')\n",
128 | " .attr('r', function(d){ return d * 3})\n",
129 | " \n",
130 | " $(targetElement).append(\n",
131 | " $(\"\")\n",
132 | " );\n",
133 | " $(targetElement).append($(\"0
\"));\n",
134 | " $(targetElement).append($(\"\"));\n",
135 | " \n",
136 | " return {};\n",
137 | " });\n",
138 | "})()"
139 | ]
140 | },
141 | {
142 | "cell_type": "code",
143 | "execution_count": 23,
144 | "metadata": {},
145 | "outputs": [
146 | {
147 | "data": {
148 | "text/html": [
149 | "\n",
150 | "\n",
151 | "\n",
152 | ""
161 | ],
162 | "text/plain": [
163 | ""
164 | ]
165 | },
166 | "metadata": {},
167 | "output_type": "display_data"
168 | }
169 | ],
170 | "source": [
171 | "%%html\n",
172 | "\n",
173 | "\n",
174 | "\n",
175 | ""
184 | ]
185 | }
186 | ],
187 | "metadata": {
188 | "kernelspec": {
189 | "display_name": "Python 3",
190 | "language": "python",
191 | "name": "python3"
192 | },
193 | "language_info": {
194 | "codemirror_mode": {
195 | "name": "ipython",
196 | "version": 3
197 | },
198 | "file_extension": ".py",
199 | "mimetype": "text/x-python",
200 | "name": "python",
201 | "nbconvert_exporter": "python",
202 | "pygments_lexer": "ipython3",
203 | "version": "3.5.2"
204 | }
205 | },
206 | "nbformat": 4,
207 | "nbformat_minor": 2
208 | }
209 |
--------------------------------------------------------------------------------
/simplenet/mnist.pkl.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/simplenet/mnist.pkl.gz
--------------------------------------------------------------------------------
/simplenet/simplenet.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "# 150줄로 된 간단한 네트워크Simple network with 150 lines\n",
8 | "\n",
9 | "2017.05.19 조준우 metamath@gmail.com
\n",
10 | "\n",
11 | "
\n",
12 | "\n",
13 | "여기에서는 아주 간단한 네트워크를 직접 구현해 보는 것을 목표로 한다. \n",
14 | "Nielsen문서[1]나 다른 문서들을 보면서 이론을 이해했으면 그 문서에서 제공하는 코드를 보게 되는데 아무래도 각 층을 객체지향적으로 구현하다보니 소스가 좀 복잡하다고 느껴진다. 특히 역전파같은 경우는 객체지향적으로 구현하기에 매우 적합한 구조라 생각하지만 모르는 입장에서는 막상 구현된 결과를 보면 조각조각 쪼개져 있어서 전체적인 그림을 그리기 힘들다는 단점이 있다. 그런 이유로 객체지향 구현보다 더 간단하지만(적어도 내가 생각하기에는) 비슷한 성능을 내는 코드를 처음부터 구현하면서 간단한 뉴럴네트워크에 대한 이해도를 높이려고 하였다.
\n",
15 | "따라서 코드는 절차지향적으로 작성되었으며 Network을 정의하는 부분만 Class를 사용하였다. 나머지는 모두 데이터가 입력되고 웨이트와 곱해지고 하는 네트워크의 절차적 흐름을 최대한 가독성 높게 구현하려고 하였다.\n",
16 | "\n",
17 | "## 1. 구현 요구 사항\n",
18 | "\n",
19 | "- 당연히 Numpy로만 구현\n",
20 | "\n",
21 | "- 최대한 간단하게 150줄 내외로 구현해서 언제나 봐도 30분안에 이해되도록\n",
22 | "\n",
23 | "- Nielsen 문서의 수식 인덱스와 구현된 파이썬 인덱스를 완전히 일치시킴. 즉 다음 기본 식 4개의 인덱스와 파이썬 프로그램 배열의 인덱스를 완전히 일치시켜서 프로그램 하여 가독성을 최대화 함.\n",
24 | "\n",
25 | "$$\\delta^{L}_{j}= \\frac{\\partial C}{\\partial a^{L}_{j}} \\sigma^{'}(z^{L}_{j})$$\n",
26 | "\n",
27 | "$$\\delta^{l}_{j} = \\sum_{k} w^{l+1}_{kj} \\delta^{l+1}_{k} \\sigma^{'}(z^{k}_{j}) $$\n",
28 | "\n",
29 | "$$\\frac{\\partial C}{\\partial b^{l}_{j}} = \\delta^{l}_{j}$$\n",
30 | "\n",
31 | "$$\\frac{\\partial C}{\\partial w^{l}_{jk}} = a^{l-1}_{k} \\delta^{l}_{j}$$\n",
32 | "\n",
33 | "- 완전 연결층만 구현\n",
34 | "\n",
35 | "- 코스트 함수 : Mean Square Error, Binary Cross Entropy, Categorical Cross Entropy\n",
36 | "\n",
37 | "- 활성 함수 : Sigmoid, Relu, Softmax\n",
38 | "\n",
39 | "- 기타 구현 : Regularization, Dropout, Batch Normalization\\*\n",
40 | "\n",
41 | "- 테스트데이터 : MNIST\n",
42 | "\n",
43 | "\\* 은 선택사항\n",
44 | "\n",
45 | "\n",
46 | "## 2. 구현 세부 사항\n",
47 | "\n",
48 | "구현에 있어서 가장 어려운 부분은 \n",
49 | "\n",
50 | "$$\\delta^{L}_{j}= \\frac{\\partial C}{\\partial a^{L}_{j}} \\sigma^{'}(z^{L}_{j})$$\n",
51 | "\n",
52 | "을 구현하는 부분인데 왜냐하면 코스트 함수와 출력층의 활성화 함수를 직접 미분해서 코딩해야하기 때문이다. 보통은 $\\boldsymbol{\\delta}^{L}$을 구하기 위해 위 식을 그대로 코딩하지는 않고 위 식이 계산된 결과를 코딩하게 된다. 수치 안정성이나 코딩의 양이 적어 훨씬 효율적이기 때문이다. 구현에 사용되는 sigmoid, softmax와 mean square error, cross entropy함수를 적당히 잘 조합하면 $\\boldsymbol{\\delta}^{L} = \\mathbf{a}-\\mathbf{y}$가 되어 간단하게 코딩할 수 있는데 여기서는 학습의 목적으로 코스트함수와 활성화 함수를 직접 다 미분해서 구현하였다. 때문에 원하는대로 출력층 활성화 함수와 코스트함수를 마음대로 조합해가며 실험할 수 있다. 예를 들어 sigmoid-cross entropy 같은 조합도 가능한데 이렇게 하면 코스트함수가 sigmoid 출력의 특성을 잘 반영하지 못하기 때문에 학습이 안되는 결과를 확인할 수 있다.\n",
53 | "\n",
54 | "전체적인 구성은 다음과 같이 활성함수와 코스트함수를 함수값 계산, 미분값 계산을 하는 두 부분으로 구현하고 각각을 'f', 'df'를 키로 하는 사전으로 묶어 둔다.\n",
55 | "\n",
56 | "```python\n",
57 | "#--------------------------------------------------------------------\n",
58 | "# activation functions\n",
59 | "# 활성함수의 함수값을 계산하는 함수와 미분값을 계산하는 함수를 한쌍으로 정의\n",
60 | "# 'f'와 'df' 키워드로 묶음\n",
61 | "def sigmoid(z):\n",
62 | " \"\"\"\n",
63 | " z : (C, N), C:클래스 수, N:샘플 수 이하 동일\n",
64 | " \"\"\"\n",
65 | " return 1.0/(1.0+np.exp(-z))\n",
66 | " \n",
67 | "def sigmoid_prime(z, y):\n",
68 | " \"\"\"\n",
69 | " z : (C, N)\n",
70 | " y : dummy\n",
71 | " \n",
72 | " [참고]\n",
73 | " 여기서는 정답 벡터 y가 필요없지만 softmax_prime_with_crossent과 인터페이스를 맞추기 위해\n",
74 | " 더미로 y를 넘겨 받음 None로 넘겨주면 됨.\n",
75 | " \"\"\" \n",
76 | " return sigmoid(z)*(1-sigmoid(z))\n",
77 | " \n",
78 | "Sigmoid = { 'f': sigmoid, 'df': sigmoid_prime }\n",
79 | "\n",
80 | "#--------------------------------------------------------------------\n",
81 | "# Cost function\n",
82 | "# 목적함수의 함수값을 계산하는 함수와 미분값을 계산하는 함수를 한쌍으로 정의\n",
83 | "# 'f'와 'df' 키워드로 묶음\n",
84 | "def mse_cost(a, y):\n",
85 | " \"\"\"\n",
86 | " a : (C, N)\n",
87 | " y : col major로 인코딩된 one hot (C, N)\n",
88 | " \"\"\"\n",
89 | " return 0.5*np.linalg.norm(a-y)**2\n",
90 | "\n",
91 | "def mse_cost_prime(a, y):\n",
92 | " \"\"\"\n",
93 | " a : (C, N)\n",
94 | " y : col major로 인코딩된 one hot (C, N)\n",
95 | " \"\"\"\n",
96 | " return (a-y)\n",
97 | "\n",
98 | "Mse = {'f':mse_cost, 'df':mse_cost_prime}\n",
99 | "```\n",
100 | "\n",
101 | "이렇게 구현해두면 필요한 것만 골라서 조합해서 네트워크를 구성할 수 있게 된다. 프로그램의 구조는 위 코드가 핵심이며 다른건 별게 없다.\n"
102 | ]
103 | },
104 | {
105 | "cell_type": "markdown",
106 | "metadata": {},
107 | "source": [
108 | "### 2.1 Cross Entropy Cost and Softmax \n",
109 | "\n",
110 | "다른 부분은 주석이 자세하게 달려 있어서 문서로는 cross entropy를 코스트함수로 쓰고 출력층 활성화함수를 softmax로 할 때 미분 관련해서만 남긴다. 네트워크를 학습시키기 위한 목적함수(또는 코스트, 로스함수)는 스칼라함수이고 sigmoid, tanh 함수같은 경우 역시 스칼라 함수이므로 둘다 미분을 해서 곱하기에 무리가 없다. $\\boldsymbol{\\delta}^{L}$을 구하기 위한 다음 식을 보면\n",
111 | "\n",
112 | "$$\\delta^{L}_{cn}= \\frac{\\partial C}{\\partial a^{L}_{cn}} \\frac{d \\sigma(z^{L}_{cn})}{d z^{L}_{cn}}$$\n",
113 | "\n",
114 | "인데 이 식에서 $c$는 $L$층의 뉴런 인덱스이고 $n$는 샘플의 인덱스이다. 그렇게 보면 위 식의 $\\delta^{L}_{cn}$은 행렬, $\\dfrac{\\partial C}{\\partial a^{L}_{cn}}$도 행렬이고, sigmoid, tanh같은 함수를 $\\sigma$로 썼다면 $\\dfrac{d \\sigma(z^{L}_{cn})}{d z^{L}_{cn}}$ 역시 행렬이 되어 두 행렬 $\\dfrac{\\partial C}{\\partial \\mathbf{a}^{L}}$와 $\\dfrac{d \\sigma(\\mathbf{z}^{L})}{ d \\mathbf{z}^{L}}$를 element-wise하게 곱하면 된다.
\n",
115 | "하지만 softmax함수는 $\\mathbb{R}^{c} \\to \\mathbb{R}^{c}$인 벡터함수로 입력벡터 $\\mathbf{z}^{L}$로의 미분이 자코비안 행렬이 된다. 샘플의 개수가 $N$개라면 이런 행렬이 $N$개가 있는 $N \\times C \\times C$인 텐서(정확히는 3차원 어레이)가 된다. 그래서 위 식을 처리할때 약간 주의를 해야한다. 단계적으로 정리를 한다.\n",
116 | "\n"
117 | ]
118 | },
119 | {
120 | "cell_type": "markdown",
121 | "metadata": {},
122 | "source": [
123 | "#### 2.1.1 Cost 함수 미분\n",
124 | "\n",
125 | "Cross Entropy 코스트의 정의는 아래와 같다.\n",
126 | "\n",
127 | "$$\\text{C}= - \\sum_{C} y_{c} \\log a_{c}\\equiv - \\log a_{y} $$\n",
128 | "\n",
129 | "이 식에서 $C$는 클래스의 수를 나타낸다. $y_c$는 크기 $C \\times 1$ 원핫벡터인 $\\mathbf{y}$의 $c$번째 요소를 나타내고 마지막 $a_{y}$는 출력 $a$중에 $y$번째 값을 나타낸다. 여기서 $y$는 원핫인코딩 되지 않은 0, 1, 2, 3, 4, 5, 6, 7, 8, 9의 값을 가지는 정답 숫자이다. N개의 샘플에 대한 네트워크의 마지막 층에서 가중합과 softmax에 의한 출력은 각각 다음과 같다. 인덱스 순서는 앞의 인덱스가 클래스 번호, 뒤에 인덱스가 샘플번호이다.\n",
130 | "\n",
131 | "$$\n",
132 | "\\begin{bmatrix}\n",
133 | "z_{11} & z_{12} & \\cdots & z_{1N} \\\\[4pt]\n",
134 | "z_{21} & z_{22} & \\cdots & z_{2N} \\\\[4pt]\n",
135 | "z_{31} & z_{32} & \\cdots & z_{3N} \\\\[4pt]\n",
136 | "\\vdots & \\vdots & \\ddots & \\vdots \\\\\n",
137 | "z_{C1} & z_{C2} & \\cdots & z_{CN} \n",
138 | "\\end{bmatrix}\n",
139 | "\\xrightarrow{\\text{softmax}}\n",
140 | "\\begin{bmatrix}\n",
141 | "a_{11} & a_{12} & \\cdots & a_{1N} \\\\[4pt]\n",
142 | "a_{21} & a_{22} & \\cdots & a_{2N} \\\\[4pt]\n",
143 | "a_{31} & a_{32} & \\cdots & a_{3N} \\\\[4pt]\n",
144 | "\\vdots & \\vdots & \\ddots & \\vdots \\\\\n",
145 | "a_{C1} & a_{C2} & \\cdots & a_{CN} \n",
146 | "\\end{bmatrix}\n",
147 | "$$\n",
148 | "\n",
149 | "그러면 이 코스트에 대한 미분은 다음과 같다.\n",
150 | "\n",
151 | "$$\n",
152 | "\\frac{\\partial \\, \\text{C}}{\\partial \\, \\mathbf{a}} = \n",
153 | "\\begin{bmatrix}\n",
154 | "\\dfrac{\\partial \\, \\text{C}}{\\partial a_{11}} & \\dfrac{\\partial \\, \\text{C}}{\\partial a_{12}}& \\cdots & \\dfrac{\\partial \\, \\text{C}}{\\partial a_{1N}} \\\\[4pt]\n",
155 | "\\dfrac{\\partial \\, \\text{C}}{\\partial a_{21}} & \\dfrac{\\partial \\, \\text{C}}{\\partial a_{22}}& \\cdots & \\dfrac{\\partial \\, \\text{C}}{\\partial a_{2N}} \\\\[4pt]\n",
156 | "\\dfrac{\\partial \\, \\text{C}}{\\partial a_{31}} & \\dfrac{\\partial \\, \\text{C}}{\\partial a_{32}}& \\cdots & \\dfrac{\\partial \\, \\text{C}}{\\partial a_{3N}} \\\\[4pt]\n",
157 | "\\vdots & \\vdots & \\ddots & \\vdots \\\\[4pt]\n",
158 | "\\dfrac{\\partial \\, \\text{C}}{\\partial a_{C1}} & \\dfrac{\\partial \\, \\text{C}}{\\partial a_{C2}}& \\cdots & \\dfrac{\\partial \\, \\text{C}}{\\partial a_{CN}}\n",
159 | "\\end{bmatrix}\n",
160 | "$$\n",
161 | "\n",
162 | "그런데 코스트함수의 정의상 정답자리를 제외한 대부분의 위치는 0 이다. 예를 들어 $y_{21}$, $y_{32}$, $y_{CN}$등이 정답자리(바꿔 말하면 1번 샘플은 2, 2번 샘플은 3, ...)라고 하면\n",
163 | "\n",
164 | "$$\n",
165 | "\\frac{\\partial \\, \\text{C}}{\\partial \\, \\mathbf{a}} = \n",
166 | "\\begin{bmatrix}\n",
167 | "\\dfrac{\\partial \\, \\text{C}}{\\partial a_{11}} & \\dfrac{\\partial \\, \\text{C}}{\\partial a_{12}}& \\cdots & \\dfrac{\\partial \\, \\text{C}}{\\partial a_{1N}} \\\\[4pt]\n",
168 | "\\dfrac{\\partial \\, \\text{C}}{\\partial a_{21}} & \\dfrac{\\partial \\, \\text{C}}{\\partial a_{22}}& \\cdots & \\dfrac{\\partial \\, \\text{C}}{\\partial a_{2N}} \\\\[4pt]\n",
169 | "\\dfrac{\\partial \\, \\text{C}}{\\partial a_{31}} & \\dfrac{\\partial \\, \\text{C}}{\\partial a_{32}}& \\cdots & \\dfrac{\\partial \\, \\text{C}}{\\partial a_{3N}} \\\\[4pt]\n",
170 | "\\vdots & \\vdots & \\ddots & \\vdots \\\\[4pt]\n",
171 | "\\dfrac{\\partial \\, \\text{C}}{\\partial a_{C1}} & \\dfrac{\\partial \\, \\text{C}}{\\partial a_{C2}}& \\cdots & \\dfrac{\\partial \\, \\text{C}}{\\partial a_{CN}}\n",
172 | "\\end{bmatrix}=\\begin{bmatrix}\n",
173 | "0 & 0 & \\cdots & 0 \\\\[4pt]\n",
174 | "-\\dfrac{1}{a_{21}} & 0 & \\cdots & 0 \\\\[4pt]\n",
175 | "0 & -\\dfrac{1}{a_{32}} & \\cdots & 0 \\\\[4pt]\n",
176 | "\\vdots & \\vdots & \\ddots & \\vdots \\\\[4pt]\n",
177 | "0 & 0 & \\cdots & -\\dfrac{1}{a_{CN}} \n",
178 | "\\end{bmatrix}\n",
179 | "$$\n",
180 | "\n",
181 | "\n",
182 | "와 같이 미분한 행렬이 완성된다. python 코드로는 다음처럼 구현할 수 있다. 이때 출력 $a$가 softmax로부터의 출력일 경우 $a$가 거의 0일 수 있기때문에 오버플로 방지대책을 아래 코드처럼 세워주면 좋다.\n",
183 | "\n",
184 | "\n",
185 | "```python\n",
186 | "def cross_entropy_cost_prime(a, y):\n",
187 | " dC = np.zeros(a.shape)\n",
188 | " a[np.where(a < np.finfo(np.float32).eps)] = np.finfo(np.float32).eps\n",
189 | " dC[np.where(y ==1)] = -y[np.where(y==1)]/a[np.where(y==1)]\n",
190 | " \n",
191 | " return dC #(C,N)\n",
192 | "```"
193 | ]
194 | },
195 | {
196 | "cell_type": "markdown",
197 | "metadata": {},
198 | "source": [
199 | "#### 2.1.2 Softmax 함수 미분\n",
200 | "\n",
201 | "샘플 $\\mathbf{x}_{1}$에 대한 softmax함수의 미분은 다음과 같다.\n",
202 | "\n",
203 | "$$\\frac{\\partial \\, \\mathbf{a}_{\\mathbf{x}_{1}}}{\\partial \\, \\mathbf{z}_{\\mathbf{x}_{1}}} = \n",
204 | "\\begin{bmatrix}\n",
205 | "\\dfrac{\\partial \\, a_{11}}{\\partial z_{11}} & \\dfrac{\\partial \\, a_{11}}{\\partial z_{21}}& \\cdots & \\dfrac{\\partial \\, a_{11}}{\\partial z_{C1}} \\\\[4pt]\n",
206 | "\\dfrac{\\partial \\, a_{21}}{\\partial z_{11}} & \\dfrac{\\partial \\, a_{21}}{\\partial z_{21}}& \\cdots & \\dfrac{\\partial \\, a_{21}}{\\partial z_{C1}} \\\\[4pt]\n",
207 | "\\dfrac{\\partial \\, a_{31}}{\\partial z_{11}} & \\dfrac{\\partial \\, a_{31}}{\\partial z_{21}}& \\cdots & \\dfrac{\\partial \\, a_{31}}{\\partial z_{C1}} \\\\[4pt]\n",
208 | "\\vdots & \\vdots & \\ddots & \\vdots \\\\[4pt]\n",
209 | "\\dfrac{\\partial \\, a_{C1}}{\\partial z_{11}} & \\dfrac{\\partial \\, a_{C1}}{\\partial z_{21}}& \\cdots & \\dfrac{\\partial \\, a_{C1}}{\\partial z_{C1}} \\\\\n",
210 | "\\end{bmatrix}\n",
211 | "$$\n",
212 | "\n",
213 | "실제 softmax의 미분은 다음과 같으므로 [https://www.facebook.com/groups/TensorFlowKR/permalink/502663916741338/]\n",
214 | "\n",
215 | "$$\n",
216 | "\\frac{\\partial \\, \\text{softmax}(z_{j})}{\\partial \\, z_{i}}=\n",
217 | "\\begin{cases}\n",
218 | "\\text{softmax}(z_{j})(1-\\text{softmax}(z_{i})), & \\mbox{if } j=i \\\\\n",
219 | "-\\text{softmax}(z_{j})\\text{softmax}(z_{i}) , & \\mbox{if } j \\neq i\n",
220 | "\\end{cases}\n",
221 | "$$\n",
222 | "\n",
223 | "샘플 $\\mathbf{x}_{1}$에 대한 softmax함수의 미분이 아래처럼 완성된다.\n",
224 | "\n",
225 | "$$\n",
226 | "\\frac{\\partial \\, \\mathbf{a}_{\\mathbf{x}_{1}}}{\\partial \\, \\mathbf{z}_{\\mathbf{x}_{1}}} = \n",
227 | "\\begin{bmatrix}\n",
228 | "\\dfrac{\\partial \\, a_{11}}{\\partial z_{11}} & \\dfrac{\\partial \\, a_{11}}{\\partial z_{21}}& \\cdots & \\dfrac{\\partial \\, a_{11}}{\\partial z_{C1}} \\\\[4pt]\n",
229 | "\\dfrac{\\partial \\, a_{21}}{\\partial z_{11}} & \\dfrac{\\partial \\, a_{21}}{\\partial z_{21}}& \\cdots & \\dfrac{\\partial \\, a_{21}}{\\partial z_{C1}} \\\\[4pt]\n",
230 | "\\dfrac{\\partial \\, a_{31}}{\\partial z_{11}} & \\dfrac{\\partial \\, a_{31}}{\\partial z_{21}}& \\cdots & \\dfrac{\\partial \\, a_{31}}{\\partial z_{C1}} \\\\[4pt]\n",
231 | "\\vdots & \\vdots & \\ddots & \\vdots \\\\[4pt]\n",
232 | "\\dfrac{\\partial \\, a_{C1}}{\\partial z_{11}} & \\dfrac{\\partial \\, a_{C1}}{\\partial z_{21}}& \\cdots & \\dfrac{\\partial \\, a_{C1}}{\\partial z_{C1}}\n",
233 | "\\end{bmatrix}=\\begin{bmatrix}\n",
234 | "a_{11}(1-a_{11}) & -a_{11}a_{21} & \\cdots & -a_{11}a_{C1} \\\\[4pt]\n",
235 | "-a_{21}a_{11} & a_{21}(1-a_{21}) & \\cdots & -a_{21}a_{C1} \\\\[4pt]\n",
236 | "-a_{31}a_{11} & -a_{31}a_{21} & \\cdots & -a_{31}a_{C1} \\\\[4pt]\n",
237 | "\\vdots & \\vdots & \\ddots & \\vdots \\\\[4pt]\n",
238 | "-a_{C1}a_{11} & -a_{C1}a_{21} & \\cdots & a_{C1}(1-a_{C1})\n",
239 | "\\end{bmatrix}\n",
240 | "$$\n",
241 | "\n",
242 | "이렇게 샘플 $\\mathbf{x}_{n}$에 대해 하나의 미분 행렬이 주어지므로 최종 결과는 $N \\times C \\times C$인 텐서가 된다.\n",
243 | "\n",
244 | "```python\n",
245 | "def softmax_prime(z, y):\n",
246 | " C, N = z.shape\n",
247 | " di = np.diag_indices(C)\n",
248 | " \n",
249 | " a = softmax(z) # a : (C,N)\n",
250 | " da = -np.einsum('ij,jk->jik', a, a.T) #da :(N,C,C)\n",
251 | " da[:,di[0],di[1]] = (a*(1-a)).T\n",
252 | " \n",
253 | " return da\n",
254 | "```\n",
255 | "\n",
256 | "이제 코스트함수의 미분 softmax함수의 미분이 끝났으므로 이 둘을 곱해서 $\\boldsymbol{\\delta}^{L}$을 만들어야 한다. $\\boldsymbol{\\delta}^{L}$의 크기는 샘플 1개당 $C \\times 1$짜리 벡터가 모여있는 것이므로 $C \\times N$이 된다. 샘플 $\\mathbf{x}_{1}$에 대한 $\\boldsymbol{\\delta}_{\\mathbf{x}_{1}}^{L}$은 다음처럼 계산할 수 있다.\n",
257 | "\n",
258 | "$$\n",
259 | "\\begin{align}\n",
260 | "\\left( \\boldsymbol{\\delta}_{\\mathbf{x}_{1}}^{L} \\right)^{\\text{T}} &= \n",
261 | "\\begin{bmatrix}\n",
262 | "0 & -\\dfrac{1}{a_{21}} & 0 & \\cdots & 0\n",
263 | "\\end{bmatrix}\n",
264 | "\\frac{\\partial \\, \\mathbf{a}_{\\mathbf{x}_{1}}}{\\partial \\, \\mathbf{z}_{\\mathbf{x}_{1}}} \\\\[5pt]\n",
265 | "&= \n",
266 | "\\underbrace{\n",
267 | "\\begin{bmatrix}\n",
268 | "0 & -\\dfrac{1}{a_{21}} & 0 & \\cdots & 0\n",
269 | "\\end{bmatrix}\n",
270 | "}_{ \\dfrac{\\partial C}{\\partial a^{L}_{c1}} }\n",
271 | "\\underbrace{\n",
272 | "\\begin{bmatrix}\n",
273 | "a_{11}(1-a_{11}) & -a_{11}a_{21} & \\cdots & -a_{11}a_{C1} \\\\[4pt]\n",
274 | "-a_{21}a_{11} & a_{21}(1-a_{21}) & \\cdots & -a_{21}a_{C1} \\\\[4pt]\n",
275 | "-a_{31}a_{11} & -a_{31}a_{21} & \\cdots & -a_{31}a_{C1} \\\\[4pt]\n",
276 | "\\vdots & \\vdots & \\ddots & \\vdots \\\\[4pt]\n",
277 | "-a_{C1}a_{11} & -a_{C1}a_{21} & \\cdots & a_{C1}(1-a_{C1})\n",
278 | "\\end{bmatrix}\n",
279 | "}_{ \\dfrac{\\partial \\, \\mathbf{a}_{\\mathbf{x}_{1}}}{\\partial \\, \\mathbf{z}_{\\mathbf{x}_{1}}} }\n",
280 | "\\end{align}\n",
281 | "$$\n",
282 | "\n",
283 | "위 식의 첫항은 코스트함수 $\\text{C}$를 $a_{c1}$로 미분한 것으로 $\\dfrac{\\partial \\, \\text{C}}{\\partial \\, \\mathbf{a}}$의 첫번째 열이 된다. 이런 계산을 샘플 $\\mathbf{x}$에 대해서 $N$번 반복하면 $C \\times N$의 $\\boldsymbol{\\delta}^{L}$이 만들어 진다. numpy의 einsum함수를 사용해서 다음처럼 구현할 수 있다.\n",
284 | "\n",
285 | "```python\n",
286 | "#코스트 미분과 출력층의 미분\n",
287 | "dC , da = self.cost['df'](self.A[l], Y) , self.activates[l]['df'](self.Z[l], Y)\n",
288 | "\n",
289 | "#둘을 곱한다.\n",
290 | "delta = np.einsum('ij,jik->jk', dC, da).T\n",
291 | "```\n",
292 | "\n",
293 | "그런데 출력층의 활성화 함수가 스칼라 함수였다면 간단하게 아래처럼 할 수 있다.\n",
294 | "\n",
295 | "```python\n",
296 | "delta = dC * da #dC/da, da/az 둘 모양이 같으면 그냥 Hadamard 곱, 결과:(C,N)\n",
297 | "```\n",
298 | "\n",
299 | "이렇게 간단히 곱하기 위해 코스트의 미분과 softmax의 미분을 각각 벡터와 행렬형태로 줄여서 구현하는 방법이 2.1.3절에 있다.\n"
300 | ]
301 | },
302 | {
303 | "cell_type": "markdown",
304 | "metadata": {},
305 | "source": [
306 | "#### 2.1.3 축약버전\n",
307 | "\n",
308 | "축약 버전은 결국 지금까지 계산한 결과에 실제 곱해지는 성분만 뽑아서 정리해서 곱하는 방법인데 TF-KR에 내가 한 질문글을 바탕으로 작성하였다. [http://fbsight.com/t/topic/117240/10] 실제 답변을 주신 Kyung Mo Kweon[https://www.facebook.com/kkweon] 의 답변을 보고 코딩함.\n",
309 | "\n",
310 | "$$\n",
311 | "\\begin{align}\n",
312 | "\\left( \\boldsymbol{\\delta}_{\\mathbf{x}_{1}}^{L} \\right)^{\\text{T}} &= \n",
313 | "\\begin{bmatrix}\n",
314 | "0 & -\\dfrac{1}{a_{21}} & 0 & \\cdots & 0\n",
315 | "\\end{bmatrix}\n",
316 | "\\frac{\\partial \\, \\mathbf{a}_{\\mathbf{x}_{1}}}{\\partial \\, \\mathbf{z}_{\\mathbf{x}_{1}}} \\\\[5pt]\n",
317 | "&= \n",
318 | "\\underbrace{\n",
319 | "\\begin{bmatrix}\n",
320 | "0 & -\\dfrac{1}{a_{21}} & 0 & \\cdots & 0 \\\\\n",
321 | "\\end{bmatrix}\n",
322 | "}_{ \\dfrac{\\partial C}{\\partial a^{L}_{c1}} }\n",
323 | "\\underbrace{\n",
324 | "\\begin{bmatrix}\n",
325 | "a_{11}(1-a_{11}) & -a_{11}a_{21} & \\cdots & -a_{11}a_{C1} \\\\[4pt]\n",
326 | "-a_{21}a_{11} & a_{21}(1-a_{21}) & \\cdots & -a_{21}a_{C1} \\\\[4pt]\n",
327 | "-a_{31}a_{11} & -a_{31}a_{21} & \\cdots & -a_{31}a_{C1} \\\\[4pt]\n",
328 | "\\vdots & \\vdots & \\ddots & \\vdots \\\\[4pt]\n",
329 | "-a_{C1}a_{11} & -a_{C1}a_{21} & \\cdots & a_{C1}(1-a_{C1})\n",
330 | "\\end{bmatrix}\n",
331 | "}_{ \\dfrac{\\partial \\, \\mathbf{a}_{\\mathbf{x}_{1}}}{\\partial \\, \\mathbf{z}_{\\mathbf{x}_{1}}} }\n",
332 | "\\end{align}\n",
333 | "$$\n",
334 | "\n",
335 | "위 식에서 $\\boldsymbol{\\delta}_{\\mathbf{x}_{1}}^{L}$를 구하기 위해서는 결국 앞 항의 $-\\dfrac{1}{a_{21}}$를 뒤 항의 두번째 행 $\\left[ -a_{21}a_{11} \\,,\\, a_{21}(1-a_{21}) \\,,\\, \\cdots \\,,\\, -a_{21}a_{C1} \\right]$와 곱하면 된다. 나머지 샘플 $\\mathbf{x}$에 대해서도 마찬가지이므로 코스트 함수 미분 행렬에서 값이 있는 것만 뽑아서 벡터로 만든다. 그리고 softmax를 미분한 $N$개의 행렬에서 코스트 함수 미분 행렬에서 값이 있는 위치의 행만을 뽑아서 하나의 행렬로 만든다. 지금 예를 들어 정답자리를 $y_{21}$, $y_{32}$, $y_{CN}$등으로 가정하고 있기 때문에 이 경우는 아래처럼 벡터와 행렬이 구성된다.\n",
336 | "\n",
337 | "$$\n",
338 | "\\left( \\boldsymbol{\\delta}^{L} \\right)^{\\text{T}} = \n",
339 | "\\underbrace{\n",
340 | "\\begin{Bmatrix} -\\dfrac{1}{a_{21}} \\\\ -\\dfrac{1}{a_{32}} \\\\ \\vdots \\\\ -\\dfrac{1}{a_{CN}} \\end{Bmatrix}\n",
341 | "}_{N \\times 1}\n",
342 | "\\times\n",
343 | "\\underbrace{\\begin{bmatrix}\n",
344 | "\\dfrac{\\partial \\, a_{21}}{\\partial z_{11}} & \\dfrac{\\partial \\, a_{21}}{\\partial z_{21}}& \\cdots & \\dfrac{\\partial \\, a_{21}}{\\partial z_{C1}} \\\\[4pt]\n",
345 | "\\dfrac{\\partial \\, a_{32}}{\\partial z_{12}} & \\dfrac{\\partial \\, a_{32}}{\\partial z_{22}}& \\cdots & \\dfrac{\\partial \\, a_{32}}{\\partial z_{C2}} \\\\[4pt]\n",
346 | "\\vdots & \\vdots & \\ddots & \\vdots \\\\[4pt]\n",
347 | "\\dfrac{\\partial \\, a_{CN}}{\\partial z_{1N}} & \\dfrac{\\partial \\, a_{CN}}{\\partial z_{2N}}& \\cdots & \\dfrac{\\partial \\, a_{CN}}{\\partial z_{CN}}\n",
348 | "\\end{bmatrix}\n",
349 | "}_{N \\times C}=\\underbrace{\\begin{bmatrix}\n",
350 | "\\left( \\boldsymbol{\\delta}_{\\mathbf{x}_{1}}^{L} \\right)^{\\text{T}} \\\\[4pt]\n",
351 | "\\left( \\boldsymbol{\\delta}_{\\mathbf{x}_{2}}^{L} \\right)^{\\text{T}} \\\\[4pt]\n",
352 | "\\vdots \\\\[4pt]\n",
353 | "\\left( \\boldsymbol{\\delta}_{\\mathbf{x}_{N}}^{L} \\right)^{\\text{T}}\n",
354 | "\\end{bmatrix} }_{N \\times C}\n",
355 | "$$\n",
356 | "\n",
357 | "여기서 곱하기는 python의 요소끼리 브로드캐스팅되는 곱을 의미한다.\n",
358 | "\n",
359 | "앞 항의 $N \\times 1$ 벡터를 전치된 상태로 구하는 코드는 다음과 같다. (위 수식에서 결과를 전치시켜야 $\\boldsymbol{\\delta}^{L}$이 되므로 바로 구하기 위해 전치된 상태로 각 미분 결과를 되될림)\n",
360 | "\n",
361 | "\n",
362 | "```python\n",
363 | "def cross_entropy_cost_prime_with_softmax(a, y):\n",
364 | " y_label = np.where(y.T==1)[1]\n",
365 | " a_t = a[y_label, np.arange(y.shape[1])].reshape(-1,1) #(N,1)\n",
366 | " a_t[np.where(a_t < np.finfo(np.float32).eps)] = np.finfo(np.float32).eps\n",
367 | " \n",
368 | " dC = -1/a_t # dC : (N,1)\n",
369 | " return dC.T # (1,N)\n",
370 | "```\n",
371 | "\n",
372 | "뒷 항의 $N \\times C$ 행렬도 전치된 상태로 구하는 코드가 다음이다. 원래 softmax 미분하는데는 정답 $\\mathbf{y}$가 필요하지 않지만 여기서는 정답자리를 알아야 하기 때문에 정답을 인자로 받는다. (그래서 원래 softmax_prime함수에서는 $\\mathbf{y}$가 필요하지 않지만 인자를 통일시키기 위해 더미로 $\\mathbf{y}$를 입력 받았었다.)\n",
373 | "\n",
374 | "```python\n",
375 | "def softmax_prime_with_crossent(z, y):\n",
376 | " C, N = z.shape\n",
377 | " y_label = np.where(y.T==1)[1] #one-hot decoding , col major라서 전치후 where 적용\n",
378 | " \n",
379 | " a = softmax(z) # a : (C,N)\n",
380 | " a_t = a[y_label, np.arange(N)].reshape(-1,1) #(N,1)\n",
381 | " \n",
382 | " #da/dz\n",
383 | " da = -a_t * a.T #dz : (N,1)*(N,C)=(N,C)\n",
384 | " da[np.arange(N), y_label] = a_t.T * (1- a_t.T)\n",
385 | " return da.T # (C,N)\n",
386 | "```\n",
387 | "\n",
388 | "그래서 이제 sigmoid, tanh, relu를 출력층 활성함수로 쓴 경우와 똑같이 아래 코드로 출력층 $\\boldsymbol{\\delta}^{L}$를 구할 수 있다.\n",
389 | "\n",
390 | "```python\n",
391 | "delta = dC * da #dC/da, da/dz 둘 모양이 같으면 그냥 Hadamard 곱, 결과:(C,N)\n",
392 | "```\n",
393 | "\n",
394 | "이런 식으로 동작하는 것을 확인 해보려면 아래 코드에서 주석을 바꿔주고 실험해보면 된다.\n",
395 | "\n",
396 | "```python\n",
397 | "Softmax = {'f':softmax, 'df':softmax_prime }\n",
398 | "#Softmax = {'f':softmax, 'df':softmax_prime_with_crossent }\n",
399 | "\n",
400 | "CrossEnt = {'f':cross_entropy_cost, 'df':cross_entropy_cost_prime}\n",
401 | "#CrossEnt = {'f':cross_entropy_cost, 'df':cross_entropy_cost_prime_with_softmax}\n",
402 | "```\n",
403 | "\n",
404 | "이렇게 해서 역전파의 기본 4개 수식을 있는 그대로 코드로 구현하게 되었다. \n"
405 | ]
406 | },
407 | {
408 | "cell_type": "markdown",
409 | "metadata": {},
410 | "source": [
411 | "#### 2.1.4 더 그냥 쉽게....\n",
412 | "\n",
413 | "2.1.2절 마지막 식의 곱이나 2.1.3절의 곱을 실제로 해보면 앞에서 이야기 했듯이 $\\boldsymbol{\\delta}^{L}_{\\mathbf{x}_{1}} = \\mathbf{a}^{L}_{\\mathbf{x}_{1}} - \\mathbf{y}_{\\mathbf{x}_{1}}$ \n",
414 | "라는 사실을 알 수 있다. 그래서 복잡하게 하지말고 softmax는 cross entropy하고 쓰니까 그냥 출력층 $\\boldsymbol{\\delta}^{L}$은 $ \\mathbf{a} - \\mathbf{y}$하면 된다. $ \\mathbf{a}$는 $C \\times N$이고 원핫인코딩된 $\\mathbf{y}$도 $C \\times N$ 이라 그냥 빼면 모든 것이 해결된다. 그래서 이 둘을 묶어서 Softmax classifier라 하기도 한다. 그냥 공부하려고 복잡하게 한 것임;;; Nielsen문서나 CS231n 숙제에도 보면 다 $ \\mathbf{a} - \\mathbf{y}$로 구현되어 있다. 이렇게 구현하면 효율적이기는 하나 위에 적은 역전파의 기본 4개 식중에서 1번식은 코드에 나타나지 않게 되어 수식을 보고 코드를 볼 때 수식이 생략된 부분때문에 코드를 해석하기 어려워진다는 단점이 있다. \n"
415 | ]
416 | },
417 | {
418 | "cell_type": "markdown",
419 | "metadata": {},
420 | "source": [
421 | "### 2.2 Dropout 구현\n",
422 | "\n",
423 | "Dropout 구현을 위해서 다음 3가지 코드가 필요하다.\n",
424 | "\n",
425 | "[1] 포워드 패스시 계산된 출력 a에 대해 다음 코드를 적용하여 강제로 일정 비율 만큼 값을 0으로 만들어 버림\n",
426 | "\n",
427 | "\n",
428 | "```python\n",
429 | "z = np.dot(self.W[l], a)+self.B[l]\n",
430 | "a = self.activates[l]['f'](z)\n",
431 | "\n",
432 | "mask = np.random.binomial(n=1, p=1-self.p_dropouts[l], size=a.shape)\n",
433 | "self.mask.append(mask)\n",
434 | "a = mask*a\n",
435 | "```\n",
436 | "\n",
437 | "[2] 백워드 패스시 위 포워드 패스시 생성된 mask를 해당 층의 $\\delta$에 곱해준다.\n",
438 | "\n",
439 | "```python\n",
440 | "delta = delta*self.mask[l] # 포워드에서 저장해둔 드랍아웃 마스크를 곱한다.\n",
441 | "```\n",
442 | "\n",
443 | "[3] 마지막으로 테스트시 Dropout이 적용된 층의 출력에 (1-Dropout 비율)을 곱하여 출력 수준을 경감시킨다. \n",
444 | "\n",
445 | "```python\n",
446 | "a = self.activates[l]['f']( (1-self.p_dropouts[l-1])*np.dot(self.W[l], a)+self.B[l] )\n",
447 | "```"
448 | ]
449 | },
450 | {
451 | "cell_type": "code",
452 | "execution_count": 30,
453 | "metadata": {},
454 | "outputs": [
455 | {
456 | "name": "stdout",
457 | "output_type": "stream",
458 | "text": [
459 | "EPOCH:01 Train Acc. : 93.970000, Test Acc. : 94.240000\n",
460 | "EPOCH:02 Train Acc. : 95.386000, Test Acc. : 95.310000\n",
461 | "EPOCH:03 Train Acc. : 96.026000, Test Acc. : 95.930000\n",
462 | "EPOCH:04 Train Acc. : 96.422000, Test Acc. : 95.990000\n",
463 | "EPOCH:05 Train Acc. : 96.826000, Test Acc. : 96.140000\n",
464 | "EPOCH:06 Train Acc. : 96.830000, Test Acc. : 96.300000\n",
465 | "EPOCH:07 Train Acc. : 97.158000, Test Acc. : 96.760000\n",
466 | "EPOCH:08 Train Acc. : 97.216000, Test Acc. : 96.640000\n",
467 | "EPOCH:09 Train Acc. : 97.340000, Test Acc. : 96.570000\n",
468 | "EPOCH:10 Train Acc. : 97.514000, Test Acc. : 96.740000\n",
469 | "EPOCH:11 Train Acc. : 97.596000, Test Acc. : 96.830000\n",
470 | "EPOCH:12 Train Acc. : 97.534000, Test Acc. : 96.690000\n",
471 | "EPOCH:13 Train Acc. : 97.682000, Test Acc. : 96.810000\n",
472 | "EPOCH:14 Train Acc. : 97.754000, Test Acc. : 96.940000\n",
473 | "EPOCH:15 Train Acc. : 97.544000, Test Acc. : 96.580000\n",
474 | "EPOCH:16 Train Acc. : 97.746000, Test Acc. : 96.720000\n",
475 | "EPOCH:17 Train Acc. : 97.902000, Test Acc. : 96.990000\n",
476 | "EPOCH:18 Train Acc. : 97.926000, Test Acc. : 96.910000\n",
477 | "EPOCH:19 Train Acc. : 97.812000, Test Acc. : 96.960000\n",
478 | "EPOCH:20 Train Acc. : 98.048000, Test Acc. : 97.050000\n",
479 | "EPOCH:21 Train Acc. : 98.184000, Test Acc. : 97.450000\n",
480 | "EPOCH:22 Train Acc. : 98.056000, Test Acc. : 97.130000\n",
481 | "EPOCH:23 Train Acc. : 97.988000, Test Acc. : 97.200000\n",
482 | "EPOCH:24 Train Acc. : 98.110000, Test Acc. : 97.250000\n",
483 | "EPOCH:25 Train Acc. : 97.990000, Test Acc. : 97.090000\n",
484 | "EPOCH:26 Train Acc. : 97.976000, Test Acc. : 97.200000\n",
485 | "EPOCH:27 Train Acc. : 97.922000, Test Acc. : 96.870000\n",
486 | "EPOCH:28 Train Acc. : 97.882000, Test Acc. : 96.900000\n",
487 | "EPOCH:29 Train Acc. : 98.114000, Test Acc. : 97.250000\n",
488 | "EPOCH:30 Train Acc. : 98.176000, Test Acc. : 97.150000\n"
489 | ]
490 | },
491 | {
492 | "data": {
493 | "image/png": "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\n",
494 | "text/plain": [
495 | ""
496 | ]
497 | },
498 | "metadata": {},
499 | "output_type": "display_data"
500 | },
501 | {
502 | "name": "stdout",
503 | "output_type": "stream",
504 | "text": [
505 | "Target : [7 2 1 0 4 1 4 9 5 9 0 6 9 0 1 5 9 7 3 4 9 6 6 5 4 0 7 4 0 1]\n",
506 | "Predict : [7 2 1 0 4 1 4 9 6 9 0 6 9 0 1 5 9 7 3 4 9 6 6 5 4 0 7 4 0 1]\n",
507 | "29\n"
508 | ]
509 | },
510 | {
511 | "data": {
512 | "image/png": "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\nAABJRU5ErkJggg==\n",
513 | "text/plain": [
514 | ""
515 | ]
516 | },
517 | "metadata": {},
518 | "output_type": "display_data"
519 | },
520 | {
521 | "data": {
522 | "image/png": "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\nf93MviFpt5k9n9U2uPt/tK89AO1SN/zuflDSwezxUTN7V9LsdjcGoL1O6z2/mfVKWiDpD9mie83s\nLTPbZGYX1NhmrZlVzKxSrVZbahZAfhoOv5lNl/QbST9y9z9L+pmkb0mar7FXBg9PtJ27D7p72d3L\npVIph5YB5KGh8JvZZI0F/9fu/ltJcvdD7n7C3U9K+rmkhe1rE0DeGrnab5J+Ieldd//JuOWzxq32\nPUnv5N8egHZp5Gr/tyWtlvS2me3JlvVLutXM5ktyScOSftCWDgG0RSNX+38vySYoPZt/OwA6hU/4\nAUERfiAowg8ERfiBoAg/EBThB4Ii/EBQhB8IivADQRF+ICjCDwRF+IGgCD8QFOEHgjJ379zOzKqS\n/jRu0QxJhzvWwOnp1t66tS+J3pqVZ2/z3L2h++V1NPxf27lZxd3LhTWQ0K29dWtfEr01q6jeeNkP\nBEX4gaCKDv9gwftP6dbeurUvid6aVUhvhb7nB1Ccos/8AApSSPjNbIWZvWdmH5jZfUX0UIuZDZvZ\n29nMw5WCe9lkZqNm9s64ZRea2fNmNpT9nnCatIJ664qZmxMzSxd67LptxuuOv+w3s0mS3pe0TNKI\npNck3eruf+xoIzWY2bCksrsXPiZsZv8g6ZikX7l7X7bs3yV94u4D2X+cF7j7v3RJb+slHSt65uZs\nQplZ42eWlnSzpH9Sgccu0dcqFXDcijjzL5T0gbt/5O7HJW2RtLKAPrqeu78o6ZOvLF4paXP2eLPG\n/vF0XI3euoK7H3T317PHRyWdmlm60GOX6KsQRYR/tqT9456PqLum/HZJvzOz3Wa2tuhmJtCTTZsu\nSR9L6imymQnUnbm5k74ys3TXHLtmZrzOGxf8vm6Ju/+dpO9I+mH28rYr+dh7tm4armlo5uZOmWBm\n6b8o8tg1O+N13ooI/wFJF417Pidb1hXc/UD2e1TSU+q+2YcPnZokNfs9WnA/f9FNMzdPNLO0uuDY\nddOM10WE/zVJl5jZN81siqTvS9pWQB9fY2bTsgsxMrNpkpar+2Yf3iZpTfZ4jaRnCuzlr3TLzM21\nZpZWwceu62a8dveO/0i6QWNX/D+U9K9F9FCjr7+V9Gb2s7fo3iQ9rrGXgV9o7NrIXZL+RtJOSUOS\n/lfShV3U26OS3pb0lsaCNqug3pZo7CX9W5L2ZD83FH3sEn0Vctz4hB8QFBf8gKAIPxAU4QeCIvxA\nUIQfCIrwA0ERfiAowg8E9f9qpE+21VoFiQAAAABJRU5ErkJggg==\n",
523 | "text/plain": [
524 | ""
525 | ]
526 | },
527 | "metadata": {},
528 | "output_type": "display_data"
529 | }
530 | ],
531 | "source": [
532 | "import pickle, gzip\n",
533 | "import numpy as np\n",
534 | "import matplotlib.pyplot as plt\n",
535 | "\n",
536 | "#--------------------------------------------------------------------\n",
537 | "# activation functions\n",
538 | "# 활성함수의 함수값을 계산하는 함수와 미분값을 계산하는 함수를 한쌍으로 정의\n",
539 | "# 'f'와 'df' 키워드로 묶음\n",
540 | "def sigmoid(z):\n",
541 | " \"\"\"\n",
542 | " Args:\n",
543 | " z : (C, N), C:클래스 수, N:샘플 수 이하 동일\n",
544 | " \"\"\"\n",
545 | " return 1.0/(1.0+np.exp(-z))\n",
546 | " \n",
547 | "def sigmoid_prime(z, y):\n",
548 | " \"\"\"\n",
549 | " Args:\n",
550 | " z : (C, N)\n",
551 | " y : dummy\n",
552 | " \n",
553 | " [참고]\n",
554 | " 여기서는 정답 벡터 y가 필요없지만 softmax_prime_with_crossent과 인터페이스를 맞추기 위해\n",
555 | " 더미로 y를 넘겨 받음 None로 넘겨주면 됨.\n",
556 | " \"\"\" \n",
557 | " return sigmoid(z)*(1-sigmoid(z))\n",
558 | "\n",
559 | "Sigmoid = { 'f': sigmoid, 'df': sigmoid_prime }\n",
560 | "\n",
561 | "def softmax(z):\n",
562 | " \"\"\"\n",
563 | " Args:\n",
564 | " z : (C, N)\n",
565 | " \n",
566 | " [참고]\n",
567 | " max값을 빼서 언더플로 방지함.\n",
568 | " \"\"\"\n",
569 | " z = z - np.max(z, axis=0) #max함수는 elmenet wise하게 작동하지 않기 때문에 axis을 지정해야함.\n",
570 | " return np.exp(z) / np.sum(np.exp(z), axis=0)\n",
571 | "\n",
572 | "def softmax_prime(z, y):\n",
573 | " \"\"\"\n",
574 | " Args:\n",
575 | " z : (C, N)\n",
576 | " y : dummy\n",
577 | " \n",
578 | " [참고]\n",
579 | " 실제 softmax 함수 미분을 그대로 구현한것 \n",
580 | " 미분 결과는 샘플 하나당 (C,C) 행렬이 되어 전체 결과는 (N,C,C)의 텐서\n",
581 | " 보통은 softmax함수와 cross entropy를 함께 쓰므로 둘의 미분 곱해진 결과만 delta로 코딩을 함.\n",
582 | " 굳이 미분을 코딩한다 하더라도 둘의 곱이 간단히 Hadamard곱이 될 수 있도록\n",
583 | " 행렬로 정리해서 되돌리는 구현을 함. 그렇게 구현한 함수가 \n",
584 | " 아래 softmax_prime_with_crossent 임.\n",
585 | " \n",
586 | " 여기서는 학습의 목적으로 미분 그대로를 다 구현했음.\n",
587 | " \"\"\"\n",
588 | " C, N = z.shape\n",
589 | " di = np.diag_indices(C)\n",
590 | " \n",
591 | " a = softmax(z) # a : (C,N)\n",
592 | " da = -np.einsum('ij,jk->jik', a, a.T) #da :(N,C,C)\n",
593 | " da[:,di[0],di[1]] = (a*(1-a)).T\n",
594 | " \n",
595 | " return da\n",
596 | "\n",
597 | "def softmax_prime_with_crossent(z, y):\n",
598 | " \"\"\"\n",
599 | " Args:\n",
600 | " z : (C, N)\n",
601 | " y : col major로 인코딩된 one hot (C, N)\n",
602 | " \n",
603 | " [참고]\n",
604 | " 이 함수는 softmax_prime의 주석에서 설명한것처럼 결과를 행렬로 만들어 되돌리는 축약형\n",
605 | " 이 함수의 구현은 Cross Entropy 코스트와 함께 사용하도록 하기 위한 것으로 결과가 텐서\n",
606 | " 가 아니라 행렬이 되도록 구현하였음. 이렇게 구현하면 Sigmoid-Mse 를 쓴 구성과 마찬가지로 \n",
607 | " 출력층의 델터를 구하는 코드가 코스트의 미분 행렬(일반적으로 샘플이 여러개일때 코스트\n",
608 | " 의미분은 행렬이 됨) * 출력층 활성함수의 미분 행렬로 동일하게 됨. \n",
609 | " 실제 Cross Entropy-Softmax일 경우는 벡터*행렬이 됨.\n",
610 | " 일반적으로 구현하여 텐서를 되돌리는 softmax_prime함수를 쓰면 코스트의 미분과 출력층 \n",
611 | " 활성함수의 미분을 곱하는 곳에서 행렬 * 텐서가 되서 적당히 알아서 곱해야 해야함.\n",
612 | " \"\"\" \n",
613 | " C, N = z.shape\n",
614 | " y_label = np.where(y.T==1)[1] #one-hot decoding , col major라서 전치후 where 적용\n",
615 | " \n",
616 | " #정답 위치를 알아아됨\n",
617 | " a = softmax(z) # a : (C,N)\n",
618 | " a_t = a[y_label, np.arange(N)].reshape(-1,1) #(N,1)\n",
619 | " \n",
620 | " #da/dz\n",
621 | " da = -a_t * a.T #dz : (N,1)*(N,C)= (N,C)\n",
622 | " da[np.arange(N), y_label] = a_t.T * (1- a_t.T)\n",
623 | " return da.T # (C,N)\n",
624 | " \n",
625 | "Softmax = {'f':softmax, 'df':softmax_prime }\n",
626 | "#Softmax = {'f':softmax, 'df':softmax_prime_with_crossent }\n",
627 | "\n",
628 | "def relu(z):\n",
629 | " \"\"\"\n",
630 | " Args:\n",
631 | " z : (C, N)\n",
632 | " \"\"\"\n",
633 | " return np.maximum(0, z) #maximum 함수는 element wise로 작동을 함.\n",
634 | "\n",
635 | "def relu_prime(z, y):\n",
636 | " \"\"\"\n",
637 | " Args:\n",
638 | " z : (C, N)\n",
639 | " y : dummy\n",
640 | " \"\"\"\n",
641 | " #da/dz\n",
642 | " da = np.zeros(z.shape)\n",
643 | " da[z >= 0] = 1\n",
644 | " return da\n",
645 | "\n",
646 | "Relu = {'f':relu, 'df':relu_prime }\n",
647 | "\n",
648 | "\n",
649 | "#--------------------------------------------------------------------\n",
650 | "# Cost function\n",
651 | "# 목적함수의 함수값을 계산하는 함수와 미분값을 계산하는 함수를 한쌍으로 정의\n",
652 | "# 'f'와 'df' 키워드로 묶음\n",
653 | "def mse_cost(a, y):\n",
654 | " \"\"\"\n",
655 | " Args:\n",
656 | " a : (C, N)\n",
657 | " y : col major로 인코딩된 one hot (C, N)\n",
658 | " \"\"\"\n",
659 | " return 0.5*np.linalg.norm(a-y)**2\n",
660 | "\n",
661 | "def mse_cost_prime(a, y):\n",
662 | " \"\"\"\n",
663 | " Args:\n",
664 | " a : (C, N)\n",
665 | " y : col major로 인코딩된 one hot (C, N)\n",
666 | " \"\"\"\n",
667 | " return (a-y)\n",
668 | "\n",
669 | "Mse = {'f':mse_cost, 'df':mse_cost_prime}\n",
670 | "\n",
671 | "def bin_cross_entropy_cost(a, y):\n",
672 | " \"\"\"\n",
673 | " Args:\n",
674 | " a : (C, N)\n",
675 | " y : col major로 인코딩된 one hot (C, N)\n",
676 | " \n",
677 | " [참고]\n",
678 | " C = \\sum_{j} -[y_j*log(a_j)+(1-y_j)*log(1-a_y)]\n",
679 | " j는 출력층 뉴런의 Index , mini-batch전체에 대해서 평균내는 부분은 없음\n",
680 | " \"\"\"\n",
681 | " return np.sum(np.nan_to_num(-y*np.log(a)-(1-y)*np.log(1-a)))\n",
682 | "\n",
683 | "def bin_cross_entropy_cost_prime(a, y): \n",
684 | " \"\"\" \n",
685 | " Args:\n",
686 | " a : (C, N)\n",
687 | " y : col major로 인코딩된 one hot (C, N)\n",
688 | " \n",
689 | " [참고]\n",
690 | " http://neuralnetworksanddeeplearning.com/chap3.html 의 Problem에 이 함수와 관련된 문제가 있음\n",
691 | " 분모의 a(네트워크의 활성값)이 1 근처로 가면 0으로 나누는 것과 같은 문제가 \n",
692 | " 발생할 가능성 있음.\n",
693 | " return (a-y) / (a*(1-a)) 이렇게 하면 실제로 오버플로 일어남.\n",
694 | " \n",
695 | " 출력증의 델타는 delta^{L} = dC/da * da/dz 인데 \n",
696 | " 결국 이 미분이 sigmoid의 미분과 곱해지면 분모가 약분되어 사라지면서 \n",
697 | " a-y가 delta가 됨. 그래서 delta를 바로 코딩하는 방식을 주로 사용. \n",
698 | " 그럼 오버플로문제도 없어짐\n",
699 | " 이 코스트함수는 그렇게 sigmoid와 함께 사용하도록 의도적으로 설계된 함수임.\n",
700 | " \n",
701 | " 여기서는 학습의 목적으로 미분을 그대로 다 구현했기 때문에 이 함수를 softmax와\n",
702 | " 조합해서 사용하는 것도 가능함.\n",
703 | " https://www.reddit.com/r/MachineLearning/comments/39bo7k/can_softmax_be_used_with_cross_entropy/?st=j51xzboh&sh=1743ac2e\n",
704 | " 위 레딧 링크에서는 안된다하는데 딱히 안될건 없어보이는데... 결과도 괜찮게 나옴\n",
705 | " \"\"\"\n",
706 | " denom = (a*(1-a))\n",
707 | " denom[np.where(denomjk', dC, da).T #모양이 다르면 einsum 으로 곱, 결과:(C,N)\n",
905 | " else : #for delta^{l}\n",
906 | " delta = np.dot(self.W[l+1].T, delta) * self.activates[l]['df'](self.Z[l], Y)\n",
907 | " delta = delta*self.mask[l] # 포워드에서 저장해둔 드랍아웃 마스크를 곱한다.\n",
908 | " \n",
909 | " self.dB[l] = delta.sum(axis=1).reshape(-1,1)\n",
910 | " self.dW[l] = np.dot(delta, self.A[l-1].T)\n",
911 | " \n",
912 | " def update(self):\n",
913 | " # w = w - eta (∂C/∂w) \n",
914 | " # = w - eta (∂C0/∂w + lambda/n * w)\n",
915 | " # = w - eta * ∂C0/∂w - eta * lambda/n * w\n",
916 | " # = (1 - eta * lambda/n)*w - eta * ∂C0/∂w\n",
917 | " # for mini-batch\n",
918 | " # = (1 - eta * lambda/n)*w - eta * (1/m) * sum_{i=1}^{m}∂C_i/∂w\n",
919 | " self.W = [None] + [ (1-self.eta*(self.lmbda/self.n))*w - (self.eta/self.m)*nw \n",
920 | " for w, nw in zip(self.W[1:], self.dW[1:]) ]\n",
921 | " self.B = [None] + [ b - (self.eta/self.m)*db for b, db in zip(self.B[1:], self.dB[1:]) ]\n",
922 | " \n",
923 | " def train(self, X, Y, epochs, mini_batch_size, learning_rate, p_dropouts, lmbda=0.0, X_test=None, Y_test=None):\n",
924 | " self.n = X.shape[1] # 학습데이터 개수 \n",
925 | " self.m = mini_batch_size # 미니배치 크기\n",
926 | " self.eta = learning_rate # 학습률\n",
927 | " self.p_dropouts = p_dropouts # 드랍아웃 퍼센티지\n",
928 | " self.lmbda = lmbda # 레귤러라이제이션 파라메터\n",
929 | " \n",
930 | " XY = np.vstack((X, Y.T)).T # shuffle을 위해 row major상태를 만든다.\n",
931 | " train_acc, test_acc = [], []\n",
932 | " \n",
933 | " for j in range(epochs):\n",
934 | " np.random.shuffle(XY) #row 를 섞어 버린다.\n",
935 | " mini_batches = [ XY[k:k+mini_batch_size] for k in range(0, self.n, mini_batch_size) ]\n",
936 | " \n",
937 | " for mini_batch in mini_batches:\n",
938 | " #row major->col major, 정답인 마지막 행을 잘라 낸다.\n",
939 | " mini_batch_X, mini_batch_Y = np.vsplit(mini_batch.T, np.array([-1])) \n",
940 | " mini_batch_Y = mini_batch_Y.T.astype(int) #잘려진 마지막 행은 다시 열로 바꾼다.\n",
941 | " \n",
942 | " self.forward(mini_batch_X) \n",
943 | " self.backward(mini_batch_Y) \n",
944 | " self.update() \n",
945 | " \n",
946 | " if X_test is not None and Y_test is not None:\n",
947 | " train_acc.append(self.evaluate(X, Y)/self.n*100)\n",
948 | " test_acc.append(self.evaluate(X_test, Y_test)/Y_test.shape[0]*100)\n",
949 | " print(\"EPOCH:{:02d} Train Acc. : {:f}, Test Acc. : {:f}\".format(j+1, train_acc[-1], test_acc[-1]))\n",
950 | " \n",
951 | " #plot acc\n",
952 | " plt.plot(train_acc)\n",
953 | " plt.plot(test_acc)\n",
954 | " plt.ylim(90, 100)\n",
955 | " plt.show()\n",
956 | " \n",
957 | " def evaluate(self, X_test, Y_test):\n",
958 | " pred = np.argmax( self.pred(X_test), axis=0 )\n",
959 | " return (pred.reshape(-1,1) == Y_test).sum()\n",
960 | " \n",
961 | "#--------------------------------------------------------------------\n",
962 | "# TEST\n",
963 | "# Layers 적당히 리스트에 숫자를 적어주면 됨 \n",
964 | "sizes = [784, 100, 10]\n",
965 | "\n",
966 | "# Activation functions 각층에 할당되는 활성화 함수를 적어줌\n",
967 | "activates = [None, Relu, Softmax]\n",
968 | "\n",
969 | "#dropouts 각층에 할당되는 drop out rate로 뉴런을 꺼버릴 비율을 나타낸다.\n",
970 | "p_dropouts = [0.0, 0.5, 0.0]\n",
971 | "\n",
972 | "Net = Network(sizes, activates, CrossEnt, 'He')\n",
973 | "Net.train(X_train, Y_train, epochs=30, mini_batch_size=10, learning_rate=0.1, \n",
974 | " p_dropouts=p_dropouts, lmbda=5.0, X_test=X_test, Y_test=Y_test)\n",
975 | "\n",
976 | "a = Net.pred(X_test[:,:30])\n",
977 | "pred = np.argmax(a, axis=0)\n",
978 | "print(\"Target : {}\".format(Y_test[:30].T[0]))\n",
979 | "print(\"Predict : {}\".format(pred))\n",
980 | "comp = pred.reshape(-1,1) == Y_test[:30]\n",
981 | "print(comp.sum())\n",
982 | "errors = np.where(comp == False)[0]\n",
983 | "plt.imshow(a)\n",
984 | "plt.show()\n",
985 | "\n",
986 | "#error rendering\n",
987 | "for err in errors :\n",
988 | " x = X_test[:,:30][:,err]\n",
989 | " x = x.reshape(28,28)\n",
990 | " plt.imshow(x, cmap='gray_r')\n",
991 | " plt.show()\n",
992 | " "
993 | ]
994 | },
995 | {
996 | "cell_type": "markdown",
997 | "metadata": {},
998 | "source": [
999 | "| Layers | Actvations | Cost | Epoches | Mini-batch | Leanrning rate | Test Acc. | etc |\n",
1000 | "|--------------|------------|-------------|---------|------------|----------------|-------|-----|\n",
1001 | "| [784 150 60 10] | Relu, Relu, Softmax | CrossEnt | 60 | 25 | 0.18 | 98.50 |Xavier, reg. $\\lambda=6.0$, max. at epoch 29 |\n",
1002 | "| [784 150 60 10] | Relu, Relu, Softmax | BinCrossEnt | 60 | 20 | 0.14 | 98.11 |Xavier, reg. $\\lambda=5.2$, max. at epoch 40 |\n",
1003 | "| [784 150 60 10] | Relu, Relu, Softmax | CrossEnt | 60 | 20 | 0.14 | 98.45 |Xavier, reg. $\\lambda=5.2$, max. at epoch 40 |\n",
1004 | "| [784 150 60 10] | Relu, Relu, Softmax | CrossEnt | 60 | 20 | 0.15 | 98.43 |Xavier, reg. $\\lambda=5.5$, max. at epoch 56 |\n",
1005 | "| [784 150 60 10] | Relu, Relu, Softmax | CrossEnt | 60 | 20 | 0.15 | 98.43 |Xavier, reg. $\\lambda=5.0$, max. at epoch 45 |\n",
1006 | "| [784 150 60 10] | Sigmoid, Sigmoid, Softmax | CrossEnt | 60 | 20 | 0.5 | 98.24 |Xavier, reg. $\\lambda=5.0$, max. at epoch 36 |\n",
1007 | "| [784 100 10] | Sigmoid, Softmax | CrossEnt | 60 | 5 | 0.1 | 98.10 |Xavier, reg. $\\lambda=5.0$, max. at epoch 58 |\n",
1008 | "| [784 100 10] | Sigmoid, Softmax | CrossEnt | 60 | 20 | 0.5 | 98.05 |Xavier, reg. $\\lambda=5.0$, max. at epoch 33 |\n",
1009 | "| [784 100 10] | Sigmoid, Softmax | CrossEnt | 60 | 20 | 0.5 | 97.80 |Xavier, max. at epoch 23 |\n",
1010 | "| [784 100 10] | Sigmoid, Softmax | BinCrossEnt | 60 | 5 | 0.1 | 98.13 |Xavier, reg. $\\lambda=5.0$, max. at 55 epoch |\n",
1011 | "| [784 100 10] | Sigmoid, Sigmoid | BinCrossEnt | 60 | 5 | 0.1 | 98.06 |Xavier, reg. $\\lambda=5.0$, max. at 45 epoch |\n",
1012 | "| [784 100 10] | Sigmoid, Sigmoid | BinCrossEnt | 60 | 20 | 0.5 | 97.92 |Xavier, max. at epoch 39, |\n",
1013 | "| [784 100 10] | Sigmoid, Sigmoid | Mse | 60 | 20 | 3 | 98.04 |Xavier, max. at epoch 45 |\n",
1014 | "\n"
1015 | ]
1016 | },
1017 | {
1018 | "cell_type": "markdown",
1019 | "metadata": {},
1020 | "source": [
1021 | "이상으로 간단한 네트워크를 작성해보았다. 애초에 의도했던대로 Numpy로만 150줄내외에서 (주석지우고 softmax, cross entropy 축약버전, 그림 그리는 코드, 마지막 테스트 코드 제외하면 165줄;;;;) 구현 요구사항을 만족시키도록 작성했다.\n"
1022 | ]
1023 | },
1024 | {
1025 | "cell_type": "code",
1026 | "execution_count": 1,
1027 | "metadata": {},
1028 | "outputs": [
1029 | {
1030 | "data": {
1031 | "text/html": [
1032 | "\n",
1033 | "\n",
1034 | "\n",
1035 | "\n",
1036 | "\n",
1037 | "\n"
1063 | ],
1064 | "text/plain": [
1065 | ""
1066 | ]
1067 | },
1068 | "metadata": {},
1069 | "output_type": "display_data"
1070 | }
1071 | ],
1072 | "source": [
1073 | "%%html\n",
1074 | "\n",
1075 | "\n",
1076 | "\n",
1077 | "\n",
1078 | "\n",
1079 | ""
1105 | ]
1106 | },
1107 | {
1108 | "cell_type": "code",
1109 | "execution_count": null,
1110 | "metadata": {},
1111 | "outputs": [],
1112 | "source": []
1113 | }
1114 | ],
1115 | "metadata": {
1116 | "kernelspec": {
1117 | "display_name": "Python 3",
1118 | "language": "python",
1119 | "name": "python3"
1120 | },
1121 | "language_info": {
1122 | "codemirror_mode": {
1123 | "name": "ipython",
1124 | "version": 3
1125 | },
1126 | "file_extension": ".py",
1127 | "mimetype": "text/x-python",
1128 | "name": "python",
1129 | "nbconvert_exporter": "python",
1130 | "pygments_lexer": "ipython3",
1131 | "version": "3.5.2"
1132 | }
1133 | },
1134 | "nbformat": 4,
1135 | "nbformat_minor": 2
1136 | }
1137 |
--------------------------------------------------------------------------------
/svd/svd_portrait1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/svd/svd_portrait1.png
--------------------------------------------------------------------------------
/svd/svd_portrait2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/svd/svd_portrait2.png
--------------------------------------------------------------------------------
/svd/svd_portrait3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/svd/svd_portrait3.png
--------------------------------------------------------------------------------
/svd/svd_randscape1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/svd/svd_randscape1.png
--------------------------------------------------------------------------------
/svd/svd_randscape2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/svd/svd_randscape2.png
--------------------------------------------------------------------------------
/svd/svd_randscape3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/svd/svd_randscape3.png
--------------------------------------------------------------------------------
/svd/svd_summary.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/svd/svd_summary.png
--------------------------------------------------------------------------------
/svm/dual1-anno.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/svm/dual1-anno.png
--------------------------------------------------------------------------------
/svm/dual2-anno.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/svm/dual2-anno.png
--------------------------------------------------------------------------------
/svm/dual3-anno.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/svm/dual3-anno.png
--------------------------------------------------------------------------------
/svm/implicit-diff.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/svm/implicit-diff.png
--------------------------------------------------------------------------------
/svm/ineq_hess1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/svm/ineq_hess1.png
--------------------------------------------------------------------------------
/svm/ineq_hess2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/svm/ineq_hess2.png
--------------------------------------------------------------------------------
/svm/lagrangian.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/svm/lagrangian.png
--------------------------------------------------------------------------------
/svm/local-dual.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metamath1/ml-simple-works/86eb723c81cf49f824e2c91f8e694bb8628fb25a/svm/local-dual.png
--------------------------------------------------------------------------------