Data-Science/NLP
SKTBrain KoBERT 텐서플로우로 돌리기
제일리
2022. 1. 31. 13:49
1. 데이터 소개
COLUMN: Premise | Hypothesis | label
LABEL : "Contradiction", "Entailment", "Neutral"
train.csv : 28998 row, 3 col
test.csv: 1666 row, 3col
submission.csv : 1666, 1col (label)
train = pd.read_csv('train_data.csv')
test = pd.read_csv('test_data.csv')
submit = pd.read_csv('submission.csv')
2. Validation 데이터 생성
** train 데이터와 validatoin데이터를 생성할 때 레이블값의 불균형이 생기면 안되기 때문에 랜덤 샘플링으로 데이터셋 구분
import random
random.seed(124)
index = train['index'].to_list()
len_index = len(index)
#train의 20%만 validation으로 지정
num_of_val = np.int32(len_index * 0.2)
val_index = random.sample(index, num_of_val)
validation = train.iloc[val_index]
train = train.drop(train.index[val_index])
3. 레이블 값 숫자변경 및 원핫 인코딩 (one-hot encoding)
from sklearn.preprocessing import LabelEncoder
from tensorflow.keras.utils import to_categorical
#레이블 숫자화
encoder = LabelEncoder()
encoder.fit(train['label'])
train_label = encoder.transform(train['label'])
valid_label = encoder.transform(validation['label'])
#레이블 원핫 인코딩
train_label = to_categorical(np.asarray(train_label))
valid_label = to_categorical(np.asarray(valid_label))
4. SKTBrain의 KoBERT 깃 클론 및 필요 라이브러리 설치
#깃 클론
!git clone https://github.com/SKTBrain/KoBERT.git
#KoBERT의 설치환경 설치
!pip install 'git+https://github.com/SKTBrain/KoBERT.git#egg=kobert_tokenizer&subdirectory=kobert_hf'
!pip install sentencepiece
!pip install transformers
import sentencepiece
import tensorflow as tf
import transformers
from kobert_tokenizer import KoBERTTokenizer
5. 데이터 파이프라인 생성
#하이퍼파라미터
max_length = 128 # Maximum length of input sentence to the model.
batch_size = 32
epochs = 10
# Labels
labels = ["contradiction", "entailment", "neutral"]
class BertSemanticDataGenerator(tf.keras.utils.Sequence):
'''Generate Batches of data
Args:
sentence_pairs: premise와 hypothesis가 함께 있는 array
labels: 원핫 인코딩 된 라벨값
batch_size: 배치 사이즈 (integer)
shuffle: True or False
include_targets: 데이터에 라벨값 사용 여부 (True or False)
Returns:
Tuples '([input_ids, attention_mask, 'token_type_ids], labels')
or just '[input_ids, attention_mask, 'token_type_ids]' if 'include_targets=False')
'''
def __init__(self, sentence_pairs, labels, batch_size=batch_size, shuffle=True, include_targets=True):
self.sentence_pairs = sentence_pairs
self.labels = labels
self.shuffle = shuffle
self.batch_size = batch_size
self.include_targets = include_targets
# BERT Tokenizer로 인코딩을 합니다.
# SKT-brain KoBERT의 사전학습 모델 사용
self.tokenizer = KoBERTTokenizer.from_pretrained('skt/kobert-base-v1')
self.indexes = np.arange(len(self.sentence_pairs))
self.on_epoch_end()
def __len__(self):
# 에폭별 배치수 지정
return len(self.sentence_pairs) // self.batch_size
def __getitem__(self, idx):
#배치의 인덱스
indexes = self.indexes[idx * self.batch_size : (idx + 1) * self.batch_size]
sentence_pairs = self.sentence_pairs[indexes]
# batch_encode_plus로 두 문장을 인코딩하고, "sep"토큰으로 두 문장을 분리한다.
encoded = self.tokenizer.batch_encode_plus(
sentence_pairs.tolist(),
add_special_tokens=True,
max_length=max_length,
return_attention_mask=True,
return_token_type_ids=True,
pad_to_max_length=True,
return_tensors='tf'
)
#인코딩된 결과를 array로 변환
input_ids = np.array(encoded['input_ids'], dtype='int32')
attention_masks = np.array(encoded['attention_mask'], dtype='int32')
token_type_ids = np.array(encoded['token_type_ids'], dtype='int32')
#레이블값의 사용여부로 다른 결과값 추출
if self.include_targets:
labels = np.array(self.labels[indexes], dtype='int32')
return [input_ids, attention_masks, token_type_ids], labels
else:
return [input_ids, attention_masks, token_type_ids]
def on_epoch_end(self):
#Shuffle indexes after each epoch if suffle is set to True.
if self.shuffle:
np.random.RandomState(42).shuffle(self.indexes)
6. 모델 생성
from transformers import TFBertModel
#GPU 사용 여부 확인
device_name = tf.test.gpu_device_name()
if device_name != '/device:GPU:0':
raise SystemError('GPU device not found')
print('Found GPU at: {}'.format(device_name))
#모델
with tf.device('/device:GPU:0'):
#Encoded token ids from BERT tokenizer.
input_ids = tf.keras.layers.Input(shape=(max_length,), dtype=tf.int32, name='input_ids')
#Attention masks indicates to the model which tokens should be attended to.
attention_masks = tf.keras.layers.Input(shape=(max_length,), dtype=tf.int32, name='attention_masks')
#Token type ids are binary masks identifying different sequences in the model.
token_type_ids = tf.keras.layers.Input(shape=(max_length,), dtype=tf.int32, name='token_type_ids')
#Loading pretrained BERT model.
bert_model = TFBertModel.from_pretrained('skt/kobert-base-v1',from_pt=True)
#Freeze the BERT model to reuse the pretrained features without modifying them.
bert_model.trainable = False
bert_output = bert_model(input_ids, attention_mask=attention_masks, token_type_ids=token_type_ids)
sequence_output = bert_output.last_hidden_state
pooled_output = bert_output.pooler_output
#Add trainable layers on top of frozen layers to adapt the pretrained features on the new data
bi_lstm = tf.keras.layers.Bidirectional(tf.keras.layers.GRU(64, return_sequences=True))(sequence_output) #Changed to GRU 2022/1/31
#Applying hybrid pooling apporach to bi_lstm sequence output.
avg_pool = tf.keras.layers.GlobalAveragePooling1D()(bi_lstm)
max_pool = tf.keras.layers.GlobalMaxPooling1D()(bi_lstm)
concat = tf.keras.layers.Concatenate()([avg_pool,max_pool])
dropout = tf.keras.layers.Dropout(0.3)(concat)
output = tf.keras.layers.Dense(3, activation='softmax')(dropout)
model = tf.keras.models.Model(inputs=[input_ids, attention_masks, token_type_ids], outputs=output)
model.compile(
optimizer=tf.keras.optimizers.Adam(),
loss='categorical_crossentropy',
metrics=['acc']
)
model.summary()
7. 모델 인풋 생성
train_data = BertSemanticDataGenerator(
sentence_pairs=train[['premise','hypothesis']].values.astype('str'),
labels=train_label,
batch_size=batch_size,
shuffle=True
)
valid_data = BertSemanticDataGenerator(
sentence_pairs=validation[['premise','hypothesis']].values.astype('str'),
labels=valid_label,
batch_size=batch_size,
shuffle=False
)
8. 학습 진행 (Feature Extraction)
* 모델을 보시면 BERT 뒤에 LSTM layer과 더불어 레이블(contradiction, entailment, neutral)을 예측하기 위한 layer을 쌓았습니다.
따라서 4epoch 정도 먼저 학습을 진행하여 본인의 데이터에 대해 feature extraction을 진행해줍니다.
history = model.fit(train_data,
epochs=4,
validation_data=valid_data,
use_multiprocessing=True,
verbose=1,
workers=-1,
)
8-1. 학습진행 (Fine-Tuning)
Feature extraction을 마치고 이제 BERT 모델과 함께 학습을 하기 위해
bert_model.trainable = True
으로 변경해주고, optimizer를 수정하여 학습을 진행해줍니다.
#BERT모델과 함께 학습
bert_model.trainable = True
#모델에 수정사항이 있었으므로 다시 compile
model.compile(
optimizer=tf.keras.optimizers.Adam(2e-5),
loss='categorical_crossentropy',
metrics=['accuracy']
)
model.summary()
from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint
#checkpoint생성
mc = ModelCheckpoint('best_model.h5',monitor='val_accuracy',mode='max',verbose=1, save_best_only=True)
#학습
history = model.fit(
train_data,
validation_data=valid_data,
epochs=20,
use_multiprocessing=True,
workers=-1,
verbose=1,
callbacks=mc
)
9. 예측
#test데이터의 premise와 hypothesis를 하나로 묶어줍니다.
sentence_pairs = test[['premise','hypothesis']].values.astype('str')
#test 데이터에 대해 모델 인풋 생성
test_data = BertSemanticDataGenerator(
sentence_pairs, labels=None, batch_size=len(sentence_pairs), shuffle=False, include_targets=False,
)
#load model
model = tf.keras.models.load_model('best_model.h5', custom_objects={"TFBertModel": transformers.TFBertModel})
예측값 : 각 레이블의 대한 softmax 확률
pred = model.predict(test_data[0]).tolist()
#softmax 확률의 최고값을 추출하여 레이블 생성
idx = [np.argmax(x) for x in pred]
prediction = [labels[index] for index in idx]
test['label'] = prediction