Cursor로 머신러닝 코딩을 시도해 보자~! (영화 흥행 여부 예측)

소개

2주차 실시사항 : make 첫 경험 // inoreader(뉴스) --> 인스타그램(그림+글 게시)

3주차 목표 : Cursor로 머신러닝 코드 짜보기

  • 계기 : 지피터스 시작하고 Cursor라는 단어가 계속 들리는데, 뭔지 정확히 모르다 11.18.(월) 잔마왕님께서 스터디장으로 계신 파이썬코딩 스터디 참관 중 Cursor에 대한 칭송을 듣고, 이용해 볼 것을 결심!

  • 머신러닝 : 머신러닝 관련 맛배기 강좌 청강 경험

진행 방법

1. 학습할 데이터 확보하기

가. Kaggle 사이트에 데이터셋 중 영화와 관련된 "Full TMDB Movies Dataset 2024 (1M Movies)"
데이터를 머신러닝으로 학습하여 성공할 영화인지 예측하는 모델을 제작해 보기로 함
* Kaggle : 데이터 과학과 머신러닝 분야의 세계적으로 유명한 온라인 플랫폼.
다양한 주제의 공개 데이터셋을 제공하여 누구나 자유롭게 분석하고 활용 가능

전체 TMD 영화 데이터베이스 2021

* TMDB : The Movie Database의 약자로 영화 및 TV 프로그램 관련 정보를 제공하는 온라인 데이터베이스 및 커뮤니티 플랫폼(하단 마인드맵 : kaggle에서 확보한 데이터셋 컬럼 목록)

2. Cursor 설치 후 머신러닝 관련 라이브러리 설치

Python 스크립트의 스크린샷
python3 - python3 - python3 - python3

< 주피터 노트북(Jupyter Notebook) 설치 >
웹 브라우저에서 코드를 Cell 단위로 실행 및 결과 확인가능하여 직관적인 개발 환경
확장자) .ipynb : (주피터) 노트북 파일
예) 구글 코랩, Cursor에도 설치하여 실행 가능

< 데이터분석 관련 파이썬 외부 라이브러리 >
ㅇ Numpy - 이번엔 사용 안함
- 대표적인 파이썬 수치 및 행렬 계산용
- !pip install numpy
import numpy as np
- 다차원 행렬 계산용 자체 자료형
: ndarray(넘파이 배열)
- 자체 자료형 : float64, int64

ㅇ Pandas
- 행과 열로 구성된 표 형태의 Tabular 데이터를 다루기 용이
- Series, DataFrame은 행과 열 인덱싱을 통해 데이터에 쉽게 접근, 조작 가능
- 다양한 내장 함수 제공 / 결측치 처리, 데이터 정렬, 그룹화, 집계 용이
- Matplotlib, Seaborn 등의 라이브러리와 연동하여 쉽게 시각화 가능

ㅇ matplotlib
- 데이터 시각화와 2D 그래프 생성을 위한 파이썬 라이브러리
- Pandas 데이터 프레임을 활용하여 여러가지 차트를 쉽게 생성 가능
- 여러가지 형식의 차트 지원 / 커스텀 가능

ㅇ Seaborn
- 파이썬에서 데이터를 시각화하는 데 사용되는 강력하고 사용하기 쉬운 라이브러리
- matplotlib에 비해 더 간단한 문법으로 복잡한 통계 그래프를 쉽게 생성 가능

ㅇ Scikit-learn
- 파이썬에서 가장 널리 사용되는 머신러닝 라이브러리 중 하나
- 다양한 알고리즘 지원 : 지도 학습(분류, 회귀 등), 비지도 학습(클러스터링, 차원 축소 등) 등

ㅇ imbalaced-learn
- 불균형한 데이터셋을 다루기 위한 파이썬 라이브러리
- 리샘플링 기법
- 오버샘플링(소수 클래스 샘플 증가) 예) SMOTE(Synthetic Minority Over-sampling Technique) 등
- 언더샘플링(다수 클래스의 샘플을 감소)

3. csv 파일 읽어 들여 데이터 살펴 보기

