Skip to main content

시각적 차트 이해를 위한 Pixtral Large 활용하기

오픈 소스 Pixtral Large와 Claude 3.5 Sonnet, GPT-4o Vision 같은 클로즈드 소스 파운데이션 모델 간의 대결 이 글은 AI 번역본입니다. 오역이 의심되는 부분이 있다면 댓글로 알려주세요.
Created on September 12|Last edited on September 12
멀티모달 최강자를 향한 경쟁에 새로운 도전자가 나타났다. Mistral AI가 최근 공개한 Pixtral Large, 1240억 파라미터 모델(123B 언어 디코더 + 1B 비전 인코더)로, 여러 벤치마크에서 업계 선두 모델들을 능가한다는 대담한 주장과 함께 AI 커뮤니티에서 큰 주목을 받았다. 특히 Pixtral은 ChartQA에서 88.1% 정확도를 달성했다고 보고했으며, 이는 양측과 비교해도 경쟁력 있는 성과다 Claude 3.5 Sonnet (89.1%) 및 GPT-4o (85.2%)
이번 글에서는 시각적 차트 이해를 위해 Pixtral Large를 설정하고, ChartQA를 사용해 강력한 경쟁자들(OpenAI의 GPT-4o Vision과 Anthropic의 Claude 3.5 Sonnet)과 비교 평가해 보겠습니다 ChartQA 데이터셋.


목차



차트 이해란 무엇인가요?

차트 이해는 멀티모달 AI의 핵심 최전선으로, 그래프, 막대 차트, 손실 곡선과 같�� 데이터 시각화를 해석하고 분석할 수 있게 합니다. 이 능력은 텍스트 정보와 시각 정보 사이의 간극을 메우어 구조화된 데이터를 매끄럽게 이해하도록 돕습니다.
하지만 차트 이해란 정확히 무엇일까요? 핵심적으로, 다음과 같은 능력을 포함합니다:
  • 축, 범례, 데이터 포인트와 같은 시각적 요소를 해석합니다.
  • 데이터 내의 관계와 추세를 파악합니다.
  • 차트에서 도출한 인사이트를 동반된 텍스트나 수치 정보와 함께 맥락화합니다.
현실 세계의 응용에서, 이 능력은 다음과 같은 분야에서 돌파구로 이어집니다:
  • 비즈니스 인텔리전스: 의사결정자를 위한 대시보드 해석 자동화
  • 과학 연구: 복잡한 데이터 시각화를 분석하여 인사이트를 도출합니다.
  • 데이터 분석: 재무 보고서, 학술 논문, 공공 데이터셋의 차트 처리 과정을 간소화합니다.
이러한 활용 사례는 ChartQA와 같은 벤치마크가 멀티모달 모델의 성능을 평가하는 데 왜 핵심적인지 보여줍니다. ChartQA는 모델이 차트 기반 정보를 얼마나 잘 추출하고, 해석하고, 추론하는지를 평가합니다. Pixtral Large가 보여준 것처럼 이 벤치마크에서의 높은 성능은 모델이 복잡한 현실 과제를 해결할 수 있는 능력을 반영합니다.
아래 차트는 ChartQA에서의 Pixtral Large 성능을 다른 주요 멀티모달 모델과 비교해 보여줍니다:


Pixtral Large 설정하기

Pixtral Large를 사용하려면 먼저 Pixtral API를 설정하고 워크플로에 통합해야 합니다. 먼저 Mistral에서 유효한 API 키를 발급받았는지 확인하세요. API 키를 확보했다면, 환경에서 Mistral 클라이언트를 초기화합니다. 이를 위해 다음을 설치하는 과정이 포함됩니다. mistralai 라이브러리를 설치하고 키로 클라이언트를 설정합니다(아래 코드에서 확인할 수 있습니다).
코드로 들어가기 전에, Python이 설치되어 있는지 확인하고, 이미지 처리용 Pillow, ChartQA 같은 벤치마크 데이터셋을 다루기 위한 datasets 등 필요한 종속 항목도 함께 설치하세요. Weave 모델의 입력과 출력을 추적하고 로깅하기 위해서입니다. 또한 Mistral AI 계정과 API 키가 필요합니다.
다음 명령어로 필요한 Python 라이브러리를 설치할 수 있습니다:
pip install -U mistralai weave pillow datasets openai anthropic
그리고 다음은 Pixtral Large로 추론을 실행하기 위한 기본 코드입니다:
import base64
from io import BytesIO
from PIL import Image
from datasets import load_dataset
from mistralai import Mistral
import weave

