아카이브/추천시스템(2019)

추천시스템 12 - 컨텐츠기반 추천 코드예제

Johnny Yoon 2019. 7. 1. 22:29
728x90
반응형

추천시스템

본 포스팅은 Minnesota대학교의 Intro to Recommender Systems코세라 강좌를 정리한 내용입니다.

https://www.coursera.org/learn/recommender-systems-introduction?specialization=recommender-systems

 

원본 코드 예제에서는 Excel로 코딩하게 되어있지만, 파이썬으로 코딩한 예제입니다.

 

 

 

 

 

 

 

1. 컨텐츠 기반 추천

20개의 문서와 10개의 속성이 있다. 그리고 각 사용자마다 5개의 문서에 대한 평가가 있다. 이번 과제에서는 각 속성의 Count를 무시하고 Boolean값 (0/1)으로만 취급하도록 하겠다. 그리고 사용자의 평가는 긍정적이면 1 부정적이면 -1로 기록되어있다.

 

사용자 프로필 구축

먼저 사용자의 프로파일을 구축하시오. 각 속성마다 긍정적인 평가와 부정적인 평가를 Count한 뒤, 그것을 기반으로 각 속성에 점수를 부여하여 프로필을 만드시오.

그리고 각 문서에 대한 예측값을 만들고 (벡터 내적곱을 이용해), 아래의 질문들에 답하시오:

  1. 사용자1이 가장 좋아할 것으로 예측되는 문서는 무엇인가?
  2. 그 문서에 대한 점수는 몇점인가?
  3. 사용자2가 부정적인 평가를 내릴 것으로 예측되는 문서는 몇개인가?

이 모델은 사용자가 평가를 바꾸지 않을것이라는 것을 전제로 한다.

 

데이터 탐색

먼저 주어진 데이터를 살펴보겠습니다.

In [2]:
import pandas as pd
import numpy as np

users_df = pd.read_csv('./users.csv', index_col=0)
users_df.head()
Out[2]:
  User 1 User 2
doc1 1.0 -1.0
doc2 -1.0 1.0
doc3 NaN NaN
doc4 NaN 1.0
doc5 NaN NaN
In [90]:
full_df = pd.read_csv('./data.csv', index_col=0)
full_df
Out[90]:
  baseball economics politics Europe Asia soccer war security shopping family
doc1 1 0 1 0 1 1 0 0 0 1
doc2 0 1 1 1 0 0 0 1 0 0
doc3 0 0 0 1 1 1 0 0 0 0
doc4 0 0 1 1 0 0 1 1 0 0
doc5 0 1 0 0 0 0 0 0 1 1
doc6 1 0 0 1 0 0 0 0 0 0
doc7 0 0 0 0 0 0 0 1 0 1
doc8 0 0 1 1 0 0 1 0 0 1
doc9 0 0 0 0 0 1 0 0 1 0
doc10 0 1 0 0 1 0 1 0 0 0
doc11 0 0 1 0 1 0 0 0 1 0
doc12 1 0 0 0 0 1 1 0 0 0
doc13 0 0 1 1 1 0 0 1 0 0
doc14 0 1 1 1 0 0 0 0 1 0
doc15 0 0 0 1 0 1 1 1 0 0
doc16 1 0 0 0 0 1 0 0 1 0
doc17 0 1 1 1 0 0 0 1 0 0
doc18 0 0 0 1 0 0 0 0 1 0
doc19 0 1 1 0 1 0 1 0 0 1
doc20 0 0 1 1 0 0 1 0 1 0
 

데이터를 살펴보았으니 원래 엑셀에 있던 두가지의 데이터를 만들어 주도록 하겠습니다.

  1. 문서별 속성 수
  2. Document Frequency