다양한 유형의 코드를 보여주는 컴퓨터 화면의 스크린샷

ㅇ head() 함수로 위에 5줄 데이터 내용 살펴보기

파일 목록을 보여주는 컴퓨터 화면의 스크린샷
이름 목록이 표시된 검은색 화면의 스크린샷
다양한 언어 목록이 표시된 검은색 화면
다양한 정보가 담긴 마인드맵

4. 전처리 하기

결측치 제거, 이상치 제거(IOR(Iterative Outlier Removal) 방식으로 반복적으로 이상치 제거)

여러 그래프를 보여주는 컴퓨터 화면의 스크린샷

5. 머신러닝 코딩

# 필요한 라이브러리 임포트
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report
from sklearn.preprocessing import StandardScaler, LabelEncoder
from sklearn.impute import SimpleImputer
from imblearn.combine import SMOTETomek
import numpy as np
import pandas as pd
import ast
import json
import joblib
import pickle

def train_movie_success_predictor(df):
    print("=== 1. 데이터 전처리 시작 ===")
    
    # 데이터 필터링 조건 강화
    valid_movies = df[
        (df['vote_count'] > 0) &
        (df['vote_average'] > 0) &
        (df['budget'] > 1000) &      
        (df['runtime'] > 40) &       
        (df['runtime'] < 300) &      
        (df['popularity'] > 0)
    ].copy()
    
    print(f"\n전체 데이터 수: {len(df):,}")
    print(f"유효 데이터 수: {len(valid_movies):,}")
    
    # NaN 값을 가진 행 제거
    valid_movies = valid_movies.dropna(subset=['vote_average', 'vote_count', 'budget', 'runtime', 'popularity'])
    print(f"NaN 제거 후 데이터 수: {len(valid_movies):,}")
    
    # 장르 처리
    print("\n=== 장르 처리 결과 ===")
    
    def extract_genres(genres_str):
        try:
            if pd.isna(genres_str):
                return []
            if isinstance(genres_str, str):
                # 쉼표로 구분된 문자열인 경우
                if ',' in genres_str and '[' not in genres_str:
                    return [g.strip() for g in genres_str.split(',')]
                # JSON 형식의 문자열인 경우
                try:
                    genres_list = json.loads(genres_str)
                    return [g['name'] for g in genres_list if isinstance(g, dict) and 'name' in g]
                except:
                    try:
                        genres_list = ast.literal_eval(genres_str)
                        if isinstance(genres_list, list):
                            return [g['name'] for g in genres_list if isinstance(g, dict) and 'name' in g]
                    except:
                        return []
            elif isinstance(genres_str, list):
                return [g['name'] for g in genres_str if isinstance(g, dict) and 'name' in g]
            return []
        except:
            return []

    # 장르 추출
    valid_movies['genres_list'] = valid_movies['genres'].apply(extract_genres)
    
    # 전체 고유 장르 추출
    all_genres = set()
    for genres in valid_movies['genres_list']:
        if genres:  # 빈 리스트가 아닌 경우에만 처리
            all_genres.update(genres)
    
    if not all_genres:
        all_genres = {'Unknown'}
        
    print(f"\n발견된 고유 장르 수: {len(all_genres)}")
    print("고유 장르 목록:", sorted(all_genres))
    
    # 장르별 원-핫 인코딩
    for genre in sorted(all_genres):
        valid_movies[f'genre_{genre}'] = valid_movies['genres_list'].apply(
            lambda x: 1 if genre in x else 0
        )
    
    # 장르 특성 리스트 생성
    genre_features = [f'genre_{genre}' for genre in sorted(all_genres)]
    
    # 기본 특성 엔지니어링
    valid_movies['vote_count_log'] = np.log1p(valid_movies['vote_count'])
    valid_movies['budget_log'] = np.log1p(valid_movies['budget'])
    valid_movies['popularity_log'] = np.log1p(valid_movies['popularity'])
    
    # 개봉 연도 처리 및 years_since_release 계산
    valid_movies['release_year'] = pd.to_datetime(valid_movies['release_date']).dt.year
    years_since_release = np.maximum(2024 - valid_movies['release_year'], 1)
    valid_movies['votes_per_year'] = valid_movies['vote_count'] / years_since_release
    
    # 새로운 복합 특성 추가
    valid_movies['budget_per_minute'] = valid_movies['budget'] / np.maximum(valid_movies['runtime'], 1)
    valid_movies['engagement_rate'] = valid_movies['vote_count'] / np.maximum(valid_movies['popularity'], 1)
    valid_movies['budget_efficiency'] = valid_movies['vote_average'] * valid_movies['budget_log']
    valid_movies['popularity_per_budget'] = valid_movies['popularity'] / np.maximum(valid_movies['budget_log'], 1)
    valid_movies['vote_budget_ratio'] = valid_movies['vote_count_log'] / np.maximum(valid_movies['budget_log'], 1)
    valid_movies['popularity_growth'] = valid_movies['popularity'] / np.maximum(years_since_release, 1)
    
    # 통계 출력
    base_numeric_columns = ['vote_average', 'vote_count', 'budget', 'popularity', 'runtime']
    print("\n=== 2. 필터링된 데이터 통계 ===\n")
    for col in base_numeric_columns:
        print(f"{col} 분포:")
        print(valid_movies[col].describe())
        print()
    
    # 목표 변수 정의 (성공 기준)
    vote_threshold = valid_movies['vote_average'].quantile(0.7)
    count_threshold = valid_movies['vote_count'].quantile(0.7)
    popularity_threshold = valid_movies['popularity'].quantile(0.7)
    
    print("\n=== 3. 목표변수 정보 ===")
    print("성공 기준:")
    print(f"- 평점 임계값: {vote_threshold:.2f}")
    print(f"- 투표수 임계값: {count_threshold:.0f}")
    print(f"- 인기도 임계값: {popularity_threshold:.2f}")
    
    # 성공 여부 정의
    valid_movies['is_successful'] = (
        (valid_movies['vote_average'] >= vote_threshold) &
        (valid_movies['vote_count'] >= count_threshold) &
        (valid_movies['popularity'] >= popularity_threshold)
    ).astype(int)
    
    # 클래스 분포 출력
    class_distribution = valid_movies['is_successful'].value_counts(normalize=True)
    print("\n클래스 분포:")
    print(class_distribution)
    
    # 특성 선택
    numeric_features = [
        'runtime', 'vote_count_log', 'budget_log', 'popularity_log',
        'vote_count', 'budget', 'budget_per_minute', 'votes_per_year',
        'engagement_rate', 'budget_efficiency', 'popularity_per_budget',
        'vote_budget_ratio', 'popularity_growth'
    ]
    
    # 범주형 특성 처리
    le = LabelEncoder()
    valid_movies['original_language'] = le.fit_transform(valid_movies['original_language'].fillna('unknown'))
    
    # 최종 특성 목록
    features = numeric_features + ['release_year', 'original_language'] + genre_features
    
    # 데이터 분할
    X = valid_movies[features]
    y = valid_movies['is_successful']
    
    # 결측치 확인 및 제거
    X = X.replace([np.inf, -np.inf], np.nan)
    X = X.dropna()
    y = y[X.index]
    
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, test_size=0.2, random_state=42, stratify=y
    )
    
    print("\n=== 4. 데이터 분할 결과 ===")
    print(f"훈련 데이터: {X_train.shape}")
    print(f"테스트 데이터: {X_test.shape}")
    
    # SMOTE-Tomek 적용
    smote_tomek = SMOTETomek(random_state=42)
    X_train_balanced, y_train_balanced = smote_tomek.fit_resample(X_train, y_train)
    
    print("\nSMOTETomek 적용 후 클래스 분포:")
    print(pd.Series(y_train_balanced).value_counts(normalize=True))
    
    # 모델 학습
    rf_model = RandomForestClassifier(
        n_estimators=800,
        max_depth=10,
        min_samples_split=20,
        min_samples_leaf=8,
        class_weight={0: 1, 1: 4},
        random_state=42,
        n_jobs=-1
    )
    
    print("\n=== 5. 모델 훈련 시작 ===")
    rf_model.fit(X_train_balanced, y_train_balanced)
    
    # 성능 평가
    prediction_threshold = 0.45
    y_pred_proba = rf_model.predict_proba(X_test)[:, 1]
    y_pred = (y_pred_proba >= prediction_threshold).astype(int)
    
    print("\n=== 6. 모델 성능 평가 ===")
    print(classification_report(y_test, y_pred))
    
    # 특성 중요도 계산 및 출력
    print("\n=== 7. 특성 중요도 ===")
    
    # 기본 특성들의 중요도 계산
    base_importances = rf_model.feature_importances_[:-len(genre_features)]  # 장르 특성 제외
    genre_total_importance = rf_model.feature_importances_[-len(genre_features):].sum()
    
    # 모든 특성의 중요도를 하나의 배열로 결합
    all_importances = np.concatenate([base_importances, [genre_total_importance]])
    
    # 전체 특성 중요도를 백분율로 정규화
    all_importances_normalized = all_importances / all_importances.sum() * 100
    
    # 기본 특성과 통합된 장르 특성의 중요도를 데이터프레임으로 변환
    importance = pd.DataFrame({
        'feature': numeric_features + ['release_year', 'original_language'] + ['genres_combined'],
        'importance': all_importances_normalized
    })
    
    print("\n전체 특성 중요도 (백분율):")
    print(importance.sort_values('importance', ascending=False).to_string(float_format=lambda x: '{:.2f}%'.format(x)))
    
    # 개별 장르 중요도 순위도 별도로 출력
    print("\n=== 장르별 중요도 순위 (백분율) ===")
    genre_importances = rf_model.feature_importances_[-len(genre_features):]
    # 장르 중요도의 합이 100%가 되도록 정규화
    genre_importances_normalized = genre_importances / genre_importances.sum() * 100
    
    genre_importance_detail = pd.DataFrame({
        'genre': [g[6:] for g in genre_features],  # 'genre_' 접두어 제거
        'importance': genre_importances_normalized
    })
    print(genre_importance_detail.sort_values('importance', ascending=False).to_string(float_format=lambda x: '{:.2f}%'.format(x)))
    
    # 예시 예측
    print("\n=== 8. 예시 영화 성공 예측 ===")
    example_indices = np.random.randint(0, len(X_test), 5)
    
    for i, idx in enumerate(example_indices):
        # 특성 이름을 포함한 데이터프레임으로 변환하여 예측
        sample_data = pd.DataFrame([X_test.iloc[idx].values], columns=X_test.columns)
        pred_prob = rf_model.predict_proba(sample_data)[0][1]
        actual = y_test.iloc[idx]
        
        original_idx = X_test.index[idx]
        
        print(f"\n영화 {i+1}:")
        print(f"- 성공 확률: {pred_prob:.2%}")
        print(f"- 예측 결과: {'성공' if pred_prob >= prediction_threshold else '실패'}")
        print(f"- 실제 결과: {'성공' if actual == 1 else '실패'}")
        print("- 주요 지표:")
        print(f"  * 평점: {valid_movies.loc[original_idx, 'vote_average']:.1f}")
        print(f"  * 투표수: {valid_movies.loc[original_idx, 'vote_count']:.0f}")
        print(f"  * 상영시간: {valid_movies.loc[original_idx, 'runtime']:.0f}분")
        print(f"  * 예산: ${valid_movies.loc[original_idx, 'budget']:,.0f}")
        print(f"  * 분당 예산: ${valid_movies.loc[original_idx, 'budget_per_minute']:,.0f}")
        print(f"  * 연간 투표수: {valid_movies.loc[original_idx, 'votes_per_year']:.1f}")
        print(f"  * 인기도: {valid_movies.loc[original_idx, 'popularity']:.2f}")
        print(f"  * 참여율: {valid_movies.loc[original_idx, 'engagement_rate']:.2f}")
        print(f"  * 예산 효율성: {valid_movies.loc[original_idx, 'budget_efficiency']:.2f}")
        print(f"  * 투표-예산 비율: {valid_movies.loc[original_idx, 'vote_budget_ratio']:.2f}")
        print(f"  * 인기도 성장률: {valid_movies.loc[original_idx, 'popularity_growth']:.2f}")
        
        # 장르 출력
        movie_genres = []
        for genre in genre_features:
            if sample_data[genre].iloc[0] == 1:
                movie_genres.append(genre[6:])  # 'genre_' 접두어 제거
        
        genre_str = ', '.join(movie_genres) if movie_genres else 'Unknown'
        print("  * 장르:", genre_str)
    
    return rf_model, X_test, y_test, valid_movies, features, genre_features, le

