Skip to main content

Amazon Bedrock에서 LLM 평가하기

Amazon Bedrock과 W&B Weave를 함께 활용해 요약 작업용 대규모 언어 모델(LLM)을 평가하고 비교하는 방법을 알아보세요. Bedrock의 관리형 인프라와 Weave의 고급 평가 기능을 결합해 효율적으로 벤치마크하고 의사결정을 가속화할 수 있습니다. 이 글은 AI 번역본입니다. 오역이 있을 수 있으니 댓글로 알려주세요.
Created on September 12|Last edited on September 12
조직들이 방대한 정보의 양과 씨름하는 가운데, 요약 작업 는 이제 업계 전반의 워크플로에서 핵심 구성 요소가 되었습니다. 연구 논문을 요약하고, 재무 보고서를 압축하며, 비즈니스 문서에서 인사이트를 추출하는 등 빠르게 변화하는 환경에서 경쟁력을 유지하려면 간결하고 일관된 요약을 생성하는 능력이 필수적입니다. 대규모 언어 모델 이 도전의 최전선에 서 있으며, 인간에 버금가는 자동 요약 기능을 제공합니다.
Weave Evaluations 대시보드.
이 글은 Amazon Bedrock과 함께 사용하는 방법을 자세히 다룹니다 W&B Weave 용을 위한 LLM 평가 요약 작업에서. Bedrock의 인프라와 Weave의 시각화 및 분석 도구를 결합하면, 다양한 사용 사례에 가장 적합한 모델을 체계적으로 비교해 찾을 수 있습니다.
Jump to the tutorial



이번 글에서 다룰 내용



Amazon Bedrock에서 사용할 수 있는 파운데이션 모델

Amazon Bedrock은 다양한 요구 사항을 충족하기 위해 기반 인프라를 직접 관리할 필요 없이 폭넓은 LLM에 유연하게 접근할 수 있는 플랫폼을 제공합니다. 이 플랫폼은 폐쇄형과 오픈 소스 모델을 모두 포함하여, 광범위한 사용 사례에 대응할 수 있는 선택지를 제공합니다. Bedrock 다음과 같은 선도적 제공업체의 모델을 지원합니다: Anthropic, Meta, 및 미스트랄, 그리고 Amazon의 자체 Titan 모델도 포함됩니다. 이처럼 다양한 선택지는 대화형 AI, 요약, 고처리량 애플리케이션과 같은 작업에 최적화된 도구에 접근할 수 있게 해 주며, 특정 운영 목표에 부합하는 모델을 유연하게 선택할 수 있도록 보장합니다.
생성형 작업을 넘어, Bedrock은 다음과 같은 특화 모델도 지원합니다: 임베딩, 의미 기반 검색, 클러스터링, 분류 작업을 가능하게 합니다. 이처럼 다양한 제공 모델을 통해 Bedrock은 최첨단 비공개 소스 성능이 필요하든, 맞춤화 가능한 오픈 소스의 유연성이 필요하든, 가장 적합한 도구를 선택할 수 있도록 보장하며, 다음 모두에 대한 지원과 함께 제공합니다: 텍스트 생성 및 임베딩 기반 애플리케이션입니다.

W&B Weave와 Amazon Bedrock을 활용한 LLM 요약 평가

Weave Evaluations는 Weave 프��임워크 내의 전용 도구로, 생성형 AI 모델을 효과적으로 벤치마크하고 비교하도록 설계되었습니다. 다양한 모델에 간편하게 접근할 수 있는 Amazon Bedrock과 결합하면, 이 조합은 모델 성능을 평가하는 강력한 솔루션이 됩니다.
Bedrock은 오픈 소스와 상용 모델 모두에 손쉽게 접근할 수 있도록 지원하므로, 사용자는 다양한 옵션을 빠르게 실험해 볼 수 있습니다. Bedrock과 함께 Weave Evaluations를 활용하면, 이러한 모델을 효율적으로 벤치마크하고 출력물을 분석하며, 핵심 지표 전반의 성능을 시각화할 수 있습니다. 이 조합을 통해 비용, 정확도, 속도, 출력 품질 등 모델 간 트레이드오프를 더욱 깊이 있게 이해할 수 있습니다.
나란히 비교와 동적 시각화를 통해 Weave Evaluations는 사용자가 특정 활용 사례에 가장 적합한 모델을 합리적으로 선택할 수 있도록 지원하며, Bedrock의 방대한 모델 카탈로그를 탐색하는 과정을 간소화합니다.
다음은 Weave Evaluations 대시보드 예시로, 우리 모델의 성능을 훌륭하게 시각화하는 방법을 제공합니다:


W&B Weave와 Amazon Bedrock을 활용한 LLM 요약 평가

Amazon Bedrock을 시작하려면 계정 설정과 액세스를 위한 몇 가지 간단한 단계를 진행하면 됩니다. 기반 AI 모델및 통합에 필요한 도구를 구성합니다. 여기서는 AWS 계정을 생성하고 과금을 활성화하는 것부터 AWS CLI를 설정하고 Bedrock의 다양한 모델을 살펴보는 것까지 초기 설정 과정을 단계별로 안내합니다.
이미 Bedrock에 접근 권한이 있는 AWS 계정을 보유하고 있다면, 다음을 수행할 수 있습니다:
Jump past the AWS & Bedrock set up


이 단계들을 따르면 텍스트 생성, 임베딩, 멀티모달 처리와 같은 작업에 Bedrock의 강력한 AI 기능을 빠르게 활용할 수 있습니다.

1단계: AWS 계정 및 결제 설정

아직 AWS 계정이 없다면 먼저 계정을 생성하세요. 가입하기 AWS 웹사이트에서 계정을 만든 뒤 결제가 계정에 활성화되어 있는지 확인하세요. 결제 활성화는 Amazon Bedrock을 포함한 AWS 서비스에 접근하기 위해 필수이며, 이러한 서비스는 사용량 기반 과금 모델로 운영됩니다. AWS 콘솔의 Billing and Cost Management 섹션으로 이동해 계정이 사용할 준비가 되었는지 확인하세요.

