Data/자연어처리

[Pytorch] 임베딩 (2) / GloVe임베딩 + CNN 을 활용한 뉴스 카테고리 분류기 구현

빛날희- 2021. 7. 29. 23:57

파이토치로 배우는 자연어처리 책을 참고하여 작성했습니다. 


 

 

이전 포스팅에선 임베딩의 개념에 대해서 정리해보았다. 이번 포스팅에선 사전 훈련된 임베딩 GloVe와 CNN모델을 활용하여 뉴스 카테고리를 분류하는 모델을 구축하는 코드를 리뷰해보고자 한다. 

 

코드리뷰는 참고한 책에서 추가 보충 설명을 달아놓고 이해를 높이기 위함이므로 전체 코드를 보고 싶다면 코드 원본 출처에서 보도록 하자. 

 

 

 

▶ 분석 개요

분석에 사용할 데이터는 AG 뉴스 데이터 셋으로 2005년에 수집한 뉴스 기사 모음이다. 해당 분석에서는 네가지 범주로 균등하게 분할된 뉴스 기사 12만개에서 뉴스 제목을 대상으로 카테고리를 예측하는 분류모델을 만들었다. 

 

 

 

▶ 분석 과정

분석과정은 다음 그림과 같다. 

그림1. 분석 과정

- 우선 텍스트를 정수로 매핑하기 위해 vocabulary class에서 어휘사전을 구축한다.

- vocabulary 클래스를 상속하여 SequenceVocabulary 클래스를 만든다. 해당 클래스에서는 특수 토큰 4개를 넣어 매핑할 수있도록 사전을 구축한다. 특수토큰은 테스트 시 처음 본 단어들을 표시하는 unk토큰, 모든 벡터의 길이가 같도록 패딩하는 mask 토큰, 문장의 시작을 의미하는 begin-of-sequence, 문장의 끝을 의미하는 end-of-sequence를 사용한다. 

- vectorizer클래스로 어휘사전 객체를 생성하고 단어 빈도가 특정임계값을 넘을 때만 어휘사전에 단어를 추가하도록 함으로써 메모리 사용량 제약을 완화한다. 

- 사전훈련된 임베딩인 GloVe를 사용하여 임베딩층의 가중치를 바꿔준다. 

- CNN모델을 구축한다. 

- val데이터로 검증하고 test 데이터로 평가한다. 

- 뉴스제목을 입력했을 때 어떤 카테고리가 예측되는지 확인한다. 

 

 

 

▶ 분석 코드

▷ class Vocabulary:

매핑을 위해 텍스트를 처리, 기본 어휘사전 만드는 클래스

 

▷ class SequenceVocabulary(Vocabulary):

Vocabulary를 상속해 특수토큰을 추가한 어휘사전을 만들어준다. 

해당 클래스로 반환되는 딕셔너리는 그림2와 같다. 

그림2. 파이토치로 배우는 자연어 처리 p. 170