In [4]:
num_attr = full_df[full_df == 1].count(axis=1)
num_attr.head()
Out[4]:
doc1    5
doc2    4
doc3    3
doc4    4
doc5    3
dtype: int64
In [5]:
doc_freq = full_df[full_df == 1].count()
doc_freq.head()
Out[5]:
baseball      4
economics     6
politics     10
Europe       11
Asia          6
dtype: int64
 

프로필 구축

이제 문제의 지시대로 사용자의 프로필을 구축해보겠습니다.
이 문제에서 사용자 프로필이란 곧 각 속성들에 대한 사용자의 선호도 입니다.

Pandas의 Boolnean Indexing을 사용해 긍정적 부정적 평가들의 합을 구한뒤 빼주면 사용자으 프로필이 완성됩니다.

In [6]:
user_profile1 = full_df[(users_df['User 1'] == 1)].sum() - full_df[(users_df['User 1'] == -1)].sum()
user_profile1
Out[6]:
baseball     3
economics   -2
politics    -1
Europe       0
Asia         0
soccer       2
war         -1
security    -1
shopping     1
family       0
dtype: int64
In [7]:
user_profile2 = full_df[(users_df['User 2'] == 1)].sum() - full_df[(users_df['User 2'] == -1)].sum()
user_profile2
Out[7]:
baseball    -2
economics    2
politics     2
Europe       3
Asia        -1
soccer      -2
war          0
security     3
shopping     0
family      -1
dtype: int64
 

예측값

이제 사용자의 프로필을 사용해 평가하지 않은 문서들에 대한 예측값을 내보도록 하겠습니다.
예측값은 각 문서의 평가를 벡터로 만들어 사용자의 프로필과 내적곱을 하면 됩니다.

In [8]:
user_predictions1 = pd.Series([
    full_df.iloc[i].dot(user_profile1) for i in range(len(full_df.index))
], index=full_df.index)

user_predictions1
Out[8]:
doc1     4
doc2    -4
doc3     2
doc4    -3
doc5    -1
doc6     3
doc7    -1
doc8    -2
doc9     3
doc10   -3
doc11    0
doc12    4
doc13   -2
doc14   -2
doc15    0
doc16    6
doc17   -4
doc18    1
doc19   -4
doc20   -1
dtype: int64
In [9]:
user_predictions2 = pd.Series([
    full_df.iloc[i].dot(user_profile2) for i in range(len(full_df.index))
], index=full_df.index)

user_predictions2
Out[9]:
doc1     -4
doc2     10
doc3      0
doc4      8
doc5      1
doc6      1
doc7      2
doc8      4
doc9     -2
doc10     1
doc11     1
doc12    -4
doc13     7
doc14     7
doc15     4
doc16    -4
doc17    10
doc18     3
doc19     2
doc20     5
dtype: int64
 

사용자1이 가장 좋아하는 문서

이제 프로필을 구했으니, 첫번째 질문에 대한 답을 구해보도록 하겠습니다.
단순하게 user_prediction내의 max값을 구하면 됩니다,

In [10]:
# idxmax() --> max값의 인덱스를 리턴
user_predictions1.idxmax()
Out[10]:
'doc16'
 

사용자1이 가장 좋아하는 문서의 점수

두번째 문제는 해당 문서에 대한 점수를 물어보고 있습니다.
이번에는 idxmax가 아닌 단순 max를 사용하면 됩니다.

In [11]:
user_predictions1.max()
Out[11]:
6
 

사용자2가 부정적으로 평가할 것 같은 문서들

세번째 질문은 사용자2가 부정적으로 평가할 것 같은 문서들입니다.
이 경우 Boolean Indexing을 통해 구해보도록 하겠습니다.

In [12]:
user_predictions2[user_predictions2 < 0]
Out[12]:
doc1    -4
doc9    -2
doc12   -4
doc16   -4
dtype: int64
 

2. 가중치

