[AI] Transformers 라이브러리 사용하기

5 분 소요

출처 : huggingface.co/transformers


더 편해진 HuggingFace Transformers


HuggingFace의 Transformers 트랜스포머 기반의 모델들을 쉽게 사용할 수 있도록 해 놓았다. 예전에 프로젝트할 때도 유용하게 썼었는데, 그 때에는 PyTorch로만 사용할 수 있었다. 그런데 이제는 Tensorflow 2.x 버전에서, 특히나 사전학습된 모델을 Keras 모델처럼 사용할 수 있다!

참고

사전학습한 모델을 불러 와서, Keras의 functional API처럼 컴파일하고, 훈련시킬 수 있다.

이 글을 참고하여, 어떻게 HuggingFace의 Transformers 라이브러리를 Keras 모델처럼 사용할 수 있는지 정리해 본다.


1. 개요

기본적으로 Transformers 라이브러리의 모델을 사용할 때 적용되는 큰 작업 흐름은 모두 동일하다. 사전학습된 모델로부터 토크나이저를 불러 와 문서를 토크나이징하고, 사전학습된 모델을 불러온 뒤 그 출력인 임베딩을 활용해 파인튜닝하면 된다.


Tokenizer

토크나이저를 불러 오고, encode 메소드를 사용하면 바로 사전학습된 모델의 word2idx에 따라 인코딩된 수치 벡터가 나온다. BERT 모델에서 한국어는 bert-base-multilingual-cased 모델에 사전 학습되어 있으므로, 해당 모델의 이름을 인자로 넘긴다. 사전학습된 모델의 어휘집에 무엇이 있는지 알고 싶다면, get_vocab() 메소드를 사용한다.

# 토크나이저 설정
tokenizer = BertTokenizer.from_pretrained('bert-base-multilingual-cased')

# 어휘집 생성
word2idx = tokenizer.get_vocab()
idx2word = {idx:word for idx, word in enumerate(word2idx)}

# 데이터 예시 확인
for idx in tokenizer.encode('뭐야 이 평점들은 나쁘진 않지만 점 짜리는 더더욱 아니잖아'):
    print(idx2word[idx], end=' ')

전처리한 데이터를 토크나이저로 인코딩하고, 어휘집에서 인덱스를 찾아 결과를 확인한다. 다음과 같이 special token과 wordpiece 토크나이징된 모습을 확인할 수 있다.

[CLS]  ##야 이 평 ##점 ##들은 나 ##쁘 ##진 않 ##지만 점 짜 ##리는 더 ##더 ##욱 [UNK] [SEP] 


토크나이저에 파라미터로 padding, truncating 등의 옵션을 줄 수 있다. 별도의 전처리를 거치지 않아도 한 번에 문장의 길이를 맞출 수 있어 편리하다. return_tensors 옵션을 사용하면 Pytorch 혹은 Tensorflow 형태의 텐서로 인코딩한다. 결과로는 dictionary가 반환된다. 주로 사용할 것은 input_ids, token_type_ids, attention_mask 등이므로, 해당 키를 사용하면 된다.

# 인코딩
train_encoded = tokenizer(train_sentences, padding=True, truncation=True, max_length=MAX_SEQUENCE_LEN, return_tensors='tf')

# 인코딩된 문장
X_train = train_encoded['input_ids']

# attention mask
X_train_masks = train_encoded['attention_mask']

# 데이터 예시
print(X_train[0]) # 인코딩된 문장
print(tokenizer.decode(X_train[0])) # 디코딩

인코딩된 결과 텐서와 그것을 디코딩한 결과를 확인하면 다음과 같다.

tf.Tensor(
[   101   9519   9074 119005   9708 119235   9715 119230  16439  77884
  48549   9284  22333  12692    102      0      0      0      0      0
      0      0      0      0      0      0      0      0      0      0
      0      0      0      0      0      0      0      0      0      0
      0      0      0      0      0      0      0      0      0      0
      0      0      0      0      0      0      0      0      0      0
      0      0      0      0], shape=(64,), dtype=int32)
[CLS]  더빙 진짜 짜증나네요 목소리 [SEP] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD]


Model

HuggingFace가 이미 Transformers 라이브러리에 각 목적에 맞는, 언어 모델을 구현해 놓았다. 분류 모델을 예로 들면, BertForSequenceClassification(BERT), AlbertForSequenceClassification(ALBERT) 와 같은 식이다. 각 언어 모델 및 목적을 선택하는 것은 documentation을 참고하면 된다. Pytorch와 Tensorflow에서 모두 활용할 수 있는데, Tensorflow 라이브러리에서 사용할 수 있는 모델의 이름에는 TF가 붙는다. TFBertForSequenceClassification과 같은 식이다.

