Search
Duplicate

5-3 CBOW 임베딩 학습하기

이번에는 범용 단어 임베딩 데이터셋을 구성하고 학습하는 모델인 Word2Vec CBOW(Continuous Bag-of-Words) 모델을 만들어보려고 합니다. CBOW는 단순하게 빈칸 채우기하는 것에 비유해 볼 수 있을 것 같습니다. 전체적인 과정을 설명드리자면 문장 속 ‘문맥 윈도’를 만들어 문맥 윈도의 중앙 단어를 제거한 후 다시 그 제거된 단어를 예측하며 학습을 하는 ‘다중 분류 작업’입니다. 누락된 단어가 무엇인지 얼마나 잘 파악해내는지가 CBOW 모델의 성능을 결정짓겠죠.

프랑켄슈타인 데이터셋을 이용한 전처리 과정

전처리 과정에서는 사용할 텍스트 데이터셋을 구축하고 그 데이터셋을 담을 파이토치 데이터셋 클래스를 만들고 마지막으로 데이터셋을 훈련, 검증, 테스트 세트로 분할해주는 단계를 거칩니다.

데이터셋 구축하기

예제에 사용될 데이터셋은 프로젝트 구텐베르크(Project Gutenberg)에서 배포하는 메리 셸리의 소설 『프랑켄슈타인』의 디지털 버전입니다. NLTK의 Punkt 토큰 분할기를 사용하여 텍스트를 개별 문장으로 분할하고 대문자는 소문자로 변환하고 구두점을 완전히 제거해줍니다. 여기서 소문자로 변환하는 이유는 NLTK가 case sensitive, 즉 대소문자를 구분하기 때문에 같은 스펠링의 단어라도 대문자로 시작하는 것과 소문자로 시작하는 것을 다르게 인식하기 때문입니다. 이 과정을 거친 후, 이제 공백으로 문자열을 분할하여 토큰 리스트를 추출해야합니다.
CBOW 모델은 데이터셋을 연속된 윈도(Window)들로 표현합니다. 위에 모이는 검은 사각형들이 각각의 연속된 원도이고 빨간 사각형들은 각 윈도의 중앙 단어입니다! 모델은 문장의 토큰 리스트를 사악 지나가면서 지정된 크기의 윈도로 단어들을 묶습니다. 위 예시에서 보이는 문맥 윈도는 길이가 양쪽으로 2(쉽게 말해서 단어 2개)인 문맥 윈도죠. 이 윈도가 텍스트 위를 슬라이딩하며 학습 샘플을 생성하여 CBOW 모델이 왼쪽 문맥과 오른쪽 문맥을 통해 타깃 단어, 즉 중앙의 단어를 예측할 수 있도록 해주는 겁니다.

파이토치 데이터셋 클래스 만들기

이렇게 만들어진 윈도 데이터셋과 타깃 단어를 판다스 데이터프레임으로 가져와주고 CBOWDataset 클래스를 통해 인덱싱해줍니다.
class CBOWDataset(Dataset): @classmethod def load_dataset_and_make_vetorizer(cls, cbow_csv) '''데이터셋을 가져오고 Vectorizer 만들기 매개변수: cbow_csv(str) 데이터셋의 위치 반환값: CBOWDataset의 instance''' cbow_df = pd.read_csv(cbow_csv) train_cbow_df = cbow_df[cbow_df.split == 'train'] return cls(cbow_df, CBOWVectorizer.from_dataframe(train_cbow_df)) def __getitem__(self, index): '''파이토치 데이터셋의 주요 진입 메서드 매개변수: index(int) 데이터 포인트의 인덱스 반환값: 특성(x_data)과 레이블(y_target)로 이루어진 딕셔너리''' row = self._target_df.iloc[index] context_vector = \ self._vectorizer.vectorize(row.context, self._max_seq_length) target_index = self._vectorizer.cbow_vocab.lookup_token(row.target) return {'x_data': context_vector, 'y_target': target_index}
Python
복사
여기서 사용된 __ getitem__() 메서드는 Vectorizer를 사용하여 윈도의 왼쪽 문맥과 오른쪽 문맥을 벡터로 변환해줍니다. 타깃 단어, 즉 중앙 단어는 Vocabulary를 사용하여 정수로 변환됩니다.

