{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 다층 퍼셉트론(multilayer perceptron)을 처음부터 구현하기\n",
"\n",
"다층 퍼셉트론(multilayer perceptron, MLP)가 어떻게 작동하는지 이론적으로 배웠으니, 직접 구현해보겠습니다. 우선 관련 패키지와 모듈을 import 합니다."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"attributes": {
"classes": [],
"id": "",
"n": "9"
}
},
"outputs": [],
"source": [
"import sys\n",
"sys.path.insert(0, '..')\n",
"\n",
"%matplotlib inline\n",
"import d2l\n",
"from mxnet import nd\n",
"from mxnet.gluon import loss as gloss"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"이 예제에서도 Fashion-MNIST 데이터셋을 사용해서, 이미지를 분류하는데 다층 퍼셉트론(multilayer perceptron)을 사용하겠습니다."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"attributes": {
"classes": [],
"id": "",
"n": "2"
}
},
"outputs": [],
"source": [
"batch_size = 256\n",
"train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 모델 파라미터 초기화하기\n",
"\n",
"이 데이터셋은 10개의 클래스로 구분되어 있고, 각 이미지는 $28 \\times 28 = 784$ 픽셀의 해상도를 가지고 있습니다. 따라서, 입력은 784개이고, 출력은 10개가 됩니다. 우리는 한 개의 은닉층(hidden layer)을 갖는 MLP를 만들어보겠는데, 이 은닉층(hidden layer)은 256개의 은닉 유닛(hidden unit)을 갖도록 하겠습니다. 만약 원한다면 하이퍼파라미터인 은닉 유닛(hidden unit) 개수를 다르게 설정할 수도 있습니다. 일반적으로, 유닛(unit) 의 개수는 메모리에 잘 배치될 수 있도록 2의 지수승의 숫자로 선택합니다."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"attributes": {
"classes": [],
"id": "",
"n": "3"
}
},
"outputs": [],
"source": [
"num_inputs, num_outputs, num_hiddens = 784, 10, 256\n",
"\n",
"W1 = nd.random.normal(scale=0.01, shape=(num_inputs, num_hiddens))\n",
"b1 = nd.zeros(num_hiddens)\n",
"W2 = nd.random.normal(scale=0.01, shape=(num_hiddens, num_outputs))\n",
"b2 = nd.zeros(num_outputs)\n",
"params = [W1, b1, W2, b2]\n",
"\n",
"for param in params:\n",
" param.attach_grad()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 활성화 함수(activation function)\n",
"\n",
"여기서 `ReLU` 를 직접 호출하는 대신, ReLU를 `maximum` 함수를 이용해서 정의합니다."
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"attributes": {
"classes": [],
"id": "",
"n": "4"
}
},
"outputs": [],
"source": [
"def relu(X):\n",
" return nd.maximum(X, 0)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 모델\n",
"\n",
"Softmax 회귀(regression)에서 그랬던 것처럼, `reshape` 함수를 이용해서 원래 이미지를 `num_inputs` 크기의 벡터로 변환한 다음에 앞에서 설명한 대로 MLP를 구현합니다."
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"attributes": {
"classes": [],
"id": "",
"n": "5"
}
},
"outputs": [],
"source": [
"def net(X):\n",
" X = X.reshape((-1, num_inputs))\n",
" H = relu(nd.dot(X, W1) + b1)\n",
" return nd.dot(H, W2) + b2"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 손실 함수(loss function)\n",
"\n",
"더 나은 계산의 안정성을 위해서, softmax 계산과 크로스-엔트로피 손실(cross-entropy loss) 계산은 Gluon 함수를 이용하겠습니다. 왜 그런지는 [앞 절](mlp.ipynb)에서 이 함수의 구현에 대한 복잡성을 이야기했으니 참고 바랍니다. Gluon 함수를 이용하면 코드를 안전하게 구현하기 위해서 신경을 써야하는 많은 세밀한 것들을 간단하게 피할 수 있습니다. (자세한 내용이 궁금하다면 소스 코드를 살펴보기 바랍니다. 소스 코드을 보면 다른 관련 함수를 구현하는데 유용한 것들을 배울 수도 있습니다.)"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"attributes": {
"classes": [],
"id": "",
"n": "6"
}
},
"outputs": [],
"source": [
"loss = gloss.SoftmaxCrossEntropyLoss()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 학습\n",
"\n",
"다층 퍼셉트론(multilayer perceptron)을 학습시키는 단계는 softmax 회귀(regression) 학습과 같습니다. `g2l` 패키지에서 제공하는 `train_ch3` 함수를 직접 호출합니다. 이 함수의 구현은 [여기](softmax-regression-scratch.ipynb) 를 참고하세요. 총 에포크(epoch) 수는 10으로 학습 속도(learning rate)는 0.5로 설정합니다."
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"attributes": {
"classes": [],
"id": "",
"n": "7"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"epoch 1, loss 0.7912, train acc 0.702, test acc 0.824\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"epoch 2, loss 0.4880, train acc 0.818, test acc 0.853\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"epoch 3, loss 0.4243, train acc 0.842, test acc 0.846\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"epoch 4, loss 0.3971, train acc 0.853, test acc 0.869\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"epoch 5, loss 0.3777, train acc 0.860, test acc 0.872\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"epoch 6, loss 0.3516, train acc 0.869, test acc 0.877\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"epoch 7, loss 0.3369, train acc 0.875, test acc 0.878\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"epoch 8, loss 0.3267, train acc 0.879, test acc 0.878\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"epoch 9, loss 0.3177, train acc 0.882, test acc 0.884\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"epoch 10, loss 0.3059, train acc 0.887, test acc 0.884\n"
]
}
],
"source": [
"num_epochs, lr = 10, 0.5\n",
"d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, batch_size,\n",
" params, lr)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"학습이 잘 되었는지 확인하기 위해서, 모델을 테스트 데이터에 적용해 보겠습니다. 이 모델의 성능이 궁금하다면, 동일한 분류를 수행하는 [linear 모델](softmax-regression-scratch.ipynb) 의 결과와 비교해보세요."
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"data": {
"image/svg+xml": [
"\n",
"\n",
"\n",
"\n"
],
"text/plain": [
"