KlueBERT를 활용한 뉴스 세 줄 요약 서비스_3(ft.모델)

shooting star·2023년 3월 23일
5

들어가며

이번 포스팅은 KlueBERT를 활용한 뉴스 세 줄 요약의 모델에 대해서 살펴보도록 하겠다. 먼저 어떤 사전학습 모델을 사용하였는지 살펴볼 것이고, RNN, Classifier, Transformer 부분의 클래스를 살펴볼 것이다. 그리고 모델 학습과 하이퍼파라미터를 어떻게 지정했는지에 관해서 간략하게 살펴보고자 한다.

모델링

1. 사용한 모델

이번 프로젝트에서 사용한 사전학습된 모델은 klue/bert-base이다. 해당 모델은 한국어로 된 MODU, CC-100-Kor, NAMUWIKI, NEWSCRAWL, PETITION의 총 62GB의 데이터를 통해 학습한 모델이다.

2. 모델 구조

(1) Classifier

class Classifier(nn.Module):
    def __init__(self, hidden_size):
        super(Classifier, self).__init__()
        self.linear1 = nn.Linear(hidden_size, 1)
        self.sigmoid = nn.Sigmoid()

    def forward(self, x, mask_cls):
        h = self.linear1(x).squeeze(-1)
        sent_scores = self.sigmoid(h) * mask_cls.float()
        return sent_scores

이 코드는 PyTorch를 사용하여 BERT 기반의 추출 요약을 수행하기 위한 Classifier 클래스를 정의하는 부분이다. 이 부분에 대해서 간략하게 설명하도록 하겠다.

  • 클래스 정의: class Classifier(nn.Module):는 Classifier라는 이름의 PyTorch 모듈을 정의한다. 이 클래스는 추출 요약을 위한 분류기로 사용이된다.

  • 생성자: def __init__(self, hidden_size):는 Classifier 객체를 초기화하는 생성자 메소드이다. hidden_size 매개변수는 입력 벡터의 크기를 나타낸다.

    • super(Classifier, self).__init__()는 nn.Module 클래스의 생성자를 호출하여 초기화한다.
    • self.linear1 = nn.Linear(hidden_size, 1)는 입력 벡터의 크기가 hidden_size인 선형 레이어를 생성하고, 출력 크기를 1로 설정한다.
    • self.sigmoid = nn.Sigmoid()는 시그모이드 활성화 함수를 생성한다.
  • 순방향 계산: def forward(self, x, mask_cls):는 순방향 계산을 수행하는 메소드이다. x는 입력 텐서를 나타내고, mask_cls는 문장 구분을 위한 마스크 텐서를 나타낸다.

    • h = self.linear1(x).squeeze(-1)는 입력 텐서 x를 선형 레이어에 통과시키고, 결과 텐서의 마지막 차원을 제거한다. 이렇게 함으로써, 각 입력 벡터에 대한 스칼라 값이 출력된다.
    • sent_scores = self.sigmoid(h) * mask_cls.float()는 선형 레이어의 출력에 시그모이드 활성화 함수를 적용한 다음, 마스크 텐서와 요소별로 곱한다. 이렇게 하면, 문장 점수가 계산되며, 마스크된 부분은 0이 된다.

이 Classifier 클래스는 주어진 입력에 대해 문장 점수를 계산한다. 이 점수는 추출 요약 과정에서 문장의 중요도를 결정하는 데 사용된다.

(2) RNN

class RNNEncoder(nn.Module):

    def __init__(self, bidirectional, num_layers, input_size,
                 hidden_size, dropout=0.0):
        super(RNNEncoder, self).__init__()
        num_directions = 2 if bidirectional else 1
        assert hidden_size % num_directions == 0
        hidden_size = hidden_size // num_directions

        self.rnn = LayerNormLSTM(
            input_size=input_size,
            hidden_size=hidden_size,
            num_layers=num_layers,
            bidirectional=bidirectional)

        self.wo = nn.Linear(num_directions * hidden_size, 1, bias=True)
        self.dropout = nn.Dropout(dropout)
        self.sigmoid = nn.Sigmoid()

    def forward(self, x, mask):
        """See :func:`EncoderBase.forward()`"""
        x = torch.transpose(x, 1, 0)
        memory_bank, _ = self.rnn(x)
        memory_bank = self.dropout(memory_bank) + x
        memory_bank = torch.transpose(memory_bank, 1, 0)

        sent_scores = self.sigmoid(self.wo(memory_bank))
        sent_scores = sent_scores.squeeze(-1) * mask.float()
        return sent_scores