2단계: Amazon Bedrock 액세스

AWS Management Console에서 서비스 메뉴의 검색창에 “Bedrock”을 입력하세요. Bedrock 콘솔을 열면 사용 가능한 파운데이션 모델에 대한 개요가 표시됩니다. Bedrock은 Anthropic, Meta, AI21 Labs, Mistral, Stability AI, Amazon의 Titan 모델에 대한 액세스를 제공합니다. 이들 모델은 텍스트 생성, 임베딩, 멀티모달 처리와 같은 작업을 지원하므로, 사용 사례에 맞게 다양한 옵션을 선택할 수 있습니다.



3단계: AWS CLI 구성

Bedrock을 프로그래밍 방식으로 사용하려면 AWS Command Line Interface를 설치하세요. 계속해서 다음 단계를 따르세요. AWS CLI 설치 가이드 사용 중인 운영 체제에 맞는 설치를 완료한 뒤, 터미널에서 다음 명령을 실행해 CLI를 초기화하세요:
aws configure
액세스 키, 시크릿 키, 기본 리전(예: 이 튜토리얼에서는 us-east-1), 그리고 출력 형식(json)을 입력하세요. 이러한 키는 AWS 콘솔의 Security Credentials에서 새 액세스 키를 만들어 생성할 수 있습니다. 새 액세스 키를 만들려면 AWS Management Console의 우측 상단에 있는 계정 이름을 클릭하고 드롭다운 메뉴에서 Security Credentials를 선택하세요. 그런 다음 Access Keys 섹션으로 스크롤하여 Create Access Key를 클릭합니다. 키가 생성되면 액세스 키 ID와 시크릿 키를 안전한 위치에 보관하세요. AWS CLI 또는 SDK를 구성할 때 필요합니다.


다음으로 “Access Keys” 섹션이 표시됩니다:


4단계: 필요한 Python 라이��러리 설치

Bedrock을 W&B Weave와 연동하려면 필요한 Python 라이브러리를 설치하세요. Python 환경에서 다음 명령을 실행합니다:
pip install boto3 botocore weave wandb
이 라이브러리들을 사용하면 Bedrock 모델에 요청을 보내고, 응답을 기록하며, W&B Weave에서 평가 결과를 시각화할 수 있습니다.

5단계: Bedrock 모델 액세스를 요청하고 추론 프로필을 구성하기

다음으로 사용하려는 모델에 대한 액세스를 요청해야 합니다. 이를 위해 아래 스크린샷에서 “Providers” 탭을 클릭하면, 사용 가능한 모델에 “Request Model Access” 버튼이 표시되며 이 버튼을 통해 해당 모델의 액세스를 요청할 수 있습니다.
다음으로 Amazon Bedrock 콘솔에서 “Cross-Region Inference” 섹션으로 이동하여 사용 가능한 모델별 추론 프로필을 확인하세요. 각 모델의 설명과 해당 프로필 ID가 표시됩니다. 이 프로필 ID를 사용해 특정 모델로 API 요청을 라우팅합니다.
예를 들어 Claude와 Llama를 모두 평가하는 경우, Bedrock API를 통해 요청할 때 각 모델에 해당하는 프로필 ID가 필요합니다.


제가 사용 중인 추론 프로필은 다음과 같습니다:


데이터세트 생성

요약 작업에서 LLM의 성능을 평가하려면 실제 환경의 난이도를 반영하는 신뢰할 수 있는 데이터세트가 필요합니다. 이 가이드에서는 풍부한 학술 콘텐츠 저장소인 arXiv의 연구 논문을 데이터 소스로 사용합니다.
목표는 머신러닝과 인공지능 주제와 관련된 논문을 추출하고 요약하는 것입니다. 이러한 논문들은 Amazon Bedrock을 통해 액세스하는 LLM의 요약 능력을 평가하기 위한 다양하고 도전적인 테스트베드로 활용됩니다. 자동화된 논문 다운로드와 함께 사용하는 방식으로 Anthropic의 Claude 모델이 생성한 요약문 Bedrock에서 각 논문별로 연구 제목, 추출한 본문 텍스트, 간결한 요약을 포함한 구조화된 데이터세트를 생성합니다.
Claude의 고도화된 기능 덕분에 생성된 요약문은 일관성이 있을 뿐 아니라 연구의 핵심을 효과적으로 포착합니다. 데이터세트는 처리와 평가의 용이성을 위해 JSONL 형식으로 저장되며, 중요한 정보를 간결하고 구조화된 출력으로 합성하는 Claude의 능력을 활용합니다. 다음은 데이터세트를 생성하는 코드입니다:
import os
import arxiv
import fitz # PyMuPDF
import json
import boto3
from botocore.exceptions import ClientError
import re
import random
import time
from time import sleep

# Directory to save downloaded papers
download_dir = "arxiv_papers"
os.makedirs(download_dir, exist_ok=True)

# Set up Amazon Bedrock client
bedrock_client = boto3.client("bedrock-runtime", region_name="us-east-1")
MODEL_ID = "anthropic.claude-3-sonnet-20240229-v1:0"

# Fixed questions for paper analysis
FIXED_QUESTIONS = """
What is the primary objective of this research?
What methodologies or algorithms are proposed or evaluated?
What datasets or experimental setups are used in this study?
What are the key findings and contributions of this research?
What are the implications of these findings for the broader field of AI?
What limitations or challenges are acknowledged by the authors?
What are the proposed future directions or next steps in this research?
"""

