개발? 새발!
개발? 새발!

Transformer Visualization: 블랙박스 AI를 들여다보는 방법

2025.12.18

Transformer Visualization: 블랙박스 AI를 들여다보는 방법

안녕하세요 🙂

요즘 ChatGPT, Claude 같은 AI 모델들 쓰다 보면 이런 생각 한 번쯤 들지 않나요?
“얘네는 대체 어떻게 이렇게 그럴듯하게 대답하는 거지?”

저도 딱 그랬어요. 그래서 Transformer 모델 내부를 “어떻게든” 들여다볼 방법들을 이것저것 찾아봤고요. 오늘은 이 블랙박스 같은 모델을 시각화로 이해하는 방법들을 정리해서 공유해볼게요!


왜 Transformer Visualization이 필요할까?

Transformer 모델이 대단하다는 건 알겠는데, 내부에서 실제로 무슨 일이 벌어지는지 모르면 괜히 답답하잖아요.

특히 모델이 이상한 답변을 내놓을 때, “왜 이런 결과가 나온 거지?” 싶은 순간들 꼭 생기죠. Transformer Visualization을 활용하면 이런 것들을 확인할 수 있어요:

- Attention이 문장의 어디에 집중하는지
- 각 레이어에서 정보가 어떻게 변형되는지
- 모델이 특정 단어를 예측할 때 어떤 토큰들을 참고하는지

디버깅할 때도 꽤 유용하고, 모델 성능 개선 포인트를 찾는 데도 힌트를 많이 얻을 수 있더라고요.

관련 이미지 1

이미지 출처: 틸노트

Attention Visualization - 가장 기본적인 방법

Transformer의 핵심은 역시 Attention 메커니즘이죠.

이걸 시각화하면 모델이 입력 문장의 어떤 부분을 주로 바라보는지 한눈에 볼 수 있어요. 보통 히트맵 형태로 표현하는데, 생각보다 구현도 간단합니다.

import torch
import matplotlib.pyplot as plt
import seaborn as sns

def visualize_attention(attention_weights, tokens):
    """
    Attention weights를 히트맵으로 시각화
    """
    fig, ax = plt.subplots(figsize=(10, 8))
    
    sns.heatmap(
        attention_weights.detach().cpu().numpy(),
        xticklabels=tokens,
        yticklabels=tokens,
        cmap='viridis',
        ax=ax
    )
    
    plt.xlabel('Key Tokens')
    plt.ylabel('Query Tokens')
    plt.title('Attention Weights Heatmap')
    plt.tight_layout()
    plt.show()


실제 사용 예시

tokens = ['The', 'cat', 'sat', 'on', 'the', 'mat']
attention = model.get_attention_weights(input_ids)
visualize_attention(attention[0][0], tokens)  # 첫 번째 레이어, 첫 번째 헤드

이렇게 보면 각 단어가 다른 단어들과 얼마나 연결되어 있는지 색으로 표현돼요. 진한 색일수록 더 많이 참고한다는 뜻입니다!


BertViz로 Multi-Head Attention 파헤치기

그런데 Transformer는 Multi-Head Attention을 쓰잖아요? 여러 개의 헤드가 동시에 작동하다 보니, 이걸 전부 일일이 시각화하려고 하면 코드가 금방 복잡해져요.

그래서 많이들 쓰는 게 BertViz라는 라이브러리예요. 한 번 써보면 “왜 다들 추천하는지” 바로 느낌 옵니다.

from bertviz import head_view, model_view
from transformers import AutoTokenizer, AutoModel


모델과 토크나이저 로드

model_name = 'bert-base-uncased'
model = AutoModel.from_pretrained(model_name, output_attentions=True)
tokenizer = AutoTokenizer.from_pretrained(model_name)


입력 텍스트

text = "The transformer visualization helps understand model behavior"
inputs = tokenizer.encode(text, return_tensors='pt')


Attention 가져오기

outputs = model(inputs)
attention = outputs.attentions


Head View로 시각화

tokens = tokenizer.convert_ids_to_tokens(inputs[0])
head_view(attention, tokens)

Head View를 쓰면 각 헤드가 어떤 패턴을 학습했는지 인터랙티브하게 확인할 수 있어요.

실제로 보면 어떤 헤드는 구문 구조 쪽을 잘 보고, 어떤 헤드는 의미적 관계를 더 강하게 잡는 식으로 역할이 갈리는 경우도 있더라고요. 꽤 신기합니다.

관련 이미지 2

