데이터 분석 Data Analytics/프로그래머스 데이터분석 데브코스 2기

[TIL] 데이터분석 데브코스 65일차 - 추천 시스템/콘텐츠 기반 필터링(Content-based Filtering)/협업 필터링(Collaborative Filtering)

상급닌자연습생 2024. 5. 21. 16:05

추천 시스템

: 사용자의 선호도 및 과거 행동을 기반으로 사용자가 관심을 가질 만한 정보(상품, 서비스 등)를 필터링해서 제공하는 기법

 

 

기대 효과

  • 정보 과부하 문제 해결
    • 수많은 옵션 중 가장 관련도 높은 항목을 선택
  • 맞춤형 경험 제공
    • 사용자 기호 반영
    • 사용자의 충성도 및 만족도 증가
  • 비즈니스 가치 생성
    • 구매 유도
    • 사용자 참여도 증가
    • 데이터 확보

※ 사용자가 유입되었다가 이탈될 확률이 적을수록 해당 서비스의 가치는 높게 평가된다.

 

 

 

추천 시스템 알고리즘

📌 필터링(Filtering) = 수많은 데이터 중 나에게 맞는 것을 찾아준다.

 

1. 콘텐츠 기반 필터링 (Content-based Filtering)

  • 사용자가 관심을 보였던 아이템의 특성을 분석해서 이와 유사한 특성을 갖는 다른 아이템 추천
  • ex. 내가 많이 본 배우를 기반으로 영화 추천

 

 

2. 협업 필터링 (Collaborative Filtering)

= 나랑 비슷한 사람이 좋아하는 것을 기반으로 추천

  • 사용자 간의 유사도를 기반으로 추천
  • ex. 나의 취향과 비슷한 것을 좋아하는 사람 B가 선호하는 아이템을 나한테 추천

 

 

3. 하이브리드 추천 시스템 

= 콘텐츠 기반 필터링의 장점 + 협업 필터링의 장점

  • 사용자 개인과 사용자 그룹 패턴을 분석해서 맞춤형 추천

 

 

 

 

 

 

추천 시스템 활용 사례

1. 유튜브 영상 추천

  • 콘텐츠 기반 필터링 추천 시스템 적용 : 내가 주로 보는 영상/선호도/상호작용을 기반으로 비슷한 영상 추천
  • 협업 필터링 추천 시스템 적용 : 내가 보는 영상을 좋아하는 다른 그룹이 보는 영상 추천
  • 기대 효과 : 
    • 사용자 : 관심있는 정보 습득, 인기 콘텐츠 및 최신 트렌드 확인
    • 유튜브 : 사용자 참여도 향상, 매출 증대

 

2. 맞춤형 건강 검진 항목 추천

  • 콘텐츠 기반 필터링 추천 시스템 적용 : 사용자 건강 이력/이전 질병/생활 습관 데이터 기반 맞춤 건강 검진 항목 추천
  • 협업 필터링 추천 시스템 적용 : 인구통계학적 정보(나이/성별 등)와 건강 상태를 공유하는 다른 사용자 그룹의 데이터를 활용하여 추천
  • 기대 효과 : 
    • 사용자 : 개인 건강 상태에 적합한 검진 항목 식별, 효율적 건강 관리 가능, 조기 진단 및 예방 가능, 장기적 의료 비용 절감
    • 병원 : 정밀 검진 추천으로 환자 만족도 및 건강 결과 개선, 병원 서비스 품질 및 효율성 향상

 

3. 음식 배달 서비스 추천

  • 콘텐츠 기반 필터링 추천 시스템 적용 : 과거 주문 음식/선호 음식 재료/검색 히스토리 기반으로 비슷한 특성을 가진 식당/메뉴 추천
  • 협업 필터링 추천 시스템 적용 : 사용자 정보와 유사한 다른 사용자들이 선호하는 식당/메뉴 추천
  • 기대 효과 : 
    • 사용자 : 선택 시간 감소
    • 음식 배달 서비스 기업 : 사용자 참여도/충성도 향상, 매출 증대, 마케팅 전략 수립, 신메뉴 개발, 서비스 개선

 

 

 

 

 

 

 

