일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- Comparisons
- Numpy data I/O
- Python 특징
- boolean & fancy index
- 가능도
- namedtuple
- subplot
- dtype
- linalg
- BOXPLOT
- VSCode
- Python 유래
- 표집분포
- ndarray
- 딥러닝
- 최대가능도 추정법
- 정규분포 MLE
- unstack
- Python
- Array operations
- pivot table
- scatter
- Operation function
- python 문법
- 부스트캠프 AI테크
- type hints
- groupby
- Numpy
- seaborn
- 카테고리분포 MLE
- Today
- Total
또르르's 개발 Story
[Stage 2 - 04] Entity Special token 본문
1️⃣ Goal
[BaseLine 작성] (추가 : 4/12, 새로운 Baseline code)- [Data Processing]
- Exploratory Data Analysis (추가 : 4/12, 기간 : 4/12 ~ )
- Cross-validation 사용 (추가 : 4/12)
- 데이터 불균형 해소 (추가 : 4/12)
-한국어 전처리 (추가 : 4/13, 기간 : 4/13 ~ 4/13 )
- 새로운 tokenizer 사용 (추가 : 4/12, 기간 : 4/13 ~ )
- 형태소 분류기 -> BERT wordpiece (추가 : 4/13)
- [ENT][/ENT] tag를 추가해서 train 돌리기 (추가 : 4/14, 기간 : 4/20 ~ ) - [Model]
- BERT 모델 사용 (추가 : 4/12, 기간 : 4/14 ~ )
- GPT 모델 사용 (추가 : 4/12)
- ELECTRA 모델 사용 (추가 : 4/12)
- KoBERT 모델 사용 (추가 : 4/12) - [Training]
- 앙상블 시도 (추가 : 4/12)
- Hyperparameter 변경 (추가 : 4/12)
- Learning Schedular 사용 (추가 : 4/12)
- 좋은 위치에서 Checkpoint 만들기 (추가 : 4/12)
- Wandb (Auto ML) 사용 (추가 : 4/12) - [Deploy]
- Python 모듈화 (추가 : 4/12)
2️⃣ Learning
1) 두 문장 관계 분류 task
[Stage 2 - 이론] 두 문장 관계 분류 task
두 문장 관계 분류 task는 주어진 2개의 문장에 대해,두 문장의 자연어 추론과 의미론적인 유사성을 측정하는 task입니다. 1️⃣ Natural Language Inference (NLI) 언어모델이 자연어의 맥락을 이해할 수 있
dororo21.tistory.com
2) IRQA 챗봇 실습
[Stage 2 - 이론] IRQA 챗봇 실습
또르르's 개발 Story [Stage 2 - 이론] IRQA 챗봇 실습 본문 [P Stage 2] KLUE/이론 [Stage 2 - 이론] IRQA 챗봇 실습 또르르21 또르르21 2021. 4. 19. 16:30 Prev 1 2 3 4 5 6 ··· 138 Next
dororo21.tistory.com
3️⃣ Main Task
1) Custom Trainer 작성
Huggingface에서 제공하는 trainer를 사용하지 않고, Epoch과 Optimizer, 그리고 여러 변수들을 추가하기 위해 새롭게 Trainer를 다시 작성했습니다.
device = torch.device('cuda') if torch.cuda.is_available() else torch.device('cpu')
train_loader = DataLoader(RE_train_dataset, batch_size=16, shuffle=True, num_workers=8)
valid_loader = DataLoader(RE_valid_dataset, batch_size=16, shuffle=True, num_workers=8)
optim = AdamW(model.parameters(), lr=5e-5)
# loss_fn = LabelSmoothingLoss()
model.train()
EPOCHS, print_every = 3, 1
for epoch in range(EPOCHS):
loss_val_sum = 0
for batch in tqdm(train_loader):
optim.zero_grad()
input_ids = batch['input_ids'].to(device)
attention_mask = batch['attention_mask'].to(device)
labels = batch['labels'].to(device)
outputs = model(input_ids, attention_mask=attention_mask, labels=labels)
loss = outputs[0]
loss.backward()
optim.step()
loss_val_sum += loss
loss_val_avg = loss_val_sum / len(train_loader)
if ((epoch % print_every) == 0 or epoch == (EPOCHS - 1)):
train_accr = func_eval(model, train_loader, device)
valid_accr = func_eval(model, valid_loader, device)
print ("epoch:[%d] loss:[%.3f] train_accr:[%.3f] valid_accr:[%.3f]."%(epoch,loss_val_avg,train_accr,valid_accr))
valid_data에 대해 accur, loss 측정을 위해 func_eval function을 정의합니다.
def func_eval(model, data_iter, device):
with torch.no_grad():
n_total, n_correct = 0, 0
model.eval() # evaluate
for batch in tqdm(data_iter):
input_ids = batch['input_ids'].to(device)
attention_mask = batch['attention_mask'].to(device)
labels = batch['labels'].to(device)
outputs = model(input_ids, attention_mask=attention_mask, labels=labels)
logits = outputs[1]
y_pred = torch.argmax(logits, axis=-1)
n_correct += (y_pred==labels).sum().item()
n_total += len(y_pred)
val_accr = (n_correct / n_total)
model.train() # back to train mode
return val_accr
Huggingface의 Bert를 사용하면서 편한 점은 Fine-tuning된 모델들을 자유롭게 사용할 수 있다는 점이었습니다.
CLS token만 검사하는 단일 분류 BertForSequenceClassification을 사용했는데 loss와 logits값을 return해주니 train code를 편리하게 짤 수 있습니다.
2) Entity token 추가
Entity token을 추가해서 각 entity의 위치에 넣어주는 방법입니다. 따라서 [ENT] token과 [/ENT] token 두 개를 tokenizer에 speical token으로 넣습니다.
special_tokens_dict = {'additional_special_tokens': ['[ENT]', '[/ENT]']}
num_added_toks = tokenizer.add_special_tokens(special_tokens_dict)
이후 model에 token embedding의 크기를 늘려줍니다.
model.resize_token_embeddings(len(tokenizer))
tokenized 하기 전 dataset에서 해당 index에 [ENT], [/ENT] token을 넣어줍니다.
token들을 넣게되면 index의 위치가 변함으로 entity01 index가 먼저인지, entity02 index가 먼저인지를 확인 후 token을 넣어줍니다.
def tokenized_dataset(dataset, tokenizer):
concat_entity = []
for e01, e02 in zip(dataset['entity_01'], dataset['entity_02']):
temp = ''
temp = e01 + '[SEP]' + e02
concat_entity.append(temp)
sentence_list = []
for column_name, sentence in dataset['sentence'].iteritems():
_sentence = list(sentence)
if dataset['entity_01_idx0'][column_name] < dataset['entity_02_idx0'][column_name]:
_sentence.insert(int(dataset['entity_01_idx0'][column_name]), "[ENT]")
_sentence.insert(int(dataset['entity_01_idx1'][column_name])+2, "[/ENT]")
_sentence.insert(int(dataset['entity_02_idx0'][column_name])+2, "[ENT]")
_sentence.insert(int(dataset['entity_02_idx1'][column_name])+4, "[/ENT]")
else:
_sentence.insert(int(dataset['entity_02_idx0'][column_name]), "[ENT]")
_sentence.insert(int(dataset['entity_02_idx1'][column_name])+2, "[/ENT]")
_sentence.insert(int(dataset['entity_01_idx0'][column_name])+2, "[ENT]")
_sentence.insert(int(dataset['entity_01_idx1'][column_name])+4, "[/ENT]")
sentence_list.append(''.join(_sentence))
tokenized_sentences = tokenizer(
concat_entity,
sentence_list,
return_tensors="pt",
max_length=160,
padding='max_length',
truncation='longest_first',
add_special_tokens=True,
)
return tokenized_sentences
다음은 시도한 방법 및 결과입니다.
- (기존방식) "entity01[SEP]entity02" + "전체 문장"
- 하이퍼파라미터 : epoch-4, checkpoint-2000, batch_size-16, Model-"bert-base-multilingual-cased"
- (시도 1) "entity01[SEP]entity02" + "[ENT]가 들어간 전체 문장"
- 하이퍼파라미터 : epoch-7, checkpoint-3500, batch_size-16, Model-"bert-base-multilingual-cased"
- Public score를 비교해보았을 때 성능이 약간 떨어지는 것을 알 수 있습니다.
- (시도 2) "[ENT]가 들어간 전체 문장"
- 하이퍼파라미터 : epoch-7, checkpoint-2000, batch_size-16, Model-"bert-base-multilingual-cased"
- Public score를 비교해보았을 떄 성능이 많이 떨어지는 것을 알 수 있습니다.
3) entity layer 추가
BERT의 Embedding layer에 Entity embedding layer를 추가해서 사용하는 방법을 구상했습니다.
Huggingface docs를 살펴보니 BertEmbeddings 클래스를 약간 수정해서 layer를 추가할 수 있을 것 같았습니다.
아래 사진은 huggingface에 있는 BertEmbeddings class입니다.
여기서 entity_embeddings를 추가해줍니다.
물론 여기서 input size, hidden size를 모두 token_type_embedding 형태를 그대로 따라 했고 필요에 따라 수정해야 합니다.
class BertEmbeddings(nn.Module):
"""Construct the embeddings from word, position and token_type embeddings."""
def __init__(self, config):
super().__init__()
self.word_embeddings = nn.Embedding(config.vocab_size, config.hidden_size, padding_idx=config.pad_token_id)
self.position_embeddings = nn.Embedding(config.max_position_embeddings, config.hidden_size)
self.token_type_embeddings = nn.Embedding(config.type_vocab_size, config.hidden_size)
self.entity_embeddings = nn.Embedding(config.type_vocab_size, config.hidden_size) # entity_embedding 추가
# self.LayerNorm is not snake-cased to stick with TensorFlow model variable name and be able to load
# any TensorFlow checkpoint file
self.LayerNorm = nn.LayerNorm(config.hidden_size, eps=config.layer_norm_eps)
self.dropout = nn.Dropout(config.hidden_dropout_prob)
# position_ids (1, len position emb) is contiguous in memory and exported when serialized
self.register_buffer("position_ids", torch.arange(config.max_position_embeddings).expand((1, -1)))
self.position_embedding_type = getattr(config, "position_embedding_type", "absolute")
def forward(
self, input_ids=None, token_type_ids=None, position_ids=None, inputs_embeds=None, past_key_values_length=0
):
if input_ids is not None:
input_shape = input_ids.size()
else:
input_shape = inputs_embeds.size()[:-1]
seq_length = input_shape[1]
if position_ids is None:
position_ids = self.position_ids[:, past_key_values_length : seq_length + past_key_values_length]
if token_type_ids is None:
token_type_ids = torch.zeros(input_shape, dtype=torch.long, device=self.position_ids.device)
if inputs_embeds is None:
inputs_embeds = self.word_embeddings(input_ids)
token_type_embeddings = self.token_type_embeddings(token_type_ids)
entity_embeddings = self.entity_embeddings(token_type_ids)
embeddings = inputs_embeds + token_type_embeddings + entity_embeddings
if self.position_embedding_type == "absolute":
position_embeddings = self.position_embeddings(position_ids)
embeddings += position_embeddings
embeddings = self.LayerNorm(embeddings)
embeddings = self.dropout(embeddings)
return embeddings
그리고 BertConfig를 가지고 와서 새롭게 정의한 BertEmbeddings 클래스에 넣어줍니다.
configuration = BertConfig()
custom_bertembeddings = BertEmbeddings(configuration)
저는 BertForSequenceClassification을 사용하기 때문에 BERT backbone 모델과 classification layer를 동시에 사용합니다.
따라서 custom_bertembeddings를 넣으려면 다음과 같이 넣어주어야 합니다.
model.bert.embeddings = custom_bertEmbeddings
이후, model에서 input_ids, attention_mask, labels와 함께 entity를 추가로 넣을 수 있게 Dataset을 수정했습니다.
([ENT]를 만나면 10이 들어가고 [/ENT] 일 경우는 0이 들어감)
class RE_Dataset(torch.utils.data.Dataset):
def __init__(self, tokenized_dataset, labels):
self.tokenized_dataset = tokenized_dataset
self.labels = labels
def __getitem__(self, idx):
item = {key: torch.tensor(val[idx]) for key, val in self.tokenized_dataset.items()}
item['labels'] = torch.tensor(self.labels[idx])
entity = []
flag = False
for value in item['input_ids']:
value = int(value)
if value == 119548:
flag = False
if flag:
entity.append(10)
else:
entity.append(0)
if value == 119547:
flag = True
item['entity'] =torch.tensor(entity)
return item
하지만 model에서 forward에 들어가는 parameter와 여러 가지 상속이 되어있는 복잡한 코드 때문에 test는 못해보았습니다.
4️⃣ Sub Task
없음.
5️⃣ Evaluation
날짜 | Data processing & Tokenizer |
Model | Training | Time | Accuracy |
4/13 | - EDA - |
1h | - | ||
4/13 | - new baseline code - |
30m | 59.3000% | ||
4/14 | - KoBERT - |
- epoch : 20 max_len : 128 batch_size : 32 - |
23m | 72.0000% | |
4/17 | - "bert-base-multilingual-cased" BERT - |
- epoch : 4 batch_size : 16 - |
14m | 72.8000% | |
4/20 | - "entity01[SEP]entity02" + "[ENT]가 들어간 전체 문장" - |
16m | 71.2000% | ||
4/20 | - "[ENT]가 들어간 전체 문장" - |
17m | 52.5000% |
1) Custom Trainer 작성
Custom trainer 작성으로 추상적이었던 Trainer보다는 좀 더 직관적으로 코드가 완성되었다고 생각합니다.
그렇지만, checkpoint 저장, logging 저장 등 여러 가지 기능을 추가해야 하고, 다양한 모델을 test 할 때 조금씩 다른 return값으로 인해 코드를 변경해야 한다는 불편함이 있습니다.
2) Entity token 추가
Entity token을 추가함으로써 얻어지는 성능 향상은 없었습니다.
다만, 새롭고 다양한 tokenizer 방법들을 사용해보아야겠다고 생각했습니다.
3) Entity layer 추가
모델 자체를 수정하는 것이 정말 어렵다는 것을 알게 되었습니다.
또한, 모델을 수정해버리니 pre-trained 되어있던 가중치 값들이 필요 없어지게 되었습니다.
다양한 실험을 해보고 시간이 넉넉할 때 다시 한번 시도해볼 만한 주제인 것 같습니다.
4) 차후 목표
- Pororo library 사용해보기
- 형태소 -> wordpiece tokenizer 사용해보기
- 음절 단위로 tokenizer 나눠보기
'[P Stage 2] KLUE > 프로젝트' 카테고리의 다른 글
[Stage 2 - 06] xlm-roberta-large 사용하기 (0) | 2021.04.22 |
---|---|
[Stage 2 - 05] Pororo 라이브러리 사용하기 (0) | 2021.04.20 |
[Stage 2 - 04] BERT MASK & 단일 문장 분류 (0) | 2021.04.16 |
[Stage 2 - 03] BERT (0) | 2021.04.15 |
[Stage 2 - 02] 한국어 전처리 / 토크나이징 (0) | 2021.04.14 |