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

18. PLM을 이용한 실습

by 사라리24 2024. 7. 12.



1. NLI(Natual Language Inference) 실습

- 두개의 문장(전제와 가설) 사이의 논리적 관계를 결정하는 자연어 처리 문제
- 사이트 : https://huggingface.co/Huffon/klue-roberta-base-nli

 

 설치하기


     
          ! pip install transformers

 
 

 

 

import


       
        from transformers import pipeline, AutoTokenizer


  • pipeline: 자연어 처리 작업을 간단한 코드로 여러 작업을 한번에 지원
  • AutoTokenizer: 자동으로 적절한 토크나이저를 선택하여 모델을 토큰화할 수 있음

 

 

객체 만들기


       
        classifier = pipeline(
            'text-classification',
            model = 'Huffon/klue-roberta-base-nli',
            return_all_scores = True
        )


 

 

 

토크나이저


       
        tokenizer = AutoTokenizer.from_pretrained('Huffon/klue-roberta-base-nli')
        tokenizer

 

 

 

seperate 토큰 확인


       
        tokenizer.sep_token


[SEP]

 

 

모순관계 문장 


 
        classifier(f'나는 게임을 하는 것을 좋아해 {tokenizer.sep_token}나는 게임을 하는 것이 싫어')
   
 

 

 

내포하는 문장


       

        classifier(f'여러 남성들이 축구를 즐기고 있어요{tokenizer.sep_token} 어떤 남자 들은 스포츠를 하고 있어요')




2. 문장요약, 번역, 텍스트 생성 실습


* BART(Sequential Bidirectional AutoRegressive Transformers)
* 허깅페이스에서 개발한 자연어 처리 모델
* 자연어 생성 및 이해 작업을 위해 사전 훈련된 언어모델
* Transformer 아키텍처를 기반으로 양벙향 인코더-디코더 구조를 가지고 있음 
* 문장 요약, 번엳 텍스트 생성에 뛰어난 성능을 보여주는 모델
- 사이트: https://huggingface.co/digit82/kobart-summarization

 

import


     
          import torch
          from transformers import PreTrainedTokenizerFast
          from transformers import BartForConditionalGeneration


 

 

 

preTrainedTokenizerFast: 허깅페이스에서 개발한 Rust 기반의 고성능 토크나이저

 

 

 객체만들기


       
      tokenizer = PreTrainedTokenizerFast.from_pretrained('digit82/kobart-summarization')
      model = BartForConditionalGeneration.from_pretrained('digit82/kobart-summarization')


 

 

 

text 넣기


       
        text = """
        중부와 남부지방에 밤사이 폭우가 내린 10일 대전 서구 정뱅이마을에서 주민들이 홍수 피해당한 마을을 살펴보고 있다. 주민들은 새벽 4시쯤 마을 입구에 있는 제방이 무너지고, 강물이 범람해 마을을 덮쳤다고 당시 상황을 설명했다. 주민 박 모씨는 “소를 집에 남겨두고 나와서 죽었다고 생각했는데 잘 버텨줘서 다행이다”라며 “천천히 정리하다 보면 일상으로 돌아갈 수 있을 것”이라고 안도의 한숨을 내쉬었다.
        이어 다른 주민 이 모씨는 “과거에도 마을이 잠긴 적 있었지만, 이 정도는 아니었는데 마음이 아프다”라면서도 “그래도 이장이 제방이 무너졌다는 방송을 빨리 해준 덕분에 인명피해가 안 나서 다행이다”라고 말하며 가슴을 쓸어내렸다.새벽에 구조된 주민들은 흑성동 기성종합복지관으로 대피했다. 서구청은 복지관에 대피소를 마련하고 구호물품을 제공할 예정이라고 밝혔다.
        """


 

 

 

줄바꿈 삭제


       
      text = text.replace('\n', ' ')


 

 

 

text 변수를 토큰화하여 raw_input_ids 리스트를 생성, 출력


       
      raw_input_ids = tokenizer.encode(text)
      print(raw_input_ids)


