SIDE PROJECT/AUTOFINDER

실시간 AI 추천 시스템 구현

sooindev 2025. 6. 3. 20:29
728x90

현재 진행 중인 사이드프로젝트, AutoFinder는 사용자의 즐겨찾기 패턴을 실시간으로 학습하여 개인화된 차량을 추천하는 AI 기반 중고차 추천 플랫폼이다.

해당 프로젝트의 핵심 기능인 AI 추천 시스템을 구현하였다.
이에 대해 포스팅하려고 한다.

 

 

 

핵심 특징


실시간 학습: 사용자가 즐겨찾기를 추가/삭제할 때마다 즉시 AI 모델 재학습
하이브리드 AI: 머신러닝 + 룰기반 추천의 조합
스마트 폴백: AI 서비스 장애 시 자동으로 대안 추천 제공
개인화: 사용자별 행동 패턴 분석을 통한 맞춤형 추천

 

 

 

3단계 추천 전략


해당 로직을 구현하기 위해 우선 파이썬을 이용하여 AI 서버를 새로 구축하였다.
프로젝트는 2단계 추천 전략을 사용한다.

 

Level 1: 머신러닝 추천 (MLRecommendationService)

public List<RecommendedCar> getSmartRecommendations(Long userId, int topK) {
    // 실제로는 머신러닝 기반 추천
    boolean shouldUseML = shouldUseDeepLearningForUser(userId); // 네이밍은 딥러닝이지만 실제는 ML

    if (shouldUseML) {
        return attemptMLRecommendation(userId, topK); // Python의 GradientBoostingRegressor 사용
    } else {
        return getLegacyRecommendations(userId, topK);
    }
}

특징

  • A/B 테스트 지원으로 사용자별 다른 알고리즘 적용
  • 화이트리스트 기반 타겟 사용자 설정 가능
  • 자동 폴백 메커니즘
  • GradientBoostingRegressor 기반
  • 후보 차량과 함께 정교한 추천

 

Level 2: 룰 기반 추천 (SimilarCarService)

private double calculateSimilarityScore(Car target, Car other) {
    double score = 0;

    // 모델 유사도 (40%)
    double modelScore = calculateModelSimilarity(target.getModel(), other.getModel());
    score += modelScore * 0.4;

    // 가격 유사도 (30%)
    double priceScore = calculatePriceSimilarity(target.getPrice(), other.getPrice());
    score += priceScore * 0.3;

    // 연식 유사도 (20%)
    double yearScore = calculateYearSimilarity(target.getYear(), other.getYear());
    score += yearScore * 0.2;

    // 주행거리 유사도 (10%)
    double mileageScore = calculateMileageSimilarity(target.getMileage(), other.getMileage());
    score += mileageScore * 0.1;

    return score;
}

특징

  • 가중치 기반 유사도 계산
  • ML 실패 시 폴백 메커니즘

 

 

실시간 학습 기능


가장 핵심인 부분은 실시간 학습 기능이다.

// FavoriteService.java
public void addFavorite(Long userId, Long carId) {
    // 즐겨찾기 저장
    favoriteRepository.save(favorite);

    // 즉시 AI 모델 재학습 트리거
    triggerRealTimeModelUpdate(totalFavorites, userFavorites, "즐겨찾기 추가");
}

private void triggerRealTimeModelUpdate(long totalFavorites, long userFavorites, String action) {
    if (totalFavorites >= 1) { // 최소 1개부터 학습 시작
        log.info("{}로 인한 즉시 AI 모델 재학습 시작...", action);
        aiRecommendationService.trainAIModelAsync(); // 비동기 학습

        if (totalFavorites == 1) {
            log.info("첫 번째 즐겨찾기! AI 개인화 학습이 시작됩니다.");
        }
    }
}

실시간 학습의 장점

  • 사용자 행동에 즉시 반응하는 추천
  • 새로운 선호도 즉시 반영
  • 시스템 전체의 추천 품질 지속적 향상

 

 

UserBehaviorService의 고도화된 분석