사전학습된 모델을 불러 올 때 모델 설정을 위해 Config를 인자로 넘긴다. 모델을 사용하는 목적에 따라 다르므로, 이 역시 documentation을 참고하면 된다. BERT 모델은 logits (로짓 값)을 반환한다.

# 분류 모델 config 설정
my_config = BertConfig.from_pretrained(
    'bert-base-multilingual-cased',
    num_labels=2,
    output_hidden_states=False,
    output_attentions=False
)

# 사전학습 모델 불러오기
bert_model = TFBertForSequenceClassification.from_pretrained('bert-base-multilingual-cased', config=my_config)


2. Keras에서 모델 사용하기

업데이트된 Transformers 라이브러리에서 가장 마음에 드는 부분이다. TF가 붙어서 Tensorflow 버전으로 사용할 수 있는 모델은 tf.keras.Model 클래스를 상속받는다. Keras에서 모델을 설정하고 커스텀하는 방식을 그대로 사용할 수 있다.

언어 모델에서 임베딩 레이어 활용, 네트워크 구성 등에 따라, 크게 사용법을 다음과 같이 세 가지로 정리해 보았다. 네이버 영화 감성분석을 수행하는 모델을 만들어 보자. (전처리는 이미 진행했다고 가정한다.)


바로 사용하기

가장 쉬운 사용법이다. 사전학습된 BERT 모델을 불러와 바로 임베딩으로 사용한 뒤, Dense 층만 얹는다.

# 모델 네트워크 설정
input_ids = Input(batch_shape=(None, MAX_SEQUENCE_LEN), dtype=tf.int32, name='input_ids')
input_masks = Input(batch_shape=(None, MAX_SEQUENCE_LEN), dtype=tf.int32, name='attention_masks')
embedding = bert_model([input_ids, input_masks])[0] # logit값 반환
y_output = Dense(1, activation='sigmoid')(embedding) # sigmoid: 이진 분류

# 모델 구성 및 컴파일
model = Model(inputs=[input_ids, attention_masks], outputs=output, name='Bert_Classification_1')
model.compile(optimizer=Adam(learning_rate=0.0001),
              loss='binary_crossentropy',
              metrics=['acc'])

모델 전체 구조를 확인하면 다음과 같다.

UseDirectly

Model: "Bert_Classification"
__________________________________________________________________________________________________
Layer (type)                    Output Shape         Param #     Connected to                     
==================================================================================================
input_ids (InputLayer)          [(None, 64)]         0                                            
__________________________________________________________________________________________________
attention_masks (InputLayer)    [(None, 64)]         0                                            
__________________________________________________________________________________________________
tf_bert_for_sequence_classifica ((None, 2),)         177854978   input_ids[0][0]                  
                                                                 attention_masks[0][0]            
__________________________________________________________________________________________________
dense (Dense)                   (None, 1)            3           tf_bert_for_sequence_classificati
==================================================================================================
Total params: 177,854,981
Trainable params: 177,854,981
Non-trainable params: 0
__________________________________________________________________________________________________
None


Embedding 레이어 추출 후 사용하기

사전학습된 BERT 모델에서 latent feature로서의 임베딩 레이어만 추출하여 사용한다. Keras에서 Embedding 레이어를 사전학습하였을 때, trainable=False 옵션을 주었던 것을 상기하면 된다. 입력 및 임베딩에 대한 train 설정을 해제하면 된다. 이후 층은 자유롭게 구성한다.

# 입력층
input_ids = Input(batch_shape=(None, MAX_SEQUENCE_LEN), dtype=tf.int32, name='input_ids')
input_masks = Input(batch_shape=(None, MAX_SEQUENCE_LEN), dtype=tf.int32, name='attention_masks')

# latent feature 추출
embedding = bert_model(input_ids, attention_mask=input_masks)[0]
cls_tokens = embedding[:, 0, :]

# train 해제
input_ids.trainable = False
input_masks.trainable = False
embedding.trainble = False

# 층 쌓기
X_latent = BatchNormalization()(cls_tokens)
X_dense = Dense(192, activation='relu')(X_latent)
X_dense = Dropout(0.3)(X_dense)
y_output = Dense(1, activation='sigmoid')(X_dense)

# 모델 구성 및 컴파일
model = Model(inputs=[input_ids, input_masks], outputs=y_output, name='Bert_Classification_2')
model.compile(optimizer=Adam(learning_rate=3e-5),
              loss='binary_crossentropy',
              metrics=['acc'])

모델 전체 구조를 확인하면 다음과 같다.

latentExtract