위의 예제에서는 속성이 많은 문서들이 많은 어드벤티지를 가져갔을 수 있다. 이를 확인하기 위해서 벡터들의 길이로 나눠 계산을 하고싶다. 이를 수행하기 위해 각 행을 정규화하는 작업을 하시오. 제대로 했다면 doc1의 값들은 1에서 0.447214로 바뀔것이다.
정규화를 적용해 새로운 값들이 나왔다면, 새로운 사용자 프로필과 예측값들을 구축하시오.
결과값이 제대로 나왔다면, 사용자2의 doc7에 대한 예측값은 0.7444, doc19에 대한 예측값은 0.4834가 될것이다.

첫번째 예측의 결과값을 보면, 사용자1에 대한 예측값의 2등과 3등은 동점이었고 doc1과 doc12였다.
새로운 데이터를 이용해 다음 질문에 답해보시오:

  1. 새로운 모델에서는 어떤 문서들이 2등인가?
  2. 2등의 점수는?
 

데이터에 가중치 적용하기

기존 데이터셋에 문제에서 이야기한 가중치를 적용해보겠습니다.
먼저 각 줄의 값들을 더하고, 그 값에 루트를 취해줍니다. 그렇다면 루트값의 벡터가 나오겠죠.
그리고 데이터셋의 한 줄을 벡터의 한 원소로 나누어주면 다음과 같은 결과를 얻을 수 있습니다.

In [38]:
import math

weights = full_df.T.apply(sum).apply(math.sqrt)
weighted_df = full_df.div(weights, axis=0)
weighted_df
Out[38]:
  baseball economics politics Europe Asia soccer war security shopping family
doc1 0.447214 0.000000 0.447214 0.000000 0.447214 0.447214 0.000000 0.000000 0.000000 0.447214
doc2 0.000000 0.500000 0.500000 0.500000 0.000000 0.000000 0.000000 0.500000 0.000000 0.000000
doc3 0.000000 0.000000 0.000000 0.577350 0.577350 0.577350 0.000000 0.000000 0.000000 0.000000
doc4 0.000000 0.000000 0.500000 0.500000 0.000000 0.000000 0.500000 0.500000 0.000000 0.000000
doc5 0.000000 0.577350 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.577350 0.577350
doc6 0.707107 0.000000 0.000000 0.707107 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000
doc7 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.707107 0.000000 0.707107
doc8 0.000000 0.000000 0.500000 0.500000 0.000000 0.000000 0.500000 0.000000 0.000000 0.500000
doc9 0.000000 0.000000 0.000000 0.000000 0.000000 0.707107 0.000000 0.000000 0.707107 0.000000
doc10 0.000000 0.577350 0.000000 0.000000 0.577350 0.000000 0.577350 0.000000 0.000000 0.000000
doc11 0.000000 0.000000 0.577350 0.000000 0.577350 0.000000 0.000000 0.000000 0.577350 0.000000
doc12 0.577350 0.000000 0.000000 0.000000 0.000000 0.577350 0.577350 0.000000 0.000000 0.000000
doc13 0.000000 0.000000 0.500000 0.500000 0.500000 0.000000 0.000000 0.500000 0.000000 0.000000
doc14 0.000000 0.500000 0.500000 0.500000 0.000000 0.000000 0.000000 0.000000 0.500000 0.000000
doc15 0.000000 0.000000 0.000000 0.500000 0.000000 0.500000 0.500000 0.500000 0.000000 0.000000
doc16 0.577350 0.000000 0.000000 0.000000 0.000000 0.577350 0.000000 0.000000 0.577350 0.000000
doc17 0.000000 0.500000 0.500000 0.500000 0.000000 0.000000 0.000000 0.500000 0.000000 0.000000
doc18 0.000000 0.000000 0.000000 0.707107 0.000000 0.000000 0.000000 0.000000 0.707107 0.000000
doc19 0.000000 0.447214 0.447214 0.000000 0.447214 0.000000 0.447214 0.000000 0.000000 0.447214
doc20 0.000000 0.000000 0.500000 0.500000 0.000000 0.000000 0.500000 0.000000 0.500000 0.000000
 

