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

09. CBOW Text Classification

by 사라리24 2024. 7. 1.



1. CBOW Text Classification

 

 

더보기

"I like studying data analysis." 라는 문장이 주어져있다고 합시다.

저희는 studying 이라는 단어의 정보를 주변의 단어의 정보로부터 얻고자 합니다.

CBOW 방법은 "studying" 을 주변 단어인 "I", 'like", "data", "analysis" 로 부터 유추됩니다.
우선 첫 번째 과정으로, "I", 'like", "data", "analysis" 단어들에게서 정보를 추출해야합니다.
정보를 추출한다는 것은 각 단어들을 vector화 시키는 것입니다.
각 단어들을 vector화 시키기 위해서 우선 가장 기본적인 방법으로 one-hot encoding을 사용합니다.
그리고, look-up table 이라고 불리는 행렬에 곱함으로써 원하는 차원으로의 embedding이 진행됩니다.

 

 

import


      
          import urllib.request
          import pandas as pd
          import numpy as np
          import matplotlib.pyplot as plt
          import torch
          import torch.nn as nn
          import torch.optim as optim
          from copy import deepcopy
          from torch.utils.data import Dataset, DataLoader
          from tqdm.auto import tqdm


 

 

 

파일 가져오기


       

        urllib.request.urlretrieve('https://raw.githubusercontent.com/e9t/nsmc/master/ratings_train.txt', filename='ratings_train.txt' )
        urllib.request.urlretrieve('https://raw.githubusercontent.com/e9t/nsmc/master/ratings_test.txt', filename='ratings_test.txt' )


        train_dataset = pd.read_table('ratings_train.txt')
        train_dataset


 

 

 

train_dataset 값 확인


       
        # pos, nag 비율
        train_dataset['label'].value_counts()

        # 결측값(null 값)의 개수
        sum(train_dataset['document'].isnull())

        # 각 값이 결측값인지 여부
        ~train_dataset['document'].isnull()


 

 

결측값 삭제



        # 결측값삭제
        train_dataset = train_dataset[~train_dataset['document'].isnull()]

       # 결측값 확인: 0
        sum(train_dataset['document'].isnull())

       # 데이터프레임 확인 (5개 행 삭제)
        train_dataset


0

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



 

 

 

1행의 문자열: 공백을 기준으로 분리(split)


       
      train_dataset['document'].iloc[0].split()


['아', '더빙..', '진짜', '짜증나네요', '목소리']

 

 Tokenization: 자연어를 숫자의 형식으로 변형시켜야 함

 

(vocab) : 추출된 모든 고유한 단어


       
      vocab = set()
      for doc in train_dataset['document']:
          for token in doc.split():
              vocab.add(token)


      len(vocab)


357862

 

 

{ vocab_cnt_dict }  |  (단어 : 등장 횟수)


       
        # 단어의 빈도수 구하기
        vocab_cnt_dict = {}
        for doc in train_dataset['document']:
            for token in doc.split():
                if token not in vocab_cnt_dict:
                    vocab_cnt_dict[token] = 0
                vocab_cnt_dict[token] += 1

        # { 단어 : 빈도수 } 로 자리 바꿔주기
        vocab_cnt_list = [(token, cnt) for token, cnt in vocab_cnt_dict.items()]

       # 처음부터 10개의 원소를 가져와 출력
        vocab_cnt_list[:10]
         


[('아',1204),
 ('더빙..',.2), 
 ('진짜', 5929),
 ('짜증나네요', 10)
 ('목소리', 99),
 ...
 ]

 

 

◼  { vocab_cnt_dict } 딕셔너리(dictionary)에서
(단어 : 등장 횟수) 를 튜플로 묶어 리스트(list)로 만들기


     
      vocab_cnt_list = [(token, cnt) for token, cnt in vocab_cnt_dict.items()]

      # 처음부터 10개의 원소를 가져와 출력
      vocab_cnt_list[:10]


[('아', 1204),
 ('더빙..', 2),
 ('진짜', 5929),
 ('짜증나네요', 10),
 ('목소리', 99),
 ('흠...포스터보고', 1),
 ('초딩영화줄....오버연기조차', 1),
 ('가볍지', 17),
 ('않구나', 2),
 ('너무재밓었다그래서보는것을추천한다', 1)]
  • vocab_cnt_dict.items()는 vocab_cnt_dict 사전의 각 요소(키-값 쌍)을 가져옵니다.
  • for token, cnt in vocab_cnt_dict.items()은 사전에서 각 요소를 가져와
         token 변수에는 단어(키), cnt 변수에는 그 단어의 등장 횟수(값)를 할당합니다.
  • (token, cnt)는 튜플을 만드는 문법으로, 각 단어와 그 단어의 등장 횟수를 튜플로 묶습니다.
  • [...]는 리스트 컴프리헨션을 사용하여, 각 단어와 그 단어의 등장 횟수를 튜플로 묶어 리스트(vocab_cnt_list)를 생성합니다.

 

 

[ vocab_cnt_list ] 등장 횟수 내림차순 정렬
                                               등장횟수가  가장 적은 top 10 출력



       # 등장횟수 내림차순으로 정렬
        top_vocabs = sorted(vocab_cnt_list, key=lambda tup: tup[1], reverse=True)

      # 처음부터 10개의 원소를 가져와 출력
        vocab_cnt_list[:10]


[('영화', 10825),
 ('너무', 8239),
 ('정말', 7791),
 ('진짜', 5929),
 ('이', 5059),
 ('영화.', 3598),
 ('왜', 3285),
 ('더', 3260),
 ('이런', 3249),
 ('그냥', 3237),

......

 ('본다면', 105),
 ('최고다.', 105),
 ('이유를', 105),
 ('웃음이', 105),
 ('ㅡㅡ;', 105),
 ('로맨틱', 105),
 ('같아', 105),
 ...]


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


 

 

 

단어 등장 횟수 평균값 , top 10 출력



       # 단어 등장 횟수 리스트로 저장        
        cnts = [cnt for _, cnt in top_vocabs]

       # 단어 등장 횟수 평균값
        np.mean(cnts)

       # 처음부터 10개의 원소 출력
        cnts[:10]



  • top_vocabs는 단어와 그 단어의 등장 횟수로 구성된 리스트입니다.
    예를 들어 [('진짜', 5929), ('아', 1204), ('목소리', 99), ...]와 같은 형태일 수 있습니다.
  • 위 코드는 top_vocabs 리스트에서 각 튜플의 두 번째 요소인 등장 횟수(cnt)만을 추출하여 cnts 리스트에 저장합니다.
  • 따라서, cnts는 단어들의 등장 횟수만을 담고 있는 리스트가 됩니다.
  • np.mean(cnts)는 NumPy 라이브러리(np)를 사용하여 cnts 리스트의 평균을 계산합니다.
  • 즉, cnts 리스트에 있는 단어들의 등장 횟수의 평균을 구합니다.
  • cnts[:10]는 cnts 리스트의 처음부터 10개의 원소를 출력합니다.
  • 이는 가장 많이 등장한 단어부터 순서대로 10개의 등장 횟수를 보여줍니다.

 

 