이 코드는 PyTorch를 사용하여 RNN 기반의 추출 요약을 수행하기 위한 RNNEncoder 클래스를 정의하는 부분이다. 간략하게 코드의 각 부분을 알아보도록 하겠다.

  • 클래스 정의: class RNNEncoder(nn.Module):는 RNNEncoder라는 이름의 PyTorch 모듈을 정의한다. 이 클래스는 추출 요약을 위한 RNN 인코더로 사용된다.

  • 생성자: def __init__(self, bidirectional, num_layers, input_size, hidden_size, dropout=0.0):는 RNNEncoder 객체를 초기화하는 생성자 메소드이다.

    • super(RNNEncoder, self).__init__()는 nn.Module 클래스의 생성자를 호출하여 초기화한다.
    • num_directions, hidden_size, self.rnn에서 양방향(bidirectional) 여부를 고려하여 RNN의 크기와 방향성을 설정한다. 이 예에서는 LayerNormLSTM이 사용되었다.
    • self.wo = nn.Linear(num_directions * hidden_size, 1, bias=True)는 RNN의 출력을 문장 점수로 변환하는 선형 레이어를 생성한다.
    • self.dropout = nn.Dropout(dropout)는 dropout 레이어를 생성하여 과적합을 방지한다.
    • self.sigmoid = nn.Sigmoid()는 시그모이드 활성화 함수를 생성한다.
  • 순방향 계산: def forward(self, x, mask):는 순방향 계산을 수행하는 메소드이다. x는 입력 텐서를 나타내고, mask는 문장 구분을 위한 마스크 텐서를 나타낸다.

    • 입력 텐서 x의 차원을 변경하고(torch.transpose(x, 1, 0)), RNN에 전달하여 메모리 뱅크를 얻는다.(memorybank, = self.rnn(x)).
    • 메모리 뱅크에 드롭아웃을 적용하고, 입력 텐서를 더한다.(memory_bank = self.dropout(memory_bank) + x).
    • 메모리 뱅크의 차원을 다시 변경한다.(memory_bank = torch.transpose(memory_bank, 1, 0)).
    • 선형 레이어의 출력에 시그모이드 활성화 함수를 적용한 후, 마스크 텐서와 요소별로 곱한다.(sent_scores = self.sigmoid(self.wo(memory_bank)) * mask.float()).

이 RNNEncoder 클래스는 주어진 입력에 대해 문장 점수를 계산한다. 이 점수는 추출 요약 과정에서 문장의 중요도를 결정하는 데 사용된다. 여기서는 BERT를 직접 사용하지는 않지만, 입력 벡터를 얻기 위해 BERT와 같은 다른 모델을 함께 사용할 수 있다.

(3) Transformer

class TransformerEncoderLayer(nn.Module):
    def __init__(self, d_model, heads, d_ff, dropout):
        super(TransformerEncoderLayer, self).__init__()

        self.self_attn = MultiHeadedAttention(
            heads, d_model, dropout=dropout)
        self.feed_forward = PositionwiseFeedForward(d_model, d_ff, dropout)
        self.layer_norm = nn.LayerNorm(d_model, eps=1e-6)
        self.dropout = nn.Dropout(dropout)

    def forward(self, iter, query, inputs, mask):
        if (iter != 0):
            input_norm = self.layer_norm(inputs)
        else:
            input_norm = inputs

        mask = mask.unsqueeze(1)
        context = self.self_attn(input_norm, input_norm, input_norm,
                                 mask=mask)
        out = self.dropout(context) + inputs
        return self.feed_forward(out)

