- 이번 포스팅에서는,
LLM 모델뿐 아니라 많은 인공지능 분석 모델에서 텍스트뿐 아닌 이미지, 음성 등의 현실적인 다양한 정보를 Input 받는 방식에 대해 정리하겠습니다.
- 본 게시글에서 다루는 범위는, 멀티 모달에 대한 기본적인 이해와, 간단한 구조의 멀티 모달 입력 기능의 구현 실습 까지입니다.
실제 멀티 모달 LLM 모델의 뉴럴 네트워크 구조나 학습 방식 등은 추후 관련 논문을 기반으로 상세히 정리할 것입니다.
(멀티 모달의 이해)
- LLM의 멀티모달(Multi-modal)이란,
언어 외의 다양한 형태의 데이터를 함께 이해하고 처리할 수 있는 능력을 의미합니다.
전통적인 LLM(Language Model)은 텍스트만 다뤘다면,
멀티모달 LLM은 텍스트 + 이미지, 음성, 비디오, 센서 데이터 등을 함께 다룰 수 있습니다.
- 멀티 모달 기술의 활용 방법은 매우 많으며,
예를들어 이미지 파일을 업로드하여 LLM 에게 해당 이미지의 내용에 대해 알려달라고 한다던지,
음성, 비디오, 센서 데이터와 같은 비정형 데이터 속의 의미를 LLM 이 동시에 받아들이고, 이를 종합하여 이해하고 응답 할 수 있습니다.
(LLM 멀티 모달 모델의 구조)
- 멀티모달에 대해 정리하기 전에,
비교를 위하여 일반적인 Text Input LLM 모델을 구조를 먼저 정리하겠습니다.
일반적으로 NLP 분야에서 자연어 데이터를 입력할 때에는,
문장 입력 -> 텍스트 전처리 -> 토큰화(의미를 가지는 단위로 쪼개기) -> 정규화 및 정제 -> 벡터 임베딩 -> 입력
의 순서로 이루어집니다.
위에서 보이듯, 자연어 문장은 정제되고 쪼개지고 분석되어, 결과적으로 어떠한 의미를 품고 있는 벡터 형태가 되는 것입니다.
Pytorch 로 작성된 간단한 코드로 아래와 같은 프로세스를 확인할 수 있습니다.
import torch
from nltk.tokenize import word_tokenize
from collections import defaultdict
import nltk
# 토크나이저를 위한 다운로드
nltk.download('punkt')
# 예제 문장 입력
sentence = "The quick brown fox jumps over the lazy dog."
# 1. 텍스트 전처리 (소문자 변환)
sentence = sentence.lower()
# 2. 토큰화 (의미 있는 단위로 분리)
tokens = word_tokenize(sentence)
print("Tokens:", tokens)
# 3. 정규화 및 정제: 여기선 간단히 특수문자 제거 등 생략 가능
# 4. 단어 사전 생성 (Vocabulary)
word2idx = defaultdict(lambda: len(word2idx)) # 새 단어가 들어오면 자동 인덱스 부여
word2idx["<PAD>"] # padding 토큰 추가
# 5. 토큰 -> 인덱스 변환
indexed_tokens = [word2idx[token] for token in tokens]
print("Indexed Tokens:", indexed_tokens)
# 6. 벡터 임베딩 전, 텐서로 변환
input_tensor = torch.tensor(indexed_tokens)
print("Input Tensor:", input_tensor)
# 7. (선택) 시퀀스 길이 맞추기 (Padding 적용)
MAX_LEN = 12
padded_tokens = indexed_tokens + [word2idx["<PAD>"]] * (MAX_LEN - len(indexed_tokens))
input_tensor = torch.tensor(padded_tokens[:MAX_LEN])
print("Padded Tensor (Fixed Length):", input_tensor)
결과값은,
Tokens: ['the', 'quick', 'brown', 'fox', 'jumps', 'over', 'the', 'lazy', 'dog', '.']
Indexed Tokens: [1, 2, 3, 4, 5, 6, 1, 7, 8, 9]
Input Tensor: tensor([1, 2, 3, 4, 5, 6, 1, 7, 8, 9])
Padded Tensor (Fixed Length): tensor([1, 2, 3, 4, 5, 6, 1, 7, 8, 9, 0, 0])
으로,
각 단계별 어떠한 처리가 이루어졌는지를 확인할 수 있습니다.
참고로,
텍스트 토큰화와 토큰 임베딩은 이전에는 전통적인 NLP 알고리즘으로 구현된 임베딩 알고리즘(통계 데이터 기반 혹은 수학적 모델)을 사용했다면, 이후 딥러닝이 각광받고 주요 기술로 취급받으며 임베딩 부분에도 DNN 을 적용하여 성능을 높였습니다.
현시점의 GPT, BERT, LLaMA 등의 대부분의 LLM 모델은 토크나이저와 임베딩 레이어를 내재화하여 본인들의 모델에 최적화된 문장 의미 추출기를 스스로 학습하는 방식이 사용됩니다.
- 바로 멀티모달에 대해 알아보겠습니다.
위와 같은 텍스트 기반 입력 프로세스만 봐도 이해할 수 있듯, LLM 에 입력되는 시점에 데이터는 '의미만 추출된 벡터' 의 형태로 바뀌게 됩니다.
즉, 음식물이 소화될 때, 원래의 형태를 잃고 영양분 상태로 녹아버린 것과 같죠. (쿠키를 먹건, 아이스크림을 먹건, 김치찌개를 먹건 영양분 단위로 분해된다면 다 같은 에너지 소스일 뿐입니다.)
텍스트에서 의미를 뽑아낼 수 있다면 당연히 이미지, 오디오, 그 외의 현실세계의 정보를 가지고 있는 비정형 데이터에서도 의미를 뽑아낼 수 있을 것입니다.
(텍스트, 이미지, 오디오 등) → 전용 인코더 → 임베딩 벡터 → LLM
단순화한 구조는 위와 같습니다.
중요한 것은 Modal Encoder 로, 텍스트, 이미지, 오디오를 가리지 않고 이 안에서 의미를 찾아내어 벡터 형식으로 임베딩 하는 기술이 필요하며,
멀티 모달의 핵심은 입력 데이터를 어떻게 벡터로 바꾸어서 LLM 에 넣을지에 대한 문제로 단순화 할 수 있습니다.
구조상으로는 아래와 같으며,
텍스트 ┐
이미지 ├─> 각각 Encoder → Projector → 통합된 임베딩 시퀀스 → LLM
음성 ┘
여기서 보시듯 텍스트, 이미지, 음성, 그외 데이터들을 각각 효율적인 방법론에 따라 인코딩 한 후, 이를 통합하여 이젠과 동일하게 LLM 에 넣어주는 것 뿐입니다.
- 각 모달에 있어서 주로 사용되는 임베딩 방식을 정리하겠습니다.
1. 텍스트 데이터 : Tokenizer + DNN Embedding Layer
대표적으로 BERT 기반의 텍스트 인코더를 사용할 수 있습니다.
2. 이미지 데이터 : CNN or ViT(Vision Transformer 활용) Encoding 방식
CNN 에서 이미지의 정보를 추출하는 비전 인코딩에 대한 기본 지식은,
출처 링크에서 확인하실 수 있고, ViT 는 추후 정리하겠습니다.
3. 오디오 데이터 : CNN+RNN 혹은 BERT 기반으로 벡터화를 하거나 ViT 를 사용할 수도 있을 것 같습니다.
위와 같은 방법이 존재하며, 각 세부 내용은 따로 이론 정리 게시글을 만들겠습니다.
(멀티 모달 실습)
- 모델에 여러 타입의 데이터를 입력하는 멀티 모달을 적용하려면 멀티 모달을 지원하는 모델을 사용해야 합니다.
제대로된 멀티 모달 모델 분석 및 구현은 추후로 미루고 가장 간편하게 이를 구현하는 방법을 알아보겠습니다.
- 멀티 모달을 지원하는 모델로는 llava-1.5-7b 를 사용하겠습니다.
llava-1.5-7b는 LLaVA(Large Language and Vision Assistant) 프로젝트의 일환으로 개발된 멀티모달 모델로, 이름 그대로 이미지와 텍스트를 함께 이해하고 응답할 수 있는 모델입니다.
구조적으로는, 입력단에 텍스트 인코딩 부분과 이미지 인코딩 부분이 동시에 존재하고,
이미지와 텍스트를 동시에 입력하면 각각을 인코딩한 후 추출된 벡터를 병합하여 LLM 에 넣어주는 개념입니다.
텍스트 인코더 + LLM 의 베이스가 되는 모델은 LLaMA 2 7B 모델로, 이에 확장하여 CLOP ViT-L/14 모델로 이미지를 인코딩하는 식으로 만들어진 모델입니다.
GPT-4V 같은 초거대 모델에는 못 미치지만, 로컬에서도 구동 가능한 수준에서 상당히 뛰어난 이미지 이해 능력을 보여주기에 실습용으로 사용하였습니다.
- 모델에 입력할 텍스트는,
"이 이미지를 설명해주세요." 라는 질문을 던질 것이고,
위와 같은 이미지를 같이 넣어줄 것입니다.
from transformers import LlavaForConditionalGeneration, LlavaProcessor
from PIL import Image
import torch
# 모델과 프로세서 로딩
model = LlavaForConditionalGeneration.from_pretrained("llava-hf/llava-1.5-7b-hf")
processor = LlavaProcessor.from_pretrained("llava-hf/llava-1.5-7b-hf")
# 이미지 열기
image = Image.open("example.jpg")
# 프롬프트에 반드시 <image> 토큰 포함!
prompt = "<image>\n이 이미지를 설명해주세요."
# 입력 처리
inputs = processor(images=image, text=prompt, return_tensors="pt")
# 모델과 입력을 같은 디바이스로 옮김 (CPU 환경)
device = torch.device("gpu")
model = model.to(device)
inputs = {k: v.to(device) for k, v in inputs.items()}
# 텍스트 생성
output = model.generate(**inputs, max_new_tokens=512)
# 디코딩 및 출력
print(processor.tokenizer.decode(output[0], skip_special_tokens=True))
코드는 위와 같습니다.
간단하게 transformers 라이브러리의 Llava 모델 객체를 가져와 사용하며,
미리 준비한 example.jpg 이미지를 텍스트와 함께 feed forward 한 결과는,
이렇게 이미지를 잘 읽어들인 것을 볼 수 있습니다.
- 추가적인 개선사항으로는,
이미지를 입력하기 전에 전처리를 통해 성능을 높이는 것이고, 그외의 활용법도 생각해낼 수 있을 것 같네요.
이번 실습을 진행하면서 개인적으로 느낀 점으로는,
아무리 기본 제공되는 모델 객체가 사용하기 편해도, 실제적인 모델에 대한 이해와 구현 및 파인튜닝이 불가능하면 이 이상의 자유로운 응용이 불가능하다는 것으로, 다음에는 텐서플로 관련 기본 내용 설명을 시작으로 최신 모델들을 하나씩 리뷰하겠습니다.