추천 시스템 평가지표 2. Recall

Intro

“실제 정답들을 얼마나 잘 재현했는가?”

이전 포스팅에서 Precision 을 통해, 추천 리스트가 얼마나 목표를 맞췄는가에 대한 지표를 살펴봤습니다. 하지만 Precision 만으로는 한 가지 중요한 질문에 대한 답변을 할 수 없습니다. “그래서, 실제 정답 중에서 어느정도를 맞췄나요?”

이 물음에 대해 답해줄 수 있는 지표가 바로 Recall, 재현율입니다. 이번 포스팅에서는 이 Recall 에 대해 알아보도록 하겠습니다.

개념

Recall 은 정답 아이템 리스트 중에서, 추천 리스트에 실제로 포함된 아이템의 비율을 의미합니다.

  • 정답 아이템 리스트 중 추천 리스트에 포함된 아이템의 비율
  • 보통은 K 개의 추천리스트에 대한 비율을 계산하는 Recall@K 를 사용한다.

Recall, 재현율

Recall을 한국어로 번역하면 “재현율”이 됩니다. 재현율을 이해할 때는 이름 그대로를 통해 기억하는 게 좋습니다. “재현하다” 라는 말은, 다시 나타내다 라는 뜻을 가진 말입니다. 곧, 원래 정답이던 아이템들을 추천 리스트에서 얼마나 잘 재현했(다시 나타냈)는가 라고 Recall 을 정의할 수 있습니다.

특징

  • 시스템이 얼마나 “폭넓게” 정답을 재현했는지를 평가
  • 추천 리스트가 길어질수록 점수가 유리해지는 경향이 있음

사용하는 경우

  • 사용자가 특정 카테고리를 탐색할 때 최대한 많은 선택지를 보고 싶어 하는 경우
  • 법률 문서 검색이나 의학 자료 탐색처럼 정답을 하나라도 놓치면 안 되는 서비스
  • 넷플릭스에서 특정 장르의 영화를 모두 훑어보고 싶어 하는 사용자
  • 풍부한 결과를 제공해야 할 때 중요

수식

Recall은 실제 정답 아이템 중에, 추천 리스트에 포함된 아이템의 비율입니다. 수식으로는 아래와 같이 표현할 수 있습니다.

\[Recall@K = \frac{|R \cap TopK|}{|R|}\]
  • $R$ : 케이스의 정답 아이템 리스트
  • $K$ : 케이스의 Top-K 추천 리스트

여기서 주의할 점이 있습니다. 만약 정답의 개수(|R|)이 100개이고, K는 3개면 어떻게 될까요? 3개 모두 정답을 재현하더라도, Recall은 매우 낮을 수밖에 없습니다. 이런 경우엔 아래와 같이 변형된 Recall 수식을 고려해볼 수 있습니다.

\[Recall@K = \frac{|R \cap TopK|}{min(|R|, K)}\]
  • $R$ : 케이스의 정답 아이템 리스트
  • $K$ : 케이스의 Top-K 추천 리스트

코드

  • 상위 5개(K=5)의 추천 결과에 대한 Recall
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_recall(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)
    if len(truth_items) == 0:
        return 0.0
    predict_list_k = predict_list[:k]
    recall = len(set(predict_list_k) & set(truth_items)) / len(truth_items)
    return recall

calc_recall(predict_list, list(ground_truth.keys()), 5)
# >> 0.5
  • predict_list : 추천 결과
  • ground_truth : 실제 사용자가 선호한 아이템(정답 아이템)과 관련도 점수
  • 정답 아이템 4개 중 2개(A, B)를 추천 목록 상위 5개(A, B, C, L, Y)에서 맞췄으므로 Recall 은 0.5

Mean Recall

개념

앞서 살펴본 Recall은 단일 케이스에 대한 평가입니다. 이전 포스팅에서 살펴봤듯, 추천 시스템은 여러 테스트케이스의 평가 결과를 종합해 그 성능을 평가해야 합니다. 이렇게 여러 케이스의 Recall을 평균해 낸 값을 바로 Mean Recall 이라고 합니다.

  • 모든 case 들의 Recall 평균값
  • 추천 시스템이 전반적으로 얼마나 정답을 잘 재현했는지 보여주는 지표

수식

\[Mean \, Recall@K = \frac{1}{n}\sum_{i=1}^{n}\frac{|R_{i} \cap TopK_{i}|}{|R_{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
25
26
27
28
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_recall(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)
    if len(truth_items) == 0:
        return 0.0
    predict_list_k = predict_list[:k]
    recall = len(set(predict_list_k) & set(truth_items)) / len(truth_items)
    return recall

def calc_mean_recall(cases, k:int=None) -> float:
    mean_r = sum(calc_recall(case["predict_list"], list(case["ground_truth"].keys()), k) for case in cases) / len(cases)
    return mean_r

calc_mean_recall(cases, 3)
# >> 0.25
  • predict_list : 추천 결과
  • ground_truth : 실제 사용자가 선호한 아이템(정답 아이템)과 관련도 점수
  • 첫 번째 케이스 : 정답 4개 중 2개(A, B)를 추천 목록 상위 3개에서 맞췄으므로 Recall 은 0.5
  • 두 번째 케이스 : 정답 2개 중 추천 목록 상위 3개에서 맞춘 것은 없으므로 Recall 은 0.0
  • 두 케이스의 평균 = (1/2)/2 = 0.25

Reference

국립국어원 표준국어대사전 - 재현하다

Comments