이 코드는 PyTorch를 사용하여 Transformer 기반의 추출 요약을 수행하기 위한 TransformerEncoderLayer 클래스를 정의하는 부분이다. 간단하게 코드의 각 부분을 설명하도록 하겠다.

  • 클래스 정의: class TransformerEncoderLayer(nn.Module):는 TransformerEncoderLayer라는 이름의 PyTorch 모듈을 정의한다. 이 클래스는 추출 요약을 위한 Transformer 인코더 레이어로 사용된다.

  • 생성자: def __init__(self, d_model, heads, d_ff, dropout):는 TransformerEncoderLayer 객체를 초기화하는 생성자 메소드이다.

    • super(TransformerEncoderLayer, self).__init__()는 nn.Module 클래스의 생성자를 호출하여 초기화한다.
    • self.self_attn = MultiHeadedAttention(heads, d_model, dropout=dropout)는 멀티헤드 어텐션 레이어를 생성한다. 멀티헤드 어텐션은 입력 벡터에 대해 여러 개의 어텐션 헤드를 사용하여 다양한 관점에서 정보를 수집한다.
    • self.feed_forward = PositionwiseFeedForward(d_model, d_ff, dropout)는 위치별 피드포워드 네트워크를 생성한다. 이 네트워크는 각 입력 위치에 대해 독립적으로 동작하며, 이전 레이어의 출력을 변환한다.
    • self.layer_norm = nn.LayerNorm(d_model, eps=1e-6)는 레이어 정규화를 생성한다. 레이어 정규화는 각 입력 벡터의 모든 요소에 대해 평균과 분산을 조정하여, 모델의 학습을 안정화한다.
    • self.dropout = nn.Dropout(dropout)는 dropout 레이어를 생성하여 과적합을 방지합니다.
  • 순방향 계산: def forward(self, iter, query, inputs, mask):는 순방향 계산을 수행하는 메소드이다. iter는 반복 횟수, query는 어텐션 쿼리 벡터, inputs는 입력 텐서, mask는 마스크 텐서를 나타낸다.

    • 첫 번째 반복이 아닌 경우, 입력 텐서에 레이어 정규화를 적용한다.(input_norm = self.layer_norm(inputs)).
    • mask = mask.unsqueeze(1)는 마스크 텐서의 차원을 늘려 어텐션 레이어에 전달할 준비를 한다.
    • 멀티헤드 어텐션 레이어를 통해 셀프 어텐션을 수행한다.(context = self.self_attn(input_norm, input_norm, input_norm, mask=mask)).
    • 드롭아웃을 적용하고, 이전 입력과 어텐션 출력을 더한다.(out = self.dropout(context) + inputs).
    • 마지막으로 위치별 피드포워드 네트워크를 통해 출력을 반환한다.(return self.feed_forward(out)).

이 TransformerEncoderLayer 클래스는 주어진 입력에 대해 셀프 어텐션 및 위치별 피드포워드 연산을 수행하여 새로운 출력 벡터를 생성한다. 이 클래스는 추출 요약을 위해 사용되며, 입력 벡터를 얻기 위해 BERT와 같은 다른 모델과 함께 사용할 수 있다. 전체 Transformer 인코더는 이 레이어를 여러 개 쌓아서 구성된다.

3. 학습 및 하이퍼파라미터


logdirlocation = 'LOG/KLUE'
os.makedirs(logdirlocation, exist_ok=True)

!python SRC/train.py \
  -mode train \
  -encoder transformer \
  -dropout 0.1 \
  -bert_data_path data/bert_data/train/korean \
  -model_path MODEL/KLUE/bert_transformer_result \
  -lr 2e-3 \
  -visible_gpus 0 \
  -gpu_ranks 0 \
  -world_size 1 \
  -report_every 1000\
  -save_checkpoint_steps 10000 \
  -batch_size 1000 \
  -decay_method noam \
  -train_steps 50000 \
  -accum_count 2 \
  -log_file LOG/KLUE/bert_transformer_result.txt \
  -use_interval true \
  -warmup_steps 10000 \
  -ff_size 2048 \
  -inter_layers 2 \
  -heads 8

해당 프로젝트를 학습시키는 방법과 하이퍼파라미터는 위의 코드를 실행하면 된다. 최종 학습은 RNN, Classifier, Transformer 중 가장 성능이 좋다고 소개된 Transformer를 사용하고자 한다. 이를 통해서 총 50,000 Step 학습시켜 최종 모델을 학습한다.

마치며

지금까지 해당 프로젝트의 논문, 데이터, 모델에 관해서 알아보았다. 마지막으로 다음 포스팅에서는 평가 방법과 기준에 대해서 살펴보도록 하겠다.

github로 이동하기 : KlueBERT를 활용한 뉴스 세 줄 요약 서비스

0개의 댓글