본문 바로가기

Etc

파이썬 협업필터링(Collaborative Filtering), 추천 알고리즘 - 1

파이썬 협업필터링(Collaborative Filtering), 추천 알고리즘 - 1

Preview

협업필터링이란 많은 사용자들로부터 얻은 기호정보에 따라 사용자들의 관심사를 자동적으로 예측하는 방법이다.
넷플릭스, 왓챠 등에서 쓰이는 방법으로 두 사람간 유사도(Similarity)를 구해 예상 평점을 구하고 비슷한 영화를 추천해주는 등의 알고리즘에 사용된다.

협업 필터링에는 
유저기반(User-based CF) 과 아이템기반(Item-based CF) 이 존재하는데
유저기반(User-based CF) 은 포스팅에 사용될 기법이고
아이템기반(Item-based CF) 은 실제 데이터에서 유저 수가 적거나 겹치는 아이템이 적어 유효한 데이터를 뽑아내기 힘들 때 거꾸로 아이템을 기반으로 유사도를 구하는 기법이다.

몇개의 포스팅에 걸쳐 간단한 데이터를 통해 왓챠처럼 영화평점을 예상해주는 기능을 구현해보자.

DATA


간단한 예를 위한 위 표를 딕셔너리로 정의한다. 
key는 이름이고 value는 {영화이름 : 평점}의 이중 dictionaray 구조가 된다.
1
2
3
4
5
6
>> critics={
     'hhd':{'guardians of the galaxy 2':5,'christmas in august':4,'boss baby':1.5},
     'chs':{'christmas in august':5,'boss baby':2},
     'kmh':{'guardians of the galaxy 2':2.5,'christmas in august':2,'boss baby':1},
     'leb':{'guardians of the galaxy 2':3.5,'christmas in august':4,'boss baby':5}
}
cs

hhd의 키값을 이용해 목록을 구해보자.
파이썬의 딕셔너리에서 value를 구하는 방법은 [key].get(key)가 있다. 둘 다 value를 뱉어내니 같다고 생각할 수 있겠다.

[key] 로 value를 꺼내올때 존재하지 않는 키라면 오류가 발생하고
.get(key)None을 출력하는 차이점이 있다.

1
2
3
>> critics.get('hhd')
 
{'boss baby'1.5'christmas in august'4'guardians of the galaxy 2'5}
cs

hhd가 boss baby에 몇점을 줬는지 출력해보자.
이중 딕셔너리 구조니까 다음과 같이 두 번에 걸쳐 value를 구한다:
1
2
3
>> critics.get('hhd').get('boss baby')
 
1.5
cs

1. 2차원에서 피타고라스 공식을 통해 두 사람간 거리 구하기

두 사람 간 유사도를 구하는 가장 기본적인 방법은 2차원 그래프에 점을 찍은 후 서로 거리를 구하는 것이다. 
여기에 피타고라스 공식이 사용되며, 거리가 가까울 수록 유사도가 높다고 할 수 있겠다.

피타고라스공식을 이용한 거리계산
(이정도는 중학교 때 마스터했잖아요?)


연습삼아 임의의 값을 주어 계산해보자. (xi-xj)가 1이고 (yi-yx)가 3인 경우.
제곱근 계산을 위해 math패키지의 sqrtimport하자.
1
2
3
4
5
>> from math import sqrt
 
>> sqrt(pow(1,2)+pow(3,2))
 
3.1622776601683795
cs

위 거리공식을 함수로 정의하면 다음과 같을 것이다.
1
2
def sim(i, j):
    return sqrt(pow(i,2)+pow(j,2))
cs

두 사람간 거리를 구하는 함수를 만들었으니 leb와 chs의 boss baby, chirstmas in august 평점 거리를 계산해보자.
2차원에 점을 찍어서 피타고라스공식을 이용해 거리를 계산한다.


단순한 수학이다. 
1
2
3
4
5
>> var1 =  critics['chs']['christmas in august']-critics['leb']['christmas in august']
>> var2 =  critics['chs']['boss baby']-critics['leb']['boss baby']
>> sim(var1,var2) #위에서 정의한 함수
 
3.1622776601683795
cs

2. 2차원에서 피타고라스 공식을 통해 2명 이상과의 거리 구하기

그럼 이제 chs을 대상으로 다른 사람들 전체와의 christmas in august, boss baby 평점을 통해 거리를 구해보자.

반복문을 통해 딕셔너리를 돌면서 key값을 순차적으로 꺼내온다. 그러나 자기 자신을 제외해야 하므로 이를 판별하는 조건문이 중간에 들어갈 것이다.
1
2
3
4
5
6
7
8
9
>> for i in critics:
    if i!='chs'#자기자신제외
        num1 = critics.get('chs').get('christmas in august')- critics.get(i).get('christmas in august')
        num2 = critics.get('chs').get('boss baby')- critics.get(i).get('boss baby')
        print(i," : ", sim(num1,num2))
 
hhd : 1.118033988749895
kmh : 3.1622776601683795
leb : 3.1622776601683795
cs

피타고라스 공식으로 도출한 거리이므로 값이 작을수록 유사한 구조를 가지고 있다. 
즉 기준이 되는 chs은 값이 가장 작은 hhd와 가장 유사한 취향을 가졌다고 유추할 수 있다.

3. 비교를 위한 정규화 (변수 재설정)

이를 정규화(normalization)를 이용해 거꾸로 값이 클수록 유사한 것으로 변경하겠다.
유사도라고 하면 왠지 숫자가 커야 비슷한 느낌이 나지 않는가?
기존 공식을 이용하면 취향에 따라 거리가 무한대로 늘어날 수 있으나, 
정규화를 통하면 변수의 범위가 0~1 사이로 일정하게 설정되기 때문에 비교하기 용이하며, 특정 범위내로 가두는 효과 또한 있다.

정규화 공식은 다음과 같다.

언뜻 보면 이게 뭔지 싶겠지만 떼놓고 보면 만들어놨던 함수에 약간만 변형을 가하는 정도로 구할 수 있다.
1
2
3
4
5
6
7
8
9
>> for i in critics:
    if i!='chs':
        num1 = critics.get('chs').get('christmas in august')- critics.get(i).get('christmas in august')
        num2 = critics.get('chs').get('boss baby')- critics.get(i).get('boss baby')
        print(i," : "1/(1+sim(num1,num2))) #정규화
 
hhd : 0.4721359549995794
kmh : 0.2402530733520421
leb : 0.2402530733520421
cs

값이 재설정되어 한결 보기 편해졌다. 값이 가장 큰 hhd와 기준이 되는 chs가 가장 유사한 취향을 가진 것으로 동일한 결과를 뱉는다.

여기까지는 2개의 아이템을 가지고 유사도가 가장 높은 사람을 구하는 것이다. 피타고라스 공식의 한계다. 

다음 포스팅에선 아이템 2개가 아닌 3개 이상, 즉 다차원에서의 거리를 이용한 유사도를 구한다.