⬅️ RNN 시뮬레이터로 돌아가기

💡 텐서 차원(Shape) 변환 원리와 축(dim)의 실무적 이해

💡 자연어 처리(NLP: Natural Language Processing)의 차원 전제 :
일반적으로 자연어 처리 분야의 딥러닝 모델에서는 문장 데이터를 임베딩하여 주입할 때, 무조건 3차원 텐서 규격 [Batch, Seq, Feature] (단어장 점수의 경우 [Batch, Seq, Vocab])으로 입력을 처리하도록 규격화되어있다.
📊 3차원 텐서 [Batch, Seq, Vocab]의 축(dim) 방향 시각화
dim=0 ⬇️ 배치 (Batch) 축 dim=1 ➡️ 시퀀스 (Sequence/Time) 축 dim=2 (dim=-1) ↗️ 단어장 (Vocab) 축 💡 핵심 용어 정의 • Batch (배치) : 동시에 처리할 문장 개수 (예: 32) • Seq (시퀀스) : 문장 내 단어(토큰) 개수 (예: 10) • Vocab (단어장) : 어휘 사전 속 총 단어 개수 (예: 30,000) • 처리 방식 : 단어 단위 시퀀스로 쪼개어 입력하고 모든 Vocab과의 관계(연관성)를 연산
📢 일반적으로 자연어 처리(NLP)에서는 위와 같이 입력 규격을 3차원 텐서 형태로 정의하여 연산하지만, 실제 모델의 내부 구동 과정에서는 이 차원 규격을 그대로만 유지하는 것이 아니라 필요에 따라 차원을 인위적으로 줄이거나(Squeeze) 늘리는(Unsqueeze) 조작을 끊임없이 동반하며 활용함. 차원을 줄이고 늘리며 사용하는 구체적인 대표 사례는 다음과 같음.
🤔 텐서 차원(Shape)을 줄이고 늘리는 근본적인 두 가지 목적
📋 원본 입력 데이터 정의 및 PyTorch 선언
Shape: [2, 3, 4] = [문장수, 단어수, vocab 수]
# PyTorch 원본 3차원 텐서 선언 (Shape: [2, 3, 4] = [문장수, 단어수, vocab 수])
tensor = torch.tensor([
  # 배치 0 (첫 번째 문장의 3개 단어별 4개 단어장 점수판)
  [[ 4.0,  1.0,  3.0,  2.0],
   [ 5.0,  8.0,  6.0,  7.0],
   [ 9.0, 10.0, 12.0, 11.0]],
  
  # 배치 1 (두 번째 문장의 3개 단어별 4개 단어장 점수판)
  [[ 3.0,  2.0,  5.0,  4.0],
   [ 6.0,  7.0,  9.0,  8.0],
   [13.0, 11.0, 10.0, 12.0]]
], dtype=torch.float32)

print("원본 텐서 크기:", tensor.shape)  # 출력: torch.Size([2, 3, 4])
Tensor [2, 3, 4] = 모든 24개 값 (전체 압축)
입력 Shape
[2, 3, 4]
출력 Shape
[] (스칼라)
크기
0차원
🔢 수학 연산식 및 결과 텐서
result = t.mean() # 전체 평균
✏️ 샘플 수식 계산 예시 :
(전체 24개 데이터 총합 168) / 24 = 7.0
📊 출력 텐서 (Tensor Output) :
tensor(7.0)
🎓 현실의 의미
배치 전체, 시퀀스 전체의 모든 오차값들을 한 덩어리로 융합하여 평균을 구한 결과물.
실제 활용 : 학습 역전파(Backpropagation) 직전에 모델의 전체 오차 대표값 딱 1개를 계산하여 모델 최적화 지표로 활용함.
직관적 이해 : 2개 문장(Batch=2) 내의 3개 단어(Seq=3)들이 가진 4개 단어장(Vocab=4) 값들(총 24개)을 축 구분 없이 싹 평균 내어, 전체 데이터의 종합 평균 딱 1개만 남겨 압축하는 개념.
핵심 용도 : 학습을 위한 최종 오차 전파
📥 단일 문장(Batch=1) 인코더 내 텐서 차원 변화
인코더(Encoder)의 주 역할은 전체 입력 문장을 통째로 읽어 단 하나의 고정된 크기의 벡터(**문맥 벡터, Context Vector**)로 압축하는 것입니다. 입력된 1차원 토큰 ID 시퀀스는 임베딩과 RNN을 거치면서 차원이 순차적으로 확장되며 문장의 의미 정보를 축적합니다.
input_ids [seq_len] (1차원 텐서) Embedding input_embeds [seq_len, 768] (2차원 텐서) nn.RNN hidden_states [seq_len, 1024] (단어별 은닉 벡터 → Attention용) context_vector (h_n) [1, 1024] (문장 최종 문맥 벡터 → Decoder h_0 주입!)
💡 인코더 차원 변화 해설
입력 ID 시퀀스 : `"나는 학교에 간다."`의 5개 토큰에 대응하는 1차원 텐서 [5]이 인코더에 들어옵니다.
Embedding : 토큰을 고차원 실수의 벡터 표현으로 변경해 2차원 [5, 768]로 확장합니다.
nn.RNN : 시퀀스를 따라 순차적으로 정보들을 누적 연산하며 최종적으로 모든 단어의 은닉 상태를 묶은 hidden_states [5, 1024]와 마지막 시퀀스 단계의 최종 요약 상태인 문맥 벡터 context_vector (h_n) [1, 1024]로 출력 분기합니다.
📥 단일 문장(Batch=1) 디코더 내 텐서 차원 변화
디코더(Decoder)의 RNN 루프는 인코더의 최종 요약 은닉 상태인 문맥 벡터(`encoder h_n`)를 초기 기억(`h_0`)으로 주입받아 동작합니다. 매 시점 예측된 `output_token`은 단어장 공간의 점수(`logit`)를 거치고 `argmax`로 디코딩된 후, 다음 시점의 입력으로 순환 피드백됩니다.
input_token [1] (1차원 텐서) Embedding embedded [1, 768] (2차원 텐서) encoder h_n [1, 1024] nn.RNN new h_n [1, 1024] (2차원 텐서) Linear logit [1, 30000] (2차원 텐서) .squeeze() logit [30000] (1차원 텐서) .argmax() Token ID 스칼라 (0차원) .item() 추출 최종 텍스트 생성용 리스트 저장 .unsqueeze(0) output_token [1] (1차원 텐서) 다음 스텝의 input_token으로 피드백 (대입)
💡 디코더 루프 차원 변화 해설
Squeeze (.squeeze()) : 배치=1 이기 때문에 [1, 30000] 크기의 logit 텐서에서 굳이 필요 없는 크기 1짜리 배치 축을 압축하여 순수 단어 가중치인 1차원 [30000]으로 바꿉니다.
Argmax (.argmax()) : 1차원 단어장 배열에서 가장 큰 값(가장 확률이 높은 단어)의 인덱스인 0차원의 단일 스칼라(정수)로 최종 단어를 찾아냅니다.
Unsqueeze (.unsqueeze(0)) : 다음 루프에서 임베딩 레이어로 넘기기 전, PyTorch 모듈의 규격에 맞추어 0차원 스칼라를 다시 대괄호가 씌워진 1차원 텐서 [1]로 포장합니다.