페이지

2023년 7월 31일 월요일

영문 오픈소스 LLM에 한글 데이터세트로 파인튜닝하기

 한글 경량화 Private LLM 개발

메타에서 2월에 Llama 거대 언어모델을 발표하였다. 커뮤니티의 연구자들은 그간 내부구조와 코드를 공개할 것으로 기대했던 OpenAI의 챗GPT의 다른 행보에 따라 거대언어모델에 관심이 높던 상태에서 Llama 언어모델 공개로 관심을 끌었다. 곧이어 스탠포드에서 이 언어모델에 instruction 세트들을 OpenAI 모델을 사용하여 instruction 과 ouput 형태의 데이터세트들로 파인튜닝한 instruction tuned LLM 을 선보임에 따라 연구 커뮤니티들은 후속되는 챗GPT를 모방할 수 있는 LLM 개발 가능성에 영감을 얻고 Llama 를 기반 모델로 한 후속 오픈소스 LLM 모델이 속속 발표되는 계기가 되었다. 
본 프로젝트는 이러한 영문 오픈소스 LLM을 한글에 적용하는데 필요한 모델 개발을 목적으로 한다. 한글 챗GPT(GPT-3.5)와 같으면서 상용화가 가능하고 비교적 저 사양의 추론 환경에서 사용할 수 있도록 훈련 가능 모델 파라미터와 메모리 공간을 절약할 수 있는 QLoRA 와 4bit quantization 기술이 적용된 모델 개발을 목적으로 한다. 
이러한 한글 경량화 LLM 은 OpenAI API 를 통한 외부 접촉을 통한 사용에 민감한 국내 기업들이 개별(Private) LLM 을 통하여 기업 내부 지식기반을 대상으로 한 챗GPT 운영의 필요성 대두에 기인한다.  Alpaca 7B을 A100 8대로 8시간 훈련하여 OpenAI 의 챗GPT에 어느정도 근접한 성능을 보인다는 경로를 따라 영문 instruction 데이터세트 대신 한글 데이터세트를 이용한 파인튜닝을 시도해보고자 한다.

기반 모델
오픈소스 LLM 중에서 상용라이선스가 허용되며 한글데이터세트로 사전학습된 모델을 찾기가 용이하지 않았다. 아래 그림1의 허깅페이스 리더보드에 나타난 EleutherAI 사가 개발한 polygot-ko-12.8b 모델을 사용하였다. 이 모델은 Apache 2.0 라이선스에 한국 AI 스타트업 TUNiB 사에 엄선된 863GB 의 한글 언어 데이터로 사전학습된 모델이다. 영문 기반모델의 경우, 254대의 A100 GPU로 GPT-NeoX 모델을 1670억 토큰을 301,000 스텝 훈련하였다. 자세한 내용을 여기 참조.


<그림 1. Polyglot-ko-12.8b 모델 허깅페이스 리더보드>


한글 데이터세트

파인튜닝을 시행할 한글데이터세트는 허깅페이스의 beomi/KoAlpaca-v1.1a 데이터세트를 사용한다. 데이터세트는 v1.0 에서 Alpaca 의 5만2천개의 instruction 예들을 지도기반 파인튜닝한 영문 데이터세트를 DeepL 번역기로 한글로 번역한 데이터세트를 적용한 결과가 만족스럽지 않아 네이버 지식인에 있는 내용중 원문과 답변을 재가공하고 엄선하여 21,155 행의 instruction, output, url 형식으로 아래 그림 2와 같이 구성한 데이터세트다. url 은 네이버 지식인의 원문 출처다.

<그림 2. 한글 데이터세트>

KoAlpaca v1.1a 데이터세트 로드

아래 그림3과 같이, ① 허깅페이스의 datasets 를 설치, ② load_dataset을 도입, ③데이터세트를 로드한다.

<그림 3. 데이터세트 로드>

아래 그림4와 같이, lambda 함수를 사용하여 data를 변형시킨다. data 의 ‘text’ 컬럼을 ① 과 같이 ###질문: 에 이어, ② x[‘instruction] 이 뒤따르고, 답변도 같은 형식으로 이어진 다음 ③에 endoftext 코드로 마감한다. 즉 기존 ‘instruction’: ‘output’: 형식에 질문: 과 답변: 이라는 한글을 매핑한다.

<그림 4. 데이터세변형>

아래 그림5와 같이 GPU 사용을 위한 설정을 한다. 저자는A100 GPU 4개를 사용하지만 huggingface 가 bitsandbytes 의 4bit precision 으로 모델 weight 파라미터 크기를 줄이고 QLoRA로 LLM 파인튜닝에 메모리 사용량이 줄어들어 단일 24GB GPU 에서 33B LLM 을 파인튜닝할 수 있도록 하고 있어서 24GB 이상의 메모리가 가용한 GPU가 있는 환경에서는 이론적으로 파인튜닝이 가능하다. 자세한 내용은 여기 참조. 

<그림 5. GPU 정의>

모델 로딩

