본문 바로가기
NLP

[TDS] Multi-WOZ 데이터셋 Delexicalization 코드 분석 (SOLOIST)

by 방구석 몽상가 2021. 9. 8.
SOLOIST_preprocess

SOLOIST 논문에서 사용된 delexicalization 및 기타 전처리 과정을 코드를 통해 살펴본다. 아마 DAMD 전처리 방법과 동일하지 않을까 생각한다. SOLOIST는 Multi-WOZ 2.0 데이터를 기준으로 처리하지만, 아주 약간만 변경해서 2.1 데이터도 처리 가능하다. SOLOIST 논문에 대한 설명은 https://wdprogrammer.tistory.com/84 를 참고하자. 코드 링크는 https://github.com/pengbaolin/soloist 다.

create_delex_data.py

먼저, main 함수는 delexicalization 과정과 대화 내 역할을 나누는 부분으로 되어있다.

create_delex_data.py/createDelexData()

createDelexData 함수의 세부 처리 과정을 친절하게 주석으로 알려주고 있다. loadData는 MultiWOZ 데이터를 data 폴더에 잘 옮겨놓지 않았을 때를 대비해 예외처리하는 함수다. 데이터를 제대로 놓았다면 아무 일도 하지 않는다.

create_delex_data.py/createDelexData()

Delexicalized values에 대한 딕셔너리를 만들고 두 개의 파일을 불러오는데 Multi-WOZ 2.1의 경우, dialogue_acts.json 파일이 system_acts.json으로 변경되어 system_acts.json 파일명을 dialogue_acts.json으로 바꿔주어야 한다.

delexicalize.py/prepareSlotValuesIndependent()

Multi-WOZ 데이터셋의 도메인 7개를 domains에 사전정의하고 있으며, requestables는 선언만 되고 쓰이진 않는다. 그러나 DAMD에서 이야기한 이전의 delexicalizaton 결함을 수정하기 위한 변수로 선언했었다고 생각된다. 다른 domain 내 같은 slots을 다르게 delexicalization 하는 문제였는데, 예로 phone, address, name 등이 <restaurant.phone>, <hotel.phone>과 같이 달리 되는 문제다.

이제 도메인 별 데이터베이스를 읽어가며 정규표현식을 통해 해당 값에 slot을 할당한다. 코드를 보기 전에 파일부터 보자.

도메인 별 데이터베이스는 json 파일로 존재하며, 내용 예시는 다음과 같다.

attraction_db.json

DB에서 1 row는 slot-values pairs로 구성된 하나의 dictionary로 표현된다. 이제 코드를 봐보자.

delexicalize.py/prepareSlotValuesIndependent()

7개의 도메인 모두에 대해 DB를 읽고 value에 slot을 할당하는 것을 반복한다.

DB 내 모든 rows에 대해 반복하고, 값이 '?'이거나 'free'이면 처리하지 않는다. Attraction 도메인에서 entrance fee에 '?'와 'free' 값이 존재한다.

Address slot은 여러 도메인 내에 존재한다. 일단 DB 내 값 그대로 하나를 추가한 다음 road나 street의 축약을 대비해 augmentation한다. 여기서 normalize 함수는 nlp.py 내에 있고 정규 표현식을 이용해 여러 전처리를 진행한다. 대문자를 소문자로 바꾸고 앞 뒤 공백을 지운 뒤 전화번호나 post code 등의 형식을 통일시킨다. 나머지 slot에 대한 처리도 이와 거의 비슷하다.

기차 도메인에 대해서 추가적으로 처리를 해준다. 출발지와 도착지에 대해 place slot을 만들고 요일별 slot을 만든다. Area, food, pricerange는 도메인에 상관없이 동일한 slot으로 맵핑되도록 한다.

다시 createDelexData 함수로 돌아와서, data는 위에서 data.json 파일로부터 로드된 dict 객체다. "data.json" 파일 내부 형식을 보자.

여러 정보를 가지고 있는데 "goal"이 target이고 "logs"는 텍스트로 된 대화와 "dialog_act" 등을 담고 있다. 이제 코드를 보자.

create_delex_data.py/createDelexData()

먼저, slot의 value에 했듯이 모든 대화의 utterance를 normalization한다. 그리고 마치 tokenization 하는 것처럼 보이지만 ' '.join(words)을 통해 다시 합쳐주어 인수로 주기에 탭이나 개행을 지우기 위해 이러한 과정을 거친 것이다. delexicalise 함수는 utterance와 key가 "value"이고 value가 "slot name"인 dict 객체를 인수로 받아 delexicalized utterance를 반환한다. 근데 이 함수의 코드를 보면

(' '+utt+' ') 이 부분 때문에 utt[1:-1]를 해주는데 왜 이렇게 하지란 생각을 했다. 몇 가지 샘플로 해봤는데 딱히 하는 이유를 찾을 수 없었다. SOLOIST의 저자들도 다른 곳에서 코드를 가져온 모양인데 (아마 DAMD?) 그래서 "why this?" 라는 주석을 달아놓았다. 다시 createDelexData 함수 코드로 돌아오자. delexicaliseReferenceNumber 함수는 logs 내 metadata로 belief state가 제공되는데 이를 기반으로 예약 번호 (예: 7GAWK763)를 delexicalization 한다. 정리하면, DB 내 entities에 대해 한 번 delexicalization 하고 belief states를 참고해서 예약 번호를 delexicalization 한다. 이 때, 계속해왔던 것처럼 "#", "ref#" 으로 시작하는 경우들을 위해 slot-key pairs를 augmentation 한다. 그 다음, 숫자를 [value_count]로 대체하고 delexicalized text를 저장한다.

create_delex_data.py/createDelexData()

그 다음, 시스템 턴에 맞춰 전처리를 진행한다. addDBPointer 함수는 모든 관련된 도메인에 대한 DB pointer를 만든다. 도메인은 "식당, 호텔, 어트랙션, 기차" 가 선택되었다. addBookingPointer 함수에서 "식당, 호텔, 기차"에 대한 예약 정보가 point vector 형식으로 붙는다. _text 함수는 말 그대로 텍스트 형식으로 반환한다 (예: 'restaurant booked;hotel booked'). 마지막으로, fixDelex 함수에서 "dialogue_acts.json" 파일로부터 system dialogue acts를 받아 도메인 내 잘못된 delexicalization을 수정한다. 그리고 "data/multi-woz/delex.json"에 저장한다.

divideData 함수는 delexicalized 대화들을 ListFile을 기준으로 train, test, val로 나누고 user, system, DB, Belief state, user no delex, sys no delex, pointer로 구분하여 나눈다.

댓글