{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# AutoEncoder\n",
    "\n",
    "![autoencoder](./images/autoencoder.png)\n",
    "\n",
    "지도학습은 단순히 말해 입력 x와 정답 y 사이의 관계를 찾는 것이고, 비지도 학습은 정답이 없는 채로 x를 예측하는 것이다.\n",
    "\n",
    "즉 오토인코더에서는 입력도 x, 정답도 x로 설계된 신경망이다.\n",
    "\n",
    "단, 신경망은 범용근사자(univeral function approximator)로서 근사치를 출력하므로 x와 똑같은 출력을 내긴 힘들다.\n",
    "\n",
    "그러므로 오찻값에도 x를 얼마나 복원했는지를 뜻하는 복원오차, 혹은 정보손실값(reconstruction loss)이라는 용어를 사용한다.\n",
    "\n",
    "오토인코더는 입력과 출력의 크기는 같지만 중간으로 갈수록 신경망의 차원이 줄어든다.\n",
    "\n",
    "작은 차원으로 압축된 표현을 잠재변수(latent variable)라 하고, 간단히 z라고 한다.\n",
    "\n",
    "잠재 변수의 앞뒤를 구분하여 앞부분을 인코더(encoder), 뒷부분을 디코더(decoder)라고 한다.\n",
    "\n",
    "인코더는 정보를 받아 압축하고, 디코더는 압축된 표현을 풀어 입력을 복원하는 역할을 한다.\n",
    "\n",
    "오토인코더에서는 필연적으로 정보의 손실이 일어나지만 이는 중요한 정보만 남겨두는 일종의 데이터 가공이라고 볼 수 있다. \n",
    "\n",
    "이런 특징으로 인해 오토인코더는 주로 복잡한 비선형 데이터의 차원을 줄이는 용도로 쓰인다."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Import"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "C:\\Users\\pinb\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python310\\site-packages\\tqdm\\auto.py:22: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
      "  from .autonotebook import tqdm as notebook_tqdm\n"
     ]
    }
   ],
   "source": [
    "import torch\n",
    "import torchvision\n",
    "import torch.nn.functional as F\n",
    "from torch import nn, optim\n",
    "from torchvision import transforms, datasets\n",
    "\n",
    "import matplotlib.pyplot as plt\n",
    "from mpl_toolkits.mplot3d import Axes3D #생성되는 이미지를 관찰하기 위함입니다. 3차원 플롯을 그리는 용도입니다.\n",
    "from matplotlib import cm # 데이터포인트에 색상을 입히는 것에 사용됩니다.\n",
    "import numpy as np"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Hyper Params"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Using Device: cuda\n"
     ]
    }
   ],
   "source": [
    "# 하이퍼파라미터 준비\n",
    "EPOCH = 10\n",
    "BATCH_SIZE = 64\n",
    "USE_CUDA = torch.cuda.is_available()\n",
    "DEVICE = torch.device(\"cuda\" if USE_CUDA else \"cpu\")\n",
    "print(\"Using Device:\", DEVICE)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Dataset (Fashion-MNIST)\n",
    "\n",
    "![fation-mnist](./images/fashionmnist.jpg)\n",
    "* Fashion-MNIST는 총 70,000개의 그레이스케일 이미지로 구성된다. \n",
    "* 60,000개는 학습용 데이터, 10,000개는 테스트용 데이터 \n",
    "* 데이터셋은 10가지 범주로 구성 ('티셔츠/탑', '바지', '풀오버', '드레스', '코트', '샌들', '셔츠', '스니커즈', '가방', '앵클 부츠' 등)\n",
    "* 각 이미지는 28x28x1 크기"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-images-idx3-ubyte.gz\n",
      "Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-images-idx3-ubyte.gz to ./.data/FashionMNIST\\raw\\train-images-idx3-ubyte.gz\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 26421880/26421880 [00:06<00:00, 4076925.90it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Extracting ./.data/FashionMNIST\\raw\\train-images-idx3-ubyte.gz to ./.data/FashionMNIST\\raw\n",
      "\n",
      "Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-labels-idx1-ubyte.gz\n",
      "Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-labels-idx1-ubyte.gz to ./.data/FashionMNIST\\raw\\train-labels-idx1-ubyte.gz\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 29515/29515 [00:00<00:00, 118201.02it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Extracting ./.data/FashionMNIST\\raw\\train-labels-idx1-ubyte.gz to ./.data/FashionMNIST\\raw\n",
      "\n",
      "Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-images-idx3-ubyte.gz\n",
      "Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-images-idx3-ubyte.gz to ./.data/FashionMNIST\\raw\\t10k-images-idx3-ubyte.gz\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 4422102/4422102 [00:03<00:00, 1360040.16it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Extracting ./.data/FashionMNIST\\raw\\t10k-images-idx3-ubyte.gz to ./.data/FashionMNIST\\raw\n",
      "\n",
      "Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-labels-idx1-ubyte.gz\n",
      "Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-labels-idx1-ubyte.gz to ./.data/FashionMNIST\\raw\\t10k-labels-idx1-ubyte.gz\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 5148/5148 [00:00<?, ?it/s]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Extracting ./.data/FashionMNIST\\raw\\t10k-labels-idx1-ubyte.gz to ./.data/FashionMNIST\\raw\n",
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "# Fashion MNIST 데이터셋 불러오기 (학습데이터만 사용)\n",
    "trainset = datasets.FashionMNIST(\n",
    "    root      = './.data/', \n",
    "    train     = True,\n",
    "    download  = True,\n",
    "    transform = transforms.ToTensor()\n",
    ")\n",
    "train_loader = torch.utils.data.DataLoader(\n",
    "    dataset     = trainset,\n",
    "    batch_size  = BATCH_SIZE,\n",
    "    shuffle     = True,\n",
    "    num_workers = 2\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Autoencoder Network"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 오토인코더 모듈 정의\n",
    "class Autoencoder(nn.Module):\n",
    "    def __init__(self):\n",
    "        super(Autoencoder, self).__init__()\n",
    "\n",
    "        #인코더는 간단한 신경망으로 분류모델처럼 생겼습니다.\n",
    "        self.encoder = nn.Sequential( # nn.Sequential을 사용해 encoder와 decoder 두 모듈로 묶어줍니다.\n",
    "            nn.Linear(28*28, 128), #차원을 28*28에서 점차 줄여나갑니다.\n",
    "            nn.ReLU(),\n",
    "            nn.Linear(128, 64),\n",
    "            nn.ReLU(),\n",
    "            nn.Linear(64, 12),\n",
    "            nn.ReLU(),\n",
    "            nn.Linear(12, 3),   # 입력의 특징을 3차원으로 압축합니다 (출력값이 바로 잠재변수가 됩니다.)\n",
    "        )\n",
    "        self.decoder = nn.Sequential(\n",
    "            nn.Linear(3, 12), #디코더는 차원을 점차 28*28로 복원합니다.\n",
    "            nn.ReLU(),\n",
    "            nn.Linear(12, 64),\n",
    "            nn.ReLU(),\n",
    "            nn.Linear(64, 128),\n",
    "            nn.ReLU(),\n",
    "            nn.Linear(128, 28*28),\n",
    "            nn.Sigmoid(),       # 픽셀당 0과 1 사이로 값을 출력하는 sigmoid()함수를 추가합니다.\n",
    "        )\n",
    "\n",
    "    def forward(self, x):\n",
    "        encoded = self.encoder(x) # encoder는 encoded라는 잠재변수를 만들고\n",
    "        decoded = self.decoder(encoded) # decoder를 통해 decoded라는 복원이미지를 만듭니다.\n",
    "        return encoded, decoded"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "autoencoder = Autoencoder().to(DEVICE)\n",
    "optimizer = torch.optim.Adam(autoencoder.parameters(), lr=0.005) \n",
    "# Adam()을 최적화함수로 사용합니다. Adam은 SGD의 변형함수이며 학습중인 기울기를 참고하여 학습 속도를 자동으로 변화시킵니다.\n",
    "criterion = nn.MSELoss() #원본값과 디코더에서 나온 값의 차이를 계산하기 위해 평균제곱오차(Mean Squared Loss) 오차함수를 사용합니다."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Data Normalize"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 원본 이미지를 시각화 하기 (첫번째 열)\n",
    "view_data = trainset.data[:5].view(-1, 28*28)\n",
    "# 복원이 어떻게 되는지 관찰하기 위해 5개의 이미지를 가져와 바로 넣어보겠습니다.\n",
    "view_data = view_data.type(torch.FloatTensor)/255.\n",
    "#픽셀의 색상값이 0~255이므로 모델이 인식하는 0부터 1사이의 값으로 만들기 위해 255로 나눠줍니다."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Train"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 학습하기 위한 함수\n",
    "def train(autoencoder, train_loader):\n",
    "    autoencoder.train()\n",
    "    for step, (x, label) in enumerate(train_loader):\n",
    "        x = x.view(-1, 28*28).to(DEVICE)\n",
    "        y = x.view(-1, 28*28).to(DEVICE) #x(입력)와 y(대상 레이블)모두 원본이미지(x)인 것을 주의해야 합니다.\n",
    "        label = label.to(DEVICE)\n",
    "\n",
    "        encoded, decoded = autoencoder(x)\n",
    "\n",
    "        loss = criterion(decoded, y) # decoded와 원본이미지(y) 사이의 평균제곱오차를 구합니다\n",
    "        optimizer.zero_grad() #기울기에 대한 정보를 초기화합니다.\n",
    "        loss.backward() # 기울기를 구합니다.\n",
    "        optimizer.step() #최적화를 진행합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[Epoch 1]\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 41612 characters]",
      "text/plain": [
       "<Figure size 500x200 with 10 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[Epoch 2]\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 41532 characters]",
      "text/plain": [
       "<Figure size 500x200 with 10 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[Epoch 3]\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 40832 characters]",
      "text/plain": [
       "<Figure size 500x200 with 10 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[Epoch 4]\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 41768 characters]",
      "text/plain": [
       "<Figure size 500x200 with 10 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[Epoch 5]\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 41252 characters]",
      "text/plain": [
       "<Figure size 500x200 with 10 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[Epoch 6]\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 40208 characters]",
      "text/plain": [
       "<Figure size 500x200 with 10 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[Epoch 7]\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 40508 characters]",
      "text/plain": [
       "<Figure size 500x200 with 10 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[Epoch 8]\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 40952 characters]",
      "text/plain": [
       "<Figure size 500x200 with 10 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[Epoch 9]\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 40760 characters]",
      "text/plain": [
       "<Figure size 500x200 with 10 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[Epoch 10]\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 40420 characters]",
      "text/plain": [
       "<Figure size 500x200 with 10 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "#학습하기\n",
    "for epoch in range(1, EPOCH+1):\n",
    "    train(autoencoder, train_loader)\n",
    "\n",
    "    # 디코더에서 나온 이미지를 시각화 하기\n",
    "    # 앞서 시각화를 위해 남겨둔 5개의 이미지를 한 이폭만큼 학습을 마친 모델에 넣어 복원이미지를 만듭니다.\n",
    "    test_x = view_data.to(DEVICE)\n",
    "    _, decoded_data = autoencoder(test_x)\n",
    "\n",
    "    # 원본과 디코딩 결과 비교해보기\n",
    "    f, a = plt.subplots(2, 5, figsize=(5, 2))\n",
    "    print(\"[Epoch {}]\".format(epoch))\n",
    "    for i in range(5):\n",
    "        img = np.reshape(view_data.data.numpy()[i],(28, 28)) #파이토치 텐서를 넘파이로 변환합니다.\n",
    "        a[0][i].imshow(img, cmap='gray')\n",
    "        a[0][i].set_xticks(()); a[0][i].set_yticks(())\n",
    "\n",
    "    for i in range(5):\n",
    "        img = np.reshape(decoded_data.to(\"cpu\").data.numpy()[i], (28, 28)) \n",
    "        # CUDA를 사용하면 모델 출력값이 GPU에 남아있으므로 .to(\"cpu\") 함수로 일반메모리로 가져와 numpy행렬로 변환합니다.\n",
    "        # cpu를 사용할때에도 같은 코드를 사용해도 무방합니다.\n",
    "        a[1][i].imshow(img, cmap='gray')\n",
    "        a[1][i].set_xticks(()); a[1][i].set_yticks(())\n",
    "    plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "참고: 도서 '펭귄브로의 3분 딥러닝 파이토치맛' ( 깃허브 : https://github.com/keon/3-min-pytorch )"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