def save_model_data(model, features, genre_features, le, file_prefix='movie_success'):
    # 1. joblib을 사용한 저장
    model_data = {
        'model': model,
        'features': features,
        'genre_features': genre_features,
        'label_encoder': le,
    }
    joblib.dump(model_data, f'{file_prefix}_model.joblib')
    print(f"모델이 '{file_prefix}_model.joblib'에 저장되었습니다.")
    
    # 2. pickle을 사용한 저장 (대체 방법)
    with open(f'{file_prefix}_model.pkl', 'wb') as f:
        pickle.dump(model_data, f)
    print(f"모델이 '{file_prefix}_model.pkl'에 저장되었습니다.")

def load_model_data(file_prefix='movie_success', use_joblib=True):
    try:
        if use_joblib:
            model_data = joblib.load(f'{file_prefix}_model.joblib')
        else:
            with open(f'{file_prefix}_model.pkl', 'rb') as f:
                model_data = pickle.load(f)
        
        print("모델을 성공적으로 불러왔습니다.")
        return model_data
    except Exception as e:
        print(f"모델 로딩 중 오류 발생: {str(e)}")
        return None

# 데이터 로드 및 전처리
def load_and_prepare_data(file_path):
    df = pd.read_csv(file_path)
    return df

# 실행
if __name__ == "__main__":
    file_path = 'TMDB_movie_dataset_v11.csv'
    df = load_and_prepare_data(file_path)
    model, X_test, y_test, valid_movies, features, genre_features, le = train_movie_success_predictor(df)
    
    # 모델 저장
    save_model_data(
        model=model,
        features=features,
        genre_features=genre_features,
        le=le,
        file_prefix='movie_success'
    )
    
    # 모델 불러오기 테스트
    loaded_model_data = load_model_data(file_prefix='movie_success', use_joblib=True)
    if loaded_model_data:
        print("저장된 특성 목록:", len(loaded_model_data['features']))
        print("저장된 장르 특성 목록:", len(loaded_model_data['genre_features']))