private Map<Long, Double> calculateCarInterestScores(List<UserBehavior> behaviors) {
    // 행동별 가중치 적용
    Map<String, Double> actionWeights = Map.of(
        "VIEW", 1.0,           // 조회
        "CLICK", 1.5,          // 클릭  
        "DETAIL_VIEW", 2.0,    // 상세보기
        "INQUIRY", 4.0,        // 문의
        "BOOKMARK", 3.0,       // 북마크
        "FAVORITE", 5.0,       // 즐겨찾기
        "CONTACT", 6.0         // 연락하기
    );

    // 차량별 관심도 점수 계산
    Map<Long, Double> interestScores = new HashMap<>();
    for (Map.Entry<Long, Map<String, Integer>> entry : carActionCounts.entrySet()) {
        double score = 0.0;
        for (Map.Entry<String, Integer> actionEntry : entry.getValue().entrySet()) {
            String actionType = actionEntry.getKey();
            int count = actionEntry.getValue();
            double weight = actionWeights.getOrDefault(actionType, 1.0);
            score += count * weight;
        }
        interestScores.put(entry.getKey(), Math.min(score / 10.0, 10.0));
    }

    return interestScores;
}

다차원 분석 지표

  • 참여도 점수: 행동 다양성 + 빈도 + 일관성
  • 다양성 점수: 관심 차량의 범위 측정
  • 세션 분석: 평균 세션 지속 시간 계산
  • 시간대별 패턴: 사용자 활동 시간 분석

 

 

Python AI 서비스


머신러닝 모델 (ml_recommender.py)

class MLCarRecommender:
    def __init__(self):
        self.model = GradientBoostingRegressor(
            n_estimators=100,
            learning_rate=0.1,
            max_depth=6,
            random_state=42
        )
        self.scaler = StandardScaler()
        self.imputer = SimpleImputer(strategy='median')

    def prepare_training_data(self, cars_df, favorites_df):
        # 긍정적 샘플 (즐겨찾기한 차량) - 평점 4-5
        positive_samples = []
        for _, fav in favorites_df.iterrows():
            car_data = cars_df_clean[cars_df_clean['id'] == fav['car_id']]
            if not car_data.empty:
                sample = car_data.iloc[0].copy()
                sample['user_id'] = fav['user_id']
                sample['rating'] = np.random.uniform(4.0, 5.0)
                positive_samples.append(sample)

        # 부정적 샘플 (랜덤 차량) - 평점 1-3
        negative_samples = []
        # ... 부정적 샘플 생성 로직

        return pd.DataFrame(positive_samples + negative_samples)

 

 

특성 공학 (Feature Engineering)

def engineer_features(self, df):
    features_df = df.copy()

    # 파생 변수 생성
    features_df['year_numeric'] = features_df['year'].apply(self._extract_year)
    features_df['age'] = 2024 - features_df['year_numeric']
    features_df['brand'] = features_df['model'].apply(self._extract_brand)

    # 범주형 변수 인코딩
    categorical_columns = ['brand', 'fuel', 'region', 'carType']
    for col in categorical_columns:
        if col not in self.label_encoders:
            self.label_encoders[col] = LabelEncoder()
            features_df[f'{col}_encoded'] = self.label_encoders[col].fit_transform(features_df[col])

    # 결측값 처리
    X_filled = pd.DataFrame(
        self.imputer.fit_transform(X),
        columns=X.columns,
        index=X.index
    )

    return X_filled

 

 

 

시스템 모니터링 & 관리


실시간 상태 모니터링 (SystemStatusController)

@GetMapping("/status")
public ResponseEntity<Map<String, Object>> getSystemStatus() {
    Map<String, Object> status = new HashMap<>();

    // 기본 데이터 현황
    status.put("dataStatus", Map.of(
        "totalCars", carRepository.count(),
        "totalUsers", userRepository.count()
    ));

    // AI 시스템 현황
    Map<String, Object> aiStatus = aiRecommendationService.getAISystemStatus();
    status.put("aiStatus", aiStatus);

    // 실시간 학습 상태
    Map<String, Object> realTimeStatus = aiRecommendationService.getRealTimeTrainingStatus();
    status.put("realTimeTraining", realTimeStatus);

    // 추천 준비 상태
    boolean systemReady = totalCars > 0;
    boolean aiReady = (Boolean) aiStatus.get("aiModelTrained");
    status.put("recommendationLevel", getRecommendationLevel(systemReady, aiReady, personalizedReady));

    return ResponseEntity.ok(status);
}

 

 

캐시 관리 전략

private static class CachedRecommendation {
    private final List<RecommendedCar> recommendations;
    private final LocalDateTime timestamp;
    private final int favoriteCount;
    private final Set<Long> favoriteCarIds;

    public boolean isExpired() {
        return timestamp.isBefore(LocalDateTime.now().minus(CACHE_EXPIRY_MINUTES, ChronoUnit.MINUTES));
    }
}

캐시 무효화 조건

  • 사용자 즐겨찾기 변경
  • AI 모델 재학습 완료
  • 시간 기반 만료 (3분)

 

성능 최적화