아래 그림6의 ① safetensor 는 허깅페이스의 새로운 텐서 저장 형식으로 텐서를 간단하고 안전하게 그리고 pytorch 에 비해 2배 이상 빠르게 weight 를 로드할 수 있는 형식이다. Huggingface 에 있는 safetensor 모델을 model_id 로 정의한다. bitsandbytes의 4비트 quantization 을 사용하므로 ② 에서 load_in_4bits=True 는 .from_pretrained 방법으로 모델을 로드할 때 4비트 정밀도로 로드하고, bitsandbytes의 bnb_4bit_use_doule_quant=True로 중첩된 한번 더 양자화(quantization)를 사용함으로써 성능 감소 없이 보다 많은 메모리를 절약할 수 있다. ③에서 새로운 4bit 데이터형식의 weights 로 normal float 4(nf4) 데이터 형식을 사용한다. ④에서 은닉층의 행렬곱 등의 연산에 사용하는 연산 유형을 default 가 float32를 사용하는데 이를 bfloat16으로 변경함으로써 속도를 증가시킨다. ⑤에서 bnf_config로 정의하고, ⑥에서 추론시에 가능한 자원에 모델을 효과적으로 전개하도록 auto 로 정의한다. 


<그림 6. 모델 로드>

text 부분 토큰화

아래 그림7의 ① 과 같이 data의 text 부분을 위에서 정의한 tokenizer로 토큰화 한다. ② 에서 dataset.map()과 같은 유틸리티를 batch와 함께 사용하면 프로세싱 속도를 높히고 생성되는 데이터세트의 크기를 자유롭게 통제할 수 있다. 단일 샘플보다 batch 모드를 이용하면 batch에 있는 모든 샘플의 토큰화를 병렬로 처리하여 속도가 빠르다.

<그림 7. text 부분 토큰화>

아래 그림8의 ① 과 같이 data train 컬럼에 첫번째 레코드의 text 컬럼내용을 조회하면 그림4에서 mapping 한 형태로 ### 질문: 이 나타나고 ② 의 ### 답변 그리고 ③의 endoftext 가 나타난다.

<그림 8. 토큰화된 text 내용 확인>

PEFT를 이용한 low bit 훈련 준비

아래 그림9의 ① 과 같이 정의하면 경사도 연산에서 그래프의 모든 단계의 내용이 마지막 경사도 변경 연산때까지 메모리에 저장되는 것을 필요할 때만 재연산하고 이전 값들은 날림으로써 메모리 사용을 절약한다. ②와 같이 준비함으로써 PEFT 를 사용하여 모델이 LoRa 즉 모델의 각 layer에 훈련 어댑터들을 추가하도록 준비한다.
PEFT는 거대언어모델을 효과적으로 파인튜닝하기위한 다양한 기술 방법들이고 LoRa 는 그 중에 하나다. LoRa 는 low rank adaption 으로 사전학습된 네트워크의 파라미터들을 파인튜닝동안 사용하지 않고(freezing) 별도의 소량 가중치(weights)를 부가하여 사용하는 방식이다. 이는 모델이 파인튜닝 기간에 원래 훈련되었던 것을 재앙에 가까울 정도로 잊어버리는 것을 방지하고 저장공간과 계산능력을 대폭 줄였다. 또한 파인튜닝에서 만들어지는 큰 크기의 체크오인트 파일과 달리 수 메가바이트의 작은 체크포인트 파일을 얻을 수 있기때문에 저장 공간 관리에 도움이 된다.

<그림 9. low bit 훈련 준비>

아래 그림10의 ① 부터 ②까지 LoraConfig 를 정의한다. r은 행렬 변경 순위다. 자세한 구성은 여기를 참고. ③에서 LoraConfig 로 모델을 호출 ④에서 모델의 훈련 파라미터를 출력하는 함수를 통해 훈련 파라미터가 ⑤와 같이 6.6GB 에서 6.55MB 크기로 0.099% 축소된 것을 확인할 수 있다.

<그림 10. LoRa 를 위한 준비>

파인튜닝 훈련

아래 그림11과 같이 huggingface 의 Trainer 와 DataCollatorForLanguageModeling을 사용하여 훈련한다. ① 에서 우리는 GPT NeoX 토크나이저(Polyglot 모델이 GPT NeoX 기반)를 사용하고, GPT NeoX 가 EOS 토큰 ID 를 가져야 하므로 PAD에 디폴트 값인 EOS를 부여, ②와 같이 허깅페이스의 transformers 에 최적화된 Trainer class 를 사용한다. ③과 같이 훈련 중의 구성을 TrainingArguments에 명시할 수 있다. 순서대로 훈련 중 기기당 배치 크기, 역전파 시행 전 경사도 수집을 위한 변경 단계 수, 전체 훈련 스텝 수, 학습률, 16비트 정밀도 사용, 100번 마다 로깅, 훈련 결과 디렉토리, 옵티마이저 사용.  ④에서 DataCollator 는 데이터가 같은 길이가 아닐 경우 자동으로 최대 길이로 패딩된다. 마스크언어모델은 사용하지 않기때문에 False 로 세팅. ⑤와 같이하여 warning 메세지를 방지한다. 이후 추론할 때는 다시 True 로 설정 필요. ⑥에서 파인튜닝 훈련이 시작된다.

<그림 11. Trainer 를 통한 훈련>

아래 그림 12와 같이 ① 을 통해 추론 모드로 전환, ②에서 추론을 위해 다시 True로 전환.

<그림 12. 추론모드로 전환>

프롬프트 양식 작성

아래 그림 13과 같이 instruction 과 response 포함한 프롬프트 양식을 작성한다. Alpaca에서 사용되던 프롬프트에 한글을 추가하여 구성하였다. ① 과 ②는 instruction 과 response 사이에 참조할 input 이 있느냐 의 여부로 구별됨. ③은 Alpaca 데이터셋의 영문 instruction 밑에 한글로 번역본을 첨부한 형식. ④는 Instruction, Input, Response 로 출력되는 양식. ⑤에서 작성한 prompt_template 를 custom.json 파일형태로 저장한다.