콘텐츠 기반 필터링 (Contents-based Filtering)

= 내가 좋아했던 것을 기반으로 추천

 

 

프로세스

1단계. 아이템 프로파일 구성

📌 프로파일

: 특정 아이템을 설명하는 특성들의 집합

  • 풀어야 하는 문제에 따라 다르게 설정
  • 서로 다르 이종 데이터의 집합도 가능
  • 특성의 구성에 따라 추천 시스템의 성능이 달라짐

 

2단계. 각 특성 당 정보 추출

: 프로파일을 구성하는 특성을 숫자의 형태로 임베딩하는 과정

  • 모든 아이템에 동일하게 적용되어야 함

 

3단계. 아이템들의 프로파일 생성

: 사용자가 사용한 아이템의 프로파일과 사용하지 않은 아이템의 프로파일을 생성하는 과정

  • 동일한 임베딩 과정을 거쳐야 함

 

4단계. 유사도 계산 및 추천

: 사용자가 갖고 있는 임베딩 값과 다른 아이템들의 임베딩 값과의 유사도를 계산하고, 이를 기반으로 상위 N개 추천

  1. 사용자가 갖고 있는 각 아이템의 임베딩 값을 구함 (단순 평균, 가중 평균 등 적용) → 사용자의 최종 임베딩 값 도출
  2. 사용자의 최종 임베딩 값과 아직 경험하지 않은 아이템간의 유사도를 구함
  3. 유사도가 높은 아이템 (상위 N개) 추천

 

 

 

 

협업 필터링 (Collaborative Filtering)

: 사용자들 사이의 상호작용 데이터 혹은 선호도 패턴 데이터를 기반으로 추천

 

📌 상호작용 데이터
: 다른 사용자가 본인과 비슷하다는 것을 알 수 있는 데이터

 

 

 

1. 사용자 기반 (User-based) 협업 필터링

: 사용자들의 아이템 선호 데이터를 활용해서 비슷한 선호도 또는 행동 패턴을 보이는 사용자의 선호를 추천

 

 

프로세스

1단계. 상호 작용 데이터 준비

: 사용자로부터 얻은 데이터(ex. 평점, 클릭, 구매 등)을 준비

  • 기존 데이터가 없다면 사용 불가 : 콜드 스타트(Cold Start)

 

 

2단계. 사용자(행)-아이템(열) 상호 작용 테이블 생성

: 행 방향으로는 사용자를, 열 방향으로는 아이템을 배치해서 상호 작용 테이블 생성

  • 상호 작용이 없는 경우 '누락값'으로 표시
  • 협업 필터링은 이러한 빈칸(누락값)을 채우는 것이 목표

 

 

 

3단계. 사용자 간 유사도 추출

유사도 = 사용자들이 아이템에 대해 얼마나 비슷한 반응을 보였는가

  • 코사인 유사도, 피어슨 상관계수 등 활용

 

 

4단계. 유사도 기반 아이템 추천

: 유사도를 기반으로 '이웃'을 선정해서, 이웃이 높게 평가한 아이템을 추천

  • (위의 예시 사진에서) a의 이웃은 : c > d, e 
  • a가 본 영화가 아닌 것 중, 이웃이 높게 평가한 영화 : 영화E 

 

 

 

 

 

2. 아이템 기반 (Item-based) 협업 필터링

: 사용자들이 아이템을 평가한 데이터를 활용해서 특정 사용자가 사용한 아이템의 평가와 비슷한 아이템을 추천

 

프로세스

1단계. 상호 작용 데이터 준비

: 사용자들의 아이템 평가 데이터를 준비

 

 

2단계. 상호 작용 테이블에서 아이템 간 유사도 계산

: 수집된 상호 작용 데이터를 기반으로 아이템 유사도를 계산

(유사도 = 사용자들이 아이템에 대해 얼마나 비슷한 반응을 보였는가)

  • 상호 작용이 없는 경우 '누락값'으로 표시
  • 협업 필터링은 이러한 빈칸(누락값)을 채우는 것이 목표

  • 사용자 a가 아직 시청하지 않은 영화 : D, E

 

 

 

3단계. 추천 점수 계산