n_vocab | 단어 등장 횟수가 3 이상인 단어 개수



       # cnts 리스트로 변환, 등장횟수가 3이상인 단어 개수
        sum(np.array(cnts) > 2)

       # 결과를 n_vocab에 저장
        n_vocab = sum(np.array(cnts) > 2)

        n_vocab



  • np.array(cnts)는 cnts 리스트를 NumPy 배열로 변환합니다.
    NumPy 배열을 사용하는 이유는 배열 연산을 효율적으로 수행할 수 있기 때문입니다.
  • np.array(cnts) > 2는 배열의 각 원소가 2보다 큰지 여부를 나타내는 Boolean 배열을 생성합니다.
    즉, 각 단어의 등장 횟수가 2보다 크면 True, 작거나 같으면 False가 됩니다.
  • sum(...)은 Boolean 배열에서 True의 개수를 세는 함수입니다.
    따라서 sum(np.array(cnts) > 2)는 cnts 리스트에서 등장 횟수가 2보다 큰 값들의 개수를 계산합니다.
  • 이 결과를 n_vocab 변수에 저장합니다. 따라서 n_vocab에는 등장 횟수가 2보다 큰 단어들의 개수가 할당됩니다.
  • 마지막으로 n_vocab 변수를 출력하여 등장 횟수가 2보다 큰 단어들의 개수를 확인할 수 있습니다.
즉, 이 코드들은 cnts 리스트에서 등장 횟수가 2보다 큰 값들의 개수를 계산하고, 이 개수를 n_vocab 변수에 저장하며 출력하는 과정을 나타냅니다.

 

 

◼ [ top_vocabs_truncated ]  top_vocabs에서 처음 n_vocab 개의 항목을 추출해 할당 
  리스트의 처음 5개 항목을 출력


       
        top_vocabs_truncated = top_vocabs[:n_vocab]

        top_vocabs_truncated[:5]


[('영화', 10825), ('너무', 8239), ('정말', 7791), ('진짜', 5929), ('이', 5059)]

 

 

[ vocabs ] top_vocabs_truncated 리스트에서 각 요소의 첫 번째 요소를 추출하여 할당,
      처음 5개 항목을 출력


      
      vocabs = [token for token, cnt in top_vocabs_truncated]
      vocabs[:5]


['영화', '너무', '정말', '진짜', '이']



 

2. special token

- [UNK] : Unkonown token
- [PAD] : Padding token

 

◼ [UNK]와 [PAD] 토큰이 어휘 목록인 vocabs에 포함되어 있는지를 확인  


     
        unk_token = '[UNK]'
        unk_token in vocabs

        pad_token = '[PAD]'
        pad_token in vocabs


  • unk_token = '[UNK]': [UNK]라는 문자열을 unk_token 변수에 할당합니다. [UNK]는 주로 '알 수 없는 토큰(unknown token)'을 나타내는 데 사용됩니다.
  • unk_token in vocabs: unk_token이 vocabs에 포함되어 있는지 확인합니다. 이 표현식은 True 또는 False 값을 반환합니다.
  • pad_token = '[PAD]': [PAD]라는 문자열을 pad_token 변수에 할당합니다. [PAD]는 주로 '패딩 토큰(padding token)'을 나타내는 데 사용됩니다.
  • pad_token in vocabs: pad_token이 vocabs에 포함되어 있는지 확인합니다. 이 표현식 역시 True 또는 False 값을 반환합니다.
False

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

False

 

 

[ vocabs  ] 맨 앞에 unk_token과 pad_token을 삽입  
                              리스트의 처음 5개 항목을 출력


       
        vocabs.insert(0, unk_token)
        vocabs.insert(0, pad_token)

        vocabs[:5]  


['[PAD]', '[UNK]', '영화', '너무', '정말']

 

 

[ vocabs ]를 사용하여 두 개의 매핑을 생성
[ idx_to_token ] 인덱스를 토큰으로 매핑
[ token_to_idx ] 토큰을 인덱스로 매핑하는 사전


       
      idx_to_token = vocabs
      token_to_idx = {token: i for i, token in enumerate(idx_to_token)}

 
 

 

 