결과를 보시면, doc1의 값이 모두 0.447214인것을 보실 수 있습니다.

 

사용자 프로필 구축

이제 사용자 프로필을 구축해 보겠습니다. 프로세스는 1번과 같습니다.

In [39]:
weighted_profile1 = weighted_df[(users_df['User 1'] == 1)].sum() - weighted_df[(users_df['User 1'] == -1)].sum()
weighted_profile1
Out[39]:
baseball     1.731671
economics   -0.947214
politics    -0.500000
Europe       0.207107
Asia         0.000000
soccer       1.024564
war         -0.447214
security    -0.500000
shopping     0.577350
family       0.000000
dtype: float64
In [45]:
weighted_profile2 = weighted_df[(users_df['User 2'] == 1)].sum() - weighted_df[(users_df['User 2'] == -1)].sum()
weighted_profile2
Out[45]:
baseball    -1.024564
economics    1.000000
politics     1.052786
Europe       1.500000
Asia        -0.447214
soccer      -1.024564
war         -0.077350
security     1.500000
shopping     0.000000
family      -0.447214
dtype: float64
 

예측값 계산

사용자 프로필을 사용해 1번과 같은 방식으로 예측값을 계산해 보겠습니다.

In [43]:
weighted_predictions1 = pd.Series([
    weighted_df.iloc[i].dot(weighted_profile1) for i in range(len(weighted_df.index))
], index=full_df.index)

weighted_predictions1
Out[43]:
doc1     1.009019
doc2    -0.870053
doc3     0.711105
doc4    -0.620053
doc5    -0.213541
doc6     1.370923
doc7    -0.353553
doc8    -0.370053
doc9     1.132724
doc10   -0.805073
doc11    0.044658
doc12    1.333114
doc13   -0.396447
doc14   -0.331378
doc15    0.142229
doc16    1.924646
doc17   -0.870053
doc18    0.554695
doc19   -0.847214
doc20   -0.081378
dtype: float64
In [77]:
weighted_predictions2 = pd.Series([
    weighted_df.iloc[i].dot(weighted_profile2) for i in range(len(weighted_df.index))
], index=full_df.index)

weighted_predictions2
Out[77]:
doc1    -0.845577
doc2     2.526393
doc3     0.016294
doc4     1.987718
doc5     0.319151
doc6     0.336184
doc7     0.744432
doc8     1.014111
doc9    -0.724476
doc10    0.274493
doc11    0.349628
doc12   -1.227723
doc13    1.802786
doc14    1.776393
doc15    0.949043
doc16   -1.183064
doc17    2.526393
doc18    1.060660
doc19    0.483442
doc20    1.237718
dtype: float64
결과를 보면, 사용자2의 doc7에대한 예측값은 문제에서 이야기한대로 0.7444, doc19에 대한 예측값은 0.4834인 것을 보실 수 있습니다.
 

사용자1이 두번째로 좋아할 것 같은 문서

이제 질문에 답해보도록 하겠습니다. 두번째로 좋아할 것 같은 문서는 간단하게 정렬해서 첫 몇개의 결과를 찾아보면 될것입니다.

In [89]:
weighted_predictions1.sort_values(ascending=False).head()
Out[89]:
doc16    1.924646
doc6     1.370923
doc12    1.333114
doc9     1.132724
doc1     1.009019
dtype: float64
 

결과를 보시면 상위 두번째 결과는 doc6 그에 대한 점수는 1.37인 것을 볼 수 있고, 위 1번의 결과와 다르다는것을 볼 수 있습니다.
흥미로운것은 doc1과 doc12가 아직 상위권에 있지만 각각 5위와 3위로 밀려난 것을 볼 수 있습니다.

 

3. IDF