# Define AI-specific search queries
search_queries = [
"Large Language Models for vision tasks AND cat:cs.AI",
"Multimodal AI techniques AND cat:cs.CV",
"Applications of Transformers in healthcare AI AND cat:cs.LG",
"Few-shot learning in AI and ML AND cat:cs.LG",
"Vision and language models integration AND cat:cs.CV",
"Domain-specific fine-tuning for ML models AND cat:cs.LG",
"Foundational models in AI and CV applications AND cat:cs.AI",
"NLP in robotics and vision systems AND cat:cs.AI",
"Bias and fairness in AI for CV AND cat:cs.CV",
"Evaluation metrics for multimodal AI AND cat:cs.LG"
]

def download_papers(max_pages=15, max_attempts_per_query=20):
"""Download one suitable paper for each query, retrying if papers exceed page limit."""
papers = []
downloaded_titles = set()
client = arxiv.Client()

for query in search_queries:
paper_found = False
attempt = 0
while not paper_found and attempt < max_attempts_per_query:
search = arxiv.Search(
query=query,
max_results=100,
sort_by=arxiv.SortCriterion.SubmittedDate
)
try:
results = list(client.results(search))
start_idx = attempt * 5
end_idx = start_idx + 5
current_batch = results[start_idx:end_idx]
for result in current_batch:
if result.title not in downloaded_titles:
print(f"Downloading: {result.title}")
paper_id = result.entry_id.split('/')[-1]
pdf_filename = f"{paper_id}.pdf"
pdf_path = os.path.join(download_dir, pdf_filename)
result.download_pdf(dirpath=download_dir, filename=pdf_filename)
try:
with fitz.open(pdf_path) as pdf:
if pdf.page_count <= max_pages:
papers.append({
"title": result.title,
"file_path": pdf_path,
"arxiv_id": paper_id
})
downloaded_titles.add(result.title)
print(f"Accepted: {result.title}")
paper_found = True
break
else:
os.remove(pdf_path)
print(f"Skipped (too many pages: {pdf.page_count}): {result.title}")
except Exception as e:
print(f"Error checking PDF {pdf_path}: {e}")
if os.path.exists(pdf_path):
os.remove(pdf_path)
attempt += 1
if not paper_found:
print(f"Attempt {attempt}/{max_attempts_per_query} for query: {query}")
sleep(3)
except Exception as e:
print(f"Error during download: {e}")
sleep(3)
attempt += 1
continue
if not paper_found:
print(f"Failed to find suitable paper for query after {max_attempts_per_query} attempts: {query}")

print(f"\nSuccessfully downloaded {len(papers)} papers")
return papers

def extract_text(pdf_path):
"""Extract text from the entire PDF."""
with fitz.open(pdf_path) as pdf:
text = ""
for page in pdf:
text += page.get_text()
return text

def generate_summary_with_claude(text, title):
"""Generate a 300-word summary using Claude via Amazon Bedrock with exponential backoff."""
prompt = (
f"Please analyze the following research paper titled '{title}' and provide a comprehensive 300-word summary. "
f"Consider these key aspects when analyzing the paper:\n\n{FIXED_QUESTIONS}\n\n"
f"Based on these questions, synthesize a coherent summary that captures the essential elements "
f"of the research while maintaining a natural flow. Ensure the summary is 300 words.\n\n"
f"Paper content:\n\n{text}"
)
request = {
"anthropic_version": "bedrock-2023-05-31",
"max_tokens": 4096,
"temperature": 0.0,
"messages": [
{
"role": "user",
"content": [{"type": "text", "text": prompt}]
}
]
}
max_retries = 15
backoff_time = 10 # Start with a 10-second delay
for attempt in range(max_retries):
try:
response = bedrock_client.invoke_model(
modelId=MODEL_ID,
body=json.dumps(request)
)
response_body = json.loads(response["body"].read())
summary = response_body["content"][0]["text"]
return {"summary": summary}
except ClientError as e:
if e.response['Error']['Code'] == 'ThrottlingException':
print(f"ThrottlingException encountered. Retrying in {backoff_time} seconds...")
time.sleep(backoff_time + random.uniform(0, 1)) # Add jitter
backoff_time *= 2 # Exponential backoff
else:
print(f"Error generating summary for {title}: {e}")
break
print(f"Failed to generate summary for {title} after {max_retries} retries.")
return {"summary": ""}

def count_words(text):
"""Count words excluding punctuation and special characters."""
cleaned_text = re.sub(r'[^\w\s]', ' ', text.lower())
words = [word for word in cleaned_text.split() if word.strip()]
return len(words)

def main():
# Download papers
papers = download_papers()
print(f"\nDownloaded {len(papers)} papers. Generating summaries...\n")
# Process papers and generate summaries
paper_data = []
for paper in papers:
title = paper["title"]
pdf_path = paper["file_path"]
print(f"Processing: {title}")
paper_text = extract_text(pdf_path)
summary_json = generate_summary_with_claude(paper_text, title)
summary_text = summary_json.get('summary', '')
word_count = count_words(summary_text)
paper_data.append({
"title": title,
"file_path": pdf_path,
"summary": summary_text,
"word_count": word_count,
"arxiv_id": paper["arxiv_id"]
})
sleep(5) # Wait before processing the next paper to avoid throttling

# Save to JSONL file
output_file = "paper_summaries.jsonl"
with open(output_file, "w") as f:
for entry in paper_data:
json.dump(entry, f)
f.write("\n")

print(f"\nProcessed {len(paper_data)} papers. Results saved to {output_file}")

if __name__ == "__main__":
main()

