추천 시스템
: 사용자의 선호도 및 과거 행동을 기반으로 사용자가 관심을 가질 만한 정보(상품, 서비스 등)를 필터링해서 제공하는 기법
기대 효과
- 정보 과부하 문제 해결
- 수많은 옵션 중 가장 관련도 높은 항목을 선택
- 맞춤형 경험 제공
- 사용자 기호 반영
- 사용자의 충성도 및 만족도 증가
- 비즈니스 가치 생성
- 구매 유도
- 사용자 참여도 증가
- 데이터 확보
※ 사용자가 유입되었다가 이탈될 확률이 적을수록 해당 서비스의 가치는 높게 평가된다.
추천 시스템 알고리즘
📌 필터링(Filtering) = 수많은 데이터 중 나에게 맞는 것을 찾아준다.
1. 콘텐츠 기반 필터링 (Content-based Filtering)
- 사용자가 관심을 보였던 아이템의 특성을 분석해서 이와 유사한 특성을 갖는 다른 아이템 추천
- ex. 내가 많이 본 배우를 기반으로 영화 추천
2. 협업 필터링 (Collaborative Filtering)
= 나랑 비슷한 사람이 좋아하는 것을 기반으로 추천
- 사용자 간의 유사도를 기반으로 추천
- ex. 나의 취향과 비슷한 것을 좋아하는 사람 B가 선호하는 아이템을 나한테 추천
3. 하이브리드 추천 시스템
= 콘텐츠 기반 필터링의 장점 + 협업 필터링의 장점
- 사용자 개인과 사용자 그룹 패턴을 분석해서 맞춤형 추천
추천 시스템 활용 사례
1. 유튜브 영상 추천
- 콘텐츠 기반 필터링 추천 시스템 적용 : 내가 주로 보는 영상/선호도/상호작용을 기반으로 비슷한 영상 추천
- 협업 필터링 추천 시스템 적용 : 내가 보는 영상을 좋아하는 다른 그룹이 보는 영상 추천
- 기대 효과 :
- 사용자 : 관심있는 정보 습득, 인기 콘텐츠 및 최신 트렌드 확인
- 유튜브 : 사용자 참여도 향상, 매출 증대
2. 맞춤형 건강 검진 항목 추천
- 콘텐츠 기반 필터링 추천 시스템 적용 : 사용자 건강 이력/이전 질병/생활 습관 데이터 기반 맞춤 건강 검진 항목 추천
- 협업 필터링 추천 시스템 적용 : 인구통계학적 정보(나이/성별 등)와 건강 상태를 공유하는 다른 사용자 그룹의 데이터를 활용하여 추천
- 기대 효과 :
- 사용자 : 개인 건강 상태에 적합한 검진 항목 식별, 효율적 건강 관리 가능, 조기 진단 및 예방 가능, 장기적 의료 비용 절감
- 병원 : 정밀 검진 추천으로 환자 만족도 및 건강 결과 개선, 병원 서비스 품질 및 효율성 향상
3. 음식 배달 서비스 추천
- 콘텐츠 기반 필터링 추천 시스템 적용 : 과거 주문 음식/선호 음식 재료/검색 히스토리 기반으로 비슷한 특성을 가진 식당/메뉴 추천
- 협업 필터링 추천 시스템 적용 : 사용자 정보와 유사한 다른 사용자들이 선호하는 식당/메뉴 추천
- 기대 효과 :
- 사용자 : 선택 시간 감소
- 음식 배달 서비스 기업 : 사용자 참여도/충성도 향상, 매출 증대, 마케팅 전략 수립, 신메뉴 개발, 서비스 개선
콘텐츠 기반 필터링 (Contents-based Filtering)
= 내가 좋아했던 것을 기반으로 추천
프로세스
1단계. 아이템 프로파일 구성
📌 프로파일
: 특정 아이템을 설명하는 특성들의 집합
- 풀어야 하는 문제에 따라 다르게 설정
- 서로 다르 이종 데이터의 집합도 가능
- 특성의 구성에 따라 추천 시스템의 성능이 달라짐
2단계. 각 특성 당 정보 추출
: 프로파일을 구성하는 특성을 숫자의 형태로 임베딩하는 과정
- 모든 아이템에 동일하게 적용되어야 함
3단계. 아이템들의 프로파일 생성
: 사용자가 사용한 아이템의 프로파일과 사용하지 않은 아이템의 프로파일을 생성하는 과정
- 동일한 임베딩 과정을 거쳐야 함
4단계. 유사도 계산 및 추천
: 사용자가 갖고 있는 임베딩 값과 다른 아이템들의 임베딩 값과의 유사도를 계산하고, 이를 기반으로 상위 N개 추천
- 사용자가 갖고 있는 각 아이템의 임베딩 값을 구함 (단순 평균, 가중 평균 등 적용) → 사용자의 최종 임베딩 값 도출
- 사용자의 최종 임베딩 값과 아직 경험하지 않은 아이템간의 유사도를 구함
- 유사도가 높은 아이템 (상위 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
1단계. 데이터 불러오기
import pandas as pd
# 데이터 로드
data_path = 'cellphones data.csv'
data_df = pd.read_csv(data_path)
# 확인 : 총 33개의 휴대폰 종류가 존재함
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`를 제일 추천