<그림 13. 프롬프트 양식 작성>

질의에 대한 응답 함수 작성

아래 그림 14와 같이 질문을 입력하면 출력이 되는 함수를 작성한다. ① 과 같이, model.generate 를 통해 생성되는 결과를 응답으로 사용, ② 여기에 입력되는 질문은 tokenizer를 통과하며 ③과 같이, ### 질문: “실제 질문” 라인 바꿔서 “### 답변:” 형식으로 변환되는 return_tensors 는 파이토치 텐서(‘pt’)로 인코딩 된다. ④ 최대 토큰 수는 256 등의 매개변수 선언,  ⑤에서 최대 토큰으로 생성된 첫번째 출력을 토크나이저로 decode 하여 출력한다. 훈련이 충분치 않을 경우에 <|endoftext|> 토큰을 잃어버릴 수 있다. 256 최대 토큰을 사용할 경우, 통상 3분 이상의 생성 시간이 소요된다.

<그림 14. 질의 응답 함수 작성>

응답 함수 출력

아래 그림 15와 같이 응답 함수를 출력한다. ① 출력 소요 시간 측정을 위해 time 설정, ② gen 함수에 질문을 입력하면, ③과 같이 답변이 출력되고 소요시간이 70초(약 1분 10초) 소요된 것으로 나타난다.

<그림 15. 응답 함수 출력>

결언

지금까지 polygot-ko-12.8b 오픈소스 LLM 에 한글 데이터셋으로 메모리 사용량과 훈련 파라미터 크기를 줄이는 4bit quantization 과 QLoRa 기술을 활용하여 한글 질의에 대해 응답하는 오픈소스 경량화 한글 LLM으로 파인튜닝을 하여 모델을 생성하는 과정을 살펴보았다.

2023년 3월 7일 화요일

트랜스포머로 구현하는 영상 언어 질의응답-2편

지난 1편에 이어 2편에서는VQA 데이터셋 생성, 모델 정의, 훈련 및 저장, 그리고 저장된 모델로부터의 응답 추론에 대해 살펴보겠다.

VQA 데이터셋 생성 

1편의 그림 3의 영상-언어 트랜스포머 구조에서 보았듯이 트랜스포머 인코더에 단어 임베딩과 이미지 조각이 펼쳐진 임베딩이 입력된다. Pytorch dataset 을 생성하여 영상, 질문(언어), 응답이 포함된 데이터셋을 생성한다. Pytorch Custom 데이터셋과 데이터로더를 작성하는 방법은 여기를 참조. 이 튜토리얼의 __getitem__ 부분을 아래 그림13과 같이 작성한다. 
 Huggingface 의 ViltProcessor 는 모델의 이미지를 준비하기위한 ViltFeatureExtractor 와 텍스트를 위한 BertToeknizerFast 를 변수로 받아 단일 processor 로 제공한다. ViLT에서 사용될 이미지와 텍스트를 연합한 인코딩 형태의 입력을 만들어준다. 

ViltFeatureExtractor 는 이미지의 크기 변환, 정규화등을 통해 pixel_values 와 pixel_mask 를 생성한다. BertTokenizerFast 는 텍스트를 tokenize 하고 input_ids, attention_mask, token_type_ids 를 생성한다. Tokenizer는 입력 문장을 단어 혹은 서브 단어 단위로 쪼갠 후 사전에 등록된 id로 변환해주는 과정이다. 

Bert Tokenizer 에 대해서는 이전 뉴스레터 16호의 Tokenizer 부분을 참고. 

아래 그림13의 ①은 id_to_filename 을 통해 image 가 위치한 전체 이미지 경로 파일 이름을 획득하여 image 를 호출. ②에서 ViltProcessor에 이미지와 텍스트가 입력된다. 

<그림 13. Pytorch 커스텀 데이터셋으로 작성한 VQAv2 데이터셋 정의 일부>

입력의 최대 길이로 남는 부분은 채워지고(padding), 넘는 부분은 잘라버리고 (truncation), Return tensor 는 Pytorch tensor 로 리턴 한다. ③으로 라벨 크기만큼의 1차원 targets tensor를 만든다. 사용되는 라벨 수는 3129 개다. ④로 3129 라벨에 해당되는 인덱스 위치에 해당 scores 가 기록된다. ⑤라벨 길이만큼의 텐서 targets 이 ‘labels’로 인코딩 된다.  ⑥데이터셋이 인코딩을 리턴 한다.

 아래 그림 14는 VQA데이터셋 생성 정의이다. ①에서 붉은색의 masked language modeling 사전학습으로부터 processor 를 호출한다.  ②앞의 VQA데이터셋 생성의 입력 변수로 questions 의 길이 100까지, annotations 의 길이 100까지로 하여 processor 와 함께 호출한다.

<그림 14. VQA 커스템 데이터셋 생성>

아래 그림 15의 ①에서 첫번째 dataset 의 key 값들이 보여진다. ②에서 processor로 input_ids 인코딩 값을 디코딩한 출력이다. 질문 앞에 [CLS], 문장 끝에 분리 [SEP] 그리고 나머지에 패딩[PAD]가 채워져 있다. ③에 labels 에 있는 인덱스 id 형태 라벨들을 config 에 있는 id2label 을 통해 라벨 값으로 치환한 값들을 출력하고 있다.

<그림 15. dataset 의 input_ids 와 labels>

영상 및 언어 트랜스포머 질의응답 모델 정의

