Data/코드 리뷰

Evaluating Student Writing_2nd Place Solution

neulvo 2022. 3. 24. 16:54
 

2nd Place Solution - [CV741 Public727 Private740]

Explore and run machine learning code with Kaggle Notebooks | Using data from multiple data sources

www.kaggle.com

최근에 참여했던 캐글 공모전의 2nd place를 차지한 코드를 리뷰하고자 한다. 공모전 참여 기간 동안 노트북 작성자의 코드를 보며 많이 배웠기 때문에 최종 코드 또한 살펴보고 싶었다. 코드를 직접 돌려볼 수 있는 환경이 되지 않아서 코드를 읽고 분석하는 작업만 수행하고자 한다.

 

Inference Script with Post Process

The following Python script accepts a filename of a saved model, infers the test texts, applies post process, and writes a submission.csv file with an extra column of confidence scores per span (i.e. the probability that this span is correct). Click "show hidden cell" to see the code.

test text를 추론하고 후처리를 적용하는 부분에 대한 내용이다.

노트북 작성자의 코멘트를 살리고 싶어서 그대로 가져왔다.

argparse는 명령행 인터페이스를 작성하는 모듈이며 인자를 프로그램에 전달한다.

ArgumentParser()를 초기화하고 model_paths, save_name, max_len 등을 설정할 수 있게 해주었다.

그리고 아래에선 deberta의 fast tokenizer를 사용하기 위해 path를 설정해주었다.

만약 지정된 path에 파일이 존재하는 경우 unlink()를 통해 삭제해 주었다.

save_name 아래는 모델 정의와 모델 경로 설정 부분인데 거기에 필요한 라이브러리를 load해주었다.

 

필요한 라이브러리를 호출해주었고

os.environ으로 환경 변수를 설정해주었다. tokenizer의 병렬 작업 수행에 관한 부분인 듯하다.

아래로 보이는 것은 Argumentative elements의 NER 맵이다.

MIN_TOKENS 딕셔너리를 통해 threshold를 만들어 준 것을 볼 수 있고

save_name에 chris가 없을 경우와 있을 경우를 나누어 O(out) 태그의 값을 다르게 해준 것 또한 볼 수 있다.

-100의 값을 가지는 Special Token 또한 만들어주었다.

 

os.listdir() 을 통해 경로에 있는 모든 파일의 이름을 담은 리스트를 가져와주었다.

generate_text_for_file 함수는 test_dir 경로 아래 input_filename의 파일에 접근하여

그것을 읽어 들이고 id와 함께 txt를 리턴해주는 함수다.

mp.Pool() 함수와 map() 함수를 활용해 병렬 처리 및 test_files(테스트 파일 리스트)에 대해 일괄 처리 해주었다.

v3와 v2는 앞서 살펴봤듯 deverta를 사용할 경우, fast tokenizer를 사용하기 위해 작성된 구문이다.

그 외에는 AutoTokenizer로 pretrained 모델에 적합한 토크나이저를 불러내주었다.

assert 이하 구문은 tokenizer가 transformers.PretrainedTokenizerFast의 인스턴스인지 아닌지를 검사하는 구문이다.

ner_text_rows에서 끝에 'input_ids' 부분이 살짝 잘렸다.

위에서 처리한 txt를 tokenizer로 처리한 후 input_ids에 접근해 그 길이 순으로 ner_text_rows를 정렬해주었다.

아래의 tokenize_with_word_ids 함수는 sorted 이하의 부분과 유사한데

주어진 매개변수에 접근하여 그 txt를 tokenizer 처리해주고

그 'id'와 'offset_mapping'(token의 idx를 가져오는 것)를 함께 받아와주는 함수이다.

정렬된 ner_test_rows를 넣어 처리해준 것을 볼 수 있다.

 

NERDataset이라는 클래스를 만들어

위에서 만든 tokenized_all의 필요 부분에 접근하기 편리하게 해주었다.

 