: 아이템 유사도와 사용자 상호 작용 데이터를 조합해 추천 점수 계산

  • 사용자가 평가한 아이템과 유사한 아이템들에 대한 가중 평균

  • 영화 D의 추천 점수 : 4 * 0.0507 + 4 * 0.0514 + 4 * 0.4105 = 0.0504
  • 영화 E의 추천 점수 : 4 * 0.6412 + 4 * 0.6118 + 4 * 0.2891 = 6.1684

 

 

4단계. 유사도 기반 아이템 추천

: 추천점수를 기반으로 추천 진행

  • (위의 예시 사진에서) a사람에게는 영화 E를 추천

 

 

 

 

 

 

 


컨텐츠 기반 필터링 실습

 

사용 데이터

`cellphones data.csv` : 2022년 미국에서 가장 인기 있었던 휴대폰에 대한 정보 (총 13개 컬럼)

`cellphones ratings.csv` : 총 99명의 사용자에게 10개의 휴대폰이 제공되어 구매 가능성을 1 ~ 10 사이로 표시

`cellphones users.csv` : 평가에 참여한 사용자들에 대한 정보 (ex. 나이, 성별, 직업) ← 사용 X

 

🔗 실습 링크 : https://www.kaggle.com/datasets/meirnizri/cellphones-recommendations

 

Cellphones Recommendations

Can be used for building a recommendation system model

www.kaggle.com

 

 

 

 

1단계. 데이터 불러오기

import pandas as pd

# 데이터 로드
data_path = 'cellphones data.csv'
data_df = pd.read_csv(data_path)

# 확인 : 총 33개의 휴대폰 종류가 존재함
data_df

data_df의 일부

 

 

 

 

2단계. 프로파일 구성 

1) 프로파일 구성

13개의 세부 정보 중, 분석에 사용할 특성을 선택한다.

도메인 지식과 구매 경험을 기반으로 프로파일 구성했다.

► 브랜드, 모델, OS, 메모리, RAM, 가격

 

 

3단계. 임베딩

  • 텍스트 데이터 : 브랜드, 모델, OS → TF-IDF 점수 적용
    ► 33개의 휴대폰 정보 중 텍스트 정보가 갖고 있는 상대적 중요도가 숫자로 매핑됨
  • 숫자 데이터 : 메모리, RAM, 가격 → MinMaxScaler 적용
    ► 각 피쳐값 중 가장 큰 값은 1, 가장 작은 값은 0으로 매핑

 

1) 전처리

TF-IDF를 적용하기 위해서는 입력으로 하나의 문자 덩어리가 들어가야 한다.

따라서 텍스트 데이터인 `brand`, `model`, `operating system`을 묶어 `feat_text`라는 하나의 문자 덩어리로 만들었다.

# 프로파일 구성을 위한 특성 선택
# 선택한 특성: 'brand', 'model', 'operating system', 'internal memory', 'RAM', 'price'

# 'brand', 'model', 'operating system'을 기반으로 문자열 생성 : TF-IDF에 입력으로 들어갈 예정
data_df['feat_text'] = data_df[['brand', 'model', 'operating system']].astype(str).agg(' '.join, axis=1)
# 'internal memory', 'RAM', 'price'은 숫자형 특성 : MinMax Scailing에 활용 예정
data_df['feat_num_IM'] = data_df['internal memory']
data_df['feat_num_RAM'] = data_df['RAM']
data_df['feat_num_PRC'] = data_df['price']

data_df.head()

새로운 컬럼 일부만 확인

 

 

 

2) 각 특성당 정보 추출

# Embedding으로 특성 표현
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.preprocessing import MinMaxScaler

# TF-IDF Vectorizer를 사용하여 특성 문자열을 벡터로 변환
tfidf_vectorizer = TfidfVectorizer()
tfidf_matrix = tfidf_vectorizer.fit_transform(data_df['feat_text'])

# MinMaxScaler를 활용해 수치형 데이터를 정규화
scaler = MinMaxScaler()
scaled_features = scaler.fit_transform(data_df[['feat_num_IM', 'feat_num_RAM', 'feat_num_PRC']])

 

