이번주엔 첫 대회를 진행하였다. 지금까지 배워온 이론을 바탕으로, 코드를 작성해 데이터를 만져보고 모델을 돌려보는 작업을 했다.
<목차> 1. 과제에 대한 이해 2. 데이터 EDA 3. 데이터 Augmentation 4. 템플릿화 - wandb, sweep 5. 그 외 시도들 - k-fold - freeze - ensemble 6. 모델
Semantic Text Similarity(STS)
STS란 두 텍스트가 얼마나 유사한지 판단하는 NLP Task이다. 일반적으로 두 개의 문장을 입력하고, 이러한 문장쌍이 얼마나 의미적으로 서로 유사한지를 판단한다. 보고서나 논문 등의 정보 전달 목적의 글에서 중복된 문장들은 가독성을 떨어뜨리는데, STS는 이러한 중복 문장 제거의 교정작업을 도와준다.
Textual Entailment (TE)
STS와 TE, 두 문제의 가장 큰 차이점은 ‘방향성’이다. STS는 두 문장이 서로 동등한 양방향성을 가정하고 진행되지만, TE의 경우 방향성이 존재한다. 예를 들어 자동차는 운송수단이지만, 운송수단 집합에 반드시 자동차만 있는 것은 아닙니다. 또한 출력 형태에 대해서도 차이가 있다. TE, STS 모두 관계 유사도에 대해 참/거짓으로 판단할 수 있지만, STS는 수치화된 점수를 출력할 수도 있다.
STS의 장점
STS의 수치화 가능한 양방향성은 정보 추출, 질문-답변 및 요약과 같은 NLP 작업에 널리 활용되고 있다. 실제 어플리케이션으로는 데이터 증강, 챗봇의 질문 제안, 혹은 중복 문장 탐지 등에 응용되고 있다.
NLP 데이터는 어떻게 EDA((Exploratory Data Analysis)를 진행해야 할까?
- EDA란? 데이터를 분석하기 전에 그래프나 통계적인 방법으로 자료를 직관적으로 바라보는 과정
- EDA의 목적: 데이터의 분포 및 값을 검토함으로써 데이터가 표현하는 현상을 더 잘 이해하고, 데이터에 대한 잠재적인 문제를 발견할 수 있다.
- EDA의 과정:
1. 데이터를 전체적으로 살펴보기 : 데이터에 문제가 없는지 확인
[결측치 확인] 결측치: NA와 같은 누락된 값
해결 방안 - 결측치가 있는 데이터 삭제 - 특정 값으로 일간 치환 - 숫자라면 보간법 활용 ex) 1, 2, ??, 4, 5 => 1, 2, 3, 4, 5 - 결측치 예측 모델을 추가적으로 활용
[데이터 분포] - Train/Dev/Test 데이터 개수 및 비율 확인 데이터 수가 충분한 가, Dev와 Test 데이터 비율이 일치하는 가 확인 - 정답 Label의 개수, 종류, 분포를 확인 - tokenizing을 통한 Train/Dev 문장 문석
[train 데이터 라벨링 관련] 라벨에 따른 데이터 수가 적절하게 분포되어 있는 지 어떤 기준으로 라벨링이 되어있는지 오라벨링된 데이터는 없는지
[데이터 내용적인 부분] 중복된 데이터는 없는지 positive pair은 충분한지 *positive pair은 contrastive learning에서 같은 이미지로부터 augment된 데이터를 의미하는데, 여기서는 라벨이 상대적으로 유사한 데이터로 사용
[NLP task] 문어체인지, 대화체인지 문법이 잘 적용된 데이터인지?, 띄어쓰기가 제대로 되어있는지?, 비문은 없는지? 글자 외, 이모티콘이나, 다른 특수한 표현은 없는지?
2. 데이터의 개별 속성값을 관찰 : 각 속성 값이 예측한 범위와 분포를 갖는지 확인. 만약 그렇지 않다면, 이유가 무엇인지를 확인.
수치가 이상한 데이터는 없는지
값의 범위가 데이터 스키마(설명서)와 동일한지
3. 속성 간의 관계에 초점을 맞추어, 개별 속성 관찰에서 찾아내지 못했던 패턴을 발견 (상관관계, 시각화 등)
도출 결과
라벨이 5인 데이터가 적어서 증강이 필요하다.
Data Augmentation
데이터 증강을 위해 일반적인 방법들 중 하나인 AEDA, back-translation, special token 생성을, 위 task에서 특수한 방법으로 sentence가 공통인 데이터 처리를 시도해봤다.
AEDA
NLP에서 Text Augmentation 방법은 크게 텍스트의 일부를 변형하여 데이터를 증강하는 방법과 생성모델을 사용하여 새로운 텍스트를 생성하여 데이터를 증강하는 방법이 있다.
그 중에서 가장 손쉽게 접근할 수 있는 방법은 KoEDA 라이브러리를 사용하는 것이었다. KoEDA는 EDA와 AEDA 논문에서 소개된 방식을 한국어 Wordnet 으로 Porting하여 공개한 오픈소스 라이브러리이다.
Easy Data Augmentation라는 논문에서는 데이터를 다음의 네 가지 기법을 통해 자연어 데이터를 증강하고자 한다.
유의어로 교체(Synonym Replacement, SR): 문장에서 랜덤으로 stop words가 아닌 n 개의 단어들을 선택해 임의로 선택한 동의어들 중 하나로 바꾸는 기법.
랜덤 삽입(Random Insertion, RI): 문장 내에서 stop word를 제외한 나머지 단어들 중에서, 랜덤으로 선택한 단어의 동의어를 임의로 정한다. 그리고 동의어를 문장 내 임의의 자리에 넣는걸 n번 반복한다.
랜덤 교체(Random Swap, RS): 무작위로 문장 내에서 두 단어를 선택하고 위치를 바꾼다. 이것도 n번 반복
랜덤 삭제(Random Deletion, RD): 확률 p를 통해 문장 내에 있는 각 단어들을 랜덤하게 삭제한다.
데이터셋이 적다는 가정에서, 데이터셋이 500개일 때 EDA를 포함하면 평균적으로 3%의 성능이 증가함을 확인할 수 있다. full set일 때도 EDA를 사용하면 평균적으로 성능의 향상이 있었다. 하지만 우리가 사용할 BERT 등의 선학습 모델은 거대 데이터셋으로 선학습되었기에 데이터셋의 개선 효과를 못 볼 수도 있다고 한다. 한 문장에 대해서 몇 개의 문장을 만들건지에 따라 α값에 조정이 필요하며, 4문장 이하는 p=0.1, 4문장 초과는 p=0.05 정도의 확률값으로 데이터를 변형하는게 가장 성능이 좋았다고 저술되어있다.
하지만 텍스트 데이터의 특성상, 위치를 바꾸거나 일부 단어를 제거하는 것은 결국 본 문장의 의미를 손실시키는 행위이기 때문에 AEDA 방법론이 등장하게 된다. AEDA는 문장을 손실시키지 않게 하기 위해 Special character를 문장 곳곳에 배치하는 방법론으로, 역시 많은 특수문자가 들어가게 되면 성능이 떨어지기 때문에 적절한 확률값을 찾는 것이 중요하다.
한글 자연어 처리 패키지 konlpy
konlpy는 형태소 등을 알아서 분석해주는 편리한 패키지이지만, konlpy 내 클래스는 Java 기반이기 때문에 그냥 pip install konlpy로 설치할 수 없다. 아래와 같은 과정을 거쳐야 하는데 1. JAVA 설치 https://www.oracle.com/java/technologies/javase-downloads.html 2. JAVA_HOME 환경변수 설정JPype 다운로드 및 설치 https://www.lfd.uci.edu/~gohlke/pythonlibs/#jpype 이때, 파이썬 버전과 맞게 다운로드해야만 cmd창에서 'not a valid wheel filename' 에러가 안 뜬다 4. konlpy 설치
- 별희님이 AEDA 코드를 작성해주셨는데 대회가 끝나면 첨부할 예정이다.
도출 결과 AEDA를 사용해서 sentence1,2를 모두 바꾼 데이터셋이 가장 성능이 높았다.
Data Augmentation - special token 생성
- 진호님이 special token 코드를 작성해주셨는데 대회가 끝나면 첨부할 예정이다. 참고한 수도 코드이다.
k-fold에 필요한 parameter을 config에 추가한다. setup 함수를 제외한 나머지 부분은 Dataloader과 유사하다.
def setup(self, stage="fit"):
if stage == "fit":
# 데이터 준비
total_data = pd.read_csv(self.train_path)
total_input, total_targets = self.preprocessing(total_data)
total_dataset = Dataset(total_input, total_targets)
# 데이터 셋 num_splits 번 fold
kf = KFold(n_splits=self.num_splits, shuffle=self.shuffle, random_state=self.split_seed)
all_splits = [k for k in kf.split(total_dataset)]
# k번째 fold 된 데이터셋의 index 선택
train_indexes, val_indexes = all_splits[self.k]
train_indexes, val_indexes = train_indexes.tolist(), val_indexes.tolist()
# fold한 index에 따라 데이터셋 분할
self.train_dataset = [total_dataset[x] for x in train_indexes]
self.val_dataset = [total_dataset[x] for x in val_indexes]
else: # 평가 데이터 준비
test_data = pd.read_csv(self.test_path)
test_inputs, test_targets = self.preprocessing(test_data)
self.test_dataset = Dataset(test_inputs, test_targets)
predict_data = pd.read_csv(self.predict_path)
predict_inputs, predict_targets = self.preprocessing(predict_data)
self.predict_dataset = Dataset(predict_inputs, [])