[14059, 16660, 20095, 16089, 11786, 15294, 19958, 14556, 19657, 18542, 16458, 16061, 20953, 14045, 10811, 12034, 16503, 14030, 21917, 29878, 14783, 17029, 15901, 12007, 16354, 15326, 15964, 23897, 17412, 21563, 12586, 15901, 14179, 17040, 14082, 14060, 19372, 14113, 15488, 14161, 14119, 15689, 14751, 10223, 13607, 15901, 12007, 18896, 12710, 14117, 14616, 16853, 14490, 15615, 14918, 14169, 14069, 14580, 14128, 15383, 17654, 20296, 15538, 16048, 11264, 14674, 18731, 14246, 17718, 14334, 14536, 13206, 12278, 11264, 14056, 18780, 26187, 14128, 12673, 22378, 17137, 15931, 14972, 14055, 16663, 27286, 14032, 14523, 16047, 14105, 15245, 27919, 14067, 11418, 16367, 14311, 14355, 14918, 14025, 14069, 14580, 14128, 25241, 14279, 15901, 12034, 14693, 9267, 14184, 27735, 14025, 23028, 14202, 19737, 17348, 16873, 14213, 10213, 16944, 18771, 19914, 14025, 14416, 14060, 19372, 18413, 21768, 21801, 17122, 29805, 19785, 25214, 13571, 18518, 14105, 15306, 14056, 18780, 19885, 27479, 24604, 16775, 11763, 9517, 21006, 11229, 10868, 11786, 15181, 9908, 23897, 16816, 11280, 9879, 20382, 16819, 15997, 21403, 25821, 15615, 20953, 16349, 16611, 18565, 25821, 15383, 14712, 14058, 14112, 13699, 10675, 15949, 21131, 27233, 14253, 14130, 1700]

 

 

input_ids  리스트를 만들기


       
        input_ids = [tokenizer.bos_token_id] + raw_input_ids + [tokenizer.eos_token_id]
        print(input_ids)

 
[0, 14059, 16660, 20095, 16089, 11786, 15294, 19958, 14556, 19657, 18542, 16458, 16061, 20953, 14045, 10811, 12034, 16503, 14030, 21917, 29878, 14783, 17029, 15901, 12007, 16354, 15326, 15964, 23897, 17412, 21563, 12586, 15901, 14179, 17040, 14082, 14060, 19372, 14113, 15488, 14161, 14119, 15689, 14751, 10223, 13607, 15901, 12007, 18896, 12710, 14117, 14616, 16853, 14490, 15615, 14918, 14169, 14069, 14580, 14128, 15383, 17654, 20296, 15538, 16048, 11264, 14674, 18731, 14246, 17718, 14334, 14536, 13206, 12278, 11264, 14056, 18780, 26187, 14128, 12673, 22378, 17137, 15931, 14972, 14055, 16663, 27286, 14032, 14523, 16047, 14105, 15245, 27919, 14067, 11418, 16367, 14311, 14355, 14918, 14025, 14069, 14580, 14128, 25241, 14279, 15901, 12034, 14693, 9267, 14184, 27735, 14025, 23028, 14202, 19737, 17348, 16873, 14213, 10213, 16944, 18771, 19914, 14025, 14416, 14060, 19372, 18413, 21768, 21801, 17122, 29805, 19785, 25214, 13571, 18518, 14105, 15306, 14056, 18780, 19885, 27479, 24604, 16775, 11763, 9517, 21006, 11229, 10868, 11786, 15181, 9908, 23897, 16816, 11280, 9879, 20382, 16819, 15997, 21403, 25821, 15615, 20953, 16349, 16611, 18565, 25821, 15383, 14712, 14058, 14112, 13699, 10675, 15949, 21131, 27233, 14253, 14130, 1700, 1]
  • tokenizer.bos_token_id는 시퀀스의 시작을 나타내는 토큰의 ID입니다.
  • raw_input_ids는 원본 입력 텍스트가 토큰화된 후의 ID들입니다.
  • tokenizer.eos_token_id는 시퀀스의 끝을 나타내는 토큰의 ID입니다.
    이러한 토큰 ID들을 하나의 리스트로 합쳐 input_ids를 생성한 후, 이를 출력합니다.

 

 

