정규 표현식(Regex)과의 사투를 멈추고 데이터 추출에 AI를 사용하기 시작한 방법
요약
비정형 제품 데이터를 정규 표현식으로 추출하려다 실패한 경험을 바탕으로, LLM을 활용한 데이터 정규화 방법을 소개합니다. 모델 미세 조정 대신 시스템 프롬프트와 일관된 출력 형식을 사용하여 효율적으로 JSON 데이터를 추출하는 실무적 접근법을 다룹니다.
핵심 포인트
- 정규 표현식은 복잡한 비정형 데이터 처리에 한계가 있음
- 모델 미세 조정 없이 프롬프트 엔지니어링만으로 데이터 추출 가능
- GPT-4o-mini와 같은 경량 모델로도 높은 JSON 추출 성능 확보 가능
- 마크다운 펜스 파싱 및 JSON 검증 로직 구현이 필수적임
지난달, 나는 정규 표현식 (Regular Expressions)과 싸우며 사흘을 보냈다.
다양한 공급업체로부터 받은 비정형 제품 설명 데이터가 쌓여 있었다. 어떤 것은 문단 속에 가격이 숨겨져 있었고, 어떤 것은 사양 (Specs)이 불렛 포인트 곳곳에 흩어져 있었다. 나의 임무는 이를 { name, price, specs, description } 형태의 깔끔한 JSON 구조로 정규화 (Normalize)하는 것이었다.
처음에는 간단했다. 몇 가지 정규 표현식 패턴을 사용했다. 가격을 위해 \$\d+\.\d{2}를 사용했고, 브랜드를 위해 (?<=Brand:)\w+를 사용했다. 그러다 예외 케이스 (Edge cases)들이 화물 열차처럼 나를 덮쳤다.
정규 표현식의 악몽 (The Regex Nightmare)
첫 번째 공급업체는 "$12.99" 형식을 사용했다. 두 번째는 "USD 12.99"를 사용했다. 심지어 어떤 곳은 "costs around twelve dollars and ninety nine cents"라고 적어두기도 했다. 나의 정규 표현식은 전방 탐색 (Lookaheads), 그룹 (Groups), 조건문 (Conditional statements)을 포함하며 40줄에 달하는 괴물로 변해갔다. 처음 20개의 제품에 대해서는 작동했다. 하지만 전체 데이터셋 (10,000개의 레코드)에 실행했을 때 문제가 발생했다.
성공률은 37%에 그쳤다. 나머지는 틀렸거나 비어 있었다. 나는 폴백 패턴 (Fallback patterns)을 추가하며 이틀을 더 보냈지만, 새로운 패턴을 추가할 때마다 새로운 오탐 (False positives)이 발생했다. 나는 내가 패배가 예정된 싸움을 하고 있다는 것을 깨달았다.
NLP 시도
spaCy와 NLTK를 고려했다. 제품 속성을 위한 맞춤형 개체명 인식 (NER) 모델을 학습시킬까? 그러려면 라벨링된 데이터 (Labeled data), 연산 시간 (Compute time), 그리고 공급업체의 형식이 바뀔 때마다 지속적인 유지보수가 필요할 것이다. 일회성 마이그레이션 프로젝트를 수행하기에는 과한 작업 (Overkill)이었다. 나에게는 학습 없이도 즉석에서 비정형 텍스트를 처리할 수 있는 무언가가 필요했다.
AI 접근 방식
한 동료가 데이터 추출을 위해 GPT 스타일의 모델을 사용하는 것을 언급했다. 나는 회의적이었다. 마치 호두를 까기 위해 대형 망치를 사용하는 것처럼 느껴졌기 때문이다. 하지만 정규 표현식의 벽에 부딪힌 후, 나는 그것을 시도해 보았다.
핵심 통찰: 모델을 미세 조정 (Fine-tune)할 필요는 없다. 잘 만들어진 시스템 프롬프트 (System prompt)와 일관된 출력 형식 (Output format)만 있으면 된다. 내가 최종적으로 구현한 방식은 다음과 같다:
import json
from openai import OpenAI
...
```
json").removesuffix("
```").strip()
return json.loads(raw)
내가 배운 것 (고생 끝에 얻은 교훈)
모델 크기보다 프롬프트 엔지니어링 (Prompt engineering)이 더 중요합니다. 처음에는 GPT-3.5로 시작했으나 결과가 일관되지 않았습니다. 엄격한 시스템 프롬프트("ONLY JSON만 반환하세요")와 함께 GPT-4o-mini로 전환하자 거의 100% 유효한 JSON을 얻을 수 있었습니다. 하지만 모델이 하지 말라고 명령해도 종종 JSON을 삼중 백틱(triple backticks)으로 감싸는 경우가 있어, 마크다운 펜스(markdown fences)를 명시적으로 파싱(parse)하는 법도 배웠습니다.
검증 (Validation)이 문제를 해결합니다. 모델이 실수로 쉼표를 하나 더 추가하는 환각 (hallucination) 현상을 보이면 json.loads는 충돌(crash)을 일으킵니다. 그래서 저는 폴백 프롬프트 (fallback prompt)를 포함한 재시도 루프 (retry loop)를 추가했습니다:
import json
import re
...
비용은 터무니없지 않습니다. GPT-4o-mini로 10,000개의 레코드를 처리하는 데 약 8달러가 들었는데, 이는 정규 표현식 (regex) 패턴을 디버깅하는 데 드는 제 시간보다 훨씬 저렴했습니다. 각 제품 설명은 평균 약 150 토큰 (tokens)이었고, 출력은 약 80 토큰이었습니다.
하지만 만능 해결책 (silver bullet)은 아닙니다. AI 모델은 여전히 매우 모호한 텍스트를 처리하는 데 어려움을 겪습니다. 예를 들어 공급업체가 "무선 마우스"라고 설명한 뒤, 가격 언급 없이 "배터리 미포함"이라고 언급한다면, 모델은 학습 데이터에 기반하여 가격을 추측할 수 있으며 이는 잘못된 정보입니다. 저는 기본값을 null로 설정하고, price가 null인 모든 레코드에 대해 인간의 검토 (human review) 단계를 추가하는 법을 배웠습니다.
이 방식을 사용하지 말아야 할 때
- 데이터베이스로부터 깨끗하고 일관된 스키마 (schema)를 가지고 있다면, SQL을 사용하세요.
- 특정 필드에 대해 라벨링된 학습 데이터 (labeled training data)가 있다면, 미세 조정된 NER (Named Entity Recognition) 모델이 대규모 처리 시 더 빠르고 저렴합니다.
- 지연 시간 (latency)이 매우 중요하다면 (실시간 API), AI 모델 호출은 요청당 1~3초를 추가합니다. 하이브리드 접근 방식(단순한 케이스에는 정규 표현식, 폴백용으로 AI 사용)을 고려하세요.
- 매우 짧은 수천 개의 문자열(예: 사용자 프로필)에서 추출하는 경우라면, 전통적인 정규 표현식이나 규칙 기반 파싱 (rule parsing)만으로도 충분할 수 있습니다.
내가 감수한 트레이드오프 (Trade-offs)
- 결정론 (Determinism) 대 유연성 (Flexibility). 정규 표현식 (Regex)은 항상 동일한 답을 내놓습니다. 반면 AI는 확률적인 출력 (probabilistic outputs)을 제공합니다. 데이터 마이그레이션 (data migration)의 경우, 파이프라인 (pipeline)이 망가지는 것보다는 몇 개의 필드가 틀리는 편이 낫습니다. 그래서 필드가 누락된 레코드에 대해서는 수동 검토 (manual review) 단계를 추가했습니다.
- 제어 (Control) 대 편의성 (Ease). 프롬프트 엔지니어링 (Prompt engineering)은 마치 흑마법처럼 느껴집니다. 모델이 가격을 지어내는 것을 방지하기 위해 시스템 프롬프트 (system prompt)를 조정하는 데 한 시간을 소비했습니다. "가격이 언급되지 않았다면 null을 사용하세요"라는 간단한 한 줄이 문제를 해결했습니다.
- 외부 API에 대한 의존성 (Dependency on external API). AI 서비스가 다운되면 데이터 추출 (extraction)도 중단됩니다. 재처리를 방지하기 위해 결과값을 로컬에 캐싱 (cached)했습니다.
다음에 다시 한다면 다르게 할 점
처음부터 AI로 시작하되, 강력한 검증 레이어 (validation layer)를 결합할 것입니다. 추출된 필드가 예상된 타입 (예: 가격은 float, 이름은 non-empty)과 일치하는지 확인하는 작업 말입니다. 구조를 강제하기 위해 Pydantic 모델을 사용할 것입니다. 또한, 지연 시간 (latency)을 분산시키고 비용을 절감하기 위해 요청을 배치 (batch) 처리할 것입니다.
아, 그리고 ai.interwestinfo.com처럼 이런 종류의 작업을 처리한다고 주장하는 특화된 추출 엔드포인트 (extraction endpoints)를 탐색해 볼 것입니다. 하지만 솔직히 말해서, 프롬프트 엔지니어링을 활용한 범용적인 접근 방식만으로도 충분한 제어력을 얻을 수 있었습니다. 다음 분기에 이 프로젝트를 다시 다루게 된다면 전용 도구를 사용할 수도 있습니다.
결국, 저는 정규 표현식을 쓰는 것을 멈췄습니다. 대신 프롬프트를 쓰기 시작했습니다. 그리고 제 주말을 되찾았습니다.
데이터 파싱 (data parsing)을 위해 AI를 사용해 본 여러분의 경험은 어떠신가요? 정규 표현식에 의존하시나요, 아니면 LLM에 올인하시나요? 여러분에게는 어떤 방식이 효과적인지 궁금합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기