[파이썬] KoELECTRA 기반의 감성 자질 모델 구현 도전기 #2-3-2

2023. 5. 18. 01:49연구 프로젝트/감성 자질 모델

2. 구현 도전

3) BERT의 사전 학습

**코드 출처: GitHub - monologg/KoELECTRA: Pretrained ELECTRA Model for Korean

 

GitHub - monologg/KoELECTRA: Pretrained ELECTRA Model for Korean

Pretrained ELECTRA Model for Korean. Contribute to monologg/KoELECTRA development by creating an account on GitHub.

github.com

(4) 감성 자질 임베딩 레이어 위치

*원래 BERT의 세 임베딩 입력값(토큰 임베딩, 세그먼트 임베딩, 위치 임베딩)은 modeling.py 파일의 embedding_postprocessor 함수에서 이루어짐

def embedding_postprocessor(input_tensor,  #[배치 사이즈, 문장 길이, 임베딩 차원]의 tensor
                            use_token_type=False,  #토큰 임베딩 사용 유무
                            token_type_ids=None,  #입력 데이터의 세그먼트 정보
                            token_type_vocab_size=16,  #입력 데이터의 세그먼트 정보의 단어 개수
                            token_type_embedding_name="token_type_embeddings",  #세그먼트 정보 임베딩 변수 이름
                            use_position_embeddings=True,  #위치 임베딩 사용 유무
                            position_embedding_name="position_embeddings",  #위치 임베딩 변수 이름
                            initializer_range=0.02,  #가중치 초기값 범위
                            max_position_embeddings=512,  #문장 최대 길이
                            dropout_prob=0.1):  #마지막 output tensor에 드롭아웃 적용할 확률

  input_shape = get_shape_list(input_tensor, expected_rank=3)  #입력값의 사이즈를 get_shape_list 함수 이용해 리스트 형태로 저장: [배치 사이즈, 문장 길이, 임베딩 차원]
  batch_size = input_shape[0]
  seq_length = input_shape[1]
  width = input_shape[2]
  
  output = input_tensor  #output 변수에 input_tensor(토큰 id화한 데이터) 저장 = 토큰 임베딩 결과★

  if use_token_type:  #세그먼트 임베딩 사용한다면,
    if token_type_ids is None:
      raise ValueError("'token_type_ids' must be specified if"
                       "'use_token_type' is True.")
    token_type_table = tf.get_variable(
        name=token_type_embedding_name,
        shape=[token_type_vocab_size, width],
        initializer=create_initializer(initializer_range))  #[세그먼트 레이어의 단어 개수, 임베딩 차원] 모양의 tensor 만들어 token_type_table 변수에 저장
    # This vocab will be small so we always do one-hot here, since it is always
    # faster for a small vocabulary.
    flat_token_type_ids = tf.reshape(token_type_ids, [-1])
    one_hot_ids = tf.one_hot(flat_token_type_ids, depth=token_type_vocab_size)  #여기에서는 매번 원-핫 인코딩 사용. 세그먼트 레이어 단어 개수를 차원으로 갖는 원-핫 인코딩 벡터 생성
    token_type_embeddings = tf.matmul(one_hot_ids, token_type_table)  #원-핫 인코딩 벡터들이 모인 행렬과 token_type_table 간 행렬곱
    token_type_embeddings = tf.reshape(token_type_embeddings,
                                       [batch_size, seq_length, width])
    output += token_type_embeddings  #output 변수에 위 결과값 더함 = 토큰 임베딩 레이어 결과값+세그먼트 임베딩 레이어 결과값★

  if use_position_embeddings:   #위치 임베딩 사용한다면,
    assert_op = tf.assert_less_equal(seq_length, max_position_embeddings)  #요소 별로 seq_length가 max_position_embedding값보다 작거나 같다고 가정
    with tf.control_dependencies([assert_op]):
      full_position_embeddings = tf.get_variable(
          name=position_embedding_name,
          shape=[max_position_embeddings, width],
          initializer=create_initializer(initializer_range))
      position_embeddings = tf.slice(full_position_embeddings, [0, 0],
                                     [seq_length, -1])  #위치 임베딩 행렬에서 첫 행, 첫 열을 시작으로 문장 길이 만큼의 열벡터 추출
      num_dims = len(output.shape.as_list())  #output 차원을 리스트화 했을 때 길이(차원)을 num_dims 변수에 저장

      # Only the last two dimensions are relevant (`seq_length` and `width`), so
      # we broadcast among the first dimensions, which is typically just
      # the batch size.
      position_broadcast_shape = []
      for _ in range(num_dims - 2):
        position_broadcast_shape.append(1)
      position_broadcast_shape.extend([seq_length, width])
      position_embeddings = tf.reshape(position_embeddings,
                                       position_broadcast_shape)  #위치 임베딩 행렬을 positio_broadcast_shape와 같은 형태로 변형
      output += position_embeddings  #위치 임베딩을 output에 더함 = 토큰 임베딩+세그먼트 임베딩+위치 임베딩 결과 반환★
      #output 변수에 polarity, intensity 임베딩 벡터를 추가하면?
  output = layer_norm_and_dropout(output, dropout_prob)
  return output

*embedding_postprocessor 함수가 반환하는 결과값: 토큰 임베딩 벡터 + 세그먼트 임베딩 벡터 + 위치 임베딩 벡터

=> 위 결과값에 감성 극성 임베딩 벡터와 감성 강도 임베딩 벡터를 더하면 참고 논문에서 말하는 감성 자질 모델의 입력값 구현 가능

 

(5) 감성 자질 레이어 추가 전 일부 클래스 및 함수 실험

(i) Tokenizer

*사전학습된 KoELECTRA의 토크나이저를 불러옴

 

*4가지 문장을 예시로 실험

-"[CLS] 나는 정말 행복해 [SEP]", "[CLS] 그래서 기분이 좋아 [SEP]", "[CLS] 나는 너무 불행해 [SEP]", "[CLS] 그래서 기분이 나빠 [SEP]"

-convert_tokens_to_ids: 토큰들을 각 토큰들에 대응되는 정수 인덱스로 변환해 정수 벡터를 반환

 

-convert_ids_to_tokens: 정수 인덱스로 변환된 토큰들을 다시 자연어로 반환

 

*한국어 형태소 분석기

-KOSAC에서 사용한 형태소 분류 체계와 동일한 체계를 갖는 Kkma(꼬꼬마) 형태소 분석기를 사용하기로 결정

 

*실제 embedding_postprocessor 함수가 3차원 tensor 형태의 입력값을 받는다는 전제 하에, 위 4개의 예시 문장으로 3차원 tensor를 만든 후 이를 1차원으로 변환

 

*1차원으로 만들어진 입력값에 대해 convert_ids_to_tokens 메소드를 사용하여 각 토큰 ID를 실제 자연어 토큰으로 변환한예시

 

(ii) 감성 극성 및 강도 임베딩 벡터

해당 부분은 논문에서 상세히 설명되어 있지 않아 임의로 구현
감성 값을 정수로 변환

*극성(polarity) 임베딩 제작

-극성 값: COMP, NEG, NEUT, None, POS

-Pos(positive)일 시 1, NEG(negative)일 시 -1, 그 외 라벨일 시 0을 반환하도록 구현

 

*강도(intensity) 임베딩 제작

-강도 값: High, Medium, Low, None

-High일 시 3, Medium일 시 2, Lower일 시 1, 그 외 라벨일 시 0을 반환하도록 구현

*이후 입력 tensor 모양과 똑같은 모양의 감성 값 임베딩 tensor로 변환