◼클래스 Tokenizer | 텍스트를 토큰 ID의 리스트로 변환
                                                단어 사전(vocabs)을 사용하여 토큰을 인덱스로 매핑하고, 선택적으로 패딩을 적용


       

        class Tokenizer:
            def __init__(self, vocabs, use_padding=True, max_padding=64, pad_token='[PAD]', unk_token='[UNK]'):
                self.idx_to_token = vocabs
                self.token_to_idx = {token: i for i, token in enumerate(self.idx_to_token)}
                self.use_padding = use_padding
                self.max_padding = max_padding
                self.pad_token = pad_token
                self.unk_token = unk_token
                self.unk_token_idx = self.token_to_idx[self.unk_token]
                self.pad_token_idx = self.token_to_idx[self.pad_token]

            def __call__(self, x:str):
                token_ids = []
                token_list = x.split()
                for token in token_list:
                    if token in self.token_to_idx:
                        token_idx = self.token_to_idx[token]
                    else:
                        token_idx = self.unk_token_idx
                    token_ids.append(token_idx)
                   
                if self.use_padding:
                    token_ids = token_ids[:self.max_padding]
                    n_pads = self.max_padding - len(token_ids)
                    token_ids = token_ids + [self.pad_token_idx] * n_pads

                return token_ids


        tokenizer = Tokenizer(vocabs, use_padding=False)


  • class Tokenizer:
    • Tokenizer 클래스를 정의합니다.
  • def __init__(self, vocabs, use_padding=True, max_padding=64, pad_token='[PAD]', unk_token='[UNK]'):
    • 클래스의 생성자 메서드를 정의합니다. 이 메서드는 다음 인자들을 받습니다:
      • vocabs: 토큰 리스트.
      • use_padding: 패딩 사용 여부 (기본값은 True).
      • max_padding: 최대 패딩 길이 (기본값은 64).
      • pad_token: 패딩 토큰 (기본값은 [PAD]).
      • unk_token: 알려지지 않은 단어 토큰 (기본값은 [UNK]).
  • self.idx_to_token = vocabs
    • vocabs 리스트를 self.idx_to_token에 저장합니다.
  • self.token_to_idx = {token: i for i, token in enumerate(self.idx_to_token)}
    • vocabs 리스트를 사용하여 토큰을 인덱스로 매핑하는 사전을 생성합니다.
  • self.use_padding = use_padding
    • 패딩 사용 여부를 저장합니다.
  • self.max_padding = max_padding
    • 최대 패딩 길이를 저장합니다.
  • self.pad_token = pad_token
    • 패딩 토큰을 저장합니다.
  • self.unk_token = unk_token
    • 알려지지 않은 단어 토큰을 저장합니다.
  • self.unk_token_idx = self.token_to_idx[self.unk_token]
    • 알려지지 않은 단어 토큰의 인덱스를 저장합니다.
  • self.pad_token_idx = self.token_to_idx[self.pad_token]
    • 패딩 토큰의 인덱스를 저장합니다.
  • def __call__(self, x: str):
    • 클래스의 __call__ 메서드를 정의합니다. 이 메서드는 입력 문자열 x를 받아서 토큰 ID 리스트로 변환합니다.
  • token_ids = []
    • 토큰 ID를 저장할 리스트를 초기화합니다.
  • token_list = x.split()
    • 입력 문자열 x를 공백 기준으로 분리하여 토큰 리스트를 생성합니다.
  • for token in token_list:
    • 토큰 리스트를 순회하면서 각 토큰을 처리합니다.
  • if token in self.token_to_idx:
    • 토큰이 사전에 존재하는지 확인합니다.
  • token_idx = self.token_to_idx[token]
    • 토큰의 인덱스를 가져옵니다.
  • else:
    • 토큰이 사전에 존재하지 않는 경우.
  • token_idx = self.unk_token_idx
    • 알려지지 않은 단어 토큰의 인덱스를 사용합니다.
  • token_ids.append(token_idx)
    • 토큰 인덱스를 token_ids 리스트에 추가합니다.
  • if self.use_padding:
    • 패딩 사용 여부를 확인합니다.
  • token_ids = token_ids[:self.max_padding]
    • 토큰 ID 리스트를 최대 패딩 길이로 자릅니다.
  • n_pads = self.max_padding - len(token_ids)
    • 필요한 패딩의 개수를 계산합니다.
  • token_ids = token_ids + [self.pad_token_idx] * n_pads
    • 패딩 토큰 인덱스를 추가하여 리스트를 패딩합니다.
  • return token_ids
    • 토큰 ID 리스트를 반환합니다.

 

 

train_dataset에서 첫 번째 문서를 가져와 출력
    이를 tokenizer를 사용하여 토큰 ID 리스트로 변환


       
        sample = train_dataset['document'].iloc[0]
        print(sample)

        tokenizer(sample) # [51, 1, 5, 10485, 1064]



 

 

train_dataset의 'document' 열에 있는 모든 문서를 순회하면서 각 문서를 토큰화
      토큰 리스트의 길이를 [ token_length_list ] 에 저장


       
        token_length_list = []
        for sample in train_dataset['document']:
            token_length_list.append(len(tokenizer(sample)))

 
 

 

 

[ token_length_list ]  토큰 길이의 분포를 시각화


       
      plt.hist(token_length_list)
      plt.xlabel('token length')
      plt.ylabel('count')


 

 

최대 토큰 길이 


       
        max(token_length_list)

 
41

 

 

최대 길이 50으로 패딩을 추가하는 토크나이저를 생성


       
      tokenizer = Tokenizer(vocabs, use_padding=True, max_padding=50)

      print(tokenizer(sample))


  • vocabs: 토크나이저에 사용할 어휘(vocabulary)를 나타내는 변수입니다. 이 변수는 주로 단어와 그에 해당하는 인덱스를 포함하는 리스트나 사전일 것입니다.
  • use_padding=True: 토크나이저가 패딩을 사용할지 여부를 설정하는 옵션입니다. 여기서는 패딩을 사용하도록 설정되어 있습니다.
  • max_padding=50: 패딩을 사용할 때, 문장의 최대 길이를 50으로 설정하는 옵션입니다. 즉, 문장의 길이가 50보다 짧으면 패딩을 추가하고, 50보다 길면 잘라내는 역할을 합니다.

 

 

훈련 및 검증 데이터 불러오기


       

        train_valid_dataset = pd.read_table('ratings_train.txt')
        test_dataset = pd.read_table('ratings_test.txt')


 

 

데이터셋에 포함된 항목의 개수 출력


       
      print(f'train_dataset : {len(train_dataset)}')
      print(f'train_valid_dataset : {len(train_valid_dataset)}')

 
train_dataset : 50000
train_valid_dataset : 150000

 

 

데이터 확인


       
      train_valid_dataset.head()


 

 

데이터프레임을 무작위로 섞기


       
          train_valid_dataset = train_valid_dataset.sample(frac=1.)
          train_valid_dataset.head()



  • train_valid_dataset.sample(frac=1.)는 train_valid_dataset 데이터프레임의 모든 행을 무작위로 섞습니다.
  • frac=1.는 데이터프레임의 전체 행을 선택하겠다는 의미입니다. 따라서 데이터프레임의 모든 행이 무작위로 섞입니다.
  • 섞인 데이터프레임은 train_valid_dataset 변수에 다시 저장됩니다.

 

 

train_valid_dataset  | 훈련 데이터셋(80%) 검증 데이터셋(20%)



        train_ratio =
0.8
        n_train = int(len(train_valid_dataset) * train_ratio)
        train_df = train_valid_dataset[:n_train]
        valid_df = train_valid_dataset[n_train:]
     
        print(f'train samples : {len(train_df)}')
        print(f'valid samples : {len(valid_df)}')
        print(f'test samples:  {len(valid_df)}')


train samples : 120000
valid samples : 30000
test samples:  30000

 

 

 

훈련 / 검증 / 테스트 데이터셋 | 10%씩 무작위로 샘플링

      
 
        # 1/10으로 샘플링
        train_df = train_df.sample(frac=0.1)
        valid_df = valid_df.sample(frac=0.1)
        test_df = test_df.sample(frac=0.1)

        print(f'train samples : {len(train_df)}')
        print(f'valid samples : {len(valid_df)}')
        print(f'test samples:  {len(test_df)}')


train samples : 12000
valid samples : 3000
test samples:  5000

 

 

 