아래 그림 16은 모델 정의 부분이다. ①에서 huggingface 의 transformers로부터 질의응답 모델을 위해 ViltForQuestionAnswering 를 import 한다. ②에서 dandelion/vilt-b32-mlm 즉, vilt 32 patch 크기의 마스크 언어모델 weight 를 사용한 ViltForQuestionAnswering 모델을 정의한다. 이때,  ③라벨 수는 config.id2label 의 길이로 그리고 id2label 과 label2id 를 config 정의를 이용하여 초기화해준다. ④에서 GPU를 사용할 경우 GPU device로 이동하도록 설정한다. 이로써, 모델은 임베딩 층을 거쳐 12개의 multi-head self-attention 층 그리고 마지막으로 linear classifier 가 포함된 모델로 정의된다.

<그림 16. 질의응답 모델 정의>


Pytorch DataLoader 정의

다음으로 데이터셋을 배치로 데이터로드로 호출하는 과정에서 아래 그림 17의 ①과 같이 배치데이터로부터 가장 큰 이미지에 맞추어 패딩과 마스크를 생성 변환하여 배치를 리턴하는 함수 collate_fn 을 정의한다. 이는 모델에 입력되는 이미지의 최대 크기에 맞춰 나머지 차이나는 부분들을 패딩으로 채우고 패딩부분은 마스크가 0으로 표시된다. ②와 같이 배치의 인코딩 item을 ③에서 

<그림 17. 배치 데이터 이미지 픽셀, 마스크 생성 변환 함수>

processor 의 ViltFeatureExtractor class에 있는 pad_and_create_pixel_mask 함수를 이용하여 batch의 픽셀값을 패딩하고 상응하는 픽셀 마스크를 생성한다. 이때 형태는 (batch_size, height, width) 로 어떤 픽셀이 실제(1)이고 어떤 픽셀이 패딩인지(0) 표시된다.  ④에서 변환된 픽셀값과 픽셀마스크 값은 batch dictionary에 순서대로 torch.stack을 통해 쌓이게 된다. ⑤ 에서 dataloader 매개변수로 collate_fn 을 호출하여 pixel_value, pixel_mask 를 변환한 값이 batch에 반영되게 한다.

 아래 그림 18의 ①을 통해 첫번째 interation 으로 데이터로더로부터 데이터를 가져오고, ②에서 픽셀 값이 [3, 384, 544] 로 (앞에 3은 채널) ③에서 픽셀마스크가 [384, 544] 형태로 확인된다. 앞의 4는 batch 크기다. ④에서 라벨 값 3129가 확인된다.

<그림 18. 배치 내의 item 확인>

모델 훈련

아래 그림19 의 ①에서 optimizer로 AdamW, learning rate 는 5e-5 로 설정. epoch를 50으로하고 ②에서 Pytorch training 을 시행한다.

<그림 19.  모델 훈련>

③은 50 epoch 훈련 후의 loss 를 나타낸다. 지금까지 ViLT 사전훈련 모델을 이용하여 질의응답에 파인튜닝하는 절차에 대해 살펴보았다.

추론을 위한 image, question 준비

 자 이제부터는 추론을 위하여 먼저 추론할 image, question을 준비하는 것으로부터 시작해보자. 아래 그림 20의 ①과 같이 COCO 이미지 데이터세트에서 이미지를 하나 선택하고 ②에서 url 로부터 image 를 담게되고, ③에서 연관되는 질문을 생성한다.

<그림 20. Image, question 준비>

image 는 아래 그림 21과 같이 출력된다. 

<그림 21. Image 확인>

ViLTProcessor 를 이용하여 image 와 text 쌍을 준비

  우선 준비된 image 와 text 를 ViLTProcessor을 이용하여 아래 그림 22와 같이 huggingface 의 VQAV2 에 파인튜닝된 ViLT 모델의 image 와 text 쌍을 준비한다. ①과 같이 hugginceface transformers 로 부터 ViLTProcessor 를 import 하고 ②에서 VQAV2에 파인튜닝된 모델로부터 ViLTProcessor 를 로드하여 processor 로 지정한다. 앞에서 살펴본 바와 같이, BertTokenizerFast 토크나이저가 사용되며 input_ids, attention_mask, token_type_ids 를 생성한다. ViltFeatureExtractor 가 이미지 크기 재조정 및 정규화를 담당하며 pixel_values 와 pixel_mask 를 생성한다. 이때 pixel_mask 는 batch 에서만 관련이 있는데, 어떤 픽셀이 실제 픽셀인지 패딩인지 여부에따라 1과 0으로 표시된다. 추론에서는 하나의 예(example)만 다루기때문에 모든 pixel_mask 는 이 경우 1 이다.

<그림 22. Processor 생성>

모델에 입력될 인코딩 준비

  추론을 위하여 준비된 image 와 text쌍을 모델에 입력하기위한 인코딩 작업을 담당할 processor 를 사용하여 아래 그림 23과 같이 encoding 하고 차원을 확인한다. ①과 같이 processor 에 image, text 를 넣어주고 파이썬 텐서를 생성한다. ②에서 인코딩 아이템들의 형태를 표시해본다. ③에서 pixel_values 가 정확히 표시됨을 확인한다. 맨 앞의  batch 값은 추론을 위한 한개의 예만 입력되므로 1 이다.


<그림 23. 입력 인코딩 생성>