이미지 출처: 콥스랩 기술 블로그 - 티스토리

Layer별로 보는 Model View

BertViz의 Model View를 쓰면 한 단계 더 재밌는 걸 볼 수 있어요.

전체 레이어를 한 번에 훑으면서, attention이 레이어를 따라 어떻게 흘러가는지 추적할 수 있거든요.

from bertviz import model_view


전체 레이어의 attention 시각화

model_view(attention, tokens)

직접 돌려보면 느껴지는데, 초반 레이어에서는 주로 인접한 단어들끼리 attention이 강하고, 레이어가 깊어질수록 멀리 떨어진 단어들 사이의 관계도 점점 더 잘 잡아내는 경향이 있더라고요.

레이어가 깊어질수록 추상화 수준이 높아지는 느낌이랄까요?


Attention Flow로 경로 추적하기

다만 여기서 아쉬운 점이 하나 있어요. 특정 토큰에 대한 attention이 레이어를 거치면서 “어떻게 변하는지”를 한눈에 보기에는 좀 어렵다는 거죠.

그래서 나온 게 Attention Flow라는 개념이에요. 말 그대로 attention의 흐름을 시각화해보는 방식입니다.

def visualize_attention_flow(attentions, token_idx, tokens):
    """
    특정 토큰에 대한 attention flow 시각화
    """
    num_layers = len(attentions)
    num_heads = attentions[0].shape[1]
    
    fig, axes = plt.subplots(num_layers, 1, figsize=(12, 3*num_layers))
    
    for layer_idx, attention in enumerate(attentions):
        # 모든 헤드의 평균
        avg_attention = attention[0].mean(dim=0)[token_idx].detach().cpu().numpy()
        
        axes[layer_idx].bar(range(len(tokens)), avg_attention)
        axes[layer_idx].set_xticks(range(len(tokens)))
        axes[layer_idx].set_xticklabels(tokens, rotation=45)
        axes[layer_idx].set_title(f'Layer {layer_idx + 1}')
        axes[layer_idx].set_ylabel('Attention Weight')
    
    plt.tight_layout()
    plt.show()


특정 단어(예: 'transformer')에 대한 flow 보기

target_idx = tokens.index('transformer')
visualize_attention_flow(attention, target_idx, tokens)

이렇게 하면 `'transformer'`라는 단어가 각 레이어에서 어떤 단어들에 주목하는지 레이어별로 쭉 추적할 수 있어요.

관련 이미지 3

이미지 출처: 파이토치 한국 사용자 모임

Neuron Activation으로 더 깊이 파고들기

Attention만 봐서는 뭔가 아쉬울 때가 있죠. Transformer Visualization에서 더 깊게 들어가려면 뉴런 활성화를 보는 방법도 있습니다.

FFN(Feed-Forward Network) 레이어의 뉴런들이 어떤 패턴에 반응하는지 확인하면, 모델이 실제로 무엇을 학습했는지에 대한 힌트를 얻을 수 있어요.

import torch.nn.functional as F

def analyze_neuron_activation(model, input_text, layer_idx=6):
    """
    특정 레이어의 뉴런 활성화 분석
    """
    # Hook을 사용해서 중간 레이어 출력 캡처
    activations = {}
    
    def hook_fn(module, input, output):
        activations['ffn'] = output
    
    # Hook 등록
    hook = model.encoder.layer[layer_idx].intermediate.register_forward_hook(hook_fn)
    
    # Forward pass
    inputs = tokenizer(input_text, return_tensors='pt')
    outputs = model(**inputs)
    
    # Hook 제거
    hook.remove()
    
    # 활성화가 높은 뉴런 찾기
    ffn_output = activations['ffn'][0]  # [seq_len, hidden_size]
    top_neurons = ffn_output.max(dim=0).values.argsort(descending=True)[:10]
    
    return top_neurons, ffn_output


실제 분석

top_neurons, activations = analyze_neuron_activation(
    model, 
    "The transformer model uses self-attention mechanism"
)

print(f"가장 활성화된 뉴런들: {top_neurons}")

이걸 여러 문장으로 돌려보면 은근히 재밌는 패턴이 보이기도 해요.

어떤 뉴런은 부정문에서만 유난히 반응한다든지, 어떤 뉴런은 특정 품사에서만 활성화가 튄다든지요.


실전 팁: Transformer Visualization 제대로 활용하기

개념은 알겠는데, “그래서 실무에서 어떻게 쓰는데?” 싶을 수 있죠.