요약내용 토큰화 


       
        summary_ids = model.generate(torch.tensor([input_ids]),  num_beams=4,  max_length=512,  eos_token_id=1)
        summary_ids


tensor([[    2, 14059, 16660, 20095, 16089, 11786, 15294, 19958, 14556, 19657,
         18542, 16458, 16061, 20953, 14045, 10811, 12034, 16503, 14030, 21917,
         14060, 19372, 14113, 15488, 14161, 14119, 15689, 14751, 10223, 13607,
         15901, 12007, 18896, 12710, 14117, 14616, 16853, 14490, 17406, 20953,
         16349, 16611, 18565, 25821, 15383, 14712, 14058, 14112, 13699, 10675,
         15949, 21131, 27233, 14253, 14130,     1]])

 

 

요약내용 출력


       
      tokenizer.decode(summary_ids.squeeze().tolist(), skip_special_tokens=True)


중부와 남부지방에 밤사이 폭우가 내린 10일 대전 서구 정뱅이마을에서 주민들이 제방이 무너지고, 강물이 범람해 마을을 덮쳤다고 당시 상황을 설명했으며 서구청은 복지관에 대피소를 마련하고 구호물품을 제공할 예정이라고 밝혔다.

 

 

3. KLUE(Korean Language Understanding Evaludation)

* 한국어 자연어 이해 평가 데이터셋 
* 한국어 언어 모델의 동정한 평가를 위한 목적을 8개의 종류가 포함된 공개 데이터셋 
* 광범위한 주제와 다양한 스타일을 포괄하기 위해 다양한 출처에서 공개적으로 사용 가능한 한국어 말뭉치를 수집 
* 약 62GB 크기의 최종 사전 학습 코퍼스를 구축 

 

 

  사용목적

  * 뉴스 헤드라인 분류
  * 문장 유사도 비교
  * 자연어 추론
  * 개체명 인식  
  * 관계 추출
  * 형태소 및 의존 구문 분석 
  * 기계 독해 이해 
  * 대화 상태 추적 

  수집

  * MODU : 국립국어원에서 배포하는 한국어 말뭉치 모음 
  * CC-100-Kor : CC-100은 CC-Net을 사용하여 대규모 다국어 웹 크로링 코퍼스를 구축 
  * 나무위키 : 나무의키는 한국어 웹 기반 백과사전으로 위키백과와 유사하지만 자유로운 형식으로 알려져 있음(2020년3월 2일에 생성된 덤프)
  * 뉴스스크롤 : 2011년부터 2020년 까지 발행한 12800000개 뉴스기사로 구성되어 있으며, 뉴스 집계 플랫품애서 수집
  * 청원 : 사회적 이슈에 대한 행정 조치를 요청하는 청와대 국민천원 모음, 2017년 8월 부터 2019년 3월 까지 게시된 청와대 국민천원의 기사를 사용 

  종류  ['ynat', 'sts', 'nli', 'ner' , 're', 'dp', 'mrc', 'wos']

  •  ynat: 유투브 비디오 댓글에서 자연스럽게 발생하는 대화
    데이터를 이용한 태스크, 주어진 문장에 대해 답변하는 작업
  • sts: 두 텍스트의 의미적 유사성을 평가하는 태스크
  • nil: 전제와 가설이라는 두 문장 간의 논리적 관계를 판별하는 태스크(참, 거짓, 중립)
  • ner: 문장에서 인명, 지명, 기관명 등 특정 개체명을 식별하고 분류하는 태스크
  • re: 문장 또는 텍스트에서 개체들 간의 관계를 추출하는 태스크
    예) "스티브잡스는 애플의 공동창립자이다" -> 스티브잡스와 애플을 뽑아내고 그 관계를 알려줌
  • dp: 문장 내 단어들 간의 문법적 관계를 파악하는 태스크. 각 단어가 어떻게 다른 단어들과 연결되어 있는지(종속성)을 구조적으로 분석
  • mrc: 주어진 텍스트와 질문에 대해 답을 추론하거나 생성하는 태스크. 모델이 텍스트를 읽고 이해하여 질문에 답할 수 있도록 함
  • wos: Web of Science 데이터베이스에서 추출한 데이터를 활용하는 태스크. 특정 태스크보다느 데이터의 출처를 나타냄

 



 설치


       
       ! pip install datasets
       ! pip install accelerate -U
       ! pip install transformers[torch]


 

 

 

