추천 시스템 평가지표 1. Precision
Intro
“추천한 것 중에서, 정답은 얼마나 섞여 있는가?”
추천 시스템 평가 지표 시리즈의 첫 번째 지표는 Precision입니다. 이 지표는 가장 단순하면서도, 가장 자주 사용되고, 동시에 가장 많은 오해를 불러일으키기도 합니다. 이번 포스팅에서는 이 Precision에 대해 알아보도록 하겠습니다.
개념
Precision은 추천 리스트에 포함된 아이템 중, 실제로 정답(relevant)인 아이템의 비율입니다.
- 추천 리스트 중 정답 아이템이 차지하는 비율
- 보통은 K개의 추천리스트에 대한 비율을 계산하는
Precision@K를 사용한다.
Precision, 정밀도
Precision을 한국어로 번역하면 “정밀도”라고 할 수 있습니다. 이 정밀도는 다음 포스팅에서 살펴볼 Recall(재현율)과 헷갈리곤 합니다.
Precision, 즉 정밀도를 이해할 때는 정밀한 사격 솜씨를 떠올리면 가장 직관적으로 기억할 수 있습니다. 사격을 할 때 우리는 10발, 또는 20발 정도의 탄환을 지급받습니다. 그리고 그 탄환을 사격했다고 생각해봅시다. 몇발 정도를 맞췄나요?
10발 중 7발을 맞췄다면 0.7의 비율로 정답을 맞춘 것입니다. 5발을 맞췄다면 0.5겠죠. 이렇게, 내가 쏜 탄환중에, 목표를 맞춘 비율이 바로 Precision 입니다. 즉, 얼마나 정밀하게 사격을 했는가 라고 연결지어 생각할 수 있습니다.
특징
- 추천 시스템이 얼마나 “정교한지”를 평가합니다.
- 정답이 아닌 아이템을 추천하는 것에 대해 엄격하게 점수를 매깁니다.
사용하는 경우
- 추천할 수 있는 지면이 매우 한정되어 있거나, 오답을 추천했을 때 사용자의 피로도가 큰 경우
- 예를 들어, 하루에 단 한 번 보내는 “푸시 알림 추천”이나 쇼핑몰 메인 상단의 “작은 배너 구좌”
- 추천의 희소성이 높은 경우 사용
수식
Precision은 추천한 아이템 중에, 실제 사용자가 선호한 (정답) 아이템의 비율입니다. 수식으로는 아래와 같이 표현할 수 있습니다.
\[Precision@K = \frac{|R \cap TopK|}{K}\]- $R$ : 케이스의 정답 아이템 리스트
- $TopK$ : 케이스의 Top-K 추천 리스트
- $K$ : 추천 리스트의 상위 Cutoff 개수
코드
- 상위 5개(K=5)의 추천 결과에 대한 Precision
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
predict_list = ["A", "B", "C", "L", "Y", "U", "F", "Z"]
ground_truth = {"A":1.0, "K":1.0, "B":1.0, "Z":1.0}
def calc_precision(predict_list:list[str|int],
truth_items:list[str|int],
k:int=None) -> float:
if (k is None) or (k > len(predict_list)):
k = len(predict_list)
predict_list_k = predict_list[:k]
if len(predict_list_k) == 0:
return 0.0
precision = len(set(predict_list_k) & set(truth_items)) / len(predict_list_k)
return precision
calc_precision(predict_list, list(ground_truth.keys()), 5)
# >> 0.4
- predict_list : 추천 결과
- ground_truth : 실제 사용자가 선호한 아이템 (1.0과 같은 점수는 추후에 살펴본다.)
- 추천 목록의 상위 5개 중 2개(A, B)를 맞췄으므로 Precision 은 0.4
Mean Precision
개념
앞서 살펴본 Precision은 단일 케이스에 대한 평가입니다. 하지만 실제 추천 시스템은 한 명의 사용자, 하나의 상황만으로 평가하기 어렵습니다. 따라서 여러 사용자, 여러 쿼리, 여러 상황을 종합적으로 평가하기 위해 여러 케이스의 Precision을 평균낸 값이 필요하고, 이때 사용하는 지표가 Mean Precision입니다.
- 모든 case 들의 Precision 평균값
- 추천 시스템이 전반적으로 얼마나 정밀한 추천을 하는지 보여주는 지표
※ 여기서의 Mean Precision은 MAP(Mean Average Precision)과는 다른 지표입니다.
수식
\[Mean \, Precision@K = \frac{1}{n}\sum_{i=1}^{n}\frac{|R_{i} \cap TopK_{i}|}{K}\]- $n$ : 전체 케이스 수
- $R_{i}$ : i번째 케이스의 정답 아이템 리스트
- $TopK_{i}$ : i번째 케이스의 Top-K 추천 리스트
- $K$ : 추천 리스트의 상위 Cutoff 개수
코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
cases = [
{
"predict_list" : ["A", "B", "C", "L", "Y", "U", "F", "Z"],
"ground_truth" : {"A":1.0, "K":1.0, "B":1.0, "Z":1.0}
},
{
"predict_list" : ["N", "X", "Y", "B", "M"],
"ground_truth" : {"E":1.0, "B":1.0}
},
]
def calc_precision(predict_list:list[str|int],
truth_items:list[str|int],
k:int=None) -> float:
if (k is None) or (k > len(predict_list)):
k = len(predict_list)
predict_list_k = predict_list[:k]
if len(predict_list_k) == 0:
return 0.0
if not predict_list_k:
return 0.0
precision = len(set(predict_list_k) & set(truth_items)) / len(predict_list_k)
return precision
def calc_mean_precision(cases, k:int=None) -> float:
mean_p = sum(calc_precision(case["predict_list"], list(case["ground_truth"].keys()), k) for case in cases) / len(cases)
return mean_p
calc_mean_precision(cases, 3)
# >> 0.33333333
- predict_list : 추천 결과
- ground_truth : 실제 사용자가 선호한 아이템(정답 아이템)과 관련도 점수
- 첫 번째 케이스 : 추천 목록 상위 3개 중 2개(A, B)를 맞췄으므로 Precision 은 0.6666…
- 두 번째 케이스 : 추천 목록 상위 3개 중 아무것도 맞추지 못했으므로 Precision 은 0
- 두 케이스의 평균 = (2/3)/2 = 0.3333…
Comments