데이터셋을 train set, validation set, test set으로 나누어주기

구성된 데이터셋을 모두 학습에 사용하는 것은 아니고, 부분 부분 나누어서 훈련하는 데 사용할 데이터셋, 검증하는 데 사용할 데이터셋, 테스트할 때 사용할 데이터셋을 마련해줍니다.
Train set(훈련 세트)는 성능을 최적화하기 위한 파라미터 업데이트에 사용하고 validation set(검증 세트)는 학습한 것을 토대로 잘 훈련되었나 모델의 성능을 측정하고 가늠하기 위해 사용합니다. 이 두 세트는 모델 훈련 중에 사용되는 것이며, test set(테스트 세트)가 실전인 것이라 검증 세트와 테스트 세트를 헷갈려 하시면 안 됩니다~
이 글의 예제에서는 구성한 데이터셋의 70%를 훈련 세트, 15%를 검증 세트, 나머지 15%를 테스트 세트로 나눕니다.

Vocabulary, Vectorizer, DataLoader

이제 텍스트를 벡터의 미니배치로 변환해야하는데, 이때 Vocabulary 함수, Vectorizer 함수, DataLoader함수가 사용됩니다. CBOW 모델은 조금 특별히 Vocabulary 함수가 원-핫 벡터를 만들지 않고 대신 문맥의 인덱스를 나타내는 정수 벡터를 만들어 반환하는 작업을 합니다. 밑에 코드를 통해 Vectorizer함수를 구현해봅니다.
class CBOWVectorizer(object): '''어휘 사전을 생성하고 관리함''' def vectorize(self, context, vector_length = -1): '''매개변수: context(str) 공백으로 나누어진 단어 문자열 vector_length(int) 인덱스 벡터의 길이 매개변수''' indices = \ [self.cbow_vocab.lookup_token(token) for token in context.split(' ')] if vector_length < 0: vector_length = len(indices) out_vector = np.zeros(vector_length, dtype = np.int64) out_vector[:len(indices)] = indices out_vector[len(indices):] = self.cbow_vocab.mask_index return out_vector
Python
복사
이때 문맥의 토큰 수가 최대 길이보다 적으면, 남은 항목들은 0으로 채워지는데, 이 현상을 0으로 ‘패딩’되었다고 표현합니다.

CBOWClassifier 모델

class CBOWClassifier(nn.Module): def __init__(self, vocabulary_size, embedding_size, padding_idx = 0): '''매개변수: vocabulary_size(int) 어휘 사전의 크기, 임베딩 개수와 예측 벡터 크기를 결정함 embedding_size(int) 임베딩 크기 padding_idx(int) 기본값 0 (임베딩이 사용하지 않는 인덱스)''' super(CBOWClassifier, self).__init__() self.embedding = nn.embedding(num_embeddings = vocabulary_size, embedding_dim = embedding_size, padding_idx = padding_idx) self.fc1 = nn.Linear(in_features = embedding_size, out_features = vocabulary_size) def forward(self, x_in, apply_softmax = False): '''분류기의 정방향 계산 매개변수: x_in (torch.Tensor) 입력 데이터 텐서 x_in.shape는 (batch, input_dim)임 apply_softmax (bool) 소프트맥스 활성화 함수를 위한 플래그 크로스 엔트로피 손실을 사용하려면 False로 저장 반환값: 결과 텐서 tensor.shape는 (batch, output_dim)임''' x_embedded_sum = self.embedding(x_in).sum(dim = 1) y_out = self.fc1(x_embedded_sum) if apply_softmax: y_out = F.softmax(y_out, dim = 1) return y_out
Python
복사
이전 글 읽기