◼ <클래스 : NSMCDataset  >  훈련, 검증 및 테스트 데이터셋을 생성


       
        class NSMCDataset(Dataset):
            def __init__(self, data_df, tokenizer=None):
                self.data_df = data_df
                self.tokenizer = tokenizer

            def __len__(self):
                return len(self.data_df)

            # doc, label, doc_ids,
            def __getitem__(self, idx):
                sample_raw = self.data_df.iloc[idx]
                sample = {}
                sample['doc'] = str(sample_raw['document'])
                sample['label'] = int(sample_raw['label'])
                if self.tokenizer is not None:
                  sample['doc_ids'] = self.tokenizer(sample['doc'])
                return sample
 

        train_dataset = NSMCDataset(data_df=train_df, tokenizer=tokenizer)
        valid_dataset = NSMCDataset(data_df=valid_df, tokenizer=tokenizer)
        test_dataset = NSMCDataset(data_df=test_df, tokenizer=tokenizer)


  • class NSMCDataset(Dataset):
    • NSMCDataset 클래스를 정의합니다. 이 클래스는 torch.utils.data.Dataset을 상속받습니다.
  • def __init__(self, data_df, tokenizer=None):
    • __init__ 메서드는 클래스의 초기화 메서드입니다.
    • data_df는 데이터셋으로 사용될 pandas 데이터프레임입니다.
    • tokenizer는 선택적인 인자로, 문서를 토큰화하는 함수 또는 객체입니다.
    • self.data_df와 self.tokenizer를 초기화합니다.

데이터셋 길이 반환

  • def __len__(self):
    • __len__ 메서드는 데이터셋의 길이를 반환합니다.
    • return len(self.data_df)는 데이터프레임의 행 수를 반환합니다.

샘플 반환

  • def __getitem__(self, idx):
    • __getitem__ 메서드는 주어진 인덱스 idx에 해당하는 샘플을 반환합니다.
    • sample_raw = self.data_df.iloc[idx]:
      • 데이터프레임에서 인덱스 idx에 해당하는 행을 가져옵니다.
    • sample = {}:
      • 빈 딕셔너리를 생성합니다.
    • sample['doc'] = str(sample_raw['document']):
      • document 열의 값을 문자열로 변환하여 sample 딕셔너리에 저장합니다.
    • sample['label'] = int(sample_raw['label']):
      • label 열의 값을 정수로 변환하여 sample 딕셔너리에 저장합니다.
    • if self.tokenizer is not None::
      • tokenizer가 주어지면, 문서를 토큰화하여 doc_ids에 저장합니다.
    • return sample:
      • 최종적으로 sample 딕셔너리를 반환합니다.

 

데이터셋 생성(훈련/검증/테스트)


       
 
        train_dataset = NSMCDataset(data_df=train_df, tokenizer=tokenizer)
        valid_dataset = NSMCDataset(data_df=valid_df, tokenizer=tokenizer)
        test_dataset = NSMCDataset(data_df=test_df, tokenizer=tokenizer)


  • train_dataset = NSMCDataset(data_df=train_df, tokenizer=tokenizer):
    • train_df 데이터프레임과 tokenizer를 사용하여 훈련 데이터셋을 생성합니다.
  • valid_dataset = NSMCDataset(data_df=valid_df, tokenizer=tokenizer):
    • valid_df 데이터프레임과 tokenizer를 사용하여 검증 데이터셋을 생성합니다.
  • test_dataset = NSMCDataset(data_df=test_df, tokenizer=tokenizer):
    • test_df 데이터프레임과 tokenizer를 사용하여 테스트 데이터셋을 생성합니다.

 

 

문서 / 레이블 /  토큰화된 문서 ID  (토크나이저가 주어진 경우) 출력


       

      print(train_dataset[0])


{
'doc': '기덕이의 쓰레기 시리즈',
'label': 0,
'doc_ids': [1, 47, 397, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
이 코드는 train_dataset의 첫 번째 샘플을 출력합니다.
train_dataset은 NSMCDataset 클래스의 인스턴스이므로,
train_dataset[0]은 NSMCDataset 클래스의 __getitem__ 메서드를 호출하여 첫 번째 샘플을 반환합니다.

__getitem__ 메서드의 동작

  1. idx가 0인 경우, train_dataset[0]은 train_dataset의 첫 번째 샘플을 가져옵니다.
  2. __getitem__ 메서드가 호출되어 data_df.iloc[0]을 사용하여 데이터프레임의 첫 번째 행을 가져옵니다.
  3. sample_raw는 첫 번째 행의 데이터를 포함합니다.
  4. sample 딕셔너리가 생성되어 다음과 같은 키-값 쌍을 포함합니다:
    • 'doc': sample_raw['document']의 문자열 변환 값
    • 'label': sample_raw['label']의 정수 변환 값
    • 'doc_ids' (선택 사항): tokenizer가 주어졌다면, tokenizer(sample['doc'])의 결과

 

 

 

< 함수: collate_fn > 여러 샘플을 하나의 배치(batch)로 병합


       
        def collate_fn(batch):
            keys = [key for key in batch[0].keys()]
            data = {key: [] for key in keys}
            for item in batch:
              for key in keys:
                data[key].append(item[key])
            return data


 

 


DataLoader  |  훈련 / 검증 / 테스트 데이터셋을 배치로 로드


       
        train_dataloader = DataLoader(
            train_dataset,
            batch_size=128,
            collate_fn=collate_fn,
            shuffle=True
        )

        valid_dataloader = DataLoader(
            valid_dataset,
            batch_size=128,
            collate_fn=collate_fn,
            shuffle=False
        )

        test_dataloader = DataLoader(
            test_dataset,
            batch_size=128,
            collate_fn=collate_fn,
            shuffle=False
        )

 
  • DataLoader는 dataset을 기반으로 배치를 생성합니다.
  • batch_size=128은 한 배치에 포함될 샘플 수를 의미합니다. 여기서는 배치 크기가 128입니다.
  • collate_fn=collate_fn배치를 병합할 때 사용할 함수입니다. 이 함수는 이전에 정의된 collate_fn입니다.
  • shuffle=True는 데이터셋을 배치로 나누기 전에 데이터를 무작위로 섞는 것을 의미합니다. 이는 모델 훈련 시 데이터의 순서가 학습에 영향을 주지 않도록 하기 위해 사용됩니다.

 

 

문서 / 레이블 /  토큰화된 문서 ID  (토크나이저가 주어진 경우) 출력


       
        sample = next(iter(test_dataloader))

        sample.keys() # dict_keys(['doc','label','doc_ids])

        sample['doc'][3] # 정말 재미지게 오랫동안 보게되는 드라마

        print(sample['doc_ids'][3]) # [4, 17366, 2223, 2798, 52, 0 ... 0 ]



  • test_dataloader에서 iter() 함수를 사용하여 반복자(iterator)를 생성하고,
    next() 함수를 사용하여 첫 번째 배치를 가져옵니다.
  • sample은 배치 내의 데이터를 담고 있는 딕셔너리입니다.

 

 

 

3. CBOW(

- 자연어 처리에서 단어의 의미를 백터로 표현하는 Word2Vec 모델 중 하나
- 문맥(context) 단어들을 사용해서 타겟(target) 단어를 예측하는 것

 

1. 모델의 원리

  • 문장은 단어의 연속으로 구성
    • 예) The cat sat on the mat
    • 타켓단어: cat, 주변 단어: ('The, sat')
  • 문맥 단어의 수를 결정하는 윈도우 크기 설정
    • 예) 윈도우 크기 : 2, 타켓 단어를 왼쪽과 오른쪽에서 각각 2개의 단어를 문맥 단어로 사용
  • 모든 단어를 고유한 인덱스로 매핑하고 원 핫 인코딩으로 변환
  • 입력으로 주어진 문맥 단어들을 이용해 타겟 단어를 예측하는 신경망을 학습
    • 예) 일반적으로 입력층, 은닉층, 출력층 3개의 층으로 구성
    • 입력층: 문맥 단어들의 원 핫 인코딩 벡터를 받음
    • 은닉층: 입력 벡터들의 평균을 계산하여 은닉층 벡터를 만듦
    • 출력층: 은닉층 벡터를 사용해 타겟 단어를 예측 것

 

 