# Initialize Weave
weave.init("pixtral_inference")

# Set up the Pixtral API client
API_KEY = "your api key" # Replace with your API key
client = Mistral(api_key=API_KEY)

# Helper function to encode an image to base64
def encode_image(image_obj):
if isinstance(image_obj, Image.Image): # Check if it's already a PIL Image
img = image_obj
else: # Otherwise, try opening it as a path
img = Image.open(image_obj)
buffered = BytesIO()
img.save(buffered, format="PNG")
return base64.b64encode(buffered.getvalue()).decode("utf-8")

# Define a function to perform inference
@weave.op
def run_inf(image_obj, prompt):
# Encode the image to base64
image_base64 = encode_image(image_obj)

# Prepare input for the Pixtral API
messages = [
{
"role": "user",
"content": [
{"type": "text", "text": prompt},
{"type": "image_url", "image_url": {"url": f"data:image/png;base64,{image_base64}"}}
]
}
]

# Perform inference
response = client.chat.complete(
model="pixtral-large-latest",
messages=messages,
max_tokens=300
)
# Return the model's output
return response.choices[0].message.content

# Example usage with the dataset
if __name__ == "__main__":
# Load the ChartQA dataset
dataset = load_dataset("TeeA/ChartQA", split="train")

# Select one sample for inference
sample_data = dataset[0] # Get the first data point

# Extract the image and prompt
image_obj = sample_data["image"] # This should be a PIL Image or file path
prompt = sample_data["qa"][0]["query"] # Get the query for the first QA pair

# Perform inference
result = run_inf(image_obj, prompt)
# Print the result
print(f"Question: {prompt}")
print(f"Model Prediction: {result}")

Weave로 모델 동작 모니터링

Weave의 통합 기능은 Pixtral 기반 애플리케이션의 입력과 출력을 추적하고 시각화하는 효율적인 방법을 제공합니다. 추론 함수를 다음과 같이 감싸면 @weave.op함수의 인자와 출력(이미지, 프롬프트, 모델의 응답 포함)을 로깅하게 됩니다. 이를 통해 모델의 실시간 성능을 손쉽게 모니터링하고, 잠재적인 병목 구간이나 개선이 필요한 영역을 식별할 수 있습니다.

최신 멀티모달 모델 대비 Pixtral 평가

GPT-4o Vision과 Claude 같은 최신 모델과 비교해 Pixtral Large의 성능을 더 잘 파악하기 위해 ChartQA 데이터셋을 활용한 소규모 평가를 진행하겠습니다.
목표는 전체 성능을 분석하는 것이 아니라, 각 모델이 차트 기반 질의를 해석하고 응답하는 방식에서 나타나는 정성적 차이를 분석하는 데 있습니다. 이러한 분석은 모델들이 멀티모달 입력을 서로 다르게 처리하는 방식을 보여 주며, 각자의 강점과 약점을 부각합니다. Weave의 평가 대시보드를 사용하면 모델 예측을 상호작용적이고 세밀하게 확인할 수 있습니다.
이번 평가를 위해 다음과 같은 작은 헬퍼 클래스를 만들었습니다, 이름은 EzModel (간단히 easy model)로 줄여 부르는 이 헬퍼는 다양한 모델과 플랫폼에 대한 API 호출을 단순화합니다. 이 클래스는 Pixtral, GPT, Claude의 API 호출에서 발생하는 복잡성을 추상화하여 하나의 통합 인터페이스로 제공합니다. 이를 통해 각 모델의 API별 특이점에 신경 쓰지 않고 평가 작업 자체에 온전히 집중할 수 있습니다. 클래스는 이미지 인코딩, API 호출 재시도 로직, 모델별 포맷 요구 사항을 처리합니다. 덕분에 서로 다른 모델들을 쉽게 반복 실험하고, 동일한 질문과 이미지 세트에서의 출력을 체계적으로 비교할 수 있습니다.
다음은 클래스 정의입니다 EzModel이 헬퍼는 Pixtral, GPT-4o, Claude, Gemini 같은 모델의 추론을 관리하도록 설계되었으며, 클라이언트를 자동으로 초기화하고 이미지를 인코딩하며 추론을 처리합니다. 이 클래스를 사용하려면 각 모델의 API 키를 환경 변수로 설정���야 합니다.
이 코드를 다음 이름의 파일로 저장하세요 ez_model.py 원한다면 사용하세요.
💡
코드는 다음과 같습니다:
import os
import base64
from io import BytesIO
from PIL import Image