import


 
        import datasets
        import random
        import pandas as pd
        import numpy as np
        from datasets import load_dataset, ClassLabel, load_metric
        from IPython.display import display, HTML
        from transformers import AutoTokenizer, pipeline, AutoModelForSequenceClassification, Trainer, TrainingArguments


 
 

 

 

데이터 불러오기


       
        model_checkpoint = 'klue/roberta-base'
        batch_size = 64
        task = 'ynat'

        datasets = load_dataset('klue', task)

 

 

 

 

데이터셋 확인


       
      datasets


 

 

datasets에 train 데이터 인덱스 0~9까지 출력


     
      datasets['train'][:10]


{'guid': ['ynat-v1_train_00000',
  'ynat-v1_train_00001',
  'ynat-v1_train_00002',
  'ynat-v1_train_00003',
  'ynat-v1_train_00004',
  'ynat-v1_train_00005',
  'ynat-v1_train_00006',
  'ynat-v1_train_00007',
  'ynat-v1_train_00008',
  'ynat-v1_train_00009'],
 'title': ['유튜브 내달 2일까지 크리에이터 지원 공간 운영',
  '어버이날 맑다가 흐려져…남부지방 옅은 황사',
  '내년부터 국가RD 평가 때 논문건수는 반영 않는다',
  '김명자 신임 과총 회장 원로와 젊은 과학자 지혜 모을 것',
  '회색인간 작가 김동식 양심고백 등 새 소설집 2권 출간',
  '야외서 생방송 하세요…액션캠 전용 요금제 잇따라',
  '월드컵 태극전사 16강 전초기지 레오강 입성종합',
  '미세먼지 속 출근길',
  '왓츠앱稅 230원에 성난 레바논 민심…총리사퇴로 이어져종합2보',
  '베트남 경제 고성장 지속…2분기 GDP 6.71% 성장'],
 'label': [3, 3, 2, 2, 3, 0, 5, 3, 4, 4],
 'url': ['https://news.naver.com/main/read.nhn?mode=LS2D&mid=shm&sid1=105&sid2=227&oid=001&aid=0008508947',
  'https://news.naver.com/main/read.nhn?mode=LS2D&mid=shm&sid1=103&sid2=248&oid=001&aid=0008384783',
  'https://news.naver.com/main/read.nhn?mode=LS2D&mid=shm&sid1=105&sid2=228&oid=001&aid=0008254585',
  'https://news.naver.com/main/read.nhn?mode=LS2D&mid=shm&sid1=105&sid2=228&oid=001&aid=0009070646',
  'https://news.naver.com/main/read.nhn?mode=LS2D&mid=shm&sid1=103&sid2=243&oid=001&aid=0009999529',
  'https://news.naver.com/main/read.nhn?mode=LS2D&mid=shm&sid1=105&sid2=226&oid=001&aid=0008547867',
  'https://sports.news.naver.com/news.nhn?oid=001&aid=0010126131',
  'https://news.naver.com/main/read.nhn?mode=LS2D&mid=shm&sid1=103&sid2=248&oid=001&aid=0009817982',
  'https://news.naver.com/main/read.nhn?mode=LS2D&mid=shm&sid1=104&sid2=234&oid=001&aid=0011176999',
  'https://news.naver.com/main/read.nhn?mode=LS2D&mid=shm&sid1=104&sid2=231&oid=001&aid=0010921012'],
 'date': ['2016.06.30. 오전 10:36',
  '2016.05.08. 오전 5:25',
  '2016.03.15. 오후 12:00',
  '2017.02.28. 오전 9:54',
  '2018.04.03. 오전 7:05',
  '2016.07.18. 오전 9:46',
  '2018.06.04 08:15',
  '2018.01.17. 오전 10:14',
  '2019.10.30. 오전 4:17',
  '2019.06.28. 오후 5:42']}

 

 