◼ < 클래스 :  CBOW (Continuous Bag of Words) >
     주변 단어들을 사용하여 중심 단어를 예측하는 모델


       

        class CBOW(nn.Module):
            def __init__(self, vocab_size, embed_dim):
                super().__init__()
                self.output_dim = embed_dim
                self.embedding = nn.Embedding(vocab_size, embed_dim, padding_idx=0)
               
            def forward(self, x):
                # (batch_size, sequence) -> (batch_size, sequence, embed_dim)
                x_embeded = self.embedding(x)
                stnc_repr = torch.mean(x_embeded, dim=1) # batch_size * embed_dim
                return stnc_repr



  1. class CBOW(nn.Module):
    • PyTorch의 nn.Module을 상속받아 CBOW라는 새로운 신경망 모듈 클래스를 정의합니다.
  2. def __init__(self, vocab_size, embed):
    • 클래스의 생성자 메서드를 정의합니다. 이 메서드는 어휘의 크기(vocab_size)와 임베딩 차원(embed)을 인자로 받습니다.
  3. super().__init__()
    • 상위 클래스 nn.Module의 생성자를 호출하여 기본 초기화를 수행합니다.
  4. self.output_dim = embed_dim
    • 인자로 받은 embed_dim을 self.output_dim에 저장합니다. 여기서 embed_dim은 임베딩 벡터의 차원을 나타냅니다.
  5. self.embeddings = nn.Embedding(vocab_size, embed_dim, padding_idx=0)
    • 단어 임베딩 레이어를 정의합니다. 이 레이어는 vocab_size 크기의 단어 집합을 embed_dim 차원의 임베딩 벡터로 변환합니다. padding_idx=0은 패딩 토큰의 인덱스를 지정합니다.
  6. def forward(self, x):
    • 신경망의 순전파(forward) 메서드를 정의합니다. 이 메서드는 입력 텐서 x를 받아서 처리합니다.
  7. x_embeded = self.embeddings(x)
    • 입력 텐서 x를 임베딩 레이어를 통해 임베딩 벡터로 변환합니다. 이 결과는 (batch_size, sequence_length, embed_dim) 형태의 텐서가 됩니다.
  8. stnc_repr = torch.mean(x_embeded, dim=1)
    • 시퀀스 차원(dim=1)을 따라 평균을 계산하여 문장의 대표 벡터를 생성합니다. 이 결과는 (batch_size, embed_dim) 형태의 텐서가 됩니다.
  9. return stnc_repr
    • 문장의 대표 벡터를 반환합니다.
  • 주석
더보기

       
 
        import torch
        import torch.nn as nn

        class CBOW(nn.Module):
            def __init__(self, vocab_size, embed_dim):
                super().__init__()
                # 임베딩 차원의 크기를 설정합니다.
                self.output_dim = embed_dim
                # 임베딩 층을 정의합니다. vocab_size는 어휘 사전의 크기, embed_dim은 임베딩 벡터의 차원입니다.
                # padding_idx=0은 패딩 토큰을 나타내며, 임베딩 벡터는 0 벡터로 초기화됩니다.
                self.embedding = nn.Embedding(vocab_size, embed_dim, padding_idx=0)
                     
            def forward(self, x):
                # 입력 x는 크기가 (batch_size, sequence)인 텐서입니다.
                # Embedding 층을 통과하여 각 단어 인덱스를 임베딩 벡터로 변환합니다.
                # 결과는 크기가 (batch_size, sequence, embed_dim)인 텐서입니다.
                x_embedded = self.embedding(x)
                # 각 문장에서 임베딩 벡터의 평균을 계산합니다. dim=1을 지정하여 sequence 차원에 대해 평균을 구합니다.
                # 결과는 크기가 (batch_size, embed_dim)인 텐서입니다.
                stnc_repr = torch.mean(x_embedded, dim=1)
                # 문장의 임베딩 벡터를 반환합니다.
                return stnc_repr



 

 

 

◼ <클래스: Classifier > 분류기 모델
주어진 단어 임베딩 모델(sr_model)을 기반으로 하여 분류 작업 수행


       
        class Classifier(nn.Module):
          def __init__(self, sr_model, output_dim, vocab_size, embed_dim, **kwargs):
            super().__init__()
            self.sr_model = sr_model(vocab_size=vocab_size, embed_dim=embed_dim, **kwargs)
            self.input_dim = self.sr_model.output_dim
            self.output_dim = output_dim
            self.fc = nn.Linear(self.input_dim,  self.output_dim)

          def forward(self, x):
            return self.fc(self.sr_model(x))



  1. class Classifier(nn.Module):
    • PyTorch의 nn.Module을 상속받아 Classifier라는 새로운 신경망 모듈 클래스를 정의합니다.
  2. def __init__(self, sr_model, output_dim, vocab_size, embed_dim, **kwargs):
    • 클래스의 생성자 메서드를 정의합니다. 이 메서드는 시퀀스 표현 모델(sr_model), 출력 차원(output_dim), 어휘 크기(vocab_size), 임베딩 차원(embed_dim), 및 추가 인자(**kwargs)를 받습니다.
  3. super().__init__()
    • 상위 클래스 nn.Module의 생성자를 호출하여 기본 초기화를 수행합니다.
  4. self.sr_model = sr_model(vocab_size=vocab_size, embed_dim=embed_dim, **kwargs)
    • 입력된 시퀀스 표현 모델(sr_model)을 초기화합니다. vocab_size와 embed_dim을 포함한 추가 인자들을 전달하여 sr_model을 생성합니다.
  5. self.input_dim = self.sr_model.output_dim
    • 시퀀스 표현 모델의 출력 차원을 self.input_dim에 저장합니다. 이는 분류기의 입력 차원이 됩니다.
  6. self.output_dim = output_dim
    • 분류기의 출력 차원을 self.output_dim에 저장합니다.
  7. self.fc = nn.Linear(self.input_dim, self.output_dim)
    • 선형 레이어를 정의합니다. 이 레이어는 self.input_dim 크기의 입력을 받아 self.output_dim 크기의 출력을 반환합니다.
  8. def forward(self, x):
    • 신경망의 순전파(forward) 메서드를 정의합니다. 이 메서드는 입력 텐서 x를 받아서 처리합니다.
  9. return self.fc(self.sr_model(x))
    • 입력 텐서 x를 시퀀스 표현 모델(self.sr_model)을 통해 시퀀스 표현으로 변환한 후, 선형 레이어(self.fc)를 통해 분류 결과를 반환합니다.
  • 주석