ViLT VQAV2 데이터셋에 파인튜닝된 모델 로드

  추론을 위하여 huggingface hub 으로 부터 vilt 모델이 vqav2 에 파인튜닝된 모델을 아래 그림 24와 같이 로드한다. ①과 같이 vqav2 에 파인튜닝된 사전훈련 모델을 사용하여 huggingface 의 ViLTForQuestionAnswering 모델을 로드한다.

<그림 24. 파인튜닝된 모델 로드>

forward pass 를 통한 답변 예측

 그 다음 추론을 위해 아래 그림 25와 같이 모델을 통해 forward pass 를 진행한다. ①과 같이 input_ids, pixel_values 등이 포함된 인코딩을 model 에 forward pass한다. 이때 outputs.logits 의 차원은 (batch_size, num_labels) 의 형태를 띄며 이 경우, (1, 3129)가 된다. VQAV2 의 답변(라벨)은 3129의 가능한 답변이 있다. torch.sigmoid 를 통해 3129개의 라벨에 대해 0 에서 1 사이의 값을 보유하게 되고 최대값이 있는 id 가 idx 로 출력된다. ③에서 idx 에 해당하는 id2label 을 통해 label 값이 출력된다. ④에서 “How many cats are there?” 에 대한 답변이 2 로 출력된다.

<그림. 25. Forward pass>

1편에 이어 2편에서 VQA 데이터셋 생성, 질의응답 모델 정의, 모델 훈련, 추론을 위한 image, text 쌍 준비, 인코딩 준비, 파인튜닝된 모델 로드 그리고 답변 예측에 대해서 살펴보았다. 




트랜스포머로 구현하는 영상 언어 질의응답-1편


영상-언어 트랜스포머 질의응답(Vision and Language Transformer for Question Answering)은 주어진 이미지에 연관된 다양한 질문에 대해 딥러닝을 이용해 올바른 답변을 예측하는 기술이다. 이전 뉴스레터19호 에서 단일 모달(modal)인 텍스트 기반의 질의응답을 BERT를 기반으로 다루었다면, 본 과제에서는  이미지에 대해 자연어로 이루어진 질문을 던지고 이에 대한 답변을 예측하는, 영상과 언어의 멀티모달(multi-modal) AI 모델을 다룬다. 

영상 및 언어의 멀티 모달을 통한 응용 AI 분야 예 

아래 그림 1과 같이 이미지와 자연어 두개의 modal 을 대상으로 AI를 통하여 이해하고 예측할 수 있는 응용분야의 예 중 일부를 표시하였다. Visual Question Answering은 자연어로 이미지에 대해 질문을 던지면, 아래 그림1과 같이 어린이 의상의 색깔은? 에 대해 오렌지라고 답하는 것을 뜻한다. Referring Expression은 이미지에 나타나는 객체에 대해 표현하는 것이다. Multi-modal verification 은 아이가 개를 쓰다듬고 있다 라는 문장을 false 라고 틀렸다고 검증한다. Caption-based Image Retrieval 은 “오렌지색 의상을 입은 어린이가 양과 놀고 있는” 이라는 문장에 대응하는 이미지를 찾아온다. Natural Language Visual Reasoning 은 “왼쪽 영상은 오른쪽 2배의 개를 보유하며 적어도 모두 2개의 개가 서있다”라는 자연어 입력을 추론하여 True 라고 답한다.

<그림 1. 멀티모달 AI 응용 예>


트랜스포머 구조

트랜스포머가 어떻게 동작하는 가는 이전 뉴스레터28호를 참고. Query, Key, Value 의 3개의 동일한 입력 임베딩을 Q 와 K의 행렬곱(dot product)에 소프트맥스를 가한 결과에 Value 를 행렬곱(dot product)을 함으로써 결국 출력이 self-attention 을 거친 벡터를 얻게 된다. 이 벡터는 문맥 간의 가중치를 이해하는 vector 로 되어 트랜스포머의 인코더부분만으로도 자연어처리의 여러 downstream 작업에서 최고 성능을 달성한 바 있고, 이전 뉴스레터19의 Ko-electra 질의응답이 그 한 예였다. 자연어 처리에서 Transformer 구조로 달성한 높은 성능들에 영향을 받아 이미지 처리 영역에서도 Transformer 구조를 이용하고자 하는 많은 노력이 있었고, 이미지를 구역을 나누어 임베딩 하는 합성곱 신경망에서 완전히 독립한 Visual Transformer(ViT) 구조가 제안되어 이미지 분류에서 당시 최고 성능을 달성하였고 이어 객체인식에서도 최고 성능을 달성하였다. 앞선 모델들의 성공으로 이미지처리의 연구흐름은 합성곱(CNN) 신경망방법에서 Transformer 방법으로 이동하고 있다.

Visual Transformer(ViT)

아래 그림2와 같이 2021년 구글은 이미지를 여러 조각(patch)으로 분할하여 분할된 조각을 마치 자연어인 것처럼 트랜스포머 인코더에 입력하는 ViT 를 발표한다. 논문에서 대용량 데이터세트에서 최고 분류 성능을 나타냈다.

<그림 2. Visual Transformer 구조>

영상 및 언어 트랜스포머(ViLT)를 기반으로 한 질의응답

