Overview

  • Text classification 모델을 생성한다.
  • 간단하게 Character embedding layer를 두고 문서의 character embedding 평균 백터를 구하여 Fully Connected Layer를 두어 (2개층) 최종적으로 binary classification을 수행하도록 한다.

Network Module 구현하기

  1. nn.Module을 상속받아 클래스를 생성한다.
  2. init 함수에 네트워크에서 필요한 Layer들(주로 pytorch에서 기본 제공되는 Layer들을 정의함. 물론 사용자 정의 모듈도 정의 가능함)
  3. forward 함수 안에 모듈의 input 부터 output까지 네트워크의 흐름을 구현해준다.

이번 구현에서 필요한 pytorch 기본 제공 모듈 살펴보기

  1. torch.nn.EmbeddingBag
    • Text 관련 네트워크에서 사용하는 Embedding Layer에 평균/합계/최대값 등 특정 계산값을 구하는 부분이 추가된 모듈이다.
    • 이번 예시에서는 EmbeddingBag을 사용하나. 실제로는 주로 Embedding 레이어를 더 많이 사용한다.
  2. torch.nn.Linear
    • 기본적인 Fully Connedted Layer 이다.
    • (주의사항) activation func 는 포함되지 않으며 input * weight 까지만 수행한다.
  3. torch.nn.BatchNorm1d
    • 1차원 batch normalization layer 이다.
    • batch normalization 은 다른 포스팅 혹은 유투브 강의를 살펴보면 된다. "Internal Covariate Shift(ICS)" 라는 term만 잘 이해하면 되긴 한다. (다만,  batch normalization 의 성능향상 효과는 ICS 와는 상관이 없었다능... 결국 loss function의 landscape가 smoothing 되는 효과 때문이라능... 그게 요 논문)
    • Andrew Ng 강의를 보면 batch normalization은 regularization 기능도 한다고 한다. 하지만 주 목적은 학습을 빠르게 하는 것이다.
    • (추가) 비교적 최신(최신이라기엔 시간이 많이 흘렀지만..)인 transformers 같은 network 상에서 사용하는 normalization을 보면 Batch normalization 보다 Layer Normalization을 사용하는 것을 볼 수 있다. 요 차이는 normalization을 수행하는 차원은 B(batch)차원으로 하느냐 L(layer) 차원으로 하느냐 차이이다. Batch Normalization이 아래와 같은 한계점 때문에 Layer Normalization 을 더 많이 사용한다고 하니 참고하면 좋을 듯 하다.
      • Batch Normalization 은 Batch 사이즈를 적절히 조절했을 때 제대로 동작한다. 따라서 메모리를 상당히 많이 사용하는 복잡한 모델을 돌릴때 상대적으로 batch 사이즈를 크게 가져갈 수 없는 경우가 발생하고 이에 따라 Batch Normalization 이 제대로 동작하지 않을 수 있다.(Batch 가 너무 커도 잘 동작하지 않는다고 한다.) 반면 Layer 단위로 Normalization을 진행할 경우 이러한 Batch size에 영향을 받지 않는다. (자세한 사항은 batch / layer normalization을 설명한 다른 포스트들을 찾아보면 좋다)
      • (참고사항)배치 정규화를 사용할 경우 학습되는 파라미터 중 bias 부분에 해당하는 베타(학습되는 값)가 있는데 이러한 부분이 기존 fc 에서 별도로 추가해서 사용할 수 있는 bias 와 역할이 동일하다. 따라서 배치 정규화를 사용하는 경우 굳이 바이어스를 사용할 필요가 없다고 한다.
  4. torch.nn.Relu
    • activation layer의 한 종류. 다른 activation 함수들도 살펴 보면 좋다.
    • Relu 의 출현은 'vanishing gradient' 와 관계가 있으니 관련 내용도 찾아보면 좋다.
  5. torch.nn.Dropout
    • Overfitting 방지를 위한 layer 이다.
    • 기본 동작은 일정 확률로 뉴런을 죽이거나 혹은 살린다. DL 프레임워크마다 파라미터로 넘겨주는 값이 때로는 죽이는 확률일때도 있고 살리는 확률일때도 있다. 따라서 사용중인 프레임워크의 기본 파라미터의 의미를 잘 알고 사용하는 것이 좋다. pytorch는 죽이는 확률이 기본 값으로 들어간다.
    • (참고사항) 사실 대부분 high level API를 사용하기 때문에 Dropout을 직접 구현할일이 없지만. 직접 구현시에 단순히 일정 확률로 죽이는것으로 끝나는 것이 아니라 backpropagation에서 죽었던 뉴런에 대한 처리를 따로 해주어야 하기 때문에 추가적인 구현이 필요하다. 이 부분은 Andrew Ng 교수님의 강의에 대략적으로 설명이 되어있다.