더보기

       
            import torch.nn as nn

            class Classifier(nn.Module):
                def __init__(self, sr_model, output_dim, vocab_size, embed_dim, **kwargs):
                    super().__init__()
                    # 주어진 sr_model로부터 단어 임베딩 모델을 생성합니다.
                    self.sr_model = sr_model(vocab_size=vocab_size, embed_dim=embed_dim, **kwargs)
                    # 입력 차원은 임베딩 모델의 출력 차원과 같습니다.
                    self.input_dim = self.sr_model.output_dim
                    # 출력 차원은 분류기에서 정의한 출력 차원입니다.
                    self.output_dim = output_dim
                    # Fully connected layer를 정의합니다. 입력은 임베딩 모델의 출력 차원, 출력은 분류기의 출력 차원입니다.
                    self.fc = nn.Linear(self.input_dim, self.output_dim)

                def forward(self, x):
                    # 주어진 입력 데이터 x를 임베딩 모델에 통과시키고,
                    # 그 결과를 fully connected layer에 통과시켜 출력을 계산합니다.
                    return self.fc(self.sr_model(x))


 

 

 

CBOW 모델을 사용하여 단어 임베딩을 구성하고, 이를 기반으로 분류를 수행


       
          model= Classifier(sr_model=CBOW, output_dim=2, vocab_size=len(vocabs), embed_dim=16)

          model.sr_model.embedding.weight[1]



tensor([-0.4840,  0.6809, -0.2001,  0.4840, -1.2526,  0.1653, -0.8083, -0.2570,
         2.8789, -0.9947, -1.7143, -0.0661,  0.0123,  0.1575, -0.1187, -1.3056],
       grad_fn=<SelectBackward0>)
    • Classifier 클래스의 인스턴스를 생성하는 것입니다.
      sr_model=CBOW: Classifier 클래스의 생성자에 CBOW 클래스를 전달하여 단어 임베딩 모델로 사용합니다.
      즉, CBOW 클래스가 단어 임베딩을 담당하는 모델로 설정됩니다.
    • output_dim=2: 분류기의 출력 차원을 2로 설정합니다. 이는 분류 문제에서 출력 클래스의 개수를 나타냅니다.
    • vocab_size=len(vocabs): 어휘 사전의 크기를 설정합니다. vocabs는 어휘 사전으로, 단어의 종류를 포함하는 리스트나 데이터 구조일 것입니다.
    • embed_dim=16: 단어 임베딩의 차원을 16으로 설정합니다. 각 단어는 16차원의 실수 벡터로 표현될 것입니다.


  • model 객체의 sr_model 속성에서 embedding 객체의 weight 행렬에서 인덱스가 1인 행을 선택합니다.
  • model.sr_model: Classifier 객체의 sr_model 속성으로, 이는 CBOW 클래스의 인스턴스입니다.
  • embedding: CBOW 클래스의 embedding 속성으로, 이는 nn.Embedding 클래스의 인스턴스입니다.
  • weight: nn.Embedding 클래스의 가중치 행렬입니다. 이 행렬은 모든 단어의 임베딩 벡터를 포함합니다.
  • [1]: 행렬에서 인덱스가 1인 행을 선택합니다. 이는 두 번째 단어의 임베딩 벡터를 의미할 수 있습니다. PyTorch의 인덱싱은 0부터 시작하므로, 실제 첫 번째 단어가 아닌 두 번째 단어에 해당하는 임베딩 벡터를 선택합니다.
  • 다른예

 

 

CUDA 사용 가능 여부를 확인, 가능한 경우 모델을 GPU로 이동


     
        use_cuda = True and torch.cuda.is_available()

        if use_cuda:
          model.cuda()

 
  • torch.cuda.is_available() 함수는 현재 시스템에서 CUDA(Compute Unified Device Architecture)를 사용할 수 있는지 여부를 확인합니다. CUDA는 NVIDIA GPU를 사용하여 수치 연산을 가속화하는 기술입니다.
  • use_cuda = True and torch.cuda.is_available()는 True와 torch.cuda.is_available()의 반환값인 True를 논리 연산자 and로 연결한 것입니다. 따라서 use_cuda는 CUDA를 사용할 수 있는지 여부에 따라 True 또는 False가 됩니다.
  • use_cuda가 True인 경우에만 실행되는 조건문입니다.
  • model.cuda()는 PyTorch 모델인 model을 GPU 메모리로 이동시키는 역할을 합니다. 즉, 모델의 연산을 GPU에서 수행하도록 설정하는 것입니다.
  • 이 코드는 CUDA를 사용할 수 있는 환경에서 model을 GPU로 이동시키는 역할을 합니다. 이로 인해 모델의 연산이 GPU를 활용하여 더 빠르게 수행될 수 있습니다.

 

 

◼ 학습에 필요한 최적화 함수 손실 함수를 설정

Adam 최적화 알고리즘은 모델 파라미터를 업데이트,
CrossEntropyLoss는 모델의 출력과 실제 레이블 간의 손실을 계산,
이를 최소화하는 방향으로 학습이 진행


       
        optimizer = optim.Adam(params=model.parameters(), lr=0.01)
        calc_loss = nn.CrossEntropyLoss()


  • optim.Adam은 Adam 최적화 알고리즘을 구현한 클래스입니다. Adam은 경사 하강법의 한 종류로, 각 매개변수에 대해 적응적인 학습률을 사용하여 최적화를 수행합니다.
  • params=model.parameters(): 최적화할 모델의 매개변수들을 model.parameters()로 지정합니다. 모델의 parameters() 메서드는 모델 내의 모든 학습 가능한 매개변수들을 반환합니다.
  • lr=0.01: 학습률(learning rate)을 0.01로 설정합니다. 학습률은 각 반복에서 모델 파라미터를 업데이트할 양을 결정하는 요소로, 너무 작으면 학습이 느리게 진행되고, 너무 크면 발산할 수 있습니다.
  • nn.CrossEntropyLoss()는 크로스 엔트로피 손실 함수를 나타냅니다. 이 함수는 주로 다중 클래스 분류 문제에서 사용됩니다.
  • CrossEntropyLoss는 예측값과 실제값 사이의 크로스 엔트로피 손실을 계산합니다. 모델의 출력값이 클래스 확률 분포로 나와야 하며, 실제 레이블은 정수 형태로 제공되어야 합니다.

 

