VGG16の特徴マップを可視化する


# VGG16とは

畳み込み13層とフル結合3層の計16層から成る畳み込みニューラルネットワークのこと。
2014年のILSVRC（ImageNet Large Scale Visual Recognition Challenge）という画像分類のコンペで提案された。
構成は以下の図を参照。

![vgg16_structure.png](https://qiita-image-store.s3.amazonaws.com/0/166345/8686ec1b-0546-c2f6-9c2e-55463df5d1ef.png)



# 可視化する

実際に特徴マップを可視化していく。

- <b>VGG16学習済みモデル</b>
- <b>特徴抽出したい画像</b>

を用意するだけで可視化できる。学習済みモデルに関してはimportすれば自動でダウンロードしてくれる。便利。


## 特徴マップの選択


- <b>Convolution Layer</b>
- <b>Pooling Layer</b>

の2種類を可視化する。

## 入力に使う画像

![turu.jpeg](https://qiita-image-store.s3.amazonaws.com/0/166345/7358a513-a377-c29f-2a3d-4e2058990576.jpeg)


今回はツルの画像を使う。というのも鳥系の画像は特徴マップが比較的みやすいからだ。

## ソースコード

画像の読み込み

```python

    # NOTE: Load image.
    img = image.load_img(args.img, target_size=(224, 224))
    img = image.img_to_array(img)
    img = np.expand_dims(img, axis=0)
    img = preprocess_input(img)
    print("IMAGE FILENAME: %s" % os.path.basename(args.img))

```


学習済みモデルのロード

```python

    # NOTE: Load trained model.
    print("[status] Loaded model...")
    if args.model is not None:
        model = load_model(args.model)
    elif not (args.model_json is None or args.model_weight is None):
        model = model_from_json(open(args.model_json).read())
        model.load_weights(args.model_weight)
    else:
        model = VGG16(weights="imagenet")
```


出力する層の選択（Convolution and Pooling Layer）

```python

    # NOTE: Select output layer and predict.
    print("[status] Extract input image features...")
    layers = model.layers[1:19]
    layer_outputs = [layer.output for layer in layers]
    activation_model = models.Model(inputs=model.input, outputs=layer_outputs)
    # activation_model.summary()
    activations = activation_model.predict(img)

    # NOTE: Select visualize layers.
    conv_and_pool_activations = []
    for layer, activation in zip(layers, activations):
        is_pooling_layer = isinstance(layer, MaxPooling2D)
        is_convolution_layer = isinstance(layer, Convolution2D)
        if is_pooling_layer or is_convolution_layer:
            conv_and_pool_activations.append([layer.name, activation])

```


特徴マップをヒートマップに変換して保存

```python

    # NOTE: Generate heatmap.
    print("[status] Generating heatmaps...")
    os.makedirs(args.directory, exist_ok=True)
    for i, (name, activation) in enumerate(conv_and_pool_activations):
        print("[status] Processing %s layer..." % name)
        n_imgs = activation.shape[3]
        n_cols = math.ceil(math.sqrt(n_imgs))
        n_rows = math.floor(n_imgs / n_cols)
        screens = []
        for y in range(0, n_rows):
            rows = []
            for x in range(0, n_cols):
                j = y * n_cols + x
                if j < n_imgs:
                    featuremap_img = activation[0, :, :, j]
                    rows.append(featuremap_img)
                else:
                    rows.append(np.zeros())
            screens.append(np.concatenate(rows, axis=1))
        screens = np.concatenate(screens, axis=0)
        plt.figure()
        sns.heatmap(screens, xticklabels=False, yticklabels=False)
        save_name = "%s.png" % name
        save_path = os.path.join(args.directory, save_name)
        plt.savefig(save_path)
        plt.close()
    print("[status] Generating heatmap has finished...")

```


## 出力結果

![heatmaps_turu.gif](https://qiita-image-store.s3.amazonaws.com/0/166345/6f44fd2d-1dca-a84b-e8af-dc80ef72deb4.gif)


Layerが進に従って画像は小さくなり、特徴も大まかになっている。これだけじゃイマイチ分からないので、特徴強度の中から平均強度が最も高い特徴強度を選んでみた。

![heatmap_turu.gif](https://qiita-image-store.s3.amazonaws.com/0/166345/d503ae28-db04-411e-9074-573f1746d284.gif)


最後は大まかな特徴を捉えられている。すごい。

# 終わりに

VGG16の特徴マップを可視化しました。
今回使用したコードは[GitHub](https://github.com/yukitaka13-1110/deep-learning/blob/master/visualize_feature_map_vgg16.py)にあげています。
https://github.com/yukitaka13-1110