이 스크립트는 먼저 다운로드한 연구 논문을 저장할 디렉터리를 설정하고, Anthropic의 Claude 같은 모델에 접근하기 위한 Amazon Bedrock 클라이언트를 초기화합니다. 이어서 요약 과정을 안내할 고정형 질문들을 정의하여 모든 논문에서 일관되고 목표 지향적인 출력이 나오도록 합니다. 이 질문들은 연구 목표, 방법론, 데이터세트, 주요 발견 등 핵심 측면을 다루며, 구조적이고 포괄적인 요약을 생성하기 위한 프레임워크로 기능합니다.
이 스크립트는 연구 논문을 자동으로 다운로드하는 과정을 자동화하며 arxiv 라이브러리. 머신러닝과 AI 주제의 논문을 선별하기 위해 특정 검색 쿼리를 사용합니다. 이 함수는 download_papers 지정한 페이지 제한 내의 논문만 처리되도록 논문을 가져옵니다. 추출된 PDF는 로컬에 저장되며, 텍스트는 다음을 사용해 추출합니다. PyMuPDF 라이브러리.
다운로드한 각 논문에 대해, generate_summary_with_claude 이 함수는 구조화된 프롬프트를 사용해 추출한 텍스트를 Amazon Bedrock을 통해 Claude 모델로 전송하고, 300단어 요약을 생성하도록 합니다. 프롬프트는 명료성과 일관성을 강조하여, 사전에 정의된 질문을 함께 다루면서 연구 내용을 요약하도록 모델을 유도합니다. 스크립트에는 Bedrock API가 일시적으로 과부하된 경우를 대비해 대기 시간을 점진적으로 늘리는 재시도 메커니즘이 포함되어 있어, 서비스와의 통신을 원활하고 안정적으로 유지합니다.
이 스크립트는 각 논문에서 제목과 본문을 추출한 뒤 Claude 모델을 사용해 요약을 생성하고, 제목, 전체 텍스트, 해당 요약을 포함한 잘 정리된 데이터세트로 통합합니다. 요약은 JSONL 형식으로 저장되며, 이후 평가 워크플로에서 손쉽게 조회하고 활용할 수 있습니다.

요약을 위한 Weave 기반 Llama 모델 평가

Amazon Bedrock에서 제공되는 여러 Llama 모델의 성능을 효과적으로 비교하기 위해 평가 프레임워크로 W&B Weave를 사용합니다. 물론 관심 있는 어떤 모델에도 확장하여 비교를 수행할 수 있습니다.
Weave 플랫폼은 사전에 정의된 평가 지표에 따라 모델 출력물을 정밀하게 분석할 수 있도록 하여, 체계적이고 효율적인 벤치마크 환경을 제공합니다. 이번 평가에서는 세 가지 서로 다른 모델을 비교합니다.
  1. Llama-1B는 비용 효율성과 고처리량 애플리케이션을 위해 설계된 경량 모델입니다.
  2. Llama-8B는 성능과 효율의 균형이 잘 잡힌 모델이며,
  3. Llama-11B는 상세하고 포괄적인 출력을 생성하도록 최적화된 대용량 모델입니다.
이 설정은 이러한 LLM들 간의 장단점과 트레이드오프를 반복 가능하게 식별할 수 있는 프로세스를 보장하여, 서로 다른 요약 작업에 대한 적합성에 대한 인사이트를 제공합니다.
평가 스크립트는 다음과 같습니다:
import weave
from weave import Model
import json
import boto3
from botocore.exceptions import ClientError
from time import sleep
import asyncio
from rouge_score.rouge_scorer import RougeScorer
from typing import Dict, Any
import bert_score
import fitz
import os
from weave.trace.box import unbox
import time

# Initialize Weave
weave.init('bedrock_abstract_eval')
client = boto3.client("bedrock-runtime", region_name="us-east-1")

def extract_paper_text(pdf_path: str) -> str:
"""Extract text from PDF paper."""
if isinstance(pdf_path, weave.trace.box.BoxedStr):
pdf_path = unbox(pdf_path)

text = ""
try:
with fitz.open(unbox(pdf_path)) as pdf:
for page in pdf:
text += page.get_text()
except Exception as e:
print(f"Error extracting text from PDF {pdf_path}: {e}")
return text

def format_prompt(text: str, title: str) -> str:
"""Format prompt for model."""
return f"""
<|begin_of_text|><|start_header_id|>user<|end_header_id|>
Please analyze this research paper and provide a comprehensive 300-word summary that covers:
- Primary research objective
- Methodology and approach
- Key findings and results
- Main contributions to the field

Title: {title}

Content: {text}

Please analyze this research paper and provide a comprehensive 300-word summary that covers:
- Primary research objective
- Methodology and approach
- Key findings and results
- Main contributions to the field

Generate a clear, coherent summary that captures the essence of the research.
<|eot_id|>
<|start_header_id|>assistant<|end_header_id|>
"""

def model_forward(model_id: str, title: str, pdf_path: str) -> str:
"""Core prediction logic to be called by predict methods."""
max_retries = 15
backoff_time = 10 # Start with 10 seconds delay

for attempt in range(max_retries):
try:
# Extract text from paper
paper_text = extract_paper_text(pdf_path)

# Prepare request
request = {
"prompt": format_prompt(paper_text, title),
"max_gen_len": 4096,
"temperature": 0.0,
}
print(f"Invoking model (Attempt {attempt + 1}/{max_retries})...")
# Make prediction
response = client.invoke_model(
modelId=model_id,
body=json.dumps(request)
)
print("Done invoking")

# Extract and clean prediction
response_body = json.loads(response["body"].read())
prediction = response_body["generation"].strip()
return prediction

except ClientError as e:
if e.response['Error']['Code'] == 'ThrottlingException':
print(f"ThrottlingException encountered. Retrying in {backoff_time} seconds...")
time.sleep(backoff_time)
backoff_time *= 2 # Exponential backoff
else:
print(f"Error generating prediction with {model_id}: {e}")
break
except Exception as e:
print(f"Unexpected error: {e}")
break

print(f"Failed to generate prediction after {max_retries} retries.")
return ""

class Llama8B(Model):
@weave.op
def predict(self, title: str, pdf_path: str) -> dict:
prediction = model_forward("us.meta.llama3-1-8b-instruct-v1:0", title, pdf_path)
return {"model_output": prediction}

