https://github.com/whatsbirddd/Fake-Documents-Detection
GitHub - whatsbirddd/Fake-Documents-Detection: CLOVA AI Rush 2022 @Naver AI Lab
CLOVA AI Rush 2022 @Naver AI Lab. Contribute to whatsbirddd/Fake-Documents-Detection development by creating an account on GitHub.
github.com
🌷 Overview
- 결과 : 18위(0.93)까지 2라운드 진출인데 제가 19위(0.928)라서 2라운드는 못가게 되었네요 🥺..
- 주제 : 엉터리 문서 검출
- 엉터리 문서 정의 : 문맥이 맞지 않는 단어들로 구성된 문서로, 단어의 순서를 바꾸어도 전혀 말이 되지 않는 문장들이 포함된 문서
- 데이터 불균형이 굉장히 심했음(130000:4000)
- 최종 스코어 솔루션 : kr-electra + lstm기반의 classifier / focal loss / 데이터 샘플링으로 20000개 엉터리 문서를 만들어서 같이 train시킴
- 데이터 샘플링 방법 : 이건 제가 그냥 실험해본 건데, 정상 문서들중에 유사도가 먼 문서2개를 뽑아서 섞어서 엉터리 데이터를 만들었음, 이방법으로 20000개의 추가 데이터를 만들었습니다.
- 외부데이터로 했다면 성능을 더 높였을수도(1등 솔루션 - 외부데이터로 셔플)
- 0.93이상의 솔루션을 보니까 언더샘플링으로 학습 배치때마다 정상문서와 엉터리 문서의 비율을 맞춰주는 게 정말 효과적이었더라구요! (학습방법도 많이 고민해봐야겠다는 걸 느꼈습니다.)
🌷 대회에 사용한 코드
- main.py :
- nsml(학습을 위한, 네이버에서 제공해주는 리소스를 잘 사용하기 위해 네이버에서 제공하는 라이브러리)
- dataloader
- model.py : 실험한 모델들 마구잡이로 넣어두었습니다😰
- util.py : 실험을 위한 함수들을 넣어두었습니다.
- preprocess : 전처리가 유효한가? 오탈자, 해시태그들(잘못 예측된 데이터들을 직접봤을 때 해시태그를 포함하고 있는 데이터들이 많았습니다…)
- mixup : 엉터리 문서 생성을 위한 코드
- focal loss vs weighted cross entropy vs cross entropy
- weight를 준 크로스엔트로피는 도움이 되지 않았고
- focal loss는 확실하게 도움이 되어서 선택!
- train.py : 네이버에서 제공한 baseline 코드 거의 그대로 사용했습니다.
🌷 스스로에 대한 피드백
1. 여러개의 모델을 테스트 할 때
- models folder로 base모델마다 모듈(.py)로 만들어서 model = __import__('models').__dict__[modelname]로 사용
- “models” folder
- 설명 : base (KRElectra,KoBert,KoBigBird) - classifier(avgpool-linear, bi-LSTM, CNN))
- resnet은 make_layer라는 함수를 통해서 init에서 레이어를 예쁘게 작성하더라…🤭
- init.py
- .은 relative import가 가능하도록 하는 부분
from .factory import create_model from .vgg import * from .sid_resnet import * from .wavelet_module import * from .detector import *
- factory.py
- model = import('models').dict[modelname]
- 다른 디렉토리에 있더라도 쉽게 모듈을 가져와서 사용하는 방법임. 그러면 코드가 굉장히 간결해짐.
- 모델의 파라미터가 다르면 if로 나누어서 모델 생성해주면 될까?
def create_model(modelname, hidden_size, output_size, embed_dim, num_layers, batch_first, bidirectional,maxlen): model = __import__('models').__dict__[modelname]( hidden_size = hidden_size output_size = output_size embed_dim = embed_dim num_layers = num_layers bidirectional = bidirectional batch_first = batch_first maxlen = maxlen ) if modelname == 'name1': #어떤 특별한 조건이 있다면 model = classname(model, dataname) if checkpoint: assert os.path.isfile(checkpoint), "checkpoint does not exist" if modelname != 'name1': model.model.load_state_dict(torch.load(checkpoint)) else: model.load_state_dict(torch.load(checkpoint)) return model
- model = import('models').dict[modelname]
- 설명 : base (KRElectra,KoBert,KoBigBird) - classifier(avgpool-linear, bi-LSTM, CNN))
- “models” folder
- 허깅페이스로 불러오는게 동일하기 때문에 model.py에서 모델이름 불러와서 관리
class DocumentClassifier(nn.Module):
def __init__(self, model_name, hidden_size, output_size, embed_dim, num_layers, batch_first, bidirectional,maxlen):
super(DocumentClassifier, self).__init__()
self.hidden_size = hidden_size
self.output_size = output_size
self.embed_dim = embed_dim
self.num_layers = num_layers
self.batch_first = batch_first
self.bidirectional = bidirectional
self.maxlen = maxlen
self.embeded = AutoModelForSequenceClassification.from_pretrained(model_name) #(batch, maxlen, embed)
self.lstm = nn.LSTM(input_size = self.embed_dim,
hidden_size = self.hidden_size,
num_layers = self.num_layers,
batch_first = self.batch_first,
bidirectional = self.bidirectional
)
self.linear = nn.Sequential(
nn.ReLU(),
nn.BatchNorm1d(self.hidden_size*2),
nn.Linear(self.hidden_size*2, self.output_size)
)
def forward(self, input_ids=None, token_type_ids=None, attention_mask=None,labels=None):
#(last_hiddne_state, hidden_states, attentions, cross_attentions)
x= self.embeded(input_ids=input_ids, token_type_ids=token_type_ids, attention_mask=attention_mask)
x = x[0]
z, _ = self.lstm(x) #(output) = (batch, max_len, hidden_size), (h_n,c_n)
#z = z[:,-1,:].squeeze(1) #last hidden state (batch, hidden_size)
z = nn.AvgPool2d((self.maxlen,1))(z).squeeze() #(batch_size, hidden_size)
y_pred = self.linear(z)
return y_pred
2. config 파일 활용하기
- modelname_config.yml
- predict_modelname_config.yml
DATALOADER: num_workers: 32 shuffle: False pin_memory: drop_last: False SEED: random_seed: 42 PREDICT: model_name: 'kykim/bert-kor-base' trained_model_name: 'bert' batch_size: 64 submission_name: 'bert'
- train_modelname_config.yml
TRAIN: data_type: 'original' model_name: 'kykim/bert-kor-base' num_epochs: 2 batch_size: 64 learning_rate: 2e-5 early_stopping_patience: 5 model: optimizer: scheduler:100 momentum: weight_decay: 0.1 loss_function: SEED: random_seed: 42 DATALOADER: num_workers: 32 shuffle: pin_memory: drop_last:
# CONFIG parser = argparse.ArgumentParser() parser.add_argument('--config_name', type=str, required=True) args = parser.parse_args() CONFIG_NAME = args.config_name print(CONFIG_NAME) TRAIN_CONFIG_PATH = os.path.join(PROJECT_DIR, 'config', f'{CONFIG_NAME}.yml') config = load_yaml(TRAIN_CONFIG_PATH) # SEED RANDOM_SEED = config['SEED']['random_seed'] # WANDB RUN_NAME = config['WANDB']['run_name'] # TRAIN DATA_TYPE = config['TRAIN']['data_type'] # original or all MODEL_NAME = config['TRAIN']['model_name'] EPOCHS = config['TRAIN']['num_epochs'] BATCH_SIZE = config['TRAIN']['batch_size'] LEARNING_RATE = config['TRAIN']['learning_rate'] EARLY_STOPPING_PATIENCE = config['TRAIN']['early_stopping_patience'] OPTIMIZER = config['TRAIN']['optimizer'] SCHEDULER = config['TRAIN']['scheduler'] MOMENTUM = config['TRAIN']['momentum'] WEIGHT_DECAY = config['TRAIN']['weight_decay']
3. Docker 이미지 사용하기
- 사실 docker, requirements.txt, setup.py를 적재적소에 사용하는 방법을 모르겠다.
- 칭찬할 점
- 3주동안 파이토치에 익숙해지려고 노력했고, 매일 코드를 보았고, 계속 시도를 했다는 것
- 허깅페이스와 친해짐
- 무작정 시도만 하는 것이 아니라 결과를 바탕으로 보완이 된 시도를 했다는 것. 결과를 계속 보려고 했는 것
- 파라미터 튜닝을 하다보니까 점점 다양한 파라미터들이 보이기 시작했다. 모델뿐만아니라 optimizer, loss function 등 이곳저곳에서 우당탕탕 실험해 볼 숫자들이 보였다.
- 부족했던 점
- 약간 조급했다? 조급하다보니까 내가 빠르게 돌릴 수 있는 것만 생각하게 되고, 지금 당장 시도할 수 있는 것만 하게되었다. 에러가 많이 날 가능성이 있고 코드 고쳐야할 것이 많다고 생각되는 부분들에 대해서는 쉽게 뒤로 미루었던 거 같다. 미루다보니까 태스크에서 잊혀졌다.
- 솔루션 보완
- 엉터리 문서 데이터셋을 만들 때 내부데이터로 셔플하는 방법으로 만들었는데, 외부 데이터로 셔플을 했으면 더 효과적이었을 것.
- 오버샘플링 해볼걸.. 블로그에서 텍스트 데이터는 오버샘플링, 언더샘플링이 잘 작동하지 않는다는 말들을 보고 적용하지 않았던 게 잘못된 판단이었다. 직접 실험해보고 눈으로 봤어야 했는데, 남의 말만 듣고 시도하지 않았다는 게 아쉽네
- 오히려 언더 샘플링을 통해서 두 라벨에 밸런스를 맞춰주는 것이 더 성능을 높이는 방법이었다.
- k-fold 앙상블 다음에 꼭 써먹자. 학습 방법도 중요하다는 것을 깨달았다. 모델의 아키텍처에 대해서만 고민을 많이 했는데, 그것보다 학습 방법에도 눈을 돌려봤어야 했다!
- 학습할 때에 엉터리문서와 정상문서의 비율을 맞춰서 학습해주는 것이 좀 더 효과적이었음.
'Project 🖥' 카테고리의 다른 글
project 07/19 (0) | 2022.07.19 |
---|---|
project 07/14 (0) | 2022.07.14 |