본 과제는 멀티모달(영상 및 언어)의 질의응답을 영상-언어 트랜스포머 사전훈련 모델을 통해 파인 튜닝하는 것을 구현하는 개념증명(proof of concept)을 살펴보는 것이다. 논문은 이전 영상-언어의 여러 사전학습 모델들과 2가지 점에서 특징을 강조한다. 이전 시각언어 접근법은 먼저 이미지내의 객체탐지를 위해 사전 세그먼트를 통해 객체의 존재유무를 판별하는데 사용되는 faster R-CNN 같은 지역 제안방식을 사용하거나, 영상 인코딩으로 컨볼루션 네트워크를 이용하고 문장 인코더와 연합하여 접근하는 방식을 사용한다. 논문은 이러한 이전의 지역 탐색 이나 컨볼루션 방식 대비 최대 10배 빠른 추론성능을 제공하고 파라미터 측면에서도 3배 가벼운 결과를 제시한다. 

무엇보다도 논문의 제목같이 처음으로 컨볼루션 이나 지역 탐색과 같이 성능에 부담이 되는 부분을 제거한 트랜스포머만으로 구현했다는 점에서 주목을 끈다. 코드와 사전훈련 모델은 여기에. 
아래 그림 3은 트랜스포머의 인코더에 ①단어 임베딩과 우측의 ②펼쳐진 이미지 조각들이 입력되는 구조를 나타낸다. ③은 트랜스포머에 입력된 이미지와 단어의 쌍이 같은 쌍인지 여부를 판별하고 ④는 ①에서 입력되는 단어 중 [MASK]처리된 단어를 예측하는 masked language modeling 을 표시한다. ⑤는 훈련 중 batch에서 샘플링된 단어와 이미지 조각 쌍들 사이의 확률분포의 거리를 훈련을 통해 손실을 최소화하도록 정렬된다. 

<그림 3. Vision-and Language Transformer>

사전학습 목표

 ViLT 모델을 사전학습 시키며 달성하고자 하는 목표는 Image Text Matching, Masked Language Modeling 을 훈련시키기 위함이다.
Image Text Matching: 위 그림3의 ③에 Fully Connected Layer 를 통과한, 이미지와 텍스트가 일치하는지 아닌지의 이진 분류로 값이 판별된다. 아울러, ⑤에서는 이미지와 텍스트의 부분집합 간의 정렬 점수를 산출하는 word patch alignment 가 학습된다.
Masked Language Modeling: 위 그림3의 ④와 같이 트랜스포머에서 출력되는 맥락 화된 벡터로부터 [MASK]의 라벨을 예측하는 방식으로 훈련된다.

ViLT 를 통한 질의응답 수행 절차

수행절차는 아래 그림4와 같다. 우선 VQAv2 데이터세트를 준비하고 image, question, annotation 에 관련된 전 처리 및 변환을 수행한다. Annotation 에 labels 과 score 를 추가하고 VQA 데이터셋을 생성하고 모델을 정의 및 훈련한 이후에 저장하고 이 모델과 config 등을 로드하여 추론을 실시한다.

<그림 4. 시각 언어 트랜스포머 수행절차>

Transformer 설치

Huggingface 의 Transformer 모델 중 ViLT를 사용하므로 아래 그림 5와 같이 트랜스포머 라이브러리를 설치한다. 

<그림 5. 트랜스포머 설치>

데이터셋 준비

질의응답을 위한 데이터셋은 공식웹사이트 로부터 다운로드 받는다. 개념증명용도로 image, question, annotations 의 validation 데이터셋 만 data/vqav2 디렉토리 밑으로 다운로드 받는다. 압축을 풀면 annotations, questions json 파일과 val2014 디렉토리 밑에 40,504개의 jpg 이미지 파일이 생성된다.

Image ID 와 Image filename 

v2_OpenEnded_mscoco_val2014_questions.json 을 열어 첫번째 내용을 보면 아래 그림 6과 같다. 

<그림 6. questions.json 내용>

questions 에 있는 image_id 로부터 관련 image filename을 획득하기위해 아래 그림 7과 같은 작업이 필요하다. 이미지파일들을 보면 COCO_val2014_000000290896.jpg 와 같은 형식으로 filename 이 되어있는데 위 그림5의 image_id 는 .jpg 전에 6자리 숫자를 의미한다. ①에서 .jpg 앞의 12자리 숫자를 패턴으로 하여 ②에서 전체 패턴이 일치하면, ③에서 일치된 패턴의 첫번째 즉 12자리 숫자를 int 로 변환하여 리턴 한다.

<그림 7. 이미지 파일명에서 id 추출하기>

아래 그림 8의 ①은 questions 의 image_id key 의 첫번째 image_id 로부터 전체 path 를 포함하는 파일명으로 ②와 같이 Image 를 호출한다. Filename_to_id 와 id_to_filename 의 2가지 dictionary 가 필요하다. 그림 9는 출력된 이미지 이다.

<그림 8. image 확인>



<그림 9. 이미지 출력>

Annotations 작업

아래 그림 10과 같이 annotations.jason 파일의 첫번째 내용을 보면 ①과 같이 복수의 답이 있다. 복수의 사람들이 주석 작업에 참여해서 이미지를 보고 그림 6과 같이 “where is he looking? 이라는 질문에 대해 각자 주관적인 답을 한 결과다. ②부터 10개 dictionary 형식의 답이 이어지고, ③image_id 와 ④question_id 가 포함되어 있다.

<그림 10. Annotations 구조>

Annotations 에 labels 와 scores 추가

Annotations의 answer로 최소 3번 이상 집계되는 것은 score 를 1로 그 이하는 1 이하의 score를 Annotations 에 scores 라는 key 로 추가하고 labels key 에는 label들의 정수형태의 인덱스 리스트 형태로 추가한다. 이 정보는 config.label2id 로 표시된다.
 아래 그림 11은  huggingface 의 transformers 라이브러리로부터 ViltConfig 사전 훈련모델을 통하여 config 을 획득한다. ①은 Huggingface의 ViLT모델 의 ViLTConfig ②는 ViltForQuestionAnswering config를 위하여 붉은색으로 표시된 사전학습 모델 파라미터로 호출.

