好きこそ物の上手なれ 人生の断想録

VGG16モデルの特徴マップを可視化してみた

f:id:yukitaka13:20190104155032p:plain

VGG16とは

f:id:yukitaka13:20190121175415p:plain

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

f:id:yukitaka13:20190121175706p:plain

可視化する

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

  • VGG16学習済みモデル
  • 特徴抽出したい画像

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

特徴マップの選択

  • Convolution Layer
  • Pooling Layer

の2種類を可視化する。

入力に使う画像

f:id:yukitaka13:20190121180926j:plain

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

ソースコード

画像の読み込み

    # 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))


学習済みモデルのロード

    # 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)

    # 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])


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

    # 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...")

出力結果

f:id:yukitaka13:20190122110320g:plain


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

f:id:yukitaka13:20190121183325g:plain

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

終わりに

VGG16の特徴マップを可視化しました。
今回使用したコードはGitHubにアップしていますのでご自由にお使いください。
github.com