'Jerry is happy'라는 시퀀스가 들어오면 우선 해당 토큰들에 맞는 정수로 매핑해준다. 만일 어휘사전에 없는 단어라면 UNK 토큰인 1로 매핑해준다. 이 후 begin-of-sequences와 end-of-sequences로 문장의 끝과 시작을 표시해주는 정수를 추가한다. 다음으로 모든 시퀀스의 길이를 맞춰주기위해 mask토큰인 0으로 끝을 채워준다. 

 

  • __init__(token_to_idx=None, unk_token="<UNK>",end_seq_token="<END>, mask_token="<MASK>", begin_seq_token="<BEGIN>"): 토큰이름들을 변수에 초기화하고 vocabulary클래스의 add_token함수를 사용해 추가할 토큰들에 대한 인덱스를 찾아놓는다. 
  • to_serializable(): 4가지 특수토큰을 포함해 직렬화한다. 
  • lookup_token(token): unk_index가 0보다 크면 UNk 인덱스를 반환하고 아니면 토큰에 해당하는 인덱스를 반환한다. 

 

▷ class NewsVectorizer(object):

vectorizer와 sequencevectorizer를 이용해 어휘사전을 생성하고 vectorizer 객체를 만든다

  • __init__(title_vocab, categoru_vocab): 기사 제목과 카테고리의 vocabulary를 클래스 내 변수에 선언한다. 
  • vectorize(title, vector_length= 1): 제목을 벡터화해준다. 제목 vocab객체에서 begin_seq_index를 시작으로 토큰에 해당하는 인덱스를 모두 추가한 후 마지막에 end_seq_index를 추가하여 벡터화한다. 시퀀스의 최대길이로 모든 벡터의 길이를 맞추기 위해 남는 자리는 mask_index로 값을 채워준다.

  • from_dataframe(cls, news_df, cutoff=25): 데이터셋에서 vocabulary를 생성하고 vectorizer객체를 만든다. 카테고리 어휘사전은 vocabulary 클래스로, 뉴스제목 어휘사전은 SequenceVocabulary로 특수토큰을 포함한 어휘사전을 구축한다. 두 어휘사전 모두 단어가 문서에 등장한 빈도가 25번이 넘었을 때 사전에 추가하도록 제한했다. 
  • from_serializable(cls, contents): title은 SequenceVocabulary의 from_serializable함수를 통해, category는 Vocabulary의 from_serializable 함수를 통해 딕셔너리로 직렬화한다.
  • to_serializable(): 직렬화된 딕셔너리를 받아 vocab 객체로 만든다. 

 

 

▷ class NewsDataset(Dataset):

train, val, test 데이터셋을 만들클래스 가중치를 설정한다. 데이터셋으로부터 vectorizer를 만들거나 불러오고 저장한다. vectorizer를 통해 데이터들을 벡터화하고 배치 개수를 도출해 배치 데이터를 생성한다. 

 

 

▷ class NewsClassifier(nn.Modeule):

사전훈련된 임베딩으로 임베딩 층을 만들고 CNN 모델을 구축한다. 

  • __init__(self, embedding_size, num_embeddings, num_channels,pretrained_embeddings=None, padding_idx=0, hidden_dim, num_classes, dropout_p): 모델 선언에 필요한 파라미터의 값들을 초기화한다. 사전훈련된 임베딩이 매개변수로 들어왔다면 nn모듈의 Embedding으로 임베딩 층을 만들고 임베딩 차원, 수, 패딩 인덱스를 설정하고 가중치를 미리 학습된 가중치로 채운다. 미리 선언된 임베딩이 없으면 가중치는 파라미터에서 제외하고 임베딩 층을 선언해준다.

      Sequential 함수를 통해 1차원 합성곱 층과 비활성화 함수 ELU를 층층이 쌓아준다. 마지막에 선형층 2개도 선언해        준다. 

  • forward(x_in, apply_softmax= False): 입력 텐서를 받아 분류기의 정방향을 계산하는 함수다. 임베딩을 적용한 입력벡터를 CNN모델에 넣어 나온 값들을 평균 풀링하여 차원을 조절해주었다. 조절한 벡터에 드랍아웃 비율을 적용한 뒤 선형함수에 넣어 예측 벡터를 구한다. 

 

 

▷ def load_glove_from_file(glove_filepath):

파일경로로부터 글로브를 불러와 단어와 인덱스가 매핑된 word_to_index 딕셔너리와 가중치를 쌓아놓은 embedding을 반환한다. 

 

 

▷ def make_embedding_matrix(glove_filepath, words):

임베딩 행렬을 만든다. 특정 단어에 대해 단어가 word_to_index 딕셔너리에 있으면 단어에 해당하는 가중치 행렬을 사용하고 단어가 딕셔너리 에 없으면 Xavier 균등분포로 임베딩을 초기화하여 사용한다. 

 

 

▷ 모델 훈련

이제 모델을 훈련하기 위해 파라미터 인자들을 설정한다. 

 

학습을 하기위해 데이터 셋과 vectorizer를 만들고 glove로 임베딩 행렬을 생성한다. 

 

이제 train에 대해 모델을 학습하고 val 데이터로 검증, test데이터로 평가한다. 손실함수는 crossentropy함수를, 옵티마이저는 아담 옵티마이저를 사용하고 옵티마이저 스케줄러는 ReduceLROnPlateau를 사용하여 성능 향상이 없을 때 learning rate를 감소시키도록 했다.

배치가 끝날때마다 막대를 업데이트 시키고 loss와 acc를 기록한다.

 

 

 

▷ 모델 성능 확인

이렇게 학습시킨 모델 중 가장 정확도가 좋은 모델을 사용해 테스트 데이터 셋의 성능을 측정해보면 다음과 같다. 

 

 

 추론

그리고 다음과 같은 함수로 새로운 뉴스 제목에 대한 카테고리를 분류할 수 있다.