추천 시스템 평가지표 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 K|}{|K|}\]
  • $R$ : 케이스의 정답 아이템 리스트
  • $K$ : 케이스의 Top-K 추천 리스트

코드

  • 상위 5개(K=5)의 추천 결과에 대한 Precision
1
2
3
4
5
6
7
8
9
10
11
12
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, truth_items, k:int=None):
    if k is None:
        k = len(recommend_result)
    predict_list_k = predict_list[:k]
    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@K = \frac{1}{n}\sum_{i=1}^{n}\frac{|R_{i} \cap K_{i}|}{|K_{i}|}\]
  • $n$ : 전체 케이스 수
  • $R_{i}$ : i번째 케이스의 정답 아이템 리스트
  • $K_{i}$ : i번째 케이스의 Top-K 추천 리스트

코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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, truth_items, k:int=None):
    if k is None:
        k = len(recommend_result)
    predict_list_k = predict_list[:k]
    precision = len(set(predict_list_k) & set(truth_items)) / len(predict_list_k)
    return precision

def calc_mean_precision(cases, k:int=None):
    mean_p = sum(calc_precision(case["predict_list"], case["ground_truth"], k) for case in cases) / len(cases)
    return mean_p

calc_mean_precision(cases, 3)
# >> 0.33333333
  • predict_list : 추천 결과
  • ground_truth : 실제 사용자가 선호한 아이템 (1.0과 같은 점수는 추후에 살펴본다.)
  • 첫 번째 케이스 : 추천 목록 상위 3개 중 2개(A, B)를 맞췄으므로 Precision 은 0.6666…
  • 두 번째 케이스 : 추천 목록 상위 3개 중 아무것도 맞추지 못했으므로 Precision 은 0
  • 두 케이스의 평균 = (2/3)/2 = 0.3333…

Comments