Data/Information

Hugging Face, Question answering

neulvo 2022. 4. 1. 13:00
 

Question answering - Hugging Face Course

Time to look at question answering! This task comes in many flavors, but the one we’ll focus on in this section is called extractive question answering. This involves posing questions about a document and identifying the answers as spans of text in the d

huggingface.co

Hugging Face 코스 리뷰의 마지막, Question Answering Task.

여기서는 extractive question answering에 대해 다룬다. (generate가 아니다.)

풀어서 얘기하자면, 문서 내에서 답이 되는 text를 뽑아내는 것이다.

 

SQuaD 데이터셋을 사용할 것이라 load_dataset()을 통해 불러와주었다.

id, title, context, question, answers 등이 features로 주어진 것을 볼 수 있다.

 

Context, Question, Answer의 text를 확인해주었고,

train 세트 answers의 text 길이, 즉 답의 개수가 1 이상인 경우가 있는지 체크해주었다.

하지만 evaluation 단계에서는 각 샘플마다 몇 개의 가능한 답들이 존재할 수가 있기 때문에, 그것을 확인해보았다.

가능한 answers 중에서 가장 score가 높은 answer이 뽑힐 것임을 짐작할 수 있다.

 

tokenizer를 불러와서 적용 후, decode해 출력해보았다.

question과 context text를 tokenizer에 함께 전달하였고 그에 따라,

[CLS] question [SEP] context [SEP] 과 같이 special token이 덧붙여진 형태가 되었다.

이제, question's answer의 label을 만들어 위와 같이 answers의 시작과 끝 지점을 가르키도록 해주고자 한다.

 

그런데 그전에 long context를 다루는 방법부터 먼저 살펴보자.

truncation="only_second" 는 question 및 context가 너무 길 경우, context를 truncate하도록 설정해주는 옵션이고

stride 는 두 연속된 chunks 사이에 overlapping 가능한 tokens의 개수를 설정해주는 옵션이며

return_overflowing_tokens=True 는 overflowing tokens을 반환하는지 여부를 tokenizer에 전달해주는 옵션이다.

위에서, decode된 출력물을 통해 같은 question에 대해 context text가 여러 개로 나뉘어진 것을 확인할 수 있다.

 

이처럼 위의 과정을 통해 long context를 truncate해주었는데

문제는 이 과정에서 answer이 포함되지 않은 training examples가 만들어진다는 것이다.

그래서 answer가 없는 examples의 start_position과 end_position의 labels을 0으로 처리해주고자 한다.

[CLS] token을 예측하도록 말이다.

또한, answer의 start_position만 존재하는 context의 label도 같은 방식으로 처리해주고자 한다.

(answer의 end_position이 없다면 완결된 answer를 extract할 수 없기 때문에)

 

그래서 그를 위해, return_offsets_mapping 옵션으로 token idices를 mapping 해주었다.

(answers의 indices를 확인해야 하므로)

또한 overflow_to_sample_mapping 옵션으로

truncate된 text가 어느 example로부터 나왔는지를 확인해주었다.

아래 예제에서는 4 examples를 tokeniz해

그것의 features의 over_flow_to_sample_mapping이 어떻게 나오는지를 보여주고 있다.

 

answers label을 위의 코드를 통해 만들어주었다.

offset_mapping을 받아오고

overflow_to_sample_mapping을 활용해 example 별로 sample answer을 받아왔다.

그리고 루프를 돌며, context의 시적과 끝 지점을 지정하고

그 안에 answer이 존재하는 경우 그 indices를 리스트 형식으로 저장해주며 라벨 작업을 해주었다.

 

위의 접근 방식이 맞는지 확인하기 위해 맞는 answer가 나오는지를 비교해주고

answer가 없는 feature를 불러와서 context 안에 answer가 없는지 확인해주었다.

 

tokenizer와 함께 위의 방법론을 적용한 full code다.

 