모델을 학습 / 검증 | 최적의 모델을 선택하는 과정

학습 중에는 훈련 데이터셋을 사용하여 모델을 업데이트,
검증 데이터셋을 사용하여 모델의 성능을 평가

 
     





        n_epoch = 10
        global_i = 0

        valid_loss_history = []
        train_loss_history = []

        best_model = None
        best_epoch_i = None
        min_valid_loss = 9e+9





  1. 변수 초기화 및 설정:
    • n_epoch: 전체 학습 과정에서 반복할 에포크(epoch) 수를 설정합니다.
    • global_i: 전체 반복 횟수를 카운트하는 변수로, 주로 로그 출력을 위해 사용됩니다.
    • valid_loss_history, train_loss_history: 학습 중간에 나타나는 검증 손실과 훈련 손실을 기록하기 위한 리스트입니다.
    • best_model: 최적의 성능을 보인 모델을 저장하기 위한 변수입니다.
    • best_epoch_i: 최적의 성능을 보인 에포크의 인덱스를 저장합니다.
    • min_valid_loss: 검증 손실의 최소값을 저장하는 변수로, 최적 모델을 찾기 위해 사용됩니다.



        
for epoch_i in range(n_epoch):
            model.train()



  1. 에포크 반복문:
    • n_epoch 만큼의 반복을 시작합니다. 각 반복은 한 번의 에포크를 의미합니다. model.train()은 모델을 학습 모드로 설정합니다.

           
for batch in train_dataloader:
                optimizer.zero_grad()
                X = torch.tensor(batch['doc_ids'])
                y = torch.tensor(batch['label'])
                if use_cuda:
                    X = X.cuda()
                    y = y.cuda()

                y_pred = model(X)
                loss = calc_loss(y_pred, y)

                if global_i % 1000 == 0:
                    print(f'i: {global_i}, epoch: {epoch_i}, loss: {loss.item()}')

                train_loss_history.append((global_i, loss.item()))

                loss.backward()
                optimizer.step()
                global_i += 1


  1. 훈련 데이터셋 반복:
    • train_dataloader에서 미니배치(batch)를 하나씩 가져와서 학습을 수행합니다.
    • optimizer.zero_grad(): optimizer의 gradient를 초기화합니다.
    • X, y: 미니배치에서 문서(doc_ids)와 레이블(label)을 가져옵니다. 이들은 PyTorch Tensor로 변환됩니다.
    • model(X): 모델에 입력을 전달하여 예측값을 계산합니다.
    • calc_loss(y_pred, y): 예측값과 실제 레이블을 비교하여 손실(loss)을 계산합니다.
    • loss.backward(): 손실에 대한 gradient를 계산합니다.
    • optimizer.step(): 계산된 gradient를 사용하여 모델의 매개변수를 업데이트합니다.
    • train_loss_history: 훈련 손실을 기록합니다.
    • global_i: 전체 반복 횟수를 증가시킵니다.


            model.
eval()

            valid_loss_list = []
            for batch in valid_dataloader:
                X = torch.tensor(batch['doc_ids'])
                y = torch.tensor(batch['label'])

                if use_cuda:
                    X = X.cuda()
                    y = y.cuda()

                y_pred = model(X)
                loss = calc_loss(y_pred, y)
                valid_loss_list.append(loss.item())

            valid_loss_mean = np.mean(valid_loss_list)
            valid_loss_history.append((global_i, valid_loss_mean.item()))


  1. 검증 데이터셋 반복:
    • model.eval(): 모델을 평가 모드로 설정합니다. 이는 dropout이나 배치 정규화와 같은 층들이 평가 모드로 동작하도록 합니다.
    • valid_loss_list: 검증 데이터셋을 사용하여 손실을 계산하기 위한 빈 리스트를 생성합니다.
    • for batch in valid_dataloader: 검증 데이터셋에서 미니배치를 하나씩 가져와서 손실을 계산합니다.
    • valid_loss_mean: 모든 검증 데이터셋에 대한 평균 손실을 계산합니다.
    • valid_loss_history: 검증 손실의 평균을 기록합니다.




            
if valid_loss_mean < min_valid_loss:
                min_valid_loss = valid_loss_mean
                best_epoch_i = epoch_i
                best_model = deepcopy(model)



  1. 최적 모델 선정:
    • min_valid_loss: 현재까지 관측된 최소 검증 손실을 업데이트하고, 이를 valid_loss_mean과 비교합니다.
    • best_epoch_i: 현재까지 최소 검증 손실을 갖는 에포크의 인덱스를 저장합니다.
    • best_model: 현재까지 관측된 최적의 모델을 deepcopy하여 저장합니다.

            
if epoch_i % 2 == 0:
                print("*"*30)
                print(f'valid_loss_mean: {valid_loss_mean}')
                print("*"*30)

  1. 로그 출력:
    • 매 두 번째 에포크(epoch)마다 현재 검증 손실을 출력합니다.


       print(f'best_epoch: {best_epoch_i}')


  1. 최적 에포크 출력:
    • 학습이 완료된 후 최적의 성능을 보인 에포크의 인덱스를 출력합니다.
i: 0, epoch: 0, loss: 0.7254136800765991
******************************
valid_loss_mean: 0.6611488809188207
******************************
******************************
valid_loss_mean: 0.5264493425687155
******************************
******************************
valid_loss_mean: 0.5448057514925798
******************************
******************************
valid_loss_mean: 0.5969234692553679
******************************
******************************
valid_loss_mean: 0.6635242116947969
******************************
best_epoch: 2

  • 전체코드
