[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 데이터 분류 문제로 보입니다.
필요한 훈련데이터
저는 코랩 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(-1, 1),
)
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)
'자연어 NLP' 카테고리의 다른 글
[NLP] 자연어처리 - 한국어 임베딩 (0) | 2020.10.30 |
---|---|
[NLP] 자연어처리 - 한국어 전처리를 위한 기법들 (4) | 2020.10.22 |
[BERT] BERT에 대해 쉽게 알아보기4 - BERT 파인튜닝 (12) | 2020.03.30 |
[BERT] BERT에 대해 쉽게 알아보기2 - colab으로 BERT Pretraining(수정) (17) | 2020.03.26 |
[BERT] BERT에 대해 쉽게 알아보기1 - BERT는 무엇인가, 동작 구조 (2) | 2020.02.12 |
소중한 공감 감사합니다