[핸즈온 추천시스템] 성능평가지표

chrisjune
7 min readMar 20, 2023

추천시스템의 대표적인 성능평가방법에 대해 알아보고, python을 활용해 직접 구현해보겠습니다.

source

시스템의 종류에 따른 평가지표

  • 평점 예측 기반의 추천시스템은 Mean Average Error, Root Mean Squared Error, Mean Squared Error를 지표로 활용합니다.
  • 순위 기반 추천시스템은 Precision, Recall, MAP, NDCG의 지표를 활용합니다.
  • 이번 포스트에서는 순위기반 추천시스템의 평가지표를 코드로 알아보겠습니다

Precision / Recall@K

@ 뒤에 붙은 K는 추천아이템 수를 의미합니다. @10, @20, @30 추천개수가 10개, 20개, 30개를 의미합니다.

Precision: 정확도, 추천한 것 중 내가 좋아하는 상품의 비율을 의미합니다.
Recall: 재현율, 내가 좋아하는 것을 추천한 비율을 의미합니다.

헷갈릴때가 많아서 저는 Precision의 의미를 먼저 생각하고, 번역하면 정확도 → 정확도는 얼마나 추천한것이 얼마나 정확한지를 의미를 떠올리면 조금 정리가 쉬워집니다.

반면에 재현율의 의미를 떠올리면, 내가 좋아하는 것을 얼마나 똑같이 Copy하여 재현하는지 의미를 떠올리면 좀더 쉽습니다.

like_item = {'a', 'b', 'c', 'h', 'i', 'j'}
recommend_item = {'a', 'b', 'c', 'd', 'e'}

intersection = like_item.intersection(recommend_item)
precision = len(intersection) / len(recommend_item)
precision
>> 0.6

recall = len(intersection) / len(like_item)
recall
>> 0.5

Average Precision@K

Precision@1 ~ Precision@K까지의 평균을 의미합니다.
이때 Precision 에 relevance값으로 0 또는 1을 곱해줍니다. 해당 인덱스의 상품을 유저가 좋아하는 상품이면 1, 아니라면 0을 곱해줍니다.

# ap@k
def ap_at_k(like_item, recommend_item, k):
precisions = []
# 1부터 K까지 Loop를 돌며 Precision을 계산합니다
for i in range(k):
# 아래로직은 Precision과 동일합니다
base = list(recommend_item)[:i+1]
intersect = set(base).intersection(like_item)
relevance = recommend_item[i] in like_item
print("intersect:",intersect, "base:", base, "relevance", relevance)

precisions.append(len(intersect) / len(base) * relevance)
print("precisions: ", precisions)

ap_k = sum(precisions) / len(precisions)
print("ap@k: ", ap_k)
return ap_k

User A의 AP@3

like_item = ['a', 'f', 'g']
recommend_item = ['a', 'b', 'c']
user_a_ap = ap_at_k(like_item, recommend_item, 3)

>> intersect: {'a'} base: ['a'] relevance True
>> intersect: {'a'} base: ['a', 'b'] relevance False
>> intersect: {'a'} base: ['a', 'b', 'c'] relevance False
>> precision: [1.0, 0.0, 0.0]
>> ap@k: 0.3333333333333333

User B의 AP@3

like_item = ['b', 'f', 'c']
recommend_item = ['d', 'e', 'f']
user_b_ap = ap_at_k(like_item, recommend_item, 3)

>> intersect: set() base: ['d'] relevance False
>> intersect: set() base: ['d', 'e'] relevance False
>> intersect: {'f'} base: ['d', 'e', 'f'] relevance True
>> precision: [0.0, 0.0, 0.3333333333333333]
>> ap@k: 0.1111111111111111

Mean Average Precision @ K

모든 유저에 대한 AP@K의 평균을 의미합니다.

meanAP_K = (user_a_ap + user_b_ap) / 2
meanAP_K

>> 0.2222222222222222

NDCG @ K (Normalized Discounted Cumulative Gain)

NDCG는 추천되는 순서를 고려한 평가지표이며 1에 가까울 수록 좋은 모델로 평가할 수 있습니다. 위에서 소개한 지표들은 순서에 상관없었고, MAP에서는 relevance를 0또는 1로만 구분하였습니다.

relevance

상품과 사용자가 관련이 있는지 값이며, 정해진 값이 있는 것은 아니고 추천 상황에 따라 다르게 정의합니다.

# key:상품번호, value: relevance
item = {
'a':3,
'b':2,
'c':1,
'd':3,
'e':0,
'f':0
}

Cumulative Gain

relevance의 총합입니다. 순서를 고려하지 않기 때문에 같은 아이템들을 추천한 경우, 순서와 상관없이 CG는 동일합니다.

recommend_item = ['a','b','c']

cg_i = [item.get(i, 0) for i in recommend_item]
print(cg_i)
>> [3, 2, 1]

cg = sum(cg_i)
cg
>> 6

Discounted CG

순서가 뒤로 갈수록 할인을 하는 개념을 적용한 계산값입니다.

y = log2(x+1) 함수를 분모에 두어 x가 커질수록 분모가 커져 영향도를 적게 주도록합니다. 하지만 추천 결과마다 개수가 다를 경우 정확한 평가가 어렵습니다.

import math

dcg_i = [item.get(i, 0) / math.log2(1 + idx)
for idx, i in enumerate(recommend_item, start=1)]
print(dcg_i)
>> [3.0, 1.261859507142915, 0.5]

dcg = sum(dcg_i)
dcg
>> 4.7618595071429155

Ideal DCG

DCG를 구하되 relevance를 높은 순으로 추천했다고 가정하고 계산합니다.

ideal_relevance = sorted(item.values(), reverse=True)[:len(recommend_item)]
print(ideal_relevance)
>> [3, 3, 2]

idcg_i = [ideal_relevance[idx-1] / math.log2(1+idx) for idx, i in enumerate(recommend_item, start=1)]
print(idcg_i)
>> [3.0, 1.8927892607143724, 1.0]

idcg = sum(idcg_i)
idcg
>> 5.892789260714372

NDCG

DCG / IDCG값, 즉 이상적인 조합대비 현재 추천 모델의 리스트가 얼마나 좋은지 평가합니다.

ndcg = dcg / idcg
ndcg

>> 0.808082437104775

오늘은 간단하게 Precision, Recall, AP, MAP, NDCG에 대하여 코드를 곁들여 알아보았습니다. 향후 지표를 추가하도록 하겠습니다.

감사합니다.

참고

--

--