<그림 11. config 호출>

아래 그림 12의 ①로 config의 label2id 를 사용하여 라벨의 정수형 인덱스를 획득하여 labels key 에 추가한다. ②의 get_score 함수는 answer count 를 입력으로 받아 score를 리턴 하는 함수이다. Score 는 로3개이상이면 1, 1개만 있으면 1/3 로 0.333 이다. ③에 labels 가 순차적으로 annotation 의  labels key 에 추가된다.

<그림 12. labels, scores 추가>

지금까지 영상-언어 트랜스포머를 이용한 질의응답의 개요, 응용분야, 트랜스포머 구조, 영상 및 언어 트랜스포머 구조, 질의응답 수행절차 중 데이터 로드 및 전 처리, Annotations 변환을 살펴보았다. 
뉴스레터 2편에서는 VQA 데이터셋 생성, 모델 정의, 훈련 및 저장, 그리고 저장된 모델로부터의 질의에 대한 응답 추론에 대해 살펴볼 예정이다.





2023년 2월 8일 수요일

AI가 온라인 학습 이력 데이터 들을 학습하여 미래 문제 정답 확률을 예측할 수 있을까?-2편

1편에 이어서 이번 호에서는 훈련을 위한 데이터세트 구성 및 분할 그리고 모델 정의 및 검증, 구축, 훈련 그리고 추론 절차에 대하여 살펴본다.

텐서플로우 데이터 생성

 아래 그림 10과 같이 텐서플로우 텐서플로우 tf.data 의 from_generator method 와 lambda 함수를 사용하여 generator 에 ①과 같이 seq 를 tensor argument 로 전달하고 output type 을 정의한다. ② skill_with_answer, ③은 skill, ④는 correct 를 정의.

<그림 10. 텐서플로우 데이터세트 생성>

 모델에 입력될 형태는 자연어처리와 마찬가지로 입력 sequence 의 user_id 당 문제 풀이 이력들이 달라서 최대 200 ( 100 skill X 2 (correct)) 에 맞추어 padding 과 batch 구성이 필요하다. 아래 그림 11의 ① 은 tf.data 의 padded_batch method 를 사용하여 batch 크기를 정의하고 ② 의 padding 값은 맨 위에 정의된 MASK_VALUE 인 -1 로 사용. ③은 아래 그림 12와 같이 [문제풀이 길이, 200], [문제풀이 길이, 101]로 차원을 구성. ④의 length는 데이터세트 분할에 사용된다.

<그림 11. padded_batch 구성>

  아래 그림 12의 ①은 tf.data.iterator method 를 사용하여 데이터세트의 요소를 가져온다. 데이터세트는 skill_with_answer 와 skill + label 이 리턴 되므로 ③과 같이 32 크기의 batch 중 첫번째 레코드만 표시했을 때 행렬의 차원이 (문제풀이 길이(122), skill_with_answer 최대수(200))로 보여지고, ④는 차원이 (문제풀이길이(122), skill 수 100 + 라벨 1 = 101) 차원으로 표시되어 데이터베이스가 잘 구성됨을 알 수 있다. ②는 padding value -1 이 채워진 모습.


<그림 12. 데이터세트 확인>

데이터세트 분할

위 그림 11의 length 를 total_size 로 하여 여기에 20%를 test로 할당하고 나머지는 train dataset 으로, 그리고 train dataset 의 20%를 validation dataset 으로 할당한다.

손실함수

  본 과제의 손실함수는 skill 과 관련한 응답 즉 0과 1에 대하여 실제 학습 이력에 있는 값과 예측 값의 차이가 손실이 된다. 이를 위하여 앞의 그림 12의 ④와 같은, 훈련 데이터세트의 skill+label 행렬(y_true)와 예측값(y_pred)를 입력하면 벗겨낸 ground truth 라벨 값(y_true)과 각 스킬들 로부터 예측된 값(y_pred)을 리턴하는 아래 그림 13의 ①의 함수가 필요하다. ②를 통하여 skill+label 의 101차원의 y_true 행렬로부터 실제 label 값이 있는 텐서 요소만을 판별하는 mask 행렬을 만든다. ③으로 실제 값을 추려 냄. 나머지는 모두 0으로 변함. ④에서 skill 과 label 을 분리. ⑤에서 차원을 유지하면서 skill 에 y_pred 를 곱한 요소들의 합을 리턴. ⑥ 에서 label 실제 값과 예측 값을 리턴. ⑦ 은 이들 값들을 입력으로  ⑧의 tf.keras.metrics.binary_crossentropy API를 호출하여 손실을 리턴.


<그림    13. 손실 함수>

심층망 모델 정의

모델은 아래 그림 14의 ①과 같이 순차적 정보를 처리하는 LSTM 모델을 사용한다. 입력으로 최대값 200 차원의skill_with_answer 인코딩이 들어가고 출력으로 100 skill 별 확률 값이 나온다. 앞에서 살펴본 바와 같이, 한 문제 씩 풀이할 때마다, 학생의 지식 상태 즉 skill 별 문제를 맞출 확률은 동적으로 변화한다. 

<그림 14. return_sequence 와 time distributed>