IDF를 적용해 하나의 모델을 더 만들어보시오. IDF = 1/DF를 구한 뒤 문서벡터 프로필 IDF로 예측값을 구해보시오.
또한 해당 모델을 기반으로 다음 두 질문에 답하시오:

  1. 사용자1의 doc1과 doc9의 값을 비교해보자. 사용자1의 doc9에 대한 예측이 이전 모델과 어떻게 다른가?
  2. 사용자2의 doc6에 대한 예측값을 보자. 전에는 약간 긍정적인 예측이었지만, 이번에는 약간 부정적인 에측이다. 왜 바뀌었는가?
In [86]:
idf = doc_freq.apply(lambda x: 1/x)
idf_predictions1 = pd.Series([
    sum(weighted_df.iloc[i].values * weighted_profile1.values * idf.values) for i in range(len(weighted_df.index))
], index=full_df.index)
idf_predictions1
Out[86]:
doc1     0.247612
doc2    -0.136187
doc3     0.109459
doc4    -0.089197
doc5    -0.043527
doc6     0.319432
doc7    -0.058926
doc8    -0.047530
doc9     0.179067
doc10   -0.128031
doc11    0.018752
doc12    0.311648
doc13   -0.057253
doc14   -0.053281
doc15    0.021184
doc16    0.396153
doc17   -0.136187
doc18    0.071635
doc19   -0.121533
doc20   -0.006291
dtype: float64
In [83]:
idf_predictions2 = pd.Series([
    sum(weighted_df.iloc[i].values * weighted_profile2.values * idf.values) for i in range(len(weighted_df.index))
], index=full_df.index)
idf_predictions2
Out[83]:
doc1    -0.217167
doc2     0.329154
doc3    -0.062892
doc4     0.240296
doc5     0.044585
doc6    -0.084695
doc7     0.113531
doc8     0.070575
doc9    -0.120746
doc10    0.046812
doc11    0.017750
doc12   -0.252852
doc13    0.208553
doc14    0.204154
doc15    0.102276
doc16   -0.246472
doc17    0.329154
doc18    0.096424
doc19    0.043343
doc20    0.115296
dtype: float64
 

이제 질문에 답해보도록 하겠습니다.
먼저 이전모델에서 사용자1의 doc1과 doc9의 값들은 각각 1.009019와 1.132724로 각 5위 4위 였습니다.
이번 모델에서는 각각 0.247612와 0.179067로 쉰위가 뒤바꼈습니다.

이전 모델에서 사용자2의 doc6값은 0.336184였습니다. 하지만 이번 모델에서는 -0.084695로 바뀌었습니다.
이것에 대한 이유는 아래 테이블을 보면 알 수 있습니다.

In [103]:
doc6_tfidf = pd.concat([doc_freq * full_df.iloc[5], idf, weighted_profile2], axis=1)
doc6_tfidf.columns = ['tf', 'idf', 'user2']
doc6_tfidf
Out[103]:
  tf idf user2
baseball 4 0.250000 -1.024564
economics 0 0.166667 1.000000
politics 0 0.100000 1.052786
Europe 11 0.090909 1.500000
Asia 0 0.166667 -0.447214
soccer 0 0.166667 -1.024564
war 0 0.142857 -0.077350
security 0 0.166667 1.500000
shopping 0 0.142857 0.000000
family 0 0.200000 -0.447214
 

원래 doc6는 Europe이라는 긍정적이고 Count가 높은 단어 덕분에 긍정적인 예측값을 얻었지만,
IDF가 적용됨으로서, Europe의 힘이 0.09에 의해 힘을 잃은것입니다.
이러한 작용으로 doc6는 tfidf모델에서는 좋지 않은 예측값을 가지게 되었습니다.

 

 

원본 소스를 확인하시려면 다음 링크로 가시면 됩니다:
https://github.com/ydy1128/coursera-recommender-systems/tree/master/ex2

728x90
반응형