アプリとサービスのすすめ

アプリやIT系のサービスを中心に書いていきます。たまに副業やビジネス関係の情報なども気ままにつづります

自然言語処理タクスでよく使うAttentionの出力のAttention weightを可視化してみた【機械学習】

Attentionといえば、すでに自然言語処理モデルではなくてはならない存在。
カニズムは割愛。別名で注意機構とか呼ばれる。

Attentionの仕組みは、(個人的に理解してる範囲では)簡単に言うと以下のポイントがある。

・人間が特定のことに集中(注意)する仕組みと同じ

・Attentionの仕組みはAttention自体が特定の単語に注意(注目)する

・Attentionの挙動は人間の直感に近い

今回はそのAttentionが「どの単語を注意して見てるのか」わかるように、Attentionの出力結果Attention weightを可視化してみた。


こんな感じ
f:id:trafalbad:20190804133028j:plain


その過程を備忘録も兼ねてまとめてく。

目次
・今回の記事の概略
1.データ読み込み
2.モデル構築・訓練
3.Attention可視化用に訓練したモデルを再構築
4.Attention weightの可視化
・まとめ




今回の記事の概略

タスクとデータセットは、前回の日本語版BERTの記事で使った" livedoorニュースコーパス "を使ったトピック分類で、「Sports、トピックニュース」のトピックを分類するタスク。

BERTでのAttention可視化は

・BERTではマスク処理がある

・MaltiHeadAttentionを使ってる

等の理由で基本的にBERTでのAttentionの可視化はできないっぽいので、簡易モデルを作ってAttentionがどの単語に注意を払ってるのか可視化してみた。


AttentionにはMaltiHeadAttentionとか、いろいろ種類があるが、可視化にはselfAttentionが使われる。
f:id:trafalbad:20190804133614j:plain




selfAttentionを含めてAttentionの仕組みは下記サイトに詳しく載ってる。
qiita.com


幸い、kerasにpipでinstallできるselfAttentionがあるので、それを使ってAttentionの出力のAttention weightを可視化してみる。





1.データ読み込み

前回記事の日本語版BERTで使用した
・前処理済みテキストデータ

・idベクトル化してない日本語のテキストデータ(all_txet.npy)

を使う。

# 読み込み
train_x = np.load('train_xs.npy')
train_y = np.load('train_label.npy')
test_x = np.load('test_xs.npy')
test_y = np.load('test_label.npy')
# id化してない日本語の文章も読み込み
all_text = np.load('all_text.npy')

# one-hot表現
n_labels = len(np.unique(train_y))
train_y=np.eye(n_labels)[train_y] 
train_y = np.array(train_y)


2.モデル構築・訓練

BERTは上述の通り、マスク処理とMaltiHeadAttentionを使ってるので、Attentionの可視化はできなかった。

なのでAttention可視化用に、双方向LSTMを使った簡易モデルを作成して、学習した

h_dim=356
seq_len = 691
vocab_size = 23569+1