영화 성공 예측 모델 설명

가. 성공의 기준(출력 데이터) 정하기 : 세 가지 핵심 지표의 상위 30%(0.7 분위수)를 기준으로 결정하기로 함.
- 세 가지 핵심 지표 : 평점(10점 만점), 투표수(가령 10,000표 이상), 인기도(TMDB 상 해당 영화에 대한 관심도)
- 최종 판단 : 세 가지 모두 상위 30% 안에 들어야 성공(1), 하나라도 만족하지 못하면 실패(0)

# 성공 기준 임계값 설정 (각각 상위 30% 기준)
vote_threshold = valid_movies['vote_average'].quantile(0.7)    # 평균 평점 임계값
count_threshold = valid_movies['vote_count'].quantile(0.7)     # 투표 수 임계값
popularity_threshold = valid_movies['popularity'].quantile(0.7) # 인기도 임계값

# 성공 여부 정의
valid_movies['is_successful'] = (
    (valid_movies['vote_average'] >= vote_threshold) &    # 평점이 임계값 이상이고
    (valid_movies['vote_count'] >= count_threshold) &     # 투표 수가 임계값 이상이고
    (valid_movies['popularity'] >= popularity_threshold)   # 인기도가 임계값 이상이면
).astype(int)  # True는 1(성공), False는 0(실패)로 변환