class EzModel:
def __init__(self, model_name, max_tokens=1024, temperature=0.0):
self.model_name = model_name.lower()
self.api_key = os.getenv(f"{model_name.upper()}_API_KEY")
self.max_tokens = max_tokens
self.temperature = temperature
if not self.api_key:
raise ValueError(f"API key for {model_name} not found in environment variables.")
self.client = self._initialize_client()

def _initialize_client(self):
if self.model_name == "claude":
from anthropic import Anthropic
return Anthropic(api_key=self.api_key)
elif self.model_name == "gpt":
from openai import OpenAI
return OpenAI(api_key=self.api_key)
elif self.model_name == "mistral":
from mistralai import Mistral
return Mistral(api_key=self.api_key)
else:
raise ValueError(f"Unsupported model name: {self.model_name}")
def _encode_image(self, image):
"""Convert PIL image to base64 with correct format per API"""
if image.mode == "RGBA":
image = image.convert("RGB")
buffered = BytesIO()
image.save(buffered, format="JPEG")
return base64.b64encode(buffered.getvalue()).decode("utf-8")

def __call__(self, prompt, pillage=None, base64Img=None, max_tokens=None, temperature=None):
# Handle image input
image_base64 = None
if pillage:
if isinstance(pillage, Image.Image):
image_base64 = self._encode_image(pillage)
else:
raise ValueError("pillage must be a PIL Image object")
elif base64Img:
image_base64 = base64Img

max_tokens = max_tokens if max_tokens is not None else self.max_tokens
temperature = temperature if temperature is not None else self.temperature

if self.model_name == "claude":
return self._infer_claude(image_base64, prompt, max_tokens, temperature)
elif self.model_name == "gpt":
return self._infer_gpt(image_base64, prompt, max_tokens, temperature)
elif self.model_name == "mistral":
return self._infer_mistral(image_base64, prompt, max_tokens, temperature)
else:
raise ValueError(f"Unsupported model: {self.model_name}")

def _infer_claude(self, image_base64, prompt, max_tokens, temperature):
messages = [
{
"role": "user",
"content": [
{
"type": "text",
"text": prompt
}
]
}
]
if image_base64:
messages[0]["content"].insert(0, {
"type": "image",
"source": {
"type": "base64",
"media_type": "image/jpeg",
"data": image_base64
}
})
return self.client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=max_tokens,
temperature=temperature,
messages=messages
).content[0].text

def _infer_gpt(self, image_base64, prompt, max_tokens, temperature):
if image_base64:
content = [
{
"type": "text",
"text": prompt
},
{
"type": "image_url",
"image_url": {
"url": f"data:image/jpeg;base64,{image_base64}"
}
}
]
else:
content = prompt
return self.client.chat.completions.create(
model="gpt-4o-2024-08-06",
messages=[{"role": "user", "content": content}],
max_tokens=max_tokens,
temperature=temperature
).choices[0].message.content

def _infer_mistral(self, image_base64, prompt, max_tokens, temperature):
messages = [
{
"role": "user",
"content": [
{
"type": "text",
"text": prompt
}
]
}
]
if image_base64:
messages[0]["content"].append({
"type": "image_url",
"image_url": {
"url": f"data:image/jpeg;base64,{image_base64}"
}
})
return self.client.chat.complete(
model="pixtral-large-latest",
messages=messages,
max_tokens=max_tokens,
temperature=temperature
).choices[0].message.content

W&B Weave로 Pixtral Large 평가하기