soft_prediction을 정의해주었고

transformers의 TrainingArguments() 함수를 활용해 학습 arguments를 customize해주었다.

pretrained model path에 접근해 모델을 불러오고 trainer로 학습 후 predict까지 해주었다.

결과를 float16으로 변환해주었고 그 후, softmax 함수로 정규화 해주었다.

model, trainer, curr_preds를 제거하고

gc.collect()로 매모리를 해제해주었다.

argmax와 max함수로 최대값의 idx 및 최대값을 받아 저장해주었다.

 

isspace()는 문자열에서 공백의 포함 여부를 확인해주는 함수.

공백이 없을 경우에만 index를 받아 w라는 리스트에 저장해주었다.

그리고 마지막에 1e6이라는 큰 값을 넣어 txt 간 구분이 가능하게 해주었다.

np.ones()함수에 -1 값을 곱해 -1값을 가지는 배열을 만들어주고 offset을 돌며 단어들을 매핑해주었다.

 

그동안 만들었던 predictions과 test_dataset 클래스 객체 및 함수를 활용해서

sample_id에 해당하는 preds, offset, txt 값을 불러들이고 mapping을 해주었다.

soft_claim_predictions 값을 뽑았던 것처럼 claim_probs 변수를 만들어 claim 부분의 비교를 신경써준 모습이다.

for 문을 돌면서 B-(Beginning)에 해당하는 단어들의 예측, 확률값 등을 뽑아와주었다.

그리고 토큰을 세개 씩 보면서 토큰들의 NER 태그가 I-로 같은데 뒤에 붙은 element 라벨이 다를 경우,

모두 같은 element 라벨로 변환해주는 작업을 수행해주었다.

같은 element 라벨을 가지는 토큰 사이에 불분명한 라벨이 들어갔을 경우도 고려하여

뒤에 따라오는 I-(element) 이하 라벨과 같게 처리해주었다.

마지막 for 문도 유사하다. I 토큰들 사이에 O 태그를 지닌 토큰이 들어갔을 경우,

O 토큰을 양 옆의 I 토큰들과 맞춰주었다.

NER 태깅 과정에서 예외 케이스가 발생했던 것 같다.

 

word_probs가 0.54를 넘는 경우에,

Lead, Position, Concluding Statement에 해당하거나 I 토큰 일 경우 ,

MIN_TOKENS threshold를 넘거나, word_probs의 max 값이 0.73을 넘는다면,

max_probs 값을 저장하고 이미 딕셔너리에 존재했던 토큰인지 확인한 후에,

그 거리가 멀지 않다면 합쳐서 max값을 계산한 후에 딕셔너리에 저장해준다.

추가로 curr_preds를 확인해서 그것의 범위가 이전 preds의 안쪽에 존재하는 값이면 지워준다.

딕셔너리에 존재하지 않았던 경우는 값을 비교해서 최대값만 남겨준다.

Lead, Position, Concluding에 해당하지 않았을 땐, cls나 B 태그에 맞춰준다.

 

바로 위의 else 구문에 같이 걸리는 코드다.

Rebuttal, CounterClaim, Evidence 등을 다루는 것을 볼 수 있고

CV값의 향상을 위해 세부 조정을 해준 것을 보인다.

volume이 작은 경우를 고려해서 한 element의 길이가 작을 경우,

동일 element의 이전 span과 합쳐 주었고 그에 따라 curr_preds도 재작성해주었다.

Claim의 경우에는 prob 값에 따라서도 제한을 두어 데이터를 선별해준 것으로 보인다.

이전 element와 gap이 존재하는 경우에는 그 gap을 조정해주었다.

 

위의 구문과 마찬가지로 Lead, Position, Concluding Statement가 아닌 경우에 걸리는 구문이고

element(discourse)의 길이를 적절한 수준으로 조정해준 모습이다.

While 구문을 빠져나온 후에,

Lead, Position, Concludinig Statement에 해당하는 토큰을 불러와

