본문 바로가기
AI/자연어처리

15. 문장임베딩 | BERT

by 사라리24 2024. 7. 5.



1. BERT(Bidiresctional Encoder Representations from Transformers)

 

17. BERT(Bidirectional Encoder Representations from Transformers)

트랜스포머(transformer)의 등장 이후, 다양한 자연어 처리 태스크에서 사용되었던 RNN 계열의 신경망인 LSTM, GRU는 트랜스포머로 대체되어가는 추세입니다. 이에 따…

wikidocs.net

 



1. BERT 모델의 개요

  • BERT는 unlabeled date로 부터 pre-train을 진행한 후, 특정 downstream task에 fine-tuning을 하는 모델
    • downstream task: 주어진 문제나 작업에 특정하게 맡투어진 task를 의미
    • fine-tuning: 사전 학습된 모겔을 새로운 작업 또는 데이터셋에 맞게 조정하는 과정
  • deep bidirectional을 더욱 강조하야 기존이 모델들과의 차별성을 강조
  • 하나의 output layer만을 pre-trained BERT모델에 추가하여 NLP의 다양한 주요(11개)에서 SOTA를 달성
  • pre-training 방법은 BERT 이전에도 많이 연구되고 있었고 실제로 좋은 성능을 내고 있었음
  • 문장 뿐 아니라 토큰 단위의 task에서도 좋은 성능을 보였음

2. BERT 모델의 구조

  • Pre-training part와 Fine-tuning part로 나눠짐
  • Pre-training 에서는 다영한 Pre-training task의 unlabeled data를 화용해 파라미터를 설정하고 이를 바탕으로 학습된 모델은 Fine-tuning에서 downstream tasks의 labeled data를 이용함
  • 양방향 Transformer encoder를 여러 층 쌓은 것
  • BERT base모델은 OpenAI의 GPT와의 비교를 위해 파라미터 수를 동일하게 만들어 진행

3. BERT의 사전학습

  • MLM(Masked Language Modeling)
    • input tokens의 일정 비율을 마스킹하고 마스킹 된 토큰을 예측하는 과정
    • 입력으로 들어온 단어 토큰 중 일부를 [MASK] token으로 바꿔서 학습
    • pre-training 과 fine-tuning 사이의 missmatch가 발생할 수 있음 ([MASK]token이 fine-tuning 과정에서는 나타나지 않기 때문 추가적인 처리가 필요)
  • NSP(Next Sentence Prediction)
    • downstream task 두 문장 사아의 연속성을 확인 하는 것이 핵심
    • 문장 A와 B를 선택할 때 50%는 실제 A의 다음 문장인 B를 고르고, 나모지 50%는 랜덤 문장B에 고름

 

 

  import


       
        import urllib.request
        import pandas as pd
        import matplotlib.pyplot as plt
        import random


 

 

 

데이터 불러오기


       
        urllib.request.urlretrieve('https://raw.githubusercontent.com/songys/Chatbot_data/master/ChatbotData.csv', filename='ChatBotData.csv')

 
('ChatBotData.csv', <http.client.HTTPMessage at 0x7c3973c61180>)



 

 

훈련 데이터 확인


       
      train_dataset = pd.read_csv('ChatBotData.csv')
      print(len(train_dataset))

      train_dataset


11823

----------------------------------------------------------------------------------------------------------------------------------------

 

 

  결측값 확인 삭제


       
          train_dataset.replace('', float('NaN'), inplace = True)
          print(train_dataset.isnull().values.any())

          train_dataset = train_dataset.drop_duplicates(['Q']).reset_index(drop=True)
          print(len(train_dataset))

          train_dataset = train_dataset.drop_duplicates(['A']).reset_index(drop=True)
          print(len(train_dataset))


False

11662

7731

 

 

◼최대 / 평균 길이 뽑기 


       
        question_list = list(train_dataset['Q'])
        answer_list = list(train_dataset['A'])


        print('질문의 최대 길이:', max(len(question) for question in question_list))
        print('질문의 평균 길이:', sum(map(len, question_list))/len(question_list))
        print('답변의 최대 길이:', max(len(answer) for answer in answer_list))
        print('답변의 평균 길이:', sum(map(len, answer_list))/len(answer_list))


질문의 최대 길이: 56
질문의 평균 길이: 13.6732634846721
답변의 최대 길이: 76
답변의 평균 길이: 15.611563833915406

 

 

 


       
          response_candidates = random.sample(answer_list, 500)
          response_candidates[:10]

 
['미안해하지 않아도 돼요. 새사람에게 충실하세요.',
 '안 좋은일이 있었나봐요.',
 '머릿속에 맴도는 것들을 입으로 내뱉는 거죠.',
 '어렵죠.',
 '곧 설레는 순간이 올 거예요.',
 '사람마다 집착하는 게 다르니 인정해주세요.',
 '그 말씀 꼭 지키기 바랄게요.',
 '같이 살자고 프로포즈 해보세요.',
 '마음이 많이 아프겠어요.',
 '축하해요.']

 

 

  설치


       
      ! pip install kobert-transformers

 
 

 