나. 입력 데이터(X / 총 16가지 스펙) : 입력 데이터를 보고, 성공 여부 매칭하여 학습 --> 학습 후에는 정확도 평가
- 기본 수치 : 상영시간(분), 투표수, 예산(달러)
- 로그 변환 수치 : 투표수(로그변환), 예산(로그변환), 인기도(로그변환)
- 시간 관련 복합 특성 : 영화 분당 투입 예산, 연간 투표수
- 효율성/비율 특성 : 참여율(투표수/인기도), 예산효율성(평점 * 예산로그), 예산 대비 인기도,
예산 대비 투표 비율, 시간 경과에 따른 인기도 성장률
- 범주형 특성 : 개봉 연도, 언어, 장르

# 입력 특성들
numeric_features = [
    'runtime',           # 상영 시간
    'vote_count_log',    # 투표수(로그변환)
    'budget_log',        # 예산(로그변환)
    'popularity_log',    # 인기도(로그변환)
    'vote_count',        # 투표수
    'budget',            # 예산
    'budget_per_minute', # 분당 예산
    'votes_per_year',    # 연간 투표수
    'engagement_rate',   # 참여율
    'budget_efficiency', # 예산 효율성
    'popularity_per_budget', # 예산 대비 인기도
    'vote_budget_ratio', # 예산 대비 투표 비율
    'popularity_growth'  # 인기도 성장률
]

