본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 05. 24. 07:05

브라우저에서 완전히 실행되는 텍스트-이미지 검색 엔진을 구축했습니다

요약

OpenAI의 CLIP 모델을 활용하여 서버나 API 없이 브라우저 환경에서만 작동하는 텍스트-이미지 검색 엔진 구축 방법을 설명합니다. 텍스트와 이미지를 동일한 벡터 공간에 배치하여 코사인 유사도로 검색하는 원리를 다룹니다.

핵심 포인트

  • CLIP 모델을 이용한 텍스트-이미지 벡터 공간 통합
  • 서버와 API 없이 브라우저 탭 내에서 완전한 파이프라인 실행
  • 코사인 유사도를 활용한 효율적인 이미지 검색 메커니즘
  • 시각적 검색, 제로샷 분류 등 다양한 AI 응용 분야의 핵심 원리

"잔디 위의 코기"라고 입력해 보세요. 갤러리에 있는 24장의 사진 중 코기 사진이 가장 상단에 올라옵니다. 점수: 0.31. "먹을 것"이라고 입력해 보세요. 딸기 한 그릇, 파스타 한 접시, 그리고 화덕 피자가 메달 순위를 차지합니다. 점수 범위: 0.25 – 0.27. 서버도 없습니다. API 키도 없습니다. 이미지가 어디에도 업로드되지 않았습니다. 150MB의 신경망 (Neural Network)과 24개의 이미지 임베딩 (Embeddings)을 포함한 전체 파이프라인 (Pipeline)이 브라우저의 탭 하나에 살아 있습니다. 이것은 현대 컴퓨터 비전 (Computer Vision)의 거대한 부분을 조용히 뒷받침하고 있는 모델인 CLIP 입니다. 그리고 2026년에는 Vercel에서 무료로 배포할 수 있습니다.

CLIP의 개념
OpenAI는 2021년에 하나의 아름답고 단순한 아이디어로 CLIP을 출시했습니다: 하나의 모델을 훈련시켜 텍스트와 이미지를 동일한 벡터 공간 (Vector Space)에 배치하는 것입니다. 그게 전부입니다. 그것이 핵심 비결입니다. CLIP에는 두 개의 인코더 (Encoder)가 있습니다. 텍스트 인코더 (Text Encoder)는 "코기 강아지"를 512차원 벡터로 변환합니다. 비전 인코더 (Vision Encoder)는 코기 사진을 512차원 벡터로 변환합니다. 이들은 웹에서 수집한 4억 개의 (캡션, 이미지) 쌍을 통해 훈련되었으며, 이를 통해 쌍을 이룬 텍스트와 이미지가 해당 벡터 공간 내에서 서로 가까운 곳에 위치하게 됩니다. 일단 이것이 실현되면, 데이터베이스가 작동하는 방식처럼 검색을 수행할 수 있습니다. "코기 강아지"의 벡터와 실제 코기 사진의 벡터 사이의 거리는 가깝습니다. "코기 강아지"와 우주비행사 사진 사이의 거리는 멉니다. 거리에 따라 정렬하면 끝입니다.

"코기 강아지" ─▶ 텍스트 인코더 ─▶ [0.04, -0.12, 0.07, ...] ─┐
├─▶ 코사인 유사도 (Cosine Similarity) [이미지 바이트] ─▶ 비전 인코더 ─▶ [0.05, -0.10, 0.08, ...] ─┘

마지막 수학 연산은 두 개의 for 루프와 곱셈뿐입니다:

function cosineSim ( a : Float32Array , b : Float32Array ) {
let dot = 0
for ( let i = 0 ; i < a . length ; i ++ ) dot += a [ i ] * b [ i ]
return dot
}

이것이 전부입니다. 이것이 검색 엔진입니다.

이것이 중요한 이유
"모든 것을 동일한 벡터 공간에 임베딩하고 내적 (Dot Product)으로 비교한다"는 개념을 이해한다면, 여러분은 다음의 핵심을 이해하는 것입니다:
Pinterest의 시각적 검색 (Visual Search). "이와 비슷한 것을 더 찾아줘."
Stable Diffusion의 텍스트 컨디셔닝 (Text Conditioning).