map() 함수를 활용해 위의 방식을 train 세트에 적용시켜주었다.

 

이것은 validation_examples을 전처리 해주는 함수이다.

labels을 만들어줄 필요가 없어서 그 부분이 사라졌고

대신에 question과 context를 구분해주기 위해 question의 offset을 None으로 만들어주었다.

 

이제 post processing 부분을 다뤄보고자 한다.

actual scores를 구하지 않고 logits 만으로 best predcition pair를 구할 것이다.

비교적 작은 데이터셋을 만들어 이를 실험해보자.

eval set을 만드는 전처리 과정이 끝난 후,

tokenizer를 다시 원래 사용했던 것으로 돌려주었다.

model이 기대하지 않는 columns을 제거해준 후 torch 포멧으로 변환해주었다.

batch를 간단하게 만들어주고 QuestionAnswering 모델을 불러 outputs을 뽑아주었다.

그리고 Trainer가 Numpy arrays 형태로 예측값을 주기 때문에 그에 맞춰서 logits를 받아주었다.

gpu 사용 코드가 내재되어 있다.

 

빈 딕셔너리를 만들어 eval_set의 features와 example을 맞춰주었다.

np.argsort()로 logit에 따라 index를 내림차순으로 정렬해주었고

Context에 answer가 없는 경우,

answer의 길이가 음수거나 max_answer_length보다 길 경우를 제해주었다.

조건에 해당하는 answers를 모아주었고 start_logit과 end_logit의 합이 가장 큰 answer를 반환해주었다.

 

예측한 answers와 eval_set의 answer를 비교해주었다.

그리고 squad metric의 score를 뽑아보았다.

 

이제 위의 방식을 Trainer에 적용시키기 위해 compute_metrics()라는 함수로 만들어주었다.

valid answer가 존재하지 않는 경우를 체크해주었다.

 

모델을 부르고 Training Arguments를 정의해주며 fine-tuning 해주었다.

compute_metric()을 활용해서 따로 evaluate 해줄 것이기 때문에 evaluation_strategy를 "no"로 해주었다.

 

Training.

 

trainer.predict()를 받는 변수의 개수가 안맞는 듯하다.

 

변수의 개수를 바꿔줘도 에러가 발생해서 일단 skip 하기로 했다.

 

이번에는 Accelerator를 사용해 DataLoader부터 Training Loop을 Customize하는 방법을 알아보자.

collat_fn 옵션을 통해 transformers에서 제공하는 default_data_collator를 사용할 수 있다.

아래는 모델은 다시 초기화해준 것.

 

Optimizer를 정의해주고 Acccelerator를 설정해주었다.

tpu 사용시 이 코드들을 training function에 넣어주어야 ㅎ나다.

linear 스케쥴러를 사용해주었다.

 

Training Loop. 여기서도 list out of index 에러가 나왔다.

아마 sample 중에 그 shape이 다른 것이 존재하는 것 같다.

start_logits과 end_logits를 구하는 식이 training_loop 안에 들어가 있다.

 

accelerator.wait_for_everyone() 은 process를 유보해주는 옵션으로

저장하기 전에 모든 process가 같은 model 아래에서 이루어졌음을 보장해주는 장치이다.

accelerator.unwrap_model()은 모델이 accelerator.prepare()로 넘어가기 전에 저장해주는 옵션이다.

accelerator.prepare() 함수가 distributed training 과정에서 모델을 변형하기 때문에 설정해주었다.

 

마지막으로 파이프라인을 활용해 문제를 해결하는 방법을 소개하며 마치겠다.

728x90

'Data > Information' 카테고리의 다른 글

정규표현식 python re  (0) 2022.04.22
Sql 문법 빠르게 훑어보기  (0) 2022.04.01
Hugging Face, Training a causal language model from scratch  (0) 2022.04.01
Hugging Face, Summarization  (0) 2022.03.25
Hugging Face, Translation  (3) 2022.03.17