{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# 설치 실패시, Internet On/Off 확인\n!pip install easydict","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import os, random, pickle\nfrom os.path import join\nfrom glob import glob\nfrom time import time\n\nfrom tqdm.notebook import tqdm\n\nfrom easydict import EasyDict as edict\n\nimport numpy as np\nfrom PIL import Image\n\nfrom sklearn.svm import OneClassSVM\n\nfrom sklearn.decomposition import PCA, KernelPCA\n\nfrom sklearn.metrics import roc_auc_score\nfrom sklearn.metrics import roc_curve\n\nfrom sklearn.preprocessing import StandardScaler, MinMaxScaler\n\nimport pandas as pd\n\nimport matplotlib.pyplot as plt\nimport matplotlib.animation as animation\nfrom IPython.display import HTML, display\n","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 산업 환경 상의  영상 기반 비정상 케이스 검출\n\n이상 상황 검출이란, 데이터에서 예상과는 다른 패턴을 보이는 개체 또는 자료를 찾는 것을 말합니다. \n다양한 분야에서 사용되어지며, 각 분야 별 사용 예시는 아래와 같습니다.\n\n-  사이버 보안. e.g. 비정상적인 트래픽 패턴을 감지\n-  의학 분야. e.g. MRI 이미지로부터의 악성 종양 검출\n-  금융 분야. e.g. 신용카드 거래 내역의 이상 검출을 통한 분실 및 도난 감지\n-  행동 패턴 분야. e.g. CCTV를 통한 이상 패턴 감지를 통한 사고 방지\n-  산업 분야. e.g.  생산된 제품의 품질 검사   <br>\n\n 이상 상황은 위의 예시와 같이 드물게 나타나며 특정하기 힘든 형태로 나타나는 경우가 많습니다. 이런 특성상 특정 분포로 정의하기 힘들어, 정상 데이터와 다른 분포를 가진 데이터를 이상 상황이라고 칭합니다. 혹은 다른 관점으로는 기존 데이터 분포와 다른 새로운 분포를 가진 데이터를 찾는다고 하여 novelty detection라고도 불리기도 합니다. 이처럼 특정 분포를 정의하기 힘든 특성상  이상상황에 대한 데이터 셋을 구축하기 매우 힘듭니다. 이러한 사유로 대부분의 이상상황 검출들은 이상상황에 대한 데이터와 라벨이 필요한 지도학습보다는 라벨이 불필요한 비지도 학습 혹은 준 비지도 학습을 기반으로 문제를 해결합니다. \n\n 이번 텀프로젝트에서는 다양한 분야 중 산업 환경 상의 이상 상황 검출 문제를 다룰 예정입니다. 해당 문제는 MVTec AD 데이터 셋을 이용하여 비지도 학습 기반의 이상 상황 검출을 구현합니다. 비지도 학습 기반 검출기는 수업에서 다룬 차원 축소 알고리즘 PCA(Principal Compoent Analysis)와  분류기인 SVM(Support-Vector Machine)류의 모델 One-class SVM을 다뤄볼 예정 입니다.","metadata":{}},{"cell_type":"markdown","source":"## Dataloader\n\n### MVTEC AD\n이번 텀프로젝트에서는 산업 분야 중 제조업 분야에서의 비지도 학습 기반 이상 검출 데이터 셋 MVTEC AD([homepage](https://www.mvtec.com/company/research/datasets/mvtec-ad), [pdf](https://www.mvtec.com/fileadmin/Redaktion/mvtec.com/company/research/datasets/mvtec_ad.pdf))를 이용하여 이상 상황 검출을 풀어보고자합니다.  해당 데이터 셋은 아래와 같이 총 15가지의 클래스를 가진 영상을 제공하며,  각 클래스에 해당하는 제조업에서 발생할 수 있는 이상 상황(품질 불량)을 인위적으로 생성하였으며, 영상에서의 제조품이 명확하도록 설계된 조명과 배경과 영상 가운데에 위치하도록 촬영되었습니다. 각 클래스의 예제 영상들은 Overview-Description Fig 1에서 확인 할 수 있습니다.\n데이터 셋 구성은 [MVTecAD_colab](https://colab.research.google.com/drive/1pdgvoPs3KDLq6pV9oLxkXDh6waEp76HT?usp=sharing)에서도 확인 할 수 있습니다.\n<br>\n\n#### 데이터 셋 구성\n\n데이터 셋의 영상 갯수는 아래와 같습니다.\n\ntrain : 3629장 -> 정상 상황에 해당하는 영상만 존재.<br>\ntest : 467(good) + 1258(fault case) -> 정상 상황에서의 영상(good)과 이상 상황에서의 영상(failt case)로 구성. <br>\nall : 5354장 <br>\n\n데이터 파일의 구조는 아래와 같습니다.\n\n{ClassName} Original dataset과 동일 <br>\n|--val.csv : col1 : ID. test 영상 파일명, col2 : label. good:1, fault:-1 <br>\n|--train <br>\n|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|---good <br>\n|__test <br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|--- good&fault case <br>","metadata":{}},{"cell_type":"code","source":"class MVTec_AD():\n    def __init__(self, DB_PATH, OUT_PATH, size=(86,86), flatten=True):\n        '''\n        DB_PATH : str. e.g. {workspace}/dataset\n        size : (int, int). default (86, 86)\n        flatten : bool. [num_of_img_per_class, h*w]/[num_of_img_per_class, h, w]\n        '''\n        self.DB_PATH = DB_PATH\n        self.out_path = OUT_PATH\n        self.size, self.flatten = size, flatten\n\n        self.class_names = ['bottle', 'cable', 'capsule', 'carpet', 'grid',\n                            'hazelnut', 'leather', 'metal_nut', 'pill', 'screw',\n                            'tile', 'transistor', 'wood', 'zipper']\n\n\n    def read_mvtec(self, cls=\"bottle\", mode='train'):\n        '''\n        input\n          cls : str. class name.\n          mode : str. {train, test, val}\n        output\n          db_dict : dict. \n                  if val in mode\n                   {'imgs':[num_of_img_per_class, h*w or h, w], 'labels':gt, 'class_name':class_name}\n                  else\n                   {'imgs':[num_of_img_per_class, h*w or h, w], 'class_name':class_name}\n        '''\n\n        # Load cache data.\n        CACHE_PATH = self.out_path + '/cache'\n        cache_ = join(CACHE_PATH, mode)\n        if self.flatten: cache_+= 't_'\n        cache_+=f'{str(self.size)}_{cls}.pkl'\n\n        # if exist cache data return data\n        if os.path.isfile(cache_):\n            with open(cache_, 'rb') as f:\n                data = pickle.load(f)\n            return data\n        # else load imgs and cache data\n        else:\n            if os.path.isdir(CACHE_PATH)!=True:\n                os.mkdir(CACHE_PATH)\n\n            # load imgs\n            data = edict()  \n            if mode == 'val':\n                csv = pd.read_csv(join(self.DB_PATH, cls, 'val.csv'))\n                data.imgs = [f'{self.DB_PATH}/{cls}/test/{csv.iloc[id, 0]:03d}.png' for id in csv.index]\n                data.labels = [csv.iloc[id, 1] for id in csv.index]\n            else:\n                data.imgs = sorted(glob(join(self.DB_PATH, cls, mode, '*.png')))\n\n            data.class_name = cls\n\n            data = self.read_img(data)\n\n            # cache data\n            with open(cache_, \"wb\") as f:\n                pickle.dump(data, f)\n\n            return data\n        \n    def read_img(self, db_dict):\n        im_path = db_dict.imgs\n        len_imgs = len(im_path)\n\n        db_dict.imgs = np.array([np.array(Image.open(im).convert(\"L\").resize(self.size)) for im in im_path])\n        if self.flatten:\n            db_dict.imgs = db_dict.imgs.reshape(len_imgs,-1)\n      \n        return db_dict","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\ndef plot_gallery(images, titles, h, w, n_row=3, n_col=4):\n    plt.figure(figsize=(1.8 * n_col, 2.4 * n_row))\n    plt.subplots_adjust(bottom=0, left=.01, right=.99, top=.90, hspace=.35)\n    for i in range(n_row * n_col):\n        plt.subplot(n_row, n_col, i + 1)\n        plt.imshow(images[i].reshape((h, w)), cmap=plt.cm.gray)\n        plt.title(titles[i], size=12)\n        plt.xticks(())\n        plt.yticks(())\n\ndef title(y_pred, y_test, target_names, i):\n    pred_name = target_names[y_pred[i]].rsplit(' ', 1)[-1]\n    true_name = target_names[y_test[i]].rsplit(' ', 1)[-1]\n    return 'predicted: %s\\ntrue:      %s' % (pred_name, true_name)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# load val, train data\nDB_PATH = \"../input/2021-ml-tp2/MVTecAD\"\nOUT_PATH = \"./\"\n\nsize = (256, 256)\ncls = 'bottle'\ndataset = MVTec_AD(DB_PATH, OUT_PATH, flatten=True, size=size)\n\nval = dataset.read_mvtec(cls=cls, mode='val')\ntrain = dataset.read_mvtec(cls=cls, mode='train')\ntest = dataset.read_mvtec(cls=cls, mode='test')\n\n# 해당 데이터 셋은 비지도 학습을 위한 데이터 셋이기 때문에,\n# train data는 정상 케이스의 영상들로만 구성되어 있습니다.\n# 베이스 코드에서는 train의 key 정보와 같이 train의 라벨을 주어주지 않습니다.\n# 데이터 구성에 대한 내용은 https://colab.research.google.com/drive/1pdgvoPs3KDLq6pV9oLxkXDh6waEp76HT?usp=sharing에 있습니다. 해당 부분도 보시기 바랍니다.\n# 위의 URL 주소는 Dataset 설명란의 MVTecAD_colab과 동일한 주소입니다.\n# ++ 만약에 train의 라벨이 필요하신 분들은 각 클래스의 train 폴더의 data.csv를 사용하시기 바랍니다.\nprint(train.keys())\nprint(val.keys())\nprint(test.keys())","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 이상 상황 검출(Anomaly Detection)\n\n 이번 텀프로젝트에서는 이상 상황 검출을 위한 평가 방법 중 하나의 클래스에서 이상치를 찾는 것을 목적으로한 One-classification 방법을 이용하여 이상 상황을 평가하도록 합니다. One-classification을 간단하게 설명드리자면 각 클래스 별 학습과 평가를 별도로 진행합니다. 즉, MVTecAD의 14가지 클래스 별 추론과 평가를 진행합니다.  사용되는 평가 메트릭은 ROC를 사용합니다.\n\n이번 텀프에서는 영상 기반의 이상 상황 검출을 적용되며 크게 두가지로 분류되어집니다.\n\n- Reconstruction based anomaly detection\n  - 정상 영상으로부터 정상적 특성을 잘표현하는 feature를 추출하는 encoder와\n  - 추출된 feature를 원본 영상과 유사하게 복원하는 Decoder로 구성\n  - 정상적인 데이터로 학습된 encoder-decoder로 이상 영상을 복원 시, 정상 영상과 유사하도록 복원 영상을 생성될 것이라고 가정을 함.\n  - 입력 영상 I과 encoder-decoder로 복원된 영상 I'의 차이를 정상/이상에 대한 분류 점수로 사용.\n  - 이상 영상과 복원 영상(이상)의 차이는 크고, 정상 영상과 복원 영상(정상)의 차이는 작음\n- Embedding feature based anomaly detection\n  - 정상 영상의 정상적인 특성을 학습한 feature extrator를 이용하여  정상/이상 영상의 feature를 추출\n  - 정상 feature의 분포 경계를 잘 배우도록 모델을 학습\n  - 정상 feature의 분포 경계에 벗어나는 경우 이상치로 검출\n\n텀프로젝트에서는 PCA를 활용하여 위의 두 가지 방법을 적용하는 것이 목표입니다. ","metadata":{}},{"cell_type":"markdown","source":"#### Reconstruction based anomaly detection\n\n이번 텀프로젝트에서의 Reconstruction based anomaly detection은 PCA로 이뤄진 encoder와 decoder를 이용합니다. \n파이프라인은 아래와 같습니다.\n\n1. PCA를 이용하여 정상 영상으로 구성된 train data의 주성분을 추출하여 train data를  feature f로 n_component 크기가 되도록 차원 축소(feature 추출)\n2. 정상/이상 영상이 포함된 test data에 1에서 적용된 PCA로 featrue f_test 추출\n3. featrue f_test를 복원\n4. 원본 영상과 복원 영상의 차를 구하여 socre를 추출\n\n","metadata":{}},{"cell_type":"code","source":"# -------------------------------------\n# [Empty Module #1] Reconstruction based anomaly detection\n# -------------------------------------\n# \n# ------------------------------------------------------------\n# 구현 가이드라인 \n# ------------------------------------------------------------\n# [1] train.imgs와 val.imgs 정규화.\n# [2] PCA 초기화 및 train imgs를 학습\n# Baseline : PCA(n_components=n_components), random_state=777\n# [3] val imgs를 transform.\n# [4] [3]로 얻은 val_pca를 inverse_transform을 통해 복원. scaler의 \n# inverse_transform을 진행 후, (num_data_imgs, img_width, img_height)로 reshape.\n# [5] Reconstruction_error = Original imgs - Reconstruction imgs\n# Original imgs : reshape (num_data_imgs, img_width, img_height)를 해야합니다.\n # ------------------------------------------------------------\n\nn_components = 0.8\n    \n# [1]\n\n# [2]\n# pca = \n# pca.fit()\n\n# [3] val_pca = pca.transform(정규화된 val.imgs)\n\n# [4] Reconstruction imgs = \n# hint. pca.inverse_transfrom()\n# hint. scaler.inverse_transfrom()\n# hint. reshape [num_of_img, width, height] \n\n# [5] \n# 변수명\n# ori = Original imgs ~ reshape [n_sample, w, h]\n# Reconstruction imgs\n\n# Reconstruction_error = Original imgs - Reconstruction_imgs\n\n\n#____________________\n# score & predict normal/abnormal\nmin_max_scaler = MinMaxScaler()\n\ncls_score = Reconstruction_error.sum(axis=1).sum(axis=1)\ncls_score = min_max_scaler.fit_transform(cls_score.reshape(-1, 1))\n\ny_pred = cls_score\n\nth = 0.5\ny_pred[cls_score < th] = -1\ny_pred[cls_score > th] = 1\ny_pred = y_pred.reshape(-1)\n\n#____________________\n# metric. ROC_AUC\ngt_list = np.asarray(val.labels)\nfpr, tpr, _ = roc_curve(gt_list, cls_score)\nimg_roc_auc = roc_auc_score(gt_list, cls_score)\n\nplt.plot(fpr, tpr, label='%s ROCAUC: %.3f' % (cls, img_roc_auc))\nplt.title(f'{cls} ROCAUC: {img_roc_auc:.3f}')\n\nprint(f'{cls} ROCAUC: {img_roc_auc:.3f}')\n\n#____________________\n# Visualize\n# original\ntarget_names = {-1:'abnormal', 1:'normal'}\norginal_title = [f'{cls}_{target_names[label]}' for label in gt_list]\nplot_gallery(ori, orginal_title, size[0], size[1])\n\n\nprediction_titles = [title(y_pred, gt_list, target_names, i)\n                     for i in range(y_pred.shape[0])]\n# Reconstruction img                     \nplot_gallery(Reconstruction_imgs, prediction_titles, size[0], size[1])\n# Reconstruction error img                     \nplot_gallery(Reconstruction_error, prediction_titles, size[0], size[1])\n\n\nplt.show()\n#____________________","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### Embedding feature based anomaly detection\n\n이번 텀프로젝트에서의 embedding feature based anomaly detection은 randomized PCA와 one-class SVM을 이용합니다.\n파이프라인은 아래와 같습니다.\n\n1. Randomized PCA를 이용하여 정상 영상의 feature f를 추출\n2.  one-class SVM을 이용하여 f의 support vector를 학습합니다.\n3. 1에서 사용된 randomized PCA 이상/정상 영상의 feature f_ts를 추출니다.\n4. one-class SVM로 f_ts가 정상 분포 경계에 포함 여부로 정상/이상을 분류합니다.\n\n\nMetric : ROC_AUC [위키피디아](https://en.wikipedia.org/wiki/Receiver_operating_characteristic)","metadata":{}},{"cell_type":"code","source":"DB_PATH = \"../input/2021-ml-tp2/MVTecAD\"\n\nOUT_PATH = './'\nFIG_PATH = join(OUT_PATH, \"fig\")\nif os.path.isdir(FIG_PATH)!=True:\n    os.mkdir(FIG_PATH)\n\n# -------------------------------------\n# [Empty Module #2]\n# -------------------------------------\n# ------------------------------------------------------------\n# 구현 가이드라인 \n# ------------------------------------------------------------\n# Empty Module\n# [0] classification module과 pca 모델 초기화\n#  사용된 PCA. e.g. PCA, RandomizePCA, KernelPCA...\n# Baseline : sklearn.svm.OneClassSVM, kernel=\"rbf\", gamma=0.001, nu=0.01\n# Baseline : PCA(n_components=n_components), random_state=777\n# RandomizePCA ~(svd_solver='randomized') 사용시, whiten=True로 사용.\n# [1] train.imgs와 val.imgs 정규화.\n# [2] PCA 초기화와 정규화된 train.imgs를 학습 후, 정규화된 train.imgs, val imgs를 transform.\n# [3] OneClassSVM을 이용하여 차원 축소된 train 학습 후 val data 추론. \n# hint. OneClassSVM.predict, OneClassSVM.score_samples 사용.\n# [4] test.imgs에 [1]~[3]과 동일한 과정을 적용. 단, 추론 시 score에 대해서만 계산\n\n\ndataset = MVTec_AD(DB_PATH, OUT_PATH, flatten=True, size=size)\n\nn_components=180\nsize = (86, 86)\n\n\ny_preds = []\ny_test = []\n\nroc_auc = dict()\ntotal_roc_auc = []\n\nplt.figure(figsize=[10, 10])\n# print(f'\\n=====clf:{clf_name}, DA:{dr_name}=====')\n# total_roc_auc = []\n\npbar = tqdm(dataset.class_names)\n\nsample_submit = pd.read_csv('../input/2021-ml-tp2/sample_submit.csv', index_col=0)\ni=0\nfor cls in pbar:\n\n    train = dataset.read_mvtec(cls=cls, mode='train')\n    val = dataset.read_mvtec(cls=cls, mode='val')\n    test = dataset.read_mvtec(cls=cls, mode='test')\n    \n    # [0]\n    # clf = ??\n    # pca = ??\n    \n    # [1]\n\n    # [2]\n\n    ## val\n    # [3]\n    # 변수명\n    # y_pred : clf.predict\n    # cls_score_val : clf.score_samples\n\n    ## test\n    #[4]\n    # 변수명\n    # cls_score : clf.score_samples\n\n    #####################\n    # eval~valid data\n    y_preds.append(y_pred)\n    gt_list = np.array(val.labels)\n    fpr, tpr, _ = roc_curve(gt_list, cls_score_val)\n    img_roc_auc = roc_auc_score(gt_list, cls_score_val)\n    total_roc_auc.append(img_roc_auc)\n    plt.plot(fpr, tpr, label='%s ROCAUC: %.3f' % (cls, img_roc_auc))\n\n\n    #=======================================================================\n    # Submit & prdict test data\n    sample_submit['score'][i:i+cls_score.shape[0]] = scaler.fit_transform(cls_score.reshape(-1,1)).reshape(-1)\n    i = i+cls_score_val.shape[0]\n    #=======================================================================\n\nprint('Average ROCAUC: %.3f' % np.mean(total_roc_auc))\nclf_name = type(clf).__name__\npca_name = type(pca).__name__\nplt.title(f'{clf_name}_{pca_name}_{n_components}\\nAverage image ROCAUC: {np.mean(total_roc_auc):.3f}' )\n\nplt.xlabel('False Positive Rate')\nplt.ylabel('True Positive Rate')\nplt.legend(loc='lower right')\n\n# Result Figure\nplt.savefig(f'{FIG_PATH}/{clf_name}_{pca_name}_{n_components}.jpg')\n\n# Submit CSV\nsample_submit.to_csv(f'./{clf_name}_{pca_name}_{n_components}.csv')","metadata":{"trusted":true},"execution_count":null,"outputs":[]}]}