class Llama11B(Model):
"""Llama 11B model."""
@weave.op
def predict(self, title: str, pdf_path: str) -> dict:
"""Generate prediction using Llama 11B."""
prediction = model_forward("us.meta.llama3-2-11b-instruct-v1:0", title, pdf_path)
return {"model_output": prediction}

class Llama1B(Model):
"""Llama 1B model."""
@weave.op
def predict(self, title: str, pdf_path: str) -> dict:
"""Generate prediction using Llama 1B."""
prediction = model_forward("us.meta.llama3-2-1b-instruct-v1:0", title, pdf_path)
return {"model_output": prediction}

@weave.op
def bert_scorer(gt_abstract: str, model_output: dict) -> Dict[str, float]:
"""Calculate BERTScore for the abstract."""
if not model_output or 'model_output' not in model_output:
return {'bert_score': 0.0}
try:
P, R, F1 = bert_score.score(
[model_output['model_output']],
[gt_abstract],
lang='en',
model_type='microsoft/deberta-xlarge-mnli'
)
return {'bert_score': float(F1.mean())}
except Exception as e:
print(f"Error calculating BERTScore: {e}")
return {'bert_score': 0.0}

@weave.op
def claude_scorer(gt_abstract: str, model_output: dict) -> dict:
"""Evaluate abstract using Claude."""
if not model_output or 'model_output' not in model_output:
return {'claude_score': 0.0}
print("claude evaluating")
# client = boto3.client("bedrock-runtime", region_name="us-east-1")
prompt = json.dumps({
"anthropic_version": "bedrock-2023-05-31",
"max_tokens": 1024,
"temperature": 0.0,
"messages": [{
"role": "user",
"content": [
{"type": "text", "text": f'''Rate how well this generated abstract captures the key information from the ground truth abstract on a scale from 1-5, where:
1: Poor - Missing most key information or seriously misrepresenting the research
2: Fair - Captures some information but misses crucial elements
3: Good - Captures most key points but has some gaps or inaccuracies
4: Very Good - Accurately captures nearly all key information with minor omissions
5: Excellent - Perfectly captures all key information and maintains accuracy

Ground Truth Abstract:
{gt_abstract}

Generated Abstract:
{model_output["model_output"]}

Provide your rating as a JSON object with this schema:
{{"score": <integer 1-5>}}'''}
]
}]
})
try:
response = client.invoke_model(
modelId="anthropic.claude-3-sonnet-20240229-v1:0",
body=prompt
)
result = json.loads(response["body"].read())
score = json.loads(result["content"][0]["text"])["score"]
print(score)
sleep(2) # Rate limiting
return {'claude_score': float(score)}
except Exception as e:
print(f"Error in Claude evaluation: {e}")
return {'claude_score': 0.0}

@weave.op
def rouge_scorer(gt_abstract: str, model_output: dict) -> Dict[str, float]:
"""Calculate ROUGE scores for the abstract."""
if not model_output or 'model_output' not in model_output:
return {
'rouge1_f': 0.0,
'rouge2_f': 0.0,
'rougeL_f': 0.0
}
try:
scorer = RougeScorer(['rouge1', 'rouge2', 'rougeL'], use_stemmer=True)
scores = scorer.score(gt_abstract, model_output['model_output'])
return {
'rouge1_f': float(scores['rouge1'].fmeasure),
'rouge2_f': float(scores['rouge2'].fmeasure),
'rougeL_f': float(scores['rougeL'].fmeasure)
}
except Exception as e:
print(f"Error calculating ROUGE scores: {e}")
return {
'rouge1_f': 0.0,
'rouge2_f': 0.0,
'rougeL_f': 0.0
}

@weave.op
def compression_scorer(gt_abstract: str, model_output: dict) -> Dict[str, float]:
"""Calculate compression ratio of the abstract."""
if not model_output or 'model_output' not in model_output:
return {'compression_ratio': 0.0}
try:
gt_words = len(gt_abstract.split())
generated_words = len(model_output['model_output'].split())
compression_ratio = min(gt_words, generated_words) / max(gt_words, generated_words)
return {'compression_ratio': float(compression_ratio)}
except Exception as e:
print(f"Error calculating compression ratio: {e}")
return {'compression_ratio': 0.0}

@weave.op
def coverage_scorer(gt_abstract: str, model_output: dict) -> Dict[str, float]:
"""Calculate content coverage using word overlap."""
if not model_output or 'model_output' not in model_output:
return {'coverage_score': 0.0}
try:
gt_words = set(gt_abstract.lower().split())
generated_words = set(model_output['model_output'].lower().split())
intersection = len(gt_words.intersection(generated_words))
union = len(gt_words.union(generated_words))
coverage_score = intersection / union if union > 0 else 0.0
return {'coverage_score': float(coverage_score)}
except Exception as e:
print(f"Error calculating coverage score: {e}")
return {'coverage_score': 0.0}

def create_evaluation_dataset(gt_file: str):
"""Create dataset from ground truth file."""
dataset = []
with open(gt_file, 'r') as f:
for line in f:
entry = json.loads(line)
dataset.append({
"title": entry["title"],
"gt_abstract": entry["summary"],
"pdf_path": entry["file_path"]
})
return dataset

async def run_evaluations(gt_file: str):
"""Run evaluations for each model."""
eval_dataset = create_evaluation_dataset(gt_file)
# Initialize models
models = {
"llama_8b": Llama8B(),
"llama_11b": Llama11B(),
"llama_1b": Llama1B()
}