show_random_elmenets(dataset, num_examples = 10) 함수 만들기

  • train데이터에서 랜덤하게 매개 변수에 전달된 갯수 만큼 데이터프레임으로 변환하여 출력

       
        def show_random_elmenets(dataset, num_examples=10):
            picks = []
            for _ in range(num_examples):
                pick = random.randint(0, len(dataset)-1)
                while pick in picks:
                    pick = random.randint(0, len(dataset)-1)
                picks.append(pick)
            df = pd.DataFrame(dataset[picks])
            return df
           
        show_random_elmenets(datasets['train'])
 

 

 

무작위 예측값 ,실제 레이블 생성


     
        fake_preds = np.random.randint(0, 2, size = (64, ))
        fake_labels = np.random.randint(0, 2, size = (64, ))
        fake_preds, fake_labels


(array([0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1,
        0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1,
        1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0]),
 array([0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1,
        0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1,
        0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1]))

 

 

f1 측정 지표(metric)를 로드하여 metric 변수에 저장


      
      metric = load_metric('f1')


 

 

metric 객체 생성



        metric.compute(predictions = fake_preds, references = fake_labels)


{'f1': 0.588235294117647}

 

 

 토크나이저


       
        tokenizer = AutoTokenizer.from_pretrained(model_checkpoint, use_fast=True)

 

  • from transformers import AutoTokenizer:
    Hugging Face의 transformers 라이브러리에서 AutoTokenizer 클래스를 임포트합니다.
  • model_checkpoint: 로드하려는 모델의 이름 또는 경로입니다.
    예를 들어, "bert-base-uncased"와 같은 사전 학습된 모델 이름이 될 수 있습니다.
  • AutoTokenizer.from_pretrained(model_checkpoint, use_fast=True):
    지정된 모델 체크포인트에서 토크나이저를 로드합니다.
    use_fast=True 옵션을 통해 빠른 토크나이저(속도와 효율성이 향상된 구현)를 사용합니다.
  • tokenizer: 로드된 토크나이저 객체가 저장되는 변수입니다.

 

 

한국어 텍스트를 토큰화


       
      tokenizer('크리스토퍼 놀란, 한국계 셀린 송 감독 ‘패스트 라이브즈’ 극찬 "미묘하게 아름다운 영화"')

      print(tokenizer.cls_token_id, tokenizer.eos_token_id)

 
0 2

 

 

 

tokenizer 객체의 주요 기능과 속성


       
      tokenizer

 


 

 

데이터셋의 첫 번째 문장을 확인


       
        print(f'문장1: {klue_datasets["train"][0]["title"]}')


문장1: 유튜브 내달 2일까지 크리에이터 지원 공간 운영

 

 

전처리하는 함수


       
        def preprocess_function(examples):
            return tokenizer(
                examples['title'],
                truncation=True, # 최대 길이를 초과할 경우 초과된 부분을 잘라냄
                return_token_type_ids = False # 토큰 타입 ID를 반환하지 않도록 설정
                )
           
        preprocess_function(klue_datasets['train'][:5])

 
{'input_ids': [[0, 10637, 8474, 22, 2210, 2299, 2118, 28940, 3691, 4101, 3792, 2], [0, 24905, 1042, 4795, 19982, 2129, 121, 6904, 16311, 3, 14392, 2], [0, 4172, 3797, 3728, 2107, 2134, 3777, 904, 6022, 2332, 2113, 2259, 4523, 1380, 2259, 2062, 2], [0, 12417, 2155, 7840, 604, 2859, 3873, 11554, 2522, 1539, 2073, 8446, 6626, 18818, 575, 2], [0, 13203, 2179, 2366, 4197, 7551, 2096, 8542, 2088, 2353, 886, 1244, 4393, 2027, 22, 2207, 8189, 2]], 'attention_mask': [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]}

 

 