마찬가지로 element의 길이가 너무 작을 경우, 그것을 늘려주고 저장해주었다.

결과를 데이터프레임 형식으로 저장하고 csv로 저장하였다.

 

Weighted Box Fusion

The following code comes from ZFTurbo's GitHub here. First, the text predictionstring are converted to 1 dimensional boxes. Next they are ensembled with ZFTurbo's WBF code. Click "show hidden cell" to see the code.

text predictionstring을 1차원 박스로 만들어주는 방법론이라는데 살펴봐야 알 것 같다.

prefilter_boxes() 함수는 shape나 threshold를 체크해주고 정렬해주는 함수.

box와 score의 길이가 다를 경우 에러 발생

score에 따라 threshold를 주어서 값을 선별

box내의 데이터를 크기 순으로 정렬

딕셔너리 내의 리스트를 정렬하고 numpy 배열로 변환

 

get_weighted_box() 함수는 매개변수로 받은 boxes의 confidence 값을 계산하여 리턴해주는 함수.

 

find_matching_box_quickly() 함수는 numpy array를 활용해 박스들 사이에서 iou의 최고값과 idx를 뽑아주는 함수.

bb_iou_array boxes와 new_box의 minimum과 maximum값을 뽑은 다음 자카드 지수를 리턴해주는 함수.

iou값을 계산한 후에 최고값을 지니는 idx와 iou를 리턴.

 

boxes_list와 weights의 len을 맞춰준다.

weights를 np.array로 만들어주고

prefilter_boxes 함수로 boxes_list를 조건 처리하고 정렬.

 

overall_boxes 리스트를 만들고

box들의 iou값을 뽑고 get_weighted_box함수로 confidence값을 도출하고 weighted_boxes에 쌓아줌.

np.vstack()은 배열을 세로로 결합해주는 함수.

conf type에 따라서 conf를 계산 후 rescale.

overall_boxes에 결과를 붙이고 합쳐준다음 boxes, scores, labels로 각 부분을 정렬해서 리턴.

 

generate_preds.py를 모델 path를 다르게 해서 돌려줌.

모델이 돌아가는 모습.

 

그리고 작성된 결과를 불러옴.

 

각 Discourse type마다 threshold를 다르게 주고 CV 값을 뽑음.

preprocess_for_wbf() 함수는 전처리 함수로 socres_list, labels_list와 함께 predictionstring을 자르고 문자로 치환한 후 첫 번째, 마지막 번째의 값을 int로 다시 바꿔준 후 boxes_list로 만들어 리턴해주는 함수.

postprocess_for_wbf() 함수는 후처리 함수로 threshold를 주어 idx, label, 시작부터 끝까지의 offset을 리턴해줌.

generate_wbf_for_id() 함수는 id에 따라 각 모델에 해당하는 데이터프레임을 부르고 만들어준 후, 전처리 및 wbf를 처리해준 후 후처리 값을 리턴해주는 함수.

wbf는 앙상블 기법으로 박스들 사이에서 가장 좋은 score를 리턴해주는 방법이라고 볼 수 있겠다.

 

멀티프로세싱 후, preds를 뽑고 submission으로 저장.

 

시각화 함수.

visualize() 함수로 데이터프레임을 받아와서

predictionstring을 나눠주고 시작 지점과 끝 지점 그리고 라벨을 딕셔너리로 만든 다음

시작 지점을 기준으로 정렬.

그리고 discourse_type을 기준으로 color를 다르게 주어 시각화하였다.

displacy.render() 함수는 spacy 라이브러리의 시각화 함수로 option에 따라

dependency parse tree 혹은 named entity를 랜더링해주는 함수.

 

이상이다. 코드를 리뷰하면서 WBF 같은 앙상블 기법을 알게 된 것도 좋았지만

무엇보다도 클래스와 함수를 짜고

process를 단계적으로 진행하는 법을 배울 수 있었던 게 가장 좋았다.

728x90