[ML] 추천 시스템 개발을 위한 surprise 라이브러리 - 컨텐츠 기반 필터링
Surprise
- 추천 시스템 개발을 위한 라이브러리
- 다양한 모델과 데이터 제공
1. 간단한 Surprise 실습
from surprise import SVD # SVD 특이값분해 : 행렬을 대각화
from surprise import Dataset
from surprise.model_selection import cross_validate # 다중평가지표
# 내장 데이터인 무비렌즈 데이터 로드
data = Dataset.load_builtin('ml-100k', prompt=False)
data.raw_ratings[:10]
# user_id item_id rating timestamp
[out]
[('196', '242', 3.0, '881250949'),
('186', '302', 3.0, '891717742'),
('22', '377', 1.0, '878887116'),
('244', '51', 2.0, '880606923'),
('166', '346', 1.0, '886397596'),
('298', '474', 4.0, '884182806'),
('115', '265', 2.0, '881171488'),
('253', '465', 5.0, '891628467'),
('305', '451', 3.0, '886324817'),
('6', '86', 3.0, '883603013')]
model = SVD()
# cross_validate(다중평가지표)
# estimator = model, data = 훈련데이터, measures = 성능평가지표(0에 가까울수록 좋음), cv = 교차검증분할수
# verbose = 상세한 로깅logging을 출력할지 결징
cross_validate(model, data, measures=['rmse','mae'], cv=5, verbose=True)
2. 컨텐츠 기반 필터링(Content-based Filtering)
- 이전의 행동과 명시적 피드백을 통해 좋아하는 것과 유사한 항목을 추천
- ex)내가 지금까지 시청한 영화 목록과 다른 사용자의 시청 목록을 비교해 나와 비슷한 취향의 사용자가 시청한 영화를 추천
- 이전 벡터들의 내적을 통해 다른 사용자들과의 유사도 구하기
- 나와 가장 높은 유사도를 가진 사용자의 시청 목록을 추천
data = Dataset.load_builtin('ml-100k', prompt=False)
raw_data = np.array(data.raw_ratings, dtype=int)
raw_data[:,0] -= 1 # [:,0]모든행의 0번째 요소
raw_data[:,1] -= 1
# 인접행렬 크기 구하기 : 총 유저수가 몇인지
n_users = np.max(raw_data[:, 0]) # 모든행의 0번째 요소 user_id
n_movies = np.max(raw_data[:,1]) # 모든행의 1번째 요소 item_id
shape = (n_users + 1, n_movies + 1)
shape # (943, 1682)
# 인접행렬 만들기 : user와 movie로 나타내서 사용자가 영화를 봤는지 인접행렬로 표시
adj_matrix = np.ndarray(shape, dtype=int)
for user_id, movie_id, rating, time in raw_data:
adj_matrix[user_id][movie_id] = 1. # 값이 있는 위치에 1을 넣기
adj_matrix
# 인접행렬이란 그래프의 연결관계를 행렬로 표현하여 이차원 배열로 나타내는 방식이다.
# adj[i][j] : 노드i에서 j로 가는 간선이 존재하면 1, 아니면 0으로 나타낸다.
my_id, my_vector = 0, adj_matrix[0]
best_match, best_match_id, best_match_vector = -1, -1, [] # 최적 찾기
for user_id, user_vector in enumerate(adj_matrix):
if my_id != user_id: # 만약 다르면
similarity = np.dot(my_vector, user_vector) # 유사성 검사
if similarity > best_match: # 만약 similarity가 best보다 크다면
best_match = similarity # best를 similarity 로 바꿈
best_match_id = user_id
best_match_vector = user_vector
print('Best Match: {}, Best Match ID: {}'.format(best_match, best_match_id))
[out]
Best Match: 183, Best Match ID: 275
recommend_list = []
for i, log in enumerate(zip(my_vector, best_match_vector)):
log1, log2 = log
if log1 < 1. and log2 > 0.:
recommend_list.append(i)
print(recommend_list) # id가 275인 사람에게 순서대로 추천결과가 나왔다
# zip함수 예제 : 여러개 순환가능한 인자를 받고 튜플형태로 하나씩 반복자를 반환하는 함수
numbers = [1, 2, 3]
letters = ["A", "B", "C"]
for pair in zip(numbers, letters):
print(pair)
[out]
(1, 'A')
(2, 'B')
(3, 'C')
3. 유사도(Similarity)
- 추천시스템은 어떤 유사도를 사용하느냐에 따라 성능에 큰 영향을 준다.
- 일반적으로 Euclidean distance, , jacard index, correlation coefficient, cosine 등을 사용한다.
- Euclidean distance, cosine, 또는 MAE.RSME는 두 샘플값(실측값과 예측값)사이의 간격을 수치화 한 것.
- Correlation coefficient는 경향성의 동조 정도를 수치화한 것이다.
- 그리고 추천시스템에 따른 예측값과 실측값의 Error term이나 이들의 correlation의 정도로 정확도를 평가한다.
Euclidean 유클리드
- 유클리드 거리를 사용해서 추천
- 거리가 가까울수록(값이 작을수록) 나와 유사한 사용자
euclidean = ㅡㅡㅡㅡㅡㅡㅡ (루트씌운거)
| D
| ∑ (Ai−Bi)²
V d=1
my_id, my_vector = 0, adj_matrix[0]
best_match, best_match_id, best_match_vector = 9999, -1, [] # 최적 찾기
for user_id, user_vector in enumerate(adj_matrix):
if my_id != user_id: # 만약 다르면
euclidean_dist = np.sqrt(np.sum(np.square(my_vector - user_vector))) # 유사성 # sqrt루트 # square제곱
if euclidean_dist < best_match: # 만약 similarity가 best보다 크다면
best_match = euclidean_dist # best를 similarity 로 바꿈
best_match_id = user_id
best_match_vector = user_vector
print('Best Match: {}, Best Match ID: {}'.format(best_match, best_match_id))
[out]
Best Match: 14.832396974191326, Best Match ID: 737
recommend_list = []
for i, log in enumerate(zip(my_vector, best_match_vector)):
log1, log2 = log
if log1 < 1. and log2 > 0.:
recommend_list.append(i)
print(recommend_list) # id가 737인 사람에게 순서대로 추천결과가 나왔다
[out][297, 312, 317, 342, 356, 366, 379, 384, 392, 402, 404, 407, 417, 422, 428, 433, 448, 454, 469, 473, 495, 510, 516, 526, 527, 549, 567, 602, 635, 649, 650, 654, 658, 661, 664, 696, 731, 746, 750, 754, 915, 918, 925, 929, 950, 968, 1015, 1046]
4. 코사인 유사도
- 두 벡터가 이루고 있는 각을 계산
- cosθ = ||A||×||B|| / A⋅B
def compute_cos_similarity(v1,v2):
norm1 = np.sqrt(np.sum(np.square(v1)))
norm2 = np.sqrt(np.sum(np.square(v2)))
dot = np.dot(v1,v2)
return dot / (norm1 * norm2)
my_id, my_vector = 0, adj_matrix[0]
best_match, best_match_id, best_match_vector = -1, -1, [] # 최적 찾기
for user_id, user_vector in enumerate(adj_matrix):
if my_id != user_id: # 만약 다르면
cos_similarity = compute_cos_similarity(my_vector, user_vector) # 유사성 # sqrt루트 # square제곱
if cos_similarity > best_match: # 만약 similarity가 best보다 크다면
best_match = cos_similarity # best를 similarity 로 바꿈
best_match_id = user_id
best_match_vector = user_vector
print('Best Match: {}, Best Match ID: {}'.format(best_match, best_match_id))
# Best Match: 0.5278586163659506, Best Match ID: 915
recommend_list = []
for i, log in enumerate(zip(my_vector, best_match_vector)):
log1, log2 = log
if log1 < 1. and log2 > 0.:
recommend_list.append(i)
print(recommend_list) # id가 915인 사람에게 순서대로 추천결과가 나왔다
기존 방법에 명시적 피드백(사용자가 평가한 영화 점수)을 추가해 실험
# user가 movie에 남긴 평점
adj_matrix = np.ndarray(shape, dtype=int)
for user_id, movie_id, rating, time in raw_data:
adj_matrix[user_id][movie_id] = rating
adj_matrix
# 유클리드 방식
my_id, my_vector = 0, adj_matrix[0]
best_match, best_match_id, best_match_vector = 9999, -1, [] # 최적 찾기
for user_id, user_vector in enumerate(adj_matrix):
if my_id != user_id: # 만약 다르면
euclidean_dist = np.sqrt(np.sum(np.square(my_vector - user_vector))) # 유사성 # sqrt루트 # square제곱
if euclidean_dist < best_match: # 만약 similarity가 best보다 크다면
best_match = euclidean_dist # best를 similarity 로 바꿈
best_match_id = user_id
best_match_vector = user_vector
print('Best Match: {}, Best Match ID: {}'.format(best_match, best_match_id))
# Best Match: 55.06359959174482, Best Match ID: 737
# 코사인cos 방식
my_id, my_vector = 0, adj_matrix[0]
best_match, best_match_id, best_match_vector = -1, -1, [] # 최적 찾기
for user_id, user_vector in enumerate(adj_matrix):
if my_id != user_id: # 만약 다르면
cos_similarity = compute_cos_similarity(my_vector, user_vector) # 유사성 # sqrt루트 # square제곱
if cos_similarity > best_match: # 만약 similarity가 best보다 크다면
best_match = cos_similarity # best를 similarity 로 바꿈
best_match_id = user_id
best_match_vector = user_vector
print('Best Match: {}, Best Match ID: {}'.format(best_match, best_match_id))