헬퍼 준비가 끝났으니, 이제 본격적으로 평가 스크립트로 넘어가겠습니다. 헬퍼를 사용해 Pixtral, GPT-4o, Claude용 모델 래퍼 세 개를 설정했습니다. 평가는 ChartQA 데이터셋의 일부 하위 집합을 준비하는 것부터 시작합니다. 이 데이터셋은 차트 이미지와 해당하는 질문-정답 쌍으로 구성됩니다. 이미지는 모든 모델과의 호환성을 보장하도록 전처리하고, 질문은 평가 전반에서 일관성을 유지하도록 형식을 통일합니다.
평가에 사용할 코드는 다음과 같습니다:
import os
import json
import asyncio
from datasets import load_dataset
from ez_model import EzModel
from weave import Evaluation, Model
import weave
from PIL import Image
from io import BytesIO
import base64

# Initialize Weave
weave.init("pixtral_vs_others_eval")

def get_pil_image(image):
"""Convert input to PIL Image"""
if isinstance(image, str): # File path
return Image.open(image)
elif isinstance(image, Image.Image): # Already PIL image
return image
elif isinstance(image, bytes): # base64 decoded bytes
return Image.open(BytesIO(image))
elif isinstance(image, str) and image.startswith('data:image'): # base64 string
# Strip data URL prefix if present
base64_data = image.split(',')[1] if ',' in image else image
image_bytes = base64.b64decode(base64_data)
return Image.open(BytesIO(image_bytes))
else:
raise ValueError("Unsupported image type")

# Instantiate shared model instances
model_instances = {
"claude": EzModel("claude"),
"gpt": EzModel("gpt"),
"mistral": EzModel("mistral"),
}

class ClaudeModel(Model):
@weave.op()
def predict(question: str, image):
# Hardcoded model instance for "claude"
model = model_instances["claude"]
# Convert image to PIL and pass as pillage
pil_image = get_pil_image(image)
response = model(prompt=question, pillage=pil_image)
return {"model_output": response}

class GPTModel(Model):
@weave.op()
def predict(question: str, image):
# Hardcoded model instance for "gpt"
model = model_instances["gpt"]
# Convert image to PIL and pass as pillage
pil_image = get_pil_image(image)
response = model(prompt=question, pillage=pil_image)
return {"model_output": response}

class MistralModel(Model):
@weave.op()
def predict(question: str, image):
# Hardcoded model instance for "mistral"
model = model_instances["mistral"]
# Convert image to PIL and pass as pillage
pil_image = get_pil_image(image)
response = model(prompt=question, pillage=pil_image)
return {"model_output": response}

@weave.op()
def llm_judge_scorer(ground_truth: str, model_output: dict) -> dict:
"""Query multiple LLMs to judge correctness and use Claude to determine the majority vote."""
if not model_output or "model_output" not in model_output:
return {"llm_judge_score": 0.0}

# Gather individual judgments from all models
judgments = {}
for judge_name, judge_model in model_instances.items():
prompt = (
f"You are an LLM judge evaluating correctness on ChartQA.\n"
f"Ground Truth: {ground_truth}\n"
f"Predicted Response: {model_output['model_output']}\n\n"
"Does the predicted response contain the ground truth answer? "
"The answer does not need to be verbatim but must contain the correct answer. "
"Respond with 'true' or 'false'."
)
response = judge_model(prompt=prompt)
judgments[judge_name] = "true" in response.lower().strip()

majority_prompt = (
f"You are tasked with determining the majority vote from multiple LLM judgments.\n"
f"The judgments from the LLMs are as follows:\n{json.dumps(judgments)}\n\n"
f"Determine the majority vote based on the judgments. "
f"If there is a tie, the decision is 'false'. "
f"Respond with 'true' or 'false'."
)
final_decision = model_instances["claude"](prompt=majority_prompt)


return {"llm_judge_score": 1.0 if "true" in final_decision.lower().strip() else 0.0}


def create_evaluation_dataset(dataset_name: str, split: str = "test", eval_size: int = 3):
"""Prepare evaluation dataset from the ChartQA dataset."""
dataset = load_dataset(dataset_name, split=split, cache_dir="./cache").shuffle(seed=42)
# Get last eval_size examples
start_idx = max(0, len(dataset) - eval_size)
eval_data = dataset.select(range(start_idx, len(dataset)))

