【深層学習】時系列データに対する1次元畳み込み層の出力を可視化

2018/07/19
このエントリーをはてなブックマークに追加

機械学習エンジニアインターン生の杉崎です。 今回は時系列データ予測に一次元畳み込み層を使用した際の出力の可視化の方法について書きたいと思います。

本記事の目的

 深層学習における畳込み層は多くは画像等の2次元データに使われることが多いです。そして、ブラックボックスであるモデルに対して理由を明らかにするため、中間層の重みや出力を取り出し可視化する様々な手法が提案されています。(下図)

画像引用元

 しかし、そんな中で一次元畳込み層(Conv1D)を用いたモデルでは可視化の事例があまり多くありません。そこで今回はConv1D層の出力の可視化の一例についてご紹介します。

目次

  • 本記事の目的
    • 画像などの2次元データに対する可視化手法は数多く提案されている。
    • しかし、1次元データに対する中間層の可視化は事例が少ない。
    • そこで、1次元データを入力とする1次元畳み込み層(Conv1D)を使用したモデルを用いて可視化を行う。
  • 実装環境
  • ソースコード
  • サンプル時系列データの作成
  • 一次元畳み込み層を用いた時系列予測モデル作成
    • 入力データの前処理
    • 予測モデル作成 (Keras)
    • モデルの学習
    • 学習済みモデルの保存
    • 学習過程の確認
    • 予測の確認
  • 中間層Conv1Dの出力を取得と可視化
    • 学習済みモデルの読み込み
    • 入力データ作成
    • 中間層の出力の取得方法
    • conv1d_1(Conv1D)の出力の描画
    • チャネル1の描画
    • (batch, steps) で描画
    • steps分を足しあわせて描画
    • すべてのチャネルを入力波形と重ねて描画
    • 各チャネルごとに入力波形と重ねて描画
  • 特定のチャネルを削除した予測モデル
    • 2つのチャネルを削除
    • 正解波形と予測波形の比較
    • MSE(二乗和誤差)のカラーマップ表示
  • 最後に
  • 参考

環境

  • OS : Ubuntu 16.04 LTS
  • Python : Python3.5.2
  • Jupyter
    • jupyter 4.4.0
    • jupyter-notebook 5.6.0

ソースコード

※もしGitHub上でipynbが表示されない場合はnbviewerのサイトへリンクのURLをペーストしてください。

サンプル時系列データの作成

 今回使用するサンプル時系列データは正弦波です。

$$
\textbf{toyfunc(t)} = \sin\left( \frac{2\pi}{T} t \right)
$$

この関数を表すコードを以下に示します。

def mySin(t, period=100):
    """
    時刻t, 周期period
    """
    return np.sin(2.0 * np.pi * t/period)

# Add Noise
def toy_func(tStart=0, tStop=100, tStep=1, noiseAmpl=0.05):
    """
    T : sin波の周期
    ampl : ノイズ振幅調整
    """
    t = np.arange(start=tStart, stop=tStop, step=tStep)
    noise = np.random.randn(t.shape[0])  # mean=0, standard_deviation=1
    return (t, mySin(t=t, period=100) + noiseAmpl * noise)

一次元畳み込み層による時系列予測モデル作成

 まず時系列予測を行うConv1dを用いた学習済みモデルが必要なので、サンプルデータとモデルの学習を行います。ソースコードはこちらです。

入力データの前処理

 次にモデルに入力する形に変えてやります。ここで行おうとしているは過去100個分のデータ(steps100)を用いてその一つ先のデータを予測する(予測サイズ1)というものです。 以下のGIF動画の示すようにモデルの学習時に与えるデータを入力データと正解データに分けます。 (各ブロックの数字はデータのインデックスです。)

<br>
 この処理を行っているコードが以下になります。 プログラムの中ではstepsの箇所をwindowsizeという変数を用いています。

>>> #----------------------------------------
>>> # Parameters
>>> #----------------------------------------
>>> windowsize = 100  # 周期と同じくとる
>>> predictsize = 1
>>> sample_data_size = 10000
>>> wave_size = sample_data_size
>>> trainsize = int(10000*0.8)

>>> #----------------------------------------
>>> # Sample Wave
>>> #----------------------------------------
>>> t, sample_wave = toy_func(tStop=sample_data_size, noiseAmpl=0)
>>> print(sample_wave.shape)
(10000,)

