Skip to content

About

서울권역 AICOSS 연합 경진대회 결과물

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

1 Commit

Folders and files

Repository files navigation

동선 (Dongseon)

이야기를 따라 지역 내 소비를 연결하는 로컬 상권 활성화 AI 서비스 대상 상권: 서울 동대문구 회기동 대학가

🏆 서울권역 AICOSS 연합 경진대회 우수상 (교육 파트) 인공지능혁신융합대학사업단 단장상 — 서울시립대학교

팀 트랜스4머 · 조강현, 박종혁, 이혜연


1. 문제 정의

회기동은 사람도 점포도 부족하지 않습니다. 20대 유동인구는 오히려 늘었고, 매출 유효업종수는 동대문구 14개 동 중 4위입니다. 진짜 문제는 유동인구가 매출로 이어지지 않는다는 점입니다.

상권 유동인구 (2021→2025) 매출 건수 (2021→2025)
경희대삼거리 -5.1% -19.2%
경희대 상권 -1.4% -23.3%

유동인구 감소보다 매출 감소가 3~16배 큰 '구매 전환 실패'. 원인은 두 가지입니다.

  1. 순환 구조의 부재 — 한 곳만 찍고 떠나 상권 내 순회가 만들어지지 않음
  2. 방문 동기(콘텐츠)의 부재 — '왜 이 골목에 머물러야 하는가'에 답하는 콘텐츠가 없음

동선은 검증된 지역 스토리를 축으로 실제 점포를 하나의 도보 경로로 엮어, 단일 소비를 연속 방문·골목 소비로 전환합니다.


2. AI 활용 구조 — 두 단계

동선에서 AI는 성격이 다른 두 단계에 쓰입니다. 이 분리가 서비스의 핵심입니다.

단계 시점 사용 AI 역할 산출물
① 데이터 정제 오프라인 (사전 1회) GPT · Gemini API, GPT-4o(VLM) + 사람 전수 검증 회기동 상권 데이터를 추천 가능한 형태로 정제 검증 점포 465건 · 검증 스토리 50청크
② 경로 추론 온라인 (요청마다) Qwen3 계열 로컬 모델 고정 자연어 해석 → 점포 선정 → 스토리 연결 개인화 도보 코스

상용 LLM·VLM은 사전 데이터베이스를 만드는 도구로만 썼고, 실제 서비스 추론은 전부 로컬 Qwen3로 돌아갑니다. 정제 단계에서 검증되지 않은 점포·사실은 애초에 인덱스에 들어가지 않으므로, 추론 단계에서 지어낼 대상 자체가 없습니다. 데이터 정제 단계는 4장에서 다룹니다.


3. 경로 추론 파이프라인 (프로젝트의 핵심)

최종 결과물/dongseon_route_pipeline_rag_multi_model_fixed (3).py — 약 5,500줄의 단일 모듈로, 역할별 로컬 LLM + FAISS RAG + 코드 기반 하드 제약을 결합한 경로 생성 엔진입니다.

3.1 설계 원칙 — "LLM은 의미를 판단하고, 제약은 파이썬이 강제한다"

자연어의 의미 해석과 경로 역할 설계는 LLM이 담당하지만, 거리·시간·영업시간·제외 조건 같은 핵심 제약은 전부 파이썬 코드가 강제합니다. LLM에게는 다음 권한이 없습니다.

  • 검증되지 않은 점포·카테고리를 새로 만들어 넣을 권한
  • 이동 거리·소요 시간·영업 여부를 무시할 권한
  • 사용자가 제외한 음식/업종/태그를 다시 넣을 권한 (제외 조건은 항상 우선)

생성된 출력은 다시 검증 단계를 거칩니다. 경로 점포 ID와 순서가 일치하는지, 인용한 스토리 청크가 실제로 검색·관계판정을 통과한 근거인지(evidence_chunk_ids ⊆ 검증된 문맥)를 확인하고, 하나라도 어긋나면 템플릿 생성기로 자동 폴백해 사실이 아닌 문장이 사용자에게 나가지 않게 합니다.

3.2 13단계 처리 흐름

