Seq2Seq with Attention 모델 구현
부스트캠프의 과제로 seq2seq with attention 모델을 구현하면서, 각 flow 별 주요 내용 기록
부스트캠프의 과제로 seq2seq with attention를 구현했습니다. 해당 과제를 진행하며 전체적인 흐름을 이해하는데 가장 중점을 두었습니다. 모델 알고리즘을 코드로 어떻게 구현하는지, 구현한 모델을 어떻게 학습/평가/시각화하는지를 확인했고, 각 과정에서 기억해둘 내용을 기록합니다.
개요
-
핵심 모델 구조
- encoder: bidirectional GRU
- decoder: unidirectional GRU
- attention: dot-product attention
-
코드 구성 흐름
- 데이터 로드
- 데이터 전처리
- 데이터 토큰화
- 어휘 사전 구축
- 어휘 사전을 활용한 데이터 토큰 ID 변환
- Seq2Seq with Attention 구현
- Encoder 구현
- Dot-product attention 구현
- Decoder 구현
- Seq2Seq 모델 구축
- 모델 학습
- 모델 weight 초기화
- Data loader 구현
- Train 함수 구현
- evaluate 함수 구현
- 전체 모델 학습, 평가, 시각화 (attention map)
0. 데이터 로드
-
datasets라이브러리 >bentrevett/multi30k데이터셋 사용- 영어, 독일어로 구성된 번역 데이터셋
- 기계번역 및 seq2seq 모델 학습에 주로 사용됨
1. 데이터 전처리
- 신경망이 문자열을 처리할 수 있도록 숫자로 변환하는 과정
- 문자열을 토큰으로 변환한 다음,
- 이들 토큰을 모아 어휘 사전을 만들고
- 이 어휘 사전을 look up table로 사용하여 각 시퀀스의 토큰들을 숫자로 변환
-
spacy라이브러리의 영어, 독일어 토큰화기 활용- 자연어 처리를 위한 오픈소스 라이브러리
- 효율적이고 빠른 텍스트 처리 기능 제공
- 토큰화, 형태소 분석, 품사 태깅, 의존 구문 분석 등 지원
- train, validation, test 각각에 대해 데이터 전처리 수행
1-1. 데이터 토큰화
- 각 시퀀스에 대해 토큰화 수행하는 함수 정의
-
spacy의tokenizer()사용해 각 시퀀스 대해 토큰화 수행. 최대 길이 지정해 길이 초과하면 trimming도 수행 -
lower = True면 각 토큰 소문자로 변환 (lower()) - 토큰 앞뒤로
sos_token,eos_token추가
-
- 데이터셋 전체에 대해 1의 함수 적용하는 함수 정의
1-2. 어휘 사전 구축
- source 및 target language에 대한 어휘 사전 구축
- 데이터셋의 각 unique token을 index(정수)로 연결시키는 데 사용됨
- 예: “hello” = 1, “world” = 2, “bye” = 3, “hates” = 4 등
-
torchtext에서 제공하는build_vocab_from_iterator()함수 사용 - 어휘 사전은 train set으로만 구축해야 함 (valid, test 안 됨)
- valid, test set도 이용하면 information leakage 발생해서 모델 성능 부풀려질 수 있음에 유의
- 만약 학습셋에 없는 토큰이 valid, test 데이터셋에서 등장했다면,
<unk>토큰으로 대체 (일반적으로 인덱스 0) - 모델이
<unk>토큰에 대해 주변 문맥을 사용해 번역하는 방법을 학습시키기 위해, 학습셋에도<unk>토큰을 포함시킴 - 이를 위해
build_vocab_from_iterator()로 어휘사전 구축 시min_freq인수를 사용해, trainset에서min_freq횟수보다 적게 나타나는 토큰은<unk>로 처리
-
build_vocab_from_iterator()의specials인수로 special token 전달- 어휘 사전에 추가하고 싶지만 토큰화된 예제에 반드시 나타나지는 않을 수 있는 토큰
-
<pad>: padding token- 모델에 문장 입력 시, 여러 문장을 한 번에 전달(“batch”)하는 것이 효율적임
- 다만 배치 처리를 위해서는 문장의 길이가 모두 같아야 한다는 조건 발생 (토큰 수 기준)
- 대부분의 문장 길이는 서로 다르므로, 패딩 토큰 사용해 이런 조건 처리
- 배치에서 가장 긴 문장과 동일한 길이 갖도록, 나머지 문장들에 패딩 토큰 추가
-
build_vocab_from_iterator()로 생성된 vocab 세부 내용 확인-
get_itos(): int to string. 토큰 확인 -
get_stoi(): string to int. 인덱스 확인 -
len(): 어휘 사전의 unique token 수 확인 - 어휘사전의 앞부분은 special token
- 그 이후는 가장 많이 등장한 순서부터 내림차순 정렬
-
1
2
3
4
5
6
7
8
en_vocab = torchtext.vocab.build_vocab_from_iterator(
train_data["en_tokens"],
min_freq=min_freq,
specials=special_tokens,
)
en_vocab.get_itos()[:10] # ['<unk>', '<pad>', '<sos>', '<eos>', 'a', '.', 'in', 'the', 'on', 'man']
en_vocab.get_stoi()["the"] # 7
len(en_vocab), len(de_vocab) # (5893, 7853)
1-3. 어휘 사전을 활용한 데이터 토큰 ID 변환
-
“numericalize”: 토큰을 인덱스로 변환하는 방법
-
lookup_indices: 토큰 리스트 -> 인덱스 리스트 -
lookup_tokens: 인덱스 리스트 -> 토큰 리스트
-
- 한편, feature를 올바른 데이터 타입으로 변환해줘야 함
- numericalize된 인덱스는 파이썬 기본 정수형임
- pytorch에서 사용하기 위해
with_format()사용해 pytorch 텐서로 변환
2. Seq2Seq with Attention 구현
2-1. Encoder 구현
- 배치 임베딩 & 드롭아웃
- $h_0$ 초기화
- GRU 통해 output, hidden 구하기
- 3의 결과값 적절히 처리해 필요한 최종 정보 return
- output: 각 시퀀스의 모든 time-step에 대한 인코딩 정보 포함
- hidden: 전체 시퀀스의 최종 인코딩 정보 통합
2-2. Dot-product attention 구현
- $H$의 각 행과 $s_t$의 내적으로 에너지 계산
- $s_t$: 현재 time-step의 hidden state
- $H$: 인코더의 모든 전방 & 후방 hidden state
- 1의 결과에 softmax 적용해 attention score 벡터 $a_t$ 생성
- attention value $\hat{a_t}$ 계산: $H$ 의 각 행에 가중치 곱해서 더하기
2-3. Decoder 구현
- 디코더의 현재 hidden state $s_t$ 연산
- 현재 디코더 hidden state의 attention value 연산
- 1과 2의 결과 concat해 최종 output 예측 (Linear)
- 디코더 output, hidden, attn_scores 리턴
2-4. Seq2Seq 모델 구축
- 인코더 적용 -> 인코더 output, hidden 얻기
- 디코더 적용 -> 디코더 output, hidden, attn_scores 얻기
- 이 때, 디코더에는 attention 반영해서 최종 결과 내도록 이미 2-3에서 처리된 상태
- 디코더의 각 time-step에서 최대 확률값 갖는 토큰 id 반환
- 이 때, teacher forcing 이용해 추론한 값 뿐만 아니라 ground truth도 사용될 수 있도록 처리함
Teacher Forcing
- seq2seq 모델 학습에서 exposure problem을 처리하기 위해 자주 사용되는 기술
-
exposure problem
- 디코더가 학습 중에 target sequence 초기에 부정확한 단어 생성할 수 있기 때문에 발생
- 이런 부정확한 단어는 이후 단계에 대한 디코더의 입력이 되어 모델을 잘못된 경로로 이끌 수 있음
-
teacher forcing
- seq2seq 모델 학습 시 decoder가 다음 state를 예측할 때, 이전 time step의 예측값이 아닌 실제 target sequence (ground truth)를 입력으로 사용하는 기법
- 학습 시에는 teacher forcing 사용하지만, 평가 시에는 사용하지 않음
- 학습 중 teacher forcing 비율을 조정하여 점진적으로 모델이 실제 사용 조건에 적응하도록 할 수 있음
- 장점: 학습을 빠르고 안정적으로 만듦
- 단점: 학습과 추론 단계의 차이로 인해 모델 성능과 안정성 낮아질 수 있음
3. 모델 학습
3-1. 모델 weight 초기화
- (거의) 영벡터로 초기화
3-2. Data loader 구현
- padding 반영해서 data loader 구현
-
nn.utils.rnn.pad_sequence사용 (이 함수 잘 알아두면 좋음. 확인해보기.)
-
- batch size 지정
- train, valid, test set 로드
- train set에서 source(출발어; 영어)와 target(도착어; 독일어) 각각의 최대 시퀀스 길이 확인
3-3. Train 함수 구현
- train에서는 teacher forcing 적절히 사용
- cross entropy loss 활용하되, Padding token은 학습 Loss에 관여하지 않도록 처리
1
criterion = nn.CrossEntropyLoss(ignore_index = corpus.pad_index)
3-4. evaluate 함수 구현
- teacher forcing 사용하면 안됨
3-5. 전체 모델 학습 진행
느낀점
-
코드 구현까지 할 수 있어야 제대로 이해한 것
- 이론으로만 이해했을 때와, 이를 코드로 구현하는 건 다른 느낌이었다.
- 이론으로 큰 그림을 그렸다면, 코드 통해 디테일을 잡아가는 느낌
- 지금까지는, 이론->코드 순서로 모델을 보는게 더 잘 이해되는 것 같다.
-
차원 맞추기가 어렵다
- 처음 과제 풀 때, 생각하는 논리 구조대로 우선 무작정 코드를 작성했더니 계속 차원이 달라서 계산이 불가능하다는 에러가 발생했다.
- 그냥 돌아가게 하는 목적으로 squeeze와 unsqueeze 활용해 겉모양은 맞출 수 있겠지만, ‘제대로’ 하려면 모델의 구조 제대로 이해해야 하고, 사용하는 코드들의 output 형상도 잘 이해해야겠다고 생각했다
- 과제였던 만큼, 각 과정에서 필요한 변수의 크기가 주석으로 세세하게 달려있어서 많은 도움을 받았다. 이 주석에 의지하지 않고도 차원 맞출 수 있을 정도로 잘 이해해보자!
-
pytorch에서 자주 사용되는 메소드의 구성을 살펴보고 싶어졌다
-
nn.Embedding,nn.Linear등, 자주 사용되는 메소드들에 대해서는 어떤 구조로 작동하는지 확인해보고싶어졌다. ‘다들 그렇게 하니까..’라는 식으로 넘기고 싶지 않음
-
-
attention의 영향?
- attention을 적용한 버전의 seq2seq를 구현했는데, attention이 없는 버전에 비해 얼마나 성능이 좋아진건지, 수치화해보고 싶어졌다. (수치화가 힘들다면 정성평가라도..)
- 기본 seq2seq 모델을 코드로 짜고, 성능 비교할 수 있는 적절한 metric 선정해서 성능 비교해보자!
- attention의 차이?
- Luong attention vs. Bahdanau attention
Reference
This post is licensed under CC BY 4.0 by the author.
Comments powered by Disqus.