# Setup scorers
scorers = [
claude_scorer,
rouge_scorer,
compression_scorer,
coverage_scorer,
bert_scorer
]
# Run evaluations
results = {}
for model_name, model in models.items():
print(f"\nEvaluating {model_name}...")
evaluation = weave.Evaluation(
dataset=eval_dataset,
scorers=scorers,
name=model_name + " Eval"
)
results[model_name] = await evaluation.evaluate(model)
# Print results
print("\nEvaluation Results:")
for model_name, result in results.items():
print(f"\n{model_name} Results:")
print(json.dumps(result, indent=2))
# Save results to file
output_file = "llama_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__":
gt_file = "paper_summaries.jsonl"
asyncio.run(run_evaluations(gt_file))
먼저 Claude로 미리 생성해 둔 정답 요약 데이터셋을 JSON 파일에서 로드합니다. 이 데이터셋에는 연구 논문 제목, 본문 파일 경로, 그리고 평가의 기준이 되는 수작업 요약이 포함되어 있습니다. 데이터셋을 불러오면 데이터 준비 작업을 반복하지 않고, 모델 출력 분석에 워크플로의 초점을 맞출 수 있습니다.
데이터셋을 로드한 뒤 스크립트는 각 항목을 처리하면서 연구 논문 본문을 여러 Llama 모델에 전달해 요약을 생성합니다. 그런 다음 다양한 평가 지표를 사용해 생성된 출력물을 정답 요약과 비교합니다.
다음과 같은 지표를 포함합니다:
  • 어휘 중복도를 측정하기 위한 ROUGE 점수,
  • 의미적 유사도를 측정하는 BERTScore,
  • 간결성을 평가하기 위한 압축 비율, 그리고
  • 보존된 정보의 양을 평가하는 커버리지 지표.
또한 Anthropic의 Claude를 활용한 채점 함수가 인간과 유사한 이해도와 원문과의 정합성을 기준으로 요약을 평가합니다.
출력 채점이 끝나면 Weave의 동적 대시보드를 사용해 결과를 시각화할 수 있습니다. 이 인터페이스는 모델과 예시 전반의 다양한 지표를 심층적으로 비교하도록 해 주어, 추세와 트레이드오프를 파악하고 궁극적으로 특정 요구 사항에 가장 적합한 모델을 합리적으로 선택할 수 있도록 돕습니다.
Amazon Bedrock의 다양한 모델과 W&B Weave의 벤치마크 기능을 결합하면, 이 워크플로는 요약 작업에서 LLM을 평가하기 위한 견고하고 확장 가능한 솔루션을 제공합니다:


결과에 따르면 Llama-8B는 Claude 점수, ROUGE-1, ROUGE-2, Coverage 점수를 포함한 여러 평가 지표에서 Llama-1B와 Llama-11B 모두보다 우수한 성능을 보였습니다. 이는 핵심 내용을 보존하면서 구조적 일관성을 유지하고, 정답 요약과 더 가깝게 맞추는 요약을 생성하는 능력을 입증합니다.
하지만 Llama-11B는 ROUGE-L, 압축 비율, BERTScore에서 Llama-8B보다 약간의 우위를 보입니다. 반면 Llama-1B는 모든 지표에서 Llama-8B와 Llama-11B 모두에 뒤처져, 최고 성능보다는 효율성에 최적화된 경량 모델로서의 위치를 확인시켜 줍니다.
전반적으로 Llama-8B와 Llama-11B는 이번 평가에서 매우 우수한 성능을 보여 주었으며, 핵심 지표에서 뛰어나면서도 지연 시간과 효율성 면에서 합리적인 균형을 유지합니다. Weave Evaluations의 장점 중 하나는 비교 보기 기능을 통해 각 모델의 정확한 응답을 더 깊이 파고들 수 있다는 점입니다. 예를 들어, 각 모델의 응답을 나란히 비교할 수 있을 뿐만 아니라, 하나의 UI에서 정답 요약과의 비교까지 함께 확인할 수 있습니다.
비교 보기의 스크린샷은 다음과 같습니다:


Llama 대 Amazon Nova

Amazon은 최근 다양한 요구를 충족하기 위한 AI 모델 제품군인 Nova 시리즈를 공개했습니다. Nova Micro는 속도와 경제성에 초점을 맞춰, 요약과 같은 단순한 작업에 적합하며 번역한편 Nova Lite는 텍스트, 이미지, 비디오를 포함한 멀티모달 입력을 처리하여 실시간 분석을 지원합니다. Nova Pro는 비용과 성능의 균형을 이루며, 복잡한 추론과 멀티모달 워크플로에서 뛰어납니다. 한편 2025년 초 출시 예정인 Nova Premier는 고급 기능으로 가장 복잡한 과제를 해결할 것으로 기대됩니다. 이제 이러한 Nova 모델들이 Llama와 어떻게 비교되는지 살펴보겠습니다.
이전에 사용한 스크립트와 유사한 평가 스크립트를 작성하여 Amazon Nova Pro를 기존 Llama 모델들과 비교 테스트하겠습니다. 참고로, 우리가 사용했던 기준은 Weave Evaluations따라서 Nova Pro 모델만 사용해 새로운 스크립트를 간단히 작성한 뒤, 나중에 Weave 대시보드에서 비교할 이전 평가 결과를 선택하면 됩니다. 코드는 다음과 같습니다:
import weave
from weave import Model
import json
import boto3
from botocore.exceptions import ClientError
from time import sleep
import asyncio
from rouge_score.rouge_scorer import RougeScorer
from typing import Dict, Any
import bert_score
import fitz
import os
from weave.trace.box import unbox
import time
import logging
logging.basicConfig(level=logging.DEBUG)
# Initialize Weave
weave.init('bedrock_abstract_eval')

client = boto3.client(service_name="bedrock-runtime", region_name="us-east-1")

def extract_paper_text(pdf_path: str) -> str:
"""Extract text from PDF paper."""
if isinstance(pdf_path, weave.trace.box.BoxedStr):
pdf_path = unbox(pdf_path)

text = ""
try:
with fitz.open(unbox(pdf_path)) as pdf:
for page in pdf:
text += page.get_text()
except Exception as e:
print(f"Error extracting text from PDF {pdf_path}: {e}")
return text