"이 프롬프트를 생성해줘(Generate this prompt)"는 "모델이 생성하도록 학습된 벡터 공간의 영역을 찾아라"라는 의미입니다. 데이터셋 중복 제거 (Dataset deduplication). "이 5천만 개의 이미지 중 어떤 것이 유사한 중복 이미지인가?" 벡터 거리 (Vector distance)를 기준으로 클러스터링합니다. 제로샷 분류 (Zero-shot classification). "이것은 고양이인가, 개인가, 아니면 염소인가?" 세 가지 레이블을 인코딩하고, 이미지를 인코딩한 뒤 가장 가까운 것을 선택합니다. 대규모 콘텐츠 모더레이션 (Content moderation at scale). "이 이미지가 우리가 레이블을 지정한 정책 위반 사항과 의미론적으로 유사한가?" 지난 5년 동안 이 모든 것들은 어떤 기업들에게는 수백만 달러 규모의 엔지니어링 문제였습니다. 핵심적인 트릭은 우리가 이제부터 200줄의 TypeScript로 구축할 내용입니다.

1단계: 브라우저에 CLIP 로드하기
과거에는 GPU를 갖춘 Python 서버가 필요했던 작업이 이제는 브라우저 탭에서 실행됩니다. 이 마법을 수행하는 라이브러리는 Transformers.js입니다. 이는 Hugging Face의 Python transformers 라이브러리를 JavaScript의 ONNX Runtime으로 포팅한 것입니다.

import { AutoTokenizer, AutoProcessor, CLIPTextModelWithProjection, CLIPVisionModelWithProjection } from '@xenova/transformers'

const MODEL_ID = 'Xenova/clip-vit-base-patch32'

const [tokenizer, processor, textModel, visionModel] = await Promise.all([
  AutoTokenizer.from_pretrained(MODEL_ID),
  AutoProcessor.from_pretrained(MODEL_ID),
  CLIPTextModelWithProjection.from_pretrained(MODEL_ID),
  CLIPVisionModelWithProjection.from_pretrained(MODEL_ID)
])

첫 방문 시: 약 150MB의 ONNX 가중치 (weights)가 Hugging Face CDN에서 브라우저의 Cache API로 스트리밍됩니다. 그 이후의 모든 방문 시: 수백 밀리초(ms) 내에 완료됩니다.

2단계: 구절 인코딩하기

async function encodeText(text: string) {
  const inputs = tokenizer(text, { padding: true, truncation: true })
  const { text_embeds } = await textModel(inputs)
  return l2normalise(text_embeds.data) // 512-d Float32Array
}

text_embeds 필드는 투영된 벡터 (projected vector)로, 공유 공간 (shared space)에 존재하는 벡터입니다. 투영되지 않은 은닉 상태 (un-projected hidden state)는 비교 대상으로 적절하지 않은 벡터입니다. 우리는 코사인 유사도 (cosine similarity)가 내적 (dot product)으로 단순화될 수 있도록 L2 정규화 (L2-normalise, 길이를 나누는 작업)를 수행합니다.

이것은 튜토리얼에서 아무도 설명해주지 않지만 매우 중요한 작은 디테일입니다. 정규화 (normalisation)가 없다면, 랭킹(ranking) 기준은 "어떤 이미지가 검색어와 일치하는가"가 아니라 "어떤 이미지가 밝은가"가 되어버립니다.

단계 3: 이미지 인코딩 (encode an image)

async function encodeImage ( url : string ) {
  const image = await RawImage . read ( url )
  const inputs = await processor ( image )
  const { image_embeds } = await visionModel ( inputs )
  return l2normalise ( image_embeds . data )
}