제가 프로젝트 하면서 실제로 유용했던 활용 방법들을 정리해보면 이렇습니다:

1. 모델 디버깅할 때
- Attention 패턴이 전체적으로 이상하게 나오면 데이터 문제일 가능성이 큼
- 특정 레이어에서만 문제가 생기면 그 레이어를 중심으로 튜닝

2. 성능 개선할 때
- Head 중복도를 체크해서 프루닝(pruning) 대상 찾기
- 중요한 attention 패턴을 발견하면 데이터 증강 아이디어로도 연결 가능

3. 설명 가능한 AI 만들 때
- Attention weights를 사용자에게 보여주면 신뢰도 상승
- “이 부분을 중요하게 봤어요” 같은 설명을 붙이기 쉬움

관련 이미지 4

이미지 출처: IT 트렌드

주의할 점들

Transformer Visualization 할 때 조심해야 할 것도 몇 가지 있어요.

첫째, Attention이 높다고 해서 그게 곧바로 “중요하다”는 뜻은 아니에요. 최근 연구들 보면 attention과 실제 모델 결정 사이의 관계가 생각보다 약하다는 결과도 있더라고요.

둘째, 시각화 결과를 과신하면 안 됩니다. 사람이 보기엔 예쁘고 그럴듯한 패턴이어도, 모델 입장에선 의미가 다를 수 있어요.

셋째, 메모리도 꼭 조심해야 해요. 큰 모델의 모든 레이어를 한꺼번에 시각화하려고 하면… 컴퓨터가 버티기 힘들 수도 있습니다.

# 메모리 절약 팁
with torch.no_grad():  # gradient 계산 안 함
    outputs = model(inputs, output_attentions=True)
    attention = outputs.attentions
    

필요한 레이어만 시각화

selected_layers = [0, 5, 11]  # 첫번째, 중간, 마지막 레이어만
for idx in selected_layers:
    visualize_attention(attention[idx][0], tokens)

더 나아가기: 커스텀 Visualization 만들기

기존 툴도 충분히 좋지만, 프로젝트 특성에 맞춘 시각화를 직접 만들면 더 유용할 때가 많아요.

예를 들어 저는 이런 방식도 한 번 만들어 봤습니다:

def custom_attention_viz(attention_weights, tokens, threshold=0.1):
    """
    threshold 이상의 attention만 그래프로 표현
    """
    import networkx as nx
    
    G = nx.DiGraph()
    
    # 노드 추가
    for token in tokens:
        G.add_node(token)
    
    # threshold 이상의 edge만 추가
    for i, query_token in enumerate(tokens):
        for j, key_token in enumerate(tokens):
            weight = attention_weights[i][j].item()
            if weight > threshold and i != j:
                G.add_edge(query_token, key_token, weight=weight)
    
    # 그래프 그리기
    plt.figure(figsize=(12, 8))
    pos = nx.spring_layout(G)
    
    # edge 두께를 attention weight에 비례하게
    edges = G.edges()
    weights = [G[u][v]['weight'] * 5 for u, v in edges]
    
    nx.draw(G, pos, with_labels=True, node_color='lightblue',
            node_size=1000, font_size=10, font_weight='bold',
            width=weights, edge_color='gray', arrows=True)
    
    plt.title('Attention Network Graph')
    plt.show()

이렇게 하면 attention을 네트워크 그래프로 볼 수 있어서, 토큰 간 관계를 좀 더 직관적으로 파악할 수 있어요.

관련 이미지 5

이미지 출처: velog


오늘은 Transformer Visualization에 대해 정리해봤습니다.

처음엔 복잡해 보이는데, 막상 하나씩 해보면 생각보다 재미있어요. 모델이 “어떻게 생각하는지”를 살짝 엿보는 느낌이랄까요?

여러분도 본인 모델 한 번 꼭 들여다보세요. 예상 못한 패턴이 나오면 진짜 흥미롭습니다.

혹시 Transformer Visualization 하다가 재밌는 거 발견하면 공유도 부탁드려요. 저도 궁금하거든요 🙂

참고 자료
- BertViz GitHub: https://github.com/jessevig/bertviz
- Attention is not Explanation 논문
- Transformer 공식 논문 "Attention is All You Need"


이전 글 보기

SpecKit vs Beads: 프론트엔드 개발 도구 비교, 어떤 걸 써야 할까?

클로드 코드 Opus 4.5, 이제 코딩 파트너로 써먹을 때가 왔다

이 블로그의 다음 이야기도 받아보세요

새 글 구독 (RSS)