사용자 자연어 한 문장
  │
  ├─ ①  임베딩 모델 ─ 점포·스토리 FAISS 질의 임베딩
  ├─ ②  판단 모델   ─ 조건·부정 의도 분석 (시간/동행/분위기/제외 조건)
  ├─ ③  판단 모델   ─ 경로 역할 슬롯 설계 + 코드 검증
  ├─ ④  점포 후보 검색 (FAISS RAG)
  ├─ ⑤  거리·시간·영업시간 하드 필터              ← 코드
  ├─ ⑥  제한 DFS 기반 경로 생성 (Beam Search 미사용) ← 코드
  ├─ ⑦  상위 경로 내 통제된 다양성 선택            ← 코드
  ├─ ⑧  단계적 대안 경로 생성 (완화 정책)          ← 코드
  ├─ ⑨  확정 장소별 스토리 후보 검색 (FAISS RAG)
  ├─ ⑩  관계 판정 모델 ─ 장소↔스토리 관계 분류
  ├─ ⑪  안전한 스토리 문맥 선택                   ← 코드
  ├─ ⑫  출력 모델   ─ 최종 경로·스토리 JSON 생성
  └─ ⑬  출력 검증 및 템플릿 fallback              ← 코드

3.3 역할별 모델 구성 (A6000 48GB 기준)

단계 역할 모델 VRAM(bf16)
임베딩 쿼리·문서 검색(RAG) Qwen/Qwen3-Embedding-0.6B ~1.2GB
판단 조건 파싱 · 슬롯 설계 Qwen/Qwen3-4B-Instruct-2507 ~8GB
관계 점포–스토리 관계 분류 (판단 모델 공유) +0
출력 코스·점포 스토리 생성 Qwen/Qwen3-4B-Instruct-2507 (공유) +0
  • 같은 모델 경로·로딩 설정을 넘기면 모델 레지스트리가 1회만 GPU에 적재해 세 역할이 공유합니다.
  • 출력 모델은 반드시 출력이 나오는 non-thinking instruct 계열을 기본으로 둡니다. (품질을 더 원하면 Qwen2.5-14B-Instruct, Qwen3-30B-A3B-Instruct-2507 4bit 등으로 교체 가능)
  • 각 역할에 PEFT LoRA/QLoRA SFT 어댑터를 선택적으로 결합할 수 있습니다 (decision_adapter_path / relation_adapter_path / output_adapter_path).
  • USE_AI=False 로 두면 동일 파이프라인이 규칙기반(휴리스틱) 폴백만으로도 완주합니다 (HeuristicConditionParser, HeuristicStoryRelationRanker, TemplateStoryGenerator).

3.4 스토리 우선(THEME_ROUTE) 전략과 폴백 체인

이 프로젝트의 차별점은 "점포를 모은 뒤 이야기를 붙이는" 것이 아니라, "이야기를 먼저 정하고 그 이야기가 성립하는 점포 조합을 찾는" 순서에 있습니다.

쿼리 → 대표 테마 스토리(스파인) 선정 → 테마에 맞는 점포 수집
     → 걷기 가능한 순서로 배치 → 각 점포에 테마의 '한 장면' 할당

조건을 채우지 못하면 자동으로 아래 순서로 내려가며 항상 결과를 보장합니다.

THEME_ROUTE → EXACT_MATCH → ALTERNATIVE_ROUTE → REDUCED_ROUTE → CURATED_ROUTE → BEST_EFFORT_ROUTE → NO_ROUTE_WITH_SUGGESTION

3.5 RAG 구성

embed_rag_data_bar_updated (2).py 가 두 개의 독립 FAISS 코퍼스를 생성합니다.

rag_data/
├─ stores/    index.faiss · metadata.jsonl · manifest.json   # 점포·관광지 465건
└─ stories/   index.faiss · metadata.jsonl · manifest.json   # 검증 스토리 50청크 / 35스토리
  • manifest.json에 인덱스 생성에 쓴 임베딩 모델 경로를 기록해, 실행 시 동일 모델을 강제합니다.
  • 점포 검색과 스토리 검색을 분리해 점포 적합도와 서사 적합도를 각각 최적화합니다.
  • 스토리 검색 결과는 그대로 쓰지 않고 ⑩ 관계 판정 모델이 direct(이 장소의 이야기) / area_context(지역 이야기) / thematic(주제로 이어진 이야기) / none 으로 분류하고 0~1 점수를 매겨, 임계값(기본 0.45) 미만은 버립니다.
  • 관계 판정 LLM 호출은 상위 story_relation_llm_top_k개 후보로 제한하고, 나머지는 값싼 휴리스틱 관계로 채워 요청당 LLM 호출량에 상한을 둡니다.