프로세서 (processor)는 CLIP의 특정 평균/표준편차 (mean/std) 값을 사용하여 리사이즈 (resize) → 중앙 크롭 (centre-crop) → 정규화 (normalisation) 과정을 처리합니다. 비전 모델 (vision model)은 Vision Transformer입니다. 이 모델은 224×224 이미지를 32×32 px 크기의 7×7 패치 (patches)로 자르고, 각 패치를 토큰 (token)으로 취급하며, 이를 트랜스포머 (transformer) (네, GPT와 동일한 아키텍처입니다)에 통과시킨 뒤 [CLS] 토큰을 512차원 (512-d)으로 투영 (project)합니다. 동일한 512차원입니다. 텍스트와 동일한 공간입니다. 이것이 마법의 전부입니다.

단계 4: 랭킹 (rank)

const queryVec = await encodeText ( " a corgi on grass " )
const imageVecs = await Promise . all ( images . map ( img => encodeImage ( img . url ))) 
const ranked = imageVecs . map (( v , i ) => ({ image : images [ i ], score : cosineSim ( queryVec , v ) })) 
  . sort (( a , b ) => b . score - a . score )

이것이 검색 엔진입니다. 단 여덟 줄입니다.

단계 5: 캐싱 (cache)하여 속도 유지하기

모델 가중치 (model weights)는 Cache API에 자동으로 캐싱됩니다. 하지만 방문할 때마다 24개의 이미지를 다시 인코딩하는 것은 벡터 (vectors)가 변하지 않음에도 불구하고 아무 이유 없이 약 5초간의 WASM 연산을 수행하는 것과 같습니다. 이미지 ID + 모델 ID를 키로 하여 IndexedDB에 저장해 두세요.

const STORE = ' embeddings '

async function putCached ( modelId : string , imageId : string , vec : Float32Array ) {
  const db = await openDb ()
  const tx = db . transaction ( STORE , ' readwrite ' )
  tx . objectStore ( STORE ). put ( vec . buffer , ` ${ modelId } :: ${ imageId } ` )
}

전체 갤러리를 합쳐도 48 KB입니다. 이제 웜 리로드 (warm reloads)가 즉각적으로 느껴집니다.

이것이 변화시킨 것들

5년 전, "텍스트-이미지 검색 (text-to-image search)"은 논문 속의 이야기였습니다. 2년 전에는 GPU와 SDK를 갖춘 Python 서버였습니다. 오늘날에는 Vercel 배포 한 번으로 가능합니다.

"진정한 AI 엔지니어링"과 "주말 프로젝트" 사이의 경계는 계속해서 이동하고 있습니다. 모델이 작아졌기 때문이 아닙니다 — CLIP은 여전히 150 MB입니다. 브라우저가 더 커졌기 때문입니다. WebAssembly, ONNX Runtime Web, IndexedDB, Cache API. 런타임 스택이 기존의 Python 서비스가 하던 모든 일을 집어삼켰습니다. 만약 이 글을 읽으며 "AI는 너무 어려워, 난 절대 저런 걸 못 만들 거야"라고 생각하는 초보자라면: 당신은 방금 그 전체 과정을 다 읽은 것입니다. 코드는 GitHub에 있으며, 모든 커밋은 하나의 개념을 단계별로 안내합니다. 또한 clip-from-zero.vercel.app 에서 라이브 데모를 확인할 수 있습니다. 클론(Clone)하세요. clip.ts를 여세요. 네 개의 함수를 읽으세요. 그것이 바로 CLIP입니다. 다음에 누군가가 "임베딩 (embeddings)", "벡터 검색 (vector search)", "RAG", 또는 "멀티모달 (multimodal)"에 대해 이야기하는 것을 듣게 된다면 — 그들이 무엇을 의미하는지 알게 될 것입니다. 512차원 공간의 숫자들. 코사인 유사도 (Cosine similarity). 내적 (Dot product). 그게 전부입니다.

🔗 코드: github.com/dev48v/clip-from-zero
🌐 라이브 데모: clip-from-zero.vercel.app
📚 시리즈: TechFromZero — 매일 새로운 기술을, 모두 무료로, 모두 오픈 소스로 제공합니다.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0