새소식

자연어 NLP

[BERT] BERT에 대해 쉽게 알아보기3 - tf-hub BERT layer를 이용한 캐글 분류문제

  • -

 

● 텐서플로우 허브의 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)

Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.