전처리 처리하고, 처리된 결과를 encoded_datasets에 저장


       
        encoded_datasets = klue_datasets.map(preprocess_function, batched = True)

 

 

 

시퀀스 분류(sequence classification) 작업을 위한 모델을 초기화


       
        num_labels = 7 # 분류할 클래스의 개수, 예를 들어 감정분석에서는 긍정, 부정 등의 클래스 수
        model = AutoModelForSequenceClassification.from_pretrained(model_checkpoint, num_labels = num_labels)

 

 

 

평가 함수인 compute_metrics를 정의
평가 예측값(eval_pred)을 기반으로 정밀도, 재현율, F1 점수 등 다양한 메트릭을 계산


       
        def compute_metrics(eval_pred):
          predictions, labels = eval_pred
          predictions = np.argmax(predictions, axis = 1)
          return metric.compute(predictions = predictions, references = labels, average = 'macro')


 

 

 

 

모델이 데이터를 어떻게 학습하고 평가할지를 정의

     
        metric_name = 'f1'

 
          args = TrainingArguments(
              'test-tc',
              evaluation_strategy='epoch',  # 에폭이 끝날 때마다 평가를 수행하는 평가 전략
              save_strategy='epoch',  # 모델 체크포인트를 매 에포크마다 저장하는 저장 전략
              learning_rate=2e-5,  # 학습률 설정
              per_device_train_batch_size=batch_size,  # 장치 당 학습 배치 크기
              per_device_eval_batch_size=batch_size,  # 장치 당 평가 배치 크기
              num_train_epochs=5,  # 학습할 에포크 수
              weight_decay=0.01,  # 가중치 감소 설정
              load_best_model_at_end=True,  # 학습이 끝난 후 가장 좋은 성능을 보인 모델을 불러올지 여부
              metric_for_best_model=metric_name  # 최적의 모델을 결정할 때 사용할 메트릭 지정
          )


 

 

 

Trainer 객체를 사용하여 모델 학습 과정을 자동화


   

        trainer = Trainer(
            model,
            args,
            train_dataset = encoded_datasets['train'],
            eval_dataset = encoded_datasets['validation'],
            tokenizer = tokenizer,
            compute_metrics = compute_metrics
        )

        trainer.train()


 

 

모델을 평가


      

        trainer.evaluate()


 

 

최적의 성능을 보인 모델 체크포인트 파일의 경로

     
        best_model_checkpoint = trainer.state.best_model_checkpoint
        best_model_checkpoint


 

 

 

텍스트 분류를 수행하는 파이프라인을 설정


       
        classifier = pipeline(
            'text-classification',
            model = '/content/test-tc/checkpoint-2142',
            return_all_scores = True
        )


        classifier('전주시, 9월까지 가시박 등 생태계 교란 식물 제거')


'''
0 (IT과학)
1 (경제)
2 (사회)
3 (생활문화)
4 (세계)
5 (스포츠)
6 (정치)
'''






 

@. 팀과제


1. 한국어 금융 뉴스, 긍정, 부정 분류

https://github.com/ukairia777/finance_sentiment_corpus


2. 다중 클래스 분류 : 두 문장을 입력받아 두 문장의 '얽힘','중립','모순'을 분류해주는 문제

* https://github.com/kakaobrain/kor-nlu-datasets

3. 한국어 혐오 발언 다중 레이블 분류
* https://github.com/adlnlp/K-MHaS

 

1. 예시) prediction ("ChatGPT의 등장으로 인공지능 스타트업들이 비상이 걸렸다.") [ 부정 ]


2. 예시) prediction ("sent1, sent2") [ 모순 ]
sent1 = '흡연자분들은 발코니가 있는 방이면 발코니에서 흡연하세요."
sent2 = '이 건물은 흡연이 금지됩니다."

3. 예시) prediction("XX들은 왜 그렇게 민폐를 끼치냐?") ['연령차별']