ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [BERT] BERT에 대해 쉽게 알아보기3 - tf-hub BERT layer를 이용한 캐글 분류문제
    ✨ AI/NLP 2020. 3. 27. 17:41

     

    ● 텐서플로우 허브의 BERT layer를 이용한 캐글 분류문제 예제

     

     

    지난번 포스팅에서 사전학습한 BERT 모델을 다른 NLP task 문제에 전이학습시켜 예제에 적용하는 포스팅을 이번 포스팅에서 작성하려고 하였는데요. 그 이전에 텐서플로우 허브, 허깅페이스 등을 이용해서 multilingual BERT 레이어를 실제로 예제에 어떻게 적용하는지 알아보는 포스팅을 먼저 쓰도록 하겠습니다.

     


     

    ● 텐서플로우 허브(TensorFlow Hub)

     

    텐서플로우 허브는 일반화된 문제들에 대해서 모델의 재사용성을 극대화 하기 위해 구글에서 새로 공개한 API입니다. 텐서플로우 1.7.0 버전 이상에서 사용할 수 있고, pip install을 통해 설치해주어야 합니다. 텐서플로우를 이용하면 지금 사용하려는 사전 훈련된 버트 모델처럼 미리 훈련된 모델을 내가 풀고자하는 문제에 파인튜닝(Fine-tuning)하여 쉽게 사용할 수 있습니다. 

     

    pip install tensorflow-hub

     

    파이토치를 사용하는 유저들은 주로 허깅페이스(Hugging Face) 버트모델을 많이 이용하는 것 같습니다. 버트모델, 트랜스포머 모델 등 다양한 사전훈련된 모델을 텐서플로우, 파이토치용으로 받아 사용할 수 있습니다.

     

     


     

    ● 캐글 아마존 pet product 리뷰 분류문제

     

     

     

     

    리더보드


    약 열달 전쯤 나왔던 문제이고 텍스트 분류 문제 입니다. 리뷰에 관한 내용이 dogs에 관한 것인지, cats에 관한 것인지 그 외 (fish, birds 등)인지 분류하는 문제 입니다..!

     

    데이터에는 id, text, label 3개의 컬럼이 있고 text에는 리뷰내용, label에는 dogs, cats, birds, fish aquatic pets 등 총 6개의 라벨로 나뉘어 집니다. 트레인 데이터에는 dog에 관한 데이터가 54%, cats에 관한 데이터가 36% 그외 동물이 11%인 Imbalanced 데이터 분류 문제로 보입니다.

     


    필요한 훈련데이터

     

    test.zip
    2.98MB
    train.zip
    8.91MB
    valid.zip
    3.01MB

     

     


    저는 코랩 GPU환경에서 실습을 진행하였습니다.

     

    1. 구글 공식 BERT코드와 필요한 패키지 임포트

     

    1
    2
    3
    4
    !wget -q https://raw.githubusercontent.com/google-research/bert/master/modeling.py 
    !wget -q https://raw.githubusercontent.com/google-research/bert/master/optimization.py 
    !wget -q https://raw.githubusercontent.com/google-research/bert/master/run_classifier.py 
    !wget -q https://raw.githubusercontent.com/google-research/bert/master/tokenization.py 
    cs
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    # 필요한 패키지 임포트
    import os
    import numpy as np
    import pandas as pd
    import datetime
    import sys
    import zipfile
    import modeling
    import optimization
    import run_classifier
    import tokenization
     
    from tokenization import FullTokenizer
    from sklearn.preprocessing import LabelEncoder
    import tensorflow as tf
    from sklearn.model_selection import train_test_split
     
    import tensorflow_hub as hub
    from tqdm import tqdm_notebook
    from tensorflow.keras import backend as K
    from tensorflow.keras.models import Model
    cs
    1
    2
    3
    4
    5
    # BERT모델과 tokenization의 파라미터 지정
    sess = tf.Session()
     
    bert_path = "https://tfhub.dev/google/bert_uncased_L-12_H-768_A-12/1"
    max_seq_length = 128
    cs

     


    2. 데이터 로드 및 정제

     

    구글 드라이브에 데이터 파일을 업로드 한 후, 내 구글 드라이브와 코랩 실습 파일은 연동하였습니다.

     

    from google.coalb import drive
    drive.mount('content/drive')

     

     

    1
    2
    3
    4
    # 파일 로드
    train_df = pd.read_csv('/content/drive/My Drive/Colab Notebooks/Kaggle/amazon_pet_product_reviews_classfication/train.csv', index_col='id')
    val_df = pd.read_csv('/content/drive/My Drive/Colab Notebooks/Kaggle/amazon_pet_product_reviews_classfication/valid.csv', index_col='id')
    test_df = pd.read_csv('/content/drive/My Drive/Colab Notebooks/Kaggle/amazon_pet_product_reviews_classfication/test.csv', index_col='id')
    cs

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    label_encoder = LabelEncoder().fit(pd.concat([train_df['label'], val_df['label']]))
     
    X_train_val, X_test = pd.concat([train_df['text'], val_df['text']]).values, test_df['text'].values
     
    y_train_val = label_encoder.fit_transform(pd.concat([train_df['label'], val_df['label']]))
     
    X_train, X_val, y_train, y_val = train_test_split(
            X_train_val,y_train_val, test_size=0.1, random_state=0, stratify = y_train_val
            )
     
    train_text = X_train
    train_text = [' '.join(t.split()[0:max_seq_length]) for t in train_text]
    train_text = np.array(train_text, dtype=object)[:, np.newaxis]
    train_label = y_train
     
    val_text = X_val
    val_text = [' '.join(t.split()[0:max_seq_length]) for t in val_text]
    val_text = np.array(val_text, dtype=object)[:, np.newaxis]
    val_label = y_val
     
    test_text = X_test
    test_text = [' '.join(t.split()[0:max_seq_length]) for t in test_text]
    test_text = np.array(test_text, dtype=object)[:, np.newaxis]
    cs

     


    3. TensorFlow Hub 의 BERT layer 사용하기

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    import tensorflow as tf
    import tensorflow_hub as hub
    import os
    import re
    import numpy as np
    from tqdm import tqdm_notebook
    from keras import backend as K
    from keras.layers import Layer
     
     
    class BertLayer(Layer):
        
        def __init__(self, n_fine_tune_layers=10, tf_hub = None, output_representation = 'pooled_output', trainable = False, **kwargs):
            
            self.n_fine_tune_layers = n_fine_tune_layers
            self.is_trainble = trainable
            self.output_size = 768
            self.tf_hub = tf_hub
            self.output_representation = output_representation
            self.supports_masking = True
            
            super(BertLayer, self).__init__(**kwargs)
     
        def build(self, input_shape):
     
            self.bert = hub.Module(
                self.tf_hub,
                trainable=self.is_trainble,
                name="{}_module".format(self.name)
            )
            
            
            variables = list(self.bert.variable_map.values())
            if self.is_trainble:
                # 1 first remove unused layers
                trainable_vars = [var for var in variables if not "/cls/" in var.name]
                
                
                if self.output_representation == "sequence_output" or self.output_representation == "mean_pooling":
                    # 1 first remove unused pooled layers
                    trainable_vars = [var for var in trainable_vars if not "/pooler/" in var.name]
                    
                # Select how many layers to fine tune
                trainable_vars = trainable_vars[-self.n_fine_tune_layers :]
                
                # Add to trainable weights
                for var in trainable_vars:
                    self._trainable_weights.append(var)
     
                # Add non-trainable weights
                for var in self.bert.variables:
                    if var not in self._trainable_weights:
                        self._non_trainable_weights.append(var)
                    
            else:
                 for var in variables:
                    self._non_trainable_weights.append(var)
                    
     
            super(BertLayer, self).build(input_shape)
     
        def call(self, inputs):
            inputs = [K.cast(x, dtype="int32"for x in inputs]
            input_ids, input_mask, segment_ids = inputs
            bert_inputs = dict(
                input_ids=input_ids, input_mask=input_mask, segment_ids=segment_ids
            )
            result = self.bert(inputs=bert_inputs, signature="tokens", as_dict=True)
            
            if self.output_representation == "pooled_output":
                pooled = result["pooled_output"]
                
            elif self.output_representation == "mean_pooling":
                result_tmp = result["sequence_output"]
            
                mul_mask = lambda x, m: x * tf.expand_dims(m, axis=-1)
                masked_reduce_mean = lambda x, m: tf.reduce_sum(mul_mask(x, m), axis=1/ (
                        tf.reduce_sum(m, axis=1, keepdims=True) + 1e-10)
                input_mask = tf.cast(input_mask, tf.float32)
                pooled = masked_reduce_mean(result_tmp, input_mask)
                
            elif self.output_representation == "sequence_output":
                
                pooled = result["sequence_output"]
           
            return pooled
        
    cs
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
        def compute_mask(self, inputs, mask=None):
            
            if self.output_representation == 'sequence_output':
                inputs = [K.cast(x, dtype="bool"for x in inputs]
                mask = inputs[1]
                
                return mask
            else:
                return None
            
            
        def compute_output_shape(self, input_shape):
            if self.output_representation == "sequence_output":
                return (input_shape[0][0], input_shape[0][1], self.output_size)
            else:
                return (input_shape[0][0], self.output_size)
    cs

     


     

    4. 모델 빌드

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    import keras
     
    def build_model(max_seq_length, tf_hub, n_classes, n_fine_tune): 
        in_id = keras.layers.Input(shape=(max_seq_length,), name="input_ids")
        in_mask = keras.layers.Input(shape=(max_seq_length,), name="input_masks")
        in_segment = keras.layers.Input(shape=(max_seq_length,), name="segment_ids")
        bert_inputs = [in_id, in_mask, in_segment]
        
        bert_output = BertLayer(n_fine_tune_layers=n_fine_tune, tf_hub = tf_hub, output_representation = 'mean_pooling', trainable = True)(bert_inputs)
        drop = keras.layers.Dropout(0.3)(bert_output)
        dense = keras.layers.Dense(256, activation='sigmoid')(drop)
        drop = keras.layers.Dropout(0.3)(dense)
        dense = keras.layers.Dense(64, activation='sigmoid')(drop)
        pred = keras.layers.Dense(n_classes, activation='softmax')(dense)
        
        model = keras.models.Model(inputs=bert_inputs, outputs=pred)
        Adam = keras.optimizers.Adam(lr = 0.0005)
        model.compile(loss='sparse_categorical_crossentropy', optimizer=Adam, metrics=['sparse_categorical_accuracy'])
        model.summary()
     
        return model
     
    def initialize_vars(sess):
        sess.run(tf.local_variables_initializer())
        sess.run(tf.global_variables_initializer())
        sess.run(tf.tables_initializer())
        K.set_session(sess)
    n_classes = len(label_encoder.classes_)
    n_fine_tune_layers = 48
    model = build_model(max_seq_length, bert_path, n_classes, n_fine_tune_layers)
     
    # Instantiate variables
    initialize_vars(sess)
    cs

     

     

    1
    model.trainable_weights
    cs

     


    5. 토큰화(Tokenization)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    class InputExample(object):
        """A single training/test example for simple sequence classification."""
     
        def __init__(self, guid, text_a, text_b=None, label=None):
            """Constructs a InputExample.
        Args:
          guid: Unique id for the example.
          text_a: string. The untokenized text of the first sequence. For single
            sequence tasks, only this sequence must be specified.
          text_b: (Optional) string. The untokenized text of the second sequence.
            Only must be specified for sequence pair tasks.
          label: (Optional) string. The label of the example. This should be
            specified for train and dev examples, but not for test examples.
        """
            self.guid = guid
            self.text_a = text_a
            self.text_b = text_b
            self.label = label
     
    def create_tokenizer_from_hub_module(tf_hub):
        """Get the vocab file and casing info from the Hub module."""
        bert_module =  hub.Module(tf_hub)
        tokenization_info = bert_module(signature="tokenization_info", as_dict=True)
        vocab_file, do_lower_case = sess.run(
            [
                tokenization_info["vocab_file"],
                tokenization_info["do_lower_case"],
            ]
        )
        
        return FullTokenizer(vocab_file=vocab_file, do_lower_case=do_lower_case)
     
    cs

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    def convert_single_example(tokenizer, example, max_seq_length=256):
        """Converts a single `InputExample` into a single `InputFeatures`."""
        if isinstance(example, PaddingInputExample):
            input_ids = [0* max_seq_length
            input_mask = [0* max_seq_length
            segment_ids = [0* max_seq_length
            label = 0
            return input_ids, input_mask, segment_ids, label
        tokens_a = tokenizer.tokenize(example.text_a)
        if len(tokens_a) > max_seq_length - 2:
            tokens_a = tokens_a[0 : (max_seq_length - 2)]
        tokens = []
        segment_ids = []
        tokens.append("[CLS]")
        segment_ids.append(0)
        for token in tokens_a:
            tokens.append(token)
            segment_ids.append(0)
        tokens.append("[SEP]")
        segment_ids.append(0)
        
        #print(tokens)
        input_ids = tokenizer.convert_tokens_to_ids(tokens)
        # The mask has 1 for real tokens and 0 for padding tokens. Only real
        # tokens are attended to.
        input_mask = [1* len(input_ids)
        # Zero-pad up to the sequence length.
        while len(input_ids) < max_seq_length:
            input_ids.append(0)
            input_mask.append(0)
            segment_ids.append(0)
        assert len(input_ids) == max_seq_length
        assert len(input_mask) == max_seq_length
        assert len(segment_ids) == max_seq_length
        return input_ids, input_mask, segment_ids, example.label
     
    cs

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    def convert_examples_to_features(tokenizer, examples, max_seq_length=256):
        """Convert a set of `InputExample`s to a list of `InputFeatures`."""
     
        input_ids, input_masks, segment_ids, labels = [], [], [], []
        for example in tqdm_notebook(examples, desc="Converting examples to features"):
            input_id, input_mask, segment_id, label = convert_single_example(
                tokenizer, example, max_seq_length
            )
            input_ids.append(input_id)
            input_masks.append(input_mask)
            segment_ids.append(segment_id)
            labels.append(label)
        return (
            np.array(input_ids),
            np.array(input_masks),
            np.array(segment_ids),
            np.array(labels).reshape(-11),
        )
     
    def convert_text_to_examples(texts, labels):
        """Create InputExamples"""
        InputExamples = []
        for text, label in zip(texts, labels):
            InputExamples.append(
                InputExample(guid=None, text_a=" ".join(text), text_b=None, label=label)
            )
        return InputExamples
    cs

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    # 토크나이저 초기화
    tokenizer = create_tokenizer_from_hub_module(bert_path)
     
    # 데이터를 입력포멧으로 변환
    train_examples = convert_text_to_examples(train_text, train_label)
    val_examples = convert_text_to_examples(val_text, val_label)
     
    # 피쳐로 변환
    (train_input_ids, train_input_masks, train_segment_ids, train_labels 
    = convert_examples_to_features(tokenizer, train_examples, max_seq_length=max_seq_length)
    (val_input_ids, val_input_masks, val_segment_ids, val_labels
    = convert_examples_to_features(tokenizer, val_examples, max_seq_length=max_seq_length)
    cs

     


     

    6. 모델 훈련

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    from keras.callbacks import EarlyStopping
     
    BATCH_SIZE = 256
    MONITOR = 'val_sparse_categorical_accuracy'
    print('BATCH_SIZE is {}'.format(BATCH_SIZE))
    e_stopping = EarlyStopping(monitor=MONITOR, patience=3, verbose=1, mode='max', restore_best_weights=True)
    callbacks =  [e_stopping]
     
    history = model.fit(
       [train_input_ids, train_input_masks, train_segment_ids], 
        train_labels,
        validation_data = ([val_input_ids, val_input_masks, val_segment_ids], val_labels),
        epochs = 10,
        verbose = 1,
        batch_size = BATCH_SIZE,
        callbacks= callbacks
    )
    cs

     

     

    256 batch, 10 epochs

    colab GPU로 1시간 좀 넘게 걸렸습니다.

     

     


    결과

     

     

     


     

    BERT 시리즈

    BERT는 무엇인가, 동작 구조

    2020/02/12 - [SW개발/AI Development] - [BERT] BERT에 대해 쉽게 알아보기1 - BERT는 무엇인가, 동작 구조

    BERT 사전훈련시키기

    2020/03/26 - [SW개발/AI Development] - [BERT] BERT에 대해 쉽게 알아보기2 - BERT 사전훈련시키기(텐서플로우)

    BERT tf-hub이용, 캐글 분류 문제

    2020/03/27 - [SW개발/AI Development] - [BERT] BERT에 대해 쉽게 알아보기3 - tf-hub BERT layer를 이용한 캐글 분류문제

    BERT 파인튜닝

    2020/03/30 - [SW개발/AI Development] - [BERT] BERT에 대해 쉽게 알아보기4 - BERT 파인튜닝

     


     

     

     

     

     

     

     

    [References]

     

    Keras.metrics.SparseCategoricalAccuracy

    BERT fine-tuning for Tensorflow 2.0 with Keras API

    Simple Text Classification using BERT in TensorFlow Keras 2.0

    BERT Text Classification in 3 Lines of Code Using Keras(ktrain)

    댓글

Designed by Tistory.