이러한 문제를 반영하려면 위의 ①과 같이 모델을 정의할 때 return_sequence=True 로 선언하여 각 timestep 별 hidden state 값이 모두 나오게 하고, ②의 TimeDistributed 기능으로 매 스텝마다 cost 가 계산되고 하위 스텝으로 오류가 전파되도록 해야 한다. 즉, many-to-many 경우로 만든다. 

아래 그림 15는 LSTM 모델 정의를 표시한다. ①의 nb_features 와 nb_skills 는 각각 skill_with_answer 최대치(200)와 skills 최대치(100)로 모델 inputs 와outputs 의 행렬 차원 변수로 사용된다. 

<그림 15.     LSTM 모델 정의>

②는 keras 로 모델 입력은 (None(batch 가 올 자리), nb_features) 즉 skill_with_answer 가 입력 값이다. 그 다음 층(layer)은 ③masking layer, 그리고 LSTM layer 에 ④와 같이 return_sequence=True 를 설정. ⑤ dropout 은 과적합을 피하기위한 설정이며 값은 0.3 이다. 그 다음은 ⑥과 같이 dense layer 에 activation 은 이진 분류 즉 correct 의 0 과 1을 구분하기에 sigmoid 함수를 사용. 마지막으로 ⑦ 에 TimeDistributed layer 통해 output 이 출력되도록 정의. 

아래 그림 16은 정의한 모델 요약이다. ①에 입력층에 200 차원이 보인다. skill_with_answer 가 입력 값이고 ②는 masking layer, ③에 lstm 층은 unit 이 100으로 설정 되어있다. ④에 TimeDistributed 층이고 outputs 는 skills 의 최대치인 100 이 출력되어 매번 사용자가 문제 풀이한 전체 skill 에 대한 정답 확률 값이 TimeDistributed 를 통하여 매 스텝마다 cost 가 계산되고 하위 스텝으로 오류가 전파된다. 즉 동적으로 각 단계에서의 사용자의 학습 상태가 업데이트 된다.

<그림 16. 모델 요약>

모델 훈련

아래 그림 17은 1000 epoch 모델 훈련을 시행했을 때 훈련과 검증데이터의 정확도와 손실 결과다. 검증데이터 손실은 0.085를 향해 지속적인 감소가 보인다. 검증(validation)데이터세트의 정확도도 0.82를 향해 점진적으로 우상향을 보여서 일단 과적합(ovefit)에 대한 우려는 없어 보인다. 

<그림 17. 모델 훈련 결과>

저장된 모델 weight 로드

 tf.keras.callbacks.ModelCheckpoint callback 함수를 통하여 model.fit 동안에 best_model_weights 로 저장된 최고 성능 모델을 아래 그림 18와 같이 다시 로드한다.

<그림 18. 모델 weight 로드>

모델 평가

아래 그림 19에 testset에 대한 성능 결과가 정확도 0.8198, AUC 가 0.8610 으로 표시되었다. 이는 DKT 논문에서 보여준 Assistment AUC 결과와 동일한 결과다.

<그림 19. 모델 테스트 결과>

추론(predict)을 위한 input 형태 구성

이제 완성된 모델을 통하여 특정 사용자(user_id)의 skill_with_answer 값을 모델에 입력하면,  모델은 이전 model.summary()에서 살펴본 것처럼 skill 별 맞출 확률을 출력한다. 다만 여기서 모델에 입력되기위해서는 skill_with_answer 값들이 이전 그림 12와 같은 데이터세트 형태의 입력 형태가 되어야한다.  

특정 사용자의 학습 상태 취약점 예측

아래 그림 20은 user_id 96409 의 model.predict 출력이다. ①에 입력되는 sa[0]는 96409 user 의 데이터프레임 df_96409 로부터 그림 12와 같은 데이터세트 형태로 변환된 후에 요소가 추출되어 넘파이 array sa 에 저장된 값이다. 첫번째 batch sa[0]가 입력되어 데이터프레임으로 변환된 출력이 아래 그림이다. 27번째 문제풀이에서 담고 있는 skill 별 정답 확률을 이용하여 이 이후에 맞추기 어려운 혹은 쉬운 문제를 찾아낼 수 있다.

<그림 20. 96409 user 의 skill 별 정답 확률 출력>

 user_id 96409 의 경우에 가장 문제를 맞추는데 어려워 보이는 문제 유형 top5 를 출력한 결과가 아래 그림 21에 나타나 있다. ①은 df_96409 같은 데이터프레임이 입력되고, 매개변수 1을 전달하여 마지막 문제풀이 상태에서 오름차순으로 정렬하여 Top5 를 출력하는 inference 함수를 작성하여 실행한 코드이다.  Inference() 함수는 매개변수 1부터 4까지에 따라 Top5 lowest, Top5 highest, single most lowest, single most highest 와 같은 정답을 맞출 확률을 출력한다. 출력 중, ②는 skill 번호와 함께, ③ 문제를 맞출 확률과 함께 출력되고 있다.

<그림 21. 가장 취약한 문제 유형 오름차순 Top5 출력>

결언

1편에 이어서 데이터세트 구성, 훈련 및 추론에 대해서 살펴보았다. 모델 평가로 AUC도 Assistment 데이터세트로 평가한 논문 결과와 유사하게 나왔고 LSTM TimeDistributed 신경망을 통하여 특정 사용자의 skill(문제 유형) 과 correct(정오답 여부) 학습 이력을 입력으로, 문제 풀이 이력 전 과정의 skill 별 문제를 맞출 확률을, 학생의 학습 상태의 취약점 예측이라는 관점에서 얻을 수 있었다.