# tfidf_matrix를 dense 형태로 변환하고, 처음 5개 행을 확인
tfidf_matrix_dense = tfidf_matrix.toarray()
print(tfidf_matrix_dense[:5])  # 처음 5개 행만 출력

feature_names = tfidf_vectorizer.get_feature_names_out()
# 특성(단어) 이름을 출력
print(feature_names)

특성 이름

 

 

3) 아이템의 프로파일을 생성

# TF-IDF 벡터와 스케일링된 수치형 특성을 결합
import numpy as np
combined_features = np.hstack((tfidf_matrix.toarray(), scaled_features))

# 특성 확인
combined_features

 

 

 

 

 

 

 

4단계. 유사도 계산 (Cosine Similarity) 및 추천

위에서 생성한 휴대폰 프로파일끼리 유사도를 계산한다.

두 핸드폰 쌍의 유사도를 미리 구해서 행렬 형태로 출력한다.

  • 정사각 행렬 (`num_cellphone` * `num_cellphone`)
  • 대각선은 자기 자신과의 유사도 = 1
# 유사도 계산
from sklearn.metrics.pairwise import cosine_similarity

# 코사인 유사도를 사용하여 항목 간 유사도 계산
cosine_sim = cosine_similarity(combined_features, combined_features)

# 유사도 확인
cosine_sim

 

 

# 추천 생성 예시
# 특정 휴대폰과 비슷한 휴대폰을 번환하기 위한 함수 구현
def get_content_based_recommendations(cellphone_index, cosine_sim=cosine_sim, data_df=data_df):
    sim_scores = list(enumerate(cosine_sim[cellphone_index]))
    sim_scores = sorted(sim_scores, key=lambda x: x[1], reverse=True)
    sim_scores = sim_scores[1:6]  # 가장 유사한 상위 5개 항목 선택, 0: 자기 자신
    cellphone_indices = [i[0] for i in sim_scores]
    return data_df.iloc[cellphone_indices][['brand', 'model', 'operating system', 'internal memory', 'RAM', 'price']]

 

예제로 하나 선택해서 실행해보자.

# 원하는 N 번째의 휴대폰에 대한 추천 생성 예시 실행
from IPython.display import display

N = 4
print('선택한 휴대폰 : ')
display(data_df.iloc[N:N+1][['brand', 'model', 'operating system', 'internal memory', 'RAM', 'price']])

example_recommendations = get_content_based_recommendations(N)
print('\n추천된 비슷한 휴대폰 : ')
display(example_recommendations)

 

 

 

 

 

 

 

 


사용자 기반 협업 필터링 실습

 

1단계. 데이터 불러오기

import pandas as pd

user_item_df = pd.read_csv('cellphones ratings.csv')
user_item_df

 

 

 

2단계. 사용자(행) - 아이템(열) 상호 작용 테이블 생성

`.pivot_table` : 기존 데이터프레임에서 필요한 정보를 요약해서 추출해 새로운 데이터프레임을 생성한다

# 사용자 기반 협업 필터링을 위한 사용자-항목 평가 행렬 생성
# 평가 점수가 없는 항목은 0으로 채워 넣기!
user_item_matrix = user_item_df.pivot_table(index='user_id', columns='cellphone_id', values='rating').fillna(0)
user_item_matrix

► 같은 `user_id`를 기준으로 `rating` 컬럼의 값을 집계함

► 전체 33개 휴대폰 중 10개를 제시하기 때문에 23개는 정보가 없다 → 0으로 채워넣기

 

 

 

 

 

3단계. 사용자 간 유사도 계산 (Cosine Similarity)

사용자 기반으로 추천을 진행하기 위해서는 '이웃'을 생성해야 한다.

► 사용자 간 유사도를 구해서 (사용자 수 x 사용자 수) 크기의 행렬을 생성한다.

# 코사인 유사도 계산
user_similarity = cosine_similarity(user_item_matrix)
print('user_similarity matrix 크기 : ', user_similarity.shape)

# 유사도 행렬을 DataFrame으로 변환
user_similarity = pd.DataFrame(user_similarity, index=user_item_matrix.index, columns=user_item_matrix.index)
display(user_similarity)