(유의사항1) BatchNorm, Activation, Dropout 의 적절한 위치는??

  • Fully Connected Layer를 구축할 때 사용되는 모듈들의 순서를 어떻게 해야할 지 모르겠는 경우가 있다. 특히 위 제목에 있는 것들...
  • 다행히 이러한 궁금증에 대한 대답은 여러 군데에서 찾을 수 있다. 여기에서도.
  • 결론은 FC - BATCH - ACTIVATION - DROPOUT 순이다.
  • 자세한 이유는 구글링...

(유의사항2) Softmax Layer의 부재... 왜?

  • 이전 포스팅에서 대답이 되었지만....
  • 구현상의 이슈로 인해 (exp 함수로 인한 overflow 문제) Softmax 와 log 함수를 합한 log_softmax 함수를 주로 사용하며
  • NLL_loss 를 사용하면 log_softmax를 모듈의 마지막 레이어에 넣어주어야 하며,
  • CrossEntropyLoss 를 사용하려면 log_softmax layer를 생략해야한다.

구현 예시

class MeanCharEmbeddingClsf(nn.Module):
    def __init__(self, vocab_size, char_emb_dim, hidden_dim1, hidden_dim2, class_cnt):
        '''
        define network structure
        :param vocab_size:
        :param char_emb_dim:
        '''
        super(MeanCharEmbeddingClsf, self).__init__()

        self.char_emb_bag = nn.EmbeddingBag(num_embeddings=vocab_size, embedding_dim=char_emb_dim, mode="mean")

        self.fc1 = nn.Linear(char_emb_dim, hidden_dim1)
        self.fc2 = nn.Linear(hidden_dim1, hidden_dim2)
        self.layer_out = nn.Linear(hidden_dim2, class_cnt)

        self.batchnorm1 = nn.BatchNorm1d(hidden_dim1)
        self.batchnorm2 = nn.BatchNorm1d(hidden_dim2)

        self.do = nn.Dropout(0.2)
        self.relu = nn.ReLU()

    def forward(self, char_seqs):
        # char_seqs (B, CSL, CED)
        mean_vec = self.char_emb_bag(char_seqs)

        h1 = self.fc1(mean_vec)
        h1 = self.batchnorm1(h1)
        h1 = self.relu(h1)
        h1 = self.do(h1)

        h2 = self.fc2(h1)
        h2 = self.batchnorm2(h2)
        h2 = self.relu(h2)
        h2 = self.do(h2)

        out = self.layer_out(h2)
        return out

'pytorch' 카테고리의 다른 글

CrossEntropyLoss in pytorch (feat. log_softmax, nll_loss)  (0) 2020.09.30
01. DataSet  (0) 2020.09.28

이론과 구현이 다르다?

  • logistic regression의 이론 공부를 마치고 나면 아래와 같이 생각이 정리된다.
    • output layer에 softmax layer를 두어 weited sum 의 output을 prob 형태로 변경한다.
    • prob 형태로 나온 output에 대해 실제 정답분포와의 cross entropy loss 를 사용하여 loss를 계산한다.
  • 근데... pytorch로 구현할때는...
    • output layer 에 softmax를 취하지 않고 바로 CrossEntropyLoss 를 적용한다...

왜?

  • pytorch 의 CrossEntropyLoss 안에 Softmax (정확하게는 log_softmax) 가 구현되어 있기 때문이다.

왜 이렇게 구현이..???

  • softmax 함수를 취할때 exp 함수를 적용하며, cross entropy를 계산할때 다시 log를 취한다....
  • 그럼 원래대로 돌아온다...
  • 다시 원복될거 두번할 필요도 없을 뿐더러... 더 중요한 이유가 있는데,
  • softmax를 취하기 위해 exp 함수를 적용하면서 overflow 문제가 발생하는 것이 더 주된 이유이다.
    • exp 함수를 취할때 overflow가 발생하면 log를 취하더라도 이미 값은 유실된 후이다(..... 는 뻘소리였나...)
  • 그래서 softmax가 따로 구현되어있지 않는 것임. 자세한 건 여기를 참조
  • 다 집어치우고, softmax + CrossEntropyLoss 를 합쳐서 구현하면... forward / backward 구현이 개 쉬워진다. 더하기 빼기로 구현되어버림... (당연히 계산량이 엄청 줄어듦...) 이제 진짜 이유. (공신력 있는 자료를 참고하는 습관을 갖자...)