evaluation_dataset = []
for example in eval_data:
question = example["qa"][0]["query"]
ground_truth = example["qa"][0]["label"]
image = example["image"]

# Convert to PIL Image if needed
if isinstance(image, str): # If it's a file path
pil_image = Image.open(image)
if pil_image.mode == "RGBA":
pil_image = pil_image.convert("RGB")
elif isinstance(image, Image.Image):
pil_image = image
else:
raise ValueError(f"Unexpected image type: {type(image)}")

evaluation_dataset.append({
"question": "Based on the image, answer the following question: " + question,
"ground_truth": ground_truth,
"image": pil_image
})

# Take first 3 examples and ensure we have data
if not evaluation_dataset:
raise ValueError("No examples were processed from the dataset")
return evaluation_dataset

async def run_evaluations():
"""Run evaluations for all models."""
eval_dataset = create_evaluation_dataset("TeeA/ChartQA")

# Initialize models
models = {
"claude": ClaudeModel(),
"gpt": GPTModel(),
"mistral": MistralModel(),
}

# Define scorers
scorers = [llm_judge_scorer]

# Run evaluations
results = {}
for model_name, model in models.items():
print(f"\nEvaluating {model_name}...")
evaluation = Evaluation(
dataset=eval_dataset,
scorers=scorers,
name=model_name + " Evaluation"
)
results[model_name] = await evaluation.evaluate(model.predict)

# Print and save results
print("\nEvaluation Results:")
for model_name, result in results.items():
print(f"\n{model_name} Results:")
print(json.dumps(result, indent=2))

output_file = "image_model_evaluation_results.json"
with open(output_file, "w") as f:
json.dump(results, f, indent=2)
print(f"\nResults saved to {output_file}")

return results

if __name__ == "__main__":
asyncio.run(run_evaluations())


평가 프레임워크는 준비된 데이터셋에 대해 각 모델이 예측을 생성하도록 합니다. 이러한 예측은 Weave를 사용해 추적 및 로깅되며, 질문과 해당 이미지라는 입력과 출력이 모두 기록됩니다. 모든 예측을 로깅함으로써 프레임워크는 각 모델의 성능에 대한 상세한 기록을 제공하고, 특정 사례에서 모델이 뛰어나거나 약점을 보이는 지점을 심층 분석할 수 있게 합니다. 또한 기록된 데이터는 각 모델의 정확한 입력과 출력을 나란히 추적하기 쉬워 직접적인 비교를 용이하게 합니다.
이번 평가에서 주목할 만한 구성 요소는 본질적으로 …인 채점 메커니즘입니다. LLM 심판 채점기. 이 채점기는 추가 언어 모델을 심판으로 활용해 모델 응답의 정답성을 평가하는 LLM 앙상블입니다. 단순한 기초 지표에만 의존하지 않고, 평가 과정에 추론과 판단의 층을 도입합니다. 모델이 생성한 각 응답에 대해 채점기는 정답과 함께 해당 응답을 Pixtral, Claude, GPT-4o 등을 포함한 언어 모델 심판단에 제시합니다. 심판들은 사실적 정확성과 의미적 타당성을 모두 고려해 응답이 정답과 일치하는지 평가합니다.
채점기는 언어 모델 심판들에게 응답의 문구 그대로가 아니라 내용에 집중하라고 명시적으로 지시합니다. 이를 통해 평가가 문체적 차이로 모델을 불이익 주는 대신, 모델의 이해 및 해석 능력을 반영하도록 합니다.
개별 판정이 수집되면 채점기는 이를 집계해 다수결로 최종 결정을 내립니다. 대부분의 심판이 응답이 정답이라고 동의하면 정답으로 표시하고, 그렇지 않으면 오답으로 표시합니다. 엄밀성을 한층 더하기 위해, Claude가 집계된 판정을 입력으로 받아 결과에 대응하는 최종 이진 출력을 생성합니다.
이번 평가의 결과는 Weave를 통해 저장되고 시각화되며, 정량적 점수와 정성적 인사이트를 모두 제공합니다. 구조화된 채점과 상세 로깅을 결합함으로써 프레임워크는 모델 성능에 대한 포괄적인 관점을 제공합니다. 단순히 정확도를 측정하는 데 그치지 않고, 특정 질문 유형에서 반복적으로 강점을 보이거나 특정 시각적 상황에서 일관된 오류가 발생하는 등 응답의 패턴도 밝혀냅니다. 이 과정을 통해 Pixtral의 역량을 면밀히 평가할 수 있으며, 멀티모달 과업의 맥락에서 GPT-4o와 Claude 같은 경쟁 모델과의 성능 비교도 효과적으로 수행할 수 있습니다.
Weave Evaluations 내부 화면은 대략 다음과 같습니다:


