import itertools
from surprise import accuracy
from collections import defaultdict
class RecommenderMetrics:
# ─────────────────────────────
# MAE: 평균 절대 오차
# 예측 평점과 실제 평점의 절댓값 평균
# ─────────────────────────────
def MAE(predictions):
return accuracy.mae(predictions, verbose=False)
# ─────────────────────────────
# RMSE: 평균 제곱근 오차
# 예측 평점과 실제 평점의 제곱 평균의 루트
# ─────────────────────────────
def RMSE(predictions):
return accuracy.rmse(predictions, verbose=False)
# ─────────────────────────────
# GetTopN: 사용자별 상위 N개 추천 목록 생성
# n: 추천 개수 (기본 10개)
# minimumRating: 최소 예측 평점 기준 (기본 4.0)
# ─────────────────────────────
def GetTopN(predictions, n=10, minimumRating=4.0):
topN = defaultdict(list)
for userID, movieID, actualRating, estimatedRating, _ in predictions:
# 예측 평점이 최소 기준 이상인 것만 추가
if (estimatedRating >= minimumRating):
topN[int(userID)].append((int(movieID), estimatedRating))
for userID, ratings in topN.items():
# 예측 평점 높은 순으로 정렬
ratings.sort(key=lambda x: x[1], reverse=True)
# 상위 N개만 유지
topN[int(userID)] = ratings[:n]
return topN
# ─────────────────────────────
# HitRate: 적중률
# Leave-One-Out 방식으로 숨긴 항목이
# 추천 목록에 있으면 적중
# ─────────────────────────────
def HitRate(topNPredicted, leftOutPredictions):
hits = 0
total = 0
for leftOut in leftOutPredictions:
userID = leftOut[0]
leftOutMovieID = leftOut[1]
# 숨긴 영화가 추천 목록에 있는지 확인
hit = False
for movieID, predictedRating in topNPredicted[int(userID)]:
if (int(leftOutMovieID) == int(movieID)):
hit = True
break
if (hit):
hits += 1
total += 1
# 적중률 = 적중 수 / 전체 사용자 수
return hits / total
# ─────────────────────────────
# CumulativeHitRate: 누적 적중률
# 실제 평점이 기준(ratingCutoff) 이상인 것만 적중으로 인정
# 사용자가 실제로 좋아하는 영화를 추천했는지 측정
# ─────────────────────────────
def CumulativeHitRate(topNPredicted, leftOutPredictions, ratingCutoff=0):
hits = 0
total = 0
for userID, leftOutMovieID, actualRating, estimatedRating, _ in leftOutPredictions:
# 실제 평점이 기준 이상인 것만 측정
if (actualRating >= ratingCutoff):
hit = False
for movieID, predictedRating in topNPredicted[int(userID)]:
if (int(leftOutMovieID) == movieID):
hit = True
break
if (hit):
hits += 1
total += 1
return hits / total
# ─────────────────────────────
# RatingHitRate: 평점별 적중률
# 평점 구간별로 적중률을 나눠서 출력
# 어떤 평점대의 영화를 잘 추천하는지 분석
# ─────────────────────────────
def RatingHitRate(topNPredicted, leftOutPredictions):
hits = defaultdict(float)
total = defaultdict(float)
for userID, leftOutMovieID, actualRating, estimatedRating, _ in leftOutPredictions:
hit = False
for movieID, predictedRating in topNPredicted[int(userID)]:
if (int(leftOutMovieID) == movieID):
hit = True
break
if (hit):
# 실제 평점별로 적중 수 카운트
hits[actualRating] += 1
total[actualRating] += 1
# 평점별 적중률 출력
# 이상적: 5점짜리 적중률이 가장 높아야 함
for rating in sorted(hits.keys()):
print(rating, hits[rating] / total[rating])
# ─────────────────────────────
# AverageReciprocalHitRank: ARHR
# 적중 위치가 상위일수록 높은 점수
# 공식: Σ(1/rank) / 전체 사용자 수
# ─────────────────────────────
def AverageReciprocalHitRank(topNPredicted, leftOutPredictions):
summation = 0
total = 0
for userID, leftOutMovieID, actualRating, estimatedRating, _ in leftOutPredictions:
hitRank = 0
rank = 0
for movieID, predictedRating in topNPredicted[int(userID)]:
rank = rank + 1
if (int(leftOutMovieID) == movieID):
hitRank = rank # 적중한 순위 저장
break
if (hitRank > 0):
# 1번 적중 → 1/1 = 1.0
# 5번 적중 → 1/5 = 0.2
summation += 1.0 / hitRank
total += 1
# ARHR = 전체 1/rank 합계 / 전체 사용자 수
return summation / total
# ─────────────────────────────
# UserCoverage: 제공률
# 최소 하나 이상의 좋은 추천을 받은 사용자 비율
# ratingThreshold: 좋은 추천 기준 평점
# ─────────────────────────────
def UserCoverage(topNPredicted, numUsers, ratingThreshold=0):
hits = 0
for userID in topNPredicted.keys():
hit = False
for movieID, predictedRating in topNPredicted[userID]:
# 기준 평점 이상인 추천이 하나라도 있으면 적중
if (predictedRating >= ratingThreshold):
hit = True
break
if (hit):
hits += 1
# 제공률 = 좋은 추천 받은 사용자 수 / 전체 사용자 수
return hits / numUsers
# ─────────────────────────────
# Diversity: 다양성
# 추천 목록 내 항목들의 평균 유사도를 1에서 뺀 값
# 공식: Diversity = 1 - S (S = 평균 유사도)
# ─────────────────────────────
def Diversity(topNPredicted, simsAlgo):
n = 0
total = 0
# 유사도 행렬 계산
simsMatrix = simsAlgo.compute_similarities()
for userID in topNPredicted.keys():
# 추천 목록 내 모든 영화 쌍 조합
pairs = itertools.combinations(topNPredicted[userID], 2)
for pair in pairs:
movie1 = pair[0][0]
movie2 = pair[1][0]
# 내부 ID로 변환
innerID1 = simsAlgo.trainset.to_inner_iid(str(movie1))
innerID2 = simsAlgo.trainset.to_inner_iid(str(movie2))
# 두 영화 간 유사도
similarity = simsMatrix[innerID1][innerID2]
total += similarity
n += 1
# 평균 유사도
S = total / n
# 다양성 = 1 - 평균 유사도
return (1 - S)
# ─────────────────────────────
# Novelty: 참신성
# 추천 항목의 평균 인기 순위
# 순위가 높을수록(비인기) 참신성 높음
# ─────────────────────────────
def Novelty(topNPredicted, rankings):
n = 0
total = 0
for userID in topNPredicted.keys():
for rating in topNPredicted[userID]:
movieID = rating[0]
# 영화의 인기 순위 가져오기
# 순위가 높은 숫자 = 비인기 영화 = 참신성 높음
rank = rankings[movieID]
total += rank
n += 1
# 참신성 = 추천 항목들의 평균 인기 순위
return total / n추천시스템 RecommenderMetrics.py 둘러보기
2026. 6. 2. 15:04