Model: "Bert_Classification_2"
__________________________________________________________________________________________________
Layer (type)                    Output Shape         Param #     Connected to                     
==================================================================================================
input_ids (InputLayer)          [(None, 64)]         0                                            
__________________________________________________________________________________________________
attention_masks (InputLayer)    [(None, 64)]         0                                            
__________________________________________________________________________________________________
tf_bert_model (TFBertModel)     ((None, 64, 768), (N 177853440   input_ids[0][0]                  
                                                                 attention_masks[0][0]            
__________________________________________________________________________________________________
tf_op_layer_strided_slice (Tens [(None, 768)]        0           tf_bert_model[6][0]              
__________________________________________________________________________________________________
batch_normalization (BatchNorma (None, 768)          3072        tf_op_layer_strided_slice[0][0]  
__________________________________________________________________________________________________
dense (Dense)                   (None, 192)          147648      batch_normalization[0][0]        
__________________________________________________________________________________________________
dropout (Dropout)               (None, 192)          0           dense[0][0]                      
__________________________________________________________________________________________________
dense_1 (Dense)                 (None, 1)            193         dropout[0][0]                    
==================================================================================================
Total params: 178,004,353
Trainable params: 178,002,817
Non-trainable params: 1,536
__________________________________________________________________________________________________
None


Embedding 레이어 추출 후 Fine-Tune

Embedding 레이어를 추출한 후, 해당 Embedding 레이어에 또 다른 네트워크를 적용하여 Fine-Tuning 과정을 거칠 수도 있다.

# 입력층
input_ids = Input(batch_shape=(None, MAX_SEQUENCE_LEN), dtype=tf.int32, name='input_ids')
input_masks = Input(batch_shape=(None, MAX_SEQUENCE_LEN), dtype=tf.int32, name='attention_masks')

# latent feature 추출
embedding = bert_model(input_ids, attention_mask=input_masks)[0]

# train 해제
input_ids.trainable = False
input_masks.trainable = False
embedding.trainble = False

# embedding layer fine-tune
X_embed = Bidirectional(LSTM(50, return_sequences=True, dropout=0.1, recurrent_dropout=0.1))(embedding)
X_embed = GlobalMaxPool1D()(X_embed)  

# 층 쌓기
X_latent = BatchNormalization()(X_embed)
X_dense = Dense(192, activation='relu')(X_latent)
X_dense = Dropout(0.3)(X_dense)
y_output = Dense(1, activation='sigmoid')(X_dense)

# 모델 구성 및 컴파일
model = Model(inputs=[input_ids, input_masks], outputs=y_output, name='Bert_Classification_2')
model.compile(optimizer=Adam(learning_rate=3e-5),
              loss='binary_crossentropy',
              metrics=['acc'])

모델 전체 구조를 확인하면 다음과 같다.

latentExtractFineTune

Model: "Bert_Classification_2"
__________________________________________________________________________________________________
Layer (type)                    Output Shape         Param #     Connected to                     
==================================================================================================
input_ids (InputLayer)          [(None, 64)]         0                                            
__________________________________________________________________________________________________
attention_masks (InputLayer)    [(None, 64)]         0                                            
__________________________________________________________________________________________________
tf_bert_model (TFBertModel)     ((None, 64, 768), (N 177853440   input_ids[0][0]                  
                                                                 attention_masks[0][0]            
__________________________________________________________________________________________________
bidirectional (Bidirectional)   (None, 64, 100)      327600      tf_bert_model[0][0]              
__________________________________________________________________________________________________
global_max_pooling1d (GlobalMax (None, 100)          0           bidirectional[0][0]              
__________________________________________________________________________________________________
batch_normalization (BatchNorma (None, 100)          400         global_max_pooling1d[0][0]       
__________________________________________________________________________________________________
dense (Dense)                   (None, 192)          19392       batch_normalization[0][0]        
__________________________________________________________________________________________________
dropout (Dropout)               (None, 192)          0           dense[0][0]                      
__________________________________________________________________________________________________
dense_1 (Dense)                 (None, 1)            193         dropout[0][0]                    
==================================================================================================
Total params: 178,201,025
Trainable params: 178,200,825
Non-trainable params: 200
__________________________________________________________________________________________________
None


모델 훈련 및 예측

model.fit, model.predict와 같이 Keras 방식대로 모델을 훈련하면 된다. 다만, 입력 층에 인자로 input_idmask를 함께 넘겨 주어야 한다. (* 아주 당연하겠지만, 만약 token_type_ids가 있다면 이 역시 넘겨 주도록 모델을 구성해야 한다.*)

# 훈련
hist = model.fit([X_train, X_train_masks], y_train,
                 validation_data=([X_test, X_test_masks], y_test),
                 batch_size=128,
                 epochs=3,
                 shuffle=True)

# 예측
y_pred = model.predict([X_test, X_test_masks])
y_pred = np.where(y_pred > 0.5, 1, 0).reshape(-1, 1)
print('Accuracy = %.4f' % np.mean(y_test == y_pred))


hit count image

댓글남기기