유사도 테이블 중 일부

 

 

 

 

 

4단계. 유사도를 기반으로 아이템 추천

 

def recommend_for_user(user_id, user_similarity, user_item_matrix, top_n=5):
    # 현재 사용자와 다른 사용자 간의 유사도 가져오기
    user_similarities = user_similarity.loc[user_id]

    # 가장 유사한 사용자들 찾기
    similar_users = user_similarities.sort_values(ascending=False)[1:].index

    # 유사한 사용자들이 높게 평가한 항목 추천
    recommended_items = pd.Series(dtype='float64')
    for similar_user in similar_users:
        # 유사한 사용자의 평가 가져오기
        user_ratings = user_item_matrix.loc[similar_user]

        # 현재 사용자가 평가하지 않은 항목만 선택(현재 사용자가 사용하지 않는 휴대폰)
        user_ratings = user_ratings[user_ratings.index.isin(user_item_matrix.loc[user_id][user_item_matrix.loc[user_id] == 0].index)]

        # 추천 목록에 추가
        recommended_items = recommended_items.add(user_ratings, fill_value=0)

    # 가중치가 높은 순으로 항목 정렬 및 상위 N개 추천
    recommended_items = recommended_items.sort_values(ascending=False)[:top_n]

    return recommended_items.index.tolist()

 

# 비교의 중심이 되는 베이스 사용자의 index 설정
order_of_user_idx = 8
base_user_idx = user_item_matrix.index.tolist()[order_of_user_idx]

recommendations_for_user = recommend_for_user(base_user_idx, user_similarity, user_item_matrix, top_n=3)
recommendations_for_user

 

 

# 베이스 사용자가 평가한 핸드폰과 해당 점수를 보여주는 코드
base_user_ratings = user_item_df[user_item_df['user_id'] == base_user_idx]

# 베이스 사용자가 평가한 핸드폰의 상세 정보를 가져오기. 원본 핸드폰 데이터와 병합
base_user_rated_phones = pd.merge(base_user_ratings, data_df, left_on='cellphone_id', right_on='cellphone_id', how='left')

# 베이스 사용자가 평가한 핸드폰 및 점수 출력
print(f"베이스 사용자({base_user_idx})가 평가한 핸드폰과 점수:")
display(base_user_rated_phones[['brand', 'model', 'operating system', 'internal memory', 'RAM', 'price', 'rating']])

# 추천 결과로 나온 핸드폰의 스팩 보여주기
# 추천된 핸드폰 ID 리스트를 기반으로 원본 핸드폰 데이터에서 해당 핸드폰의 상세 정보를 가져옴
recommended_phones_info = data_df[data_df['cellphone_id'].isin(recommendations_for_user)]

# 등수 정보를 포함
rankings = [rank + 1 for rank, _ in enumerate(recommendations_for_user)]

# 추천된 핸드폰 ID와 등수를 DataFrame으로 변환
recommended_phones_with_rank = pd.DataFrame({
    'cellphone_id': recommendations_for_user,
    'rank': rankings
})

# 추천된 핸드폰의 상세 정보와 등수 정보를 병합
recommended_phones_info = pd.merge(recommended_phones_info, recommended_phones_with_rank, on='cellphone_id')


# 추천된 핸드폰의 상세 정보 출력
print("\n추천된 핸드폰의 상세 정보:")
display(recommended_phones_info[['cellphone_id', 'brand', 'model', 'operating system', 'internal memory', 'RAM', 'price', 'rank']])

 

► `rank` 컬럼에 따라서 `Xiamoi`를 제일 추천

 

 

📌 사용자 기반 협업 필터링의 한계 (Cold Start)

  • 실제 사용자가 추천받은 휴대폰을 사용하지 않고 평가한 것이기 때문에, 평가하지 않은 것 중에 선택해야 하다 보니 사용자의 선호도와 유사한 휴대폰이 뽑히지 않는 경향이 있다.
  • 따라서 데이터가 어느정도 있어야 성능이 보장된다.

 

 

 

 

 

 


아이템 기반 협업 필터링 실습

1단계는 동일하다.

 