# 추가 입력 특성들
features = numeric_features + ['release_year', 'original_language'] + genre_features

다. 학습과정
마치 영화 전문가가 과거의 영화들을 보면서 "이런 스펙을 가진 영화는 성공했고, 저런 스펙을 가진 영화는 실패했다"를 학습하는 것과 같음

1) 데이터 분할 : 80%는 학습용, 20%는 평가용

X_train, X_test, y_train, y_test = train_test_split(
    X,  # 입력 특성들
    y,  # 출력값(성공/실패)
    test_size=0.2,  # 20%는 테스트용
    random_state=42,
    stratify=y  # 성공/실패 비율 유지
)

2) 모델학습
* 랜덤 포레스트(Ramdom Forest) : 머신러닝에서 널리 사용되는 앙상블 학습 방법. 여러 명의 전문가(결정 트리)에게 의견을 물어본 후, 그들의 의견을 종합하여 최종 결정을 내리는 것과 유사. 데이터의 무작위 부분집합(bootstrap sample)으로 학습.

rf_model = RandomForestClassifier(
    n_estimators=800,
    max_depth=10,
    min_samples_split=20,
    min_samples_leaf=8,
    class_weight={0: 1, 1: 4},
    random_state=42,
    n_jobs=-1
)

# 모델이 입력(X)을 보고 출력(y)을 예측하도록 학습
rf_model.fit(X_train_balanced, y_train_balanced)

라. 예측과정(정확도 테스트)
새로운 영화의 스펙을 보고 "이 영화는 성공할 것 같다/실패할 것 같다"를 판단
> 예측 과정 예시

# 새로운 영화 데이터
new_movie = {
    "runtime": 120,
    "budget": 1000000,
    "popularity": 10.5,
    "vote_count": 1000,
    "vote_average": 7.5,
    "release_year": 2024,
    "original_language": "en",
    "genres": ["Action", "Adventure"]
}

# 1. 입력 데이터 전처리
X_new = preprocess_features(new_movie)

# 2. 성공 확률 예측
prediction_proba = model.predict_proba(X_new)[0][1]  # 0~1 사이의 확률값

# 3. 최종 예측 (성공/실패)
prediction = "성공" if prediction_proba >= 0.45 else "실패"

출력 결과

=== 1. 데이터 전처리 시작 ===

전체 데이터 수: 1,134,752
유효 데이터 수: 19,683
NaN 제거 후 데이터 수: 19,683

=== 장르 처리 결과 ===

발견된 고유 장르 수: 19
고유 장르 목록: ['Action', 'Adventure', 'Animation', 'Comedy', 'Crime', 'Documentary', 'Drama', 'Family', 'Fantasy', 'History', 'Horror', 'Music', 'Mystery', 'Romance', 'Science Fiction', 'TV Movie', 'Thriller', 'War', 'Western']