'pytorch' 카테고리의 다른 글

02. model(network) 구현하기  (0) 2020.10.03
01. DataSet  (0) 2020.09.28

Dataset

Dataset 사용 전 참고사항

  • CASE1. 훈련 데이터셋을 메모리에 다 들고 있을 정도로 여유가 있는 경우. (아래의 예제를 참조, Dataframe으로 데이터셋 전체를 들고 있는 방법)
  • CASE2. 메모리에 다 들고 있을 수 없을 만큼 데이터의 양이 많은 경우.
    • IterableDataset을 참조

Custom Dataset 생성 방법

  1. torch.util.data.Dataset을 상속
  2. len 함수 구현
    • 전체 데이터셋의 크기를 반환하도록 구현
  3. init 함수 구현
    • Datapoint의 변환메소드나 사전 / Tokenizer 등의 초기화 작업을 진행
  4. getitem 함수 구현
    • 객체[i] 형태처럼 index를 통해 특정 원소를 참조하기 위해 구현하는 함수
  5. iter 함수 구현 (IterableDataset 에서 필요)
    • for 문과 같은 iteration 에서 어떤 순서로 원소를 가져오는 지 구현하는 부분

Transformers 사용하기

  • 일반적으로 Dataset 의 초기화 과정에서는 원본 형태의 Datapoint를 들고 있으며, 이 Datapoint를 torch 의 네트워크에 주입해 주기 위해 Tensor 형태로 바꾸기 위한 일련의 작업 과정이 필요하다. 보통 이러한 데이터 변환을 담당하는 Class 들을 구현한 후 이들의 객체를 리스트로 담아 Dataset의 초기화 인자로 넘겨주어 Dataset에서 리턴하는 모든 datapoint가 동일하게 변환과정을 거치도록 구현한다.

  • 텍스트의 경우, tokenizing / padding / index변환 등의 변환 과정이 구현된 객체가 transformers 인자로 전달된다.

  • 일반적으로 transformer의 마지막 step은 torch 의 Tensor로 변환하는 과정이다.

    class ToIdxTransform(object):
      def __init__(self, vocab_path, max_seq_length = 30, unk_tok = '[UNK]', pad_tok = '[PAD]'):
          with codecs.open(vocab_path, 'r', 'utf-8') as fin:
              chars = [i.strip('\n') for i in fin.readlines() if i.strip()]
    
          self.chars = chars
          self.idx_map = dict(zip(self.chars, range(len(self.chars))))
          self.max_seq_length = max_seq_length
    
          self.unk_tok = unk_tok
          self.pad_tok = pad_tok
    
          assert self.unk_tok in self.idx_map
          assert self.pad_tok in self.idx_map
    
          self.unk_idx = self.idx_map[self.unk_tok]
          self.pad_idx = self.idx_map[self.pad_tok]
    
      def __call__(self, dp):
          d = dp['document']
          char_ids = [self.idx_map.get(c, self.unk_idx) for c in d][:self.max_seq_length]
          char_ids.extend([self.pad_idx] * (self.max_seq_length - len(char_ids)))
    
          dp['document'] = char_ids
          return dp

Custom Dataset 의 구현 예시

class CharacterSequenceDataset(Dataset):
    def __init__(self, df, transformers=None):
        self.df = df
        self.labels = df['label'].map(lambda x : int(x)).to_list()
        self.titles = df['title'].map(lambda x : str(x)).to_list()
        self.transformers = transformers

        assert len(self.labels) == len(self.titles)

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

    def __getitem__(self, idx):
        # Data point
        dp = OrderedDict( {
            'label' : self.labels[idx],
            'title' : self.titles[idx]
        })

        if self.transformers is not None:
            for transformer in self.transformers:
                dp = transformer(dp)

        return dp

'pytorch' 카테고리의 다른 글

02. model(network) 구현하기  (0) 2020.10.03
CrossEntropyLoss in pytorch (feat. log_softmax, nll_loss)  (0) 2020.09.30

+ Recent posts