def format_prompt(text: str, title: str) -> str:
"""Format prompt for model."""
return f"""
<|begin_of_text|><|start_header_id|>user<|end_header_id|>
Please analyze this research paper and provide a comprehensive 300-word summary that covers:
- Primary research objective
- Methodology and approach
- Key findings and results
- Main contributions to the field

Title: {title}

Content: {text}

Please analyze this research paper and provide a comprehensive 300-word summary that covers:
- Primary research objective
- Methodology and approach
- Key findings and results
- Main contributions to the field

Generate a clear, coherent summary that captures the essence of the research.
<|eot_id|>
<|start_header_id|>assistant<|end_header_id|>
"""


def model_forward_nova(title: str, pdf_path: str) -> str:
"""Core prediction logic for Amazon Nova Pro."""
max_retries = 15
backoff_time = 10 # Start with 10 seconds delay

for attempt in range(max_retries):
try:
paper_text = extract_paper_text(pdf_path)
messages = [
{"role": "user", "content": [{"text": format_prompt(paper_text, title)}]},
]

# Make prediction
print(f"Invoking Nova Pro model (Attempt {attempt + 1}/{max_retries})...")
response = client.converse(
modelId="us.amazon.nova-pro-v1:0",
messages=messages
)
prediction = response["output"]["message"]["content"][0]["text"].strip()
print("Done invoking")
return prediction

except Exception as e:
print(f"Error generating prediction with Nova Pro (Attempt {attempt + 1}/{max_retries}): {e}")
if attempt < max_retries - 1: # Avoid sleeping on the last attempt
time.sleep(backoff_time)
backoff_time *= 2 # Exponential backoff

print(f"Failed to generate prediction after {max_retries} retries.")
return ""

class NovaPro(Model):
"""Amazon Nova Pro model."""
@weave.op
def predict(self, title: str, pdf_path: str) -> dict:
"""Generate prediction using Amazon Nova Pro."""
prediction = model_forward_nova(title, pdf_path)
return {"model_output": prediction}

@weave.op
def bert_scorer(gt_abstract: str, model_output: dict) -> Dict[str, float]:
"""Calculate BERTScore for the abstract."""
if not model_output or 'model_output' not in model_output:
return {'bert_score': 0.0}
try:
P, R, F1 = bert_score.score(
[model_output['model_output']],
[gt_abstract],
lang='en',
model_type='microsoft/deberta-xlarge-mnli'
)
return {'bert_score': float(F1.mean())}
except Exception as e:
print(f"Error calculating BERTScore: {e}")
return {'bert_score': 0.0}

@weave.op
def rouge_scorer(gt_abstract: str, model_output: dict) -> Dict[str, float]:
"""Calculate ROUGE scores for the abstract."""
if not model_output or 'model_output' not in model_output:
return {
'rouge1_f': 0.0,
'rouge2_f': 0.0,
'rougeL_f': 0.0
}
try:
scorer = RougeScorer(['rouge1', 'rouge2', 'rougeL'], use_stemmer=True)
scores = scorer.score(gt_abstract, model_output['model_output'])
return {
'rouge1_f': float(scores['rouge1'].fmeasure),
'rouge2_f': float(scores['rouge2'].fmeasure),
'rougeL_f': float(scores['rougeL'].fmeasure)
}
except Exception as e:
print(f"Error calculating ROUGE scores: {e}")
return {
'rouge1_f': 0.0,
'rouge2_f': 0.0,
'rougeL_f': 0.0
}



@weave.op
def compression_scorer(gt_abstract: str, model_output: dict) -> Dict[str, float]:
"""Calculate compression ratio of the abstract."""
if not model_output or 'model_output' not in model_output:
return {'compression_ratio': 0.0}
try:
gt_words = len(gt_abstract.split())
generated_words = len(model_output['model_output'].split())
compression_ratio = min(gt_words, generated_words) / max(gt_words, generated_words)
return {'compression_ratio': float(compression_ratio)}
except Exception as e:
print(f"Error calculating compression ratio: {e}")
return {'compression_ratio': 0.0}

@weave.op
def coverage_scorer(gt_abstract: str, model_output: dict) -> Dict[str, float]:
"""Calculate content coverage using word overlap."""
if not model_output or 'model_output' not in model_output:
return {'coverage_score': 0.0}
try:
gt_words = set(gt_abstract.lower().split())
generated_words = set(model_output['model_output'].lower().split())
intersection = len(gt_words.intersection(generated_words))
union = len(gt_words.union(generated_words))
coverage_score = intersection / union if union > 0 else 0.0
return {'coverage_score': float(coverage_score)}
except Exception as e:
print(f"Error calculating coverage score: {e}")
return {'coverage_score': 0.0}


@weave.op
def claude_scorer(gt_abstract: str, model_output: dict) -> dict:
"""Evaluate abstract using Claude."""
if not model_output or 'model_output' not in model_output:
return {'claude_score': 0.0}
print("claude evaluating")
# client = boto3.client("bedrock-runtime", region_name="us-east-1")
prompt = json.dumps({
"anthropic_version": "bedrock-2023-05-31",
"max_tokens": 1024,
"temperature": 0.0,
"messages": [{
"role": "user",
"content": [
{"type": "text", "text": f'''Rate how well this generated abstract captures the key information from the ground truth abstract on a scale from 1-5, where:
1: Poor - Missing most key information or seriously misrepresenting the research
2: Fair - Captures some information but misses crucial elements
3: Good - Captures most key points but has some gaps or inaccuracies
4: Very Good - Accurately captures nearly all key information with minor omissions
5: Excellent - Perfectly captures all key information and maintains accuracy

Ground Truth Abstract:
{gt_abstract}

Generated Abstract:
{model_output["model_output"]}

Provide your rating as a JSON object with this schema:
{{"score": <integer 1-5>}}'''}
]
}]
})
try:
response = client.invoke_model(
modelId="anthropic.claude-3-sonnet-20240229-v1:0",
body=prompt
)
result = json.loads(response["body"].read())
score = json.loads(result["content"][0]["text"])["score"]
print(score)
sleep(2) # Rate limiting
return {'claude_score': float(score)}
except Exception as e:
print(f"Error in Claude evaluation: {e}")
return {'claude_score': 0.0}