inp = Input(batch_shape = [None, seq_len])
emb = Embedding(vocab_size, 300)(inp) # (?,128,32)
att_layer = SeqSelfAttention(name='attention')(emb)  # embbedingレイヤーの後にselfattentionを配置
out = Bidirectional(LSTM(h_dim))(att_layer)
output = Dense(2, activation='softmax')(out)  # shape=(?, 2)
model = Model(inp, output)
model.compile(optimizer='Adam', loss='categorical_crossentropy', metrics=['acc', 'mse', 'mae'])
model.summary()
>>>
==================================================
input_1 (InputLayer) (None, 691) 0 _________________________________________________________________ embedding_1 (Embedding) (None, 691, 300) 7071000 _________________________________________________________________ attention (SeqSelfAttention) (None, 691, 300) 19265 _________________________________________________________________ bidirectional_1 (Bidirection (None, 712) 1871136 _________________________________________________________________ dense_1 (Dense) (None, 2) 1426 ======================================================



# train
model.fit(train_x, train_y, epochs=1, batch_size=10)

# 予測
predicts = model.predict(test_x, verbose=True).argmax(axis=-1)
print(np.sum(test_y == predicts) / test_y.shape[0])

正解率は、前処理とAttentionのおかげで、BERT並みの92%





3.Attention可視化用に訓練したモデルを再構築

訓練したモデルでAttention weight可視化用にモデルの再構築。

selfAttentionのレイヤーの出力

最後の出力層の出力

の両方をModelのoutputに追加

emodel = Model(inputs=model.input, outputs=[model.output, model.layers[2].output])
emodel.summary()

>>>
======================================================
 input_2 (InputLayer) (None, 691) 0 _________________________________________________________________ embedding_2 (Embedding) (None, 691, 300) 7071000 _________________________________________________________________ attention (SeqSelfAttention) (None, 691, 300) 19265 _________________________________________________________________ bidirectional_2 (Bidirection (None, 712) 1871136 _________________________________________________________________ dense_2 (Dense) (None, 2) 1426 ====================================================

精度も申し分ないので、後はどこの単語をAttentionが注目してるのかを可視化するだけ。




4.Attention weightの可視化

まずkeras のselfAttentionをこのサイトからインストール。

$ pip install keras-self-attention


Attention weightを可視化


どの単語に注目してるかの重みの総和を計算。


from keras_self_attention import SeqSelfAttention
import pandas as pd

# 予測後、Attentionの出力((batch, words, 300)=(batch, 691, 300))を取り出す
predict=emodel.predict(test_x)

token = all_text[700]

# 対象の文章(1batch)の中の175個のwords一つ一つから、3次元目(300dim)のmax値とる  =>shape=(1, 175, 1)
weight = [w.max() for w in predict[1][0][:175]]  # test_x[0][:176]

# pandasに入れてまず数値化。そのあとHTML形式にして、jupyter上で可視化
df = pd.DataFrame([token, weight], index=['token', 'weight'])
mean = np.array(weight).mean()
print(df.shape, mean)
df = df.T

df['rank'] = df['weight'].rank(ascending=False)

# 各wordsのmax値から全max値の平均を引き Attention  weightを計算。マイナスの値は0扱い
df['normalized'] = df['weight'].apply(lambda w: max(w - mean, 0))
df['weight'] = df['weight'].astype('float32')
df['attention'] = df['normalized'] > 0



【pandasの可視化結果】
f:id:trafalbad:20190804133443j:plain

ちなみにHTML形式でjupyter上で可視化するときは下のメソッドを使った。

from IPython.core.display import display, HTML
# HTMLで可視化
display(HTML(html))


【トピックを「Sports」と予測できたときのattention weight & その文章の可視化結果】
f:id:trafalbad:20190804133028j:plain




データセットが少なく、語彙数が少ないのもあるが、「ドラフト、日本ハム、指名、会議」とか、スポーツ(野球)に関連しそうなワードが赤いので、そこにAttentionが注目してるのがわかる。

噂通り、割と人間の直感に近い感じの語彙に注目してる。





Attention weight可視化で気づいたことメモ



①可視化するselfattentionレイヤーの出力はshapeは3次元でもいい


②selfattentionレイヤーはembbedingレイヤーの後に配置するのが定石っぽい


③BERTでのAttention可視化は無理(っぽい)
→マスク処理してる

→MaltiHeadAttentionを使ってる

→工夫次第ではできそう



④matplotlibで可視化もできる。
f:id:trafalbad:20190804133632j:plain


まとめ

はじめはAttentionを書籍で読んだり、調べたりしてもサッパリだった。

けど、実務で使って考えまくることで、仕組み・種類、使い方、なんで精度高くなるのとかかなり理解できた。

経験に勝る知識なしっていう格言のいい経験。



参考サイト
selfattentionを簡単に予測理由を可視化できる文書分類モデルを実装する

自然言語処理で使われるAttentionのWeightを可視化する