STEP 18 / 18

학습된 모델 들여다보기: 어텐션 시각화

1단계에서 attention이 가중치 p_attn을 돌려주게 했고, 3단계에서 MultiHeadedAttention이 그것을 self.attn에 저장해 두게 했다. 그 가중치를 꺼내 그림으로 그린다.

  • get_encoder/get_decoder_self/get_decoder_src: 층 번호를 받아 그 층의 attn(마지막 forward 때의 가중치)을 꺼낸다.
  • mtx2df, attn_map: 가중치 행렬을 DataFrame으로 바꿔 altair 히트맵으로 그린다. 행은 query 토큰, 열은 key 토큰이다.
  • visualize_layer: 한 층의 head 8개 중 짝수 번째 4개(0, 2, 4, 6)를 가로로 붙인다.

차트 위에 마우스를 올리면 칸마다 토큰과 값이 보인다. 차트는 드래그·휠로 움직일 수 있다.

원본은 디코더 셀프 어텐션 그림의 행·열 이름에도 원문(독일어) 토큰을 붙인다. 원본 그대로 두었으므로 그 그림의 축 이름은 실제 토큰과 맞지 않을 수 있다.

실행 결과에서 볼 것

  • 인코더 셀프 어텐션(1, 3, 5층): 얕은 층에서는 자기 자신이나 바로 옆 토큰을 보는 head가 많다.
  • 디코더 소스 어텐션: 영어 단어를 만들 때 대응하는 독일어 단어 쪽이 밝다. Sportkleidung ↔ athletic clothes처럼 번역 대응이 보인다.

코드 변화

+87 −0 · 파일 전체 1031줄

이전 단계의 transformer.py와 비교했다. 초록 줄이 새로 생긴 줄, 빨간 줄이 사라진 줄이다.

GitHub에서 보기 ↗
55import time
66from collections import Counter
77
8+import altair as alt
9+import pandas as pd
810import spacy
911import torch
1012import torch.nn as nn
942944 print("Model Output : " + model_txt.replace("\n", ""))
943945 results[idx] = (rb, src_tokens, tgt_tokens, model_out, model_txt)
944946 return results
947+
948+
949+def mtx2df(m, max_row, max_col, row_tokens, col_tokens):
950+ "convert a dense matrix to a data frame with row and column indices"
951+ return pd.DataFrame(
952+ [
953+ (
954+ r,
955+ c,
956+ float(m[r, c]),
957+ "%.3d %s"
958+ % (r, row_tokens[r] if len(row_tokens) > r else "<blank>"),
959+ "%.3d %s"
960+ % (c, col_tokens[c] if len(col_tokens) > c else "<blank>"),
961+ )
962+ for r in range(m.shape[0])
963+ for c in range(m.shape[1])
964+ if r < max_row and c < max_col
965+ ],
966+ # if float(m[r,c]) != 0 and r < max_row and c < max_col],
967+ columns=["row", "column", "value", "row_token", "col_token"],
968+ )
969+
970+
971+def attn_map(attn, layer, head, row_tokens, col_tokens, max_dim=30):
972+ df = mtx2df(
973+ attn[0, head].data,
974+ max_dim,
975+ max_dim,
976+ row_tokens,
977+ col_tokens,
978+ )
979+ return (
980+ alt.Chart(data=df)
981+ .mark_rect()
982+ .encode(
983+ x=alt.X("col_token", axis=alt.Axis(title="")),
984+ y=alt.Y("row_token", axis=alt.Axis(title="")),
985+ color="value",
986+ tooltip=["row", "column", "value", "row_token", "col_token"],
987+ )
988+ .properties(height=400, width=400)
989+ .interactive()
990+ )
991+
992+
993+def get_encoder(model, layer):
994+ return model.encoder.layers[layer].self_attn.attn
995+
996+
997+def get_decoder_self(model, layer):
998+ return model.decoder.layers[layer].self_attn.attn
999+
1000+
1001+def get_decoder_src(model, layer):
1002+ return model.decoder.layers[layer].src_attn.attn
1003+
1004+
1005+def visualize_layer(model, layer, getter_fn, ntokens, row_tokens, col_tokens):
1006+ # ntokens = last_example[0].ntokens
1007+ attn = getter_fn(model, layer)
1008+ n_heads = attn.shape[1]
1009+ charts = [
1010+ attn_map(
1011+ attn,
1012+ 0,
1013+ h,
1014+ row_tokens=row_tokens,
1015+ col_tokens=col_tokens,
1016+ max_dim=ntokens,
1017+ )
1018+ for h in range(n_heads)
1019+ ]
1020+ assert n_heads == 8
1021+ return alt.vconcat(
1022+ charts[0]
1023+ # | charts[1]
1024+ | charts[2]
1025+ # | charts[3]
1026+ | charts[4]
1027+ # | charts[5]
1028+ | charts[6]
1029+ # | charts[7]
1030+ # layer + 1 due to 0-indexing
1031+ ).properties(title="Layer %d" % (layer + 1))

