AI4NLP

추천 시스템(Recommendation System) - 협업 필터링 (Collaborative filtering) 설명 (1) 본문

Machine Learning

추천 시스템(Recommendation System) - 협업 필터링 (Collaborative filtering) 설명 (1)

nlp user 2020. 7. 19. 20:36

 

이번 포스팅에서는 추천 시스템의 기본이 되는 Collaborative filtering에 대해 기존에 작성했던 코드를 기반으로 다뤄보려고 합니다.

 

Collaborative filtering은 축적해놓은 데이터 (memory)를 이용하기 때문에 memory based method의 한 종류로 분류됩니다.

Collaborative filtering의 간단한 Pipeline은 아래와 같습니다.

  1. 과거 사용자의 평가를 훈련데이터로 사용합니다.

  2. 훈련 데이터의 결측값을 메꾸고, 적절한 행렬로 바꿔줍니다.

  3. 쿼리(신규 사용자)에 대해 훈련데이터와 Knn 알고리즘을 이용, 쿼리와 비슷하다고 판단되는 사용자 그룹을 추출합니다.

  4. 추출된 사용자 그룹들의 값(Knn 결과물)을 토대로 점수를 예측합니다.

 

협업 필터링(Collaborative filtering)을 처음 공부할 때에는 왜 협업이라는 말이 붙는지 궁금했는데 과거의 사용자 데이터(Memory)를 이용하기 때문, 과거의 사용자의 데이터가 신규 사용자 (쿼리)의 점수를 예측하는데에 도움을 주기 때문이라는 생각이 들었습니다.

코드의 출처는 github.com/Ykmoon/Collaborative-Filtering-Netflix/blob/master/itemitem.py 입니다.

데이터 형태는 "item_number 공백 user_number 공백 score(1~5)"로 이루어져 있습니다.

영화 데이터라면 item은 영화, 쇼핑 데이터일 경우 item은 상품입니다.

 


 

data = extract_data(path)
mtx = get_matrix(3).toarray()

extract_data 함수는 query를 갖고 오는 코드입니다. user와 item이 주어져있고, 특정 item에 대한 user의 score를 예측하는 것이 이 코드의 목적입니다.

def extract_data(path):
    movie = []
    user = []
    with open(path) as f:
        data = csv.reader(f)
        for i in data:
            movie.append(int(i[0]))
            user.append(int(i[1]))
    data_ = np.column_stack((movie, user))
    return data_

 

get_matrix 함수는 train data를 불러오는 함수입니다. score normalize 값으로 3을 받아서 1~5의 범위를 갖는 score 값을 -2~+2의 범위로 바꾸고, scipy의 coo_matrix method를 sparse matrix로 바꿔줍니다. 제 코드에서는 sparse matrix로 불러온 후에 array로 바꾸고 있는데, 실제 데이터에서는 굉장히 많은 user와 item 값이 존재하므로 sparse matrix 형태로 계속해서 사용해야합니다... 구현 중에 실수했습니다.

def get_matrix(normalize):
    assert type(normalize)==int
    path = "data/train.csv"
    data = []
    item = []
    user = []
    with open(path) as f:
        input_data = csv.reader(f)
        for i in input_data:
            item.append(int(i[0]))
            user.append(int(i[1]))
            data.append(float(i[2]) - normalize)
    mtx = coo_matrix((data, (item, user)), dtype=np.float)
    return mtx

정리. data는 저희가 예측하려는 user와 item을 통해 score를 예측하려 하는 데이터이고, mtx는 이전 유저들의 기록(memory)이 담긴 행렬입니다.

 


item based collaborative filtering에서는 user-item 행렬에 Transpose된 user-item 행렬을 적절하게 곱해서 item-item 행렬로 바꿔서 사용합니다.  item_mtx는 item-item 행렬 담기 위한 리스트입니다. (코드에서는 변수명만 사용하고, 리스트는 사용하진 않습니다. 설명을 위해 추가했습니다.)

result는 예측한 score가 담길 리스트입니다.

그 후에 계산 오류를 막기 위해 0을 갖는 값들을 작은 값 (제 코드에서는 0.00001)으로 바꿔줍니다.

item_mtx = []
zero = np.where(~mtx.any(axis=0))[0] # get zero
mtx[:, [zero]] = 0.00001 # prevent zero-devide

 