>>> #----------------------------------------
>>> # create input data
>>> #----------------------------------------
>>> input_data  = np.empty(shape=(0, windowsize))
>>> output_data = np.empty(shape=(0, predictsize))
>>> print(input_data.shape)
(0, 100)
>>> print(output_data.shape)
(0, 1)
>>> for i in range( sample_wave.shape[0] - (windowsize + predictsize) + 1 ):
        input_data = np.append(arr=input_data,
                               values=sample_wave[i:(i+windowsize)].reshape(1, -1),
                               axis=0)
        output_data = np.append(arr=output_data,
                                values=sample_wave[(i+windowsize):(i+windowsize+predictsize)].reshape(1, -1),
                                axis=0)
>>> print("input_data.shape  : ", input_data.shape)
input_data.shape  :  (9900, 100)
>>> print("output_data.shape : ", output_data.shape)
output_data.shape :  (9900, 1)
>>> #--------------------
>>> # Kerasのモデルに入力できる形にするためにreshapeして次元を足す
>>> #--------------------
>>> input_data = input_data.reshape((-1, windowsize, 1))
>>> output_data = output_data.reshape((-1, predictsize,))
>>> print("input_data.shape  : ", input_data.shape)
input_data.shape  :  (9900, 100, 1)
>>> print("output_data.shape : ", output_data.shape)
output_data.shape :  (9900, 1)
>>> train_x, test_x = input_data[:trainsize], input_data[trainsize:]
>>> train_y, test_y = output_data[:trainsize], output_data[trainsize:]
>>> print("train_x.shape : ", train_x.shape)
train_x.shape :  (8000, 100, 1)
>>> print("train_y.shape : ", train_y.shape)
train_y.shape :  (8000, 1)
>>> print("test_x.shape  : ", test_x.shape)
test_x.shape  :  (1900, 100, 1)
>>> print("test_y.shape  : ", test_y.shape)
test_y.shape  :  (1900, 1)

予測モデル作成 (Keras)

 今回はConv1Dを用いた小さめのモデルを作成します。
 入力や各層の出力の形は(batch, steps, channels)のように表されますが、入力のstepsを維持しています。

>>> from keras.models import Sequential
>>> from keras.layers.convolutional import Conv1D
>>> from keras.layers.pooling import GlobalMaxPooling1D

>>> channel_size = 8
>>> kernel_size = 10

>>> model = Sequential()
>>> model.add( Conv1D(filters=channel_size, kernel_size=kernel_size,
                      strides=1, padding="same", activation="relu",
                      input_shape=(windowsize, 1) ) )
>>> model.add( Conv1D(filters=1, kernel_size=8, padding='same', activation='tanh' ) )
>>> model.add( GlobalMaxPooling1D() )