더보기

       
          n_epoch = 10  # 총 에포크 수
          global_i = 0  # 전체 반복 횟수

          valid_loss_history = []  # 검증 손실 기록 리스트
          train_loss_history = []  # 훈련 손실 기록 리스트

          best_model = None  # 최적의 모델을 저장할 변수
          best_epoch_i = None  # 최적의 에포크 인덱스를 저장할 변수
          min_valid_loss = 9e+9  # 초기 최소 검증 손실 설정

          # 에포크 반복
          for epoch_i in range(n_epoch):
              model.train()  # 모델을 학습 모드로 설정

              # 훈련 데이터셋 반복
              for batch in train_dataloader:
                  optimizer.zero_grad()  # optimizer의 gradient를 초기화

                  X = torch.tensor(batch['doc_ids'])  # 입력 데이터
                  y = torch.tensor(batch['label'])  # 레이블

                  if use_cuda:
                      X = X.cuda()
                      y = y.cuda()

                  y_pred = model(X)  # 모델에 입력 데이터를 전달하여 예측값을 계산
                  loss = calc_loss(y_pred, y)  # 손실 계산

                  # 매 1000번째 반복마다 손실 출력
                  if global_i % 1000 == 0:
                      print(f'i: {global_i}, epoch: {epoch_i}, loss: {loss.item()}')

                  train_loss_history.append((global_i, loss.item()))  # 훈련 손실 기록

                  loss.backward()  # 역전파를 통해 gradient 계산
                  optimizer.step()  # optimizer를 사용하여 모델 파라미터 업데이트
                  global_i += 1  # 전체 반복 횟수 증가

              model.eval()  # 모델을 평가 모드로 설정

              valid_loss_list = []  # 검증 손실을 저장할 리스트 초기화

              # 검증 데이터셋 반복
              for batch in valid_dataloader:
                  X = torch.tensor(batch['doc_ids'])  # 입력 데이터
                  y = torch.tensor(batch['label'])  # 레이블

                  if use_cuda:
                      X = X.cuda()
                      y = y.cuda()

                  y_pred = model(X)  # 모델에 입력 데이터를 전달하여 예측값을 계산
                  loss = calc_loss(y_pred, y)  # 손실 계산
                  valid_loss_list.append(loss.item())  # 검증 손실 기록

              valid_loss_mean = np.mean(valid_loss_list)  # 검증 손실의 평균 계산
              valid_loss_history.append((global_i, valid_loss_mean.item()))  # 검증 손실 기록

              # 현재 검증 손실이 최소 검증 손실보다 작으면 최적 모델을 업데이트
              if valid_loss_mean < min_valid_loss:
                  min_valid_loss = valid_loss_mean  # 최소 검증 손실 업데이트
                  best_epoch_i = epoch_i  # 최적의 에포크 인덱스 업데이트
                  best_model = deepcopy(model)  # 최적의 모델을 deepcopy하여 저장

              # 매 두 번째 에포크마다 현재 검증 손실을 출력
              if epoch_i % 2 == 0:
                  print("*"*30)
                  print(f'valid_loss_mean: {valid_loss_mean}')
                  print("*"*30)

          # 최적의 에포크 인덱스 출력
          print(f'best_epoch: {best_epoch_i}')


 

 

훈련과 검증 손실의 추이를 시각화


       
        def calc_moving_average(arr, win_size=100):
            new_arr = []
            win = []
            for i, val in enumerate(arr):
                win.append(val)
                if len(win) > win_size:
                    win.pop(0)
                new_arr.append(np.mean(win))
            return np.array(new_arr)

        valid_loss_history = np.array(valid_loss_history)
        train_loss_history =  np.array(train_loss_history)
        plt.figure(figsize=(12,8))
        plt.plot(train_loss_history[:,0],
                calc_moving_average(train_loss_history[:,1]), color='blue')
        plt.plot(valid_loss_history[:,0],
                valid_loss_history[:,1], color='red')
        plt.xlabel("step")
        plt.ylabel("loss")


  • 주석
더보기

       
            def calc_moving_average(arrwin_size=100):
                new_arr = []  # 이동 평균을 계산하여 저장할 리스트
                win = []  # 이동 평균을 계산할 윈도우 리스트
                for i, val in enumerate(arr):
                    win.append(val)  # 현재 값을 윈도우에 추가
                    if len(win) > win_size:
                        win.pop(0)  # 윈도우 크기를 유지하기 위해 가장 오래된 값 제거
                    new_arr.append(np.mean(win))  # 윈도우 내 값들의 평균을 계산하여 new_arr에 추가
                return np.array(new_arr)  # 계산된 이동 평균 배열 반환

            valid_loss_history = np.array(valid_loss_history)  # 검증 손실 기록을 NumPy 배열로 변환
            train_loss_history = np.array(train_loss_history)  # 훈련 손실 기록을 NumPy 배열로 변환

            # 손실 추이를 시각화하는 그래프 생성
            plt.figure(figsize=(12, 8))  # 그래프 크기 설정
            plt.plot(train_loss_history[:, 0], calc_moving_average(train_loss_history[:, 1]), color='blue')  # 훈련 손실의 이동 평균을 파란색으로 플롯
            plt.plot(valid_loss_history[:, 0], valid_loss_history[:, 1], color='red')  # 검증 손실을 빨간색으로 플롯
            plt.xlabel("step")  # x축 레이블 설정
            plt.ylabel("loss")  # y축 레이블 설정

 
 

 

테스트 데이터셋에서 정확도를 계산


   

        model = best_model  # 최적의 모델을 선택하여 model 변수에 할당

        model.eval()  # 모델을 평가 모드로 설정 (드롭아웃, 배치 정규화 등의 동작이 평가 모드로 변경됨)

        total = 0  # 전체 예측 수 초기화
        correct = 0  # 정확하게 예측된 수 초기화

        # 테스트 데이터셋의 각 배치에 대해 반복하면서 예측 정확도 계산
        for batch in tqdm(test_dataloader, total=len(test_dataloader.dataset)//test_dataloader.batch_size):
            X = torch.tensor(batch['doc_ids'])  # 입력 데이터
            y = torch.tensor(batch['label'])  # 실제 레이블

            if use_cuda:
                X = X.cuda()
                y = y.cuda()

            y_pred = model(X)  # 모델을 사용하여 예측값 계산

            # 예측값과 실제 레이블을 비교하여 정확한 예측 수 계산
            curr_correct = y_pred.argmax(dim=1) == y

            total += len(curr_correct)  # 현재 배치의 전체 예측 수를 누적
            correct += sum(curr_correct)  # 현재 배치에서 정확하게 예측된 수를 누적

        # 전체 테스트 데이터셋에 대한 정확도 계산 및 출력
        print(f'test accuracy: {correct/total}')



0.7779250144958496

 

 

 

'AI > 자연어처리' 카테고리의 다른 글

11. LSTM과 GRU  (0) 2024.07.02
10. CNN Text Classification  (0) 2024.07.02
08. RNN 기초  (0) 2024.06.27
07. 워드 임베딩 시각화  (0) 2024.06.27
06. 자연어처리 - 워드 임베딩  (0) 2024.06.25