아래 코드는 knn 계산을 위한 유사도 측정 방식에 대한 내용입니다. dot 옵션은 말 그대로 dot-product (내적)한 결과물이며, 코드상으로는 np.dot(mtx, np.transpose(mtx))의 결과물을 반환합니다.

코사인 유사도(cosine similarity)의 경우는 normalize된 값들의 dot product로 계산합니다. (이전에는 scipy의 cdistance를 사용했는데 너무 느려서 변경했습니다.)

if method =='dot':
    item_mtx = dot_sim(mtx,name)
elif method=='cos':
    inputs=(mtx.T*np.linalg.norm(mtx,axis=1)).T #normalize before cos_sim
    item_mtx = dot_sim(inputs,name) #honestly this is dot product but input is normalized so same with cos_similarity

 

이렇게 item_mtx(item-item matrix)를 계산하는 과정까지는 offline으로 이루어졌고, 이 item_mtx를 이용하여 쿼리(사용자)에 대해 online으로 계산합니다. item_mtx는 각 아이템에 대한 vector를 담고 있는 행렬입니다.

 

* 이 코드에서는 vector 계산에 내적과 코사인 유사도를 이용하였지만, 이 외에도 굉장히 많은 vector 계산방법들이 존재합니다. 또한, 훈련에 사용하는 데이터가 많아질 수록 편향(bias)가 존재할 수 밖에 없는데, 이 부분에 대해서는 추후에 작성하게 될 user based metho에 첨부하여 작성해보도록 하겠습니다.

 

이제 쿼리들(data)에 대해 for loop를 돌며 item_mtx를 이용하여 score를 예측하는 과정입니다.

item_mtx에서 해당 item_id에 해당하는 vector를 불러온 후, 이 item과 유사한 값을 갖는 k개를 KNN을 이용하여 추출합니다.

이렇게 추출한 k개를 토대로 user의 score를 구합니다. score 계산 방식에는 mean(평균)과 weighted_sum(가중합) 방식이 있습니다.

점수의 범위가 -2~2 이므로 3만큼을 더해서 원래 범위인 1~5로 바꿔줍니다.

k+1개 만큼 뽑는 이유는 본인이 뽑히는 경우가 있기 때문입니다.

for i in data:
    score = 0
    item_id = i[0] #get item_id
    user_id = i[1] #get user_id
    item = item_mtx[item_id] #get item
    knn = np.argsort(item,kind='heapsort')[::-1][0: k+1]
    if item_id in knn: # delte query
        idx = np.where(knn == item_id)
        knn = np.delete(knn, idx)
    else:
        knn = np.delete(knn, len(knn) - 1)
    #get score
    if rating == 'mean':
        score = np.sum(np.take(mtx[:, user_id], knn.tolist())) / float(k) + 3
    elif rating=='weighted':
        knn_sim = item[knn]
        if np.sum(knn_sim) != 0: #prevent zero-devide
            weight = knn_sim / np.sum(knn_sim)
            score = np.sum(np.multiply(np.take(mtx[:, user_id], knn.tolist()), weight)) + 3
        else:
            score = np.sum(mtx[:, user_id]) / np.size(np.nonzero(mtx[:, user_id])) + 3

 

아이템 기반의 협업 필터링을 정리하자면, 특정 사용자가 선호하는 아이템(영화, 상품 등등..)이 주어져있을 때 기존 유저 데이터를 이용하여 이 유저가 선호할 것 같은 아이템을 추천해주는 것입니다. 넷플릭스에 접속할 때마다 영화를 추천해주는 것을 떠올리시면 됩니다. 지금까지 봤던 영화들(아이템)을 토대로 영화를 추천해주고 있으니까요.

 

제 코드에서는 기존 사용자 데이터를 이용하여, item-item matrix를 만든 후, knn 알고리즘을 이용하여 비슷한 성향을 갖는 그룹을 추출하고 이 그룹을 기반으로 특정 아이템에 대한 유저의 선호도(score)를 계산했습니다.

 

다음 포스팅에서는 user based collaborative filtering 방법과 코사인 유사도와 내적 이외의 matrix 계산 방법, 데이터 내의 편향(bias)을 완화하는 방법에 대해 다뤄보겠습니다.

감사합니다.

 

Comments