Mini Project 01 — LLM/RAG 기반 영화 흥행 분석 시스템
전체 소스코드는 GitHub에서 확인할 수 있습니다. hojjang98 / skshielders-rookies-28 — projects/week_03 & week_04
프로젝트 소개
이 프로젝트는 SK쉴더스 루키즈 28기 첫 번째 팀 미니프로젝트로, Week 3 & 4 (총 2주) 를 통합한 집중 개발 기간에 수행되었다.
본인은 해당 프로젝트에서 팀장을 맡아 전체 아키텍처 설계, 역할 분담, QA, 발표 구성까지 총괄하였다.
단순한 기술 구현을 넘어, “왜 이 데이터가 이 결과를 내는가?” 라는 인사이트 중심의 분석을 팀 전체가 발표에서 설명할 수 있도록 이끄는 것이 팀장으로서의 핵심 목표였다.
팀원 모두가 혼자 앞서가는 100점이 아닌, 함께 도달하는 70~80점을 목표로 운영했다. 결과보다 과정의 이해를 강조하고, 최종 발표에서는 숫자가 아닌 “이유” 를 설명하도록 이끌었다.
프로젝트 개요
LLM/RAG 기반 영화 흥행 분석 시스템은 TMDB 영화 데이터를 기반으로 흥행 요인을 다각도로 분석하고, RAG(Retrieval-Augmented Generation) 기술을 통해 사용자 질의에 근거 기반 영화 추천 및 AI 분석 리포트를 제공하는 종합 분석 플랫폼이다.
- 데이터 소스 — TMDB (The Movie Database) API + 직접 수집한 정제 데이터셋
- 핵심 기술 — ChromaDB Vector Store, LangChain, OpenAI GPT-4o-mini, Streamlit
- 분석 범위 — 수천 편의 영화 데이터를 기반으로 ROI / Profit / 평점 / 장르 / 감독 등 다차원 분석
프로젝트 일정 (Week 3 & 4)
| 날짜 | 주요 작업 |
|---|---|
| 11.15 (Day 1) | 데이터 클리닝 계획 수립, TMDB 데이터 인코딩 / 이상치 / 결측치 처리 기준 정의 |
| 11.17 (Day 2) | TMDB API 연동, Embedding용 텍스트 결합 및 Vector DB 구축 |
| 11.18 (Day 3) | Streamlit 멀티 페이지 구조 설계, @st.cache 전략 적용, Plotly 시각화 구현 |
| 11.19 (Day 4) | LangChain / RAG 분석 모델 통합, ProductionRAG 클래스 완성, PPT 초안 제작 |
| 11.20 (Day 5) | RAG 일관성 검증, 시연 스크립트 확정, 최종 발표 자료 완성 |
| 11.21 (Day 6) | 최종 프레젠테이션 발표 및 Q & A |
시스템 아키텍처
전체 시스템은 데이터 레이어 -> 분석 레이어 -> 서비스 레이어 의 3단 구조로 설계했다.
데이터 레이어
- TMDB API 호출 (tmdb_api.py) => 최신 인기 / 개봉 예정 영화 수집
- Movie_Preprocessing.ipynb => 이상치 제거, 결측치 보정, ROI / Profit 컬럼 생성
- Movie_Vector_DB.ipynb => 정제된 데이터를 텍스트로 변환, OpenAI Embedding 후 ChromaDB 저장
분석 레이어
- data_loader.py => @st.cache_data 기반 DataFrame 로드 및 캐싱
- vector_db.py => @st.cache_resource 기반 ChromaDB 클라이언트 로드 및 검색
- llm_utils.py => RAG 추천 체인 + 대화형 추천 + 흥행 요인 분석 (OpenAI 호출)
- production_rag.py => ProductionRAG 클래스 — 보수적 ROI 예측 + LLM 종합 리포트 생성
서비스 레이어 (Streamlit 멀티 페이지)
- 홈.py => 대시보드 진입점, 데이터 통계 요약 카드 UI
- 1_영화 데이터 탐색 대시보드.py => EDA (장르 / 연도 / 국가 분포)
- 2_흥행 지표 분석.py => ROI / Profit 기반 Top 10% 영화 분석
- 3_감독별 흥행 분석.py => 감독 랭킹, 흥행 성공률, 장르별 분석
- 4_영화 제작 분석 시스템.py => 기획안 입력 -> AI 투자 판단 리포트 출력
주요 기능
- RAG 기반 영화 추천 — 자연어 질의 또는 영화 제목 입력 시 Vector DB 검색 후 LLM 이 추천 사유를 자연어로 설명
- 흥행 지표 분석 — ROI (투자 수익률) 와 Profit (이익) 기준으로 Top 10% 영화의 특징 분석
- EDA 대시보드 — 연도별 / 장르별 / 국가별 통계 및 추이를 Plotly 인터랙티브 차트로 시각화
- 성능 최적화 — @st.cache_data (데이터 로드) 와 @st.cache_resource (Vector DB 클라이언트) 를 분리 적용
- 모듈 분리 — llm_utils.py / vector_db.py / data_loader.py / production_rag.py 로 책임을 명확히 분리
- AI 투자 판단 시스템 — 기획안 (장르 / 예산 / 시놉시스) 입력 시 보수적 ROI 계산 + LLM 종합 리포트 생성
핵심 코드 설명
1. RAG 추천 파이프라인 — 검색과 생성의 결합
RAG 의 핵심은 “Vector DB 에서 가져온 실제 데이터를 LLM 에게 근거로 제공” 하는 것이다. 할루시네이션 없이 우리 데이터셋 안에서만 추천하도록 설계한 이유도 여기에 있다.
전체 흐름 (get_rag_movie_recommendation):
1. 사용자 질의 (user_query) 를 ChromaDB 에 벡터 검색
=> collection.query(query_texts=[user_query], n_results=5)
2. 검색된 메타데이터 (제목, 장르, 평점, 줄거리 등) 를 문자열로 변환
=> documents_text 리스트로 조합
3. LangChain ChatOpenAI (gpt-4o-mini) 에 컨텍스트로 삽입
=> SystemMessage + HumanMessage 구조
4. LLM 이 "왜 이 영화를 추천하는가" 를 자연어로 설명
=> response.content 반환
핵심 RAG 프롬프트 구성 (축약):
context = "\n\n---\n\n".join(documents_text)
prompt = f"""
사용자 요청: "{user_query}"
검색된 영화 정보:
{context}
위 영화들을 바탕으로 추천 사유를 작성해주세요:
1. 사용자 요청과 추천 영화들의 연관성
2. 각 영화의 주요 특징 (장르, 평점, 분위기 등)
3. 왜 이 영화들을 추천하는지 구체적인 이유
"""
2. Vector DB 캐싱 — @st.cache_resource 적용
Streamlit 에서 ChromaDB 클라이언트를 매 렌더링마다 새로 생성하면 심각한 성능 저하가 발생한다. @st.cache_resource 데코레이터를 사용해 최초 1회만 로드하고 이후 재사용한다.
@st.cache_resource
def load_vector_db():
openai_api_key = os.getenv('OPENAI_API_KEY')
embedding_function = embedding_functions.OpenAIEmbeddingFunction(
api_key=openai_api_key,
model_name="text-embedding-ada-002"
)
# 두 경로를 순차 탐색하여 DB 위치 유연하게 처리
possible_paths = [
Path("../vector_db/chroma_db"),
Path("vector_db/chroma_db"),
]
db_path = next((p for p in possible_paths if p.exists()), None)
chroma_client = chromadb.PersistentClient(path=str(db_path))
collection = chroma_client.get_collection(
name="movies",
embedding_function=embedding_function
)
return collection
@st.cache_data 와의 역할 구분:
- @st.cache_data => DataFrame 같은 직렬화 가능한 데이터 객체 캐싱 (data_loader.py)
- @st.cache_resource => DB 연결, API 클라이언트 등 공유 자원 캐싱 (vector_db.py)
3. ProductionRAG — 보수적 투자 분석 엔진
이 프로젝트에서 가장 공을 들인 부분이다. 단순히 “평균 ROI 가 몇 배입니다"가 아니라, 실제 투자 판단에 쓸 수 있는 보수적 수치를 제공하는 것이 목표였다.
보수적 기준 설계 원칙:
- 기대 수익 기준 => 평균(mean) 이 아닌 하위 40% (quantile 0.40)
- 최악 시나리오 => 하위 15% 수익을 별도 제시
- BEP (손익분기점) 기준 => ROI 2.5배 이상 을 수익성 달성으로 판정
- 투자 판단 체계:
- ROI 2.5배 이상 => 투자 추천
- ROI 2.0 ~ 2.5배 => 조건부 추천
- ROI 2.0배 미만 => 재검토 필요
핵심 계산 로직 (_analyze_similar_performance 축약):
# 예산 범위 ±50% 내 유사 영화만 필터링
budget_range = (proposed_budget * 0.5, proposed_budget * 1.5)
similar_subset = [m for m in similar_movies if budget_range[0] <= m['budget'] <= budget_range[1]]
rev_series = pd.Series([m['revenue'] for m in similar_subset])
# 보수적 기준: 하위 40% 수익을 베이스로 설정
conservative_base = rev_series.quantile(0.40)
# 상업성 페널티 × 예산 효율 페널티 동시 적용
final_adjusted_revenue = conservative_base * commercial_factor * budget_efficiency
final_adjusted_roi = final_adjusted_revenue / proposed_budget
is_profitable = final_adjusted_roi >= 2.5 # BEP 달성 여부
4. 상업성 리스크 스코어링 — 시놉시스 분석
시놉시스 내 특정 키워드가 포함될 경우 기대 수익을 자동으로 하향 조정하는 로직이다. 예술 영화 또는 실험적 작품처럼 대중성이 낮은 기획안에 현실적인 경고를 제공한다.
주의 키워드 예시:
-
치명적 독소 => “실험적”, “흑백”, “다큐멘터리”, “관조적”, “철학적”
-
중간 수준 주의 => “초현실”, “전위”, “미니멀”, “독백”, “무대극”
penalty_factor = 1.0 for word in caution_keywords: if word in synopsis.lower(): penalty_factor *= 0.8 # 키워드 발견 시마다 기대 수익 20% 삭감 return max(penalty_factor, 0.4) # 최소 40% 보장 (극단적 경우도 기회 부여)
5. 대화형 RAG — 의도 분류 후 분기 처리
단순 추천 응답이 아닌, 사용자 발화 의도를 먼저 분류 한 뒤 처리하는 2단계 구조로 설계했다.
1단계 — 의도 분류 (LLM 호출)
형식: RECOMMEND | 검색키워드 또는 CHAT | 응답내용
2단계 — 분기 처리
RECOMMEND => Vector DB 검색 후 RAG 추천 체인 실행
CHAT => 이전 대화 컨텍스트(최근 5개)를 포함하여 일반 대화 응답
이 구조 덕분에 “액션 영화 추천해줘” 와 “지금 몇 시야?” 를 같은 채팅창에서 자연스럽게 처리할 수 있다.
프로젝트 파일 구조
movie_project/
├── app/
│ ├── 홈.py -- Streamlit 메인 앱 (통계 카드 + 기능 소개)
│ ├── pages/
│ │ ├── 1_영화 데이터 탐색 대시보드.py -- EDA 시각화
│ │ ├── 2_흥행 지표 분석.py -- ROI / Profit 기반 분석
│ │ ├── 3_감독별 흥행 분석.py -- 감독 랭킹 및 성공률
│ │ └── 4_영화 제작 분석 시스템.py -- AI 기획안 투자 판단
│ └── utils/
│ ├── data_loader.py -- @st.cache_data 기반 데이터 로드
│ ├── llm_utils.py -- RAG 추천 체인 + 흥행 요인 분석
│ ├── production_rag.py -- ProductionRAG 클래스 (보수적 ROI)
│ ├── tmdb_api.py -- TMDB API 호출 유틸리티
│ └── vector_db.py -- @st.cache_resource ChromaDB 관리
├── data/ -- 원본 / 정제 데이터 저장소
├── notebooks/
│ ├── Movie_Analysis.ipynb -- EDA 및 일반 분석
│ ├── Movie_Preprocessing.ipynb -- 전처리 상세 기록
│ └── Movie_Vector_DB.ipynb -- Vector DB 구축 기록
└── vector_db/ -- (Git 제외) ChromaDB 임베딩 저장소
SOC 관점 연계 — 데이터 기반 의사결정 파이프라인
이 프로젝트의 구조는 보안관제(SOC) 의 분석 사이클과 구조적으로 동일하다.
| 실무 단계 | 보안 관제 예시 | 본 프로젝트 대응 |
|---|---|---|
| 수집 (Collection) | 로그 / 패킷 수집 | TMDB API + CSV 영화 데이터 수집 |
| 전처리 (Preprocessing) | 로그 정규화, 노이즈 제거 | 결측치 / 이상치 제거, ROI 컬럼 생성 |
| 분석 (Analysis) | 이상 행위 탐지, 패턴 분석 | 흥행 지표 분석, 감독별 성공률 분석 |
| 인텔리전스 (Intelligence) | 위협 인텔리전스 연계 | TMDB API 실시간 트렌드 + RAG 근거 추천 |
| 보고 (Reporting) | 대시보드 / 경보 발령 | Streamlit 대시보드 + AI 투자 판단 리포트 |
학습 연결 포인트
- Pandas & EDA => TMDB 데이터 클리닝, 결측치 및 이상치 제거, ROI / Profit 컬럼 생성
- Streamlit Caching => @st.cache_data / @st.cache_resource 를 역할에 따라 분리 적용
- RAG System => ChromaDB Vector Store 로 텍스트 임베딩 검색 구조 구현
- LLM Prompting => 추천 사유 생성 및 흥행 분석을 위한 시스템 프롬프트 설계
- Modularity => utils 폴더 내 기능별 모듈 분리로 유지보수성 확보
- Plotly.express => 장르 분포, ROI 분포, 시계열 추이 동적 시각화
팀장으로서의 회고
이번 프로젝트는 기술보다 팀을 어떻게 이끌 것인가 를 더 많이 고민한 경험이었다.
- 역할 분담 원칙 — 각자의 이해 수준에 맞는 파트를 맡기되, 전체 흐름을 모두가 알 수 있도록 공유
- QA 과정 — 최종 발표 전날 RAG 응답 일관성을 직접 검증하고, 불안정한 케이스는 프롬프트를 수정
- 발표 전략 — “이 데이터에서 이런 인사이트를 발견했고, 그 이유는 ~이다” 라는 구조로 전환하도록 팀원들을 코칭
- 기술 선택 기준 — 화려한 기능보다 설명 가능하고 재현 가능한 구조 를 우선시
100점짜리 혼자 돌아가는 시스템보다, 팀 전체가 이해하고 설명할 수 있는 70점짜리 시스템 이 실제로 더 가치 있다는 것을 체감한 프로젝트였다.
확장 아이디어
- 시계열 흥행 예측 — 개봉 전 데이터(예산, 장르, 감독 이력) 로 수익 예측 모델 구축
- 실시간 트렌드 반영 — TMDB 실시간 API 를 주기적으로 호출하여 Vector DB 자동 업데이트
- 지도 시각화 — 제작 국가별 흥행 성과를 Folium 지도로 시각화
- 멀티 에이전트 확장 — 사용자 취향 기반 대화 이력을 누적하여 개인화 추천 품질 향상
- 보안 인텔리전스 연계 — 영화 산업 사례를 보안 위협 인텔리전스 분석 구조로 전환하는 응용 연구