{
 "cells": [
  {
   "cell_type": "markdown",
   "source": [
    "# 환경 구성\r\n",
    "1. keras 의 back end 로 open cl을 활용\r\n",
    "2. 데이터 전처리에 pandas와 sklearn을 활용\r\n",
    "3. 시각화를 위해 Matplotlib 사용"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "source": [
    "import numpy as np\r\n",
    "import os\r\n",
    "import time\r\n",
    "\r\n",
    "os.environ[\"KERAS_BACKEND\"] = \"plaidml.keras.backend\"\r\n",
    "\r\n",
    "'''\r\n",
    "-----------------------------------------------------------------------------------------\r\n",
    "Network Name         Inference Latency         Time / FPS\r\n",
    "-----------------------------------------------------------------------------------------\r\n",
    "mobilenet-opencl      24.60 ms                  2.89 ms / 345.84 fps\r\n",
    "Correctness: PASS, max_error: 1.0642305824148934e-05, max_abs_error: 7.450580596923828e-07, fail_ratio: 0.0\r\n",
    "\r\n",
    "mobilenet-cpu        411.35 ms                 408.90 ms / 2.45 fps\r\n",
    "Correctness: PASS, max_error: 2.0418785425135866e-05, max_abs_error: 1.5869736671447754e-06, fail_ratio: 0.0\r\n",
    "'''\r\n",
    "\r\n",
    "import matplotlib.pyplot as plt\r\n",
    "import pandas as pd\r\n",
    "import tensorflow as tf\r\n",
    "import keras\r\n",
    "\r\n",
    "from sklearn.metrics import accuracy_score, precision_score, recall_score\r\n",
    "from sklearn.model_selection import train_test_split\r\n",
    "from tensorflow.keras import layers, losses\r\n",
    "from tensorflow.keras.models import Model\r\n",
    "\r\n",
    "df = pd.read_csv('./anoicos_prep.csv')\r\n",
    "df['time'] = pd.to_datetime(df['time'], format=\"%Y-%m-%d %H:%M\")\r\n",
    "df = pd.pivot_table(df, index=['time']) + 0.000000001\r\n",
    "df = df.fillna(df.mean())"
   ],
   "outputs": [
    {
     "output_type": "stream",
     "name": "stderr",
     "text": [
      "Using plaidml.keras.backend backend.\n"
     ]
    }
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "# 데이터 전처리\r\n",
    "## 훈련 데이터 선별 \r\n",
    "1. 측정값에 대한 컬럼명 생성 및 확인\r\n",
    "2. 설비의 동작여부를 나타내는 컬럼명 생성 및 선별 확인\r\n",
    "## 라벨 생성\r\n",
    "1. 데이터의 이상 유무를 판단할 수 있는 라벨은 따로 없음\r\n",
    "1. 설비의 동작여부를 조합하여 설비의 동작시간에는 정상치 라벨을 / 동작하지 않는시간에는 비정상 라벨을 부여\r\n",
    "1. 데이터 프레임을 Numpy array 로 변경"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "source": [
    "# 컬럼명 생성을 위한 코드테이블 - hard coding\r\n",
    "operation_suffix = ['_ST', '_SF_ST', '_RF_ST']\r\n",
    "#열원설비\r\n",
    "hsource_suffix = ['_RTD','_STD']\r\n",
    "hsource = ['M2.CH1', 'M2.CH1_1', 'M2.CH1_2', 'M3.CH2', 'PH_3.CT1', 'PH_2.CT2', 'PH_1.CT3', 'M4.HE']\r\n",
    "#공조기\r\n",
    "AHU_suffix = ['_SAT', '_RAT', '_RAH', '_EAD', '_OAT', '_OAD', '_MAT', '_MAD', '_CSV', '_CV', '_HV']\r\n",
    "AHU = ['S2F.AHU4', 'S3F_1.AHU3F', 'S4F_1.AHU4F', 'S5F_1.AHU5F', 'S6F_1.AHU6F', 'S7F_1.AHU7F', 'S8F_1.AHU8F', 'S9F_1.AHU9F']\r\n",
    "\r\n",
    "# 규칙에 따라 컬럼명을 조합하고, 컬럼명이 데이터프레임에 있는지 확인후 저장\r\n",
    "cols = []\r\n",
    "operation = []\r\n",
    "for prefix in hsource+AHU:\r\n",
    "    for suffix in hsource_suffix+AHU_suffix:\r\n",
    "        target = prefix+suffix        \r\n",
    "        if target in df.columns: \r\n",
    "            cols.append(target)\r\n",
    "    for suffix in operation_suffix:\r\n",
    "        opl = prefix+suffix\r\n",
    "        if opl in df.columns: operation.append(opl)\r\n",
    "\r\n",
    "# 라벨 생성 : 원하는 데이터들이 동작 여부가 모두 참 일경우 참으로 기록\r\n",
    "df['label'] = (df[operation].sum(axis=1) >= len(operation)-17)\r\n",
    "\r\n",
    "# 필요한 데이터만 필터링\r\n",
    "cols.append('label')\r\n",
    "df_prem = df[cols]\r\n",
    "\r\n",
    "raw_data = df_prem.values\r\n",
    "\r\n",
    "# display(df.head(10))\r\n",
    "print('테이블 사이즈', df_prem.shape)"
   ],
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "테이블 사이즈 (70158, 102)\n"
     ]
    }
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "# 라벨 지정 및 훈련데이터 분리 : train, test, label 등등\r\n",
    "- sklearn 의 train_test_split 메소드를 사용하여 한번에 처리"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "source": [
    "# The last element contains the labels\r\n",
    "labels = raw_data[:, -1]\r\n",
    "\r\n",
    "# The other data points are the electrocadriogram data\r\n",
    "data = raw_data[:, :-1]\r\n",
    "\r\n",
    "train_data, test_data, train_labels, test_labels = train_test_split(data, labels, test_size=0.2, random_state=21)\r\n",
    "\r\n",
    "whole = len(labels)\r\n",
    "tr = len(train_labels)\r\n",
    "te = len(test_labels)\r\n",
    "print('총합 {}, 총합-훈련데이터={}, 훈련데이터 {}, 테스트 데이터 {}'.format(whole, whole-tr, tr, te))\r\n",
    "print('훈련라벨 True = {}, 테스트 라벨 True = {}'.format(train_labels.sum(), test_labels.sum()))\r\n",
    "\r\n",
    "# keras 업데이트후 텐서와 어레이간에 타입불일치생김. float 으로 명시적으로 변환해줌으로써 해결\r\n",
    "train_data = train_data.astype(float)\r\n",
    "test_data = test_data.astype(float)\r\n",
    "train_labels = train_labels.astype(float)\r\n",
    "test_labels = test_labels.astype(float)\r\n"
   ],
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "총합 70158, 총합-훈련데이터=14032, 훈련데이터 56126, 테스트 데이터 14032\n",
      "훈련라벨 True = 27022, 테스트 라벨 True = 6658\n"
     ]
    }
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "# 데이터 Nomalization : min max norm\r\n",
    "- 컬럼 변수 간의 Scale 을 맞춰주기 위한 Nomalization \r\n",
    "- 간단하게 Min Max norm 을 사용"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "source": [
    "train_data = tf.cast(train_data, tf.float32)\r\n",
    "test_data = tf.cast(test_data, tf.float32)\r\n",
    "\r\n",
    "min_val = tf.reduce_min(train_data)\r\n",
    "max_val = tf.reduce_max(train_data)\r\n",
    "\r\n",
    "train_data = (train_data - min_val) / (max_val - min_val)\r\n",
    "test_data = (test_data - min_val) / (max_val - min_val)\r\n",
    "\r\n",
    "train_labels = train_labels.astype(bool)\r\n",
    "test_labels = test_labels.astype(bool)\r\n",
    "print (len(train_labels), len(test_labels))\r\n",
    "\r\n",
    "normal_train_data = train_data[train_labels]\r\n",
    "normal_test_data = test_data[test_labels]\r\n",
    "print (normal_train_data.shape, normal_test_data.shape)\r\n",
    "\r\n",
    "anomalous_train_data = train_data[~train_labels]\r\n",
    "anomalous_test_data = test_data[~test_labels]\r\n",
    "print (anomalous_train_data.shape, anomalous_test_data.shape)\r\n"
   ],
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "56126 14032\n",
      "(27022, 101) (6658, 101)\n",
      "(29104, 101) (7374, 101)\n"
     ]
    }
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "# Normal / Anomal 데이터간의 비교\r\n",
    "- 모델링 편의상 나눠놓은 상태이므로 정상/비정상 여부에 대한 신뢰도는 떨어짐\r\n",
    "- 정상 데이터로만 모델링 예정이므로, 다음 단계에서 자가 재구성 신뢰도를 판단하기 위한 척도로만 의미를 부여"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "source": [
    "plt.figure(figsize=(14,8))\r\n",
    "plt.grid()\r\n",
    "# plt.plot(np.arange(101), normal_train_data[0])\r\n",
    "# plt.plot(np.arange(101), anomalous_train_data[0])\r\n",
    "plt.plot(np.arange(101), normal_train_data.numpy().mean(axis=0))\r\n",
    "plt.plot(np.arange(101), anomalous_train_data.numpy().mean(axis=0))\r\n",
    "plt.title(\"Normal/anomal Data compare\")\r\n",
    "plt.show()"
   ],
   "outputs": [
    {
     "output_type": "display_data",
     "data": {
      "text/plain": [
       "<Figure size 1008x576 with 1 Axes>"
      ],
      "image/png": "[encoded data, 102768 characters]"
     },
     "metadata": {
      "needs_background": "light"
     }
    }
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "# 모델 빌드"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "source": [
    "class AnomalyDetector(Model):\r\n",
    "  def __init__(self):\r\n",
    "    super(AnomalyDetector, self).__init__()\r\n",
    "    self.encoder = tf.keras.Sequential([\r\n",
    "      layers.Dense(32, activation=\"relu\"),\r\n",
    "      layers.Dense(16, activation=\"relu\"),\r\n",
    "      layers.Dense(8, activation=\"relu\")])\r\n",
    "\r\n",
    "    self.decoder = tf.keras.Sequential([\r\n",
    "      layers.Dense(16, activation=\"relu\"),\r\n",
    "      layers.Dense(32, activation=\"relu\"),\r\n",
    "      layers.Dense(101, activation=\"sigmoid\")])\r\n",
    "\r\n",
    "  def call(self, x):\r\n",
    "        encoded = self.encoder(x)\r\n",
    "        decoded = self.decoder(encoded)\r\n",
    "        return decoded\r\n",
    "\r\n",
    "autoencoder = AnomalyDetector()\r\n",
    "autoencoder.compile(optimizer='adam', loss='mae')\r\n"
   ],
   "outputs": [],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "# 훈련 진행\r\n",
    "## Custom Callback 을 정의하고 모델훈련을 진행\r\n",
    "## Callback 은 사용하지 않아도 무방하며, 훈련이나 추론시간이 오래걸릴경우 중간 확인용으로 사용"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "source": [
    "class CustomCallback(tf.keras.callbacks.Callback):\r\n",
    "    def on_test_begin(self, logs=None):\r\n",
    "        keys = list(logs.keys())\r\n",
    "        print(\"Start testing; got log keys: {}\".format(keys))\r\n",
    "\r\n",
    "    def on_test_end(self, logs=None):\r\n",
    "        keys = list(logs.keys())\r\n",
    "        print(\"Stop testing; got log keys: {}\".format(keys))\r\n",
    "\r\n",
    "    def on_predict_begin(self, logs=None):\r\n",
    "        keys = list(logs.keys())\r\n",
    "        print(\"Start predicting; got log keys: {}\".format(keys))\r\n",
    "\r\n",
    "    def on_predict_end(self, logs=None):\r\n",
    "        keys = list(logs.keys())\r\n",
    "        print(\"Stop predicting; got log keys: {}\".format(keys))\r\n",
    "\r\n",
    "    def on_test_batch_begin(self, batch, logs=None):\r\n",
    "        keys = list(logs.keys())\r\n",
    "        print(\"...Evaluating: start of batch {}; got log keys: {}\".format(batch, keys))\r\n",
    "\r\n",
    "    def on_test_batch_end(self, batch, logs=None):\r\n",
    "        keys = list(logs.keys())\r\n",
    "        print(\"...Evaluating: end of batch {}; got log keys: {}\".format(batch, keys))\r\n",
    "\r\n",
    "    def on_predict_batch_begin(self, batch, logs=None):\r\n",
    "        keys = list(logs.keys())\r\n",
    "        print(\"...Predicting: start of batch {}; got log keys: {}\".format(batch, keys))\r\n",
    "\r\n",
    "    def on_predict_batch_end(self, batch, logs=None):\r\n",
    "        keys = list(logs.keys())\r\n",
    "        print(\"...Predicting: end of batch {}; got log keys: {}\".format(batch, keys))\r\n",
    "\r\n",
    "\r\n",
    "history = autoencoder.fit   (normal_train_data, normal_train_data, \r\n",
    "                            epochs=50, \r\n",
    "                            batch_size=64,\r\n",
    "                            validation_data=(normal_test_data, normal_test_data),\r\n",
    "                            shuffle=True)"
   ],
   "outputs": [],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "# 학습 곡선\r\n",
    "- Training loss 와 Validation loss가 수렴하는 것이 관찰됨\r\n",
    "  - 둘사이에 차이가 별로 없음\r\n",
    "  - 주어진 정보 만으로 self reconstruction 이 잘될수 있을 것으로 판단됨"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "source": [
    "plt.figure(figsize=(14,8))\r\n",
    "plt.plot(history.history[\"loss\"], label=\"Training Loss\")\r\n",
    "plt.plot(history.history[\"val_loss\"], label=\"Validation Loss\")\r\n",
    "plt.legend()"
   ],
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "<matplotlib.legend.Legend at 0x29e46debf70>"
      ]
     },
     "metadata": {},
     "execution_count": 8
    },
    {
     "output_type": "display_data",
     "data": {
      "text/plain": [
       "<Figure size 1008x576 with 1 Axes>"
      ],
      "image/png": "[encoded data, 26280 characters]"
     },
     "metadata": {
      "needs_background": "light"
     }
    }
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "# 입력 / 출력 값 비교 (정상 데이터)\r\n",
    "- 입출력 값의 오차가 별로 없음\r\n",
    "- 자가 재구성이 잘 되는 것으로 보임\r\n",
    "  - 정상 데이터를 정제해서 모델링을 할 경우 이상치 탐지에 활용 간능성 높음\r\n",
    "- 훈련 편의상 정상/비정상 데이터를 나누었으나, 나눈 기준이 애매하여 크게 의미를 두기 어려움\r\n"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "source": [
    "encoded_imgs = autoencoder.encoder(normal_test_data).numpy()\r\n",
    "decoded_imgs = autoencoder.decoder(encoded_imgs).numpy()\r\n",
    "\r\n",
    "plt.figure(figsize=(14,8))\r\n",
    "plt.plot(normal_test_data.numpy().mean(axis=0), 'b')\r\n",
    "plt.plot(decoded_imgs.mean(axis=0), 'r')\r\n",
    "plt.fill_between(np.arange(101), decoded_imgs.mean(axis=0), normal_test_data.numpy().mean(axis=0), color='lightcoral')\r\n",
    "plt.legend(labels=[\"Input\", \"Reconstruction\", \"Error\"])\r\n",
    "plt.show()"
   ],
   "outputs": [
    {
     "output_type": "display_data",
     "data": {
      "text/plain": [
       "<Figure size 1008x576 with 1 Axes>"
      ],
      "image/png": "[encoded data, 98872 characters]"
     },
     "metadata": {
      "needs_background": "light"
     }
    }
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "source": [
    "encoded_imgs = autoencoder.encoder(anomalous_test_data).numpy()\r\n",
    "decoded_imgs = autoencoder.decoder(encoded_imgs).numpy()\r\n",
    "\r\n",
    "plt.figure(figsize=(14,8))\r\n",
    "plt.plot(anomalous_test_data.numpy().mean(axis=0), 'b')\r\n",
    "plt.plot(decoded_imgs.mean(axis=0), 'r')\r\n",
    "plt.fill_between(np.arange(101), decoded_imgs.mean(axis=0), anomalous_test_data.numpy().mean(axis=0), color='lightcoral')\r\n",
    "plt.legend(labels=[\"Input\", \"Reconstruction\", \"Error\"])\r\n",
    "plt.show()"
   ],
   "outputs": [
    {
     "output_type": "display_data",
     "data": {
      "text/plain": [
       "<Figure size 1008x576 with 1 Axes>"
      ],
      "image/png": "[encoded data, 104880 characters]"
     },
     "metadata": {
      "needs_background": "light"
     }
    }
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "# 평가\r\n",
    "1. I-Cos 데이터는 시계열 정보를 이용하지 않아도 순간 데이터를 추론하는데 충분한 정보를 가지고 있는 것으로 **보임**\r\n",
    "   1. 보다 정확한 평가를 위하여 자가 재구성효율을 **정략적 수치로 표현할 필요가 있음**   \r\n",
    "2. 타겟 설비의 이상여부 판단을 어떻게 할지 고민하고 구현할 필요가 있음3. "
   ],
   "metadata": {}
  }
 ],
 "metadata": {
  "orig_nbformat": 4,
  "language_info": {
   "name": "python",
   "version": "3.8.8",
   "mimetype": "text/x-python",
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "pygments_lexer": "ipython3",
   "nbconvert_exporter": "python",
   "file_extension": ".py"
  },
  "kernelspec": {
   "name": "python3",
   "display_name": "Python 3.8.8 64-bit ('base': conda)"
  },
  "interpreter": {
   "hash": "8fac594bfae6525c0c41b4041d2d72effa188cc8ead05f81b1fab2bb098927fb"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}