diff --git a/P6_Dogs_VS_Cats/Class Activation Map Visualizations.ipynb b/P6_Dogs_VS_Cats/Class Activation Map Visualizations.ipynb new file mode 100644 index 0000000..ef7df20 --- /dev/null +++ b/P6_Dogs_VS_Cats/Class Activation Map Visualizations.ipynb @@ -0,0 +1,473 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 可视化类激活图随模型训练而动态变化的过程\n", + "\n", + "- 自定义能够存储输出层历史权重矩阵的回调函数" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Using TensorFlow backend.\n" + ] + } + ], + "source": [ + "from keras.preprocessing.image import ImageDataGenerator\n", + "from keras.models import load_model, Model\n", + "from keras.layers import Input, Dense, Dropout, Activation, Flatten\n", + "from keras.layers import Convolution2D, MaxPooling2D, GlobalAveragePooling2D\n", + "from keras.layers.normalization import BatchNormalization\n", + "from keras.applications import ResNet50, VGG16, InceptionV3\n", + "from keras.applications.vgg16 import preprocess_input, decode_predictions\n", + "# from utils import make_parallel\n", + "import os\n", + "import sys\n", + "import cv2\n", + "import numpy as np\n", + "import pandas as pd\n", + "import matplotlib.pyplot as plt\n", + "from time import time\n", + "from keras.utils import to_categorical\n", + "from tqdm import tqdm\n", + "from sklearn.model_selection import train_test_split\n", + "%matplotlib inline" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. 载入训练集\n", + "\n", + "- 从硬盘读取25000张猫狗图片,存入data\n", + "- 定义训练集配套的图片类型标签,存入label\n", + "- 将训练集进一步划分为训练集和验证集" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "100%|███████████████████████████████████████████████████| 12500/12500 [00:28<00:00, 437.40it/s]\n", + "100%|███████████████████████████████████████████████████| 12500/12500 [00:28<00:00, 436.55it/s]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Training Data Size = 3.50 GB\n" + ] + } + ], + "source": [ + "path = os.getcwd() + '\\\\train\\\\'\n", + "label = np.array([0] * 12500 + [1] * 12500)\n", + "data = np.zeros((25000, 224, 224, 3), dtype=np.uint8)\n", + "\n", + "for i in tqdm(range(12500)):\n", + " img = cv2.imread(path + 'cat\\\\' + str(i) + '.jpg')\n", + " img = img[:, :, ::-1]\n", + " img = cv2.resize(img, (224, 224))\n", + " data[i] = img\n", + " \n", + "for i in tqdm(range(12500)):\n", + " img = cv2.imread(path + 'dog\\\\' + str(i) + '.jpg')\n", + " img = img[:, :, ::-1]\n", + " img = cv2.resize(img, (224, 224))\n", + " data[i+12500] = img\n", + " \n", + "print('Training Data Size = %.2f GB' % (sys.getsizeof(data)/1024**3))" + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "X_train, X_val, y_train, y_val = train_test_split(data, label, shuffle=True, test_size=0.2, random_state=42)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. 创建模型\n", + "\n", + "- 基模型直接使用只含卷积层部分的预训练 `VGG16` 模型\n", + "- 在基模型的基础上追加GAP、Dropout、Dense,即分类器部分\n", + "- 由于要解决的是二分类问题,因此使用 `binary_crossentropy` 作为损失函数,y也不用 one-hot 编码了\n", + "- 采用 Transfer Learning 策略:锁定 base_model 所有层的参数,仅允许训练分类器部分的参数(只有512+1=513个参数)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "base_model = VGG16(include_top=False, weights='imagenet')\n", + "for layers in base_model.layers:\n", + " layers.trainable = False\n", + "y = GlobalAveragePooling2D()(base_model.output)\n", + "y = Dropout(0.25)(y)\n", + "y = Dense(1, activation='sigmoid')(y)\n", + "model = Model(inputs=base_model.input, outputs=y)\n", + "model.compile(optimizer='adadelta', loss='binary_crossentropy', metrics=['accuracy'])" + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Trainable Parameters: 513\n" + ] + } + ], + "source": [ + "from keras import backend as K\n", + "\n", + "def get_params_count(model):\n", + " trainable = int(np.sum([K.count_params(p) for p in set(model.trainable_weights)]))\n", + " non_trainable = int(np.sum([K.count_params(p) for p in set(model.non_trainable_weights)]))\n", + " return trainable, non_trainable\n", + "\n", + "print('Trainable Parameters: ', get_params_count(model)[0])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. 训练模型\n", + "\n", + "- 想要看到模型”成长的过程“,就需要了解模型在每个Batch后权重更新的状态。具体在这里,由于由于模型的卷积层参数已经冻结,并不会对模型的成长有任何贡献,因此实际上模型的进化过程完全由输出层的这513个权重参数决定。\n", + "\n", + "- 具体实现上,我们需要定义一个Lambda Callback,用来备份在每个batch结束时最后一层的权重数值。\n", + "\n", + "- 模型仅经过一代训练就可以达到 94.5% 的验证集准确率,我们应该能在稍后的处理之后欣赏到模型从50%上升到94.5%的完整心路历程。" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "from keras.callbacks import LambdaCallback\n", + "weights_history = []\n", + "get_weights_cb = LambdaCallback(on_batch_end=lambda batch, \n", + " logs: weights_history.append(model.layers[-1].get_weights()[0]))" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Train on 20000 samples, validate on 5000 samples\n", + "Epoch 1/1\n", + "20000/20000 [==============================] - 230s - loss: 0.6573 - acc: 0.8566 - val_loss: 0.1929 - val_acc: 0.9442\n" + ] + } + ], + "source": [ + "history = model.fit(x=X_train, y=y_train, \n", + " batch_size=16, \n", + " epochs=1, \n", + " validation_data=(X_val, y_val), \n", + " callbacks=[get_weights_cb])" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import pickle\n", + "with open('weights_history.p', 'wb') as f:\n", + " pickle.dump(weights_history, f)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "with open('weights_history.p', 'rb') as f:\n", + " lalala = pickle.load(f)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. 权重处理\n", + "\n", + "$$\n", + "\\begin{matrix}\n", + "\\mathbf{Input} & \\longrightarrow & \\mathbf{CONV ~ Output} & \\longrightarrow & \\mathbf{GAP ~ Output} & \\stackrel{\\color{red}{W^{(512)}}}{\\longrightarrow} & \\mathbf{Dense} ~ \\mathrm{(Sigmoid)} ~ \\mathbf{Output}\\\\[2ex]\n", + "[224, ~224, ~3] && [7, ~7, ~512] && [~512~] && [~1~]\n", + "\\end{matrix}\n", + "$$\n", + "\n", + "- 对于同一张图片,由于基模型恒定不变,因此 $\\mathbf{CONV ~ Output}$(特征图集 Feature Maps)固定不变, 也就是下面的 out_base\n", + "- 从 $\\mathbf{CONV ~ Output}$ 到最后预测的概率值还要先经过全局平均池化、乘以权重、经过Sigmoid这三步才能得到,直接用下面的函数手动实现了下" + ] + }, + { + "cell_type": "code", + "execution_count": 60, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "target = data[1][:, :, ::-1]" + ] + }, + { + "cell_type": "code", + "execution_count": 61, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(7, 7, 512)\n" + ] + } + ], + "source": [ + "out_base = base_model.predict(np.expand_dims(target, axis=0))\n", + "out_base = out_base[0]\n", + "print(out_base.shape)" + ] + }, + { + "cell_type": "code", + "execution_count": 62, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "0.37235825669154349" + ] + }, + "execution_count": 62, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# 根据卷积层输出特征图集和模型某一参数状态计算预测概率(为了简单省略了bias计算)\n", + "def predict_on_weights(out_base, weights):\n", + " gap = np.average(out_base, axis=(0, 1))\n", + " logit = np.dot(gap, np.squeeze(weights))\n", + " return 1 / (1 + np.e ** (-logit))\n", + "\n", + "predict_on_weights(out_base, weights_history[42])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5. 类激活图计算\n", + "\n", + "- 类激活图其实就是将卷积层输出的特征图按输出层分类器的权重进行加权合成的一副图样\n", + "- 基本流程\n", + " - **归一化**:由于ColorMap的工作原理是将任意矩阵的取值范围映射到0~255范围内,因此为了之后好挑选颜色,需要归一化一下。\n", + " - 手动放大至[0,255]\n", + " - 元素格式化\n", + " - **设定门限**:由于只希望将模型认为比较重要的区域标记出来,因此应该选择一个门限值,将该门限值之下的像素位置至0(此时该像素透明)\n", + " - 在图像本体添加文字标注(方便之后导出为视频)" + ] + }, + { + "cell_type": "code", + "execution_count": 69, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "image/png": "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\nSUVORK5CYII=\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import seaborn as sns\n", + "plt.figure(figsize=(15, 0.5))\n", + "band = np.array([list(np.arange(0, 255, 10))] * 1)\n", + "sns.heatmap(band, annot=True, fmt=\"d\", cmap='jet', cbar=False)\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 64, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def getCAM(weights, display=False):\n", + " \n", + " predict = predict_on_weights(out_base, weights)\n", + " \n", + " # Weighted Feature Map\n", + " cam = (predict - 0.5) * np.matmul(out_base, weights)\n", + " # Normalize\n", + " cam = (cam - cam.min()) / (cam.max() - cam.min())\n", + " # Resize as image size\n", + " cam_resize = cv2.resize(cam, (224, 224))\n", + " # Format as CV_8UC1 (as applyColorMap required)\n", + " cam_resize = 255 * cam_resize\n", + " cam_resize = cam_resize.astype(np.uint8)\n", + " # Get Heatmap\n", + " heatmap = cv2.applyColorMap(cam_resize, cv2.COLORMAP_JET)\n", + " # Zero out\n", + " heatmap[np.where(cam_resize <= 100)] = 0\n", + " \n", + " out = cv2.addWeighted(src1=target, alpha=0.8, src2=heatmap, beta=0.4, gamma=0)\n", + " out = cv2.resize(out, dsize=(500, 500))\n", + " \n", + " if predict < 0.5:\n", + " text = 'cat %.2f%%' % (100 - predict * 100)\n", + " else:\n", + " text = 'dog %.2f%%' % (predict * 100)\n", + " \n", + " cv2.putText(out, text, (290, 50), fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=0.9, \n", + " color=(123,222,238), thickness=2, lineType=cv2.LINE_AA)\n", + " if display:\n", + " cv2.imshow('img', out)\n", + " cv2.waitKey(0)\n", + " cv2.destroyAllWindows()\n", + " return out" + ] + }, + { + "cell_type": "code", + "execution_count": 65, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "getCAM(weights_history[1000], display=True);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 6. 导出视频\n", + "\n", + "- 视频就是一个播放的很快的图片幻灯片\n", + "- 使用VideoWriter生成视频,由于iOS使用的是H.264,这里用MP4格式。" + ] + }, + { + "cell_type": "code", + "execution_count": 66, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "fourcc = cv2.VideoWriter_fourcc(*'MP4V')\n", + "out = cv2.VideoWriter('outputx.mp4',fourcc, 20.0, (500, 500))\n", + "\n", + "for i in range(1250):\n", + " img = getCAM(weights_history[i])\n", + " out.write(img)\n", + " cv2.imshow('frame', img)\n", + " if cv2.waitKey(1) & 0xFF == ord('q'):\n", + " break\n", + "\n", + "out.release()\n", + "cv2.destroyAllWindows()" + ] + } + ], + "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.5.3" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +}