huny.log

기술 포스트 · AI·LLM

LLM으로 광고 카피 100개 + CTR 예측까지 — 운영자용 4단계 워크플로우

카피라이터 한 명이 하루에 30개 만들 시간에, LLM은 100개를 5분에 만듭니다. 그런데 그 100개를 그대로 광고에 태우면 안 돼요. 임베딩으로 중복 제거하고 과거 CTR로 사전 스코어링하는 운영 파이프라인.

“이번 캠페인 카피 30개만 더 뽑아주세요” — 마케터의 단골 주문이었던 이 한 줄이, GPT/Claude 등장 이후 의미가 달라졌어요. 이제 100개도 5분이면 나옵니다. 그런데 정작 광고 매니저에 100개를 다 태우면 학습 분산이 깨지고, 비슷한 카피끼리 서로 잠식해서 결과가 망가져요. 이 글은 LLM으로 양산한 카피를 중복 제거 → 사전 스코어링 → A/B 후보 선별까지 가는 운영자용 4단계 파이프라인입니다.

마케터가 이 글을 읽어야 하는 이유: “LLM으로 카피 뽑기”는 이미 많이들 합니다. 뽑은 다음 어떻게 고르고 어떻게 태우는지를 아는 팀이 실제로 광고 효율을 올립니다. 이 파이프라인을 한 번 세팅해두면 다음 캠페인부터 카피 선별에 드는 시간이 반 이상 줄고, 광고 학습 분배 효율도 높아집니다.

LLM 광고 카피 파이프라인 — 양산, 임베딩 클러스터링, CTR 예측, A/B 선별 4단계
100개를 그대로 태우면 망한다. 클러스터링으로 5~7개 컨셉만 살리고, 과거 CTR로 사전 스코어링한 뒤 상위 20개만 광고에 올린다.

0단계: 왜 100개를 그대로 태우면 안 되나

처음 LLM으로 카피를 100개 뽑아 광고에 태웠을 때의 패턴 — 익숙한 사람 많을 거예요.

  1. 학습 데이터 분산: Meta·Google 광고 매니저는 카피별로 노출을 학습 분배합니다. 100개면 한 개당 노출이 100분의 1로 쪼개져, 통계적 유의에 도달하기 전에 캠페인이 끝남.
  2. 카니발리제이션: 비슷한 카피 5개가 같은 입찰 풀에서 서로 잠식. CPC만 올라가고 CTR은 안 오름.
  3. 품질 편차: LLM이 100개 중 70개는 평이하고 10개는 이상하고 20개만 쓸 만한데, 그걸 사람이 한 번도 안 거른 채 태움.
  4. 브랜드 톤 이탈: “재밌는 카피” 100개 중에 브랜드 가이드를 어기는 게 섞여 있음.

해결은 중간에 필터를 두는 것입니다. 4단계 파이프라인:

1단계: LLM 카피 양산 — 좋은 프롬프트 패턴

핵심은 페르소나 + 제약 + 다양성 강제예요. “광고 카피 100개 줘” 같은 빈약한 프롬프트는 비슷한 100개를 만들어냅니다.

[페르소나]
당신은 30대 워킹맘을 타겟으로 하는 유아용품 브랜드의 카피라이터입니다.
브랜드 톤은 "안심·따뜻함·과장 금지"입니다.
[제품 정보]
- 제품: 무염 유아 간식 X
- USP: 6개월부터 먹일 수 있는 유일한 무염 라이스 스낵
- 가격: 한 박스 12,900원
[제약]
- 길이: Meta 광고 primary text 기준 한 줄 (최대 90자)
- 의문문 금지, 과장된 형용사("최고의", "유일한") 금지
- 가격 명시 금지 (정책)
- 같은 첫 단어로 시작하는 카피가 5개를 넘지 말 것
[요청]
다음 5가지 다른 각도로 각각 4개씩, 총 20개의 카피를 만들어주세요.
1. "안심" 프레임 (성분, 만든 사람)
2. "일상" 프레임 (먹이는 장면)
3. "후기" 프레임 (구매 고객의 목소리)
4. "비교" 프레임 (기존 간식과의 차이)
5. "FOMO" 프레임 (한정·시즌)
각 카피는 번호 + 카피 본문만 출력. 설명 금지.

이렇게 프레임을 5개로 강제하면 LLM이 자연스럽게 다양성을 만들어냅니다. 한 번에 100개를 요청하지 말고 20개씩 5번 돌리면 더 안정적이에요.

운영 단계에서 한 가지 더 — 프롬프트 캐싱(Anthropic의 cache_control, OpenAI의 자동 캐시)을 걸면 페르소나·제약 부분이 재사용되어 토큰 비용이 70~80% 절감됩니다. 하루 수백 캠페인 돌리는 팀에는 필수.

2단계: 임베딩으로 중복 제거하기

100개 중에 사실 의미가 비슷한 카피가 30~40개씩 묶여 있어요. 사람 눈으로 골라내는 건 비효율이고 일관성도 떨어집니다. 임베딩 + cosine similarity로 자동화합니다.

핵심 수식은 두 카피의 의미적 거리:

거리 0이면 두 카피 의미가 거의 같음, 1이면 완전히 다른 컨셉. 거리 0.2 이하면 같은 컨셉으로 묶고 대표 1개만 살린다는 룰만 정하면 끝.

100개 카피를 임베딩한 뒤 Agglomerative Clustering(거리 0.2 임계)을 돌리면 보통 다음과 같은 결과가 나와요.

클러스터카피 수대표 카피
015개”엄마가 먼저 먹어보고 골랐어요”
122개”6개월부터 시작하는 첫 간식”
218개”쌀로만 만든 진짜 무염”
311개”이유식 졸업 후 어떡할지 고민이라면”
414개”할머니가 좋아하시는 간식”
512개”성분표를 자세히 보세요”
68개”재구매율 높은 비밀”

100개 → 7개 컨셉으로 압축. 다음 단계는 이 7개에 점수를 매기는 일.

3단계: 과거 CTR 데이터로 사전 스코어링

컨셉 7개가 나왔으면, 이제 어느 컨셉이 잘 먹힐지 광고로 태우기 전에 점수를 매겨봅니다. 과거 캠페인 데이터가 있으면 가능해요.

핵심 아이디어: 과거 카피들의 임베딩그 카피들의 실제 CTR을 가지고 회귀 또는 nearest-neighbor 모델을 학습. 새 카피의 임베딩을 넣으면 예상 CTR을 출력.

가장 단순한 방법은 k-NN(가장 가까운 이웃) 예측이에요.

새 카피의 임베딩과 가장 가까웠던 과거 카피 개(보통 5)의 실제 CTR 평균을 그대로 예측값으로 씁니다. 해석이 쉬워서 좋아요. “이 카피는 작년 X 캠페인의 카피 5개와 가장 비슷한데, 그것들 평균 CTR이 1.8이었으니 이번에도 비슷할 것”이라고 회의에서 설명할 수 있죠. 데이터가 800개 이상이면 Gradient Boosting Regressor로 한 단계 정밀도 높일 수 있고요.

7개 컨셉 카피의 예측 결과는 다음처럼 나옵니다.

컨셉예측 CTR (%)순위
쌀로만 만든 진짜 무염2.411
엄마가 먼저 먹어보고 골랐어요2.182
재구매율 높은 비밀2.053
6개월부터 시작하는 첫 간식1.944
할머니가 좋아하시는 간식1.325
이유식 졸업 후 어떡할지 고민이라면1.186
성분표를 자세히 보세요0.947

4단계: A/B 후보 선별

7개 컨셉 × 예상 CTR이 나왔으면, 어떻게 광고에 태울까. 실무 가이드:

시나리오후보 수분배 전략
주간 예산 100만 원 미만3~5개상위 점수 컨셉 위주, 컨셉당 1개
주간 100~500만 원7~10개상위 5컨셉 × 2개 변형, 하위 1~2개 챌린저
주간 500만 원 이상10~15개모든 컨셉 + 변형, 자동 학습 분배

최종 선별 룰 한 줄: 상위 점수 5개 + 하위 점수에서 무작위 1개 = 총 6개를 광고 매니저에 업로드. 컨셉당 변형 12개를 더 만들어 1012개로 확장하면 학습 분배가 안정적이에요.

5단계: 4단계 파이프라인 한 번에 실행하기 (E2E)

각 단계 코드를 하나로 연결해서 실제로 돌릴 수 있는 흐름을 보여줍니다. 실제 환경에서는 각 함수를 라이브러리로 분리하지만, 전체 흐름을 파악하는 데 의사코드로도 충분합니다.