=== 2. 필터링된 데이터 통계 ===

vote_average 분포:
count    19683.000000
mean         6.045901
std          1.523057
min          0.500000
25%          5.271500
50%          6.160000
75%          6.945000
max         10.000000
Name: vote_average, dtype: float64

vote_count 분포:
count    19683.000000
mean       816.989788
std       2252.458381
min          1.000000
25%          7.000000
50%         58.000000
75%        510.000000
max      34495.000000
Name: vote_count, dtype: float64

budget 분포:
count    1.968300e+04
mean     1.379390e+07
std      2.965956e+07
min      1.050000e+03
25%      4.000000e+05
50%      2.800000e+06
75%      1.300000e+07
max      4.600000e+08
Name: budget, dtype: float64

popularity 분포:
count    19683.000000
mean        12.318513
std         46.331331
min          0.600000
25%          1.724000
50%          6.290000
75%         14.336500
max       2994.357000
Name: popularity, dtype: float64

runtime 분포:
count    19683.000000
mean       102.188284
std         23.448058
min         41.000000
25%         89.000000
50%         98.000000
75%        113.000000
max        283.000000
Name: runtime, dtype: float64


=== 3. 목표변수 정보 ===
성공 기준:
- 평점 임계값: 6.79
- 투표수 임계값: 324
- 인기도 임계값: 12.65

클래스 분포:
is_successful
0    0.887416
1    0.112584
Name: proportion, dtype: float64

=== 4. 데이터 분할 결과 ===
훈련 데이터: (15652, 34)
테스트 데이터: (3913, 34)

SMOTETomek 적용 후 클래스 분포:
is_successful
0    0.5
1    0.5
Name: proportion, dtype: float64

=== 5. 모델 훈련 시작 ===

=== 6. 모델 성능 평가 ===
              precision    recall  f1-score   support

           0       1.00      0.93      0.96      3470
           1       0.64      1.00      0.78       443

    accuracy                           0.94      3913
   macro avg       0.82      0.96      0.87      3913
weighted avg       0.96      0.94      0.94      3913


=== 7. 특성 중요도 ===

전체 특성 중요도 (백분율):
                  feature  importance
1          vote_count_log      15.81%
4              vote_count      15.76%
3          popularity_log      15.38%
10  popularity_per_budget      12.23%
11      vote_budget_ratio       9.99%
9       budget_efficiency       6.79%
8         engagement_rate       6.47%
7          votes_per_year       5.66%
12      popularity_growth       2.94%
2              budget_log       2.05%
5                  budget       2.00%
15        genres_combined       1.90%
6       budget_per_minute       1.65%
0                 runtime       0.88%
13           release_year       0.37%
14      original_language       0.13%

=== 장르별 중요도 순위 (백분율) ===
              genre  importance
10           Horror      28.60%
3            Comedy      20.93%
16         Thriller      16.97%
0            Action      11.05%
4             Crime       4.72%
14  Science Fiction       4.10%
12          Mystery       3.86%
13          Romance       2.16%
1         Adventure       2.01%
8           Fantasy       1.91%
7            Family       1.38%
6             Drama       0.79%
11            Music       0.60%
2         Animation       0.29%
5       Documentary       0.24%
9           History       0.17%
15         TV Movie       0.16%
17              War       0.05%
18          Western       0.01%

=== 8. 예시 영화 성공 예측 ===

영화 1:
- 성공 확률: 0.02%
- 예측 결과: 실패
- 실제 결과: 실패
- 주요 지표:
  * 평점: 5.9
  * 투표수: 164
  * 상영시간: 107분
  * 예산: $18,000,000
  * 분당 예산: $168,224
  * 연간 투표수: 4.3
  * 인기도: 8.97
  * 참여율: 18.29
  * 예산 효율성: 97.80
  * 투표-예산 비율: 0.31
  * 인기도 성장률: 0.24
  * 장르: Adventure, Family, Science Fiction