3.6 상권 활성화를 위한 추천 제어

단순 인기순 추천이 아니라, 상권 정책 목표를 점수 함수에 직접 반영했습니다.

파라미터 목적
small_business_target / small_business_bonus small_business 태그 점포(465건 중 331건)를 경로에 1~2곳 우선 편입해 저노출 골목 점포로 방문 분산
theme_keyword_bonus 테마 스토리 본문·제목의 음식/이름과 겹치는 '주인공 점포' 가산 (예: 파전골목 스토리 ↔ foods에 파전)
repeated_category_penalty / prevent_consecutive_same_category 같은 업종 연속 배치 차단 (밥→밥, 카페→카페 방지)
sequence_flow_weight 현실적 흐름(식사 → 볼거리·산책 → 휴식) 유도
fill_duration_ratio 요청 시간의 85%까지 정거장·체류를 채워 체류 시간 극대화
route_randomness + seed 같은 쿼리에도 매번 다른 경로. seed 저장으로 재현 가능
previous_store_penalty 재방문 시 이전에 갔던 점포에 페널티를 줘 새로운 점포로 유도
route_overlap_penalty 후보 경로 간 중복을 눌러 통제된 다양성 확보

모든 보너스·페널티는 하드 제약을 통과한 후보 사이에서만 순위를 조정합니다. 조건에 맞지 않는 점포가 강제 삽입되는 일은 없습니다.

3.7 생성 텍스트 품질 제어

출력 모델은 코스당 다음을 한 번에 생성합니다.

  • 코스 제목 / 요약 / 테마 스토리(코스 전체를 관통하는 이야기)
  • 정거장별 short_story(카드 요약) · detail_story(상세 본문) · next_reason(다음 장소로 가야 할 이유)

품질 제어 장치:

  • detail_theme_ratio=0.6 — 상세 본문을 테마 이야기 60% : 점포 고유 정보 40% 비중으로 강제
  • detail_min/max_chars(180~380자) — 분량 하한·상한 강제
  • companion 반영 — solo로 판정되면 '친구들과·함께·단체' 같은 동행 표현 사용 금지를 프롬프트로 주입
  • next_store_name 고정 주입 — 모델이 상상한 순서로 새지 않도록 실제 다음 방문 장소를 못박음
  • 요청당 LLM 호출 예산 = story_relation_llm_top_k(관계) + 1(파싱) + 1(출력) 로 명시적 상한

4. 데이터 — 사전 구축 파이프라인

추천의 품질은 결국 어떤 데이터 위에서 도는가로 결정됩니다. 회기동 상권 데이터를 그대로 쓰지 않고, AI로 정제한 뒤 사람이 전수 검증해 사전 데이터베이스를 만들었습니다. 이 데이터가 있었기에 "상권을 골라서 추천"하는 일이 가능해졌습니다.

4.1 정제 흐름 (오프라인 · 사전 1회)

① 공공데이터 점포 리스트        회기동 점포명 확보 · 실존 여부 1차 확인
        ↓
② LLM 태그 추출                GPT · Gemini API로 대표메뉴 · 분위기 · 역사 · 요약 생성
        ↓
③ VLM 외관 교차검증            GPT-4o로 매장 사진과 추출 태그의 일치 여부 확인
        ↓
④ 사람 전수 검증               팀원이 점포를 일일이 직접 검색해 실존 · 정보 확인 후 확정
        ↓
⑤ 임베딩 → Vector DB          역사 · 정보 · 태그 · 고유성 · 평점 등을 벡터로 적재
  • ②~③은 자동 정제, ④는 수작업 확정입니다. AI가 만든 태그는 후보일 뿐이며, 사람이 확인하지 않은 항목은 최종 데이터에 넣지 않았습니다.
  • 그 결과 점포 465건이 전량 verified: true, 스토리 50청크가 전량 verified: true + source 명시 상태로 남았습니다.
  • 이 단계에서 걸러진 데이터는 인덱스에 아예 존재하지 않으므로, 추론 단계의 LLM이 지어낼 대상 자체가 없습니다.