>>> model.compile(loss='mse', optimizer='adam')
>>> model.summary()
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv1d_1 (Conv1D)            (None, 100, 8)            88        
_________________________________________________________________
conv1d_2 (Conv1D)            (None, 100, 1)            65        
_________________________________________________________________
global_max_pooling1d_1 (Glob (None, 1)                 0         
=================================================================
Total params: 153
Trainable params: 153
Non-trainable params: 0
_________________________________________________________________
>>> from keras.utils import plot_model
>>> file = str(plot_images_Path / "model.png")
>>> plot_model(model=model, to_file=file)
>>> from IPython.display import SVG
>>> from keras.utils.vis_utils import model_to_dot
>>> SVG(data=model_to_dot(model).create(prog='dot', format='svg'))

 イメージとしては以下の図のようになります。 色の付いているconv1d_1は後ほど可視化する対象です。

モデルの学習

 それではこのモデルを学習して保存します。model.save()overwriteFalseにすると同じ名前のファイルが存在しているとき上書きするかを確認するようになります。

>>> #--------------------
>>> #  Fit
>>> #--------------------
>>> epochs = 100
>>> from keras.callbacks import EarlyStopping
>>> earlystop = EarlyStopping(monitor='val_loss', min_delta=0, patience=5, verbose=1, mode='auto')
>>> callbacks_list = [earlystop]
>>> history = model.fit(x=train_x,
                        y=train_y,
                        epochs=epochs,
                        verbose=1,
                        validation_split=0.1,
                        callbacks=callbacks_list)
>>> #--------------------
>>> #  Save Model
>>> #--------------------
>>> modelpath = str(keras_model_h5_Path / "model__{}_kernelsize{}.h5".format(ipynb_title, kernel_size))
>>> model.save(filepath=modelpath, overwrite=False)

学習過程の確認

 下図はepochsを重ねるごとのlossの値です。学習データに対するlossとバリデーションデータに対するlossがともに収束しているため、上手く学習できています。

 正解波形と予測波形を比較してもある程度予測出来ています。

中間層Conv1Dの出力を取得と可視化

 モデルの学習が終わりましたのでこのモデルで使用したConv1D層のどのチャネルが、波形のどの部分に強く反応しているのかを可視化してみます。

学習済みモデルの読み込み

 それでは先ほど保存したモデルを読み込みます。

>>> from keras.models import load_model
>>> model_filename = "model__create_OnlyConv1dModel__SimpleSinFuncWithNoNoise_kernelsize10.h5"
>>> modelpath = str(keras_model_h5_Path / model_filename)
>>> model = load_model(filepath=modelpath)
>>> model.summary()
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv1d_1 (Conv1D)            (None, 100, 8)            88        
_________________________________________________________________
conv1d_2 (Conv1D)            (None, 100, 1)            65        
_________________________________________________________________
global_max_pooling1d_1 (Glob (None, 1)                 0         
=================================================================
Total params: 153
Trainable params: 153
Non-trainable params: 0
_________________________________________________________________

入力データの作成と処理

 conv1d_1 (Conv1D)の出力を得るためには一度データを入力する必要がありますので、学習に使用したものと同様に波形を作成します。学習ではt=0〜9,999のデータを使いましたので、ここで入力するデータはt=10,000〜10,300とします。
 波形サイズ:300、ステップサイズ:100、予測サイズ:1の入力データとします。

>>> tStart = 10000
>>> windowsize = 100
>>> predictsize = 1
>>> wave_size = 300
>>> assert wave_size - windowsize > windowsize  # 後の畳み込むコードではこの条件が必要

>>> #----------------------------------------
>>> # create a wave
>>> #----------------------------------------
>>> t, wave = toy_func(tStart=tStart, tStop=tStart+wave_size, noiseAmpl=0)

>>> #----------------------------------------
>>> # create input data
>>> #----------------------------------------
>>> input_arr = np.empty(shape=(0, windowsize))
>>> print(input_arr.shape)
(0, 100)
>>> for i in range( wave.shape[0] - (windowsize + predictsize) + 1 ):
>>>     input_arr = np.append(arr=input_arr,
>>>                            values=wave[i:(i+windowsize)].reshape(1, -1),
>>>                            axis=0)
>>> print("input_arr.shape  : ", input_arr.shape)
input_arr.shape  :  (200, 100)
>>> input_arr = input_arr.reshape((-1, windowsize, 1))
>>> print("input_arr.shape  : ", input_arr.shape)
input_arr.shape  :  (200, 100, 1)

中間層の出力の取得方法

 詳しくはKerasのドキュメントを参照してください。ここでは以下のような手法で取得しています。例としてconv1d_1(Conv1D)の出力を取得しています。

>>> from keras import backend as K
>>> get_hidden_layer_model = K.function(inputs=[model.input],
                                        outputs=[model.layers[0].output])
>>> hidden_layer_output = get_hidden_layer_model(inputs=[input_arr])[0]
>>> print(hidden_layer_output.shape)
(200, 100, 8)

conv1d_1(Conv1D)の出力の描画

 conv1d_1(Conv1D)の出力を(batch, steps, channels)=(200, 100, 8)の形で取得出来たので、このデータをチャネルごとに分けて描画してみます。 値の大きさはカラーマップで区別するのが良いと思います。

チャネル1の描画

 それでは例として8つのチャネル(ch0-ch7)のうち、ch1の描画を行います。 ちなみにch1を取り上げた理由は見た目が分かりやすかったからです。 最終的にはすべて描画します。

(batch, steps) で描画

 最初はconv1d_1(Conv1D)の出力からch1のみを取り出したもの((batch, steps, channels)=(200, 100, 1))をそのまま描画します。(x軸, y軸)=(batch, steps)のようにとれば問題無さそうです。

>>> ch = 1
>>> assert ch < hidden_layer_output.shape[2]
>>> #--------------------
>>> # Resize
>>> #--------------------
>>> outputs = hidden_layer_output[:, :, ch].squeeze()
>>> print(outputs.shape)
(200, 100)
>>> outputs = outputs.T
>>> print(outputs.shape)
(100, 200)
>>> #--------------------
>>> # get max value for plot Color
>>> #--------------------
>>> #    カラーマップでは値が0に近づくほど無色にしたほうがわかりやすいため, 
>>> #    最大値と最小値の絶対値のうち最大をとり, それを両極端の色とする.
>>> print("max : ", np.max(outputs))
max :  3.1358464
>>> print("min : ", np.min(outputs))
min :  0.0
>>> max_abs = np.maximum(np.max(outputs),
                         abs(np.min(outputs))
                        )
>>> print("max abs : ", max_abs)
max abs :  3.1358464

>>> #--------------------------------------------------------------------------------
>>> # Setting Parameter
>>> #--------------------------------------------------------------------------------
>>> title = "{}__channel{}__allWindow__windowsize_{}".format(ipynb_title, ch, windowsize)
>>> filename = title + ".png"

>>> ##------------------------------------------------------------
>>> ## Plot
>>> ##------------------------------------------------------------
>>> figsize=(14, 7)
>>> fig = plt.figure(figsize=figsize)
>>> ax = fig.add_subplot(1,1,1)
>>> mappable = ax.imshow(outputs,
                         cmap='seismic',  # <-- (min,max)=(blue, red)
                         vmin=-max_abs,
                         vmax=max_abs,
                         )
>>> fig.colorbar(mappable,
                 ax=ax,
                 #orientation='horizontal',
                 orientation='vertical',
                 shrink=0.5,
                 )

>>> ###----------------------------------------
>>> ### change x,y axis ratio
>>> ###----------------------------------------
>>> ### height is aspect_num times the width
>>> ### 縦:横=1:2
>>> print("ax.get_xlim() : {}".format(ax.get_xlim()))
ax.get_xlim() : (-0.5, 199.5)
>>> print("ax.get_ylim() : {}".format(ax.get_ylim()))
ax.get_ylim() : (99.5, -0.5)
>>> xratio = 6
>>> aspect = (1/xratio) * (ax.get_xlim()[1] - ax.get_xlim()[0]) / (ax.get_ylim()[0] - ax.get_ylim()[1])
>>> ax.set_aspect(aspect=aspect)

>>> ###----------------------------------------
>>> ### plot config
>>> ###----------------------------------------
>>> ax.set_title(label=title, fontsize=20, y=1.5)
>>> ax.set_xlabel(xlabel="t'", fontsize=15)
>>> ax.set_ylabel(ylabel="windowsize index", fontsize=15, rotation=0)
>>> ax.yaxis.set_label_coords(x=0.01, y=1.1)  # ylabel position
>>> ax.tick_params(labelsize=20)  # tick fontsize

>>> fig.savefig(fname=str(plot_images_Path / filename))
>>> plt.show()

 この結果より右上から左下に同じくらいの値が並んでいます。 これはモデル定義の際にConv1Dのstridesの値がデフォルトで1になっているからです。 各stepが入力として渡した時系列を1つずつずらして畳み込みフィルタに入れているためです。
 もう少し詳しく説明します。 以下のGIFは入力データ((batch, steps, channels)=(200, 100, 1))を(batch, steps)=(200, 100)の行列とみた配列です。 四角の中の数字が波形のインデックスであり、カラーマップ同様、右上から左下に同じインデックスが並んでいます。

 この入力データが kernelsize=10, strides=1, padding="same" の畳み込みフィルタを通る様子が以下のGIFになります(このときのpaddingは入力データの左右に5個ずつデータを加えると出力サイズが同じ(same)になります)。 つまり、フィルタを通ったあとのデータは右上から左下にかけて同じ波形のデータを入力とした出力になります。 これが先ほどのカラーマップの特徴の理由です。 (正確に言えば、両端の、パディングを含んでいる箇所の畳み込みは取り込むパディングの数が違うので一致はしませんが近い値になります。)

steps分を足しあわせて描画

 上の結果を見る限り各stepsを別々に考えることにはあまり意味はありません。 そこで斜めの値の和あるいは平均などの形で一つにまとめます。 ここではsteps分を足しあわせて一つの値にしています。 今回はを取ってまとめようと思いますが、以下の図のオレンジの箇所は足りないので値を複製して埋めます(詳細はコードを確認してください)。 プログラムでは畳み込みの出力(行列)を転置して扱ったほうがわかりやすいため転置されていることに注意してください。

プログラムコードは以下のように書くことで steps を一つにまとめることができます。

>>> figsize = (25, 10)
>>> cmap = "seismic"

>>> #--------------------------------------------------------------------------------
>>> # Setting Parameter
>>> #--------------------------------------------------------------------------------
>>> title = "{}__channel{}__convolveWindows__windowsize_{}".format(ipynb_title, ch, windowsize)
>>> filename = title + ".png"

>>> #----------------------------------------
>>> # Convolved window size
>>> #----------------------------------------
>>> outputs_tmp = hidden_layer_output[:, :, ch].squeeze()
>>> outputs_tmp = outputs_tmp.T

>>> # Prepare for convolved
>>> outputs = np.empty(shape=(0, wave_size - predictsize))
>>> for window_idx in range(windowsize):
        _shape = outputs_tmp.shape[1]
        # append last
        if window_idx < windowsize-1:
            _val = outputs_tmp[-1, -windowsize+window_idx+1:]
            insert_arr = np.append(arr=outputs_tmp[window_idx],
                                   values=_val,
                                   axis=None)
        else:
            insert_arr = outputs_tmp[window_idx]

        # insert first
        _val = outputs_tmp[0, :window_idx]
        insert_arr = np.insert(arr=insert_arr,
                               obj=[0 for i in range(window_idx)],
                               values=_val,
                               ).reshape(1, -1)
        # append to the array
        #print(outputs.shape)
        #print(insert_arr.shape)
        outputs = np.append(
            arr=outputs,
            values=insert_arr,
            axis=0,
        )
>>> print("outputs.shape : ", outputs.shape)
outputs.shape :  (100, 299)

>>> # Convolved
>>> outputs_convolve_windows = outputs.sum(axis=0).reshape(1, -1)
>>> print("outputs_convolve_windows.shape : ", outputs_convolve_windows.shape)
outputs_convolve_windows.shape :  (1, 299)

 steps を一つにまとめたあと(outputs_convolve_windows) では、100だったstepsが1になっていることがわかります。 これを描画したものが下図です。

 横軸は0-298の299個あり、これは入力に使用した0-298の波形データ数と一致させています。 これより元の波形と重ねて表示することができます。(下図)

すべてのチャネルを入力波形と重ねて描画

 他のチャネルについても表示します。(下図)
 全体を表示してみると ch0 と ch7 がほとんど寄与していないことがわかります。 また、各チャネルごとに波形のなかで注目している箇所が異なることもわかると思います。

各チャネルごとに入力波形と重ねて描画

 具体的にどの箇所でどのチャネルが反応しているのかをわかりやすくなるように分けて描画します(下図)。 すると、いくつかの特徴を把握できます。
– ch0,ch7 の反応はかなり小さい
– ch1 の反応は正弦波ので特に大きい
– ch3 は降下箇所での反応が大きい
– ch4 は上昇箇所での反応が大きい
– ch5, ch6 は谷付近で反応

特定のチャネルを削除した予測モデル

 今まででの方法では中間層の出力値からどのチャネルの出力値が高いかということしかわかりません。 そこで具体的にどのチャネルが重要であるかを判定するために、あるチャネルを削除したときのMSE(二乗和誤差)の利用を考えてみます。 特定のチャネルを削除するということは、中間層における出力から特定のチャネルの値をすべて0にして次の層に渡すことであるとみなすことができます。
 今回は以下のようにconv1d_1(Conv1D)の出力のチャネルを削除して次の層に渡しています。

2つのチャネルを削除

 8チャネルのうち2つのチャネルの削除する場合の数は28通りなので全組み合わせについて試してみます。 正解波形と予測波形の比較とMSE(二乗和誤差)のカラーマップ表示を行います。

 以下の関数はchannel_indexに与えたチャネルを取り除いた結果を返してくれます。

def removeIntermidiateChannels(input_array, model, layer_index=0, channel_index=[0]):
    """
    model : keras model
    layer_index : int
        層の順番
    channel_index=[]
        削除するチャネル
    reference:
      - https://keras.io/getting-started/faq/#how-can-i-obtain-the-output-of-an-intermediate-layer
    """
    from keras import backend as K

    # check input
    assert type(model) == keras.models.Sequential
    assert type(layer_index) == int
    assert type(channel_index) == list 

    # get first half layers output
    get_first_half = K.function(inputs=[model.input],
                                outputs=[model.layers[layer_index].output])
    first_half_output = get_first_half(inputs=[input_array])[0]

    # remove channels
    shape = first_half_output.shape[:2]
    for idx in channel_index:
        first_half_output[:,:,idx] = np.zeros(shape)

    # get second half layers output
    get_second_half = K.function(inputs=[model.layers[layer_index+1].input],
                                 outputs=[model.output])
    second_half_output = get_second_half(inputs=[first_half_output])[0]
    return second_half_output

正解波形と予測波形の比較

 チャネルを削除したモデルによる予測波形を正解波形と並べて描画してみます。

>>> channels_num = int(model.layers[0].output.shape[2])

>>> mse_remove2ch = np.zeros((channels_num, channels_num))
>>> mse_remove2ch.shape
(8, 8)

>>> fig = plt.figure(figsize=(50, 50))
>>> for ch1 in range(channels_num):
        for ch2 in range(channels_num):
            if ch1 == ch2:
                ax = fig.add_subplot(channels_num, channels_num, ch1*channels_num+ch2+1)
                #ax.plot(t, t)
            else:
                ax = fig.add_subplot(channels_num, channels_num, ch1*channels_num+ch2+1)
                ax.plot(t, wave)
                last_layer_output = removeIntermidiateChannels(input_array=input_arr,
                                                               model=model,
                                                               layer_index=0,
                                                               channel_index=[ch1, ch2])
                ax.plot(t[-len(last_layer_output):], last_layer_output.squeeze())
                mse = mean_squared_error(y_true=wave[windowsize:], y_pred=last_layer_output)
                ax.set_title(label="(ch{}, ch{})'s, MSE : {}".format(ch1, ch2, mse))
                mse_remove2ch[ch1, ch2] = mse
>>> plt.show()

これらの図は全体で、左から順にch0-ch7を、上から順にch0-ch7を削除したものです。 よって、右上と左下の図は対称的になっています。

拡大図リンク

 これにより視覚的ずれの大きいものずれ方の特徴などをつかむことができます。 よりズレが大きいほどそのとき削除したチャネルの役割が大きかったと言えます。

MSE(二乗和誤差)のカラーマップ表示

 視覚的情報だけではズレを正しく判断できないときがあるので、評価指標としてMSE(二乗和誤差)を用います。 先ほどの描画の際にmse_remove2chにMSEの値を保存しておいたのでカラーマップ表示によってMSEの大きいものをすぐに確認できます。

>>> #--------------------
>>> #  get max value for plot Color
>>> #--------------------
>>> max_abs = np.max(mse_remove2ch)
>>> print("max abs : ", max_abs)
max abs :  0.4231775843779829

>>> #------------------------------------------------------------
>>> #  Plot
>>> #------------------------------------------------------------
>>> figsize = None
>>> fontsize = 20

>>> fig = plt.figure(figsize=figsize)
>>> ax = fig.add_subplot(1,1,1)
>>> mappable = ax.imshow(mse_remove2ch,
                         cmap='seismic',  # <-- (min,max)=(blue, red)
                         vmin=-max_abs,
                         vmax=max_abs,
                         )
>>> fig.colorbar(mappable,
                 ax=ax,
                 orientation='vertical',
                 shrink=1.0,
                 )
>>> ax.set_title(label="MSE", fontsize=fontsize)
>>> ax.set_xticks(ticks=np.arange(channels_num))
>>> ax.set_yticks(ticks=np.arange(channels_num))
>>> ax.set_xlabel(xlabel="ch2", fontsize=fontsize)
>>> ax.xaxis.set_label_coords(x=1.0, y=-0.1)
>>> ax.set_ylabel(ylabel="ch1", fontsize=fontsize, rotation=0)
>>> ax.yaxis.set_label_coords(x=0, y=1.02)
>>> ax.tick_params(labelsize=fontsize)
>>> plt.show()

 この結果よりMSEが大きいペアである(ch1, ch3), (ch2, ch3)の重要度が高く、全体的にはch3が大きく寄与していることが把握できます。

最後に

 本記事では一次元畳み込み層(Conv1D)の可視化手法について扱いました。 大きく分けて出力の可視化とチャネルを削除することによる重要度の把握しました。 深い層のモデルについては挑戦中ですが、入力層に近い層に関してはこれらの方法が役にたつと思います。 ソースコードへのリンクも載せましたので、ぜひコードを参考にしてみてください。

参考

その他の記事

Other Articles

2022/06/03
拡張子に Web アプリを関連付ける File Handling API の使い方

2022/03/22
<selectmenu> タグできる子; <select> に代わるカスタマイズ可能なドロップダウンリスト

2022/03/02
Java 15 のテキストブロックを横目に C# 11 の生文字列リテラルを眺めて ECMAScript String dedent プロポーザルを想う

2021/10/13
Angularによる開発をできるだけ型安全にするためのKabukuでの取り組み

2021/09/30
さようなら、Node.js

2021/09/30
Union 型を含むオブジェクト型を代入するときに遭遇しうるTypeScript型チェックの制限について

2021/09/16
[ECMAScript] Pipe operator 論争まとめ – F# か Hack か両方か

2021/07/05
TypeScript v4.3 の機能を使って immutable ライブラリの型付けを頑張る

2021/06/25
Denoでwasmを動かすだけの話

2021/05/18
DOMMatrix: 2D / 3D 変形(アフィン変換)の行列を扱う DOM API

2021/03/29
GoのWASMがライブラリではなくアプリケーションであること

2021/03/26
Pythonプロジェクトの共通のひな形を作る

2021/03/25
インラインスタイルと Tailwind CSS と Tailwind CSS 入力補助ライブラリと Tailwind CSS in JS

2021/03/23
Serverless NEGを使ってApp Engineにカスタムドメインをワイルドカードマッピング

2021/01/07
esbuild の機能が足りないならプラグインを自作すればいいじゃない

2020/08/26
TypeScriptで関数の部分型を理解しよう

2020/06/16
[Web フロントエンド] esbuild が爆速すぎて webpack / Rollup にはもう戻れない

2020/03/19
[Web フロントエンド] Elm に心折れ Mint に癒しを求める

2020/02/28
さようなら、TypeScript enum

2020/02/14
受付のLooking Glassに加えたひと工夫

2020/01/28
カブクエンジニア開発合宿に行ってきました 2020冬

2020/01/30
Renovateで依存ライブラリをリノベーションしよう 〜 Bitbucket編 〜

2019/12/27
Cloud Tasks でも deferred ライブラリが使いたい

2019/12/25
*, ::before, ::after { flex: none; }

2019/12/21
Top-level awaitとDual Package Hazard

2019/12/20
Three.jsからWebGLまで行きて帰りし物語

2019/12/18
Three.jsに入門+手を検出してAR.jsと組み合わせてみた

2019/12/04
WebXR AR Paint その2

2019/11/06
GraphQLの入門書を翻訳しました

2019/09/20
Kabuku Connect 即時見積機能のバックエンド開発

2019/08/14
Maker Faire Tokyo 2019でARゲームを出展しました

2019/07/25
夏休みだョ!WebAssembly Proposal全員集合!!

2019/07/08
鵜呑みにしないで! —— 書籍『クリーンアーキテクチャ』所感 ≪null 篇≫

2019/07/03
W3C Workshop on Web Games参加レポート

2019/06/28
TypeScriptでObject.assign()に正しい型をつける

2019/06/25
カブクエンジニア開発合宿に行ってきました 2019夏

2019/06/21
Hola! KubeCon Europe 2019の参加レポート

2019/06/19
Clean Resume きれいな環境できれいな履歴書を作成する

2019/05/20
[Web フロントエンド] 状態更新ロジックをフレームワークから独立させる

2019/04/16
C++のenable_shared_from_thisを使う

2019/04/12
OpenAPI 3 ファーストな Web アプリケーション開発(Python で API 編)

2019/04/08
WebGLでレイマーチングを使ったCSGを実現する

2019/03/29
その1 Jetson TX2でk3s(枯山水)を動かしてみた

2019/04/02
『エンジニア採用最前線』に感化されて2週間でエンジニア主導の求人票更新フローを構築した話

2019/03/27
任意のブラウザ上でJestで書いたテストを実行する

2019/02/08
TypeScript で “radian” と “degree” を間違えないようにする

2019/02/05
Python3でGoogle Cloud ML Engineをローカルで動作する方法

2019/01/18
SIGGRAPH Asia 2018 参加レポート

2019/01/08
お正月だョ!ECMAScript Proposal全員集合!!

2019/01/08
カブクエンジニア開発合宿に行ってきました 2018秋

2018/12/25
OpenAPI 3 ファーストな Web アプリケーション開発(環境編)

2018/12/23
いまMLKitカスタムモデル(TF Lite)は使えるのか

2018/12/21
[IoT] Docker on JetsonでMQTTを使ってCloud IoT Coreと通信する

2018/12/11
TypeScriptで実現する型安全な多言語対応(Angularを例に)

2018/12/05
GASでCompute Engineの時間に応じた自動停止/起動ツールを作成する 〜GASで簡単に好きなGoogle APIを叩く方法〜

2018/12/02
single quotes な Black を vendoring して packaging

2018/11/14
3次元データに2次元データの深層学習の技術(Inception V3, ResNet)を適用

2018/11/04
Node Knockout 2018 に参戦しました

2018/10/24
SIGGRAPH 2018参加レポート-後編(VR/AR)

2018/10/11
Angular 4アプリケーションをAngular 6に移行する

2018/10/05
SIGGRAPH 2018参加レポート-特別編(VR@50)

2018/10/03
Three.jsでVRしたい

2018/10/02
SIGGRAPH 2018参加レポート-前編

2018/09/27
ズーム可能なSVGを実装する方法の解説

2018/09/25
Kerasを用いた複数入力モデル精度向上のためのTips

2018/09/21
競技プログラミングの勉強会を開催している話

2018/09/19
Ladder Netwoksによる半教師あり学習

2018/08/10
「Maker Faire Tokyo 2018」に出展しました

2018/08/02
Kerasを用いた複数時系列データを1つの深層学習モデルで学習させる方法

2018/07/26
Apollo GraphQLでWebサービスを開発してわかったこと

2018/07/11
きたない requirements.txt から Pipenv への移行

2018/06/26
CSS Houdiniを味見する

2018/06/25
不確実性を考慮した時系列データ予測

2018/06/20
Google Colaboratory を自分のマシンで走らせる

2018/06/18
Go言語でWebAssembly

2018/06/15
カブクエンジニア開発合宿に行ってきました 2018春

2018/06/08
2018 年の tree shaking

2018/06/07
隠れマルコフモデル 入門

2018/05/30
DASKによる探索的データ分析(EDA)

2018/05/10
TensorFlowをソースからビルドする方法とその効果

2018/04/23
EGLとOpenGLを使用するコードのビルド方法〜libGLからlibOpenGLへ

2018/04/23
技術書典4にサークル参加してきました

2018/04/13
Python で Cura をバッチ実行するためには

2018/04/04
ARCoreで3Dプリント風エフェクトを実現する〜呪文による積層造形映像制作の舞台裏〜

2018/04/02
深層学習を用いた時系列データにおける異常検知

2018/04/01
音声ユーザーインターフェースを用いた新方式積層造形装置の提案

2018/03/31
Container builderでコンテナイメージをBuildしてSlackで結果を受け取る開発スタイルが捗る

2018/03/23
ngUpgrade を使って AngularJS から Angular に移行

2018/03/14
Three.jsのパフォーマンスTips

2018/02/14
C++17の新機能を試す〜その1「3次元版hypot」

2018/01/17
時系列データにおける異常検知

2018/01/11
異常検知の基礎

2018/01/09
three.ar.jsを使ったスマホAR入門

2017/12/17
Python OpenAPIライブラリ bravado-core の発展的な使い方

2017/12/15
WebAssembly(wat)を手書きする

2017/12/14
AngularJS を Angular に移行: ng-annotate 相当の機能を TypeScrpt ファイルに適用

2017/12/08
Android Thingsで4足ロボットを作る ~ Android ThingsとPCA9685でサーボ制御)

2017/12/06
Raspberry PIとDialogflow & Google Cloud Platformを利用した、3Dプリンターボット(仮)の開発 (概要編)

2017/11/20
カブクエンジニア開発合宿に行ってきました 2017秋

2017/10/19
Android Thingsを使って3Dプリント戦車を作ろう ① ハードウェア準備編

2017/10/13
第2回 魁!! GPUクラスタ on GKE ~PodからGPUを使う編~

2017/10/05
第1回 魁!! GPUクラスタ on GKE ~GPUクラスタ構築編~

2017/09/13
「Maker Faire Tokyo 2017」に出展しました。

2017/09/11
PyConJP2017に参加しました

2017/09/08
bravado-coreによるOpenAPIを利用したPythonアプリケーション開発

2017/08/23
OpenAPIのご紹介

2017/08/18
EuroPython2017で2名登壇しました。

2017/07/26
3DプリンターでLチカ

2017/07/03
Three.js r86で何が変わったのか

2017/06/21
3次元データへの深層学習の適用

2017/06/01
カブクエンジニア開発合宿に行ってきました 2017春

2017/05/08
Three.js r85で何が変わったのか

2017/04/10
GCPのGPUインスタンスでレンダリングを高速化

2017/02/07
Three.js r84で何が変わったのか

2017/01/27
Google App EngineのFlexible EnvironmentにTmpfsを導入する

2016/12/21
Three.js r83で何が変わったのか

2016/12/02
Three.jsでのクリッピング平面の利用

2016/11/08
Three.js r82で何が変わったのか

2016/12/17
SIGGRAPH 2016 レポート

2016/11/02
カブクエンジニア開発合宿に行ってきました 2016秋

2016/10/28
PyConJP2016 行きました

2016/10/17
EuroPython2016で登壇しました

2016/10/13
Angular 2.0.0ファイナルへのアップグレード

2016/10/04
Three.js r81で何が変わったのか

2016/09/14
カブクのエンジニアインターンシッププログラムについての詩

2016/09/05
カブクのエンジニアインターンとして3ヶ月でやった事 〜高橋知成の場合〜

2016/08/30
Three.js r80で何が変わったのか

2016/07/15
Three.js r79で何が変わったのか

2016/06/02
Vulkanを試してみた

2016/05/20
MakerGoの作り方

2016/05/08
TensorFlow on DockerでGPUを使えるようにする方法

2016/04/27
Blenderの3DデータをMinecraftに送りこむ

2016/04/20
Tensorflowを使ったDeep LearningにおけるGPU性能調査

→
←

関連職種

Recruit

→
←

お客様のご要望に「Kabuku」はお応えいたします。
ぜひお気軽にご相談ください。

お電話でも受け付けております
03-6380-2750
営業時間:09:30~18:00
※土日祝は除く