def create_evaluation_dataset(gt_file: str):
"""Create dataset from ground truth file."""
dataset = []
with open(gt_file, 'r') as f:
for line in f:
entry = json.loads(line)
dataset.append({
"title": entry["title"],
"gt_abstract": entry["summary"],
"pdf_path": entry["file_path"]
})
return dataset



async def run_evaluations(gt_file: str):
"""Run evaluations for each model."""
eval_dataset = create_evaluation_dataset(gt_file)
# Initialize models
models = {
"nova_pro": NovaPro(),
}

# Setup scorers
scorers = [
claude_scorer,
rouge_scorer,
compression_scorer,
coverage_scorer,
bert_scorer
]
# Run evaluations
results = {}
for model_name, model in models.items():
print(f"\nEvaluating {model_name}...")
evaluation = weave.Evaluation(
dataset=eval_dataset,
scorers=scorers,
name=model_name + " Eval"
)
results[model_name] = await evaluation.evaluate(model)
# Print results
print("\nEvaluation Results:")
for model_name, result in results.items():
print(f"\n{model_name} Results:")
print(json.dumps(result, indent=2))
# Save results to file
output_file = "llama_vs_nova_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__":
gt_file = "paper_summaries.jsonl"
asyncio.run(run_evaluations(gt_file))
Weave Evaluations 비교 대시보드에서 확인한 결과는 다음과 같습니다:


결과를 보면 Nova Pro가 여러 평가 지표에서 두 가지 Llama 기반 모델보다 전반적으로 우수한 성능을 보입니다. Nova Pro의 Claude 점수는 4.8로 Llama-8B(4.6)와 Llama-11B(4.4)를 앞서며, Claude 평가 기준에 비추어 사람과 유사한 판단에 더 가깝게 요약을 수행한다는 것을 시사합니다. 또한 ROUGE 지표에서도 뛰어나 ROUGE-1 점수 0.6308, ROUGE-2 점수 0.3038, ROUGE-L 점수 0.3543을 기록해 두 Llama 모델보다 모두 높았습니다.
Nova Pro의 압축률(0.8198)은 Llama-8B(0.822)와 Llama-11B(0.8233)보다 약간 낮지만, 커버리지 점수 0.31로 Llama-8B의 0.2819와 Llama-11B의 0.2791을 넘어서는 강한 콘텐츠 보존력으로 이를 보완합니다. 또한 Nova Pro는 BERTScore 0.7252로 최고 점수를 기록해 기준 요약과의 의미적 유사성이 가장 높음을 보여줍니다.
ROUGE와 BERTScore와 같은 지표가 어휘적·의미적 중복을 포착하긴 하지만, 가독성, 일관성, 전반적인 사용자 만족도를 충분히 측정하지는 못한다는 점을 유의해야 합니다. Claude 점수는 보다 인간에 맞춰진 기준을 제공하여, Nova Pro가 구조화가 잘 되고 일관적이며 매력적인 요약을 생성하는 능력을 부각하는 데 도움을 줍니다.
전반적으로 Nova Pro는 여러 지표에서 강력한 성능을 보였으며, 인간 평가자와의 높은 정렬까지 갖춰 기술적 정확성과 인간 중심의 품질을 모두 중시하는 작업에 매우 설득력 있는 선택지입니다.

왜 Amazon Bedrock을 선택할까?

Amazon Bedrock은 다양한 파운데이션 모델을 AWS 에코시스템과 매끄럽게 통합해 생성형 AI에 강력한 플랫폼을 제공합니다. Anthropic, Meta, AI21 Labs, Mistral, 그리고 Amazon의 Titan과 같은 선도 제공자의 모델에 접근할 수 있어, 텍스트 요약, 임베딩 생성, 멀티모달 처리와 같은 작업에 맞춰 모델 선택을 조정할 수 있습니다. AWS 서비스와의 연계를 통해 Bedrock은 워크플로를 단순화하고, 신뢰할 수 있는 인프라를 활용하며, 기반 시스템을 직접 관리하는 복잡성 없이 확장 가능한 배포를 가능하게 합니다.
확장성, 보안, 신뢰성을 중점으로 하는 Bedrock은 고급 암호화와 업계 표준 준수를 통해 데이터 보호를 유지하면서, 증가하는 수요에 맞춰 손쉽게 리소스를 조정할 수 있도록 보장합니다. AWS의 견고한 글로벌 인프라를 기반으로 일관된 성능과 가용성을 제공하여, 안정적이고 유연한 AI 플랫폼을 찾는 조직에 이상적인 솔루션이 됩니다.

결론

Amazon Bedrock과 W&B Weave는 텍스트 요약과 같은 다양한 사용 사례에서 대규모 언어 모델의 성능을 평가하고 비교하기에 매우 설득력 있는 조합입니다. Bedrock의 방대한 파운데이션 모델 라인업과 AWS 에코시스템과의 통합은 강력한 AI 솔루션을 찾는 기업에 유연성과 확장성을 제공합니다.
Weave의 정교한 벤치마킹과 시각화 기능을 활용하면, 조직은 모델 간 트레이드오프를 체계적으로 분석하여 단순한 수치 지표를 넘어 실질적인 강점과 한계를 파악할 수 있습니다. AI 기술이 계속 발전함에 따라 Bedrock과 Weave 같은 플랫폼은 기업이 생성형 AI의 힘을 효과적으로 활용하도록 길을 열어 주며, 데이터 중심의 환경에서 경쟁력을 유지하도록 돕습니다.

관련 문서



이 글은 AI로 번역되었습니다. 오역이 있을 경우 댓글로 알려 주세요. 원문 링크는 다음과 같습니다: 원문 보고서 보기