실행 결과

이 단계는 CUDA GPU로 8 epoch 학습한 모델이 필요해서 빌드 때 다시 실행하지 않는다. output.txt와 charts/는 annotated-transformer-modern의 노트북을 RTX 3070 한 장에서 처음부터 끝까지 실행한 기록이다(tools/extract_recorded.py로 추출).

실행한 코드 demo.py 보기
1# 17단계에서 학습한 multi30k_model_final.pt가 있어야 한다. 웹 페이지의 출력은 RTX 3070에서 실행한 기록이다.
2import altair as alt
3import torch
4
5from show import show
6from transformer import (
7 check_outputs, create_dataloaders, get_decoder_self, get_decoder_src, get_encoder,
8 load_tokenizers, load_vocab, make_model, visualize_layer,
9)
10
11spacy_de, spacy_en = load_tokenizers()
12vocab_src, vocab_tgt = load_vocab(spacy_de, spacy_en)
13
14
15def run_model_example(n_examples=5):
16 _, valid_dataloader = create_dataloaders(
17 torch.device("cpu"), vocab_src, vocab_tgt, spacy_de, spacy_en,
18 batch_size=1, is_distributed=False,
19 )
20 model = make_model(len(vocab_src), len(vocab_tgt), N=6)
21 model.load_state_dict(
22 torch.load("multi30k_model_final.pt", map_location=torch.device("cpu"))
23 )
24 example_data = check_outputs(
25 valid_dataloader, model, vocab_src, vocab_tgt, n_examples=n_examples
26 )
27 return model, example_data
28
29
30# 원본 노트북의 viz_encoder_self / viz_decoder_self / viz_decoder_src
31def viz_encoder_self():
32 model, example_data = run_model_example(n_examples=1)
33 example = example_data[
34 len(example_data) - 1
35 ] # batch object for the final example
36
37 layer_viz = [
38 visualize_layer(
39 model, layer, get_encoder, len(example[1]), example[1], example[1]
40 )
41 for layer in range(6)
42 ]
43 return alt.hconcat(
44 layer_viz[0]
45 # & layer_viz[1]
46 & layer_viz[2]
47 # & layer_viz[3]
48 & layer_viz[4]
49 # & layer_viz[5]
50 )
51
52
53def viz_decoder_self():
54 model, example_data = run_model_example(n_examples=1)
55 example = example_data[len(example_data) - 1]
56
57 layer_viz = [
58 visualize_layer(
59 model,
60 layer,
61 get_decoder_self,
62 len(example[1]),
63 example[1],
64 example[1],
65 )
66 for layer in range(6)
67 ]
68 return alt.hconcat(
69 layer_viz[0]
70 & layer_viz[1]
71 & layer_viz[2]
72 & layer_viz[3]
73 & layer_viz[4]
74 & layer_viz[5]
75 )
76
77
78def viz_decoder_src():
79 model, example_data = run_model_example(n_examples=1)
80 example = example_data[len(example_data) - 1]
81
82 layer_viz = [
83 visualize_layer(
84 model,
85 layer,
86 get_decoder_src,
87 max(len(example[1]), len(example[2])),
88 example[1],
89 example[2],
90 )
91 for layer in range(6)
92 ]
93 return alt.hconcat(
94 layer_viz[0]
95 & layer_viz[1]
96 & layer_viz[2]
97 & layer_viz[3]
98 & layer_viz[4]
99 & layer_viz[5]
100 )
101
102
103show(viz_encoder_self(), "encoder_self")
104show(viz_decoder_self(), "decoder_self")
105show(viz_decoder_src(), "decoder_src")
── 인코더 셀프 어텐션 ──

Source Text (Input)        : <s> Ein Mann balanciert einen <unk> auf seinem Arm . </s>
Target Text (Ground Truth) : <s> A man is balancing a metal ball on his arm . </s>
Model Output               : <s> A man is balancing a <unk> on his arm . </s>
차트 불러오는 중…
── 디코더 셀프 어텐션 ──

Source Text (Input)        : <s> Eine Frau in gestreiften Strumpfhosen wird von Schnüren gelenkt . </s>
Target Text (Ground Truth) : <s> A woman in striped tights is being <unk> with strings . </s>
Model Output               : <s> A woman in a striped dress is being <unk> by <unk> . </s>
차트 불러오는 중…
── 디코더 소스 어텐션 ──

Source Text (Input)        : <s> Eine Frau in <unk> Sportkleidung fährt Rollschuh . </s>
Target Text (Ground Truth) : <s> A woman is rollerblading in a skimpy uniform . </s>
Model Output               : <s> A woman in athletic clothes is rollerskating . </s>
차트 불러오는 중…