영화 2:
- 성공 확률: 80.49%
- 예측 결과: 성공
- 실제 결과: 실패
- 주요 지표:
  * 평점: 6.7
  * 투표수: 750
  * 상영시간: 125분
  * 예산: $31,000,000
  * 분당 예산: $248,000
  * 연간 투표수: 41.7
  * 인기도: 19.08
  * 참여율: 39.31
  * 예산 효율성: 115.31
  * 투표-예산 비율: 0.38
  * 인기도 성장률: 1.06
  * 장르: Action, Adventure, Drama, Romance

영화 3:
- 성공 확률: 0.00%
- 예측 결과: 실패
- 실제 결과: 실패
- 주요 지표:
  * 평점: 6.9
  * 투표수: 7
  * 상영시간: 148분
  * 예산: $390,000
  * 분당 예산: $2,635
  * 연간 투표수: 0.7
  * 인기도: 1.85
  * 참여율: 3.78
  * 예산 효율성: 88.28
  * 투표-예산 비율: 0.16
  * 인기도 성장률: 0.19
  * 장르: Unknown

영화 4:
- 성공 확률: 1.01%
- 예측 결과: 실패
- 실제 결과: 실패
- 주요 지표:
  * 평점: 7.0
  * 투표수: 39
  * 상영시간: 100분
  * 예산: $6,750,000
  * 분당 예산: $67,500
  * 연간 투표수: 39.0
  * 인기도: 12.46
  * 참여율: 3.13
  * 예산 효율성: 110.28
  * 투표-예산 비율: 0.23
  * 인기도 성장률: 12.46
  * 장르: Drama, History, Romance

영화 5:
- 성공 확률: 0.00%
- 예측 결과: 실패
- 실제 결과: 실패
- 주요 지표:
  * 평점: 5.9
  * 투표수: 28
  * 상영시간: 121분
  * 예산: $9,000,000
  * 분당 예산: $74,380
  * 연간 투표수: 0.6
  * 인기도: 6.67
  * 참여율: 4.20
  * 예산 효율성: 94.07
  * 투표-예산 비율: 0.21
  * 인기도 성장률: 0.14
  * 장르: Unknown
모델이 'movie_success_model.joblib'에 저장되었습니다.
모델이 'movie_success_model.pkl'에 저장되었습니다.
모델을 성공적으로 불러왔습니다.
저장된 특성 목록: 34
저장된 장르 특성 목록: 19

결과와 배운 점

모델 성능 평가 결과
가. 정밀도(Precision) : 성공이라 예측한 것 중 실제 성공한 비율
1) 실패 예측 : 100% 정확, 성공 예측 : 64% 정확 --> 실패는 매우 정확하게 예측
나. 재현율(Recall) : 실제 성공한 것 중 성공이라고 예측한 비율
1) 실패 감지 : 93% 포착, 성공 감지 : 100% 포착 --> 성공 가능성이 있는 모든 케이스 포착
다. F1-Score : 정밀도와 재현율의 조화 평균(전반적 예측 성능을 하나의 숫자로 표현)
1) 실패예측 : 0.96(매우 좋음), 성공 예측 : 0.78(괜찮음)
라. 지원(Support) : 테스트한 영화 수
1) 실패 사례 : 3,470개, 성공 사례 : 443개

마. 영화 성공의 주요 지표 : vote_count_log(15.81%), vote_count(15.76%), popularity_log(15.38%)
바. 성공한 영화 주요 장르 : 공포영화(28.60%), 코미디(20.93%), 스릴러(16.97%), 액션영화(11.05%)


배운 점
가. 개발 비전공자인 내가 Cursor로 파이썬 코딩을 할 수 있다는 자신감을 얻음
나. 덕분에 다른 머신러닝 모델도 구현해 볼 수 있는 기회가 열림

향후 계획
가. 머신러닝 모델을 UI와 연결하여 결과를 써먹을 수 있도록 하고 싶음

도움 받은 글 (옵션)

‎​(내용 입력)

10
4개의 답글

👉 이 게시글도 읽어보세요