4.2 AI와 사람의 역할 분담

AI 담당 사람 담당
대표메뉴 · 분위기 · 역사 · 요약 태그 추출 장소의 실제 존재 여부 2차 검증(직접 검색)
매장 사진 ↔ 태그 외관 교차검증(VLM) 역사 · 장소 사실의 임의 창작 금지 관리
리뷰·공개 데이터에서 지역 특색 점포 발굴 스토리 골격의 사전 검증과 품질 기준 설정
후보 점포 분류 · 경로 변형(테마·컨셉) 확장 개인정보 비식별화 등 데이터 보안·윤리
장소 간 '왜 이어지는가' 연결 서사 생성 데이터베이스 관리 및 트렌드 반영 업데이트

환각(Hallucination) 차단 원칙 — 존재가 검증된 점포와 사실 기반 스토리만 인덱싱합니다. 임의 생성·미검증 데이터는 추천 및 스토리 생성 과정에서 배제됩니다. AI가 창의적 언어 경험을 만들되, 사실 검증과 최종 판단은 사람이 책임집니다.

4.3 최종 데이터셋

파일 내용
stores_embedding_ready_first.json 회기동 점포·관광지 465건 (전량 verified: true)
stories__hoegi_verified_valid (1).json 회기동 검증 스토리 50청크 / 35스토리

점포 스키마 — store_id(OSM 등 원천 ID), name, category(11종 표준 분류), latitude/longitude, address, tags, foods, average_visit_minutes, minimum_visit_minutes, opening_windows, verified, exposure_score, base_description

카테고리 분포: restaurant 249 · bar 67 · cafe 63 · experience 39 · dessert 24 · shopping 9 · walk 4 · landmark 4 · campus 3 · culture 2 · photo_spot 1

스토리 스키마 — chunk_id, story_id, title, region, themes, content, source, verified

회기동 지명 유래(폐비 윤씨·회묘), 서대문–청량리 전차, 경희대 사자상 전설, 헐떡고개, 벽화 골목, 회기 파전골목 등 출처가 명시된 사실 기반 스토리만 사용합니다.

이렇게 축적한 검증 데이터와 '성공한 점포 조합' 기록은 다른 지역으로도 같은 절차로 복제할 수 있으며, 동시에 다른 서비스가 그대로 가져다 쓸 수 없는 지역 고유 자산이 됩니다.


5. 실행

5.1 요구 환경

CUDA GPU(A6000 48GB 기준 검증) · torch · transformers>=4.51.0 · sentence-transformers · faiss · accelerate · huggingface_hub

5.2 노트북으로 실행 (권장)

최종 결과물/new-start.ipynb — 스토리 우선 파이프라인 실행 노트북. 셀 순서대로 실행하면 패키지·CUDA 확인 → 인덱스 탐색/생성 → 모델 준비 → 파이프라인 로드 → 쿼리 실행까지 이어집니다.

config = pipeline_module.RoutePipelineConfig(
    candidates_per_slot=12,
    story_relation_llm_top_k=3,   # 요청당 관계판정 LLM 호출 수
    small_business_target=2,      # 소상공인 점포 우선 편입
    max_route_nodes=5,
    fill_duration_ratio=0.85,
    route_randomness=0.3,
    random_seed=42,
)

pipeline = pipeline_module.build_ready_pipeline(
    embedding_model_path=embedding_model_path,
    rag_data_dir=RAG_DIR,
    decision_model_path=decision_model_path,
    relation_model_path=relation_model_path,   # None → 판단 모델 공유
    output_model_path=output_model_path,
    config=config,
)

result = pipeline.run(
    "회기동 하면 파전이지! 파전 골목 포함해서 3시간 동안 친구랑 알차게 놀 루트 짜줘.",
    visit_at=VISIT_AT,
    start_point=pipeline_module.GeoPoint(37.5963, 127.0528),
    start_label="경희대학교 정문",
)

test_story_first (2).ipynb 는 단계별 진단(조건 파싱 결과, 슬롯 설계, 후보/탈락 사유, 관계 판정 점수)을 확인하는 노트북입니다.

5.3 CLI로 실행