ChartQA 데이터셋의 이 작은 하위 집합에서, Pixtral Large의 성능은 GPT-4o와 Claude에 견줄 만했습니다.세 모델 모두 차트 기반 데이터를 해석하고 의미 있는 응답을 제공하는 데 강력한 역량을 보여줬으며, 과제를 처리하는 능력에서 유의미한 차이는 보이지 않았습니다. 이러한 결과는 Pixtral이 멀티모달 AI 분야에서 경쟁력 있는 플레이어이며, 확립된 상용 모델들과 어깨를 나란히 할 수 있음을 다시금 확인시켜 줍니다.
하지만 응답의 문체와 구성에서는 눈에 띄는 차이가 관찰되었습니다. Pixtral은 GPT-4o와 Claude에 비해 일관되게 더 길고 상세한 답변을 생성했습니다.. GPT-4o와 Claude는 대체로 최소한의 부연으로 질문에 직접 답하는 간결한 응답을 선택한 반면, Pixtral은 자신의 추론 과정을 단계별로 풀어 설명하는 경향을 보였습니다. 이는 차트에서 특정 요소를 추출하고, 그 해석을 설명하며, 최종 결과를 도출하는 등 중간 단계를 명시적으로 제시하는 “chain of thought” 방식과 유사했습니다. 이러한 특성이야말로 Pixtral의 인상적인 역량을 뒷받침하는 비결일 수 있습니다.
이는 Weave Evaluations의 뛰어난 비교 보기 덕분에 알게 되었습니다. 비교 보기 화면은 대략 다음과 같습니다:


결론

우리는 전체 ChartQA 테스트 세트로 모델들을 평가하지는 않았지만, Mistral이 Pixtral Large를 대상으로 ChartQA 전체 데이터셋에서 수행한 포괄적 평가를 전적으로 신뢰합니다. 더 폭넓은 예시 범위에서 Pixtral Large가 강력한 성능을 보인다는 그들의 공개 결과는 모델의 실제 역량을 신뢰할 수 있게 보여줍니다. 우리의 작은 하위 집합 평가는 정확도와 추론 능력 측면에서 Pixtral이 다른 선도 모델들과 보조를 맞춘다는 점을 확인해 주며, 또한 모델이 높은 성능을 달성하기 위해 Chain-of-Thought를 어떻게 활용하고 있는지에 대한 통찰도 제공합니다.
Pixtral의 오픈 소스 특성은 그 성과에 또 하나의 인상적인 면모를 더합니다. 대규모 상업적 자원을 바탕으로 한 상용 모델인 GPT-4o와 Claude와 달리, Pixtral은 공개되어 있어 연구자와 개발자가 다양한 활용 사례에 맞게 탐구하고, 미세 조정하고, 적응시킬 수 있습니다. 오픈 소스 상태를 유지하면서도 시장을 선도하는 상용 시스템에 근접한 결과를 달성했다는 점은 매우 큰 성취로, 경쟁이 치열한 AI 환경에서 Pixtral의 강점과 잠재력을 잘 보여줍니다.
요약하면, 이번 하위 집합에서의 경쟁력 있는 성능과 높은 접근성 덕분에 Pixtral은 연구자와 실무자 모두에게 훌륭한 선택지입니다. 입증된 역량을 바탕으로 멀티모달 AI 응용 분야의 발전을 이끄는 중요한 동력으로 계속 자리매김할 것입니다.


이 글은 AI로 번역된 기사입니다. 오역이 의심되는 부분이 있다면 댓글로 알려 주세요. 원문 보고서는 아래 링크에서 확인하실 수 있습니다: 원문 보고서 보기