1. 비동기 처리

@Async
public void recordUserAction(Long userId, Long carId, String actionType, Object value) {
    // 비동기로 사용자 행동 기록
}

@Async
public void trainAIModelAsync() {
    // 비동기로 AI 모델 학습
}

 

 

2. 데이터베이스 최적화

-- 성능 최적화 인덱스
INDEX idx_user_timestamp (user_id, timestamp),
INDEX idx_car_timestamp (car_id, timestamp),
INDEX idx_action_type (action_type),
INDEX idx_user_car (user_id, car_id)

 

 

3. 스마트 후보 생성

private List<Car> generateOptimizedCandidateCars(Long userId) {
    return carRepository.findAll().stream()
        .filter(car -> !favoriteCarIds.contains(car.getId()))
        .filter(this::isValidCarData)
        .filter(this::isRecentOrPopular) // 최근 30일 또는 인기 브랜드
        .sorted((a, b) -> b.getCreatedAt().compareTo(a.getCreatedAt()))
        .limit(1000)
        .collect(Collectors.toList());
}

 

 

 

추천 품질 향상 기법


1. 다양성 보장

private List<RecommendedCar> improveDiversity(List<RecommendedCar> recommendations, FavoriteAnalysis analysis) {
    List<RecommendedCar> diversifiedList = new ArrayList<>();
    Set<String> usedBrands = new HashSet<>();
    Set<String> usedPriceRanges = new HashSet<>();

    for (RecommendedCar rec : recommendations) {
        String brand = extractBrand(rec.getCar().getModel());
        String priceRange = getPriceRange(rec.getCar().getPrice());

        if (!usedBrands.contains(brand) || !usedPriceRanges.contains(priceRange)) {
            diversifiedList.add(rec);
            usedBrands.add(brand);
            usedPriceRanges.add(priceRange);
        }
    }

    return diversifiedList;
}

 

 

2. 개인화 분석

private FavoriteAnalysis analyzeFavoritePatterns(List<Favorite> favorites) {
    // 가격 패턴 분석
    double avgPrice = favorites.stream()
        .mapToLong(f -> f.getCar().getPrice())
        .average().orElse(0);

    // 브랜드 선호도 분석
    Map<String, Long> brandPreferences = favorites.stream()
        .collect(Collectors.groupingBy(
            f -> extractBrand(f.getCar().getModel()),
            Collectors.counting()
        ));

    // 추천 신뢰도 계산
    double confidence = calculateRecommendationConfidence(analysis, favorites.size());

    return analysis;
}

 

 

 

프로젝트의 핵심 포인트


실시간 학습: 사용자가 즐겨찾기를 추가/삭제할 때마다 즉시 AI 모델이 재학습되는 시스템
2단계 하이브리드 추천: 머신러닝 → 룰기반 순서의 폴백 메커니즘
포괄적 행동 분석: 단순 클릭이 아닌 가중치 기반 다차원 사용자 행동 분석
스마트 캐시 관리: 즐겨찾기 변경 시 즉시 캐시 무효화로 최신 추천 제공

 

 

 

보완점


딥러닝은 아직 구현되지 않은 상태이다.
코드를 보면
MLRecommendationService의 메소드명은 getDeepLearningRecommendations이지만 실제로는 파이썬의 GradientBoostingRegressor를 호출한다.
진짜 딥러닝(신경망) 모델은 없다.

추후 AI 성능을 향상시키기 위해 딥러닝 모델을 탑재할 수도 있을 것 같다.
이는 더 고민을 해봐야하겠지만..

 

 

 

결론


아직 추가되지 않은 기능이 많다. 우선 현재 차량 비교하기 기능을 넣고 있고, 추후 다른 기능들도 차례차례 추가할 예정이다.
아무래도 수정하기 용이한 이메일 인증 로직부터 넣을 것 같다.

이번 추천 시스템을 구현하면서 한가지 깨달은 점이 있다면 하이브리드 형식의 추천 시스템이 꽤나 많이 사용되고 있다는 사실을 알았다.

아무래도 머신러닝/딥러닝 기반의 시스템이 장애를 일으켰을 때, 이를 대처할만한 대체제가 있어야하기 때문일 것이다.

딥러닝까지는 아니어도 머신러닝 기반 AI 추천 시스템을 넣은 것에 대해 매우 만족하고 있다.
AI 관련한 지식은 많이 부족하지만, 현재로서는 서버의 백엔드쪽에 집중을 하면서 공부를 하고 있기 때문에, 추후 파이썬을 활용한 AI 관련 로직들도 공부를 해보고 싶다.