python "dongseon_route_pipeline_rag_multi_model_fixed (3).py" \
  --embedding-model-path ./models/Qwen__Qwen3-Embedding-0.6B \
  --decision-model-path  ./models/Qwen__Qwen3-4B-Instruct-2507 \
  --output-model-path    ./models/Qwen__Qwen3-4B-Instruct-2507 \
  --rag-dir ./rag_data \
  --query "비 오는 날 혼자 파전 먹고 책 읽기 좋은 카페 가고 싶어" \
  --start-lat 37.5963 --start-lon 127.0528 --start-label "경희대학교 정문" \
  --diagnostics

5.4 인덱스 생성

python "embed_rag_data_bar_updated (2).py" \
  --embedding-model-path ./models/Qwen__Qwen3-Embedding-0.6B \
  --stores-json stores_embedding_ready_first.json \
  --stories-json "stories__hoegi_verified_valid (1).json" \
  --output-dir ./rag_data

모델 다운로드·업로드·학습 코드는 파이프라인에 포함하지 않습니다. 서버에 이미 배치된 로컬 모델 경로를 넘겨 바로 실행하는 구조입니다.


6. 데모 웹 (부수 요소)

최종 결과물/WebBase/ — 파이프라인 결과를 보여주기 위한 프로토타입 UI입니다. FastAPI + Jinja2 기반이며, 경로 생성 로직은 전부 위 파이프라인에 있습니다.

  • 노트북에서 이미 로드한 pipeline 객체를 run_in_notebook.launch()로 주입 → 모델 재적재 없음
  • /api/chat → pipeline.run() → 프론트 course 스키마로 변환 → generated_routes/<rid>.json 저장
  • 저장 레코드에 meta.seed / meta.message / meta.start 가 남아 동일 코스 재현·재출력 가능
  • 화면: 홈 · 카드 스택 코스 뷰 · AI 길잡이(대화) · 방문 인증(GPS/영수증) · 리워드 지갑

세부 내용은 WebBase/README.md, WebBase/README_PIPELINE.md 참고.


7. 저장소 구조

AICOSS 경진대회/
├─ 동선_제안서.pdf                                  # 제안서 (27p)
├─ 발표 ppt.pdf                                     # 발표 자료 (24p)
└─ 최종 결과물/
   ├─ dongseon_route_pipeline_rag_multi_model_fixed (3).py   # ★ 경로 생성 AI 파이프라인
   ├─ embed_rag_data_bar_updated (2).py                      # FAISS 인덱스 빌더
   ├─ new-start.ipynb                                        # 실행 노트북 (스토리 우선)
   ├─ test_story_first (2).ipynb                             # 단계별 진단 노트북
   ├─ stores_embedding_ready_first.json                      # 점포 465건
   ├─ stories__hoegi_verified_valid (1).json                 # 검증 스토리 50청크
   └─ WebBase/                                               # 데모 웹 (FastAPI + Jinja2)
      ├─ app/{main.py, pipeline_bridge.py, templates/, static/}
      ├─ run_in_notebook.py
      └─ generated_routes/*.json                             # 생성 경로 기록 (재현용)

8. 기대 효과

지표 근거
평균 체류시간 +66.7% (2.1h→3.5h) 김포 '체류를 부르는 5味로드' — 관광 거점을 미식 코스로 연계해 동선 재설계
대표 거점 방문객 +112% (15만→31만) 동일 사례
외식업 매출 +14~29% 서울시 로컬브랜드 상권 육성 사업 (상권 브랜딩·콘텐츠 확충 이후)

자체 추정: 이용자 50명/일 · 완주율 30% · 완주자 1인당 추가소비 16,000원 기준 → 연간 약 8,760만 원의 추가 소비 유발, 리워드 1원당 2.67원의 지역 소비 창출. (이용자 수·평균 소비금액을 가정한 추정치입니다.)

생성된 경로는 일회성으로 끝나지 않고 계절·시간대·사용자 유형·지역 행사별로 축적됩니다 (generated_routes/*.json에 입력·seed·결과가 함께 기록됨). 반응이 좋은 스토리는 지역 대표 코스로 승격하고, 영화·드라마·학교·역사와 결합해 IP로 확장할 수 있습니다. 검증 데이터베이스 구축 절차 자체가 다른 상권으로 이식 가능한 방법론입니다.


스쳐 지나가는 회기동을 머무는 회기동으로 — 트랜스4머

About

서울권역 AICOSS 연합 경진대회 결과물

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages