Attentionといえば、すでに自然言語処理モデルではなくてはならない存在。
メカニズムは割愛。別名で注意機構とか呼ばれる。
Attentionの仕組みは、(個人的に理解してる範囲では)簡単に言うと以下のポイントがある。
・Attentionの仕組みはAttention自体が特定の単語に注意(注目)する
・Attentionの挙動は人間の直感に近い
今回はそのAttentionが「どの単語を注意して見てるのか」わかるように、Attentionの出力結果Attention weightを可視化してみた。
その過程を備忘録も兼ねてまとめてく。
目次
・今回の記事の概略
1.データ読み込み
2.モデル構築・訓練
3.Attention可視化用に訓練したモデルを再構築
4.Attention weightの可視化
・まとめ
今回の記事の概略
タスクとデータセットは、前回の日本語版BERTの記事で使った" livedoorニュースコーパス "を使ったトピック分類で、「Sports、トピックニュース」のトピックを分類するタスク。
BERTでのAttention可視化は
・MaltiHeadAttentionを使ってる
等の理由で基本的にBERTでのAttentionの可視化はできないっぽいので、簡易モデルを作ってAttentionがどの単語に注意を払ってるのか可視化してみた。
AttentionにはMaltiHeadAttentionとか、いろいろ種類があるが、可視化にはselfAttentionが使われる。
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可視化用にモデルの再構築。
・最後の出力層の出力
の両方を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
ちなみにHTML形式でjupyter上で可視化するときは下のメソッドを使った。
from IPython.core.display import display, HTML # HTMLで可視化 display(HTML(html))
データセットが少なく、語彙数が少ないのもあるが、「ドラフト、日本ハム、指名、会議」とか、スポーツ(野球)に関連しそうなワードが赤いので、そこにAttentionが注目してるのがわかる。
噂通り、割と人間の直感に近い感じの語彙に注目してる。
Attention weight可視化で気づいたことメモ
①可視化するselfattentionレイヤーの出力はshapeは3次元でもいい
②selfattentionレイヤーはembbedingレイヤーの後に配置するのが定石っぽい
③BERTでのAttention可視化は無理(っぽい)
→マスク処理してる
→MaltiHeadAttentionを使ってる
→工夫次第ではできそう
④matplotlibで可視化もできる。
まとめ
はじめはAttentionを書籍で読んだり、調べたりしてもサッパリだった。けど、実務で使って考えまくることで、仕組み・種類、使い方、なんで精度高くなるのとかかなり理解できた。
経験に勝る知識なしっていう格言のいい経験。