ad_copy_pipeline.py
import openai
import numpy as np
from sklearn.cluster import AgglomerativeClustering
from sklearn.neighbors import KNeighborsRegressor
client = openai.OpenAI()
# 1단계: 카피 양산
def generate_copies(brief: str, n: int = 100) -> list[str]:
resp = client.chat.completions.create(
model="gpt-4o", messages=[{"role": "user", "content": brief}]
)
lines = [l.split(". ", 1)[-1].strip() for l in resp.choices[0].message.content.splitlines() if l.strip()]
return lines[:n]
# 공통: 임베딩
def embed(texts: list[str]) -> np.ndarray:
res = client.embeddings.create(model="text-embedding-3-small", input=texts)
return np.array([e.embedding for e in res.data])
# 2단계: 중복 제거
def deduplicate(copies: list[str], threshold: float = 0.25) -> list[str]:
vecs = embed(copies)
norms = np.linalg.norm(vecs, axis=1, keepdims=True)
dist = 1 - (vecs / norms) @ (vecs / norms).T
labels = AgglomerativeClustering(
n_clusters=None, distance_threshold=threshold,
metric="precomputed", linkage="complete"
).fit_predict(dist)
seen, reps = set(), []
for label, copy in zip(labels, copies):
if label not in seen:
seen.add(label); reps.append(copy)
return reps # 보통 7~12개로 압축
# 3단계: k-NN CTR 예측
def score_copies(candidates, past_copies, past_ctrs, k=5):
knn = KNeighborsRegressor(n_neighbors=k, metric="cosine")
knn.fit(embed(past_copies), past_ctrs)
preds = knn.predict(embed(candidates))
return sorted(zip(candidates, preds), key=lambda x: -x[1])
# 4단계: 후보 선별
def select_candidates(scored, top_n=5):
return [c for c, _ in scored[:top_n]] + [scored[-1][0]] # 상위 + 챌린저 1
# E2E 실행
if __name__ == "__main__":
BRIEF = "..." # 1단계 프롬프트
PAST_COPIES = ["엄마가 직접 고른 간식", "아이 건강 먼저"]
PAST_CTRS = [2.1, 1.8]
copies = generate_copies(BRIEF, n=100)
unique = deduplicate(copies)
scored = score_copies(unique, PAST_COPIES, PAST_CTRS)
final = select_candidates(scored)
print(f"생성 {len(copies)}개 → 압축 {len(unique)}개 → 최종 {len(final)}개")
for c, ctr in scored[:len(final)]:
print(f" {ctr:.2f}% {c}")
실행 결과 예시
생성 100개 → 압축 7개 → 최종 6개
2.41% 쌀로만 만든 진짜 무염
2.18% 엄마가 먼저 먹어보고 골랐어요
2.05% 재구매율 높은 비밀
1.94% 6개월부터 시작하는 첫 간식
1.32% 할머니가 좋아하시는 간식
0.94% 성분표를 자세히 보세요 ← 챌린저

해석: 100개 → 7개 컨셉 → 예측 CTR 순위 → 상위 5개 + 챌린저 1개 = 6개만 광고 매니저에 올립니다. 나머지 94개는 “확인 완료 후 제외”된 것이지, 버린 게 아니에요. 다음 캠페인에서 성분표 프레임 카피를 다시 시도할 때 이 데이터가 baseline이 됩니다.

6단계: 운영 팁 — 실무에서 부딪히는 것들

1) 임베딩 모델 선택

OpenAI text-embedding-3-small이 가성비 좋고, 한국어도 충분히 잘 합니다. 더 좋은 한국어 성능이 필요하면 BGE-M3 (오픈소스) 같은 multilingual 모델 시도해볼 만해요.

2) 과거 데이터가 없을 때

신규 브랜드라 과거 캠페인이 없으면, 3단계는 건너뛰고 12단계 + 4단계의 편의적 분배(컨셉당 12개)만 적용. 첫 한 달 데이터 쌓이면 그때부터 학습.

3) 사람 검수는 빼지 마라

자동화의 마지막 안전장치는 사람 눈입니다. 정책 위반(가격 명시, 비교 광고), 브랜드 톤 이탈은 모델이 못 거르는 게 많아요. 광고 업로드 전에 1분만 훑기 룰 권장.

4) 카피의 다국어 변환은 따로

같은 캠페인을 영어·일본어로 확장할 때, LLM에 “한국어 카피를 영어로 번역해줘”는 잘 안 됩니다. 처음부터 각 언어의 페르소나·제약으로 다시 양산하세요. 번역은 의미는 옮기지만 광고 톤은 못 옮겨요.

마치며

LLM은 카피 작가의 일을 뺏는 도구가 아니라 카피 작가의 손을 100배로 늘리는 도구입니다. 양산은 LLM이 하지만, 컨셉 정의·페르소나 설계·최종 검수는 여전히 사람의 일이에요. 이 4단계 파이프라인을 한 번 만들어두면, 다음 캠페인부터는 카피라이터가 컨셉 5개만 적어줘도 광고에 태울 후보 10~15개가 자동으로 나옵니다.

다음에 읽으면 좋은 글:

참고

AI·LLM 카테고리의 다른 글

전체 보기 →