2단계. 아이템(행) - 사용자(열)  상호 작용 테이블 생성

위의 사용자 기반 협업 필터링 실습에서의 상호 작용 테이블을 전치시켜주기만 하면 된다.

# 아이템-사용자 평가 행렬 생성
item_user_matrix = user_item_matrix.T

 

 

3단계. 아이템 간 유사도 계산 (Cosine Similarity)

# 코사인 유사도 계산
item_similarity = cosine_similarity(item_user_matrix)

# 유사도 행렬을 DataFrame으로 변환
item_similarity_df = pd.DataFrame(item_similarity, index=item_user_matrix.index, columns=item_user_matrix.index)

item_similarity_df

 

 

 

4단계. 추천 점수 계산

# 사용자가 평가하지 않은 항목에 대해 추천 점수를 계산하는 함수
def calculate_recommendation_scores(user_id, item_similarity_df, user_item_matrix, topn=5):
    # 사용자가 평가한 항목 확인
    user_ratings = user_item_matrix.loc[user_id]
    rated_items = user_ratings[user_ratings > 0].index

    # 사용자가 평가하지 않은 항목 확인
    unrated_items = user_ratings[user_ratings == 0].index

    # 추천 점수를 저장할 빈 Series 생성
    recommendation_scores = pd.Series(index=unrated_items, dtype=np.float64)

    # 사용자가 평가하지 않은 각 항목에 대해 추천 점수 계산
    for item in unrated_items:
        # 현재 항목과 사용자가 평가한 항목들 간의 유사도
        item_similarities = item_similarity_df.loc[item, rated_items]

        # 유사도와 사용자의 평가 점수의 가중합 계산
        weighted_scores = item_similarities * user_ratings[rated_items]

        # 가중합의 총합을 추천 점수로 사용
        recommendation_scores[item] = weighted_scores.sum() / item_similarities.sum()

    # NaN 값을 제거하고 점수가 높은 순으로 정렬
    recommendation_scores = recommendation_scores.dropna().sort_values(ascending=False)

    return recommendation_scores[:topn]

 

 

# 비교의 중심이 되는 베이스 사용자의 index 설정
order_of_user_idx = 8
base_user_idx = user_item_matrix.index.tolist()[order_of_user_idx]

recommendation_scores = calculate_recommendation_scores(base_user_idx, item_similarity_df, user_item_matrix, topn=3)
recommendation_scores

 

# 베이스 사용자의 평가한 핸드폰과 해당 점수를 보여주는 코드
base_user_ratings = user_item_df[user_item_df['user_id'] == base_user_idx]

# 베이스 사용자가 평가한 핸드폰의 상세 정보를 가져오기. 원본 핸드폰 데이터와 병합
base_user_rated_phones = pd.merge(base_user_ratings, data_df, left_on='cellphone_id', right_on='cellphone_id', how='left')

# 베이스 사용자가 평가한 핸드폰 및 점수 출력
print(f"베이스 사용자({base_user_idx})가 평가한 핸드폰과 점수:")
display(base_user_rated_phones[['brand', 'model', 'operating system', 'internal memory', 'RAM', 'price', 'rating']])

# 추천된 핸드폰의 상세 정보를 보여주는 부분에 추천 점수를 추가하기 위한 수정
# 추천 점수를 DataFrame으로 변환하여 상세 정보와 병합
recommendation_scores_df = recommendation_scores.reset_index()
recommendation_scores_df.columns = ['cellphone_id', 'recommendation_score']

# 추천된 핸드폰의 상세 정보와 추천 점수 정보를 병합
recommended_phones_info_with_scores = pd.merge(data_df, recommendation_scores_df, on='cellphone_id')

# 등수 정보를 포함
recommended_phones_info_with_scores['rank'] = recommended_phones_info_with_scores['recommendation_score'].rank(ascending=False)

# 결과 출력
print("\n추천된 핸드폰의 상세 정보와 추천 점수:")
display(recommended_phones_info_with_scores[['cellphone_id', 'brand', 'model', 'operating system', 'internal memory', 'RAM', 'price', 'recommendation_score', 'rank']])

 

► `rank` 컬럼에 따라서 `Motorola`를 제일 추천