import


       
        import torch
        from kobert_transformers import get_kobert_model

 
 

 

 

 


       

        model = get_kobert_model()


 

 

 

 


       
      model.eval()


 

 

 

 


     
          # 문장에서 토큰들의 인덱스
          input_ids = torch.LongTensor([[31,51,99],[15,5,0]])
          # 모델이 어떤 토큰을 무시해야 하는지 나타내는 텐서(0: 무시, 1: 고려)
          attention_mask = torch.LongTensor([[1,1,1],[1,1,0]])
          # 다중 문장 입력을 다룰 때, 각 토큰이 어떤 문장에 속하는지 구분 (0,1)
          token_type_ids = torch.LongTensor([[0,0,1],[0,1,0]])
          output = model(input_ids, attention_mask, token_type_ids)
          output


 

 

 


       
      # Seqence Embeddings : 각 토큰의 대한 임베딩, 의미적 표현
      # pooler_output : 입력 시퀀스에서 추출한 특징의 요약
      # hidden_state : 모델 내부의 각 레이어에서의 숨겨진 상태값

      output[0]


 

 

 

Collecting kobert_tokenizer
  Cloning https://github.com/SKTBrain/KoBERT.git to /tmp/pip-install-z2khqp0c/kobert-tokenizer_6abbc084b55d4480a76950b785498ee4
  Running command git clone --filter=blob:none --quiet https://github.com/SKTBrain/KoBERT.git /tmp/pip-install-z2khqp0c/kobert-tokenizer_6abbc084b55d4480a76950b785498ee4
  Resolved https://github.com/SKTBrain/KoBERT.git to commit 47a69af87928fc24e20f571fe10c3cc9dd9af9a3
  Preparing metadata (setup.py) ... done
Building wheels for collected packages: kobert_tokenizer
  Building wheel for kobert_tokenizer (setup.py) ... done
  Created wheel for kobert_tokenizer: filename=kobert_tokenizer-0.1-py3-none-any.whl size=4633 sha256=02c21d6d8bdf1eef4f3ad14d45fbf32231180a1e807ef7fcc7804d7911944eed
  Stored in directory: /tmp/pip-ephem-wheel-cache-njk3d1m2/wheels/e9/1a/3f/a864970e8a169c176befa3c4a1e07aa612f69195907a4045fe
Successfully built kobert_tokenizer
Installing collected packages: kobert_tokenizer
Successfully installed kobert_tokenizer-0.1

 

 

 


       
      from kobert_tokenizer import KoBERTTokenizer

 
 

 



 


       
        tokenizer = KoBERTTokenizer.from_pretrained('skt/kobert-base-v1')

 
 

 

 

 


       
        tokenizer.tokenize('[CLS] 한국어 모델을 공유합니다. [SEP]')


['[CLS]', '▁한국', '어', '▁모델', '을', '▁공유', '합니다', '.', '[SEP]']

 

 

 

 


       
          import numpy as np
          import torch
          from sklearn.metrics.pairwise import cosine_similarity


 

 

 

 


       
        def get_cls_token(sentence):
            model.eval()
            tokenized_sent = tokenizer(
                sentence,
                return_tensors='pt',
                truncation=True,
                add_special_tokens=True,
                max_length=128
            )

            input_ids = tokenized_sent['input_ids']
            attention_mask = tokenized_sent['attention_mask']
            token_type_ids = tokenized_sent['token_type_ids']

            with torch.no_grad():
                output = model(input_ids, attention_mask, token_type_ids)

            cls_output = output[1]
            cls_token = cls_output.detach().cpu().numpy()
            return cls_token


        get_cls_token('너 요즘 바뻐?')


 

 

 예측함수


       
        def predict(query, candidates):
            candidates_cls = []

            for cand in candidates:
                cand_cls = get_cls_token(cand)
                candidates_cls.append(cand_cls)

            candidates_cls = np.array(candidates_cls).squeeze(axis=1)
            query_cls = get_cls_token(query)
            similarity_list = cosine_similarity(query_cls, candidates_cls)
            target_idx = np.argmax(similarity_list)
            return candidates[target_idx]


      sample_query = '너 요즘 바뻐?'
      # print(get_cls_token(sample_query))
      print(get_cls_token(sample_query).shape)

(1, 768)

 

 

 


       
        sample_query = '너 요즘 바뻐?'
        sample_candidates = ['바쁘면 가버려', '아니 안바뻐', '오늘은 이만', '에붸붸붸']

        predicted_answer = predict('sample_query', sample_candidates)
        print(f'결과: {predicted_answer}')


결과: 아니 안바뻐

 


 


       
        sample_query = '힘든 연애 좋은 연애라는게 무슨 차이일까?'
        sample_candidates = random.sample(answer_list, 100)

        predicted_answer = predict('sample_query', sample_candidates)
        print(f'결과: {predicted_answer}')

 
결과: 너무 늦지 않았길 바라요

 

 

챗봇 완성


       
      end = 1
      while end == 1:
          sentence = input('질문을 입력하세요: ')
          if len(sentence) == 0:
              break
          predicted_answer = predict(sentence, response_candidates)
          print(predicted_answer)
          print('\n')