1. 코드 구조
1) shell argument
option.py : shell argument add
2) train & validation 진행
train_simmc_agent.py : train SIMMC baseline
eval_simmc_agent.py : evaluate
3) dataloader
loader/ : DataLoader (SIMMC data)
1> loader_base.py : 추후 loader_simmc가 상속해서 활용
2> loader_simmc.py : simmc dataset의 dataloader 만든다.
4) Model
model/ : Model 파일들 존재
0> assistant.py : 아래 모델의 총합
1> encoders/ : Utterance & History Encoder
- history_agnostic.py (HAE or T-HAE)
- hierarchical_recurrent.py (HRE)
- memory_network.py (MN)
- tf_idf_encoder.py (과거 모델)
2> Multimodal Fusion
- carousel_embedder.py : learn furniture data embeddding
- user_memory_embedder.py : learn fashion data embeddding
3> Action Predictor : action_executor.py
4> Responce Generator : decoder.py (LSTM 혹은 Transformer 사용)
5> 기타 모델
- self_attention.py : 일반 self_attention network
- positional_encoding.py : transformer에서 위치 정보를 주기 위한 encoding
6> json 파일
{DOMAIN}_model_metainfo.json : model의 action 별 attribute를 정리했다.
5) tools/
전처리 하는데 필요한 파일들 존재
6> scripts/
shell 파일들 존재
2. shell 파일 분석
1) preprocess_simmc.sh
json과 csv 형태의 data를 model에 넣을 수 있는 형태로 preprocess
0> variable & argument
#!/bin/bash
DOMAIN="furniture"
# DOMAIN="fashion"
ROOT="../data/simmc_${DOMAIN}/"
# Input files.
TRAIN_JSON_FILE="${ROOT}${DOMAIN}_train_dials.json"
DEV_JSON_FILE="${ROOT}${DOMAIN}_dev_dials.json"
DEVTEST_JSON_FILE="${ROOT}${DOMAIN}_devtest_dials.json"
if [ "$DOMAIN" == "furniture" ]; then
METADATA_FILE="${ROOT}furniture_metadata.csv"
elif [ "$DOMAIN" == "fashion" ]; then
METADATA_FILE="${ROOT}fashion_metadata.json"
else
echo "Invalid domain!"
exit 0
fi
# Output files.
VOCAB_FILE="${ROOT}${DOMAIN}_vocabulary.json"
METADATA_EMBEDS="${ROOT}${DOMAIN}_asset_embeds.npy"
ATTR_VOCAB_FILE="${ROOT}${DOMAIN}_attribute_vocabulary.json"
- DOMAIN은 "furniture"와 "fashion" 중에서 선택이 가능하다.
- input file 이름 : ${DOMAIN}_{dataset 종류}_dials.json
- metadata file 이름 : furniture_metadata.csv / fashion_metadata.json
- output file 이름 :
- ${DOMAIN}_vocabulary.json
- ${DOMAIN}_asset_embeds.npy
- ${DOMAIN}_attribute_vocabulary.json
1> Step 1 : extract assistant API
# Step 1: Extract assistant API.
INPUT_FILES="${TRAIN_JSON_FILE} ${DEV_JSON_FILE} ${DEVTEST_JSON_FILE}"
# If statement.
if [ "$DOMAIN" == "furniture" ]; then
python tools/extract_actions.py \
--json_path="${INPUT_FILES}" \
--save_root="${ROOT}" \
--metadata_path="${METADATA_FILE}"
elif [ "$DOMAIN" == "fashion" ]; then
python tools/extract_actions_fashion.py \
--json_path="${INPUT_FILES}" \
--save_root="${ROOT}" \
--metadata_path="${METADATA_FILE}"
else
echo "Invalid domain!"
exit 0
fi
- domain에 따라 tools/extract_action.py 혹은 tools/extract_actions_fashion.py 파일 실행
=> action data를 extract
2> Step 2 : Extract vocabulary from train.
# Step 2: Extract vocabulary from train.
python tools/extract_vocabulary.py \
--train_json_path="${TRAIN_JSON_FILE}" \
--vocab_save_path="${VOCAB_FILE}" \
--threshold_count=5
- tools/extract_vocabulary.py 파일 실행
=> SIMMC dataset(${DOMAIN}_{dataset 종류}_dials.json)에 있는 data를 tokenize하고 ${DOMAIN}_vocabulary.json 에 저장
3> Step 3: Read and embed shopping assets
# Step 3: Read and embed shopping assets.
if [ "$DOMAIN" == "furniture" ]; then
python tools/embed_furniture_assets.py \
--input_csv_file="${METADATA_FILE}" \
--embed_path="${METADATA_EMBEDS}"
elif [ "$DOMAIN" == "fashion" ]; then
python tools/embed_fashion_assets.py \
--input_asset_file="${METADATA_FILE}" \
--embed_path="${METADATA_EMBEDS}"
else
echo "Invalid domain!"
exit 0
fi
- tools/embed_{DOMAIN}_asset.py 파일 실행
=> furniture_metadata.csv / fashion_metadata.json data를 Glove Embedding을 적용하여 ${DOMAIN}_asset_embeds.npy 에 저장
=> { "asset_id": id_list, "embedding": embeddings, "asset_feature_size": feature_size} 형태로 저장
4> Step 4: Convert all the splits into npy files for dataloader
# Step 4: Convert all the splits into npy files for dataloader.
SPLIT_JSON_FILES=("${TRAIN_JSON_FILE}" "${DEV_JSON_FILE}" "${DEVTEST_JSON_FILE}")
for SPLIT_JSON_FILE in "${SPLIT_JSON_FILES[@]}" ; do
python tools/build_multimodal_inputs.py \
--json_path="${SPLIT_JSON_FILE}" \
--vocab_file="${VOCAB_FILE}" \
--save_path="$ROOT" \
--action_json_path="${SPLIT_JSON_FILE/.json/_api_calls.json}" \
--retrieval_candidate_file="${SPLIT_JSON_FILE/.json/_retrieval_candidates.json}" \
--domain="${DOMAIN}"
done
- tools/build_multimodal_inputs.py 파일 실행
=> SIMMC dataset(${DOMAIN}_{dataset 종류}_dials.json)에 있는 data를 npy 형태로 저장
(아직 Step 2와 Step 4의 차이가 확실하게 구분이 되지는 않음)
5> Step 5: Extract vocabulary for attributes from train npy file
# Step 5: Extract vocabulary for attributes from train npy file.
python tools/extract_attribute_vocabulary.py \
--train_npy_path="${TRAIN_JSON_FILE/.json/_mm_inputs.npy}" \
--vocab_save_path="${ATTR_VOCAB_FILE}" \
--domain="${DOMAIN}"
- tools/extract_attribute_vocabulary.py 파일 실행
=> attribute(object 간에 연결되는 속성들)를 추출하여 vocabulary 형태로 기록
2) train_simmc_model.sh
단일 모델 train 실행 파일
3) train_all_simmc_model.sh
모든 모델 train 실행 파일
0> variable 및 parameter 관리
#!/bin/bash
GPU_ID=0
# DOMAIN="furniture"
DOMAIN="fashion"
ROOT="../data/simmc_${DOMAIN}/"
# Input files.
TRAIN_JSON_FILE="${ROOT}${DOMAIN}_train_dials.json"
DEV_JSON_FILE="${ROOT}${DOMAIN}_dev_dials.json"
DEVTEST_JSON_FILE="${ROOT}${DOMAIN}_devtest_dials.json"
METADATA_FILE="${ROOT}${DOMAIN}_metadata.csv"
# Output files.
VOCAB_FILE="${ROOT}${DOMAIN}_vocabulary.json"
METADATA_EMBEDS="${ROOT}${DOMAIN}_asset_embeds.npy"
ATTR_VOCAB_FILE="${ROOT}${DOMAIN}_attribute_vocabulary.json"
MODEL_METAINFO="models/${DOMAIN}_model_metainfo.json"
CHECKPOINT_PATH="checkpoints"
LOG_PATH="logs/"
COMMON_FLAGS="
--train_data_path=${TRAIN_JSON_FILE/.json/_mm_inputs.npy} \
--eval_data_path=${DEV_JSON_FILE/.json/_mm_inputs.npy} \
--asset_embed_path=${METADATA_EMBEDS} \
--metainfo_path=${MODEL_METAINFO} \
--attr_vocab_path=${ATTR_VOCAB_FILE} \
--learning_rate=0.0001 --gpu_id=$GPU_ID --use_action_attention \
--num_epochs=100 --eval_every_epoch=5 --batch_size=20 \
--save_every_epoch=5 --word_embed_size=256 --num_layers=2 \
--hidden_size=512 \
--use_multimodal_state --use_action_output --use_bahdanau_attention \
--skip_bleu_evaluation --domain=${DOMAIN}"
1> 5개 모델 지원
# History-agnostic model.
function history_agnostic () {
python -u train_simmc_agent.py $COMMON_FLAGS \
--encoder="history_agnostic" --text_encoder="lstm" \
--snapshot_path="${CHECKPOINT_PATH}/$1/hae/" &> "${LOG_PATH}/$1/hae.log" &
}
# Hierarchical recurrent encoder model.
function hierarchical_recurrent () {
python -u train_simmc_agent.py $COMMON_FLAGS \
--encoder="hierarchical_recurrent" --text_encoder="lstm" \
--snapshot_path="${CHECKPOINT_PATH}/$1/hre/" &> "${LOG_PATH}/$1/hre.log" &
}
# Memory encoder model.
function memory_network () {
python -u train_simmc_agent.py $COMMON_FLAGS \
--encoder="memory_network" --text_encoder="lstm" \
--snapshot_path="${CHECKPOINT_PATH}/$1/mn/" &> "${LOG_PATH}/$1/mn.log" &
}
# TF-IDF model.
function tf_idf () {
python -u train_simmc_agent.py $COMMON_FLAGS \
--encoder="tf_idf" --text_encoder="lstm" \
--snapshot_path="${CHECKPOINT_PATH}/$1/tf_idf/" &> "${LOG_PATH}/$1/tf_idf.log" &
}
# Transformer model.
function transformer () {
python -u train_simmc_agent.py $COMMON_FLAGS \
--encoder="history_agnostic" \
--text_encoder="transformer" \
--num_heads_transformer=4 --num_layers_transformer=4 \
--hidden_size_transformer=2048 --hidden_size=256\
--snapshot_path="${CHECKPOINT_PATH}/$1/transf/" &> "${LOG_PATH}/$1/transf.log" &
}
- history_agonstic() : HAE (--text_encoder를 "transformer" 사용시, T-HAE 모델 사용 가능 <마지막 모델에서 구현>)
- hierarchical_recurrent() : HRE
- memory_network() : MN
- tf-idf() : tf-idf 기반 모델
- transformer() : T-HAE
2> train all model
# Train all models on a domain Save checkpoints and logs with unique label.
UNIQ_LABEL="${DOMAIN}_dstc_split"
CUR_TIME=$(date +"_%m_%d_%Y_%H_%M_%S")
UNIQ_LABEL+=$CUR_TIME
mkdir "${LOG_PATH}${UNIQ_LABEL}"
history_agnostic "$UNIQ_LABEL"
hierarchical_recurrent "$UNIQ_LABEL"
memory_network "$UNIQ_LABEL"
tf_idf "$UNIQ_LABEL"
transformer "$UNIQ_LABEL
3. train_simmic_agent.py
from __future__ import absolute_import, division, print_function, unicode_literals
import json
import math
import time
import os
import torch
import loaders
import models
import options
import eval_simmc_agent as evaluation
from tools import support
# Arguments.
args = options.read_command_line()
1) options.py
read_command_line 함수에서 parameter들을 받는다.
0> 기본 배경지식
- class argparse.ArgumentParser : 새로운 ArgumentParser 객체 생성 (description : parameter 도움말 전에 표시할 텍스트)
- required=True : 필수 argument
- default= : default 값 설정
- help= : 해당 argument에 대한 설명
1> --train_data_path : training data의 경로 (required=True)
2> --eval_data_path : val data의 경로 (default=None)
3> --snapshot_path : checkpoint save할 경로 (default="checkpoints/")
4> --metainfo_path : metainfo json 파일이 있는 경로 (default="data/furniture_metainfo.json")
5> --attr_vocab_path : attribule vocabulary가 있는 경로 (default="data/attr_vocab_file.json")
6> --domain : 사용할 data (furniture vs fashion)
7> --asset_embed_path : (default="data/furniture_asset_path.npy")
8> --encoder : Utterance & History Encoder (required=True)(choices=[ "history_agnostic", "history_aware", "pretrained_transformer", "hierarchical_recurrent", "memory_network", "tf_idf", ]
- parsed_args["encoder"] == "pretrained_transformer"인 경우 현재 사용한 모델 이외의 pretrained transformer를 사용 가능
- parsed_args["encoder"] == "tf_idf"인 경우 => parsed_args["use_action_output"] = False
9> --text_encoder : (required=True)(choices=["lstm", "transformer"])
10> --word_embed_size : embedding size (default=128)(type=int)
11> --hidden_size : LSTM/transformer의 hidden state 크기 (word_embed_size와 같아야 한다.) (default=128)(type=int)
(transformer 사용시 word_embed_size와 hidden_size는 같아야 한다.)
<transformer text encoder의 parameter>
12> --num_heads_transformer : transformer의 head 개수 (default=-1)(type=int)
13> --num_layers_transformer : transformer의 layer 개수 (default=-1)(type=int)
14> --hidden_size_transformer : transformer의 hidden size (default=2048)(type=int)
15> --num_layers : LSTM layer의 개수
16> --use_action_attention : 모든 encoder에서 attenttion을 사용하는지 (default=False)
17> --use_action_output :
18> --use_multimodal_state : fashion task에서 multimodal state를 사용하는지 ?? (default=False)
19> --use_bahdanau_attention : bahdanau방식의 attention 사용 여부
- parsed_args["use_action_output"] == True
- parsed_args["text_encoder"] == "lstm"
20> --skip_bleu_evaluation : validation 과정에서 BLEU score를 사용하는지 (default=True)
21> --max_encoder_len : 문장의 최대 encoding 길이 (default=24)(type=int)
22> --max_history_len : history의 최대 encoding 길이 (default=100)(type=int)
23> --max_decoder_len : 문장의 chleo decoding 길이 (default=26)(type=int)
24> --max_rounds : 대화의 최대 round<한 번 왔다 갔다> (default=30)(type=int)
25> --share_embeddings : encoder와 decoder가 embedding을 share하는지 (default=True)
<optimization hyperparameter>
26> --batch_size : <왜 기본이 30인가?> (default=30)(type=int)
27> --learning_rate : (default=0.001)(type=float)
28> --dropout : drop_rate (default=0.2)(type=float)
29> --num_epochs : (default=20)(type=int)
30> --eval_every_epoch : epoch마다 validation 진행 여부 (default=1)(type=int)
31> --save_every_epoch : epoch마다 저장 여부 <-1이면 저장 X> (default=-1)(type=int)
32> --save_prudently : best score인 경우만 모델 저장 (default=False)
33> --gpu_id : 사용하는 gpu <CPU 사용시 -1로 설정> (default=-1)
2) dataloader
1> dataloader_args
dataloader_args = {
"single_pass": False,
"shuffle": True,
"data_read_path": args["train_data_path"],
"get_retrieval_candidates": False
}
2> 기존 argument (위의 options.py에서 가져온 것)도 추가
dataloader_args.update(args)
3> loader/loader_simmc.py의 DataloaderSIMMC(loader_base.py에 존재하는 LoaderParent를 상속받음)를 가져온다.
train_loader = loaders.DataloaderSIMMC(dataloader_args)
4> validation을 해야하는 경우(validation 경로 지정된 경우) 일부 argument update
if args["eval_data_path"]:
dataloader_args = {
"single_pass": True,
"shuffle": False,
"data_read_path": args["eval_data_path"],
"get_retrieval_candidates": args["retrieval_evaluation"]
}
dataloader_args.update(args)
val_loader = loaders.DataloaderSIMMC(dataloader_args)
else:
val_loader = None
3) Model
1> Load Model : models/assistant.py에서 모델을 불러온다. (assistant.py 전체 모델을 합친 구조이다.)
wizard = models.Assistant(args)
wizard.train()
2> encoder가 tf-idf일 경우 _ship_helper함수를 통해 numpy array에서 tensor로 변환
if args["encoder"] == "tf_idf":
wizard.encoder.IDF.data = train_loader._ship_helper(train_loader.IDF)
3> optimizer : Adam
optimizer = torch.optim.Adam(wizard.parameters(), args["learning_rate"])
4) Train
1> 용어 정리
smoother = support.ExponentialSmoothing()
num_iters_per_epoch = train_loader.num_instances / args["batch_size"]
print("Number of iterations per epoch: {:.2f}".format(num_iters_per_epoch))
eval_dict = {}
best_epoch = -1
iter_per_epoch = num_instances(dataloader에 존재하는 data 개수) / batch_size
num_iters_per_epoch : epoch당 train 횟수
train_loader.get_batch() : batch generator (loader_base.py의 LoaderParent 참고)
iter_ind : batch의 개수를 나타내는 index
=> 그래서 num_iters_per_epoch이 iter_ind와 같아질 때마다 1 epoch이 된다.
2> propagation & parameter update
smoother = support.ExponentialSmoothing()
for iter_ind, batch in enumerate(train_loader.get_batch()):
epoch = iter_ind / num_iters_per_epoch
batch_loss = wizard(batch)
batch_loss_items = {key: val.item() for key, val in batch_loss.items()}
losses = smoother.report(batch_loss_items)
# Optimization steps.
optimizer.zero_grad()
batch_loss["total"].backward()
torch.nn.utils.clip_grad_value_(wizard.parameters(), 1.0)
optimizer.step()
- 모델은 4가지의 loss를 계산 (loss_token, loss_action, loss_action_attr, loss_total)
- ExponentialSmoothing
(exponentially weighted moving average를 기반으로 [이전 값에 0.95], [현재 loss에 0.05]의 가중치를 주며 loss를 축적)
(사용하는 이유에 대한 추측 : 논문 6.2 Task 1에서 밝히길 perplexity는 exp(loglikelihood mean)를 의미해서)
class ExponentialSmoothing:
"""Exponentially smooth and track losses.
"""
def __init__(self):
self.value = None
self.blur = 0.95
self.op = lambda x, y: self.blur * x + (1 - self.blur) * y
def report(self, new_val):
"""Add a new score.
Args:
new_val: New value to record.
"""
if self.value is None:
self.value = new_val
else:
self.value = {
key: self.op(value, new_val[key]) for key, value in self.value.items()
}
return self.value
3> iteration progress
if iter_ind % 50 == 0:
cur_time = time.strftime("%a %d%b%y %X", time.gmtime())
print_str = (
"[{}][Ep: {:.2f}][It: {:d}][A: {:.2f}][Aa: {:.2f}]" "[L: {:.2f}][T: {:.2f}]"
)
print_args = (
cur_time,
epoch,
iter_ind,
losses["action"],
losses["action_attr"],
losses["token"],
losses["total"],
)
print(print_str.format(*print_args))
5) validation
# Perform evaluation, every X number of epochs.
if (
val_loader
and int(epoch) % args["eval_every_epoch"] == 0
and (iter_ind == math.ceil(int(epoch) * num_iters_per_epoch))
):
eval_dict[int(epoch)], eval_outputs = evaluation.evaluate_agent(
wizard, val_loader, args
)
# Print the best epoch so far.
best_epoch, best_epoch_dict = support.sort_eval_metrics(eval_dict)[0]
print("\nBest Val Performance: Ep {}".format(best_epoch))
for item in best_epoch_dict.items():
print("\t{}: {:.2f}".format(*item))
evaluation.evaluate_agent(wizard, val_loader, args) : val_loader(DataLoader)와 wizard(Model)에 대한 validation 과정
beam이라는 개념?
1> BLEU score
(loader_simmc.py의 DataloaderSIMMC 클래스의 메서드인 stringify_beam_output, evaluate_response_generation)
- stringify_beam_output은 모델의 output을 string으로 바꿔주는 작업을 진행
- 그리고 evaluate_response_generation을 가지고 BLEU score를 계산
2> retrieval score
(정확한 retrieval score의 정의)
3> perplexity
4> action prediction
- tools의 action_evaluation.py에서 evaluate_action_prediction을 통해 action_accuracy, action_perplexity, attribute_accuracy 계산
※ 결론
eval_dict에는 BLEU score, retrieval score, perplexity, action_accuracy, action_perplexity, attribute_accuracy
eval_outputs에는 action_prediction과 model_responses가 있다.
6) Save the model
if (
args["save_every_epoch"] > 0
and int(epoch) % args["save_every_epoch"] == 0
and (iter_ind == math.ceil(int(epoch) * num_iters_per_epoch))
):
# Create the folder if it does not exist.
os.makedirs(args["snapshot_path"], exist_ok=True)
# If prudent, save only if best model.
checkpoint_dict = {
"model_state": wizard.state_dict(),
"args": args,
"epoch": best_epoch,
}
if args["save_prudently"]:
if best_epoch == int(epoch):
save_path = os.path.join(args["snapshot_path"], "epoch_best.tar")
print("Saving the model: {}".format(save_path))
torch.save(checkpoint_dict, save_path)
else:
save_path = os.path.join(
args["snapshot_path"], "epoch_{}.tar".format(int(epoch))
)
print("Saving the model: {}".format(save_path))
torch.save(checkpoint_dict, save_path)
# Save the file with evaluation metrics.
eval_file = os.path.join(args["snapshot_path"], "eval_metrics.json")
with open(eval_file, "w") as file_id:
json.dump(eval_dict, file_id)
1> "save_every_epoch"인 경우
- "save_prudently"이면 최고 성능인 model을 계속해서 덮어 쓰는 형태로 저장하고
- 그렇지 않으면 모든 epoch의 모델을 저장한다.
2> 저장 파일
- 모델은 .tar 확장자로 저장하고
- validation metric은 json 형태로 저장 (원래도 dictionary 형태로 관리하고 있어서)
4. Model (Assistant)
1) __init__
import torch
import torch.nn as nn
from tools import weight_init, torch_support
import models
import models.encoders as encoders
class Assistant(nn.Module):
"""SIMMC Assistant Agent.
"""
def __init__(self, params):
super(Assistant, self).__init__()
self.params = params
self.encoder = encoders.ENCODER_REGISTRY[params["encoder"]](params)
self.decoder = models.GenerativeDecoder(params)
if params["encoder"] == "pretrained_transformer":
self.decoder.word_embed_net = (
self.encoder.models.decoder.bert.embeddings.word_embeddings
)
self.decoder.decoder_unit = self.encoder.models.decoder
# Learn to predict and execute actions.
self.action_executor = models.ActionExecutor(params)
self.criterion = nn.CrossEntropyLoss(reduction="none")
# Initialize weights.
weight_init.weight_init(self)
if params["use_gpu"]:
self = self.cuda()
# Sharing word embeddings across encoder and decoder.
if self.params["share_embeddings"]:
if hasattr(self.encoder, "word_embed_net") and hasattr(
self.decoder, "word_embed_net"
):
self.decoder.word_embed_net = self.encoder.word_embed_net
2) forward
1> 기본 forward propagation 과정
def forward(self, batch, mode=None):
"""Forward propagation.
Args:
batch: Dict of batch input variables.
mode: None for training or teaching forcing evaluation;
BEAMSEARCH / SAMPLE / MAX to generate text
"""
outputs = self.encoder(batch)
action_output = self.action_executor(batch, outputs)
outputs.update(action_output)
decoder_output = self.decoder(batch, outputs)
- outpus = self.encoder(batch)
- Utterance & History Encoder
- action_output = self.action_executor(batch, outputs)
(action_executor 함수 내부에서 Multimodal Fusion이 구현되어 있음에 주의)
- Multimodal Fusion
- Action Predictor
- decoder_output = self.decoder(batch, outputs)
- Response Generator
'NLP 연구실 업무 > DSTC9 - SIMMC' 카테고리의 다른 글
코드 분석 - Task 3 (0) | 2020.08.16 |
---|---|
[Background] Situated and Interactive Multimodal Conversations (0) | 2020.08.06 |